Skip to content

[codex] Add ngspice backend and passive parasitics#50

Merged
mfiumara merged 1 commit into
mainfrom
codex-swappable-ngspice-passive-parasitics
May 16, 2026
Merged

[codex] Add ngspice backend and passive parasitics#50
mfiumara merged 1 commit into
mainfrom
codex-swappable-ngspice-passive-parasitics

Conversation

@mfiumara
Copy link
Copy Markdown
Owner

Summary

Adds a swappable core simulator backend API with an optional ngspice WASM adapter powered by eecircuit-engine.

Also completes passive capacitor and inductor modeling by resolving model/instance parameters and expanding parasitic equivalent networks for AC/transient analysis.

What changed

  • Added SimulationOptions.simulator, simulator adapter types, and createSimulator() routing.
  • Added WasmNgspiceSimulator with lazy eecircuit-engine import, programmatic Circuit serialization, include preprocessing, .op/.dc/.tran/.ac/.step result mapping, and stream expansion support.
  • Added optional peer dependency metadata and tsup external config for eecircuit-engine.
  • Added capacitor/inductor model parsing and resolution for model cards, geometry/temperature values, ESR/ESL/leakage, DCR/core loss, winding capacitance, Q/DF/SRF derived parameters.
  • Expanded passive parasitic models into primitive R/C/L networks during compile while exposing resolved metadata through IR.
  • Updated README docs and focused tests.

Validation

  • pnpm --dir packages/core exec vitest run src/simulators/simulator-backends.test.ts — 9 passed
  • pnpm --dir packages/core run lint
  • pnpm --dir packages/core test — 427 passed, 1 skipped
  • pnpm --dir packages/core run build
  • git diff --check

Notes

Unrelated untracked local files were intentionally not included: output.gif, packages/ui/.codex/.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 16, 2026

PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-05-16 13:05 UTC

@codecov
Copy link
Copy Markdown

codecov Bot commented May 16, 2026

Codecov Report

❌ Patch coverage is 78.06897% with 159 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.87%. Comparing base (a96ef87) to head (d4ae19b).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
packages/core/src/circuit.ts 57.59% 81 Missing ⚠️
packages/core/src/simulators/ngspice-wasm.ts 79.42% 50 Missing ⚠️
packages/core/src/parser/passive-parser.ts 77.35% 12 Missing ⚠️
packages/core/src/devices/passive-model.ts 93.61% 9 Missing ⚠️
packages/core/src/simulate.ts 88.52% 7 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #50      +/-   ##
==========================================
- Coverage   88.93%   87.87%   -1.06%     
==========================================
  Files          74       77       +3     
  Lines        5712     6419     +707     
  Branches     1289     1542     +253     
==========================================
+ Hits         5080     5641     +561     
- Misses        632      778     +146     
Flag Coverage Δ
core 87.87% <78.06%> (-1.06%) ⬇️
ui 87.87% <78.06%> (-1.06%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@mfiumara mfiumara marked this pull request as ready for review May 16, 2026 13:04
@mfiumara mfiumara merged commit 40cf87f into main May 16, 2026
7 of 10 checks passed
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d4ae19b87f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +67 to +70
const prepared = await prepareInput(input, options);

const warnings: SimulationWarning[] = [];
const result: SimulationResult = { warnings };
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate analyses before using ngspice backend

When simulator: 'ngspice-wasm' is selected, this path compiles the input but never applies the native validateCircuit check. For inputs with devices but no .op/.dc/.tran/.ac, runAnalyses() iterates over an empty analysis list and returns a successful { warnings: [] } result instead of the documented InvalidCircuitError, so invalid user netlists can appear to have simulated successfully.

Useful? React with 👍 / 👎.

Comment on lines +859 to +861
const model = desc.modelName ? this._models.get(desc.modelName) : undefined;
if (desc.modelName && !model) {
throw new Error(`Capacitor '${desc.name}' references unknown model '${desc.modelName}'`);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Normalize passive model lookups

SPICE model names are case-insensitive, but this lookup uses the passive's model name verbatim while addModel stores the original spelling. A valid netlist such as .model cstd C(CAP=1n) followed by C1 1 0 CSTD now throws references unknown model; normalize model keys on insertion and lookup so passive model support matches SPICE casing rules.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

View 6 additional findings in Devin Review.

Open in Devin Review

const trimmed = line.trim();
if (!trimmed || trimmed.toLowerCase() === '.end') continue;

const mosfetMatch = line.match(/^(M\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(NMOD|PMOD|NMOS\S*|PMOS\S*)\s*(.*)$/i);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MOSFET regex in adaptNetlistForNgspice fails on indented netlist lines due to ^ anchor on untrimmed input

The MOSFET 3→4 terminal adaptation regex at packages/core/src/simulators/ngspice-wasm.ts:310 matches against line (which includes leading whitespace) instead of trimmed. The ^ anchor in /^(M\S+)\s+.../ requires the MOSFET name at position 0, so any indented MOSFET line (e.g., M1 drain gate source NMOD W=1u) won't match. The line passes through unmodified as a 3-terminal MOSFET, which ngspice cannot parse (it requires 4 terminals), causing the simulation to fail. The trimmed variable is already computed at line 307 but not used for the match.

Suggested change
const mosfetMatch = line.match(/^(M\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(NMOD|PMOD|NMOS\S*|PMOS\S*)\s*(.*)$/i);
const mosfetMatch = trimmed.match(/^(M\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(NMOD|PMOD|NMOS\S*|PMOS\S*)\s*(.*)$/i);
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +109 to +110
wf.width ?? Infinity,
wf.period ?? Infinity,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 formatWaveform serializes PULSE width/period as literal "Infinity" — invalid SPICE syntax

When a PULSE source is created programmatically without explicit width or period, the defaults at packages/core/src/circuit.ts:109-110 are Infinity. The formatNumber(Infinity) function returns the string "Infinity" (since Number.isFinite(Infinity) is false, it falls through to String(Infinity) at line 75). This produces invalid SPICE output like PULSE(0 5 0 1e-12 1e-12 Infinity Infinity) from toNetlist(), which ngspice cannot parse. This primarily impacts the ngspice-wasm backend when a programmatic Circuit with an incomplete PULSE spec is passed as input.

Prompt for agents
The formatWaveform function in circuit.ts uses Infinity as the default for PULSE width and period (lines 109-110), and formatNumber (line 74-76) converts Infinity to the literal string "Infinity" which is not valid SPICE syntax.

To fix this, either:
1. Have formatNumber map Infinity to a very large number string (e.g. 1e99) that SPICE parsers accept, OR
2. Change the PULSE defaults from Infinity to a very large finite number, OR
3. Filter out Infinity values before serialization and omit trailing PULSE parameters that are at their defaults.

Option 1 is simplest: in formatNumber, add a check like if (value === Infinity) return '1e99' (or similar large number). This keeps the semantics close to the original Infinity intent while producing valid SPICE.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant