Reduced-wall order study
Reduced-wall order study
The wall eigenmode basis (Wall eigenmodes,
#473) can represent the
vessel current in any number of modes. This page is about how many, and
which — the representation order M_repr = (M_1, …, M_G) that
VEST-Tokamak/vfit#10 asks
for, implemented in vaft.validation.wall_reduction
(#494).
Three layers, kept apart on purpose:
| layer | question | function |
|---|---|---|
| full ↔ reduced | how much of the wall’s own response does a truncated basis miss? | order_convergence |
| representation order | which is the smallest order within the tolerances an application sets? | representation_order |
| measurement ↔ model | do full and reduced walls disagree with the data alike (vessel-model error) or differently (reduction error)? | experimental_comparison |
Metrics only, no verdicts: tolerances are the study’s and are reported with the result.
The reduced circuit
With the projected operators of the basis the reduced wall obeys
L_r da/dt + R_r a = -M_r dI_coil/dt, I_w = V a
integrated by the same routine as the full wall
(vaft.process.electromagnetics.solve_eddy_currents), so the only difference
between the two solutions is the retained subspace. Keeping every mode
reproduces the 950-loop solve to 1e-12; the error of any truncation is the
basis span, not the closure.
from vaft.process import wall_modes as wm
from vaft.validation import wall_reduction as wr
system = wr.wall_system(ods) # R, L, source coupling, basis, PF drive
keep = wm.select_by_score(system["basis"], wm.mode_scores(system["basis"], system["R_mat"],
system["M_mat"], system["L_mat"], drive=system["drive"],
time=system["time"])["output_weight"], 76)
ops = wm.reduced_operators(system["basis"], system["R_mat"], system["M_mat"], system["L_mat"], keep)
a, I_w = wm.solve_reduced_eddy(ops, system["drive"], system["time"], V=system["basis"].V(keep))
Which modes: rankings, not the spectrum
mode_scores returns one value per mode under four rankings — tau (the
decay time), drive_gain (quasi-static amplitude a unit source ramp excites,
times observability; needs no drive), response_energy (rms projected
amplitude under a drive) and output_weight (energy times observability).
select_by_score keeps the top M across segments; allocate_per_segment
grows a per-segment allocation greedily until a dissipation or output
tolerance is met, adding modes where the remaining error is largest.
On the packaged wall (shot 39915, its PF programme, 62 probes and 11 flux loops), relative error of the wall term at the probes:
| retained | slowest (tau) |
one per segment | output_weight |
|---|---|---|---|
| 19 | 28 % | 15 % | 7.4 % |
| 76 | 24 % | 6.1 % | 1.2 % |
| 152 | 5.6 % | 3.4 % | 0.26 % |
Decay time alone is a poor guide: the driven response is not sparse in eigen coordinates, and a whole-wall eigenbasis behaves the same as the segment-wise one, so the penalty is intrinsic to eigen coordinates rather than to the segmentation.
Moment patterns: the enrichment the contract does not yet include
The response to the coils lives in the block Krylov space
span{R⁻¹M, (R⁻¹L)R⁻¹M, …} — the coil-controllable subspace — and
moment_patterns returns an R-orthonormal basis of it, block by block, built
as a block Arnoldi iteration in the R inner product. The first block is the
resistive limit, the wall current a constant ramp settles into; each further
block is the next inductive correction (the transfer function’s moments at
zero frequency).
| patterns | probe error, PF programme | probe error, step drive |
|---|---|---|
| 10 (resistive limit) | 1.7 % | — |
| 19 | 0.9 % | 25 % |
| 76 | 1e-4 | 0.6 % |
Ten drive-independent vectors do what 76 eigenmodes do, and 76 do what no
eigen truncation reaches. These patterns are global, not segment-tagged, so
they are outside the reduced-wall contract (vfit #8); the study reports them
as the alternative the downstream EFIT issues can decide on.
combined_operators projects the wall onto any R-orthonormal basis, eigen
or not.
Running the study
observation = wr.observation_set(ods, n_coils=system["n_coils"]) # probes, loops, boundary ring, grid
rows = wr.order_convergence(system, observation=observation) # every (rule, order, drive)
order = wr.representation_order(rows, {"probe": 0.01, "flux_loop": 0.01, "grid_psi": 0.01})
order_convergence scores the drive-dependent rankings on the PF programme
and then tests every selection on three drives — the programme, the loudest
coil alone, and a step on it — so a row for step says how a selection made
for the programme transfers to a transient it never saw. Rows carry the
Euclidean and dissipation-weighted current error, the error per segment, the
relative error at each observation class, the probe peak error and the cost.
Two plots draw the rows: plot_passive_structure_overview_wall_reduction
(error against retained order, one panel per metric) and
plot_passive_structure_field_wall_reduction (the wall’s flux on the
equilibrium region — full, reduced or their difference).
Against the measurements
result = wr.experimental_comparison(ods, {"tau_19": wm.select_slowest(system["basis"], 19),
"moments_30": wm.moment_patterns(system["R_mat"],
system["M_mat"], system["L_mat"], 3)})
This runs the #190
plasma-free benchmark for the full PF-only wall and for each reduced wall
over the same window, and reports two blocks per model: measurement
(residual against the data) and reduction (distance from the full model
at the same channels). On shot 39915 the full wall improves the coil-only
residual by a median 78 % over the 73 usable channels, and a converged
reduction improves it by the same 78 % to within a tenth of a point
(moments_30, 0.5 % from the full wall term at the probes): the residual
that remains is the vessel model’s, not the truncation’s. The nineteen
slowest modes are the counter-example rather than a second instance of it:
they reach only 64 %, and sit 34 % from the full wall term at the probes over
this benchmark’s 25 ms window — the 28 % in the table above is the same rule
and the same metric scored over the whole record instead, so the two are not
in conflict. The separation is a claim about converged reductions, not about
reduction in general.
find_plasma_free_shots classifies database shots as plasma_free,
plasma, undriven or daq_missing from their plasma-current and coil
peaks, with the evidence, so dedicated vacuum shots can be brought into the
comparison; the plasma-free interval of a mapped shot is judged by
vaft.validation.vacuum_benchmark.plasma_free_interval as for any other case.