Process reference: equilibrium

Generated from the docstrings of vaft.process.equilibrium: 80 public functions. Each entry is what vaft.process.describe("equilibrium.<name>") prints. All process categories

Overview

Physics only. Nothing here reads or writes an ODS: every function takes arrays and returns arrays or plain records, so the same code serves a GEQDSK, an ODS slice and an analytic model. The adapters that turn those into one record, and the parametric models fitted to them, live in _equilibrium_parametric and are re-exported through this module, which is their stable import location.

What the module does, in the order a flux map is usually consumed::

psi(R, Z) on a grid
-> radial coordinates: psi_norm, and the toroidal radius through q
-> flux surfaces: contours, their shape, their averages
-> fields: the poloidal field from psi, the toroidal field from F
-> global scalars: diamagnetism, the Shafranov integrals, force balance
SymbolMeaningUnit
psipoloidal fluxWb/rad
psi_norm(psi - psi_axis)/(psi_boundary - psi_axis)-
Fpoloidal current function, R*B_phiT m
B_ppoloidal field magnitudeT
qsafety factor-
kSauter Eq. 20 prefactor carrying sign and 2*pi-

Flux is per radian unless a function says otherwise. The poloidal field from psi carries a factor k = sigma_RphiZ * sigma_Bp / (2*pi)**e_Bp [1], which folds the orientation sign and the 2pi normalization together. A function that forms a field from psi takes a COCOS index, and with none it uses the historical weber-per-radian form. Getting this wrong scales the field by 2pi and the poloidal beta by its square, plausibly and silently. A function that takes a precomputed field instead inherits whatever convention produced it and cannot check it.

Three radial coordinates, never interchangeable. rho_tor_norm equals sqrt(psi_norm), which is rho_pol_norm, only for a flat safety factor. derive_radial_coordinates returns all three with an explicit record of which is unavailable and why.

Flux maps are indexed (R, Z), matching the IMAS two-dimensional profile layout. Boundary contours run counter-clockwise, because the Shafranov outward normal is only outward then.

Functions

as_equilibriumAdapt a GEQDSK, ODS, IMAS handle, or native model to one equilibrium record. Convention
calculate_average_boundary_poloidal_fieldContour-length average of the poloidal field around a closed boundary. Convention
calculate_diamagnetismDiamagnetism from its volume-integral definition. Convention
calculate_q_profile_from_psiCalculate the safety factor profile q(psi) from a 2D poloidal flux map and F(psi). Convention
calculate_reconstructed_diamagnetic_fluxDiamagnetic flux reconstructed from the equilibrium’s own toroidal field. Convention
check_equilibrium_requirementsWhether an equilibrium carries what a named algorithm needs to run. Convention
compare_contoursHow far apart two closed contours are, point to curve. Convention
computed_diamagnetism_from_phiEFIT-style diamagnetism from an already-computed diamagnetic flux. Convention
connection_length_mapConnection length of the field line through each of many starting points. Convention
contour_shape_parametersShape parameters of one closed flux-surface contour. Convention
contour_shaping_observablesModel-independent shaping observables of one closed contour: indentation and asymmetry. Convention
convert_cocosConvert an equilibrium between two explicitly known COCOS conventions. Convention
current_centroidTotal toroidal current and its centroid, from a gridded current density. Convention
current_covarianceSecond central moment tensor of a gridded current density. Convention
current_momentOne moment integral (R - R_0)**p (Z - Z_0)**q J_phi dA of a gridded current density. Convention
derive_boundary_representationClassify the plasma boundary and derive its geometric diagnostics. Convention
derive_current_momentsReduce an equilibrium’s toroidal current density to its low-order moments. Convention
derive_global_descriptorsGlobal scalar descriptors of one equilibrium, each with its derivation record. Convention
derive_radial_coordinatesThe three normalized radial coordinates, each with its derivation record. Convention
efit_virial_volume_integralsEFIT-style weighted volume integrals for the virial closures. Convention
ejiri_mirror_geometryEjiri mirror-confinement proxy for one magnetic snapshot. Convention
equilibrium_field_on_grid(B_R, B_Z, B_phi) on the whole (R, Z) grid, each (nR, nZ). Convention
evaluate_fourier_surfacePoints on a Fourier-represented flux surface at given arc-length angles. Convention
evaluate_guazzotto_freidbergNormalized flux and its analytic derivatives of a Guazzotto-Freidberg equilibrium. Convention
evaluate_millerPoints on a Miller-parameterized flux surface at given poloidal angles. Convention
evaluate_mxh_chebyshevFlux-surface points of an MXH-Chebyshev representation. Convention
evaluate_solovevEvaluate an analytic Solov’ev equilibrium on a set of points. Convention
extract_flux_surface_contoursIso-flux contours at requested normalized levels, by marching squares. Convention
find_rational_surfacesWhere a toroidal mode resonates: the surfaces with q = m / n.
find_stationary_pointsLocate the O-points and saddles of psi to sub-grid accuracy. Convention
fit_fourier_surfaceRepresent one closed flux-surface contour as a truncated Fourier series. Convention
fit_fourier_surface_sequenceRepresent a sequence of internal flux surfaces as Fourier series. Convention
fit_free_boundary_coilsFit physical PF circuit currents to a prescribed LCFS in free space. Convention
fit_miller_sequenceFit Miller parameters across a sequence of flux surfaces, with radial derivatives. Convention
fit_miller_surfaceFit the Miller shape parameters to one closed flux-surface contour. Convention
fit_mxh_chebyshevRepresent an equilibrium’s flux surfaces by MXH shapes with Chebyshev radial profiles. Convention
fit_solovevRepresent an equilibrium by the Solov’ev solution closest to its flux map. Convention
flux_surface_quantitiesFlux-surface averages and shape parameters on each normalized flux level. Convention
fractional_cell_weights_from_boundaryFractional area of each grid cell that lies inside the plasma boundary. Convention
grad_shafranov_operatorThe Grad-Shafranov elliptic operator on a rectangular grid. Convention
grad_shafranov_residualHow far an equilibrium is from satisfying its own force balance, surface by surface. Convention
grad_shafranov_residual_modesProject the Grad-Shafranov residual onto poloidal harmonics on nested flux surfaces. Convention
guazzotto_freidberg_parametersThe dimensionless plasma parameters of Section 6 for a Guazzotto-Freidberg equilibrium. Convention
guazzotto_freidberg_to_equilibriumExport a Guazzotto-Freidberg equilibrium as a gridded record in physical units. Convention
integrate_romero_closureIntegrate Romero’s first-order closure from a boundary-voltage history. Convention
lab_to_straight_field_lineRelabel a geometric poloidal angle into the straight-field-line one. Convention
make_equilibrium_field_interpolatorBuild a callable giving the full magnetic field anywhere on one time slice. Convention
make_vacuum_field_interpolatorBuild a callable giving the vacuum magnetic field anywhere on one grid. Convention
miller_surfacesA family of Miller surfaces for a shape scan. Convention
parallel_current_from_toroidalConvert a shell toroidal driven current to the IMAS parallel current. Convention
pest_angle_from_jacobian_angleRelabel a DCON flux-surface angle into the PEST angle, from R alone. Convention
plasma_cell_weightsFraction of each grid cell that is plasma, from the boundary outline when there is one. Convention
poloidal_field_at_boundaryPoloidal field on a boundary contour, from the flux map by bicubic spline. Convention
prepare_boundary_for_shafranovCondition a boundary contour into the form the Shafranov integrals require. Convention
psi_to_radialInboard and outboard major radius of each flux value, along the axis row. Convention
psi_to_rhoNormalized toroidal-flux radius at one poloidal flux value, by integrating q. Convention
psi_to_rzMap a flux-surface profile onto the two-dimensional grid through psi. Convention
r_at_z_extremumMajor radius where a contour reaches its highest or lowest point. Convention
radial_to_psiPoloidal flux at a major radius on the midplane row of the flux map. Convention
rational_surfacesEvery flux surface where the safety factor takes a requested rational value. Convention
resistive_layer_atResistivity and mass density at flux surfaces someone else located.
resistive_layer_parametersResistivity and mass density at each rational surface of a toroidal mode.
rho_to_psiPoloidal flux at one normalized toroidal-flux radius, by root finding. Convention
romero_flux_balanceRomero’s exact voltage and volt-second balance over a sampled discharge. Convention
scale_boundary_conformalScale a closed boundary about a centre, giving a contour conformal to it. Convention
shafranov_integralsThe three Shafranov boundary integrals and the virial coefficient. Convention
solovev_exampleBuild a shaped analytic Solov’ev equilibrium in a chosen boundary topology. Convention
solovev_shape_constraintsThe Cerfon-Freidberg boundary conditions for a shaped, possibly diverted, plasma. Convention
solovev_to_equilibriumExport an analytic Solov’ev model as a gridded equilibrium record. Convention
solovev_xpoint_constraintsThe three conditions that make a point an X-point on the boundary flux surface.
solve_guazzotto_freidbergSolve the Guazzotto-Freidberg analytic equilibrium for a shape and a beta parameter. Convention
solve_solovev_constraintsSolve for the Solov’ev coefficients that satisfy a set of geometric constraints. Convention
straight_field_line_angle_on_gridThe PEST straight-field-line angle on every node of a flux map. Convention
straight_field_line_mapThe PEST straight-field-line poloidal angle of a flux map, built once. Convention
straight_field_line_tablesPer-surface tables taking the geometric poloidal angle to the code’s own. Convention
trace_field_lineTrace one magnetic field line through the equilibrium field. Convention
validate_equilibriumDeprecated compatibility wrapper for check_equilibrium_requirements. Deprecated
virial_alpha_conformal_annulusVirial closure coefficient from a thin annulus conformal to the LCFS. Convention
virial_alpha_thin_annulusVirial closure coefficient in the thin-annulus limit, from the boundary field. Convention
volume_averageVolume average of a gridded quantity over the confined region. Convention

as_equilibrium

Machine-independentConvention-sensitive source
as_equilibrium(source, *, time_index=0, profile_index=0, convention=None)
Show source vaft/process/_equilibrium_parametric.py, lines 301–376
def as_equilibrium(
    source: Any, *, time_index: int = 0, profile_index: int = 0,
    convention: int | None = None,
) -> EquilibriumData:
    """Adapt a GEQDSK, ODS, IMAS handle, or native model to one equilibrium record.

    The single entry point every algorithm here goes through, so that they operate
    on one normalized shape regardless of where the equilibrium came from.

    Parameters
    ----------
    source : EquilibriumData, ODS, GEQDSK, IMAS equilibrium IDS, path or mapping
        A record is returned as is, or converted when a convention is asserted; a
        path or path-like is read as a GEQDSK; an object exposing an OMAS export
        is exported first; a native IDS root is wrapped [-].
    time_index : int, optional
        Which time slice to take from a multi-slice source [-].
    profile_index : int, optional
        Which 2-D profile grid to take from a slice carrying more than one [-].
    convention : int, optional
        A COCOS index the caller asserts for *source*, 1 to 18.  When given it
        always wins over identification, and the result is converted to it [-].

    Returns
    -------
    EquilibriumData
        The normalized record, with its ``convention`` set from the assertion when
        one was made and from identification otherwise [-].

    Convention
    ----------
    Normalizes *shape*, not *convention*: the returned record is not converted to
    any internal standard unless *convention* asks for it, so its psi may be in
    weber or weber per radian and only ``convention.psi_per_radian`` says which.
    A record built without a convention reports neither, and downstream field
    calculations then fall back to the historical per-radian form.

    Applicability
    -------------
    Machine-independent.

    Limitations
    -----------
    A GEQDSK is identified as a two-dimensional grid by the presence of its
    characteristic keys, so a mapping that happens to carry them is treated as
    one.  Only the requested time slice and profile grid are read; everything else
    in a multi-slice source is discarded.

    Provenance
    ----------
    .. [1] The per-source adapters below preserve each format's own layout
       conventions; see :func:`vaft.process.cocos.identify_convention` for how the
       convention is inferred when the caller does not assert one.
    """
    if isinstance(source, EquilibriumData):
        return convert_cocos(source, convention) if convention is not None else source
    if isinstance(source, (str, bytes)) or hasattr(source, "__fspath__"):
        from vaft.data.eqdsk import read_geqdsk

        source = read_geqdsk(source)
    if hasattr(source, "to_omas") and not hasattr(source, "mapping"):
        source = source.to_omas()
    # A native equilibrium IDS is already the authoritative root object but,
    # unlike ODS, it does not contain an outer ``equilibrium`` mapping.  Wrap
    # that root and let the same path adapter traverse native attributes/AoS.
    metadata = getattr(source, "metadata", None)
    if getattr(metadata, "name", None) == "equilibrium":
        source = {"equilibrium": source}
    mapping = source.mapping if hasattr(source, "mapping") else source
    try:
        keys = set(mapping.keys())
    except Exception:
        keys = set()
    if {"NW", "NH", "PSIRZ"}.issubset(keys):
        return _from_geqdsk(source, convention)
    return _from_ods(source, time_index, profile_index, convention)

Adapt a GEQDSK, ODS, IMAS handle, or native model to one equilibrium record.

The single entry point every algorithm here goes through, so that they operate on one normalized shape regardless of where the equilibrium came from.

NameTypeUnitDescription
sourceEquilibriumData, ODS, GEQDSK, IMAS equilibrium IDS, path or mapping-

A record is returned as is, or converted when a convention is asserted; a path or path-like is read as a GEQDSK; an object exposing an OMAS export is exported first; a native IDS root is wrapped.

time_indexint, optional-

Which time slice to take from a multi-slice source.

profile_indexint, optional-

Which 2-D profile grid to take from a slice carrying more than one.

conventionint, optional-

A COCOS index the caller asserts for source, 1 to 18. When given it always wins over identification, and the result is converted to it.

Returns
—EquilibriumData-

The normalized record, with its convention set from the assertion when one was made and from identification otherwise.

Convention

Normalizes shape, not convention: the returned record is not converted to any internal standard unless convention asks for it, so its psi may be in weber or weber per radian and only convention.psi_per_radian says which. A record built without a convention reports neither, and downstream field calculations then fall back to the historical per-radian form.

Applicability · Limitations · Provenance

Applicability

Machine-independent.

Limitations

A GEQDSK is identified as a two-dimensional grid by the presence of its characteristic keys, so a mapping that happens to carry them is treated as one. Only the requested time slice and profile grid are read; everything else in a multi-slice source is discarded.

Provenance

  1. The per-source adapters below preserve each format’s own layout conventions; see vaft.process.cocos.identify_convention for how the convention is inferred when the caller does not assert one.

calculate_average_boundary_poloidal_field

Machine-independentConvention-sensitive source
calculate_average_boundary_poloidal_field(R_bdry, Z_bdry, B_p_bdry)
Show source vaft/process/equilibrium.py, lines 1078–1140
def calculate_average_boundary_poloidal_field(R_bdry, Z_bdry, B_p_bdry):
    """Contour-length average of the poloidal field around a closed boundary.

    Parameters
    ----------
    R_bdry : array_like
        Major radius of the contour points [m].
    Z_bdry : array_like
        Height of the contour points [m].
    B_p_bdry : array_like
        Poloidal field magnitude at those points [T].

    Returns
    -------
    float
        The average poloidal field, the contour integral of the field over the
        contour length [T].

    Convention
    ----------
    Weighted by arc length, not by point count, so an unevenly sampled contour
    still gives the physical average. The midpoint rule is used between
    consecutive points, and the contour is treated as closed. This is the
    normalizing field that the poloidal beta and the diamagnetism are taken
    against, so it must come from the same convention as the field that produced
    it.

    Applicability
    -------------
    Machine-independent.

    Limitations
    -----------
    Midpoint rule on the given points, so a coarsely sampled contour underweights
    regions of high curvature. Takes the field magnitudes as given and cannot
    check they were computed in a consistent convention.

    Provenance
    ----------
    .. [1] The definition used with :func:`shafranov_integrals` and
       :func:`calculate_diamagnetism` as their reference field.
    """
    # 1. 배열이 닫혀있는지 확인 (마지막 점 != 첫 점이면 닫아줌)
    if (R_bdry[0] != R_bdry[-1]) or (Z_bdry[0] != Z_bdry[-1]):
        R_bdry = np.append(R_bdry, R_bdry[0])
        Z_bdry = np.append(Z_bdry, Z_bdry[0])
        B_p_bdry = np.append(B_p_bdry, B_p_bdry[0])

    # 2. 미소 길이 성분 계산 (dl)
    dR = np.diff(R_bdry)
    dZ = np.diff(Z_bdry)
    dl = np.sqrt(dR**2 + dZ**2)

    # 3. 적분 구간의 대푯값 (Midpoint rule or Trapezoidal)
    B_p_mid = 0.5 * (B_p_bdry[:-1] + B_p_bdry[1:])

    # 4. 선적분 수행
    L_total = np.sum(dl)             # ∮ dl
    integral_Bp = np.sum(B_p_mid * dl) # ∮ B_p dl

    B_pa = integral_Bp / L_total

    return B_pa

Contour-length average of the poloidal field around a closed boundary.

NameTypeUnitDescription
R_bdryarray_likem

Major radius of the contour points.

Z_bdryarray_likem

Height of the contour points.

B_p_bdryarray_likeT

Poloidal field magnitude at those points.

Returns
—floatT

The average poloidal field, the contour integral of the field over the contour length.

Convention

Weighted by arc length, not by point count, so an unevenly sampled contour still gives the physical average. The midpoint rule is used between consecutive points, and the contour is treated as closed. This is the normalizing field that the poloidal beta and the diamagnetism are taken against, so it must come from the same convention as the field that produced it.

Applicability · Limitations · Provenance

Applicability

Machine-independent.

Limitations

Midpoint rule on the given points, so a coarsely sampled contour underweights regions of high curvature. Takes the field magnitudes as given and cannot check they were computed in a consistent convention.

Provenance

  1. The definition used with shafranov_integrals and calculate_diamagnetism as their reference field.

calculate_diamagnetism

Machine-independentConvention-sensitive source
calculate_diamagnetism(R_grid, Z_grid, psi_RZ, psi_axis, psi_lcfs, psiN_1d, f_1d, f_vac_val, B_pa, V_p=None, weights=None)

Diamagnetism from its volume-integral definition.

NameTypeUnitDescription
R_gridarray_likem

Major-radius grid axis.

Z_gridarray_likem

Height grid axis.

psi_RZarray_likeWb/rad

Poloidal flux on the grid, indexed (R, Z).

psi_axisfloatWb/rad

Poloidal flux on the magnetic axis.

psi_lcfsfloatWb/rad

Poloidal flux at the last closed flux surface.

psiN_1darray_like-

Normalized poloidal flux the poloidal-current profile is given on.

f_1darray_likeT m

Poloidal current function F = R*B_phi on that abscissa.

f_vac_valfloatT m

The same function at the boundary, standing in for the vacuum field

B_pafloatT

Boundary-average poloidal field, the normalizing scale, strictly positive

V_pfloat, optionalm^3

Plasma volume. Computed from the same grid and mask when not given

weightsarray_like, optional-

Fraction of each cell inside the plasma, from plasma_cell_weights; replaces the normalized-flux mask when given.

Returns
—float-

The diamagnetism, positive for a diamagnetic plasma and negative for a paramagnetic one.

Convention

mu_i = (1/(B_pa^2 * V)) * integral (B_tv^2 - B_t^2) dV, with the toroidal fields taken as the poloidal current function over the major radius and the axisymmetric volume element. Positive means the plasma reduced the toroidal field. A result of the unexpected sign usually means the vacuum proxy was taken on axis rather than at the boundary, or that the poloidal current profile’s sign convention disagrees with the equilibrium it came from.

Raises

  • ValueError The boundary-average poloidal field is not positive.
Applicability · Limitations · Provenance

Applicability

Machine-independent.

Limitations

The same per-cell boundary mask as volume_average, so the boundary is resolved only to the grid. The vacuum field is a single boundary value rather than a profile.

Provenance

  1. The volume-integral definition of diamagnetism; the surface-integral form is calculate_reconstructed_diamagnetic_flux, and the two should agree for a consistent equilibrium.

calculate_q_profile_from_psi

Machine-independentConvention-sensitive source
calculate_q_profile_from_psi(psi_grid, R, Z, f_profile, psi_axis=None, psi_boundary=None, levels_norm=None, *, axis_rz=None, boundary=None, cocos=11, sigma_ip=1, sigma_b0=1, min_points=16, return_details=False)

Calculate the safety factor profile q(psi) from a 2D poloidal flux map and F(psi).

NameTypeUnitDescription
psi_gridarray_likeWb or Wb/rad

Poloidal flux on the grid, shaped (len(R), len(Z)), in the convention cocos specifies.

Rarray_likem

Major-radius grid axis.

Zarray_likem

Height grid axis.

f_profilefloat, array_like, callable, or tuple of array_likeT m

Poloidal current function F = R B_phi, as a scalar float, 1D array, callable f(psi), or tuple (psi_f, f_values).

psi_axisfloat, optionalWb or Wb/rad

Poloidal flux at the magnetic axis, in the convention cocos specifies.

psi_boundaryfloat, optionalWb or Wb/rad

Poloidal flux at the plasma boundary/LCFS, in the convention cocos specifies.

levels_normsequence of float, optional-

Normalized flux levels to evaluate on, 0 on axis and 1 at the boundary.

axis_rztuple of float, optionalm

Magnetic axis coordinates (R_axis, Z_axis).

boundarytuple of np.ndarray, optionalm

Boundary outline (R_bdry, Z_bdry).

cocosint, optional-

COCOS coordinate convention index (1-8, 11-18) describing the input conventions and target sign for q.

sigma_ipint, optional-

Sign of plasma current (+1 or -1) in the equilibrium coordinate system.

sigma_b0int, optional-

Sign of toroidal field (+1 or -1) in the equilibrium coordinate system.

min_pointsint, optional-

Fewest contour vertices a level needs before it is treated as resolved.

return_detailsbool, optional-

Whether to return diagnostic parameters and the full flux-surface quantities alongside the safety factor array.

Returns
—np.ndarray or dict of str to Any-

If return_details is False, returns a 1D array of safety factor values q on levels_norm [-]. If return_details is True, returns a dict with keys "q", "levels_norm", "psi_levels", "q_axis", "q_95", and "surfaces".

Convention

In Sauter and Medvedev (2013), the safety factor is defined as: \(q(\psi) = \frac{F(\psi)}{2\pi} \oint \frac{dl_p}{R^2 B_p} = \frac{F(\psi)}{2\pi} (2\pi)^{e_{B_p}} \oint \frac{dl_p}{R |\nabla\psi|}\) Expressed in terms of the geometric flux-surface averages computed by flux_surface_quantities (which operates in Wb/rad, $e_{B_p} = 0$, $dV/d\psi$ per radian): \(q(\psi) = \sigma \cdot \frac{F(\psi)}{(2\pi)^2} \left\langle \frac{1}{R^2} \right\rangle \frac{dV}{d\psi}\) where $\langle 1/R^2 \rangle$ is gm1, $dV/d\psi$ is dvolume_dpsi, and $\sigma$ is the orientation sign from Sauter Eq. 23: $\sigma_q = \sigma_{Ip} \sigma_{B0} \sigma_{\rho\theta\varphi}$. When cocos indicates full Weber storage ($e_{B_p} = 1$, COCOS 11-18), the input fluxes are divided by $2\pi$ to enter the contour tracing engine, and the resulting profile carries the exact COCOS-mandated sign.

Raises

  • ValueError The flux map is not shaped to (len(R), len(Z)), psi_axis or psi_boundary cannot be determined or are equal, or f_profile format is invalid.
Processing steps · Defaults · Applicability · Provenance

Processing steps

  1. Validate grid dimensions and identify the flux conversion factor from cocos.
  2. Normalize or deduce psi_axis and psi_boundary, converting to Wb/rad if necessary.
  3. Evaluate and interpolate f_profile onto the requested levels_norm.
  4. Call flux_surface_quantities to trace contours and compute geometric averages.
  5. Apply on-axis quadratic extrapolation in radius (linear in normalized flux $\psi_N$) to resolve $q_0$.
  6. Attach the COCOS sign factor $\sigma_q$ and package the output.

Defaults

levels_norm defaults to 65 points from 0 to 1 (numerical convenience). cocos=11 matches the IMAS standard data dictionary convention.

Applicability

Machine-independent. Works for any 2D tokamak poloidal flux map.

Provenance

  1. O. Sauter and S. Yu. Medvedev, Comput. Phys. Commun. 184 (2013) 293, Eq. (17) and Table I for COCOS relations and safety factor definitions.
  2. J. Wesson, Tokamaks, 4th ed., Oxford University Press (2011), Sec. 3.4.

calculate_reconstructed_diamagnetic_flux

Machine-independentConvention-sensitive source
calculate_reconstructed_diamagnetic_flux(R_grid, Z_grid, psi_RZ, psi_axis, psi_lcfs, psiN_1d, f_1d, f_vac_val, weights=None)

Diamagnetic flux reconstructed from the equilibrium’s own toroidal field.

The surface integral of the difference between the plasma toroidal field and the vacuum field it displaces. Physics only: it reads no ODS and writes none.

NameTypeUnitDescription
R_gridarray_likem

Major-radius grid axis.

Z_gridarray_likem

Height grid axis.

psi_RZarray_likeWb/rad

Poloidal flux on the grid, indexed (R, Z).

psi_axisfloatWb/rad

Poloidal flux on the magnetic axis.

psi_lcfsfloatWb/rad

Poloidal flux at the last closed flux surface.

psiN_1darray_like-

Normalized poloidal flux the poloidal-current profile is given on.

f_1darray_likeT m

Poloidal current function F = R*B_phi on that abscissa.

f_vac_valfloatT m

The same function at the boundary, standing in for the vacuum field

weightsarray_like, optional-

Fraction of each cell inside the plasma, from plasma_cell_weights; replaces the normalized-flux mask when given.

Returns
—floatWb

The reconstructed diamagnetic flux, negative for a diamagnetic plasma

Convention

The sign follows the physics, not a storage choice: a diamagnetic plasma reduces the toroidal field inside it, so the integral is negative. Only the normalized flux enters, so the absolute unit of the three flux arguments cancels as long as they agree. The vacuum proxy must be taken at the boundary, not on axis, or the difference is offset everywhere.

Processing steps · Applicability · Limitations · Provenance

Processing steps

  1. Map the poloidal current profile onto the grid through psi_to_rz.
  2. Form the plasma and vacuum toroidal fields as that function over the major radius.
  3. Integrate their difference over the cross-section inside the boundary.

Applicability

Machine-independent.

Limitations

A cross-sectional integral on the grid, so the boundary is resolved only to the cell. The vacuum field is taken as a single boundary value rather than a profile, which is exact only where the poloidal current is flat outside the plasma.

Provenance

  1. The definition of diamagnetic flux as the toroidal-field deficit integrated over the plasma cross-section, in the form the EFIT-style workflow this package reproduces uses.

check_equilibrium_requirements

Machine-independentConvention-sensitive source
check_equilibrium_requirements(equilibrium, *, required_for='general')
Show source vaft/process/_equilibrium_parametric.py, lines 379–454
def check_equilibrium_requirements(
    equilibrium: EquilibriumData, *, required_for: str = "general"
) -> ValidationReport:
    """Whether an equilibrium carries what a named algorithm needs to run.

    A **precondition**, not a scientific verdict (issue #337): it answers "can the
    global, Miller, edge or Solov'ev algorithms be applied to this input", which is
    why the answer depends on which one is asked about.  Whether the equilibrium is
    *credible* is a different question, asked by :mod:`vaft.validation` (#72).
    Renamed from ``validate_equilibrium``, which read as the latter and is now the
    name of the latter.

    Parameters
    ----------
    equilibrium : EquilibriumData
        The record to check [-].
    required_for : str, optional
        Which requirement set to apply: ``general``, ``global``, ``miller``,
        ``edge`` or ``solovev`` [-].

    Returns
    -------
    ValidationReport
        One issue per unmet requirement, at error severity for a missing or
        malformed field and warning severity for an ambiguous or self-contradictory
        convention.  An empty report means the algorithm can run [-].

    Convention
    ----------
    Reports two convention problems without resolving either: an ambiguous COCOS,
    where identification left more than one candidate, and a declared COCOS that
    contradicts the signs actually observed.  Both are warnings, because an
    algorithm can still run on a wrongly labelled equilibrium; it will simply be
    wrong in a way this function refuses to hide.

    Applicability
    -------------
    Machine-independent.

    Limitations
    -----------
    *required_for* is a free string; an unrecognized value applies the general
    requirements rather than being rejected.  A clean report says the fields are
    present and shaped correctly, not that their values are physical.

    Provenance
    ----------
    .. [1] Issue #337 for the split between a precondition and a scientific
       verdict, and #253 for the validation layer that owns the latter.
    """
    issues: list[ValidationIssue] = []
    if equilibrium.psi is not None and equilibrium.r is not None and equilibrium.z is not None:
        expected = (equilibrium.r.size, equilibrium.z.size)
        if equilibrium.psi.shape != expected:
            issues.append(ValidationIssue("error", "psi_shape", "psi", f"psi shape {equilibrium.psi.shape} must be {expected}"))
    elif required_for in {"global", "miller", "edge", "solovev"}:
        issues.append(ValidationIssue("error", "missing_grid", "psi", "R, Z, and 2-D psi are required"))
    if equilibrium.psi_axis is None or equilibrium.psi_boundary is None:
        issues.append(ValidationIssue("error", "missing_flux_bounds", "psi_axis", "psi_axis and psi_boundary are required"))
    elif equilibrium.psi_axis == equilibrium.psi_boundary:
        issues.append(ValidationIssue("error", "degenerate_flux", "psi_boundary", "psi_boundary must differ from psi_axis"))
    if equilibrium.lcfs is None or equilibrium.lcfs.r.size < 3:
        issues.append(ValidationIssue("error" if required_for in {"global", "miller", "edge"} else "warning", "missing_lcfs", "lcfs", "a closed LCFS with at least three points is required"))
    if equilibrium.convention.ambiguous:
        candidates = equilibrium.convention.candidates or ("none",)
        issues.append(ValidationIssue("warning", "ambiguous_cocos", "convention", f"COCOS is ambiguous ({candidates}); pass convention= explicitly before conversion"))
    if equilibrium.convention.contradicted:
        # A declaration the data does not support: a stale GEQDSK CASE token, a
        # mislabelled ODS, or an explicit argument that does not match the file.
        issues.append(ValidationIssue(
            "warning", "cocos_declared_conflicts_with_signs", "convention",
            f"COCOS {equilibrium.convention.cocos} is declared ({equilibrium.convention.source}) "
            f"but the observable signs and flux scale support "
            f"{equilibrium.convention.identified}; the declaration is being used as given",
        ))
    return ValidationReport(tuple(issues))

Whether an equilibrium carries what a named algorithm needs to run.

A precondition, not a scientific verdict (issue #337): it answers “can the global, Miller, edge or Solov’ev algorithms be applied to this input”, which is why the answer depends on which one is asked about. Whether the equilibrium is credible is a different question, asked by vaft.validation (#72). Renamed from validate_equilibrium, which read as the latter and is now the name of the latter.

NameTypeUnitDescription
equilibriumEquilibriumData-

The record to check.

required_forstr, optional-

Which requirement set to apply: general, global, miller, edge or solovev.

Returns
—ValidationReport-

One issue per unmet requirement, at error severity for a missing or malformed field and warning severity for an ambiguous or self-contradictory convention. An empty report means the algorithm can run.

Convention

Reports two convention problems without resolving either: an ambiguous COCOS, where identification left more than one candidate, and a declared COCOS that contradicts the signs actually observed. Both are warnings, because an algorithm can still run on a wrongly labelled equilibrium; it will simply be wrong in a way this function refuses to hide.

Applicability · Limitations · Provenance

Applicability

Machine-independent.

Limitations

required_for is a free string; an unrecognized value applies the general requirements rather than being rejected. A clean report says the fields are present and shaped correctly, not that their values are physical.

Provenance

  1. Issue #337 for the split between a precondition and a scientific verdict, and #253 for the validation layer that owns the latter.

compare_contours

Machine-independentConvention-sensitive source
compare_contours(reference_r, reference_z, other_r, other_z)

How far apart two closed contours are, point to curve.

Every vertex of each contour is measured to the nearest point on the other contour’s polyline – to a segment, not to a vertex – and the averages weight each vertex by the arc length it stands for, half of each adjoining segment. So the result does not depend on where either contour starts, which way it runs, or how densely or unevenly it is sampled, beyond the sampling’s own resolution of the curve.

NameTypeUnitDescription
reference_rarray_likem

Major radius of the reference contour.

reference_zarray_likem

Height of the reference contour.

other_rarray_likem

Major radius of the contour compared against it.

other_zarray_likem

Height of the contour compared against it.

Returns
—dict of str to float-

rms and mean of the separation, arc-length weighted over both contours; max, the symmetric Hausdorff distance; max_reference and max_other, the largest separation from each side; all in metres. length_reference and length_other, each contour’s poloidal length, in metres.

Convention

Both contours are treated as closed: the last point joins the first whether or not it repeats it. The measure is symmetric – both contours’ vertices contribute – and orientation-independent.

Raises

  • ValueError Either contour has fewer than three points.
Applicability · Limitations · Provenance

Applicability

Machine-independent.

Limitations

A separation is not a correspondence: two contours can be close everywhere and still differ in where along the curve a feature sits. Time grows as the product of the two point counts; memory is bounded by working through the vertices in blocks.

Provenance

  1. Point-to-segment distance and the symmetric Hausdorff distance are standard computational geometry.

computed_diamagnetism_from_phi

Machine-independentConvention-sensitive source
computed_diamagnetism_from_phi(phi_dia_comp, B_t0, R_0, volume, B_ref)
Show source vaft/process/equilibrium.py, lines 2291–2353
def computed_diamagnetism_from_phi(
    phi_dia_comp: float,
    B_t0: float,
    R_0: float,
    volume: float,
    B_ref: float,
) -> float:
    """EFIT-style diamagnetism from an already-computed diamagnetic flux.

    Parameters
    ----------
    phi_dia_comp : float
        The computed diamagnetic flux [Wb].
    B_t0 : float
        Vacuum toroidal field at the reference radius [T].
    R_0 : float
        Reference major radius [m].
    volume : float
        Plasma volume, strictly positive [m^3].
    B_ref : float
        Reference field the result is normalized by, normally the boundary-average
        poloidal field, strictly positive [T].

    Returns
    -------
    float
        The EFIT-style diamagnetism [-].

    Raises
    ------
    ValueError
        The volume or the reference field is not positive.

    Convention
    ----------
    ``4*pi*B_t0*R_0/(V*B_ref^2)`` times the flux. The arithmetic itself lives in
    :func:`vaft.formula.equilibrium.virial_muihat_from_Bt_R0_dphi`; this wrapper
    adds only the positivity precondition its callers rely on. Keeping one
    definition matters because the validation layer compares a diamagnetism from
    this path against one from the formula path, and a correction applied to only
    one of them would desynchronize them silently.

    Applicability
    -------------
    Machine-independent.

    Limitations
    -----------
    Takes the flux as given and cannot check that it was computed against the
    same reference radius and field used here.

    Provenance
    ----------
    .. [1] The EFIT ``xmui`` definition, evaluated through
       :func:`vaft.formula.equilibrium.virial_muihat_from_Bt_R0_dphi`.
    """
    if volume <= 0.0 or B_ref <= 0.0:
        raise ValueError("volume and B_ref must be positive.")
    from vaft.formula.equilibrium import virial_muihat_from_Bt_R0_dphi

    return float(
        virial_muihat_from_Bt_R0_dphi(B_t0, R_0, phi_dia_comp, B_ref, volume)
    )

EFIT-style diamagnetism from an already-computed diamagnetic flux.

NameTypeUnitDescription
phi_dia_compfloatWb

The computed diamagnetic flux.

B_t0floatT

Vacuum toroidal field at the reference radius.

R_0floatm

Reference major radius.

volumefloatm^3

Plasma volume, strictly positive.

B_reffloatT

Reference field the result is normalized by, normally the boundary-average poloidal field, strictly positive.

Returns
—float-

The EFIT-style diamagnetism.

Convention

4*pi*B_t0*R_0/(V*B_ref^2) times the flux. The arithmetic itself lives in vaft.formula.equilibrium.virial_muihat_from_Bt_R0_dphi; this wrapper adds only the positivity precondition its callers rely on. Keeping one definition matters because the validation layer compares a diamagnetism from this path against one from the formula path, and a correction applied to only one of them would desynchronize them silently.

Raises

  • ValueError The volume or the reference field is not positive.
Applicability · Limitations · Provenance

Applicability

Machine-independent.

Limitations

Takes the flux as given and cannot check that it was computed against the same reference radius and field used here.

Provenance

  1. The EFIT xmui definition, evaluated through vaft.formula.equilibrium.virial_muihat_from_Bt_R0_dphi.

connection_length_map

Machine-independentConvention-sensitive source
connection_length_map(seed_r, seed_z, b_field, *, wall_r, wall_z, phi0=0.0, dphi=np.float64(0.03490658503988659), max_length_m=150.0)

Connection length of the field line through each of many starting points.

How far a field line runs in both directions before it strikes the wall, which is what decides whether an electron accelerating along it avalanches or is lost. Every line is stepped together rather than one at a time, so a whole grid costs one pass instead of one pass per point.

NameTypeUnitDescription
seed_rnp.ndarraym

Major radius of each starting point.

seed_znp.ndarraym

Height of each starting point, broadcastable against seed_r.

b_fieldcallableT

A function of (R, Z) returning (B_R, B_Z, B_phi) for arrays, normally from make_vacuum_field_interpolator.

wall_rnp.ndarraym

Major radius of the limiting polygon.

wall_znp.ndarraym

Height of the limiting polygon.

phi0float, optionalrad

Starting toroidal angle, the same for every line.

dphifloat, optionalrad

Fixed step in toroidal angle.

max_length_mfloat, optionalm

Path length at which to stop each direction.

Returns
—dict of str to np.ndarraym

length_m the two directions summed, with nan at points outside the wall and wherever a step left the field model – a callable that returned a non-finite field – since nothing is then known about where that line ends; forward_m and backward_m the branches; saturated true where either branch stopped on max_length_m rather than on the wall; outside true where the point itself is not inside the wall. Every array has the shape of seed_r.

Convention

The toroidal angle is the integration variable, as in trace_field_line, so a vanishing toroidal field ends a line rather than slowing it. The length is the sum of both directions from the seed, which is the quantity a Townsend avalanche sees; a caller wanting one branch should read forward_m or backward_m. A saturated line reports max_length_m, not infinity, and saturated is how a consumer tells the difference.

Raises

  • ValueError A non-positive dphi or max_length_m, or seed arrays whose shapes do not match [-].
Processing steps · Defaults · Applicability · Limitations · Provenance

Processing steps

  1. Drop the seeds that are already outside the limiting polygon; they have no connection length and are reported as nan.
  2. Step every surviving line together by fourth-order Runge-Kutta in the toroidal angle, once forward and once backward, retiring a line as it leaves the polygon or reaches max_length_m.
  3. Add the two branches. A line retired on length contributes max_length_m and is flagged rather than being read as a wall hit.

Defaults

The two-degree step is a numerical convenience, chosen by convergence against a half-degree trace on VEST’s vacuum field: the median length moves by 7e-5 and the 95th percentile by 4e-3 of itself. The 150 m limit is an assumed value, long enough that a Lloyd threshold evaluated on it has already saturated and short enough that a line circulating near a null terminates.

Applicability

Machine-independent. The wall polygon and the field are both the caller’s.

Limitations

The wall test is applied once per step, so a line ends at the last point inside the polygon rather than at the true crossing – of order $R\,\mathrm{d}\varphi$, millimetres against the tens of metres the length itself runs to. Fixed step, so accuracy is set by the step alone and there is no error control. A field line that closes on itself never strikes the wall, and this returns max_length_m for it, flagged; distinguishing a closed line from a merely long one is not attempted.

Provenance

  1. The field-line equations in toroidal-angle form, as in trace_field_line, stepped over many seeds at once.
  2. B. Lloyd et al., Nucl. Fusion 31 (1991) 2031, Sec. 2, for the role the connection length plays in the breakdown threshold this feeds.

contour_shape_parameters

Machine-independentConvention-sensitive source
contour_shape_parameters(r_seg, z_seg)
Show source vaft/process/equilibrium.py, lines 2566–2645
def contour_shape_parameters(r_seg: np.ndarray, z_seg: np.ndarray) -> dict[str, float]:
    """Shape parameters of one closed flux-surface contour.

    Parameters
    ----------
    r_seg : array_like
        Major radius of the contour points [m].
    z_seg : array_like
        Height of the contour points [m].

    Returns
    -------
    dict of str to float
        ``volume`` in cubic metres; ``area`` and ``surface`` in square metres;
        ``elongation``, ``triangularity_upper`` and ``triangularity_lower``
        dimensionless; ``r_inboard`` and ``r_outboard`` in metres [-].

    Raises
    ------
    ValueError
        The contour is degenerate, having zero minor radius.

    Convention
    ----------
    Volume is the exact revolution ``pi * closed_integral(R^2 dZ)``, not Pappus's
    approximation, so it is right even for a contour whose centroid is far from
    its geometric centre. Area is the shoelace formula and surface is
    ``closed_integral 2*pi*R dl``.

    Elongation and both triangularities are taken against the geometric centre
    ``(R_out + R_in)/2``, which is what the IMAS data dictionary reports for the
    boundary. Positive triangularity means the extremum of height sits inboard of
    that centre. The extremum's major radius comes from
    :func:`r_at_z_extremum` rather than from the nearest vertex.

    Applicability
    -------------
    Machine-independent.

    Limitations
    -----------
    Assumes one simple closed contour; a self-intersecting or open segment gives
    a signed area that does not mean what the formulas take it to mean. Every
    quantity is only as accurate as the contour's sampling.

    Provenance
    ----------
    .. [1] IMAS data dictionary definitions for the boundary's geometric axis and
       triangularity, which fix the centre these are measured against.
    .. [2] The exact revolution volume is
       :func:`vaft.formula.equilibrium.exact_volume_from_RZ_contour`.
    """
    from vaft.formula.equilibrium import exact_volume_from_RZ_contour

    r_seg = np.asarray(r_seg, dtype=float).reshape(-1)
    z_seg = np.asarray(z_seg, dtype=float).reshape(-1)
    r_min, r_max = float(np.min(r_seg)), float(np.max(r_seg))
    z_min, z_max = float(np.min(z_seg)), float(np.max(z_seg))
    minor = 0.5 * (r_max - r_min)
    r_geo = 0.5 * (r_max + r_min)
    if minor <= 0.0:
        raise ValueError("degenerate contour")
    r_closed = np.r_[r_seg, r_seg[0]]
    z_closed = np.r_[z_seg, z_seg[0]]
    segment_length = np.hypot(np.diff(r_closed), np.diff(z_closed))
    r_mid = 0.5 * (r_closed[1:] + r_closed[:-1])
    return {
        "volume": exact_volume_from_RZ_contour(r_seg, z_seg),
        # Poloidal cross-section area, by the shoelace formula.
        "area": 0.5
        * abs(
            float(np.dot(r_seg, np.roll(z_seg, 1)) - np.dot(z_seg, np.roll(r_seg, 1)))
        ),
        "surface": 2.0 * np.pi * float(np.sum(r_mid * segment_length)),
        "elongation": (z_max - z_min) / (2.0 * minor),
        "triangularity_upper": (r_geo - r_at_z_extremum(r_seg, z_seg, upper=True)) / minor,
        "triangularity_lower": (r_geo - r_at_z_extremum(r_seg, z_seg, upper=False)) / minor,
        "r_inboard": r_min,
        "r_outboard": r_max,
    }

Shape parameters of one closed flux-surface contour.

NameTypeUnitDescription
r_segarray_likem

Major radius of the contour points.

z_segarray_likem

Height of the contour points.

Returns
—dict of str to float-

volume in cubic metres; area and surface in square metres; elongation, triangularity_upper and triangularity_lower dimensionless; r_inboard and r_outboard in metres.

Convention

Volume is the exact revolution pi * closed_integral(R^2 dZ), not Pappus’s approximation, so it is right even for a contour whose centroid is far from its geometric centre. Area is the shoelace formula and surface is closed_integral 2*pi*R dl.

Elongation and both triangularities are taken against the geometric centre (R_out + R_in)/2, which is what the IMAS data dictionary reports for the boundary. Positive triangularity means the extremum of height sits inboard of that centre. The extremum’s major radius comes from r_at_z_extremum rather than from the nearest vertex.

Raises

  • ValueError The contour is degenerate, having zero minor radius.
Applicability · Limitations · Provenance

Applicability

Machine-independent.

Limitations

Assumes one simple closed contour; a self-intersecting or open segment gives a signed area that does not mean what the formulas take it to mean. Every quantity is only as accurate as the contour’s sampling.

Provenance

  1. IMAS data dictionary definitions for the boundary’s geometric axis and triangularity, which fix the centre these are measured against.
  2. The exact revolution volume is vaft.formula.equilibrium.exact_volume_from_RZ_contour.

contour_shaping_observables

Machine-independentConvention-sensitive source
contour_shaping_observables(r_seg, z_seg)

Model-independent shaping observables of one closed contour: indentation and asymmetry.

Measurable from any valid boundary, whatever model – Miller, Fourier or none – describes it, so a model coefficient such as MillerSurface.indentation can be checked against a geometric fact rather than against another model.

NameTypeUnitDescription
r_segarray_likem

Major radius of the contour points.

z_segarray_likem

Height of the contour points.

Returns
—dict of str to float or bool-

indentation_depth, the largest distance from an inboard point to the convex hull of the contour, in metres; normalized_indentation_depth, that over the minor radius [-]; inboard_concave, whether that normalized depth exceeds 1e-3 [-]; and up_down_asymmetry, the Hausdorff distance between the contour and its mirror image about its mid-height, over the minor radius, zero for an up-down symmetric contour.

Convention

Inboard means major radius below the geometric centre (R_out + R_in)/2; the minor radius is (R_out - R_in)/2 and the mirror plane is Z = (Z_max + Z_min)/2, the same references as contour_shape_parameters. Distances are measured against the contour polyline resampled to 4096 points of equal arc length.

Raises

  • ValueError Fewer than four points, or a contour with zero minor radius.
Applicability · Limitations · Provenance

Applicability

Machine-independent.

Limitations

The 1e-3 concavity threshold sits above the chord error of a smooth contour sampled with about a hundred points; a coarser contour can read a small spurious depth. Outboard concavity is not reported.

Provenance

  1. The observables proposed in #942, section 7, kept separate from the coefficients of any one parameterization.

convert_cocos

Machine-independentConvention-sensitive source
convert_cocos(equilibrium, target_cocos)

Convert an equilibrium between two explicitly known COCOS conventions.

The only COCOS transform in the package. vaft.process.cocos identifies and validates conventions but never changes data; this is where a sign or a factor of 2*pi is actually applied.

NameTypeUnitDescription
equilibriumEquilibriumData-

The record to convert. Its source convention must be known: either declared, or identified down to exactly one candidate.

target_cocosint-

The convention to convert to, 1 to 18.

Returns
—EquilibriumData-

A copy with every convention-dependent field transformed and its convention set to target_cocos.

Convention

Direction is source to target, and both must be in 1 to 18; 9 and 10 do not exist. The flux factor is where a sign flip and a factor of 2*pi land together. Pressure and the magnetic axis are deliberately untouched: they are physical quantities that no convention changes. The pressure gradient scales by its own factor rather than by the flux factor, because a derivative against flux scales by the inverse of what flux does.

Raises

  • ValueError target_cocos is outside 1 to 18, or the source convention is unknown or ambiguous.
Processing steps · Applicability · Limitations · Provenance

Processing steps

  1. Resolve the source convention from the declared index, or from a single surviving identification candidate.
  2. Take the per-quantity factors for the pair from OMAS.
  3. Scale the flux quantities, the poloidal current, the safety factor, the two source gradients, the plasma current and the vacuum field, each by its own factor.
  4. Record the target index on the returned convention.

Applicability

Machine-independent.

Limitations

Refuses rather than guesses when the source convention is ambiguous, which is the common case for a file that declares nothing and whose flux exponent could not be identified. The identified-candidate list carried on the result is shifted on the assumption that every candidate shares the source’s storage family, which is not guaranteed and can produce an out-of-range index; tracked in #601.

Provenance

  1. Sauter and Medvedev (2013) for the conventions; the per-quantity factors are omas.cocos_transform.

current_centroid

Machine-independentConvention-sensitive source
current_centroid(j_tor, r, z, *, weights=None)
Show source vaft/process/_equilibrium_moments.py, lines 44–95
def current_centroid(j_tor: Any, r: Any, z: Any, *, weights: Any = None) -> tuple[float, float, float]:
    """Total toroidal current and its centroid, from a gridded current density.

    The zeroth and first current moments: ``I_p = integral J_phi dA`` and
    ``(R_c, Z_c) = (1/I_p) integral (R, Z) J_phi dA``.  A single filament at
    the centroid carrying ``I_p`` is the lowest-order representation of the
    distribution.

    Parameters
    ----------
    j_tor : array_like
        Toroidal current density, indexed ``(R, Z)`` [A/m^2].
    r : array_like
        Major-radius grid axis [m].
    z : array_like
        Height grid axis [m].
    weights : array_like, optional
        Fraction of each cell to integrate, such as
        :func:`fractional_cell_weights_from_boundary` gives; all ones when not
        given [-].

    Returns
    -------
    tuple of float
        ``(I_p, R_c, Z_c)`` in ampere, metre and metre [-].

    Raises
    ------
    ValueError
        The shapes disagree, or the integrated current is zero, which leaves
        the centroid undefined.

    Convention
    ----------
    The area element is the cell area ``dR dZ`` times its weight, not
    ``2 pi R dR dZ``: these are moments of the current through the poloidal
    cross-section, whose zeroth moment is the plasma current.  The sign of
    ``I_p`` is that of ``j_tor``; the centroid does not depend on it.

    Applicability
    -------------
    Machine-independent.

    Provenance
    ----------
    .. [1] Definition of the current moments; see issue #943.
    """
    element, rm, zm, _ = _grid(j_tor, r, z, weights)
    total = float(np.sum(element))
    if total == 0.0 or not np.isfinite(total):
        raise ValueError("the integrated current is zero or not finite; the centroid is undefined")
    return total, float(np.sum(rm*element)/total), float(np.sum(zm*element)/total)

Total toroidal current and its centroid, from a gridded current density.

The zeroth and first current moments: I_p = integral J_phi dA and (R_c, Z_c) = (1/I_p) integral (R, Z) J_phi dA. A single filament at the centroid carrying I_p is the lowest-order representation of the distribution.

NameTypeUnitDescription
j_torarray_likeA/m^2

Toroidal current density, indexed (R, Z).

rarray_likem

Major-radius grid axis.

zarray_likem

Height grid axis.

weightsarray_like, optional-

Fraction of each cell to integrate, such as fractional_cell_weights_from_boundary gives; all ones when not given.

Returns
—tuple of float-

(I_p, R_c, Z_c) in ampere, metre and metre.

Convention

The area element is the cell area dR dZ times its weight, not 2 pi R dR dZ: these are moments of the current through the poloidal cross-section, whose zeroth moment is the plasma current. The sign of I_p is that of j_tor; the centroid does not depend on it.

Raises

  • ValueError The shapes disagree, or the integrated current is zero, which leaves the centroid undefined.
Applicability · Provenance

Applicability

Machine-independent.

Provenance

  1. Definition of the current moments; see issue #943.

current_covariance

Machine-independentConvention-sensitive source
current_covariance(j_tor, r, z, *, weights=None)
Show source vaft/process/_equilibrium_moments.py, lines 167–212
def current_covariance(j_tor: Any, r: Any, z: Any, *, weights: Any = None) -> np.ndarray:
    """Second central moment tensor of a gridded current density.

    ``[[mu_20, mu_11], [mu_11, mu_02]]`` about the current centroid,
    normalized by the total current.  Its eigenvalues are the squared
    principal widths of the current distribution and its eigenvectors their
    directions.

    Parameters
    ----------
    j_tor : array_like
        Toroidal current density, indexed ``(R, Z)`` [A/m^2].
    r : array_like
        Major-radius grid axis [m].
    z : array_like
        Height grid axis [m].
    weights : array_like, optional
        Fraction of each cell to integrate [-].

    Returns
    -------
    np.ndarray
        The symmetric 2 x 2 tensor [m^2].

    Raises
    ------
    ValueError
        As for :func:`current_moment`.

    Convention
    ----------
    Central and normalized, as :func:`current_moment` with its defaults.  A
    width of the *current*, not of the plasma boundary: a peaked current
    profile has a smaller covariance than its LCFS suggests.

    Applicability
    -------------
    Machine-independent.

    Provenance
    ----------
    .. [1] Definition of the current moments; see issue #943.
    """
    _, r0, z0 = current_centroid(j_tor, r, z, weights=weights)
    mu = {pq: current_moment(j_tor, r, z, *pq, weights=weights, center=(r0, z0)) for pq in ((2, 0), (1, 1), (0, 2))}
    return np.array([[mu[(2, 0)], mu[(1, 1)]], [mu[(1, 1)], mu[(0, 2)]]], dtype=float)

Second central moment tensor of a gridded current density.

[[mu_20, mu_11], [mu_11, mu_02]] about the current centroid, normalized by the total current. Its eigenvalues are the squared principal widths of the current distribution and its eigenvectors their directions.

NameTypeUnitDescription
j_torarray_likeA/m^2

Toroidal current density, indexed (R, Z).

rarray_likem

Major-radius grid axis.

zarray_likem

Height grid axis.

weightsarray_like, optional-

Fraction of each cell to integrate.

Returns
—np.ndarraym^2

The symmetric 2 x 2 tensor.

Convention

Central and normalized, as current_moment with its defaults. A width of the current, not of the plasma boundary: a peaked current profile has a smaller covariance than its LCFS suggests.

Raises

  • ValueError As for current_moment.
Applicability · Provenance

Applicability

Machine-independent.

Provenance

  1. Definition of the current moments; see issue #943.

current_moment

Machine-independentConvention-sensitive source
current_moment(j_tor, r, z, p, q, *, weights=None, center=None, normalize=True)
Show source vaft/process/_equilibrium_moments.py, lines 98–164
def current_moment(
    j_tor: Any, r: Any, z: Any, p: int, q: int, *, weights: Any = None,
    center: tuple[float, float] | None = None, normalize: bool = True,
) -> float:
    """One moment ``integral (R - R_0)**p (Z - Z_0)**q J_phi dA`` of a gridded current density.

    Parameters
    ----------
    j_tor : array_like
        Toroidal current density, indexed ``(R, Z)`` [A/m^2].
    r : array_like
        Major-radius grid axis [m].
    z : array_like
        Height grid axis [m].
    p : int
        Order in R, non-negative [-].
    q : int
        Order in Z, non-negative [-].
    weights : array_like, optional
        Fraction of each cell to integrate [-].
    center : tuple of float, optional
        Reference point ``(R_0, Z_0)``; the current centroid when not given,
        which makes the moment a central one [m].
    normalize : bool, optional
        Divide by the total current, giving m**(p+q) instead of A m**(p+q) [-].

    Returns
    -------
    float
        The moment, in m**(p+q) when normalized and A m**(p+q) otherwise [-].

    Raises
    ------
    ValueError
        A negative order, disagreeing shapes, or a zero total current where
        the centroid or the normalization needs it.

    Convention
    ----------
    ``center=None`` means *central*: about ``(R_c, Z_c)`` from
    :func:`current_centroid`, so the first central moments vanish.  Pass
    ``center=(0.0, 0.0)`` for raw moments about the origin of the (R, Z)
    plane.  The area element is ``dR dZ`` times the weight.

    Applicability
    -------------
    Machine-independent.

    Provenance
    ----------
    .. [1] Definition of the current moments; see issue #943.
    """
    p, q = int(p), int(q)
    if p < 0 or q < 0:
        raise ValueError("moment orders must be non-negative")
    element, rm, zm, _ = _grid(j_tor, r, z, weights)
    if center is None:
        _, r0, z0 = current_centroid(j_tor, r, z, weights=weights)
    else:
        r0, z0 = map(float, center)
    value = float(np.sum((rm - r0)**p*(zm - z0)**q*element))
    if not normalize:
        return value
    total = float(np.sum(element))
    if total == 0.0 or not np.isfinite(total):
        raise ValueError("the integrated current is zero or not finite; cannot normalize")
    return value/total

One moment integral (R - R_0)**p (Z - Z_0)**q J_phi dA of a gridded current density.

NameTypeUnitDescription
j_torarray_likeA/m^2

Toroidal current density, indexed (R, Z).

rarray_likem

Major-radius grid axis.

zarray_likem

Height grid axis.

pint-

Order in R, non-negative.

qint-

Order in Z, non-negative.

weightsarray_like, optional-

Fraction of each cell to integrate.

centertuple of float, optionalm

Reference point (R_0, Z_0); the current centroid when not given, which makes the moment a central one.

normalizebool, optional-

Divide by the total current, giving m(p+q) instead of A m(p+q).

Returns
—float-

The moment, in m(p+q) when normalized and A m(p+q) otherwise.

Convention

center=None means central: about (R_c, Z_c) from current_centroid, so the first central moments vanish. Pass center=(0.0, 0.0) for raw moments about the origin of the (R, Z) plane. The area element is dR dZ times the weight.

Raises

  • ValueError A negative order, disagreeing shapes, or a zero total current where the centroid or the normalization needs it.
Applicability · Provenance

Applicability

Machine-independent.

Provenance

  1. Definition of the current moments; see issue #943.

derive_boundary_representation

Machine-independentConvention-sensitive source
derive_boundary_representation(equilibrium, *, gap_angles=None, flux_tolerance=None, fourier_modes=16)

Classify the plasma boundary and derive its geometric diagnostics.

Topology is decided from the flux map alone. Stationary points of psi are located and split into O-points and saddles by the sign of the Hessian determinant; a saddle counts as a physical X-point only when it is relevant to the boundary, meaning its flux agrees with the boundary flux to within what the grid resolves and the confined region’s level set reaches it.

NameTypeUnitDescription
equilibriumEquilibriumData, ODS, GEQDSK or path-

Adapted through as_equilibrium; needs a psi map, and a wall or limiter to classify a limited plasma.

gap_anglesmapping of str to float, optionalrad

Named directions from the geometric centre along which to measure the plasma-wall gap.

flux_tolerancefloat, optional-

Overrides the normalized-flux window of the saddle test with one value for every saddle.

fourier_modesint, optional-

How many harmonics to fit to the boundary contour.

Returns
—BoundaryRepresentation-

The topology and the reason for it; the X-points with their active flags; the strike points with their flux expansion and incidence; the named gaps; the outer-midplane separation between the first two separatrices; and the Fourier coefficients with their fit error. Distances in metres, angles in radians, everything else dimensionless.

Convention

At least one boundary-relevant X-point makes the equilibrium diverted; none, together with a last closed flux surface in contact with the wall, makes it limited. Anything that cannot be settled – a grid-clipped confined region, no wall to test against, or a surface bounded by neither – is reported ambiguous with a reason rather than forced into one of the two.

Defaults · Applicability · Limitations · Provenance

Defaults

Leaving flux_tolerance at None is the justified choice, and an override is a numerical convenience only: each saddle otherwise gets its own window from its Hessian curvature and the grid spacing, because psi is stationary there and a one-cell position error perturbs its flux only at second order. A single global tolerance cannot be right for saddles of different sharpness. fourier_modes = 16 is likewise a numerical convenience, enough harmonics to represent a tokamak boundary without fitting the contour’s own sampling. The coefficients are fit_fourier_surface’s, one convention for every contour (#945). Before #945 the phase origin was the sampled point of largest R; it is now the phase of R’s first harmonic, so individual cosine and sine values differ from older results while the per-harmonic amplitudes and the reconstruction error do not.

Applicability

Machine-independent. The gap directions are where a machine’s own geometry enters, and they are supplied by the caller.

Limitations

Topology needs a wall or limiter to distinguish limited from ambiguous; without one, a plasma with no X-point is ambiguous rather than limited. Strike points and gaps require the corresponding geometry and are simply absent otherwise. The Fourier fit assumes a single closed contour.

Provenance

  1. The Hessian classification of stationary points of psi is the standard O-point and X-point test; the boundary-relevance condition is what distinguishes a physical X-point from a saddle elsewhere in the domain.

derive_current_moments

Machine-independentConvention-sensitive source
derive_current_moments(equilibrium, *, max_order=4, j_tor=None)

Reduce an equilibrium’s toroidal current density to its low-order moments.

Between a single filament and the full J_phi(R, Z): the total current, its centroid, and every normalized central moment up to max_order, integrated over the plasma with fractional LCFS cells. Two equilibria on different grids can then be compared moment by moment.

NameTypeUnitDescription
equilibriumEquilibriumData, ODS, GEQDSK or path-

Adapted through as_equilibrium; needs a psi map and an LCFS.

max_orderint, optional-

Highest total order p + q of the central moments, at least two.

j_torarray_like, optionalA/m^2

A canonical toroidal current density on the record’s grid, indexed (R, Z), such as an ODS profiles_2d.j_tor; derived from the record’s p' and FF' when not given.

Returns
—CurrentMomentRepresentation-

Total current, centroid, the central moments, the order, where the current density came from, and the derivation record.

Convention

The derived current density is the same expression, and the same resolved psi convention, as vaft.omas.update.update_equilibrium_profiles_2d_j_tor uses for an ODS, resolved the same way (the 2*pi exponent from psi_per_radian, sigma_Bp from agreeing candidates, else +1), so the two paths agree; it is never a dpsi/dR pseudo-current. Moments are central about the current centroid and normalized by the total current (m**(p+q)); the area element is dR dZ, so the zeroth moment is the plasma current.

Raises

  • ValueError max_order below two, no psi map or LCFS, a record with neither a j_tor nor the convention and profiles to derive one, or zero integrated current.
Processing steps · Defaults · Applicability · Limitations · Provenance

Processing steps

  1. Take j_tor as given, or evaluate the Grad-Shafranov current J_phi = -sigma_Bp (2 pi)**e_Bp (R p'(psi) + FF'(psi)/(mu0 R)) from the record’s flux-function profiles in its declared COCOS.
  2. Weight each cell by the fraction of it inside the LCFS.
  3. Integrate the total current and the centroid, then every central moment with 2 <= p + q <= max_order.

Defaults

max_order = 4 is a numerical convenience: second order gives the widths and tilt, third the skewness and up-down asymmetry, fourth the first shape beyond them; higher orders grow more sensitive to the edge.

Applicability

Machine-independent.

Limitations

An ODS’s own profiles_2d.j_tor is not read automatically – as_equilibrium does not carry it – so pass it as j_tor; the derived density agrees with update_equilibrium_profiles_2d_j_tor to about 1e-10 inside the LCFS. NaN cells of a supplied j_tor that the fractional weights still count are filled from p'/FF' and the source label says how many. The integrated current is checked against the record’s ip, and a disagreement beyond five percent – typically a factor 2*pi or -1 from a wrongly declared convention – is warned about, not corrected. Current outside the LCFS, such as a halo or a vessel current, is excluded by the weighting. The Shafranov (magnetic) moments of #943 are a separate representation and are not computed here.

Provenance

  1. Definition of the current moments; see issue #943.
  2. O. Sauter and S. Yu. Medvedev, Tokamak coordinate conventions: COCOS, Comput. Phys. Commun. 184, 293 (2013), Eq. 12, for the sign and 2*pi of the Grad-Shafranov current.

derive_global_descriptors

Machine-independentConvention-sensitive source
derive_global_descriptors(equilibrium, *, rational_q=(1.0, 1.5, 2.0, 3.0))

Global scalar descriptors of one equilibrium, each with its derivation record.

Geometry from the boundary, energy from a grid quadrature, the safety factor from the stored profile, and the Shafranov virial closures from the boundary integrals. Every quantity is a vaft.data.equilibrium.DerivedValue carrying the method it came from, the fields it read, and the convention it was computed under, so a number that could not be formed says why instead of being absent.

NameTypeUnitDescription
equilibriumEquilibriumData, ODS, GEQDSK or path-

Adapted through as_equilibrium. What can be derived depends on which fields it carries; nothing is required.

rational_qsequence of float, optional-

Safety-factor values whose surfaces to locate, reported as the normalized radii where q crosses each.

Returns
—GlobalEquilibriumDescriptors-

A mapping of name to vaft.data.equilibrium.DerivedValue plus the vaft.data.equilibrium.ValidationReport from the precondition check. Shape and radii in metres, areas in square metres, volume in cubic metres, current in amperes, field in tesla, energies in joules, betas and the safety factor and the virial terms dimensionless.

Convention

Shape descriptors use the geometric centre (R_out+R_in)/2, which is what the IMAS data dictionary reports for the boundary’s geometric axis and triangularity. The area centroid is a different quantity, used only where Pappus’s theorem needs it for the volume, and at low aspect ratio the two sit centimetres apart. The poloidal field is computed through the equilibrium’s own declared COCOS and per-radian flag, so a wrongly declared convention propagates into every field-derived quantity here. The magnetic shear is taken against the poloidal radius. The normalized beta follows the Troyon convention beta_N = 100 * beta_t * a * |B_t0| / |I_p| (with a in metres, B_t0 in tesla, and I_p in megamperes); its published unit is "1" (dimensionless) conforming to IMAS global_quantities.beta_normal, with the conventional % * m * T / MA scaling factor folded into the value and recorded in the derivation quality metadata.

Processing steps · Applicability · Limitations · Provenance

Processing steps

  1. Check preconditions for the global requirement set and keep the report.
  2. Boundary geometry: major and minor radius, elongation, upper and lower triangularity, volume and surface area from the last closed flux surface.
  3. Energy: integrate pressure over the plasma volume by grid quadrature for the pressure integral and the thermal energy.
  4. Betas: toroidal from the volume-average pressure and the vacuum field, normalized from that and the current, poloidal from the boundary-average poloidal field.
  5. Virial: the Shafranov boundary integrals and the internal inductance from the Lao closure.
  6. Safety factor: on-axis, at 95 percent of the normalized flux, at the edge, the magnetic shear, and the radius of each requested rational surface.

Applicability

Machine-independent.

Limitations

Every quantity degrades independently: a missing pressure profile costs the energies and the betas but not the geometry. A failure inside the pressure integration is captured as a warning on the returned report rather than raised. The virial internal inductance is ill-conditioned as its coefficient approaches one.

Provenance

  1. IMAS data dictionary definitions for the boundary geometric axis, triangularity, and the global quantities this mirrors.
  2. Lao’s virial closure for the internal inductance from the Shafranov boundary integrals, evaluated by vaft.formula.equilibrium.virial_li_from_S_alpha_rt.
  3. The boundary integrals themselves come from vaft.process.equilibrium.shafranov_integrals and vaft.process.equilibrium.efit_virial_volume_integrals, the EFIT-style weighted forms.

derive_radial_coordinates

Machine-independentConvention-sensitive source
derive_radial_coordinates(equilibrium)

The three normalized radial coordinates, each with its derivation record.

psi_norm, rho_pol_norm and rho_tor_norm are three different coordinates and are not interchangeable; rho_tor_norm equals sqrt(psi_norm) only for a flat-q cylinder. A coordinate that cannot be derived is returned as an explicit unavailable record naming the reason, so a caller can refuse rather than substitute a different radius.

NameTypeUnitDescription
equilibriumEquilibriumData, ODS, GEQDSK or path-

Adapted through as_equilibrium. Needs psi_1d and distinct axis and boundary flux values; rho_tor_n additionally needs q.

Returns
—Mapping of str to DerivedValue-

Keys psi_n, rho_pol_n and rho_tor_n, each dimensionless and on the equilibrium’s own psi_1d grid. An entry whose value is None carries a reason instead.

Convention

All three are dimensionless and run 0 on axis to 1 at the boundary. rho_tor_n is COCOS-independent: it is a ratio of two integrals of the same q over the same variable, so the factor of 2*pi and the sign of psi both cancel. psi_n is likewise invariant under the storage choice, being normalized by its own span. This function therefore needs no COCOS and imposes none.

Processing steps · Applicability · Limitations · Provenance

Processing steps

  1. psi_n = (psi_1d - psi_axis)/(psi_boundary - psi_axis).
  2. rho_pol_n = sqrt(psi_n), by definition of that coordinate.
  3. rho_tor_n = sqrt(Phi/Phi_boundary) with Phi the cumulative trapezoidal integral of q over the normalized flux, returned only if q is finite, of matching length, and produces a monotonic result.

Applicability

Machine-independent.

Limitations

rho_tor_n is unavailable when q is missing, non-finite, of the wrong length, or gives a non-monotonic toroidal flux, and when psi_n is not strictly monotonic. The integral is trapezoidal on the stored grid, so a coarse or axis-clustered q limits its accuracy near the axis. The published definition string names dpsi where the integration variable is psi_n; the value is unaffected and the wording is tracked in #602.

Provenance

  1. The toroidal flux definition Phi = int q dpsi and its normalized radius, as used by the IMAS data dictionary for rho_tor_norm.
  2. vaft.process.profile.core_profiles consumes this to evaluate kinetic fits on the equilibrium grid, and refuses by name when the record here is unavailable (issue #420).

efit_virial_volume_integrals

Machine-independentConvention-sensitive source
efit_virial_volume_integrals(R_grid, Z_grid, R_bdry, Z_bdry, B_R_grid, B_Z_grid, p_tot_grid=None, B_phi_grid=None, B_phi_vac_grid=None, F_grid=None, F_boundary=None, cell_weights=None)

EFIT-style weighted volume integrals for the virial closures.

NameTypeUnitDescription
R_gridarray_likem

Major-radius grid, as an axis or a mesh.

Z_gridarray_likem

Height grid, as an axis or a mesh.

R_bdryarray_likem

Major radius of the boundary contour.

Z_bdryarray_likem

Height of the boundary contour.

B_R_gridarray_likeT

Major-radius field component on the grid.

B_Z_gridarray_likeT

Height field component on the grid.

p_tot_gridarray_like, optionalPa

Total pressure on the grid.

B_phi_gridarray_like, optionalT

Plasma toroidal field on the grid.

B_phi_vac_gridarray_like, optionalT

Vacuum toroidal field on the same grid.

F_gridarray_like, optionalT m

Poloidal current function on the grid.

F_boundaryfloat, optionalT m

That function at the boundary.

cell_weightsarray_like, optional-

Per-cell plasma area fractions; computed from the boundary when not given

Returns
—dict of str to float-

alpha and rt_denominator_ratio dimensionless, phi_dia_comp in weber, dV and volume in cubic metres, b_p_squared in tesla squared, and rt in metres.

Convention

Weighted by fractional cell area rather than a per-cell mask, which is what makes these the EFIT-style forms rather than a plain grid sum. Like shafranov_integrals, the COCOS is inherited from the field arrays passed in and is neither declared nor checked here.

Applicability · Limitations · Provenance

Applicability

Machine-independent.

Limitations

The reference radius is guarded against a near-singular denominator, so a degenerate geometry returns a finite but meaningless value rather than diverging. Every optional input governs one output: omit the pressure and the pressure-dependent terms are simply absent.

Provenance

  1. EFIT’s weighted volume integrals, with the fractional cell weights standing in for its externally supplied weight map; see fractional_cell_weights_from_boundary.

ejiri_mirror_geometry

Machine-independentConvention-sensitive source
ejiri_mirror_geometry(r_start, b_field, *, wall_r, wall_z, z_fit=None, dphi=np.float64(0.017453292519943295), max_length_m=150.0)

Ejiri mirror-confinement proxy for one magnetic snapshot.

Traces the field line through $(R_S, 0)$ and reads off the four lengths the Ejiri low-energy orbit model needs – the starting radius, the inboard limiter, the curvature radius and the vertical extent over which the line keeps raising $|B|$ – then evaluates the boundary slope $\alpha$ and the geometry factor $F_3$.

NameTypeUnitDescription
r_startfloatm

Major radius the electron starts at, on the midplane.

b_fieldcallableT

A function of (R, Z) returning (B_R, B_Z, B_phi), normally from make_vacuum_field_interpolator.

wall_rnp.ndarraym

Major radius of the limiting polygon.

wall_znp.ndarraym

Height of the limiting polygon.

z_fitfloat, optionalm

Half-height of the window for the diagnostic local parabola fit; default a quarter of z_max.

dphifloat, optionalrad

Fixed step in toroidal angle for the trace.

max_length_mfloat, optionalm

Path length at which to stop each branch.

Returns
—dict of str to Any-

r_start, r_inboard_limiter, curvature_radius and z_max in metres; alpha and f3 dimensionless; mirror true when the line dips inward before it ends; saturated true when a branch stopped on max_length_m rather than on the wall or a turning point; binding_branch naming the weaker mirror; curvature_radius_local the local parabola fit in metres, for comparison only; per-branch z_max_upper, z_max_lower, r_mirror_upper, r_mirror_lower in metres with reason_upper and reason_lower; and the two trace_field_line branches as trace_upper and trace_lower.

Convention

$R_C$ is the secant through the mirror point, not a local fit. Ejiri’s curvature term needs $R_C$ only through $Z_{\max}^2/2R_C = R_S - R_m$, so defining $R_C$ that way makes the term exactly $1/\sqrt{M - 1}$ for the line’s true mirror ratio $M = R_S/R_m$ – the inboard term with the limiter replaced by the line’s own mirror point – whatever shape the line has. On an exact parabola it equals the parabola’s $R_C$. A local fit, which is what the model’s derivation suggests, is not stable on a real field: on the same VEST case it gives $R_C$ from 0.07 to 0.34 m as the window widens from a tenth of $Z_{\max}$ to all of it, and had it fed $\alpha$, $F_3$ would have run from 0.99 to 0.62 on that choice alone. The secant is 0.52 m for every window. curvature_radius_local still reports the fit, so how far the line is from Ejiri’s parabola stays visible.

The mirror point is the global minimum of $R$ along the branch, not the first local one. An escaping electron has to pass the largest field on its way to the wall, wherever it is. The distinction is not academic: at the breakdown onset of VEST’s packaged shot the line through the electron-cyclotron resonance is tilted at the midplane, so on one side $R$ rises for a few steps before dipping to 0.60 m. A first-local-minimum rule gives up on that side, reads the wall end at 0.76 m as the mirror point, and reports no confinement at all.

The weaker mirror decides. An electron bouncing between the two mirror points escapes through the one with the smaller field rise, which is the one at larger $R$, not necessarily the one at smaller $|Z|$.

No inward dip means nothing is confined, not an error. When $R_m \ge R_S$ the line is straight or bows outward, $|B|$ does not rise away from the midplane, and curvature traps nothing: this returns $\alpha = \infty$, $F_3 = 0$ and mirror false without calling the formulas, which reject a non-positive curvature radius.

The mirror ratio is read in $R$, which assumes $|B| \propto 1/R$: true when the toroidal field dominates, as it does in a pre-breakdown vacuum field.

Raises

  • ValueError A start point outside the wall, no wall crossing of the midplane inboard of it, or a non-positive z_fit [-].
Processing steps · Defaults · Applicability · Limitations · Provenance

Processing steps

  1. Trace the line through $(R_S, 0)$ forward and backward against the wall, and label the branch that rises the upper one.
  2. $R_{\mathrm{LIN}}$: the wall polygon’s midplane crossing nearest the start on its inboard side.
  3. Mirror point of each branch: the smallest $R$ the branch reaches before it ends, excluding the seed – the strongest field an electron escaping that way has to pass. Record $|Z|$ and $R$ there.
  4. Keep the weaker mirror: the branch whose mirror point sits at the larger $R$, so the smaller field rise. Its $|Z|$ is $Z_{\max}$ and its $R$ is $R_m$. If $R_m \ge R_S$ the line never dips inward.
  5. $R_C = Z_{\max}^2 / \bigl(2(R_S - R_m)\bigr)$, the parabola through the start and the mirror point, then $\alpha$ and $F_3$ from vaft.formula.startup.ejiri_mirror_alpha_from_R_S_R_LIN_R_C_Z_max and vaft.formula.startup.ejiri_f3_from_alpha.
  6. For comparison, fit $R - R_S = c_1 Z + c_2 Z^2$ over $|Z| \le$ z_fit and report $-1/(2c_2)$ as curvature_radius_local.

Defaults

The 150 m limit is a numerical convenience matching connection_length_map, and it is where the result converges on VEST: at the breakdown onset of the packaged shot, from the electron-cyclotron resonance, 50 m stops both branches short and gives $Z_{\max} = 0.13$ m and $F_3 = 0.38$, while 150, 500 and 1500 m all give $0.30$ m and $0.51$. The quarter-$Z_{\max}$ window and the one-degree step are numerical conveniences: at 2, 1, 0.5 and 0.25 degrees $F_3$ agrees to six figures and $R_C$ and $Z_{\max}$ to four.

Applicability

Machine-independent. The wall polygon and the field are both the caller’s, and so is r_start – normally the electron-cyclotron resonance, from vaft.formula.startup.electron_cyclotron_resonance_radius.

Limitations

An Ejiri-inspired geometric proxy, not the numerical orbit boundary: it says nothing about EC power, collisions or breakdown itself. A branch whose first step already leaves the wall is read as an immediate loss on that side, so a start point within one step of the wall reports no confinement – shorten dphi if that is not the answer wanted. A branch that stops on max_length_m sets saturated and raises a RuntimeWarning: that result is not converged and is not a bound, since a longer trace can move both the mirror point and the branch that binds.

Provenance

  1. A. Ejiri and Y. Takase, Nucl. Fusion 47 (2007) 403, Sec. 3, for the orbit-boundary slope and the geometry factor.
  2. The trace is trace_field_line, and the two relations are the vaft.formula.startup kernels named in the processing steps.

equilibrium_field_on_grid

Machine-independentConvention-sensitive source
equilibrium_field_on_grid(R_grid_1d, Z_grid_1d=None, psi_grid=None, psi_1d=None, f_1d=None, cocos=None, *, psi_per_radian=None)

(B_R, B_Z, B_phi) on the whole (R, Z) grid, each (nR, nZ).

The vectorised twin of make_equilibrium_field_interpolator, for a caller that wants the field everywhere rather than at a point: the same bicubic psi spline, the same Sauter Eq. 20 prefactor, and the same F(psi)/R with F clipped to the profile’s own range outside the confined region. Evaluating the point interpolator over a 129x129 grid would be sixteen thousand Python calls; this is one spline evaluation.

Called as equilibrium_field_on_grid(equilibrium) with an vaft.data.equilibrium.EquilibriumData, the grid, profiles and flux convention all come from the record, which is the form to prefer; the record is only as right as its convention, which nothing checks against the arrays.

NameTypeUnitDescription
R_grid_1darray_like or EquilibriumData-

Major-radius grid axis [m], or the whole equilibrium record, in which case every other argument must be left unset.

Z_grid_1darray_like, optionalm

Height grid axis; required with arrays.

psi_gridarray_like, optionalWb or Wb/rad

Poloidal flux on (len(R), len(Z)), in the convention cocos describes.

psi_1darray_like, optionalsame psi unit

profiles_1d.psi.

f_1darray_like, optionalT m

profiles_1d.f.

cocosint or None, optional-

COCOS index of psi_grid.

psi_per_radianbool or None, optional-

Storage family of the flux when the index is not known: False for weber, True for weber per radian.

Returns
—tuple of numpy.ndarrayT

(B_R, B_Z, B_phi), each shaped (len(R), len(Z)).

Convention

The same prefactor as make_equilibrium_field_interpolator, per Sauter Eq. 20: B_R = k (1/R) dpsi/dZ and B_Z = -k (1/R) dpsi/dR with k = sigma_RphiZ * sigma_Bp / (2*pi)**e_Bp, evaluated by vaft.formula.equilibrium.poloidal_field_factor from cocos or, failing that, psi_per_radian. A record supplies both from its vaft.data.equilibrium.EquilibriumData.convention. With neither, the weber-per-radian k = -1 is assumed, with a warning. The toroidal field is the poloidal current function over the major radius and inherits the sign of f_1d. The flux map is indexed major radius first.

Raises

  • ValueError The flux map is not shaped to the two grid axes, the two profile arrays have different lengths, the record lacks a field, cocos and psi_per_radian contradict each other, or the record’s open COCOS candidates disagree on the field direction.
  • TypeError A record is combined with array arguments, or the array form is missing one.
Applicability · Limitations · Provenance

Applicability

Machine-independent.

Limitations

With neither cocos nor psi_per_radian known, from the arguments or the record, the flux is taken as weber per radian and a UserWarning says so (#1313); a weber flux then gives a field 2*pi too large. With the storage family but no index, the orientation is assumed (k < 0) and the warning says that instead; a record’s open candidates settle it when they agree, and leave it to the same announced assumption when they do not (the packaged g-file, open between COCOS 1 and 2, warns like its arrays do). Outside the confined region the poloidal current function clips to its nearest edge value, the clip-and-interpolate convention psi_to_rz uses, so the toroidal field there is that clipped function over the major radius rather than the true vacuum field. The spline extrapolates beyond the grid, where the field should not be read.

Provenance

  1. Sauter and Medvedev (2013), Eq. 20, for the prefactor.
  2. The point-wise twin make_equilibrium_field_interpolator in this module, which this routine is pinned to by test.

evaluate_fourier_surface

Machine-independentConvention-sensitive source
evaluate_fourier_surface(surface, theta)
Show source vaft/process/_equilibrium_parametric.py, lines 3022–3060
def evaluate_fourier_surface(surface: FourierSurface, theta: Any) -> tuple[np.ndarray, np.ndarray]:
    """Points on a Fourier-represented flux surface at given arc-length angles.

    The forward evaluation of the spectral surface representation;
    :func:`fit_fourier_surface` is its inverse.

    Parameters
    ----------
    surface : FourierSurface
        The cosine and sine coefficients of each harmonic of R and Z [m].
    theta : array_like
        Arc-length angles ``2 pi s / L`` at which to evaluate [rad].

    Returns
    -------
    tuple of np.ndarray
        ``(R, Z)`` on the surface, same shape as *theta* [m].

    Convention
    ----------
    ``R = sum_m r_cos[m] cos(m theta) + r_sin[m] sin(m theta)`` and likewise
    ``Z``, with ``theta`` increasing counter-clockwise in (R, Z) from the
    phase origin of :class:`~vaft.data.equilibrium.FourierSurface`.

    Applicability
    -------------
    Machine-independent.

    Provenance
    ----------
    .. [1] Truncated Fourier series of a closed curve; the arc-length angle
       and phase are VAFT's convention (#945).
    """
    theta = np.asarray(theta, dtype=float)
    m = np.arange(surface.modes+1).reshape((-1,)+(1,)*theta.ndim)
    cos, sin = np.cos(m*theta), np.sin(m*theta)
    r = np.tensordot(surface.r_cos, cos, axes=(0, 0)) + np.tensordot(surface.r_sin, sin, axes=(0, 0))
    z = np.tensordot(surface.z_cos, cos, axes=(0, 0)) + np.tensordot(surface.z_sin, sin, axes=(0, 0))
    return r, z

Points on a Fourier-represented flux surface at given arc-length angles.

The forward evaluation of the spectral surface representation; fit_fourier_surface is its inverse.

NameTypeUnitDescription
surfaceFourierSurfacem

The cosine and sine coefficients of each harmonic of R and Z.

thetaarray_likerad

Arc-length angles 2 pi s / L at which to evaluate.

Returns
—tuple of np.ndarraym

(R, Z) on the surface, same shape as theta.

Convention

R = sum_m r_cos[m] cos(m theta) + r_sin[m] sin(m theta) and likewise Z, with theta increasing counter-clockwise in (R, Z) from the phase origin of vaft.data.equilibrium.FourierSurface.

Applicability · Provenance

Applicability

Machine-independent.

Provenance

  1. Truncated Fourier series of a closed curve; the arc-length angle and phase are VAFT’s convention (#945).

evaluate_guazzotto_freidberg

Machine-independentConvention-sensitive source
evaluate_guazzotto_freidberg(model, x, y)
Show source vaft/process/_equilibrium_guazzotto_freidberg.py, lines 539–584
def evaluate_guazzotto_freidberg(model: GuazzottoFreidbergEquilibrium, x: Any, y: Any) -> Mapping[str, np.ndarray]:
    """Normalized flux and its analytic derivatives of a Guazzotto-Freidberg equilibrium.

    Parameters
    ----------
    model : GuazzottoFreidbergEquilibrium
        A solved equilibrium [-].
    x : array_like
        Normalized radial coordinate, ``-1`` inboard and ``1`` outboard on the
        midplane [-].
    y : array_like
        Normalized height ``Z/a`` [-].

    Returns
    -------
    Mapping of str to np.ndarray
        ``psi`` and its derivatives ``psi_x``, ``psi_y``, ``psi_xx``,
        ``psi_yy``, ``psi_xy``; ``psi_j``, the shifted flux ``psi + f_J/(1 -
        f_J)`` of Part 2 (equal to psi without a current pedestal); and
        ``gs_residual``, the Grad-Shafranov equation evaluated as ``(1 +
        eps_hat x) psi_xx + psi_yy/(1 + eps**2) + alpha**2 (1 + nu G(x))
        psi_J``, zero to round-off (Eq. 2.9; Part 2, Eq. 5.14) [-].

    Convention
    ----------
    The normalized coordinates and flux of Eq. (2.8); physical fields follow
    from :func:`guazzotto_freidberg_to_equilibrium`.

    Applicability
    -------------
    Machine-independent.

    Provenance
    ----------
    .. [1] Guazzotto and Freidberg (2021), Eqs. (2.9), (3.2) and (A4); Part 2,
       Eqs. (5.12)-(5.14).
    """
    values = _evaluate(model.topology, model.inverse_aspect_ratio, _model_source(model), model.alpha,
                       model.coefficients, x, y, model.series_terms)
    eps = model.inverse_aspect_ratio; eps_hat = 2*eps/(1 + eps**2)
    xx = np.asarray(x, dtype=float)
    s1, s2, s3 = _model_source(model)
    residual = ((1 + eps_hat*xx)*values["xx"] + values["yy"]/(1 + eps**2)
                + model.alpha**2*(1 + s1*xx + s2*xx**2 + s3*xx**3)*values["psi"])
    return {"psi": values["psi"] - _flux_offset(model), "psi_j": values["psi"], "psi_x": values["x"], "psi_y": values["y"], "psi_xx": values["xx"],
            "psi_yy": values["yy"], "psi_xy": values["xy"], "gs_residual": residual}

Normalized flux and its analytic derivatives of a Guazzotto-Freidberg equilibrium.

NameTypeUnitDescription
modelGuazzottoFreidbergEquilibrium-

A solved equilibrium.

xarray_like-

Normalized radial coordinate, -1 inboard and 1 outboard on the midplane.

yarray_like-

Normalized height Z/a.

Returns
—Mapping of str to np.ndarray-

psi and its derivatives psi_x, psi_y, psi_xx, psi_yy, psi_xy; psi_j, the shifted flux psi + f_J/(1 - f_J) of Part 2 (equal to psi without a current pedestal); and gs_residual, the Grad-Shafranov equation evaluated as (1 + eps_hat x) psi_xx + psi_yy/(1 + eps**2) + alpha**2 (1 + nu G(x)) psi_J, zero to round-off (Eq. 2.9; Part 2, Eq. 5.14).

Convention

The normalized coordinates and flux of Eq. (2.8); physical fields follow from guazzotto_freidberg_to_equilibrium.

Applicability · Provenance

Applicability

Machine-independent.

Provenance

  1. Guazzotto and Freidberg (2021), Eqs. (2.9), (3.2) and (A4); Part 2, Eqs. (5.12)-(5.14).

evaluate_miller

Machine-independentConvention-sensitive source
evaluate_miller(surface, theta)

Points on a Miller-parameterized flux surface at given poloidal angles.

The Miller parameterization writes a shaped, up-down symmetric surface in a handful of numbers: minor radius, centre, elongation, triangularity and – optionally – squareness. This is the forward evaluation; fit_miller_surface is the inverse.

NameTypeUnitDescription
surfaceMillerSurface-

The shape parameters. r, r0 and z0 in metres, kappa, delta, zeta and indentation dimensionless.

thetaarray_likerad

Poloidal angles at which to evaluate.

Returns
—tuple of np.ndarraym

(R, Z) on the surface, same shape as theta.

Convention

R = r0 + r*(cos(theta + arcsin(delta)*sin(theta)) + b*sin(theta)**2*cos(theta)) and Z = z0 + kappa*r*sin(theta + zeta*sin(2*theta)), with b the indentation. theta is the parameter of the parameterization, not a geometric poloidal angle about the centre, and the two differ once the surface is shaped. Positive delta shifts the extremum of Z inboard. Positive zeta squares the surface off, negative rounds it. zeta = 0 is the five-parameter surface exactly, to the bit, so nothing that does not ask for squareness moves; the same holds for indentation = 0. The indentation term vanishes at the four cardinal points, and with alpha = arcsin(delta) the inboard side turns concave – a bean – once b > (1 - alpha)**2/2. The surface is up-down symmetric by construction, squareness included.

Raises

  • ValueError The geometry is degenerate: minor radius or elongation not positive, triangularity of magnitude one or more, squareness of magnitude one half or more, or an indentation at or below -sqrt(1 - delta**2).
Applicability · Limitations · Provenance

Applicability

Machine-independent.

Limitations

Up-down symmetric and single-valued in theta, so it cannot represent a separatrix, an X-point, or an asymmetric boundary at all. Triangularity is bounded by one because the arcsine of it must exist. Squareness is bounded by one half because d(theta + zeta*sin(2*theta))/dtheta = 1 + 2*zeta*cos(2*theta) changes sign beyond it: the parameterization then doubles back and the “surface” crosses itself, so a larger magnitude does not describe a squarer plasma but a curve that is not one. The indentation is bounded below for the same reason: at equal height the two sides are 2 r cos(theta) (cos(alpha sin(theta)) + b sin(theta)**2) apart, which stays positive for every theta exactly when b > -cos(alpha) = -sqrt(1 - delta**2).

Provenance

  1. Miller, Chu, Greene, Lin-Liu and Waltz, Noncircular, finite aspect ratio, local equilibrium model, Phys. Plasmas 5, 973 (1998), which introduced this parameterization.
  2. The squareness term follows the extended form used for shape comparison, e.g. Barr, Boyer, Humphreys et al., ShapeFIT, Nucl. Fusion 58, 076011 (2018), App. A.
  3. The inboard-indentation term for bean-shaped plasmas (PBX/PBX-M) is VAFT’s, specified with its bean-onset criterion in issue #941.

evaluate_mxh_chebyshev

Machine-independentConvention-sensitive source
evaluate_mxh_chebyshev(representation, rho, theta)
Show source vaft/process/_equilibrium_compact.py, lines 290–333
def evaluate_mxh_chebyshev(representation: MXHChebyshevRepresentation, rho: Any, theta: Any) -> tuple[np.ndarray, np.ndarray]:
    """Flux-surface points of an MXH-Chebyshev representation.

    Parameters
    ----------
    representation : MXHChebyshevRepresentation
        A fitted representation [-].
    rho : array_like
        ``sqrt(psi_N)``, in (0, 1] [-].
    theta : array_like
        Geometric poloidal angle of Eq. (7), broadcast against *rho* [rad].

    Returns
    -------
    tuple of np.ndarray
        ``(R, Z)`` [m].

    Convention
    ----------
    Eqs. (6)-(11) of Xie and Li: ``R = R0 + h + rho a cos(theta_bar)``,
    ``Z = Z0 + v + kappa rho a sin(theta)``, every profile ``f(rho) = f_edge +
    sum_l f_l (1 - rho**2) T_l(2 rho**2 - 1)``.

    Applicability
    -------------
    Machine-independent.

    Provenance
    ----------
    .. [1] H. Xie and Y. Li, arXiv:2601.02942 (2026), Eqs. (6)-(11).
    """
    rho, theta = np.broadcast_arrays(np.asarray(rho, dtype=float), np.asarray(theta, dtype=float))
    basis = _radial_basis(rho.ravel(), representation.radial_order)

    def profile(name: str) -> np.ndarray:
        edge, coefficients = representation.profiles[name]
        return (edge + basis @ np.asarray(coefficients)).reshape(rho.shape)

    theta_bar = theta + profile("c0")
    for m in range(1, representation.harmonics + 1):
        theta_bar = theta_bar + profile(f"c{m}")*np.cos(m*theta) + profile(f"s{m}")*np.sin(m*theta)
    radius = rho*profile("a")
    return (representation.r0 + profile("h") + radius*np.cos(theta_bar),
            representation.z0 + profile("v") + profile("kappa")*radius*np.sin(theta))

Flux-surface points of an MXH-Chebyshev representation.

NameTypeUnitDescription
representationMXHChebyshevRepresentation-

A fitted representation.

rhoarray_like-

sqrt(psi_N), in (0, 1].

thetaarray_likerad

Geometric poloidal angle of Eq. (7), broadcast against rho.

Returns
—tuple of np.ndarraym

(R, Z).

Convention

Eqs. (6)-(11) of Xie and Li: R = R0 + h + rho a cos(theta_bar), Z = Z0 + v + kappa rho a sin(theta), every profile f(rho) = f_edge + sum_l f_l (1 - rho**2) T_l(2 rho**2 - 1).

Applicability · Provenance

Applicability

Machine-independent.

Provenance

  1. H. Xie and Y. Li, arXiv:2601.02942 (2026), Eqs. (6)-(11).

evaluate_solovev

Machine-independentConvention-sensitive source
evaluate_solovev(model, r, z, *, cocos=11, convention=None)

Evaluate an analytic Solov’ev equilibrium on a set of points.

The Solov’ev solution is the Grad-Shafranov equation’s closed form for a pressure and squared poloidal current that are both linear in the poloidal flux. It is exact, which makes it the reference against which numerical machinery here is checked.

NameTypeUnitDescription
modelSolovevEquilibrium-

The homogeneous basis coefficients, the basis they expand in, and the two linear source slopes.

rarray_likem

Major radius, strictly positive.

zarray_likem

Height.

cocosint or None, optional-

COCOS coordinate convention index (1-8, 11-18) setting the poloidal field orientation factor sigma = sigma_RphiZ * sigma_Bp; None selects the historical orientation fallback (k = -1).

conventionint or None, optional-

Alias for cocos.

Returns
—Mapping of str to np.ndarray-

psi [Wb/rad] with its two first derivatives [Wb/(rad m)] and its three second derivatives d2psi_dr2, d2psi_dz2, d2psi_drdz [Wb/(rad m^2)], whose Hessian determinant separates an O-point from an X-point; b_r, b_z and b_phi [T]; pressure [Pa]; f [T m]; j_phi [A/m^2]; and grad_shafranov_source, the right-hand side the solution satisfies [T/m].

Convention

Works in poloidal flux per radian, with the poloidal field taken as B_R = (sigma / R) dpsi/dz and B_Z = -(sigma / R) dpsi/dR, where sigma = sigma_RphiZ * sigma_Bp is determined by cocos. The default is vaft.data.cocos.VAFT_INTERNAL_COCOS (11), which has sigma = +1; passing cocos=None retains the historical fallback sigma = -1 (the COCOS 2/3/6/7 orientation family).

j_phi is the component along the convention’s own toroidal unit vector, -sigma_Bp (R dp/dpsi + F dF/dpsi / (mu0 R)) per Sauter Eq. 12, which is what Ampere’s law gives on the returned field; the historical fallback takes the right-handed member of its family (COCOS 3/7, sigma_Bp = -1). Before #966 the sign was dropped, so a COCOS 11 model reported the current opposite to its own field. solovev_to_equilibrium is where this is reconciled with a declared convention, scaling the flux by 2*pi for full-weber conventions and transforming signs appropriately. Pressure and the squared poloidal current are linear in psi by construction, so their gradients are the model’s own constants.

Raises

  • ValueError Any point has a non-positive major radius, or an invalid COCOS index is provided.
Applicability · Limitations · Provenance

Applicability

Machine-independent.

Limitations

Linear sources only, so it cannot represent a peaked pressure profile or a realistic current density. The poloidal current is recovered as a square root, clipped at zero, so a model whose flux excursion drives the square negative silently flattens rather than failing.

Provenance

  1. Solov’ev, Hydromagnetic stability of closed plasma configurations (1968), for the linear-source closed form.
  2. The five-term homogeneous basis and the particular integral follow the standard polynomial construction for that solution.
  3. A. J. Cerfon and J. P. Freidberg, Phys. Plasmas 17, 032502 (2010), for the seven- and twelve-term bases of basis="cerfon_freidberg_even" and "cerfon_freidberg".

extract_flux_surface_contours

Machine-independentConvention-sensitive source
extract_flux_surface_contours(psi_grid, R, Z, psi_axis, psi_boundary, levels_norm)

Iso-flux contours at requested normalized levels, by marching squares.

NameTypeUnitDescription
psi_gridarray_likeWb/rad

Poloidal flux on the grid, indexed (R, Z).

Rarray_likem

Major-radius grid axis.

Zarray_likem

Height grid axis.

psi_axisfloatWb/rad

Poloidal flux on the magnetic axis, the zero of the normalization

psi_boundaryfloatWb/rad

Poloidal flux at the boundary, the one.

levels_normsequence of float-

Normalized flux levels to trace, 0 on axis and 1 at the boundary.

Returns
—dict of float to list of tuple of np.ndarraym

Each requested level mapped to its contour segments as (R, Z) point arrays. A level with no contour on this grid maps to an empty list.

Convention

The flux map is indexed major radius first, matching the IMAS data dictionary’s two-dimensional profile layout with the first and second grid dimensions as major radius and height. Levels are normalized, so the absolute unit of the three flux arguments cancels as long as they agree.

A level may have more than one contour. Inside a diverted plasma a single normalized level can trace disconnected pieces, so the value is always a list and a caller wanting “the” surface must choose among them, normally the closed one containing the magnetic axis.

Applicability · Limitations · Provenance

Applicability

Machine-independent.

Limitations

Marching squares on the grid, so a contour is resolved only to the cell and a surface smaller than a few cells is poorly represented. Contours are returned in the order the algorithm finds them, not sorted by size or by containment.

Provenance

  1. The IMAS data dictionary layout for a two-dimensional flux profile, which fixes the axis order this assumes.

find_rational_surfaces

Machine-independent source
find_rational_surfaces(psi_norm, q, n, *, m_range=None)

Where a toroidal mode resonates: the surfaces with q = m / n.

A perturbation with toroidal mode number n resonates where the safety factor equals m / n for an integer m. This finds those crossings by linear interpolation on the q profile, so the result is the equilibrium’s own answer and needs no perturbed-equilibrium code.

NameTypeUnitDescription
psi_normarray_like-

Normalized poloidal flux, increasing.

qarray_like-

Safety factor on psi_norm.

nint-

Toroidal mode number; its sign is ignored.

m_rangetuple of int, optional-

(m_min, m_max) to search; the range q actually spans by default.

Returns
—dict of str to ndarray-

m (poloidal mode number), q_rational (= m / n) and psi_n_rational (where the crossing is), ordered outward.

Raises

  • ValueError psi_norm and q differ in length, psi_norm does not increase, or n is zero.
Processing steps · Applicability · Limitations · Provenance

Processing steps

  1. Drop non-finite samples.
  2. For each integer m in range, find every bracket where q - m/n changes sign and interpolate the crossing linearly.
  3. Sort the crossings outward.

Applicability

Machine-independent. Any monotonic radial coordinate; the name says psi_norm because that is what the perturbed-equilibrium codes report their rational surfaces on.

Limitations

Accuracy is that of linear interpolation, so the error scales with the square of the local grid spacing and with the curvature of q – which means it is worst exactly where q is steepest, at the edge. On the grid GPEC itself writes, which is refined around the rational surfaces (a spacing of 3.6e-06 at the outermost surface of the DIII-D example against 7.4e-03 in the core), the surfaces come back to 1.4e-09 in psi_norm. On a uniform grid of the kind an equilibrium reconstruction produces they do not: 6.5e-04 at 129 points and 4.0e-05 at 513, in both cases dominated by the outermost surface. Interpolate q onto a finer grid before asking, if the answer has to be better than that.

A reversed-shear q resonates twice on the same m, and both crossings are returned. A root that sits exactly on a grid point, or a q that is flat at m / n over an interval, is returned once – at the node and at the start of the flat region respectively – rather than once per sign change, which would double-count it.

Provenance

  1. gpec_multimode_metrics.rational_surface_table derived the same surfaces from GPEC output; this takes them from the equilibrium, so they can be known before a run – at the accuracy the equilibrium’s own grid supports, which the Limitations quantify.

find_stationary_points

Machine-independentConvention-sensitive source
find_stationary_points(equilibrium, *, kind=None)
Show source vaft/process/_equilibrium_parametric.py, lines 2732–2806
def find_stationary_points(equilibrium: Any, *, kind: str | None = None) -> tuple[StationaryPoint, ...]:
    """Locate the O-points and saddles of psi to sub-grid accuracy.

    Every point where grad(psi) vanishes inside the grid is found by root finding
    on a bicubic spline of psi, seeded from the local minima of |grad psi| on the
    grid, and classified by the sign of its Hessian determinant.  This is the
    search :func:`derive_boundary_representation` runs before it decides the
    topology, exposed on its own for a consumer that needs the magnetic axis or
    the saddles without the rest of the boundary analysis.

    Parameters
    ----------
    equilibrium : EquilibriumData, ODS, GEQDSK or path
        Adapted through :func:`as_equilibrium`; needs a psi map on an R-Z grid of
        at least four points each way [-].
    kind : {"o", "x"}, optional
        Keep only extrema (``"o"``) or only saddles (``"x"``); both when not
        given [-].

    Returns
    -------
    tuple of StationaryPoint
        Position, psi, normalized psi, kind, Hessian determinant and flattest
        curvature of each point.  O-points come first, nearest the axis flux
        first; then saddles, nearest the boundary flux first; points without a
        normalized flux last within their kind.  Empty when the record has no
        usable psi map.  Positions in metres; psi in the record's flux unit;
        the determinant in that unit squared per m^4 and the curvature in that
        unit per m^2, so both scale with the record's flux normalization [-].

    Raises
    ------
    ValueError
        *kind* is neither ``None``, ``"o"`` nor ``"x"``.

    Convention
    ----------
    Classification uses the sign of the Hessian determinant, which no COCOS
    transform changes: a sign flip of psi negates both second derivatives and
    leaves the determinant alone.  An O-point may therefore be a maximum or a
    minimum of psi.  ``psi_n`` is ``(psi - psi_axis) / (psi_boundary - psi_axis)``
    in the record's own convention, and NaN when the record lacks those two
    values.

    Applicability
    -------------
    Machine-independent.

    Limitations
    -----------
    A saddle is only a candidate X-point: numerical saddles far from the plasma
    are common on reconstructed maps.  Whether one bounds the plasma is decided
    by :func:`derive_boundary_representation`.  Searches start only from
    interior grid nodes and keep only roots strictly inside the grid, so a point
    in the outermost cell may be missed; two closer than half a cell are
    reported once.

    Provenance
    ----------
    .. [1] The Hessian classification of stationary points of psi is the standard
       O-point and X-point test.
    """
    if kind not in (None, "o", "x"):
        raise ValueError(f"kind must be None, 'o' or 'x'; got {kind!r}")
    points = _stationary_points(as_equilibrium(equilibrium))
    if kind is not None:
        points = tuple(point for point in points if point.kind == kind)

    def order(point: StationaryPoint) -> tuple[int, int, float]:
        target = 0.0 if point.kind == "o" else 1.0
        known = bool(np.isfinite(point.psi_n))
        return (0 if point.kind == "o" else 1, 0 if known else 1,
                abs(point.psi_n - target) if known else 0.0)

    return tuple(sorted(points, key=order))

Locate the O-points and saddles of psi to sub-grid accuracy.

Every point where grad(psi) vanishes inside the grid is found by root finding on a bicubic spline of psi, seeded from the local minima of |grad psi| on the grid, and classified by the sign of its Hessian determinant. This is the search derive_boundary_representation runs before it decides the topology, exposed on its own for a consumer that needs the magnetic axis or the saddles without the rest of the boundary analysis.

NameTypeUnitDescription
equilibriumEquilibriumData, ODS, GEQDSK or path-

Adapted through as_equilibrium; needs a psi map on an R-Z grid of at least four points each way.

kind{"o", "x"}, optional-

Keep only extrema ("o") or only saddles ("x"); both when not given.

Returns
—tuple of StationaryPoint-

Position, psi, normalized psi, kind, Hessian determinant and flattest curvature of each point. O-points come first, nearest the axis flux first; then saddles, nearest the boundary flux first; points without a normalized flux last within their kind. Empty when the record has no usable psi map. Positions in metres; psi in the record’s flux unit; the determinant in that unit squared per m^4 and the curvature in that unit per m^2, so both scale with the record’s flux normalization.

Convention

Classification uses the sign of the Hessian determinant, which no COCOS transform changes: a sign flip of psi negates both second derivatives and leaves the determinant alone. An O-point may therefore be a maximum or a minimum of psi. psi_n is (psi - psi_axis) / (psi_boundary - psi_axis) in the record’s own convention, and NaN when the record lacks those two values.

Raises

  • ValueError kind is neither None, "o" nor "x".
Applicability · Limitations · Provenance

Applicability

Machine-independent.

Limitations

A saddle is only a candidate X-point: numerical saddles far from the plasma are common on reconstructed maps. Whether one bounds the plasma is decided by derive_boundary_representation. Searches start only from interior grid nodes and keep only roots strictly inside the grid, so a point in the outermost cell may be missed; two closer than half a cell are reported once.

Provenance

  1. The Hessian classification of stationary points of psi is the standard O-point and X-point test.

fit_fourier_surface

Machine-independentConvention-sensitive source
fit_fourier_surface(contour, *, modes=6, radial_value=None, radial_coordinate='psi_n', max_normalized_rms=0.02)

Represent one closed flux-surface contour as a truncated Fourier series.

The general counterpart of fit_miller_surface: where Miller holds a surface in a few interpretable, up-down symmetric numbers, this keeps every harmonic up to modes in both R and Z, sine and cosine, so up-down asymmetry and higher-order shaping are represented rather than left in the residual. The truncation error it reports answers how many harmonics the surface needs.

NameTypeUnitDescription
contourContour or tuple of array_likem

The closed surface, as a contour record or an (R, Z) pair.

modesint, optional-

Highest poloidal harmonic kept, at least one.

radial_valuefloat, optional-

The surface’s radial coordinate, carried onto the result for the caller’s bookkeeping.

radial_coordinatestr, optional-

Which coordinate radial_value is in.

max_normalized_rmsfloat, optional-

Largest geometric RMS error, divided by the contour’s half radial extent, that still counts as accepted.

Returns
—FourierFitResult-

The surface, the resampled observed contour, the reconstruction on the same angles, the symmetric geometric RMS, its normalized form, the maximum and Hausdorff distances, acceptance with a reason, and the derivation record.

Convention

The angle is theta = 2 pi s / L, arc length counter-clockwise over the perimeter, with its origin where the first harmonic of R peaks (so r_sin[1] = 0); the m = 0 terms are the perimeter centroid. An up-down symmetric surface therefore has r_sin = 0 and z_cos[1:] = 0, and a translation changes the m = 0 terms only. This is the same definition derive_boundary_representation uses for the LCFS, so there is one Fourier convention for a contour, not two. Purely geometric: no flux, field or COCOS enters.

Raises

  • ValueError modes is below one, or the contour has zero arc length or zero radial extent.
Processing steps · Defaults · Applicability · Limitations · Provenance

Processing steps

  1. Resample the contour to max(256, 8*modes) points uniform in arc length.
  2. Orient it counter-clockwise and start at its largest R.
  3. Solve the cosine and sine coefficients of R and Z by least squares.
  4. Rotate the angle origin so the first harmonic of R is a pure cosine (r_sin[1] = 0, r_cos[1] > 0), which fixes the phase independently of the contour’s sampling and starting point.
  5. Score the reconstruction geometrically: contour-to-model and model-to-contour nearest distances on a dense evaluation.

Defaults

modes = 6 and max_normalized_rms = 0.02 are numerical conveniences: six harmonics resolve the shaped surfaces of a tokamak to well under a percent, and the two-percent threshold is the one the Miller fit uses, so the two acceptances are comparable.

Applicability

Machine-independent.

Limitations

The phase origin is undefined only for a contour with no first harmonic in R, which no closed plasma surface is. max_error and hausdorff_distance are the same number, the larger of the two directed maxima, kept separately to parallel MillerFitResult. Arc length is not a flux-coordinate angle, so the coefficients describe shape, not straight-field-line harmonics.

Provenance

  1. Least-squares Fourier series of a closed curve; the convention is VAFT’s (#945), shared with the LCFS representation.

fit_fourier_surface_sequence

Machine-independentConvention-sensitive source
fit_fourier_surface_sequence(equilibrium, levels, *, radial_coordinate='psi_n', modes=6, max_normalized_rms=0.02)

Represent a sequence of internal flux surfaces as Fourier series.

Traces each requested surface and fits it with fit_fourier_surface, so each harmonic becomes a radial profile such as R_2^c(rho) that can be compared between equilibria independently of their grids.

NameTypeUnitDescription
equilibriumEquilibriumData, ODS, GEQDSK or path-

Adapted through as_equilibrium; needs a psi map and the axis and boundary flux.

levelssequence of float-

Radial positions to represent, in radial_coordinate.

radial_coordinatestr, optional-

Coordinate of levels; anything other than normalized poloidal flux is converted through derive_radial_coordinates.

modesint, optional-

Highest harmonic kept on every surface.

max_normalized_rmsfloat, optional-

Passed to fit_fourier_surface as the acceptance threshold.

Returns
—FourierSequenceResult-

The fits sorted by radial value, the levels that gave no closed surface, and the derivation record.

Convention

Every surface uses the arc-length convention of fit_fourier_surface, so coefficients of one harmonic are comparable across the sequence. The radial coordinate follows derive_radial_coordinates.

Raises

  • ValueError radial_coordinate is not available for this equilibrium.
Processing steps · Applicability · Limitations · Provenance

Processing steps

  1. Convert the levels to normalized poloidal flux when needed.
  2. Trace the closed surface at each level; a level outside the coordinate’s range, or whose surface is open or missing, is recorded as skipped rather than dropped or clamped silently.
  3. Fit each traced surface and sort the fits by radial value.

Applicability

Machine-independent.

Limitations

Near an X-point the arc-length series needs many harmonics to follow the corner, which the acceptance threshold reports rather than hides. Radial derivatives of the coefficients are not computed yet.

Provenance

  1. See fit_fourier_surface; the surface tracing is the one fit_miller_sequence uses.

fit_free_boundary_coils

Machine-independentConvention-sensitive source
fit_free_boundary_coils(equilibrium, machine, *, method='flux', boundary_samples=96, x_points=None, regularization=0.0, current_penalties=None, current_bounds=None, flux_weight=None, field_weight=None, current_scale_A=None, samples_per_axis=5, flux_tolerance=0.02, max_flux_tolerance=0.04, field_tolerance_T=0.01, ip_tolerance=0.05)

Fit physical PF circuit currents to a prescribed LCFS in free space.

flux_normal adds B dot n to relative-flux rows. Prescribed X-points always use separate Br=Bz=0 rows rather than a singular tangent.

NameTypeUnitDescription
equilibriumEquilibriumData-

Target with a closed LCFS, grid, source profiles and declared COCOS.

machineODS-like-

Machine pf_active element rectangles and signed turns.

methodstr, optional-

flux or flux_normal.

boundary_samplesint, optional-

Number of equally spaced LCFS points.

x_pointssequence, optionalm

Prescribed saddle coordinates as (R,Z) pairs.

regularizationfloat, optional-

Tikhonov coefficient on scaled circuit currents.

current_penaltiesmapping, optional-

Nonnegative penalty multiplier for each named circuit.

current_boundsmapping, optionalA

Per-circuit lower and upper physical currents.

flux_weightfloat, optional1/Wb

Objective multiplier for relative flux.

field_weightfloat, optional1/T

Objective multiplier for normal and saddle fields.

current_scale_Afloat, optionalA

Current dividing the regularization variables.

samples_per_axisint, optional-

Subsamples per plasma-cell axis for LCFS area weights.

flux_tolerancefloat, optional-

Acceptance limit for RMS relative flux divided by target span.

max_flux_tolerancefloat, optional-

Acceptance limit for maximum relative flux divided by target span.

field_tolerance_Tfloat, optionalT

Acceptance limit for RMS normal and maximum saddle field.

ip_tolerancefloat, optional-

Acceptance limit for integrated versus declared plasma current.

Returns
—CoilFitResult-

Currents, separate plasma/coil fields, residuals and diagnostics.

Convention

All flux responses are full weber in the Green orientation; COCOS 11 target psi has the opposite field-derivative orientation. Circuit currents are A in the signed-turn pf_active convention. Relative flux discards the additive gauge. accepted requires both residual limits and finite caller-supplied bounds for every circuit.

Processing steps · Defaults · Applicability · Limitations · Provenance

Processing steps

Convert the source profiles to COCOS 11, integrate toroidal plasma current inside fractional LCFS cells, and evaluate exact ring Green responses for the plasma and signed-turn PF rectangles. Assemble weighted boundary and saddle rows, then solve bounded, optionally regularized least squares.

Defaults

As a numerical convenience, omitted flux and field weights divide by the target flux span and peak plasma boundary field, and the current scale defaults to |Ip|/number of circuits. No hardware current limits are inferred from geometry.

Applicability

Machine-independent. Axisymmetric free-space coils and static equilibria.

Limitations

Passive currents, iron, surface-current sheets and toroidal flow are not modeled. Highly conditioned current vectors need hardware bounds and later free-boundary closure for a physical interpretation.

Provenance

  1. Exact kernels in vaft.process.electromagnetics and the COCOS-aware source profiles in vaft.data.equilibrium.EquilibriumData.

fit_miller_sequence

Machine-independentConvention-sensitive source
fit_miller_sequence(equilibrium, levels, *, radial_coordinate='psi_n', max_normalized_rms=0.02)

Fit Miller parameters across a sequence of flux surfaces, with radial derivatives.

A local equilibrium model needs not only the shape of each surface but how the shape changes outward, so this traces every requested level, fits each, and differentiates the accepted ones against the fitted minor radius.

NameTypeUnitDescription
equilibriumEquilibriumData, ODS, GEQDSK or path-

Adapted through as_equilibrium; needs a psi map and an axis and boundary flux to trace contours.

levelssequence of float-

The radial positions to fit, in radial_coordinate.

radial_coordinatestr, optional-

Which coordinate levels are given in. Anything other than the normalized poloidal flux is converted through derive_radial_coordinates first.

max_normalized_rmsfloat, optional-

Passed to fit_miller_surface as the acceptance threshold.

Returns
—MillerSequenceResult-

Every fit in the order requested, including the rejected ones; the reason radial derivatives could not be formed, if they could not; and the derivation record. Accepted surfaces additionally carry the derivatives of centre, elongation and triangularity against minor radius, plus the safety factor, the magnetic shear and the ballooning parameter.

Convention

The magnetic shear is (r/q) dq/dr and the ballooning parameter is -2*mu0*q^2*R0*(dp/dr)/Bt0^2, both against the fitted minor radius, not against a flux coordinate. Only an X-point that is active, meaning relevant to the boundary, vetoes a fit; a saddle elsewhere in the domain must not. The safety factor and pressure are taken from the equilibrium as stored, in whatever sign convention it carries.

Raises

  • ValueError The requested radial coordinate cannot be derived from this equilibrium.
Processing steps · Defaults · Applicability · Limitations · Provenance

Processing steps

  1. Convert the requested levels into normalized poloidal flux when they are given in another coordinate.
  2. Trace the contour at each level and keep the closed one containing the magnetic axis.
  3. Fit each with fit_miller_surface, vetoing surfaces within five percent of a minor radius of an active X-point.
  4. On the accepted surfaces sorted by minor radius, spline-differentiate the three shape parameters, then the safety factor for the magnetic shear, and the pressure for the ballooning parameter.

Defaults

The five percent X-point proximity is a numerical convenience marking where the surface stops being locally Miller-like; so is the requirement of four accepted surfaces for radial derivatives, which is the smallest set a cubic spline can use.

Applicability

Machine-independent.

Limitations

Rejected surfaces are returned with the rest, so a caller must filter on acceptance before using them. Derivatives are unavailable when fewer than four surfaces are accepted or when the fitted minor radii are not strictly increasing, and the reason says which. The shear and the ballooning term additionally need the safety factor, the pressure and the vacuum field, and are simply absent without them.

Provenance

  1. Miller et al., Phys. Plasmas 5, 973 (1998); the radial derivatives are what makes the parameterization a local equilibrium model rather than a shape description.

fit_miller_surface

Machine-independentConvention-sensitive source
fit_miller_surface(contour, *, radial_value=None, radial_coordinate='psi_n', max_normalized_rms=0.02, near_xpoint=False, squareness=False, indentation=False)

Fit the Miller shape parameters to one closed flux-surface contour.

Answers “how Miller-like is this surface, and with what parameters”, and says when the answer is that it is not Miller-like enough to use. Acceptance is a property of the fit, reported on the result rather than raised.

NameTypeUnitDescription
contourContour or tuple of array_likem

The closed surface to fit, as a contour record or an (R, Z) pair.

radial_valuefloat, optional-

The radial coordinate of this surface, carried onto the fitted surface for the caller’s bookkeeping and used for the near-boundary veto.

radial_coordinatestr, optional-

Which radial coordinate radial_value is in.

max_normalized_rmsfloat, optional-

Largest residual, as a root-mean-square distance divided by the fitted minor radius, that still counts as accepted.

near_xpointbool, optional-

Caller’s determination that this surface lies close to an active X-point, which vetoes acceptance regardless of the residual.

squarenessbool, optional-

Fit the sixth parameter, zeta, as well. Off by default: adding a free parameter changes the answer of a fit that did not ask for it.

indentationbool, optional-

Fit the inboard indentation as well, for bean-shaped surfaces. Off by default for the same reason as squareness.

Returns
—MillerFitResult-

The fitted surface, the observed and reconstructed contours, the residual statistics in metres, whether the solve converged, whether the fit is accepted, the reason if not, and the derivation record.

Convention

Purely geometric: it reads a contour and returns shape parameters, touching no flux, no field and no COCOS. radial_value is carried, never interpreted. The residual is a symmetric point-set distance in metres, and the normalized form divides it by the fitted minor radius so the threshold means the same thing on every surface.

Raises

  • ValueError The contour has zero arc length or zero radial extent.
Processing steps · Defaults · Applicability · Limitations · Provenance

Processing steps

  1. Resample the contour to a uniform arc-length parameterization.
  2. Seed the five parameters from the contour’s bounding box.
  3. Solve a bounded least squares on point-to-surface distances, refining each point’s local angle by five Newton steps per evaluation so the residual is a true nearest-point distance rather than an index-matched one.
  4. Score with a symmetric distance: contour-to-model and model-to-contour, giving a root-mean-square, a maximum, and a Hausdorff distance.
  5. Reject, in order, a surface too near the boundary, a surface near an X-point, a fitted indentation that makes the surface cross itself, a residual over the threshold, or a solve that did not converge.

Defaults

max_normalized_rms = 0.02 is a validated-workflow threshold, not a physical constant: two percent of the minor radius is where a fitted surface stops being a useful stand-in for the traced one. The parameter bounds, elongation in 0.05 to 10 and triangularity within 0.999, are numerical conveniences that keep the solve inside the parameterization’s own domain; so is the indentation box of -0.95 to 2, whose delta-dependent lower limit is enforced after the solve instead.

Applicability

Machine-independent.

Limitations

Miller is up-down symmetric, so a genuinely asymmetric surface fits poorly by construction and is rejected rather than approximated. Without squareness a surface whose corners are squarer or rounder than a sine can express has nowhere to put that shape either: it goes into the residual, and past max_normalized_rms the fit is rejected as “not Miller-like enough to use” when what is true is “not five-parameter Miller-like” (#867). Surfaces at or beyond 95.5 percent of the normalized flux are rejected outright, because separatrix geometry is not locally Miller-like there. The X-point proximity has to be determined by the caller; this function only honours the flag. Unlike every other derived quantity in this module, the provenance recorded here carries no convention, tracked in #604.

Provenance

  1. Miller et al., Phys. Plasmas 5, 973 (1998), for the parameterization being fitted.

fit_mxh_chebyshev

Machine-independentConvention-sensitive source
fit_mxh_chebyshev(equilibrium, *, harmonics=2, radial_order=4, surfaces=24, tolerance=0.01, boundary_tolerance=0.02)

Represent an equilibrium’s flux surfaces by MXH shapes with Chebyshev radial profiles.

Every surface rho = sqrt(psi_N) is an MXH shape – centre, minor radius, elongation and a harmonic distortion of the angle – and each of those numbers is a shifted-Chebyshev series in rho anchored to its LCFS value. The coefficient vector is a compact, grid-independent stand-in for the flux map, and the fit reports how faithful it is.

NameTypeUnitDescription
equilibriumEquilibriumData, ODS, GEQDSK or path-

Adapted through as_equilibrium; needs psi and an LCFS.

harmonicsint, optional-

MXH harmonics M of the angle distortion, at least one.

radial_orderint, optional-

Chebyshev order L of every radial profile.

surfacesint, optional-

Traced surfaces the profiles are fitted to.

tolerancefloat, optional-

Largest RMS normalized-flux error at which the fit is accepted.

boundary_tolerancefloat, optional-

Largest RMS boundary error, in minor radii, at which it is accepted.

Returns
—MXHChebyshevRepresentation-

R0, Z0, the orders, every profile’s edge value and Chebyshev coefficients, the parameter count, fidelity metrics (RMS and maximum psi_N error of the represented surfaces, boundary error in minor radii, axis displacement, per-surface MXH shape error), the status and reason, and the provenance.

Convention

The radial coordinate is rho_psi = sqrt(psi_N) (Eq. 4), so the flux is psi_axis + (psi_boundary - psi_axis) rho**2 exactly and the geometric profiles carry the shape; R0 and Z0 are the LCFS centre, so h and v vanish there. Purely geometric: no COCOS enters.

Raises

  • ValueError harmonics below one, a negative radial_order, fewer surfaces than the radial order needs, or no psi map or LCFS.
Processing steps · Defaults · Applicability · Limitations · Provenance

Processing steps

  1. Trace surfaces at rho from 0.15 to 0.97 and take the LCFS as rho = 1.
  2. Extract each surface’s MXH parameters: centre and minor radius from its radial extent, elongation from its height, the geometric angle from Eq. (7), the distorted angle from Eq. (6), and their difference as a harmonic series (Eq. 8).
  3. Fit every parameter’s radial profile as f_edge + sum_l f_l u_l(rho) with u_l of Eq. (10), the edge value fixed to the LCFS.
  4. Evaluate the representation on a (rho, theta) grid and measure psi_N(R, Z) - rho**2 on the equilibrium’s own flux map.

Defaults

harmonics = 2 and radial_order = 4 give 9 x 5 = 45 parameters, inside the paper’s “fewer than 100” for high fidelity; tolerance = 0.01 matches its 1e-2 accuracy level and boundary_tolerance = 0.02 leaves room for a diverted corner. All are numerical conveniences.

Applicability

Machine-independent.

Limitations

Xie and Li use this basis as the unknown of a Grad-Shafranov solver; here it only represents an existing equilibrium, so no force balance is imposed and the source profiles are not fitted. A diverted LCFS has a corner an MXH shape of low order cannot follow; the boundary error says so. The profiles are anchored at rho = 1 only, so the axis position is an extrapolation, reported as a metric.

Provenance

  1. H. Xie and Y. Li, What is the minimum number of parameters required to represent solutions of the Grad-Shafranov equation?, arXiv:2601.02942 (2026), Eqs. (4), (6)-(11).

fit_solovev

Machine-independentConvention-sensitive source
fit_solovev(equilibrium, *, basis='cerfon_freidberg', psi_n_max=1.0, tolerance=0.02)

Represent an equilibrium by the Solov’ev solution closest to its flux map.

The inverse of solve_solovev_constraints: instead of shaping a Solov’ev model to requested boundary points, find the constant p' and FF' and the homogeneous coefficients whose flux best matches a given equilibrium inside its boundary. The flux is linear in all of them, so the fit is one linear least squares; how far the result is from the equilibrium is reported, not assumed.

NameTypeUnitDescription
equilibriumEquilibriumData, ODS, GEQDSK or path-

Adapted through as_equilibrium; needs psi, its axis and boundary values, an LCFS and a declared flux unit.

basisstr, optional-

Homogeneous basis of vaft.data.equilibrium.SolovevEquilibrium: "classic", "cerfon_freidberg_even" or "cerfon_freidberg".

psi_n_maxfloat, optional-

Outermost normalized flux included in the fit.

tolerancefloat, optional-

Largest RMS flux error, and boundary error in minor radii, at which the fit is accepted.

Returns
—SolovevFit-

The Solov’ev model (per-radian flux, rref the geometric centre), the fidelity metrics – RMS and maximum flux error over the flux span, RMS |grad psi| error, magnetic-axis displacement, boundary RMS and maximum distance, and whether the topology matches (None when it could not be classified) – the status (accepted, poor_fidelity, not_representable when the fitted flux has no closed boundary, failed) and reason, and the provenance.

Convention

The fit is done in per-radian flux, so the returned pprime and ffprime are per weber per radian whatever the input convention. The model carries the record’s orientation, which the metrics do not depend on; exporting it with solovev_to_equilibrium needs the record’s COCOS as the target convention, not the default. Errors are relative to the flux span |psi_boundary - psi_axis| and to the minor radius.

Raises

  • ValueError An unknown basis, or a record without psi, an LCFS or a declared flux unit.
Processing steps · Defaults · Applicability · Limitations · Provenance

Processing steps

  1. Convert the flux to per radian and select the grid points inside the LCFS with psi_N <= psi_n_max.
  2. Solve for the basis coefficients, p' and FF' by least squares on the flux there.
  3. Evaluate the fitted model on the grid and measure the flux, field, axis, boundary and topology errors against the equilibrium.

Defaults

basis = "cerfon_freidberg" (twelve terms, up-down asymmetry allowed) and tolerance = 0.02 are numerical conveniences: two percent of the flux span is roughly where a Solov’ev stand-in stops being useful for shape and field studies.

Applicability

Machine-independent.

Limitations

A Solov’ev model has constant sources, so a reconstruction with peaked or hollow profiles fits only approximately, and the fitted p' and FF' are effective values, not the equilibrium’s. A poor fit is returned with status poor_fidelity, not rejected.

Provenance

  1. Solov’ev (1968) and Cerfon and Freidberg, Phys. Plasmas 17, 032502 (2010), for the model; the fit is the linear inverse of #1166.

flux_surface_quantities

Machine-independentConvention-sensitive source
flux_surface_quantities(psi_grid, R, Z, psi_axis, psi_boundary, levels_norm, *, f_profile=None, axis_rz=None, boundary=None, min_points=16)

Flux-surface averages and shape parameters on each normalized flux level.

NameTypeUnitDescription
psi_gridarray_likeWb/rad

Poloidal flux on the grid, indexed (R, Z), per radian.

Rarray_likem

Major-radius grid axis.

Zarray_likem

Height grid axis.

psi_axisfloatWb/rad

Poloidal flux on the magnetic axis.

psi_boundaryfloatWb/rad

Poloidal flux at the boundary.

levels_normsequence of float-

Normalized flux levels to evaluate on, 0 on axis and 1 at the boundary

f_profilearray_like, optionalT m

Poloidal current function F = R B_phi on the same levels. Required for the mean square field and safety factor.

axis_rztuple of float, optionalm

Magnetic axis position, used to pick the confined segment when a level traces several.

boundarytuple of np.ndarray, optionalm

Boundary outline to use at the outermost level instead of tracing it.

min_pointsint, optional-

Fewest contour vertices a level needs before it is treated as resolved

Returns
—dict of str to np.ndarray-

One array per name in FLUX_SURFACE_QUANTITIES, each as long as levels_norm. Volume in cubic metres, areas in square metres, radii and length_pol in metres, bp_dl in tesla-metre, gm1 in inverse square metres, gm5 in tesla squared, gm8 in metres, gm9 in inverse metres, the shape parameters and safety factor q dimensionless, and the two derivatives per radian.

Convention

Per radian. The normalization by axis and boundary flux is scale-free, but the flux gradient that weights every average is not, which is why the per-radian contract matters. dvolume_dpsi and darea_dpsi are likewise per radian; a caller storing them against a weber flux divides by 2*pi.

The average weights by the volume element, <X> = closed_integral(X R dl / |grad psi|) / closed_integral(R dl / |grad psi|), giving gm1 = <1/R^2>, gm8 = <R>, gm9 = <1/R> and, with f_profile, gm5 = <B^2> from |B|^2 = (|grad psi|^2 + F^2)/R^2. bp_dl is instead the plain contour integral of the poloidal field, which is what an integral of that field squared over volume needs, since the volume element cancels one power of it. The flux map is indexed major radius first, matching extract_flux_surface_contours.

Processing steps · Defaults · Applicability · Limitations · Provenance

Processing steps

  1. Trace the contour at each level, taking the confined segment when several are returned, and substituting the supplied outline at the boundary.
  2. Form the averages and the shape parameters on each resolved contour.
  3. Return NaN for a level whose contour is missing or below min_points, then fill those by interpolation against the square root of the normalized flux, the coordinate in which near-axis geometry is linear.
  4. Set the on-axis level to its exact point limits.

Defaults

min_points is a numerical convenience tied to what a flux map can resolve; see MIN_FLUX_SURFACE_POINTS for the measurement behind its value.

Applicability

Machine-independent.

Limitations

The innermost one or two levels are the least accurate everywhere, because the flux gradient is small and varies fastest there. That is a property of the map, not of this routine: on the packaged OMFIT reference the stored dvolume_dpsi runs 8.53, 22.85, 29.59, 29.05, 28.85 from the axis out, a ramp that cannot be physical since the derivative approaches a finite limit, while the trace here gives a smooth 30.8, 29.3, 29.3. Compare against a reference only outside a normalized flux of about 0.05, and check the near-axis values against the integral of the derivative recovering the volume instead.

On axis the surface is a point: the volume, area and surface are exactly zero and the three geometric averages take their axis limits, but the shape parameters and the two derivatives are undefined there and are extrapolated from the innermost resolved surface.

Provenance

  1. The flux-surface average as the IMAS data dictionary defines the gm family, and the same contour extraction extract_flux_surface_contours provides.

fractional_cell_weights_from_boundary

Machine-independentConvention-sensitive source
fractional_cell_weights_from_boundary(R_grid, Z_grid, R_bdry, Z_bdry, samples_per_axis=5)

Fractional area of each grid cell that lies inside the plasma boundary.

An internal replacement for the externally supplied weight map EFIT expects, computed from the boundary polygon so that no external file is needed.

NameTypeUnitDescription
R_gridarray_likem

Major-radius grid, as an axis or a mesh.

Z_gridarray_likem

Height grid, as an axis or a mesh.

R_bdryarray_likem

Major radius of the boundary contour.

Z_bdryarray_likem

Height of the boundary contour.

samples_per_axisint, optional-

Sub-samples per axis within each cell, at least one.

Returns
—np.ndarray-

Area fraction in [0, 1] for every cell, on the grid’s own shape.

Convention

A fraction, not a mask: a cell the boundary crosses gets its area share rather than a zero or a one. That is what makes an integral weighted by these converge with grid resolution instead of stepping.

Raises

  • ValueError samples_per_axis is less than one.
Defaults · Applicability · Limitations · Provenance

Defaults

samples_per_axis = 5 is a numerical convenience: twenty-five sub-samples per cell resolve the boundary to about a fifth of a cell, which is finer than the flux map that produced the boundary in the first place.

Applicability

Machine-independent.

Limitations

Sub-sampling, not exact polygon clipping, so a cell is resolved only to the sub-sample spacing and a boundary feature smaller than a cell is missed. Assumes a single closed polygon.

Provenance

  1. Replaces EFIT’s externally provided cell-weight map; consumed by shafranov_integrals and efit_virial_volume_integrals.

grad_shafranov_operator

Machine-independentConvention-sensitive source
grad_shafranov_operator(psi, r, z)
Show source vaft/process/equilibrium.py, lines 4793–4851
def grad_shafranov_operator(psi: Any, r: Any, z: Any) -> np.ndarray:
    """The Grad-Shafranov elliptic operator on a rectangular grid.

    Parameters
    ----------
    psi : array_like
        Poloidal flux on the grid, indexed ``(R, Z)`` [Wb/rad].
    r : array_like
        Major-radius grid axis [m].
    z : array_like
        Height grid axis [m].

    Returns
    -------
    np.ndarray
        The operator applied to the flux, on the same grid [Wb/(rad m^2)].

    Raises
    ------
    ValueError
        The flux is not indexed ``(R, Z)`` with the given axes.

    Convention
    ----------
    ``Delta* psi = d2psi/dR2 - (1/R) dpsi/dR + d2psi/dZ2``, the axisymmetric
    elliptic operator whose source is the Grad-Shafranov right-hand side. The flux
    is indexed major radius first, the orientation an EFIT-sourced ODS stores.
    The result scales with the flux unit, so it is per radian for a per-radian
    flux; comparing it against a source term requires both in the same convention.

    Applicability
    -------------
    Machine-independent.

    Limitations
    -----------
    Second-order central differences, one-sided at the edges, so the outermost two
    cells inherit that lower accuracy. A caller comparing against a source term
    should ignore that frame, as :func:`grad_shafranov_residual` does.

    Provenance
    ----------
    .. [1] The Grad-Shafranov equation in its standard axisymmetric form.
    """
    psi = np.asarray(psi, dtype=float)
    r = np.asarray(r, dtype=float).ravel()
    z = np.asarray(z, dtype=float).ravel()
    if psi.ndim != 2:
        raise ValueError(f"psi must be a 2-D (R, Z) grid; got shape {psi.shape}")
    if psi.shape != (r.size, z.size):
        raise ValueError(
            f"psi has shape {psi.shape} but the grid is (R={r.size}, Z={z.size}); "
            "psi is indexed (R, Z)"
        )

    dpsi_dr = np.gradient(psi, r, axis=0, edge_order=2)
    d2psi_dr2 = np.gradient(dpsi_dr, r, axis=0, edge_order=2)
    d2psi_dz2 = np.gradient(np.gradient(psi, z, axis=1, edge_order=2), z, axis=1, edge_order=2)
    return d2psi_dr2 - dpsi_dr / r[:, None] + d2psi_dz2

The Grad-Shafranov elliptic operator on a rectangular grid.

NameTypeUnitDescription
psiarray_likeWb/rad

Poloidal flux on the grid, indexed (R, Z).

rarray_likem

Major-radius grid axis.

zarray_likem

Height grid axis.

Returns
—np.ndarrayWb/(rad m^2)

The operator applied to the flux, on the same grid.

Convention

Delta* psi = d2psi/dR2 - (1/R) dpsi/dR + d2psi/dZ2, the axisymmetric elliptic operator whose source is the Grad-Shafranov right-hand side. The flux is indexed major radius first, the orientation an EFIT-sourced ODS stores. The result scales with the flux unit, so it is per radian for a per-radian flux; comparing it against a source term requires both in the same convention.

Raises

  • ValueError The flux is not indexed (R, Z) with the given axes.
Applicability · Limitations · Provenance

Applicability

Machine-independent.

Limitations

Second-order central differences, one-sided at the edges, so the outermost two cells inherit that lower accuracy. A caller comparing against a source term should ignore that frame, as grad_shafranov_residual does.

Provenance

  1. The Grad-Shafranov equation in its standard axisymmetric form.

grad_shafranov_residual

Machine-independentConvention-sensitive source
grad_shafranov_residual(psi, r, z, *, psi_1d, pprime, ffprime, psi_axis=None, psi_boundary=None, boundary_r=None, boundary_z=None, n_surfaces=32, psi_norm_max=0.99, edge_cells=2)

How far an equilibrium is from satisfying its own force balance, surface by surface.

The Grad-Shafranov equation says a flux map and its own pressure and poloidal current gradients are not independent. Evaluating both sides on the stored grid says how well a particular reconstruction closed it, which is a different and stricter question than whether a solver’s iteration converged. The number EFIT reports as its Grad-Shafranov error is the latter.

NameTypeUnitDescription
psiarray_likeWb/rad

Poloidal flux on the grid, indexed (R, Z).

rarray_likem

Major-radius grid axis.

zarray_likem

Height grid axis.

psi_1darray_likeWb/rad

Flux abscissa the two source profiles are given on.

pprimearray_likePa rad/Wb

Pressure gradient against flux.

ffprimearray_likeT^2 m^2 rad/Wb

Poloidal current term’s gradient against flux.

psi_axisfloat, optionalWb/rad

Poloidal flux on the magnetic axis.

psi_boundaryfloat, optionalWb/rad

Poloidal flux at the boundary.

boundary_rarray_like, optionalm

Major radius of the separatrix, used to exclude vacuum points.

boundary_zarray_like, optionalm

Height of the separatrix.

n_surfacesint, optional-

How many flux surfaces to reduce the residual onto.

psi_norm_maxfloat, optional-

Outermost normalized flux included.

edge_cellsint, optional-

Grid cells framed off the border, where the stencil is one-sided.

Returns
—GradShafranovResidual-

The residual reduced along the flux coordinate, with the normalization it was scaled by and the point count that survived the masks.

Convention

Every flux quantity here must be per radian, COCOS 1 to 8. The textbook equation takes this form only in that convention: rescaling to full weber multiplies the left side by 2*pi and divides each right-hand derivative by it, so the two sides move apart by (2*pi)**2. A caller holding a dictionary-conformant ODS converts first; the wrapper vaft.omas.compute_grad_shafranov_residual does it for them.

Raises

  • ValueError The three profiles are not the same length, or no grid point survives the boundary, the flux-range trim, and the edge frame together.
Applicability · Limitations · Provenance

Applicability

Machine-independent.

Limitations

The two source profiles are interpolated onto the grid through the flux itself, so the residual is meaningful only where they are the equilibrium’s own.

Pass the boundary. Outside the separatrix the flux is not monotonic; it folds back into the normalized range near the poloidal field coils, so a flux-value cutoff alone re-admits vacuum points where the two profiles are extrapolation and the operator is reading coil current. On a free-boundary grid that is a third of the points and two orders of magnitude of spurious residual, and it does not affect a fixed-boundary grid at all, so comparing the two without it is not comparing like with like.

Provenance

  1. The Grad-Shafranov equation; the operator is grad_shafranov_operator and the source term is formed from the two profiles the equilibrium stores.

grad_shafranov_residual_modes

Machine-independentConvention-sensitive source
grad_shafranov_residual_modes(equilibrium, levels, *, max_mode=8, radial_coordinate='psi_n', residual_map=None)

Project the Grad-Shafranov residual onto poloidal harmonics on nested flux surfaces.

A pointwise or surface-RMS residual says that force balance fails; the harmonics say how: a large m = 0 term is a radial offset of the whole surface’s balance, while m >= 1 terms follow the shaping. The angle is the arc-length angle of fit_fourier_surface, so residual and shape harmonics of the same surface are directly comparable.

NameTypeUnitDescription
equilibriumEquilibriumData, ODS, GEQDSK or path-

Adapted through as_equilibrium; needs psi, p’ and FF’ and a declared flux convention.

levelssequence of float-

Surfaces to analyse, in radial_coordinate.

max_modeint, optional-

Highest harmonic kept, at least zero.

radial_coordinatestr, optional-

Coordinate of levels, converted through derive_radial_coordinates when it is not psi_n.

residual_maparray_like, optionalT/m

A residual on the record’s grid, indexed (R, Z), to project instead of the computed one – for comparing residual definitions or testing the projection.

Returns
—GradShafranovResidualModes-

Per surface: the radial value, the cosine and sine coefficients of the residual for m = 0 .. max_mode, and its RMS on the surface; the global source scale for normalization; levels without a closed surface; and the derivation record.

Convention

The residual is grad_shafranov_residual’s delta_star - source in per-radian flux (T/m); a full-weber record is converted by its declared convention. Coefficients are raw, in T/m: G(theta) = sum_m a_m cos(m theta) + b_m sin(m theta) with the arc-length angle and phase of vaft.data.equilibrium.FourierSurface. Divide by scale for a normalized form.

Raises

  • ValueError A negative max_mode, a record without the profiles or without a known flux unit, an unavailable radial coordinate, or a residual map of the wrong shape.
Processing steps · Defaults · Applicability · Limitations · Provenance

Processing steps

  1. Evaluate G = Delta* psi - (-mu0 R^2 p' - FF') on the grid with grad_shafranov_residual, in per-radian flux.
  2. Trace each requested surface and resample it uniformly in arc length from its outboard side, as fit_fourier_surface does.
  3. Interpolate G onto the samples and take its discrete Fourier coefficients, then rotate them by the surface’s canonical phase.

Defaults

max_mode = 8 is a numerical convenience; the surface is sampled at max(256, 8*max_mode) points, at least four per wavelength of the highest harmonic, so the kept modes do not alias.

Applicability

Machine-independent.

Limitations

The angle is geometric (arc length), not a straight-field-line angle; a PEST projection waits on the flux-coordinate framework (#472). The residual is a finite-difference quantity, so near a sharp boundary or on a coarse grid its harmonics carry discretization error of the grid scale.

Provenance

  1. The projection of a force-balance residual onto poloidal harmonics follows the moment step of variational-moment equilibrium methods (VMOMS); here it is a diagnostic of an existing equilibrium (#948).

guazzotto_freidberg_parameters

Machine-independentConvention-sensitive source
guazzotto_freidberg_parameters(model, *, q0=1.0, beta0=None, resolution=500)

The dimensionless plasma parameters of Section 6 for a Guazzotto-Freidberg equilibrium.

The flux surfaces fix alpha; one more number – the on-axis toroidal beta, or equivalently the safety factor on axis – fixes the pressure and the diamagnetism, and with them every global quantity.

NameTypeUnitDescription
modelGuazzottoFreidbergEquilibrium-

A solved equilibrium.

q0float or None, optional-

Safety factor on the magnetic axis, converted to beta0 by Eq. (6.12); when beta0 is given, the q0 it implies is reported instead.

beta0float or None, optional-

Toroidal beta on axis, 2 mu0 p0/B0**2.

resolutionint, optional-

Grid points across -1 <= x <= 1 for the plasma integrals.

Returns
—Mapping of str to float-

alpha, beta0, q0, delta_b_over_b0 (Eq. 6.4), beta_t (6.6), beta_p (6.7), li (6.9), q_star (6.10) with the elongation it used as kappa_q_star, and q95 from Eq. (6.11) on the psi = 0.05 surface; and from Part 2, beta0_hat (6.4), surface_field_ratio Delta_B = B_hat0/B0 (6.3), total_current_ratio I_hat/I (6.9), and the core current mu0 I/(a B0) as core_current (area integral of J_phi) and core_current_line_integral (closed integral of b_P on the surface), which agree by Ampere’s law.

Convention

beta_p is the paper’s volume-averaged form, Eq. (6.7), not the IMAS DD definition. For a diverted equilibrium q_star uses kappa95, the height of the psi = 0.05 surface over the full plasma width 2a – the reading that reproduces the published values – and for a limited one the input elongation. With nu = 0 (force free) beta0 is zero and only q0 can fix the normalization. beta_t is normalized to the exterior vacuum field B_hat0 (Part 2, Eq. 6.7 prints the ratio B_hat0**2/B0**2 inverted), and li weights the current with 1 + nu G where Part 2, Eq. (6.10) prints 1 + nu eps_hat x; the two readings coincide without flow.

Raises

  • ValueError Neither q0 nor beta0, a q0 that Eq. (6.12) cannot reach, or a surface that does not close on the grid.
Processing steps · Defaults · Applicability · Limitations · Provenance

Processing steps

  1. Take beta0, or obtain it from q0 by Eq. (6.12) (Part 2, Eq. 6.1).
  2. Integrate psi_J, psi_J**2 and their (1 + nu G)/(1 + eps_hat x) and (1 + G)/(1 + eps_hat x) weighted forms over the plasma in (x, y), where the volume element is uniform.
  3. Iterate the exterior field ratio Delta_B until the surface current carries the bootstrap fraction (Part 2, Eqs. 3.9 and 6.3).
  4. Evaluate Eqs. (6.4)-(6.10) (Part 2, 6.4-6.11), and q95 as the line integral F/(2 pi) closed_integral dl/(R**2 B_p) on the psi = 0.05 surface.

Defaults

q0 = 1 is the paper’s choice for Table 4, a conventional value. resolution = 500 is a numerical convenience giving about three significant figures, the precision the paper publishes.

Applicability

Machine-independent.

Limitations

Integrals are on a Cartesian grid masked by the surface, so they carry an error of order the cell size. Part 2’s Table 2 is reproduced to 0.5 % in beta_p and 1.1 % in q_star. The surface-current jump uses Eq. (3.9), whose toroidal-field term carries R0**2/R**2; Eq. (6.2) prints it without, a 0.2 % difference in q*.

Provenance

  1. Guazzotto and Freidberg (2021), Section 6, Eqs. (6.3)-(6.12).
  2. Guazzotto and Freidberg (2021), Part 2, doi:10.1017/S0022377821000118, Eqs. (3.6)-(3.10) and Section 6, Eqs. (6.1)-(6.12).

guazzotto_freidberg_to_equilibrium

Machine-independentConvention-sensitive source
guazzotto_freidberg_to_equilibrium(model, *, major_radius, toroidal_field, q0=1.0, beta0=None, resolution=129, convention=11)

Export a Guazzotto-Freidberg equilibrium as a gridded record in physical units.

NameTypeUnitDescription
modelGuazzottoFreidbergEquilibrium-

A solved equilibrium.

major_radiusfloatm

R0, positive.

toroidal_fieldfloatT

Vacuum field B0 at R0, positive.

q0float or None, optional-

Safety factor on axis, fixing beta0 by Eq. (6.12).

beta0float or None, optional-

Toroidal beta on axis, taking precedence over q0.

resolutionint, optional-

Grid points across the major radius.

conventionint, optional-

COCOS index to export in, 1 to 18.

Returns
—EquilibriumData-

psi, axis and boundary flux, LCFS, pressure, F, p’, FF’ (psi = 0 on the boundary), the core plasma current and the vacuum field inside the surface, with metadata carrying the model and the Section 6 parameters.

Convention

Built in COCOS 11 for a positive current: the stored flux is -2 pi Psi_0 psi, a minimum on the axis rising to zero at the boundary, as solovev_example stores it. p' and FF' are per weber of that stored flux.

Raises

  • ValueError Invalid physical inputs or COCOS index, an unreachable q0, a boundary that does not close on the grid, or toroidal flow, whose pressure is not a flux function.
Processing steps · Defaults · Applicability · Limitations · Provenance

Processing steps

  1. Fix beta0 and from it p0 (6.3), dB/B0 (6.4) and the axis flux Psi_0 (6.5).
  2. Evaluate psi on an R-Z grid through x = (R**2/R0**2 - 1 - eps**2)/(2 eps).
  3. Build p = p0 (f_P + (1 - f_P) psi**2 + A1 (psi - psi**2)) and F**2 = R0**2 B0**2 (1 + 2 (dB/B0) psi**2 + 2 A2 (psi - psi**2)) (Part 2, Eqs. 4.1, 4.3, 4.5; A1 = A2 = 0 without pedestals), their flux derivatives, and I_p from Ampere’s law on J_phi.
  4. Store in COCOS 11 and convert when another convention is asked for.

Defaults

q0 = 1 as in the paper’s Table 4, a conventional value; resolution = 129 is a numerical convenience.

Applicability

Machine-independent.

Limitations

q is not filled; compute it with calculate_q_profile_from_psi or take q95 from guazzotto_freidberg_parameters. The surface currents of Part 2 are not on the grid: ip is the core current I and bt0 the interior B0; I_hat/I and B_hat0/B0 are in metadata["parameters"].

Provenance

  1. Guazzotto and Freidberg (2021), Eqs. (2.2), (2.8), (6.3)-(6.8); Part 2, Eqs. (4.1)-(4.6) and (6.5)-(6.6).

integrate_romero_closure

Machine-independentConvention-sensitive source
integrate_romero_closure(time, V_B, R_p, I_ni, *, I_p0, L_i0, V_CB0=0.0, k, tau, rtol=1e-08)

Integrate Romero’s first-order closure from a boundary-voltage history.

Evolves the plasma current $I_p$, the internal inductance $L_i$ and $V = V_C - V_B$ under a prescribed boundary loop voltage, resistance and non-inductive current, with the rates of vaft.formula.transformer.romero_closure_rates_from_I_p_L_i_V_CB_V_B_V_R_k_tau.

NameTypeUnitDescription
timenp.ndarrays

Output times, strictly increasing, at least two; the inputs are given on the same axis.

V_Bnp.ndarrayV

Boundary loop voltage, Romero’s sign, one per time.

R_pnp.ndarrayOhm

Plasma resistance, one per time or one value.

I_ninp.ndarrayA

Non-inductively driven current, one per time or one value; 0 asserts a purely Ohmic plasma.

I_p0floatA

Plasma current at the first time, non-zero.

L_i0floatH

Internal inductance at the first time, positive.

V_CB0float, optionalV

$V_C - V_B$ at the first time.

kfloat-

Closure gain.

taufloats

Closure time constant, positive.

rtolfloat, optional-

Relative tolerance of the integrator.

Returns
—dict of str to np.ndarray-

time [s]; I_p [A]; L_i [H]; V_CB and V_C $= V_B + V$ and V_R $= R_p (I_p - \hat I)$ [V].

Convention

Romero’s signs: full-weber flux, $V = -\dot\psi$, and a positive $V_B$ driving a positive $I_p$. The first two state equations are exact; the third is the closure, so every departure of the trajectory from a measured one is attributable to $k$, $\tau$ or the inputs.

Raises

  • ValueError Inputs of the wrong length, a time axis that does not strictly increase, a non-finite input, or a trajectory that reaches zero current or non-positive inductance [-].
Processing steps · Defaults · Applicability · Limitations · Provenance

Processing steps

  1. Interpolate V_B, R_p and I_ni linearly in time.
  2. Integrate the three rates with scipy.integrate.solve_ivp (RK45) from the initial state, reporting at time.
  3. Rebuild $V_C$ and $V_R$ from the state and the inputs.

Defaults

V_CB0 = 0 starts from a flat loop-voltage profile, a modelling choice the caller should replace with a measured $V_C - V_B$ when there is one. rtol = 1e-8 is a numerical convenience; the absolute tolerances are scaled from the initial state.

Applicability

Machine-independent. $k$ and $\tau$ are the caller’s, identified for the machine and regime; none are supplied.

Limitations

Linear interpolation of the inputs, so a voltage history sampled more coarsely than $\tau$ is smoothed. The integration stops with an error if the current falls to 0.1 % of its initial value: $\dot L_i \propto 1/I_p$ is undefined at a reversal, and the step size collapses before one.

Provenance

  1. J. A. Romero and JET-EFDA contributors, Nucl. Fusion 50 (2010) 115002, eqs. (41)-(45).

lab_to_straight_field_line

Machine-independentConvention-sensitive source
lab_to_straight_field_line(theta_lab, table)
Show source vaft/process/equilibrium.py, lines 5875–5929
def lab_to_straight_field_line(theta_lab, table: tuple[np.ndarray, np.ndarray]):
    """Relabel a geometric poloidal angle into the straight-field-line one.

    Parameters
    ----------
    theta_lab : float or array_like
        Geometric angle about the magnetic axis, any branch [rad].
    table : tuple of ndarray
        One ``(lab, sfl)`` pair from :func:`straight_field_line_tables` [rad].

    Returns
    -------
    float or ndarray
        The straight-field-line angle, wrapped to ``[0, 2 pi)`` [rad].

    Raises
    ------
    ValueError
        The table's two arrays disagree in length, or it is too short to
        interpolate.

    Convention
    ----------
    The input is wrapped onto the table's own branch before interpolating.
    The table spans one period starting wherever the surface's outboard
    midplane fell, **not** at zero, so wrapping to ``[0, 2 pi)`` and
    interpolating would read off the end for every angle below that start.

    Applicability
    -------------
    Machine-independent.

    Processing steps
    ----------------
    1. Wrap the angle onto the table's branch.
    2. Interpolate, and wrap the result into ``[0, 2 pi)``.

    Provenance
    ----------
    .. [legacy] ``library/flare_plotting.py::_interp_periodic_inverse``.
    """
    lab_grid, sfl_grid = (np.asarray(part, dtype=float) for part in table)
    if lab_grid.size != sfl_grid.size:
        raise ValueError(
            f"the table pairs {lab_grid.size} geometric angles with "
            f"{sfl_grid.size} straight-field-line ones"
        )
    if lab_grid.size < 3:
        raise ValueError("a relabelling table needs at least three entries")
    angle = np.mod(np.asarray(theta_lab, dtype=float), 2.0 * np.pi)
    start = float(lab_grid[0])
    angle = angle + 2.0 * np.pi * np.ceil((start - angle) / (2.0 * np.pi))
    angle = np.where(angle >= start + 2.0 * np.pi, angle - 2.0 * np.pi, angle)
    result = np.mod(np.interp(angle, lab_grid, sfl_grid), 2.0 * np.pi)
    return float(result) if np.ndim(theta_lab) == 0 else result

Relabel a geometric poloidal angle into the straight-field-line one.

NameTypeUnitDescription
theta_labfloat or array_likerad

Geometric angle about the magnetic axis, any branch.

tabletuple of ndarrayrad

One (lab, sfl) pair from straight_field_line_tables.

Returns
—float or ndarrayrad

The straight-field-line angle, wrapped to [0, 2 pi).

Convention

The input is wrapped onto the table’s own branch before interpolating. The table spans one period starting wherever the surface’s outboard midplane fell, not at zero, so wrapping to [0, 2 pi) and interpolating would read off the end for every angle below that start.

Raises

  • ValueError The table’s two arrays disagree in length, or it is too short to interpolate.
Processing steps · Applicability · Provenance

Processing steps

  1. Wrap the angle onto the table’s branch.
  2. Interpolate, and wrap the result into [0, 2 pi).

Applicability

Machine-independent.

Provenance

  1. library/flare_plotting.py::_interp_periodic_inverse.

make_equilibrium_field_interpolator

Machine-independentConvention-sensitive source
make_equilibrium_field_interpolator(R_grid_1d, Z_grid_1d=None, psi_grid=None, psi_1d=None, f_1d=None, cocos=None, *, psi_per_radian=None)

Build a callable giving the full magnetic field anywhere on one time slice.

Pass an vaft.data.equilibrium.EquilibriumData – make_equilibrium_field_interpolator(equilibrium) – and the grid, the profiles and the flux convention are read from the record, so the caller need not restate the unit of psi. The record is only as right as its convention, which nothing checks against the arrays. The array form remains for flux maps that have no record, and then the convention must be stated.

NameTypeUnitDescription
R_grid_1darray_like or EquilibriumData-

Major-radius grid axis [m], or the whole equilibrium record, in which case every other argument must be left unset.

Z_grid_1darray_like, optionalm

Height grid axis; required with arrays.

psi_gridarray_like, optionalWb or Wb/rad

Poloidal flux on the grid, indexed (R, Z), in the convention cocos describes.

psi_1darray_like, optionalWb or Wb/rad

Flux abscissa the poloidal current function is given on, in the same unit as psi_grid.

f_1darray_like, optionalT m

Poloidal current function F = R*B_phi on that abscissa.

cocosint, optional-

COCOS index of psi_grid.

psi_per_radianbool or None, optional-

Storage family of the flux when the index is not known: False for weber, True for weber per radian.

Returns
—callableT

A function of (R, Z) returning (B_R, B_Z, B_phi).

Convention

Same prefactor as poloidal_field_at_boundary, per Sauter Eq. 20: B_R = k (1/R) dpsi/dZ and B_Z = -k (1/R) dpsi/dR, with k = sigma_RphiZ * sigma_Bp / (2*pi)**e_Bp. The toroidal field is the poloidal current function over the major radius. The flux map is indexed major radius first, matching extract_flux_surface_contours.

k comes from cocos, or from psi_per_radian when the index is open, exactly as for poloidal_field_at_boundary; a record supplies both from its convention. Supplying neither assumes weber per radian and warns, because a weber flux then gives a field 2*pi too large (#1313).

Raises

  • ValueError The flux map is not shaped to the two grid axes, the record lacks a field, cocos and psi_per_radian contradict each other, or the record’s open COCOS candidates disagree on the field direction.
  • TypeError A record is combined with array arguments, or the array form is missing one.
Applicability · Limitations · Provenance

Applicability

Machine-independent.

Limitations

With neither cocos nor psi_per_radian known, from the arguments or the record, the flux is taken as weber per radian, the historical default, and a UserWarning says so (#1313); a weber flux, such as every ODS equilibrium carries, then gives a field 2*pi too large. With the storage family but no index, the orientation is assumed (k < 0) and the warning says that instead; a record’s open candidates settle it when they agree, and leave it to the same announced assumption when they do not (the packaged g-file, open between COCOS 1 and 2, warns like its arrays do). The poloidal current function is defined only from axis to boundary. Points outside that range, in the scrape-off layer, clip to the nearest edge value, the same clip-and-interpolate convention psi_to_rz uses. That is an approximation for a field line that leaves the confined region: the toroidal field there is this clipped function over the major radius rather than the true vacuum field. The spline is built once over the whole map and reused, so evaluation is cheap but the map must not change.

Provenance

  1. Sauter and Medvedev (2013), Eq. 20, for the prefactor.
  2. The clip-and-interpolate convention of psi_to_rz, kept deliberately the same.

make_vacuum_field_interpolator

Machine-independentConvention-sensitive source
make_vacuum_field_interpolator(R_grid_1d, Z_grid_1d, b_r, b_z, r0_b0)

Build a callable giving the vacuum magnetic field anywhere on one grid.

The twin of make_equilibrium_field_interpolator for a machine with no plasma in it. That one reads the poloidal field off a flux map and the toroidal field off profiles_1d.f, both of which a reconstruction supplies; before breakdown there is no reconstruction, and the field the coils and the vessel make is all there is.

NameTypeUnitDescription
R_grid_1dnp.ndarraym

Major radius of the grid columns, increasing.

Z_grid_1dnp.ndarraym

Height of the grid rows, increasing.

b_rnp.ndarrayT

Radial field on the (len(R), len(Z)) grid.

b_znp.ndarrayT

Vertical field on the same grid.

r0_b0floatT m

The vacuum toroidal field product $R_0 B_0$, as tf.b_field_tor_vacuum_r stores it.

Returns
—callableT

A function of (R, Z) returning (B_R, B_Z, B_phi), accepting scalars or arrays of matching shape.

Convention

The toroidal field is $B_\varphi = R_0 B_0 / R$ exactly, not interpolated: in a vacuum it is a pure $1/R$ and the grid would only add error to it. The poloidal components are interpolated, so they carry the grid’s resolution – which is the caller’s choice, not this function’s.

Raises

  • ValueError Grids and field arrays whose shapes disagree [-].
Assumptions · Applicability · Limitations · Provenance

Assumptions

The grid is the one vaft.omas.compute_vacuum_field_map builds, whose axes are monotonic and whose fields already include the vessel’s eddy currents. Nothing here checks that the field is curl-free.

Applicability

Machine-independent. The grid and the toroidal product are the caller’s.

Limitations

Bicubic over the supplied grid, so the field between nodes is as good as the grid is fine: poor within a coil’s near field, where the true field varies on the scale of the conductor, and good near a null, where it varies on the scale of the machine. Outside the grid the spline extrapolates rather than raising, so a caller that can leave the grid must terminate on its own bound.

Provenance

  1. The vacuum field decomposition: the poloidal part from the Green’s function response of the coils and the passive structure, the toroidal part from the TF product.

miller_surfaces

Machine-independentConvention-sensitive source
miller_surfaces(r_minor, *, r0, z0=0.0, kappa=1.0, delta=0.0, zeta=0.0)
Show source vaft/process/_equilibrium_parametric.py, lines 2557–2632
def miller_surfaces(
    r_minor: Any,
    *,
    r0: Any,
    z0: Any = 0.0,
    kappa: Any = 1.0,
    delta: Any = 0.0,
    zeta: Any = 0.0,
) -> tuple[MillerSurface, ...]:
    """A family of Miller surfaces for a shape scan.

    A teaching helper: scan the minor radius at fixed shape, or vary any shape
    parameter along the family, and feed the result to :func:`evaluate_miller`.

    Parameters
    ----------
    r_minor : float or sequence of float
        Minor radius of each surface, positive [m].
    r0 : float or sequence of float
        Major radius of each surface's centre [m].
    z0 : float or sequence of float, optional
        Height of each surface's centre [m].
    kappa : float or sequence of float, optional
        Elongation, positive [-].
    delta : float or sequence of float, optional
        Triangularity, of magnitude below one [-].
    zeta : float or sequence of float, optional
        Squareness, of magnitude below one half [-].

    Returns
    -------
    tuple of MillerSurface
        One surface per entry after broadcasting the arguments against each
        other, in order [-].

    Raises
    ------
    ValueError
        Sequences of unequal length (other than length one), or a surface that
        :func:`evaluate_miller` would reject.

    Convention
    ----------
    The parameters mean what :func:`evaluate_miller` means by them:
    ``R = r0 + r cos(theta + arcsin(delta) sin(theta))``, ``Z = z0 + kappa r
    sin(theta + zeta sin(2 theta))``, so the elongation is height over width and
    the triangularity is ``(r0 - R at Z max) / r`` exactly.  Scalars broadcast
    against sequences.

    Applicability
    -------------
    Machine-independent.

    Limitations
    -----------
    Only the shape is filled in; the radial label and the local-equilibrium
    fields of :class:`MillerSurface` (q, shear, alpha, radial derivatives) are
    left empty, so these surfaces are geometry, not an equilibrium.

    Provenance
    ----------
    .. [1] Miller, Chu, Greene, Lin-Liu and Waltz, Phys. Plasmas 5, 973 (1998),
       through :func:`evaluate_miller`.
    """
    arrays = [np.atleast_1d(np.asarray(value, dtype=float)).reshape(-1) for value in (r_minor, r0, z0, kappa, delta, zeta)]
    lengths = {array.size for array in arrays if array.size != 1}
    if len(lengths) > 1:
        raise ValueError(f"sequence arguments must share one length, got lengths {sorted(lengths)}")
    count = lengths.pop() if lengths else 1
    arrays = [np.broadcast_to(array, (count,)) for array in arrays]
    surfaces = []
    for r, rc, zc, k, d, s in zip(*arrays):
        surface = MillerSurface(r=float(r), r0=float(rc), z0=float(zc), kappa=float(k), delta=float(d), zeta=float(s))
        evaluate_miller(surface, 0.0)  # the same validity rules, raised here
        surfaces.append(surface)
    return tuple(surfaces)

A family of Miller surfaces for a shape scan.

A teaching helper: scan the minor radius at fixed shape, or vary any shape parameter along the family, and feed the result to evaluate_miller.

NameTypeUnitDescription
r_minorfloat or sequence of floatm

Minor radius of each surface, positive.

r0float or sequence of floatm

Major radius of each surface’s centre.

z0float or sequence of float, optionalm

Height of each surface’s centre.

kappafloat or sequence of float, optional-

Elongation, positive.

deltafloat or sequence of float, optional-

Triangularity, of magnitude below one.

zetafloat or sequence of float, optional-

Squareness, of magnitude below one half.

Returns
—tuple of MillerSurface-

One surface per entry after broadcasting the arguments against each other, in order.

Convention

The parameters mean what evaluate_miller means by them: R = r0 + r cos(theta + arcsin(delta) sin(theta)), Z = z0 + kappa r sin(theta + zeta sin(2 theta)), so the elongation is height over width and the triangularity is (r0 - R at Z max) / r exactly. Scalars broadcast against sequences.

Raises

  • ValueError Sequences of unequal length (other than length one), or a surface that evaluate_miller would reject.
Applicability · Limitations · Provenance

Applicability

Machine-independent.

Limitations

Only the shape is filled in; the radial label and the local-equilibrium fields of MillerSurface (q, shear, alpha, radial derivatives) are left empty, so these surfaces are geometry, not an equilibrium.

Provenance

  1. Miller, Chu, Greene, Lin-Liu and Waltz, Phys. Plasmas 5, 973 (1998), through evaluate_miller.

parallel_current_from_toroidal

Machine-independentConvention-sensitive source
parallel_current_from_toroidal(shell_current_tor, *, f, gm1, gm5, shell_volume, b0, shell_area=None)

Convert a shell toroidal driven current to the IMAS parallel current.

A code that drives current usually reports it as a toroidal current per flux shell, or as a profile of current enclosed by each surface. IMAS asks for something different: j_parallel is <J.B>/B0. The two are not interchangeable, and in a spherical tokamak they differ by tens of percent because <B^2> is much larger than B0^2.

NameTypeUnitDescription
shell_current_torarray_likeA

Toroidal current in each flux shell. Not the enclosed profile: pass numpy.diff of an enclosed one.

farray_likeT.m

F = R B_phi at the shell centres. A profile, not a constant: a paramagnetic plasma can carry an F well above its vacuum value.

gm1array_likem^-2

Flux-surface-averaged <R^-2> at the shell centres.

gm5array_likeT^2

Flux-surface-averaged <B^2> at the shell centres.

shell_volumearray_likem^3

Volume of each shell.

b0floatT

The vacuum toroidal field IMAS normalizes by. For a core_sources consumer this is vacuum_toroidal_field.b0 of that IDS, which the data dictionary names explicitly in the definition of j_parallel.

shell_areaarray_like, optionalm^2

Cross-sectional area of each shell. Supplying it also returns current_parallel_inside, the cumulative surface integral of j_parallel.

Returns
—ParallelCurrentResult-

j_parallel in A/m^2 and, when shell_area was given, current_parallel_inside in A, plus the intermediates.

Convention

Sign is inherited from shell_current_tor and f, not imposed. The result therefore carries the sign convention of whatever produced them, which is the caller’s to reconcile with the equilibrium it will sit beside.

Assumptions · Applicability · Limitations · Notes · Provenance

Assumptions

The driven current is field-aligned on each flux surface, J = lambda(psi) B. This is the usual statement for a current driven by parallel momentum input – beams, EC, LH – and it is what makes the conversion possible at all from a toroidal quantity. It is an assumption, not an identity: it omits any perpendicular (diamagnetic, Pfirsch-Schlueter) part, so a caller converting a total plasma current with this routine will see that part as a residual.

Applicability

Machine-independent. Requires an axisymmetric equilibrium and a driven current that is field-aligned. Nothing here is specific to a solver: the inputs are physics quantities, so any code reporting a toroidal driven current and an equilibrium can use it.

Limitations

Shells where F <R^-2> dV vanishes – a degenerate or zero-width shell, typically at the magnetic axis – yield zero rather than a division by zero. The field-aligned assumption is the dominant error for a total current and a much smaller one for a driven current.

Notes

With J = lambda B and B_phi = F/R, the toroidal current through a shell is dI = lambda * int B_phi dA. Writing the poloidal area element as dA = dV / (2 pi R) turns that integral into flux-surface averages::

int B_phi dA = F <R^-2> dV / (2 pi)
lambda       = 2 pi dI / (F <R^-2> dV)
j_parallel   = lambda <B^2> / B0

In the large-aspect-ratio limit – F -> R0 B0, <B^2> -> B0^2, <R^-2> -> R0^-2, dV -> 2 pi R0 dA – this reduces to dI / dA, the intuitive toroidal current density. That limit is a check on the result, never a substitute for it.

Provenance

  1. IMAS definitions read from the data dictionary: j_parallel is average(J.B)/B0 and current_parallel_inside its cumulative surface integral.

pest_angle_from_jacobian_angle

Machine-independentConvention-sensitive source
pest_angle_from_jacobian_angle(theta_norm, r, jacobian)

Relabel a DCON flux-surface angle into the PEST angle, from R alone.

NameTypeUnitDescription
theta_normarray_like-

One poloidal circuit of DCON’s own angle, normalized so that a full circuit spans 1, ascending, the last sample repeating the first (0 ... 1).

rarray_likem

Major radius at those samples, on one flux surface.

jacobianstr-

The jac_type the run solved in, a key of DCON_JACOBIAN_ANGLE_WEIGHTS; only "hamada" and "pest" are convertible from R alone.

Returns
—np.ndarrayrad

The PEST angle at the same samples, ascending from 0 to 2 pi

Convention

The PEST angle, referenced to the input angle’s own origin. Both angles run in the same direction and start at the same point on the surface, so theta_pest[0] = 0 wherever theta_norm[0] = 0 is: the reparametrisation is monotonic and fixes no new origin. That matters because a resonant harmonic phase GPEC reports – arg(Phi_res) – is referenced to GPEC’s angle origin, and a conversion that silently moved the origin would rotate every pattern built on it.

Only the poloidal angle is converted. Hamada, PEST and Boozer are all straight-field-line systems, but each pairs its poloidal angle with its own toroidal angle; PEST is the one whose toroidal angle is the machine angle phi. So m theta - n phi is the helical phase in the PEST angle and in no other, which is the reason to convert rather than to accept any straight-field-line angle.

Raises

  • ValueError theta_norm and r disagree in shape, theta_norm does not span one circuit normalized to 1, theta_norm is not strictly increasing, r is not positive and finite, or jacobian is a DCON jacobian whose weight needs a field this signature does not take (boozer, park, equal_arc), one whose powers only the namelist knows (other), or not a DCON jacobian at all.
Processing steps · Applicability · Limitations · Provenance

Processing steps

  1. Two angles on one surface satisfy J_1 d theta_1 = J_2 d theta_2, because the volume element between two surfaces does not depend on how the surface is parametrised. DCON’s angle advances as w = R jacfac * B_p**power_bp * |B|**power_b / R**power_r, which is proportional to 1 / J (see DCON_JACOBIAN_ANGLE_WEIGHTS – the powers sit in the reciprocal, so J ~ R**power_r / (B_p**power_bp |B|**power_b)). The ratio of the two weights is therefore d theta_pest / d theta_in = J_in / J_pest = R**(power_r - 2) |B|**-power_b B_p**-power_bp.
  2. For hamada (0, 0, 0) that weight is R**-2 and no field is needed; for pest it is 1 and the angle passes through unchanged.
  3. Integrate the weight over the input angle by the trapezoid rule and normalize the circuit to 2 pi.

Applicability

Machine-independent. One flux surface of a DCON or GPEC run whose jac_type is recorded. It says nothing about the radial label.

Limitations

Exact to the quadrature only: the weight is integrated on the samples given, so a coarse circuit converts coarsely. Measured against straight_field_line_map on the DIII-D GPEC example’s 513-point Hamada circuits, the agreement is 7.5e-06 rad at psi_N = 0.594 and 9.2e-06 rad at 0.819, degrading to 1.9e-03 at 0.928 and 0.25 at 0.988 – where the comparison, not this conversion, is the weaker side, because a ray-traced angle map loses accuracy against the separatrix. Taking the Hamada angle as the PEST angle, the control for those four numbers, is off by 0.49 to 0.65 rad on the same surfaces.

It is a relabelling, not a check: it cannot tell that the mesh really is in the jacobian it was told, and a mesh labelled hamada that is not will be converted confidently and wrongly.

Provenance

  1. GPEC equil/equil.f, SELECT CASE(jac_type): the (power_r, power_b, power_bp) of each named jacobian.
  2. GPEC equil/inverse.f: DCON’s poloidal angle is spl%fsi(:,5) / spl%fsi(mtheta,5), the normalized cumulative integral of r*jacfac * bp**power_bp * b**power_b / r**power_r.
  3. Grimm, Dewar and Manickam, J. Comput. Phys. 49, 94 (1983), the PEST coordinate system.
  4. The agreement quoted under Limitations, measured on GPEC’s own DIII-D example (publishable under D-13, 2026-09-28, which requires this record): DIII-D 147131 @ 2300 ms, equilibrium g147131.02300_DIIID_KEFIT (SHA-256 35bf902f1d02759ad655f7b5315e3e2b4bc96d88ffc9753f261d48e86dbfe579) and gpec_profile_output_n1.nc (SHA-256 8c17f9675c7046331130d0e6315b38c9e6642c832d3598129c087939072a16b5) from GPEC v1.5.5-378-gf06e6ab with jac_type = "hamada". The comparison is this function applied to the file’s theta_dcon and R, against straight_field_line_map built from the equilibrium’s own PSIRZ, on each of the four rational surfaces the run reports; no solver runs. Same record as test/test_gpec_island_geometry.py::R01_PROVENANCE.

plasma_cell_weights

Machine-independentConvention-sensitive source
plasma_cell_weights(R, Z, psiN_RZ, outline_r=None, outline_z=None)
Show source vaft/process/equilibrium.py, lines 802–876
def plasma_cell_weights(
    R: np.ndarray,
    Z: np.ndarray,
    psiN_RZ: np.ndarray,
    outline_r: np.ndarray | None = None,
    outline_z: np.ndarray | None = None,
) -> np.ndarray:
    """Fraction of each grid cell that is plasma, from the boundary outline when there is one.

    Parameters
    ----------
    R : array_like
        Major-radius axis, or the full mesh [m].
    Z : array_like
        Height axis, or the full mesh [m].
    psiN_RZ : array_like
        Normalized poloidal flux on the grid, used only when there is no
        outline [-].
    outline_r : array_like, optional
        Major radius of the last closed flux surface outline [m].
    outline_z : array_like, optional
        Height of the same outline [m].

    Returns
    -------
    np.ndarray
        Area fraction in ``[0, 1]`` on the grid's ``(R, Z)`` shape [-].

    Raises
    ------
    ValueError
        The outline coordinates differ in length [-].

    Convention
    ----------
    With at least three finite outline points this is
    :func:`fractional_cell_weights_from_boundary`: containment in the boundary,
    with each crossed cell's area share.  Otherwise it falls back to the
    normalized-flux threshold ``0 <= psiN <= 1`` as zeros and ones, and logs a
    warning, because that threshold also admits exterior cells wherever psi
    turns over near the coils (see :func:`volume_average`).

    Applicability
    -------------
    Machine-independent.

    Limitations
    -----------
    The outline is trusted as given: an open or self-intersecting polygon gives
    whatever point-in-polygon makes of it.
    """
    R = np.asarray(R, float)
    Z = np.asarray(Z, float)
    if R.ndim == 1 and Z.ndim == 1:
        Rm, Zm = np.meshgrid(R, Z, indexing="ij")
    else:
        Rm, Zm = R, Z
    if outline_r is not None and outline_z is not None:
        outline_r = np.asarray(outline_r, float).reshape(-1)
        outline_z = np.asarray(outline_z, float).reshape(-1)
        if outline_r.size != outline_z.size:
            raise ValueError("outline_r and outline_z differ in length")
        finite = np.isfinite(outline_r) & np.isfinite(outline_z)
        if finite.sum() >= 3:
            return fractional_cell_weights_from_boundary(
                Rm, Zm, outline_r[finite], outline_z[finite]
            )
    warnings.warn(
        "no usable boundary outline; taking the plasma as 0 <= psiN <= 1, which "
        "also admits cells outside the boundary",
        RuntimeWarning,
        stacklevel=2,
    )
    psiN = np.asarray(psiN_RZ, float)
    return ((psiN >= 0.0) & (psiN <= 1.0)).astype(float)

Fraction of each grid cell that is plasma, from the boundary outline when there is one.

NameTypeUnitDescription
Rarray_likem

Major-radius axis, or the full mesh.

Zarray_likem

Height axis, or the full mesh.

psiN_RZarray_like-

Normalized poloidal flux on the grid, used only when there is no outline.

outline_rarray_like, optionalm

Major radius of the last closed flux surface outline.

outline_zarray_like, optionalm

Height of the same outline.

Returns
—np.ndarray-

Area fraction in [0, 1] on the grid’s (R, Z) shape.

Convention

With at least three finite outline points this is fractional_cell_weights_from_boundary: containment in the boundary, with each crossed cell’s area share. Otherwise it falls back to the normalized-flux threshold 0 <= psiN <= 1 as zeros and ones, and logs a warning, because that threshold also admits exterior cells wherever psi turns over near the coils (see volume_average).

Raises

  • ValueError The outline coordinates differ in length [-].
Applicability · Limitations

Applicability

Machine-independent.

Limitations

The outline is trusted as given: an open or self-intersecting polygon gives whatever point-in-polygon makes of it.

poloidal_field_at_boundary

Machine-independentConvention-sensitive source
poloidal_field_at_boundary(R_grid_1d, Z_grid_1d, psi_grid, R_bdry, Z_bdry, cocos=None, psi_per_radian=None)

Poloidal field on a boundary contour, from the flux map by bicubic spline.

NameTypeUnitDescription
R_grid_1darray_likem

Major-radius grid axis of the flux map.

Z_grid_1darray_likem

Height grid axis of the flux map.

psi_gridarray_likeWb/rad

Poloidal flux on the grid, indexed (R, Z).

R_bdryarray_likem

Major radius of the contour points.

Z_bdryarray_likem

Height of the contour points.

cocosint, optional-

COCOS index of psi_grid. None keeps the historical weber-per-radian form.

psi_per_radianbool, optional-

Whether psi_grid is per radian, when the index alone does not settle it; given together with cocos it must agree with that index.

Returns
B_p_bdrynp.ndarrayT

Poloidal field magnitude at each contour point.

B_R_bdrynp.ndarrayT

Its major-radius component.

B_Z_bdrynp.ndarrayT

Its height component.

Convention

B_R = k * (1/R) * dpsi/dZ and B_Z = -k * (1/R) * dpsi/dR, where k = sigma_RphiZ * sigma_Bp / (2*pi)**e_Bp carries both the orientation sign and the 2*pi normalization together, per Sauter Eq. 20.

With cocos unset the historical EFIT weber-per-radian form k = -1 is used. Passing psi_per_radian alone leaves the sign untouched and applies only the 2*pi, which is what a weber-stored flux needs. The flux map is indexed major radius first.

Raises

  • ValueError cocos and psi_per_radian are both given and disagree on the storage family.
Applicability · Limitations · Provenance

Applicability

Machine-independent.

Limitations

A bicubic spline over the whole map, so contour points must lie inside the grid and the field near a steep edge is only as good as the map’s resolution. The convention must be supplied; a weber flux passed without either argument gives a field too large by 2*pi, and a poloidal beta too small by its square.

Provenance

  1. Sauter and Medvedev (2013), Eq. 20, for the prefactor that carries the orientation sign and the 2*pi together; evaluated by vaft.formula.equilibrium.poloidal_field_factor.
  2. Plays the role of EFIT’s seva2d subroutine, from which the bicubic-spline-on-the-flux-map approach is taken.

prepare_boundary_for_shafranov

Machine-independentConvention-sensitive source
prepare_boundary_for_shafranov(R_bdry, Z_bdry, n_points=256, enforce_ccw=True, eps=1e-12)

Condition a boundary contour into the form the Shafranov integrals require.

NameTypeUnitDescription
R_bdryarray_likem

Major radius of the contour points.

Z_bdryarray_likem

Height of the contour points.

n_pointsint, optional-

How many points to resample the contour to.

enforce_ccwbool, optional-

Whether to reverse the contour when it runs clockwise.

epsfloat, optionalm

Distance below which two consecutive points count as duplicates.

Returns
—tuple of np.ndarraym

The conditioned (R, Z) contour: finite, closed, without duplicate points, counter-clockwise, and evenly spaced in arc length.

Convention

Counter-clockwise is not cosmetic. shafranov_integrals takes the outward unit normal as (dZ/dl, -dR/dl), which points outward only for a counter-clockwise contour; a clockwise one silently flips the sign of every integral that uses it. That is why the orientation is enforced here rather than assumed there.

Processing steps · Defaults · Applicability · Limitations · Provenance

Processing steps

  1. Drop non-finite points.
  2. Close the contour if it is not already closed.
  3. Remove consecutive duplicates within eps.
  4. Reverse the contour if its signed area says it runs clockwise.
  5. Resample uniformly in arc length to n_points.

Defaults

n_points = 256 and eps = 1e-12 are numerical conveniences: enough points to resolve a tokamak boundary’s curvature, and a duplicate threshold far below any real machine dimension.

Applicability

Machine-independent.

Limitations

Assumes a single simple closed curve. A self-intersecting contour, or two disjoint pieces, gives a signed area that does not mean what the orientation test takes it to mean.

Provenance

  1. The outward-normal convention of shafranov_integrals, which is what this exists to guarantee.

psi_to_radial

Machine-independentConvention-sensitive source
psi_to_radial(psi_1d, psi_2d_slice, grid_r, boundary_r, r_axis)

Inboard and outboard major radius of each flux value, along the axis row.

NameTypeUnitDescription
psi_1darray_likeWb/rad

The poloidal flux profile to map.

psi_2d_slicearray_likeWb/rad

Poloidal flux along the grid row at the magnetic axis height.

grid_rarray_likem

Major-radius grid points that row is on.

boundary_rarray_likem

Major radius of the boundary, used for the inboard and outboard limits

r_axisfloatm

Major radius of the magnetic axis, where the row is split.

Returns
r_inboardnp.ndarraym

Major radius on the high-field side for each flux value.

r_outboardnp.ndarraym

Major radius on the low-field side.

Convention

Two answers per flux value, because a flux surface crosses the axis height twice. The split is at the magnetic axis, which is where the flux turns, so each branch is single-valued. Everything is in the flux unit the caller supplied; nothing is rescaled.

Processing steps · Applicability · Limitations · Provenance

Processing steps

  1. Split the axis row at the magnetic axis into an inboard and an outboard branch, on each of which the flux is monotonic.
  2. Build an interpolation of major radius against flux on each branch.
  3. Evaluate both at every value of the profile, clipped to the boundary.

Applicability

Machine-independent.

Limitations

Only the row nearest the axis height is used, so this describes the midplane and not the surface as a whole. A flux value outside the row’s own span is clipped to the boundary rather than extrapolated. Assumes the flux is monotonic on each branch, which fails for a non-nested equilibrium.

Provenance

  1. The inboard and outboard midplane radii as the IMAS data dictionary defines them for a one-dimensional profile.

psi_to_rho

Machine-independentConvention-sensitive source
psi_to_rho(psi_val, q_profile, psi_axis, psi_boundary)
Show source vaft/process/equilibrium.py, lines 245–309
def psi_to_rho(psi_val, q_profile, psi_axis, psi_boundary):
    """Normalized toroidal-flux radius at one poloidal flux value, by integrating q.

    Parameters
    ----------
    psi_val : float
        Poloidal flux at which to evaluate [Wb/rad].
    q_profile : callable
        Safety factor as a function of *normalized* poloidal flux, called on
        ``[0, 1]`` [-].
    psi_axis : float
        Poloidal flux on the magnetic axis [Wb/rad].
    psi_boundary : float
        Poloidal flux at the plasma boundary [Wb/rad].

    Returns
    -------
    float
        The normalized toroidal-flux radius, 0 on axis and 1 at the boundary [-].

    Convention
    ----------
    This is **rho_tor_norm**, the toroidal coordinate, not the poloidal one:
    ``sqrt(int q dpsi_n / int_0^1 q dpsi_n)``. It equals ``sqrt(psi_norm)``, which
    is ``rho_pol_norm``, only for a flat safety factor. The parameter name says
    "rho" for historical reasons and the two must not be interchanged; see
    :func:`vaft.process.equilibrium.derive_radial_coordinates` for the record-based
    form that returns all three coordinates at once and says which is unavailable.

    Because both integrals are of the same profile over the same variable, the
    result is independent of the flux storage convention: the sign and any factor
    of ``2*pi`` cancel in the ratio.

    Applicability
    -------------
    Machine-independent.

    Limitations
    -----------
    Takes the safety factor as a callable on normalized flux, so a caller holding
    it on a grid must interpolate first. Adaptive quadrature is evaluated afresh
    for every call, which makes converting a whole profile point by point far more
    expensive than :func:`derive_radial_coordinates`, and it assumes the profile
    is integrable to the boundary.

    Provenance
    ----------
    .. [1] The toroidal flux definition ``Phi = int q dpsi`` and its normalized
       radius, as the IMAS data dictionary defines ``rho_tor_norm``.
    """
    from scipy.integrate import quad

    # First normalize psi
    psi_N = (psi_val - psi_axis) / (psi_boundary - psi_axis)

    # Define the integration for numerator and denominator
    def integrand(x):
        return q_profile(x)

    # Compute the integrals
    numerator, _ = quad(integrand, 0, psi_N)
    denominator, _ = quad(integrand, 0, 1.0)

    # Return normalized radius
    return np.sqrt(numerator / denominator)

Normalized toroidal-flux radius at one poloidal flux value, by integrating q.

NameTypeUnitDescription
psi_valfloatWb/rad

Poloidal flux at which to evaluate.

q_profilecallable-

Safety factor as a function of normalized poloidal flux, called on [0, 1].

psi_axisfloatWb/rad

Poloidal flux on the magnetic axis.

psi_boundaryfloatWb/rad

Poloidal flux at the plasma boundary.

Returns
—float-

The normalized toroidal-flux radius, 0 on axis and 1 at the boundary.

Convention

This is rho_tor_norm, the toroidal coordinate, not the poloidal one: sqrt(int q dpsi_n / int_0^1 q dpsi_n). It equals sqrt(psi_norm), which is rho_pol_norm, only for a flat safety factor. The parameter name says “rho” for historical reasons and the two must not be interchanged; see vaft.process.equilibrium.derive_radial_coordinates for the record-based form that returns all three coordinates at once and says which is unavailable.

Because both integrals are of the same profile over the same variable, the result is independent of the flux storage convention: the sign and any factor of 2*pi cancel in the ratio.

Applicability · Limitations · Provenance

Applicability

Machine-independent.

Limitations

Takes the safety factor as a callable on normalized flux, so a caller holding it on a grid must interpolate first. Adaptive quadrature is evaluated afresh for every call, which makes converting a whole profile point by point far more expensive than derive_radial_coordinates, and it assumes the profile is integrable to the boundary.

Provenance

  1. The toroidal flux definition Phi = int q dpsi and its normalized radius, as the IMAS data dictionary defines rho_tor_norm.

psi_to_rz

Machine-independentConvention-sensitive source
psi_to_rz(psiN_1d, f_1d, psi_RZ, psi_axis, psi_lcfs, fill_outside='zero')

Also exported as psi_to_RZ.

Map a flux-surface profile onto the two-dimensional grid through psi.

NameTypeUnitDescription
psiN_1darray_like-

Normalized poloidal flux the profile is given on.

f_1darray_likeany

The profile, in whatever unit it has.

psi_RZarray_likeWb/rad

Poloidal flux on the grid, indexed (R, Z).

psi_axisfloatWb/rad

Poloidal flux on the magnetic axis.

psi_lcfsfloatWb/rad

Poloidal flux at the last closed flux surface.

fill_outsidestr, optional-

"zero" (default) zeroes every cell outside 0 <= psiN <= 1; "edge" leaves the clamped profile there, so a cell continues at the nearest end value.

Returns
f_RZnp.ndarrayany

The profile on the grid, zero outside the boundary unless fill_outside="edge".

psiN_RZnp.ndarray-

Normalized poloidal flux on the grid.

Convention

The flux map is indexed major radius first. Outside the boundary the value is zero, not the edge value and not a NaN, so a sum over the grid is already a plasma-only integral – as far as 0 <= psiN <= 1 is the plasma, which near the coils it is not (see volume_average). Pass fill_outside="edge" when the map is to be weighted by plasma_cell_weights: an outline-weighted edge cell can sit just past psiN = 1, and a zero there biases the average low (0.5-0.8 % for a profile whose edge value is 30 % of its core, on the packaged samples). Because only the normalized flux is used, the absolute unit of the three flux arguments cancels: weber and weber per radian give the same answer as long as all three agree.

Raises

  • ValueError The profile and its abscissa are not one-dimensional and of equal length, or fill_outside is neither "zero" nor "edge".
Processing steps · Applicability · Limitations · Provenance

Processing steps

  1. Normalize the flux map to (psi - psi_axis)/(psi_lcfs - psi_axis).
  2. Sort the profile by its abscissa, clamp the normalized map into the profile’s own span, and interpolate linearly.
  3. Zero every cell outside the closed region.

Applicability

Machine-independent.

Limitations

Linear interpolation, so a profile sampled coarsely near a steep gradient is smoothed. Clamping means a grid cell outside the profile’s own span takes the nearest profile value before the zeroing mask is applied, which matters only for a profile that does not reach the boundary.

Provenance

  1. The sort, clamp and interpolate sequence reproduces the legacy MATLAB workflow’s mapping, kept so that results match it cell for cell.

r_at_z_extremum

Machine-independentConvention-sensitive source
r_at_z_extremum(r_seg, z_seg, *, upper)
Show source vaft/process/equilibrium.py, lines 2740–2784
def r_at_z_extremum(r_seg: np.ndarray, z_seg: np.ndarray, *, upper: bool) -> float:
    """Major radius where a contour reaches its highest or lowest point.

    Parameters
    ----------
    r_seg : array_like
        Major radius of the contour points [m].
    z_seg : array_like
        Height of the contour points [m].
    upper : bool
        ``True`` for the highest point, ``False`` for the lowest [-].

    Returns
    -------
    float
        The major radius at that extremum [m].

    Convention
    ----------
    Sub-vertex, not nearest-vertex. Taking the major radius at the sampled vertex
    of extreme height is off by several percent in triangularity, because the true
    extremum falls between vertices. A parabola is fitted to height over the three
    points around the extreme vertex, and the major radius is interpolated there.

    Applicability
    -------------
    Machine-independent.

    Limitations
    -----------
    Falls back to the extreme vertex itself for a contour of fewer than three
    points. The parabola is local, so a contour sampled too coarsely to resolve
    its own curvature near the extremum is still limited by that sampling.

    Provenance
    ----------
    .. [1] Consumed by :func:`contour_shape_parameters` for both triangularities,
       which is where the several-percent error would otherwise land.
    .. [2] The arithmetic is
       :func:`vaft.formula.equilibrium.r_at_z_extremum_from_RZ_contour`; this is
       the process-layer name for it, so the two layers cannot drift apart.
    """
    from vaft.formula.equilibrium import r_at_z_extremum_from_RZ_contour

    return r_at_z_extremum_from_RZ_contour(r_seg, z_seg, upper=upper)

Major radius where a contour reaches its highest or lowest point.

NameTypeUnitDescription
r_segarray_likem

Major radius of the contour points.

z_segarray_likem

Height of the contour points.

upperbool-

True for the highest point, False for the lowest.

Returns
—floatm

The major radius at that extremum.

Convention

Sub-vertex, not nearest-vertex. Taking the major radius at the sampled vertex of extreme height is off by several percent in triangularity, because the true extremum falls between vertices. A parabola is fitted to height over the three points around the extreme vertex, and the major radius is interpolated there.

Applicability · Limitations · Provenance

Applicability

Machine-independent.

Limitations

Falls back to the extreme vertex itself for a contour of fewer than three points. The parabola is local, so a contour sampled too coarsely to resolve its own curvature near the extremum is still limited by that sampling.

Provenance

  1. Consumed by contour_shape_parameters for both triangularities, which is where the several-percent error would otherwise land.
  2. The arithmetic is vaft.formula.equilibrium.r_at_z_extremum_from_RZ_contour; this is the process-layer name for it, so the two layers cannot drift apart.

radial_to_psi

Machine-independentConvention-sensitive source
radial_to_psi(r, psi_R, psi_Z, psi)
Show source vaft/process/equilibrium.py, lines 191–243
def radial_to_psi(r, psi_R, psi_Z, psi):
    """Poloidal flux at a major radius on the midplane row of the flux map.

    Parameters
    ----------
    r : float
        Major radius at which to evaluate [m].
    psi_R : array_like
        Major-radius grid axis of the flux map, increasing [m].
    psi_Z : array_like
        Height grid axis of the flux map [m].
    psi : array_like
        Poloidal flux on the grid, indexed ``(R, Z)`` [Wb/rad].

    Returns
    -------
    float
        Poloidal flux at *r*, in the same unit the input map was in [Wb/rad].

    Convention
    ----------
    The flux map is indexed major radius first. The row taken is the one whose
    height is nearest zero, not an interpolation between rows, so on a grid with
    no node at the midplane the result is the flux on the nearest row rather than
    on the midplane itself. The unit is whatever the caller's map carries; nothing
    here rescales it, so a weber map returns weber.

    Applicability
    -------------
    Machine-independent.

    Limitations
    -----------
    Cubic interpolation along one row, so *r* must lie inside the grid's radial
    span and the result is only as smooth as the map. A machine whose midplane is
    not at zero height needs the row selected before calling.

    Provenance
    ----------
    .. [1] The midplane-row convention follows the EFIT-style flux maps this
       package reads, where the grid is centred on the machine midplane.
    """
    # Find the index of Z=0 in psi_Z array
    z0_idx = np.argmin(np.abs(psi_Z))

    # Extract the psi values at Z=0
    psi_at_z0 = psi[:, z0_idx]

    # Create 1D interpolation function
    psi_interp = interp1d(psi_R, psi_at_z0, kind='cubic')

    # Return interpolated value
    return float(psi_interp(r))

Poloidal flux at a major radius on the midplane row of the flux map.

NameTypeUnitDescription
rfloatm

Major radius at which to evaluate.

psi_Rarray_likem

Major-radius grid axis of the flux map, increasing.

psi_Zarray_likem

Height grid axis of the flux map.

psiarray_likeWb/rad

Poloidal flux on the grid, indexed (R, Z).

Returns
—floatWb/rad

Poloidal flux at r, in the same unit the input map was in.

Convention

The flux map is indexed major radius first. The row taken is the one whose height is nearest zero, not an interpolation between rows, so on a grid with no node at the midplane the result is the flux on the nearest row rather than on the midplane itself. The unit is whatever the caller’s map carries; nothing here rescales it, so a weber map returns weber.

Applicability · Limitations · Provenance

Applicability

Machine-independent.

Limitations

Cubic interpolation along one row, so r must lie inside the grid’s radial span and the result is only as smooth as the map. A machine whose midplane is not at zero height needs the row selected before calling.

Provenance

  1. The midplane-row convention follows the EFIT-style flux maps this package reads, where the grid is centred on the machine midplane.

rational_surfaces

Machine-independentConvention-sensitive source
rational_surfaces(psi_norm, q, *, q_targets=(), resonances=(), rho_tor_norm=None)

Every flux surface where the safety factor takes a requested rational value.

The one place a q_target -> radius question is answered: the 2-D and 1-D rational-surface overlays of vaft.plot and any mode analysis that needs q = m/n read their locations from here. Requests come as plain values (q_targets) or as magnetic harmonics (resonances); harmonics with the same m/n share one surface. Every crossing is returned, so a reversed-shear profile reports both of its q = 2 surfaces.

NameTypeUnitDescription
psi_normarray_like-

Normalized poloidal flux, increasing over its finite samples.

qarray_like-

Safety factor on psi_norm, of either sign.

q_targetsiterable of float, optional-

Rational values to locate, compared with |q|.

resonancesiterable of (int, int), optional-

Magnetic harmonics (m, n), each locating |q| = |m/n|.

rho_tor_normarray_like, optional-

An authentic normalized toroidal-flux radius on the same grid; the roots carry no toroidal radius without it.

Returns
—tuple of RationalSurface-

One record per distinct requested value, ascending in q_target, each holding its harmonics, its status and its roots ordered outward.

Convention

Resonance is |q| = |m/n|: the sign the source’s COCOS gives q (sgn(Ip) sgn(B0) in the 11 family) never decides whether a surface exists. rho_pol_norm is sqrt(psi_norm); rho_tor_norm is never derived from psi_norm here, since the two agree only for a flat q.

Raises

  • ValueError Nothing is requested, a request is not a positive value or a whole non-zero (m, n), the arrays are not one-dimensional and of equal length, fewer than two samples are finite, or psi_norm does not increase.
Processing steps · Applicability · Limitations · Provenance

Processing steps

  1. Merge the requests into distinct values of |q|, keeping which harmonics named each.
  2. Split the profile into runs of finite samples; a gap is never bridged.
  3. In each run, take every node where |q| equals the value and every bracket where |q| - q_target changes sign, interpolated linearly (the bracketing of find_rational_surfaces).
  4. Map each root to rho_pol_norm = sqrt(psi_norm) and, when given, to rho_tor_norm by the same interpolation weight.

Applicability

Machine-independent. Any equilibrium profile on an increasing flux grid.

Limitations

Linear interpolation, with the accuracy find_rational_surfaces quantifies; interpolate q onto a finer grid first if that is not enough. A sample within a relative 1e-9 of the value (RATIONAL_SURFACE_RTOL) is taken to equal it, so rounding noise on a |q| that hovers at the value yields one root, not one per sign flip. Nothing is extrapolated: a value the profile reaches only beyond its last finite sample is reported absent. A q flat at the value over an interval yields one root, at its start. Roots are not associated across time slices; each slice is resolved on its own.

Provenance

  1. find_rational_surfaces for the bracketing, which this shares rather than repeats; issue #506 for the request and record semantics.

resistive_layer_at

Machine-independent source
resistive_layer_at(psi_n_surfaces, *, psi_norm, t_e, n_e, ion_mass_amu=1.0, z_eff=None, ln_lambda=None)

Resistivity and mass density at flux surfaces someone else located.

The companion to resistive_layer_parameters, for when the surfaces are already known – read back from a solver that reported its own, which is the only way to be sure the values line up with what that solver will index.

NameTypeUnitDescription
psi_n_surfacesarray_like-

Normalized poloidal flux of each surface, ordered as the consumer expects them.

psi_normarray_like-

Normalized poloidal flux of t_e/n_e.

t_earray_likeeV

Electron temperature.

n_earray_likem^-3

Electron density.

ion_mass_amufloat, optional-

Mass of the bulk ion in atomic mass units; 1 (hydrogen) by default.

z_efffloat-

Effective ion charge; omitting it is deprecated as in resistive_layer_parameters (#1188).

ln_lambdafloat-

Coulomb logarithm; omitting it is deprecated as in resistive_layer_parameters (#1188).

Returns
—dict of str to ndarrayOhm m

mass_density [kg m^-3], the t_e [eV] and n_e [m^-3] each was computed from, and the Spitzer resistivity eta.

Raises

  • ValueError A kinetic profile does not match its coordinate in length, psi_norm is not strictly increasing, or ion_mass_amu is not positive.
Processing steps · Applicability · Limitations · Provenance

Processing steps

  1. Interpolate t_e and n_e linearly onto psi_n_surfaces.
  2. Evaluate the Spitzer resistivity at each, and take the mass density as n_e * ion_mass_amu * m_p.

Applicability

Machine-independent.

Limitations

As resistive_layer_parameters: Spitzer carries no neoclassical correction and diverges as T_e goes to zero, and the mass density ignores impurity dilution.

Provenance

  1. #716.

resistive_layer_parameters

Machine-independent source
resistive_layer_parameters(psi_norm, q, n, *, t_e, n_e, psi_norm_kinetic=None, ion_mass_amu=1.0, z_eff=None, ln_lambda=None, m_range=None)

Resistivity and mass density at each rational surface of a toroidal mode.

RDCON’s asymptotic matching (rmatch) asks for one resistivity and one mass density per rational surface; STRIDE, an ideal outer-region code, takes neither. Matching needs them per surface because the resistive layer width and the reconnection rate are set locally. This composes the two things needed to answer that from an equilibrium and its kinetic profiles: where the surfaces are, and what the plasma is like there.

NameTypeUnitDescription
psi_normarray_like-

Normalized poloidal flux of the q profile, increasing.

qarray_like-

Safety factor on psi_norm.

nint-

Toroidal mode number; its sign is ignored.

t_earray_likeeV

Electron temperature.

n_earray_likem^-3

Electron density.

psi_norm_kineticarray_like, optional-

Normalized poloidal flux of t_e/n_e; psi_norm by default, which requires the kinetic profiles to already be on the q grid.

ion_mass_amufloat, optional-

Mass of the bulk ion in atomic mass units; 1 (hydrogen) by default, which is what VEST runs.

z_efffloat-

Effective ion charge, passed to the Spitzer resistivity. Omitting it is deprecated (#1188): it falls back to 2 with a FutureWarning and will raise in 0.9.

ln_lambdafloat-

Coulomb logarithm, passed to the Spitzer resistivity. Omitting it is deprecated (#1188): it falls back to 17 with a FutureWarning and will raise in 0.9.

m_rangetuple of int, optional-

Forwarded to find_rational_surfaces.

Returns
—dict of str to ndarrayOhm m

m, q_rational and psi_n_rational as find_rational_surfaces returns them, ordered outward, plus mass_density [kg m^-3], the t_e [eV] and n_e [m^-3] each was computed from, and the Spitzer resistivity eta.

Raises

  • ValueError A kinetic profile does not match its coordinate in length, or ion_mass_amu is not positive. Propagates find_rational_surfaces’s own validation.
Processing steps · Applicability · Limitations · Provenance

Processing steps

  1. Locate the rational surfaces with find_rational_surfaces.
  2. Interpolate t_e and n_e linearly onto those surfaces.
  3. Evaluate vaft.formula.equilibrium.spitzer_resistivity_from_T_e_Z_eff_ln_Lambda at each, and take the mass density as n_e * ion_mass_amu * m_p, which assumes quasineutrality with a single bulk ion species.

Applicability

Machine-independent. The ion_mass_amu default of 1 is the only VEST choice, and it is a keyword.

Limitations

Spitzer resistivity carries no neoclassical trapped-particle correction, so it underestimates the parallel resistivity of a spherical tokamak, where the trapped fraction is large; the returned eta is a lower bound in that sense.

It also diverges as T_e goes to zero. A reconstruction whose T_e reaches zero at the separatrix therefore yields a non-finite eta at any rational surface that sits on that point, and surfaces within a few percent of it are dominated by however the profile was extrapolated rather than by measurement. Both are reported rather than clipped, because the right floor is a property of the discharge and not of this function.

The mass density ignores impurities: with a non-unit z_eff the bulk ion density is below n_e, so this overestimates it by roughly the dilution factor.

Provenance

  1. #716 – the packaged rmatch.in supplied a single scalar where the code wants one value per rational surface, so rmatch stopped before writing its global solution.

rho_to_psi

Machine-independentConvention-sensitive source
rho_to_psi(rho, q_profile, psi_axis, psi_boundary, tol=1e-06)
Show source vaft/process/equilibrium.py, lines 311–374
def rho_to_psi(rho, q_profile, psi_axis, psi_boundary, tol=1e-6):
    """Poloidal flux at one normalized toroidal-flux radius, by root finding.

    The inverse of :func:`psi_to_rho`, obtained numerically because the forward
    map is an integral with no closed-form inverse.

    Parameters
    ----------
    rho : float
        Normalized toroidal-flux radius, between 0 and 1 [-].
    q_profile : callable
        Safety factor as a function of normalized poloidal flux [-].
    psi_axis : float
        Poloidal flux on the magnetic axis, one end of the bracket [Wb/rad].
    psi_boundary : float
        Poloidal flux at the plasma boundary, the other end [Wb/rad].
    tol : float, optional
        Relative tolerance passed to the root finder [-].

    Returns
    -------
    float
        The poloidal flux whose toroidal radius is *rho* [Wb/rad].

    Convention
    ----------
    *rho* is the toroidal coordinate, matching :func:`psi_to_rho`, not
    ``sqrt(psi_norm)``. The returned flux is in whatever unit *psi_axis* and
    *psi_boundary* were given in, since the bracket sets the scale.

    Defaults
    --------
    ``tol = 1e-6`` is a numerical convenience: the forward map is monotonic and
    smooth, so Brent's method reaches it in few iterations, and a tighter value
    buys accuracy the quadrature underneath does not have.

    Applicability
    -------------
    Machine-independent.

    Limitations
    -----------
    Brackets on the axis and boundary flux, so it assumes the forward map is
    monotonic between them, which needs a safety factor of one sign. Each
    iteration runs the forward quadrature, making this the most expensive way to
    convert a coordinate; invert an interpolated table instead when converting
    many points.

    Provenance
    ----------
    .. [1] :func:`psi_to_rho`, whose definition this inverts.
    """
    from scipy.optimize import root_scalar

    def objective(psi):
        return psi_to_rho(psi, q_profile, psi_axis, psi_boundary) - rho

    # Find psi value that gives desired rho
    result = root_scalar(objective, 
                        bracket=[psi_axis, psi_boundary],
                        method='brentq',
                        rtol=tol)

    return result.root

Poloidal flux at one normalized toroidal-flux radius, by root finding.

The inverse of psi_to_rho, obtained numerically because the forward map is an integral with no closed-form inverse.

NameTypeUnitDescription
rhofloat-

Normalized toroidal-flux radius, between 0 and 1.

q_profilecallable-

Safety factor as a function of normalized poloidal flux.

psi_axisfloatWb/rad

Poloidal flux on the magnetic axis, one end of the bracket.

psi_boundaryfloatWb/rad

Poloidal flux at the plasma boundary, the other end.

tolfloat, optional-

Relative tolerance passed to the root finder.

Returns
—floatWb/rad

The poloidal flux whose toroidal radius is rho.

Convention

rho is the toroidal coordinate, matching psi_to_rho, not sqrt(psi_norm). The returned flux is in whatever unit psi_axis and psi_boundary were given in, since the bracket sets the scale.

Defaults · Applicability · Limitations · Provenance

Defaults

tol = 1e-6 is a numerical convenience: the forward map is monotonic and smooth, so Brent’s method reaches it in few iterations, and a tighter value buys accuracy the quadrature underneath does not have.

Applicability

Machine-independent.

Limitations

Brackets on the axis and boundary flux, so it assumes the forward map is monotonic between them, which needs a safety factor of one sign. Each iteration runs the forward quadrature, making this the most expensive way to convert a coordinate; invert an interpolated table instead when converting many points.

Provenance

  1. psi_to_rho, whose definition this inverts.

romero_flux_balance

Machine-independentConvention-sensitive source
romero_flux_balance(time, I_p, psi_boundary, psi_equilibrium, R_p, I_ni)

Romero’s exact voltage and volt-second balance over a sampled discharge.

Differentiates the boundary and equilibrium fluxes and the plasma current, evaluates every term of Romero’s balance with vaft.formula.transformer, and integrates the three volt-second budgets, so the residual of each identity is visible sample by sample and cumulatively.

NameTypeUnitDescription
timenp.ndarrays

Sample times, strictly increasing, at least three.

I_pnp.ndarrayA

Plasma current at each time, non-zero, same sense throughout.

psi_boundarynp.ndarrayWb

Boundary flux $\psi_B$, full flux in Romero’s sign.

psi_equilibriumnp.ndarrayWb

Current-weighted flux $\psi_C$ from vaft.formula.transformer.current_weighted_flux_from_psi_j_dS, same convention.

R_pnp.ndarrayOhm

Plasma resistance at each time, or one value for all.

I_ninp.ndarrayA

Non-inductively driven current at each time, or one value; 0 asserts a purely Ohmic discharge.

Returns
—dict of str to np.ndarray-

Per sample: time [s]; L_i [H] and dL_i_dt [H/s]; dI_p_dt [A/s]; the voltages V_B, V_C (from $-\dot\psi_C$), V_R, V_I $= L_i\dot I_p + \tfrac12 I_p\dot L_i$, V_C_from_L_i and V_C_from_I_p (Romero’s eqs. 39 and 40 solved for $V_C$) and balance_residual $= V_B - V_R - V_I$ [V]; and, cumulative from the first sample, Phi_B $=\int V_B\,dt$, Phi_B_direct $= -(\psi_B - \psi_B(t_0))$, Phi_R, Phi_I and Phi_closure $= \Phi_B - \Phi_R - \Phi_I$ [Wb].

Convention

Romero’s signs: full-weber flux and $V = -\dot\psi$. This is not vaft.formula.equilibrium.loop_voltage_from_total_flux’s convention (#354); a COCOS-11 equilibrium has to be brought to this one before the call, and step 1 refuses fluxes that were not.

What each residual measures. With $L_i$ read from the same fluxes, $V_B - V_C = \mathrm{d}(L_i I_p)/\mathrm{d}t$ holds to differencing error on its own, so the three $V_C$ and balance_residual all disagree only through $V_R$ – they test the resistance estimate and $\hat I$, not the equilibria. Phi_B against Phi_B_direct tests the differencing and the integration alone: the two agree to the trapezoid error whatever the physics.

Raises

  • ValueError Arrays of different lengths, fewer than three samples, a time axis that does not strictly increase, a non-finite input, a zero plasma current or one that changes sign, or fluxes whose sign says they are not in Romero’s convention [-].
Processing steps · Applicability · Limitations · Provenance

Processing steps

  1. $L_i = (\psi_C - \psi_B)/I_p$ at every sample (vaft.formula.transformer.internal_inductance_from_psi_C_psi_B_I_p), which refuses a flux in the opposite convention.
  2. $\dot I_p$, $\dot L_i$, $V_B = -\dot\psi_B$ and $V_C = -\dot\psi_C$ with vaft.process.numerical.time_derivative.
  3. $V_R = R_p(I_p - \hat I)$ and $V_I = L_i\dot I_p + \tfrac12 I_p\dot L_i$.
  4. The two inferred $V_C$: eq. (39) from $\dot L_i$ and $V_R$, eq. (40) from $\dot I_p$, $V_B$ and $V_R$.
  5. Cumulative trapezoidal integrals of $V_B$, $V_R$ and $V_I$ from the first sample, and the direct flux change $-\Delta\psi_B$ beside them.

Applicability

Machine-independent. Closed-flux phase only: $\psi_C$ and $L_i$ need an equilibrium with a boundary, so a start-up window before flux closure must be cut off by the caller, and a zero $I_p$ is refused rather than divided by.

Limitations

All inputs must already be on one time axis, matched by time and not by index; nothing here resamples. Differentiation amplifies equilibrium noise, and $\dot L_i$ from reconstructed fluxes is the noisiest term – smooth upstream if the residuals are dominated by it. $R_p$ and $\hat I$ are the caller’s estimates, and the balance cannot tell which one is wrong.

Provenance

  1. J. A. Romero and JET-EFDA contributors, Nucl. Fusion 50 (2010) 115002, eqs. (12), (23)-(27), (34)-(40); the volt-second split follows issue #781, Sec. 5.

scale_boundary_conformal

Machine-independentConvention-sensitive source
scale_boundary_conformal(R_bdry, Z_bdry, scale, center=None)
Show source vaft/process/equilibrium.py, lines 1573–1647
def scale_boundary_conformal(
    R_bdry: np.ndarray,
    Z_bdry: np.ndarray,
    scale: float,
    center: tuple[float, float] | None = None,
) -> tuple[np.ndarray, np.ndarray]:
    """Scale a closed boundary about a centre, giving a contour conformal to it.

    ``(R, Z) -> center + scale * ((R, Z) - center)``, so the result has the same
    shape as the input and lies inside it for ``scale < 1``.

    Parameters
    ----------
    R_bdry : array_like
        Major radius of the boundary points [m].
    Z_bdry : array_like
        Height of the boundary points [m].
    scale : float
        Similarity factor; below 1 shrinks, above 1 grows [-].
    center : tuple of float, optional
        Centre to scale about. The boundary's bounding-box centre when not
        given [m].

    Returns
    -------
    tuple of numpy.ndarray
        The scaled ``(R, Z)``, closed, in the input's point order [m].

    Raises
    ------
    ValueError
        ``R_bdry`` and ``Z_bdry`` differ in length, or ``scale`` is not finite
        and positive.

    Convention
    ----------
    The default centre is the bounding-box centre rather than the area
    centroid, matching what the shape code reports as the geometric axis.

    Applicability
    -------------
    Machine-independent. Any closed contour; nothing here is specific to a
    boundary or to an equilibrium.

    Limitations
    -----------
    A purely geometric offset. It does not consult the flux map, which is the
    point when the contour is a reconstructed LCFS -- a psi_N band would carry
    the interior psi's error into the result, and this does not. The scaled
    contour lies strictly inside the original only for a boundary star-shaped
    about ``center``; otherwise it can fold over itself, and the caller has to
    decide what that means.

    Provenance
    ----------
    .. [1] The conformal-annulus construction of M. W. Bongard et al., Phys.
       Plasmas 23 (2016), as used by
       :func:`virial_alpha_conformal_annulus`.
    """
    R_bdry = np.asarray(R_bdry, dtype=float).reshape(-1)
    Z_bdry = np.asarray(Z_bdry, dtype=float).reshape(-1)
    if R_bdry.size != Z_bdry.size:
        raise ValueError("R_bdry and Z_bdry must have the same length.")
    scale = float(scale)
    if not np.isfinite(scale) or scale <= 0.0:
        raise ValueError(f"scale must be finite and positive; got {scale}.")

    R_bdry, Z_bdry = _ensure_closed_boundary(R_bdry, Z_bdry)
    if center is None:
        R_c = 0.5 * (float(np.min(R_bdry)) + float(np.max(R_bdry)))
        Z_c = 0.5 * (float(np.min(Z_bdry)) + float(np.max(Z_bdry)))
    else:
        R_c, Z_c = float(center[0]), float(center[1])

    return R_c + scale * (R_bdry - R_c), Z_c + scale * (Z_bdry - Z_c)

Scale a closed boundary about a centre, giving a contour conformal to it.

(R, Z) -> center + scale * ((R, Z) - center), so the result has the same shape as the input and lies inside it for scale < 1.

NameTypeUnitDescription
R_bdryarray_likem

Major radius of the boundary points.

Z_bdryarray_likem

Height of the boundary points.

scalefloat-

Similarity factor; below 1 shrinks, above 1 grows.

centertuple of float, optionalm

Centre to scale about. The boundary’s bounding-box centre when not given.

Returns
—tuple of numpy.ndarraym

The scaled (R, Z), closed, in the input’s point order.

Convention

The default centre is the bounding-box centre rather than the area centroid, matching what the shape code reports as the geometric axis.

Raises

  • ValueError R_bdry and Z_bdry differ in length, or scale is not finite and positive.
Applicability · Limitations · Provenance

Applicability

Machine-independent. Any closed contour; nothing here is specific to a boundary or to an equilibrium.

Limitations

A purely geometric offset. It does not consult the flux map, which is the point when the contour is a reconstructed LCFS – a psi_N band would carry the interior psi’s error into the result, and this does not. The scaled contour lies strictly inside the original only for a boundary star-shaped about center; otherwise it can fold over itself, and the caller has to decide what that means.

Provenance

  1. The conformal-annulus construction of M. W. Bongard et al., Phys. Plasmas 23 (2016), as used by virial_alpha_conformal_annulus.

shafranov_integrals

Machine-independentConvention-sensitive source
shafranov_integrals(R_bdry, Z_bdry, B_p_bdry, R_grid, Z_grid, B_R_grid, B_Z_grid, R_0=None, Z_0=None, p_boundary=0.0, B_ref=None, cell_weights=None, volume=None)

The three Shafranov boundary integrals and the virial coefficient.

NameTypeUnitDescription
R_bdryarray_likem

Major radius of the boundary contour.

Z_bdryarray_likem

Height of the boundary contour.

B_p_bdryarray_likeT

Poloidal field magnitude on that contour.

R_gridarray_likem

Major-radius grid, as an axis or a mesh.

Z_gridarray_likem

Height grid, as an axis or a mesh.

B_R_gridarray_likeT

Major-radius field component on the grid.

B_Z_gridarray_likeT

Height field component on the grid.

R_0float, optionalm

Reference major radius. The boundary’s geometric centre when not given

Z_0float, optionalm

Reference height, likewise.

p_boundaryfloat, optionalPa

Pressure at the boundary.

B_reffloat, optionalT

Field the integrals are normalized by. The boundary-average poloidal field when not given.

cell_weightsarray_like, optional-

Per-cell plasma area fractions, from fractional_cell_weights_from_boundary.

volumefloat, optionalm^3

Plasma volume, computed from the boundary when not given.

Returns
—tuple of float-

(S1, S2, S3, alpha), all dimensionless. Zeros when the conditioned boundary has too few points or the reference volume or field is not positive.

Convention

The boundary must run counter-clockwise, because the outward unit normal is taken as (dZ/dl, -dR/dl), which points outward only then. The contour is conditioned through prepare_boundary_for_shafranov on entry, so a clockwise input is corrected rather than silently inverting every integral.

The COCOS is inherited, not declared: the poloidal field arrays are taken as given, so whatever convention produced them is the convention these integrals are in. Passing a field computed in the wrong one gives plausible numbers that are wrong by that factor.

Applicability · Limitations · Provenance

Applicability

Machine-independent.

Limitations

Returns zeros rather than raising when the geometry is degenerate, so a caller must distinguish a real zero from a refusal. Without cell_weights the plasma mask is per-cell and the boundary is resolved only to the grid.

Provenance

  1. The Shafranov boundary integrals as EFIT evaluates them; the virial internal inductance built from them is vaft.formula.equilibrium.virial_li_from_S_alpha_rt.

solovev_example

Machine-independentConvention-sensitive source
solovev_example(topology='limited', *, major_radius=0.4, aspect_ratio=1.7, elongation=1.7, triangularity=0.35, plasma_current=100000.0, toroidal_field=0.1, a_parameter=0.9, resolution=129, convention=11)

Build a shaped analytic Solov’ev equilibrium in a chosen boundary topology.

A teaching example: the same exactly-known kind of equilibrium as solovev_to_equilibrium, but with a basis rich enough to put an X-point on the boundary, so that a limited, a lower single-null and a double-null plasma can be compared on otherwise equal footing. The shape is prescribed and the flux map is the exact solution that has it.

NameTypeUnitDescription
topologystr, optional-

"limited", "single_null" (lower) or "double_null".

major_radiusfloat, optionalm

Geometric major radius R0 of the boundary.

aspect_ratiofloat, optional-

R0 over the minor radius, greater than one.

elongationfloat, optional-

Boundary elongation kappa, positive.

triangularityfloat, optional-

Boundary triangularity delta, of magnitude below one.

plasma_currentfloat, optionalA

Toroidal plasma current the flux is scaled to; its sign is the current direction.

toroidal_fieldfloat, optionalT

Vacuum toroidal field at R0, sets F at the boundary.

a_parameterfloat, optional-

Cerfon-Freidberg A: the normalized source is (1-A) x**2 + A, so A near one puts the current in F dF/dpsi and lowers beta, A = 0 is a pure-pressure equilibrium.

resolutionint, optional-

Grid points across the major radius; the height axis gets the same spacing.

conventionint, optional-

COCOS index to export in, 1 to 18.

Returns
—EquilibriumData-

A gridded record with psi, the magnetic axis, the last closed flux surface, a limiter (touching the boundary for "limited", a clear rectangular wall for the diverted cases), linear-in-flux pressure and F**2, the constant sources, the plasma current and vacuum field, and metadata carrying the requested shape, the requested X-points and the basis coefficients.

Convention

Built in COCOS 11 – flux in full weber, psi increasing outward for a positive current, B_Z = -(1/R) dpsi/dR per radian – and converted by convert_cocos when another convention is asked for. In per-radian units Psi = psi0 * psi(x, y) solves Delta* Psi = -mu0 R**2 p' - F F' with p' = -psi0 (1 - A)/(mu0 R0**4) and F F' = -psi0 A / R0**2; psi_boundary = 0, p = p' Psi and F**2 = (B0 R0)**2 + 2 F F' Psi. The toroidal current density is Delta* Psi / (mu0 R), the sign consistent with this convention’s poloidal field. The requested X-points are at (R0 - 1.1 delta a, -+1.1 kappa a).

Raises

  • ValueError An unknown topology, a non-physical shape, a non-positive radius or resolution, an invalid COCOS index, a constraint set that is singular for the shape, or a boundary that does not close around the axis on the grid.
Processing steps · Defaults · Applicability · Limitations · Provenance

Processing steps

  1. Solve the Cerfon-Freidberg constraint set for the topology in normalized coordinates x = R/R0, y = Z/R0: seven up-down symmetric terms for limited and double-null, twelve for the asymmetric single-null.
  2. Evaluate the normalized flux on a grid framing the plasma and the X-points, and locate the magnetic axis as the O-point of the analytic field.
  3. Extract the closed boundary surface, stepping a hair inside the separatrix when it passes through an X-point.
  4. Scale the flux so the toroidal current integrated over the boundary interior equals plasma_current, which fixes both sources.

Defaults

The shape defaults are an assumed value chosen to look like a small spherical tokamak such as VEST: R0 = 0.4 m, aspect ratio 1.7 (a = 0.235 m), elongation 1.7, triangularity 0.35, 100 kA and 0.1 T. a_parameter = 0.9 is likewise an assumed value that keeps the central toroidal beta near five percent at those settings (about 0.2 kPa on axis); resolution = 129 is a numerical convenience.

Applicability

Machine-independent. The defaults merely resemble VEST; nothing here reads VEST data.

Limitations

Linear sources only, so profiles are not realistic, and beta, current and shape are not independent: a_parameter trades pressure against poloidal current at fixed shape. The shape parameters are imposed on the boundary at its outboard, inboard and top points; elsewhere the boundary is whatever the basis gives. On an X-point side the separatrix follows Cerfon and Freidberg in putting the X-point at 1.1 times the nominal height and triangularity, so a double-null boundary measures about 1.1 kappa and 1.1 delta, and a lower single-null one is 1.1 times the request below the midplane only. The limiter is a construction to make the topology classifiable, not a machine wall.

Provenance

  1. A. J. Cerfon and J. P. Freidberg, “One size fits all” analytic solutions to the Grad-Shafranov equation, Phys. Plasmas 17, 032502 (2010): the particular solution, the twelve homogeneous polynomials and the limited, double-null and single-null constraint sets used here.
  2. Solov’ev (1968), for the linear-source closed form.

solovev_shape_constraints

Machine-independentConvention-sensitive source
solovev_shape_constraints(*, major_radius, minor_radius, elongation, triangularity, topology='smooth', x_point=None, psi_boundary=0.0)

The Cerfon-Freidberg boundary conditions for a shaped, possibly diverted, plasma.

Turns an elongation, triangularity and minor radius into the point, slope and curvature conditions that make an analytic Solov’ev boundary have that shape, with the X-points the topology asks for. Solving them with solve_solovev_constraints gives the equilibrium.

NameTypeUnitDescription
major_radiusfloatm

Geometric major radius R0 of the boundary, positive.

minor_radiusfloatm

Minor radius a, positive and below major_radius.

elongationfloat-

Boundary elongation kappa, positive.

triangularityfloat-

Boundary triangularity delta, of magnitude below one.

topologystr, optional-

"smooth" (a closed boundary with no X-point), "double_null", "lower_single_null" or "upper_single_null".

x_pointtuple of float, optionalm

(R, Z) of the X-point, the upper one for a double null; refused for "smooth". Defaults to Cerfon and Freidberg’s (R0 - 1.1 delta a, +-1.1 kappa a).

psi_boundaryfloat, optionalWb/rad

Flux the boundary and the separatrix carry.

Returns
—tuple of SolovevConstraint-

Seven conditions for "smooth" and "double_null", to be solved in the "cerfon_freidberg_even" basis, or twelve for a single null, to be solved in "cerfon_freidberg".

Convention

The outboard, inboard and top points of the boundary are (R0 +- a, 0) and (R0 - delta a, kappa a). The curvature conditions are Cerfon and Freidberg’s in metres: psi_ZZ + N1 psi_R = 0 outboard with N1 = -(1 + alpha)**2 / (a kappa**2), N2 = (1 - alpha)**2 / (a kappa**2) inboard, and psi_RR + N3 psi_Z = 0 at the top with N3 = -kappa / (a cos(alpha)**2), where alpha = arcsin(delta). An upper single null is the lower one mirrored in Z.

Raises

  • ValueError An unknown topology, a non-physical shape, an X-point given for a smooth boundary, or one on the wrong side of the midplane for the topology.
Applicability · Limitations · Provenance

Applicability

Machine-independent.

Limitations

The shape is imposed at three or four points only; elsewhere the boundary is what the basis gives, so a measured elongation or triangularity differs slightly from the request, and on an X-point side the boundary follows the X-point instead. The conditions are exact for the basis sizes above; with the classic five-term basis they are only met in a least-squares sense.

Provenance

  1. A. J. Cerfon and J. P. Freidberg, “One size fits all” analytic solutions to the Grad-Shafranov equation, Phys. Plasmas 17, 032502 (2010), Secs. III and IV: the point, slope and curvature conditions and the X-point placement at 1.1 times the nominal height and triangularity.

solovev_to_equilibrium

Machine-independentConvention-sensitive source
solovev_to_equilibrium(model, r, z, *, magnetic_axis=None, limiter=None, convention=11)

Export an analytic Solov’ev model as a gridded equilibrium record.

Turns the closed-form solution into the same record shape a GEQDSK or an ODS produces, so that an exactly known equilibrium can be fed to the same machinery as a measured one. That is what makes it useful as a reference.

NameTypeUnitDescription
modelSolovevEquilibrium-

The analytic model to evaluate.

rarray_likem

Major-radius grid axis, strictly positive and increasing.

zarray_likem

Height grid axis, increasing.

magnetic_axistuple of float, optionalm

The axis position, as (R, Z). Located from the flux map when not given.

limiterContour, optionalm

A limiting surface to carry onto the record.

conventionint, optional-

The COCOS index to declare and export in, 1 to 18.

Returns
—EquilibriumData-

A record on the requested grid carrying psi, its axis and boundary values, the flux surface, the profiles, the plasma current, the vacuum field, and the declared convention.

Convention

evaluate_solovev works in weber per radian under the requested convention’s orientation. The exported equilibrium is natively constructed in vaft.data.cocos.VAFT_INTERNAL_COCOS (11, full weber) and converted to convention via convert_cocos if different, reconciling both the 2*pi normalisation and the orientation sign across all COCOS conventions. Pressure and the magnetic axis are invariant under convention transforms.

The 2pi factor multiplies psi, its axis and boundary values and psi_1d, and divides the two d/dpsi sources pprime and ffprime. Nothing else is rescaled, deliberately: ip is integrated from the analytic j_phi, a current density that no flux-storage choice changes, and f, bt0 and pressure are physical fields too. Any orientation sign they carry under another *convention is applied by convert_cocos. That says nothing about whether the sign of evaluate_solovev’s j_phi is consistent with its poloidal field; that is a separate question (#966).

Raises

  • ValueError convention is outside 1 to 18, or the boundary flux does not form a closed contour enclosing the magnetic axis on this grid.
Applicability · Limitations · Provenance

Applicability

Machine-independent.

Limitations

The closed-boundary requirement is checked explicitly, stepping inward to 99 percent of the normalized flux before giving up, because an open boundary would silently corrupt the plasma current, the pressure integrals and every shape descriptor rather than failing. The grid must resolve the boundary well enough for that contour to close.

Provenance

  1. Solov’ev (1968) through evaluate_solovev, which supplies every field this exports.

solovev_xpoint_constraints

Machine-independent source
solovev_xpoint_constraints(r, z, *, psi=0.0)
Show source vaft/process/_equilibrium_parametric.py, lines 1841–1890
def solovev_xpoint_constraints(r: float, z: float, *, psi: float = 0.0) -> tuple[SolovevConstraint, ...]:
    """The three conditions that make a point an X-point on the boundary flux surface.

    A prescribed magnetic null is not a new kind of equilibrium, only three
    linear conditions on the flux: it takes the boundary value there and both
    of its first derivatives vanish.  This expands one X-point into those
    conditions for :func:`solve_solovev_constraints`.

    Parameters
    ----------
    r : float
        Major radius of the X-point, strictly positive [m].
    z : float
        Height of the X-point [m].
    psi : float, optional
        Flux the separatrix carries, normally the model's boundary flux
        [Wb/rad].

    Returns
    -------
    tuple of SolovevConstraint
        The flux, radial-slope and vertical-slope conditions at the point [-].

    Raises
    ------
    ValueError
        *r* is not positive.

    Applicability
    -------------
    Machine-independent.

    Limitations
    -----------
    A vanishing gradient is necessary, not sufficient: the solved point can
    still be an extremum.  Confirm the saddle from the sign of the Hessian
    determinant returned by :func:`evaluate_solovev`.

    Provenance
    ----------
    .. [1] A. J. Cerfon and J. P. Freidberg, Phys. Plasmas 17, 032502 (2010),
       Sec. IV, for imposing the X-point as a flux and two slope conditions.
    """
    if r <= 0:
        raise ValueError("the X-point radius must be positive")
    return (
        SolovevConstraint(float(r), float(z), "psi", float(psi)),
        SolovevConstraint(float(r), float(z), "dpsi_dr", 0.0),
        SolovevConstraint(float(r), float(z), "dpsi_dz", 0.0),
    )

The three conditions that make a point an X-point on the boundary flux surface.

A prescribed magnetic null is not a new kind of equilibrium, only three linear conditions on the flux: it takes the boundary value there and both of its first derivatives vanish. This expands one X-point into those conditions for solve_solovev_constraints.

NameTypeUnitDescription
rfloatm

Major radius of the X-point, strictly positive.

zfloatm

Height of the X-point.

psifloat, optionalWb/rad

Flux the separatrix carries, normally the model’s boundary flux

Returns
—tuple of SolovevConstraint-

The flux, radial-slope and vertical-slope conditions at the point.

Raises

  • ValueError r is not positive.
Applicability · Limitations · Provenance

Applicability

Machine-independent.

Limitations

A vanishing gradient is necessary, not sufficient: the solved point can still be an extremum. Confirm the saddle from the sign of the Hessian determinant returned by evaluate_solovev.

Provenance

  1. A. J. Cerfon and J. P. Freidberg, Phys. Plasmas 17, 032502 (2010), Sec. IV, for imposing the X-point as a flux and two slope conditions.

solve_guazzotto_freidberg

Machine-independentConvention-sensitive source
solve_guazzotto_freidberg(topology='limited', *, inverse_aspect_ratio, nu, elongation=None, triangularity=None, x_point_elongation=None, x_point_triangularity=None, alpha_range=(0.5, 6.0), series_terms=250, current_pedestal=0.0, pressure_pedestal=0.0, bootstrap_fraction=0.0, mach_number=0.0, adiabatic_index=None, allow_current_reversal=False)

Solve the Guazzotto-Freidberg analytic equilibrium for a shape and a beta parameter.

An exact solution of the Grad-Shafranov equation whose pressure and F**2 are quadratic in the flux, so that – unlike Solov’ev – the pressure, its gradient and the toroidal current all vanish at the plasma surface. The shape is imposed through the flux, slope and curvature at the midplane and top points (or an X-point), and the eigenvalue alpha that makes a non-trivial solution exist is found by scanning.

NameTypeUnitDescription
topologystr, optional-

"limited" (smooth, up-down symmetric, Miller model surface), "double_null" or "lower_single_null".

inverse_aspect_ratiofloat-

eps = a/R0, in (0, 1).

nufloat-

The source-balance parameter mu0 p0/(mu0 p0 + B0 dB/(1 + eps**2)), approximately the poloidal beta; 0 is force free.

elongationfloat, optional-

Elongation of the smooth (upper) boundary; required for limited and single-null.

triangularityfloat, optional-

Triangularity of the smooth (upper) boundary, magnitude below one.

x_point_elongationfloat, optional-

kappa_X, height of the X-point over the minor radius; required for diverted topologies.

x_point_triangularityfloat, optional-

delta_X, triangularity at the X-point, magnitude below one.

alpha_rangetuple of float, optional-

Interval scanned for the lowest eigenvalue.

series_termsint, optional-

Terms kept in the radial power series.

current_pedestalfloat, optional-

f_J, the edge-to-axis ratio of the toroidal current density (Part 2, Eq. 4.5), in [0, 1).

pressure_pedestalfloat, optional-

f_P = p(surf)/p(axis) (Part 2, Eq. 3.1), in [0, 1); it leaves the flux unchanged and enters the plasma parameters.

bootstrap_fractionfloat, optional-

f_B = 1 - I/I_hat, the edge-localized bootstrap current carried as a surface current (Part 2, Eq. 3.5), in [0, 1); plasma parameters only.

mach_numberfloat, optional-

M0, the toroidal flow Mach number on the axis (Part 2, Eq. 5.6).

adiabatic_indexfloat or None, optional-

gamma, 2 (adiabatic) or inf (incompressible); required with flow, the two cases whose source is a polynomial (Part 2, Eq. 5.8).

allow_current_reversalbool, optional-

Accept nu above nu_max, where the inboard edge current reverses, and mark the result status = "current_reversal".

Returns
—GuazzottoFreidbergEquilibrium-

The eigenvalue, the coefficients normalized so psi is one on the magnetic axis, the separation constants, the axis in (x, y), the eigen-condition residual and conditioning, and the inputs.

Convention

Normalized coordinates of Eq. (2.8): R = R0 sqrt(1 + eps**2 + 2 eps x), Z = a y, psi = Psi/Psi_0 with psi = 1 on the axis and 0 on the surface; eps_hat = 2 eps/(1 + eps**2). With flow the source is alpha**2 (1 + nu G(x)), G = (1 + eps_hat x)(1 + 2 M0**2 eps x/(1 + M0**2 eps**2))**(gamma/(gamma - 1)) - 1 (Part 2, Eq. 5.6). The lower single null places its X-point at (-x_X, -kappa_X) with x_X = delta_X + (eps/2) (1 - delta_X**2).

Raises

  • ValueError An unknown topology or missing shape input; invalid geometry, including kappa_X <= 2 sqrt(1 - delta_X**2) (Eq. 4.4); nu beyond nu_max (1/eps_hat, or -1/G(-1) with flow), where the inboard current reverses, unless allow_current_reversal; a pedestal or bootstrap fraction outside [0, 1); flow without adiabatic_index 2 or inf; or no eigenvalue in alpha_range, or no pedestal root below it (no physical root).
Processing steps · Defaults · Applicability · Limitations · Provenance

Processing steps

  1. Fix the separation constants by Eq. (3.3) and build the radial functions by the power series of Appendix A.
  2. For a trial alpha, set c_1 = 1 and solve all but the last matching condition; the last one’s normalized miss is Eq. (3.11).
  3. Scan alpha_range for the lowest minimum of that error, refine it, and accept it only if the error is at round-off level.
  4. Locate the magnetic axis as the maximum of psi and normalize psi to one there.
  5. With a current pedestal (Part 2, Eqs. 4.8-4.11), solve the inhomogeneous system psi_J = f_J/(1 - f_J) on the surface instead, and find the alpha below the pedestal-free eigenvalue at which psi_J(axis) = 1/(1 - f_J).

Defaults

series_terms = 250 follows the paper, which keeps 150 routinely and needs about 220 at eps = 0.75; alpha_range brackets every case the paper publishes (1.88 to 2.38). Both are numerical conveniences.

Applicability

Machine-independent.

Limitations

The paper’s Eq. (5.4 h, i) evaluate the single-null X-point conditions at (-delta_X, -kappa_X); this uses the X-point itself, (-x_X, -kappa_X), which is the reading that reproduces the published eigenvalues. Only the lowest eigenvalue is sought; the model surface is matched at three or four points only. With a current pedestal the midplane curvature conditions keep their pedestal-free values, so the X-point angle is no longer the model’s right angle (Part 2, Section 4.3). Part 2’s Table 2 cases C and D keep nu = 1.04 above the flow-reduced nu_max = 1.025; they need allow_current_reversal.

Provenance

  1. L. Guazzotto and J. P. Freidberg, J. Plasma Phys. 87, 905870303 (2021), doi:10.1017/S002237782100009X: Eqs. (2.8)-(2.9), (3.2)-(3.11), (4.3)-(4.7), (5.3)-(5.8) and Appendix A.
  2. L. Guazzotto and J. P. Freidberg, J. Plasma Phys. 87, 905870305 (2021), doi:10.1017/S0022377821000118: Eqs. (3.1)-(3.5), (4.1)-(4.11), (5.6)-(5.14) and Appendix B.

solve_solovev_constraints

Machine-independentConvention-sensitive source
solve_solovev_constraints(constraints, *, pprime, ffprime, rref, psi_boundary=0.0, pressure_boundary=0.0, f_boundary=1.0, f_sign=None, basis='classic', max_condition_number=1000000000000.0)

Solve for the Solov’ev coefficients that satisfy a set of geometric constraints.

The inverse of evaluate_solovev: given where the flux surface should pass, where it should be flat and how it should curve, recover the basis coefficients that put it there. This is how a Solov’ev model is shaped to a wanted boundary, and, with the richer bases, how an X-point is prescribed.

NameTypeUnitDescription
constraintssequence of SolovevConstraint-

At least as many as the basis has terms, each pinning the flux, one of its first or second derivatives, or a weighted combination of them at a point. Flux in Wb/rad, derivatives in Wb/(rad m^n).

pprimefloatPa rad/Wb

The pressure gradient against flux, constant by construction

ffprimefloatT^2 m^2 rad/Wb

The poloidal-current term’s gradient against flux, likewise constant

rreffloatm

Reference major radius that sets the logarithm’s origin in the basis and, for the Cerfon-Freidberg bases, the normalization length.

psi_boundaryfloat, optionalWb/rad

Flux value taken as the boundary, the zero point of the linear sources

pressure_boundaryfloat, optionalPa

Pressure at that boundary.

f_boundaryfloat, optionalT m

Poloidal current function at that boundary, signed: its sign is the direction of B_phi, both in F(psi) and in bt0 = f_boundary/rref. Must be non-zero.

f_signint or None, optional-

Sign given to the poloidal current when the square root is taken. None takes sign(f_boundary); an explicit +1 or -1 must agree with it. Breaking change in 0.8.0: through 0.7.x this defaulted to +1 and overrode the sign of f_boundary, so f_sign=-1 with a positive f_boundary was accepted (and gave an F(psi) whose sign disagreed with bt0); it is now refused – pass a signed f_boundary instead (#1307).

basisstr, optional-

"classic" (five up-down symmetric terms), "cerfon_freidberg_even" (seven symmetric terms, enough for a double null) or "cerfon_freidberg" (twelve terms including odd-Z ones, needed for an asymmetric single null).

max_condition_numberfloat, optional-

Largest condition number of the column-normalized constraint matrix accepted before the system is refused as ill-conditioned.

Returns
—SolovevEquilibrium-

The coefficients in basis, the inputs echoed, the numerical rank of the solve, the residual norm, and metadata with the constraint count and the condition number.

Convention

Same per-radian flux and same orientation as evaluate_solovev, whose basis this inverts. The basis size is not a tolerance but the dimension of the homogeneous solution space kept; anything less leaves the surface unpinned in some direction.

Raises

  • ValueError A zero f_boundary, an f_sign that contradicts the sign of f_boundary (#1307), an unknown basis, fewer constraints than basis terms, an unrecognized constraint kind, a constraint set whose rank is below the basis size, or one whose condition number exceeds max_condition_number.
Processing steps · Defaults · Applicability · Limitations · Provenance

Processing steps

  1. For each constraint, evaluate the analytic derivative the constraint names of every basis function, and of the particular integral of the two sources, at its point; a combination sums them with its weights.
  2. Move the particular integral to the right-hand side, so the unknowns are the homogeneous coefficients alone.
  3. Normalize each column, since the basis functions differ by powers of the radius, and solve by least squares.
  4. Refuse a rank-deficient or ill-conditioned system rather than returning an answer that is underdetermined in some direction.

Defaults

The boundary values default to a zero-flux, zero-pressure, unit-current reference, which is the natural normalization for a shape-only model. f_sign=None makes f_boundary the single source of the B_phi sign, so reversing the toroidal field is a matter of negating f_boundary alone. basis="classic" keeps the original five-term model. The condition limit of 1e12 is a numerical convenience: about four digits short of double precision, below which the least-squares answer still means something.

Applicability

Machine-independent.

Limitations

Constraints that are geometrically distinct can still be linearly dependent in the basis – an up-down symmetric basis gives an all-zero row for a vertical derivative on the midplane, for instance – in which case the rank check rejects them and the caller must move or add points. With more constraints than terms the solve is a least squares and the result satisfies none exactly; the residual norm reports how far off. A derivative-free point is not by itself an X-point: check the sign of the Hessian determinant from evaluate_solovev.

Provenance

  1. Solov’ev (1968); see evaluate_solovev for the basis being solved.
  2. A. J. Cerfon and J. P. Freidberg, Phys. Plasmas 17, 032502 (2010), for the extended bases and the point, slope and curvature constraints.

straight_field_line_angle_on_grid

Machine-independentConvention-sensitive source
straight_field_line_angle_on_grid(psi, r, z, psi_axis, psi_boundary, magnetic_axis, *, n_theta=256, n_surfaces=96, sqrt_psi_norm_min=0.02)
Show source vaft/process/equilibrium.py, lines 6490–6557
def straight_field_line_angle_on_grid(
    psi,
    r,
    z,
    psi_axis: float,
    psi_boundary: float,
    magnetic_axis,
    *,
    n_theta: int = 256,
    n_surfaces: int = 96,
    sqrt_psi_norm_min: float = 0.02,
) -> np.ndarray:
    """The PEST straight-field-line angle on every node of a flux map.

    Parameters
    ----------
    psi : array_like
        Poloidal flux on the grid, indexed ``(R, Z)`` [Wb or Wb/rad].
    r : array_like
        Major-radius grid axis [m].
    z : array_like
        Height grid axis [m].
    psi_axis : float
        Flux on the magnetic axis, in the unit of *psi* [Wb or Wb/rad].
    psi_boundary : float
        Flux on the boundary surface, in the unit of *psi* [Wb or Wb/rad].
    magnetic_axis : sequence of float
        ``(R, Z)`` of the magnetic axis [m].
    n_theta : int, optional
        Geometric-angle samples per surface, as in
        :func:`straight_field_line_map` [-].
    n_surfaces : int, optional
        Tabulated surfaces, as in :func:`straight_field_line_map` [-].
    sqrt_psi_norm_min : float, optional
        Innermost tabulated surface, as ``sqrt(psi_norm)`` [-].

    Returns
    -------
    numpy.ndarray
        ``theta*`` shaped ``(len(r), len(z))``, in ``[0, 2 pi)`` inside the
        boundary and NaN outside it [rad].

    Convention
    ----------
    The angle of :func:`straight_field_line_map`: PEST, zero on the outboard
    midplane, increasing in the sense of ``atan2(Z - Z_axis, R - R_axis)``.
    The array is indexed major radius first, like the flux map.

    Applicability
    -------------
    Machine-independent.

    Limitations
    -----------
    Node values only; a caller that needs the angle between nodes should keep
    the map from :func:`straight_field_line_map` and evaluate it there, since
    interpolating a wrapped angle across its ``2 pi`` seam is wrong.

    Provenance
    ----------
    .. [1] Grimm, Dewar and Manickam, J. Comput. Phys. 49, 94 (1983), through
       :func:`straight_field_line_map`.
    """
    sfl = straight_field_line_map(psi, r, z, psi_axis, psi_boundary, magnetic_axis,
                                  n_theta=n_theta, n_surfaces=n_surfaces,
                                  sqrt_psi_norm_min=sqrt_psi_norm_min)
    rr, zz = np.meshgrid(np.asarray(r, float).reshape(-1), np.asarray(z, float).reshape(-1), indexing="ij")
    return sfl.theta_star(rr, zz)

The PEST straight-field-line angle on every node of a flux map.

NameTypeUnitDescription
psiarray_likeWb or Wb/rad

Poloidal flux on the grid, indexed (R, Z).

rarray_likem

Major-radius grid axis.

zarray_likem

Height grid axis.

psi_axisfloatWb or Wb/rad

Flux on the magnetic axis, in the unit of psi.

psi_boundaryfloatWb or Wb/rad

Flux on the boundary surface, in the unit of psi.

magnetic_axissequence of floatm

(R, Z) of the magnetic axis.

n_thetaint, optional-

Geometric-angle samples per surface, as in straight_field_line_map.

n_surfacesint, optional-

Tabulated surfaces, as in straight_field_line_map.

sqrt_psi_norm_minfloat, optional-

Innermost tabulated surface, as sqrt(psi_norm).

Returns
—numpy.ndarrayrad

theta* shaped (len(r), len(z)), in [0, 2 pi) inside the boundary and NaN outside it.

Convention

The angle of straight_field_line_map: PEST, zero on the outboard midplane, increasing in the sense of atan2(Z - Z_axis, R - R_axis). The array is indexed major radius first, like the flux map.

Applicability · Limitations · Provenance

Applicability

Machine-independent.

Limitations

Node values only; a caller that needs the angle between nodes should keep the map from straight_field_line_map and evaluate it there, since interpolating a wrapped angle across its 2 pi seam is wrong.

Provenance

  1. Grimm, Dewar and Manickam, J. Comput. Phys. 49, 94 (1983), through straight_field_line_map.

straight_field_line_map

Machine-independentConvention-sensitive source
straight_field_line_map(psi, r, z, psi_axis, psi_boundary, magnetic_axis, *, n_theta=256, n_surfaces=96, sqrt_psi_norm_min=0.02)

The PEST straight-field-line poloidal angle of a flux map, built once.

NameTypeUnitDescription
psiarray_likeWb or Wb/rad

Poloidal flux on the grid, indexed (R, Z), in any storage family: only ratios and the geometry enter.

rarray_likem

Major-radius grid axis.

zarray_likem

Height grid axis.

psi_axisfloatWb or Wb/rad

Flux on the magnetic axis, in the unit of psi.

psi_boundaryfloatWb or Wb/rad

Flux on the boundary surface, in the unit of psi.

magnetic_axissequence of floatm

(R, Z) of the magnetic axis, the origin of the geometric angle.

n_thetaint, optional-

Geometric-angle samples per surface, uniform on one period.

n_surfacesint, optional-

Tabulated surfaces, uniform in sqrt(psi_norm).

sqrt_psi_norm_minfloat, optional-

Innermost tabulated surface, as sqrt(psi_norm); points closer to the axis take its angle offset.

Returns
—StraightFieldLineMap-

An object evaluating theta_star, psi_norm, grad_psi and the boundary test at arbitrary points, and solving any single surface exactly with surface(psi_norm).

Convention

The PEST angle, in which field lines are straight: theta* = 2 pi int dl/(R |grad psi|) / oint dl/(R |grad psi|) along a flux surface, so d phi / d theta* = q on every surface. The poloidal current function and the flux normalisation are constant on a surface and cancel, which is why neither F nor the COCOS storage family is needed.

Origin and direction are geometric, not COCOS. theta* = 0 on the outboard midplane ray (Z = Z_axis, R > R_axis) and it increases in the same sense as atan2(Z - Z_axis, R - R_axis): outboard, top, inboard, bottom. Whether that is the direction of the poloidal field depends on the equilibrium’s current direction, which a caller forming a helical phase must take from the field, not from this angle.

Raises

  • ValueError The flux map is not shaped (len(r), len(z)), the axis and boundary flux coincide, the boundary surface leaves the grid, or a surface is not star-shaped about the axis.
Processing steps · Applicability · Limitations · Provenance

Processing steps

  1. Fit a bicubic spline to the flux.
  2. Move the axis to the spline’s O-point. Along each geometric-angle ray from it, find where the normalized flux reaches one, rejecting a ray on which it is not monotonic.
  3. On every tabulated surface, solve the ray radius by Newton iteration on the spline and form rho / (R d psi_norm / d rho), which is dl / (R |grad psi|) per unit geometric angle.
  4. Integrate the rate spectrally into theta* - theta, zero at the outboard midplane.
  5. Spline that periodic offset over (sqrt(psi_norm), theta).

Applicability

Machine-independent.

Limitations

The supplied axis is moved to the flux spline’s own O-point when that is within two cells of it, so the angle’s origin is the axis of the map as gridded, not the solver’s; StraightFieldLineMap.magnetic_axis records where it went. Needs surfaces that every ray from the axis crosses once, which holds for the nested surfaces of a tokamak inside its boundary but not beyond an X-point, so the boundary surface itself must be closed on the grid. Inside the innermost tabulated surface the angle offset is frozen at that surface’s value; the angle is undefined on the axis itself. Accuracy is set by the flux spline, not by the table, because every surface is solved on the spline rather than on grid contours.

Provenance

  1. Grimm, Dewar and Manickam, J. Comput. Phys. 49, 94 (1983), the PEST coordinate system this angle belongs to.
  2. Sauter and Medvedev, Comput. Phys. Commun. 184, 293 (2013), for the straight-field-line relation d phi / d theta* = q.

straight_field_line_tables

Machine-independentConvention-sensitive source
straight_field_line_tables(theta_turns, r, z, magnetic_axis, *, jacobian, minimum_separation=1e-10)

Per-surface tables taking the geometric poloidal angle to the code’s own.

NameTypeUnitDescription
theta_turnsarray_like-

The code’s poloidal angle in turns, one value per row of r and z. A closing duplicate is dropped.

r, zarray_likem

Flux-surface geometry, (theta, psi).

magnetic_axissequence of floatm

(r, z) of the axis the geometric angle is measured about.

jacobianstrn/a

Which straight-field-line angle these are – hamada, pest, boozer and so on. Required and recorded, not inferred: the file does not carry it, and the mapping is meaningless without knowing which angle it lands in.

minimum_separationfloat, optionalrad

Geometric-angle spacing below which two samples are treated as one

Returns
—tuple of (ndarray, ndarray)rad

One (lab, sfl) pair per flux surface, both strictly increasing and spanning exactly one period, ready for lab_to_straight_field_line.

Convention

The angle arrives in turns, and nothing in the file says so. GPEC writes theta_dcon from 0 to 1 with no units attribute, so a caller that took it for radians would be wrong by 2 pi and the error would look like a mis-shaped plasma rather than a unit mistake. It is converted here, once, and the parameter is named for what it holds.

The geometric angle is atan2(z - z_axis, r - r_axis), which is the lab angle FLARE’s Poincare output is in; the straight-field-line angle is what GPEC and DCON label their harmonics by. The two agree only on a circular concentric equilibrium.

Raises

  • ValueError r and z disagree in shape, theta_turns does not match their first axis, an input is not finite, jacobian is empty, or a surface collapses to fewer than three distinct angles.
Processing steps · Applicability · Limitations · Provenance

Processing steps

  1. Drop the closing duplicate, which GPEC writes so its grid is periodic.
  2. Per surface, take the geometric angle about the axis.
  3. Roll so the table starts near the outboard midplane, then unwrap both angles together.
  4. Reverse when the geometric angle runs backwards, so the table increases whichever way the surface was traced.
  5. Drop samples closer than minimum_separation, which a near-stagnation point in the geometric angle can produce.
  6. Close the period by repeating the first entry shifted by 2 pi, in the direction the straight-field-line angle runs along the table.

Applicability

Machine-independent.

Limitations

The relabelling is one flux surface at a time and says nothing about the radial coordinate: a caller with points between surfaces has to decide how to interpolate across them, and doing that in the lab angle is not the same as doing it in the straight-field-line one.

Provenance

  1. library/flare_plotting.py::_build_lab_to_sfl_inverse_tables and _interp_periodic_inverse.

trace_field_line

Machine-independentConvention-sensitive source
trace_field_line(R0, Z0, phi0, b_field, *, dphi=np.float64(0.017453292519943295), max_length_m=50.0, direction='forward', wall_r=None, wall_z=None, r_bounds=None, z_bounds=None)

Trace one magnetic field line through the equilibrium field.

NameTypeUnitDescription
R0floatm

Starting major radius.

Z0floatm

Starting height.

phi0floatrad

Starting toroidal angle.

b_fieldcallable-

A function of (R, Z) returning (B_R, B_Z, B_phi), normally from make_equilibrium_field_interpolator.

dphifloat, optionalrad

Fixed step in toroidal angle.

max_length_mfloat, optionalm

Path length at which to stop.

directionstr, optional-

"forward", "backward", or "both" for the two branches joined at the start point.

wall_rarray_like, optionalm

Major radius of the limiting polygon.

wall_zarray_like, optionalm

Height of the limiting polygon.

r_boundstuple of float, optionalm

Major-radius range outside which to stop.

z_boundstuple of float, optionalm

Height range outside which to stop.

Returns
—dict of str to Any-

phi in radians, R and Z in metres, arc_length_m in metres, and termination_reason as a string.

Convention

The toroidal angle is the integration variable, not path length or time, which is why a vanishing toroidal field terminates the trace rather than slowing it. Points are always returned in order of increasing angle whatever the direction traced. The arc length is cumulative along the returned array from zero at its first point, so for "both" it measures from the backward end and not from the start point.

Processing steps · Defaults · Applicability · Limitations · Provenance

Processing steps

  1. Integrate dR/dphi = R*B_R/B_phi and dZ/dphi = R*B_Z/B_phi by fixed-step fourth-order Runge-Kutta in the toroidal angle.
  2. After each step, test the termination conditions in order: leaving the limiting polygon, exceeding the path length, leaving the coordinate bounds, or a numerically zero toroidal field.
  3. For "both", trace each branch and concatenate them anchored at the start.

Defaults

The one-degree step and the fifty-metre limit are numerical conveniences: fine enough to resolve a tokamak field line’s pitch, and long enough to reach a wall from anywhere inside a small machine.

Applicability

Machine-independent. The wall polygon is where a machine’s own geometry enters, and it is supplied by the caller.

Limitations

Fixed step, so accuracy is set by the step alone and there is no error control; a line near a separatrix, where the pitch changes fastest, is the least accurate. Terminating on the coordinate bounds is a safety net against extrapolating far outside the equilibrium grid, not a physical condition.

Provenance

  1. The field-line equations in toroidal-angle form; the field itself comes from make_equilibrium_field_interpolator.

validate_equilibrium

Deprecated source
validate_equilibrium(equilibrium, *, required_for='general')
Show source vaft/process/_equilibrium_parametric.py, lines 457–468
def validate_equilibrium(
    equilibrium: EquilibriumData, *, required_for: str = "general"
) -> ValidationReport:
    """Deprecated compatibility wrapper for :func:`check_equilibrium_requirements`."""
    warnings.warn(
        "vaft.process.validate_equilibrium() is deprecated; use "
        "check_equilibrium_requirements(). The name now belongs to the scientific "
        "equilibrium assessment in vaft.validation (issues #253, #337).",
        DeprecationWarning,
        stacklevel=2,
    )
    return check_equilibrium_requirements(equilibrium, required_for=required_for)

Deprecated compatibility wrapper for check_equilibrium_requirements.

virial_alpha_conformal_annulus

Machine-independentConvention-sensitive source
virial_alpha_conformal_annulus(R_grid, Z_grid, B_R_grid, B_Z_grid, R_bdry, Z_bdry, thickness=0.1, samples_per_axis=5)

Virial closure coefficient from a thin annulus conformal to the LCFS.

alpha_2 = 2 * sum(R Bz^2 w dA) / sum(R Bp^2 w dA) over an annulus bounded by the boundary and a copy of it scaled by 1 - thickness. Obtains the closure coefficient without the volume integral over the plasma interior, which a reconstruction that fixes the boundary but not the internal field cannot supply.

NameTypeUnitDescription
R_gridarray_likem

Major-radius grid, as an axis or a mesh.

Z_gridarray_likem

Height grid, as an axis or a mesh.

B_R_gridarray_likeT

Major-radius field component on that grid.

B_Z_gridarray_likeT

Vertical field component on that grid.

R_bdryarray_likem

Major radius of the boundary points.

Z_bdryarray_likem

Height of the boundary points.

thicknessfloat, optional-

Annulus width as a fraction of the distance from the centre to the boundary, not an absolute length; default 0.1.

samples_per_axisint, optional-

Sub-samples per cell axis in the area-fraction weighting; default 5.

Returns
—dict-

alpha the closure coefficient, NaN unless valid; valid whether the annulus could be integrated; reason why not, or None; n_cells the summed annulus weight.

Convention

alpha = 2 <R Bz^2> / <R Bp^2>, 1 for a symmetric circular cross-section and 2 in the infinitely elongated limit. Only squares of the field enter, so the sign convention the components arrive in does not matter.

Processing steps · Defaults · Assumptions · Applicability · Limitations · Provenance

Processing steps

Orient the boundary counter-clockwise, build the inner contour with scale_boundary_conformal, take the annulus weight map as the difference of two fractional_cell_weights_from_boundary maps so that cells cut by either contour carry their area fraction, reject an annulus too small or too narrow for the grid, then integrate over the cells that survive a shared finite-field mask.

Defaults

thickness=0.1 is a validated workflow default: near the accuracy optimum on analytic Solov’ev equilibria and still thousands of cells on a reconstruction-sized grid. samples_per_axis=5 is a numerical convenience matching fractional_cell_weights_from_boundary, and it sets how thin an annulus can be resolved.

Assumptions

The annulus is built by geometric conformal scaling, not as a psi_N band: a psi_N band would reintroduce the dependence on reconstructed interior flux that this estimate exists to avoid.

Applicability

Machine-independent. Any boundary with a poloidal-field map covering it.

Limitations

Abstains – NaN with a reason – rather than returning a number when the boundary leaves the grid, when the annulus holds fewer than MIN_ANNULUS_CELLS, or when it is narrower than MIN_ANNULUS_WIDTH_SUBSAMPLES sub-samples at its narrowest point, in which case the integral would report the grid phase rather than the field. Cells whose field is only partly finite are dropped from both integrals together, since dropping one from the denominator alone would bias alpha up. As thickness -> 0 this tends to virial_alpha_thin_annulus, whose accuracy against the true alpha is slightly worse – a finite annulus samples some interior, and that helps.

Provenance

  1. M. W. Bongard et al., Phys. Plasmas 23 (2016), the low-aspect-ratio virial closure and its annulus estimate of alpha.
  2. Measured against analytic Solov’ev equilibria in test/test_virial_alpha_approximations.py.

virial_alpha_thin_annulus

Machine-independentConvention-sensitive source
virial_alpha_thin_annulus(R_bdry, Z_bdry, B_R_bdry, B_Z_bdry, center=None, mode='conformal', n_points=512)

Virial closure coefficient in the thin-annulus limit, from the boundary field.

The exact thickness -> 0 limit of virial_alpha_conformal_annulus, evaluating 2 * closed_integral(R Bz^2 w dl) / closed_integral(R Bp^2 w dl). Needs only the field on the LCFS, so unlike the volume integral it asks nothing of the plasma interior.

NameTypeUnitDescription
R_bdryarray_likem

Major radius of the boundary points.

Z_bdryarray_likem

Height of the boundary points.

B_R_bdryarray_likeT

Major-radius field component at those points.

B_Z_bdryarray_likeT

Vertical field component at those points.

centertuple of float, optionalm

Centre the conformal annulus shrinks towards. The boundary’s bounding-box centre when not given.

modestr, optional-

"conformal" (default) weights each segment by the support function, the limit of a conformally scaled annulus; "uniform" weights every segment equally, the limit of a uniform-offset annulus.

n_pointsint, optional-

Segments in the arc-length resampling; default 512.

Returns
—float-

The closure coefficient, or NaN when the contour is degenerate or folds over the centre.

Convention

alpha = 2 <R Bz^2> / <R Bp^2>, which is 1 for a symmetric circular cross-section and 2 in the infinitely elongated limit. Only squares of the field enter, so the result does not depend on the sign convention the components arrive in.

Raises

  • ValueError mode is neither "conformal" nor "uniform", or the boundary and field arrays differ in length.
Processing steps · Assumptions · Applicability · Limitations · Provenance

Processing steps

Drop non-finite points, close the contour, remove zero-length segments, orient counter-clockwise so the normal (dZ, -dR)/dl points outward, resample to n_points by arc length, then take the segment-midpoint quadrature through vaft.formula.equilibrium.virial_alpha_from_R_Bz_Bp_dl.

Assumptions

mode="conformal" assumes the boundary is star-shaped about center, so that shrinking it about that point sweeps a simple annulus. A conformal annulus has local width t * (x - c).n, so its area element is t * (x - c).n * dl and the constant t cancels; that support function is the weight. Weighting uniformly instead is a different region and on a VEST-like boundary a ~5% different answer, so the two modes are not interchangeable.

Applicability

Machine-independent. Any closed boundary with its poloidal field, from a reconstruction or an analytic equilibrium.

Limitations

Returns NaN rather than a number when more than MAX_NONCONVEX_ARC_FRACTION of the arc length has negative support: the conformal annulus folds over itself there, and an unclamped negative weight would subtract from both integrals. mode="uniform" makes no such assumption and still answers. How closely the limit matches a finite annulus is a property of the equilibrium, not of this quadrature.

Provenance

  1. M. W. Bongard et al., Phys. Plasmas 23 (2016), the low-aspect-ratio virial closure and its annulus estimate of alpha.
  2. Measured against analytic Solov’ev equilibria in test/test_virial_alpha_approximations.py.

volume_average

Machine-independentConvention-sensitive source
volume_average(f_RZ, psiN_RZ, R, Z, weights=None)

Volume average of a gridded quantity over the confined region.

NameTypeUnitDescription
f_RZarray_likeany

The quantity on the grid, in whatever unit it has.

psiN_RZarray_like-

Normalized poloidal flux on the same grid, used only as the mask.

Rarray_likem

Major-radius axis, or the full mesh.

Zarray_likem

Height axis, or the full mesh.

weightsarray_like, optional-

Fraction of each cell inside the plasma, from plasma_cell_weights; replaces the normalized-flux mask when given.

Returns
favgfloatany

The volume-weighted average of f_RZ inside the boundary.

volumefloatm^3

The volume the average was taken over.

Convention

The volume element is the axisymmetric 2*pi*R dR dZ, so cells at large major radius weigh proportionally more; this is a torus average, not a cross-sectional one. Accepts either one-dimensional axes or a full mesh.

Pass weights whenever the slice has a boundary outline. Without them the plasma is every cell with normalized flux between zero and one and a positive major radius – a flux threshold, not a containment test. Outside the plasma psi is not monotonic and turns over by the coils, so the threshold admits exterior cells: on the packaged VEST samples the flux-mask volume is 1.7 to 18 times the plasma’s, and a volume-averaged pressure comes out 40-50 % low. With plasma_cell_weights from the outline both match the contour-traced reference to 0.1 % and 1 %.

Applicability · Limitations · Provenance

Applicability

Machine-independent.

Limitations

Cell areas come from a gradient of the axes, so the outermost cells are one-sided and a strongly non-uniform grid is approximated. The flux mask is a per-cell test with no sub-cell weighting; weights carry each boundary cell’s area fraction instead.

Provenance

  1. The axisymmetric volume element, and the same normalized-flux mask psi_to_rz applies.

results matching ""

    No results matching ""