Geometry models and loading
dkx solves a radially local neoclassical kinetic problem on a single flux surface, so geometry is not an incidental input: the magnetic field strength \(\hat B(\theta,\zeta)\), its derivatives, the Jacobian factor \(\hat D\), and the covariant/contravariant field components set the coefficients of the streaming, mirror, \(E\times B\), and magnetic-drift terms (Physics reference: the radially local drift-kinetic model), as well as the flux-surface averages used in every diagnostic.
All geometry is consolidated in the single canonical module
dkx.magnetic_geometry, which mirrors the Fortran v3 geometry.F90
(initializeGeometry / computeBHat_* / setBoozerCoordinates /
computeBIntegrals). Every geometry source produces the same normalized
container, dkx.magnetic_geometry.FluxSurfaceGeometry, on a
(Ntheta, Nzeta) grid.
Supported geometry schemes
|
Model |
Coordinates |
Constructor |
|---|---|---|---|
1 |
Analytic three-helicity straight-field-line model |
Boozer |
|
2 |
LHD standard reduced model (Beidler 2011 table) |
Boozer |
|
3 |
LHD inward-shifted reduced model |
Boozer |
|
4 |
W7-X standard reduced model |
Boozer |
|
5 |
VMEC |
VMEC |
|
11 |
Boozer |
Boozer |
|
12 |
Boozer |
Boozer |
|
13 |
Namelist-supplied Boozer \(|B|\) spectrum (optimization path) |
Boozer |
|
The namelist entry point
dkx.drift_kinetic.KineticOperator.from_namelist() builds geometry
schemes {1, 2, 3, 4, 5, 11, 12} directly from an input.namelist. Scheme
13 is reachable through the differentiable from_fourier constructor in
Python; parsing bmnc/bmns amplitudes from a namelist is deferred (see
Deferrals below).
The analytic reduced models (schemes 2/3/4) use the harmonic tables from Beidler et al., Nucl. Fusion 51, 076001 (2011). For example, the W7-X standard model (scheme 4) has five field periods and the field strength shown below.
\(\hat B(\theta,\zeta) = B/\bar B\) on one field period of a W7-X-like
flux surface, evaluated analytically from
FluxSurfaceGeometry.from_scheme(4, ...) (geometryScheme = 4). The
low-field “bean” region and the high-field lobes set the trapped-passing
boundary that dominates low-collisionality transport. Reproduce with
python docs/figures/generate_docs_figures.py.
Boozer vs VMEC coordinates and the \(|B|\) Fourier representation
The analytic and .bc schemes work in Boozer coordinates, where the
field strength is a Fourier series in the Boozer angles
with \(N_p\) the number of field periods (the n index does not carry the
period factor, matching the Fortran boozer_bmnc(m,n) convention). The
covariant components reduce to the flux functions
\(\hat B_\theta = \hat I\), \(\hat B_\zeta = \hat G\) plus the field-
period structure, which is why Boozer geometry is the natural setting for the
drift coefficients and for monoenergetic (RHSMode>3) runs.
The retained harmonics follow the representable-mode policy imposed by the discrete \((\theta,\zeta)\) grid, matching the v3 Jacobian assembly at a given resolution:
with the expected Nyquist exclusions for sine/cosine pairs. This truncation matters numerically: the resolved harmonic content changes both the geometric coefficients and the trapped-passing boundary structure.
For geometryScheme = 5 the equilibrium is a VMEC wout file. VMEC
stores the field in its own flux coordinates and on a radial mesh, so the loader
interpolates the requested surface (half/full-mesh with the v3 finite-difference
conventions) and reconstructs \(\hat B\), its derivatives, and the
\(|\nabla\hat\psi|^2\) metric used by the classical fluxes and the
Sugama-form magnetic drifts.
The differentiable geometry path
Two constructors are pure JAX and safe to jit / grad:
FluxSurfaceGeometry.from_scheme (analytic schemes 1–4) and
FluxSurfaceGeometry.from_fourier (a Boozer \(|B|\) spectrum). The latter
is the geometry entry point for optimization loops: gradients with respect to
the spectral amplitudes bmnc/bmns flow through \(\hat B\),
\(\hat D\), and the flux-surface averages.
import jax
import jax.numpy as jnp
from dkx.magnetic_geometry import FluxSurfaceGeometry
theta = jnp.linspace(0.0, 2.0 * jnp.pi, 24, endpoint=False)
zeta = jnp.linspace(0.0, 2.0 * jnp.pi / 5.0, 20, endpoint=False)
# A tiny W7-X-like |B| spectrum: (m, n) modes and cosine amplitudes in Bbar units.
m = jnp.asarray([0, 0, 1, 1])
n = jnp.asarray([0, 1, 0, 1])
bmnc = jnp.asarray([3.0, 0.05, -0.02, -0.04])
def objective(amps):
geom = FluxSurfaceGeometry.from_fourier(
theta=theta, zeta=zeta, bmnc=amps, m=m, n=n,
n_periods=5, iota=0.87, g_hat=-17.885, i_hat=0.0,
)
# e.g. minimize mirror-ripple-like content in |B|
return jnp.mean((geom.b_hat - jnp.mean(geom.b_hat)) ** 2)
value, grad = jax.value_and_grad(objective)(bmnc)
Grid truncation of unrepresentable modes is applied by zeroing amplitudes, which
keeps array shapes static under jit. A worked differentiable-geometry
gradient check is in examples/autodiff/differentiable_geometry_gradients.py.
The FluxSurfaceGeometry container
The operator does not carry an opaque geometry object; the solve path works with
explicitly normalized arrays collected in the frozen dataclass
dkx.magnetic_geometry.FluxSurfaceGeometry. Its fields follow the
SFINCS v3 sfincsOutput.h5 names (BHat \(\to\) b_hat, DHat
\(\to\) d_hat, …):
scalars:
n_periods,b0_over_bbar,iota,g_hat(GHat),i_hat(IHat);(Ntheta, Nzeta)arrays:b_hatand its \(\theta\)/\(\zeta\) derivatives,d_hat, and the co/contravariant componentsb_hat_sub_theta/zeta,b_hat_sup_theta/zetawith their derivatives;radial/drift arrays (populated from neighbouring surfaces for
.bc/VMEC input) and the optionalgpsipsi\(=|\nabla\hat\psi|^2\) metric.
Two flux-surface averages are methods on the container:
vprime_hat (\(\hat V' = \sum_{ij} w^\theta_i w^\zeta_j/\hat D_{ij}\)) and
fsab_hat2 (\(\langle\hat B^2\rangle\)).
Where in the code
dkx.magnetic_geometry. Constructors:
FluxSurfaceGeometry.from_scheme (analytic 1–4, magnetic_geometry.py),
from_fourier (differentiable Boozer spectrum, magnetic_geometry.py),
from_boozer (.bc schemes 11/12), from_vmec (VMEC scheme 5).
Readers: dkx.magnetic_geometry.read_boozer_bc() and
dkx.magnetic_geometry.read_vmec_wout() are plain-NumPy pure
functions kept separate from geometry construction. The namelist dispatch is
_geometry_and_radial.
VMEC workflow
For geometryScheme = 5 the workflow is: pick a target radius, load the
wout file, interpolate the flux-surface quantities onto the requested
\((\theta,\zeta)\) grid, and convert to the normalized SFINCS arrays. The
radial coordinate can be requested in any supported form (\(\psi\),
\(\psi_N\), \(\hat r\), \(r_N\)) through VMECRadialOption, but
the solve is always local to one surface.
VMEC-centered runs use either the namelist equilibriumFile entry or the
explicit wout_path=... / --wout-path override (Inputs (namelist) reference). Large
public VMEC fixtures such as wout_w7x_standardConfig.nc are release-hosted
rather than tracked in the repository; a namelist that references one by
basename resolves it through the path search in Inputs (namelist) reference and downloads the
checked release asset into the user cache when needed.
A JAX-native equilibrium producer (for example vmex) can be coupled
through the same scheme-5 formulas. A primal finite-beta end-to-end example is
examples/vmex_finite_beta/finite_beta_vmec_to_sfincs.py, which builds a
wout with vmex, evaluates scheme-5 geometry, scans Er on several
surfaces, and plots core-to-edge ambipolar Er and bootstrap current versus
\(\psi_N = r_N^2\).
Boozer .bc workflow
For geometryScheme = 11 (stellarator-symmetric) and 12
(non-stellarator-symmetric) the loader reads the Boozer-coordinate Fourier
tables and evaluates the fields on the requested angular grid, applying the
representable-mode truncation above. Packaged examples can refer to
release-hosted .bc fixtures by basename (for example hsx3free.bc or
w7x_standardConfig.bc); the resolver verifies the release archive checksum
and each extracted-file checksum before use.
Radial coordinates and geometry-derived scales
The geometry layer defines the conversion between the radial labels used throughout dkx:
selected by inputRadialCoordinate (surface) and
inputRadialCoordinateForGradients (profile gradients). It also computes the
surface scalars that enter normalization and diagnostics:
and the metric contractions used by the classical transport and NTV diagnostics.
Deferrals and non-modes
Stated plainly:
Scheme 13 via namelist: the differentiable
from_fourierspectrum path exists in Python, but parsingbmnc/bmnsfrom aninput.namelistis deferred; use the Python constructor for optimization loops.Non-stellarator-symmetric VMEC:
geometryScheme = 12Boozer.bcfiles are supported; a non-stellarator-symmetric VMEC equilibrium is routed to the legacy owner rather than the canonical stack.Miller geometry: there is no separate Miller-parameter public geometry mode. For tokamak studies the supported path is the analytic straight-field- line family (primarily
geometryScheme = 1).
Worked examples
analytic tokamak (scheme 1):
examples/getting_started/write_sfincs_output_tokamak.pyVMEC (scheme 5) with explicit
wout_path:examples/getting_started/write_sfincs_output_vmec.pymixed Boozer + VMEC transport (schemes 11 and 5):
examples/transport/transport_matrix_rhsmode2_scheme11_and_scheme5.pydifferentiable geometry gradient check:
examples/autodiff/differentiable_geometry_gradients.py
For the input knobs see Inputs (namelist) reference; for the way geometry enters the DKE see Physics reference: the radially local drift-kinetic model, Drift-kinetic equation and system of equations, and Numerics and algorithms.