Skip to content

ALL - IMPROVEMENT - Self-describing HDF5 metadata (issue #226, PR B) - #364

Open
logan-nc wants to merge 2 commits into
refactor/hdf5-module-schemafrom
refactor/hdf5-metadata
Open

ALL - IMPROVEMENT - Self-describing HDF5 metadata (issue #226, PR B)#364
logan-nc wants to merge 2 commits into
refactor/hdf5-module-schemafrom
refactor/hdf5-metadata

Conversation

@logan-nc

Copy link
Copy Markdown
Collaborator

Summary

PR B of the two stacked PRs closing #226 (stacked on #363review/merge that first; this diff is additive on top of it). Every gpec.h5 dataset now answers "what is this, in what units, plotted against what" without opening the source — netCDF-grade, natively readable by h5py/xarray/HDFView.

What every dataset gets

  • long_name — plain-text physics description.
  • units — SI strings; "1" for dimensionless (CF convention); normalized quantities state the normalization in the long_name.
  • dims (rank ≥ 2) — greppable axis-name string in Julia (column-major) order, e.g. "(psi, mode)".
  • HDF5 Dimension Scales — coordinate datasets (ψ_N grids, rational-surface ψ, geometry xs/ys, KF quadrature ψ) marked via h5ds_set_scale and attached per axis with labels: h5py .dims, xarray, and HDFView resolve axes natively (verified with h5py: DIMENSION_LIST + labels present).
  • Root attributesschema_version="2.0" (readers get a dispatch point for future breaking changes), Conventions="GPEC-HDF5-2.0", references, title, date_created (ISO 8601 UTC).

Exempt (whitelisted in the enforcement test): Input/** (raw rerun snapshot) and GalerkinIntegration/Match/** (debug-only, drop pending Galerkin-owner decision — no point hand-curating ~15 entries for it).

Mechanism — one deviation from the approved plan wording

The plan said "route all writers through a write_annotated! helper". Implemented instead as table-driven post-hoc annotation: each writer keeps a path => (; long_name, units, dims) table next to it and applies it with one annotate! call after its datasets are written (Utilities.HDF5Annotations + src/HDF5Schema.jl for the main writer). Same contract, enforced identically by the schema test — but the writer bodies are untouched, keeping this PR genuinely low-conflict with #354/#345/#339/#318 and handling dynamic writes (per-field equilibrium loop, per-method KF groups) naturally. ~340 annotation entries total.

Physics review

The tables were audited by the fortran-physics-reviewer agent against the layer/field-reconstruction sources and the Fortran gpout.f attribute crib; 27 corrections applied, including: J-weighted contravariant fields are T*m^2 (χ₁-weighted), not T; Tearing/PerSurface/tauk is the Q-normalization time S^⅓·τ_H, not a resistive-kink time; island_width_sq is the squared half-width; Roots/omega_Hz is actually an angular frequency (rad/s) — the dataset name is a misnomer, flagged here rather than renamed (renaming is a breaking change; candidate for a future schema bump); delta_n is the Δ′-normalization S^⅓/r_s in 1/m. Reviewers: the covariant-component units (T*m, m^2) were settled by dimensional analysis — worth a second look.

Enforcement

test/runtests_h5_schema.jl now asserts on a full-run output: long_name+units on every non-exempt dataset, dims on every rank ≥ 2 array, root attributes present, and the ψ_N scale attached (28/28 passing). Additional metadata walks were run on the DIIID SLAYER, Solovev kinetic-NTV, and DIIID gal-resistive decks — zero violations. When adding a dataset, add its table entry in the same commit or the test fails — this is the anti-drift teeth #226 asked for.

Notes

  • One HDF5.jl wart worked around: the h5ds_set_label wrapper mis-types the C const char* as Ref{UInt8}; we pass a NUL-terminated byte buffer.
  • Dates (stdlib) added to Project.toml for date_created.
  • Known gap (pre-existing): LocalStability/di/dr have no stored coordinate grid to attach (their ψ grid isn't written); their dims attribute documents the axis name only.

Regression harness (mandatory gate) — zero movement

regress --cases <all 12> --refs develop,local: identical table to PR A — every surviving quantity OK at 0.0e+00 (48/21/15/16/10/4+4 pre-existing N/A/14/14/6/5/4/4). Attributes are invisible to the extractor and the rerun leaf-walk, as designed; the bit-for-bit rerun round-trip test passes with annotations present.

Other verification

Full schema test 28/28; SLAYER runner, coils, fullruns, rerun-from-h5 all pass; docs build clean (new HDF5Annotations autodocs covered); h5py external check confirms scales/attrs.

⚠️⚠️ NO MERGE WITHOUT THIRD-PARTY HUMAN REVIEW ⚠️⚠️

This PR must not be merged until a third-party human reviewer has approved it — non-negotiable project policy. Merge order: #363 first, then this.

🤖 Generated with Claude Code

https://claude.ai/code/session_0129rSTCmYJDBbcH9khHqYnz

logan-nc and others added 2 commits August 12, 2026 20:38
…dims, scales)

Every gpec.h5 dataset outside Input/ and the debug-only GalerkinIntegration/Match/
now answers "what is this, in what units, plotted against what" without opening
the source, readable natively by h5py/xarray/HDFView:

- New Utilities.HDF5Annotations: annotate!(parent, table) applies per-writer
  path => (; long_name, units, dims) tables post-write (skipping absent paths);
  make_scale!/attach_scale! wrap the H5DS dimension-scale API (netCDF-4
  coordinate mechanism) with Julia-axis -> C-dim index translation;
  write_root_attrs! stamps schema_version=2.0, Conventions=GPEC-HDF5-2.0,
  references, title, date_created.
- Table-driven, not per-write-call: writers are untouched except one annotate
  call at the end of each (src/HDF5Schema.jl for the main writer; tables live
  next to write_galerkin!, the PE writer, KineticForces/Output.jl, and
  Tearing/Runner/HDF5Output.jl). ~340 dataset annotations total.
- Coordinate datasets (psi grids, rational-surface psi, geometry xs/ys) are
  marked as HDF5 Dimension Scales and attached to the profiles sharing the axis;
  a greppable "dims" attribute mirrors the scales in Julia axis order.
- Attribute wording/units audited by the fortran-physics-reviewer against the
  layer/field-reconstruction sources; 27 corrections applied (J-weighted field
  units T*m^2, Q-normalization time vs resistive-kink time, island half-width
  vs full width, omega_Hz actually rad/s, etc.).
- runtests_h5_schema.jl now enforces the contract: long_name+units on every
  non-exempt dataset, dims on rank >= 2 arrays, root attrs present, scales
  attached; docs/development/hdf5-conventions.md gains the metadata contract.

Attributes are invisible to the rerun leaf-walk and the regression extractor:
no tracked value moves.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0129rSTCmYJDBbcH9khHqYnz
@logan-nc

Copy link
Copy Markdown
Collaborator Author

Full test/runtests.jl suite (all 57 testsets) run on this branch @ 0ece9c4: all pass, zero failures/errors. Together with the harness table in the description (zero movement, identical to #363's), the approved plan's verification checklist is complete.

@logan-nc

Copy link
Copy Markdown
Collaborator Author

@d-burg please review your outputs carefully here. In particular, the PR overview points out:

Roots/omega_Hz is actually an angular frequency (rad/s) — the dataset name is a misnomer, flagged here rather than renamed (renaming is a breaking change; candidate for a future schema bump);

It looks like you also have a delta_n for the Δ′-normalization S^⅓/r_s in 1/m? This seems dangerously close to delta_mlow, delta_mhigh which means likely confusion when @jhalpern30 extends the code to full 3D (thus perhaps getting delta_nlow type settings? Maybe not exactly that, but still...). How about changing your output to something more unique like delta_prime_norm? In general, please be explicit about prime things - there are far too many deltas in GPEC to be loosey goosey with primes! Apply the fixes on this branch rather than elsewhere to minimize merge conflicts, since the h5 changes have a stack of PRs going.

@logan-nc

Copy link
Copy Markdown
Collaborator Author

@d-burg is voluntold to do the full diff review on this one since he has some specific things called out.
@matt-pharr is in charge of checking units and long names (feel free to do more if interested).
@jhalpern30 please concentrate on the general h5 structuring logic (feel free to do more if interested).

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.

1 participant