Struct MnaSystem
pub struct MnaSystem {
pub num_nodes: usize,
pub num_vsources: usize,
pub g_matrix: Vec<f64>,
pub b_matrix: Vec<f64>,
pub c_matrix: Vec<f64>,
pub d_matrix: Vec<f64>,
}Expand description
MNA (Modified Nodal Analysis) system for deriving WDF scattering matrices.
Element stamps define the internal circuit topology. The system is solved at compile time to produce the scattering matrix.
Fields§
§num_nodes: usizeNumber of nodes (excluding ground)
num_vsources: usizeNumber of voltage sources / controlled sources
g_matrix: Vec<f64>Conductance matrix G (num_nodes × num_nodes)
b_matrix: Vec<f64>Voltage source matrix B (num_nodes × num_vsources)
c_matrix: Vec<f64>Current output matrix C (num_vsources × num_nodes)
d_matrix: Vec<f64>Direct coupling matrix D (num_vsources × num_vsources)
Implementations§
§impl MnaSystem
impl MnaSystem
pub fn stamp_resistor(
&mut self,
n1: Option<usize>,
n2: Option<usize>,
resistance: f64,
)
pub fn stamp_resistor( &mut self, n1: Option<usize>, n2: Option<usize>, resistance: f64, )
Add a resistor stamp between nodes n1 and n2.
pub fn stamp_voltage_source(
&mut self,
n_pos: Option<usize>,
n_neg: Option<usize>,
vsrc_idx: usize,
)
pub fn stamp_voltage_source( &mut self, n_pos: Option<usize>, n_neg: Option<usize>, vsrc_idx: usize, )
Add a voltage source stamp (vsrc_idx) between nodes n+ and n-.
pub fn stamp_transformer(
&mut self,
p_pos: Option<usize>,
p_neg: Option<usize>,
s_pos: Option<usize>,
s_neg: Option<usize>,
vsrc_p: usize,
vsrc_s: usize,
turns_ratio: f64,
)
pub fn stamp_transformer( &mut self, p_pos: Option<usize>, p_neg: Option<usize>, s_pos: Option<usize>, s_neg: Option<usize>, vsrc_p: usize, vsrc_s: usize, turns_ratio: f64, )
Add an ideal transformer stamp.
Primary: nodes p+ to p- (voltage source vsrc_p) Secondary: nodes s+ to s- (voltage source vsrc_s) Turns ratio: n = V_primary / V_secondary
pub fn stamp_vccs(
&mut self,
out_pos: Option<usize>,
out_neg: Option<usize>,
in_pos: Option<usize>,
in_neg: Option<usize>,
gm: f64,
)
pub fn stamp_vccs( &mut self, out_pos: Option<usize>, out_neg: Option<usize>, in_pos: Option<usize>, in_neg: Option<usize>, gm: f64, )
Add a VCCS (Voltage-Controlled Current Source) stamp.
Models I_out = gm * V_in where:
- Input voltage: V(in_pos) - V(in_neg)
- Output current: flows from out_pos to out_neg
MNA stamp: G[out+][in+] += gm, G[out+][in-] -= gm, G[out-][in+] -= gm, G[out-][in-] += gm
pub fn stamp_vcvs(
&mut self,
pos: Option<usize>,
neg: Option<usize>,
out_pos: Option<usize>,
out_neg: Option<usize>,
aol: f64,
ro: f64,
vsrc_idx: usize,
)
pub fn stamp_vcvs( &mut self, pos: Option<usize>, neg: Option<usize>, out_pos: Option<usize>, out_neg: Option<usize>, aol: f64, ro: f64, vsrc_idx: usize, )
Add a finite-gain VCVS (Voltage-Controlled Voltage Source) stamp for
modelling an op-amp as V_out = Aol·(V_pos − V_neg) − Ro·i_vsrc.
This is the nullor (Werner 2016) generalised to finite open-loop gain
and non-zero output impedance. With Aol → ∞, Ro → 0 it becomes a
pure nullor (infinite-gain ideal op-amp). Typical datasheet values for
audio op-amps: Aol ∈ [50k, 400k], Ro ∈ [50, 200]Ω.
Reserves one auxiliary MNA row/column (the norator branch current
i_vsrc). The constraint equation written into row vsrc_idx is:
v(out_pos) − v(out_neg) − Aol·v(pos) + Aol·v(neg) + Ro·i_vsrc = 0Stamp:
B[out_pos, vsrc] += +1,B[out_neg, vsrc] += −1— branch current i_vsrc is injected into the out_pos/out_neg node pairC[vsrc, out_pos] += +1,C[vsrc, out_neg] += −1— output voltageC[vsrc, pos] += −Aol,C[vsrc, neg] += +Aol— controlling voltageD[vsrc, vsrc] += Ro— finite output impedance
Inputs at pos/neg draw zero current (nullator / infinite input
impedance).
pos,neg— non-inverting and inverting input nodes (None = gnd)out_pos,out_neg— output node pair (None = gnd)aol— open-loop voltage gain (dimensionless)ro— output resistance in Ohms (use 0.0 for ideal nullor)vsrc_idx— auxiliary MNA branch index (must be < num_vsources)
pub fn dc_gain(&self, vs_idx: usize, output_pos: Option<usize>) -> f64
pub fn dc_gain(&self, vs_idx: usize, output_pos: Option<usize>) -> f64
Derive the scattering matrix for WDF ports.
Compute the DC transfer function gain: V_out / V_in at f=0.
Solves the augmented MNA system [G, B; C, D] · [V; I] = [0; …1…] and returns the voltage at the output node when VS injects 1V. For purely resistive networks (no caps), this IS the transfer function.
pub fn derive_scattering_matrix(&self, port_resistances: &[f64]) -> Vec<f64>
pub fn derive_scattering_matrix(&self, port_resistances: &[f64]) -> Vec<f64>
Each port corresponds to a Thévenin equivalent at a node pair. The last port is adapted (reflection-free).
Returns: NxN scattering matrix in row-major order.
pub fn derive_scattering_matrix_general(&self, ports: &[WdfPort]) -> Vec<f64>
pub fn derive_scattering_matrix_general(&self, ports: &[WdfPort]) -> Vec<f64>
Derive scattering matrix for arbitrary port terminal pairs.
Generalizes derive_scattering_matrix to support ports that span
arbitrary node pairs (not just node-to-ground). This is required for
BJT collector-to-emitter ports where neither terminal is ground.
Uses the generalized Werner formula:
S[i][j] = 2/R_j · (X⁻¹[ai,aj] - X⁻¹[ai,bj] - X⁻¹[bi,aj] + X⁻¹[bi,bj]) - δ_ijwhere a_k/b_k are the positive/negative terminal nodes of port k.
The last port is adapted (reflection-free, S[n-1][n-1] ≈ 0).
pub fn derive_scattering_and_vs_injection(
&self,
ports: &[WdfPort],
vs_idx: usize,
) -> (Vec<f64>, Vec<f64>)
pub fn derive_scattering_and_vs_injection( &self, ports: &[WdfPort], vs_idx: usize, ) -> (Vec<f64>, Vec<f64>)
Derive scattering matrix and VS injection vector for ports driven by an ideal voltage source (zero internal impedance).
Unlike derive_scattering_matrix_general where the VS is a WDF port,
this method stamps the VS directly into the MNA B/C/D matrices so its
internal impedance is zero. The scattering matrix is derived for the
remaining (reactive + probe) ports only, and a separate injection
vector k maps the VS voltage to port incident waves:
a[i] = Σ_j S[i][j] · b[j] + k[i] · V_inports— WDF ports (reactive elements + output probe; no VS port)vs_idx— index of the voltage source branch in the MNA system
Returns (scattering, vs_injection).
pub fn derive_extraction_coeffs(
&self,
ports: &[WdfPort],
vs_idx: usize,
output_pos: Option<usize>,
output_neg: Option<usize>,
) -> (Vec<f64>, f64)
pub fn derive_extraction_coeffs( &self, ports: &[WdfPort], vs_idx: usize, output_pos: Option<usize>, output_neg: Option<usize>, ) -> (Vec<f64>, f64)
Derive node-voltage extraction coefficients for reading the output voltage at a specific MNA node from the WDF port b-waves and VS input.
The output voltage is computed as:
V(node) = Σ_k extract[k] * b[k] + extract_vs * V_inThis bypasses WDF port impedance mismatch by reading the circuit’s nodal voltage directly from the X⁻¹ matrix.
ports— WDF ports (same as passed to scattering derivation)vs_idx— index of the voltage source branch in the MNA systemoutput_pos— positive output MNA node (Some(idx) or None for ground)output_neg— negative output MNA node (Some(idx) or None for ground)
Returns (port_coeffs, vs_coeff).
pub fn derive_node_extraction_coeffs(
&self,
ports: &[WdfPort],
output_pos: Option<usize>,
output_neg: Option<usize>,
) -> Vec<f64>
pub fn derive_node_extraction_coeffs( &self, ports: &[WdfPort], output_pos: Option<usize>, output_neg: Option<usize>, ) -> Vec<f64>
Derive node-voltage extraction coefficients for reading the output voltage at a specific MNA node from the WDF port b-waves, without a voltage source.
This is the same computation as derive_extraction_coeffs but omits the VS
term. Use this for standard adapted-WDF-port stages (non-VS-injection mode)
where the adapted port is itself the last element of ports.
Returns port_coeffs where V(out) = Σ_k port_coeffs[k] * b[k].
pub fn build_iir(
&self,
reactive_one_ports: &[OnePort<DomainIndex<MnaNodeDomain>>],
_vs_idx: usize,
_output_pos: Option<usize>,
_output_neg: Option<usize>,
sample_rate: f64,
feedback_r: Option<(f64, f64, f64)>,
) -> Option<(Vec<f64>, Vec<f64>)>
pub fn build_iir( &self, reactive_one_ports: &[OnePort<DomainIndex<MnaNodeDomain>>], _vs_idx: usize, _output_pos: Option<usize>, _output_neg: Option<usize>, sample_rate: f64, feedback_r: Option<(f64, f64, f64)>, ) -> Option<(Vec<f64>, Vec<f64>)>
Build discrete-time state-space matrices from the continuous-time MNA using the bilinear (trapezoidal) transform.
This avoids WDF port-impedance scaling issues by working directly with node voltages. Capacitors are treated as continuous-time elements (C matrix) rather than WDF ports (port conductance 1/R_p = 2·f_s·C).
The continuous-time augmented system is:
[G B] [V ] [C_cap 0] [dV/dt] [0 ]
[E D] [I ] + [0 0] [dI/dt] = [V_s]Compile a linear MNA circuit to IIR filter coefficients.
For stable circuits: derives the transfer function H(z) from the MNA and returns IIR coefficients directly. Order = number of caps.
For unstable circuits (oscillators): detects the instability via eigenvalue analysis, computes f0 from cap/R values and Q from the loop gain margin, and returns a biquad IIR.
Returns Some((b_coeffs, a_coeffs)) where b is the numerator
and a is the denominator (a[0] = 1.0, normalized).
Returns None if the circuit can’t be compiled to IIR.
pub fn build_state_space_matrices(
&self,
reactive_one_ports: &[OnePort<DomainIndex<MnaNodeDomain>>],
vs_idx: usize,
output_pos: Option<usize>,
output_neg: Option<usize>,
sample_rate: f64,
) -> (Vec<f64>, Vec<f64>, Vec<f64>, usize, f64)
pub fn build_state_space_matrices( &self, reactive_one_ports: &[OnePort<DomainIndex<MnaNodeDomain>>], vs_idx: usize, output_pos: Option<usize>, output_neg: Option<usize>, sample_rate: f64, ) -> (Vec<f64>, Vec<f64>, Vec<f64>, usize, f64)
Bilinear transform s → 2·f_s·(z-1)/(z+1) gives:
M · x[n] = N · x[n-1] + F · u[n]where M = [G+2fsC B; E D], N = [2fsC-G -B; 0 0],
and F = [0...1...] maps the VS input.
Returns (A_d, b_d, c_out, n_states) where:
A_d = M⁻¹·N(n×n state transition matrix)b_d = M⁻¹·F(n×1 input vector)c_out(1×n output extraction vector)n_states= num_nodes + num_vsources