MAC-based poloidal-m estimator with even/odd confidence#66
Draft
d-burg wants to merge 4 commits into
Draft
Conversation
Wire the Olofsson MAC (eigspec eq 9) into a poloidal mode-number estimator that answers even/odd (1/1 vs 2/1) with a probability, an independent cross-check on poloidal_phase_fit's slope fit. - core.mode_shape: mac_m_probability() Monte-Carlos the MAC over the per-probe phase σ to yield P(m); poloidal_m_spectrum() does the poloidal-specific prep — match the *phase pattern* (unit phasors, since a mode number is a phase winding and the per-probe amplitude spans 3-4 decades of probe sensitivity), SNR-gating out dead/saturated probes. - contracts: a `bar` kind (both sides) for the P(m) spectrum. - service: poloidal_m_spectrum node; verdict gated on the absolute MAC so a low match reads as "no clean poloidal mode", not a spurious m/n. - web: MAC+confidence tile (P(m) bars, nominal MAC overlay, verdict banner coloured by quality) in the rotating tab. On real DIII-D 174446 this resolves clean 3/2 and 1/3 modes (MAC 0.6-0.77) and honestly flags mode-free slices. Co-Authored-By: Claude Opus 4.8 <[email protected]>
There was a problem hiding this comment.
Pull request overview
This PR wires an existing MAC (Modal Assurance Criterion) mode-shape implementation into a new poloidal mode-number (m) probability spectrum node, exposing an even/odd confidence (e.g., 1/1 vs 2/1) as a GUI tile and providing an independent cross-check to the existing poloidal_phase_fit slope-based estimator.
Changes:
- Add core logic for a Monte-Carlo MAC-based P(m) estimator (
mac_m_probability) plus a poloidal-specific phase-pattern + SNR-gated wrapper (poloidal_m_spectrum). - Introduce a new contract node kind
barand render it in the frontend (NodeView) and the Rotating tab (new “MAC + Confidence” card). - Add service wiring for a new node id
poloidal_m_spectrumand extend tests to cover the new node and core behavior.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_service_api.py | Expands valid node-kind set to include the new bar kind. |
| tests/test_nodes.py | Adds end-to-end node-builder test for poloidal_m_spectrum. |
| tests/test_mode_shape.py | Adds unit tests for mac_m_probability and poloidal_m_spectrum behavior (parity, normalization, gating, reproducibility). |
| src/magnetics/service/nodes.py | Adds _poloidal_m_spectrum builder and registers it in _BUILDERS. |
| src/magnetics/core/mode_shape.py | Implements MacMResult, mac_m_probability, and poloidal_m_spectrum (SNR gate + phase-pattern MAC). |
| src/magnetics/core/contracts.py | Adds contracts.bar() node constructor. |
| gui/web/src/lib/NodeView.tsx | Adds Plotly rendering for bar nodes (with optional secondary overlay). |
| gui/web/src/lib/contract.ts | Extends TypeScript contract with BarNode and updates the Node union. |
| gui/web/src/components/tabs/RotatingTab.tsx | Fetches and displays poloidal_m_spectrum as a MAC+confidence UI tile. |
Comments suppressed due to low confidence (1)
src/magnetics/service/nodes.py:940
except ValueError, KeyError:is invalid Python syntax (it triggers a SyntaxError at import time). Use a tuple for multiple exceptions so the service can start and the node builder can be imported.
return geometry.elongation_theta_star(thetas_deg, kappa), True
Add EFIT 2-D profile support so the q-profile (q vs ψ_N, in time) can be pulled and read back, for the rotating mode-number anchor. - signals: TREE_PROFILES / tree_profiles_for — q_profile from EFIT02 only (MSE-constrained; no efit01 fallback, so a non-MSE q is never silently substituted — the profile is simply omitted if efit02 lacks it). - toksearch: a Profile record + _mdsthin_tree_profiles (2-D fetch, orientation-robust, ψ_N = uniform gEQDSK grid), wired additively into the mdsthin backend and written as its own h5 group; the existing scalar/ pointname path is untouched. - h5source: load_q_profile() reader; channel_names() skips profile groups so they never masquerade as (data, time) channels. - synthetic_shot: a physical synthetic q-profile (q0≈1.05→q_edge≈4.5) so the read/anchor chain is exercised in CI without any tokamak data. The live mdsthin fetch needs a re-pull to validate against real EFIT02; toksearch/remote backends don't yet fetch profiles (follow-up). Co-Authored-By: Claude Opus 4.8 <[email protected]>
The poloidal MAC winner is easily an alias on the sparse, uneven poloidal
array (a physical high-m mode folds down to a spurious small one), so the
bare fit could report an unphysical mode like m/n = 1/3 (q ≈ 0.33, inside the
q ≈ 1 axis) with false confidence. Fix it three ways:
- core.equilibrium (new): resonant_surfaces() + anchor_poloidal_m() — for the
reliable toroidal n, keep only m whose q = m/n has a real rational surface
(q_min ≤ m/n ≤ q_max) and, if the raw MAC winner is unphysical, swap in the
best-MAC physical alias. Returns the q surface + its ψ_N.
- core.mode_shape: mac_m_probability now also reports the MAC margin
(best − next-best) and the alias set within a margin of the best.
- service.nodes: poloidal_m_spectrum reads the q-profile (_q_at) and anchors m
to it; a margin-aware quality ladder — weak < ambiguous (alias near-tie, no q
to break it) < marginal < clean < q-anchored — so a near-tie reads as
"ambiguous: |m| ∈ {…}" instead of a false-confident integer.
- web: verdict banner shows the q surface / ψ_N when anchored, the alias set +
margin otherwise, coloured by trust.
On the synthetic shot the ground-truth 2/1 is recovered from an aliased MAC
(q=2.00 @ ψ_N=0.52); real shots without a q-profile now honestly report the
ambiguity (e.g. 174446 @ 3000 ms → "|m| ∈ {1,2,6}") instead of "1/3".
Co-Authored-By: Claude Opus 4.8 <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Wires the Olofsson MAC (eigspec eq 9) into a poloidal mode-number estimator that answers even/odd — 1/1 vs 2/1 — with a probability, an independent shape-based cross-check on
poloidal_phase_fit's slope fit.The GP mode-shape machinery +
mac()/mac_n_spectrum()already existed but were wired into nothing; θ* elongation landed in #38. This connects them for the poloidal side and adds the uncertainty layer.How
core.mode_shapemac_m_probability()— Monte-Carlos the MAC over the per-probe phase σ → a posterior P(m) (even/odd split), deterministic under a fixed seed.poloidal_m_spectrum()— the poloidal-specific prep: match the phase pattern (unit phasors), because a mode number is a phase winding and the per-probe amplitude on a poloidal array spans 3–4 decades of probe sensitivity/flux-surface geometry (an amplitude-weighted MAC gets captured by one big probe). SNR-gates dead/saturated channels.core.contracts/contract.ts— abarkind for the P(m) spectrum (+NodeViewrenderer).service.nodes—poloidal_m_spectrumnode; the verdict is gated on the absolute MAC, so a poor match reads as "no clean poloidal mode" rather than a spuriously precise m/n.web(RotatingTab) — a MAC + confidence tile: P(m) bars, nominal-MAC overlay, and a verdict banner coloured by fit quality.Why phase-pattern + SNR gate
On real DIII-D 174446 the raw amplitude-weighted MAC was ~0.07 everywhere (dominated by 1–2 near-saturated probes; amplitude spread up to 29000×). Phase-pattern + a 5% SNR gate (typically 8–28 of 48 MPID probes carry signal) recovers clean identifications:
Tests
tests/test_mode_shape.py::TestMacMProbability,::TestPoloidalMSpectrum— peak/parity on clean modes, posterior normalization, noise spreading the posterior, SNR-gate drops dead probes (+ aliasing-aware fixture), seed reproducibility.tests/test_nodes.py::test_poloidal_m_spectrum_node— end-to-end against the synthetic-shot fixture.ruff format/checkclean; GUItsc/eslintclean; production build OK.Notes / follow-ups
🤖 Generated with Claude Code