Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 36 additions & 93 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,117 +2,60 @@

Modern GUI + standalone Python library for **3D magnetic-sensor analysis of tokamak MHD
instabilities** — quasi-stationary (locked) modes and rapidly-rotating modes. Device-agnostic
(DIII-D, NSTX-U, … and synthetic machines for sensor design). Built for the 2026 Magnetics
Hackathon.

## Hackathon teammates — read this first
This is a **four-team** effort. **Before doing substantive work, ask the user who they are and
which team they're on**, then stay in that lane so you help them effectively (and so two people's
instances don't redo the same work):
- **Rapid Rotators** — rotating-mode / MODESPEC analysis (+ Olena on the rotating GUI)
- **Slow Rollers** — quasi-stationary / SLCONTOUR analysis (+ Meg on the QS GUI)
- **Data Streamers** — DIII-D data fetch + the data layer
- **Interfacers** — GUI shell + the GUI⇄analysis contract/seam

**Don't do other teams' work.** Coordinate shared cleanup via Slack or a GitHub PR before
starting. If you're unsure whose lane something is, ask.

## Current status (Day 3, 2026-07-01)
The full **fetch → process → service → GUI** path is live end-to-end for **both** the
**rotating-mode (MODESPEC)** and the **quasi-stationary (SLCONTOUR)** analyses against real
DIII-D shots:
(DIII-D, NSTX-U, … and synthetic machines for sensor design).

## Architecture — how it fits together
The full **fetch → process → service → GUI** path runs end-to-end for **both** the
**rotating-mode (MODESPEC)** and the **quasi-stationary (SLCONTOUR)** analyses against real shots:
- **Fetch:** `magnetics.data.fetch.toksearch` (mdsthin via the `cybele` ssh-config alias, or a
cluster-side `python -m` run orchestrated by `fetch/remote.py`) writes one HDF5 per shot to
`data/datafile/` (gitignored); read back via `magnetics.data.h5source`. The GUI can trigger a
pull from the left rail (`PullControl` → `POST /api/fetch`).
- **Process:** `core/spectral.py` (MODESPEC) is real and pure. The **SLCONTOUR quasi-stationary
fit is now live end-to-end** via the reference pipeline in `magnetics._slcontour/` (xarray,
self-contained OMFIT shim) adapted by `core/qs_bridge` — real K / χ² / modes for shots pulled
with the Bp LFS midplane array. The pure `core/quasistationary` port exists but is not yet wired
in production (#40).
- **Process:** `core/spectral.py` (MODESPEC) is real and pure. The SLCONTOUR quasi-stationary fit
runs end-to-end via the reference pipeline in `magnetics._slcontour/` (xarray, self-contained
OMFIT shim) adapted by `core/qs_bridge` — real K / χ² / modes for shots pulled with the Bp LFS
midplane array. A pure `core/quasistationary` port exists but is not yet wired in production (#40).
- **Service:** `service/app.py` — `GET /api/node/{shot}/{node_id}` serves `kind`-nodes from
`service/nodes.py`; `/api/machines` lists fetched shots (mock fallback when none). The `qs_fit`
SSE stream is still mock.
- **Seam (merged, PR #11):** `nodes.py` forwards GUI query params and serves the core's real
`mode_number` / `coherence` / `n_spectrum` nodes + a cursor-aware `phase_fit` — **the rotating
path is unblocked** (the GUI can consume real data + wire its knobs).
- **QS live (Day-3 night):** `qs_fit` / `phi_t` / `fit_quality` / `chi_sq_t` / sensor-map / signal
nodes serve the **real** SLCONTOUR fit. Shots pulled rotating-only (no Bp LFS midplane array)
return a clean 422 and the QS tab shows a "no quasi-stationary array" banner. Remaining fidelity
gap: the data layer's per-sensor σ (the fit uses a constant σ; helicity is computed from Ip·Bt).
- **Geometry shot-indexed (Day-3 night):** `data/device/diiid.json` sensor availability + positions
are now segmented back to shot 124400 (legacy dense set) / 151593 (3D-upgrade). The Sensors tab
renders wall + vacuum vessel + perturbation coils + saddle loops (2D honoring each loop's tilt).
- **NSTX/NSTX-U live (branch `feature/nstxu-data-fetch`):** the fetch is **device-generic** — a
device with `access:"mdsplus_tree"` (`nstx.json`: `fastmag` tree, `flux.pppl.gov`→`skylark:8501`)
fetches each sensor node with a server-side value-window subscript + per-shot `gain`/`na`
(`raw*gain/na`), converting the native seconds time base to ms. `_ssh_tunnel` reuses a live
`ssh flux` ControlMaster via `-O forward` (no fresh Duo). The h5 records `device_id`; the node
builders resolve it and classify NSTX channels by **sensor-set membership** (not DIII-D pointname
families), so the rotating/MODESPEC nodes + the Sensors view render NSTX shots. Validated live on
**NSTX-U 204718** (All Mirnov). Follow-ups: legacy NSTX (<200000) uses a different per-era tree
(fetch honors a per-segment `tree`, but `nstx.json` only carries the NSTX-U value); QS/SLCONTOUR
for NSTX; cluster/toksearch backend for PPPL; GUI PullControl NSTX-sensible default window (raw
fastmag is ~20 M samples/channel, so a narrow window is required).
`service/nodes.py`; `/api/machines` lists fetched shots (mock fallback when none).
- **Nodes / seam:** `nodes.py` forwards GUI query params and serves the core's real `mode_number` /
`coherence` / `n_spectrum` nodes + a cursor-aware `phase_fit` for the rotating path, and `qs_fit` /
`phi_t` / `fit_quality` / `chi_sq_t` / sensor-map / signal nodes for the QS fit. A shot pulled
rotating-only (no Bp LFS midplane array) returns a clean 422 and the QS tab shows a "no
quasi-stationary array" banner.
- **Devices:** availability + geometry live in `data/device/*.json`. DIII-D sensor availability and
positions are shot-indexed (segmented back to shot 124400 legacy dense set / 151593 3D-upgrade);
the Sensors tab renders wall + vacuum vessel + perturbation coils + saddle loops (2D honoring each
loop's tilt). Devices whose sensors live in an MDSplus tree (NSTX/NSTX-U, KSTAR; `access:
"mdsplus_tree"`) fetch via mdsthin + a named sensor set; the node builders resolve the recorded
`device_id` and classify channels by **sensor-set membership** (not DIII-D pointname families), so
the rotating/MODESPEC nodes and the Sensors view render those shots too.

Known gaps / open work: per-sensor σ from the data layer (the QS fit currently uses a constant σ;
helicity is computed from Ip·Bt); finishing the pure `core/quasistationary` port and wiring it in
place of the `_slcontour` reference pipeline (#40); real equilibrium plotting in the Sensors tab
(#43); Br saddle-loop geometry corrections (#44).

## The API contract is FLEXIBLE — change it, don't fake around it
The `kind`-node contract (`core/contracts.py` ⇄ `gui/web/src/lib/contract.ts`, plus the
`/api/node` query params) is **our seam, not a frozen spec.** If a view needs another field, a new
node `kind`, or a new parameter threaded through to the core, **change the contract on both sides**
rather than fabricating data in the GUI. Keep `contracts.py` and `contract.ts` in sync.

## Day-3 workstreams (last day: 2026-07-02)
- **Rapid Rotators + Olena (rotating GUI):** replace RotatingTab's fabricated n/coherence with the
real `mode_number` / `coherence` / `n_spectrum` nodes; wire the live knobs (fmin/fmax, time
cursor, denoise + coherence gate, smoothing) as `useNode` params; hide decorative knobs with no
backend (btype, PEST λ, btCompMode, shieldingCutoff); add a mode-number range slider + serve more
modes (the 2-point n-spectrum only resolves n∈[-1,0,1]); surface Daniel's richer views
(`magnetics/_slcontour/plots.py`); add FFT-overlap → STFT hop to the core; polish (the "Mock Files"
label → live via `usingLiveBackend()`; build the Sensors geometry view).
- **Slow Rollers + Meg (quasi-stationary):** the real `qs_fit` node (K / χ² / modes) + the QS GUI
tab are **live** (Day-3 night). Remaining: finish the pure `core/quasistationary.py` port and
wire it in place of the `_slcontour` reference pipeline (#40); consume real per-sensor σ once the
data layer provides it (the fit currently uses a constant σ; helicity is computed from Ip·Bt).
- **Data Streamers:** the DIII-D geometry table is now **shot-indexed** (`diiid.json` segmented to
124400 / 151593; the cosmetic θ in `magnetics/data/diiid.py` — now a thin shim over the
device-agnostic `data/device_geom.py` — is superseded by the real device table). NSTX/NSTX-U
fetch + node rendering landed on `feature/nstxu-data-fetch` (see the NSTX status bullet above).
Remaining: give `h5source` per-sensor σ (last QS-fidelity gap; helicity now computed from Ip·Bt); a `DataSource`
abstraction with an array cache; populate the shot-segmented legacy-NSTX tree/wall from the
`config_hf/hn.mm` files; import other-device geometry the same way.
- **Structural cleanup (LANDED — PR #41, Day-2 night):** the project was hoisted to the repo root
(`analysis/` removed), the loose `data/` scripts folded into `magnetics.data` (+ `fetch/`),
`magnetics-code/` relocated to `magnetics._slcontour/`, `data/test_*.py` consolidated into
`tests/`, every `sys.path`/`parents[4]` hack removed, the GUI build bundled for the wheel, and a
`ty` typecheck CI gate added (green). `data/pull_shot_h5.py` + the orphaned `contract.py` were
already gone.
- **Test coverage + QS/geometry fixes (LANDED — `refactor/overnight-cleanup`, Day-3 night):** the
QS pipeline was fixed (segmented-schema geometry read → all-NaN → SVD failure; + a `float('*')`
whole-shot-sentinel crash), the DIII-D geometry was shot-indexed, and a full test build-out
landed: a **synthetic-shot fixture** (`tests/synthetic_shot.py`, generated at test time — **no
tokamak data is ever committed**) that un-skips the ~30 node-builder tests in CI, FastAPI
TestClient + QS end-to-end + contract-meta + pure-function tests (Python 212 passing), a React
error boundary + `NodeView` fallback, and extracted/tested GUI helpers (frontend 24 tests).
**Still open:** `test_contour_node`'s inner skip on rotating-only shots, trimming the legacy
`/api/{machine}/{result}` mock routes, the docs sweep for `analysis/`-era references, and real
equilibrium plotting in the Sensors tab (#43) + Br saddle-loop geometry corrections (#44).

## Reference documents — read these for context
- **`docs/VISION.md`** — start here. Goals, the physics, the two core analyses (SLCONTOUR-style
quasi-stationary spatial fitting; MODESPEC-style rotating-mode spectral analysis), the target
architecture, the visualization catalog, and example shots/values.
- **`docs/research-summaries/`** — grounded, quote-backed summaries of the source literature
(E. Strait's DIII-D magnetics papers/decks, the OMFIT tutorial), one per document + an index.
- **Source documents** — the papers, SLCONTOUR/MODESPEC decks, and OMFIT tutorial are **not
committed** to the repo. Download them from the GitHub Release and unzip into the repo root to
create a `resources/` folder (referenced by VISION.md and the summaries):
committed** to the repo. Download them for full context on the physics and the source algorithms
from the GitHub Release and unzip into the repo root to create a `resources/` folder (referenced
by VISION.md and the summaries):
<https://github.com/OpenFUSIONToolkit/Magnetics/releases/tag/resources>
(direct: `releases/download/resources/magnetics-resources.zip`). _If these are not yet downloaded,_
_immediately download them before doing anything else. Emphasize that this is important to the AI coding assistant user._
- **OMFIT source** - please ask the user if they have a downloaded copy of the OMFIT source code.
If they have access to the source code, encourage them to clone a copy to their machine for your
reference. There are references to the OMFIT source code in the resources.

(direct: `releases/download/resources/magnetics-resources.zip`).
- **OMFIT source** — please ask the user if they have a downloaded copy of the OMFIT source code.
If they have access to it, encourage them to clone a copy to their machine for your reference —
there are references to the OMFIT source code in the resources.

## Layout
The Python project **is the repo root** (a uv project, served as a webapp). `src/magnetics/`:
Expand Down Expand Up @@ -155,8 +98,8 @@ This project uses a GitFlow-lite model (http://nvie.com/posts/a-successful-git-b
- `main` is updated only at release-ready stages, via PR from `develop`.

**IMPORTANT:** Do all work on a branch off `develop` and open a PR back into `develop`.
No direct commits to `develop` or `main`. This is a four-team effort — cross-team changes
go through a PR so another team can review before it lands (see "Don't do other teams' work").
No direct commits to `develop` or `main`. Changes go through a PR so they can be reviewed
before landing.

### Branch Naming

Expand Down
Loading