Validation
Every reference circuit is solved twice: against its closed-form answer, and again in ngspice. The numbers below come straight from the test harness, failures included.
4,169
checks passed
0
failures
13 of 14
circuits agree with ngspice
9
suites
Open issues
- BJT emitter follower: differs from ngspice. DIVERGES from ngspice-45.2+, 16.1% on Ic(Q1) at 0.000 (spp 0.006219 vs ngspice 0.005217), treat neither number as verified
Reference circuits
Worst error against the closed-form answer, and whether ngspice agrees. Open a circuit for every measurement and its deck.
Resistive dividerDC network.op3.2e-7%Agrees
5 V across 10 kΩ + 4.7 kΩ. Reference: Vo = Vin·R2/(R1+R2). The most boring circuit in the report and the one that would catch a sign or stamping error instantly.
| Measurement | Closed form | Spice++ | Error |
|---|---|---|---|
| V(o) | 1.59864 V | 1.59864 V | 3.2e-7% |
cross-checked vs ngspice-45.2+, max Δ 0% across 3 signals
.op: compared, largest difference 0%
.op: I(R1), I(R2) excluded, this ngspice build exposes no resistor-current vector, so adapt.ts derives them from the node voltages; comparing them would add no independent information
55a37a474209ec91Download .op deck
RC low-passFirst-order filter.ac, .tran0.0024%Agrees
R = 1 kΩ, C = 1 µF (fc = 159.15 Hz). References: |H| = 1/√(1+(f/fc)²), ∠H = −atan(f/fc), and the step response 1−e^{−t/τ}.
| Measurement | Closed form | Spice++ | Error |
|---|---|---|---|
| |H| at 0.1·fc | 0.995037 | 0.995037 | 9.9e-8% |
| |H| at 1·fc | 0.707118 | 0.707118 | 5.0e-8% |
| |H| at 10·fc | 0.0995068 | 0.0995068 | 9.9e-10% |
| ∠H at fc | -44.9991 ° | -44.9991 ° | 6.4e-8% |
| V(o) at 1τ | 0.632121 V | 0.632106 V | 0.0024% |
| V(o) at 5τ | 0.993262 V | 0.993262 V | 4.9e-6% |
cross-checked vs ngspice-45.2+, max Δ 0.0043% across 6 signals across 2 analyses
.ac dec 40 1.5915→15915 Hz: compared, largest difference 0%
.tran 0.00001s/0.006s: compared, largest difference 0.0043%
.ac dec 40 1.5915→15915 Hz: I(R1) excluded, this ngspice build exposes no resistor-current vector, so adapt.ts derives them from the node voltages; comparing them would add no independent information
.tran 0.00001s/0.006s: 1 TS signal(s) have no ngspice counterpart and were NOT cross-checked (I(C1))
.tran 0.00001s/0.006s: I(R1) excluded, this ngspice build exposes no resistor-current vector, so adapt.ts derives them from the node voltages; comparing them would add no independent information
061610ec7af5647eDownload .ac dec 40 1.5915→15915 Hz deckDownload .tran 0.00001s/0.006s deck
RC high-passFirst-order filter.ac9.9e-8%Agrees
Same R and C, output taken across R. References: |H| = (f/fc)/√(1+(f/fc)²), ∠H = 90° − atan(f/fc).
| Measurement | Closed form | Spice++ | Error |
|---|---|---|---|
| |H| at 0.1·fc | 0.0995007 | 0.0995007 | 9.9e-8% |
| |H| at 1·fc | 0.707096 | 0.707096 | 5.0e-8% |
| |H| at 10·fc | 0.995037 | 0.995037 | 9.9e-10% |
| ∠H at fc | 45.0009 ° | 45.0009 ° | 6.4e-8% |
cross-checked vs ngspice-45.2+, max Δ 0% across 3 signals
.ac dec 40 1.5915→15915 Hz: compared, largest difference 0%
.ac dec 40 1.5915→15915 Hz: I(R1) excluded, this ngspice build exposes no resistor-current vector, so adapt.ts derives them from the node voltages; comparing them would add no independent information
df6453fc4085145eDownload .ac dec 40 1.5915→15915 Hz deck
Series RLC stepSecond-order transient.tran0.00042%Agrees
L = 1 mH, C = 1 µF, R = 10 Ω (ζ = 0.158). References: peak overshoot exp(−πζ/√(1−ζ²)) and a final value of exactly 1 V. Overshoot is read off a discrete record, so a fraction of a percent of the residual is sampling, not solver error.
| Measurement | Closed form | Spice++ | Error |
|---|---|---|---|
| peak overshoot | 0.604679 V | 0.604677 V | 0.00042% |
| final value | 1.00000 V | 1.00000 V | 2.3e-5% |
cross-checked vs ngspice-45.2+, max Δ 0.0018% across 5 signals
.tran 1e-7s/0.003s: compared, largest difference 0.0018%
.tran 1e-7s/0.003s: 1 TS signal(s) have no ngspice counterpart and were NOT cross-checked (I(C1))
.tran 1e-7s/0.003s: I(R1) excluded, this ngspice build exposes no resistor-current vector, so adapt.ts derives them from the node voltages; comparing them would add no independent information
61fbcd5922c5c5a3Download .tran 1e-7s/0.003s deck
Parallel RLC tankResonance.ac1.0e-6%Agrees
1 A AC into R‖L‖C with L = 1 mH, C = 1 µF, R = 10 kΩ. At f0 = 1/(2π√LC) the reactances cancel exactly and |Z| = R; the swept grid puts a point on f0 so the peak is captured rather than straddled.
| Measurement | Closed form | Spice++ | Error |
|---|---|---|---|
| |Z| at f0 | 10000.0 Ω | 10000.0 Ω | 1.0e-6% |
cross-checked vs ngspice-45.2+, max Δ 5.7e-6% across 2 signals
.ac lin 401 4832.921210448703→5232.921210448703 Hz: compared, largest difference 5.7e-6%
.ac lin 401 4832.921210448703→5232.921210448703 Hz: I(R1) excluded, this ngspice build exposes no resistor-current vector, so adapt.ts derives them from the node voltages; comparing them would add no independent information
af8f93cac792c668Download .ac lin 401 4832.921210448703→5232.921210448703 Hz deck
RL first-order stepFirst-order transient.tran0.0018%Agrees
L = 10 mH into R = 100 Ω (τ = 100 µs). Reference: iL(t) = (V/R)(1−e^{−t/τ}). Exercises the inductor branch current, which is a solved unknown rather than a derived one.
| Measurement | Closed form | Spice++ | Error |
|---|---|---|---|
| iL at 1τ | 0.00632121 A | 0.00632109 A | 0.0018% |
| iL at 5τ | 0.00993262 A | 0.00993262 A | 1.3e-6% |
cross-checked vs ngspice-45.2+, max Δ 0.0041% across 4 signals
.tran 0.000001s/0.001s: compared, largest difference 0.0041%
.tran 0.000001s/0.001s: I(R1) excluded, this ngspice build exposes no resistor-current vector, so adapt.ts derives them from the node voltages; comparing them would add no independent information
29ba30956dc557c3Download .tran 0.000001s/0.001s deck
Coupled inductorsMagnetics.ac1.3e-11%Agrees
L1 = 1 H, L2 = 4 H, k = 0.999, secondary effectively open (1 GΩ). Reference: the open-circuit voltage ratio of a coupled pair is exactly k·√(L2/L1), the ideal-transformer turns ratio scaled by the coupling coefficient.
| Measurement | Closed form | Spice++ | Error |
|---|---|---|---|
| V(s)/V(p) | 1.99800 | 1.99800 | 1.3e-11% |
cross-checked vs ngspice-45.2+, max Δ 0% across 6 signals
.ac dec 5 1000→10000 Hz: compared, largest difference 0%
.ac dec 5 1000→10000 Hz: I(RL), I(Rs) excluded, this ngspice build exposes no resistor-current vector, so adapt.ts derives them from the node voltages; comparing them would add no independent information
6add9f1d2e9dfdabDownload .ac dec 5 1000→10000 Hz deck
Inverting amplifier ×10Op-amp.op0.0112%Agrees
R1 = 1 kΩ, R2 = 10 kΩ, Vin = 0.3 V. Reference is the IDEAL −R2/R1 result; the residual error is the real finite-gain term (1+R2/R1)/A0 ≈ 1.1e−4 with A0 = 1e5, so a near-zero error here would actually indicate a missing non-ideality.
| Measurement | Closed form | Spice++ | Error |
|---|---|---|---|
| V(o) | -3.00000 V | -2.99966 V | 0.0112% |
cross-checked vs ngspice-45.2+, max Δ 0% across 4 signals
.op: compared, largest difference 0%
this circuit contains an ideal-ish OPAMP element, which deck.ts transcribes into ngspice as an equivalent B-source macromodel, agreement there validates the two SOLVERS on the same device equations, not two independent device models
.op: 6 TS signal(s) have no ngspice counterpart and were NOT cross-checked (@U1[gloc], @U1[region], @U1[vdiff], @U1[vsat], V(OPout:U1), V(OPx:U1))
.op: I(R1), I(R2) excluded, this ngspice build exposes no resistor-current vector, so adapt.ts derives them from the node voltages; comparing them would add no independent information
12c464d3923529b9Download .op deck
Non-inverting amplifier ×10Op-amp.op0.0103%Agrees
R1 = 1 kΩ to ground, R2 = 9 kΩ feedback, Vin = 0.4 V. Reference: Vo = (1 + R2/R1)·Vin, again ideal, so the residual is the finite-gain term.
| Measurement | Closed form | Spice++ | Error |
|---|---|---|---|
| V(o) | 4.00000 V | 3.99959 V | 0.0103% |
cross-checked vs ngspice-45.2+, max Δ 0% across 4 signals
.op: compared, largest difference 0%
this circuit contains an ideal-ish OPAMP element, which deck.ts transcribes into ngspice as an equivalent B-source macromodel, agreement there validates the two SOLVERS on the same device equations, not two independent device models
.op: 6 TS signal(s) have no ngspice counterpart and were NOT cross-checked (@U1[gloc], @U1[region], @U1[vdiff], @U1[vsat], V(OPout:U1), V(OPx:U1))
.op: I(R1), I(R2) excluded, this ngspice build exposes no resistor-current vector, so adapt.ts derives them from the node voltages; comparing them would add no independent information
44890956ecfd2f70Download .op deck
Sallen–Key low-passActive filter.ac0.103%Agrees
Equal-R/C unity-gain Sallen-Key, R = 1 kΩ, C = 159.155 nF (fc ≈ 1 kHz, Q = 0.5). Reference is the exact transfer function ω0²/((ω0²−ω²) + j·2ω0ω) with ω0 = 1/RC, not a corner-frequency approximation.
| Measurement | Closed form | Spice++ | Error |
|---|---|---|---|
| |H| at 0.1·fc | 0.990099 | 0.990099 | 5.0e-5% |
| |H| at 1·fc | 0.500000 | 0.499992 | 0.0016% |
| |H| at 10·fc | 0.00990098 | 0.00989077 | 0.103% |
cross-checked vs ngspice-45.2+, max Δ 0.745% across 5 signals
.ac dec 30 10→100000 Hz: compared, largest difference 0.745%
this circuit contains an ideal-ish OPAMP element, which deck.ts transcribes into ngspice as an equivalent B-source macromodel, agreement there validates the two SOLVERS on the same device equations, not two independent device models
.ac dec 30 10→100000 Hz: 2 TS signal(s) have no ngspice counterpart and were NOT cross-checked (V(OPout:U1), V(OPx:U1))
.ac dec 30 10→100000 Hz: I(R1), I(R2) excluded, this ngspice build exposes no resistor-current vector, so adapt.ts derives them from the node voltages; comparing them would add no independent information
9a0c2b618a8fc253Download .ac dec 30 10→100000 Hz deck
Diode I–V sweepNonlinear device.dc0.00019%Agrees
1N4148-class diode in series with 100 Ω, swept 0→1 V. Reference: Newton solution of Vin = I·(R+Rs) + N·Vt·ln(I/Is + 1) using the model's own Is/N/Rs, the same physics solved by a scalar root-find instead of MNA.
| Measurement | Closed form | Spice++ | Error |
|---|---|---|---|
| V(diode) at Vin=0.40 V | 0.398345 V | 0.398345 V | 1.9e-8% |
| V(diode) at Vin=0.60 V | 0.551581 V | 0.551581 V | 9.7e-9% |
| V(diode) at Vin=0.80 V | 0.613479 V | 0.613479 V | 1.3e-7% |
| V(diode) at Vin=1.00 V | 0.643765 V | 0.643766 V | 0.00019% |
cross-checked vs ngspice-45.2+, max Δ 0.026% across 3 signals
.dc V1 0→1: compared, largest difference 0.0258%
.dc V1 0→1: 7 TS signal(s) have no ngspice counterpart and were NOT cross-checked (@D1[gd], @D1[id], @D1[rd], @D1[region], @D1[vd], I(D1), V(Dint:D1))
.dc V1 0→1: I(R1) excluded, this ngspice build exposes no resistor-current vector, so adapt.ts derives them from the node voltages; comparing them would add no independent information
50f6c1b2675ce604Download .dc V1 0→1 deck
BJT emitter followerNonlinear device.op0.0072%Diverges
NPN, Vb = 6 V, RE = 1 kΩ, Vcc = 12 V, Early voltage set to infinity so the closed form is exact. Reference: Newton solution of Ie = Is(e^{Vbe/NeVt}−1)(1+1/Bf) = (Vb−Vbe)/RE.
| Measurement | Closed form | Spice++ | Error |
|---|---|---|---|
| V(e) | 5.24260 V | 5.24223 V | 0.0072% |
| Ie | 0.00524260 A | 0.00524223 A | 0.0072% |
DIVERGES from ngspice-45.2+, 16.1% on Ic(Q1) at 0.000 (spp 0.006219 vs ngspice 0.005217), treat neither number as verified
.op: compared, largest difference 16.1%
.op: 17 TS signal(s) have no ngspice counterpart and were NOT cross-checked (@Q1[beta], @Q1[gm], @Q1[gmu], @Q1[go], @Q1[gpi], @Q1[ib], @Q1[ic], @Q1[ie], @Q1[region], @Q1[ro], @Q1[rpi], @Q1[vbc], @Q1[vbe], @Q1[vce], I(Q1), Ib(Q1), Ie(Q1))
.op: I(RE) excluded, this ngspice build exposes no resistor-current vector, so adapt.ts derives them from the node voltages; comparing them would add no independent information
6c913e221fb98100Download .op deck
NMOS common sourceNonlinear device.op5.0e-8%Agrees
Level-1 NMOS, Vto = 1 V, Kp = 1 mA/V², W/L = 10, λ = 0, Vgs = 2.5 V, RD = 500 Ω, Vdd = 10 V. Saturation is verified (Vds > Vov), so Id = ½·Kp·(W/L)·Vov² and Vd = Vdd − Id·RD are exact square-law.
| Measurement | Closed form | Spice++ | Error |
|---|---|---|---|
| Id | 0.0112500 A | 0.0112500 A | 0% |
| V(d) | 4.37500 V | 4.37500 V | 5.0e-8% |
cross-checked vs ngspice-45.2+, max Δ 0% across 5 signals
.op: compared, largest difference 0%
.op: 11 TS signal(s) have no ngspice counterpart and were NOT cross-checked (@M1[gds], @M1[gm], @M1[gmb], @M1[id], @M1[region], @M1[ro], @M1[vbs], @M1[vds], @M1[vgs], @M1[vth], I(M1))
.op: I(RD) excluded, this ngspice build exposes no resistor-current vector, so adapt.ts derives them from the node voltages; comparing them would add no independent information
fe209547a017ce11Download .op deck
VCVS gain blockControlled source.op0%Agrees
E-element with gain 4 driving 1 kΩ. Reference: Vo = 4·Vin exactly. A pure linear-algebra check on the extra branch equation a VCVS adds to the MNA matrix.
| Measurement | Closed form | Spice++ | Error |
|---|---|---|---|
| V(o) | 2.00000 V | 2.00000 V | 0% |
cross-checked vs ngspice-45.2+, max Δ 0% across 3 signals
.op: compared, largest difference 0%
.op: I(RL) excluded, this ngspice build exposes no resistor-current vector, so adapt.ts derives them from the node voltages; comparing them would add no independent information
7984be3e35157938Download .op deck
Suites
| Suite | Passed | Failed |
|---|---|---|
| Tier-A ECE acceptance, every textbook class vs closed form AND vs ngspice | 109 / 109 | 0 |
| Independent numerical oracle, random networks vs a from-scratch MNA | 522 / 522 | 0 |
| Analytic golden, Sallen-Key/MFB H(jω), RLC regimes, R-2R, rectifier Fourier | 317 / 317 | 0 |
| Circuit-theory & device-physics laws | 23 / 23 | 0 |
| Conservation laws, KCL / Tellegen / energy / charge / passivity | 1,200 / 1,200 | 0 |
| Metamorphic invariance, series/parallel/Y-Δ/source transform/relabel | 1,558 / 1,558 | 0 |
| Cross-method consistency, .op = .tran(∞), .ac = DFT(.tran), integration order | 338 / 338 | 0 |
| Loop gain & stability margins, Middlebrook injection vs closed form | 21 / 21 | 0 |
| Second opinion, the cross-engine agreement machinery itself | 81 / 81 | 0 |
Known limitations
- MOSFETs are Level 1 (Shichman-Hodges)
- Square law with body effect, channel-length modulation, a subthreshold region and overlap capacitances. No BSIM, EKV, PSP or foundry PDKs. Right for board-level and coursework analog, wrong for on-chip design. Model cards that declare another level are rejected, not silently degraded.
- BJTs are Ebers-Moll with Early voltage
- Forward and reverse injection, emission coefficients, Vaf and Var, ohmic resistances, depletion capacitances and standard temperature scaling. Not Gummel-Poon: no high-injection knee and no quasi-saturation.
- No RF or distributed elements
- No S-parameters, Touchstone import, Smith charts or transmission lines. Lumped LC matching networks simulate correctly; anything where geometry matters is not modelled.
- No harmonic balance or periodic steady state
- Steady state is reached by running .tran until the transient decays. Fine for the circuits here, slow for a high-Q oscillator or a PLL.
- Noise analysis is not cross-checked
- The ngspice adapter does not map noise vectors back to Spice++ signal names, so .noise results are not compared rather than compared against an empty set.
- Switching converters are not in this reference set
- Buck, boost and buck-boost converters simulate with the switch and VDMOS models and have their own test suite, but none of them is among the reference circuits cross-checked against ngspice on this page.
- THD below about 0.05% is a sampling limit
- A .tran record is adaptively stepped and must be resampled before the FFT. The interpolation sets a floor near −66 dBc at 100 samples per cycle, so smaller THD figures describe the sampling, not the circuit.
- Op-amps are the same macromodel on both sides
- The OPAMP element is transcribed into the ngspice deck as an equivalent behavioural network, so agreement on an op-amp circuit validates the two solvers, not two independent op-amp models.
- Parameter sweeps are compared at nominal values
- The ngspice deck compiler emits no .step, so a swept netlist is cross-checked at its nominal values only.
- Differences under 1 µV or 1 nA count as zero
- Both engines add GMIN for conditioning, so a branch carrying no current reads 0 A in one and a few picoamps in the other. Comparing those would report a false divergence, so signals that live entirely below these levels are not cross-checked.
- Resistor currents are excluded from agreement counts
- This ngspice build does not expose resistor currents, so they are derived from node voltages on that side and left out of every count rather than inflating it.
Reproduce it
npm install npx tsx scripts/gen_validation.ts --full
Generated 2026-07-28 05:33:06 UTC, commit 4b70d76 (uncommitted changes), engine spp-ts-1.0, oracle ngspice-45.2+.