spectre.converters.fix2free

Convert a fixed-boundary SPECTRE InputParameters to free-boundary.

The workflow separates boundary-shape generation from field computation so the user can supply any simsopt SurfaceRZFourier as the computational boundary (e.g. one extended with extend_via_normal()).

Module Contents

spectre.converters.fix2free.params_to_simsopt_boundary(params, which='plasma', ntheta=248, nzeta_per_period=252)

Convert InputParameters boundary to a simsopt SurfaceRZFourier.

Args:

params: InputParameters pydantic object. which: 'plasma' → uses rbc/zbs; 'comp' → uses rwc/zws. ntheta: number of theta quadrature points for the full torus. nzeta_per_period: number of zeta quadrature points per field period.

Returns:

simsopt SurfaceRZFourier.

spectre.converters.fix2free.fix2free(params, bs, comp_boundary, nvns_mpol=None, nvns_ntor=None, filename=None, curpol=None, vacuum=False)

Convert a fixed-boundary SPECTRE InputParameters to free-boundary.

The caller is responsible for building the computational boundary — e.g. via params_to_simsopt_boundary() followed by extend_via_normal().

If params.physics.lfreebound == 1 on input, the function replaces the existing computational boundary and vacuum field while leaving all other fields unchanged.

The output lconstraint is chosen as follows:

  • vacuum=Truelconstraint=3 (toroidal-current constraint; the transform is not meaningful in a vacuum equilibrium).

  • input lconstraint == 1 → preserved (rotational-transform constraint).

  • otherwise → lconstraint=3 (default for free-boundary).

The sign convention is anchored to the actual coil field: B_φ is evaluated from bs at \(R=R_{00}, \phi=0, Z=0\) and its sign is given to BOTH curpol and phiedge. This prevents VMEC fixed boundary runs with unsigned Phiedge giving beleivable-but-wrong results, and ensures consistency with VNS, which is directly evaluated from the same biot-savart evaluation.

if curpol (μ₀ × net poloidal linking current) is not specified, it is calculated from the sum of all coil currents. This assumes every coil in your set links the plasma exactly once in the same direction. All the ways coils can (un) link the plasma cannot be captured in a neat algorithm, so we ask you to compute this yourself.

The output phiedge keeps the magnitude of params.physics.phiedge but takes its sign from the coil field. The coils also set the comp/plasma flux ratio that fills the vacuum-slot tflux[nvol].

A good sanity check can be performed against a ground truth, buy running this with vacuum=True, and comparing the converged state with the Biot-Savart field directly from the coils.

Args:
params: InputParameters object

(fixed- or free-boundary).

bs: simsopt BiotSavart built from the full coil set

including all symmetry copies.

comp_boundary: simsopt SurfaceRZFourier representing

the computational (outer) boundary, already extended beyond the plasma. Its quadrature resolution is used for the B·n evaluation grid.

nvns_mpol: poloidal Fourier resolution for the B·n modes. Defaults to

params.physics.mpol.

nvns_ntor: toroidal Fourier resolution for the B·n modes. Defaults to

params.physics.ntor.

filename: if given, write the result as a TOML file at this path. curpol: μ₀ × total poloidal linking current [T·m]. If None (default),

computed automatically from bs.coils as sign(B_φ_coils) × μ₀ × Σ|I_coil|. That formula assumes every coil is a modular coil that links the plasma exactly once (a warning is issued); pass curpol explicitly for coil sets that include poloidal field coils or coils linking the plasma more than once. When given, only its magnitude is used.

vacuum: if True, produce a vacuum free-boundary equilibrium.

A vacuum field carries no plasma pressure and no plasma current, this sets pressure, pscale, curtor, ivolume and isurf to zero, and lbnszero

Returns:

Validated InputParameters with lfreebound = 1.

Raises:

ImportError: if simsopt is not installed.

spectre.converters.fix2free.plot_freeboundary_modes(bs, params, ntheta=248, nzeta=252, log_scale=False, vns_rtol=0.001)

Plot B·n mode spectrum and B·n map for a free-boundary setup.

Evaluates B·n on the computational boundary, FFTs it at twice the stored resolution (−2·ntor … 2·ntor × 0 … 2·mpol), and draws a red box around the modes that are actually stored in params (−ntor … ntor × 0 … mpol). The truncation error — the fraction of the B·n spectral RMS that falls outside that box — is printed, and the freshly evaluated modes are checked against the vns already stored in params.

Args:

bs: simsopt BiotSavart. params: InputParameters with

lfreebound == 1 (rwc/zws populated).

ntheta: theta quadrature points for the B·n evaluation. nzeta: zeta quadrature points per field period for the B·n evaluation. log_scale: if True, colour-map shows log₁₀ of the mode amplitude,

floored at 1e-7. Default False plots the raw amplitude, which makes the spectrum decay and the dominant modes easier to compare.

vns_rtol: tolerance for the vns consistency check, as a peak-normalised

max deviation between the stored and re-evaluated modes. The match is not bitwise: B·n is quadrature-sampled and the comp boundary is rebuilt from the stored (truncated) rwc/zws, so the modes shift slightly when this grid or resolution differs from the one used at creation. For the standard same-resolution workflow the deviation is ~1e-5; raise this if the comp boundary was built at higher resolution than mpol/ntor.

Returns:

matplotlib Figure with two panels: mode spectrum (left) and B·n map (right).

Raises:
ValueError: if params.physics.vns deviates from the B·n modes evaluated

here by more than vns_rtol (peak-normalised).