Repository navigation
Repo - FEATURE - Per-stage runtime records in gpec.h5 under Info/Runtimes - #385
Conversation
…Runtimes main_from_inputs already timed every pipeline stage but only logged the durations. Persist them under Info/Runtimes/<stage> (Float64 seconds, annotated with long_name/units) so an output file answers "where did the time go" on its own. Only the stages that ran are recorded; timings are informational, never a regression quantity. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…Runtimes Collect Info/Runtimes inside the warm-run loop (each run overwrites gpec.h5), average over the stages present in every run, and print a per-stage delta table alongside the existing totals. Refs predating the record yield an empty dict and the table is skipped silently, so old-vs-new comparisons still work. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ture/h5-runtime-records # Conflicts: # src/GeneralizedPerturbedEquilibrium.jl
…erge) Temporary scaffolding for continuing this branch on another machine: what the branch does, the merge resolution against upstream's inline-path convention, verification already done, the wrong-baseline regression pitfall, and the outstanding work. Carries its own removal instructions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…sion question Both test files pass on the merged tree, and the regression residuals are resolved: the case is deterministic, and parent-vs-feature run through the same worktree path shows 48 unchanged / 0 changed. Documents the commit-vs-local environment trap that produced the earlier spurious diffs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Verification complete — post-mergeAll checks re-run against the merged tree (
Regression tableZero movement, as expected — this change computes Note for anyone rerunning the harness on this branchTwo baseline traps produced misleading reports along the way; both are documented in the handoff doc:
The case itself is deterministic: two
|
…pinning fix Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ture/h5-runtime-records
…put file The isfile guard alone let a run with write_outputs_to_HDF5=false stamp its timings into a stale gpec.h5 left over from an earlier run. Gate both call sites on the run having produced the file, keeping the SLAYER-created case. Move the stage long_names into HDF5Schema.jl as RUNTIME_H5_ANNOTATIONS, next to the other Info/ metadata; annotate! skips the stages that did not run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dh1NCejnd3fYMmcRKoQRcG
Temporary scaffolding for a machine switch; its contents are folded into the PR description. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dh1NCejnd3fYMmcRKoQRcG
…ture/h5-runtime-records
…arison The replay guard compares every dataset in a source gpec.h5 against its rerun. Wall-clock seconds cannot repeat, so the new records broke it the same way Info/git_version would; skip the group alongside it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dh1NCejnd3fYMmcRKoQRcG
…le the run wrote Four corrections to the Info/Runtimes records after the schema overhaul landed: - The stage token is `tearing`, not `slayer`. The `slayer/` group became `Tearing/` under the "name the physics, not the algorithm" rule, and every other token already mirrors its group. The solver is named in the long_name instead. - `perturbed_equilibrium` is recorded only when the section is present. It was pushed unconditionally, so a stability-only run stamped the stage at ~0 s for work that never happened. - `_write_runtimes!` now targets the file the run actually wrote. It hardcoded ctrl.HDF5_filename, but PerturbedEquilibrium, KineticForces and SLAYER each write their own filename, so timings were silently dropped whenever a stage other than ForceFreeStates produced the output. Each write site records its path in `written_h5` (last writer wins, matching where SLAYER appends) and both exit paths stamp that file, replacing the write_outputs_to_HDF5/slayer_result proxy with the fact it was approximating. - The benchmark per-stage table prints an em dash instead of Inf/NaN when the branch-1 baseline is zero. The galerkin and total long_names now state that the values nest rather than partition the run, so a reader does not sum them, and workflow.md's Info/ row mentions Runtimes/. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VGmFuAw5JdYrAyBSXCVssR
Brought current with develop + four correctionsBranch updated. #364 merged this morning, so GitHub auto-retargeted this PR from Re-reviewed against the conventions as they finally landed. Four things needed fixing (
Also: the The rest reviewed clean: the annotation table, the Local Important This PR requires a third-party human review approval of its current head before merge. No auto-merge label applied. |
|
All checks green, and the suite ran against this branch in CI for the first time now that the base is
Local, against the final tree: Direct check of the written files on two real runs, confirming the two behavioural fixes: The SLAYER run records Important Green CI is not review. This PR still requires a third-party human approval of its current head ( |
|
@priyanshlunia review & merge this in the next 24 hrs please. Just fix any minor issues yourself directly. Ping me if anything significant changes. |
Re-thread the Info/Runtimes instrumentation through the staged pipeline (run_force_free_states / run_perturbed_equilibrium / run_kinetic_forces / run_slayer_stage): stage windows and the written-file tracking now live at the call sites in main_from_inputs, with an optional runtimes collector kwarg on run_force_free_states (galerkin) and run_slayer_stage (tearing) for the timers those stages own.
|
Context. The PR head ( The resolution taken: keep all bookkeeping decisions in The three mechanisms1. Call-site timing (equilibrium, force_free_states, perturbed_equilibrium, kinetic_forces, total)
2. Collector kwarg (galerkin, tearing)Two timers measure something inside a stage function, so the call-site window can't
Both default to 3. Write-site inference (
|
| Stage | Where the write happens now | Inference in main_from_inputs |
|---|---|---|
| ForceFreeStates | still in the orchestrator | direct assignment inside the existing if ctrl.write_outputs_to_HDF5 block (unchanged) |
| PerturbedEquilibrium | inside public perturbed_equilibrium, gated on pe_ctrl.write_outputs_to_HDF5 (default true) with filename fallback output_filename == "" → ctrl.HDF5_filename |
section present && get(inputs["PerturbedEquilibrium"], "write_outputs_to_HDF5", true) → written_h5 = pe_file; the pe_file derivation (which already mirrored the filename fallback for SLAYER) was hoisted above the PE call |
| KineticForces | inside run_kinetic_forces, gated on section && pe_state !== nothing && kf_ctrl.write_outputs_to_HDF5 |
same three conditions, all visible in the orchestrator (kf_ctrl is in scope) |
| SLAYER | inside run_slayer_stage's guarded try |
slayer_result !== nothing ⟹ the Tearing/ append succeeded → written_h5 = pe_file (or ctrl.HDF5_filename on the force_termination path) |
"Last writer wins" ordering and the two stamp points (the force_termination early return
and the final banner) are unchanged from the PR head, as are _write_runtimes!,
RUNTIME_H5_ANNOTATIONS, the tests, the benchmark-script changes, and the docs rows.
What changed relative to the PR head, in one list
- Removed the
_run_slayer_stageclosure instrumentation (the closure itself is gone
on develop) → replaced by theruntimeskwarg on the top-levelrun_slayer_stage. - Moved the galerkin timer into
run_force_free_statesvia the same kwarg pattern. - Moved PE/KF timing from inside the (former) inline blocks to call-site windows —
numerically equivalent (same window + function-call overhead). - Replaced direct
written_h5assignments at the PE and KF write sites with the
gate-mirroring inference above; SLAYER's assignment became result-based inference. - Reverted a bad auto-merge: git had spliced the PR's
push!(runtimes, "perturbed_equilibrium" => ...)intorun_perturbed_equilibrium, whereruntimes
doesn't exist — it parsed fine and would have thrownUndefVarErroron every PE run.
Trade-off and review focus
This implementation keeps the diff minimal and every stage function's signature/return contract
intact (only two additive kwargs), at the cost of duplicating three write-gate
conditions in the orchestrator. If a default flips (e.g.
PerturbedEquilibriumControl.write_outputs_to_HDF5) or a write gate gains a condition, the
mirror in main_from_inputs must follow — each mirror carries a one-line comment naming
what it mirrors. These three conditions are the right review focus; the alternative
(stage functions return the filename they wrote) is drift-proof but touches
internal signatures — easy to switch to later if the mirrors prove fragile.
Verification: 99 tracked harness quantities across 5 runnable cases, zero moved;
19/19 fullruns tests plus schema and replay tests pass; Info/Runtimes confirmed in real
outputs including the tearing record.
|
@logan-nc pinging you since this was a relatively significant edit. |
Brings the branch current with develop (309 commits). Three source conflicts and one test conflict, resolved per docs/development/git-workflow.md: - prepare_force_free_states!/galerkin_solve: develop renamed the coefficient-matrix binding ffit -> mats. Applied this branch's runtimes instrumentation to the renamed symbol rather than reverting either side. - main_from_inputs force_termination early return: combined this branch's runtime recording with develop's widened named tuple (coil_sensitivities, monte_carlo, locking_risk, efc_couplings). - runtests_fullruns.jl: both sides appended independent assertions at the same point; kept both. Develop's run_error_fields stage is preserved in the pipeline. NOTE: that stage is a new HDF5 write site that this feature's `written_h5` bookkeeping does not yet mirror, and it records no Info/Runtimes entry. Both are follow-ups, not merge artifacts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSrf6JCViFfVzqzkQ66o6b
…write gate Develop added run_error_fields between KineticForces and SLAYER while this branch was stale. It is a stage of the pipeline and a write site of its own, and the orchestrator tracked neither, so two things were wrong once the branches met: - Info/Runtimes had no error_fields entry, although the stage already measures its own wall-clock and every other stage is recorded. - written_h5 did not mirror the ErrorFields write gate. With ForceFreeStates and PerturbedEquilibrium writes switched off and no SLAYER section, an ErrorFields run produces a real output file that no tracked stage had written, so written_h5 stayed nothing and the timings were dropped silently. With a distinct output_filename the stamp could also land in a file that was not the last one written. Both fixed by one block after the stage call, matching the existing PerturbedEquilibrium and KineticForces pattern: push the timing, and mirror the gate (write_outputs_to_HDF5 defaults to true; the filename falls back to the ForceFreeStates file, which is the same control the result carries). The regression test is the sole-writer configuration, which is the case that was silently broken: ErrorFields writing to its own ef_only.h5 with every other write off. Without the mirror no stage has written anything, so Info/Runtimes never appears in that file. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSrf6JCViFfVzqzkQ66o6b
…assertion The sole-writer test had five assertions and a four-line preamble for what is a single fact: with every other write switched off, the Info/Runtimes record lands in the file ErrorFields wrote. That one assertion is self-guarding -- if any other stage were the last writer, the record would not be in ef_only.h5 and the test fails -- so the isfile, total and absent-gpec.h5 checks added nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSrf6JCViFfVzqzkQ66o6b
… runtime ForcingTerms is its own workflow box between ForceFreeStates and PerturbedEquilibrium, with its own TOML section, and on coil decks it can be expensive: compute_coil_forcing_modes! runs Biot-Savart from every coil segment onto the mtheta_coil x nzeta_coil plasma-surface grid, once per toroidal mode. That cost was folded silently into perturbed_equilibrium. Time the materialize_forcing_modes prologue inside compute_perturbed_equilibrium and record it as Info/Runtimes/forcing_terms, nested in perturbed_equilibrium exactly as galerkin is nested in force_free_states. The collector threads run_perturbed_equilibrium -> perturbed_equilibrium -> compute_perturbed_equilibrium as a defaulted keyword; perturbed_equilibrium takes it explicitly so it is not swallowed into the PerturbedEquilibriumControl kwargs splat. Recorded only when materialization runs. Injected modes (h5 replay, and file-format decks whose modes snapshot_forcing_modes pre-loads) short-circuit it, so those runs carry no forcing_terms record. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSrf6JCViFfVzqzkQ66o6b
|
@priyanshlunia this will auto-merge if/when you change the review to "accept". Thanks |
Release note
Every GPEC run now records how long each stage took, under
Info/Runtimes/in the output file(seconds, one dataset per stage that actually ran, plus
total). The values nest rather thanpartition the run —
galerkinis measured insideforce_free_states,forcing_termsinsideperturbed_equilibrium, andtotalspans everything —so they do not sum.
benchmarks/benchmark_git_branches.jlreports the per-stage deltas between tworefs. Timings are informational only: machine- and load-dependent, never regression quantities.
Regression report
What and why
main_from_inputstimed every pipeline stage but only logged the durations, so an output file could not answer "where did the time go". This persists them underInfo/Runtimes/<stage>(Float64 seconds,long_name+units="s"per the self-describing-HDF5 contract from #364), and teachesbenchmarks/benchmark_git_branches.jlto report per-stage deltas averaged over its warm runs.Stages, each recorded only when it actually runs:
equilibrium,galerkin,force_free_states,forcing_terms,perturbed_equilibrium,kinetic_forces,error_fields,tearing, plustotal.The values nest rather than partition:
galerkinis measured inside theforce_free_stateswindow,forcing_termsinside theperturbed_equilibriumwindow, andtotalspans the whole run, so the numbers do not sum. Theirlong_names say so, since that is the one thing a reader of the file cannot infer.Timings are informational only — machine- and load-dependent. They are not regression quantities and should never be added to the harness.
Notes for review
total_dt, so the log line and the recorded value cannot disagree._write_runtimes!guards onisfile(no-op whenwrite_outputs_to_HDF5=falseand nothing else wrote a file) and deletes an existingInfo/Runtimesbefore writing, so reruns into an existing file stay idempotent. It targets the file this run actually produced:ctrl.HDF5_filenamenormally, or the SLAYER stage'spe_filewhenwrite_outputs_to_HDF5=false.Utilities.HDF5Annotations.annotate!; no new annotation path.test/runtests_h5_schema.jlenforces the metadata automatically — its walker fails if anyInfo/Runtimes/*dataset lackslong_name/units.Info/Runtimesread is inside the warm-run loop, since each run overwritesgpec.h5. Refs predating the feature yield an empty dict and the table is skipped silently, so old-vs-new comparisons keep working.tearing, notslayer: ALL - IMPROVEMENT - Self-describing HDF5 metadata (issue #226, PR B) #364 renamed theslayer/group toTearing/under the "name the physics, not the algorithm" rule, and the other tokens already mirror their groups. The solver is named in thelong_nameinstead.Verification
runtests_h5_schema.jlruntests_fullruns.jlruntests_rerun_from_h5.jlInfo/Runtimesexempted from the bit-for-bit replay comparison)diiid_n1total-last ordering, silent skip on empty/disjoint sides, zero-baseline Δ% guard🤖 Generated with Claude Code
Update 2026-09-28: brought current with develop, plus the ErrorFields stage
The branch was 309 commits behind and
CONFLICTING. Merged develop; four conflicts, resolved perdocs/development/git-workflow.mdrather than taking a side:prepare_force_free_states!/galerkin_solve: develop renamed the coefficient-matrix bindingffit→mats. Applied this branch's instrumentation to the renamed symbol.main_from_inputsforce_terminationearly return: combined this branch's runtime recording withdevelop's widened named tuple (
coil_sensitivities,monte_carlo,locking_risk,efc_couplings).runtests_fullruns.jl: both sides appended independent assertions at the same point; kept both.Develop added a stage while this sat:
run_error_fields, between KineticForces and SLAYER. It isboth a pipeline stage and an HDF5 write site of its own, and this feature tracked neither. Two
consequences, one cause:
error_fieldsentry inInfo/Runtimes, though the stage already measures its own wall-clock.written_h5did not mirror the ErrorFields write gate. With ForceFreeStates and PerturbedEquilibriumwrites off and no SLAYER section, an ErrorFields run produces a real output file that no tracked
stage wrote — so
written_h5stayednothingand the timings were dropped silently. With adistinct
output_filenamethe stamp could also land in a file that was not the last one written.Fixed with one block after the stage call, matching the existing PE/KF pattern. The regression test is
the sole-writer configuration — ErrorFields writing to its own
ef_only.h5with every other write off— which is precisely the case that was broken, and which no existing fixture covered.
Also corrected for CI: the title (
GPECis not an Area — the checker says useRepo; and the TAG mustbe the single word
FEATURE), and the missing## Release noteblock.Verification at
b87a66aa9Pkg.testonruntests_error_fields.jl,runtests_h5_schema.jl,runtests_fullruns.jl,runtests_rerun_from_h5.jl— all pass.diiid_n1,develop @ 786a12154vs this branch: 53 quantities, all unchanged(table above). Expected: the feature appends timings after all computation.
A design note for the reviewer
The
written_h5bookkeeping now carries 6 tracking points inmain_from_inputsmirroring 5 writesites that live in 4 different functions, none adjacent to their mirror. @priyanshlunia flagged this
when resolving the last conflict: "If a default flips or a write gate gains a condition, the mirror in
main_from_inputsmust follow." That drift then happened, within five weeks, via ErrorFields. Thefourth mirror is the minimal correct fix for this PR; a more robust design is discussed separately and
is deliberately not in scope here.
Update 2026-10-01: ForcingTerms runtime, test trim
Info/Runtimes/forcing_terms. ForcingTerms is its own workflow box between ForceFreeStates andPerturbedEquilibrium, and on coil decks it can be a large share of the PE window:
compute_coil_forcing_modes!runs Biot-Savart from every coil segment onto themtheta_coil × nzeta_coilplasma-surface grid, once per toroidal mode. That cost was folded silentlyinto
perturbed_equilibrium. It is now timed around thematerialize_forcing_modesprologue andrecorded nested inside
perturbed_equilibrium, exactly asgalerkinnests insideforce_free_states. The collector threadsrun_perturbed_equilibrium→perturbed_equilibrium→compute_perturbed_equilibriumas a defaulted keyword;perturbed_equilibriumtakes it as a namedkeyword so it is not swallowed into its
PerturbedEquilibriumControlsplat.It is recorded only when materialization actually runs. Coil decks always record it. Runs with
injected modes — gpec.h5 replay, and
ascii/hdf5forcing decks, whose modessnapshot_forcing_modespre-loads in the orchestrator — short-circuit the prologue and carry no
forcing_termsrecord.Promoting ForcingTerms to a sibling section with its own banner is the structurally cleaner end state,
but it moves materialization out of the
perturbed_equilibriumpublic API and has to settle whethercoil modes enter the
Input/replay snapshot, so it is deliberately left for a follow-up.Test trim. The ErrorFields sole-writer test is down to its one decisive assertion; it is
self-guarding, since if any other stage were the last writer the record would not be in
ef_only.h5.Found in passing, not addressed here (develop bug): that same short-circuit means TOML-path
ascii/hdf5forcing decks skipconvert_forcing_normalization!—snapshot_forcing_modesdiscardsthe file's declared normalization tag. On
Solovev_ideal_examplePE'sforcing_b,forcing_b_rootareaand
dominant_forcing_overlapcome out 16.848× smaller than with materialization. Coil decks areunaffected. To be tracked separately; nothing in this PR changes that behaviour.
Verification at
592cfb5f3Pkg.testonruntests_error_fields.jl,runtests_h5_schema.jl,runtests_fullruns.jl,runtests_rerun_from_h5.jl,runtests_solve_api.jl(the last becauseperturbed_equilibrium'ssignature changed) — all pass.
diiid_n1,develop @ 003e939e0vs this branch: 53 quantities, all unchanged.This PR has zero submitted reviews. No auto-merge enabled.
🤖 Generated with Claude Code
https://claude.ai/code/session_01PSrf6JCViFfVzqzkQ66o6b