Summary
Add Ip / Bt / β (βN) as first-class context signals and let the operator overplot them
on any time-series plot. When a fit goes wonky at some time, overlaying the global plasma
context makes the cause obvious at a glance — an Ip spike, a Bt ramp, or β collapsing (an ELM /
minor disruption) lines up with the feature in the magnetics.
The overlay must be generic — available on every time-series view (raw signals, amplitude
& phase vs time, χ²(t), φ(t), n(t) …), not bolted onto one plot — because the whole point is to
correlate an anomaly in any trace against the machine context.
What already exists (don't rebuild it)
- Ip and Bt are already fetched on every pull —
data/signals.py::AUX = ["ip", "bt"], pulled
for all analyses via ANALYSIS_GROUPS[...] += "AUX". They land in the HDF5 and are readable
through h5source.load_channel(shot, "ip"|"bt").
- κ (elongation) already comes from the EFIT tree —
signals.TREE_SIGNALS["kappa"] — and is
already consumed (nodes._kappa_at). This is the exact template for adding β.
- The
line kind exists (core/contracts.line) and the GUI renders it generically.
So most of the fetch plumbing is in place. Two real gaps remain.
Gap 1 — β/βN is not fetched
βN lives in the EFIT tree, not PTDATA, so it needs a TREE_SIGNALS entry exactly like kappa:
- Add to
data/signals.py::TREE_SIGNALS a "betan" (and probably "beta" / "li") entry with
(tree, node) candidates — e.g. ("efit01", r"\betan"), aeqdsk fallbacks — mirroring the kappa
candidate list.
- Add those names to
ANALYSIS_TREE_SIGNALS for quasi-stationary / rotating / both.
- No fetcher code changes needed — the tree-signal path (
toksearch._*_tree_channels) already
resolves candidate lists generically.
(Decide: βN vs β vs stored energy — βN is the usual "did we just have an ELM/βN collapse" quantity;
li is cheap to grab alongside and useful. Keep the set small.)
Gap 2 — the line node/renderer has no secondary axis or context overlay
contracts.line today is just {series, axes} with a single y-scale. Ip (~MA), Bt (~T), and βN
(~O(1)) live on wildly different scales than the magnetics traces, so overplotting needs a
secondary y-axis and per-context-trace scaling. Proposed generic mechanism:
- Contract: extend the
line kind (both core/contracts.py and gui/web/src/lib/contract.ts)
with an optional context list — traces the renderer draws on a secondary axis (each with its
own label/units/scale), distinct from the primary series. Keep it a first-class field so
every line view inherits it for free.
- A context provider the GUI can fetch once per shot. Options:
- a new
context_traces node kind that returns Ip/Bt/βN(/κ/li) as {t, y, label, units}, or
- a small
/api/context/{shot} endpoint.
Fetched once, cached, and reused across all time-series views (these are cheap, coarse EFIT/scalar
traces — a few hundred points).
- GUI (generic
line renderer): a toggle set ("Ip / Bt / βN") that overlays the chosen context
traces on the right-hand axis of any line plot, sharing the plot's time axis and the global time
cursor. Off by default; state ideally shared across tabs so a toggle sticks as the user moves views.
Why this belongs in the generic renderer, not per-view
If each analysis node has to hand-assemble its own Ip/Bt overlay, the feature will be inconsistent
and half the plots won't have it. Putting context on the line kind + overlay logic in the shared
line renderer means it "just works" on raw signals, phase-vs-time, χ²(t), φ(t), n(t), and anything
added later.
Open questions
- βN vs β vs Wmhd (+ li?) — which context set ships? (lean: Ip, Bt, βN, and κ which is already there)
- Secondary-axis policy when 2–3 context traces are on at once — one shared normalized axis, or one
axis per trace? (Normalized/overlaid with a small legend is simplest and readable.)
- Should the time cursor readout show the context values at the cursor time (Ip=…, βN=… at t)?
- Auto-annotate obvious events (ELM/βN-drop, Ip spike) as vertical markers — nice follow-up, separate.
Touch points
src/magnetics/data/signals.py — add βN(/β/li) to TREE_SIGNALS + ANALYSIS_TREE_SIGNALS
src/magnetics/core/contracts.py ⇄ gui/web/src/lib/contract.ts — context on the line kind
src/magnetics/service/nodes.py (+ maybe service/app.py) — a context_traces node / endpoint
gui/web/.../ shared line renderer — the overlay toggles + secondary axis + cursor readout
Cross-team: Data Streamers own the βN fetch, Interfacers own the line-kind contract + generic
renderer; both analysis teams get the overlay for free. Worth a quick Slack/PR before implementing.
Summary
Add Ip / Bt / β (βN) as first-class context signals and let the operator overplot them
on any time-series plot. When a fit goes wonky at some time, overlaying the global plasma
context makes the cause obvious at a glance — an Ip spike, a Bt ramp, or β collapsing (an ELM /
minor disruption) lines up with the feature in the magnetics.
The overlay must be generic — available on every time-series view (raw signals, amplitude
& phase vs time, χ²(t), φ(t), n(t) …), not bolted onto one plot — because the whole point is to
correlate an anomaly in any trace against the machine context.
What already exists (don't rebuild it)
data/signals.py::AUX = ["ip", "bt"], pulledfor all analyses via
ANALYSIS_GROUPS[...] += "AUX". They land in the HDF5 and are readablethrough
h5source.load_channel(shot, "ip"|"bt").signals.TREE_SIGNALS["kappa"]— and isalready consumed (
nodes._kappa_at). This is the exact template for adding β.linekind exists (core/contracts.line) and the GUI renders it generically.So most of the fetch plumbing is in place. Two real gaps remain.
Gap 1 — β/βN is not fetched
βN lives in the EFIT tree, not PTDATA, so it needs a
TREE_SIGNALSentry exactly likekappa:data/signals.py::TREE_SIGNALSa"betan"(and probably"beta"/"li") entry with(tree, node)candidates — e.g.("efit01", r"\betan"), aeqdsk fallbacks — mirroring the kappacandidate list.
ANALYSIS_TREE_SIGNALSforquasi-stationary/rotating/both.toksearch._*_tree_channels) alreadyresolves candidate lists generically.
(Decide: βN vs β vs stored energy — βN is the usual "did we just have an ELM/βN collapse" quantity;
li is cheap to grab alongside and useful. Keep the set small.)
Gap 2 — the
linenode/renderer has no secondary axis or context overlaycontracts.linetoday is just{series, axes}with a single y-scale. Ip (~MA), Bt (~T), and βN(~O(1)) live on wildly different scales than the magnetics traces, so overplotting needs a
secondary y-axis and per-context-trace scaling. Proposed generic mechanism:
linekind (bothcore/contracts.pyandgui/web/src/lib/contract.ts)with an optional
contextlist — traces the renderer draws on a secondary axis (each with itsown label/units/scale), distinct from the primary
series. Keep it a first-class field soevery line view inherits it for free.
context_tracesnode kind that returns Ip/Bt/βN(/κ/li) as{t, y, label, units}, or/api/context/{shot}endpoint.Fetched once, cached, and reused across all time-series views (these are cheap, coarse EFIT/scalar
traces — a few hundred points).
linerenderer): a toggle set ("Ip / Bt / βN") that overlays the chosen contexttraces on the right-hand axis of any line plot, sharing the plot's time axis and the global time
cursor. Off by default; state ideally shared across tabs so a toggle sticks as the user moves views.
Why this belongs in the generic renderer, not per-view
If each analysis node has to hand-assemble its own Ip/Bt overlay, the feature will be inconsistent
and half the plots won't have it. Putting
contexton thelinekind + overlay logic in the sharedline renderer means it "just works" on raw signals, phase-vs-time, χ²(t), φ(t), n(t), and anything
added later.
Open questions
axis per trace? (Normalized/overlaid with a small legend is simplest and readable.)
Touch points
src/magnetics/data/signals.py— add βN(/β/li) toTREE_SIGNALS+ANALYSIS_TREE_SIGNALSsrc/magnetics/core/contracts.py⇄gui/web/src/lib/contract.ts—contexton thelinekindsrc/magnetics/service/nodes.py(+ maybeservice/app.py) — acontext_tracesnode / endpointgui/web/.../shared line renderer — the overlay toggles + secondary axis + cursor readoutCross-team: Data Streamers own the βN fetch, Interfacers own the
line-kind contract + genericrenderer; both analysis teams get the overlay for free. Worth a quick Slack/PR before implementing.