Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
81c6675
Add Bouquet orchestrator API scaffold (config + sources + physics)
d-burg Jun 4, 2026
ea5c1be
Implement physics reductions + IDA/FUSE readers (phase 2, step 1)
d-burg Jun 4, 2026
2486288
Wire IMAS resolve_baseline + config validation (phase 2, step 2-ii)
d-burg Jun 4, 2026
cd03564
Reconstruction baseline + setup_solver (phase 2, step 3)
d-burg Jun 4, 2026
3ec415e
Add p-file profile support to reconstruction baseline
d-burg Jun 4, 2026
43149d6
Wire Bouquet.generate + sum fixed components into generate_bouquet (p…
d-burg Jun 4, 2026
1473d2d
Complete Bouquet API: filter/export, exports, IMAS solver setup (phas…
d-burg Jun 5, 2026
bfd2764
Add synthetic non-proprietary OMAS example fixture (IMAS part A)
d-burg Jun 9, 2026
d9e3e99
IMAS forward-solve + li-target alignment (IMAS part B)
d-burg Jun 9, 2026
ad15c81
Checkpoint: switchboard extras, output capture, IMAS li alignment, pl…
d-burg Jun 12, 2026
7aaf0b1
Implement field-aligned parallel->toroidal j_BS conversion (Redl Eq.2…
d-burg Jun 12, 2026
0c9a36c
Consolidate SWB debug scaffolding behind one flag; wire real converge…
d-burg Jun 12, 2026
9c8fb73
Scrub proprietary DIII-D references; rewrite docs around the class API
d-burg Jun 12, 2026
845fa15
Notebook cell fixes: honest OFT guard, version print, seed, filter wh…
d-burg Jun 12, 2026
7dc8f4b
Zeff-primary densities + aux channel naming; purge stale draws; fix p…
d-burg Jun 12, 2026
2b18199
Zeff-consistent densities for all sources; derive ni in IDA path; imp…
d-burg Jun 15, 2026
ffee420
Notebooks: Zeff-consistent scheme + impurity_Z; port OMAS to mirror g…
d-burg Jun 15, 2026
aa09fa1
continued API and notebook overhauls, l_i fixes and j_phi diff profil…
d-burg Jun 23, 2026
08508db
SWB diff and rescale are working. geqdsks still a WIP. Validated FUSE…
d-burg Jun 23, 2026
1f9acf3
Lock per-path bouquet workflows (IMAS diff+C / geqdsk standard-loop) …
d-burg Jun 24, 2026
55a7dce
FUSE/IMAS bouquet: anchor recon to FUSE (pressure + current) + nthrea…
d-burg Jun 26, 2026
bab072e
notebook update checkpoint: fixes to VSC common mode via error additi…
d-burg Jun 27, 2026
9d215a5
IDA-hybrid IMAS baseline: IDA kinetics + FUSE Zeff + EFIT01 separatrix
d-burg Jun 30, 2026
9fff65a
geqdsk recon: full Sauter bootstrap baseline (isolate_edge_jBS=False)…
d-burg Jun 30, 2026
ed05657
profile diff fixes, notebook and plotting cleanups and readability
d-burg Jun 30, 2026
bf43c70
WIP: checkpoint backup 070126
d-burg Jul 2, 2026
dff5ada
parallel: fresh shards, merge-side baseline guard, shard accounting, …
d-burg Jul 2, 2026
d77ff77
physics: impurity radial force balance E_r from CER (read_ida_cer + r…
d-burg Jul 2, 2026
7c09abf
schema v2 + provenance + BouquetArchive: clean-break h5 layout, confi…
d-burg Jul 2, 2026
d0dcd11
docs: mark all review-plan phases implemented + post-review fixes
d-burg Jul 2, 2026
be4af3f
geqdsk: fix scalar conversion at axis point for scipy>=1.18/numpy>=2.5
d-burg Jul 2, 2026
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
34 changes: 33 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,24 @@ env/
!tests/golden/*.h5
# ...and the small DIII-D mesh the solver suite needs in CI (~432 KB)
!examples/D3D-like/DIIID_mesh.h5
# NetCDF / proprietary IDA data (e.g. IDA_204441_.cdf) — never commit
# NetCDF / proprietary IDA data (IDA_*.cdf) — never commit
*.nc
*.cdf
*.dat
*.npz
*.npy
*.mat

# FUSE IMAS/OMAS data dumps (proprietary DIII-D data) — never commit.
# Only the readers in bouquet/io are synced, never the data they read.
# dd_sim = FUSE simulation DD; dd_exp = raw experimental DD (both proprietary).
# The synthetic example (examples/D3D-like/D3Dlike_baseline_omas.json) is a
# different name and IS tracked.
dd_sim.json
*_dd_sim.json
dd_exp.json
*_dd_exp.json

# Fortran / compiled artifacts (in case any build steps live nearby)
*.mod
*.o
Expand All @@ -56,3 +66,25 @@ Thumbs.db
.vscode/
.idea/
*.swp

# Test fixtures must ship (the global *.geqdsk / *.npz rules above would
# otherwise silently drop them on fresh clones, skipping the D3D-like tests)
!tests/data/*.geqdsk
!tests/data/*.npz

# Working notes / one-off scaffolding — never commit
PR3_body_DRAFT.md
PR_BODY*.md
examples/**/_*
examples/D3D-like/PORT_STATE.md

# Notebook §8 demo outputs (extracted g/p-files, reconstructed IMAS IDS)
examples/D3D-like/extracted/
examples/D3D-like/imas_out/
examples/D3D-like/*_perturbed.json
examples/D3D-like/*_draw*.json

# Local-only generator for the synthetic fixture (needs OFT + SPARC + a live
# solver). Not committed -- only its output D3Dlike_baseline_omas.json ships.
examples/D3D-like/build_synthetic_omas.py
examples/D3D-like/slurm_jobs/*.pkl
237 changes: 181 additions & 56 deletions README.md

Large diffs are not rendered by default.

66 changes: 58 additions & 8 deletions architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ understand exactly what is — and is not — guaranteed by the code.
## Table of Contents

1. [Overview](#1-overview)
- [1.5 Class API and Baseline Sources](#15-class-api-and-baseline-sources)
2. [Coordinate Systems and Sign Conventions](#2-coordinate-systems-and-sign-conventions)
3. [Equilibrium Perturbation Methodology](#3-equilibrium-perturbation-methodology)
4. [Quasi-Neutrality and Impurity Handling](#4-quasi-neutrality-and-impurity-handling)
Expand All @@ -32,7 +33,9 @@ understand exactly what is — and is not — guaranteed by the code.

Bouquet generates families ("bouquets") of perturbed tokamak equilibria by:

1. Starting from a baseline kinetic equilibrium (g-file + p-file).
1. Starting from a baseline kinetic equilibrium — either a g-file plus
kinetic profiles (p-file or IDA netCDF), or an IMAS/OMAS data-dictionary
JSON with pre-separated currents (see §1.5).
2. Drawing correlated perturbations of ne, Te, ni, Ti from Gaussian
process regression (GPR) posteriors, respecting user-supplied
uncertainty envelopes.
Expand All @@ -48,6 +51,52 @@ Bouquet generates families ("bouquets") of perturbed tokamak equilibria by:

Each of these steps carries assumptions documented below.

### 1.5 Class API and Baseline Sources

The class-based orchestrator (`bouquet/run.py`, `bouquet/config.py`,
`bouquet/baseline.py`) wraps the functional pipeline. `Bouquet(config)` owns
the TokaMaker solver and resolves any source to a common `Baseline`
(separated currents + targets + kinetic profiles), so generation never
depends on where the baseline came from:

| Module | Role |
|---|---|
| `config.py` | Typed dataclass config: `SolverConfig`, `ReconstructionSource` / `ImasSource`, `UncertaintyConfig`, `GenerationConfig`, `FilterConfig`, `FixedComponentsConfig` |
| `baseline.py` | `Baseline` dataclass + `resolve_baseline()` dispatch + `resolve_uncertainty()` (sigma precedence: explicit profile > IDA `.cdf` > flat scalar fraction) |
| `io/ida.py` | DIII-D IDA netCDF reader (via h5py; profiles + `*_err` sigmas; ni = ne quasi-neutrality) |
| `io/imas.py` | FUSE/IMAS JSON reader (stdlib json; no OMAS install needed) |
| `physics.py` | Convention reductions shared by all sources (below) |
| `run.py` | The `Bouquet` driver: solver → baseline → generate → filter → export |

**IMAS-path physics assumptions** (all in `bouquet/physics.py` and
`io/imas.py` docstrings, summarized here because this document is the
assumption catalogue):

* **Toroidal-current authority**: the IDS `j_tor` is taken as the total
`j_phi`; the inductive component is the residual
`j_tor − j_BS − j_NBI − j_RF`, so the decomposition sums exactly and Ip
is preserved.
* **Parallel→toroidal conversion**: IMAS/neoclassical currents are FSA
*parallel* densities `<j·B>/B0`. Components are converted to bouquet's
toroidal convention `<j_phi/R>/<1/R>` by the per-surface ratio
`c = j_tor_total / j_parallel_total` when the source carries both totals
(FUSE does), or by the field-aligned analytic projection
`j_tor = <j·B> F <1/R²> / (<B²> <1/R>)` otherwise. The same analytic
projection corrects the per-draw `solve_with_bootstrap` output (which is
`<j_BS·B>` crudely divided by `B_phi(<R>)`); the net factor
`1/(<R><1/R>)` is a ~5% reduction at a DIII-D-like pedestal.
References: Redl et al., Phys. Plasmas 28, 022502 (2021), Eq. (2);
Wesson, *Tokamaks*, eqn 3.3.6.
* **Fast-pressure isotropization**: anisotropic fast-ion pressure
(`pressure_fast_perpendicular` / `_parallel`) is reduced to the scalar
GS pressure via `tr(P)/3 = (2 p_perp + p_par)/3` by default ("trace";
also "mean", "perp"). Fast pressure and driven currents are *fixed
components*: summed into every draw, never GPR-perturbed.
* **l_i target**: the IDS-reported `li_3` is provisional only; the
forward solve on the TokaMaker mesh re-evaluates the baseline and sets
`l_i_target` to the TokaMaker `li_1`, recording both for comparison
(`Baseline.li_metrics`).

---

## 2. Coordinate Systems and Sign Conventions
Expand Down Expand Up @@ -256,7 +305,7 @@ forward operator (sigma -> 0 reproduces the reconstructed equilibrium):
`create_power_flux_fun(1.5, 1.5)` (broad shape) and alpha-scales to
match Ip, but the resulting inductive has a fundamentally different
*shape* than recon's eqdsk-fit inductive (typical recon/SWB ratio
varies 1.4-3.4x with psi on DIII-D 204441@4400). Without this
varies 1.4-3.4x with psi on the DIII-D reference case). Without this
substitution the pipeline's "zero-perturbation baseline" lives at
l_i ~0.89 vs recon's 1.10 -- a ~19% systematic offset that breaks
sigma -> 0 reproducibility regardless of how the matching loop is
Expand All @@ -276,15 +325,16 @@ forward operator (sigma -> 0 reproduces the reconstructed equilibrium):

- **Cylindrical proxy filter:** A fast 1-D proxy for l_i is computed
without solving Grad-Shafranov, using pre-computed geometry.
Draws whose proxy l_i falls outside `l_i_proxy_threshold` (default
5%) of the *corrected* target are rejected cheaply.
Draws whose proxy l_i falls outside a margin of the *corrected*
target (`PRESCREEN_MARGIN` x `l_i_tolerance`, default 3x) are
rejected cheaply before any GS solve.
- **Secant iteration:** Scales the inductive amplitude to match the
true Grad-Shafranov l_i within `l_i_tolerance`.
- **Adaptive proxy correction:** After each GS solve, the proxy
target is updated based on the observed proxy-vs-reality offset,
blended 70% new / 30% old.

**Empirical reproducibility at sigma=0** (DIII-D 204441@4400, 6 draws):
**Empirical reproducibility at sigma=0** (DIII-D reference case, 6 draws):

| Quantity | recon | sigma=0 pipeline |
|---|---|---|
Expand Down Expand Up @@ -892,7 +942,7 @@ Per-coil and channel bounds are enforced simultaneously by the GS solver
* The `#VSC` channel bound uses the *minimum* of `|F9A_base|` and
`|F9B_base|` so that the bound is conservative for both coils (their
baselines are typically asymmetric: ~45 kA vs ~37 kA on
204441@4400).
the reference case).
* Total drift on F9A/F9B = bare drift + VSC channel contribution.
Caller selects passes so that `drift_F + drift_VSC <=` the desired
total F9 tolerance.
Expand Down Expand Up @@ -964,7 +1014,7 @@ After homotopy, every stored draw carries these attributes (see

Out-of-spec draws are still archived (with `in_spec=False`) so
downstream analysis can filter as needed. Empirical survival rates
at the strict +/-2% global spec on DIII-D 204441@4400 (n=8 draws per
at the strict +/-2% global spec on the DIII-D reference case (n=8 draws per
sigma):

| `SIGMA_SCALE` | yield | `in_spec` rate |
Expand Down Expand Up @@ -993,7 +1043,7 @@ The F9 pair has two physical degrees of freedom (F9A_bare, F9B_bare).
The per-coil soft-reg already pins both at weight 1e4. Adding
`vsc_soft_reg_weight` is a *third* pull on a 2-DoF subspace and at
sufficiently high weight makes the QP over-determined. Empirical
findings on DIII-D 204441@4400 (n=15 per setting):
findings on the DIII-D reference case (n=15 per setting):

| `vsc_soft_reg_weight` | σ=0 outcome | σ=0.5 in-spec | σ=1.0 in-spec |
|---|---|---|---|
Expand Down
Loading
Loading