use nalgebra::DVector;
use sindr_devices::bjt::{self, BjtKind, BjtParams};
use sindr_devices::diode::{self, temperature_scale_is, DiodeParams};
use sindr_devices::jfet::JfetKind;
use sindr_devices::led::led_params_from_str;
use sindr_devices::mosfet::{self, MosfetKind, MosfetParams};
use sindr_devices::photoresistor::{ldr_resistance as rs_ldr_resistance, PhotoresistorParams};
use sindr_devices::zener::{zener_companion as rs_zener_companion, ZenerParams};
use crate::circuit::{Circuit, CircuitElement};
use crate::error::SimError;
use crate::mna::MnaSystem;
use crate::node_map::NodeMap;
pub const SWITCH_R_CLOSED: f64 = 0.01;
pub const SWITCH_R_OPEN: f64 = 1e9;
pub fn ldr_resistance(light_level: f64) -> f64 {
rs_ldr_resistance(light_level, &PhotoresistorParams::default())
}
pub fn stamp_circuit(
circuit: &Circuit,
system: &mut MnaSystem,
node_map: &NodeMap,
v_prev: Option<&DVector<f64>>,
) -> Result<(), SimError> {
let num_nodes = node_map.num_nodes();
let mut vsource_index: usize = 0;
for component in &circuit.components {
match component {
CircuitElement::Resistor {
id,
nodes,
resistance,
} => {
if *resistance <= 0.0 {
return Err(SimError::InvalidResistance(id.clone()));
}
stamp_resistor(system, node_map, nodes, *resistance);
}
CircuitElement::VoltageSource { nodes, voltage, .. } => {
let branch = num_nodes + vsource_index;
stamp_voltage_source(system, node_map, nodes, *voltage, branch);
vsource_index += 1;
}
CircuitElement::CurrentSource { nodes, current, .. } => {
stamp_current_source(system, node_map, nodes, *current);
}
CircuitElement::Switch { nodes, closed, .. } => {
let resistance = if *closed {
SWITCH_R_CLOSED
} else {
SWITCH_R_OPEN
};
stamp_resistor(system, node_map, nodes, resistance);
}
CircuitElement::Capacitor { .. } => {}
CircuitElement::Inductor { .. } => {}
CircuitElement::Diode {
nodes, temperature, ..
} => {
if let Some(vp) = v_prev {
let mut params = DiodeParams::silicon();
if (*temperature - 300.15).abs() > 1e-6 {
params.is =
temperature_scale_is(params.is, *temperature, 300.15, 1.11, 2.0);
}
stamp_diode_companion(system, node_map, nodes, vp, ¶ms);
}
}
CircuitElement::Led {
nodes,
color,
temperature,
..
} => {
if let Some(vp) = v_prev {
let mut params = led_params_from_str(color);
if (*temperature - 300.15).abs() > 1e-6 {
params.is =
temperature_scale_is(params.is, *temperature, 300.15, 1.11, 2.0);
}
stamp_diode_companion(system, node_map, nodes, vp, ¶ms);
}
}
CircuitElement::Bjt {
nodes,
kind,
bf,
temperature,
..
} => {
if let Some(vp) = v_prev {
let mut params = BjtParams::new(*bf);
if (*temperature - 300.15).abs() > 1e-6 {
params.is =
temperature_scale_is(params.is, *temperature, 300.15, 1.11, 3.0);
}
stamp_bjt_companion(system, node_map, nodes, vp, ¶ms, *kind);
}
}
CircuitElement::Mosfet {
nodes,
kind,
params,
..
} => {
if let Some(vp) = v_prev {
stamp_mosfet_companion(system, node_map, nodes, vp, params, *kind);
}
}
CircuitElement::Vcvs {
nodes,
control_nodes,
gain,
..
} => {
let branch = num_nodes + vsource_index;
stamp_vcvs(system, node_map, nodes, control_nodes, *gain, branch);
vsource_index += 1;
}
CircuitElement::Vccs {
nodes,
control_nodes,
gm,
..
} => {
stamp_vccs(system, node_map, nodes, control_nodes, *gm);
}
CircuitElement::Ccvs {
nodes,
control_source,
rm,
..
} => {
let branch = num_nodes + vsource_index;
let ctrl_branch = circuit.vsource_branch_index(control_source);
if let Some(cb) = ctrl_branch {
let ctrl_branch_idx = num_nodes + cb;
stamp_ccvs(system, node_map, nodes, *rm, branch, ctrl_branch_idx);
}
vsource_index += 1;
}
CircuitElement::Cccs {
nodes,
control_source,
alpha,
..
} => {
let ctrl_branch = circuit.vsource_branch_index(control_source);
if let Some(cb) = ctrl_branch {
let ctrl_branch_idx = num_nodes + cb;
stamp_cccs(system, node_map, nodes, *alpha, ctrl_branch_idx);
}
}
CircuitElement::Pushbutton { nodes, closed, .. } => {
let resistance = if *closed {
SWITCH_R_CLOSED
} else {
SWITCH_R_OPEN
};
stamp_resistor(system, node_map, nodes, resistance);
}
CircuitElement::Fuse { nodes, blown, .. } => {
let resistance = if *blown { SWITCH_R_OPEN } else { 0.001 };
stamp_resistor(system, node_map, nodes, resistance);
}
CircuitElement::Photoresistor {
nodes, light_level, ..
} => {
let resistance = ldr_resistance(*light_level);
stamp_resistor(system, node_map, nodes, resistance);
}
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 top_wiper: [String; 2] = [nodes[0].clone(), nodes[1].clone()];
let wiper_bot: [String; 2] = [nodes[1].clone(), nodes[2].clone()];
stamp_resistor(system, node_map, &top_wiper, r_top);
stamp_resistor(system, node_map, &wiper_bot, r_bot);
}
CircuitElement::Relay {
nodes,
coil_resistance,
pickup_voltage,
inductance,
..
} => {
let coil_nodes: [String; 2] = [nodes[0].clone(), nodes[1].clone()];
stamp_resistor(system, node_map, &coil_nodes, *coil_resistance);
let _ = inductance; let contact_closed = if let Some(vp) = v_prev {
let vc_pos = node_map.index(&nodes[0]).map_or(0.0, |i| vp[i]);
let vc_neg = node_map.index(&nodes[1]).map_or(0.0, |i| vp[i]);
(vc_pos - vc_neg).abs() >= *pickup_voltage
} else {
false
};
let contact_r = if contact_closed {
SWITCH_R_CLOSED
} else {
SWITCH_R_OPEN
};
let contact_nodes: [String; 2] = [nodes[2].clone(), nodes[3].clone()];
stamp_resistor(system, node_map, &contact_nodes, contact_r);
}
CircuitElement::ZenerDiode {
nodes,
vz,
temperature,
..
} => {
if let Some(vp) = v_prev {
let mut zparams = ZenerParams::new(*vz);
if (*temperature - 300.15).abs() > 1e-6 {
zparams.is =
temperature_scale_is(zparams.is, *temperature, 300.15, 1.11, 2.0);
}
stamp_zener_companion(system, node_map, nodes, vp, &zparams);
}
}
CircuitElement::OpAmp { nodes, .. } => {
let branch = num_nodes + vsource_index;
let out_nodes: [String; 2] = [nodes[2].clone(), circuit.ground_node.clone()];
let ctrl_nodes: [String; 2] = [nodes[0].clone(), nodes[1].clone()];
stamp_vcvs(system, node_map, &out_nodes, &ctrl_nodes, 1e5, branch);
vsource_index += 1;
}
CircuitElement::Comparator { nodes, .. } => {
let branch = num_nodes + vsource_index;
let out_nodes: [String; 2] = [nodes[2].clone(), circuit.ground_node.clone()];
let ctrl_nodes: [String; 2] = [nodes[0].clone(), nodes[1].clone()];
stamp_vcvs(system, node_map, &out_nodes, &ctrl_nodes, 1e5, branch);
vsource_index += 1;
}
CircuitElement::SchottkyDiode {
nodes, temperature, ..
} => {
if let Some(vp) = v_prev {
let schottky = sindr_devices::schottky::SchottkyParams::default();
let is_scaled = if (*temperature - 300.15).abs() > 1e-6 {
temperature_scale_is(schottky.is, *temperature, 300.15, 1.11, 2.0)
} else {
schottky.is
};
stamp_diode_companion(
system,
node_map,
nodes,
vp,
&DiodeParams {
is: is_scaled,
n: schottky.n,
rs: 0.0,
temperature: *temperature,
},
);
}
}
CircuitElement::Thermistor {
nodes, temperature, ..
} => {
let params = sindr_devices::thermistor::ThermistorParams::default();
let r = sindr_devices::thermistor::thermistor_resistance(*temperature, ¶ms);
stamp_resistor(system, node_map, nodes, r);
}
CircuitElement::Varactor { nodes, .. } => {
stamp_varactor_dc(system, node_map, nodes);
}
CircuitElement::Igbt { nodes, params, .. } => {
if let Some(vp) = v_prev {
stamp_igbt_companion(system, node_map, nodes, vp, params);
}
}
CircuitElement::Jfet {
nodes,
kind,
idss,
vp,
..
} => {
if let Some(vp_prev) = v_prev {
stamp_jfet_companion(system, node_map, nodes, *kind, *idss, *vp, vp_prev);
}
}
CircuitElement::Transformer { nodes, .. } => {
stamp_resistor(
system,
node_map,
&[nodes[0].clone(), nodes[1].clone()],
1e-9,
);
stamp_resistor(
system,
node_map,
&[nodes[2].clone(), nodes[3].clone()],
1e-9,
);
}
CircuitElement::VoltageRegulator { nodes, voltage, .. } => {
let branch = num_nodes + vsource_index;
let vs_nodes: [String; 2] = [nodes[1].clone(), nodes[2].clone()];
stamp_voltage_source(system, node_map, &vs_nodes, *voltage, branch);
vsource_index += 1;
}
CircuitElement::Photodiode {
nodes,
irradiance,
temperature,
..
} => {
if let Some(vp) = v_prev {
let _ = temperature; let params = sindr_devices::photodiode::PhotodiodeParams::default();
let v_a = node_map.index(&nodes[0]).map_or(0.0, |i| vp[i]);
let v_c = node_map.index(&nodes[1]).map_or(0.0, |i| vp[i]);
let v_d = v_a - v_c;
let (g_d, i_eq) =
sindr_devices::photodiode::photodiode_companion(v_d, *irradiance, ¶ms);
let p = node_map.index(&nodes[0]);
let q = node_map.index(&nodes[1]);
if let Some(pi) = p {
system.a[(pi, pi)] += g_d;
}
if let Some(qi) = q {
system.a[(qi, qi)] += g_d;
}
if let (Some(pi), Some(qi)) = (p, q) {
system.a[(pi, qi)] -= g_d;
system.a[(qi, pi)] -= g_d;
}
if let Some(pi) = p {
system.b[pi] -= i_eq;
}
if let Some(qi) = q {
system.b[qi] += i_eq;
}
}
}
}
}
Ok(())
}
pub fn stamp_resistor(
system: &mut MnaSystem,
node_map: &NodeMap,
nodes: &[String; 2],
resistance: f64,
) {
let g = 1.0 / resistance;
let p = node_map.index(&nodes[0]);
let q = node_map.index(&nodes[1]);
if let Some(pi) = p {
system.a[(pi, pi)] += g;
}
if let Some(qi) = q {
system.a[(qi, qi)] += g;
}
if let (Some(pi), Some(qi)) = (p, q) {
system.a[(pi, qi)] -= g;
system.a[(qi, pi)] -= g;
}
}
pub fn stamp_voltage_source(
system: &mut MnaSystem,
node_map: &NodeMap,
nodes: &[String; 2],
voltage: f64,
branch: usize,
) {
let p = node_map.index(&nodes[0]);
let q = node_map.index(&nodes[1]);
if let Some(pi) = p {
system.a[(pi, branch)] += 1.0;
}
if let Some(qi) = q {
system.a[(qi, branch)] -= 1.0;
}
if let Some(pi) = p {
system.a[(branch, pi)] += 1.0;
}
if let Some(qi) = q {
system.a[(branch, qi)] -= 1.0;
}
system.b[branch] = voltage;
}
pub fn stamp_current_source(
system: &mut MnaSystem,
node_map: &NodeMap,
nodes: &[String; 2],
current: f64,
) {
let from = node_map.index(&nodes[0]);
let to = node_map.index(&nodes[1]);
if let Some(ti) = to {
system.b[ti] += current;
}
if let Some(fi) = from {
system.b[fi] -= current;
}
}
pub(crate) fn stamp_diode_companion(
system: &mut MnaSystem,
node_map: &NodeMap,
nodes: &[String; 2],
v_prev: &DVector<f64>,
params: &DiodeParams,
) {
let p = node_map.index(&nodes[0]); let q = node_map.index(&nodes[1]);
let v_anode = p.map_or(0.0, |i| v_prev[i]);
let v_cathode = q.map_or(0.0, |i| v_prev[i]);
let v_d = v_anode - v_cathode;
let (g_d, i_eq) = diode::diode_companion(v_d, params);
if let Some(pi) = p {
system.a[(pi, pi)] += g_d;
}
if let Some(qi) = q {
system.a[(qi, qi)] += g_d;
}
if let (Some(pi), Some(qi)) = (p, q) {
system.a[(pi, qi)] -= g_d;
system.a[(qi, pi)] -= g_d;
}
if let Some(pi) = p {
system.b[pi] -= i_eq;
}
if let Some(qi) = q {
system.b[qi] += i_eq;
}
}
pub(crate) fn stamp_zener_companion(
system: &mut MnaSystem,
node_map: &NodeMap,
nodes: &[String; 2],
v_prev: &DVector<f64>,
params: &ZenerParams,
) {
let p = node_map.index(&nodes[0]); let q = node_map.index(&nodes[1]);
let v_anode = p.map_or(0.0, |i| v_prev[i]);
let v_cathode = q.map_or(0.0, |i| v_prev[i]);
let v_d = v_anode - v_cathode;
let (g_eq, i_eq) = rs_zener_companion(v_d, params);
if let Some(pi) = p {
system.a[(pi, pi)] += g_eq;
}
if let Some(qi) = q {
system.a[(qi, qi)] += g_eq;
}
if let (Some(pi), Some(qi)) = (p, q) {
system.a[(pi, qi)] -= g_eq;
system.a[(qi, pi)] -= g_eq;
}
if let Some(pi) = p {
system.b[pi] -= i_eq;
}
if let Some(qi) = q {
system.b[qi] += i_eq;
}
}
pub fn stamp_capacitor_companion(
system: &mut MnaSystem,
node_map: &NodeMap,
nodes: &[String; 2],
capacitance: f64,
dt: f64,
v_prev_across: f64,
) {
let g_eq = capacitance / dt;
let i_eq = g_eq * v_prev_across;
let p = node_map.index(&nodes[0]);
let q = node_map.index(&nodes[1]);
if let Some(pi) = p {
system.a[(pi, pi)] += g_eq;
}
if let Some(qi) = q {
system.a[(qi, qi)] += g_eq;
}
if let (Some(pi), Some(qi)) = (p, q) {
system.a[(pi, qi)] -= g_eq;
system.a[(qi, pi)] -= g_eq;
}
if let Some(pi) = p {
system.b[pi] += i_eq;
}
if let Some(qi) = q {
system.b[qi] -= i_eq;
}
}
pub fn stamp_inductor_companion(
system: &mut MnaSystem,
node_map: &NodeMap,
nodes: &[String; 2],
inductance: f64,
dt: f64,
i_prev: f64,
) {
let g_eq = dt / inductance;
let i_eq = i_prev;
let p = node_map.index(&nodes[0]);
let q = node_map.index(&nodes[1]);
if let Some(pi) = p {
system.a[(pi, pi)] += g_eq;
}
if let Some(qi) = q {
system.a[(qi, qi)] += g_eq;
}
if let (Some(pi), Some(qi)) = (p, q) {
system.a[(pi, qi)] -= g_eq;
system.a[(qi, pi)] -= g_eq;
}
if let Some(pi) = p {
system.b[pi] -= i_eq;
}
if let Some(qi) = q {
system.b[qi] += i_eq;
}
}
pub(crate) fn stamp_bjt_companion(
system: &mut MnaSystem,
node_map: &NodeMap,
nodes: &[String; 3],
v_prev: &DVector<f64>,
params: &BjtParams,
kind: BjtKind,
) {
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| v_prev[i]);
let vc = c.map_or(0.0, |i| v_prev[i]);
let ve = e.map_or(0.0, |i| v_prev[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 comp = bjt::bjt_companion(vbe_eff, vbc_eff, params);
let g_be = comp.g_be;
let g_bc = comp.g_bc;
let alpha_f = params.bf / (params.bf + 1.0);
let alpha_r = params.br / (params.br + 1.0);
let stamp_g = |system: &mut MnaSystem, row: Option<usize>, col: Option<usize>, val: f64| {
if let (Some(r), Some(c)) = (row, col) {
system.a[(r, c)] += val;
}
};
stamp_g(system, b, b, g_be / params.bf + g_bc / params.br);
stamp_g(system, b, c, -g_bc / params.br);
stamp_g(system, b, e, -g_be / params.bf);
stamp_g(system, c, b, g_be - g_bc / alpha_r); stamp_g(system, c, c, g_bc / alpha_r); stamp_g(system, c, e, -g_be);
stamp_g(system, e, b, -g_be / alpha_f + g_bc); stamp_g(system, e, c, -g_bc); stamp_g(system, e, e, g_be / alpha_f);
let ib_eq = comp.ib - (g_be / params.bf) * vbe_eff - (g_bc / params.br) * vbc_eff;
let ic_eq = comp.ic - g_be * vbe_eff + (g_bc / alpha_r) * vbc_eff;
let ie_eq = -(ib_eq + ic_eq);
let ib_rhs = sign * ib_eq;
let ic_rhs = sign * ic_eq;
let ie_rhs = sign * ie_eq;
if let Some(bi) = b {
system.b[bi] -= ib_rhs;
}
if let Some(ci) = c {
system.b[ci] -= ic_rhs;
}
if let Some(ei) = e {
system.b[ei] -= ie_rhs;
}
let g_ce = comp.g_ce;
if g_ce > 0.0 {
let c_idx = node_map.index(&nodes[1]); let e_idx = node_map.index(&nodes[2]); if let Some(ci) = c_idx {
system.a[(ci, ci)] += g_ce;
}
if let Some(ei) = e_idx {
system.a[(ei, ei)] += g_ce;
}
if let (Some(ci), Some(ei)) = (c_idx, e_idx) {
system.a[(ci, ei)] -= g_ce;
system.a[(ei, ci)] -= g_ce;
}
}
}
pub(crate) fn stamp_mosfet_companion(
system: &mut MnaSystem,
node_map: &NodeMap,
nodes: &[String; 3],
v_prev: &DVector<f64>,
params: &MosfetParams,
kind: MosfetKind,
) {
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| v_prev[i]);
let vd = d.map_or(0.0, |i| v_prev[i]);
let vs = s.map_or(0.0, |i| v_prev[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 stamp_g = |system: &mut MnaSystem, row: Option<usize>, col: Option<usize>, val: f64| {
if let (Some(r), Some(c)) = (row, col) {
system.a[(r, c)] += val;
}
};
stamp_g(system, d, g, comp.gm);
stamp_g(system, d, d, comp.gds);
stamp_g(system, d, s, -(comp.gm + comp.gds + comp.gmb));
stamp_g(system, s, g, -comp.gm);
stamp_g(system, s, d, -comp.gds);
stamp_g(system, s, s, comp.gm + comp.gds + comp.gmb);
let i_eq = comp.id - comp.gm * vgs - comp.gds * vds - comp.gmb * vbs;
let sign = match kind {
MosfetKind::Nmos => 1.0,
MosfetKind::Pmos => -1.0,
};
let i_rhs = sign * i_eq;
if let Some(di) = d {
system.b[di] -= i_rhs;
}
if let Some(si) = s {
system.b[si] += i_rhs;
}
}
pub(crate) fn stamp_varactor_dc(system: &mut MnaSystem, node_map: &NodeMap, nodes: &[String; 2]) {
stamp_resistor(system, node_map, nodes, 1e12);
}
pub(crate) fn stamp_varactor_transient(
system: &mut MnaSystem,
node_map: &NodeMap,
nodes: &[String; 2],
v_prev_across: f64,
dt: f64,
params: &sindr_devices::varactor::VaractorParams,
) {
let (g_eq, i_eq) = sindr_devices::varactor::varactor_companion(v_prev_across, dt, params);
let p = node_map.index(&nodes[0]);
let q = node_map.index(&nodes[1]);
if let Some(pi) = p {
system.a[(pi, pi)] += g_eq;
}
if let Some(qi) = q {
system.a[(qi, qi)] += g_eq;
}
if let (Some(pi), Some(qi)) = (p, q) {
system.a[(pi, qi)] -= g_eq;
system.a[(qi, pi)] -= g_eq;
}
if let Some(pi) = p {
system.b[pi] += i_eq;
}
if let Some(qi) = q {
system.b[qi] -= i_eq;
}
}
pub(crate) fn stamp_igbt_companion(
system: &mut MnaSystem,
node_map: &NodeMap,
nodes: &[String; 3],
v_prev: &DVector<f64>,
params: &sindr_devices::igbt::IgbtParams,
) {
let g = node_map.index(&nodes[0]); let c = node_map.index(&nodes[1]); let e = node_map.index(&nodes[2]);
let vg = g.map_or(0.0, |i| v_prev[i]);
let vc = c.map_or(0.0, |i| v_prev[i]);
let ve = e.map_or(0.0, |i| v_prev[i]);
let vge = vg - ve;
let vce = vc - ve;
let comp = sindr_devices::igbt::igbt_companion(vge, vce, params);
let g_total = comp.gm + comp.g_ce;
if let Some(ci) = c {
system.a[(ci, ci)] += g_total;
}
if let Some(ei) = e {
system.a[(ei, ei)] += g_total;
}
if let (Some(ci), Some(ei)) = (c, e) {
system.a[(ci, ei)] -= g_total;
system.a[(ei, ci)] -= g_total;
}
if let Some(ci) = c {
system.b[ci] -= comp.i_eq;
}
if let Some(ei) = e {
system.b[ei] += comp.i_eq;
}
}
pub(crate) fn stamp_jfet_companion(
system: &mut MnaSystem,
node_map: &NodeMap,
nodes: &[String; 3], kind: JfetKind,
idss: f64,
vp: f64,
v_prev: &nalgebra::DVector<f64>,
) {
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| v_prev[i]);
let vd = d.map_or(0.0, |i| v_prev[i]);
let vs = s.map_or(0.0, |i| v_prev[i]);
let vgs = vg - vs;
let vds = vd - vs;
let c = sindr_devices::jfet::jfet_companion(vgs, vds, kind, idss, vp);
if let Some(di) = d {
system.a[(di, di)] += c.gds;
}
if let Some(si) = s {
system.a[(si, si)] += c.gds;
}
if let (Some(di), Some(si)) = (d, s) {
system.a[(di, si)] -= c.gds;
system.a[(si, di)] -= c.gds;
}
if let (Some(di), Some(gi)) = (d, g) {
system.a[(di, gi)] += c.gm;
}
if let (Some(di), Some(si)) = (d, s) {
system.a[(di, si)] -= c.gm;
}
if let (Some(si), Some(gi)) = (s, g) {
system.a[(si, gi)] -= c.gm;
}
if let (Some(si2), Some(si)) = (s, s) {
system.a[(si2, si)] += c.gm;
}
if let Some(di) = d {
system.b[di] -= c.i_eq;
}
if let Some(si) = s {
system.b[si] += c.i_eq;
}
}
pub(crate) fn stamp_bjt_parasitic_caps(
system: &mut MnaSystem,
node_map: &NodeMap,
nodes: &[String; 3],
caps: &crate::circuit::BjtParasiticCaps,
dt: f64,
v_be_prev: f64,
v_bc_prev: f64,
) {
if caps.cbe > 0.0 {
stamp_capacitor_companion(
system,
node_map,
&[nodes[0].clone(), nodes[2].clone()], caps.cbe,
dt,
v_be_prev,
);
}
if caps.cbc > 0.0 {
stamp_capacitor_companion(
system,
node_map,
&[nodes[0].clone(), nodes[1].clone()], caps.cbc,
dt,
v_bc_prev,
);
}
}
pub(crate) fn stamp_mosfet_parasitic_caps(
system: &mut MnaSystem,
node_map: &NodeMap,
nodes: &[String; 3],
caps: &crate::circuit::MosfetParasiticCaps,
dt: f64,
v_gs_prev: f64,
v_gd_prev: f64,
) {
if caps.cgs > 0.0 {
stamp_capacitor_companion(
system,
node_map,
&[nodes[0].clone(), nodes[2].clone()], caps.cgs,
dt,
v_gs_prev,
);
}
if caps.cgd > 0.0 {
stamp_capacitor_companion(
system,
node_map,
&[nodes[0].clone(), nodes[1].clone()], caps.cgd,
dt,
v_gd_prev,
);
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn stamp_transformer_companion(
system: &mut MnaSystem,
node_map: &NodeMap,
nodes: &[String; 4], l1: f64,
l2: f64,
k: f64,
dt: f64,
i1_prev: f64,
i2_prev: f64,
k1: usize, k2: usize, ) {
let m = k * (l1 * l2).sqrt();
let p1 = node_map.index(&nodes[0]);
let q1 = node_map.index(&nodes[1]);
let p2 = node_map.index(&nodes[2]);
let q2 = node_map.index(&nodes[3]);
if let Some(p1i) = p1 {
system.a[(p1i, k1)] += 1.0;
system.a[(k1, p1i)] += 1.0;
}
if let Some(q1i) = q1 {
system.a[(q1i, k1)] -= 1.0;
system.a[(k1, q1i)] -= 1.0;
}
system.a[(k1, k1)] += l1 / dt;
system.a[(k1, k2)] += m / dt;
if let Some(p2i) = p2 {
system.a[(p2i, k2)] += 1.0;
system.a[(k2, p2i)] += 1.0;
}
if let Some(q2i) = q2 {
system.a[(q2i, k2)] -= 1.0;
system.a[(k2, q2i)] -= 1.0;
}
system.a[(k2, k2)] += l2 / dt;
system.a[(k2, k1)] += m / dt;
system.b[k1] += (l1 / dt) * i1_prev + (m / dt) * i2_prev;
system.b[k2] += (l2 / dt) * i2_prev + (m / dt) * i1_prev;
}
pub fn stamp_vcvs(
system: &mut MnaSystem,
node_map: &NodeMap,
nodes: &[String; 2],
control_nodes: &[String; 2],
gain: f64,
branch: usize,
) {
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]);
if let Some(pi) = p {
system.a[(pi, branch)] += 1.0;
}
if let Some(qi) = q {
system.a[(qi, branch)] -= 1.0;
}
if let Some(pi) = p {
system.a[(branch, pi)] += 1.0;
}
if let Some(qi) = q {
system.a[(branch, qi)] -= 1.0;
}
if let Some(cpi) = cp {
system.a[(branch, cpi)] -= gain;
}
if let Some(cqi) = cq {
system.a[(branch, cqi)] += gain;
}
}
pub fn stamp_vccs(
system: &mut MnaSystem,
node_map: &NodeMap,
nodes: &[String; 2],
control_nodes: &[String; 2],
gm: f64,
) {
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]);
if let (Some(ti), Some(cpi)) = (to, cp) {
system.a[(ti, cpi)] += gm;
}
if let (Some(ti), Some(cqi)) = (to, cq) {
system.a[(ti, cqi)] -= gm;
}
if let (Some(fi), Some(cpi)) = (from, cp) {
system.a[(fi, cpi)] -= gm;
}
if let (Some(fi), Some(cqi)) = (from, cq) {
system.a[(fi, cqi)] += gm;
}
}
pub fn stamp_ccvs(
system: &mut MnaSystem,
node_map: &NodeMap,
nodes: &[String; 2],
rm: f64,
branch: usize,
ctrl_branch: usize,
) {
let p = node_map.index(&nodes[0]);
let q = node_map.index(&nodes[1]);
if let Some(pi) = p {
system.a[(pi, branch)] += 1.0;
}
if let Some(qi) = q {
system.a[(qi, branch)] -= 1.0;
}
if let Some(pi) = p {
system.a[(branch, pi)] += 1.0;
}
if let Some(qi) = q {
system.a[(branch, qi)] -= 1.0;
}
system.a[(branch, ctrl_branch)] -= rm;
}
pub fn stamp_cccs(
system: &mut MnaSystem,
node_map: &NodeMap,
nodes: &[String; 2],
alpha: f64,
ctrl_branch: usize,
) {
let from = node_map.index(&nodes[0]);
let to = node_map.index(&nodes[1]);
if let Some(ti) = to {
system.a[(ti, ctrl_branch)] += alpha;
}
if let Some(fi) = from {
system.a[(fi, ctrl_branch)] -= alpha;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::circuit::Circuit;
use approx::assert_relative_eq;
#[test]
fn stamp_resistor_to_ground() {
let circuit = Circuit {
ground_node: "0".into(),
components: vec![CircuitElement::Resistor {
id: "R1".into(),
nodes: ["n1".into(), "0".into()],
resistance: 1000.0,
}],
};
let node_map = NodeMap::from_circuit(&circuit);
let mut system = MnaSystem::new(node_map.num_nodes(), 0);
stamp_circuit(&circuit, &mut system, &node_map, None).unwrap();
assert_eq!(system.size(), 1);
assert_relative_eq!(system.a[(0, 0)], 0.001, epsilon = 1e-15);
}
#[test]
fn stamp_resistor_between_nodes() {
let circuit = Circuit {
ground_node: "0".into(),
components: vec![CircuitElement::Resistor {
id: "R1".into(),
nodes: ["n1".into(), "n2".into()],
resistance: 1000.0,
}],
};
let node_map = NodeMap::from_circuit(&circuit);
let mut system = MnaSystem::new(node_map.num_nodes(), 0);
stamp_circuit(&circuit, &mut system, &node_map, None).unwrap();
let i1 = node_map.index("n1").unwrap();
let i2 = node_map.index("n2").unwrap();
assert_relative_eq!(system.a[(i1, i1)], 0.001, epsilon = 1e-15);
assert_relative_eq!(system.a[(i2, i2)], 0.001, epsilon = 1e-15);
assert_relative_eq!(system.a[(i1, i2)], -0.001, epsilon = 1e-15);
assert_relative_eq!(system.a[(i2, i1)], -0.001, epsilon = 1e-15);
}
#[test]
fn stamp_voltage_source_to_ground() {
let circuit = Circuit {
ground_node: "0".into(),
components: vec![CircuitElement::VoltageSource {
id: "V1".into(),
nodes: ["n1".into(), "0".into()],
voltage: 10.0,
waveform: None,
}],
};
let node_map = NodeMap::from_circuit(&circuit);
let mut system = MnaSystem::new(node_map.num_nodes(), circuit.count_voltage_sources());
stamp_circuit(&circuit, &mut system, &node_map, None).unwrap();
assert_relative_eq!(system.a[(0, 1)], 1.0, epsilon = 1e-15);
assert_relative_eq!(system.a[(1, 0)], 1.0, epsilon = 1e-15);
assert_relative_eq!(system.b[1], 10.0, epsilon = 1e-15);
}
#[test]
fn stamp_current_source_from_ground() {
let circuit = Circuit {
ground_node: "0".into(),
components: vec![CircuitElement::CurrentSource {
id: "I1".into(),
nodes: ["0".into(), "n1".into()],
current: 0.002,
waveform: None,
}],
};
let node_map = NodeMap::from_circuit(&circuit);
let mut system = MnaSystem::new(node_map.num_nodes(), 0);
stamp_circuit(&circuit, &mut system, &node_map, None).unwrap();
assert_relative_eq!(system.b[0], 0.002, epsilon = 1e-15);
}
#[test]
fn stamp_capacitor_companion_to_ground() {
let circuit = Circuit {
ground_node: "0".into(),
components: vec![CircuitElement::Capacitor {
id: "C1".into(),
nodes: ["n1".into(), "0".into()],
capacitance: 100e-6,
}],
};
let node_map = NodeMap::from_circuit(&circuit);
let mut system = MnaSystem::new(node_map.num_nodes(), 0);
let nodes = ["n1".into(), "0".into()];
stamp_capacitor_companion(&mut system, &node_map, &nodes, 100e-6, 1e-3, 5.0);
assert_relative_eq!(system.a[(0, 0)], 0.1, epsilon = 1e-12);
assert_relative_eq!(system.b[0], 0.5, epsilon = 1e-12);
}
#[test]
fn stamp_capacitor_companion_between_nodes() {
let circuit = Circuit {
ground_node: "0".into(),
components: vec![
CircuitElement::Capacitor {
id: "C1".into(),
nodes: ["n1".into(), "n2".into()],
capacitance: 1e-6,
},
CircuitElement::Resistor {
id: "R1".into(),
nodes: ["n2".into(), "0".into()],
resistance: 1000.0,
},
],
};
let node_map = NodeMap::from_circuit(&circuit);
let mut system = MnaSystem::new(node_map.num_nodes(), 0);
let nodes = ["n1".into(), "n2".into()];
stamp_capacitor_companion(&mut system, &node_map, &nodes, 1e-6, 1e-4, 3.0);
let p = node_map.index("n1").unwrap();
let q = node_map.index("n2").unwrap();
let g_eq = 1e-6 / 1e-4; let i_eq = g_eq * 3.0;
assert_relative_eq!(system.a[(p, p)], g_eq, epsilon = 1e-12);
assert_relative_eq!(system.a[(q, q)], g_eq, epsilon = 1e-12);
assert_relative_eq!(system.a[(p, q)], -g_eq, epsilon = 1e-12);
assert_relative_eq!(system.a[(q, p)], -g_eq, epsilon = 1e-12);
assert_relative_eq!(system.b[p], i_eq, epsilon = 1e-12);
assert_relative_eq!(system.b[q], -i_eq, epsilon = 1e-12);
}
#[test]
fn stamp_inductor_companion_to_ground() {
let circuit = Circuit {
ground_node: "0".into(),
components: vec![CircuitElement::Inductor {
id: "L1".into(),
nodes: ["n1".into(), "0".into()],
inductance: 10e-3,
}],
};
let node_map = NodeMap::from_circuit(&circuit);
let mut system = MnaSystem::new(node_map.num_nodes(), 0);
let nodes = ["n1".into(), "0".into()];
stamp_inductor_companion(&mut system, &node_map, &nodes, 10e-3, 1e-3, 0.5);
assert_relative_eq!(system.a[(0, 0)], 0.1, epsilon = 1e-12);
assert_relative_eq!(system.b[0], -0.5, epsilon = 1e-12);
}
#[test]
fn stamp_inductor_companion_between_nodes() {
let circuit = Circuit {
ground_node: "0".into(),
components: vec![
CircuitElement::Inductor {
id: "L1".into(),
nodes: ["n1".into(), "n2".into()],
inductance: 1e-3,
},
CircuitElement::Resistor {
id: "R1".into(),
nodes: ["n2".into(), "0".into()],
resistance: 1000.0,
},
],
};
let node_map = NodeMap::from_circuit(&circuit);
let mut system = MnaSystem::new(node_map.num_nodes(), 0);
let nodes = ["n1".into(), "n2".into()];
stamp_inductor_companion(&mut system, &node_map, &nodes, 1e-3, 1e-4, 0.1);
let p = node_map.index("n1").unwrap();
let q = node_map.index("n2").unwrap();
let g_eq = 1e-4 / 1e-3; let i_eq = 0.1;
assert_relative_eq!(system.a[(p, p)], g_eq, epsilon = 1e-12);
assert_relative_eq!(system.a[(q, q)], g_eq, epsilon = 1e-12);
assert_relative_eq!(system.a[(p, q)], -g_eq, epsilon = 1e-12);
assert_relative_eq!(system.a[(q, p)], -g_eq, epsilon = 1e-12);
assert_relative_eq!(system.b[p], -i_eq, epsilon = 1e-12);
assert_relative_eq!(system.b[q], i_eq, epsilon = 1e-12);
}
#[test]
fn stamp_zero_resistance_returns_error() {
let circuit = Circuit {
ground_node: "0".into(),
components: vec![CircuitElement::Resistor {
id: "R1".into(),
nodes: ["n1".into(), "0".into()],
resistance: 0.0,
}],
};
let node_map = NodeMap::from_circuit(&circuit);
let mut system = MnaSystem::new(node_map.num_nodes(), 0);
let result = stamp_circuit(&circuit, &mut system, &node_map, None);
assert!(result.is_err());
match result.unwrap_err() {
SimError::InvalidResistance(id) => assert_eq!(id, "R1"),
other => panic!("expected InvalidResistance, got: {other}"),
}
}
#[test]
fn stamp_bjt_companion_matrix_entries() {
use sindr_devices::bjt::{BjtKind, BjtParams};
use sindr_devices::diode::V_T;
let circuit = Circuit {
ground_node: "0".into(),
components: vec![
CircuitElement::VoltageSource {
id: "Vcc".into(),
nodes: ["n2".into(), "0".into()],
voltage: 5.0,
waveform: None,
},
CircuitElement::Resistor {
id: "Rb".into(),
nodes: ["n1".into(), "0".into()],
resistance: 100000.0,
},
CircuitElement::Bjt {
id: "Q1".into(),
nodes: ["n1".into(), "n2".into(), "0".into()],
kind: BjtKind::Npn,
bf: 100.0,
temperature: 300.15,
parasitic_caps: None,
},
],
};
let node_map = NodeMap::from_circuit(&circuit);
let n_nodes = node_map.num_nodes();
let n_vsources = circuit.count_voltage_sources();
let size = n_nodes + n_vsources;
let mut system = MnaSystem::new(n_nodes, n_vsources);
let mut v_prev = nalgebra::DVector::zeros(size);
let bi = node_map.index("n1").unwrap();
let ci = node_map.index("n2").unwrap();
v_prev[bi] = 0.7;
v_prev[ci] = 5.0;
let params = BjtParams::new(100.0);
let nodes: [String; 3] = ["n1".into(), "n2".into(), "0".into()];
stamp_bjt_companion(
&mut system,
&node_map,
&nodes,
&v_prev,
¶ms,
BjtKind::Npn,
);
let vbe = 0.7;
let vbc = -4.3;
let nf_vt = 1.0 * V_T;
let nr_vt = 1.0 * V_T;
let exp_be = (vbe / nf_vt).exp();
let exp_bc = (vbc / nr_vt).exp();
let g_be = (1e-14 / nf_vt) * exp_be;
let g_bc = (1e-14 / nr_vt) * exp_bc;
let bf = 100.0;
let br = 1.0;
let alpha_r = br / (br + 1.0);
assert_relative_eq!(system.a[(bi, bi)], g_be / bf + g_bc / br, epsilon = 1e-15);
assert_relative_eq!(system.a[(bi, ci)], -g_bc / br, epsilon = 1e-15);
assert_relative_eq!(system.a[(ci, bi)], g_be - g_bc / alpha_r, epsilon = 1e-15);
assert_relative_eq!(system.a[(ci, ci)], g_bc / alpha_r, epsilon = 1e-15);
assert!(system.a[(bi, bi)] > 0.0, "G[B,B] should be positive");
assert!(system.a[(ci, ci)] >= 0.0, "G[C,C] should be non-negative");
assert!(
system.a[(ci, bi)] > 0.1,
"G[C,B] should be substantial in active region, got {}",
system.a[(ci, bi)]
);
assert!(system.b[bi].abs() > 1e-10, "RHS[B] should be non-zero");
assert!(system.b[ci].abs() > 1e-10, "RHS[C] should be non-zero");
let i_f = 1e-14 * (exp_be - 1.0);
let i_r = 1e-14 * (exp_bc - 1.0);
let ic = i_f - i_r / alpha_r;
let ib = i_f / bf + i_r / br;
let ib_eq = ib - (g_be / bf) * vbe - (g_bc / br) * vbc;
let ic_eq = ic - g_be * vbe + (g_bc / alpha_r) * vbc;
assert_relative_eq!(system.b[bi], -ib_eq, epsilon = 1e-15);
assert_relative_eq!(system.b[ci], -ic_eq, epsilon = 1e-15);
}
#[test]
fn test_bjt_stamp_kcl_column_sum() {
use sindr_devices::bjt::{BjtKind, BjtParams};
let circuit = Circuit {
ground_node: "0".into(),
components: vec![
CircuitElement::VoltageSource {
id: "Vbb".into(),
nodes: ["n_b".into(), "0".into()],
voltage: 0.7,
waveform: None,
},
CircuitElement::Resistor {
id: "Rc".into(),
nodes: ["n_c".into(), "0".into()],
resistance: 1000.0,
},
CircuitElement::Bjt {
id: "Q1".into(),
nodes: ["n_b".into(), "n_c".into(), "0".into()],
kind: BjtKind::Npn,
bf: 100.0,
temperature: 300.15,
parasitic_caps: None,
},
],
};
let node_map = NodeMap::from_circuit(&circuit);
let n_nodes = node_map.num_nodes();
let n_vsources = circuit.count_voltage_sources();
let size = n_nodes + n_vsources;
let mut system = MnaSystem::new(n_nodes, n_vsources);
let mut v_prev = nalgebra::DVector::zeros(size);
let bi = node_map.index("n_b").unwrap();
let ci = node_map.index("n_c").unwrap();
v_prev[bi] = 0.7;
v_prev[ci] = 0.0;
let params = BjtParams::new(100.0);
let nodes: [String; 3] = ["n_b".into(), "n_c".into(), "0".into()];
stamp_bjt_companion(
&mut system,
&node_map,
&nodes,
&v_prev,
¶ms,
BjtKind::Npn,
);
let params2 = BjtParams::new(100.0);
let bf = params2.bf;
let br = params2.br;
let alpha_f = bf / (bf + 1.0);
let alpha_r = br / (br + 1.0);
let vbe = 0.7;
let vbc = 0.7 - 0.0;
let comp = sindr_devices::bjt::bjt_companion(vbe, vbc, ¶ms2);
let g_be = comp.g_be;
let g_bc = comp.g_bc;
assert!(
g_bc > 1e-6,
"g_bc={} should be significant in saturation",
g_bc
);
let col_b = (g_be / bf + g_bc / br) + (g_be - g_bc / alpha_r) + (-g_be / alpha_f + g_bc);
assert!(
col_b.abs() < 1e-12,
"Column B should sum to 0 (KCL), got {}",
col_b
);
let col_c = (-g_bc / br) + (g_bc / alpha_r) + (-g_bc);
assert!(
col_c.abs() < 1e-12,
"Column C should sum to 0 (KCL), got {}",
col_c
);
let col_e = (-g_be / bf) + (-g_be) + (g_be / alpha_f);
assert!(
col_e.abs() < 1e-12,
"Column E should sum to 0 (KCL), got {}",
col_e
);
assert_relative_eq!(system.a[(bi, bi)], g_be / bf + g_bc / br, epsilon = 1e-12);
assert_relative_eq!(system.a[(bi, ci)], -g_bc / br, epsilon = 1e-12);
assert_relative_eq!(system.a[(ci, bi)], g_be - g_bc / alpha_r, epsilon = 1e-12);
assert_relative_eq!(system.a[(ci, ci)], g_bc / alpha_r, epsilon = 1e-12);
}
#[test]
fn test_bjt_stamp_entries_npn_active() {
use sindr_devices::bjt::{BjtKind, BjtParams};
let circuit = Circuit {
ground_node: "0".into(),
components: vec![
CircuitElement::VoltageSource {
id: "Vcc".into(),
nodes: ["n_vcc".into(), "0".into()],
voltage: 10.0,
waveform: None,
},
CircuitElement::Resistor {
id: "Rb".into(),
nodes: ["n_b".into(), "0".into()],
resistance: 100_000.0,
},
CircuitElement::Resistor {
id: "Re".into(),
nodes: ["n_e".into(), "0".into()],
resistance: 100.0,
},
CircuitElement::Resistor {
id: "Rc".into(),
nodes: ["n_c".into(), "0".into()],
resistance: 1000.0,
},
CircuitElement::Bjt {
id: "Q1".into(),
nodes: ["n_b".into(), "n_c".into(), "n_e".into()],
kind: BjtKind::Npn,
bf: 100.0,
temperature: 300.15,
parasitic_caps: None,
},
],
};
let node_map = NodeMap::from_circuit(&circuit);
let n_nodes = node_map.num_nodes();
let n_vsources = circuit.count_voltage_sources();
let size = n_nodes + n_vsources;
let mut system = MnaSystem::new(n_nodes, n_vsources);
let bi = node_map.index("n_b").unwrap();
let ci = node_map.index("n_c").unwrap();
let ei = node_map.index("n_e").unwrap();
let mut v_prev = nalgebra::DVector::zeros(size);
v_prev[bi] = 0.7;
v_prev[ci] = 5.0;
v_prev[ei] = 0.0;
let params = BjtParams::new(100.0);
let nodes: [String; 3] = ["n_b".into(), "n_c".into(), "n_e".into()];
stamp_bjt_companion(
&mut system,
&node_map,
&nodes,
&v_prev,
¶ms,
BjtKind::Npn,
);
let vbe = 0.7;
let vbc = 0.7 - 5.0;
let comp = sindr_devices::bjt::bjt_companion(vbe, vbc, ¶ms);
let g_be = comp.g_be;
let g_bc = comp.g_bc;
let bf = 100.0;
let br = 1.0;
let alpha_f = bf / (bf + 1.0);
let alpha_r = br / (br + 1.0);
assert_relative_eq!(system.a[(bi, bi)], g_be / bf + g_bc / br, epsilon = 1e-12);
assert_relative_eq!(system.a[(bi, ci)], -g_bc / br, epsilon = 1e-12);
assert_relative_eq!(system.a[(bi, ei)], -g_be / bf, epsilon = 1e-12);
assert_relative_eq!(system.a[(ci, bi)], g_be + g_bc / alpha_r, epsilon = 1e-6);
assert_relative_eq!(system.a[(ci, ei)], -g_be, epsilon = 1e-12);
assert_relative_eq!(system.a[(ei, ei)], g_be / alpha_f, epsilon = 1e-12);
}
#[test]
fn test_bjt_stamp_entries_npn_saturation() {
use sindr_devices::bjt::{BjtKind, BjtParams};
let circuit = Circuit {
ground_node: "0".into(),
components: vec![
CircuitElement::VoltageSource {
id: "Vcc".into(),
nodes: ["n_vcc".into(), "0".into()],
voltage: 10.0,
waveform: None,
},
CircuitElement::Resistor {
id: "Rb".into(),
nodes: ["n_b".into(), "0".into()],
resistance: 100_000.0,
},
CircuitElement::Resistor {
id: "Re".into(),
nodes: ["n_e".into(), "0".into()],
resistance: 100.0,
},
CircuitElement::Resistor {
id: "Rc".into(),
nodes: ["n_c".into(), "0".into()],
resistance: 1000.0,
},
CircuitElement::Bjt {
id: "Q1".into(),
nodes: ["n_b".into(), "n_c".into(), "n_e".into()],
kind: BjtKind::Npn,
bf: 100.0,
temperature: 300.15,
parasitic_caps: None,
},
],
};
let node_map = NodeMap::from_circuit(&circuit);
let n_nodes = node_map.num_nodes();
let n_vsources = circuit.count_voltage_sources();
let size = n_nodes + n_vsources;
let mut system = MnaSystem::new(n_nodes, n_vsources);
let bi = node_map.index("n_b").unwrap();
let ci = node_map.index("n_c").unwrap();
let ei = node_map.index("n_e").unwrap();
let mut v_prev = nalgebra::DVector::zeros(size);
v_prev[bi] = 0.7;
v_prev[ci] = 0.0;
v_prev[ei] = 0.0;
let params = BjtParams::new(100.0);
let nodes: [String; 3] = ["n_b".into(), "n_c".into(), "n_e".into()];
stamp_bjt_companion(
&mut system,
&node_map,
&nodes,
&v_prev,
¶ms,
BjtKind::Npn,
);
let vbe = 0.7;
let vbc = 0.7;
let comp = sindr_devices::bjt::bjt_companion(vbe, vbc, ¶ms);
let g_be = comp.g_be;
let g_bc = comp.g_bc;
let bf = 100.0;
let br = 1.0;
let alpha_f = bf / (bf + 1.0);
let alpha_r = br / (br + 1.0);
assert!(
g_bc > 1e-3,
"g_bc={} should be significant in saturation",
g_bc
);
assert_relative_eq!(system.a[(bi, bi)], g_be / bf + g_bc / br, epsilon = 0.01);
assert_relative_eq!(system.a[(bi, ci)], -g_bc / br, epsilon = 0.01);
assert_relative_eq!(system.a[(bi, ei)], -g_be / bf, epsilon = 0.01);
assert_relative_eq!(system.a[(ci, bi)], g_be - g_bc / alpha_r, epsilon = 0.01);
assert_relative_eq!(system.a[(ci, ci)], g_bc / alpha_r, epsilon = 0.01);
assert_relative_eq!(system.a[(ci, ei)], -g_be, epsilon = 0.01);
assert_relative_eq!(system.a[(ei, bi)], -g_be / alpha_f + g_bc, epsilon = 0.01);
assert_relative_eq!(system.a[(ei, ci)], -g_bc, epsilon = 0.01);
assert_relative_eq!(system.a[(ei, ei)], g_be / alpha_f, epsilon = 0.01);
}
#[test]
fn transformer_dc_solve() {
use crate::solve_circuit;
let circuit = Circuit {
ground_node: "0".into(),
components: vec![
CircuitElement::VoltageSource {
id: "V1".into(),
nodes: ["p1".into(), "0".into()],
voltage: 10.0,
waveform: None,
},
CircuitElement::Resistor {
id: "R1".into(),
nodes: ["p1".into(), "n1".into()],
resistance: 100.0,
},
CircuitElement::Transformer {
id: "T1".into(),
nodes: ["n1".into(), "0".into(), "n2".into(), "0".into()],
l1: 1e-3,
l2: 4e-3,
k: 0.999,
},
CircuitElement::Resistor {
id: "R2".into(),
nodes: ["n2".into(), "0".into()],
resistance: 1000.0,
},
],
};
let result = solve_circuit(&circuit);
assert!(
result.is_ok(),
"Transformer DC circuit solve failed: {:?}",
result.err()
);
let result = result.unwrap();
let v_n1 = result.node_voltages["n1"];
assert!(v_n1 > 9.0, "Primary voltage should be ~10V, got {}", v_n1);
}
#[test]
fn varactor_dc_circuit_solves() {
use crate::solve_circuit;
let circuit = Circuit {
ground_node: "0".into(),
components: vec![
CircuitElement::VoltageSource {
id: "V1".into(),
nodes: ["n1".into(), "0".into()],
voltage: 5.0,
waveform: None,
},
CircuitElement::Resistor {
id: "R1".into(),
nodes: ["n1".into(), "n2".into()],
resistance: 1000.0,
},
CircuitElement::Varactor {
id: "CV1".into(),
nodes: ["n2".into(), "0".into()],
params: sindr_devices::varactor::VaractorParams::default(),
},
],
};
let result = solve_circuit(&circuit);
assert!(
result.is_ok(),
"Varactor DC circuit solve failed: {:?}",
result.err()
);
let result = result.unwrap();
let v_n1 = result.node_voltages["n1"];
assert_relative_eq!(v_n1, 5.0, epsilon = 1e-6);
let cv1 = result
.component_results
.iter()
.find(|c| c.id == "CV1")
.unwrap();
assert!(
cv1.voltage_across > 4.9,
"Varactor voltage should be ~5V, got {}",
cv1.voltage_across
);
}
#[test]
fn igbt_cutoff_circuit() {
use crate::solve_circuit;
let circuit = Circuit {
ground_node: "0".into(),
components: vec![
CircuitElement::VoltageSource {
id: "Vg".into(),
nodes: ["gate".into(), "0".into()],
voltage: 0.0,
waveform: None,
},
CircuitElement::VoltageSource {
id: "Vcc".into(),
nodes: ["coll".into(), "0".into()],
voltage: 12.0,
waveform: None,
},
CircuitElement::Resistor {
id: "Rload".into(),
nodes: ["coll".into(), "0".into()],
resistance: 100.0,
},
CircuitElement::Igbt {
id: "T1".into(),
nodes: ["gate".into(), "coll".into(), "0".into()],
params: sindr_devices::igbt::IgbtParams::default(), },
],
};
let result = solve_circuit(&circuit);
assert!(
result.is_ok(),
"IGBT cutoff circuit failed: {:?}",
result.err()
);
let result = result.unwrap();
let t1 = result
.component_results
.iter()
.find(|c| c.id == "T1")
.unwrap();
assert_relative_eq!(t1.current_through, 0.0, epsilon = 1e-9);
}
#[test]
fn igbt_active_circuit() {
use crate::solve_circuit;
let circuit = Circuit {
ground_node: "0".into(),
components: vec![
CircuitElement::VoltageSource {
id: "Vg".into(),
nodes: ["gate".into(), "0".into()],
voltage: 10.0,
waveform: None,
},
CircuitElement::VoltageSource {
id: "Vcc".into(),
nodes: ["n_vcc".into(), "0".into()],
voltage: 12.0,
waveform: None,
},
CircuitElement::Resistor {
id: "Rload".into(),
nodes: ["n_vcc".into(), "coll".into()],
resistance: 100.0,
},
CircuitElement::Igbt {
id: "T1".into(),
nodes: ["gate".into(), "coll".into(), "0".into()],
params: sindr_devices::igbt::IgbtParams::default(), },
],
};
let result = solve_circuit(&circuit);
assert!(
result.is_ok(),
"IGBT active circuit failed: {:?}",
result.err()
);
let result = result.unwrap();
let t1 = result
.component_results
.iter()
.find(|c| c.id == "T1")
.unwrap();
assert!(
t1.current_through > 0.0,
"IGBT should have non-zero collector current, got {}",
t1.current_through
);
}
}