Wednesday, April 20, 2022

33-Y-Matrix Analysis Console Application

 In this post, we will build a console application to flush out the details of the Y-Matrix or Admittance Matrix Analysis method. In the Engineering Tools solution, add a new project called YMatrixConsole and set it as the startup project. We need the matrix math library, click on Tools > NuGet Package Manager > Manage NuGet Packages for Solution.. > Browse > Search for MathNet. Click on MathNet.Numerics from the available options. Under the list of projects, select YMatrixConsole. Click Install. The project will now have access to the MathNet libraries.

To confirm access, add the following libraries to the Program.cs file in YMatrixConsole. Confirm that the three MatNet libraries are accepted with to errors.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using MathNet.Numerics;
using MathNet.Numerics.LinearAlgebra;
using MathNet.Numerics.LinearAlgebra.Complex32;
 
namespace YMatrixConsole
{
    class Program
    { . . .

Component Classes

To test the method, we will use the RLC circuit with the following values:

  • R = 75 ohms
  • L = 5 nH
  • C = 1 pF
  • F = 2 GHz
Where F is the frequency of operation.

We will include the definitions for all classes in the Program.cs file to create a super simple console application.

Create a Settings class to define static default units for the program. We define 3 public static float for GHz, nH, and pF.

    class Program
    {
        public class Settings
        {
            // Static units variables
            public static float GHz = 1e9f;
            public static float nH = 1e-9f;
            public static float pF = 1e-12f;
        }

Add the Comp class which is the base class for the component subclasses.

        public class Comp
        {
            public Matrix<Complex32> Y;
            public int[] N;
        }

First, we define the component admittance matrix Y using the MathNet Matrix library for complex 32-bit numbers. We also define the component node array called N which is an integer array.

Next, add the first component class called RES which represents a lumped resistor with Comp as the base class and will inherit all of the base class attributes.

        public class Resistor : Comp
        {
            public Resistor(int[] nodes, float res)
            {
                Matrix<Complex32> Yres = Matrix<Complex32>.Build.Dense(2, 2);
                Yres[0, 0] = 1;
                Yres[0, 1] = -1;
                Yres[1, 0] = -1;
                Yres[1, 1] = 1;
 
                Yres = Yres / res; // Won't work with a double, must be a float
                Y = Yres;
                N = nodes;
            }
        }

The class is public and contains a public constructor that accepts a node array (nodes) and the value of the resistance (res). In the constructor, we define the component admittance matrix called Yres, which is defined as a 2x2 complex matrix. We initialize the elements of the component matrix and then divide the array by the component impedance. For a resistor, Z = R in ohms. Finally, knowing that this is the first component in the circuit, we set the component admittance matrix Y = Yres. Also, we set the component node array N = nodes.

In a similar manner, lets define the inductor class IND and the capacitor class CAP. Note that the impedance of a lumped element inductor Z = j𝝎L where πŽ = 2𝝅F. The impedance of a capacitor Z = 1/j𝝎C.

        public class Inductor : Comp
        {
            public Inductor(int[] nodes, float ind, float f)
            {
                Matrix<Complex32> Yind = Matrix<Complex32>.Build.Dense(2, 2);
                Yind[0, 0] = 1;
                Yind[0, 1] = -1;
                Yind[1, 0] = -1;
                Yind[1, 1] = 1;
 
                Complex32 denom = new Complex32(0, (float)(2 * Constants.Pi * f * ind));
                Yind = Yind / denom; // Won't work with a double, must be a float
                Y = Yind;
                N = nodes;
            }
        }

        public class Capacitor : Comp
        {
            public Capacitor(int[] nodes, float cap, float f)
            {
                Matrix<Complex32> Ycap = Matrix<Complex32>.Build.Dense(2, 2);
                Ycap[0, 0] = 1;
                Ycap[0, 1] = -1;
                Ycap[1, 0] = -1;
                Ycap[1, 1] = 1;
 
                Complex32 denom = new Complex32(0, (float)(-1 / (2 * Constants.Pi * f * cap)));
                Ycap = Ycap / denom; // Won't work with a double, must be a float
                Y = Ycap;
                N = nodes;
            }
        }

Next, we will define the Circuit class which will implement the key equation methodss used in the Y-Matrix analysis.

Add four attributes for the netlist (list of components), circuit admittance matrix Y, reduced admittance matrix Yreduced, and the circuit S-parameter matrix S.

       public class Circuit
        {
            public List<Comp> netlist = new List<Comp>();
            public Matrix<Complex32> Y = Matrix<Complex32>.Build.Dense(0,0);
            public Matrix<Complex32> Yreduced;
            public Matrix<Complex32> S;

Add a method to update the circuit Y matrix with the component Y matrix.

            public void UpdateY(Comp C)
            {
                if (Y.RowCount == 0) // Y is empty
                {
                    Y = C.Y;
                }
                else
                {
                    Y = Y.Resize(Y.RowCount+1, Y.ColumnCount+1);
 
                    Y[C.N[0] - 1, C.N[0] - 1] = Y[C.N[0] - 1, C.N[0] - 1] + C.Y[0, 0];
                    Y[C.N[0] - 1, C.N[1] - 1] = Y[C.N[0] - 1, C.N[1] - 1] + C.Y[0, 1];
                    Y[C.N[1] - 1, C.N[0] - 1] = Y[C.N[1] - 1, C.N[0] - 1] + C.Y[1, 0];
                    Y[C.N[1] - 1, C.N[1] - 1] = Y[C.N[1] - 1, C.N[1] - 1] + C.Y[1, 1];
                }
            }

Next, add the ShuffleY() method which processes the final circuit Y matrix to move the external nodes to the upper left corner of the matrix.

            public void ShuffleY(int[] extN)
            {
                var M = Matrix<Complex32>.Build;
                var V = Vector<Complex32>.Build;
 
                Matrix<Complex32> Ytmp = Matrix<Complex32>.Build.Dense(0, 0);
                Ytmp = Ytmp.Resize(Y.RowCount, Y.ColumnCount);
                Vector<Complex32> Vtmp = Vector<Complex32>.Build.Dense(Y.ColumnCount);
 
                int[] allN = new int[0];
                int[] intN = new int[0];
 
                Array.Resize(ref allN, Y.RowCount);
                for (int i = 1; i < allN.Length + 1; i++)
                {
                    allN[i - 1] = i;
                }
 
                bool foundFlag;
                for (int i = 1; i < allN.Length; i++)
                {
                    foundFlag = false;
                    for (int j = 1; j < extN.Length; j++)
                    {
                        if (allN[i - 1] == extN[j - 1]) foundFlag = true;
                    }
                    if (!foundFlag)
                    {
                        Array.Resize(ref intN, intN.Length + 1);
                        intN[intN.Length - 1] = allN[i - 1];
                    }
                }
                var swapN = new int[extN.Length + intN.Length];
                extN.CopyTo(swapN, 0);
                intN.CopyTo(swapN, extN.Length);
 
                // Swap the rows
                for (int i = 0; i < swapN.Length; i++)
                {
                    Vtmp=V.DenseOfVector(Y.Row(swapN[i] - 1));
                    Ytmp.SetRow(i, Vtmp);
                }
                Y = M.DenseOfMatrix(Ytmp);
 
                // Swap the cols
                for (int i = 0; i < swapN.Length; i++)
                {
                    Vtmp = V.DenseOfVector(Y.Column(swapN[i] - 1));
                    Ytmp.SetColumn(i, Vtmp);
                }
                Y = M.DenseOfMatrix(Ytmp);
            }

Next, add the ReduceY() method which reduces the N-port Y matrix to an equivalent 2-port Y matrix. Note that MathNet provides a matrix invert method called Inverse() which is used to invert the Yii portion of the Y matrix.

           public void ReduceY(int[] extN)
            {
                var M = Matrix<Complex32>.Build;
                var V = Vector<Complex32>.Build;
 
                int nExtN = extN.Length;
                int nIntN = Y.RowCount - nExtN;
                Matrix<Complex32> Yee = Matrix<Complex32>.Build.Dense(nExtN, nExtN);
                Matrix<Complex32> Yei = Matrix<Complex32>.Build.Dense(nExtN, nIntN);
                Matrix<Complex32> Yie = Matrix<Complex32>.Build.Dense(nIntN, nExtN);
                Matrix<Complex32> Yii = Matrix<Complex32>.Build.Dense(nIntN, nIntN);
 
                //Yee = Y.topLeftCorner(nExtN, nExtN);
                //Yei = Y.block(0, nExtN, nIntN, nIntN);
                //Yie = Y.bottomLeftCorner(nIntN, nExtN);
                //Yii = Y.block(nExtN, nExtN, nIntN, nIntN);
 
                Yee = M.DenseOfMatrix(Y.SubMatrix(0, nExtN, 0, nExtN)); // SubMatrix(int rowIndex, int rowCount, int columnIndex, int columnCount)
                Yei = M.DenseOfMatrix(Y.SubMatrix(0, nExtN, nIntN, nIntN));
                Yie = M.DenseOfMatrix(Y.SubMatrix(nIntN, nExtN, 0, nExtN));
                Yii = M.DenseOfMatrix(Y.SubMatrix(nExtN, nExtN, nIntN, nIntN));
 
                Yreduced = Yee - (Yei * Yii.Inverse() * Yie);
            }

Next, we add the ConvertY() method which converts Yreduced to a 2x2 S-parameter matrix.

            public void ConvertY()
            {
                var M = Matrix<Complex32>.Build;
                var V = Vector<Complex32>.Build;
 
                Matrix<Complex32> I = Matrix<Complex32>.Build.Dense(2, 2);
                I = DiagonalMatrix.CreateIdentity(2);
                Matrix<Complex32> Yo = Matrix<Complex32>.Build.Dense(2, 2);
                float Zo = 50.0f;
                Yo = I * 1.0f / Zo;
 
                Matrix<Complex32> Stemp1 = M.Dense(2, 2);
                Stemp1 = M.DenseOfMatrix(Yo + Yreduced);
                Stemp1 = Stemp1.Inverse();
 
                Matrix<Complex32> Stemp2 = M.Dense(2, 2);
                Stemp2 = M.DenseOfMatrix((Yo - Yreduced));
 
                S = M.DenseOfMatrix(Stemp1 * Stemp2);
            }

Add a debug helper method to print a vector. This is the final method of the Circuit class.

           public void printVector(int[] vec)
            {
                for (int i = 0; i < vec.Length; i++)
                    Console.WriteLine(vec[i]);
            }
        }

Test Y-Matrix Methods

To test the classes we created, in the Main() function define the frequency, create the RLC circuit, add the component Y-matrices to the circuit Y-matrix, define the external node array, call ShuffleY, ReduceY, ConvertY and display the results in Mag-Ang format.

       static void Main(string[] args)
        {
            // *********************** FREQUENCY ***********************
            float f = 2.0f * Settings.GHz;
 
            // *********************** NETLIST ***********************
            Circuit ckt = new Circuit();
 
            // Define the resistor
            int[] resNodes = new int[] { 1, 2 };
            Resistor r1 = new Resistor(resNodes, 75.0f);
            ckt.netlist.Add(r1);
 
            // Define the inductor
            int[] indNodes = new int[] { 2, 3 };
            Inductor l1 = new Inductor(indNodes, 5.0f * Settings.nH, f);
            ckt.netlist.Add(l1);
 
            //Define the capacitor
            int[] capNodes = new int[] { 3, 4 };
            Capacitor c1 = new Capacitor(capNodes, 1.0f * Settings.pF, f);
            ckt.netlist.Add(c1);
 
            // *********************** Create Y-Matrix ***********************
            foreach (Comp C in ckt.netlist)
            {
                ckt.UpdateY(C);
            }
 
            // Define external nodes
            int[] extNodes = new int[] { 1, 4 };
            ckt.ShuffleY(extNodes);
            ckt.ReduceY(extNodes);
            ckt.ConvertY();
 
            // *********************** Display Results ***********************
            Console.WriteLine("f\t" + "S11\t\t\t" + "S12\t\t\t" + "S21\t\t\t" + "S22");
            Console.WriteLine(f + "  \t" + String.Format("{0:0.###}", ckt.S[0, 0]) +
                                  "  \t" + String.Format("{0:0.###}", ckt.S[0, 1]) +
                                  "  \t" + String.Format("{0:0.###}", ckt.S[1, 0]) +
                                  "  \t" + String.Format("{0:0.###}", ckt.S[1, 1]));
 
            Console.WriteLine("Press any key to continue...");
            Console.ReadLine();
        }

The S-parameters are displayed on the output console.


We can confirm these results using another circuit simulator such as QUCS or we can check them against a similar analysis in "Microwave Circuit Design Using Programmable Calculators" Allen, Medley, pg. 27. The source code for this post including the reference document is found in this GitHub commit.


No comments:

Post a Comment

34-Microwave Tools with Analysis (Series Final Post)

In this final blog post, I have integrated Y-Matrix analysis into the Microwave Tools project. This version has addition Lumped, Ideal, Micr...