Microwave RLC circuit analysis
My interests are in the area of Engineering Tools. After reviewing the references and tutorials, I decided to write a simple microwave RLC circuit analysis program to demonstrate my understanding of Rust. Two-port network analysis at high frequencies involves the following:
- Modeling of the circuit components
- Calculation of component impedance at the frequency of interest
- Calculation of each components ABCD matrix
- Calculation of the cascaded ABCD matrix for the complete circuit
- Conversion of the ABCD matrx to S-parameters
Impedance calculations involve complex numbers resulting in complex matrix multiplation and complex math.
I used the following crates for complex math:
ndarray = "0.15.4"
num = "0.4.0"
num-complex = "0.4.2"
Dependencies can be added to a rust project using Cargo to get the latest version:
For example, to get the ndarray crate, type cargo add ndarray in the terminal.
The dependency will be added to the project Cargo.toml file
[package]
name = "engr_tools"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
ndarray = "0.15.4"
num = "0.4.0"
num-complex = "0.4.2"
The first time the project is compiled with dependencies, all of the crates will be compiled with the source code. Thereafter, only the source code is compiled.
Let's create a new rust project called "engr_tools" in the rust_projects directory:
In the terminal, cd rust_projects
Type cargo new engr_tools
cd engr_tools
type cargo run to verify that main.rs compiles and displays Hello, world!
Create a subdirectory called examples.
In examples, create a new file called rlc1.rs
Note that with this directory structure you can run the program as follows:
cargo run --example rlc1
Hereafter, you can run the program again by hitting the up arrow in the terminal which will bring up the last command.
First, add the math libraries:
// rlc1.rs - Calculate the S-parameters for an RLC circuit
// Method: 2-port cascade analysis using ABCD matrices
// Math libraries
use ndarray::arr2;
use num::complex::Complex;
use std::f64::consts::PI;
Define the models for the Resistor, Inductor, and Capacitor components using structs and associated methods:
// Resistor component
#[derive(Debug)]
struct Resistor {
pub value: f64,
}
impl Resistor {
fn Z(&self) -> f64 {
let z = self.value;
z
}
}
// Inductor component
#[derive(Debug)]
struct Inductor {
value: f64,
frequency: f64,
}
impl Inductor {
fn Z(&self) -> Complex<f64> {
let z = Complex::new(0.0, 1.0 / (2.0 * PI * self.frequency * self.value));
z
}
}
// Capacitor component
#[derive(Debug)]
struct Capacitor {
value: f64,
frequency: f64,
}
impl Capacitor {
fn Z(&self) -> Complex<f64> {
let z = Complex::new(0.0, -1.0 * (2.0 * PI * self.frequency * self.value));
z
}
}
With the models defined, we are ready to begin the analysis in main() by instantiating each struct, calculating the component impendance and the component ABCD matrix:
fn main() {
let r1 = Resistor { value: 75.0 };
let Zr1 = r1.Z();
let ABCD1 = arr2(&[
[Complex::new(1.0, 0.0), Complex::new(Zr1, 0.0)],
[Complex::new(0.0, 0.0), Complex::new(1.0, 0.0)],
]);
let l1 = Inductor {
value: 5e-9,
frequency: 2e9,
};
let Zl1 = l1.Z();
let ABCD2 = arr2(&[
[Complex::new(1.0, 0.0), Zl1],
[Complex::new(0.0, 0.0), Complex::new(1.0, 0.0)],
]);
let c1 = Capacitor {
value: 1e-12,
frequency: 2e9,
};
let Zc1 = c1.Z();
let ABCD3 = arr2(&[
[Complex::new(1.0, 0.0), Zc1],
[Complex::new(0.0, 0.0), Complex::new(1.0, 0.0)],
]);
// Display netlist
println!("\nRLC Netlist:");
println!("R1 = {:#?}", r1);
println!("L1 = {:#?}", l1);
println!("C1 = {:#?}\n", c1);
Next, we calculate the cascaded ABCD matrix for the circuit:
// Multiply cascaded component ABCD matrices
let mut ABCD = ABCD1.dot(&ABCD2);
ABCD = ABCD.dot(&ABCD3);
Finally, we convert the circuit ABCD matrix to S-parameters as follows:
// Convert ABCD to S
// https://www.rfwireless-world.com/Terminology/abcd-matrix-vs-s-matrix.html
// S-parameter matrix
let mut S = arr2(&[
[Complex::new(0.0, 0.0), Complex::new(0.0, 0.0)],
[Complex::new(0.0, 0.0), Complex::new(0.0, 0.0)],
]);
// Define A, B, C, D & denominator from the ABCD matrix
let A: Complex<f64> = ABCD[[0, 0]];
let B: Complex<f64> = ABCD[[0, 1]];
let C: Complex<f64> = ABCD[[1, 0]];
let D: Complex<f64> = ABCD[[1, 1]];
let denom: Complex<f64> = &A + &B / 50.0 + &C * 50.0 + &D;
// S-parmater equations from ABCD matrix
S[[0, 0]] = (&A + &B / 50.0 - &C * 50.0 - &D) / &denom;
S[[0, 1]] = 2.0 * (&A * &D - &B * &C) / &denom;
S[[1, 0]] = 2.0 / &denom;
S[[1, 1]] = (-&A + &B / 50.0 - &C * 50.0 + &D) / &denom;
The program will output the circuit netlist using the component structs and the final S-parameter calculations:
RLC Netlist:
R1 = Resistor {
value: 75.0,
}
L1 = Inductor {
value: 5e-9,
frequency: 2000000000.0,
}
C1 = Capacitor {
value: 1e-12,
frequency: 2000000000.0,
}
S-parameters:
[[0.42857142878071847+0.000010935914101563167i, 0.5714285712192816-0.000010935914101563167i],
[0.5714285712192816-0.000010935914101563167i, 0.42857142878071847+0.000010935914101563167i]]
We have completed our first Engineering Tool in Rust. Of course, all of this took some trial and error to get the rust language and math syntax right., The full source code and directory for this project can be found on GitHub.
Comments
Post a Comment