From e8537eec6f4b8173b58182640d2797d34edf2281 Mon Sep 17 00:00:00 2001 From: davidmonterocrespo24 Date: Fri, 15 May 2026 19:19:05 +0200 Subject: [PATCH] =?UTF-8?q?feat(sim):=20Phase=201c=20A1=20=E2=80=94=20defi?= =?UTF-8?q?ne=20SolverPort=20(hexagonal=20port)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First commit of the full migration to a single WASM-driven solver. Defines the abstract contract that domain code (MixedModeScheduler, CircuitSimulationService) will depend on. Adapters in ./adapters/ implement the port against concrete engines. Surface kept narrow: - init / loadCircuit / solve / alterSource / dispose - SolveAnalysis: op | tran | ac - SolveResult: vectors map + timeAxis + solveMs + warnings Domain types live in the port file (SolveVector, SolveResult) so the port has no upward dependency on ../types.ts. Adapters bridge between domain types and engine-specific shapes. Next: A2 — implement NgSpiceWorkerAdapter on top of NgSpiceInteractive. Then A3 (resolveTran), A4 (scheduler refactor), A5 (fake + tests). See velxio-prod/project/sim-mixedmode/phase-1c-migration-plan.md for the full sub-step roadmap. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/simulation/spice/ports/SolverPort.ts | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 frontend/src/simulation/spice/ports/SolverPort.ts diff --git a/frontend/src/simulation/spice/ports/SolverPort.ts b/frontend/src/simulation/spice/ports/SolverPort.ts new file mode 100644 index 00000000..ea4cb764 --- /dev/null +++ b/frontend/src/simulation/spice/ports/SolverPort.ts @@ -0,0 +1,95 @@ +/** + * SolverPort — the abstract contract for a SPICE solver as Velxio + * consumes it. This is a Hexagonal-style port: domain code depends on + * the interface, and adapters in ./adapters/ implement it against + * concrete WASM builds or test fakes. + * + * Design rationale: + * - The domain (MixedModeScheduler, CircuitSimulationService) must + * not depend on Web Worker semantics, the vendored WASM, or any + * particular ngspice version. Swapping engines (or going node- + * native in tests) only touches an adapter. + * - The port surface is intentionally narrow: load a netlist, solve + * an analysis, read named vectors, alter a source for incremental + * re-solves. Anything more specific to ngspice is below this line. + */ + +/** + * The analysis kinds a Velxio canvas can request. Mirrors + * `AnalysisMode` in ../types.ts but lives here so the port doesn't + * import from outside the SPICE subsystem. + */ +export type SolveAnalysis = + | { kind: 'op' } + | { kind: 'tran'; step: string; stop: string } + | { kind: 'ac'; sweep: 'dec' | 'oct' | 'lin'; points: number; fstart: number; fstop: number }; + +/** + * One vector — a named time / frequency series. For `.op` the array + * has length 1. For `.tran` / `.ac` it's the full waveform. + */ +export interface SolveVector { + /** Lower-case vector name as ngspice would emit it (e.g. `v(n0)`, `i(v1)`). */ + name: string; + /** Real samples, monotonically time-ordered for `.tran`. */ + real: Float64Array; + /** Imag samples — present only for `.ac` (complex frequency response). */ + imag: Float64Array | null; +} + +/** + * Result of a complete solve. Includes every vector the engine + * computed; consumers index by lower-case name (e.g. `v(n2)` for the + * voltage at net `n2`). + */ +export interface SolveResult { + analysis: SolveAnalysis; + /** Map keyed by vector name → vector. Always lower-case keys. */ + vectors: Map; + /** + * For `.tran`: the time axis in seconds, monotonically increasing. + * For `.op` / `.ac`: empty (length 0). + */ + timeAxis: Float64Array; + /** Wall-clock duration of the solve, in milliseconds. */ + solveMs: number; + /** Anything ngspice wrote to stderr during the solve. Empty when clean. */ + warnings: string[]; +} + +/** + * The port itself. An adapter (real WASM, fake, etc.) implements + * this; domain code accepts an instance via constructor injection. + * + * Lifecycle: + * 1. `init()` — boot the underlying engine (WASM, native, etc.). + * Idempotent; safe to await multiple times. + * 2. `loadCircuit(netlist)` — submit the SPICE netlist string. + * Replaces any previously loaded circuit. + * 3. `solve(analysis)` — run the requested analysis. Pure: doesn't + * mutate the circuit, only reads from the engine state. + * 4. `alterSource(name, dcValue)` — update a voltage source between + * solves. Used for MCU-edge re-resolves. + * 5. `dispose()` — release the engine (terminate worker, free heap). + * + * Concurrency: implementations are responsible for serialising + * concurrent calls. Callers may issue overlapping `solve()` calls; + * the adapter is free to queue them. + */ +export interface SolverPort { + init(): Promise; + loadCircuit(netlist: string): Promise; + solve(analysis: SolveAnalysis): Promise; + alterSource(name: string, dcValue: number): Promise; + dispose(): void; +} + +/** + * The set of vector names a caller is interested in. Adapters MAY + * optimise by only fetching these from the engine, but they're + * allowed to return more — the caller filters on read. + * + * Reserved for future optimisation: not currently honoured by any + * adapter, but the contract leaves the door open. + */ +export type VectorFilter = ReadonlySet | undefined;