Skip to content

Feat/bouquet class api#8

Merged
d-burg merged 31 commits into
mainfrom
feat/bouquet-class-api
Jul 2, 2026
Merged

Feat/bouquet class api#8
d-burg merged 31 commits into
mainfrom
feat/bouquet-class-api

Conversation

@d-burg

@d-burg d-burg commented Jul 2, 2026

Copy link
Copy Markdown
Owner

Summary

Replaces the hand-threaded functional workflow with a class-based orchestrator, and moves archives to a self-describing v2 schema.

  • bq.Bouquet(config).run() — typed BouquetConfig (validated at construction, JSON-serializable), staged setup_solver → prepare_baseline → generate → filter → export with inspection gates, validated per-path workflow presets (geqdsk standard l_i loop / IMAS diff+C), multi-slice sweeps via run_slices.
  • Two baseline sourcesReconstructionSource (g-file + IDA .cdf / p-file) and ImasSource (FUSE IMAS/OMAS, incl. IDA-hybrid kinetics + EFIT01 separatrix).
  • Schema-v2 archives (clean break, no legacy readers — decided while there are no external users): bare dataset names with units attrs, fixed eqdsk/pfile names, unified coil storage, scan/<key>/ layout only, and full provenance (schema_version, package version, config_json — including merged cluster archives). New BouquetArchive reader replaces downstream h5 tree-walking; readers accept a Bouquet/archive/header/path uniformly and fail loudly on bad scan keys or pre-v2 files.
  • Process-parallel generation — laptop ProcessPoolExecutor and SLURM job-array backends over one run_shard; strict one-core-per-TokaMaker (nthreads=1) doctrine, slice-decorrelated seeding, fresh-shard + merge-side baseline guards, loud partial-merge accounting.
  • Also: impurity radial force balance E_r from CER (read_ida_cer + radial_field_from_cer).

Validation

  • 187 tests green (+ 3 solver-marked replay tests); golden-fixture regression on the synthetic D3D-like case.
  • Multi-agent adversarial review of the final diff: 8 findings, all fixed (see docs/ux-review-feat-bouquet-class-api.md, which also records the design decisions — delete or fold into CHANGES_SUMMARY at merge).

d-burg and others added 30 commits June 4, 2026 01:33
Stub-level scaffold for the class-based bouquet API. Composable Bouquet
class (setup_solver -> prepare_baseline -> generate -> filter -> export)
plus a thin run() convenience, driven by typed config dataclasses.

Two independent axes:
- Baseline source: ReconstructionSource (g-file + IDA/p-file -> GS recon)
  or ImasSource (FUSE dd_sim.json, currents pre-separated, no recon).
  Both resolve to a common Baseline.
- Uncertainty envelope: kinetic sigmas (from IDA) + j_phi sigma + GPR
  length scales, in UncertaintyConfig.

Conventions baked in:
- All current components stored as toroidal j_phi; physics.parallel_to_toroidal
  converts IMAS/neoclassical parallel currents and TokaMaker solve_with_bootstrap
  output (analytic vs j_tor/j_total-ratio methods).
- Fixed additive components (p_fast, j_NBI, j_RF) summed into every draw, never
  GPR-perturbed; all accept plain psi_N arrays. j_RF is user-array-only.
- p_fast isotropization defaults to trace/3 = (2 p_perp + p_par)/3.
- IDA reader uses h5py (file is netCDF4/HDF5); FUSE reader uses stdlib json.
  No OMFIT / IMAS / OMAS install required.

All modules are import-validated stubs (NotImplementedError bodies); no
backend wiring yet.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
physics.py:
- isotropize_fast_pressure: trace/mean/perp reductions, default trace=(2p_perp+p_par)/3
- parallel_to_toroidal: ratio method (c=j_tor/j_total) with on-axis zero-crossing
  guard; analytic method deferred to the solve_with_bootstrap integration

io/ida.py (read_ida, h5py only):
- handles the operational multi-time direct-*_err IDA_*.cdf layout
- time selection by nearest slice; ni from carbon quasineutrality (Z=6);
  stored Zeff returned as-is; raw bytes captured
- verified against IDA_204441_.cdf @ 4.4s

io/imas.py (read_imas_baseline, stdlib json only):
- FUSE dd_sim.json -> separated Baseline, provenance "imas", no GS recon
- per-IDS nearest-time selection; psi_N from grid.psi; parallel->toroidal via
  ratio; j_phi=j_tor with inductive=residual (decomposition closes, Ip preserved)
- j_NBI = sum of beam-source j_parallel; j_RF zeros (user-override only)
- p_fast = sum of per-species isotropized fast pressure
- verified against a D3D FUSE run @ 2.0s

config: sigma_mode default -> "direct" (matches operational IDA files)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- read_ida: ni = ne (quasi-neutrality approx) to match the operational IDA
  workflow (204441_4400 feeds ni=ne, not the carbon-corrected density);
  sigma_ni tracks sigma_ne. Drops the unused carbon-quasineutrality path.
- resolve_baseline(config, mygs=None): dispatch implemented. ImasSource branch
  delegates to read_imas_baseline, threading fixed_components and
  p_fast_reduction; ReconstructionSource branch raises NotImplementedError
  (step 3); unknown types raise TypeError.
- BouquetConfig.__post_init__: validates output_header, source-specific required
  paths, p_fast_reduction, and sigma_mode -- configs are now constructible.
- Bouquet.prepare_baseline: wired to resolve_baseline; caches self.baseline.

Verified the full IMAS path through Bouquet(config).prepare_baseline() against a
D3D FUSE run: provenance imas, Ip/l_i correct, decomposition closes (~2e-10).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Bouquet.setup_solver: stand up TokaMaker (mesh, regions, F0 from g-file,
  VSC, isoflux from LCFS, weak coil regularisation) per the operational recipe.
- resolve_baseline ReconstructionSource branch (_resolve_reconstruction):
  read g-file + IDA profiles (ni=ne), interpolate to the g-file psi_N grid,
  run reconstruct_equilibrium, package a toroidal-current Baseline on dual grids
  (equilibrium psi_N + IDA psi_N_kinetic). Fixed components default to zero
  (the reconstructed j_phi_fit already contains driven current); user-supplied
  j_NBI/j_RF/p_fast re-partition the inductive residual.
- ReconstructionSource.time field for multi-time IDA selection.
- .gitignore: never commit FUSE dd_sim.json (proprietary DIII-D data).

Verified against the 204441_4400 testbed (live GS solve): recon converged
(l_i err 1e-4, Ip err 0.035%, bnd RMS 2.8mm); decomposition closes; and a
get_jphi_from_GS cross-check confirms j_phi_fit is TOROIDAL to ~1% -- same
convention as the IMAS path, resolving the solve_with_bootstrap question.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
_load_kinetic_profiles dispatches on profiles_path: IDA .cdf (ni=ne,
quasi-neutrality) or Osborne p-file (real ni via compute_quasineutrality,
Zeff from the ion mix; ne/ni 1e20 m^-3, Te/Ti keV -> SI). _resolve_reconstruction
now consumes that helper, so both sources flow through one path.

This makes the synthetic, non-proprietary examples/D3D-like fixtures a
committable end-to-end test of the reconstruction path. Verified against
D3Dlike_Hmode_baseline.{geqdsk,peqdsk}: recon converged (l_i err 3e-5,
Ip err 0.027%, bnd RMS 2.55mm), ni<ne (real ion density), decomposition closes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…hase 2, step 3-generate)

generate_bouquet / perturb_kinetic_equilibrium (approach A, backward-compatible):
- New optional kwargs p_fast / j_NBI / j_RF (default None -> provable no-op).
- Fixed driven currents (j_NBI+j_RF) are treated exactly like the bootstrap
  spike: additive, non-scaled, summed in at the 5 j_phi assembly/Ip-constraint
  points (recon-anchor, Ip root-solve, _matched, find_optimal_scale spike_prof,
  corrective target). Gated by recalculate_j_BS so the inductive base isn't
  double-counted. True bootstrap reporting (shelf-blend) is untouched.
- p_fast (fast-ion pressure) is added to the pressure after the perturbation;
  the thermal pressure-match diagnostic stays thermal-only.

Bouquet.generate:
- Resolves the uncertainty envelope (baseline.resolve_uncertainty: kinetic sigmas
  from IDA or fractional fallback + sigma_jphi + GPR lengths), threads the
  Baseline + configs into generate_bouquet, passes the fixed components.
- Zeff interpolated to the equilibrium grid (solve_with_bootstrap consumes it on
  psi_N, not the kinetic grid).

GenerationConfig reconciled with the actual generate_bouquet API and proven
operational defaults: homotopy_passes schedule [(0.05,0.10),(0.02,0.05),
(0.01,0.01)] (a single tight ±1% solve is infeasible), l_i_tolerance 0.05,
jBS_scale_range (0.99,1.01), coil_drift 0.01.

Verified end-to-end against the synthetic D3D-like fixture (live GS solves):
- zero fixed components: draw archived IN_SPEC (backward-compatible path);
- nonzero synthetic j_NBI: re-partitions inductive (j_phi unchanged), Ip
  constraint absorbs it (Ip err 0.029%), l_i matched, archived IN_SPEC.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e 2, steps 1-3)

- Bouquet.filter(): wraps filter_coil_currents + filter_boundaries (coil-current
  spec + LCFS boundary RMS), writing pass flags into the HDF5; returns the
  summaries.
- Bouquet.export(): wraps export_filtered -> pruned {header}_selected.h5 of the
  in-spec subset.
- setup_solver extended to ImasSource: F0 = |r0*b0(t)| and the isoflux LCFS
  derived from the FUSE IDS (vacuum_toroidal_field + boundary.outline) via the
  new io.imas.read_imas_geometry, replacing the g-file path.
- __init__.py: export the orchestrator API (Bouquet, BouquetConfig, the
  sources/configs, read_ida, read_imas_baseline, read_imas_geometry, the physics
  reductions, resolve_baseline/resolve_uncertainty). run.py keeps OFT imports
  lazy so package import stays headless-safe.

Verified: `bouquet.Bouquet(cfg).run()` (setup -> baseline -> generate -> filter
-> export) on the synthetic D3D-like fixture archives 3 draws and exports the
in-spec subset (full [0,1,2] -> selected [0,2]); IMAS setup_solver + baseline
resolve confirmed against a D3D FUSE run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
D3Dlike_baseline_omas.json: a minimal FUSE-style OMAS data dictionary
(equilibrium + core_profiles + core_sources, single slice) built from the
synthetic D3Dlike geqdsk + p-file with smooth generic fits for the
parallel/toroidal ratio, bootstrap split, beam current, and fast pressure.
Non-proprietary; exercises the ImasSource path. .gitignore also blocks dd_exp.json.
- Bouquet.prepare_baseline forward-solves the IMAS baseline on mygs (the recon
  path gets this from reconstruct_equilibrium): init_psi from the IDS LCFS shape,
  then jphi-linterp solve with the IMAS j_phi + thermal+fast pressure.
- l_i_target set to the TokaMaker-solved li_1; IDS li_1/li_3 kept in
  baseline.li_metrics for sanity (read_imas_baseline captures them).
- recalculate_j_BS=True path: the forward-solve rebuilds the baseline on the
  SWB-bootstrap basis the draws use (inductive*scale + SWB spike + fixed,
  Ip-matched), so the target l_i is reachable. recalc=False keeps the IDS
  bootstrap. Verified: draws now land in-band (l_i err ~1.6%).
- read_imas_baseline: pfile_bytes=None (the OMAS JSON is not an Osborne p-file).
- Baseline gains an li_metrics field.

Known follow-up: IMAS draws currently exceed the coil/VSC homotopy bounds
(VSC drift ~15%) and are filtered out; needs VSC/coil conditioning work.
…otting overhaul

Working-tree checkpoint of the class-API branch before the pre-merge
cleanup pass (audit 2026-06-12): extra-profile perturbation switchboard,
captured solver output (capture_native_output), reconstruction/generation
summaries, IMAS forward-solve li-target alignment, and the plotting
refactor. Untracked notebook/scaffolding files intentionally excluded.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Remove the shot-named legacy notebook and the superseded baseline derived
from an experimental boundary; genericize all shot references in code
comments and docs. Rewrite README Quick Start around bq.Bouquet (old example
passed removed/percent-valued parameters), add architecture.md \xC2\xA71.5 for the
class-API layer, mark CHANGES_SUMMARY historical, gitignore working notes,
and track the tests/data fixtures CI needs.
…at-if, sigma-alias plot, uniform config idiom
…er-draw nz1

Perturb what is measured, derive what is inferred: with the zeff channel
active, each draw samples (ne, Zeff) and derives ni via single-impurity
quasineutrality (physics.effective_impurity_charge /
main_ion_density_from_zeff; Zeff clipped to [1, Z_imp] so ni, nz >= 0).
sigma_ni is unused in this mode (one-time NOTE). One Zeff per draw across
the archive, aux datasets, and per-draw p-file (verified to ~2e-4).

Rename the switchboard API extra_* -> aux_* (plural dicts: aux_sigmas /
aux_baselines / aux_length_scales; Baseline.aux; h5 aux_<name> with legacy
extra_* read fallback; plot_aux_profiles, plot_transport_profiles alias).

Fixes surfaced while validating: generate_bouquet now purges its own
header/scan's draw groups up front (append-mode archives let a previous
run's draws survive under failed counts -- mixed-baseline ghosts); the
per-draw p-file nz1 is recomputed from the draw's own (ne, ni) instead of
frozen at baseline (the source of the second, uncorrelated Zeff family).
…urity_Z

IDA reader now derives ni from its measured Zeff (single-impurity
quasineutrality) instead of ni=ne, making it information-equivalent to a
p-file and fixing the latent pressure-vs-bootstrap inconsistency. New
ReconstructionSource.impurity_Z (carbon 6.0; from_geqdsk kwarg) controls the
mapping; OMAS takes Z_imp from IDS species. zeff channel is on by default for
every source (UncertaintyConfig.zeff_scalar_sigma=0.05, auto-injected in
resolve_uncertainty; 0.0 disables). Reconstruction Baseline now exposes
baseline.aux['zeff'] like IMAS. Plotting: opaque legend frames (fix marker
ghosting), P'/FF' in gold/orange. +impurity round-trip tests (151 pass).
…eqdsk

geqdsk example documents the Zeff-consistent density default and impurity_Z.
OMAS example rebuilt to mirror the geqdsk arc (honest OFT guard, version
print, seed, plot_traces li_band, plot_spec_summary, knob table, aux naming)
with OMAS-specific calls preserved (from_imas/prepare_baseline,
plot_imas_bouquet, IDS-auto-filled aux, 3-slice time evolution). Both
re-executed clean: 0 errors/warnings/personal-paths.
…/IMAS 148798 diff+C: 15/15 yield, FUSE-faithful q=2 bootstrap, F9B +/-1.05%.
…+ robustness guards

from_imas auto-applies diff+C (jBS_baseline_mode='diff', perturb_jind_in_anchor=True);
from_geqdsk auto-applies the standard flagship l_i loop (perturb_jind_in_anchor=False).
generate() now hard-guards known-bad combos via _validate_workflow (geqdsk+FixC,
IMAS-without-FixC, jphi_scalar_sigma<=0 which freezes j_inductive); bypass with
config.generation.allow_unsafe_workflow=True for backend tests.

Config defaults: floor_j_BS=False (a no-op with isolate_edge_jBS=True; was a red
herring on 204441), perturb_jind_in_anchor=False (geqdsk default -- the standard
l_i loop is the flagship GPR j_inductive perturbation; both paths perturb j_ind).

Guard per-draw get_stats against degenerate draws (Ip->0 after Sauter corrector
failures) so one bad draw no longer crashes the whole run (was losing all stored
draws). Add FLOOR_DIAG / BQ_SKIPLOG env diagnostics.

Validated by an n=20 robustness campaign: IMAS diff+C 65-100% in-spec at 20/20
completion; geqdsk standard-loop 18-20/20 completion (in-spec tracks coil headroom).
…ds=1 default

Make the IMAS-path baseline reproduce the FUSE equilibrium GPEC reads, so per-draw
2/1 gamma ensembles frame the FUSE baseline (148798: 8/8 slices within 1 sigma).

Pressure:
- physics.impurity_pressure: add the impurity (carbon) thermal pressure nz*Ti
  (single-impurity model) that single-ion e*(ne*Te+ni*Ti) omitted (~10% low at
  high Zeff). io/imas, perturb, and the run.py baseline solve all include it.
- p_diff anchor: baseline solve pressure == dd equilibrium.pressure exactly (fixed
  offset added to baseline + every draw, mirrors jBS_diff); absorbs the fast-ion
  treatment residual.
- _validate_pressure_completeness: fail-fast if the source carries thermal/fast
  pressure the reconstruction omits; GenerationConfig.allow_incomplete_pressure
  bypasses (backend tests only).

Current:
- jphi_diff anchor: target the GS-consistent equilibrium.j_tor (carries the pedestal
  current) instead of the smooth core_profiles.j_tor. Fixed offset folded into
  j_fixed_eff so it rides under every per-draw build; the SWB bootstrap + perturbed
  j_ind still drive per-draw variation. GenerationConfig.anchor_jtor_to_equilibrium
  (default True), IMAS path only.
- from_imas: isolate_edge_jBS=False -- FUSE/IMAS carries a full Sauter bootstrap
  (core hump + edge), so the DIII-D g-file isolated-edge-spike decomposition is
  skipped (it mislabels a redundant j_BS,edge and mangles per-draw j_inductive).

Reproducibility:
- SolverConfig.nthreads default 4 -> 1: GS solves bit-reproducible. nthreads>1
  OpenMP nondeterminism caused ~1% li_1 scatter AND intermittent DLSODE
  non-convergence hangs at stiff slices; parallelize across slices as separate
  single-threaded BLAS-pinned processes instead.

Stored-field fixes (diagnostics only): _baseline now stores the ACTUAL solved
quantities (pressure_solve incl. carbon/fast/p_diff; j_phi incl. jphi_diff) rather
than the un-anchored inputs, so archived diagnostics match what was solved.
from_imas gains ida_path/efit01_geqdsk/impurity_Z/kinetic_source/
anchor_pressure_to_equilibrium. kinetic_source='ida_hybrid' resamples IDA
ne/Te/Ti/omega onto the FUSE psi_N grid (single-grid; psi_N==psi_N_kinetic),
keeps FUSE Z_eff/Z_imp/ni-dilution (IDA Z_eff is unreliable), and derives ni via
single-impurity quasineutrality. p_diff equilibrium-pressure anchor gated behind
anchor_pressure_to_equilibrium (default False). read_imas_geometry swaps the LCFS
isoflux target to a magnetics-only EFIT01 g-file when efit01_geqdsk is set.
Sigma envelopes auto-sourced from the same IDA .cdf. Validated on 148798:
2/1 baseline gamma jitter ~2.5x lower than the FUSE-fit baseline.
…1-thread doctrine

- run_shard removes pre-existing shard h5 (stale-draw contamination fix)
- merge_archives verifies cross-shard l_i/Ip targets before copying (covers SLURM path)
- CLI merge counts expected-vs-found shards; --allow-missing for deliberate partial merges
- warn on threads_per_worker>1; re-emit SLURM example at nthreads=1 (was 4)
…adial_field_from_cer)

IDACERProfiles carbon channels + sigma envelopes; three-term E_r with uncertainty propagation. Unit tests for terms/units, diamagnetic sign, sigma positivity.
…g serialization, de-threaded readers

Bare dataset names with units attrs, fixed eqdsk/pfile names, unified coil storage (golden regenerated); BouquetConfig.to_dict/from_dict + config_json provenance (incl. merged archives; load_config scan-aware); BouquetArchive reader with cached views and O(N) flags; schema.find_bytes_dataset replaces 8 duplicated lookups; workflow presets; JSON SLURM bundle; review fixes: solver-marked tests un-broken, unique eqdsk extraction filenames, legacy-archive warnings/errors.
@d-burg d-burg self-assigned this Jul 2, 2026
Copilot AI review requested due to automatic review settings July 2, 2026 19:42

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Refactors Bouquet’s workflow into a class-based orchestrator API (BouquetConfigBouquet.run() stages), introduces a clean-break schema v2 HDF5 archive layout (self-describing datasets + fixed blob names), and adds supporting readers/physics utilities plus parallel generation tooling. This PR also updates tests and documentation to align with the new API and schema conventions.

Changes:

  • Added the class-based config/orchestrator stack (bouquet/config.py, bouquet/baseline.py, bouquet/archive.py, updated __init__.py) and schema v2 helpers (bouquet/schema.py).
  • Added new physics + IO utilities (bouquet/physics.py, bouquet/io/ida.py, bouquet/paths.py, bouquet/parallel.py) and extensive new/updated tests.
  • Updated docs/examples and migrated codepaths from scan_valuescan_key and bracketed HDF5 dataset names → bare names + units attrs.

Reviewed changes

Copilot reviewed 38 out of 49 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tests/test_systematics.py Updates golden replay to schema v2 dataset names and coil-name reading helper.
tests/test_physics.py Adds pure-numpy unit tests for new physics reductions and derived quantities.
tests/test_ida.py Adds synthetic IDA .cdf tests for both direct and ensemble layouts (incl. CER channels).
tests/test_golden_bouquet.py Updates golden fixture tests for scan_key, schema v2 blob/dataset names, and filter return shapes.
tests/test_core.py Updates tests to discover_scan_keys and schema v2 baseline keys.
tests/test_config.py Adds serialization/provenance tests for BouquetConfig and schema v2 provenance storage.
tests/test_archive.py Adds tests for BouquetArchive reader, suffix-scan fallback, selection partitioning, and coil schema v2 regression.
README.md Updates docs to describe class API, schema v2, and IMAS/OMAS pipeline; refreshes examples.
pyproject.toml Switches to dynamic version sourced from bouquet.__version__.
examples/README.md Adds guidance for running the synthetic examples and OFT path setup.
examples/D3D-like/slurm_jobs/bouquet_2000ms_submit.sh Adds SLURM submission helper chaining array + merge.
examples/D3D-like/slurm_jobs/bouquet_2000ms_merge.sbatch Adds SLURM merge job script for parallel generation.
examples/D3D-like/slurm_jobs/bouquet_2000ms_array.sbatch Adds SLURM job-array script for shard generation.
examples/D3D-like/legacy/superseded_0p8MA_baseline/D3Dlike_Hmode_baseline_RECIPE.md Removes superseded legacy baseline recipe.
examples/D3D-like/generate_baseline.py Pins fixture generation knob (isolate_edge_jBS=True) for reproducibility.
examples/D3D-like/D3Dlike_Hmode_baseline_RECIPE.md Updates baseline recipe narrative to remove obsolete references.
docs/gui-display-guide.md Renames notebook plotting entrypoints to plot_bouquet.
docs/CHANGES_SUMMARY.md Marks change summary as historical and updates solver-test invocation note.
bouquet/uncertainties.py Updates comments to avoid proprietary shot-specific wording.
bouquet/schema.py Adds schema v2 canonical constants + profile IO helpers + blob dataset resolver.
bouquet/sampling.py Adjusts monotonic draw constraint handling for non-monotonic baselines.
bouquet/physics.py Adds shared physics reductions (fast pressure isotropization, parallel→toroidal conversion, impurity/fast-pressure helpers, CER E_r).
bouquet/paths.py Adds OFT and mesh path resolution helpers for portable environments.
bouquet/parallel.py Adds process-parallel generation + SLURM emission + robust merge/baseline checks.
bouquet/io/ida.py Adds IDA .cdf reader + CER channel reader with dual-layout support.
bouquet/gui.py Migrates GUI browsing to discover_scan_keys and schema v2 dataset naming.
bouquet/filtering.py Migrates selection/filter APIs to scan_key, improves scan-key validation, and uses schema v2 blob resolution.
bouquet/config.py Introduces typed config dataclasses with JSON round-trip serialization.
bouquet/baseline.py Adds Baseline abstraction + source resolution and uncertainty resolution.
bouquet/archive.py Adds high-level BouquetArchive reader abstraction for schema-aware access.
bouquet/init.py Exposes new public API surface, adds __version__, and updates exports.
architecture.md Documents class API/baseline sources and updates wording to avoid proprietary references.
.gitignore Tightens proprietary data ignores and explicitly keeps required test fixtures.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread README.md
Comment on lines +233 to 236
# Visualise any run from its HDF5 archive
from bouquet import plot_bouquet, plot_traces
plot_bouquet("my_run.h5", scan_value=0, mode="all")
plot_traces("my_run", scan_value="all")
Comment thread docs/gui-display-guide.md
Comment on lines +176 to +179
fig, axes = plot_bouquet("equilibria.h5", scan_value=1.0, mode="kinetic")

# All plot types at once (returns lists)
figs, axes_list = plot_family("equilibria.h5", scan_value=1.0, mode="all")
figs, axes_list = plot_bouquet("equilibria.h5", scan_value=1.0, mode="all")
export OMP_NUM_THREADS=1
export OMP_PROC_BIND=close OMP_PLACES=cores
export OPENBLAS_NUM_THREADS=1 MKL_NUM_THREADS=1
python -m bouquet.parallel shard bouquet_2000ms_bundle.pkl $SLURM_ARRAY_TASK_ID
#SBATCH --mem=16G
# compute-node environment (adjust for your cluster), e.g.:
# export OFT_PYTHONPATH=/path/to/OpenFUSIONToolkit/build_release/python
python -m bouquet.parallel merge bouquet_2000ms_bundle.pkl
Comment thread bouquet/baseline.py
Comment on lines +371 to +387
if source.profile_overrides:
raise NotImplementedError("profile_overrides is not yet applied")

with open(source.geqdsk_path, "rb") as fh:
eqdsk_bytes = fh.read()
eqdsk = read_geqdsk(source.geqdsk_path, cocos=source.cocos)
psi_N = np.asarray(eqdsk.psi_N, dtype=float)

kin = _load_kinetic_profiles(source)
psi_N_kin = kin["psi_N"]

# kinetic profiles (native SI) interpolated onto the equilibrium psi_N grid
def to_eq(arr):
return np.interp(psi_N, psi_N_kin, np.asarray(arr, dtype=float))

ne_eq, te_eq, ni_eq, ti_eq = to_eq(kin["ne"]), to_eq(kin["te"]), to_eq(kin["ni"]), to_eq(kin["ti"])
Zeff_eq = np.clip(to_eq(kin["Zeff"]), 1.0, None)
Comment thread bouquet/io/ida.py
Comment on lines +132 to +134
Opens with ``h5py.File(path, "r")`` -- the file is netCDF4/HDF5, so no
OMFIT or netCDF4 package is needed. Units are already SI; ``T_12C6`` maps to
Ti, and ``ni`` is reconstructed as ``n_e - Z_imp * n_12C6``.
RectBivariateSpline.ev(scalar, scalar) returns shape (1,) as of scipy 1.18, and numpy 2.5 makes float() of a 1-d size-1 array a TypeError. Use .item() (valid for 0-d and (1,)) at the single scalar .ev() call site; all other .ev() uses take arrays. Verified against numpy 2.5.0 + scipy 1.18.0 (CI versions) and 2.4.2/1.17.1.
@d-burg
d-burg merged commit 8753c3a into main Jul 2, 2026
2 checks passed
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.

2 participants