Process reference: wall_modes
Generated from the docstrings of vaft.process.wall_modes:
22 public functions. Each entry is what
vaft.process.describe("wall_modes.<name>") prints.
All process categories
Overview
The reduced vessel-wall contract (VEST-Tokamak/vfit#8) in one line: local
eigenbasis, global electromagnetic dynamics. The wall’s current vector is
partitioned by physical segment, each segment gets its own L/R eigenbasis,
the bases are block-assembled into V_seg, and every reduced operator is
then a projection of the full matrices – so the mutual inductance between
segments survives in the off-diagonal blocks of the reduced inductance.
Naming trap, stated once. Throughout vaft.process.electromagnetics
the passive-passive inductance is called M_mat and the passive-to-source
coupling L_mat; this module keeps those argument names for compatibility
and says which is which at every signature. In the formulas below L is
the physics inductance (code M_mat), R the diagonal resistance (code
R_mat) and M the source coupling (code L_mat).
Per segment g with diagonal R_gg and symmetric-definite L_gg::
S_g = R_gg^{-1/2} L_gg R_gg^{-1/2} (symmetric)
S_g q = tau q (eigh; tau ARE the L/R times)
v = R_gg^{-1/2} q (R-orthonormal)
so that v^T R_gg v = 1, v^T L_gg v = tau and R_gg v = L_gg v / tau.
The eigenvalues are the local decay times; modes are ordered by descending
tau within each segment and the largest-|component| entry of every mode
is positive. Assembled over segments (scattered by element index, so an
interleaved loop order needs no permutation)::
V_seg = blockdiag(V_1, ..., V_G) (N, M_tot)
R_r = V_seg^T R V_seg = I (computed and checked)
L_r = V_seg^T L V_seg diag blocks diag(tau_g), off-diagonal coupling
M_r = V_seg^T M (M_tot, n_src)
G_red = G_full V_seg for any (n_obs, N) response
The projection is exact in the R inner product: a = V_seg^T R I_w, which
for a truncated basis is the R-orthogonal (least-dissipation) projection.
Amplitudes carry units of sqrt(W): a^T a is the ohmic dissipation of the
wall current. Conversions – Euclidean-normalized modes have a_E = a
||v||_2, inductance-normalized ones a_L = a sqrt(tau).
This module chooses no reduced order. It returns every mode of every segment
and offers selection helpers; which modes to keep is the validation study’s
question (vfit #10, vaft #494), and the tools for that question live here too:
the reduced circuit solve (solve_reduced_eddy), rankings of the modes
by response rather than by decay time (mode_scores), a greedy
segment-wise allocation to a tolerance (allocate_per_segment), and the
drive-independent moment patterns (moment_patterns) that the study
compares the eigenbasis against.
Nothing here reads an ODS; vaft.omas.process_wrapper.compute_wall_mode_basis_ods
does the mapping.
Functions
allocate_per_segment | Greedy segment-wise allocation M_repr to a response tolerance.
Convention |
build_wall_mode_basis | Build the segment-wise eigenbasis of a wall. Convention |
canonical_sign | Flip each column so its largest-magnitude entry is positive. Convention |
check_wall_mode_basis | Metrics of a basis against the matrices it claims to diagonalize; no verdict. |
combined_operators | Reduced operators for an arbitrary R-orthonormal basis. Convention |
global_time_constants | The whole wall’s decay times, from the full-rank reduced inductance. Convention |
mode_scores | Rankings of the retained modes, one value per retained coefficient. |
moment_patterns | Drive-independent wall patterns from the source coupling. Convention |
orthonormalize_r | An R-orthonormal basis of span(X), canonical in sign.
Convention |
project | Modal amplitudes a = V^T R I_w.
Convention |
reconstruct | Wall currents I_w = V a from modal amplitudes.
|
reconstruction_error | How far a reconstruction is from the full current, in the norms that matter. Convention |
reduce_response | Reduce any linear response with the wall elements as its columns. |
reduced_operators | Project the full wall matrices onto the retained modes. Convention |
segment_eigenmodes | Solve one segment’s pencil R_gg v = L_gg v / tau.
Convention |
select_all | Keep every mode of every segment: the full-rank selection. |
select_by_score | Keep the highest-scoring modes across segments. Convention |
select_slowest | Keep the slowest modes, globally or per segment. |
select_tau_range | Keep the modes whose decay time lies in a range, per segment. |
solve_reduced_eddy | Integrate the reduced circuit L_r da/dt + R_r a = -M_r dI_src/dt.
|
subspace_angles_r | Principal angles between two mode subspaces in the R inner product. Convention |
symmetrize_inductance | Fold a mutual-inductance matrix onto its symmetric part, with a refusal. |
No function matches.
allocate_per_segment
Machine-independentConvention-sensitive
source
allocate_per_segment(basis, R_mat, M_mat, L_mat, drive, time, *, tolerance, metric='dissipation', G=None, score=None, step=1, max_modes=None, dt_sub=5e-05)
Greedy segment-wise allocation M_repr to a response tolerance.
A global allocation for a given total is simply select_by_score;
this routine answers the other question – the smallest total that meets a
response tolerance, and how it should be split between segments.
| Name | Type | Unit | Description |
|---|---|---|---|
basis | WallModeBasis | - | The eigenbasis. |
R_mat | numpy.ndarray | Ohm | Diagonal loop resistance, |
M_mat | numpy.ndarray | H | Passive-passive inductance in code naming, |
L_mat | numpy.ndarray | H | Passive-to-source coupling in code naming, |
drive | numpy.ndarray | A | Source currents, |
time | numpy.ndarray | s | Time grid of the drive. |
tolerance | float | - | Relative error the allocation stops at. |
metric | {'dissipation', 'output'}, optional | - | Which error to measure. |
G | numpy.ndarray or None, optional | any/A | Observation response, required by |
score | numpy.ndarray or None, optional | - | Ranking within each segment; |
step | int, optional | - | Modes added per round. |
max_modes | int or None, optional | - | Cap on the total. |
dt_sub | float, optional | s | Sub-step of the solves. |
| Returns | |||
keep | tuple of numpy.ndarray | - | The allocation, one index array per segment. |
history | list of dict | - | One row per round: the running |
Convention
The per-segment error is the segment’s share of the global squared error in
the same norm, so a segment the drive barely reaches never attracts modes.
metric="output" measures the relative error of G I_w instead, which
ranks a mode by what a diagnostic would see rather than by what it
dissipates.
Processing steps · Defaults · Applicability · Limitations · Provenance
Processing steps
- Solve the full wall once under
driveand rank the candidates within each segment (theoutput_weightranking ofmode_scoresby default, which needs no more than that solve). - Start from no modes at all.
- Each round, re-solve the reduced wall, measure the global
metricand each segment’s share of it, and addstepmodes to the segment carrying the largest remaining error, in that segment’s ranked order. - Stop when the metric drops to
tolerance, whenmax_modesis reached, or when every candidate is used.
Defaults
metric="dissipation" is the relative R-energy error of the wall current,
the norm this basis is built in; it is a conventional choice, not a tuned
one. dt_sub is a validated-workflow default, as in
solve_reduced_eddy.
Applicability
Machine-independent.
Limitations
Greedy, so the allocation is not proven minimal; it is the smallest total
this order of additions reaches. Each round costs a reduced solve, and the
result is only as general as the drive it was allocated under.
Provenance
- Issue #473, which built the segment-wise eigenbasis, and #500, which added the selection, scoring and moment tools.
- The reduced vessel-wall contract, VEST-Tokamak/vfit#8: local eigenbasis, global electromagnetic dynamics.
- The reduced-order validation study (VEST-Tokamak/vfit#10, vaft #494), which asks which modes to keep; this module answers no part of that question by itself.
build_wall_mode_basis
Machine-independentConvention-sensitive
source
build_wall_mode_basis(R_mat, M_mat, segments, *, symmetry_rtol=1e-08, symmetry_reject=1e-06, cluster_rtol=1e-06, on_cluster='raise', residual_atol=1e-10, cond_max=1000000000000.0, provenance=None)
Build the segment-wise eigenbasis of a wall.
Partitions the wall’s current vector by physical segment, gives each segment
its own eigenbasis (segment_eigenmodes), and records how it was done.
No coupling is discarded here: the inter-segment mutual inductance survives
because every reduced operator is later a projection of the full matrices.
| Name | Type | Unit | Description |
|---|---|---|---|
R_mat | numpy.ndarray | Ohm | Diagonal loop resistance of the wall, |
M_mat | numpy.ndarray | H | Passive-passive inductance in code naming; the physics |
segments | sequence | - | Objects with |
symmetry_rtol, symmetry_reject | float, optional | - | Passed to |
cluster_rtol | float, optional | - | Relative decay-time gap below which a within-segment pair counts as near-degenerate. |
on_cluster | {'raise', 'warn'}, optional | - | What to do about such a pair. |
residual_atol, cond_max | float, optional | - | Passed to |
provenance | mapping of str to str or None, optional | - | Extra entries – the asset the matrices came from, a shot, a build – merged into the recorded provenance. |
| Returns | |||
| — | WallModeBasis | - | Every segment’s complete eigenbasis, the element count, and the provenance. |
Convention
Argument naming follows vaft.process.electromagnetics, not the
physics: M_mat is the passive-passive inductance (physics L) and
L_mat the passive-to-source coupling (physics M). R_mat is the
diagonal loop resistance either way.
Raises
WallModeErrorWhen the segment map does not cover the elements exactly once, when a segment’s pencil is refused, or, withon_cluster="raise", when a near-degenerate pair is found.
Processing steps · Defaults · Applicability · Limitations · Provenance
Processing steps
- Fold the inductance onto its symmetric part
(
symmetrize_inductance) and record the asymmetry. - Read the segment map; the segments must cover every element exactly once.
- Solve each segment’s pencil.
- Check the within-segment decay-time gaps and act on
on_cluster. - Record normalization, sign rule, mode order, projection formula, input asymmetry and the segment ids in the basis provenance.
Defaults
cluster_rtol = 1e-6 is a numerical convenience: inside a closer pair the
individual modes are arbitrary up to rotation, so the default refuses rather
than let the provenance claim a determinism it does not have. The remaining
tolerances are those of the routines they are passed to.
Applicability
Machine-independent. The VEST wall’s segment map comes from
vaft.machine_mapping; nothing here knows a VEST number.
Limitations
A near-degenerate eigenspace is refused, not resolved: the honest comparison
between two such bases is subspace_angles_r, not a column-by-column
one.
Provenance
- Issue #473, which built the segment-wise eigenbasis, and #500, which added the selection, scoring and moment tools.
- The reduced vessel-wall contract, VEST-Tokamak/vfit#8: local eigenbasis, global electromagnetic dynamics.
canonical_sign
Machine-independentConvention-sensitive
source
canonical_sign(V)
Show source vaft/process/wall_modes.py, lines 209–256
def canonical_sign(V: np.ndarray) -> np.ndarray:
"""Flip each column so its largest-magnitude entry is positive.
An eigenvector is defined up to sign; without a rule two runs of the same
build can disagree, and a stored basis cannot be compared with a fresh one.
Parameters
----------
V : numpy.ndarray
Modes as columns, ``(n, m)`` [any].
Returns
-------
numpy.ndarray
The same modes with each column's sign fixed [any].
Convention
----------
The sign rule of this module (``SIGN_RULE = "max_abs_positive"``): the
largest-magnitude entry of every column is made positive, ties going to the
lowest index (``argmax`` on ``|v|``). A pure function of the column, so
repeated runs agree bitwise.
Applicability
-------------
Machine-independent.
Limitations
-----------
Two entries of exactly equal magnitude and opposite sign make the rule
index-dependent rather than ill-defined; a near-degenerate *eigenspace* is
the real ambiguity, and :func:`build_wall_mode_basis` refuses that case
instead of signing it.
Provenance
----------
.. [473] Issue #473, which built the segment-wise eigenbasis, and #500, which
added the selection, scoring and moment tools.
.. [vfit8] The reduced vessel-wall contract, VEST-Tokamak/vfit#8: local
eigenbasis, global electromagnetic dynamics.
"""
V = np.array(V, dtype=float, copy=True)
if V.ndim != 2:
raise WallModeError("modes must be a 2-D array of columns")
pivots = np.argmax(np.abs(V), axis=0)
signs = np.sign(V[pivots, np.arange(V.shape[1])])
signs[signs == 0.0] = 1.0
return V * signs[None, :]Flip each column so its largest-magnitude entry is positive.
An eigenvector is defined up to sign; without a rule two runs of the same build can disagree, and a stored basis cannot be compared with a fresh one.
| Name | Type | Unit | Description |
|---|---|---|---|
V | numpy.ndarray | any | Modes as columns, |
| Returns | |||
| — | numpy.ndarray | any | The same modes with each column’s sign fixed. |
Convention
The sign rule of this module (SIGN_RULE = "max_abs_positive"): the
largest-magnitude entry of every column is made positive, ties going to the
lowest index (argmax on |v|). A pure function of the column, so
repeated runs agree bitwise.
Applicability · Limitations · Provenance
Applicability
Machine-independent.
Limitations
Two entries of exactly equal magnitude and opposite sign make the rule
index-dependent rather than ill-defined; a near-degenerate eigenspace is
the real ambiguity, and build_wall_mode_basis refuses that case
instead of signing it.
Provenance
- Issue #473, which built the segment-wise eigenbasis, and #500, which added the selection, scoring and moment tools.
- The reduced vessel-wall contract, VEST-Tokamak/vfit#8: local eigenbasis, global electromagnetic dynamics.
check_wall_mode_basis
Machine-independent
source
check_wall_mode_basis(basis, R_mat, M_mat)
Show source vaft/process/wall_modes.py, lines 1139–1200
def check_wall_mode_basis(basis: WallModeBasis, R_mat: np.ndarray, M_mat: np.ndarray) -> dict[str, Any]:
"""Metrics of a basis against the matrices it claims to diagonalize; no verdict.
Numbers a caller or a report can threshold as it sees fit; nothing here
decides whether a basis is good enough.
Parameters
----------
basis : WallModeBasis
The eigenbasis [-].
R_mat : numpy.ndarray
Diagonal loop resistance, ``(N, N)`` or its diagonal [Ohm].
M_mat : numpy.ndarray
Passive-passive inductance in code naming, ``(N, N)`` [H].
Returns
-------
dict
``n_elements``, ``n_modes``, ``r_r_identity_error``
(``max|V^T R V - I|``), ``l_r_symmetry_error``,
``max_segment_residual``, ``min_relative_gap`` and ``coupling`` [-].
``coupling`` holds, per segment pair, the Frobenius norm of that
off-diagonal block of ``L_r`` relative to the geometric mean of the two
diagonal blocks, so a reader can see which segments actually talk to
each other [-].
Applicability
-------------
Machine-independent.
Provenance
----------
.. [473] Issue #473, which built the segment-wise eigenbasis, and #500, which
added the selection, scoring and moment tools.
.. [vfit8] The reduced vessel-wall contract, VEST-Tokamak/vfit#8: local
eigenbasis, global electromagnetic dynamics.
"""
ops = reduced_operators(basis, R_mat, M_mat)
M_tot = ops.L_r.shape[0]
identity_error = float(np.max(np.abs(ops.R_r - np.eye(M_tot))))
offsets = np.cumsum([0] + [seg.size for seg in basis.segments])
coupling: dict[str, float] = {}
for i, a in enumerate(basis.segments):
for j, b in enumerate(basis.segments):
if j <= i:
continue
block = ops.L_r[offsets[i]:offsets[i + 1], offsets[j]:offsets[j + 1]]
scale = np.sqrt(
np.linalg.norm(ops.L_r[offsets[i]:offsets[i + 1], offsets[i]:offsets[i + 1]])
* np.linalg.norm(ops.L_r[offsets[j]:offsets[j + 1], offsets[j]:offsets[j + 1]])
)
coupling[f"{a.id}-{b.id}"] = float(np.linalg.norm(block) / scale) if scale > 0 else float("nan")
return {
"n_elements": basis.n_elements,
"n_modes": basis.n_modes(),
"r_r_identity_error": identity_error,
"l_r_symmetry_error": float(np.max(np.abs(ops.L_r - ops.L_r.T)) / np.max(np.abs(ops.L_r))),
"max_segment_residual": max(seg.residual for seg in basis.segments),
"min_relative_gap": min(seg.min_relative_gap for seg in basis.segments),
"coupling": coupling,
}Metrics of a basis against the matrices it claims to diagonalize; no verdict.
Numbers a caller or a report can threshold as it sees fit; nothing here decides whether a basis is good enough.
| Name | Type | Unit | Description |
|---|---|---|---|
basis | WallModeBasis | - | The eigenbasis. |
R_mat | numpy.ndarray | Ohm | Diagonal loop resistance, |
M_mat | numpy.ndarray | H | Passive-passive inductance in code naming, |
| Returns | |||
| — | dict | - |
|
Applicability · Provenance
Applicability
Machine-independent.
Provenance
- Issue #473, which built the segment-wise eigenbasis, and #500, which added the selection, scoring and moment tools.
- The reduced vessel-wall contract, VEST-Tokamak/vfit#8: local eigenbasis, global electromagnetic dynamics.
combined_operators
Machine-independentConvention-sensitive
source
combined_operators(V, R_mat, M_mat, L_mat=None, *, label='pattern')
Show source vaft/process/wall_modes.py, lines 1812–1883
def combined_operators(
V: np.ndarray,
R_mat: np.ndarray,
M_mat: np.ndarray,
L_mat: np.ndarray | None = None,
*,
label: str = "pattern",
) -> ReducedWall:
"""Reduced operators for an arbitrary R-orthonormal basis.
The same projections as :func:`reduced_operators`, for a basis that is not
(only) segment eigenmodes -- an enrichment by :func:`moment_patterns`, say,
or a POD basis.
Input semantics
---------------
Element space: the assembled wall matrices and a basis whose columns are wall
patterns.
Output semantics
----------------
Mode space: operators of the basis's own coordinates, labelled ``(label, k)``
rather than by segment.
Parameters
----------
V : numpy.ndarray
R-orthonormal basis as columns, ``(N, m)`` [Ohm**-0.5].
R_mat : numpy.ndarray
Diagonal loop resistance, ``(N, N)`` or its diagonal [Ohm].
M_mat : numpy.ndarray
Passive-passive inductance in code naming, ``(N, N)`` [H].
L_mat : numpy.ndarray or None, optional
Passive-to-source coupling in code naming, ``(N, n_src)`` [H].
label : str, optional
Label component of every coefficient's ``(label, k)`` name [-].
Returns
-------
ReducedWall
``L_r`` [H], ``R_r`` [Ohm], ``M_r`` [H] when a source coupling was given,
and the labels [-].
Convention
----------
``R_r`` is computed, not assumed: a basis that is not R-orthonormal in the
matrices passed here shows up as a non-identity rather than silently changing
the dynamics. Use :func:`orthonormalize_r` to make one that is.
Applicability
-------------
Machine-independent.
Provenance
----------
.. [473] Issue #473, which built the segment-wise eigenbasis, and #500, which
added the selection, scoring and moment tools.
.. [vfit8] The reduced vessel-wall contract, VEST-Tokamak/vfit#8: local
eigenbasis, global electromagnetic dynamics.
"""
V = np.asarray(V, dtype=float)
r = _diagonal_resistance(R_mat, where="R_mat")
if V.ndim != 2 or V.shape[0] != r.size:
raise WallModeError(f"basis has shape {V.shape}; expected ({r.size}, m)")
L = np.asarray(M_mat, dtype=float)
return ReducedWall(
L_r=V.T @ L @ V,
R_r=V.T @ (r[:, None] * V),
M_r=None if L_mat is None else V.T @ np.asarray(L_mat, dtype=float),
labels=tuple((str(label), k) for k in range(V.shape[1])),
keep=(np.arange(V.shape[1], dtype=np.int64),),
)Reduced operators for an arbitrary R-orthonormal basis.
The same projections as reduced_operators, for a basis that is not
(only) segment eigenmodes – an enrichment by moment_patterns, say,
or a POD basis.
| Name | Type | Unit | Description |
|---|---|---|---|
V | numpy.ndarray | Ohm**-0.5 | R-orthonormal basis as columns, |
R_mat | numpy.ndarray | Ohm | Diagonal loop resistance, |
M_mat | numpy.ndarray | H | Passive-passive inductance in code naming, |
L_mat | numpy.ndarray or None, optional | H | Passive-to-source coupling in code naming, |
label | str, optional | - | Label component of every coefficient’s |
| Returns | |||
| — | ReducedWall | - |
|
Convention
R_r is computed, not assumed: a basis that is not R-orthonormal in the
matrices passed here shows up as a non-identity rather than silently changing
the dynamics. Use orthonormalize_r to make one that is.
Input semantics · Output semantics · Applicability · Provenance
Input semantics
Element space: the assembled wall matrices and a basis whose columns are wall patterns.
Output semantics
Mode space: operators of the basis’s own coordinates, labelled (label, k)
rather than by segment.
Applicability
Machine-independent.
Provenance
- Issue #473, which built the segment-wise eigenbasis, and #500, which added the selection, scoring and moment tools.
- The reduced vessel-wall contract, VEST-Tokamak/vfit#8: local eigenbasis, global electromagnetic dynamics.
global_time_constants
Machine-independentConvention-sensitive
source
global_time_constants(basis, M_mat)
Show source vaft/process/wall_modes.py, lines 1097–1136
def global_time_constants(basis: WallModeBasis, M_mat: np.ndarray) -> np.ndarray:
"""The whole wall's decay times, from the full-rank reduced inductance.
With ``R_r = I`` the reduced pencil is an ordinary symmetric eigenproblem, so
``eigvalsh(L_r)`` are the global L/R times. They are not the segments' local
times: the difference between the two is exactly the inter-segment coupling
the basis keeps.
Parameters
----------
basis : WallModeBasis
The eigenbasis [-].
M_mat : numpy.ndarray
Passive-passive inductance in code naming, ``(N, N)`` [H].
Returns
-------
numpy.ndarray
Global decay times, descending, ``(N,)`` [s].
Convention
----------
Full rank by construction: every mode of every segment takes part, because a
truncated basis would report the decay times of a different wall.
Applicability
-------------
Machine-independent.
Provenance
----------
.. [473] Issue #473, which built the segment-wise eigenbasis, and #500, which
added the selection, scoring and moment tools.
.. [vfit8] The reduced vessel-wall contract, VEST-Tokamak/vfit#8: local
eigenbasis, global electromagnetic dynamics.
"""
V = basis.V(None)
L_r = V.T @ np.asarray(M_mat, dtype=float) @ V
L_r = 0.5 * (L_r + L_r.T)
return np.sort(np.linalg.eigvalsh(L_r))[::-1]The whole wall’s decay times, from the full-rank reduced inductance.
With R_r = I the reduced pencil is an ordinary symmetric eigenproblem, so
eigvalsh(L_r) are the global L/R times. They are not the segments’ local
times: the difference between the two is exactly the inter-segment coupling
the basis keeps.
| Name | Type | Unit | Description |
|---|---|---|---|
basis | WallModeBasis | - | The eigenbasis. |
M_mat | numpy.ndarray | H | Passive-passive inductance in code naming, |
| Returns | |||
| — | numpy.ndarray | s | Global decay times, descending, |
Convention
Full rank by construction: every mode of every segment takes part, because a truncated basis would report the decay times of a different wall.
Applicability · Provenance
Applicability
Machine-independent.
Provenance
- Issue #473, which built the segment-wise eigenbasis, and #500, which added the selection, scoring and moment tools.
- The reduced vessel-wall contract, VEST-Tokamak/vfit#8: local eigenbasis, global electromagnetic dynamics.
mode_scores
Machine-independent
source
mode_scores(basis, R_mat, M_mat, L_mat, *, G=None, drive=None, time=None, keep=None, dt_sub=5e-05)
Rankings of the retained modes, one value per retained coefficient.
Rankings, not verdicts: each is a different answer to which modes carry the wall’s response, and the order study compares them.
tau
the decay time – what the spectrum alone would rank by;
drive_gain
tau_k ||M_r[k, :]|| ||G_red[:, k]|| – the quasi-static amplitude a
unit source ramp excites in mode k, times how visible the mode is at
the observation points (all ones without G); needs no drive;
response_energy
the rms of the projected full response a = V^T R I_w(t) under
drive – how much dissipation each mode actually carried;
output_weight
response_energy times the observability, the ranking that minimizes
the diagnostic-space error fastest on the packaged wall.
| Name | Type | Unit | Description |
|---|---|---|---|
basis | WallModeBasis | - | The eigenbasis. |
R_mat | numpy.ndarray | Ohm | Diagonal loop resistance, |
M_mat | numpy.ndarray | H | Passive-passive inductance in code naming, |
L_mat | numpy.ndarray | H | Passive-to-source coupling in code naming, |
G | numpy.ndarray or None, optional | any/A | Observation response with wall elements as columns, |
drive | numpy.ndarray or None, optional | A | Source currents, |
time | numpy.ndarray or None, optional | s | Time grid of the drive. |
keep | sequence of numpy.ndarray or None, optional | - | Candidate modes per segment; every mode when |
dt_sub | float, optional | s | Sub-step of the full-wall solve the last two rankings need. |
| Returns | |||
| — | dict of str to numpy.ndarray | - |
|
Defaults · Applicability · Limitations · Provenance
Defaults
dt_sub = 50 us is a validated-workflow default, as in
solve_reduced_eddy.
Applicability
Machine-independent. The claim that output_weight falls fastest is an
observation on the packaged VEST wall, not a theorem.
Limitations
The last two rankings cost one full wall solve. Every ranking is relative to the drive and observation set it was computed with; a mode invisible to one diagnostic set is not an unimportant mode.
Provenance
- Issue #473, which built the segment-wise eigenbasis, and #500, which added the selection, scoring and moment tools.
- The reduced vessel-wall contract, VEST-Tokamak/vfit#8: local eigenbasis, global electromagnetic dynamics.
- The reduced-order validation study (VEST-Tokamak/vfit#10, vaft #494), which asks which modes to keep; this module answers no part of that question by itself.
moment_patterns
Machine-independentConvention-sensitive
source
moment_patterns(R_mat, M_mat, L_mat, order=1)
Show source vaft/process/wall_modes.py, lines 1731–1809
def moment_patterns(R_mat: np.ndarray, M_mat: np.ndarray, L_mat: np.ndarray, order: int = 1) -> np.ndarray:
"""Drive-independent wall patterns from the source coupling.
An R-orthonormal basis of the block Krylov space
``span{R^{-1} M, (R^{-1} L) R^{-1} M, ...}`` up to ``order`` blocks. The
first block is the resistive limit -- the wall current a constant source ramp
settles into, ``I_w = -R^{-1} M dI_src/dt`` -- and each further block is the
next inductive correction of the slowly driven response: the Laplace-domain
moments of the wall's transfer function, matched at zero frequency.
Built as a block Arnoldi iteration in the R inner product, so the blocks stay
independent where the raw powers would collapse onto the slowest mode; a
block that adds no new direction ends the iteration early and the basis is
narrower than ``order * n_src``.
Parameters
----------
R_mat : numpy.ndarray
Diagonal loop resistance, ``(N, N)`` or its diagonal [Ohm].
M_mat : numpy.ndarray
Passive-passive inductance in code naming; the physics ``L``, ``(N, N)``
[H].
L_mat : numpy.ndarray
Passive-to-source coupling in code naming; the physics ``M``,
``(N, n_src)`` [H].
order : int, optional
How many Krylov blocks to build [-].
Returns
-------
numpy.ndarray
R-orthonormal patterns as columns, ``(N, m)`` with
``m <= order * n_src`` [Ohm**-0.5].
Convention
----------
Argument naming follows :mod:`vaft.process.electromagnetics`, not the
physics: ``M_mat`` is the passive-passive inductance (physics ``L``) and
``L_mat`` the passive-to-source coupling (physics ``M``). ``R_mat`` is the
diagonal loop resistance either way.
Applicability
-------------
Machine-independent. The comparison quoted below is a VEST observation, not
a property of the method.
Limitations
-----------
A zero-frequency expansion: on the packaged VEST wall ten resistive patterns
reproduce the probe response of a real PF drive to about 1 %, where 150
eigenmodes are needed for the same, but fast transients remain the
eigenmodes' territory.
Provenance
----------
.. [473] Issue #473, which built the segment-wise eigenbasis, and #500, which
added the selection, scoring and moment tools.
.. [vfit8] The reduced vessel-wall contract, VEST-Tokamak/vfit#8: local
eigenbasis, global electromagnetic dynamics.
.. [vfit10] The reduced-order validation study (VEST-Tokamak/vfit#10, vaft
#494), which asks which modes to keep; this module answers no part of that
question by itself.
"""
if int(order) < 1:
raise WallModeError("order must be at least 1")
r = _diagonal_resistance(R_mat, where="R_mat")
L = np.asarray(M_mat, dtype=float)
Q = orthonormalize_r(np.asarray(L_mat, dtype=float) / r[:, None], R_mat)
blocks = [Q]
for _ in range(int(order) - 1):
X = (L @ blocks[-1]) / r[:, None]
V = np.hstack(blocks)
X = X - V @ (V.T @ (r[:, None] * X)) # R-orthogonal to everything so far
X = X - V @ (V.T @ (r[:, None] * X)) # twice, for the usual reason
Q = orthonormalize_r(X, R_mat, rtol=1e-8)
if Q.shape[1] == 0:
break
blocks.append(Q)
return canonical_sign(np.hstack(blocks))Drive-independent wall patterns from the source coupling.
An R-orthonormal basis of the block Krylov space
span{R^{-1} M, (R^{-1} L) R^{-1} M, ...} up to order blocks. The
first block is the resistive limit – the wall current a constant source ramp
settles into, I_w = -R^{-1} M dI_src/dt – and each further block is the
next inductive correction of the slowly driven response: the Laplace-domain
moments of the wall’s transfer function, matched at zero frequency.
Built as a block Arnoldi iteration in the R inner product, so the blocks stay
independent where the raw powers would collapse onto the slowest mode; a
block that adds no new direction ends the iteration early and the basis is
narrower than order * n_src.
| Name | Type | Unit | Description |
|---|---|---|---|
R_mat | numpy.ndarray | Ohm | Diagonal loop resistance, |
M_mat | numpy.ndarray | H | Passive-passive inductance in code naming; the physics |
L_mat | numpy.ndarray | H | Passive-to-source coupling in code naming; the physics |
order | int, optional | - | How many Krylov blocks to build. |
| Returns | |||
| — | numpy.ndarray | Ohm**-0.5 | R-orthonormal patterns as columns, |
Convention
Argument naming follows vaft.process.electromagnetics, not the
physics: M_mat is the passive-passive inductance (physics L) and
L_mat the passive-to-source coupling (physics M). R_mat is the
diagonal loop resistance either way.
Applicability · Limitations · Provenance
Applicability
Machine-independent. The comparison quoted below is a VEST observation, not a property of the method.
Limitations
A zero-frequency expansion: on the packaged VEST wall ten resistive patterns reproduce the probe response of a real PF drive to about 1 %, where 150 eigenmodes are needed for the same, but fast transients remain the eigenmodes’ territory.
Provenance
- Issue #473, which built the segment-wise eigenbasis, and #500, which added the selection, scoring and moment tools.
- The reduced vessel-wall contract, VEST-Tokamak/vfit#8: local eigenbasis, global electromagnetic dynamics.
- The reduced-order validation study (VEST-Tokamak/vfit#10, vaft #494), which asks which modes to keep; this module answers no part of that question by itself.
orthonormalize_r
Machine-independentConvention-sensitive
source
orthonormalize_r(X, R_mat, *, rtol=1e-10)
Show source vaft/process/wall_modes.py, lines 1674–1728
def orthonormalize_r(X: np.ndarray, R_mat: np.ndarray, *, rtol: float = 1e-10) -> np.ndarray:
"""An R-orthonormal basis of ``span(X)``, canonical in sign.
QR of ``R^{1/2} X`` with the dependent columns dropped, so a set of patterns
that repeats a direction does not return a singular basis.
Parameters
----------
X : numpy.ndarray
Patterns as columns, ``(N, m)`` [any].
R_mat : numpy.ndarray
Diagonal loop resistance, ``(N, N)`` or its diagonal [Ohm].
rtol : float, optional
A column whose triangular-factor diagonal falls below this times the
largest is dependent and is dropped [-].
Returns
-------
numpy.ndarray
R-orthonormal, sign-canonical basis, ``(N, m')`` with ``m' <= m``
[Ohm**-0.5].
Defaults
--------
``rtol = 1e-10`` is a numerical convenience: the rank cut of the QR in double
precision.
Convention
----------
Modes are R-orthonormal (``v^T R v = 1``) and sign-canonical
(:func:`canonical_sign`), so amplitudes carry units of sqrt(W) and ``a^T a``
is the wall current's ohmic dissipation. This is the same normalization the eigenbasis carries, which
is what lets a pattern basis and an eigenbasis be combined in one reduction
(:func:`combined_operators`).
Applicability
-------------
Machine-independent.
Provenance
----------
.. [473] Issue #473, which built the segment-wise eigenbasis, and #500, which
added the selection, scoring and moment tools.
.. [vfit8] The reduced vessel-wall contract, VEST-Tokamak/vfit#8: local
eigenbasis, global electromagnetic dynamics.
"""
r = _diagonal_resistance(R_mat, where="R_mat")
X = np.asarray(X, dtype=float)
if X.ndim != 2 or X.shape[0] != r.size:
raise WallModeError(f"patterns have shape {X.shape}; expected ({r.size}, m)")
w = np.sqrt(r)[:, None]
Q, T = np.linalg.qr(w * X)
diag = np.abs(np.diag(T))
independent = diag > rtol * max(diag.max() if diag.size else 0.0, 1e-300)
return canonical_sign(Q[:, independent] / w)An R-orthonormal basis of span(X), canonical in sign.
QR of R^{1/2} X with the dependent columns dropped, so a set of patterns
that repeats a direction does not return a singular basis.
| Name | Type | Unit | Description |
|---|---|---|---|
X | numpy.ndarray | any | Patterns as columns, |
R_mat | numpy.ndarray | Ohm | Diagonal loop resistance, |
rtol | float, optional | - | A column whose triangular-factor diagonal falls below this times the largest is dependent and is dropped. |
| Returns | |||
| — | numpy.ndarray | Ohm**-0.5 | R-orthonormal, sign-canonical basis, |
Convention
Modes are R-orthonormal (v^T R v = 1) and sign-canonical
(canonical_sign), so amplitudes carry units of sqrt(W) and a^T a
is the wall current’s ohmic dissipation. This is the same normalization the eigenbasis carries, which
is what lets a pattern basis and an eigenbasis be combined in one reduction
(combined_operators).
Defaults · Applicability · Provenance
Defaults
rtol = 1e-10 is a numerical convenience: the rank cut of the QR in double
precision.
Applicability
Machine-independent.
Provenance
- Issue #473, which built the segment-wise eigenbasis, and #500, which added the selection, scoring and moment tools.
- The reduced vessel-wall contract, VEST-Tokamak/vfit#8: local eigenbasis, global electromagnetic dynamics.
project
Machine-independentConvention-sensitive
source
project(basis, I_w, R_mat, keep=None)
Show source vaft/process/wall_modes.py, lines 769–826
def project(
basis: WallModeBasis, I_w: np.ndarray, R_mat: np.ndarray, keep: Sequence[np.ndarray] | None = None
) -> np.ndarray:
"""Modal amplitudes ``a = V^T R I_w``.
Exact for a current that lies inside the retained subspace; for a truncated
basis it is the R-orthogonal projection, which is the least-dissipation one.
Input semantics
---------------
Element space: a wall current, one entry per element, measured or solved.
Output semantics
----------------
Mode space: amplitudes in the R inner product, so ``a^T a`` is the current's
ohmic dissipation.
Parameters
----------
basis : WallModeBasis
The eigenbasis [-].
I_w : numpy.ndarray
Wall current, ``(N,)`` or ``(n_times, N)`` [A].
R_mat : numpy.ndarray
Diagonal loop resistance, ``(N, N)`` or its diagonal [Ohm].
keep : sequence of numpy.ndarray or None, optional
Retained mode indices per segment; every mode when ``None`` [-].
Returns
-------
numpy.ndarray
Amplitudes, ``(M_tot,)`` or ``(n_times, M_tot)`` [W**0.5].
Convention
----------
Modes are R-orthonormal (``v^T R v = 1``) and sign-canonical
(:func:`canonical_sign`), so amplitudes carry units of sqrt(W) and ``a^T a``
is the wall current's ohmic dissipation. Two other normalizations are in use elsewhere and
convert from this one: Euclidean-normalized modes have ``a_E = a ||v||_2``,
inductance-normalized ones ``a_L = a sqrt(tau)``.
Applicability
-------------
Machine-independent.
Provenance
----------
.. [473] Issue #473, which built the segment-wise eigenbasis, and #500, which
added the selection, scoring and moment tools.
.. [vfit8] The reduced vessel-wall contract, VEST-Tokamak/vfit#8: local
eigenbasis, global electromagnetic dynamics.
"""
V = basis.V(keep)
r = _diagonal_resistance(R_mat, where="R_mat")
I_w = np.asarray(I_w, dtype=float)
if I_w.ndim == 1:
return V.T @ (r * I_w)
return (I_w * r[None, :]) @ VModal amplitudes a = V^T R I_w.
Exact for a current that lies inside the retained subspace; for a truncated basis it is the R-orthogonal projection, which is the least-dissipation one.
| Name | Type | Unit | Description |
|---|---|---|---|
basis | WallModeBasis | - | The eigenbasis. |
I_w | numpy.ndarray | A | Wall current, |
R_mat | numpy.ndarray | Ohm | Diagonal loop resistance, |
keep | sequence of numpy.ndarray or None, optional | - | Retained mode indices per segment; every mode when |
| Returns | |||
| — | numpy.ndarray | W**0.5 | Amplitudes, |
Convention
Modes are R-orthonormal (v^T R v = 1) and sign-canonical
(canonical_sign), so amplitudes carry units of sqrt(W) and a^T a
is the wall current’s ohmic dissipation. Two other normalizations are in use elsewhere and
convert from this one: Euclidean-normalized modes have a_E = a ||v||_2,
inductance-normalized ones a_L = a sqrt(tau).
Input semantics · Output semantics · Applicability · Provenance
Input semantics
Element space: a wall current, one entry per element, measured or solved.
Output semantics
Mode space: amplitudes in the R inner product, so a^T a is the current’s
ohmic dissipation.
Applicability
Machine-independent.
Provenance
- Issue #473, which built the segment-wise eigenbasis, and #500, which added the selection, scoring and moment tools.
- The reduced vessel-wall contract, VEST-Tokamak/vfit#8: local eigenbasis, global electromagnetic dynamics.
reconstruct
Machine-independent
source
reconstruct(basis, a, keep=None)
Show source vaft/process/wall_modes.py, lines 829–870
def reconstruct(basis: WallModeBasis, a: np.ndarray, keep: Sequence[np.ndarray] | None = None) -> np.ndarray:
"""Wall currents ``I_w = V a`` from modal amplitudes.
The inverse direction of :func:`project`; exact only for the retained
subspace, which is what :func:`reconstruction_error` measures.
Input semantics
---------------
Mode space: amplitudes of the retained modes.
Output semantics
----------------
Element space: a wall current on every element, whatever the retained order.
Parameters
----------
basis : WallModeBasis
The eigenbasis [-].
a : numpy.ndarray
Amplitudes, ``(M_tot,)`` or ``(n_times, M_tot)`` [W**0.5].
keep : sequence of numpy.ndarray or None, optional
Retained mode indices per segment; every mode when ``None`` [-].
Returns
-------
numpy.ndarray
Wall current, ``(N,)`` or ``(n_times, N)`` [A].
Applicability
-------------
Machine-independent.
Provenance
----------
.. [473] Issue #473, which built the segment-wise eigenbasis, and #500, which
added the selection, scoring and moment tools.
.. [vfit8] The reduced vessel-wall contract, VEST-Tokamak/vfit#8: local
eigenbasis, global electromagnetic dynamics.
"""
V = basis.V(keep)
a = np.asarray(a, dtype=float)
return V @ a if a.ndim == 1 else a @ V.TWall currents I_w = V a from modal amplitudes.
The inverse direction of project; exact only for the retained
subspace, which is what reconstruction_error measures.
| Name | Type | Unit | Description |
|---|---|---|---|
basis | WallModeBasis | - | The eigenbasis. |
a | numpy.ndarray | W**0.5 | Amplitudes, |
keep | sequence of numpy.ndarray or None, optional | - | Retained mode indices per segment; every mode when |
| Returns | |||
| — | numpy.ndarray | A | Wall current, |
Input semantics · Output semantics · Applicability · Provenance
Input semantics
Mode space: amplitudes of the retained modes.
Output semantics
Element space: a wall current on every element, whatever the retained order.
Applicability
Machine-independent.
Provenance
- Issue #473, which built the segment-wise eigenbasis, and #500, which added the selection, scoring and moment tools.
- The reduced vessel-wall contract, VEST-Tokamak/vfit#8: local eigenbasis, global electromagnetic dynamics.
reconstruction_error
Machine-independentConvention-sensitive
source
reconstruction_error(I_w, I_rec, R_mat, basis=None)
Show source vaft/process/wall_modes.py, lines 873–933
def reconstruction_error(
I_w: np.ndarray, I_rec: np.ndarray, R_mat: np.ndarray, basis: WallModeBasis | None = None
) -> dict[str, Any]:
"""How far a reconstruction is from the full current, in the norms that matter.
Parameters
----------
I_w : numpy.ndarray
The full wall current, ``(N,)`` or ``(n_times, N)`` [A].
I_rec : numpy.ndarray
The reconstruction, same shape [A].
R_mat : numpy.ndarray
Diagonal loop resistance, ``(N, N)`` or its diagonal [Ohm].
basis : WallModeBasis or None, optional
When given, the same measures are reported per segment [-].
Returns
-------
dict
``relative_l2``, ``relative_dissipation`` and ``max_abs``, plus one
such entry per segment id when a basis was given [-].
``max_abs`` is the largest absolute element error, in amperes; the
other two are relative.
Convention
----------
``relative_dissipation`` weighs each element by its resistance -- the natural
norm of this basis, where the error's ohmic power is compared with the
current's -- and ``relative_l2`` is the plain Euclidean measure. They differ
by however unevenly the wall's resistance is distributed, so a reported
error means nothing without saying which of the two it is.
Applicability
-------------
Machine-independent.
Provenance
----------
.. [473] Issue #473, which built the segment-wise eigenbasis, and #500, which
added the selection, scoring and moment tools.
.. [vfit8] The reduced vessel-wall contract, VEST-Tokamak/vfit#8: local
eigenbasis, global electromagnetic dynamics.
"""
r = _diagonal_resistance(R_mat, where="R_mat")
I_w = np.asarray(I_w, dtype=float).reshape(-1, r.size)
I_rec = np.asarray(I_rec, dtype=float).reshape(-1, r.size)
err = I_rec - I_w
def _measures(e: np.ndarray, ref: np.ndarray, w: np.ndarray) -> dict[str, float]:
l2 = float(np.linalg.norm(e) / max(np.linalg.norm(ref), 1e-300))
diss = float(np.sqrt(np.sum(e**2 * w) / max(np.sum(ref**2 * w), 1e-300)))
return {"relative_l2": l2, "relative_dissipation": diss, "max_abs": float(np.max(np.abs(e)))}
out: dict[str, Any] = _measures(err, I_w, r[None, :])
if basis is not None:
out["segments"] = {
seg.id: _measures(err[:, seg.index], I_w[:, seg.index], r[None, seg.index])
for seg in basis.segments
}
return outHow far a reconstruction is from the full current, in the norms that matter.
| Name | Type | Unit | Description |
|---|---|---|---|
I_w | numpy.ndarray | A | The full wall current, |
I_rec | numpy.ndarray | A | The reconstruction, same shape. |
R_mat | numpy.ndarray | Ohm | Diagonal loop resistance, |
basis | WallModeBasis or None, optional | - | When given, the same measures are reported per segment. |
| Returns | |||
| — | dict | - |
|
Convention
relative_dissipation weighs each element by its resistance – the natural
norm of this basis, where the error’s ohmic power is compared with the
current’s – and relative_l2 is the plain Euclidean measure. They differ
by however unevenly the wall’s resistance is distributed, so a reported
error means nothing without saying which of the two it is.
Applicability · Provenance
Applicability
Machine-independent.
Provenance
- Issue #473, which built the segment-wise eigenbasis, and #500, which added the selection, scoring and moment tools.
- The reduced vessel-wall contract, VEST-Tokamak/vfit#8: local eigenbasis, global electromagnetic dynamics.
reduce_response
Machine-independent
source
reduce_response(G_full, basis, keep=None)
Show source vaft/process/wall_modes.py, lines 936–982
def reduce_response(
G_full: np.ndarray, basis: WallModeBasis, keep: Sequence[np.ndarray] | None = None
) -> np.ndarray:
"""Reduce any linear response with the wall elements as its columns.
Probe field, flux-loop flux, grid psi or B, an EFIT table -- one matmul, no
per-class logic, so every response class is reduced by the same basis.
Input semantics
---------------
Element space: a response matrix mapping every wall element to observations.
Output semantics
----------------
Mode space: the same map from the retained modal amplitudes.
Parameters
----------
G_full : numpy.ndarray
Response with wall elements as columns, ``(n_obs, N)`` [any/A].
basis : WallModeBasis
The eigenbasis [-].
keep : sequence of numpy.ndarray or None, optional
Retained mode indices per segment; every mode when ``None`` [-].
Returns
-------
numpy.ndarray
``G_red = G_full V_seg``, ``(n_obs, M_tot)`` [any/W**0.5].
Applicability
-------------
Machine-independent.
Provenance
----------
.. [473] Issue #473, which built the segment-wise eigenbasis, and #500, which
added the selection, scoring and moment tools.
.. [vfit8] The reduced vessel-wall contract, VEST-Tokamak/vfit#8: local
eigenbasis, global electromagnetic dynamics.
"""
G_full = np.asarray(G_full, dtype=float)
if G_full.shape[-1] != basis.n_elements:
raise WallModeError(
f"response has {G_full.shape[-1]} wall columns, basis has {basis.n_elements} elements"
)
return G_full @ basis.V(keep)Reduce any linear response with the wall elements as its columns.
Probe field, flux-loop flux, grid psi or B, an EFIT table – one matmul, no per-class logic, so every response class is reduced by the same basis.
| Name | Type | Unit | Description |
|---|---|---|---|
G_full | numpy.ndarray | any/A | Response with wall elements as columns, |
basis | WallModeBasis | - | The eigenbasis. |
keep | sequence of numpy.ndarray or None, optional | - | Retained mode indices per segment; every mode when |
| Returns | |||
| — | numpy.ndarray | any/W**0.5 |
|
Input semantics · Output semantics · Applicability · Provenance
Input semantics
Element space: a response matrix mapping every wall element to observations.
Output semantics
Mode space: the same map from the retained modal amplitudes.
Applicability
Machine-independent.
Provenance
- Issue #473, which built the segment-wise eigenbasis, and #500, which added the selection, scoring and moment tools.
- The reduced vessel-wall contract, VEST-Tokamak/vfit#8: local eigenbasis, global electromagnetic dynamics.
reduced_operators
Machine-independentConvention-sensitive
source
reduced_operators(basis, R_mat, M_mat, L_mat=None, keep=None)
Show source vaft/process/wall_modes.py, lines 693–766
def reduced_operators(
basis: WallModeBasis,
R_mat: np.ndarray,
M_mat: np.ndarray,
L_mat: np.ndarray | None = None,
keep: Sequence[np.ndarray] | None = None,
) -> ReducedWall:
"""Project the full wall matrices onto the retained modes.
The global half of the contract. Because the *full* matrices are projected,
the mutual inductance between segments survives in the off-diagonal blocks of
``L_r``; only the diagonal blocks are the local ``diag(tau_g)``.
Input semantics
---------------
Element space: the assembled wall matrices, one row and column per wall
element, as :mod:`vaft.process.electromagnetics` builds them.
Output semantics
----------------
Mode space: operators of the retained modal coordinates, whose amplitudes
carry units of sqrt(W). A solve on them is the reduced circuit
(:func:`solve_reduced_eddy`); to come back to element space, use
:func:`reconstruct`.
Parameters
----------
basis : WallModeBasis
The eigenbasis [-].
R_mat : numpy.ndarray
Diagonal loop resistance, ``(N, N)`` or its diagonal [Ohm].
M_mat : numpy.ndarray
Passive-passive inductance in code naming, ``(N, N)`` [H].
L_mat : numpy.ndarray or None, optional
Passive-to-source coupling in code naming, ``(N, n_src)`` [H].
keep : sequence of numpy.ndarray or None, optional
Retained mode indices per segment; every mode when ``None`` [-].
Returns
-------
ReducedWall
``L_r`` ``(M_tot, M_tot)`` [H], ``R_r`` ``(M_tot, M_tot)`` [Ohm],
``M_r`` ``(M_tot, n_src)`` [H] when a source coupling was given, and the
``(segment_id, k)`` label of every coefficient [-].
Convention
----------
Argument naming follows :mod:`vaft.process.electromagnetics`, not the
physics: ``M_mat`` is the passive-passive inductance (physics ``L``) and
``L_mat`` the passive-to-source coupling (physics ``M``). ``R_mat`` is the
diagonal loop resistance either way.
``R_r`` is computed rather than assumed to be the identity, so a basis built
on other matrices than the ones passed here shows up as a non-identity
instead of silently changing the dynamics.
Applicability
-------------
Machine-independent.
Provenance
----------
.. [473] Issue #473, which built the segment-wise eigenbasis, and #500, which
added the selection, scoring and moment tools.
.. [vfit8] The reduced vessel-wall contract, VEST-Tokamak/vfit#8: local
eigenbasis, global electromagnetic dynamics.
"""
V = basis.V(keep)
r = _diagonal_resistance(R_mat, where="R_mat")
L = np.asarray(M_mat, dtype=float)
L_r = V.T @ L @ V
R_r = V.T @ (r[:, None] * V)
M_r = None if L_mat is None else V.T @ np.asarray(L_mat, dtype=float)
return ReducedWall(L_r=L_r, R_r=R_r, M_r=M_r, labels=basis.labels(keep), keep=basis._keep(keep))Project the full wall matrices onto the retained modes.
The global half of the contract. Because the full matrices are projected,
the mutual inductance between segments survives in the off-diagonal blocks of
L_r; only the diagonal blocks are the local diag(tau_g).
| Name | Type | Unit | Description |
|---|---|---|---|
basis | WallModeBasis | - | The eigenbasis. |
R_mat | numpy.ndarray | Ohm | Diagonal loop resistance, |
M_mat | numpy.ndarray | H | Passive-passive inductance in code naming, |
L_mat | numpy.ndarray or None, optional | H | Passive-to-source coupling in code naming, |
keep | sequence of numpy.ndarray or None, optional | - | Retained mode indices per segment; every mode when |
| Returns | |||
| — | ReducedWall | - |
|
Convention
Argument naming follows vaft.process.electromagnetics, not the
physics: M_mat is the passive-passive inductance (physics L) and
L_mat the passive-to-source coupling (physics M). R_mat is the
diagonal loop resistance either way.
R_r is computed rather than assumed to be the identity, so a basis built
on other matrices than the ones passed here shows up as a non-identity
instead of silently changing the dynamics.
Input semantics · Output semantics · Applicability · Provenance
Input semantics
Element space: the assembled wall matrices, one row and column per wall
element, as vaft.process.electromagnetics builds them.
Output semantics
Mode space: operators of the retained modal coordinates, whose amplitudes
carry units of sqrt(W). A solve on them is the reduced circuit
(solve_reduced_eddy); to come back to element space, use
reconstruct.
Applicability
Machine-independent.
Provenance
- Issue #473, which built the segment-wise eigenbasis, and #500, which added the selection, scoring and moment tools.
- The reduced vessel-wall contract, VEST-Tokamak/vfit#8: local eigenbasis, global electromagnetic dynamics.
segment_eigenmodes
Machine-independentConvention-sensitive
source
segment_eigenmodes(R_gg, L_gg, *, residual_atol=1e-10, cond_max=1000000000000.0, tau_floor_rel=1e-12)
Solve one segment’s pencil R_gg v = L_gg v / tau.
The local half of the contract: each segment’s own L/R eigenbasis, from which the global dynamics are then built by projecting the full matrices.
| Name | Type | Unit | Description |
|---|---|---|---|
R_gg | numpy.ndarray | Ohm | The segment’s diagonal resistance, as a matrix or as its diagonal |
L_gg | numpy.ndarray | H | The segment’s symmetric inductance block. |
residual_atol | float, optional | - | Largest relative pencil residual accepted. |
cond_max | float, optional | - | Largest accepted spread of decay times within the segment. |
tau_floor_rel | float, optional | - | Smallest accepted decay time, relative to the largest. |
| Returns | |||
tau | numpy.ndarray | s | Local decay times, descending, |
V | numpy.ndarray | Ohm**-0.5 | R-orthonormal, sign-canonical modes as columns, |
residual | float | - |
|
Convention
Modes are R-orthonormal (v^T R v = 1) and sign-canonical
(canonical_sign), so amplitudes carry units of sqrt(W) and a^T a
is the wall current’s ohmic dissipation. Ordering is by descending tau within the
segment, which is the mode index every keep and every label refers to.
Raises
WallModeErrorWhen the block is not a usable inductance (a non-positive eigenvalue is a current pattern with no stored energy), when the spread exceedscond_maxso the fastest mode is numerically indistinguishable from zero, or when the residual exceedsresidual_atol.
Processing steps · Defaults · Applicability · Provenance
Processing steps
- Whiten by the resistance:
S = R^{-1/2} L R^{-1/2}, symmetric. eigh(S); the eigenvalues are the local L/R decay times.- Map back,
v = R^{-1/2} q, sov^T R v = 1andv^T L v = tau. - Order by descending
tauand fix the signs (canonical_sign).
Defaults
All three are numerical convenience, set where double precision stops supporting the answer rather than where physics changes.
Applicability
Machine-independent.
Provenance
- Issue #473, which built the segment-wise eigenbasis, and #500, which added the selection, scoring and moment tools.
- The reduced vessel-wall contract, VEST-Tokamak/vfit#8: local eigenbasis, global electromagnetic dynamics.
select_all
Machine-independent
source
select_all(basis)
Show source vaft/process/wall_modes.py, lines 989–1013
def select_all(basis: WallModeBasis) -> tuple[np.ndarray, ...]:
"""Keep every mode of every segment: the full-rank selection.
Parameters
----------
basis : WallModeBasis
The eigenbasis [-].
Returns
-------
tuple of numpy.ndarray
One index array per segment, holding every mode index [-].
Applicability
-------------
Machine-independent.
Provenance
----------
.. [473] Issue #473, which built the segment-wise eigenbasis, and #500, which
added the selection, scoring and moment tools.
.. [vfit8] The reduced vessel-wall contract, VEST-Tokamak/vfit#8: local
eigenbasis, global electromagnetic dynamics.
"""
return basis._keep(None)Keep every mode of every segment: the full-rank selection.
| Name | Type | Unit | Description |
|---|---|---|---|
basis | WallModeBasis | - | The eigenbasis. |
| Returns | |||
| — | tuple of numpy.ndarray | - | One index array per segment, holding every mode index. |
Applicability · Provenance
Applicability
Machine-independent.
Provenance
- Issue #473, which built the segment-wise eigenbasis, and #500, which added the selection, scoring and moment tools.
- The reduced vessel-wall contract, VEST-Tokamak/vfit#8: local eigenbasis, global electromagnetic dynamics.
select_by_score
Machine-independentConvention-sensitive
source
select_by_score(basis, score, M, keep=None)
Show source vaft/process/wall_modes.py, lines 1454–1506
def select_by_score(
basis: WallModeBasis, score: np.ndarray, M: int, keep: Sequence[np.ndarray] | None = None
) -> tuple[np.ndarray, ...]:
"""Keep the highest-scoring modes across segments.
The global counterpart of :func:`select_slowest`, for any ranking -- usually
one of :func:`mode_scores`.
Parameters
----------
basis : WallModeBasis
The eigenbasis [-].
score : numpy.ndarray
One value per coefficient of ``basis.labels(keep)`` [-].
M : int
How many modes to keep in total [-].
keep : sequence of numpy.ndarray or None, optional
Candidate modes per segment; every mode when ``None`` [-].
Returns
-------
tuple of numpy.ndarray
One index array per segment [-].
Convention
----------
Ties resolve toward the earlier label, so the selection is deterministic for
a given score vector.
Applicability
-------------
Machine-independent.
Provenance
----------
.. [473] Issue #473, which built the segment-wise eigenbasis, and #500, which
added the selection, scoring and moment tools.
.. [vfit8] The reduced vessel-wall contract, VEST-Tokamak/vfit#8: local
eigenbasis, global electromagnetic dynamics.
.. [vfit10] The reduced-order validation study (VEST-Tokamak/vfit#10, vaft
#494), which asks which modes to keep; this module answers no part of that
question by itself.
"""
labels = basis.labels(keep)
score = np.asarray(score, dtype=float).reshape(-1)
if score.size != len(labels):
raise WallModeError(f"score has {score.size} entries for {len(labels)} candidate modes")
order = np.argsort(-score, kind="stable")[: max(int(M), 0)]
chosen = set(labels[i] for i in order)
return tuple(
np.array(sorted(k for (seg_id, k) in chosen if seg_id == seg.id), dtype=np.int64)
for seg in basis.segments
)Keep the highest-scoring modes across segments.
The global counterpart of select_slowest, for any ranking – usually
one of mode_scores.
| Name | Type | Unit | Description |
|---|---|---|---|
basis | WallModeBasis | - | The eigenbasis. |
score | numpy.ndarray | - | One value per coefficient of |
M | int | - | How many modes to keep in total. |
keep | sequence of numpy.ndarray or None, optional | - | Candidate modes per segment; every mode when |
| Returns | |||
| — | tuple of numpy.ndarray | - | One index array per segment. |
Convention
Ties resolve toward the earlier label, so the selection is deterministic for a given score vector.
Applicability · Provenance
Applicability
Machine-independent.
Provenance
- Issue #473, which built the segment-wise eigenbasis, and #500, which added the selection, scoring and moment tools.
- The reduced vessel-wall contract, VEST-Tokamak/vfit#8: local eigenbasis, global electromagnetic dynamics.
- The reduced-order validation study (VEST-Tokamak/vfit#10, vaft #494), which asks which modes to keep; this module answers no part of that question by itself.
select_slowest
Machine-independent
source
select_slowest(basis, M)
Show source vaft/process/wall_modes.py, lines 1016–1060
def select_slowest(basis: WallModeBasis, M: int | Sequence[int]) -> tuple[np.ndarray, ...]:
"""Keep the slowest modes, globally or per segment.
The selection the spectrum alone suggests; whether it is the right one for a
given drive is what :func:`mode_scores` exists to question.
Parameters
----------
basis : WallModeBasis
The eigenbasis [-].
M : int or sequence of int
A count taken globally across segments by descending decay time, or one
count per segment [-].
Returns
-------
tuple of numpy.ndarray
One index array per segment [-].
Applicability
-------------
Machine-independent.
Provenance
----------
.. [473] Issue #473, which built the segment-wise eigenbasis, and #500, which
added the selection, scoring and moment tools.
.. [vfit8] The reduced vessel-wall contract, VEST-Tokamak/vfit#8: local
eigenbasis, global electromagnetic dynamics.
.. [vfit10] The reduced-order validation study (VEST-Tokamak/vfit#10, vaft
#494), which asks which modes to keep; this module answers no part of that
question by itself.
"""
if isinstance(M, (int, np.integer)):
tau = basis.tau()
labels = basis.labels()
chosen = set(labels[i] for i in np.argsort(-tau, kind="stable")[: int(M)])
return tuple(
np.array([k for k in range(seg.size) if (seg.id, k) in chosen], dtype=np.int64)
for seg in basis.segments
)
counts = list(M)
if len(counts) != len(basis.segments):
raise WallModeError(f"per-segment M has {len(counts)} entries for {len(basis.segments)} segments")
return tuple(np.arange(min(int(m), seg.size), dtype=np.int64) for seg, m in zip(basis.segments, counts))Keep the slowest modes, globally or per segment.
The selection the spectrum alone suggests; whether it is the right one for a
given drive is what mode_scores exists to question.
| Name | Type | Unit | Description |
|---|---|---|---|
basis | WallModeBasis | - | The eigenbasis. |
M | int or sequence of int | - | A count taken globally across segments by descending decay time, or one count per segment. |
| Returns | |||
| — | tuple of numpy.ndarray | - | One index array per segment. |
Applicability · Provenance
Applicability
Machine-independent.
Provenance
- Issue #473, which built the segment-wise eigenbasis, and #500, which added the selection, scoring and moment tools.
- The reduced vessel-wall contract, VEST-Tokamak/vfit#8: local eigenbasis, global electromagnetic dynamics.
- The reduced-order validation study (VEST-Tokamak/vfit#10, vaft #494), which asks which modes to keep; this module answers no part of that question by itself.
select_tau_range
Machine-independent
source
select_tau_range(basis, tau_min, tau_max=inf)
Show source vaft/process/wall_modes.py, lines 1063–1094
def select_tau_range(basis: WallModeBasis, tau_min: float, tau_max: float = np.inf) -> tuple[np.ndarray, ...]:
"""Keep the modes whose decay time lies in a range, per segment.
Parameters
----------
basis : WallModeBasis
The eigenbasis [-].
tau_min : float
Lower bound, inclusive [s].
tau_max : float, optional
Upper bound, inclusive [s].
Returns
-------
tuple of numpy.ndarray
One index array per segment [-].
Applicability
-------------
Machine-independent.
Provenance
----------
.. [473] Issue #473, which built the segment-wise eigenbasis, and #500, which
added the selection, scoring and moment tools.
.. [vfit8] The reduced vessel-wall contract, VEST-Tokamak/vfit#8: local
eigenbasis, global electromagnetic dynamics.
"""
return tuple(
np.flatnonzero((seg.tau >= tau_min) & (seg.tau <= tau_max)).astype(np.int64)
for seg in basis.segments
)Keep the modes whose decay time lies in a range, per segment.
| Name | Type | Unit | Description |
|---|---|---|---|
basis | WallModeBasis | - | The eigenbasis. |
tau_min | float | s | Lower bound, inclusive. |
tau_max | float, optional | s | Upper bound, inclusive. |
| Returns | |||
| — | tuple of numpy.ndarray | - | One index array per segment. |
Applicability · Provenance
Applicability
Machine-independent.
Provenance
- Issue #473, which built the segment-wise eigenbasis, and #500, which added the selection, scoring and moment tools.
- The reduced vessel-wall contract, VEST-Tokamak/vfit#8: local eigenbasis, global electromagnetic dynamics.
solve_reduced_eddy
Machine-independent
source
solve_reduced_eddy(reduced, drive, time, *, V=None, dt_sub=5e-05, method='auto')
Show source vaft/process/wall_modes.py, lines 1253–1332
def solve_reduced_eddy(
reduced: ReducedWall,
drive: np.ndarray,
time: np.ndarray,
*,
V: np.ndarray | None = None,
dt_sub: float = 5.0e-5,
method: str = "auto",
) -> tuple[np.ndarray, np.ndarray | None]:
"""Integrate the reduced circuit ``L_r da/dt + R_r a = -M_r dI_src/dt``.
The same integrator as the full wall
(:func:`vaft.process.electromagnetics.solve_eddy_currents`), fed the projected
operators, so the only difference between the two solutions is the retained
subspace: with every mode kept they agree to rounding.
Input semantics
---------------
Mode space: reduced operators and a source drive in element-independent
coordinates.
Output semantics
----------------
Mode space amplitudes, and -- when the retained ``V`` is given -- the
reconstructed element-space wall current alongside them.
Parameters
----------
reduced : ReducedWall
The projected operators; ``M_r`` must be present [-].
drive : numpy.ndarray
Source currents, ``(n_times, n_src)`` on ``time`` [A].
time : numpy.ndarray
Time grid of the drive [s].
V : numpy.ndarray or None, optional
The retained modes, ``(N, M_tot)``; when given, the wall current is
reconstructed too [Ohm**-0.5].
dt_sub : float, optional
Sub-step of the integrator [s].
method : str, optional
Integration method, forwarded to the full-wall solver [-].
Returns
-------
a : numpy.ndarray
Modal amplitudes, ``(n_times, M_tot)`` [W**0.5].
I_w : numpy.ndarray or None
``V a``, ``(n_times, N)``, or ``None`` when no ``V`` was given [A].
Defaults
--------
``dt_sub = 50 us`` is a validated-workflow default: the sub-step the full-wall
solver is used with in the VEST eddy-current workflow. ``method="auto"``
leaves the choice to that solver.
Applicability
-------------
Machine-independent.
Provenance
----------
.. [473] Issue #473, which built the segment-wise eigenbasis, and #500, which
added the selection, scoring and moment tools.
.. [vfit8] The reduced vessel-wall contract, VEST-Tokamak/vfit#8: local
eigenbasis, global electromagnetic dynamics.
.. [vfit10] The reduced-order validation study (VEST-Tokamak/vfit#10, vaft
#494), which asks which modes to keep; this module answers no part of that
question by itself.
"""
from vaft.process.electromagnetics import solve_eddy_currents
if reduced.M_r is None:
raise WallModeError("solve_reduced_eddy needs the projected source coupling M_r")
a = solve_eddy_currents(
reduced.R_r, reduced.M_r, reduced.L_r,
np.asarray(drive, dtype=float), np.asarray(time, dtype=float),
dt_sub=dt_sub, method=method,
)
I_w = None if V is None else a @ np.asarray(V, dtype=float).T
return a, I_wIntegrate the reduced circuit L_r da/dt + R_r a = -M_r dI_src/dt.
The same integrator as the full wall
(vaft.process.electromagnetics.solve_eddy_currents), fed the projected
operators, so the only difference between the two solutions is the retained
subspace: with every mode kept they agree to rounding.
| Name | Type | Unit | Description |
|---|---|---|---|
reduced | ReducedWall | - | The projected operators; |
drive | numpy.ndarray | A | Source currents, |
time | numpy.ndarray | s | Time grid of the drive. |
V | numpy.ndarray or None, optional | Ohm**-0.5 | The retained modes, |
dt_sub | float, optional | s | Sub-step of the integrator. |
method | str, optional | - | Integration method, forwarded to the full-wall solver. |
| Returns | |||
a | numpy.ndarray | W**0.5 | Modal amplitudes, |
I_w | numpy.ndarray or None | A |
|
Input semantics · Output semantics · Defaults · Applicability · Provenance
Input semantics
Mode space: reduced operators and a source drive in element-independent coordinates.
Output semantics
Mode space amplitudes, and – when the retained V is given – the
reconstructed element-space wall current alongside them.
Defaults
dt_sub = 50 us is a validated-workflow default: the sub-step the full-wall
solver is used with in the VEST eddy-current workflow. method="auto"
leaves the choice to that solver.
Applicability
Machine-independent.
Provenance
- Issue #473, which built the segment-wise eigenbasis, and #500, which added the selection, scoring and moment tools.
- The reduced vessel-wall contract, VEST-Tokamak/vfit#8: local eigenbasis, global electromagnetic dynamics.
- The reduced-order validation study (VEST-Tokamak/vfit#10, vaft #494), which asks which modes to keep; this module answers no part of that question by itself.
subspace_angles_r
Machine-independentConvention-sensitive
source
subspace_angles_r(V_a, V_b, R_mat)
Show source vaft/process/wall_modes.py, lines 1203–1246
def subspace_angles_r(V_a: np.ndarray, V_b: np.ndarray, R_mat: np.ndarray) -> np.ndarray:
"""Principal angles between two mode subspaces in the R inner product.
Two bases of a near-degenerate eigenspace differ by a rotation, so the right
question is whether they span the same space, not whether their columns
match.
Parameters
----------
V_a : numpy.ndarray
First set of modes as columns, ``(N, m_a)`` [Ohm**-0.5].
V_b : numpy.ndarray
Second set, ``(N, m_b)`` [Ohm**-0.5].
R_mat : numpy.ndarray
Diagonal loop resistance, ``(N, N)`` or its diagonal [Ohm].
Returns
-------
numpy.ndarray
Principal angles, ascending, ``(min(m_a, m_b),)`` [rad].
Convention
----------
The angles are taken in the R metric, not the Euclidean one: both sets are
mapped by ``R^{1/2}`` first, so the ordinary angles of the mapped columns are
the R-metric angles of the originals. A zero angle means a shared direction;
``pi/2`` means an R-orthogonal one.
Applicability
-------------
Machine-independent.
Provenance
----------
.. [473] Issue #473, which built the segment-wise eigenbasis, and #500, which
added the selection, scoring and moment tools.
.. [vfit8] The reduced vessel-wall contract, VEST-Tokamak/vfit#8: local
eigenbasis, global electromagnetic dynamics.
"""
from scipy.linalg import subspace_angles
r = _diagonal_resistance(R_mat, where="R_mat")
w = np.sqrt(r)[:, None]
return subspace_angles(w * np.asarray(V_a, dtype=float), w * np.asarray(V_b, dtype=float))Principal angles between two mode subspaces in the R inner product.
Two bases of a near-degenerate eigenspace differ by a rotation, so the right question is whether they span the same space, not whether their columns match.
| Name | Type | Unit | Description |
|---|---|---|---|
V_a | numpy.ndarray | Ohm**-0.5 | First set of modes as columns, |
V_b | numpy.ndarray | Ohm**-0.5 | Second set, |
R_mat | numpy.ndarray | Ohm | Diagonal loop resistance, |
| Returns | |||
| — | numpy.ndarray | rad | Principal angles, ascending, |
Convention
The angles are taken in the R metric, not the Euclidean one: both sets are
mapped by R^{1/2} first, so the ordinary angles of the mapped columns are
the R-metric angles of the originals. A zero angle means a shared direction;
pi/2 means an R-orthogonal one.
Applicability · Provenance
Applicability
Machine-independent.
Provenance
- Issue #473, which built the segment-wise eigenbasis, and #500, which added the selection, scoring and moment tools.
- The reduced vessel-wall contract, VEST-Tokamak/vfit#8: local eigenbasis, global electromagnetic dynamics.
symmetrize_inductance
Machine-independent
source
symmetrize_inductance(L, *, rtol=1e-08, reject=1e-06)
Fold a mutual-inductance matrix onto its symmetric part, with a refusal.
Reciprocity makes a mutual-inductance matrix symmetric, so a departure is a
defect in the asset, not noise. The sanctioned place to repair reciprocity is
the coupling mapper (vaft.machine_mapping.em_coupling.em_coupling,
issue #373), which is why this one refuses rather than repairs beyond a
rounding-sized asymmetry.
| Name | Type | Unit | Description |
|---|---|---|---|
L | numpy.ndarray | H | The inductance matrix, |
rtol | float, optional | - | Below this relative asymmetry the fold is silent. |
reject | float, optional | - | Above this relative asymmetry the matrix is refused. |
| Returns | |||
L_sym | numpy.ndarray | H |
|
asymmetry | float | - |
|
Raises
WallModeErrorWhen the asymmetry exceedsreject, and before that when the matrix is not square, carries a non-finite entry, or is identically zero.
Defaults · Applicability · Warnings · Provenance
Defaults
rtol = 1e-8 is a numerical convenience, the size of double-precision
round-off accumulated over an assembly of this scale. reject = 1e-6 is
empirical: the asymmetry of the packaged VEST coupling asset once its
one-sided material factor was repaired (issue #373).
Applicability
Machine-independent.
Warnings
An asymmetry between rtol and reject is folded with a
UserWarning, so the repair reaches the provenance rather than
happening silently.
Provenance
- Issue #473, which built the segment-wise eigenbasis, and #500, which added the selection, scoring and moment tools.
- The reduced vessel-wall contract, VEST-Tokamak/vfit#8: local eigenbasis, global electromagnetic dynamics.