Skip to content

mfiumara/spice-ts

Repository files navigation

spice-ts

CI codecov npm @spice-ts/core npm @spice-ts/ui GitHub stars

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.5

Install

npm install @spice-ts/core

Requires Node.js ≥ 20.

Features

  • DC operating point — Newton-Raphson with voltage limiting for convergence
  • DC sweep.dc transfer 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/.ends definitions with X device instantiation, nested expansion, parameterized subcircuits
  • Library support.include file resolution, .lib/.endl section selection (process corners), .param expressions with SI suffixes
  • Async parsingparseAsync() with platform-agnostic IncludeResolver callback for loading external files
  • Streaming APIsimulateStream() yields results as an AsyncIterableIterator
  • Programmatic API — build circuits in code with Circuit, or parse SPICE netlists with parse()
  • Typed errorsConvergenceError, SingularMatrixError, ParseError, CycleError, and more
  • Full TSDoc — every public API export has JSDoc comments for IDE hover-docs

Usage

Netlist (SPICE format)

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 voltages

Programmatic (no netlist)

import { 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.5

Streaming

import { 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')}`);
  }
}

AC analysis

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)

API

simulate(input, options?)

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, options?)

simulateStream(input: string | Circuit, options?: SimulationOptions): AsyncIterableIterator<TransientStep | ACPoint>

Yields each timestep/frequency point as it is computed.

parse(netlist)

parse(netlist: string): Circuit

Parses 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, resolver?)

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.

SimulationOptions

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

Benchmarks

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.

DC Operating Point

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

Transient

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

AC Small-Signal

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

Nonlinear (CMOS / Ring Oscillators)

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).

Accuracy

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.

Limitations

  • 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.

Development

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 ngspice

The 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.

License

MIT

About

TypeScript-native SPICE circuit simulator

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors