Transmission (ABCD) Matrix - Check My Work - Page 1
Forum section: electronics / rf-microwave

author: T3sl4co1l
author_rank: Super Contributor
timestamp: July 02, 2017, 04:15:46 pm
content: This is not a homework question, by the way.In my AC filter simulator,https://www.seventransistorlabs.com/Calc/Filter1~.htmlI'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: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:{a: {real: 1, imag: 0},b: cplxPar({real: inputs, imag: inputs * 2 * Math.PI * freq - 1 / (inputs * 2 * Math.PI * freq)},{real: inputs, imag: inputs * 2 * Math.PI * freq - 1 / (inputs * 2 * Math.PI * freq)},{real: inputs, imag: inputs * 2 * Math.PI * freq - 1 / (inputs * 2 * Math.PI * freq)}),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:Input VSInput I(VS)Input ZInput R.L.Output VLOutput I(VL)Output ZOutput R.L.Power GainVoltage GainCurrent 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: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: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:/***Functions for converting the filterData structure into*analytical measurements.*@param*dStructure containing frequency and network arrays:*d.freqfrequency array (with properties min, max, log)*d.abcdabcd matrix array*d.srcsource impedance array*d.src.eqsource equivalent type (0 = Thevenin, 1 = Norton)*d.loadload impedance array*d.load.eqload equivalent type*@returns*array of complex values.*/function filterDataToComplexInVS(d) {var result = [];if (d.src.eq) {//Nortonfor (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, abcdImpedance(d.abcd, d.load)));}} else {//Theveninfor (var i = 0; i < d.abcd.length; i++) {//Voltage divider: d.src to (d.load applied to d.abcd)result.push(cplxImpedanceDivider(d.src, abcdImpedance(d.abcd, d.load)));}}return result;}function filterDataToComplexInIS(d) {var result = [];if (d.src.eq) {//Nortonfor (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, d.load), d.src));}} else {//Theveninfor (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, abcdImpedance(d.abcd, d.load))));}}return result;}function filterDataToComplexInZ(d) {var result = [];for (var i = 0; i < d.abcd.length; i++) {//Input impedance: d.load applied to d.abcdresult.push(abcdImpedance(d.abcd, d.load));}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, abcdImpedance(d.abcd, d.load));zr.real += 1;result.push(cplxDiv(cplxSub({real: 2, imag: 0}, zr), zr));}return result;}function filterDataToComplexOutVL(d) {var result = [];if (d.load.eq) {//Nortonfor (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, abcdImpedance(abcdInvert(d.abcd), d.src)));}} else {//Theveninfor (var i = 0; i < d.abcd.length; i++) {//Voltage divider: d.load to (d.load applied to d.abcd)result.push(cplxImpedanceDivider(d.load, abcdImpedance(abcdInvert(d.abcd), d.src)));}}return result;}function filterDataToComplexOutIL(d) {var result = [];if (d.load.eq) {//Nortonfor (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), d.load), d.src));}} else {//Theveninfor (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, abcdImpedance(abcdInvert(d.abcd), d.src))));}}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), d.src));}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, abcdImpedance(abcdInvert(d.abcd), d.src));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,cplxMul(cplxAdd(cplxMul(d.abcd.a, d.load), d.abcd.b),cplxConj(cplxAdd(cplxMul(d.abcd.c, d.load), d.abcd.d)))));}return result;}function filterDataToComplexVG(d) {//Voltage gain: VL / VSvar 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.a, cplxDiv(d.abcd.b, d.load))));//result.push(cplxDiv({real: 1, imag: 0}, cplxMul(d.src, cplxAdd(d.abcd.c, cplxDiv(d.abcd.d, d.load)))));}return result;}function filterDataToComplexIG(d) {//Current gain: IL / ISvar 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.c, d.load), d.abcd.d)));//result.push(cplxDiv(d.src, cplxAdd(cplxMul(d.abcd.a, d.load), d.abcd.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