Skip to content

feat: Band 4 — the answer holds up (live verification across transports and the converter pack) - #90

Open
tinix84 wants to merge 9 commits into
feat/band3-legible-answersfrom
feat/band4-answer-holds-up
Open

feat: Band 4 — the answer holds up (live verification across transports and the converter pack)#90
tinix84 wants to merge 9 commits into
feat/band3-legible-answersfrom
feat/band4-answer-holds-up

Conversation

@tinix84

@tinix84 tinix84 commented Aug 28, 2026

Copy link
Copy Markdown
Owner

Stacked on #83 (Band 3), which is stacked on #74 (Band 2). Merge order: #74#83 → this.

Band 4 — "the answer holds up". Spec #84 (answers grilling #62 on map #56); tickets #85#89; decision ADR-0013.

What this proves, live, on the PLECS installed here (4.7.7)

  • uv run pytest -m live_plecs — the canonical buck (data/simple_buck_prb.plecs, Operating Point nominal) passes a metric-free-of-goldens oracle: strictly increasing time, ≥ 5 periods, required signals present, ≤ 1 % convergence, analytic buck invariants (D·Vi bound, v_C ≡ v_R, i_R = v_R/Ro, i_L ≈ i_R, ripple ≈ (Vi−Vo)·D/(fs·Lo)), and every steady-state Design Quantity within the Choose the semi-manual .cir and .asc acceptance matrix #61 tolerances of a tracked recorded reference.
  • Python, REST (async routes + sync route) and a real MCP ClientSession against the Simulation MCP Server return the same normalized answer — identical signal names, 1205 samples, max |Δsample| = 0 — and a second transport hits the Cache Record the first one wrote.
  • PYPLECS_LTSPICE=… uv run pytest -m converter_acceptance — the emitted .cir reproduces PLECS in LTspice 26 on all 65 steady-state Design Quantities (v_C 11.7827 vs 11.7811 V; i_L RMS 4.2783 vs 4.2786 A). The .asc does not load (known emitter defect, reported on feat: PLECS-to-netlist converter (.cir + .asc) #20, imperative xfail with a written checklist). Missing evidence fails naming the file; nothing skips.

Every run leaves an Evidence Bundle under tests/evidence/… (tracked: manifest, versions, quantities, comparison, summary; untracked: raw series, overlays).

Code changes outside tests

  • pyplecs/normalization.py: simulation_result_payload — the one place a Simulation Result becomes time + signals; REST result route (new fields, timeseries_data kept), sync route and the MCP tool use it (architecture 2/5: concentrate Raw PLECS Result normalization #46).
  • pyplecs/api/__init__.py: pyplecs-api boots its orchestrator with LivePlecsAdapter — a REST submission reaches PLECS instead of "no adapter" (architecture 4/5: concentrate the Simulation Task lifecycle #48).
  • pyplecs/api/simulation_sync.py: honours the app's resolved PLECS endpoint / auto-launch instead of hard-coding port 1080.
  • pyplecs/converter: PLECS ^ (power) → SPICE ** in every emitted expression (^ is XOR in SPICE; found by the pack).
  • pyproject.toml: live_plecs / converter_acceptance markers, deselected by default. The pre-push gate is unchanged.
  • Docs: ADR-0013, CONTEXT.md Evidence Bundle, README opt-in commands (150-line cap kept), Decision Log row; the Extract the ntbees2 waveform-equivalence method for converter acceptance #58 research finding cherry-picked from its orphan branch.

Architecture epic

#46#49 were implemented in Bands 1–3 and never closed; each carries an evidence comment mapping its acceptance list to tests. The only remaining gap (#48's pyplecs-api bootstrap) is fixed here.

Review

Two-axis review run; both axes' findings addressed in ca7d61a and 9be474a (line endings restored; Design-Quantity vocabulary; no stale-recording fallback; floor-free 0.1 % transport agreement; .asc net-count check; imperative xfail).

Added at the maintainer's request: LTspice → PLECS (#43 track 1; spec #91, tickets #92/#93)

  • parse_ltspice (.asc → Circuit Model) — pin geometry from LTspice's own symbol library, rotate-then-mirror placement verified against LTspice 26's netlister in all eight orientations; fail-closed on unknown symbols. ltspice_to_plecs and pyplecs-convert x.asc --format plecs --probe C1:'Capacitor voltage'.
  • tests/fixtures/rc_step.asc: RC low-pass with a 1 V step authored in LTspice. Under -m converter_acceptance it runs in LTspice, is imported and run in PLECS, and v_C(t) is compared point-wise and against the analytic response: LTspice 26.0.1 vs PLECS 4.7.7 agree to 1.06e-7 of the step, both within 1.3e-7 of 1 − e^(−t/τ) (evidence: tests/evidence/rc_step/step/*-ltspice-import/).

Left open on purpose

Closes #85, #86, #87, #88, #89, #91, #92, #93, #46, #47, #48, #49, #29.
Refs: #84, #56, #57, #60, #20, #43.

🤖 Generated with Claude Code

tinix84 and others added 9 commits August 28, 2026 20:07
Live checks are selected by the `live_plecs` marker (deselected by default),
probe the configured XML-RPC endpoint once and skip naming host:port, and
run under temporary isolated configuration. The canonical buck is pinned as
data (model, Operating Point `nominal`, Signal Map for the 13 probe outputs,
five-period window, #61 tolerances, observed PLECS 4.7.7 facts). The oracle
fails closed on missing signals, non-monotonic time, too few periods or
non-convergence, checks analytic buck invariants, and compares time-weighted
steady-state metrics against a recorded reference — never sample goldens.
Each live run leaves an Evidence Bundle under tests/evidence.

The Band 1 TAS smoke moves from PYPLECS_RUN_LIVE_TAS onto the same marker.

Refs: #85, #86, #57, #60, #56

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-api boots the live adapter

`simulation_result_payload` next to normalization is now the single place
that turns a Simulation Result into `time` + named `signals`; the REST result
route gains those fields (keeping `timeseries_data` for v1.x), the sync route
and the MCP `simulation_result` tool call the same function. The sync route
honours the app's resolved PLECS endpoint and auto-launch setting instead of
hard-coding port 1080. `pyplecs-api` startup builds its orchestrator with
`LivePlecsAdapter`, so a REST submission reaches PLECS instead of failing with
"no adapter" (#48).

Live: Python, REST async, REST sync and a real MCP `ClientSession` over
memory streams return the same normalized answer for the canonical buck on
PLECS 4.7.7 — identical signal names, 1205 samples, max |Δsample| = 0 — and a
second transport hits the Cache Record the first one wrote.

Refs: #87, #48, #46, #80, #59

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…o SPICE **

The pack (`uv run pytest -m converter_acceptance`) reads LTspice ASCII raw
exports from the evidence inbox (or runs LTspice when PYPLECS_LTSPICE is set),
maps raw variables to the canonical Signal Map through explicit signed
expressions, checks fail-closed preconditions, compares the last five periods'
time-weighted metrics against PLECS under the #61 tolerances, records
phase-aligned NRMSE as advisory only, and leaves an Evidence Bundle with a
dependency-free overlay. Missing evidence fails naming the file; nothing skips.

First run against PLECS 4.7.7 / LTspice 26.0.1 exposed two converter defects:

- `.param Ro=Vo_ref^2/Po` was copied verbatim; `^` is XOR in SPICE, so the
  load was wrong and every current ~3x off. Fixed here: PLECS `^` becomes
  SPICE `**` in every emitted expression. With that, all 65 steady-state
  metrics agree (v_C 11.7827 vs 11.7811 V, i_L RMS 4.2783 vs 4.2786 A).
- The `.asc` wires symbol origins rather than pin offsets (12 floating nodes)
  and carries no gate source, so LTspice cannot run it. Left as a strict xfail
  pointing at #20.

Refs: #88, #61, #20, #56

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Record the Band 4 decision once: live checks are selected by marker, probe
availability, run under isolated configuration, compare steady-state metrics
against a recorded reference plus analytic invariants, leave an Evidence
Bundle, and never gate a push. CONTEXT.md gains Evidence Bundle; README shows
the two opt-in commands inside its 150-line cap; the Decision Log has its row.

The sync-route tests pass a minimal request object now that the route reads
the app's resolved PLECS endpoint.

Refs: #89, #62, #56

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`tests/test_basic.py` (PLECS GUI automation) and `tests/test_webgui.py`
(a script against a running dashboard) broke collection of the whole tree
whenever `pywinauto` or `aiohttp` was absent, so `uv run pytest` and
`uv run pytest -m live_plecs` could only be run with hand-typed ignores. Both
now `importorskip` their optional dependency; the GUI script carries the
`live_plecs` marker because it drives PLECS.

Refs: #85

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Python edits had rewritten CLAUDE.md, CONTEXT.md, README.md, the ADR index,
.gitignore, pyproject.toml and simulation_sync.py as LF, turning one-line
changes into whole-file diffs. Endings are restored line by line from the
base blobs; content is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Standards: the oracle speaks CONTEXT.md's language — it compares Design
Quantities, not "metrics" (ADR-0013 renamed accordingly); the duplicated
payload reducer in the oracle is gone in favour of
`simulation_result_payload`; `to_spice_operators` says what it does and why
the double swap is idempotent; pandas is imported at module level.

Spec: the converter pack always takes PLECS live as its reference (no stale
recorded-series fallback); the `.asc` check is a plain failure on missing
evidence and an imperative xfail only when the known #20 defect is present;
the `.asc` structural check now compares LTspice's netlisted node count with
the Circuit Model's nets; cross-transport agreement applies the 0.1 % rule
to every quantity with no absolute floors; the selection test covers both
markers. Evidence and the recorded reference were regenerated once under the
new schema (one recording, one comparison, transports, converter, asc).

Refs: #84, #85, #86, #87, #88, #89

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…response proven against PLECS

The first import adapter on the Circuit Model seam (ADR-0001, #43 track 1):
`parse_ltspice` reads WIRE/FLAG/SYMBOL/SYMATTR/TEXT, places pins with the
LTspice library's offsets under the rotate-then-mirror rule (verified against
LTspice 26's netlister for all eight orientations), joins pins to wires at
endpoints and interiors, names nets from labels with `FLAG 0` as ground, and
turns values into PLECS expressions (unit suffixes to exponents, braces off,
`**` to `^`); `.param`/`.tran` become the initialization variables the
`.plecs` emitter expects. Only symbols with a PLECS counterpart are accepted;
anything else fails naming the symbol. `ltspice_to_plecs` and
`pyplecs-convert file.asc --format plecs --probe COMPONENT:SIGNAL` expose it.

`tests/fixtures/rc_step.asc` is an RC low-pass with a 1 V step at t = 0 (DC
source, capacitor initial voltage 0), authored in LTspice. Under the
`converter_acceptance` marker it is run in LTspice, imported and run in the
live PLECS, and the capacitor voltage compared point-wise on a common grid
and against step*(1 - exp(-t/tau)): LTspice 26.0.1 vs PLECS 4.7.7 agree to
1.06e-7 of the step, both within 1.3e-7 of the analytic response.

Refs: #91, #92, #93, #43

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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