Add full-orbit (FO) Boris pusher (orbit_model=7)#426
Merged
Conversation
Full orbit (FO) is the gyro-resolved Lorentz counterpart to the guiding-center (GC) model, the ASCOT-style reference for it. A charged particle advances by an explicit Boris drift-rotate-drift in Cartesian (x, v): the magnetic rotation is exact per step, the kinetic metric is the identity, and the magnetic axis is an ordinary point. Field and geometry come from the production Boozer field through the chartmap: invert the Cartesian point to (rho, theta_B, phi_B), evaluate the flux potential, and build B = curl A so B is tangent to the flux surface; the axis is healed by a pseudo-Cartesian (X,Y,phi) chart. The only confinement loss is the guiding-centre crossing s >= 1; a field-inversion non-convergence is a numerical fault, reported (fo_fault) but never counted as a loss. orbit_model = 7 (ORBIT_FULL_ORBIT) selects it; orbit_coord = 1 (Boozer) is required. Wall loss, collisions, and orbit classification are not yet wired and stop at config validation with a clear message. New modules orbit_fo_field (B = curl A from field_can, chartmap-reparametrized) and orbit_fo_boris (the pusher). diag_counters gains fo_loss / fo_fault. test_fo_boris validates machine-exact energy conservation, the near-axis crossing, and absence of spurious loss on the reactor-scale Boozer field.
The FO field assembly builds B = curl A through ref_coords%covariant_basis; a transposed Jacobian (the libneo cart-spline chartmap bug) flips the field while Boris still conserves energy, so the existing energy gates would not catch it. Check covariant_basis directly against a finite difference of evaluate_cart at a generic interior point; rel err 2e-10 with the fix, O(1) without it.
Member
Author
|
Depends on libneo #325 (fix transposed Cartesian Jacobian from chartmap |
krystophny
added a commit
that referenced
this pull request
Jun 22, 2026
Brings in the merged full-orbit (FO) Boris pusher (orbit_model=7, orbit_fo_boris/orbit_fo_field, test_fo_boris). The four files both branches touch (params, diag_counters, simple, simple_main) kept this branch's experimental CP/CPP/RK code; the FO modules build alongside under distinct names and test_fo_boris passes. ORBIT_FULL_ORBIT vs this branch's ORBIT_CP6D_BORIS both claim model 7, so the field/dispatch dedup (adopt FO as the canonical model 7, renumber the experimental models) is a follow-up.
krystophny
added a commit
that referenced
this pull request
Jun 25, 2026
…d symplectic FO (#429) ## Summary Rebase-and-strip of the shelved shared-symplectic-core work (PR #407) onto current `main`. Keeps only the keep-worthy nucleus: the guiding-centre (GC) Gauss implicit-RK / Newton core, extracted into a new device-callable module `orbit_rk_core`. Everything CPP and symplectic-full-orbit is dropped. ### What lands - New `src/orbit_rk_core.f90`: the GC Gauss-collocation residual, analytic Jacobian, a device-portable dense LU solve (`rk_solve`), and a device-callable Newton shell (`rk_gauss_newton`). Reduced to a single GC kernel; the former `MODEL_CPP` / `MODEL_FO_SYMPL` codes and their dispatch shells are gone. - `src/orbit_symplectic.f90`: `f_rk_gauss` / `jac_rk_gauss` become thin wrappers over the shared core (`MODEL_GC`). The arithmetic is moved byte-identically, so GC numerics do not change. - `src/CMakeLists.txt`: compile `orbit_rk_core.f90` before `orbit_symplectic.f90`. ### What is dropped (not merged) - The CPP pusher (`orbit_cpp.f90`, `MODEL_CPP`) and its tests (`test_cpp_equals_gc_largestep`, `test_cpp_invariants`). - The shelved symplectic / mocked-Boris full-orbit prototype (`orbit_full.f90`, `MODEL_FO_SYMPL`), its provider seam (`orbit_full_provider.f90`) and the two mocks, and their tests (`test_fo_symplectic`, `test_full_orbit`, `test_orbit_model_dispatch`). - The branch's `params.f90` / `simple_main.f90` `orbit_model` hunks: `main` already owns `orbit_model` (`ORBIT_GC=0`, `ORBIT_FULL_ORBIT=7`) and the validated explicit Boris full-orbit pusher (PR #426), which this PR leaves untouched. ## Why `orbit_rk_core` is the single home for the GC Gauss step shared by the CPU integrator and (later) the OpenACC GPU kernel; it is written `!$acc routine seq`-ready with its own device-portable LU. CPP is retired; the full-orbit path is the explicit Cartesian Boris (`orbit_model=7`), not the shelved symplectic prototype. ## Verification `make CONFIG=Fast`: build OK. `make test`: **100% tests passed, 0 failed out of 77** (total 677.80 s). All 14 golden-record regression cases (albert, albert_coils, boozer, boozer_ncsx, canonical, classifier, classifier_combined, classifier_fast, collision, meiss, meiss_coils, and the tokamak variants) pass, confirming the GC numerics are byte-identical after the extraction. Before (on `main`): the same 14 golden-record cases pass with the inlined GC residual/Jacobian. After (this branch): identical results with the arithmetic moved into `orbit_rk_core` -- a pure refactor, no numeric change. ## Follow-ups (separate PRs) - Wire `rk_gauss_newton` / `rk_solve` onto the fortnum multiroot core in place of `dgesv` for the CPU GC Newton; must preserve the verified GC numerics. - The device (OpenACC) GC solver path uses fortnum's reverse-communication `multiroot_step` (fortnum PR for the device-capable solver), keeping `orbit_rk_core` as the shared CPU+GPU home.
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.
Risk tier
Correctness contract
Intended behavior change
Adds a new orbit model:
orbit_model = 7(ORBIT_FULL_ORBIT) selects agyro-resolved full-orbit (FO) Boris pusher, the ASCOT-style counterpart to the
guiding-center (GC) model. A charged particle advances by explicit Boris
drift-rotate-drift in Cartesian (x, v); field and geometry come from the
production Boozer field through the chartmap (B = curl A, tangent to the flux
surface), with the magnetic axis healed by a pseudo-Cartesian (X,Y,phi) chart.
Output
z(1:5)is the guiding-centre reduction each step, sotimes_lostandconfined_fractionare written exactly as for GC.Behavior that must not change
orbit_model = 0(guiding center, the default) is untouched: the symplectic GCpath, its init, and its macrostep are unchanged. No existing test output moves.
Coordinate / unit conventions
Cartesian (x, v) in the scaled chartmap frame; logical chart u=(rho, theta_B,
phi_B) with rho = sqrt(s). CGS-style normalized velocity with the GC sqrt(2)
convention, matching the GC seed so both integrators start from the identical
particle.
orbit_coord = 1(Boozer) is required and enforced.Numerical invariants
Energy is conserved to machine precision (the Boris rotation is exact for
constant B over a step; no electric field here). The only confinement loss is
the guiding-centre crossing s >= 1; a field-inversion non-convergence is a
numerical fault (
fo_fault), reported but never counted as a loss.Tests added
test_fo_boris(energy bound < 1e-3, near-axis crossing,no spurious loss; passing/trapped/near-axis on the reactor-scale Boozer field)
Golden-record impact
GC is the default and is untouched; FO is opt-in via
orbit_model = 7.Failure modes considered
numerical fault (
fo_fault), never a loss, so a mid-radius particle is notspuriously lost.
(X,Y,phi) chart; the near-axis test orbit crosses s in [0.04, 0.07] with
energy bounded.
orbit_coord /= 1) stop at config validation with a clear message instead of
running silently wrong.
Manual validation
Reactor-scale W7-X benchmark (separate run): FO confined fraction agrees with
ASCOT5 full orbit (0.894 vs 0.898 at 1 ms, 1000 alphas), and with GC within
marker statistics.
Verification
Before: there is no full-orbit model on
main;orbit_modeland the FO modulesdo not exist.
After:
test_fo_borispasses and the fast suite is green.Relationship to #408
This is the working full-orbit Boris slice, extracted clean off
main. Theexperimental models on #408 (CPP Pauli, the adaptive RK45 ORBIT_CP6D_RK, the 6D
canonical CP/CPP, device/mock variants) stay on that branch; #408 rebases on top
once this merges.