Author Topic: Transmission (ABCD) Matrix - Check My Work  (Read 1569 times)

0 Members and 1 Guest are viewing this topic.

Offline T3sl4co1lTopic starter

  • Super Contributor
  • ***
  • Posts: 21728
  • Country: us
  • Expert, Analog Electronics, PCB Layout, EMC
    • Seven Transistor Labs
Transmission (ABCD) Matrix - Check My Work
« on: July 02, 2017, 04:15:46 pm »
This is not a homework question, by the way. ;D

In my AC filter simulator,
https://www.seventransistorlabs.com/Calc/Filter1~.html
I'm using transmission matrices to solve the network.  I'm using the convention: current into port 1, current out of port 2.  This way I don't have to flip signs when cascading stages.

The math used is in f1.js, used on the above page.  The important functions follow below.


The network's total transmission matrix is built by multiplying the matrices of each stage:

Code: [Select]
function abcdMultiply(f, g) {
return {
a: (cplxAdd(cplxMul(f.a, g.a), cplxMul(f.b, g.c))),
b: (cplxAdd(cplxMul(f.a, g.b), cplxMul(f.b, g.d))),
c: (cplxAdd(cplxMul(f.c, g.a), cplxMul(f.d, g.c))),
d: (cplxAdd(cplxMul(f.c, g.b), cplxMul(f.d, g.d)))
};
}

(Note I have to pass everything through functions, because the numbers are complex valued, and I can't do operator overloading in JS.  Likewise there's no matrix primitive, I'm just using an associative array and calling out ABCD elements by name.  Good enough.)

A series branch's matrix is generated this way:

Code: [Select]
{
a: {real: 1, imag: 0},
b: cplxPar(
{real: inputs[0], imag: inputs[1] * 2 * Math.PI * freq[i] - 1 / (inputs[2] * 2 * Math.PI * freq[i])},
{real: inputs[3], imag: inputs[4] * 2 * Math.PI * freq[i] - 1 / (inputs[5] * 2 * Math.PI * freq[i])},
{real: inputs[6], imag: inputs[7] * 2 * Math.PI * freq[i] - 1 / (inputs[8] * 2 * Math.PI * freq[i])}
),
c: {real: 0, imag: 0},
d: {real: 1, imag: 0}
}

(The inputs array is the component values, in sequence.  Also, this is done inside a loop over all frequencies being calculated, hence freq[i ].)

The shunt branch is calculated similarly, with b = 0 and c = shunt admittance.

There are also data for the source and load impedances, which are simple RL or RC networks.  The impedances are fine, but I'm not sure I'm using them correctly.  I'm also not sure I'm generating the output measurements correctly.

The outputs are selected from this list:
Code: [Select]
Input VS
Input I(VS)
Input Z
Input R.L.
Output VL
Output I(VL)
Output Z
Output R.L.
Power Gain
Voltage Gain
Current Gain

("R.L." = return loss.)

Output-referred measurements are computed by inverting the ABCD matrix and exchanging source and load impedances.  The inversion is here:

Code: [Select]
function abcdInvert(a) {
var det = cplxSub(cplxMul(a.a, a.d), cplxMul(a.b, a.c)); // Though the abcd matrix should always be reciprocal (det = 1)
return {a: cplxDiv(a.d, det), b: cplxDiv({real: -a.b.real, imag: -a.b.imag}, det),
c: cplxDiv({real: -a.c.real, imag: -a.c.imag}, det), d: cplxDiv(a.a, det)};
}

The gains should be symmetrical(?) so there is no output or input referred option for them.

Finally, there is a function converting an ABCD matrix and load impedance into an input impedance:

Code: [Select]
function abcdImpedance(a, z) {
return cplxDiv(cplxAdd(cplxMul(a.a, z), a.b), cplxAdd(cplxMul(a.c, z), a.d));
}

The voltage and current measurements take one more option: whether the source or load is a Thevenin or Norton equivalent.  Rather than cleaning up and trying to explain these, these are just the whole functions (including checking source/load types, and looping over all frequency points).

Code: [Select]
/**
 * Functions for converting the filterData structure into
 * analytical measurements.
 * @param
 * d Structure containing frequency and network arrays:
 * d.freq frequency array (with properties min, max, log)
 * d.abcd abcd matrix array
 * d.src source impedance array
 * d.src.eq source equivalent type (0 = Thevenin, 1 = Norton)
 * d.load load impedance array
 * d.load.eq load equivalent type
 * @returns
 * array of complex values.
 */
function filterDataToComplexInVS(d) {
var result = [];
if (d.src.eq) { // Norton
for (var i = 0; i < d.abcd.length; i++) {
// Current into parallel impedance: d.src || (d.load applied to d.abcd)
result.push(cplxPar(d.src[i], abcdImpedance(d.abcd[i], d.load[i])));
}
} else { // Thevenin
for (var i = 0; i < d.abcd.length; i++) {
// Voltage divider: d.src to (d.load applied to d.abcd)
result.push(cplxImpedanceDivider(d.src[i], abcdImpedance(d.abcd[i], d.load[i])));
}
}
return result;
}

function filterDataToComplexInIS(d) {
var result = [];
if (d.src.eq) { // Norton
for (var i = 0; i < d.abcd.length; i++) {
// Current divider: d.src to (d.load applied to d.abcd)
result.push(cplxImpedanceDivider(abcdImpedance(d.abcd[i], d.load[i]), d.src[i]));
}
} else { // Thevenin
for (var i = 0; i < d.abcd.length; i++) {
// Voltage into series impedance: 1 / (d.src + (d.load applied to d.abcd))
result.push(cplxDiv({real: 1, imag: 0}, cplxAdd(d.src[i], abcdImpedance(d.abcd[i], d.load[i]))));
}
}
return result;
}

function filterDataToComplexInZ(d) {
var result = [];
for (var i = 0; i < d.abcd.length; i++) {
// Input impedance: d.load applied to d.abcd
result.push(abcdImpedance(d.abcd[i], d.load[i]));
}
return result;
}

// Not return loss per se, but reflectance.  Use log magnitude to get dB R.L..
function filterDataToComplexInRL(d) {
var result = [], zr;
for (var i = 0; i < d.abcd.length; i++) {
// Gamma = (ZL - ZS) / (ZL + ZS) = (1 - ZS/ZL) / (1 + ZS/ZL)
zr = cplxDiv(d.src[i], abcdImpedance(d.abcd[i], d.load[i]));
zr.real += 1;
result.push(cplxDiv(cplxSub({real: 2, imag: 0}, zr), zr));
}
return result;
}

function filterDataToComplexOutVL(d) {
var result = [];
if (d.load.eq) { // Norton
for (var i = 0; i < d.abcd.length; i++) {
// Current into parallel impedance: d.load || (d.src applied to inverse(d.abcd))
result.push(cplxPar(d.load[i], abcdImpedance(abcdInvert(d.abcd[i]), d.src[i])));
}
} else { // Thevenin
for (var i = 0; i < d.abcd.length; i++) {
// Voltage divider: d.load to (d.load applied to d.abcd)
result.push(cplxImpedanceDivider(d.load[i], abcdImpedance(abcdInvert(d.abcd[i]), d.src[i])));
}
}
return result;
}

function filterDataToComplexOutIL(d) {
var result = [];
if (d.load.eq) { // Norton
for (var i = 0; i < d.abcd.length; i++) {
// Current divider: d.load to (d.src applied to inverse(d.abcd))
result.push(cplxImpedanceDivider(abcdImpedance(abcdInvert(d.abcd[i]), d.load[i]), d.src[i]));
}
} else { // Thevenin
for (var i = 0; i < d.abcd.length; i++) {
// Voltage into series impedance: 1 / (d.load + (d.src applied to inverse(d.abcd)))
result.push(cplxDiv({real: 1, imag: 0}, cplxAdd(d.load[i], abcdImpedance(abcdInvert(d.abcd[i]), d.src[i]))));
}
}
return result;
}

function filterDataToComplexOutZ(d) {
var result = [];
for (var i = 0; i < d.abcd.length; i++) {
// Output impedance: d.src applied to inverse(d.abcd)
result.push(abcdImpedance(abcdInvert(d.abcd[i]), d.src[i]));
}
return result;
}

function filterDataToComplexOutRL(d) {
var result = [], zr;
for (var i = 0; i < d.abcd.length; i++) {
// Gamma = (ZL - ZS) / (ZL + ZS) = (1 - ZS/ZL) / (1 + ZS/ZL)
zr = cplxDiv(d.load[i], abcdImpedance(abcdInvert(d.abcd[i]), d.src[i]));
zr.real += 1;
result.push(cplxDiv(cplxSub({real: 2, imag: 0}, zr), zr));
}
return result;
}

function filterDataToComplexPG(d) {
// Power gain: Pout / Pin, where P = V * conj(I)
var result = [];
for (var i = 0; i < d.abcd.length; i++) {
result.push(cplxDiv(d.load[i],
cplxMul(cplxAdd(cplxMul(d.abcd[i].a, d.load[i]), d.abcd[i].b),
cplxConj(cplxAdd(cplxMul(d.abcd[i].c, d.load[i]), d.abcd[i].d))))
);
}
return result;
}

function filterDataToComplexVG(d) {
// Voltage gain: VL / VS
var result = [];
for (var i = 0; i < d.abcd.length; i++) {
// V2/V1 = 1 / (Zs * (C + D / ZL)) or 1 / (A + B/ZL), which is right?
result.push(cplxDiv({real: 1, imag: 0}, cplxAdd(d.abcd[i].a, cplxDiv(d.abcd[i].b, d.load[i]))));
//result.push(cplxDiv({real: 1, imag: 0}, cplxMul(d.src[i], cplxAdd(d.abcd[i].c, cplxDiv(d.abcd[i].d, d.load[i])))));
}
return result;
}

function filterDataToComplexIG(d) {
// Current gain: IL / IS
var result = [];
for (var i = 0; i < d.abcd.length; i++) {
// I2/I1 = ZS / (A*ZL + B) or 1 / (C*ZL + D), which is right?
result.push(cplxDiv({real: 1, imag: 0}, cplxAdd(cplxMul(d.abcd[i].c, d.load[i]), d.abcd[i].d)));
//result.push(cplxDiv(d.src[i], cplxAdd(cplxMul(d.abcd[i].a, d.load[i]), d.abcd[i].b)));
}
return result;
}

I ran through these on paper, and they should be about right.  But I'm concerned that several measurements don't depend on source impedance (i.e., source impedance canceled out of the calculation!), yet I'm fairly confident they should.

I also have reason to believe these calculations suffer from numerical instability.  Offhand, floating point numbers should be reasonably well behaved around complex domain impedance calculations, but I've definitely seen different results depending on the "default" values (the 1e(+/-)15 values for inactive R/L/C parts).  But I'll look into that later.

Tim
« Last Edit: July 02, 2017, 04:17:59 pm by T3sl4co1l »
Seven Transistor Labs, LLC
Electronic design, from concept to prototype.
Bringing a project to life?  Send me a message!
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf