Skip to content

Differentiable rotational transform from coil fields (weighted Birkhoff + SOLVAX axis) - #99

Open
gftd13 wants to merge 8 commits into
mainfrom
feat/rotational-transform-from-coils
Open

gftd13 wants to merge 8 commits into
mainfrom
feat/rotational-transform-from-coils

Conversation

@gftd13

@gftd13 gftd13 commented Sep 30, 2026

Copy link
Copy Markdown

Summary

Adds essos/rotational_transform.py: a concise (~110 lines), jit-able and differentiable computation of the rotational transform ι of the flux surfaces of a coil field (BiotSavart).

  • rotational_transform(field, R0, Z0, nfp, axis=None, n_periods=64, steps=32): traces field lines in φ (dR/dφ = R B_R/B_φ, dZ/dφ = R B_Z/B_φ) with fixed-step RK4 alongside the magnetic axis, and returns ι = nfp/2π · Σ wₙ Δθₙ, a weighted Birkhoff average of the poloidal advance per field period. It converges faster than any power of N on regular surfaces (Das, Saiki, Sander & Yorke 2017; Sander & Meiss 2020; Ruth & Bindel 2024), while the plain winding-number average converges like 1/N. It accepts arrays of starting points, so one call gives a whole ι profile.
  • magnetic_axis(field, RZ0, nfp): the fixed point of the field-period map. The guess is first refined with Poincaré-orbit centroids, because Newton diverges from field.r_axis for the QA coils. Newton then polishes it inside SOLVAX root_solve, so gradients use the implicit function theorem and never unroll the solver.
  • field_period_map: a reusable Poincaré map.

Context and alternatives

  • SIMSOPT gets ι from BoozerSurface (Giuliani et al. 2022), which solves for the surface and ι together, or from Poincaré tracing. DESC computes ι from the equilibrium's current and λ. Both are heavier than needed when you only have coils.
  • This PR follows the tracing/Birkhoff route: it needs no surface fit, works for any field with B(xyz), and is differentiable with respect to coil DOFs and currents. None of the open PRs compute ι from coils. QFM #27 (QFM) is related work on surfaces.
  • SOLVAX is added as a dependency.

Verification

examples/rotational_transform/iota_from_coils_convergence.py (≈30 s on CPU) produces the figure below:

  • (a) Weighted Birkhoff error falls from 3e-2 to ~1e-6 at N=512 periods, versus 3e-4 for the plain average.
  • (b) Error falls with RK4 steps per period at 4th order, reaching 5e-8 at 64 steps.
  • (c) The ι(R) profile of the Landreman–Paul QA coils matches the VMEC equilibrium they were optimized for (0.423→0.416). The largest gap, 4e-4, is near the axis, as expected from coil ripple.

convergence

tests/test_rotational_transform.py (3 tests, ~13 s) checks the axis fixed point (1e-10), agreement with the VMEC ι range and convergence in N, and jax.grad of ι with respect to all coil DOFs against central finite differences (rtol 1e-4).

🤖 Generated with Claude Code

Trace field lines in phi with fixed-step RK4 alongside the magnetic axis and
take a weighted Birkhoff average of the poloidal advance per field period.
The axis is the fixed point of the field-period map, solved with Newton
inside solvax.root_solve for implicit gradients. Includes tests (axis, VMEC
agreement, gradient vs finite differences) and a convergence example.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov

codecov Bot commented Sep 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Files with missing lines Coverage Δ
essos/rotational_transform.py 100.00% <100.00%> (ø)

... and 1 file with indirect coverage changes

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.

2 participants