sindr
MNA (Modified Nodal Analysis) circuit solver. Build a circuit, call solve_circuit, get voltages, currents, and power for every component.
Supports: resistive DC, nonlinear DC (Newton-Raphson), transient (backward Euler), AC analysis, DC sweep, and temperature sweep. The solver picks the right path automatically based on what components are in the circuit.
Device physics (BJT, MOSFET, diode models) live in the companion crate sindr-devices.
Add to your project
[]
= { = "../sindr" }
Serde support is on by default. To disable:
= { = "../sindr", = false }
Quick start
use ;
let circuit = Circuit ;
let result = solve_circuit?;
println!; // 6.667 V
println!;
Building circuits
Every component has an id (arbitrary string) and nodes (node names as strings). One node must match ground_node — it is held at 0 V.
Passive components
Resistor
Capacitor
Inductor
Capacitors and inductors trigger transient analysis automatically. In pure DC (no reactive elements, no waveforms), they are treated as open circuits.
Sources
VoltageSource
CurrentSource
nodes[0] is the positive terminal for voltage sources; current flows from nodes[0] toward nodes[1] for current sources.
A Waveform (sine, square, sawtooth, pulse) on any source triggers transient analysis. See waveform.rs for the Waveform enum.
Switches
Switch
Pushbutton
Modeled as a 0.01 Ω resistor when closed, 1 GΩ when open.
Diodes
Diode // silicon, Vf ≈ 0.7 V
Led // "red"|"green"|"blue"|"yellow"|"white"
ZenerDiode // breakdown voltage
SchottkyDiode // Vf ≈ 0.3 V
Photodiode // irradiance in W
All diodes use Newton-Raphson automatically when present. Each diode variant accepts an optional temperature: f64 field (K, default 300.15 K) which scales IS using the SPICE temperature formula.
Varactor diode
Varactor
Varactors are purely reactive — treated as open circuit in DC, and stamped as a voltage-dependent capacitor C_j(V) = cj0 / (1 - V/phi)^m each transient timestep. Always triggers transient analysis.
Transistors
// BJT — nodes: [base, collector, emitter]
Bjt
// MOSFET — nodes: [gate, drain, source]
Mosfet
// IGBT — nodes: [gate, collector, emitter]
Igbt
BjtKind, MosfetKind, BjtParasiticCaps, MosfetParasiticCaps, IgbtParams are re-exported from sindr directly.
When parasitic_caps is set, Cbe/Cbc (BJT) or Cgs/Cgd (MOSFET) are stamped as Backward Euler capacitor companions each transient timestep, with per-junction voltage state tracked internally. This automatically triggers transient analysis.
NPN common-emitter example:
use ;
let circuit = Circuit ;
let result = solve_circuit?;
let q1 = result.bjt_results.iter.find.unwrap;
println!;
Transformer (coupled inductors)
Transformer
Mutual inductance M = k·√(L1·L2). In DC analysis both windings are stamped as short circuits. In transient, two branch current unknowns are added to the MNA system. k=1 is mathematically singular — use k≤0.999.
Controlled sources
// Voltage-controlled voltage source: V_out = gain × V_ctrl
Vcvs
// Voltage-controlled current source: I_out = gm × V_ctrl
Vccs
// Current-controlled voltage source: V_out = rm × I_ctrl
Ccvs
// Current-controlled current source: I_out = alpha × I_ctrl
Cccs
control_source is the id of the voltage source whose branch current is sensed.
Op-amp / Comparator
// nodes: [in_plus, in_minus, out]
OpAmp // supply rails, default ±15 V
Comparator // default 5 V / 0 V
Both are modeled as a high-gain VCVS (gain = 1×10⁵) that saturates at the supply rails.
Sensors / misc
Photoresistor // 0.0 (dark, ~1 MΩ) – 1.0 (bright, ~1 kΩ)
Thermistor // Kelvin, default 298.15 K (25 °C)
Potentiometer // position 0.0–1.0
Relay
When inductance > 0, the relay coil is modeled as an RL circuit in transient analysis (L stamped as a Backward Euler inductor companion). This triggers transient automatically.
Reading results
solve_circuit returns Result<SimulationResult, SimError>.
For transient circuits, result.transient contains a time series:
DC sweep
Sweep a voltage source across a range and collect operating points at each step.
use ;
let sweep = dc_sweep?;
let v_curve = sweep.node_voltage_curve;
let i_curve = sweep.component_current_curve;
Temperature sweep
Solve a circuit at a series of junction temperatures and collect operating points. Useful for BJT Ic vs. temperature curves and thermal characterisation.
use ;
// All junction devices (BJT, diode, LED, Zener, Schottky, Photodiode) have their
// temperature field overridden at each step.
let result = temperature_sweep?; // 250 K → 350 K, 11 points
for point in &result.points
num_steps must be ≥ 2.
Error handling
ConvergenceFailed means Newton-Raphson did not converge — usually caused by a nonlinear circuit with no DC path to ground, or component values far outside typical operating ranges. The max_step_volts field gives the largest per-node Newton step (V) on the final iteration, useful for distinguishing slow convergence from genuine divergence. (Note: this is a step magnitude max_i |V_new[i] − V_prev[i]|, not a KCL residual |F(x)|.)
Solver routing
solve_circuit picks a path automatically — you never select it manually:
| Circuit contains | Solver used |
|---|---|
| Only linear elements | Direct MNA (LU factorisation) |
| Nonlinear elements (diodes, BJTs, MOSFETs, IGBTs…) | Newton-Raphson iteration |
| Reactive elements (C, L, Varactor, Transformer) or waveform sources | Backward Euler transient |
| Both reactive and nonlinear | Transient with per-step Newton-Raphson |
The transient solver uses adaptive timestepping: dt halves on Newton-Raphson convergence failure, and doubles after 5 consecutive successful timesteps (clamped to 10× the initial dt).
Serde
With the default serde feature, Circuit and SimulationResult (and all nested types) implement Serialize/Deserialize. Component types use snake_case tags:
Fields with defaults (bf, temperature, k, coil_resistance, position, v_pos, v_neg, parasitic_caps) can be omitted from JSON and will take their defaults.