Skip to content

DESC and near-axis fields; full orbits in toroidal fields - #149

Closed
rogeriojorge wants to merge 1 commit into
feat/field-algebra-mrxfrom
feat/desc-field
Closed

rogeriojorge wants to merge 1 commit into
feat/field-algebra-mrxfrom
feat/desc-field

Conversation

@rogeriojorge

Copy link
Copy Markdown
Member

Stacked on #146. It adds DESC and near-axis fields, and full orbits in toroidal fields, so every ESSOS model runs on coils, permanent magnets, VMEX/VMEC, MRX and DESC.

DescField(ToroidalField)

A DESC equilibrium, evaluated as DESC writes it:

  • R, Z and the stream function lambda as Fourier-Zernike series, with the radial Zernike polynomials from their Jacobi recurrence;
  • iota tabulated on 1025 radii and interpolated by cubic Hermite polynomials;
  • in (s, theta, phi) = (rho^2, theta, zeta), B^theta = Psi (iota - d_phi lambda) / (2 pi sqrt(g)) and B^phi = Psi (1 + d_theta lambda) / (2 pi sqrt(g)).

The field is exact on the axis.

DescField.from_desc takes a DESC Equilibrium, a DESC output file, or the .npz that save writes. Reading the .npz needs no DESC install, which helps because DESC pins jax < 0.10.

Against DESC's own compute at 500 random points:

equilibrium x B |B| sqrt(g) iota
precise QA (vacuum) 1.7e-15 4.4e-13 1.6e-13 2.2e-15 3.9e-12
QA at 2.5% beta, bootstrap current 3.9e-15 2.1e-9 3.8e-10 3.0e-15 7.8e-7

NearAxisField(MagneticField)

A first-order pyQSC_JAX near-axis field in its Boozer coordinates (r, theta, varphi). It uses the expansion's own covariant and contravariant components, |B| and Jacobian. to_xyz maps through the axis's Frenet frame and is first order like the field: |e_i B^i| / |B| - 1 is 1% at r = 0.01 and 7% at r = 0.1.

(r, theta) is singular on the axis, so orbits must stay off it. Full orbits are refused with a clear error, because the field has no inverse map to Cartesian points.

Full orbits in toroidal fields

Before this PR, FullOrbit* models with a VMEC field read (s, theta, phi) as (x, y, z). Now, for a ToroidalField:

  • Tracing maps each guiding-center start through to_xyz;
  • it traces in field.cartesian(), a Cartesian view built on B_xyz;
  • by default it stops orbits at s = 1, where the inverse map is clamped.

Every model on every field

Run on Landreman-Paul QA where available. Settings:

  • 8 orbits per row: 100 keV alphas for 2 us, field lines for 50 samples;
  • the near-axis field at r = 0.01-0.05;
  • the energy error is the largest over the orbits;
  • the collisional model has no conserved energy.
field field lines guiding centers full orbits (adaptive) full orbits (Boris) collisional GC
coils 3.0 s 18 s, 1e-8 1.2 s, 1e-15 3.3 s, 4e-16 64 s
coils + magnets (PointDipoleField) 1.9 s 2.4 s, 2e-8 1.0 s, 3e-9 2.3 s, 3e-14 78 s
VMEX 2.6 s 3.9 s, 1e-7 45 s, 1e-10 4.7 s, 2e-14 31 s
MRX 1.1 s 5.1 s, 5e-9 8.3 s, 4e-9 3.1 s, 3e-14 33 s
DESC 1.2 s 2.5 s, 8e-11 13 s, 4e-8 15 s, 1e-14 61 s
near-axis 0.7 s 2.7 s, see below not available not available 16 s
  • Near-axis guiding centers. With pitch 0.5 they conserve energy to 2e-4, the size of the first-order expansion's own inconsistency: B_i B^i differs from |B|^2 by 1.4% at r = 0.04. With random pitch, one of the 8 orbits reached the axis and the error grew to 0.34.
  • VMEX adaptive full orbits. Before the stop at s = 1, orbits that left the plasma ran 6912 s; they now take 45 s.

Tests

tests/test_fields.py:

  • Fourier-Zernike against closed forms;
  • DescField: covariant and contravariant consistency, zero divergence, Hermite iota, the .npz round trip;
  • full orbits in a VMEC field (start and energy);
  • near-axis first-order consistency (skipped without pyQSC_JAX).

The DESC comparisons against DESC itself ran in a DESC environment.

DescField evaluates a DESC equilibrium as DESC writes it: Fourier-Zernike
series for R, Z and lambda (Jacobi recurrence), iota tabulated on 1025
radii with cubic Hermite interpolation, and B^theta, B^phi from the
toroidal flux, lambda and iota. On precise QA it matches DESC's own B to
4e-13 (2e-9 at 2.5% beta). from_desc reads a DESC Equilibrium or file, or
the .npz that save writes, which needs no DESC install.

NearAxisField traces a pyQSC_JAX near-axis field in its Boozer coordinates
with the expansion's own components, |B| and Jacobian; to_xyz is first order
like the field.

Full orbits move in Cartesian space, so Tracing now traces full-orbit models
in a toroidal field through field.cartesian() (B at Cartesian points), from
the Cartesian position of each guiding center. Before, the Lorentz force
read (s, theta, phi) as (x, y, z).

The minor radius from the boundary cross-sections moves to ToroidalField,
shared by MRXField and DescField.
@rogeriojorge

Copy link
Copy Markdown
Member Author

@rogeriojorge
rogeriojorge deleted the feat/desc-field branch October 30, 2026 21:34
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