GVEC QA Boozer chartmap validation#334
Merged
Merged
Conversation
3a42c9b to
fbd730d
Compare
08e0b6d to
4b1c23a
Compare
krystophny
added a commit
that referenced
this pull request
Jun 3, 2026
## Summary Fix the Boozer chartmap radial contract end to end. Changes: - `A_phi`, `B_theta`, `B_phi`, and `Bmod` are read on the chartmap `rho` grid. Radial derivatives are converted to `s = rho^2` by chain rule. - Chartmap files now carry and restore `rmajor`; both chartmap field consumers use `boozer_chartmap_io.read_boozer_chartmap`. - `Bmod` is read on the endpoint-included `theta_field`/`zeta_field` grid. - Chartmap startmode=1 now stores sampled starts in reference coordinates. The tracer returns integrator coordinates `(s, theta_B, phi_B)`; chartmap `zstart` stores `(rho, theta_B, phi_B)`, so `sbeg=0.5` now writes `rho=sqrt(0.5)`. - Chartmap `generate_start_only` now stops after writing corrected starts instead of tracing orbits. - Grid starts use the same conversion, and their linear index stride is fixed. - Duplicate chartmap diagnostics were removed. The synthetic `test_chartmap_aphi_abscissa` now covers reader metadata and the analytic `A_phi(rho)` chain-rule contract. - Chartmap tolerances were tightened to measured floors. The roundtrip `Bmod` floor is the current export resolution, about `7.90e-5`; the orbit floor is about `1.68e-9`. Closes #358 and fixes the chartmap `sbeg` issue tracked in #359. ## Stack Base: `main`. Merge order: 1. Merge this PR first. 2. Restack #334 after this PR. Do not merge #334 as it stands; its branch is based before several commits that are already on `main` and before this fix. Already on `main`: #329, #332, #333, #338, #340, #346, #348, #355, and the Boozer flux-sign doc commit. ## Verification ### Test fails before the start-sampling fix ``` $ OMP_NUM_THREADS=1 ctest --test-dir build -R '^test_chartmap_startmode1$' --output-on-failure -j1 AssertionError: chartmap startmode=1 wrote s instead of rho 0% tests passed, 1 tests failed out of 1 ``` ### Tests pass after the fix ``` $ cmake --build build --target simple.x -j2 [25/26] Linking Fortran executable simple.x ``` ``` $ OMP_NUM_THREADS=1 ctest --test-dir build -R '^test_chartmap_startmode1$' --output-on-failure -j1 100% tests passed, 0 tests failed out of 1 Total Test time (real) = 3.96 sec ``` ``` $ OMP_NUM_THREADS=1 ctest --test-dir build -R '^test_chartmap_aphi_abscissa$|^test_boozer_chartmap$|^test_chartmap_scaling$|^test_boozer_chartmap_roundtrip$|^test_chartmap_startmode1$' --output-on-failure -j1 100% tests passed, 0 tests failed out of 5 Total Test time (real) = 8.43 sec ``` ``` $ OMP_NUM_THREADS=1 ctest --test-dir build -R '^test_chartmap_pipeline$|^test_chartmap_rz_consistency$|^test_coord_transform_roundtrip$' --output-on-failure -j1 100% tests passed, 0 tests failed out of 3 Total Test time (real) = 94.36 sec ``` ``` $ OMP_NUM_THREADS=1 ctest --test-dir build -R '^test_e2e_boozer_chartmap$' --output-on-failure -j1 100% tests passed, 0 tests failed out of 1 Total Test time (real) = 102.43 sec ``` ``` $ check-writing-slop.py test/tests/test_chartmap_startmode1.py PASS: no writing-slop candidates at threshold medium ``` ### External W7X/GVEC reruns Run with `OMP_NUM_THREADS=1` using the rebased `fix/chartmap-aphi-rho-abscissa` build. These cases do not use `generate_start_only`, so they were not rerun after `cf3ff9e`. ``` W7X/vmec/simple-s1-p4k: final confined_fraction.dat line: 1.0000000000000000E-003 0.67993164062500000 1.4648437500000000E-003 4096 W7X/vmec2gvec/simple-f1-n50/s316-p1k: final confined_fraction.dat line: 9.9999999999999980E-004 0.64746093750000000 1.0742187500000000E-002 1024 start.dat radial min=max=0.56213877290220782 = sqrt(0.316) W7X/vmec2gvec/simple-f1-n50/s317-p1k: final confined_fraction.dat line: 9.9999999999999980E-004 0.64843750000000000 4.8828125000000000E-003 1024 start.dat radial min=max=0.5630275304103699 = sqrt(0.317) ```
Contributor
The figure-8 benchmark requires external QUASR data fetched via GITLAB_ACCESS_TOKEN. When the token or data is unavailable (e.g. fork PRs), exit with code 77 so CTest marks the test as skipped instead of failed.
This reverts commit ad155a9.
The figure-8 benchmark no longer depends on private GitLab data. Instead it downloads the QUASR surface (ID 112714) from the public Flatiron API, builds the boundary and chartmap via GVEC on the fly, and caches the result keyed by script content hash. Static test inputs (simple.in, start.dat) and the golden signature reference are committed to test/test_data/figure8/. The CI workflow no longer needs the GITLAB_ACCESS_TOKEN-gated figure-8 fetch step.
The GVEC solver is not bit-reproducible across environments (different BLAS, FP ordering), so the figure-8 golden record generated locally never matches what CI produces from a fresh GVEC solve.
Rykath
reviewed
Jun 8, 2026
| field_input = 'boozer_chartmap.nc' ! Boozer chartmap NetCDF file for field data | ||
| coord_input = 'boozer_chartmap.nc' ! same file for reference coordinates | ||
| isw_field_type = 2 ! Boozer field type | ||
| startmode = 2 ! particle coordinates given in chartmap Boozer space |
Contributor
There was a problem hiding this comment.
From what I know, this comment isn't quite correct. It would also be preferable if the example didn't force startmode = 2, which requires a start.dat file, right?
|
|
||
| Key differences from VMEC mode: | ||
| - Both `field_input` and `coord_input` reference the same chartmap NetCDF file | ||
| - `startmode = 2` means particle coordinates are in chartmap Boozer space |
2 tasks
Member
Author
|
@Rykath your review comments here are now addressed in #368:
map2disc-based chartmaps are unaffected: they go through the libneo coordinate reader, not |
krystophny
added a commit
that referenced
this pull request
Jun 8, 2026
) Follow-up to the review comments on #334 (cc @Rykath — this is now changed). ## Risk tier - [x] T2: local numerical logic ## Correctness contract ### Intended behavior change - `read_boozer_chartmap` no longer supports legacy chartmap files. It now requires: - the `rmajor` global attribute (previously optional via a `has_rmajor` flag that defaulted `rmajor` to `0.0`), and - the endpoint-included `theta_field`/`zeta_field` Bmod grid (previously fell back to the geometry grid). GVEC-exported chartmaps (`tools/gvec_to_boozer_chartmap.py`) always write both, so current files are unaffected. Pre-converter legacy files now abort with a clear NetCDF error instead of being silently accepted with `rmajor = 0`. ### Behavior that must not change - map2disc-based chartmaps: unaffected. They are read through the libneo coordinate reader (`make_chartmap_coordinate_system`), not `read_boozer_chartmap`. - Field values, scaling, and the symplectic/Boozer paths for valid current-format files. ### Coordinate / unit conventions - Unchanged. Bmod stays on the endpoint-included field grid spanning the full 2*pi (poloidal) and 2*pi/nfp (toroidal) period. ### Numerical invariants - Unchanged. ## Tests added - unit: updated `generate_test_boozer_chartmap.c` to emit the current format (`rmajor` + field-grid Bmod); updated `test_chartmap_aphi_abscissa.f90` (the `rmajor`-present check is now covered by mandatory read + value assertion). ## Golden-record impact - [x] unchanged ## Failure modes considered - Legacy file fed to the strict reader: now aborts with `NetCDF error at att rmajor` / `inq_dim theta_field`, by design. ## Manual validation Also addresses the two doc comments on #334: - `examples/simple_chartmap.in`: dropped the forced `startmode = 2` (the comment was incorrect, and it required a `start.dat`). The default `startmode = 1` samples the `sbeg` flux surface and needs no `start.dat`. - `README.md`: removed the incorrect "`startmode = 2` means particle coordinates are in chartmap Boozer space" bullet (startmode is field-type independent). ## Verification Failing before (strict reader against the old legacy-format test generator): ``` read_boozer_chartmap: NetCDF error at att rmajor: NetCDF: Attribute not found ERROR STOP read_boozer_chartmap failed 0% tests passed, 1 tests failed out of 1 20 - test_boozer_chartmap (Failed) ``` Passing after (generator updated to current format): ``` 1/5 Test #18: test_chartmap_aphi_abscissa ...... Passed 2/5 Test #20: test_boozer_chartmap ............. Passed 3/5 Test #21: test_chartmap_startmode1 ......... Passed 4/5 Test #22: test_chartmap_scaling ............ Passed 5/5 Test #23: test_boozer_chartmap_roundtrip ... Passed 100% tests passed, 0 tests failed out of 5 ``` Pre-existing, unrelated to this PR: the `*_map2disc` chartmap tests fail on a clean `main` in this environment because the optional libneo Python binding `_efit_to_boozer` is not built (`ModuleNotFoundError: No module named '_efit_to_boozer'`). This PR introduces no new failures relative to that baseline.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
GVEC QA branch for Boozer chartmap validation after #360. The branch is now restacked on
mainat171c998and keeps the remaining GVEC converter, QA, fixture, anddocumentation work.
Current contracts in this branch:
A_phiis tabulated on the file rho grid and chain-ruled toson readback.rmajor; the reader restores it beforeparameter initialization so VMEC and chartmap runs use the same
dtauminandntau.rmajorand compares the VMEC/chartmapmicrostep directly.
gvec.contracts: reader metadata,
A_phiabscissa, start-mode sampling, scaling, roundtrip,and selected e2e equilibria.
Stack
Merge order is now:
main.Already on
mainbefore this branch:sbegstart-sampling fixes (boozer chartmap: fix rho-grid fields and start sampling #360)Verification
Failing before data refresh:
Passing after pulling the external figure8 LFS files:
The long e2e test passed before the external LFS-pointer failure stopped the first
focused run:
The default build Python on this machine does not provide
gvec, sotest_boozer_chartmap_gvec_qais not registered in this local CTest configuration.