Skip to content

feat(core): mutual inductance K, ic= ICs, .tran uic + Chua & Lin showcase#49

Open
mfiumara wants to merge 1 commit into
mainfrom
worktree-issue-48-chua-lin
Open

feat(core): mutual inductance K, ic= ICs, .tran uic + Chua & Lin showcase#49
mfiumara wants to merge 1 commit into
mainfrom
worktree-issue-48-chua-lin

Conversation

@mfiumara
Copy link
Copy Markdown
Owner

@mfiumara mfiumara commented May 10, 2026

Closes #48.

Summary

  • Mutual inductance (K-element): parses K1 La Lb k, stamps -M = -k·√(L_a·L_b) into off-diagonals of the C matrix at the two inductor branch rows.
  • Per-element initial conditions: ic= on capacitors and inductors.
  • .tran ... uic: builds a substituted DC system (caps-with-ic → V sources, inductors-with-ic → I sources, K dropped), solves it, and maps node voltages and branch currents back to the original layout. Inductor → I-source flips node order to match the inductor branch-current sign convention.
  • Trapezoidal bootstrap on UIC seed: the seeded solution doesn't satisfy the trapezoidal companion's history, so step 1 falls back to Backward Euler — same pattern used after waveform breakpoints.
  • Tokenizer: trailing unit letters (2H, 100Ohm, 1uF) no longer throw ParseError.
  • Showcase: Chua & Lin 8-7 (page 343) wired into a new "Benchmarks" group.

Why this circuit is interesting

It's a deliberate cross-section of the parts of SPICE that diverge between simulators:

  1. K-coupling matrix — three inductors with three K's. Sign and magnitude of M depends on dot convention; a sign flip reverses the secondary's response.
  2. uic semantics — without explicit ic=, some simulators force V/I to 0, others let topology decide. With C2 (no ic) on x, C12 (ic=2) on x–y, and C3 (ic=5) on y, the constraints aren't simultaneously satisfiable, so different policies pick different starts.
  3. Inductor cutset with mutual coupling — the L8/L9/L6 loop is conditionally singular as |k|→1 on any pair.
  4. Stiff multi-time-scale transient — trapezoidal rings on the abrupt t=0 discontinuity unless the engine bootstraps with BE.

Test plan

  • pnpm -r lint clean
  • pnpm -r test — 405/405 core, 206/206 ui
  • New mutual-inductor.test.ts: K parsing, K coupling alters dynamics, RC/RL UIC seed
  • Full Chua & Lin 200s sim runs in ~20ms
  • Showcase builds and bundles the new entry

🤖 Generated with Claude Code


Open in Devin Review

Adds the SPICE K-element for magnetically-coupled inductors, ic= initial
conditions on capacitors and inductors, and the .tran ... uic flag that
seeds the transient from those ICs instead of a DC operating point. Lets
spice-ts run the Chua & Lin 8-7 (page 343) benchmark — a classic SPICE
torture test where every simulator gives a slightly different answer
because it stresses K-coupling, IC handling, and stiff multi-time-scale
dynamics simultaneously. Wired into the showcase under "Benchmarks".

UIC seed builds a substituted DC system (caps with ic become V sources,
inductors with ic become I sources, K-elements drop) and maps the result
back to the original circuit's branch layout. Inductor → I-source flips
node order to match the inductor branch-current sign convention. The
seeded solution does not satisfy the trapezoidal companion's history, so
the driver bootstraps with Backward Euler on step 1 — same pattern used
after waveform breakpoints.

Tokenizer now ignores trailing unit letters after numeric values
("2H", "100Ohm", "1uF"); previously these threw ParseError.

Issue #48.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://mfiumara.github.io/spice-ts/pr-preview/pr-49/

Built to branch gh-pages at 2026-05-10 13:50 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 10, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.09%. Comparing base (b6b57ba) to head (2f26f77).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #49      +/-   ##
==========================================
+ Coverage   88.92%   89.09%   +0.17%     
==========================================
  Files          74       76       +2     
  Lines        5713     5798      +85     
  Branches     1290     1310      +20     
==========================================
+ Hits         5080     5166      +86     
+ Misses        633      632       -1     
Flag Coverage Δ
core 89.09% <100.00%> (+0.17%) ⬆️
ui 89.09% <100.00%> (+0.17%) ⬆️

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.

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 1 potential issue.

View 6 additional findings in Devin Review.

Open in Devin Review

Comment on lines +91 to +94
const seed = analysis.uic
? computeUICInitialSolution(circuit, compiled, opts)
: solveDCOperatingPoint(compiled, opts).assembler.solution;
result.transient = solveTransient(compiled, analysis, opts, seed, analysis.uic);
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.

🔴 UIC handling not implemented in simulateStream, simulateStepStream, and solveStep paths

The PR adds UIC (Use Initial Conditions) support to simulate() at packages/core/src/simulate.ts:91-94, correctly computing a UIC seed and passing seedIsUIC to the transient driver. However, the three other public/internal simulation entry points that run .tran analyses were not updated:

  • simulateStream() (packages/core/src/simulate.ts:157-160) always calls solveDCOperatingPoint, ignoring analysis.uic.
  • simulateStepStream()streamWithSteps() (packages/core/src/simulate.ts:259-265) same issue.
  • solveStep() (packages/core/src/analysis/step.ts:115-118) same issue.

Additionally, the internal streamTransient() helper (packages/core/src/simulate.ts:300-310) never passes seedIsUIC to createDriverFromCompiled, so even if the correct seed were provided, the driver wouldn't use the required Backward-Euler bootstrap for the first step.

When a user calls simulateStream or simulateStepStream with a .tran ... uic netlist, the uic flag is parsed and stored in the TransientAnalysis object, but these paths silently ignore it, seeding the transient from a DC operating point instead of the declared ic= values. This produces incorrect simulation results.

Prompt for agents
The UIC feature was implemented in simulate() but not in the parallel code paths. The fix requires changes in multiple locations:

1. In simulateStream() (simulate.ts ~line 155-161): When analysis.uic is true, compute the UIC seed via computeUICInitialSolution(circuit, compiled, opts) instead of solveDCOperatingPoint. Pass the seed and seedIsUIC flag to streamTransient.

2. In streamTransient() (simulate.ts ~line 294-320): Add a seedIsUIC parameter and pass it through to createDriverFromCompiled in the config object.

3. In simulateStepStream() no-step path (simulate.ts ~line 207-215): Same pattern as simulateStream - check analysis.uic and compute UIC seed when needed.

4. In streamWithSteps() (simulate.ts ~line 257-266): This function only receives compiled (not circuit), so it needs circuit passed in as well to call computeUICInitialSolution. Alternatively, the UIC seed computation could be done in the callers.

5. In solveStep() (analysis/step.ts ~line 115-120): Same issue - needs access to circuit, needs to check analysis.uic, and needs to pass seedIsUIC to solveTransient.

The key challenge is that streamWithSteps and solveStep don't currently have access to the Circuit object, which is needed by computeUICInitialSolution. The function signatures need to be extended to accept the circuit parameter.
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.

Chua circuit

1 participant