Process reference: transport_state

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

Overview

Routine transport analysis (issues #1428, #1431, #1435) asks the same upstream question before any solver runs: which equilibrium and kinetic-profile slice describe this instant, where did each kinetic quantity come from, and is the result fit to hand to a model? This module answers it once, so a TGLF and a NEO result that are later compared describe the same plasma rather than two independently resolved ones.

The chain::

ODS (equilibrium + core_profiles)
-> equilibrium slice at the state time, core_profiles slice within dt   [times]
-> ion temperature: measured -> pressure-inferred (#1426) -> policy      [ti]
-> composition closure via prepare_gacode_profile(impurity=, z_eff=)     [GACODEProfile]
-> ResolvedTransportState (profile + per-quantity provenance + identity)
-> canonical species state + physics projection (#1567)                  [optional]
-> solver readiness: assess_tglf_readiness / assess_neo_readiness

Nothing here runs a solver. Execution stays with vaft.code.gacode; this layer only decides what is run and records why.

time_efit_s is the equilibrium slice time and is the state’s time. The core_profiles slice is matched to it by time within an explicit tolerance, never by index. r_over_a is GACODE’s rmin/rmin[-1] on the converted profile, whose outer edge is rho_max; it is not rho_tor_norm. The ODS passed in is never modified: the ion-temperature policy writes into a deep copy.

Functions

assess_neo_readinessDecide whether a resolved state can go to NEO.
assess_tglf_readinessDecide whether a resolved state, and each requested surface, can go to TGLF.
classical_heat_fluxesClassical perpendicular heat fluxes at one surface, from a TGLF local input. Convention
inferred_ti_supportedWhether a surface’s solver input is independent of the inferred-Ti gap fill. Convention
physics_parametersThe physics half of a GACODE config, with runtime-only fields dropped.
project_transport_stateOne physics projection of a resolved state’s species, with the profile its solver reads.
resolve_transport_stateResolve one plasma state into a shared GACODE profile with per-quantity provenance.
run_identityDeterministic identity of one solver calculation on one resolved state.
surface_toroidal_fieldThe toroidal field at a surface’s centre, |B_centr| R_centr / R.
transport_partitionSigned model fluxes and their magnitude partition at one surface of one state.
transport_species_stateThe canonical species state of a resolved transport state: the species its solvers read. Convention

assess_neo_readiness

Machine-independent source
assess_neo_readiness(state, surfaces=(0.3, 0.4, 0.5, 0.6, 0.7, 0.8), *, projection=None)
Show source vaft/process/transport_state.py, lines 1114–1164
def assess_neo_readiness(state: ResolvedTransportState,
                         surfaces: Sequence[float] = DEFAULT_SURFACES, *,
                         projection: Optional["TransportProjection"] = None) -> ReadinessReport:
    """Decide whether a resolved state can go to NEO.

    Parameters
    ----------
    state : ResolvedTransportState
        From :func:`resolve_transport_state` [-].
    surfaces : sequence of float
        The r/a NEO will solve; with an inferred-Ti gap fill, at least one must have
        an input independent of the fill [-].
    projection : TransportProjection, optional
        A ``neoclassical`` projection of this state; NEO accepts only explicit
        species, which is the state's own list [-].

    Returns
    -------
    ReadinessReport
        ``ready``/``conditional``/``insufficient``; NEO is a profile code, so no
        per-surface entries [-].

    Raises
    ------
    ValueError
        A projection of another state, for another physics, or a reduced one.

    Processing steps
    ----------------
    1. An unresolved state is ``insufficient``.
    2. ``GACODEProfile.check_neo_requirements`` (the executable-input contract) must
       report nothing missing.
    3. Declared assumptions make the state ``conditional``.

    Applicability
    -------------
    Machine-independent.
    """
    if not state.resolved:
        return ReadinessReport("neo", "insufficient", reasons=state.reasons)
    state, projected = _projected_state(state, projection, "neo")
    missing = tuple(state.profile.check_neo_requirements())
    if missing:
        return ReadinessReport("neo", "insufficient",
                               reasons=tuple(f"missing_{name}" for name in missing))
    if state.fill_probe is not None and not any(
            inferred_ti_supported(state, r, solver="neo") for r in surfaces):
        return ReadinessReport("neo", "insufficient", reasons=("no_surface_independent_of_ti_fill",))
    conditions = _state_conditions(state) + projected
    return ReadinessReport("neo", "conditional" if conditions else "ready",
                           conditions=conditions)

Decide whether a resolved state can go to NEO.

NameTypeUnitDescription
stateResolvedTransportState-

From resolve_transport_state.

surfacessequence of float-

The r/a NEO will solve; with an inferred-Ti gap fill, at least one must have an input independent of the fill.

projectionTransportProjection, optional-

A neoclassical projection of this state; NEO accepts only explicit species, which is the state’s own list.

Returns
—ReadinessReport-

ready/conditional/insufficient; NEO is a profile code, so no per-surface entries.

Raises

  • ValueError A projection of another state, for another physics, or a reduced one.
Processing steps · Applicability

Processing steps

  1. An unresolved state is insufficient.
  2. GACODEProfile.check_neo_requirements (the executable-input contract) must report nothing missing.
  3. Declared assumptions make the state conditional.

Applicability

Machine-independent.

assess_tglf_readiness

Machine-independent source
assess_tglf_readiness(state, surfaces=(0.3, 0.4, 0.5, 0.6, 0.7, 0.8), *, config=None, projection=None)
Show source vaft/process/transport_state.py, lines 1032–1111
def assess_tglf_readiness(
    state: ResolvedTransportState,
    surfaces: Sequence[float] = DEFAULT_SURFACES,
    *,
    config: Any = None,
    projection: Optional["TransportProjection"] = None,
) -> ReadinessReport:
    """Decide whether a resolved state, and each requested surface, can go to TGLF.

    Parameters
    ----------
    state : ResolvedTransportState
        From :func:`resolve_transport_state` [-].
    surfaces : sequence of float
        Requested surfaces as r/a [-].
    config : TGLFConfig, optional
        Only ``n_species`` is consulted, through ``prepare_tglf_input`` [-].
    projection : TransportProjection, optional
        A ``turbulence`` projection of this state (:func:`project_transport_state`);
        the surfaces are then built on its profile, and a reduced projection adds
        ``species_projection_<method>`` to ``conditions``.  Default: the state's own
        species list, unchanged [-].

    Returns
    -------
    ReadinessReport
        State verdict plus one :class:`SurfaceReadiness` per surface, each carrying
        the built ``TGLFInput`` when ready [-].

    Raises
    ------
    ValueError
        A projection of another state, for another physics, or by a method TGLF
        does not accept.

    Processing steps
    ----------------
    1. An unresolved state is ``insufficient`` and no surface is built; a projection
       is checked against the state and the solver.
    2. Each surface is projected with ``prepare_tglf_input``; its documented refusal
       becomes a reason code, then ``check_tglf_requirements`` and finite gradients
       are required.
    3. A runnable state with declared assumptions is ``conditional``.

    Applicability
    -------------
    Machine-independent.
    """
    if not state.resolved:
        return ReadinessReport("tglf", "insufficient", reasons=state.reasons)
    state, projected = _projected_state(state, projection, "tglf")
    from vaft.code.gacode.tglf.inputs import LocalConversionError, prepare_tglf_input

    results = []
    for radius in surfaces:
        try:
            local = prepare_tglf_input(state.profile, float(radius), config=config)
        except LocalConversionError as error:
            results.append(SurfaceReadiness(float(radius), _surface_code(error), str(error)))
            continue
        if not inferred_ti_supported(state, float(radius), local=local):
            results.append(SurfaceReadiness(float(radius), "ti_not_inferred_here",
                                             "this surface's solver input depends on the inferred-Ti gap fill"))
            continue
        absent = local.check_tglf_requirements()
        if absent:
            code = "invalid_gradient" if set(absent) & {"rlns", "rlts"} else "local_conversion_failure"
            results.append(SurfaceReadiness(float(radius), code, ", ".join(absent), local))
            continue
        results.append(SurfaceReadiness(float(radius), "ready", "", local))
    if not any(s.ready for s in results):
        return ReadinessReport("tglf", "insufficient", reasons=("no_ready_surface",),
                               surfaces=tuple(results))
    conditions = _state_conditions(state) + projected
    if any(s.ready and s.local_input.vexb_shear is None for s in results):
        # prepare_tglf_input does not derive ExB shear yet (#553), so TGLF runs with
        # its own zero whatever rotation core_profiles carries.
        conditions = (*conditions, "no_exb_shear")
    return ReadinessReport("tglf", "conditional" if conditions else "ready",
                           conditions=conditions, surfaces=tuple(results))

Decide whether a resolved state, and each requested surface, can go to TGLF.

NameTypeUnitDescription
stateResolvedTransportState-

From resolve_transport_state.

surfacessequence of float-

Requested surfaces as r/a.

configTGLFConfig, optional-

Only n_species is consulted, through prepare_tglf_input.

projectionTransportProjection, optional-

A turbulence projection of this state (project_transport_state); the surfaces are then built on its profile, and a reduced projection adds species_projection_<method> to conditions. Default: the state’s own species list, unchanged.

Returns
—ReadinessReport-

State verdict plus one SurfaceReadiness per surface, each carrying the built TGLFInput when ready.

Raises

  • ValueError A projection of another state, for another physics, or by a method TGLF does not accept.
Processing steps · Applicability

Processing steps

  1. An unresolved state is insufficient and no surface is built; a projection is checked against the state and the solver.
  2. Each surface is projected with prepare_tglf_input; its documented refusal becomes a reason code, then check_tglf_requirements and finite gradients are required.
  3. A runnable state with declared assumptions is conditional.

Applicability

Machine-independent.

classical_heat_fluxes

Machine-independentConvention-sensitive source
classical_heat_fluxes(local, b_tesla)

Classical perpendicular heat fluxes at one surface, from a TGLF local input.

NameTypeUnitDescription
localTGLFInput-

From assess_tglf_readiness, supplying n_e, T_e, Ti/Te, every ion’s n/n_e and charge, a/L_T, the main ion’s mass and a at the surface.

b_teslafloatT

Magnetic-field magnitude at the surface, e.g. surface_toroidal_field; must be non-zero and finite, else ValueError.

Returns
—ClassicalHeatFluxesW/m^2

r_over_a; electron_energy_flux_W_m2; ion_energy_flux_W_m2 keyed z=<charge> for the main ion; chi_e_m2_s, chi_i_m2_s; the collision times, Coulomb logarithms, coulomb_log_valid and model (CLASSICAL_MODEL); ClassicalHeatFluxes.as_record gives the JSON mapping.

Convention

kappa_perp,e = gamma_1'(Z_eff) n_e T_e / (m_e Omega_e^2 tau_e) and kappa_perp,i = 2 n_i T_i / (m_i Omega_i^2 tau_i); q = kappa T (a/L_T) / a, so a positive flux runs down the temperature gradient, the sign the TGLF and NEO mappers use. This is a physical flux of a stated reduced model, not the order-of-magnitude nu rho^2 reference scale (#780/#1112). This function writes nothing; vaft.machine_mapping.classical.core_transport_from_classical projects its records into core_transport (#1654).

Applicability · Limitations · Provenance

Applicability

Machine-independent.

Limitations

Electrons and one main ion only; no particle flux or thermal-force terms; unlike field ions enter tau_i in the like-particle form; slab geometry and B_pol neglected. coulomb_log_valid is False below 10 eV, where the electron form is not defined. gamma_1' is interpolated linearly in Z between Braginskii’s tabulated charges and is held at its Z = 4 value (3.6) for every Z_eff above 4, the Z = inf asymptote (3.25) being unreachable; below Z = 1 it is clamped to 4.66. The rest is listed in #1453.

Provenance

  1. S. I. Braginskii, “Transport processes in a plasma”, Rev. Plasma Phys. 1 (1965) 205, Table 2: kappa_perp,e coefficient gamma_1’(Z) (4.66, 4.0, 3.7, 3.6, 3.25 for Z = 1, 2, 3, 4, inf) and kappa_perp,i coefficient 2.
  2. NRL Plasma Formulary: tau_e = 3.44e5 T_e^1.5 / (n lnLambda), tau_i = 2.09e7 T_i^1.5 mu^0.5 / (n lnLambda Z^4) and the ion-ion lnLambda (cgs, eV), which the SI forms reproduce (test_transport_state.py). Every coefficient and collision time comes from vaft.formula.kinetic (#1899).

inferred_ti_supported

Machine-independentConvention-sensitive source
inferred_ti_supported(state, r_over_a, *, solver='tglf', local=None)
Show source vaft/process/transport_state.py, lines 482–539
def inferred_ti_supported(state: "ResolvedTransportState", r_over_a: float, *,
                          solver: str = "tglf", local: Any = None) -> bool:
    """Whether a surface's solver input is independent of the inferred-Ti gap fill.

    Parameters
    ----------
    state : ResolvedTransportState
        From :func:`resolve_transport_state` [-].
    r_over_a : float
        The surface [-].
    solver : str
        ``"tglf"`` compares the full TGLF local input; ``"neo"`` compares the ion
        temperatures and their log-gradients the way NEO reads them [-].
    local : TGLFInput, optional
        The surface's input already built from ``state.profile``, to avoid rebuilding
        it [-].

    Returns
    -------
    bool
        True when no gap was filled, or when the solver's input agrees (rtol 1e-4,
        atol 1e-4) between the profile and its ``fill_probe`` [-].

    Convention
    ----------
    GACODE differentiates on three grid points and evaluates with a spline over the
    whole profile, so a filled node can reach a surface several grid steps away.
    Rather than guess a margin, the gate builds the solver's input twice -- once from
    the profile, once from ``fill_probe``, whose gaps are filled with a different
    scale *and* offset -- and runs the surface only when nothing moves. TGLF's local
    input uses a not-a-knot spline; NEO (PROFILE_MODEL=2, ``expro_locsim``'s
    ``cub_spline1``) a natural one, so each solver is checked with its own.

    Applicability
    -------------
    Machine-independent.
    """
    probe = getattr(state, "fill_probe", None)
    if probe is None:
        return True
    if solver == "neo":
        return _neo_ion_inputs_agree(state.profile, probe, float(r_over_a))
    from vaft.code.gacode.tglf.inputs import LocalConversionError, prepare_tglf_input

    try:
        a = local if local is not None else prepare_tglf_input(state.profile, float(r_over_a))
        b = prepare_tglf_input(probe, float(r_over_a))
    except LocalConversionError:
        return False
    numeric = ("taus", "rlts", "as_", "rlns", "zs", "mass", "betae", "xnue", "zeff", "debye",
               "p_prime_loc", "q_loc", "q_prime_loc", "rmin_loc", "rmaj_loc", "kappa_loc",
               "delta_loc")
    for name in numeric:
        x = np.atleast_1d(np.asarray(getattr(a, name), dtype=float))
        y = np.atleast_1d(np.asarray(getattr(b, name), dtype=float))
        if x.shape != y.shape or not np.allclose(x, y, rtol=1e-4, atol=1e-4, equal_nan=True):
            return False
    return True

Whether a surface’s solver input is independent of the inferred-Ti gap fill.

NameTypeUnitDescription
stateResolvedTransportState-

From resolve_transport_state.

r_over_afloat-

The surface.

solverstr-

"tglf" compares the full TGLF local input; "neo" compares the ion temperatures and their log-gradients the way NEO reads them.

localTGLFInput, optional-

The surface’s input already built from state.profile, to avoid rebuilding it.

Returns
—bool-

True when no gap was filled, or when the solver’s input agrees (rtol 1e-4, atol 1e-4) between the profile and its fill_probe.

Convention

GACODE differentiates on three grid points and evaluates with a spline over the whole profile, so a filled node can reach a surface several grid steps away. Rather than guess a margin, the gate builds the solver’s input twice – once from the profile, once from fill_probe, whose gaps are filled with a different scale and offset – and runs the surface only when nothing moves. TGLF’s local input uses a not-a-knot spline; NEO (PROFILE_MODEL=2, expro_locsim’s cub_spline1) a natural one, so each solver is checked with its own.

Applicability

Applicability

Machine-independent.

physics_parameters

Machine-independent source
physics_parameters(config, *, exclude=())
Show source vaft/process/transport_state.py, lines 1527–1559
def physics_parameters(config: Any, *, exclude: Iterable[str] = ()) -> dict[str, Any]:
    """The physics half of a GACODE config, with runtime-only fields dropped.

    ``backend``, ``timeout``, ``env``, ``home``, ``executable``, ``platform`` and the
    MPI/OMP counts decide *where* a run happens, not *what* it computes, so they stay
    out of identity.

    Parameters
    ----------
    config : GACODEConfig
        A ``TGLFConfig`` or ``NEOConfig`` dataclass [-].
    exclude : iterable of str
        Further field names to leave out [-].

    Returns
    -------
    dict
        Field name to JSON-ready value, for :func:`run_identity` [-].

    Applicability
    -------------
    Machine-independent.
    """
    from dataclasses import fields as dataclass_fields

    runtime = {"backend", "timeout", "env", "home", "executable", "workdir", "args",
               "platform", "n_mpi", "n_omp", "memory_mb", *exclude}
    out = {}
    for entry in dataclass_fields(config):
        if entry.name in runtime:
            continue
        out[entry.name] = _jsonable(getattr(config, entry.name))
    return out

The physics half of a GACODE config, with runtime-only fields dropped.

backend, timeout, env, home, executable, platform and the MPI/OMP counts decide where a run happens, not what it computes, so they stay out of identity.

NameTypeUnitDescription
configGACODEConfig-

A TGLFConfig or NEOConfig dataclass.

excludeiterable of str-

Further field names to leave out.

Returns
—dict-

Field name to JSON-ready value, for run_identity.

Applicability

Applicability

Machine-independent.

project_transport_state

Machine-independent source
project_transport_state(state, target_physics, method=None)
Show source vaft/process/transport_state.py, lines 1370–1436
def project_transport_state(state: ResolvedTransportState, target_physics: str,
                            method: Optional[str] = None) -> TransportProjection:
    """One physics projection of a resolved state's species, with the profile its solver reads.

    Parameters
    ----------
    state : ResolvedTransportState
        A resolved state [-].
    target_physics : str
        A target of :data:`vaft.process.species.PROJECTION_POLICY`, e.g.
        ``"turbulence"`` (TGLF), ``"neoclassical"`` (NEO), ``"resistive"`` [-].
    method : str, optional
        A method the target allows (``"explicit"``, ``"effective_impurity"``, ...);
        default the target's own [-].

    Returns
    -------
    TransportProjection
        The species contract, ``canonical_state_id`` (the state's identity) and
        ``species_state_id``, and the projected profile for an ``explicit`` or
        ``effective_impurity`` method (``None`` for ``moments_only``) [any].

    Raises
    ------
    ValueError
        An unresolved state; a target or method the species policy refuses (an
        effective impurity for NEO); an effective impurity whose charge or mass
        varies with radius, which one GACODE species cannot carry.

    Processing steps
    ----------------
    1. :func:`transport_species_state` from the resolved profile.
    2. :func:`vaft.process.species.project_species_state` on that state.
    3. ``explicit``: the state's own profile, unchanged; ``effective_impurity``:
       the profile with the thermal impurities replaced by one pseudo-ion
       (charge, mass constant over the grid; temperature density-weighted);
       the inferred-Ti probe, if any, gets the same replacement.

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

    Limitations
    -----------
    The effective impurity keeps the charge density and the Z^2 moment at every
    point, not the impurity thermal pressure: its density
    ``(sum n Z)^2 / sum n Z^2`` is below ``sum n``, so the impurity ``n T`` (and
    a gyrokinetic ``p'``) is slightly lower than the explicit species give;
    ``ptot`` is left as the state carries it.

    Provenance
    ----------
    .. [issue] #1567 Sec. 6-7 and Phase C; policy from #1709.
    """
    from vaft.process.species import project_species_state

    species_state = transport_species_state(state)
    projection = project_species_state(species_state, target_physics, method)
    profile = probe = None
    if projection.method == "explicit":
        profile, probe = state.profile, state.fill_probe
    elif projection.method == "effective_impurity":
        profile = _species_profile(state.profile, species_state, projection)
        if state.fill_probe is not None:
            probe = _species_profile(state.fill_probe, species_state, projection)
    return TransportProjection(canonical_state_id=state.identity, species_state_id=species_state.state_id,
                               projection=projection, profile=profile, fill_probe=probe)

One physics projection of a resolved state’s species, with the profile its solver reads.

NameTypeUnitDescription
stateResolvedTransportState-

A resolved state.

target_physicsstr-

A target of vaft.process.species.PROJECTION_POLICY, e.g. "turbulence" (TGLF), "neoclassical" (NEO), "resistive".

methodstr, optional-

A method the target allows ("explicit", "effective_impurity", …); default the target’s own.

Returns
—TransportProjectionany

The species contract, canonical_state_id (the state’s identity) and species_state_id, and the projected profile for an explicit or effective_impurity method (None for moments_only).

Raises

  • ValueError An unresolved state; a target or method the species policy refuses (an effective impurity for NEO); an effective impurity whose charge or mass varies with radius, which one GACODE species cannot carry.
Processing steps · Applicability · Limitations · Provenance

Processing steps

  1. transport_species_state from the resolved profile.
  2. vaft.process.species.project_species_state on that state.
  3. explicit: the state’s own profile, unchanged; effective_impurity: the profile with the thermal impurities replaced by one pseudo-ion (charge, mass constant over the grid; temperature density-weighted); the inferred-Ti probe, if any, gets the same replacement.

Applicability

Machine-independent.

Limitations

The effective impurity keeps the charge density and the Z^2 moment at every point, not the impurity thermal pressure: its density (sum n Z)^2 / sum n Z^2 is below sum n, so the impurity n T (and a gyrokinetic p') is slightly lower than the explicit species give; ptot is left as the state carries it.

Provenance

  1. #1567 Sec. 6-7 and Phase C; policy from #1709.

resolve_transport_state

VEST-specific source
resolve_transport_state(ods, key, *, efit_quality, quality_source='criteria', tolerance=0.0005, ti_te_ratio='policy', ti_te_ratio_sigma=None, inferred_ti=None, use_stored_inferred_ti=False, z_eff=2.0, impurity='C', rho_max=0.95, inputs=None)

Resolve one plasma state into a shared GACODE profile with per-quantity provenance.

NameTypeUnitDescription
odsomas.ODS-

Carries equilibrium and core_profiles for the shot; never modified.

keyTransportStateKeys

(shot, time_efit_s, efit_lineage); time_efit_s must be an equilibrium slice time within tolerance.

efit_qualitystr-

The #1331 slice label, "good" or "admissible"; anything else is refused with ValueError, an unreconstructible slice not being a state.

quality_sourcestr-

Where efit_quality came from, e.g. "criteria" or "magnetic_slice_at_same_time".

tolerancefloats
Largest allowed t_core_profiles - t_equilibrium and t_equilibrium - time_efit_s .
ti_te_ratiostr or float-

Ion-temperature fallback when no ion temperature is measured: "policy" reads vest.yaml through vaft.machine_mapping.core_profiles.policy_for_ods; a number is used as given and recorded as caller-supplied; None disables the fallback. Any other string is refused with ValueError at entry. A product whose ion temperature is itself a ratio record (ti_te_ratio=...; status=assumed, #1414) is resolved at the record’s own ratio, as assumed.

ti_te_ratio_sigmafloat, optional-

Its 1-sigma, for provenance; the policy’s when None.

inferred_timapping, optionaleV

A pressure-partition result (#1426), {"temperature", "method", "time"} on the core_profiles grid of the matched slice, preferred over the policy fallback; its time must match the paired slice within tolerance.

use_stored_inferred_tibool-

Whether an ion temperature the product itself labels as inferred (temperature_fit.parameters reading origin=inferred; ... by vaft.machine_mapping.core_profiles.classify_ti_record, lane K’s #1426 partition) is used as the inferred_ti step. Off by default: the routine atlas keeps Ti = Te (#1414), and such a state is refused as inferred_ti_not_enabled. A temperature is taken as measured only when its record is a measured fit’s (or absent); a record in no known grammar is refused as ti_record_unrecognised.

z_efffloat, optional-

Effective charge the composition closure realizes.

impuritystr, optional-

Impurity species of the closure ("C" -> C6+); None keeps one main ion.

rho_maxfloat, optional-

Outer edge of the converted profile, as rho_tor_norm.

inputsmapping, optional-

Identity of the products the ODS was assembled from (paths, sha256).

Returns
—ResolvedTransportState-

status "resolved" with profile set, or "insufficient" with machine-readable reasons.

Processing steps · Applicability · Provenance

Processing steps

  1. Match the equilibrium slice to time_efit_s and the core_profiles slice to that equilibrium time, both by time within tolerance.
  2. Classify each ion’s temperature_fit.parameters record, then resolve the ion temperature: measured, then inferred_ti, then the product’s own ratio record, then the policy ratio, else insufficient; write a main ion into a deep copy when needed.
  3. Derive r_inboard/r_outboard and, when absent, the elongation and triangularity profiles from the 2-D flux map (on the copy), recording that they were derived here.
  4. Convert with vaft.code.gacode.inputs.prepare_gacode_profile, which applies the composition closure and refuses non-positive profiles.

Applicability

VEST-specific. The policy default reads VEST’s vest.yaml (diagnostics.core_profiles.ti_te_ratio) and the composition default is the VEST modelling policy (H+, C6+, Z_eff = 2); campaign core_profiles and EFIT stage products are the data it was built against.

Provenance

  1. Issue #1428 sections 2-7: the Ti hierarchy (measured, #1426 pressure partition, empirical ratio, insufficient), the VEST H+/C6+ Z_eff = 2 composition policy and explicit time alignment.
  2. Issue #1414: VEST Thomson-only slices take Ti = Te with sigma 0.5 (vest.yaml diagnostics.core_profiles.ti_te_ratio, status assumed).
  3. GACODE input.gacode: rmin/rmaj are the midplane half-width and centre at the axis height (https://gacode.io/input_gacode.html), derived here with vaft.omas.update_equilibrium_profiles_1d_radial_coordinates.

run_identity

Machine-independent source
run_identity(state, *, solver, parameters, surface=None, solver_revision=None, projection=None)
Show source vaft/process/transport_state.py, lines 1467–1524
def run_identity(
    state: ResolvedTransportState,
    *,
    solver: str,
    parameters: Mapping[str, Any],
    surface: Optional[float] = None,
    solver_revision: Optional[str] = None,
    projection: Optional["TransportProjection"] = None,
) -> str:
    """Deterministic identity of one solver calculation on one resolved state.

    Parameters
    ----------
    state : ResolvedTransportState
        The upstream state; its own identity is folded in [-].
    solver : str
        ``"tglf"``, ``"neo"`` or ``"classical"`` [-].
    parameters : mapping
        The solver's effective settings (e.g. ``dataclasses.asdict(config)`` minus
        runtime-only fields) [-].
    surface : float, optional
        r/a of a local calculation [-].
    solver_revision : str, optional
        The GACODE revision the run used or will use [-].
    projection : TransportProjection, optional
        The species projection the run used (#1567).  A reduced projection's
        ``projection_id`` is folded in, so an effective-impurity run differs from
        the explicit one; an explicit projection is the state's own species list
        and gives the identity this function always gave [-].

    Returns
    -------
    str
        sha256 hex digest; equal inputs give equal identities, which is the cache key [-].

    Raises
    ------
    ValueError
        A projection of another state.

    Applicability
    -------------
    Machine-independent.
    """
    payload = {
        "state": state.identity,
        "solver": solver,
        "parameters": dict(parameters),
        "surface": None if surface is None else round(float(surface), 6),
        "solver_revision": solver_revision,
    }
    if projection is not None:
        if projection.canonical_state_id != state.identity:
            raise ValueError("the projection was made from another transport state")
        if projection.method != "explicit":
            # an explicit projection is the state's own species list: same run, same key
            payload["species_projection"] = projection.projection_id
    return _digest(payload)

Deterministic identity of one solver calculation on one resolved state.

NameTypeUnitDescription
stateResolvedTransportState-

The upstream state; its own identity is folded in.

solverstr-

"tglf", "neo" or "classical".

parametersmapping-

The solver’s effective settings (e.g. dataclasses.asdict(config) minus runtime-only fields).

surfacefloat, optional-

r/a of a local calculation.

solver_revisionstr, optional-

The GACODE revision the run used or will use.

projectionTransportProjection, optional-

The species projection the run used (#1567). A reduced projection’s projection_id is folded in, so an effective-impurity run differs from the explicit one; an explicit projection is the state’s own species list and gives the identity this function always gave.

Returns
—str-

sha256 hex digest; equal inputs give equal identities, which is the cache key.

Raises

  • ValueError A projection of another state.
Applicability

Applicability

Machine-independent.

surface_toroidal_field

Machine-independent source
surface_toroidal_field(profile, local)
Show source vaft/process/transport_state.py, lines 1756–1784
def surface_toroidal_field(profile: Any, local: Any) -> float:
    """The toroidal field at a surface's centre, ``|B_centr| R_centr / R``.

    GACODE's ``B_unit`` is a flux-coordinate field (about twice B0 on VEST), not the
    field a gyro-orbit sees, so the classical baseline does not use it.

    Parameters
    ----------
    profile : GACODEProfile
        Supplies ``bcentr`` [T] and ``rcentr`` [m].
    local : TGLFInput
        Supplies ``rmaj_loc`` and the minor radius of the surface [-].

    Returns
    -------
    float
        ``|B_T|`` at ``R = rmaj_loc * a``; the poloidal field is neglected [T].

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

    Provenance
    ----------
    .. [1] Vacuum toroidal field ``B_T = B_0 R_0 / R``; GACODE ``input.gacode``
       ``bcentr``/``rcentr`` (https://gacode.io/input_gacode.html).
    """
    r_surface = float(local.rmaj_loc) * float(local.normalisation.minor_radius)
    return abs(float(profile.bcentr)) * float(profile.rcentr) / r_surface

The toroidal field at a surface’s centre, |B_centr| R_centr / R.

GACODE’s B_unit is a flux-coordinate field (about twice B0 on VEST), not the field a gyro-orbit sees, so the classical baseline does not use it.

NameTypeUnitDescription
profileGACODEProfilem

Supplies bcentr [T] and rcentr.

localTGLFInput-

Supplies rmaj_loc and the minor radius of the surface.

Returns
—floatT

|B_T| at R = rmaj_loc * a; the poloidal field is neglected.

Applicability · Provenance

Applicability

Machine-independent.

Provenance

  1. Vacuum toroidal field B_T = B_0 R_0 / R; GACODE input.gacode bcentr/rcentr (https://gacode.io/input_gacode.html).

transport_partition

Machine-independent source
transport_partition(neoclassical, turbulent, *, turbulent_charges=None, classical=None, rho_tolerance=0.001)
Show source vaft/process/transport_state.py, lines 1600–1679
def transport_partition(
    neoclassical: Mapping[str, Any],
    turbulent: Mapping[str, Any],
    *,
    turbulent_charges: Optional[Mapping[str, float]] = None,
    classical: Optional[Mapping[str, Any]] = None,
    rho_tolerance: float = 1e-3,
) -> dict[str, Any]:
    """Signed model fluxes and their magnitude partition at one surface of one state.

    Parameters
    ----------
    neoclassical : mapping
        One mapped NEO surface: ``r_over_a``, ``rho_tor_norm`` and electron/ion energy
        and particle fluxes in SI, ions keyed ``z=<charge>`` [W/m^2].
    turbulent : mapping
        One mapped TGLF surface in the same shape, ions keyed by label [W/m^2].
    turbulent_charges : mapping, optional
        Species label -> charge for the TGLF row (``{"H+": 1, "C6+": 6}``) [e].
    classical : mapping, optional
        A classical row in the same shape, added as a third component when given [-].
    rho_tolerance : float
        Largest allowed rho_tor_norm disagreement between the two rows [-].

    Returns
    -------
    dict
        ``status`` ``"available"`` with per-channel ``neo``, ``turb``, (``classical``),
        ``model`` (the modelled sum, not a measurement), ``f_neo`` =
        |neo| / (|neo| + |turb| [+ |classical|]) and ``neo_over_turb`` (``None`` below a
        floor), or ``"unavailable"`` with a reason [-].

    Applicability
    -------------
    Machine-independent. Callers join only rows with one ``state_identity``.
    """
    if neoclassical is None or turbulent is None:
        missing = "neoclassical" if neoclassical is None else "turbulent"
        return {"status": "unavailable", "reason": f"missing_{missing}_component"}
    if abs(float(neoclassical["r_over_a"]) - float(turbulent["r_over_a"])) > 1e-4:
        return {"status": "unavailable", "reason": "surfaces_differ_in_r_over_a"}
    if abs(float(neoclassical["rho_tor_norm"]) - float(turbulent["rho_tor_norm"])) > rho_tolerance:
        # Same r/a but a different flux surface means two different profiles.
        return {"status": "unavailable", "reason": "surfaces_differ_in_rho_tor_norm"}
    if classical is not None and abs(float(classical["r_over_a"]) - float(turbulent["r_over_a"])) > 1e-4:
        return {"status": "unavailable", "reason": "classical_surface_differs"}
    neo = _channels(neoclassical)
    turb = _channels(turbulent, turbulent_charges)
    cl = _channels(classical) if classical is not None else {}
    # The ion total is a channel only when both models carry the same ion species;
    # a classical row (main ion only) does not take part in it.
    for kind in ("energy", "particle"):
        ion_n = {k for k in neo if k.startswith(f"ion_{kind}_z=")}
        ion_t = {k for k in turb if k.startswith(f"ion_{kind}_z=")}
        if ion_n and ion_n == ion_t:
            neo[f"ion_{kind}_total"] = _ion_total(neo, kind)
            turb[f"ion_{kind}_total"] = _ion_total(turb, kind)
    channels = {}
    for name in sorted(set(neo) & set(turb)):
        kind = "energy" if "_energy" in name else "particle"
        n, t = neo[name], turb[name]
        parts = [abs(n), abs(t)] + ([abs(cl[name])] if name in cl else [])
        denominator = sum(parts)
        entry = {
            "neo": n, "turb": t,
            "model": n + t + (cl[name] if name in cl else 0.0),
            "f_neo": None if denominator == 0.0 else abs(n) / denominator,
            "f_turb": None if denominator == 0.0 else abs(t) / denominator,
            "neo_over_turb": None if abs(t) < _RATIO_FLOOR[kind] else n / t,
        }
        if name in cl:
            entry["classical"] = cl[name]
            entry["f_classical"] = None if denominator == 0.0 else abs(cl[name]) / denominator
        channels[name] = entry
    if not channels:
        return {"status": "unavailable", "reason": "no_common_channel"}
    return {"status": "available", "r_over_a": float(turbulent["r_over_a"]),
            "rho_tor_norm": float(turbulent["rho_tor_norm"]),
            "components": ["neo", "turb"] + (["classical"] if classical is not None else []),
            "channels": channels}

Signed model fluxes and their magnitude partition at one surface of one state.

NameTypeUnitDescription
neoclassicalmappingW/m^2

One mapped NEO surface: r_over_a, rho_tor_norm and electron/ion energy and particle fluxes in SI, ions keyed z=<charge>.

turbulentmappingW/m^2

One mapped TGLF surface in the same shape, ions keyed by label.

turbulent_chargesmapping, optionale

Species label -> charge for the TGLF row ({"H+": 1, "C6+": 6}).

classicalmapping, optional-

A classical row in the same shape, added as a third component when given.

rho_tolerancefloat-

Largest allowed rho_tor_norm disagreement between the two rows.

Returns
—dict-
status "available" with per-channel neo, turb, (classical), model (the modelled sum, not a measurement), f_neo = neo / ( neo + turb [+ classical ]) and neo_over_turb (None below a floor), or "unavailable" with a reason.
Applicability

Applicability

Machine-independent. Callers join only rows with one state_identity.

transport_species_state

Machine-independentConvention-sensitive source
transport_species_state(state)
Show source vaft/process/transport_state.py, lines 1238–1307
def transport_species_state(state: ResolvedTransportState):
    """The canonical species state of a resolved transport state: the species its solvers read.

    Parameters
    ----------
    state : ResolvedTransportState
        A resolved state from :func:`resolve_transport_state` [-].

    Returns
    -------
    vaft.process.species.CanonicalSpeciesState
        One component per species of ``state.profile`` (element and mass, charge,
        population from the GACODE type, density [m^-3] and temperature [eV] on the
        profile's ``rho``), with ``n_e`` and the state time [any].

    Raises
    ------
    ValueError
        An unresolved state, a profile that is itself a projection, or a species
        whose label and mass match no element.

    Convention
    ----------
    A species the converter lumped from a bundled ion (``provenance["ni"]
    ["bundled_ions"]``) or with a non-integer charge is a bundled component.  The
    element comes from the label only where its standard weight lies within 1 u
    of the stored mass and its Z_n bounds the charge, else from the mass.  The
    density origin is ``assumed`` for a policy composition closure and
    ``unlabelled`` otherwise; the state's own record says which.

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

    Provenance
    ----------
    .. [issue] #1567 Phase C (transport-state integration).
    """
    from vaft.process.species import CanonicalSpeciesState, SpeciesComponent

    if not state.resolved:
        raise ValueError(f"the transport state is not resolved ({', '.join(state.reasons)})")
    profile = state.profile
    if "species_projection" in (getattr(profile, "provenance", {}) or {}):
        raise ValueError("this profile is already a species projection; take the canonical state "
                         "from the resolved state it was projected from")
    ni = np.atleast_2d(np.asarray(profile.ni, dtype=float))
    ti = np.atleast_2d(np.asarray(profile.ti, dtype=float))
    charges = np.atleast_1d(np.asarray(profile.z, dtype=float))
    masses = np.atleast_1d(np.asarray(profile.mass, dtype=float))
    names = list(profile.name) or [f"ion{k}" for k in range(charges.size)]
    kinds = list(profile.type) or ["[therm]"] * charges.size
    record = dict(getattr(profile, "provenance", {}) or {}).get("ni", {})
    bundled = set((record or {}).get("bundled_ions", {}) or {})
    origin = "assumed" if state.composition.get("origin") == "policy_assumption" else "unlabelled"
    components = []
    for k, label in enumerate(names):
        charge = float(charges[k])
        nuclide, mass = _profile_nuclide(label, charge, float(masses[k]))
        is_bundled = label in bundled or abs(charge - round(charge)) > 1e-9
        components.append(SpeciesComponent(
            nuclide, charge, _POPULATION_OF_TYPE.get(str(kinds[k]).strip(), "other"),
            ni[k] * _DENSITY_SCALE, mass, temperature=ti[min(k, ti.shape[0] - 1)] * _TEMPERATURE_SCALE,
            bundled=is_bundled, origin=origin,
            source=f"transport state {state.identity[:12]}, GACODE species {label}"))
    return CanonicalSpeciesState(
        tuple(components), rho=np.asarray(profile.rho, dtype=float),
        n_e=np.asarray(profile.ne, dtype=float) * _DENSITY_SCALE, time=float(state.key.time_efit_s),
        provenance={"source": "resolved transport state profile", "state_identity": state.identity,
                    "composition": dict(state.composition)})

The canonical species state of a resolved transport state: the species its solvers read.

NameTypeUnitDescription
stateResolvedTransportState-

A resolved state from resolve_transport_state.

Returns
—vaft.process.species.CanonicalSpeciesStateany

One component per species of state.profile (element and mass, charge, population from the GACODE type, density [m^-3] and temperature [eV] on the profile’s rho), with n_e and the state time.

Convention

A species the converter lumped from a bundled ion (provenance["ni"] ["bundled_ions"]) or with a non-integer charge is a bundled component. The element comes from the label only where its standard weight lies within 1 u of the stored mass and its Z_n bounds the charge, else from the mass. The density origin is assumed for a policy composition closure and unlabelled otherwise; the state’s own record says which.

Raises

  • ValueError An unresolved state, a profile that is itself a projection, or a species whose label and mass match no element.
Applicability · Provenance

Applicability

Machine-independent.

Provenance

  1. #1567 Phase C (transport-state integration).

results matching ""

    No results matching ""