Skip to content

Commit 0f86bd7

Browse files
d-burgclaude
andcommitted
Regression - FEATURE - Pin the del_s layer width against Fitzpatrick figs 6.2/6.3
Every other case in this suite pins GPEC against its own previous output, which answers "did this change?" but never "is this right?". This one pins it against published values: riccati_del_s integrates Fitzpatrick (IOP 2023) equations (6.29)/(6.30)/(6.38) directly and returns the plotted delta_s/d_beta, so prescribing the normalized parameters reproduces the figures' own axes. Verified at eight points inside the published contour bands, and the magnitudes match the book's table 6.2 reactor estimates at DIII-D-like parameters. Pure ODE -- no equilibrium, no geqdsk, no kinetic file -- so it is sub-second, fast-tier eligible, and carries no equilibrium dependence, meaning its golden values survive equilibrium-side re-baselining. Complements slayer_delta_probe, which pins the dispersion solve rather than the width solve. tau is not stated in the book's figure captions, so the reproduction's assumption (tau = 1, table 5.1) is pinned as a tracked quantity rather than left implicit. P_hat = 0 is excluded: it is a singular edge of the model, where alpha vanishes and F -> -i Q_hat at the origin, so pinning it would record a modelling boundary rather than a result. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 16842d3 commit 0f86bd7

2 files changed

Lines changed: 110 additions & 0 deletions

File tree

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Regression case: GPEC's del_s Riccati layer-width solver against Fitzpatrick, "Tearing Mode
2+
# Dynamics in Tokamak Plasmas" (IOP 2023), figures 6.2 and 6.3 (p. 6-10).
3+
#
4+
# This is the suite's only case pinned to an EXTERNAL published reference rather than to GPEC's own
5+
# previous output: a failure means we no longer reproduce the textbook. Verified at eight points
6+
# against the published contour bands, e.g. (Q,P) = (4.0, 0.5) -> Re 2.008 against the 1.9-2.1
7+
# contours, (0.5, 4.0) -> Re 1.537 against the 1.5 contour. The magnitudes also match the book's
8+
# table 6.2 reactor estimates (|delta_s| 2.95-4.69 mm) at DIII-D-like parameters.
9+
#
10+
# Pure ODE: no equilibrium, no geqdsk, no kinetic file. Sub-second, so it is fast-tier eligible, and
11+
# it carries no equilibrium dependence -- its golden values survive equilibrium-side re-baselining.
12+
# Complements slayer_delta_probe, which pins the dispersion solve Delta(Q); this pins the width
13+
# solve delta_s/d_beta over (Q_hat, P_hat).
14+
[case]
15+
name = "slayer_dels_fitzpatrick"
16+
description = "del_s Riccati layer width vs Fitzpatrick 2023 figs 6.2/6.3 over (Q_hat, P_hat)"
17+
kind = "computed"
18+
19+
# The scan axes, pinned so a silently reshaped grid is a failure rather than a re-gold.
20+
[quantities.q_hat]
21+
h5path = "fitzpatrick/Q_hat"
22+
type = "real_vector"
23+
extract = "all_real"
24+
label = "grid Q_hat (= Q_*/D^4)"
25+
noise_threshold = 0
26+
order = 10
27+
28+
[quantities.p_hat]
29+
h5path = "fitzpatrick/P_hat"
30+
type = "real_vector"
31+
extract = "all_real"
32+
label = "grid P_hat (= P/D^6)"
33+
noise_threshold = 0
34+
order = 11
35+
36+
# tau is not stated in the book's figure captions; tau = 1 (table 5.1, low-field reactor) is the
37+
# assumption this reproduction rests on, so it is pinned rather than left implicit.
38+
[quantities.tau]
39+
h5path = "fitzpatrick/tau"
40+
type = "real_scalar"
41+
extract = "value"
42+
label = "tau (assumed; not given in the figure captions)"
43+
noise_threshold = 0
44+
order = 12
45+
46+
[quantities.dels_db_re]
47+
h5path = "fitzpatrick/dels_db_re"
48+
type = "real_vector"
49+
extract = "all_real"
50+
label = "Re(delta_s/d_beta) — Fitzpatrick fig 6.2"
51+
noise_threshold = 1e-10
52+
order = 20
53+
54+
[quantities.dels_db_im]
55+
h5path = "fitzpatrick/dels_db_im"
56+
type = "real_vector"
57+
extract = "all_real"
58+
label = "Im(delta_s/d_beta) — Fitzpatrick fig 6.3"
59+
noise_threshold = 1e-10
60+
order = 21
61+
62+
[quantities.runtime]
63+
h5path = ""
64+
type = "runtime"
65+
extract = "value"
66+
label = "Runtime (s)"
67+
noise_threshold = 0.0
68+
order = 999

regression-harness/src/runner.jl

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,46 @@ h5open(ARGS[1], "w") do fid
155155
end
156156
%RUNINFO%
157157
"""
158+
# External-reference validation: GPEC's del_s Riccati solver against Fitzpatrick, "Tearing Mode
159+
# Dynamics in Tokamak Plasmas" (IOP 2023), figures 6.2 and 6.3. Unlike every other case in this
160+
# suite -- which pins GPEC against its own previous output -- this one pins it against published
161+
# values, so a failure means "we no longer reproduce the textbook", not "we changed".
162+
#
163+
# riccati_del_s integrates the book's layer equations directly: E and F are (6.29)/(6.30), the
164+
# Riccati form is (6.38), and the returned dels_db is the plotted delta_s/d_beta. Prescribing the
165+
# normalized parameters (D_norm = 1, P_perp = P_tor = Phat, Q_e = Qhat/(1+1/tau)) makes the solver's
166+
# internal Q_hat equal the book's Qhat_*, so the grid below is exactly the figures' axes.
167+
#
168+
# tau is NOT stated in the figure captions; tau = 1 (table 5.1, low-field reactor) is pinned here as
169+
# an explicit assumption of the case, not inherited from a default that could drift.
170+
#
171+
# Phat = 0 is a singular edge of the model, not a hard case: alpha = sqrt(Phat_perp/(1+1/tau))
172+
# vanishes so the large-q boundary form degenerates, and F -> -i*Qhat at the origin. The grid starts
173+
# just inside so the pins record results rather than a modelling boundary.
174+
const COMPUTED_SLAYER_DELS_FITZPATRICK_SCRIPT_TEMPLATE = """
175+
using Pkg
176+
%INSTANTIATE%
177+
using GeneralizedPerturbedEquilibrium
178+
using GeneralizedPerturbedEquilibrium.InnerLayer
179+
using HDF5
180+
const TAU = 1.0
181+
probe(Q, P) = SLAYERParameters(; tau=TAU, lu=1.0, c_beta=0.0, D_norm=1.0,
182+
P_perp=P, P_tor=P, Q_e=Q / (1 + 1/TAU), Q_i=0.0, iota_e=0.0, tauk=1.0,
183+
tau_r=1.0, delta_n=1.0, rs=1.0, R0=1.0, bt=1.0, sval_r=1.0, eta=1.0, d_beta=1.0)
184+
axis = [0.02, 0.5, 1.0, 2.0, 4.0]
185+
QP = [(q, p) for p in axis for q in axis]
186+
t_start = time()
187+
dels = ComplexF64[riccati_del_s(probe(q, p)) for (q, p) in QP]
188+
elapsed = time() - t_start
189+
h5open(ARGS[1], "w") do fid
190+
fid["fitzpatrick/Q_hat"] = Float64[q for (q, _) in QP]
191+
fid["fitzpatrick/P_hat"] = Float64[p for (_, p) in QP]
192+
fid["fitzpatrick/dels_db_re"] = real.(dels)
193+
fid["fitzpatrick/dels_db_im"] = imag.(dels)
194+
fid["fitzpatrick/tau"] = TAU
195+
end
196+
%RUNINFO%
197+
"""
158198
# Self-contained separatrix-finder regression (PR #296). Loads a fixed-boundary EFIT whose
159199
# computational box hugs the LCFS (eps=0.05 TokaMaker aspect-scan g-file): outside the prescribed
160200
# LCFS the coil-vacuum flux turns back above the boundary value before the grid edge, so the old
@@ -291,6 +331,8 @@ function _computed_script_template(case_spec::CaseSpec)
291331
return COMPUTED_SLAYER_DELTA_PROBE_SCRIPT_TEMPLATE
292332
elseif case_spec.name == "efit_fixedbdy_separatrix"
293333
return COMPUTED_SEPARATRIX_SCRIPT_TEMPLATE
334+
elseif case_spec.name == "slayer_dels_fitzpatrick"
335+
return COMPUTED_SLAYER_DELS_FITZPATRICK_SCRIPT_TEMPLATE
294336
end
295337
error("No computed-script template registered for case '$(case_spec.name)'")
296338
end

0 commit comments

Comments
 (0)