use sindr_devices::bjt::BjtKind;
use sindr_devices::jfet::JfetKind;
use sindr_devices::mosfet::{MosfetKind, MosfetParams};
use crate::waveform::Waveform;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Default)]
pub struct BjtParasiticCaps {
#[cfg_attr(feature = "serde", serde(default))]
pub cbe: f64,
#[cfg_attr(feature = "serde", serde(default))]
pub cbc: f64,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Default)]
pub struct MosfetParasiticCaps {
#[cfg_attr(feature = "serde", serde(default))]
pub cgs: f64,
#[cfg_attr(feature = "serde", serde(default))]
pub cgd: f64,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", serde(tag = "type"))]
pub enum CircuitElement {
#[cfg_attr(feature = "serde", serde(rename = "resistor"))]
Resistor {
id: String,
nodes: [String; 2],
resistance: f64,
},
#[cfg_attr(feature = "serde", serde(rename = "voltage_source"))]
VoltageSource {
id: String,
nodes: [String; 2],
voltage: f64,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
waveform: Option<Waveform>,
},
#[cfg_attr(feature = "serde", serde(rename = "current_source"))]
CurrentSource {
id: String,
nodes: [String; 2],
current: f64,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
waveform: Option<Waveform>,
},
#[cfg_attr(feature = "serde", serde(rename = "switch"))]
Switch {
id: String,
nodes: [String; 2],
closed: bool,
},
#[cfg_attr(feature = "serde", serde(rename = "capacitor"))]
Capacitor {
id: String,
nodes: [String; 2],
capacitance: f64,
},
#[cfg_attr(feature = "serde", serde(rename = "inductor"))]
Inductor {
id: String,
nodes: [String; 2],
inductance: f64,
},
#[cfg_attr(feature = "serde", serde(rename = "diode"))]
Diode {
id: String,
nodes: [String; 2],
#[cfg_attr(feature = "serde", serde(default = "default_junction_temperature"))]
temperature: f64,
},
#[cfg_attr(feature = "serde", serde(rename = "led"))]
Led {
id: String,
nodes: [String; 2],
color: String,
#[cfg_attr(feature = "serde", serde(default = "default_junction_temperature"))]
temperature: f64,
},
#[cfg_attr(feature = "serde", serde(rename = "bjt"))]
Bjt {
id: String,
nodes: [String; 3], kind: BjtKind,
#[cfg_attr(feature = "serde", serde(default = "default_bf"))]
bf: f64,
#[cfg_attr(feature = "serde", serde(default = "default_junction_temperature"))]
temperature: f64,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
parasitic_caps: Option<BjtParasiticCaps>,
},
#[cfg_attr(feature = "serde", serde(rename = "mosfet"))]
Mosfet {
id: String,
nodes: [String; 3], kind: MosfetKind,
#[cfg_attr(feature = "serde", serde(default))]
params: MosfetParams,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
parasitic_caps: Option<MosfetParasiticCaps>,
},
#[cfg_attr(feature = "serde", serde(rename = "jfet"))]
Jfet {
id: String,
nodes: [String; 3], kind: JfetKind,
#[cfg_attr(feature = "serde", serde(default = "default_jfet_idss"))]
idss: f64,
#[cfg_attr(feature = "serde", serde(default = "default_jfet_vp"))]
vp: f64,
},
#[cfg_attr(feature = "serde", serde(rename = "vcvs"))]
Vcvs {
id: String,
nodes: [String; 2],
control_nodes: [String; 2],
gain: f64, },
#[cfg_attr(feature = "serde", serde(rename = "vccs"))]
Vccs {
id: String,
nodes: [String; 2],
control_nodes: [String; 2],
gm: f64,
},
#[cfg_attr(feature = "serde", serde(rename = "ccvs"))]
Ccvs {
id: String,
nodes: [String; 2],
control_source: String,
rm: f64,
},
#[cfg_attr(feature = "serde", serde(rename = "cccs"))]
Cccs {
id: String,
nodes: [String; 2],
control_source: String,
alpha: f64,
},
#[cfg_attr(feature = "serde", serde(rename = "pushbutton"))]
Pushbutton {
id: String,
nodes: [String; 2],
closed: bool,
},
#[cfg_attr(feature = "serde", serde(rename = "relay"))]
Relay {
id: String,
nodes: [String; 4],
#[cfg_attr(feature = "serde", serde(default = "default_relay_coil_resistance"))]
coil_resistance: f64,
pickup_voltage: f64,
#[cfg_attr(feature = "serde", serde(default))]
inductance: f64,
},
#[cfg_attr(feature = "serde", serde(rename = "photoresistor"))]
Photoresistor {
id: String,
nodes: [String; 2],
light_level: f64,
},
#[cfg_attr(feature = "serde", serde(rename = "potentiometer"))]
Potentiometer {
id: String,
nodes: [String; 3],
resistance: f64,
position: f64,
},
#[cfg_attr(feature = "serde", serde(rename = "zener_diode"))]
ZenerDiode {
id: String,
nodes: [String; 2],
vz: f64,
#[cfg_attr(feature = "serde", serde(default = "default_junction_temperature"))]
temperature: f64,
},
#[cfg_attr(feature = "serde", serde(rename = "op_amp"))]
OpAmp {
id: String,
nodes: [String; 3], #[cfg_attr(feature = "serde", serde(default = "default_v_pos"))]
v_pos: f64,
#[cfg_attr(feature = "serde", serde(default = "default_v_neg"))]
v_neg: f64,
},
#[cfg_attr(feature = "serde", serde(rename = "comparator"))]
Comparator {
id: String,
nodes: [String; 3], #[cfg_attr(feature = "serde", serde(default = "default_v_pos"))]
v_pos: f64,
#[cfg_attr(feature = "serde", serde(default = "default_v_neg"))]
v_neg: f64,
},
#[cfg_attr(feature = "serde", serde(rename = "schottky_diode"))]
SchottkyDiode {
id: String,
nodes: [String; 2],
#[cfg_attr(feature = "serde", serde(default = "default_junction_temperature"))]
temperature: f64,
},
#[cfg_attr(feature = "serde", serde(rename = "thermistor"))]
Thermistor {
id: String,
nodes: [String; 2],
#[cfg_attr(feature = "serde", serde(default = "default_temperature"))]
temperature: f64,
},
#[cfg_attr(feature = "serde", serde(rename = "photodiode"))]
Photodiode {
id: String,
nodes: [String; 2],
#[cfg_attr(feature = "serde", serde(default))]
irradiance: f64,
#[cfg_attr(feature = "serde", serde(default = "default_junction_temperature"))]
temperature: f64,
},
#[cfg_attr(feature = "serde", serde(rename = "varactor"))]
Varactor {
id: String,
nodes: [String; 2],
#[cfg_attr(feature = "serde", serde(default))]
params: sindr_devices::varactor::VaractorParams,
},
#[cfg_attr(feature = "serde", serde(rename = "igbt"))]
Igbt {
id: String,
nodes: [String; 3],
#[cfg_attr(feature = "serde", serde(default))]
params: sindr_devices::igbt::IgbtParams,
},
#[cfg_attr(feature = "serde", serde(rename = "transformer"))]
Transformer {
id: String,
nodes: [String; 4], l1: f64, l2: f64, #[cfg_attr(feature = "serde", serde(default = "default_coupling"))]
k: f64, },
#[cfg_attr(feature = "serde", serde(rename = "fuse"))]
Fuse {
id: String,
nodes: [String; 2],
#[cfg_attr(feature = "serde", serde(default = "default_fuse_rating"))]
rating: f64,
#[cfg_attr(feature = "serde", serde(default))]
blown: bool,
},
#[cfg_attr(feature = "serde", serde(rename = "voltage_regulator"))]
VoltageRegulator {
id: String,
nodes: [String; 3], voltage: f64,
},
}
fn default_bf() -> f64 {
100.0
}
fn default_temperature() -> f64 {
298.15
}
fn default_junction_temperature() -> f64 {
300.15
}
fn default_relay_coil_resistance() -> f64 {
500.0
}
fn default_v_pos() -> f64 {
15.0
}
fn default_v_neg() -> f64 {
-15.0
}
fn default_coupling() -> f64 {
0.999
}
fn default_fuse_rating() -> f64 {
1.0
}
fn default_jfet_idss() -> f64 {
10e-3 }
fn default_jfet_vp() -> f64 {
-2.0 }
impl CircuitElement {
pub fn nodes(&self) -> &[String] {
match self {
CircuitElement::Resistor { nodes, .. } => nodes,
CircuitElement::VoltageSource { nodes, .. } => nodes,
CircuitElement::CurrentSource { nodes, .. } => nodes,
CircuitElement::Switch { nodes, .. } => nodes,
CircuitElement::Capacitor { nodes, .. } => nodes,
CircuitElement::Inductor { nodes, .. } => nodes,
CircuitElement::Diode { nodes, .. } => nodes,
CircuitElement::Led { nodes, .. } => nodes,
CircuitElement::Bjt { nodes, .. } => nodes,
CircuitElement::Mosfet { nodes, .. } => nodes,
CircuitElement::Jfet { nodes, .. } => nodes,
CircuitElement::Vcvs { nodes, .. } => nodes,
CircuitElement::Vccs { nodes, .. } => nodes,
CircuitElement::Ccvs { nodes, .. } => nodes,
CircuitElement::Cccs { nodes, .. } => nodes,
CircuitElement::Pushbutton { nodes, .. } => nodes,
CircuitElement::Relay { nodes, .. } => nodes,
CircuitElement::Photoresistor { nodes, .. } => nodes,
CircuitElement::Potentiometer { nodes, .. } => nodes,
CircuitElement::ZenerDiode { nodes, .. } => nodes,
CircuitElement::OpAmp { nodes, .. } => nodes,
CircuitElement::Comparator { nodes, .. } => nodes,
CircuitElement::SchottkyDiode { nodes, .. } => nodes,
CircuitElement::Thermistor { nodes, .. } => nodes,
CircuitElement::Photodiode { nodes, .. } => nodes,
CircuitElement::Varactor { nodes, .. } => nodes,
CircuitElement::Igbt { nodes, .. } => nodes,
CircuitElement::Transformer { nodes, .. } => nodes,
CircuitElement::Fuse { nodes, .. } => nodes,
CircuitElement::VoltageRegulator { nodes, .. } => nodes,
}
}
pub fn all_nodes(&self) -> Vec<&String> {
match self {
CircuitElement::Vcvs {
nodes,
control_nodes,
..
} => {
vec![&nodes[0], &nodes[1], &control_nodes[0], &control_nodes[1]]
}
CircuitElement::Vccs {
nodes,
control_nodes,
..
} => {
vec![&nodes[0], &nodes[1], &control_nodes[0], &control_nodes[1]]
}
CircuitElement::Ccvs { nodes, .. } => vec![&nodes[0], &nodes[1]],
CircuitElement::Cccs { nodes, .. } => vec![&nodes[0], &nodes[1]],
_ => self.nodes().iter().collect(),
}
}
pub fn id(&self) -> &str {
match self {
CircuitElement::Resistor { id, .. } => id,
CircuitElement::VoltageSource { id, .. } => id,
CircuitElement::CurrentSource { id, .. } => id,
CircuitElement::Switch { id, .. } => id,
CircuitElement::Capacitor { id, .. } => id,
CircuitElement::Inductor { id, .. } => id,
CircuitElement::Diode { id, .. } => id,
CircuitElement::Led { id, .. } => id,
CircuitElement::Bjt { id, .. } => id,
CircuitElement::Mosfet { id, .. } => id,
CircuitElement::Jfet { id, .. } => id,
CircuitElement::Vcvs { id, .. } => id,
CircuitElement::Vccs { id, .. } => id,
CircuitElement::Ccvs { id, .. } => id,
CircuitElement::Cccs { id, .. } => id,
CircuitElement::Pushbutton { id, .. } => id,
CircuitElement::Relay { id, .. } => id,
CircuitElement::Photoresistor { id, .. } => id,
CircuitElement::Potentiometer { id, .. } => id,
CircuitElement::ZenerDiode { id, .. } => id,
CircuitElement::OpAmp { id, .. } => id,
CircuitElement::Comparator { id, .. } => id,
CircuitElement::SchottkyDiode { id, .. } => id,
CircuitElement::Thermistor { id, .. } => id,
CircuitElement::Photodiode { id, .. } => id,
CircuitElement::Varactor { id, .. } => id,
CircuitElement::Igbt { id, .. } => id,
CircuitElement::Transformer { id, .. } => id,
CircuitElement::Fuse { id, .. } => id,
CircuitElement::VoltageRegulator { id, .. } => id,
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone)]
pub struct Circuit {
pub components: Vec<CircuitElement>,
pub ground_node: String,
}
impl Circuit {
pub fn count_voltage_sources(&self) -> usize {
self.components
.iter()
.map(|c| match c {
CircuitElement::VoltageSource { .. }
| CircuitElement::Vcvs { .. }
| CircuitElement::Ccvs { .. }
| CircuitElement::OpAmp { .. }
| CircuitElement::Comparator { .. }
| CircuitElement::VoltageRegulator { .. } => 1,
CircuitElement::Transformer { .. } => 2, _ => 0,
})
.sum()
}
pub fn has_nonlinear_elements(&self) -> bool {
self.components.iter().any(|c| {
matches!(
c,
CircuitElement::Diode { .. }
| CircuitElement::Led { .. }
| CircuitElement::Bjt { .. }
| CircuitElement::Mosfet { .. }
| CircuitElement::Relay { .. }
| CircuitElement::ZenerDiode { .. }
| CircuitElement::SchottkyDiode { .. }
| CircuitElement::Photodiode { .. }
| CircuitElement::Igbt { .. }
| CircuitElement::Jfet { .. } )
})
}
pub fn has_reactive_elements(&self) -> bool {
self.components.iter().any(|c| match c {
CircuitElement::Capacitor { .. }
| CircuitElement::Inductor { .. }
| CircuitElement::Varactor { .. }
| CircuitElement::Transformer { .. } => true,
CircuitElement::Relay { inductance, .. } if *inductance > 0.0 => true,
CircuitElement::Bjt {
parasitic_caps: Some(caps),
..
} if caps.cbe > 0.0 || caps.cbc > 0.0 => true,
CircuitElement::Mosfet {
parasitic_caps: Some(caps),
..
} if caps.cgs > 0.0 || caps.cgd > 0.0 => true,
_ => false,
})
}
pub fn has_waveform_sources(&self) -> bool {
self.components.iter().any(|c| match c {
CircuitElement::VoltageSource { waveform, .. }
| CircuitElement::CurrentSource { waveform, .. } => waveform.is_some(),
_ => false,
})
}
pub fn vsource_branch_index(&self, source_id: &str) -> Option<usize> {
let mut idx = 0;
for component in &self.components {
match component {
CircuitElement::VoltageSource { id, .. }
| CircuitElement::Vcvs { id, .. }
| CircuitElement::Ccvs { id, .. }
| CircuitElement::OpAmp { id, .. }
| CircuitElement::Comparator { id, .. }
| CircuitElement::VoltageRegulator { id, .. } => {
if id == source_id {
return Some(idx);
}
idx += 1;
}
CircuitElement::Transformer { .. } => {
idx += 2; }
_ => {}
}
}
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn has_reactive_elements_true_with_capacitor() {
let circuit = Circuit {
ground_node: "0".into(),
components: vec![CircuitElement::Capacitor {
id: "C1".into(),
nodes: ["n1".into(), "0".into()],
capacitance: 100e-6,
}],
};
assert!(circuit.has_reactive_elements());
}
#[test]
fn has_reactive_elements_true_with_inductor() {
let circuit = Circuit {
ground_node: "0".into(),
components: vec![CircuitElement::Inductor {
id: "L1".into(),
nodes: ["n1".into(), "0".into()],
inductance: 10e-3,
}],
};
assert!(circuit.has_reactive_elements());
}
#[test]
fn has_reactive_elements_false_resistor_only() {
let circuit = Circuit {
ground_node: "0".into(),
components: vec![CircuitElement::Resistor {
id: "R1".into(),
nodes: ["n1".into(), "0".into()],
resistance: 1000.0,
}],
};
assert!(!circuit.has_reactive_elements());
}
#[test]
fn bjt_with_parasitic_caps_is_reactive() {
let circuit = Circuit {
ground_node: "0".into(),
components: vec![CircuitElement::Bjt {
id: "Q1".into(),
nodes: ["b".into(), "c".into(), "e".into()],
kind: sindr_devices::bjt::BjtKind::Npn,
bf: 100.0,
temperature: 300.15,
parasitic_caps: Some(BjtParasiticCaps {
cbe: 10e-12,
cbc: 0.0,
}),
}],
};
assert!(
circuit.has_reactive_elements(),
"BJT with cbe>0 should be reactive"
);
}
#[test]
fn bjt_without_parasitic_caps_not_reactive() {
let circuit = Circuit {
ground_node: "0".into(),
components: vec![CircuitElement::Bjt {
id: "Q1".into(),
nodes: ["b".into(), "c".into(), "e".into()],
kind: sindr_devices::bjt::BjtKind::Npn,
bf: 100.0,
temperature: 300.15,
parasitic_caps: None,
}],
};
assert!(
!circuit.has_reactive_elements(),
"BJT without parasitic caps should not be reactive"
);
}
#[test]
fn mosfet_with_parasitic_caps_is_reactive() {
let circuit = Circuit {
ground_node: "0".into(),
components: vec![CircuitElement::Mosfet {
id: "M1".into(),
nodes: ["g".into(), "d".into(), "s".into()],
kind: sindr_devices::mosfet::MosfetKind::Nmos,
params: sindr_devices::mosfet::MosfetParams::default(),
parasitic_caps: Some(MosfetParasiticCaps {
cgs: 100e-12,
cgd: 50e-12,
}),
}],
};
assert!(
circuit.has_reactive_elements(),
"MOSFET with cgs>0 should be reactive"
);
}
#[cfg(feature = "serde")]
#[test]
fn bjt_serde_roundtrip() {
let json = r#"{
"components": [
{"type": "voltage_source", "id": "V1", "nodes": ["n1", "0"], "voltage": 10.0},
{"type": "resistor", "id": "RC", "nodes": ["n1", "n2"], "resistance": 1000.0},
{"type": "resistor", "id": "RB", "nodes": ["n1", "n3"], "resistance": 100000.0},
{"type": "bjt", "id": "Q1", "nodes": ["n3", "n2", "0"], "kind": "npn", "bf": 100}
],
"ground_node": "0"
}"#;
let circuit: Circuit = serde_json::from_str(json).unwrap();
assert_eq!(circuit.components.len(), 4);
let bjt = &circuit.components[3];
assert_eq!(bjt.id(), "Q1");
assert_eq!(bjt.nodes().len(), 3);
assert_eq!(bjt.nodes()[0], "n3"); assert_eq!(bjt.nodes()[1], "n2"); assert_eq!(bjt.nodes()[2], "0"); }
#[cfg(feature = "serde")]
#[test]
fn bjt_default_bf() {
let json = r#"{
"components": [
{"type": "bjt", "id": "Q1", "nodes": ["b", "c", "e"], "kind": "npn"}
],
"ground_node": "0"
}"#;
let circuit: Circuit = serde_json::from_str(json).unwrap();
match &circuit.components[0] {
CircuitElement::Bjt { bf, .. } => assert_eq!(*bf, 100.0),
_ => panic!("Expected Bjt variant"),
}
}
#[test]
fn has_nonlinear_elements_true_with_bjt() {
let circuit = Circuit {
ground_node: "0".into(),
components: vec![CircuitElement::Bjt {
id: "Q1".into(),
nodes: ["b".into(), "c".into(), "e".into()],
kind: sindr_devices::bjt::BjtKind::Npn,
bf: 100.0,
temperature: 300.15,
parasitic_caps: None,
}],
};
assert!(circuit.has_nonlinear_elements());
}
#[test]
fn relay_has_nonlinear_returns_true() {
let circuit = Circuit {
ground_node: "0".into(),
components: vec![CircuitElement::Relay {
id: "K1".into(),
nodes: ["coil_p".into(), "0".into(), "c1".into(), "c2".into()],
coil_resistance: 500.0,
pickup_voltage: 5.0,
inductance: 0.0,
}],
};
assert!(circuit.has_nonlinear_elements());
}
#[test]
fn relay_with_inductance_has_reactive_elements() {
let circuit = Circuit {
ground_node: "0".into(),
components: vec![CircuitElement::Relay {
id: "K1".into(),
nodes: ["coil_p".into(), "0".into(), "c1".into(), "c2".into()],
coil_resistance: 500.0,
pickup_voltage: 5.0,
inductance: 0.1,
}],
};
assert!(circuit.has_reactive_elements());
}
#[test]
fn relay_without_inductance_not_reactive() {
let circuit = Circuit {
ground_node: "0".into(),
components: vec![CircuitElement::Relay {
id: "K1".into(),
nodes: ["coil_p".into(), "0".into(), "c1".into(), "c2".into()],
coil_resistance: 500.0,
pickup_voltage: 5.0,
inductance: 0.0,
}],
};
assert!(!circuit.has_reactive_elements());
}
#[test]
fn pushbutton_has_nonlinear_returns_false() {
let circuit = Circuit {
ground_node: "0".into(),
components: vec![CircuitElement::Pushbutton {
id: "PB1".into(),
nodes: ["n1".into(), "0".into()],
closed: true,
}],
};
assert!(!circuit.has_nonlinear_elements());
}
#[cfg(feature = "serde")]
#[test]
fn relay_default_coil_resistance() {
let json = r#"{
"components": [
{"type": "relay", "id": "K1", "nodes": ["cp", "cn", "c1", "c2"], "pickup_voltage": 5.0}
],
"ground_node": "0"
}"#;
let circuit: Circuit = serde_json::from_str(json).unwrap();
match &circuit.components[0] {
CircuitElement::Relay {
coil_resistance, ..
} => {
assert_eq!(*coil_resistance, 500.0)
}
_ => panic!("Expected Relay variant"),
}
}
#[cfg(feature = "serde")]
#[test]
fn potentiometer_serde_roundtrip() {
let json = r#"{
"components": [
{"type": "potentiometer", "id": "P1", "nodes": ["top", "wiper", "bot"], "resistance": 10000.0, "position": 0.5}
],
"ground_node": "0"
}"#;
let circuit: Circuit = serde_json::from_str(json).unwrap();
assert_eq!(circuit.components.len(), 1);
let p1 = &circuit.components[0];
assert_eq!(p1.id(), "P1");
assert_eq!(p1.nodes().len(), 3);
}
#[cfg(feature = "serde")]
#[test]
fn zener_diode_serde_roundtrip() {
let json = r#"{
"components": [
{"type": "zener_diode", "id": "Z1", "nodes": ["n1", "0"], "vz": 5.1}
],
"ground_node": "0"
}"#;
let circuit: Circuit = serde_json::from_str(json).unwrap();
assert_eq!(circuit.components.len(), 1);
match &circuit.components[0] {
CircuitElement::ZenerDiode { id, vz, .. } => {
assert_eq!(id, "Z1");
assert_eq!(*vz, 5.1);
}
_ => panic!("Expected ZenerDiode variant"),
}
assert!(circuit.has_nonlinear_elements());
}
#[test]
fn op_amp_in_count_voltage_sources() {
let circuit = Circuit {
ground_node: "0".into(),
components: vec![
CircuitElement::OpAmp {
id: "U1".into(),
nodes: ["in_p".into(), "in_m".into(), "out".into()],
v_pos: 15.0,
v_neg: -15.0,
},
CircuitElement::Comparator {
id: "U2".into(),
nodes: ["in_p".into(), "in_m".into(), "out2".into()],
v_pos: 5.0,
v_neg: 0.0,
},
],
};
assert_eq!(circuit.count_voltage_sources(), 2);
assert!(!circuit.has_nonlinear_elements());
}
#[cfg(feature = "serde")]
#[test]
fn op_amp_default_supply_rails() {
let json = r#"{
"components": [
{"type": "op_amp", "id": "U1", "nodes": ["inp", "inm", "out"]}
],
"ground_node": "0"
}"#;
let circuit: Circuit = serde_json::from_str(json).unwrap();
match &circuit.components[0] {
CircuitElement::OpAmp { v_pos, v_neg, .. } => {
assert_eq!(*v_pos, 15.0);
assert_eq!(*v_neg, -15.0);
}
_ => panic!("Expected OpAmp variant"),
}
}
}