use std::collections::HashMap;
use nalgebra::{DMatrix, DVector};
use num_complex::Complex64;
use sindr_devices::bjt::{self, BjtKind, BjtParams};
use sindr_devices::diode::{self, DiodeParams};
use sindr_devices::mosfet::{self, MosfetKind};
use crate::circuit::{Circuit, CircuitElement};
use crate::error::SimError;
use crate::node_map::NodeMap;
#[derive(Debug, Clone, Copy)]
pub enum FrequencySpacing {
Logarithmic,
Linear,
}
#[derive(Debug, Clone)]
pub struct AcConfig {
pub f_start: f64,
pub f_stop: f64,
pub num_points: usize,
pub spacing: FrequencySpacing,
pub source_id: String,
pub ac_magnitude: f64,
}
#[derive(Debug, Clone)]
pub struct AcPoint {
pub frequency: f64,
pub node_voltages: HashMap<String, Complex64>,
}
impl AcPoint {
pub fn gain_db(&self, node: &str, source_magnitude: f64) -> Option<f64> {
self.node_voltages.get(node).map(|v| {
let gain = v.norm() / source_magnitude;
20.0 * gain.log10()
})
}
pub fn phase_deg(&self, node: &str) -> Option<f64> {
self.node_voltages.get(node).map(|v| v.arg().to_degrees())
}
}
#[derive(Debug, Clone)]
pub struct AcResult {
pub points: Vec<AcPoint>,
pub config: AcConfig,
}
impl AcResult {
pub fn gain_curve(&self, node: &str) -> Vec<(f64, f64)> {
self.points
.iter()
.filter_map(|p| {
p.gain_db(node, self.config.ac_magnitude)
.map(|g| (p.frequency, g))
})
.collect()
}
pub fn phase_curve(&self, node: &str) -> Vec<(f64, f64)> {
self.points
.iter()
.filter_map(|p| p.phase_deg(node).map(|ph| (p.frequency, ph)))
.collect()
}
}
#[allow(dead_code)]
struct ComplexMnaSystem {
a: DMatrix<Complex64>,
b: DVector<Complex64>,
num_nodes: usize,
num_vsources: usize,
}
impl ComplexMnaSystem {
fn new(num_nodes: usize, num_vsources: usize) -> Self {
let size = num_nodes + num_vsources;
Self {
a: DMatrix::from_element(size, size, Complex64::new(0.0, 0.0)),
b: DVector::from_element(size, Complex64::new(0.0, 0.0)),
num_nodes,
num_vsources,
}
}
fn solve(&self) -> Result<DVector<Complex64>, SimError> {
let lu = self.a.clone().lu();
let solution = lu.solve(&self.b).ok_or(SimError::SingularMatrix)?;
for i in 0..solution.len() {
if solution[i].re.is_nan()
|| solution[i].re.is_infinite()
|| solution[i].im.is_nan()
|| solution[i].im.is_infinite()
{
return Err(SimError::InvalidSolution);
}
}
Ok(solution)
}
}
struct SmallSignalModel {
conductances: Vec<(Option<usize>, Option<usize>, f64)>,
}
fn linearize_at_dc(
circuit: &Circuit,
node_map: &NodeMap,
dc_solution: &nalgebra::DVector<f64>,
) -> Vec<SmallSignalModel> {
let mut models = Vec::new();
for component in &circuit.components {
match component {
CircuitElement::Diode { nodes, .. } => {
let p = node_map.index(&nodes[0]);
let q = node_map.index(&nodes[1]);
let vp = p.map_or(0.0, |i| dc_solution[i]);
let vq = q.map_or(0.0, |i| dc_solution[i]);
let v_d = vp - vq;
let params = DiodeParams::silicon();
let (g_d, _) = diode::diode_companion(v_d, ¶ms);
models.push(SmallSignalModel {
conductances: vec![(p, p, g_d), (q, q, g_d), (p, q, -g_d), (q, p, -g_d)],
});
}
CircuitElement::Led { nodes, color, .. } => {
let p = node_map.index(&nodes[0]);
let q = node_map.index(&nodes[1]);
let vp = p.map_or(0.0, |i| dc_solution[i]);
let vq = q.map_or(0.0, |i| dc_solution[i]);
let v_d = vp - vq;
let params = DiodeParams::for_led_color(color);
let (g_d, _) = diode::diode_companion(v_d, ¶ms);
models.push(SmallSignalModel {
conductances: vec![(p, p, g_d), (q, q, g_d), (p, q, -g_d), (q, p, -g_d)],
});
}
CircuitElement::Bjt {
nodes, kind, bf, ..
} => {
let b = node_map.index(&nodes[0]);
let c = node_map.index(&nodes[1]);
let e = node_map.index(&nodes[2]);
let vb = b.map_or(0.0, |i| dc_solution[i]);
let vc = c.map_or(0.0, |i| dc_solution[i]);
let ve = e.map_or(0.0, |i| dc_solution[i]);
let sign = match kind {
BjtKind::Npn => 1.0,
BjtKind::Pnp => -1.0,
};
let vbe_eff = sign * (vb - ve);
let vbc_eff = sign * (vb - vc);
let params = BjtParams::new(*bf);
let comp = bjt::bjt_companion(vbe_eff, vbc_eff, ¶ms);
let gm = comp.g_be;
let gpi = comp.g_be / params.bf;
let go = comp.g_bc;
let conds = vec![
(b, b, gpi),
(e, e, gpi),
(b, e, -gpi),
(e, b, -gpi),
(c, b, gm),
(c, e, -gm),
(e, b, -gm),
(e, e, gm),
(c, c, go),
(e, e, go),
(c, e, -go),
(e, c, -go),
];
models.push(SmallSignalModel {
conductances: conds,
});
}
CircuitElement::Mosfet {
nodes,
kind,
params,
..
} => {
let g = node_map.index(&nodes[0]);
let d = node_map.index(&nodes[1]);
let s = node_map.index(&nodes[2]);
let vg = g.map_or(0.0, |i| dc_solution[i]);
let vd = d.map_or(0.0, |i| dc_solution[i]);
let vs = s.map_or(0.0, |i| dc_solution[i]);
let (vgs, vds, vbs) = match kind {
MosfetKind::Nmos => (vg - vs, vd - vs, -vs), MosfetKind::Pmos => (vs - vg, vs - vd, vs),
};
let comp = mosfet::mosfet_companion(vgs, vds, vbs, params);
let mut conds = vec![
(d, g, comp.gm),
(d, s, -comp.gm),
(s, g, -comp.gm),
(s, s, comp.gm),
(d, d, comp.gds),
(s, s, comp.gds),
(d, s, -comp.gds),
(s, d, -comp.gds),
];
if comp.gmb.abs() > 1e-15 {
conds.push((d, s, -comp.gmb));
conds.push((d, d, 0.0)); conds.push((s, s, comp.gmb));
}
models.push(SmallSignalModel {
conductances: conds,
});
}
_ => {}
}
}
models
}
fn generate_frequencies(config: &AcConfig) -> Vec<f64> {
let mut freqs = Vec::with_capacity(config.num_points);
match config.spacing {
FrequencySpacing::Logarithmic => {
let log_start = config.f_start.log10();
let log_stop = config.f_stop.log10();
let step = (log_stop - log_start) / (config.num_points - 1) as f64;
for i in 0..config.num_points {
freqs.push(10.0_f64.powf(log_start + step * i as f64));
}
}
FrequencySpacing::Linear => {
let step = (config.f_stop - config.f_start) / (config.num_points - 1) as f64;
for i in 0..config.num_points {
freqs.push(config.f_start + step * i as f64);
}
}
}
freqs
}
fn stamp_complex_conductance(
system: &mut ComplexMnaSystem,
row: Option<usize>,
col: Option<usize>,
val: Complex64,
) {
if let (Some(r), Some(c)) = (row, col) {
system.a[(r, c)] += val;
}
}
pub fn solve_ac(circuit: &Circuit, config: &AcConfig) -> Result<AcResult, SimError> {
let dc_result = crate::solve_circuit(circuit)?;
let node_map = NodeMap::from_circuit(circuit);
let num_nodes = node_map.num_nodes();
let num_vsources = circuit.count_voltage_sources();
let mut dc_solution = nalgebra::DVector::zeros(num_nodes + num_vsources);
for i in 0..num_nodes {
if let Some(name) = node_map.node_name(i) {
if let Some(&v) = dc_result.node_voltages.get(name) {
dc_solution[i] = v;
}
}
}
let ss_models = linearize_at_dc(circuit, &node_map, &dc_solution);
let frequencies = generate_frequencies(config);
let mut points = Vec::with_capacity(frequencies.len());
for &freq in &frequencies {
let omega = 2.0 * std::f64::consts::PI * freq;
let j_omega = Complex64::new(0.0, omega);
let mut system = ComplexMnaSystem::new(num_nodes, num_vsources);
let mut vsource_index: usize = 0;
for component in &circuit.components {
match component {
CircuitElement::Resistor {
nodes, resistance, ..
} => {
let g = Complex64::new(1.0 / resistance, 0.0);
let p = node_map.index(&nodes[0]);
let q = node_map.index(&nodes[1]);
stamp_complex_conductance(&mut system, p, p, g);
stamp_complex_conductance(&mut system, q, q, g);
stamp_complex_conductance(&mut system, p, q, -g);
stamp_complex_conductance(&mut system, q, p, -g);
}
CircuitElement::Capacitor {
nodes, capacitance, ..
} => {
let y = j_omega * *capacitance; let p = node_map.index(&nodes[0]);
let q = node_map.index(&nodes[1]);
stamp_complex_conductance(&mut system, p, p, y);
stamp_complex_conductance(&mut system, q, q, y);
stamp_complex_conductance(&mut system, p, q, -y);
stamp_complex_conductance(&mut system, q, p, -y);
}
CircuitElement::Inductor {
nodes, inductance, ..
} => {
let y = if omega.abs() > 1e-15 {
Complex64::new(1.0, 0.0) / (j_omega * *inductance)
} else {
Complex64::new(1e12, 0.0)
};
let p = node_map.index(&nodes[0]);
let q = node_map.index(&nodes[1]);
stamp_complex_conductance(&mut system, p, p, y);
stamp_complex_conductance(&mut system, q, q, y);
stamp_complex_conductance(&mut system, p, q, -y);
stamp_complex_conductance(&mut system, q, p, -y);
}
CircuitElement::VoltageSource { id, nodes, .. } => {
let branch = num_nodes + vsource_index;
let p = node_map.index(&nodes[0]);
let q = node_map.index(&nodes[1]);
let one = Complex64::new(1.0, 0.0);
if let Some(pi) = p {
system.a[(pi, branch)] += one;
system.a[(branch, pi)] += one;
}
if let Some(qi) = q {
system.a[(qi, branch)] -= one;
system.a[(branch, qi)] -= one;
}
if id == &config.source_id {
system.b[branch] = Complex64::new(config.ac_magnitude, 0.0);
}
vsource_index += 1;
}
CircuitElement::CurrentSource { nodes, .. } => {
let _ = nodes;
}
CircuitElement::Switch { nodes, closed, .. } => {
let r = if *closed { 0.01 } else { 1e9 };
let g = Complex64::new(1.0 / r, 0.0);
let p = node_map.index(&nodes[0]);
let q = node_map.index(&nodes[1]);
stamp_complex_conductance(&mut system, p, p, g);
stamp_complex_conductance(&mut system, q, q, g);
stamp_complex_conductance(&mut system, p, q, -g);
stamp_complex_conductance(&mut system, q, p, -g);
}
CircuitElement::Pushbutton { nodes, closed, .. } => {
let r = if *closed { 0.01 } else { 1e9 };
let g = Complex64::new(1.0 / r, 0.0);
let p = node_map.index(&nodes[0]);
let q = node_map.index(&nodes[1]);
stamp_complex_conductance(&mut system, p, p, g);
stamp_complex_conductance(&mut system, q, q, g);
stamp_complex_conductance(&mut system, p, q, -g);
stamp_complex_conductance(&mut system, q, p, -g);
}
CircuitElement::Photoresistor { nodes, light_level, .. } => {
use crate::stamp::ldr_resistance;
let r = ldr_resistance(*light_level);
let g = Complex64::new(1.0 / r, 0.0);
let p = node_map.index(&nodes[0]);
let q = node_map.index(&nodes[1]);
stamp_complex_conductance(&mut system, p, p, g);
stamp_complex_conductance(&mut system, q, q, g);
stamp_complex_conductance(&mut system, p, q, -g);
stamp_complex_conductance(&mut system, q, p, -g);
}
CircuitElement::Potentiometer { nodes, resistance, position, .. } => {
let pos = position.clamp(0.001, 0.999);
let r_top = resistance * pos;
let r_bot = resistance * (1.0 - pos);
let g_top = Complex64::new(1.0 / r_top, 0.0);
let g_bot = Complex64::new(1.0 / r_bot, 0.0);
let p = node_map.index(&nodes[0]);
let w = node_map.index(&nodes[1]);
let q = node_map.index(&nodes[2]);
stamp_complex_conductance(&mut system, p, p, g_top);
stamp_complex_conductance(&mut system, w, w, g_top);
stamp_complex_conductance(&mut system, p, w, -g_top);
stamp_complex_conductance(&mut system, w, p, -g_top);
stamp_complex_conductance(&mut system, w, w, g_bot);
stamp_complex_conductance(&mut system, q, q, g_bot);
stamp_complex_conductance(&mut system, w, q, -g_bot);
stamp_complex_conductance(&mut system, q, w, -g_bot);
}
CircuitElement::Relay { nodes, coil_resistance, .. } => {
let g = Complex64::new(1.0 / coil_resistance, 0.0);
let p = node_map.index(&nodes[0]);
let q = node_map.index(&nodes[1]);
stamp_complex_conductance(&mut system, p, p, g);
stamp_complex_conductance(&mut system, q, q, g);
stamp_complex_conductance(&mut system, p, q, -g);
stamp_complex_conductance(&mut system, q, p, -g);
let g_open = Complex64::new(1.0 / 1e9, 0.0);
let c1 = node_map.index(&nodes[2]);
let c2 = node_map.index(&nodes[3]);
stamp_complex_conductance(&mut system, c1, c1, g_open);
stamp_complex_conductance(&mut system, c2, c2, g_open);
stamp_complex_conductance(&mut system, c1, c2, -g_open);
stamp_complex_conductance(&mut system, c2, c1, -g_open);
}
CircuitElement::Thermistor { nodes, temperature, .. } => {
let params = sindr_devices::thermistor::ThermistorParams::default();
let r = sindr_devices::thermistor::thermistor_resistance(*temperature, ¶ms);
let g = Complex64::new(1.0 / r, 0.0);
let p = node_map.index(&nodes[0]);
let q = node_map.index(&nodes[1]);
stamp_complex_conductance(&mut system, p, p, g);
stamp_complex_conductance(&mut system, q, q, g);
stamp_complex_conductance(&mut system, p, q, -g);
stamp_complex_conductance(&mut system, q, p, -g);
}
CircuitElement::Diode { .. }
| CircuitElement::Led { .. }
| CircuitElement::Bjt { .. }
| CircuitElement::Mosfet { .. }
| CircuitElement::ZenerDiode { .. }
| CircuitElement::SchottkyDiode { .. }
| CircuitElement::Photodiode { .. }
| CircuitElement::Varactor { .. }
| CircuitElement::Igbt { .. }
| CircuitElement::Jfet { .. } => {}
CircuitElement::Transformer { nodes, l1, l2, .. } => {
let y1 = if omega.abs() > 1e-15 {
Complex64::new(1.0, 0.0) / (j_omega * *l1)
} else {
Complex64::new(1e12, 0.0)
};
let p1 = node_map.index(&nodes[0]);
let q1 = node_map.index(&nodes[1]);
stamp_complex_conductance(&mut system, p1, p1, y1);
stamp_complex_conductance(&mut system, q1, q1, y1);
stamp_complex_conductance(&mut system, p1, q1, -y1);
stamp_complex_conductance(&mut system, q1, p1, -y1);
let y2 = if omega.abs() > 1e-15 {
Complex64::new(1.0, 0.0) / (j_omega * *l2)
} else {
Complex64::new(1e12, 0.0)
};
let p2 = node_map.index(&nodes[2]);
let q2 = node_map.index(&nodes[3]);
stamp_complex_conductance(&mut system, p2, p2, y2);
stamp_complex_conductance(&mut system, q2, q2, y2);
stamp_complex_conductance(&mut system, p2, q2, -y2);
stamp_complex_conductance(&mut system, q2, p2, -y2);
}
CircuitElement::OpAmp { nodes, .. }
| CircuitElement::Comparator { nodes, .. } => {
let branch = num_nodes + vsource_index;
let p = node_map.index(&nodes[2]); let gnd = node_map.index(&circuit.ground_node);
let cp = node_map.index(&nodes[0]); let cq = node_map.index(&nodes[1]); let one = Complex64::new(1.0, 0.0);
let gain = Complex64::new(1e5, 0.0);
if let Some(pi) = p {
system.a[(pi, branch)] += one;
}
if let Some(qi) = gnd {
system.a[(qi, branch)] -= one;
}
if let Some(pi) = p {
system.a[(branch, pi)] += one;
}
if let Some(qi) = gnd {
system.a[(branch, qi)] -= one;
}
if let Some(cpi) = cp {
system.a[(branch, cpi)] -= gain;
}
if let Some(cqi) = cq {
system.a[(branch, cqi)] += gain;
}
vsource_index += 1;
}
CircuitElement::Vcvs {
nodes, control_nodes, gain, ..
} => {
let branch = num_nodes + vsource_index;
let p = node_map.index(&nodes[0]);
let q = node_map.index(&nodes[1]);
let cp = node_map.index(&control_nodes[0]);
let cq = node_map.index(&control_nodes[1]);
let one = Complex64::new(1.0, 0.0);
let g = Complex64::new(*gain, 0.0);
if let Some(pi) = p { system.a[(pi, branch)] += one; }
if let Some(qi) = q { system.a[(qi, branch)] -= one; }
if let Some(pi) = p { system.a[(branch, pi)] += one; }
if let Some(qi) = q { system.a[(branch, qi)] -= one; }
if let Some(cpi) = cp { system.a[(branch, cpi)] -= g; }
if let Some(cqi) = cq { system.a[(branch, cqi)] += g; }
vsource_index += 1;
}
CircuitElement::Vccs {
nodes, control_nodes, gm, ..
} => {
let from = node_map.index(&nodes[0]);
let to = node_map.index(&nodes[1]);
let cp = node_map.index(&control_nodes[0]);
let cq = node_map.index(&control_nodes[1]);
let g = Complex64::new(*gm, 0.0);
if let (Some(ti), Some(cpi)) = (to, cp) { system.a[(ti, cpi)] += g; }
if let (Some(ti), Some(cqi)) = (to, cq) { system.a[(ti, cqi)] -= g; }
if let (Some(fi), Some(cpi)) = (from, cp) { system.a[(fi, cpi)] -= g; }
if let (Some(fi), Some(cqi)) = (from, cq) { system.a[(fi, cqi)] += g; }
}
CircuitElement::Ccvs {
nodes, control_source, rm, ..
} => {
let branch = num_nodes + vsource_index;
let p = node_map.index(&nodes[0]);
let q = node_map.index(&nodes[1]);
let one = Complex64::new(1.0, 0.0);
let r = Complex64::new(*rm, 0.0);
if let Some(pi) = p { system.a[(pi, branch)] += one; }
if let Some(qi) = q { system.a[(qi, branch)] -= one; }
if let Some(pi) = p { system.a[(branch, pi)] += one; }
if let Some(qi) = q { system.a[(branch, qi)] -= one; }
if let Some(cb) = circuit.vsource_branch_index(control_source) {
system.a[(branch, num_nodes + cb)] -= r;
}
vsource_index += 1;
}
CircuitElement::Cccs {
nodes, control_source, alpha, ..
} => {
let from = node_map.index(&nodes[0]);
let to = node_map.index(&nodes[1]);
let a = Complex64::new(*alpha, 0.0);
if let Some(cb) = circuit.vsource_branch_index(control_source) {
if let Some(ti) = to { system.a[(ti, num_nodes + cb)] += a; }
if let Some(fi) = from { system.a[(fi, num_nodes + cb)] -= a; }
}
}
CircuitElement::Fuse { nodes, blown, .. } => {
let r = if *blown { 1e9_f64 } else { 0.001_f64 };
let g = Complex64::new(1.0 / r, 0.0);
let p = node_map.index(&nodes[0]);
let q = node_map.index(&nodes[1]);
stamp_complex_conductance(&mut system, p, p, g);
stamp_complex_conductance(&mut system, q, q, g);
stamp_complex_conductance(&mut system, p, q, -g);
stamp_complex_conductance(&mut system, q, p, -g);
}
CircuitElement::VoltageRegulator { nodes, .. } => {
let branch = num_nodes + vsource_index;
let p = node_map.index(&nodes[1]); let q = node_map.index(&nodes[2]); let one = Complex64::new(1.0, 0.0);
if let Some(pi) = p {
system.a[(pi, branch)] += one;
system.a[(branch, pi)] += one;
}
if let Some(qi) = q {
system.a[(qi, branch)] -= one;
system.a[(branch, qi)] -= one;
}
vsource_index += 1;
}
}
}
for model in &ss_models {
for &(row, col, val) in &model.conductances {
stamp_complex_conductance(&mut system, row, col, Complex64::new(val, 0.0));
}
}
let solution = system.solve()?;
let mut node_voltages = HashMap::new();
for i in 0..num_nodes {
if let Some(name) = node_map.node_name(i) {
node_voltages.insert(name.to_string(), solution[i]);
}
}
points.push(AcPoint {
frequency: freq,
node_voltages,
});
}
Ok(AcResult {
points,
config: config.clone(),
})
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
#[test]
fn rc_lowpass_corner_frequency() {
let circuit = Circuit {
ground_node: "0".into(),
components: vec![
CircuitElement::VoltageSource {
id: "V1".into(),
nodes: ["n1".into(), "0".into()],
voltage: 1.0,
waveform: None,
},
CircuitElement::Resistor {
id: "R1".into(),
nodes: ["n1".into(), "n2".into()],
resistance: 1000.0,
},
CircuitElement::Capacitor {
id: "C1".into(),
nodes: ["n2".into(), "0".into()],
capacitance: 1e-6,
},
],
};
let config = AcConfig {
f_start: 1.0,
f_stop: 100_000.0,
num_points: 100,
spacing: FrequencySpacing::Logarithmic,
source_id: "V1".into(),
ac_magnitude: 1.0,
};
let result = solve_ac(&circuit, &config).unwrap();
let gain_curve = result.gain_curve("n2");
let phase_curve = result.phase_curve("n2");
assert!(!gain_curve.is_empty());
assert!(!phase_curve.is_empty());
let f_corner = 1.0 / (2.0 * std::f64::consts::PI * 1000.0 * 1e-6);
let corner_point = gain_curve
.iter()
.min_by(|a, b| {
(a.0 - f_corner)
.abs()
.partial_cmp(&(b.0 - f_corner).abs())
.unwrap()
})
.unwrap();
assert_abs_diff_eq!(corner_point.1, -3.0, epsilon = 1.0);
let corner_phase = phase_curve
.iter()
.min_by(|a, b| {
(a.0 - f_corner)
.abs()
.partial_cmp(&(b.0 - f_corner).abs())
.unwrap()
})
.unwrap();
assert_abs_diff_eq!(corner_phase.1, -45.0, epsilon = 5.0);
}
#[test]
fn rc_lowpass_passband() {
let circuit = Circuit {
ground_node: "0".into(),
components: vec![
CircuitElement::VoltageSource {
id: "V1".into(),
nodes: ["n1".into(), "0".into()],
voltage: 1.0,
waveform: None,
},
CircuitElement::Resistor {
id: "R1".into(),
nodes: ["n1".into(), "n2".into()],
resistance: 1000.0,
},
CircuitElement::Capacitor {
id: "C1".into(),
nodes: ["n2".into(), "0".into()],
capacitance: 1e-6,
},
],
};
let config = AcConfig {
f_start: 1.0,
f_stop: 10.0,
num_points: 5,
spacing: FrequencySpacing::Linear,
source_id: "V1".into(),
ac_magnitude: 1.0,
};
let result = solve_ac(&circuit, &config).unwrap();
let gain_curve = result.gain_curve("n2");
assert_abs_diff_eq!(gain_curve[0].1, 0.0, epsilon = 0.1);
}
#[test]
fn rc_lowpass_stopband_rolloff() {
let circuit = Circuit {
ground_node: "0".into(),
components: vec![
CircuitElement::VoltageSource {
id: "V1".into(),
nodes: ["n1".into(), "0".into()],
voltage: 1.0,
waveform: None,
},
CircuitElement::Resistor {
id: "R1".into(),
nodes: ["n1".into(), "n2".into()],
resistance: 1000.0,
},
CircuitElement::Capacitor {
id: "C1".into(),
nodes: ["n2".into(), "0".into()],
capacitance: 1e-6,
},
],
};
let config = AcConfig {
f_start: 10_000.0,
f_stop: 100_000.0,
num_points: 3,
spacing: FrequencySpacing::Logarithmic,
source_id: "V1".into(),
ac_magnitude: 1.0,
};
let result = solve_ac(&circuit, &config).unwrap();
let gain_curve = result.gain_curve("n2");
let gain_10k = gain_curve.first().unwrap().1;
let gain_100k = gain_curve.last().unwrap().1;
let rolloff = gain_10k - gain_100k;
assert_abs_diff_eq!(rolloff, 20.0, epsilon = 2.0);
}
#[test]
fn frequency_generation_logarithmic() {
let config = AcConfig {
f_start: 1.0,
f_stop: 1000.0,
num_points: 4,
spacing: FrequencySpacing::Logarithmic,
source_id: "V1".into(),
ac_magnitude: 1.0,
};
let freqs = generate_frequencies(&config);
assert_eq!(freqs.len(), 4);
assert_abs_diff_eq!(freqs[0], 1.0, epsilon = 1e-10);
assert_abs_diff_eq!(freqs[1], 10.0, epsilon = 1e-10);
assert_abs_diff_eq!(freqs[2], 100.0, epsilon = 1e-8);
assert_abs_diff_eq!(freqs[3], 1000.0, epsilon = 1e-6);
}
#[test]
fn frequency_generation_linear() {
let config = AcConfig {
f_start: 100.0,
f_stop: 400.0,
num_points: 4,
spacing: FrequencySpacing::Linear,
source_id: "V1".into(),
ac_magnitude: 1.0,
};
let freqs = generate_frequencies(&config);
assert_eq!(freqs.len(), 4);
assert_abs_diff_eq!(freqs[0], 100.0, epsilon = 1e-10);
assert_abs_diff_eq!(freqs[1], 200.0, epsilon = 1e-10);
assert_abs_diff_eq!(freqs[2], 300.0, epsilon = 1e-10);
assert_abs_diff_eq!(freqs[3], 400.0, epsilon = 1e-10);
}
}