A zero-dependency TypeScript SPICE circuit simulator. Parse netlists and run DC, transient, and AC analysis directly in Node.js or the browser — no native binaries, no WASM.
Live Showcase — interactive demos with streaming parametric sweeps running entirely in your browser.
import { simulate } from '@spice-ts/core';
const result = await simulate(`
V1 1 0 DC 5
R1 1 2 1k
R2 2 0 1k
.op
.end
`);
console.log(result.dc?.voltage('2')); // 2.5npm install @spice-ts/coreRequires Node.js ≥ 20.
- DC operating point — Newton-Raphson with voltage limiting for convergence
- DC sweep —
.dctransfer curves and I-V characteristics - Transient analysis — backward Euler and trapezoidal integration with adaptive timestep
- AC small-signal — frequency sweep (dec/oct/lin) via complex LU solve
- Device models — R, C, L, V, I, Diode (Shockley), BJT (Ebers-Moll NPN/PNP), MOSFET (Level 1 Shichman-Hodges NMOS/PMOS), BSIM3v3 (LEVEL=49)
- Controlled sources — VCVS (E), VCCS (G), CCVS (H), CCCS (F) with DC, AC, and subcircuit support
- Sparse solver — Gilbert-Peierls LU with symbolic/numeric split, typed-array stamping, batch MOSFET evaluation. Competitive with ngspice-WASM on DC, AC, and nonlinear circuits
- Complex AC solver — native complex sparse LU (no 2n×2n real expansion)
- Subcircuits —
.subckt/.endsdefinitions withXdevice instantiation, nested expansion, parameterized subcircuits - Library support —
.includefile resolution,.lib/.endlsection selection (process corners),.paramexpressions with SI suffixes - Async parsing —
parseAsync()with platform-agnosticIncludeResolvercallback for loading external files - Streaming API —
simulateStream()yields results as anAsyncIterableIterator - Programmatic API — build circuits in code with
Circuit, or parse SPICE netlists withparse() - Typed errors —
ConvergenceError,SingularMatrixError,ParseError,CycleError, and more - Full TSDoc — every public API export has JSDoc comments for IDE hover-docs
import { simulate } from '@spice-ts/core';
// RC low-pass filter — transient step response
const result = await simulate(`
V1 in 0 PULSE(0 5 0 1n 1n 500u 1m)
R1 in out 1k
C1 out 0 1u
.tran 10u 5m
.end
`);
const { time, voltage } = result.transient!;
// time: Float64Array of timepoints
// voltage('out'): Float64Array of node voltagesimport { Circuit, simulate } from '@spice-ts/core';
const ckt = new Circuit();
ckt.addVoltageSource('V1', 'in', '0', { dc: 5 });
ckt.addResistor('R1', 'in', 'out', 1000);
ckt.addResistor('R2', 'out', '0', 1000);
ckt.addAnalysis('op');
const result = await simulate(ckt);
console.log(result.dc?.voltage('out')); // 2.5import { simulateStream } from '@spice-ts/core';
for await (const step of simulateStream(netlist)) {
if ('time' in step) {
console.log(`t=${step.time} V(out)=${step.voltages.get('out')}`);
}
}const result = await simulate(`
V1 in 0 AC 1
R1 in out 1k
C1 out 0 1u
.ac dec 10 1 100k
.end
`);
const freq = result.ac!.frequencies; // Float64Array
const mag = result.ac!.magnitude('out'); // Float64Array
const phase = result.ac!.phase('out'); // Float64Array (degrees)simulate(input: string | Circuit, options?: SimulationOptions): Promise<SimulationResult>Runs all analyses declared in the netlist/circuit. Returns a SimulationResult with .dc, .transient, and .ac fields (populated for each analysis present).
simulateStream(input: string | Circuit, options?: SimulationOptions): AsyncIterableIterator<TransientStep | ACPoint>Yields each timestep/frequency point as it is computed.
parse(netlist: string): CircuitParses a SPICE netlist into a Circuit object without running any analysis. Throws ParseError if the netlist contains .include or .lib directives — use parseAsync() for those.
parseAsync(netlist: string, resolver?: IncludeResolver): Promise<Circuit>Async variant of parse() that runs the preprocessor first, resolving .include, .lib/.endl, and .param directives. The optional resolver callback loads external files:
import { parseAsync } from '@spice-ts/core';
import { readFile } from 'fs/promises';
const ckt = await parseAsync(netlist, async (path) => {
return readFile(path, 'utf-8');
});The resolver is platform-agnostic — use fetch() in the browser, readFile() in Node, or return bundled strings for static assets.
| Option | Default | Description |
|---|---|---|
abstol |
1e-12 |
Absolute current tolerance (A) |
vntol |
1e-6 |
Absolute voltage tolerance (V) |
reltol |
1e-3 |
Relative tolerance |
maxIterations |
100 |
Max Newton-Raphson iterations (DC) |
maxTransientIterations |
50 |
Max NR iterations per timestep |
integrationMethod |
'trapezoidal' |
'euler' or 'trapezoidal' |
resolveInclude |
— | Async callback (path: string) => Promise<string> for .include/.lib |
Three-way comparison: spice-ts (pure TypeScript) vs eecircuit (ngspice compiled to WASM via EEcircuit-engine) vs ngspice (native C). Measured on Apple M4 Pro, Node.js v24. Run pnpm bench:compare to reproduce.
spice-ts uses a sparse LU solver (Gilbert-Peierls with symbolic/numeric split) and typed-array direct stamping. For DC analysis, it beats ngspice-WASM across all sizes:
| Circuit | Size | spice-ts | eecircuit (WASM) | ngspice (native) | vs eecircuit |
|---|---|---|---|---|---|
| Resistor ladder | 10 | 0.16 ms | 0.9 ms | 0.9 ms | 5.5x faster |
| Resistor ladder | 100 | 1.2 ms | 1.4 ms | 0.5 ms | 1.2x faster |
| Resistor ladder | 500 | 2.2 ms | 4.0 ms | 0.7 ms | 1.8x faster |
| Circuit | Size | spice-ts | eecircuit (WASM) | ngspice (native) | vs eecircuit |
|---|---|---|---|---|---|
| RC chain | 10 | 5.1 ms | 4.2 ms | 1.6 ms | 1.2x slower |
| RC chain | 50 | 14.7 ms | 17.1 ms | 4.0 ms | 1.2x faster |
| RC chain | 100 | 25.9 ms | 21.0 ms | 7.0 ms | 1.2x slower |
| LC ladder | 10 | 10.7 ms | 10.6 ms | 3.7 ms | ~parity |
| LC ladder | 50 | 41.6 ms | 35.9 ms | 11.2 ms | 1.2x slower |
spice-ts uses a native complex sparse LU solver — no 2n×2n real expansion. Faster than WASM across all sizes:
| Circuit | Size | spice-ts | eecircuit (WASM) | ngspice (native) | vs eecircuit |
|---|---|---|---|---|---|
| RC chain | 10 | 0.46 ms | 1.4 ms | 0.5 ms | 3.1x faster |
| RC chain | 50 | 1.7 ms | 4.1 ms | 0.7 ms | 2.4x faster |
| RC chain | 100 | 3.3 ms | 5.8 ms | 1.1 ms | 1.8x faster |
Batch MOSFET evaluation bypasses polymorphic dispatch — all transistors in a model group are evaluated in one tight loop with direct array writes:
| Circuit | spice-ts | eecircuit (WASM) | ngspice (native) | vs eecircuit |
|---|---|---|---|---|
| CMOS inv chain (5 stg) | 13.2 ms | 16.9 ms | 8.5 ms | 1.3x faster |
| CMOS inv chain (10 stg) | 21.7 ms | 28.4 ms | 13.3 ms | 1.3x faster |
| Ring oscillator (3 stg) | 25.7 ms | 29.8 ms | 14.5 ms | 1.2x faster |
| Ring oscillator (5 stg) | 37.6 ms | 40.5 ms | 20.8 ms | 1.1x faster |
| Ring oscillator (11 stg) | 84.3 ms | 69.8 ms | 37.4 ms | 1.2x slower |
Where spice-ts shines: DC (up to 5x faster than WASM), AC (1.6-3x faster via native complex sparse solver), nonlinear CMOS (1.1-1.3x faster), and small-medium transient (parity). All with zero dependencies — no WASM, no native binary, no process spawn. The only remaining gap is the largest ring oscillator (11-stage, 1.2x slower than WASM).
| Test | Metric | spice-ts | Expected | Error |
|---|---|---|---|---|
| RC step response | V(out) at t=τ | 3.151 V | 3.161 V | 0.29% |
| BJT CE amplifier | V(base) DC bias | 2.048 V | 2.105 V | 2.7% |
| RLC bandpass | peak frequency | 1585 Hz | 1592 Hz | 0.4% |
| RC ladder 5-stage | f-3dB | 12.76 Hz | 12.73 Hz | 0.23% |
| RLC resonance (transient) | oscillation frequency | 1579 Hz | 1592 Hz | 0.8%† |
†RLC transient resonance error is due to numerical damping in the trapezoidal integrator with the default timestep. Use a finer timestep or integrationMethod: 'euler' to reduce it.
Run pnpm bench:accuracy to see the full accuracy report including SPICE3 Quarles reference circuits.
- Large ring oscillators: The 11-stage ring oscillator is 1.2x slower than ngspice-WASM. This is near the fundamental TypeScript-vs-compiled-C overhead floor.
- BSIM3v3 MOSFET: Supported alongside Level 1 Shichman-Hodges. BSIM4, EKV not yet available.
git clone https://github.com/mfiumara/spice-ts
cd spice-ts
pnpm install
pnpm test # run all tests
pnpm build # build @spice-ts/core
pnpm bench # vitest bench (ops/sec, mean, p99 — no external deps)
pnpm bench:accuracy # accuracy vs analytical + ngspice diff (requires ngspice)
pnpm bench:compare # 3-way comparison: spice-ts vs eecircuit (WASM) vs ngspiceThe bench script uses vitest bench (backed by tinybench) for statistically sound ops/sec and latency metrics. The bench:compare script runs 16 circuits through all three engines and outputs a markdown table. Pass --json for machine-readable output or --no-ngspice to skip native ngspice.
See ROADMAP.md for planned features.
MIT