Thursday, April 21, 2022

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, Microstrip and CPW components added to the component menus. Although, some microstrip and CPW equations are not complete.


The S-parameter results for the RLC circuit are displayed on a new form with a chart and data display. This version does not include component select, delete, or line stretch which is left as an exercise for the reader.


The source code for this final post is available on GitHub. I plan to start a new implementation in C++/Qt using the Eigen math library which I believe will give more accurate results because it implements 64-bit complex matrix math. I will post the results periodically on GitHub.

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.


Tuesday, April 19, 2022

32-RF/Microwave Circuit Analysis Overview

In this post, we will shift gears from creating a circuit diagram to circuit analysis. The circuit analysis method is based on methods described in Chapter 6 of "Computer-Aided Analysis of Linear Circuits" by M. L. Edwards published in September 2001 online.  The paper describes general N-port linear RF/microwave circuit analysis in Matlab circa 2001. A modern Matlab version is available in this GitHub repository.

The circuit analysis method is:

// *********************** Circuit analysis process ***********************
// 1) Define default units
// 2) Deine the frequency range
// 3) Define the circuit as a netlist
// 4) Add each component admittance Y-matrix to circuit Y-matrix
// 5) Reduce circuit Y-matrix to 2-port Y-matrix
// 6) Convert Y-matrix to S-matrix
// 7) Repeat steps 4-6 for each frequency
// 8) Display the results on plot or/and data table
// ***********************************************************************

The key equations used in the admittance matrix method are:


From "Computer-Aided Analysis of Linear Circuits", M.L. Edwards, pg. 6-6.

Note that the matrices are created using the complex admittance of the circuit components. Because of this, we need a complex matrix library for C#. For this project, we will use a Nuget library called MathNet.Numerics. The library supports complex numbers, complex math operations, complex vector operations and complex matrix math. The reader can find documentation and tutorials on the library here.



31-Microwave Tools Feature Integration

In this post, the code for component rotation, selection, deletion, and wire stretch will be integrated into the MicrowaveTools project. Note that all of these features are available in the TestStrechWires project. Set MicrowaveTools as the startup project.

No changes are required for the Circuit.cs class.

Component Classes

Copy the following classes from TestStretchWires to the MicrowaveTools project.

  • Comp.cs
  • InPort.cs
  • OutPort.cs
  • RES.cs
  • Wire.cs
Don't forget to change the namespace to MicrowaveTools.

Ground Class (Ground.cs)

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
 
namespace MicrowaveTools.Components.Ideal
{
    public class Ground : Comp
    {
        public Ground()
        {
 
        }
 
        public Ground(Point pt)
        {
            Loc.X = pt.X;
            Loc.Y = pt.Y;
            Width = 60;
            Height = 60;
            boundBox = new Rectangle(Loc.X, Loc.Y, Width, Height);
 
            Pout = new Point(Loc.X + compSize, Loc.Y + halfCompSize);
        }
 
        // Let the Ground draw itself called from the canvas paint event
        public override void Draw(Graphics gr)
        {
            checkSelect();
            drawSelectRect(gr, new Point(Loc.X, Loc.Y + 30));
 
            GraphicsPath gp = new GraphicsPath();
 
            // Define the points
            Point p1 = Loc;
            Point p2 = new Point(p1.X, p1.Y + leadL);
            Point p3 = new Point(p2.X - 10, p2.Y);
            Point p4 = new Point(p2.X + 10, p2.Y);
            Point p5 = new Point(p3.X + 5, p3.Y + 5);
            Point p6 = new Point(p5.X + 10, p5.Y);
            Point p7 = new Point(p5.X + 3, p5.Y + 5);
            Point p8 = new Point(p7.X + 4, p7.Y);
 
            // Draw the input lead
            gp.AddLine(p1, p2);
            gp.AddLine(p3, p4);
            gp.AddLine(p5, p6);
            gp.AddLine(p7, p8);
 
            // Set rotation angle
            if (isRotated)
                angle = 90;
            else
                angle = 0;
 
            // Rotate the component by angle deg
            PointF rotatePoint = new PointF(Loc.X + 30, Loc.Y + 30); // Rotate about component center point
            Matrix myMatrix = new Matrix();
            myMatrix.RotateAt(angle, rotatePoint, MatrixOrder.Append);
            gr.Transform = myMatrix;
 
            // Update the bounding box location
            boundBox = new Rectangle(Loc.X, Loc.Y, Width, Height);
 
            // Draw the component path
            gr.DrawPath(drawPen, gp);
 
            // Draw the bounding box for debug
            //gr.DrawRectangle(redPen, boundBox);
 
            gp.Dispose();
        }
    }
}

Inductor Class (IND.cs)

// C# Libraries
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
 
namespace MicrowaveTools.Components.Lumped
{
    public class IND : Comp
    {
        public IND()
        {
 
        }
 
        public IND(double value, Point pt)
        {
            Loc.X = pt.X;
            Loc.Y = pt.Y;
            Width = 60;
            Height = 60;
            Value = value;
            boundBox = new Rectangle(Loc.X, Loc.Y, Width, Height);
 
            Pin = new Point(Loc.X, Loc.Y + 30);
            Pout = new Point(Loc.X + compSize, Loc.Y + 30);
        }
 
        public override void Draw(Graphics gr)
        {
            // Component selection
            checkSelect();
            drawSelectRect(gr, new Point(Loc.X, Loc.Y + 30));
            drawSelectRect(gr, new Point(Loc.X + compL - endcap_radius, Loc.Y + 30));
 
            GraphicsPath gp = new GraphicsPath();
 
            // Draw the input leads
            gp.AddLine(Loc.X, Loc.Y + 30, Loc.X + leadL, Loc.Y + 30);
 
            // Draw the inductor body curves
            for (int i = 1; i < 5; i++)
            {
                float startAngle = 180;
                float sweepAngle = 180;
                Rectangle rect = new Rectangle(Loc.X + leadL * (i), Loc.Y - leadL - 5 + 30, leadL, leadL);
                gp.AddArc(rect, startAngle, sweepAngle);
            }
 
            // Draw the inductor body vertical lines
            for (int i = 1; i < 6; i++)
            {
                gr.DrawLine(drawPen, Loc.X + leadL * (i), Loc.Y + 30, Loc.X + leadL * (i), Loc.Y - leadL + 30);
            }
 
            // Draw the output leads
            gp.AddLine(Loc.X + bodyL + leadL, Loc.Y + 30, Loc.X + compL, Loc.Y + 30);   // Output lead
 
            // Draw the component text
            compText = "L = " + this.Value + "nH";
            pt = new Point(Loc.X + 5, Loc.Y);
            gp.AddString(compText, family, fontStyle, emSize, pt, format);
 
            // Set rotation angle
            if (isRotated)
                angle = 90;
            else
                angle = 0;
 
            // Rotate the component by angle deg
            PointF rotatePoint = new PointF(Loc.X + 30, Loc.Y + 30); // Rotate about component center point
            Matrix myMatrix = new Matrix();
            myMatrix.RotateAt(angle, rotatePoint, MatrixOrder.Append);
            gr.Transform = myMatrix;
 
            // Update the bounding box location
            boundBox = new Rectangle(Loc.X, Loc.Y, Width, Height);
 
            // Draw the component path
            gr.DrawPath(drawPen, gp);
 
            // Draw the bounding box for debug
            //gr.DrawRectangle(redPen, boundBox);
 
            gp.Dispose();
        }
    }
}

Capacitor Class (CAP.cs)

// C# Libraries
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
 
namespace MicrowaveTools.Components.Lumped
{
    public class CAP : Comp
    {
        public CAP()
        {
 
        }
 
        public CAP(double value, Point pt)
        {
            Loc.X = pt.X;
            Loc.Y = pt.Y;
            Width = 60;
            Height = 60;
            Value = value;
            boundBox = new Rectangle(Loc.X, Loc.Y, Width, Height);
 
            Pin = new Point(Loc.X, Loc.Y + 30);
            Pout = new Point(Loc.X + compSize, Loc.Y + 30);
        }
 
        // Let the Capacitor draw itself called from the canvas paint event
        public override void Draw(Graphics gr)
        {
            // Component selection
            checkSelect();
            drawSelectRect(gr, new Point(Loc.X, Loc.Y + 30));
            drawSelectRect(gr, new Point(Loc.X + compL - endcap_radius, Loc.Y + 30));
 
            GraphicsPath gp = new GraphicsPath();
 
            // Draw the input leads
            gp.AddLine(Loc.X, Loc.Y + 30, Loc.X + 2 * leadL + 5, Loc.Y + 30);
 
            // Draw the capacitor body vertical line
            gp.AddLine(Loc.X + 2 * leadL + 5, Loc.Y - leadL + 30, Loc.X + 2 * leadL + 5, Loc.Y + leadL + 30);
 
            gp.StartFigure(); // Starts a new figure without closing the current figure.
 
            // Draw the capacitor body curves
            float startAngle = 90;
            float sweepAngle = 180;
            Rectangle rect = new Rectangle(Loc.X + 3 * leadL, Loc.Y - 10 + 30, leadL, leadL * 2);
            gp.AddArc(rect, startAngle, sweepAngle);
 
            // Draw the output leads
            gp.AddLine(Loc.X + 3 * leadL, Loc.Y + 30, Loc.X + compL, Loc.Y + 30);
 
            // Draw the component text
            compText = "C = " + this.Value + "pF";
            pt = new Point(Loc.X + 5, Loc.Y);
            gp.AddString(compText, family, fontStyle, emSize, pt, format);
 
            // Set rotation angle
            if (isRotated)
                angle = 90;
            else
                angle = 0;
 
            // Rotate the component by angle deg
            PointF rotatePoint = new PointF(Loc.X + 30, Loc.Y + 30); // Rotate about component center point
            Matrix myMatrix = new Matrix();
            myMatrix.RotateAt(angle, rotatePoint, MatrixOrder.Append);
            gr.Transform = myMatrix;
 
            // Update the bounding box location
            boundBox = new Rectangle(Loc.X, Loc.Y, Width, Height);
 
            // Draw the component path
            gr.DrawPath(drawPen, gp);
 
            // Draw the bounding box for debug
            //gr.DrawRectangle(redPen, boundBox);
 
            gp.Dispose();
        }
    }
} 

Microstrip Line Class (MLIN.cs)

// C# Libraries
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
 
namespace MicrowaveTools.Components.Microstrip
{
    public class MLIN : Comp
    {
        public MLIN()
        {
 
        }
 
        public MLIN(double value, Point pt)
        {
            Loc.X = pt.X;
            Loc.Y = pt.Y;
            Width = 60;
            Height = 60;
            Value = value;
            boundBox = new Rectangle(Loc.X, Loc.Y, Width, Height);
 
            Pin = new Point(Loc.X, Loc.Y + 30);
            Pout = new Point(Loc.X + compSize, Loc.Y + 30);
        }
 
        // Let the MLIN draw itself called from the canvas paint event
        public override void Draw(Graphics gr)
        {
            // Component selection
            checkSelect();
            drawSelectRect(gr, new Point(Loc.X, Loc.Y + 30));
            drawSelectRect(gr, new Point(Loc.X + compL - endcap_radius, Loc.Y + 30));
 
            GraphicsPath gp = new GraphicsPath();
 
            Point p1 = Loc;             // Assume p1 is the end of the lead at the output of Pin
            Point p2 = new Point(p1.X + 10, p1.Y);
            Point p3 = new Point(p2.X, p2.Y - 10); // Location of MLIN rectangle
            Point p4 = new Point(p2.X + 40, p2.Y);
            Point p5 = new Point(p4.X + 10, p4.Y);
 
            gp.AddLine(p1, p2);
            gp.AddLine(p4, p5);
            gp.AddRectangle(new Rectangle(p3.X, p3.Y, 40, 20));
 
            // Draw the component text
            compText = "W = " + this.Value + " mils";
            pt = new Point(Loc.X + 5, Loc.Y + 5);
            gp.AddString(compText, family, fontStyle, emSize, pt, format);
 
            // Set rotation angle
            if (isRotated)
                angle = 90;
            else
                angle = 0;
 
            // Rotate the component by angle deg
            PointF rotatePoint = new PointF(Loc.X + 30, Loc.Y + 30); // Rotate about component center point
            Matrix myMatrix = new Matrix();
            myMatrix.RotateAt(angle, rotatePoint, MatrixOrder.Append);
            gr.Transform = myMatrix;
 
            // Update the bounding box location
            boundBox = new Rectangle(Loc.X, Loc.Y, Width, Height);
 
            // Draw the component path
            gr.DrawPath(drawPen, gp);
 
            // Draw the bounding box for debug
            //gr.DrawRectangle(redPen, boundBox);
 
            gp.Dispose();
        }
    }
}

Microstirp Cross Class (MCROS.cs)

// C# Libraries
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
 
namespace MicrowaveTools.Components.Microstrip
{
    public class MCROS : Comp
    {
        Point[] p = new Point[21];
 
        public MCROS()
        {
 
        }
 
        public MCROS(double value, Point pt)
        {
            Loc.X = pt.X;
            Loc.Y = pt.Y;
            Width = 60;
            Height = 60;
            Value = value;
            boundBox = new Rectangle(Loc.X, Loc.Y, Width, Height);
 
            Pin = new Point(Loc.X, Loc.Y + 30);
            Pout = new Point(Loc.X + compSize, Loc.Y + 30);
        }
 
        // Let the MCROS draw itself called from the canvas paint event
        public override void Draw(Graphics gr)
        {
            // Component selection
            checkSelect();
            drawSelectRect(gr, new Point(Loc.X, Loc.Y + 30));
            drawSelectRect(gr, new Point(Loc.X + compL - endcap_radius, Loc.Y + 30));
 
            GraphicsPath gp = new GraphicsPath();
 
            p[1] = Loc;            // Assume p1 is the input lead to the left
            p[2] = new Point(p[1].X + 10, p[1].Y);
            p[3] = new Point(p[2].X, p[2].Y - 10);
            p[4] = new Point(p[3].X + 10, p[3].Y);
            p[5] = new Point(p[4].X, p[4].Y - 10);
            p[6] = new Point(p[5].X + 20, p[5].Y);
            p[7] = new Point(p[6].X, p[6].Y + 10);
            p[8] = new Point(p[7].X + 10, p[7].Y);
            p[9] = new Point(p[8].X, p[8].Y + 20);
            p[10] = new Point(p[9].X - 10, p[9].Y);
            p[11] = new Point(p[10].X, p[10].Y + 10);
            p[12] = new Point(p[11].X - 20, p[11].Y);
            p[13] = new Point(p[12].X, p[12].Y - 10);
            p[14] = new Point(p[13].X - 10, p[13].Y);
            p[15] = new Point(p[5].X + 10, p[5].Y - 10);
            p[16] = new Point(p[15].X, p[15].Y + 10);
            p[17] = new Point(p[8].X + 10, p[8].Y + 10);
            p[18] = new Point(p[17].X - 10, p[17].Y);
            p[19] = new Point(p[11].X - 10, p[11].Y + 10);
            p[20] = new Point(p[19].X, p[19].Y - 10);
 
            for (int i = 1; i < 14; i++)
                gp.AddLine(p[i], p[i + 1]);
            gp.AddLine(p[14], p[2]);
            for (int i = 15; i < 21; i += 2)
                gp.AddLine(p[i], p[i + 1]);
 
             // Draw the component text
            compText = "W = " + this.Value + " mils";
            pt = new Point(Loc.X + 5, Loc.Y + 5);
            gp.AddString(compText, family, fontStyle, emSize, pt, format);
 
            // Set rotation angle
            if (isRotated)
                angle = 90;
            else
                angle = 0;
 
            // Rotate the component by angle deg
            PointF rotatePoint = new PointF(Loc.X + 30, Loc.Y + 30); // Rotate about component center point
            Matrix myMatrix = new Matrix();
            myMatrix.RotateAt(angle, rotatePoint, MatrixOrder.Append);
            gr.Transform = myMatrix;
 
            // Update the bounding box location
            boundBox = new Rectangle(Loc.X, Loc.Y, Width, Height);
 
            // Draw the component path
            gr.DrawPath(drawPen, gp);
 
            // Draw the bounding box for debug
            //gr.DrawRectangle(redPen, boundBox);
 
            gp.Dispose();
        }
    }
}

Microstrip Tee Class (MTEE)

// C# Libraries
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
 
namespace MicrowaveTools.Components.Microstrip
{
    public class MTEE : Comp
    {
        public MTEE()
        {
 
        }
 
        public MTEE(double value, Point pt)
        {
            Loc.X = pt.X;
            Loc.Y = pt.Y;
            Width = 60;
            Height = 60;
            Value = value;
            boundBox = new Rectangle(Loc.X, Loc.Y, Width, Height);
 
            Pin = new Point(Loc.X, Loc.Y + 30);
            Pout = new Point(Loc.X + compSize, Loc.Y + 30);
        }
 
        // Let the MTEE draw itself called from the canvas paint event
        public override void Draw(Graphics gr)
        {
            // Component selection
            checkSelect();
            drawSelectRect(gr, new Point(Loc.X, Loc.Y + 30));
            drawSelectRect(gr, new Point(Loc.X + compL - endcap_radius, Loc.Y + 30));
 
            GraphicsPath gp = new GraphicsPath();
 
            Point[] p = new Point[21];
            p[1] = Loc;            // Assume p1 is the input lead to the left
            p[2] = new Point(p[1].X + 10, p[1].Y);
            p[3] = new Point(p[2].X, p[2].Y - 10);
            p[4] = new Point(p[3].X + 10, p[3].Y);
            p[5] = new Point(p[4].X, p[4].Y - 10);
            p[6] = new Point(p[5].X + 20, p[5].Y);
            p[7] = new Point(p[6].X, p[6].Y + 10);
            p[8] = new Point(p[7].X + 10, p[7].Y);
            p[9] = new Point(p[8].X, p[8].Y + 20);
            p[14] = new Point(p[9].X - 40, p[9].Y);
            p[15] = new Point(p[5].X + 10, p[5].Y - 10);
            p[16] = new Point(p[15].X, p[15].Y + 10);
            p[17] = new Point(p[8].X + 10, p[8].Y + 10);
            p[18] = new Point(p[17].X - 10, p[17].Y);
 
            for (int i = 1; i < 9; i++)
                gp.AddLine(p[i], p[i + 1]);
            gp.AddLine(p[9], p[14]);
            gp.AddLine(p[14], p[2]);
            for (int i = 15; i < 19; i += 2)
                gp.AddLine(p[i], p[i + 1]);
 
            // Draw the component text
            compText = "W = " + this.Value + " mils";
            pt = new Point(Loc.X + 5, Loc.Y + 5);
            gp.AddString(compText, family, fontStyle, emSize, pt, format);
 
            // Set rotation angle
            if (isRotated)
                angle = 90;
            else
                angle = 0;
 
            // Rotate the component by angle deg
            PointF rotatePoint = new PointF(Loc.X + 30, Loc.Y + 30); // Rotate about component center point
            Matrix myMatrix = new Matrix();
            myMatrix.RotateAt(angle, rotatePoint, MatrixOrder.Append);
            gr.Transform = myMatrix;
 
            // Update the bounding box location
            boundBox = new Rectangle(Loc.X, Loc.Y, Width, Height);
 
            // Draw the component path
            gr.DrawPath(drawPen, gp);
 
            // Draw the bounding box for debug
            //gr.DrawRectangle(redPen, boundBox);
 
            gp.Dispose();
        }
    }
}

Main Form Modifications

Add the attributes from the TestStretchLine Main Form. Keep the Wire NewWire = New Wire(); attribute.

Update the mouse event handlers keeping the line drawing options

  • Mouse down event handler
  • Mouse move event handler
  • Mouse up event handler

Add the following items:

  • Main form keypress event handler
  • Main form keydown event handler
  • confirmDelete() method
  • deleteSelectedComps() method

Add a new button in the Main Menu select panel:
  • Text: Select Mode
  • Name: btnSelect
Add a btnSelect Click event handler:

        private void btnSelect_Click(object sender, EventArgs e)
        {
            isSelectMode = !isSelectMode;
        }

Finally, update the hitTest() method to support the component bounding boxes.

Run the program and test component rotate, select, delete, and wire stretch. The complete source code for this post is available on GitHub.



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...