Process reference: resonant_coil_response

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

Overview

On a fixed equilibrium, toroidal mode n and plasma-response model, the complex resonant flux GPEC writes at the rational surfaces (Phi_res total, Phi_res_v vacuum; area-normalized, tesla) responds linearly to the coil excitation. This module holds that operator, built from per-group unit runs and verified on held-out runs rather than assumed, and the optimizations issue #1884 asks of it (issue #1178 umbrella). It runs no solver.

resonant_response_operator ─▶ H from the unit runs e_k, Phi = H conj(c)
qualify_response_operator ──▶ held-out complex runs -> qualified / rejected / insufficient
window_weights ─────────────▶ W: mean |Phi|^2 over a resonant window (RMS^2)
ResonantResponseOperator.quadratic(W) ─▶ Q with J = c^H Q c, for the generic optima of
                                vaft.process.ntv_coil_response (two-group, budgeted, fixed-amplitude)
match_resonant_target ──────▶ stage B: regularized complex least squares, with rank and SVD
constrained_window_extremum ─▶ stage D: maximize one window under a cap on another and a budget
SymbolMeaningUnit
Knumber of independently driven coil groups-
N_snumber of rational surfaces-
cVAFT excitation phasor per group, c = A exp(+i delta)phasor
Phicomplex resonant flux per surface, area-normalizedT
HN_s x K operator, Phi = H conj(c)T / phasor
WN_s x N_s Hermitian positive-semidefinite surface weights-
Jquadratic resonant metric, (H conj c)^H W (H conj c) = c^H Q cT^2
DK x K Hermitian positive-definite excitation metric-

The response is linear in conj(c). VAFT’s excitation (vaft.process.coil_operating_space.COIL_EXCITATION_CONVENTION) is I_k = A cos(n phi_k + delta) = Re(c exp(+i n phi_k)); GPEC decomposes in exp(-i n phi), whose coefficient is conj(c) / 2. Measured on the DIII-D-like example (#1884): Phi(i e_k) = -i Phi(e_k) exactly, and Phi = H conj(c) reproduces every held-out run to 4e-6. Hence Q = conj(H^H W H) for a quadratic metric in c; using H^H W H instead would mirror every optimal relative phase.

Squared, not RMS, is optimized. J with window_weights is the window’s mean squared magnitude; its square root is vaft.process.perturbation.rms_resonant_field for the same surfaces, so the ordering is the same for a fixed non-empty window. Results report which one they hold.

Total and vacuum stay apart. Phi_res and Phi_res_v get separate operators; nothing here subtracts them.

Functions

constrained_window_extremumStage D: maximize c^H Q_o c with c^H Q_c c <= limit and c^H D c = budget. Convention
match_resonant_targetStage B: the excitation whose resonant response best matches a complex target. Convention
qualify_response_operatorQualify or reject Phi = H conj(c) against held-out runs. Convention
resonant_operating_spaceDirect resonant-field RMS per window (and a qualified operator’s predictions) as a #1178 result. Convention
resonant_response_operatorThe operator H from the responses to the unit excitations c = e_k. Convention
window_weightsSurface weights whose quadratic metric is the window’s mean squared |Phi|. Convention

constrained_window_extremum

Machine-independentConvention-sensitive source
constrained_window_extremum(Q_objective, Q_constraint, *, metric, budget, limit, tolerance=1e-10)

Stage D: maximize c^H Q_o c with c^H Q_c c <= limit and c^H D c = budget.

NameTypeUnitDescription
Q_objectivenumpy.ndarrayT^2 / phasor^2

Hermitian positive-semidefinite metric to maximize, e.g. the edge window’s operator.quadratic(W_edge).

Q_constraintnumpy.ndarrayT^2 / phasor^2

Hermitian positive-semidefinite metric to cap, e.g. the core window’s.

metricnumpy.ndarray-

Hermitian positive-definite excitation metric D.

budgetfloatphasor^2

P_0 > 0 of c^H D c = P_0.

limitfloatT^2

Cap on c^H Q_c c.

tolerancefloat, optional-

Relative tolerance of the multiplier bisection; a numerical-method control.

Returns
solutiondict-

c [phasor], objective and constraint values [T^2], multiplier mu >= 0 [-], and status: unconstrained (the cap is not active), active (the constraint sits on the cap), at_floor (only the least-constraint excitations meet it; the multiplier diverges) or infeasible (none does; c is then the least-constraint excitation).

Convention

Squared metrics; report sqrt for an RMS. The answer is defined up to a common phase.

Raises

  • ValueError Shapes disagree, a matrix is not Hermitian, D is not positive definite, or budget/limit is not positive / negative.
Processing steps · Applicability · Limitations · Provenance

Processing steps

  1. infeasible when even the least c^H Q_c c on the budget (the bottom generalized eigenvector of (Q_c, D)) exceeds the cap.
  2. For a multiplier mu, the top generalized eigenspace of (Q_o - mu Q_c, D); mu = 0 if some excitation in it meets the cap.
  3. Otherwise bisect mu on the least constraint within that space, and at the converged mu combine the space’s extreme directions so the constraint lands on the cap (the objective is then maximal).

Applicability

Machine-independent.

Limitations

The multiplier route is exact for two Hermitian quadratic forms on a complex sphere (no duality gap); degenerate top eigenvalues, which occur whenever the cap’s null space lies inside the objective’s (e.g. more groups than capped surfaces), are handled by working on the eigenspace. Eigenvalues within 1e-9 of the spectral scale are treated as equal. Sector-current limits are not imposed.

Provenance

  1. Issue #1884, Sec. 4 stage D.

match_resonant_target

Machine-independentConvention-sensitive source
match_resonant_target(operator, target, *, weights, regularization, metric)

Stage B: the excitation whose resonant response best matches a complex target.

NameTypeUnitDescription
operatorResonantResponseOperator-

A qualified operator.

targetsequence of complexT

Target Phi per surface, phase included.

weightsnumpy.ndarray-

Hermitian positive-semidefinite surface weights W.

regularizationfloatT^2 / phasor^2

lambda >= 0 of the excitation penalty.

metricnumpy.ndarray-

Hermitian positive-definite excitation metric D of the penalty.

Returns
solutiondictphasor

c [phasor]; response [T]; residual ||W^1/2 (H conj c - t)|| and relative_residual against ||W^1/2 t|| [T, -]; singular_values of W^1/2 H [T / phasor], rank and condition (largest over smallest nonzero singular value; finite even when rank deficient) [-]; null_space (columns of excitations with no weighted response).

Convention

The target is complex: its phase is physical here, so no group’s phase is fixed. With lambda = 0 and a rank-deficient W^1/2 H the returned c is the minimum-norm one, and the null space says what else fits.

Raises

  • ValueError Shapes disagree, W or D is not Hermitian, D is not positive definite, or regularization is negative.
Processing steps · Applicability · Limitations · Provenance

Processing steps

  1. With g = conj(c): minimize ||W^1/2 (H g - t)||^2 + lambda g^H conj(D) g.
  2. Solve the stacked system [W^1/2 H; sqrt(lambda) L^H] g = [W^1/2 t; 0] (conj(D) = L L^H) by least squares, never by an explicit inverse.
  3. c = conj(g); report the SVD of W^1/2 H and its null space.

Applicability

Machine-independent.

Limitations

A phasor-space answer: actual sector currents, winding signs and circuit limits must be checked separately. A zero target with lambda > 0 returns c = 0; ask for a nontrivial target instead.

Provenance

  1. Issue #1884, Sec. 4 stage B.

qualify_response_operator

Machine-independentConvention-sensitive source
qualify_response_operator(operator, phasors, responses, *, sample_ids, rtol)
Show source vaft/process/resonant_coil_response.py, lines 205–283
def qualify_response_operator(
    operator: ResonantResponseOperator,
    phasors: np.ndarray,
    responses: Sequence[Sequence[complex]],
    *,
    sample_ids: Sequence[str],
    rtol: float,
) -> ResponseOperatorQualification:
    """Qualify or reject ``Phi = H conj(c)`` against held-out runs.

    Parameters
    ----------
    operator : ResonantResponseOperator
        From :func:`resonant_response_operator` [-].
    phasors : numpy.ndarray
        ``(N, K)`` excitations not among the unit runs, complex ones included [phasor].
    responses : sequence of sequence of complex
        Their direct ``Phi``; an all-NaN row marks a failed run [T].
    sample_ids : sequence of str
        One id per run [-].
    rtol : float
        Largest accepted ``||Phi_model - Phi_direct|| / ||Phi_direct||`` [-].

    Returns
    -------
    qualification : ResponseOperatorQualification
        ``rejected`` when any run exceeds ``rtol``; ``insufficient`` when any
        failed or none was given, or no run had a non-real phasor (the
        conjugate relation would be untested); otherwise ``qualified`` [-].

    Raises
    ------
    ValueError
        Shapes disagree or ``rtol`` is negative.

    Processing steps
    ----------------
    1. Set aside failed (non-finite) runs and count them.
    2. Relative vector error of ``H conj(c)`` against each direct ``Phi``.
    3. One status, requiring at least one run with a non-real phasor.

    Convention
    ----------
    The comparison is on the complex vector, phase included; matching the
    magnitudes alone would not catch the ``conj`` relation.

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

    Provenance
    ----------
    .. [1884] Issue #1884, independent combined-current validation.
    """
    if not rtol >= 0:
        raise ValueError("rtol must not be negative")
    c = np.atleast_2d(np.asarray(phasors, dtype=complex))
    direct = np.atleast_2d(np.asarray(responses, dtype=complex))
    ids = [str(s) for s in sample_ids]
    if c.shape != (len(ids), operator.H.shape[1]) or direct.shape != (len(ids), operator.H.shape[0]):
        raise ValueError("phasors, responses and sample_ids disagree with the operator")
    finite = np.all(np.isfinite(direct), axis=1)
    model = operator.response(c[finite])
    errors = {i: float(np.linalg.norm(m - d) / np.linalg.norm(d))
              for i, m, d in zip(np.asarray(ids)[finite], model, direct[finite])}
    reasons: list[str] = []
    failed = [i for i, e in errors.items() if e > rtol]
    if failed:
        status = "rejected"
        reasons.append(f"relative error above {rtol:g}: {failed}")
    elif not finite.all() or not finite.any():
        status = "insufficient"
        reasons.append(f"{int((~finite).sum())} run(s) failed; the operator is identified, not validated")
    elif not np.any(np.abs(c[finite].imag) > 0):
        status = "insufficient"
        reasons.append("no held-out run had a non-real phasor, so Phi = H conj(c) is untested")
    else:
        status = "qualified"
    return ResponseOperatorQualification(status=status, relative_errors=errors, reasons=tuple(reasons))

Qualify or reject Phi = H conj(c) against held-out runs.

NameTypeUnitDescription
operatorResonantResponseOperator-

From resonant_response_operator.

phasorsnumpy.ndarrayphasor

(N, K) excitations not among the unit runs, complex ones included.

responsessequence of sequence of complexT

Their direct Phi; an all-NaN row marks a failed run.

sample_idssequence of str-

One id per run.

rtolfloat-

Largest accepted ||Phi_model - Phi_direct|| / ||Phi_direct||.

Returns
qualificationResponseOperatorQualification-

rejected when any run exceeds rtol; insufficient when any failed or none was given, or no run had a non-real phasor (the conjugate relation would be untested); otherwise qualified.

Convention

The comparison is on the complex vector, phase included; matching the magnitudes alone would not catch the conj relation.

Raises

  • ValueError Shapes disagree or rtol is negative.
Processing steps · Applicability · Provenance

Processing steps

  1. Set aside failed (non-finite) runs and count them.
  2. Relative vector error of H conj(c) against each direct Phi.
  3. One status, requiring at least one run with a non-real phasor.

Applicability

Machine-independent.

Provenance

  1. Issue #1884, independent combined-current validation.

resonant_operating_space

Machine-independentConvention-sensitive source
resonant_operating_space(groups, n, phasors, responses, *, psi_n_rational, windows, sample_ids, roles, amplitude_unit, frame, provenance, operators=None)

Direct resonant-field RMS per window (and a qualified operator’s predictions) as a #1178 result.

NameTypeUnitDescription
groupssequence of str-

Coil-group names in phasor order.

nint-

Toroidal mode number.

phasorsnumpy.ndarrayphasor

(N, K) excitations.

responsesmapping of str to numpy.ndarrayT

(N, N_s) direct complex flux per field, keyed Phi_res and/or Phi_res_v; a row of NaN marks a failed run.

psi_n_rationalsequence of float-

Surface positions.

windowsmapping of str to ResonantWindow-

Windows to reduce over, e.g. core and edge of vaft.process.perturbation.resonant_windows.

sample_idssequence of str-

One id per sample.

rolessequence of str-

Role per sample.

amplitude_unitfloatA per turn

Cosine amplitude of one phasor unit.

framestr-

The toroidal frame of the sector angles.

provenancemapping-

Equilibrium, code, pedestal source.

operatorsmapping, optional-

Per field, a (operator, qualification) pair; a qualified one adds <metric>_model rows.

Returns
resultvaft.process.coil_operating_space.CoilOperatingSpace-

Metrics phi_res_rms_<window> / phi_res_v_rms_<window> [T] and their _model counterparts.

Convention

Reported statistic is the RMS (tesla), the square root of the optimized mean square; measured and modelled values never share a metric.

Raises

  • ValueError Lengths disagree, an unknown field, or an operator that is not qualified.
Processing steps · Applicability · Provenance

Processing steps

  1. Per sample, field and window, vaft.process.perturbation.rms_resonant_field of the direct response; failed (NaN) for a failed run, undefined for a window with no surface.
  2. With a qualified operator, the same reduction of H conj(c).
  3. Window bounds and sources go into provenance.

Applicability

Machine-independent.

Provenance

  1. Issue #1884; [1886] the result schema’s phi_res_rms_edge naming.

resonant_response_operator

Machine-independentConvention-sensitive source
resonant_response_operator(unit_responses, *, field, psi_n_rational, m_rational, n, groups, amplitude_unit, provenance=None)
Show source vaft/process/resonant_coil_response.py, lines 124–202
def resonant_response_operator(
    unit_responses: Sequence[Sequence[complex]],
    *,
    field: str,
    psi_n_rational: Sequence[float],
    m_rational: Sequence[int],
    n: int,
    groups: Sequence[str],
    amplitude_unit: float,
    provenance: Mapping[str, object] | None = None,
) -> ResonantResponseOperator:
    """The operator ``H`` from the responses to the unit excitations ``c = e_k``.

    Parameters
    ----------
    unit_responses : sequence of sequence of complex
        ``Phi`` of the run with ``c = e_k`` (group ``k`` at one phasor unit,
        phase 0, the others off), one per group, in group order [T].
    field : str
        ``Phi_res`` or ``Phi_res_v`` [-].
    psi_n_rational : sequence of float
        Surface positions, the same in every run [-].
    m_rational : sequence of int
        Poloidal mode number per surface [-].
    n : int
        Toroidal mode number [-].
    groups : sequence of str
        Coil-group names in phasor order [-].
    amplitude_unit : float
        Cosine amplitude of one phasor unit [A per turn].
    provenance : mapping, optional
        Equilibrium, code version, run identities [-].

    Returns
    -------
    operator : ResonantResponseOperator
        ``H[:, k] = Phi(e_k)``, so ``Phi = H conj(c)`` [-].

    Raises
    ------
    ValueError
        Unknown field, mismatched lengths, a non-finite response, or no group.

    Convention
    ----------
    Because ``e_k`` is real, ``Phi(e_k) = H e_k``; the conjugate shows only
    for complex excitations.  :func:`qualify_response_operator` is what
    establishes the relation for complex ``c``.

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

    Limitations
    -----------
    Built from ``K`` runs; trustworthy only once qualified on held-out complex
    runs at the same equilibrium, ``n`` and numerical settings.

    Provenance
    ----------
    .. [1884] Issue #1884, the verified linear operator.
    """
    if field not in RESONANT_FIELDS:
        raise ValueError(f"field must be one of {RESONANT_FIELDS}, got {field!r}")
    columns = [np.asarray(r, dtype=complex).ravel() for r in unit_responses]
    psi = np.asarray(psi_n_rational, dtype=float).ravel()
    m = np.asarray(m_rational, dtype=int).ravel()
    if not columns or len(columns) != len(groups):
        raise ValueError(f"{len(columns)} unit responses for {len(groups)} groups")
    if any(col.shape != psi.shape for col in columns) or m.shape != psi.shape:
        raise ValueError("every unit response must have one value per rational surface")
    h = np.column_stack(columns)
    if not np.all(np.isfinite(h)):
        raise ValueError("a unit response is not finite")
    if not amplitude_unit > 0:
        raise ValueError("amplitude_unit must be positive")
    return ResonantResponseOperator(field=field, H=h, psi_n_rational=psi, m_rational=m, n=int(n),
                                    groups=tuple(str(g) for g in groups), amplitude_unit=float(amplitude_unit),
                                    provenance=dict(provenance or {}))

The operator H from the responses to the unit excitations c = e_k.

NameTypeUnitDescription
unit_responsessequence of sequence of complexT

Phi of the run with c = e_k (group k at one phasor unit, phase 0, the others off), one per group, in group order.

fieldstr-

Phi_res or Phi_res_v.

psi_n_rationalsequence of float-

Surface positions, the same in every run.

m_rationalsequence of int-

Poloidal mode number per surface.

nint-

Toroidal mode number.

groupssequence of str-

Coil-group names in phasor order.

amplitude_unitfloatA per turn

Cosine amplitude of one phasor unit.

provenancemapping, optional-

Equilibrium, code version, run identities.

Returns
operatorResonantResponseOperator-

H[:, k] = Phi(e_k), so Phi = H conj(c).

Convention

Because e_k is real, Phi(e_k) = H e_k; the conjugate shows only for complex excitations. qualify_response_operator is what establishes the relation for complex c.

Raises

  • ValueError Unknown field, mismatched lengths, a non-finite response, or no group.
Applicability · Limitations · Provenance

Applicability

Machine-independent.

Limitations

Built from K runs; trustworthy only once qualified on held-out complex runs at the same equilibrium, n and numerical settings.

Provenance

  1. Issue #1884, the verified linear operator.

window_weights

Machine-independentConvention-sensitive source
window_weights(operator, window)
Show source vaft/process/resonant_coil_response.py, lines 286–323
def window_weights(operator: ResonantResponseOperator, window) -> np.ndarray:
    """Surface weights whose quadratic metric is the window's mean squared ``|Phi|``.

    Parameters
    ----------
    operator : ResonantResponseOperator
        Supplies the surface positions [-].
    window : vaft.process.perturbation.ResonantWindow
        From :func:`vaft.process.perturbation.resonant_windows` [-].

    Returns
    -------
    W : numpy.ndarray
        Diagonal ``N_s x N_s``, ``1 / N_in`` on the surfaces inside [-].

    Raises
    ------
    ValueError
        The window holds no surface: an empty window is not a zero response.

    Convention
    ----------
    Endpoints included, as :meth:`ResonantWindow.mask`; a surface on the
    core/edge boundary is in both.  ``sqrt(c^H Q c)`` with ``Q =
    operator.quadratic(W)`` is :func:`vaft.process.perturbation.rms_resonant_field`.

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

    Provenance
    ----------
    .. [1884] Issue #1884, Sec. 3 (core/edge metrics).
    """
    inside = window.mask(operator.psi_n_rational)
    if not inside.any():
        raise ValueError(f"the {window.name} window [{window.low:g}, {window.high:g}] holds no rational surface")
    return np.diag(inside / inside.sum()).astype(float)

Surface weights whose quadratic metric is the window’s mean squared |Phi|.

NameTypeUnitDescription
operatorResonantResponseOperator-

Supplies the surface positions.

windowvaft.process.perturbation.ResonantWindow-

From vaft.process.perturbation.resonant_windows.

Returns
Wnumpy.ndarray-

Diagonal N_s x N_s, 1 / N_in on the surfaces inside.

Convention

Endpoints included, as ResonantWindow.mask; a surface on the core/edge boundary is in both. sqrt(c^H Q c) with Q = operator.quadratic(W) is vaft.process.perturbation.rms_resonant_field.

Raises

  • ValueError The window holds no surface: an empty window is not a zero response.
Applicability · Provenance

Applicability

Machine-independent.

Provenance

  1. Issue #1884, Sec. 3 (core/edge metrics).

results matching ""

    No results matching ""