Skip to content

chartmap: type-bound invert_cart (Cartesian inverse) on fortnum solvers#340

Merged
krystophny merged 1 commit into
mainfrom
feat/chartmap-invert-cart
Jun 24, 2026
Merged

chartmap: type-bound invert_cart (Cartesian inverse) on fortnum solvers#340
krystophny merged 1 commit into
mainfrom
feat/chartmap-invert-cart

Conversation

@krystophny

Copy link
Copy Markdown
Member

What

Add a type-bound cs%invert_cart(x, u_guess, u, geom_status) to the chartmap
coordinate system: the warm single-point Cartesian inverse the full-orbit
pusher needs, ported onto fortnum's released solvers, with the map geometry
owned by the chart and loss policy left to the caller.

  • Interior solve: multiroot_hybrid in the pseudo-Cartesian chart
    w = (X, Y, phi) = (rho cos theta, rho sin theta, phi) with the analytic
    Jacobian
    from covariant_basis (no finite differences). The (X, Y) chart
    is regular through the magnetic axis where the polar Jacobian
    dx/dtheta ~ rho makes det(Jc) -> 0.
  • Near-edge solve: a bracketed 1-D radial solve (multiroot_hybrid at
    n = 1) along the seeded (theta, phi) ray, with the bracket [rho_lo, 1]
    enforced in the callback so the radius cannot overshoot past 1. A marker
    leaving the plasma stalls cleanly on the clamped edge instead of grinding.
  • Cold multi-seed fallback for seam-stale guesses; the orchestrator keeps
    the lowest-residual root across paths.

Map-specific geometry moves into the chart: chartmap_pseudocart_basis
(polar -> (X,Y) chain rule), chartmap_w_to_u (X/Y axis regularization),
chartmap_radial_scale (|dx/drho|).

geom_status is geometric only (CHARTMAP_LOCATED / CHARTMAP_CLAMPED_EDGE
/ CHARTMAP_OUTSIDE / CHARTMAP_NO_ROOT) and never encodes a confinement loss;
that decision stays with the caller (in SIMPLE, the guiding-centre test).

Why

The full-orbit Boris pusher in SIMPLE hand-rolled a damped Newton with a
backtracking line search to invert the chartmap every microstep. The inverse
is a property of the chart, so it belongs with the chart, and the generic
iteration belongs in fortnum. This PR is the libneo half: a chart-owned
inverse on fortnum solvers. The near-edge bracket removes the line-search
overshoot-into-the-clamp that made near-loss markers expensive. SIMPLE adopts
this in a separate change and keeps its loss policy unchanged.

Verification

fo check: build and tests pass. Full fo pipeline: Static OK (43 modules),
Build OK, Lint OK.

New behavioral test test_chartmap_invert_cart (ctest #48) passes all six
checks: interior roundtrip LOCATED; seam roundtrip with stale phi LOCATED;
near-axis rho recovered via X/Y healing; axis point handled; last-surface point
CLAMPED_EDGE; past-edge target OUTSIDE with rho clamped to 1.

Pre-existing failures, not introduced here: 11 ctest cases
(setup_chartmap_volume, setup_vmec_chartmap_python and the fixture-gated
tests that consume their output) fail because the system Python the test runner
uses lacks the map2disc module (installed only in the project .venv), so the
chartmap/vmec fixtures are never generated. Confirmed identical on pristine
HEAD before any edit; none touch the invert_cart code path. Pointing the
runner at the project .venv is tracked as a follow-up.

Follow-ups (not in this PR)

  • SIMPLE side: rewrite invert_cart_warm as a thin loss-policy shell over
    cs%invert_cart (map geom_status -> FO_OK/FO_LOCATE_FAIL via
    accept_or_fail, keep fo_to_gc's guiding-centre loss test, the #427
    contract), then delete the duplicated SIMPLE-side inversion routines. Gate on
    the FO golden-record / W7-X confined-fraction benchmark.
  • Optionally surface res_norm/radial_scale from cs%invert_cart so the
    caller classifies without re-evaluating the map.
  • Point the libneo test runner's Python at the project .venv so the 11
    fixture-gated chartmap tests run.

Add a type-bound cs%invert_cart(x, u_guess, u, geom_status) to the chartmap
coordinate system: the warm single-point Cartesian inverse the full-orbit
pusher needs, ported onto fortnum's multiroot_hybrid.

- Interior solve: multiroot_hybrid in the pseudo-Cartesian chart w=(X,Y,phi)
  with the analytic Jacobian built from covariant_basis (no finite
  differences). The (X,Y) chart is regular through the magnetic axis where the
  polar Jacobian is singular.
- Near-edge solve: a bracketed 1-D radial solve (multiroot_hybrid at n=1) along
  the seeded ray with the bracket [rho_lo, 1] enforced in the callback, so the
  radius cannot overshoot past 1; a marker leaving the plasma stalls cleanly on
  the clamped edge.
- Cold multi-seed fallback reuses chartmap_invert_cart for seam-stale guesses.
  The orchestrator keeps the lowest-residual root across all paths.
- Both callbacks reject rho>1 trials (large outward residual) so the line
  search cannot accept a clamped-edge point.

Move the map-specific helpers into the chart: pseudocart_basis (polar->(X,Y)
chain rule), w_to_u (X/Y axis regularization), radial_scale (|dx/drho|).

geom_status is GEOMETRIC ONLY (CHARTMAP_LOCATED / CLAMPED_EDGE / OUTSIDE /
NO_ROOT) and never encodes a confinement loss; any loss policy stays with the
caller.

Link fortnum into the neo target. Add a behavioral unit test that writes a
self-contained reactor-scale Boozer chartmap (no map2disc dependency) and
checks the round-trip recovers interior u to tolerance (interior, seam,
near-axis, axis) and that near-edge and just-outside targets report the
correct geometric status.
@krystophny krystophny merged commit bdf9282 into main Jun 24, 2026
4 checks passed
@krystophny krystophny deleted the feat/chartmap-invert-cart branch June 24, 2026 05:39
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