Skip to content

Draw the vacuum field a start-up is read from: one cached evaluation, four quantities, a time slider - #690

Open
HongSik-Yun-Fusion wants to merge 5 commits into
developfrom
claude/vacuum-field-map
Open

Draw the vacuum field a start-up is read from: one cached evaluation, four quantities, a time slider#690
HongSik-Yun-Fusion wants to merge 5 commits into
developfrom
claude/vacuum-field-map

Conversation

@HongSik-Yun-Fusion

Copy link
Copy Markdown
Contributor

An interactive vacuum-field map

vacuum_field draws the field the coils and the vessel make with no plasma in
it: the flux, |B_p| in gauss, the field decay index, and the breakdown figure
of merit E_t B_t / B_p. field= chooses among them, time_index= steps along
the PF time base, and interactive=True gives both as widgets.

All four come from one evaluation, compute_vacuum_field_map, so the panels
of a start-up figure cannot end up describing four slightly different instants.

Why it is fast enough to slide

A grid's response to each coil and vessel loop is a pure function of geometry,
so it is built once and cached; a different time then costs a contraction with
that instant's currents. The vessel eddy solve is cached the same way, because
a recipe hands the evaluator a private copy of the ODS on every frame and would
otherwise repeat the same two-second solve each time.

packaged shot
first evaluation, 65×65 0.67 s
another time, same grid 44 ms
a whole redrawn frame from an untouched ODS 0.22 s

The map is confined to the limiter, and that is the load-bearing choice

VEST's limiter bounding box contains 330 of the machine's 1480 current
filaments, so a map over the box is dominated by conductors — at the breakdown
instant the entire vessel fell in the bottom contour bin while a handful of coil
points took the whole colour range. Not one filament lies inside the limiter
outline, so masking to it leaves exactly the field a discharge would see, and
the levels then describe that field.

Levels come from a percentile of what is left, and Field2D gained an extend
policy so the points the percentile excludes saturate at the end colours rather
than appearing as holes indistinguishable from missing data.

A dense time base is a slider, not a list

This is the first plot to emit a range control. The kind was declared and unit
tested but never instantiated, so the widget path was unexercised; it works in
both the Matplotlib and ipywidgets backends. A times block names the option it
drives, so a stored equilibrium's handful of slices keeps its radio buttons.
Discovery resolves the default instant the way the builder does — from the
magnetics, before the private copy that drops them — so opening the controls
does not move the figure.

What the plan asked for and measurement refused

The plan had equilibrium_field_psi_vacuum move onto the same evaluator. It
should not: on the 129×129 equilibrium grid that plot draws, the exact elliptic
kernels cost 18 s against the polynomial path's 5.8 s, and their cached matrices
would be 591 MB. That trade buys a slider, not a single figure. It stays where
it is, and the evaluator now refuses a grid whose response would not fit its
budget instead of quietly allocating half a gigabyte and keeping it.

Also here

  • Three reusable kernels in vaft.formula.equilibrium:
    poloidal_field_magnitude, decay_index_from_bz (nan where B_Z vanishes,
    not a spike), toroidal_electric_field (full weber in, per Formula psi helpers assume Wb/rad silently while the Green's functions return full Wb #354).
  • Display policy: gauss for magnetic_field, a new electric_field quantity,
    and the decay index as dimensionless. Units are three vocabularies here, so
    the existing applies_to gate offers the flux units only while flux is drawn.
  • _vacuum_sources factored out of compute_point_response_matrices_ods, so
    the cache key is derived from the same extraction the matrices are built from
    rather than restated beside it.

The ids declaration is load-bearing

With no time=, the map is drawn at the breakdown onset, and that timing reads
H-alpha from spectrometer_uv alongside the plasma current. My first version
left it out of the plot's ids. The OMAS adapter hands over the whole ODS, so
it never noticed; the IMAS adapter loads only declared IDSs, got no spectrometer
channels, and resolved an onset one PF sample earlier — during the ramp a 1.3%
difference in the drawn flux, not just the title. test_imas_omas_plot_equivalence
caught it; equilibrium_field_psi_vacuum has declared both all along for this
reason, and a direct test now pins it.

30 new tests across test_vacuum_field_map.py and test_vacuum_field_plot.py;
the formula catalog count moves 107 → 110 for the three new kernels.

Test run

Full suite on this branch, rebased onto 399f4e13: 9855 passed, 1 failed. The
one failure is test_flux_loop_voltage.py::test_legacy_flux_is_the_documented_voltage_composition,
which fails identically on a clean checkout of develop and is the single
failure in develop's own Package CI at the same commit: #679 made a degenerate
baseline window raise, and that test still feeds a 2000-sample signal with the
default windows at samples 3499:5000 and 11999:15000. Not touched here; it needs
its own fix.

Part of #487.

🤖 Generated with Claude Code

HongSik-Yun-Fusion and others added 5 commits September 11, 2026 15:10
The startup maps -- flux, poloidal field strength, decay index, the
breakdown figure of merit -- are four readings of one field, so they are
now four readings of one evaluation. Splitting them across separate
computations is how they drift apart.

The grid's response to each coil and vessel loop depends on geometry and
not on time, so it is built once and cached; a different time then costs
a contraction with that instant's currents. On the packaged shot that is
0.67 s once against 44 ms a frame, which is the difference between a
time slider and a progress bar.

Two details the cache had to get right. Its key comes from the same
source extraction the matrices are built from, now factored out of
compute_point_response_matrices_ods, so a cached matrix cannot outlive a
geometry change it failed to notice. And each waveform is read once per
frame rather than once per sample -- the smaller sibling of the defect
fixed in #645.

dpsi/dt is this map's own flux differenced against its neighbouring
samples, so the induced electric field belongs to the same field as
everything else instead of being spliced in from a measured loop
voltage.

The flux agrees with compute_null_ods to 0.0001% at the 95th percentile,
which is what makes re-pointing the existing psi map safe. It disagrees
only where a grid point lands on a source filament and both answers are
meaningless, so the test bounds a percentile rather than a maximum.
Note that compute_null_ods lays its grid out (Z, R) and this evaluator
lays it out (R, Z).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Four quantities on one plot -- the flux, |B_p| in gauss, the field decay
index, and the breakdown figure E_t B_t / B_p -- chosen with field=, and
stepped along the PF time base with time_index=. They come from one
cached evaluation, so the panels of a start-up figure cannot end up
describing four slightly different instants.

The map is confined to the limiter outline, and that is the load-bearing
choice. VEST's limiter bounding box contains 330 of the machine's 1480
current filaments, so a map over the box is dominated by conductors: at
the breakdown instant the whole vessel fell in the bottom contour bin
while a handful of coil points took the entire colour range. Not one
filament lies inside the limiter outline itself, so masking to it leaves
exactly the field a discharge would see -- and the levels then describe
that field. Levels come from a percentile of what is left, and Field2D
gained an extend policy so the excluded points saturate at the end
colours instead of coming out as holes indistinguishable from no data.

A dense time base is a slider, not a list. This is the first plot to
emit a range control, declared through a times block that names the
option it drives, so a stored equilibrium's handful of slices keeps its
radio buttons. Discovery resolves the default instant the way the
builder does -- from the magnetics, before the private copy that drops
them -- so opening the controls does not move the figure.

Units are three vocabularies here, not one: gauss for the field, V/m for
the breakdown figure, nothing at all for the decay index. The existing
applies_to gate handles it, offering the flux units only while the flux
is drawn.

The vessel currents are cached alongside the response matrices. A recipe
hands the evaluator a private copy each frame, which would otherwise
repeat the same two-second eddy solve every time; a frame now costs
0.22 s from a caller's untouched ODS.

The plan also had equilibrium_field_psi_vacuum move onto this evaluator.
Measurement says no: on the 129x129 equilibrium grid that plot draws,
the exact elliptic kernels cost 18 s against the polynomial path's 5.8 s
and their cached matrices would be 591 MB. That trade buys a slider, not
a single figure. It stays where it is, and the evaluator now refuses a
grid whose response would not fit its budget rather than quietly
allocating half a gigabyte and keeping it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The level percentile's justification had the ratio backwards: away from a
null the map's maximum sits about 30% above its 99th percentile, not
within a third of it. Also note that only Matplotlib needs telling about
the extend policy -- Plotly clamps to its level range already, so the two
backends agree on these maps either way.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
With no time= the map is drawn at the breakdown onset, and that timing
reads H-alpha from spectrometer_uv alongside the plasma current. I left
it out of the plot's ids, so an adapter that loads only the declared IDSs
resolved a different onset than one handed the whole entry -- one PF
sample apart, which during the ramp is a 1.3% difference in the drawn
flux, not just a different number in the title. Only the IMAS side showed
it, because the OMAS adapter passes the whole ODS regardless.
equilibrium_field_psi_vacuum has declared both all along, for this exact
reason. Found by test_imas_omas_plot_equivalence, and now pinned directly
so it cannot come back quietly.

The flux equivalence test also has to respect the response budget added
after it was written: it now compares against every third point of
compute_null_ods's own grid, and bounds a percentile plus the fraction of
points that disagree at all rather than a 99th percentile that was really
measuring the subsampling. Measured: median 3e-8, 95th percentile 7e-7,
1.4% of points past 1e-3, all of them beside a source filament.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
poloidal_field_magnitude, decay_index_from_bz and toroidal_electric_field
take the equilibrium category from 107 to 110. Checked against develop's
own surface rather than inferred from the failure: exactly those three are
new, and nothing develop added was dropped in the rebase.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant