Skip to content

ErrorFields/ForcingTerms - FEATURE - Add the tolerance TOML schema and parser - #449

Merged
logan-nc merged 30 commits into
developfrom
feature/errorfields-tolerance-toml
Sep 25, 2026
Merged

logan-nc merged 30 commits into
developfrom
feature/errorfields-tolerance-toml

Conversation

@logan-nc

@logan-nc logan-nc commented Sep 11, 2026 •

Copy link
Copy Markdown
Collaborator

Fourth PR of the OMFIT → Julia error-field tolerance migration, stacked on #448. It fixes the tolerance input format: a TOML file, separate from gpec.toml, holding the manufacturing tolerances of each coil set and coherent group, the correctability split, and the unattributed error-field budget. The sampling, Monte Carlo, and risk stages that consume it follow in PR5–PR7; this PR gives them a validated, replayable input and keeps device numbers out of the repository.

What it does

  • src/ErrorFields/ToleranceTOML.jl: read_tolerance_toml / parse_tolerance_toml → ToleranceSet (CoilTolerance, CoherentGroupTolerance, OtherFieldBudget), with an [ErrorFields.defaults] table for per-coil fallbacks. Every key is schema-checked (a misspelled key errors), tolerances non-negative, enumerations normalized, efc_factor ≥ 1, a cylinder model needs a positive half-height.
  • validate_tolerances(ts, coil_names) cross-checks coil, member, and uncorrectable names against a run's coil sets; tilt_tolerance_deg converts a rim-displacement tilt tolerance in metres to degrees through the coil set's arc-length-weighted major radius, exactly as the coil loader's tilt_in_meters does — that radius is now ForcingTerms.nominal_major_radius, factored out of apply_transforms.
  • ErrorFieldsControl.tolerance_file (relative to the run directory): the driver reads, validates against the run's coil sets, and echoes the text into Input/RawInputs/ErrorFields/tolerance_toml_raw; read_tolerance_snapshot(h5path) reads it back.
  • Docs section with the annotated schema; fixture test/test_data/ErrorFields/tolerances_two_hoops.toml; 45 parser tests plus the echo round trip inside the existing Solovev error-fields run.

Three schema points that differ from the plan sketch, taken from the OMFIT source rather than the plan (worth a look, @logan-nc):

  1. phasing_index in OMFIT is a per-row label: coherent rows sharing a label share the random direction while keeping independent amplitudes. The schema therefore has phase_group = "<label>" per group (default: the group's own name), not per-member phases in radians.
  2. OMFIT's coherent tilt in degrees is a rigid rotation of the whole group about the machine centre, adding a lateral shift (z − z_pivot)·θ to each member, with a code note that a specifiable centre was wanted. rotation_center_z_m (default 0) carries that for PR6.
  3. Default radial shape is "hollow" (OMFIT's misalignment_pdf_types default, used by the SPP risk scans), other_field.radial_shape defaults to "ring" (OMFIT's other_delta_pdf_type). flat keeps its OMFIT meaning (uniform in radius); uniform_area is the new evenly-sampled option.

One thing noticed for PR5, not encoded here: OMFIT's cylinder sampler sets phase_bot = phase_unique_top[...] — the bottom-disk phase it computes is never used, so both axis endpoints share a direction and the sampled axis line is always coplanar with ẑ. That looks like a typo rather than a model; PR5 will sample the endpoints independently and can carry a reproduction toggle if you want the benchmark to match it.

cc @matt-pharr

Release note

  • Audience: users
  • Numerical impact: none (harness @ 69a41a9)
  • Migration: none

Error-field tolerances can now be given in a TOML file named by tolerance_file in [ErrorFields]: per-coil shift and tilt tolerances (with sampling shape and additive or cylinder model), coherent groups, the correctable/uncorrectable split, and an unattributed budget. The file is validated against the run's coil sets and echoed into gpec.h5 for replay.

Regression report

Regression Report: diiid_n1
====================================================================================================================
Ref 1: develop  @ 9578c9b86 (2026-09-24)
       env: julia 1.11.7, x86_64-linux-gnu, manifest 06e27666 (pinned), 16 threads/16 BLAS
Ref 2: feature/errorfields-tolerance-toml  @ 69a41a912 (2026-09-24)
       env: julia 1.11.7, x86_64-linux-gnu, manifest 06e27666 (pinned), 16 threads/16 BLAS
--------------------------------------------------------------------------------------------------------------------
Quantity                                      develop          feature/errorfields-tolerance-toml  Diff       Status
--------------------------------------------------------------------------------------------------------------------
total energy Re(et[1])                        8.012318e-01     8.012318e-01                        0.0e+00    OK    
total energy Im(et[1])                        4.142529e-05     4.142529e-05                        0.0e+00    OK    
plasma energy Re(ep[1])                       -1.348486e+00    -1.348486e+00                       0.0e+00    OK    
vacuum energy Re(ev[1])                       2.149718e+00     2.149718e+00                        0.0e+00    OK    
vacuum matrix min eigenvalue                  1.873976e-01     1.873976e-01                        0.0e+00    OK    
plasma energy (all)                           [35 elem]        [35 elem]                           0.0e+00    OK    
vacuum energy (all)                           [35 elem]        [35 elem]                           0.0e+00    OK    
total energy (all)                            [35 elem]        [35 elem]                           0.0e+00    OK    
ODE steps (saved)                             2576             2576                                0.0e+00    OK    
ODE steps (total)                             4572             4572                                0.0e+00    OK    
q0                                            1.204212e+00     1.204212e+00                        0.0e+00    OK    
q95                                           4.781723e+00     4.781723e+00                        0.0e+00    OK    
beta_t                                        1.327024e-02     1.327024e-02                        0.0e+00    OK    
beta_n                                        1.372511e+00     1.372511e+00                        0.0e+00    OK    
internal inductance li1                       8.842392e-01     8.842392e-01                        0.0e+00    OK    
internal inductance li2                       7.080847e-01     7.080847e-01                        0.0e+00    OK    
internal inductance li3                       7.304433e-01     7.304433e-01                        0.0e+00    OK    
poloidal beta betap1                          6.680744e-01     6.680744e-01                        0.0e+00    OK    
poloidal beta betap2                          5.349834e-01     5.349834e-01                        0.0e+00    OK    
poloidal beta betap3                          5.518761e-01     5.518761e-01                        0.0e+00    OK    
# singular surfaces                           5                5                                   0.0e+00    OK    
singular psi locations                        [5 elem]         [5 elem]                            0.0e+00    OK    
singular q values                             [5 elem]         [5 elem]                            0.0e+00    OK    
current beta betaj                            4.236478e-01     4.236478e-01                        0.0e+00    OK    
plasma volume                                 1.829472e+01     1.829472e+01                        0.0e+00    OK    
plasma current                                1.152130e+00     1.152130e+00                        0.0e+00    OK    
mpert                                         35               35                                  0.0e+00    OK    
npert                                         1                1                                   0.0e+00    OK    
toroidal field bt0                            2.006573e+00     2.006573e+00                        0.0e+00    OK    
wall field bwall                              3.880145e-01     3.880145e-01                        0.0e+00    OK    
aspect ratio                                  2.845746e+00     2.845746e+00                        0.0e+00    OK    
elongation kappa                              1.708350e+00     1.708350e+00                        0.0e+00    OK    
q profile (checksum)                          0cd285cea88d...  0cd285cea88d...                     identical  OK    
pressure profile (checksum)                   a1c48b266622...  a1c48b266622...                     identical  OK    
Mercier D_I profile (checksum)                eeb06744e795...  eeb06744e795...                     identical  OK    
resistive interchange D_R profile (checksum)  fa37296851f6...  fa37296851f6...                     identical  OK    
ballooning Delta' profile (checksum)          6eb0ea075ccf...  6eb0ea075ccf...                     identical  OK    
island half-widths                            [5 elem]         [5 elem]                            0.0e+00    OK    
Chirikov parameter                            [5 elem]         [5 elem]                            0.0e+00    OK    
||resonant area-weighted field||              5.189179e-04     5.189179e-04                        0.0e+00    OK    
dominant-coupling singular values             [3 elem]         [3 elem]                            0.0e+00    OK    
|forcing overlap with dominant mode|          1.415683e-04     1.415683e-04                        0.0e+00    OK    
|delta_nominal| of coil set 1                 7.055228e-05     7.055228e-05                        0.0e+00    OK    
||ddelta/d(shift)|| over coil sets            1.246233e-04     1.246233e-04                        0.0e+00    OK    
||ddelta/d(tilt)|| over coil sets             4.180643e-07     4.180643e-07                        0.0e+00    OK    
PE plasma energy                              3.422677e+00     3.422677e+00                        0.0e+00    OK    
PE vacuum energy                              3.174510e+00     3.174510e+00                        0.0e+00    OK    
PE surface energy                             5.826684e+00     5.826684e+00                        0.0e+00    OK    
PE toroidal torque                            5.087465e-02     5.087465e-02                        0.0e+00    OK    
NTV torque FGAR [N·m]                         5.296762e-01     5.296762e-01                        0.0e+00    OK    
NTV kinetic energy dW FGAR [J]                7.924971e-02     7.924971e-02                        0.0e+00    OK    
Runtime (s)                                   280.2s           288.5s                                         --    
||forcing b~|| (root-area-weighted)           4.683328e-04     4.683328e-04                        0.0e+00    OK    
resonant area-weighted field b^r              [5 elem]         [5 elem]                            0.0e+00    OK    
====================================================================================================================
Summary: 53 unchanged

Harness, re-run at this branch head

69a41a912 compared against develop (9578c9b86), case diiid_n1:

Summary: 53 unchanged

Every tracked quantity is bit-identical; only wall-clock Runtime differs, which the
harness does not track. The branch carries a merge of the current develop.

logan-nc and others added 6 commits September 11, 2026 13:22
…ivities

New src/ErrorFields module: for every coil set of a coil-forced run, central-difference
linearization of the root-area-weighted control-surface spectrum with respect to rigid
shifts (m) and tilts (deg), on one shared boundary grid per toroidal mode, conformed with
R⁻¹ through ResonantCoupling. The stored primitive is the spectrum linearization
(CoilSensitivities) so the ψ window, singular mode, and B_T0 normalization remain post-hoc
analysis choices; sensitivity_table projects it (in memory or from gpec.h5) to the
dimensionless overlap δ and its derivatives, with the in-plane RMS and the closed-form
cancelling offsets. Writes ErrorFields/CoilSensitivities/ with a DominantMode/ full-window
summary, driven by an [ErrorFields] TOML section (fd steps, rotation_center = conductor|set).

Rerun gains a side-effect-free equilibrium_from_h5 (equilibrium rebuilt on the run's stored ψ
grid; shared input rebuild factored out of build_inputs_from_h5) and the post-hoc entry point
compute_coil_sensitivities(h5path, coil_sets) that sweeps any coil geometry against a stored
solve. The driver runs the stage after KineticForces; the DIII-D example carries the section
and the diiid_n1 harness case tracks |δ_nominal| and the sensitivity norms.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BzKDmtKYDotJWrcxWf1oJu
… set's largest tap

A tap whose first difference vanishes by symmetry (vertical shift of an axisymmetric hoop,
in-plane shift of an n=1-phased array) still has a genuine second-order response; dividing it
by its own near-zero first difference flagged every such coil. The residual is now the second
difference relative to the set's largest first difference over all six taps, which is the
scale the linear tolerance model actually keeps.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BzKDmtKYDotJWrcxWf1oJu
DIIID-like_error_field_example: the DIII-D-like ideal solve with the C-coil as the nominal n=1
source and the eighteen DIII-D F coils added as single-filament hoops at the centroids of
their winding packs (OpenFUSIONToolkit TokaMaker DIIID_geom.json), run through the
[ErrorFields] stage. analyze_example.jl ranks the coils by error field per millimetre of shift
and per tenth of a degree of tilt on the dominant mode, over all rational surfaces and over
the edge, and plots the stored spectra behind the ranking.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BzKDmtKYDotJWrcxWf1oJu
…d parser

The manufacturing-tolerance input of an error-field assessment is a TOML file separate from
gpec.toml, named by the new ErrorFieldsControl.tolerance_file: [[ErrorFields.coil]] blocks
(shift disk radius, tilt in degrees or metres, Gaussian uncertainties, radial sampling shape,
additive or cylinder model), [[ErrorFields.coherent_group]] blocks (members sharing one draw,
phase_group labels sharing a direction, rigid-rotation pivot height), the correctability split
and the unattributed budget, with an [ErrorFields.defaults] table for omitted keys. Parsing is
schema-checked (unknown keys error), validate_tolerances cross-checks names against a run's
coil sets, and the driver echoes the file into Input/RawInputs/ErrorFields/tolerance_toml_raw
for replay (read_tolerance_snapshot). tilt_tolerance_deg converts a rim-displacement tilt
through the coil set's arc-length-weighted major radius, now ForcingTerms.nominal_major_radius,
factored out of apply_transforms unchanged.

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

Copy link
Copy Markdown
Collaborator Author

Stack summary — OMFIT → Julia error-field tolerance migration (updated 2026-09-17)

Nothing in this set merges without a third-party human review, the three small bugfix PRs included.

Full page with the diagram, per-PR table, review order and links to every review package (a private claude.ai artifact until the author shares it; DIII-D-like and synthetic data only): https://claude.ai/code/artifact/408ddd36-e52c-42d8-8f5c-52f680f41da9

Group PRs Base
Bugfixes the stack's numbers depend on — review first #458 root-area weight (careful review: Fourier convention inside one routine), #457 helicity from the current sign, #460 interior-start initialization develop
Prerequisites #446 resonant-coupling SVD API, #447 per-coil-set forcing modes develop
ErrorFields stack, each based on the previous branch #448 sensitivities → #449 tolerance TOML → #450 sampling → #451 Monte Carlo → #452 risk + scan → #453 plots + phasing → #455 NTV limits → #462 NTV torque against rotation #448 on develop (carries merges of #446/#447); rebase the chain after the bugfixes and #446/#447 land

Benchmark, qualitatively. The original OMFIT project's case, rebuilt from that project's own run inputs, is reproduced once #458, #457 and #460 are in: dominant-mode singular values agree to 1e-4, singular-coupling rows match Fortran GPEC at 1.0000 correlation and within 0.5 % in norm, and every coil set's dominant-mode overlap agrees within 1 % once both codes use the same converged toroidal coil grid. The DIII-D-like example agrees with Fortran to 1e-4 throughout. Any comparison shown on these PRs uses the DIII-D-like examples only.

Design issue for what comes after the stack: #461 (momentum-balance module fed by tabulated torque surfaces; stationary solvers only).

logan-nc and others added 8 commits September 17, 2026 16:20
…y point to the core window

Follows the PerturbedEquilibrium change that makes psi_N <= 0.9 (CORE_PSI_HIGH) the default
window of the dominant-mode SVD: sensitivity_table(h5path) now defaults to the same window, so
in-memory and post-hoc tables agree, and the example's analysis uses the core window alone
instead of contrasting it with an edge-only window.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BzKDmtKYDotJWrcxWf1oJu
… log line

The curvature and sign-convention comments explained their derivations at length where the
claim itself is the point; the non-positive b_t0 guard and the sensitivity table's basis-length
guard had no test; one log line ran past the 180-column margin.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BzKDmtKYDotJWrcxWf1oJu
The error-field stage appends its own output to what main returns, which the result-struct test
asserted exactly. It now pins the three stages it is about, so stages added above it in the
stack do not break it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BzKDmtKYDotJWrcxWf1oJu
logan-nc and others added 10 commits September 22, 2026 11:45
…s-sensitivities

# Conflicts:
#	src/ForcingTerms/CoilFourier.jl
…gineering-unit sensitivities

Comparing coil revisions against a stored solve is what this module was built for, but doing it
needed convention knowledge the package did not hand over. The chain from a run and a directory of
coil files to a table of overlaps existed only as a private closure inside the sensitivity sweep, so
a user either reimplemented it or paid for thirteen finite-difference taps per set and discarded
twelve. Four different quantities were all called "overlap", differing by factors of B_T0 and the
spectrum norm, none distinguished by name or signature.

- `CoilOverlap` carries every normalization at once — δ, the raw projection, the resonant fraction
  and the spectrum norm — so a caller never has to work out which one a bare number was in. It also
  carries the spectrum itself, which is what lets a diagnostic ask why a coil couples as it does.
- `coil_overlaps` is the one-call path, and the sensitivity sweep now shares its `applied_spectrum`,
  so the conform-and-project convention has a single implementation.
- `combine_overlaps` applies a design current pattern by scaling, which is the whole reason a
  spectrum is worth caching. An unmatched name raises rather than contributing zero, so a coil
  renamed between revisions cannot quietly drop out of a comparison.
- `PostHocContext` gathers the five objects that always travel together and is where the overrides
  hang: the boundary resolution a stored deck otherwise supplies silently, and its `dat_dir`, which
  records an absolute path from whichever machine ran it.
- `SensitivityTable` reports `delta_per_mm_shift`, `delta_per_deg_tilt` and `delta_per_mm_rim` in
  place of the SI direction-RMS pair, the units mechanical tolerances actually arrive in. That needs
  `CoilSensitivities.nominal_radius`, computed where the coil sets are in hand.

`MIN_NZETA_PER_PERIOD` is the package's own default rather than a magic number: against a 128-point
reference, 16 points per period is about 1 % off, 20 about 0.4 %, 24 about 0.1 %, and the default 32
is within one part in 1e5. A deck coarser than that now warns.

Tests assert the one-call path against the chain assembled by hand, and that combining the run's own
coil sets at unit weight reproduces the forcing the run wrote out.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BzKDmtKYDotJWrcxWf1oJu
…arries

`PostHocContext` said when it is built, not what it holds. It carries the dominant resonant mode and
the field normalization alongside the equilibrium and boundary grids, which is error-field work
specifically: everything needed to ask how much resonant field a coil drives, and nothing that
depends on which coils they are.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BzKDmtKYDotJWrcxWf1oJu
…elds-tolerance-toml

Resolved: the duplicate nominal_major_radius, which moved up to the ForcingTerms branch, and the
ErrorFields export list, which gained both sides.
…page prose

Documenter resolves `[`X`](@ref)` in a markdown page against `CurrentModule`, which this page does
not set, so the two links pointing at the new types failed the cross-reference pass and terminated
the build. No other prose page in `docs/src/` uses them; plain backticks match the convention, and
the `@ref`s inside docstrings still resolve because those carry their module context.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BzKDmtKYDotJWrcxWf1oJu
@logan-nc
logan-nc marked this pull request as ready for review September 23, 2026 02:24
@ebursch
ebursch added this pull request to stack #466 September 23, 2026 14:43

@ebursch ebursch left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approve

Base automatically changed from feature/errorfields-sensitivities to develop September 24, 2026 20:18
Picks up the rebuilt sensitivities branch as it landed on develop. The
branch was based on the pre-rebuild tip, so the two lineages had no common
ancestor for the ErrorFields files; merged against the real base so the
accepted review changes carry through rather than being reverted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BzKDmtKYDotJWrcxWf1oJu
@logan-nc

Copy link
Copy Markdown
Collaborator Author

@ebursch heads up on one commit added after your approval, since it touches files you reviewed.

eb668d58e merges the current develop — no new work, only the sensitivities branch as it
landed in #448. It needed care rather than a plain merge: #448 was rebuilt during review, so this
branch sat on the pre-rebuild tip and the two lineages shared no common ancestor for the
ErrorFields files. Git therefore picked a merge base from before ErrorFields existed and reported
every one of those files as an add/add conflict. Resolving that naively would have reverted your
accepted suggestions (b65bcf761, 2d527b434, ee27b9138), none of which were on this branch.

Merged against the real base instead, which resolved cleanly with no conflicts. Verified two ways:
no file differs from develop that this branch did not itself touch, and the specific fixes are
present in the merged tree — the PerturbedEquilibrium spelling in sensitivity_table, the
keyword-only ResonantDriveContext, and S_y = −i·S_x in cancelling_offset.

Tests on the merged branch: runtests_tolerance_toml.jl 45/45, runtests_error_fields.jl 84/84.
Harness stamp will be refreshed once the whole chain is re-merged.

Carries the develop merge down the chain after the sensitivities and
mode-basis branches landed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BzKDmtKYDotJWrcxWf1oJu
@logan-nc
logan-nc merged commit 46580ca into develop Sep 25, 2026
9 of 10 checks passed
@logan-nc
logan-nc deleted the feature/errorfields-tolerance-toml branch September 25, 2026 11:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New capability

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants