Theory and conventions

This page summarizes conventions used in vmex. The goal is compatibility with VMEC2000 (wout_*.nc) and with standard VMEC literature.

Coordinates and angles

VMEC uses curvilinear coordinates on nested flux surfaces. In this repo we use:

  • \(s \in [0,1]\): normalized toroidal flux label (VMEC’s “radial” coordinate).

  • \(\theta \in [0, 2\pi)\): poloidal angle.

  • \(\zeta \in [0, 2\pi)\): field-period toroidal angle (VMEC internal coordinate).

  • physical toroidal angle \(\phi_{\mathrm{phys}} = \zeta / \mathrm{NFP}\), where \(\mathrm{NFP}\) is the number of field periods.

Fourier phases are written as:

\[\mathrm{phase}(m,n;\theta,\zeta) = m\theta - n\zeta.\]

Here \(n\) is the field-period toroidal mode number (VMEC stores xn = n*NFP in wout).

Derivatives w.r.t. the physical toroidal angle satisfy:

\[\frac{\partial}{\partial \phi_{\mathrm{phys}}} = \mathrm{NFP}\,\frac{\partial}{\partial \zeta}.\]

Surface representation

VMEC represents a surface in cylindrical coordinates using Fourier series:

\[R(s,\theta,\zeta) = \sum_{m,n} \Bigl( R_{mn}^c(s)\cos(m\theta-n\zeta) + R_{mn}^s(s)\sin(m\theta-n\zeta) \Bigr),\]
\[Z(s,\theta,\zeta) = \sum_{m,n} \Bigl( Z_{mn}^c(s)\cos(m\theta-n\zeta) + Z_{mn}^s(s)\sin(m\theta-n\zeta) \Bigr).\]

vmex stores these coefficients in the SpectralState pytree as arrays shaped (ns, K) where K is the number of (m,n) modes in the main VMEC ordering (see vmex.core.fourier for the mode bookkeeping).

Parities and stellarator symmetry (lasym)

A stellarator-symmetric equilibrium is invariant under \((\theta,\zeta) \to (-\theta,-\zeta)\) with \(R \to R\), \(Z \to -Z\). Each field therefore keeps only one Fourier parity:

Field

symmetric (lasym = F)

antisymmetric partner (lasym = T only)

\(R\)

\(\cos(m\theta-n\zeta)\) (rmnc)

\(\sin\) (rmns)

\(Z\)

\(\sin(m\theta-n\zeta)\) (zmns)

\(\cos\) (zmnc)

\(\lambda\)

\(\sin(m\theta-n\zeta)\) (lmns)

\(\cos\) (lmnc)

SpectralState always carries all six blocks (R_cos, R_sin, Z_cos, Z_sin, L_cos, L_sin); in symmetric runs the antisymmetric partners are structurally zero and do not evolve. The synthesis fourier_to_real() (VMEC totzsps + totzspa) handles both parities in one signed-\((m,n)\) cos/sin packing.

Stellarator symmetry also halves the angular grid: the stored poloidal extent is \(\theta \in [0,\pi]\) (ntheta2) for symmetric runs and the full \([0, 2\pi)\) (ntheta1) when lasym — the ntheta3 property of Resolution. For lasym runs, the force kernels are first split into symmetric/antisymmetric parts on the reduced interval (VMEC symforce.f, symforce_split(), applied by symmetrize_forces()) and each part is projected with the matching analysis transform (tomnsps() / tomnspa()).

Regularity and internal storage

VMEC stores odd-m harmonics in an internal form that factors out the radial \(\sqrt{s}\) behavior so that coefficients remain finite on-axis. This is implemented through the scalxc array (1 for even-m, \(1/\sqrt{s}\) for odd-m) and is applied whenever VMEC interpolates between radial grids or builds preconditioned residuals.

When LCONM1 is enabled, VMEC also applies a constrained internal basis for the \(m=1\) boundary modes:

\[R^{s}_{1n,\mathrm{int}} = \frac{1}{2}\left(R^{s}_{1n,\mathrm{phys}} + Z^{c}_{1n,\mathrm{phys}}\right), \qquad Z^{c}_{1n,\mathrm{int}} = \frac{1}{2}\left(R^{s}_{1n,\mathrm{phys}} - Z^{c}_{1n,\mathrm{phys}}\right).\]

vmex uses the same internal storage so that its boundary mapping, multigrid interpolation, and parity diagnostics match VMEC2000.

The lambda field

VMEC introduces a scalar field \(\lambda(s,\theta,\zeta)\) to define the “straight-field-line” poloidal angle:

\[u = \theta + \lambda(s,\theta,\zeta),\]

so that magnetic field lines are straight in the (u,\zeta) angle pair.

Important scaling convention

In VMEC2000 wout files, the stored Fourier coefficients of \(\lambda\) are scaled by a run-dependent scalar lamscale. VMEC multiplies \(\partial\lambda/\partial\theta\) and \(\partial\lambda/\partial\zeta\) by lamscale before using them in the contravariant field formulas.

vmex follows this convention so we can validate against wout values.

Geometry, metric, and Jacobian

We form covariant basis vectors by embedding the surface into 3D Cartesian coordinates using the physical toroidal angle \(\phi_{\mathrm{phys}}\):

\[x = R\cos\phi_{\mathrm{phys}}, \qquad y = R\sin\phi_{\mathrm{phys}}, \qquad z = Z.\]

The covariant basis vectors are:

\[\mathbf{e}_s = \partial_s \mathbf{r}, \qquad \mathbf{e}_\theta = \partial_\theta \mathbf{r}, \qquad \mathbf{e}_\phi = \partial_{\phi_{\mathrm{phys}}}\mathbf{r}.\]

The covariant metric is:

\[g_{ij} = \mathbf{e}_i \cdot \mathbf{e}_j, \qquad i,j \in \{s,\theta,\phi\}.\]

The signed Jacobian is:

\[\sqrt{g} = \mathbf{e}_s \cdot (\mathbf{e}_\theta \times \mathbf{e}_\phi).\]

VMEC stores a sign convention signgs = ±1 such that signgs*sqrtg is positive away from the axis.

Contravariant magnetic field (VMEC form)

For fixed-boundary VMEC, the magnetic field is represented in terms of contravariant components \(B^u\) and \(B^v\) (VMEC’s bsupu and bsupv) and 1D flux functions:

  • phipf(s) = dΦ/ds (toroidal flux derivative),

  • chipf(s) = dΧ/ds (poloidal flux derivative),

plus lambda derivatives.

In this repo (matching VMEC’s bcovar + add_fluxes logic), the formulas are:

\[\mathrm{bsupv} = \frac{\mathrm{phipf} + \mathrm{lamscale}\,\partial_\theta\lambda} {\mathrm{signgs}\,\sqrt{g}\,2\pi},\]
\[\mathrm{bsupu} = \frac{\mathrm{chipf} - \mathrm{lamscale}\,\partial_\zeta\lambda} {\mathrm{signgs}\,\sqrt{g}\,2\pi}.\]

Note that \(\partial_\zeta \lambda\) is w.r.t. the field-period coordinate \(\zeta\), while the geometry kernel returns \(\partial_{\phi_{\mathrm{phys}}}\lambda\), so we convert using \(\partial_\zeta = (1/\mathrm{NFP})\,\partial_{\phi_{\mathrm{phys}}}\).

Covariant components and \(|B|\)

Because \(B^s = 0\), lowering the index needs only the angular metric block:

\[B_u = g_{uu} B^u + g_{uv} B^v, \qquad B_v = g_{uv} B^u + g_{vv} B^v,\]

and the field magnitude follows without ever forming Cartesian components:

\[|B|^2 = B^u B_u + B^v B_v.\]

The computation chain per iteration is:

  1. real_space_geometry() — synthesize \(R, Z, \lambda\) and their angular derivatives (even/odd-m planes);

  2. half_mesh_jacobian() — half-mesh \(\sqrt{g}\) and the \(\tau\) sign proxy;

  3. metric_elements() — half-mesh guu, guv, gvv;

  4. magnetic_fields()bsupu/bsupv from the flux profiles and \(\lambda\) derivatives, then bsubu/bsubv and the total pressure \(|B|^2/2 + p\) (VMEC bsq).

Energy scalars (wb and wp)

VMEC reports the magnetic energy scalar wb and thermal energy scalar wp in wout files. In VMEC normalization:

\[\mathrm{wb} = \frac{1}{(2\pi)^2}\int \frac{B\cdot B}{2}\,dV, \qquad \mathrm{wp} = \frac{1}{(2\pi)^2}\int p\,dV.\]

Important: VMEC treats internal pressure in units of \(\mu_0\,\mathrm{Pa}\) (i.e. \(B^2\) units). vmex follows this convention for parity.