d0fus 2.3.4__tar.gz → 2.3.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {d0fus-2.3.4 → d0fus-2.3.5}/D0FUS_BIB/D0FUS_physical_functions.py +877 -460
- {d0fus-2.3.4 → d0fus-2.3.5}/D0FUS_EXE/D0FUS_run.py +1 -1
- {d0fus-2.3.4 → d0fus-2.3.5}/PKG-INFO +1 -1
- {d0fus-2.3.4 → d0fus-2.3.5}/d0fus.egg-info/PKG-INFO +1 -1
- {d0fus-2.3.4 → d0fus-2.3.5}/pyproject.toml +1 -1
- {d0fus-2.3.4 → d0fus-2.3.5}/D0FUS.py +0 -0
- {d0fus-2.3.4 → d0fus-2.3.5}/D0FUS_BIB/D0FUS_cost_data.py +0 -0
- {d0fus-2.3.4 → d0fus-2.3.5}/D0FUS_BIB/D0FUS_cost_functions.py +0 -0
- {d0fus-2.3.4 → d0fus-2.3.5}/D0FUS_BIB/D0FUS_figures.py +0 -0
- {d0fus-2.3.4 → d0fus-2.3.5}/D0FUS_BIB/D0FUS_import.py +0 -0
- {d0fus-2.3.4 → d0fus-2.3.5}/D0FUS_BIB/D0FUS_parameterization.py +0 -0
- {d0fus-2.3.4 → d0fus-2.3.5}/D0FUS_BIB/D0FUS_radial_build_functions.py +0 -0
- {d0fus-2.3.4 → d0fus-2.3.5}/D0FUS_EXE/D0FUS_genetic.py +0 -0
- {d0fus-2.3.4 → d0fus-2.3.5}/D0FUS_EXE/D0FUS_popcon.py +0 -0
- {d0fus-2.3.4 → d0fus-2.3.5}/D0FUS_EXE/D0FUS_scan.py +0 -0
- {d0fus-2.3.4 → d0fus-2.3.5}/D0FUS_EXE/D0FUS_uncertainty.py +0 -0
- {d0fus-2.3.4 → d0fus-2.3.5}/LICENSE +0 -0
- {d0fus-2.3.4 → d0fus-2.3.5}/README.md +0 -0
- {d0fus-2.3.4 → d0fus-2.3.5}/d0fus.egg-info/SOURCES.txt +0 -0
- {d0fus-2.3.4 → d0fus-2.3.5}/d0fus.egg-info/dependency_links.txt +0 -0
- {d0fus-2.3.4 → d0fus-2.3.5}/d0fus.egg-info/entry_points.txt +0 -0
- {d0fus-2.3.4 → d0fus-2.3.5}/d0fus.egg-info/requires.txt +0 -0
- {d0fus-2.3.4 → d0fus-2.3.5}/d0fus.egg-info/top_level.txt +0 -0
- {d0fus-2.3.4 → d0fus-2.3.5}/setup.cfg +0 -0
|
@@ -27,6 +27,166 @@ else:
|
|
|
27
27
|
from D0FUS_BIB.D0FUS_import import *
|
|
28
28
|
from D0FUS_BIB.D0FUS_parameterization import *
|
|
29
29
|
|
|
30
|
+
if __name__ == "__main__":
|
|
31
|
+
# ════════════════════════════════════════════════════════════════════
|
|
32
|
+
# Executable verification suite
|
|
33
|
+
# ────────────────────────────────────────────────────────────────────
|
|
34
|
+
# Active only under direct execution:
|
|
35
|
+
# python D0FUS_BIB/D0FUS_physical_functions.py
|
|
36
|
+
#
|
|
37
|
+
# Each function group below is followed by a __main__ block that
|
|
38
|
+
# (i) re-evaluates the published anchors of its functions, and/or
|
|
39
|
+
# (ii) advances ONE step of a single coherent ITER chain, the same
|
|
40
|
+
# machine from A to Z, mirroring the production solver of
|
|
41
|
+
# D0FUS_EXE/D0FUS_run.py on the shipped deck
|
|
42
|
+
# D0FUS_INPUTS/1_run_ITER.txt.
|
|
43
|
+
#
|
|
44
|
+
# Every block ends with a uniform comparison table and a PASS/FAIL
|
|
45
|
+
# verdict (_bench below). A global summary closes the file and exits
|
|
46
|
+
# with a non-zero status if any check failed, so the suite can serve
|
|
47
|
+
# as a regression guard.
|
|
48
|
+
# ════════════════════════════════════════════════════════════════════
|
|
49
|
+
|
|
50
|
+
_BENCH = {"checks": 0, "failed": [], "blocks": 0}
|
|
51
|
+
|
|
52
|
+
def _fmt(v):
|
|
53
|
+
"""Compact numeric formatting for the verification tables."""
|
|
54
|
+
if isinstance(v, str):
|
|
55
|
+
return v
|
|
56
|
+
if v is None:
|
|
57
|
+
return "-"
|
|
58
|
+
av = abs(v)
|
|
59
|
+
if av != 0.0 and (av < 1e-3 or av >= 1e5):
|
|
60
|
+
return f"{v:.3e}"
|
|
61
|
+
return f"{v:.4g}"
|
|
62
|
+
|
|
63
|
+
def _bench(title, rows, notes=()):
|
|
64
|
+
"""Print a uniform verification table with a PASS/FAIL verdict.
|
|
65
|
+
|
|
66
|
+
Parameters
|
|
67
|
+
----------
|
|
68
|
+
title : str
|
|
69
|
+
Block title, printed as a section header.
|
|
70
|
+
rows : list of tuple
|
|
71
|
+
(quantity, value, reference, rel_tol, source) with:
|
|
72
|
+
- rel_tol = float : checked row, |value/reference - 1| < rel_tol;
|
|
73
|
+
- reference = (lo, hi) tuple : checked row, lo <= value <= hi;
|
|
74
|
+
- rel_tol = None (and reference not a tuple) : informative row,
|
|
75
|
+
printed but not counted.
|
|
76
|
+
notes : iterable of str, optional
|
|
77
|
+
Footnotes printed under the table.
|
|
78
|
+
"""
|
|
79
|
+
_BENCH["blocks"] += 1
|
|
80
|
+
print(f"\n-- {title} " + "-" * max(2, 92 - len(title)))
|
|
81
|
+
print(f" {'Quantity':<36} {'D0FUS':>11} {'Reference':>11} "
|
|
82
|
+
f"{'D [%]':>8} {'tol':>6} Source")
|
|
83
|
+
print(" " + "-" * 94)
|
|
84
|
+
n_pass, n_chk = 0, 0
|
|
85
|
+
for qty, val, ref, tol, src in rows:
|
|
86
|
+
if tol is None and not isinstance(ref, tuple):
|
|
87
|
+
print(f" {qty:<36} {_fmt(val):>11} {_fmt(ref):>11} "
|
|
88
|
+
f"{'-':>8} {'info':>6} {src}")
|
|
89
|
+
continue
|
|
90
|
+
n_chk += 1
|
|
91
|
+
_BENCH["checks"] += 1
|
|
92
|
+
if isinstance(ref, tuple):
|
|
93
|
+
lo, hi = ref
|
|
94
|
+
ok = bool(lo <= val <= hi)
|
|
95
|
+
dev_s, tol_s = ("in" if ok else "OUT"), "range"
|
|
96
|
+
ref_s = f"{_fmt(lo)}..{_fmt(hi)}"
|
|
97
|
+
else:
|
|
98
|
+
dev = val / ref - 1.0
|
|
99
|
+
ok = bool(abs(dev) < tol)
|
|
100
|
+
dev_s = f"{dev * 100:+.2f}"
|
|
101
|
+
tol_s = f"{tol * 100:g}%"
|
|
102
|
+
ref_s = _fmt(ref)
|
|
103
|
+
if ok:
|
|
104
|
+
n_pass += 1
|
|
105
|
+
else:
|
|
106
|
+
_BENCH["failed"].append((title, qty))
|
|
107
|
+
print(f" {qty:<36} {_fmt(val):>11} {ref_s:>11} "
|
|
108
|
+
f"{dev_s:>8} {tol_s:>6} {src:<22} "
|
|
109
|
+
f"{'ok' if ok else '** FAIL **'}")
|
|
110
|
+
for note in notes:
|
|
111
|
+
print(f" . {note}")
|
|
112
|
+
if n_chk:
|
|
113
|
+
verdict = "PASSED" if n_pass == n_chk else "** FAILED **"
|
|
114
|
+
print(f" -> {verdict} ({n_pass}/{n_chk} checks within tolerance)")
|
|
115
|
+
|
|
116
|
+
def _bench_summary():
|
|
117
|
+
"""Global verdict over all blocks; non-zero exit on any failure."""
|
|
118
|
+
print("\n" + "=" * 96)
|
|
119
|
+
if not _BENCH["failed"]:
|
|
120
|
+
print(f"ALL TESTS PASSED - {_BENCH['checks']} checks "
|
|
121
|
+
f"in {_BENCH['blocks']} blocks")
|
|
122
|
+
else:
|
|
123
|
+
print(f"{len(_BENCH['failed'])} CHECK(S) FAILED "
|
|
124
|
+
f"out of {_BENCH['checks']}:")
|
|
125
|
+
for blk, qty in _BENCH["failed"]:
|
|
126
|
+
print(f" - [{blk}] {qty}")
|
|
127
|
+
raise SystemExit(1)
|
|
128
|
+
|
|
129
|
+
# ────────────────────────────────────────────────────────────────────
|
|
130
|
+
# Shared ITER Q=10 reference case - single source of truth for the
|
|
131
|
+
# chain blocks below.
|
|
132
|
+
#
|
|
133
|
+
# ITER : deck inputs, mirroring D0FUS_INPUTS/1_run_ITER.txt
|
|
134
|
+
# (benchmark conventions: peak TF field referenced at the
|
|
135
|
+
# winding-pack front face, published inboard stack
|
|
136
|
+
# b = 1.10 m, Tbar solved by resolve_Tbar for the Greenwald
|
|
137
|
+
# fraction f_GW = 0.85). The dict is progressively enriched
|
|
138
|
+
# with the chain results (V, nbar, pbar, P_rad, tau_E, Ip...)
|
|
139
|
+
# so that every block consumes the outputs of the previous
|
|
140
|
+
# ones, exactly like the production solver.
|
|
141
|
+
# FROZEN : converged outputs of that deck (frozen 2026-06),
|
|
142
|
+
# re-asserted by the final full-deck regression block. Chain
|
|
143
|
+
# blocks read FROZEN only (i) to check consistency, and
|
|
144
|
+
# (ii) as forward references where the file order places a
|
|
145
|
+
# function after its first use (q95 for the bootstrap and
|
|
146
|
+
# SOL blocks, I_Ohm for the ohmic-power block, f_alpha and
|
|
147
|
+
# f_imp_dil for the density block); each forward reference
|
|
148
|
+
# is then independently re-derived and closed by the chain
|
|
149
|
+
# block that follows the corresponding definition.
|
|
150
|
+
#
|
|
151
|
+
# Published anchors: Shimada et al., Nucl. Fusion 47 (2007) S1
|
|
152
|
+
# (machine values) and Kim et al., Nucl. Fusion 58 (2018) 056013
|
|
153
|
+
# (flat-top heating mix and profiles), as documented in the deck.
|
|
154
|
+
# ────────────────────────────────────────────────────────────────────
|
|
155
|
+
ITER = dict(
|
|
156
|
+
# Geometry and field (deck section 1)
|
|
157
|
+
R0=6.2, a=2.0, b=1.10, Bmax_TF=10.60,
|
|
158
|
+
# Power and operation
|
|
159
|
+
P_fus=500.0,
|
|
160
|
+
P_NBI=33.0, P_ECRH=6.7, P_ICRH=10.0, P_LH=0.0, # flat-top mix [Kim 2018]
|
|
161
|
+
P_aux=33.0 + 6.7 + 10.0, # = 49.7 MW
|
|
162
|
+
Tbar=7.754, # solved by resolve_Tbar for f_GW_target = 0.85
|
|
163
|
+
H=1.0, M=2.5,
|
|
164
|
+
# Profiles (deck section 2c)
|
|
165
|
+
nu_n=0.01, nu_T=2.80,
|
|
166
|
+
rho_ped=0.95, n_ped_frac=0.99, T_ped_frac=0.55,
|
|
167
|
+
# Composition and radiation (deck section 4)
|
|
168
|
+
Zeff=1.65, imp={'W': 2e-5, 'Ne': 7e-3}, r_synch=0.6,
|
|
169
|
+
rho_rad_core=0.75, C_Alpha=5.0,
|
|
170
|
+
# Current-drive deposition (deck section 11)
|
|
171
|
+
A_beam=2, E_beam_keV=1000.0, rho_NBI=0.30, rho_EC=0.40,
|
|
172
|
+
angle_NBI_deg=20.0,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
FROZEN = dict(
|
|
176
|
+
kappa=1.8792, kappa95=1.67785, delta=0.527517, delta95=0.351678,
|
|
177
|
+
V=847.587, S=687.331,
|
|
178
|
+
f_alpha=0.0297618, f_imp_dil=0.07103,
|
|
179
|
+
nbar=0.98561, nbar_line=1.01245, pbar=0.267547, nG=1.19112,
|
|
180
|
+
B0=5.300, W_th=340.154, betaT=0.023938, betaP=0.651514, betaN=1.63515,
|
|
181
|
+
P_Ohm=0.3910, I_Ohm=8.52244,
|
|
182
|
+
P_Brem=13.3005, P_syn=3.65678, P_line_core=24.9375, P_line=44.8641,
|
|
183
|
+
tauE=3.14385, Ip=14.9681, q95=3.59843, Ib=4.81601,
|
|
184
|
+
eta_LH=0.310283, eta_EC=0.0463759, eta_NBI=0.292349, I_CD=1.62962,
|
|
185
|
+
Q=9.98184, P_sep=87.8792, P_LH_th=73.522,
|
|
186
|
+
B_pol=0.715156, lambda_q_mm=1.12391, Gamma_n=0.58196,
|
|
187
|
+
tau_alpha=15.7193,
|
|
188
|
+
)
|
|
189
|
+
|
|
30
190
|
#%% Geometry formulas
|
|
31
191
|
|
|
32
192
|
# Explicit scipy import for the three-point shaping profiles below.
|
|
@@ -657,13 +817,6 @@ if __name__ == "__main__":
|
|
|
657
817
|
import D0FUS_BIB.D0FUS_figures as figs
|
|
658
818
|
figs.plot_volume_comparison(R0=6.2, a=2.0)
|
|
659
819
|
|
|
660
|
-
if __name__ == "__main__":
|
|
661
|
-
# ITER plasma volume anchor — 831 m³ (Shimada et al., NF 47 (2007) S1).
|
|
662
|
-
# Analytic shaped-torus vs true separatrix: 6 % tolerance.
|
|
663
|
-
_V = f_plasma_volume(6.2, 2.0, 1.85, 0.485)
|
|
664
|
-
assert abs(_V/831. - 1) < 0.06, _V
|
|
665
|
-
print(f"OK Volume ITER: {_V:.0f} m³ vs 831 publié")
|
|
666
|
-
|
|
667
820
|
def f_first_wall_surface(R0, a, kappa_edge, delta_edge=0.0,
|
|
668
821
|
geometry_model='Academic', N_theta=2000):
|
|
669
822
|
"""
|
|
@@ -716,6 +869,47 @@ if __name__ == "__main__":
|
|
|
716
869
|
import D0FUS_BIB.D0FUS_figures as figs
|
|
717
870
|
figs.plot_first_wall_surface(R0=6.2, a=2.0)
|
|
718
871
|
|
|
872
|
+
if __name__ == "__main__":
|
|
873
|
+
# ── ITER chain (1/12) - shaping and geometry ─────────────────────────
|
|
874
|
+
# Deck options: Option_Kappa = 'Wenninger' (shaping derived from the
|
|
875
|
+
# aspect ratio, NOT imposed), refined Miller geometry on the
|
|
876
|
+
# production grid (N_rho = 500, N_theta = 200).
|
|
877
|
+
# Published anchor: V = 831 m3 inside the true separatrix (Shimada
|
|
878
|
+
# 2007, Table 2); the analytic shaped torus carries a known +1.5 %
|
|
879
|
+
# bias at the published shaping (kappa = 1.85, delta = 0.485),
|
|
880
|
+
# tolerance 6 %.
|
|
881
|
+
_k = f_Kappa(ITER['R0'] / ITER['a'], 'Wenninger', 1.85, 0.3)
|
|
882
|
+
_k95 = f_Kappa_95(_k)
|
|
883
|
+
_d = f_Delta(_k)
|
|
884
|
+
_d95 = f_Delta_95(_d)
|
|
885
|
+
ITER_Vpd = precompute_Vprime(ITER['R0'], ITER['a'], _k, _d,
|
|
886
|
+
geometry_model='refined',
|
|
887
|
+
kappa_95=_k95, delta_95=_d95,
|
|
888
|
+
N_rho=500, N_theta=200)
|
|
889
|
+
_V = f_plasma_volume(ITER['R0'], ITER['a'], _k, _d, Vprime_data=ITER_Vpd)
|
|
890
|
+
_S = f_first_wall_surface(ITER['R0'], ITER['a'], _k, _d,
|
|
891
|
+
geometry_model='refined')
|
|
892
|
+
_V_pub = f_plasma_volume(6.2, 2.0, 1.85, 0.485) # analytic, published shaping
|
|
893
|
+
_kappa_a = _V / (2.0 * np.pi**2 * ITER['R0'] * ITER['a']**2)
|
|
894
|
+
ITER.update(kappa=_k, kappa95=_k95, delta=_d, delta95=_d95,
|
|
895
|
+
V=_V, S=_S, kappa_a=_kappa_a)
|
|
896
|
+
_bench("ITER chain 1/12 - shaping and geometry", [
|
|
897
|
+
("kappa_sep (Wenninger)", _k, FROZEN['kappa'], 1e-3, "deck frozen"),
|
|
898
|
+
("kappa_95", _k95, FROZEN['kappa95'], 1e-3, "deck frozen"),
|
|
899
|
+
("delta_sep", _d, FROZEN['delta'], 1e-3, "deck frozen"),
|
|
900
|
+
("delta_95", _d95, FROZEN['delta95'], 1e-3, "deck frozen"),
|
|
901
|
+
("V analytic, published shaping [m3]", _V_pub, 831.0, 0.06, "Shimada 2007"),
|
|
902
|
+
("V Miller, deck shaping [m3]", _V, FROZEN['V'], 5e-3, "deck frozen"),
|
|
903
|
+
("V Miller, deck shaping [m3]", _V, 831.0, 0.06, "Shimada 2007"),
|
|
904
|
+
("S first wall, Miller [m2]", _S, FROZEN['S'], 5e-3, "deck frozen"),
|
|
905
|
+
("kappa_area = V/(2 pi^2 R0 a^2)", _kappa_a, None, None, "IPB98 convention"),
|
|
906
|
+
], notes=[
|
|
907
|
+
"Published separatrix shaping: kappa = 1.85, delta = 0.485 "
|
|
908
|
+
"(Shimada 2007, Table 2); the Wenninger scaling at A = 3.1 "
|
|
909
|
+
"returns kappa = 1.879, delta = 0.528.",
|
|
910
|
+
])
|
|
911
|
+
|
|
912
|
+
|
|
719
913
|
#%% n, T, p and Pfus
|
|
720
914
|
|
|
721
915
|
"""
|
|
@@ -1129,14 +1323,20 @@ def f_sigmav(T):
|
|
|
1129
1323
|
|
|
1130
1324
|
|
|
1131
1325
|
if __name__ == "__main__":
|
|
1132
|
-
#
|
|
1133
|
-
#
|
|
1134
|
-
#
|
|
1135
|
-
|
|
1136
|
-
|
|
1326
|
+
# ── Published anchors - D-T reactivity (Bosch & Hale 1992) ──────────
|
|
1327
|
+
# Bosch & Hale, NF 32 (1992) 611, Table VII fit. Coefficients
|
|
1328
|
+
# cross-checked against cfspopcon 8.0.0 (machine precision); values
|
|
1329
|
+
# consistent with Table VIII (1.136e-16 cm3/s at 10 keV). The peak of
|
|
1330
|
+
# the reactivity sits near 64 keV (Wesson, Tokamaks).
|
|
1331
|
+
_sv_rows = [(f"<sigma v> at {_T:g} keV [m3/s]", f_sigmav(_T), _sv, 5e-4,
|
|
1332
|
+
"B&H 1992 Tab. VIII")
|
|
1333
|
+
for _T, _sv in ((1.0, 6.8569e-27), (10.0, 1.1362e-22),
|
|
1334
|
+
(20.0, 4.3302e-22))]
|
|
1137
1335
|
_Tg = np.linspace(20., 150., 600)
|
|
1138
|
-
|
|
1139
|
-
|
|
1336
|
+
_sv_rows.append(("peak position [keV]",
|
|
1337
|
+
float(_Tg[np.argmax(f_sigmav(_Tg))]),
|
|
1338
|
+
(55.0, 75.0), 1, "Wesson 2004"))
|
|
1339
|
+
_bench("Published anchors - D-T reactivity (Bosch-Hale)", _sv_rows)
|
|
1140
1340
|
|
|
1141
1341
|
# =============================================================================
|
|
1142
1342
|
# Required electron density for a target fusion power
|
|
@@ -1564,75 +1764,68 @@ def f_density_limit(model, Ip, a, P_sol=None, P_tot=None, R0=None, kappa=None,
|
|
|
1564
1764
|
|
|
1565
1765
|
|
|
1566
1766
|
if __name__ == "__main__":
|
|
1567
|
-
#
|
|
1568
|
-
# Greenwald, PPCF 44 (2002) R27: n_GW = Ip/(
|
|
1569
|
-
|
|
1570
|
-
#
|
|
1571
|
-
#
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1767
|
+
# ── Published anchors - operational density limits ───────────────────
|
|
1768
|
+
# Greenwald, PPCF 44 (2002) R27: n_GW = Ip/(pi a^2), exact identity at
|
|
1769
|
+
# the ITER 15 MA point. Giacomin et al., PRL 128 (2022) 185003,
|
|
1770
|
+
# Eq. 12: the paper's own machine predictions (A = 2) are 2.5e20 for
|
|
1771
|
+
# ITER and 8.7e20 for SPARC (rounding of the quoted inputs). Zanca et
|
|
1772
|
+
# al., NF 59 (2019) 126011, Eq. 20 (as in Manz, NF 63 (2023) 076026,
|
|
1773
|
+
# Eq. 7): algebraic identity at an ITER-like point.
|
|
1774
|
+
_zref = (0.4 * 1.7**(4 / 9) * (0.5 + 1.7 - 1)**(-5 / 9) * (50 / 15)**(4 / 9)
|
|
1775
|
+
* f_nG(15., 2.)**(8 / 9))
|
|
1776
|
+
_bench("Published anchors - density limits", [
|
|
1777
|
+
("n_GW ITER identity [1e20 m-3]", f_nG(15., 2.), 15 / (4 * np.pi),
|
|
1778
|
+
1e-12, "Greenwald 2002"),
|
|
1779
|
+
("n_lim Giacomin ITER [1e20 m-3]",
|
|
1780
|
+
f_n_limit_giacomin(50., 6.2, 2., 1.8, 5.3, 3.), 2.5, 0.06,
|
|
1781
|
+
"Giacomin 2022"),
|
|
1782
|
+
("n_lim Giacomin SPARC [1e20 m-3]",
|
|
1783
|
+
f_n_limit_giacomin(28., 1.85, .57, 2., 12.2, 3.), 8.7, 0.06,
|
|
1784
|
+
"Giacomin 2022"),
|
|
1785
|
+
("n_lim Zanca identity [1e20 m-3]",
|
|
1786
|
+
f_n_limit_zanca(50., 15., 2., Z_eff=1.7), _zref, 1e-9,
|
|
1787
|
+
"Zanca 2019"),
|
|
1788
|
+
])
|
|
1580
1789
|
|
|
1581
1790
|
if __name__ == "__main__":
|
|
1582
|
-
|
|
1583
|
-
#
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
Vprime_data=Vpd)
|
|
1617
|
-
p_d0 = f_pbar(nu_n, nu_T, n_d0, Tbar,
|
|
1618
|
-
rho_ped=rho_ped, n_ped_frac=n_ped_frac, T_ped_frac=T_ped_frac,
|
|
1619
|
-
Vprime_data=Vpd)
|
|
1620
|
-
|
|
1621
|
-
# Console summary
|
|
1622
|
-
print("\n── ITER reference: Academic vs refined — H-mode pedestal profile ──")
|
|
1623
|
-
print(f" Profile: nu_n={nu_n}, nu_T={nu_T}, rho_ped={rho_ped}, "
|
|
1624
|
-
f"n_ped={n_ped_frac}, T_ped={T_ped_frac}")
|
|
1625
|
-
print(f" {'Quantity':<30} {'Academic':>10} {'refined':>10} {'ITER ref.':>10}")
|
|
1626
|
-
print(" " + "─"*62)
|
|
1627
|
-
print(f" {'V [m3]':<30} {V_ac:>10.1f} {V_d0:>10.1f} {'830':>10}")
|
|
1628
|
-
print(f" {'n_e [1e20 m-3]':<30} {n_ac:>10.3f} {n_d0:>10.3f} {'1.01':>10}")
|
|
1629
|
-
print(f" {'p_bar [MPa]':<30} {p_ac:>10.3f} {p_d0:>10.3f} {'0.28':>10}")
|
|
1630
|
-
print(f" {'n_G [1e20 m-3]':<30} {nG:>10.3f} {nG:>10.3f} {'1.19':>10}")
|
|
1631
|
-
print(f" {'f_n = n_e/n_G [-]':<30} {n_ac/nG:>10.3f} {n_d0/nG:>10.3f} {'0.85':>10}")
|
|
1791
|
+
# ── ITER chain (2/12) - operating point: density and pressure ───────
|
|
1792
|
+
# The deck specifies the operating point through the Greenwald
|
|
1793
|
+
# fraction (Tbar_mode = greenwald, f_GW_target = 0.85): resolve_Tbar
|
|
1794
|
+
# solves Tbar = 7.754 keV, used here as the chain input. Two forward
|
|
1795
|
+
# references are taken from FROZEN and closed downstream:
|
|
1796
|
+
# - f_alpha (helium ash fraction), closed by chain 12;
|
|
1797
|
+
# - f_imp_dil = sum_j <Z_j> c_j (fuel dilution by the W + Ne mix),
|
|
1798
|
+
# closed by chain 4 where get_Z_mean is defined.
|
|
1799
|
+
_n = f_nbar(ITER['P_fus'], ITER['nu_n'], ITER['nu_T'], FROZEN['f_alpha'],
|
|
1800
|
+
ITER['Tbar'], ITER['R0'], ITER['a'], ITER['kappa'],
|
|
1801
|
+
rho_ped=ITER['rho_ped'], n_ped_frac=ITER['n_ped_frac'],
|
|
1802
|
+
T_ped_frac=ITER['T_ped_frac'],
|
|
1803
|
+
Vprime_data=ITER_Vpd, f_imp=FROZEN['f_imp_dil'], tau_i_e=1.0)
|
|
1804
|
+
_p = f_pbar(ITER['nu_n'], ITER['nu_T'], _n, ITER['Tbar'],
|
|
1805
|
+
rho_ped=ITER['rho_ped'], n_ped_frac=ITER['n_ped_frac'],
|
|
1806
|
+
T_ped_frac=ITER['T_ped_frac'],
|
|
1807
|
+
Vprime_data=ITER_Vpd, tau_i_e=1.0)
|
|
1808
|
+
_nl = f_nbar_line(_n, ITER['nu_n'], ITER['rho_ped'], ITER['n_ped_frac'])
|
|
1809
|
+
_nl_flat = f_nbar_line(1.0, 0.0) # analytic identity for a flat profile
|
|
1810
|
+
ITER.update(nbar=_n, pbar=_p, nbar_line=_nl)
|
|
1811
|
+
_bench("ITER chain 2/12 - operating point (density, pressure)", [
|
|
1812
|
+
("nbar volume-avg [1e20 m-3]", _n, FROZEN['nbar'], 1e-3, "deck frozen"),
|
|
1813
|
+
("nbar line-avg [1e20 m-3]", _nl, FROZEN['nbar_line'], 1e-3,
|
|
1814
|
+
"deck frozen"),
|
|
1815
|
+
("nbar line-avg [1e20 m-3]", _nl, 1.01, 0.01, "Shimada 2007"),
|
|
1816
|
+
("pbar [MPa]", _p, FROZEN['pbar'], 1e-3, "deck frozen"),
|
|
1817
|
+
("flat-profile identity n_line/n_vol", _nl_flat, 1.0, 1e-9,
|
|
1818
|
+
"analytic"),
|
|
1819
|
+
("f_GW at frozen Ip", _nl / f_nG(FROZEN['Ip'], ITER['a']), 0.85,
|
|
1820
|
+
5e-3, "deck target"),
|
|
1821
|
+
], notes=[
|
|
1822
|
+
"f_GW is re-closed in chain 11 with the chain Ip from the "
|
|
1823
|
+
"scaling-law inversion.",
|
|
1824
|
+
])
|
|
1632
1825
|
|
|
1633
1826
|
#%% B and beta
|
|
1634
1827
|
|
|
1635
|
-
def f_B0(Bmax, a, b, R0):
|
|
1828
|
+
def f_B0(Bmax, a, b, R0, b_cover=0.0):
|
|
1636
1829
|
"""
|
|
1637
1830
|
Estimate the on-axis toroidal magnetic field B0 from the peak field at the
|
|
1638
1831
|
TF coil inboard midplane, using the 1/R dependence of a toroidal solenoid.
|
|
@@ -1665,7 +1858,7 @@ def f_B0(Bmax, a, b, R0):
|
|
|
1665
1858
|
Wesson, Tokamaks, 4th ed. (2011), §3.2.
|
|
1666
1859
|
Freidberg, Plasma Physics and Fusion Energy (2007), §12.3.
|
|
1667
1860
|
"""
|
|
1668
|
-
B0 = Bmax * (1.0 - (a + b) / R0)
|
|
1861
|
+
B0 = Bmax * (1.0 - (a + b + b_cover) / R0)
|
|
1669
1862
|
return B0
|
|
1670
1863
|
|
|
1671
1864
|
def f_Bpol(q95, B_tor, a, R0, kappa=1.0):
|
|
@@ -2009,40 +2202,43 @@ def f_beta_fast_alpha(P_alpha_MW, Te_keV, ne_20, B0, V_m3, Z_eff=1.65,
|
|
|
2009
2202
|
|
|
2010
2203
|
|
|
2011
2204
|
if __name__ == "__main__":
|
|
2012
|
-
|
|
2013
|
-
#
|
|
2014
|
-
#
|
|
2015
|
-
#
|
|
2016
|
-
#
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2205
|
+
# ── ITER chain (3/12) - on-axis field, stored energy and beta ───────
|
|
2206
|
+
# Field convention of the deck: Bmax_TF = 10.60 T is the peak field at
|
|
2207
|
+
# the winding-pack FRONT FACE, at radius R0 - a - b with the published
|
|
2208
|
+
# inboard stack b = 1.10 m. ITER quotes 11.8 T on the conductor,
|
|
2209
|
+
# deeper inside the winding pack; both describe the same machine and
|
|
2210
|
+
# the front-face convention reproduces B0 = 5.30 T exactly.
|
|
2211
|
+
# W_th = (3/2) pbar V is evaluated inline (definition of f_W_th,
|
|
2212
|
+
# re-checked through f_W_th in chain 11). Ip is a forward reference
|
|
2213
|
+
# (FROZEN), closed by the scaling-law inversion of chain 11.
|
|
2214
|
+
_B0 = f_B0(ITER['Bmax_TF'], ITER['a'], ITER['b'], ITER['R0'])
|
|
2215
|
+
_W = 1.5 * ITER['pbar'] * 1e6 * ITER['V'] / 1e6 # [MJ]
|
|
2216
|
+
_bT = f_beta_T(ITER['pbar'], _B0)
|
|
2217
|
+
_bP = f_beta_P(ITER['a'], ITER['kappa'], ITER['pbar'], FROZEN['Ip'])
|
|
2218
|
+
_bN = f_beta_N(f_beta(_bT, _bP), ITER['a'], _B0, FROZEN['Ip'])
|
|
2219
|
+
# Fast-alpha pressure (Stix slowing-down), to be added to the thermal
|
|
2220
|
+
# beta for MHD-limit comparisons. P_alpha = P_fus/5 = f_P_alpha
|
|
2221
|
+
# (defined in the next section, re-checked in chain 7).
|
|
2222
|
+
_bfa, _tau_se, _W_fast = f_beta_fast_alpha(
|
|
2223
|
+
ITER['P_fus'] / 5.0, ITER['Tbar'], ITER['nbar'], _B0, ITER['V'],
|
|
2224
|
+
Z_eff=ITER['Zeff'])
|
|
2225
|
+
ITER.update(B0=_B0, W_th=_W, betaN=_bN)
|
|
2226
|
+
_bench("ITER chain 3/12 - field, stored energy and beta", [
|
|
2227
|
+
("B0 on axis [T]", _B0, 5.30, 1e-3, "Shimada 2007"),
|
|
2228
|
+
("W_th [MJ]", _W, FROZEN['W_th'], 1e-3, "deck frozen"),
|
|
2229
|
+
("W_th [MJ]", _W, 350.0, 0.05, "Shimada 2007"),
|
|
2230
|
+
("beta_T [-]", _bT, FROZEN['betaT'], 2e-3, "deck frozen"),
|
|
2231
|
+
("beta_P [-]", _bP, FROZEN['betaP'], 2e-3, "deck frozen"),
|
|
2232
|
+
("beta_N [% m T/MA]", _bN, FROZEN['betaN'], 2e-3, "deck frozen"),
|
|
2233
|
+
("beta_N [% m T/MA]", _bN, "1.8", None, "Shimada 2007"),
|
|
2234
|
+
("Troyon margin beta_N", _bN, (0.0, 2.8), 1, "deck limit"),
|
|
2235
|
+
("beta_fast_alpha / beta_T [-]", _bfa / _bT, None, None,
|
|
2236
|
+
"Stix slowing-down"),
|
|
2237
|
+
], notes=[
|
|
2238
|
+
"beta_N solves below the published 1.8 because the deck-solved "
|
|
2239
|
+
"temperature (7.75 keV) sits under the 8.9 keV design assumption; "
|
|
2240
|
+
"the deviation follows the stored energy and density directly.",
|
|
2241
|
+
])
|
|
2046
2242
|
|
|
2047
2243
|
#%% Power definitions
|
|
2048
2244
|
|
|
@@ -2431,12 +2627,15 @@ def f_P_bremsstrahlung(nbar, Tbar, Z_eff, V, nu_n=0.0, nu_T=0.0,
|
|
|
2431
2627
|
|
|
2432
2628
|
|
|
2433
2629
|
if __name__ == "__main__":
|
|
2434
|
-
#
|
|
2435
|
-
#
|
|
2436
|
-
#
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2630
|
+
# ── Published anchor - bremsstrahlung constant ───────────────────────
|
|
2631
|
+
# Wesson, Tokamaks, Sec. 4.25 (5.35e-37 W m3 in SI; same constant in
|
|
2632
|
+
# the NRL Formulary). Flat unit plasma (n = 1e20 m-3, T = 1 keV,
|
|
2633
|
+
# Z_eff = 1, V = 1 m3) radiates 5.35e3 W = 5.35e-3 MW.
|
|
2634
|
+
_bench("Published anchor - bremsstrahlung constant (Wesson)", [
|
|
2635
|
+
("P_brem flat unit plasma [MW]",
|
|
2636
|
+
f_P_bremsstrahlung(1., 1., 1., 1., nu_n=0., nu_T=0.),
|
|
2637
|
+
5.35e-3, 0.02, "Wesson 2004"),
|
|
2638
|
+
])
|
|
2440
2639
|
|
|
2441
2640
|
def f_P_line_radiation(nbar, f_imp, L_z, V):
|
|
2442
2641
|
"""
|
|
@@ -3092,29 +3291,62 @@ if __name__ == "__main__":
|
|
|
3092
3291
|
figs.plot_Lz_cooling()
|
|
3093
3292
|
|
|
3094
3293
|
if __name__ == "__main__":
|
|
3095
|
-
# ITER
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3294
|
+
# ── ITER chain (4/12) - radiation budget ─────────────────────────────
|
|
3295
|
+
# Deck composition: W (2e-5) + Ne (7e-3) with the Zeff = 1.65
|
|
3296
|
+
# override, wall reflectivity r_synch = 0.6, profile-integrated
|
|
3297
|
+
# radiation with the core/edge split at rho_rad_core = 0.75 and
|
|
3298
|
+
# coreradiationfraction = 1 (deck defaults).
|
|
3299
|
+
# Convention: the bremsstrahlung term uses the FUEL effective charge
|
|
3300
|
+
# Z_eff,fuel = 1 + 2 f_alpha - f_imp_dil (D, T, He only); the
|
|
3301
|
+
# impurity contribution (line + recombination + impurity
|
|
3302
|
+
# bremsstrahlung) is carried by the Mavrin cooling rates inside
|
|
3303
|
+
# P_line. Term-by-term comparisons with published decompositions are
|
|
3304
|
+
# therefore convention-dependent; the underlying pieces are anchored
|
|
3305
|
+
# separately (Wesson constant above, Mavrin/TORAX block below).
|
|
3306
|
+
# This block also closes the f_imp_dil forward reference of chain 2.
|
|
3307
|
+
_fdil = sum(get_Z_mean(s, ITER['Tbar']) * c for s, c in ITER['imp'].items())
|
|
3308
|
+
_zimp2 = sum(get_Z_mean(s, ITER['Tbar'])**2 * c
|
|
3309
|
+
for s, c in ITER['imp'].items())
|
|
3310
|
+
_zf = 1.0 + 2.0 * FROZEN['f_alpha'] - _fdil
|
|
3311
|
+
_Pb = f_P_bremsstrahlung(ITER['nbar'], ITER['Tbar'], _zf, ITER['V'],
|
|
3312
|
+
ITER['nu_n'], ITER['nu_T'],
|
|
3313
|
+
rho_ped=ITER['rho_ped'],
|
|
3314
|
+
n_ped_frac=ITER['n_ped_frac'],
|
|
3315
|
+
T_ped_frac=ITER['T_ped_frac'],
|
|
3316
|
+
Vprime_data=ITER_Vpd)
|
|
3317
|
+
_Ps = f_P_synchrotron(ITER['Tbar'], ITER['R0'], ITER['a'], ITER['B0'],
|
|
3318
|
+
ITER['nbar'], ITER['kappa'], ITER['nu_n'],
|
|
3319
|
+
ITER['nu_T'], ITER['r_synch'],
|
|
3320
|
+
rho_ped=ITER['rho_ped'],
|
|
3321
|
+
n_ped_frac=ITER['n_ped_frac'],
|
|
3322
|
+
T_ped_frac=ITER['T_ped_frac'],
|
|
3323
|
+
Vprime_data=ITER_Vpd)
|
|
3324
|
+
_Plc, _Plt = 0.0, 0.0
|
|
3325
|
+
for _sp, _c in ITER['imp'].items():
|
|
3326
|
+
_pc, _pt = f_P_line_radiation_profile(
|
|
3327
|
+
_sp, _c, ITER['nbar'], ITER['Tbar'], ITER['nu_n'], ITER['nu_T'],
|
|
3328
|
+
ITER['V'], rho_ped=ITER['rho_ped'],
|
|
3329
|
+
n_ped_frac=ITER['n_ped_frac'], T_ped_frac=ITER['T_ped_frac'],
|
|
3330
|
+
Vprime_data=ITER_Vpd, rho_core=ITER['rho_rad_core'], N=150)
|
|
3331
|
+
_Plc += _pc
|
|
3332
|
+
_Plt += _pt
|
|
3333
|
+
_Prc = _Pb + _Ps + _Plc # coreradiationfraction = 1 (deck default)
|
|
3334
|
+
_Prt = _Pb + _Ps + _Plt
|
|
3335
|
+
ITER.update(P_rad_core=_Prc, P_rad_tot=_Prt)
|
|
3336
|
+
_bench("ITER chain 4/12 - radiation budget (W + Ne, pedestal profiles)", [
|
|
3337
|
+
("fuel dilution sum <Z_j> c_j [-]", _fdil, FROZEN['f_imp_dil'], 2e-3,
|
|
3338
|
+
"deck frozen"),
|
|
3339
|
+
("Z_eff,fuel [-]", _zf, None, None, "convention"),
|
|
3340
|
+
("Z_eff computed (fuel + imp) [-]", _zf + _zimp2, "1.65", None,
|
|
3341
|
+
"deck override"),
|
|
3342
|
+
("P_brem (fuel) [MW]", _Pb, FROZEN['P_Brem'], 2e-3, "deck frozen"),
|
|
3343
|
+
("P_synchrotron [MW]", _Ps, FROZEN['P_syn'], 2e-3, "deck frozen"),
|
|
3344
|
+
("P_line core (rho < 0.75) [MW]", _Plc, FROZEN['P_line_core'], 2e-3,
|
|
3345
|
+
"deck frozen"),
|
|
3346
|
+
("P_line total [MW]", _Plt, FROZEN['P_line'], 2e-3, "deck frozen"),
|
|
3347
|
+
("P_rad,core -> tau_E, Ip [MW]", _Prc, None, None, "chain 11"),
|
|
3348
|
+
("P_rad,total -> P_sep [MW]", _Prt, None, None, "chain 6"),
|
|
3349
|
+
])
|
|
3118
3350
|
|
|
3119
3351
|
#%% Current drive
|
|
3120
3352
|
"""
|
|
@@ -3170,31 +3402,41 @@ Gormezano et al. (ITER PIPB Ch. 6), Nucl. Fusion 47 (2007) S285.
|
|
|
3170
3402
|
|
|
3171
3403
|
|
|
3172
3404
|
if __name__ == "__main__":
|
|
3173
|
-
#
|
|
3174
|
-
# Mavrin (2018)
|
|
3175
|
-
# (google-deepmind/torax, charge_states.py)
|
|
3176
|
-
# W(8.9 keV) = 53.06 (hand evaluation of the interval-3
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
#
|
|
3180
|
-
#
|
|
3181
|
-
#
|
|
3182
|
-
#
|
|
3183
|
-
#
|
|
3405
|
+
# ── Published anchors - atomic data (Mavrin, SOL tables, Lengyel) ───
|
|
3406
|
+
# Mavrin (2018) <Z>(T_e): coefficients cross-checked against TORAX
|
|
3407
|
+
# (google-deepmind/torax, charge_states.py); Ne fully stripped above
|
|
3408
|
+
# 5 keV; W(8.9 keV) = 53.06 (hand evaluation of the interval-3
|
|
3409
|
+
# polynomial). Frozen non-coronal SOL L_z tables (OpenADAS/radas,
|
|
3410
|
+
# ne tau = 5e16 m-3 s, cfspopcon SPARC-PRD convention): peak
|
|
3411
|
+
# integrity at freezing time, and the one-sided invariant
|
|
3412
|
+
# Lz_SOL >= Lz_coronal on the 0.1-2 keV overlap (the finite residence
|
|
3413
|
+
# time keeps line-radiating charge states alive). Lengyel: frozen
|
|
3414
|
+
# cfspopcon SPARC-PRD regression point (chain validated to 0.3 % over
|
|
3415
|
+
# five decades of q_par, 2026-06 review).
|
|
3416
|
+
_rows = [
|
|
3417
|
+
("<Z> Ne at 8.9 keV [-]", get_Z_mean('Ne', 8.9), 10.0, 1e-9,
|
|
3418
|
+
"Mavrin 2018 / TORAX"),
|
|
3419
|
+
("<Z> W at 8.9 keV [-]", get_Z_mean('W', 8.9), 53.06, 1e-3,
|
|
3420
|
+
"Mavrin 2018 / TORAX"),
|
|
3421
|
+
]
|
|
3184
3422
|
for _sp, (_Tp, _Lp) in {'N': (13.2, 5.745e-32), 'Ne': (49.8, 5.642e-32),
|
|
3185
3423
|
'Ar': (22.6, 2.121e-31)}.items():
|
|
3186
|
-
_Tt = np.logspace(0, 3, 600)
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3424
|
+
_Tt = np.logspace(0, 3, 600)
|
|
3425
|
+
_lz = get_Lz_SOL(_sp, _Tt)
|
|
3426
|
+
_kk = int(np.argmax(_lz))
|
|
3427
|
+
_rows.append((f"Lz_SOL {_sp} peak value [W m3]", float(_lz[_kk]),
|
|
3428
|
+
_Lp, 0.05, "frozen table"))
|
|
3429
|
+
_rows.append((f"Lz_SOL {_sp} peak position ratio", float(_Tt[_kk]) / _Tp,
|
|
3430
|
+
(1 / 1.6, 1.6), 1, "frozen table"))
|
|
3431
|
+
_rmin = min(get_Lz_SOL(_sp, _Tk * 1e3) / get_Lz(_sp, _Tk)
|
|
3432
|
+
for _Tk in (0.1, 0.5, 2.0))
|
|
3433
|
+
_rows.append((f"invariant Lz_SOL/Lz_coronal {_sp}", float(_rmin),
|
|
3434
|
+
(0.95, 1e9), 1, "one-sided physics"))
|
|
3435
|
+
_rows.append(("Lengyel c_z (Ar, SPARC point) [-]",
|
|
3436
|
+
f_lengyel_concentration(9101., 2.1e19, 280.1, 25.0, 'Ar',
|
|
3437
|
+
0.988),
|
|
3438
|
+
0.7569, 0.02, "cfspopcon ref."))
|
|
3439
|
+
_bench("Published anchors - atomic data and Lengyel chain", _rows)
|
|
3198
3440
|
|
|
3199
3441
|
def _ln_Lambda_CD(Te_keV, ne_20):
|
|
3200
3442
|
"""
|
|
@@ -3658,75 +3900,61 @@ def f_etaCD_NBI_physics(A_beam, E_beam_keV, a, R0, Tbar, nbar, Z_eff,
|
|
|
3658
3900
|
|
|
3659
3901
|
|
|
3660
3902
|
if __name__ == "__main__":
|
|
3661
|
-
# ── NBCD physics model
|
|
3662
|
-
# Expected values from METIS zicd0.m benchmark
|
|
3663
|
-
# Tolerance
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
("EU-DEMO
|
|
3675
|
-
Tbar=12.0, nbar=0.8, Z_eff=1.6,
|
|
3676
|
-
|
|
3903
|
+
# ── Published anchors - NBCD physics model vs METIS ──────────────────
|
|
3904
|
+
# Expected values from the METIS zicd0.m benchmark
|
|
3905
|
+
# (test_NBCD_physics.py). Tolerance 2 % (lnLambda formula difference,
|
|
3906
|
+
# NRL vs METIS). Perpendicular injection must drive zero current.
|
|
3907
|
+
_nbcd_cases = [
|
|
3908
|
+
("ITER 1 MeV D", 0.3336, dict(A_beam=2, E_beam_keV=1000, a=2.0, R0=6.2,
|
|
3909
|
+
Tbar=8.9, nbar=1.0, Z_eff=1.65, nu_T=1.5,
|
|
3910
|
+
nu_n=0.3, rho_NBI=0.3, f_alpha=0.04,
|
|
3911
|
+
angle_NBI_deg=20.0)),
|
|
3912
|
+
("ARC 150 keV D", 0.1147, dict(A_beam=2, E_beam_keV=150, a=1.13,
|
|
3913
|
+
R0=3.3, Tbar=14.0, nbar=1.8, Z_eff=1.5,
|
|
3914
|
+
nu_T=1.2, nu_n=0.5, rho_NBI=0.4, f_alpha=0.06,
|
|
3915
|
+
angle_NBI_deg=25.0)),
|
|
3916
|
+
("EU-DEMO 1 MeV", 0.4006, dict(A_beam=2, E_beam_keV=1000, a=2.88,
|
|
3917
|
+
R0=9.07, Tbar=12.0, nbar=0.8, Z_eff=1.6,
|
|
3918
|
+
nu_T=1.5, nu_n=0.3, rho_NBI=0.3, f_alpha=0.05,
|
|
3919
|
+
angle_NBI_deg=20.0)),
|
|
3677
3920
|
]
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
print(f" {_name:<16s} {_g:8.4f} {_metis_ref:8.4f} {_err*100:5.1f}% {'PASS' if _ok else 'FAIL'}")
|
|
3687
|
-
# Physics sanity checks
|
|
3688
|
-
_g_perp, _ = f_etaCD_NBI_physics(2, 500, 2.0, 6.2, 10.0, 1.0, 1.65,
|
|
3689
|
-
1.5, 0.3, 0.3, angle_NBI_deg=90.0), None
|
|
3690
|
-
_ok_perp = abs(_g_perp) < 1e-6
|
|
3691
|
-
_all_ok = _all_ok and _ok_perp
|
|
3692
|
-
print(f" {'perp. -> 0':<16s} {_g_perp:8.1e} {'0':>8s} {'':>6s} {'PASS' if _ok_perp else 'FAIL'}")
|
|
3693
|
-
print(" " + "-" * 55)
|
|
3694
|
-
print(f" {'ALL PASS' if _all_ok else 'SOME FAILED'}")
|
|
3695
|
-
|
|
3921
|
+
_rows = [(f"gamma_NBI {_nm}", f_etaCD_NBI_physics(**_kw), _ref, 0.02,
|
|
3922
|
+
"METIS zicd0.m")
|
|
3923
|
+
for _nm, _ref, _kw in _nbcd_cases]
|
|
3924
|
+
_g_perp = f_etaCD_NBI_physics(2, 500, 2.0, 6.2, 10.0, 1.0, 1.65,
|
|
3925
|
+
1.5, 0.3, 0.3, angle_NBI_deg=90.0)
|
|
3926
|
+
_rows.append(("gamma_NBI perpendicular limit", float(_g_perp),
|
|
3927
|
+
(-1e-6, 1e-6), 1, "physics limit"))
|
|
3928
|
+
_bench("Published anchors - NBCD (Stix/Cordey/Lin-Liu) vs METIS", _rows)
|
|
3696
3929
|
|
|
3697
3930
|
if __name__ == "__main__":
|
|
3698
|
-
# ── ECCD physics model
|
|
3699
|
-
#
|
|
3700
|
-
#
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
# Reference values computed from the METIS formula at local Te
|
|
3705
|
-
# (Te_local evaluated from parabolic profile at rho_EC)
|
|
3931
|
+
# ── Published anchors - ECCD physics model vs METIS ──────────────────
|
|
3932
|
+
# The formula is identical to METIS zicd0.m lhmode = 5 (Giruzzi 1987 +
|
|
3933
|
+
# Lin-Liu trapped-particle correction): the reference is re-evaluated
|
|
3934
|
+
# inline at the same local T_e, so the agreement must hold to machine
|
|
3935
|
+
# precision. Physics ordering: HFS > top > LFS > 0.
|
|
3706
3936
|
_ec_cases = [
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
Z_eff=1.6, nu_T=1.5, nu_n=0.3,
|
|
3720
|
-
rho_EC=0.3)),
|
|
3937
|
+
("ITER HFS 160 deg", 160.0, dict(a=2.0, R0=6.2, Tbar=8.9, nbar=1.0,
|
|
3938
|
+
Z_eff=1.65, nu_T=1.0, nu_n=0.3,
|
|
3939
|
+
rho_EC=0.3)),
|
|
3940
|
+
("ITER LFS 0 deg", 0.0, dict(a=2.0, R0=6.2, Tbar=8.9, nbar=1.0,
|
|
3941
|
+
Z_eff=1.65, nu_T=1.0, nu_n=0.3,
|
|
3942
|
+
rho_EC=0.3)),
|
|
3943
|
+
("ITER top 90 deg", 90.0, dict(a=2.0, R0=6.2, Tbar=8.9, nbar=1.0,
|
|
3944
|
+
Z_eff=1.65, nu_T=1.0, nu_n=0.3,
|
|
3945
|
+
rho_EC=0.3)),
|
|
3946
|
+
("EU-DEMO HFS 160 deg", 160.0, dict(a=2.88, R0=9.07, Tbar=12.0,
|
|
3947
|
+
nbar=0.8, Z_eff=1.6, nu_T=1.5,
|
|
3948
|
+
nu_n=0.3, rho_EC=0.3)),
|
|
3721
3949
|
]
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
for _name, _theta, _kw in _ec_cases:
|
|
3950
|
+
_rows = []
|
|
3951
|
+
_g_by_case = {}
|
|
3952
|
+
for _nm, _theta, _kw in _ec_cases:
|
|
3727
3953
|
_g = f_etaCD_EC_physics(**_kw, theta_EC_pol_deg=_theta)
|
|
3728
|
-
|
|
3729
|
-
|
|
3954
|
+
_g_by_case[_nm] = _g
|
|
3955
|
+
# METIS reference evaluated inline at the same local T_e
|
|
3956
|
+
_Te_loc = max(float(f_Tprof(_kw['Tbar'], _kw['nu_T'], _kw['rho_EC'])),
|
|
3957
|
+
0.1)
|
|
3730
3958
|
_eps = abs(_kw['rho_EC']) * _kw['a'] / _kw['R0']
|
|
3731
3959
|
_theta_r = np.radians(_theta)
|
|
3732
3960
|
_mut = np.sqrt(max(_eps * (1 + np.cos(_theta_r))
|
|
@@ -3737,30 +3965,21 @@ if __name__ == "__main__":
|
|
|
3737
3965
|
_fc = 1.0 - np.sqrt(2.0 * _eps / (1.0 + _eps))
|
|
3738
3966
|
_g_metis = (_Te_loc / (_Te_loc + 100.0) * _Gt
|
|
3739
3967
|
* 6.0 / (1.0 + 4.0 * _fc + _kw['Z_eff']))
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
_ok_order = _g_hfs > _g_top > _g_lfs > 0
|
|
3756
|
-
_all_ok_ec = _all_ok_ec and _ok_order
|
|
3757
|
-
print(f" {'HFS>top>LFS>0':<18s} {'':>8s} {'':>5s} {'PASS' if _ok_order else 'FAIL'}"
|
|
3758
|
-
f" ({_g_hfs:.3f}>{_g_top:.3f}>{_g_lfs:.3f})")
|
|
3759
|
-
print(" " + "-" * 45)
|
|
3760
|
-
print(f" {'ALL PASS' if _all_ok_ec else 'SOME FAILED'}")
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3968
|
+
_rows.append((f"gamma_EC {_nm}", _g, _g_metis, 1e-9, "METIS zicd0.m"))
|
|
3969
|
+
_g_hfs = f_etaCD_EC_physics(a=2.0, R0=6.2, Tbar=8.9, nbar=1.0, Z_eff=1.65,
|
|
3970
|
+
nu_T=1.0, nu_n=0.3, rho_EC=0.3,
|
|
3971
|
+
theta_EC_pol_deg=180.0)
|
|
3972
|
+
_g_top = f_etaCD_EC_physics(a=2.0, R0=6.2, Tbar=8.9, nbar=1.0, Z_eff=1.65,
|
|
3973
|
+
nu_T=1.0, nu_n=0.3, rho_EC=0.3,
|
|
3974
|
+
theta_EC_pol_deg=90.0)
|
|
3975
|
+
_g_lfs = f_etaCD_EC_physics(a=2.0, R0=6.2, Tbar=8.9, nbar=1.0, Z_eff=1.65,
|
|
3976
|
+
nu_T=1.0, nu_n=0.3, rho_EC=0.3,
|
|
3977
|
+
theta_EC_pol_deg=0.0)
|
|
3978
|
+
_rows.append(("ordering gamma_HFS/gamma_top", _g_hfs / _g_top,
|
|
3979
|
+
(1.0 + 1e-9, 100.0), 1, "trapping physics"))
|
|
3980
|
+
_rows.append(("ordering gamma_top/gamma_LFS", _g_top / _g_lfs,
|
|
3981
|
+
(1.0 + 1e-9, 100.0), 1, "trapping physics"))
|
|
3982
|
+
_bench("Published anchors - ECCD (Giruzzi/Lin-Liu) vs METIS", _rows)
|
|
3764
3983
|
|
|
3765
3984
|
def f_etaCD_effective(config, a, R0, B0, nbar, Tbar, nu_n, nu_T, Z_eff,
|
|
3766
3985
|
rho_ped=1.0, n_ped_frac=0.0, T_ped_frac=0.0):
|
|
@@ -4055,56 +4274,71 @@ def f_PLH(eta_RF, f_RP, P_CD):
|
|
|
4055
4274
|
|
|
4056
4275
|
|
|
4057
4276
|
if __name__ == "__main__":
|
|
4058
|
-
|
|
4059
|
-
#
|
|
4060
|
-
#
|
|
4061
|
-
#
|
|
4062
|
-
#
|
|
4063
|
-
#
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
#
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4277
|
+
# ── ITER chain (5/12) - current drive and current decomposition ─────
|
|
4278
|
+
# Part A: PIPB-style figures of merit at the historical comparison
|
|
4279
|
+
# point (rho = 0.3, Tbar = 8.9 keV) against the ITER Chapter 6
|
|
4280
|
+
# projections (Gormezano et al., NF 47 (2007) S285), kept as
|
|
4281
|
+
# informative context: gamma_LH ~ 0.24-0.33, gamma_EC ~ 0.20,
|
|
4282
|
+
# gamma_NBI ~ 0.15-0.40 MA/(MW m2) across the ITER scenarios.
|
|
4283
|
+
_kwq = dict(a=2.0, R0=6.2, Tbar=8.9, nbar=1.01, nu_n=0.1, nu_T=1.0,
|
|
4284
|
+
Z_eff=1.6, rho_ped=0.94, n_ped_frac=0.80, T_ped_frac=0.40)
|
|
4285
|
+
_gLH_pub = f_etaCD_LH_physics(_kwq['Tbar'], _kwq['Z_eff'])
|
|
4286
|
+
_gEC_pub = f_etaCD_EC_physics(_kwq['a'], _kwq['R0'], _kwq['Tbar'],
|
|
4287
|
+
_kwq['nbar'], _kwq['Z_eff'], _kwq['nu_T'],
|
|
4288
|
+
_kwq['nu_n'], rho_EC=0.3,
|
|
4289
|
+
rho_ped=_kwq['rho_ped'],
|
|
4290
|
+
n_ped_frac=_kwq['n_ped_frac'],
|
|
4291
|
+
T_ped_frac=_kwq['T_ped_frac'])
|
|
4292
|
+
_gNBI_pub = f_etaCD_NBI_physics(2, 1000., _kwq['a'], _kwq['R0'],
|
|
4293
|
+
_kwq['Tbar'], _kwq['nbar'], _kwq['Z_eff'],
|
|
4294
|
+
_kwq['nu_T'], _kwq['nu_n'], rho_NBI=0.3,
|
|
4295
|
+
rho_ped=_kwq['rho_ped'],
|
|
4296
|
+
n_ped_frac=_kwq['n_ped_frac'],
|
|
4297
|
+
T_ped_frac=_kwq['T_ped_frac'])
|
|
4298
|
+
# Part B: chain evaluation at the deck point (Tbar = 7.754 keV,
|
|
4299
|
+
# rho_EC = 0.40 for NTM control, rho_NBI = 0.30, 1 MeV D, 20 deg).
|
|
4300
|
+
_eLH = f_etaCD_LH_physics(ITER['Tbar'], ITER['Zeff'])
|
|
4301
|
+
_eEC = f_etaCD_EC_physics(ITER['a'], ITER['R0'], ITER['Tbar'],
|
|
4302
|
+
ITER['nbar'], ITER['Zeff'], ITER['nu_T'],
|
|
4303
|
+
ITER['nu_n'], ITER['rho_EC'],
|
|
4304
|
+
rho_ped=ITER['rho_ped'],
|
|
4305
|
+
n_ped_frac=ITER['n_ped_frac'],
|
|
4306
|
+
T_ped_frac=ITER['T_ped_frac'])
|
|
4307
|
+
_eNBI = f_etaCD_NBI_physics(ITER['A_beam'], ITER['E_beam_keV'], ITER['a'],
|
|
4308
|
+
ITER['R0'], ITER['Tbar'], ITER['nbar'],
|
|
4309
|
+
ITER['Zeff'], ITER['nu_T'], ITER['nu_n'],
|
|
4310
|
+
ITER['rho_NBI'], f_alpha=FROZEN['f_alpha'],
|
|
4311
|
+
angle_NBI_deg=ITER['angle_NBI_deg'],
|
|
4312
|
+
rho_ped=ITER['rho_ped'],
|
|
4313
|
+
n_ped_frac=ITER['n_ped_frac'],
|
|
4314
|
+
T_ped_frac=ITER['T_ped_frac'])
|
|
4315
|
+
_IEC = f_I_CD(ITER['R0'], ITER['nbar'], _eEC, ITER['P_ECRH'])
|
|
4316
|
+
_INBI = f_I_CD(ITER['R0'], ITER['nbar'], _eNBI, ITER['P_NBI'])
|
|
4317
|
+
_ICD = _IEC + _INBI # ICRH drives no current (gamma_ICR = 0)
|
|
4318
|
+
ITER.update(I_CD=_ICD)
|
|
4319
|
+
_bench("ITER chain 5/12 - current drive (Multi: NBI + EC + IC)", [
|
|
4320
|
+
("gamma_LH, PIPB point [MA/(MW m2)]", _gLH_pub, "~0.24-0.33", None,
|
|
4321
|
+
"Gormezano 2007"),
|
|
4322
|
+
("gamma_EC, PIPB point (rho=0.3)", _gEC_pub, "~0.20", None,
|
|
4323
|
+
"Gormezano 2007"),
|
|
4324
|
+
("gamma_NBI, PIPB point", _gNBI_pub, "0.15-0.40", None,
|
|
4325
|
+
"Gormezano 2007"),
|
|
4326
|
+
("eta_LH chain [MA/(MW m2)]", _eLH, FROZEN['eta_LH'], 2e-3,
|
|
4327
|
+
"deck frozen"),
|
|
4328
|
+
("eta_EC chain (rho=0.40)", _eEC, FROZEN['eta_EC'], 2e-3,
|
|
4329
|
+
"deck frozen"),
|
|
4330
|
+
("eta_NBI chain (1 MeV, 20 deg)", _eNBI, FROZEN['eta_NBI'], 2e-3,
|
|
4331
|
+
"deck frozen"),
|
|
4332
|
+
("I_EC [MA]", _IEC, None, None, "P_EC = 6.7 MW"),
|
|
4333
|
+
("I_NBI [MA]", _INBI, None, None, "P_NBI = 33 MW"),
|
|
4334
|
+
("I_CD total [MA]", _ICD, FROZEN['I_CD'], 2e-3, "deck frozen"),
|
|
4335
|
+
], notes=[
|
|
4336
|
+
"Chain efficiencies are lower than the PIPB-point values because "
|
|
4337
|
+
"the deck deposits EC off-axis (rho = 0.40, NTM control) at the "
|
|
4338
|
+
"solved Tbar = 7.75 keV.",
|
|
4339
|
+
"Current budget context (Eriksson et al., NF 64 (2024) 126033): "
|
|
4340
|
+
"inductive ~10 MA, non-inductive ~5 MA with bootstrap dominant.",
|
|
4341
|
+
])
|
|
4108
4342
|
|
|
4109
4343
|
#%% L-H transition threshold
|
|
4110
4344
|
|
|
@@ -4338,44 +4572,55 @@ def f_P_thresh(nbar, B0, a, R0, kappa, M_ion, Ip=None,
|
|
|
4338
4572
|
|
|
4339
4573
|
|
|
4340
4574
|
if __name__ == "__main__":
|
|
4341
|
-
#
|
|
4342
|
-
#
|
|
4343
|
-
#
|
|
4344
|
-
#
|
|
4345
|
-
#
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
#
|
|
4350
|
-
#
|
|
4351
|
-
# (2
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4575
|
+
# ── Published anchors - L-H power threshold ──────────────────────────
|
|
4576
|
+
# Martin, JPCS 123 (2008) 012033, against the PUBLISHED ITER
|
|
4577
|
+
# predictions of Table 5 in the ITPA TC-26 paper (NF 2026,
|
|
4578
|
+
# 10.1088/1741-4326/ae39f2): 52.3 MW at 0.5e20 m-3 and 86.0 MW at
|
|
4579
|
+
# 1.0e20 m-3 (deuterium, full field). Tolerance 10 %: D0FUS evaluates
|
|
4580
|
+
# the plasma surface from the Ramanujan ellipse perimeter, the
|
|
4581
|
+
# reference from the true ITER LCFS (~680 m2). The 'New_S' option
|
|
4582
|
+
# (TC-26 draft 2017, Delabie) must sit within the published 1-sigma
|
|
4583
|
+
# intervals of TC-26(Bt): P/S = (0.0441+-0.0025) B^(0.580+-0.039)
|
|
4584
|
+
# n^(1.08+-0.03) (2/M)^(0.975+-0.032); updating to the published
|
|
4585
|
+
# central values (2-4 % on P_LH) is a deliberate maintainer decision.
|
|
4586
|
+
_b_ref = P_Thresh_New_S(1., 1., 2., 6.2, 1.7, 2.)
|
|
4587
|
+
_bench("Published anchors - L-H threshold (Martin, New_S)", [
|
|
4588
|
+
("P_LH Martin, 0.5e20, D [MW]",
|
|
4589
|
+
P_Thresh_Martin(0.5, 5.3, 2.0, 6.2, 1.85, 2.0), 52.3, 0.10,
|
|
4590
|
+
"ITPA TC-26 2026"),
|
|
4591
|
+
("P_LH Martin, 1.0e20, D [MW]",
|
|
4592
|
+
P_Thresh_Martin(1.0, 5.3, 2.0, 6.2, 1.85, 2.0), 86.0, 0.10,
|
|
4593
|
+
"ITPA TC-26 2026"),
|
|
4594
|
+
("New_S exponent on B [-]",
|
|
4595
|
+
float(np.log2(P_Thresh_New_S(1., 2., 2., 6.2, 1.7, 2.) / _b_ref)),
|
|
4596
|
+
0.580, 0.07, "TC-26 1-sigma"),
|
|
4597
|
+
("New_S exponent on n [-]",
|
|
4598
|
+
float(np.log2(P_Thresh_New_S(2., 1., 2., 6.2, 1.7, 2.) / _b_ref)),
|
|
4599
|
+
1.08, 0.037, "TC-26 1-sigma"),
|
|
4600
|
+
])
|
|
4357
4601
|
|
|
4358
4602
|
if __name__ == "__main__":
|
|
4359
|
-
|
|
4360
|
-
#
|
|
4361
|
-
#
|
|
4362
|
-
#
|
|
4363
|
-
#
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4603
|
+
# ── ITER chain (6/12) - L-H threshold and separatrix power ──────────
|
|
4604
|
+
# The Martin scaling is evaluated with the LINE-averaged chain density
|
|
4605
|
+
# and the D-T isotope mass M = 2.5 (P_LH proportional to 1/M), as in
|
|
4606
|
+
# the production solver. P_sep = P_alpha + P_CD - P_rad,total
|
|
4607
|
+
# (f_P_sep; P_Ohm is not included, matching D0FUS_run.py). The
|
|
4608
|
+
# metal-wall correction (W/Be vs C, factor ~0.70: Ryter et al., NF 53
|
|
4609
|
+
# (2013) 113003; Maggi et al., NF 54 (2014) 023007) yields the ~50 MW
|
|
4610
|
+
# best estimate usually quoted for ITER.
|
|
4611
|
+
_PLH = P_Thresh_Martin(ITER['nbar_line'], ITER['B0'], ITER['a'],
|
|
4612
|
+
ITER['R0'], ITER['kappa'], ITER['M'])
|
|
4613
|
+
_Psep = f_P_sep(ITER['P_fus'], ITER['P_aux'], ITER['P_rad_tot'])
|
|
4614
|
+
ITER.update(P_sep=_Psep, P_LH_th=_PLH)
|
|
4615
|
+
_bench("ITER chain 6/12 - L-H threshold and P_sep", [
|
|
4616
|
+
("P_LH Martin (D-T, M=2.5) [MW]", _PLH, FROZEN['P_LH_th'], 2e-3,
|
|
4617
|
+
"deck frozen"),
|
|
4618
|
+
("P_LH x 0.70 metal wall [MW]", 0.70 * _PLH, "~50", None,
|
|
4619
|
+
"Ryter 2013 / Maggi 2014"),
|
|
4620
|
+
("P_sep [MW]", _Psep, FROZEN['P_sep'], 2e-3, "deck frozen"),
|
|
4621
|
+
("H-mode access P_sep/P_LH [-]", _Psep / _PLH, (1.0, 3.0), 1,
|
|
4622
|
+
"operational"),
|
|
4623
|
+
])
|
|
4379
4624
|
|
|
4380
4625
|
def f_P_LH_thresh(nbar, B0, a, R0, kappa, M_ion, Ip=None,
|
|
4381
4626
|
Option_PLH='Martin'):
|
|
@@ -4745,6 +4990,37 @@ def f_Vloop(I_Ohm, a, kappa, R0, Tbar, nbar, Z_eff, q95, nu_T, nu_n,
|
|
|
4745
4990
|
# The duplicate definition that was previously here has been removed to avoid
|
|
4746
4991
|
# silent name shadowing in Python's module namespace.
|
|
4747
4992
|
|
|
4993
|
+
if __name__ == "__main__":
|
|
4994
|
+
# ── ITER chain (7/12) - ohmic power and resistivity models ──────────
|
|
4995
|
+
# P_Ohm closes the power balance at the 0.4 MW level, negligible for
|
|
4996
|
+
# ITER but the natural place to exercise the resistivity chain. The
|
|
4997
|
+
# frozen I_Ohm and q95 are forward references, both closed by
|
|
4998
|
+
# chain 12 (I_Ohm = Ip - I_bs - I_CD and the q95 inversion). The
|
|
4999
|
+
# four implemented resistivity models are compared at the chain
|
|
5000
|
+
# point: the neoclassical values (Sauter 1999; Redl 2021) must exceed
|
|
5001
|
+
# Spitzer because trapped electrons cannot carry parallel current.
|
|
5002
|
+
_Palpha = f_P_alpha(ITER['P_fus'])
|
|
5003
|
+
_po = {m: f_P_Ohm(FROZEN['I_Ohm'], ITER['Tbar'], ITER['R0'], ITER['a'],
|
|
5004
|
+
ITER['kappa'], Z_eff=ITER['Zeff'], nbar=ITER['nbar'],
|
|
5005
|
+
eta_model=m, q95=FROZEN['q95'])
|
|
5006
|
+
for m in ('old', 'spitzer', 'sauter', 'redl')}
|
|
5007
|
+
ITER.update(P_alpha=_Palpha, P_Ohm=_po['sauter']) # deck: eta_model=sauter
|
|
5008
|
+
_bench("ITER chain 7/12 - ohmic power and resistivity models", [
|
|
5009
|
+
("P_alpha = P_fus Ea/(Ea+En) [MW]", _Palpha,
|
|
5010
|
+
ITER['P_fus'] * E_ALPHA / (E_ALPHA + E_N), 1e-12, "definition"),
|
|
5011
|
+
("P_alpha approx P_fus/5 [MW]", _Palpha, 100.0, 1e-3, "rule of thumb"),
|
|
5012
|
+
("P_Ohm Sauter (deck) [MW]", _po['sauter'], FROZEN['P_Ohm'], 5e-3,
|
|
5013
|
+
"deck frozen"),
|
|
5014
|
+
("P_Ohm Spitzer [MW]", _po['spitzer'], None, None, "model comparison"),
|
|
5015
|
+
("P_Ohm Redl [MW]", _po['redl'], None, None, "model comparison"),
|
|
5016
|
+
("P_Ohm Wesson 'old' [MW]", _po['old'], None, None, "model comparison"),
|
|
5017
|
+
("neoclassical enhancement Sauter/Spitzer",
|
|
5018
|
+
_po['sauter'] / _po['spitzer'], (1.0, 5.0), 1, "trapped electrons"),
|
|
5019
|
+
("Sauter vs Redl consistency",
|
|
5020
|
+
_po['sauter'] / _po['redl'], (0.7, 1.4), 1, "model agreement"),
|
|
5021
|
+
])
|
|
5022
|
+
|
|
5023
|
+
|
|
4748
5024
|
def f_I_Ohm(Ip, Ib, I_CD):
|
|
4749
5025
|
"""
|
|
4750
5026
|
Inductive (Ohmic) plasma current from the current balance.
|
|
@@ -4829,16 +5105,21 @@ def f_Q_multiaux(P_fus, P_LH, P_ECRH, P_NBI, P_ICRH, P_Ohm):
|
|
|
4829
5105
|
|
|
4830
5106
|
|
|
4831
5107
|
if __name__ == "__main__":
|
|
4832
|
-
# ITER
|
|
4833
|
-
#
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
5108
|
+
# ── ITER chain (8/12) - fusion gain ──────────────────────────────────
|
|
5109
|
+
# Q = P_fus / (P_aux + P_Ohm) with the flat-top heating mix of the
|
|
5110
|
+
# deck (33 NBI + 6.7 EC + 10 IC = 49.7 MW [Kim 2018]) and the chain
|
|
5111
|
+
# ohmic power from chain 4. The published design target is Q = 10
|
|
5112
|
+
# with ~50 MW of auxiliary power (Shimada 2007).
|
|
5113
|
+
_Q = f_Q_multiaux(P_fus=ITER['P_fus'], P_LH=ITER['P_LH'],
|
|
5114
|
+
P_ECRH=ITER['P_ECRH'], P_NBI=ITER['P_NBI'],
|
|
5115
|
+
P_ICRH=ITER['P_ICRH'], P_Ohm=ITER['P_Ohm'])
|
|
5116
|
+
ITER.update(Q=_Q)
|
|
5117
|
+
_bench("ITER chain 8/12 - fusion gain", [
|
|
5118
|
+
("Q = P_fus/(P_aux + P_Ohm) [-]", _Q, FROZEN['Q'], 2e-3,
|
|
5119
|
+
"deck frozen"),
|
|
5120
|
+
("Q [-]", _Q, 10.0, 0.05, "Shimada 2007"),
|
|
5121
|
+
])
|
|
5122
|
+
|
|
4842
5123
|
# Historical model extracted from
|
|
4843
5124
|
# D.J. Segal, A.J. Cerfon, J.P. Freidberg, "Steady state versus pulsed tokamak reactors",
|
|
4844
5125
|
# Nuclear Fusion, 61(4), 045001, 2021.
|
|
@@ -4959,12 +5240,6 @@ def f_Segal_Ib(nu_n, nu_T, epsilon, kappa, n20, Tk, R0, I_M,
|
|
|
4959
5240
|
|
|
4960
5241
|
return I_b
|
|
4961
5242
|
|
|
4962
|
-
if __name__ == "__main__":
|
|
4963
|
-
# ITER Q=10 baseline — Shimada et al., Nucl. Fusion 47 (2007) S1
|
|
4964
|
-
Ib_Segal = f_Segal_Ib(nu_n=0.5, nu_T=1.0, epsilon=2.0/6.2, kappa=1.75,
|
|
4965
|
-
n20=1.01, Tk=8.9, R0=6.2, I_M=15.0)
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
5243
|
"""
|
|
4969
5244
|
Neoclassical Bootstrap Current Model - Sauter et al. (1999)
|
|
4970
5245
|
|
|
@@ -5176,14 +5451,20 @@ def _nu_e_star(n_e, T_e, q, R0, epsilon, Z_eff):
|
|
|
5176
5451
|
|
|
5177
5452
|
|
|
5178
5453
|
if __name__ == "__main__":
|
|
5179
|
-
#
|
|
5180
|
-
#
|
|
5181
|
-
#
|
|
5454
|
+
# ── Published anchor - electron collisionality identity ──────────────
|
|
5455
|
+
# Sauter, Angioni & Lin-Liu, Phys. Plasmas 6 (1999) 2834, Eq. 18b
|
|
5456
|
+
# with the Coulomb logarithm of Eq. 18d (helper takes T_e in eV).
|
|
5457
|
+
# The ITER core sits deep in the banana regime, nu*_e ~ 0.03.
|
|
5182
5458
|
_ne, _Te = 1.0e20, 8900.0
|
|
5183
|
-
_lnL = 31.3 - np.log(np.sqrt(_ne)/_Te)
|
|
5184
|
-
_nref = 6.921e-18 * 3.0 * 6.2 * _ne * 1.7 * _lnL / (_Te**2 * (2/6.2)**1.5)
|
|
5185
|
-
|
|
5186
|
-
|
|
5459
|
+
_lnL = 31.3 - np.log(np.sqrt(_ne) / _Te)
|
|
5460
|
+
_nref = 6.921e-18 * 3.0 * 6.2 * _ne * 1.7 * _lnL / (_Te**2 * (2 / 6.2)**1.5)
|
|
5461
|
+
_bench("Published anchor - Sauter collisionality (Eq. 18b)", [
|
|
5462
|
+
("nu*_e identity, ITER core [-]",
|
|
5463
|
+
float(_nu_e_star(_ne, _Te, 3.0, 6.2, 2 / 6.2, 1.7)), _nref, 1e-6,
|
|
5464
|
+
"Sauter 1999"),
|
|
5465
|
+
("banana-regime check nu*_e", float(_nref), (0.0, 0.1), 1,
|
|
5466
|
+
"ITER core"),
|
|
5467
|
+
])
|
|
5187
5468
|
|
|
5188
5469
|
def _nu_i_star(n_i, T_i, q, R0, epsilon):
|
|
5189
5470
|
"""Ion collisionality [Eq. 18c]."""
|
|
@@ -6368,20 +6649,34 @@ def f_q_profile_refined(
|
|
|
6368
6649
|
|
|
6369
6650
|
|
|
6370
6651
|
if __name__ == "__main__":
|
|
6371
|
-
#
|
|
6372
|
-
#
|
|
6373
|
-
|
|
6374
|
-
|
|
6375
|
-
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
6380
|
-
|
|
6381
|
-
|
|
6382
|
-
|
|
6383
|
-
|
|
6384
|
-
|
|
6652
|
+
# ── ITER chain (9/12) - bootstrap current ────────────────────────────
|
|
6653
|
+
# Sauter-Redl evaluated at the chain operating point with the deck
|
|
6654
|
+
# pedestal profiles. q95 is a forward reference (FROZEN), closed by
|
|
6655
|
+
# chain 12 after f_q95 is defined. The production solver additionally
|
|
6656
|
+
# refines the collisionality with the Picard q(rho) cache, worth
|
|
6657
|
+
# ~0.2 % on I_bs, hence the 1 % tolerance. The Segal (2021) model is
|
|
6658
|
+
# reported for model comparison at the same point.
|
|
6659
|
+
_Ib = f_Sauter_Redl_Ib(ITER['R0'], ITER['a'], ITER['kappa'], ITER['B0'],
|
|
6660
|
+
ITER['nbar'], ITER['Tbar'], FROZEN['q95'],
|
|
6661
|
+
ITER['Zeff'], ITER['nu_n'], ITER['nu_T'],
|
|
6662
|
+
rho_ped=ITER['rho_ped'],
|
|
6663
|
+
n_ped_frac=ITER['n_ped_frac'],
|
|
6664
|
+
T_ped_frac=ITER['T_ped_frac'],
|
|
6665
|
+
Vprime_data=ITER_Vpd, kappa_95=ITER['kappa95'],
|
|
6666
|
+
tau_i_e=1.0)
|
|
6667
|
+
_Ib_seg = f_Segal_Ib(ITER['nu_n'], ITER['nu_T'], ITER['a'] / ITER['R0'],
|
|
6668
|
+
ITER['kappa'], ITER['nbar'], ITER['Tbar'],
|
|
6669
|
+
ITER['R0'], FROZEN['Ip'],
|
|
6670
|
+
rho_ped=ITER['rho_ped'],
|
|
6671
|
+
n_ped_frac=ITER['n_ped_frac'],
|
|
6672
|
+
T_ped_frac=ITER['T_ped_frac'])
|
|
6673
|
+
ITER.update(Ib=_Ib)
|
|
6674
|
+
_bench("ITER chain 9/12 - bootstrap current (Sauter-Redl)", [
|
|
6675
|
+
("I_bs Sauter-Redl [MA]", _Ib, FROZEN['Ib'], 0.01, "deck frozen"),
|
|
6676
|
+
("bootstrap fraction I_bs/Ip [-]", _Ib / FROZEN['Ip'], None, None,
|
|
6677
|
+
"chain"),
|
|
6678
|
+
("I_bs Segal (2021) [MA]", _Ib_seg, None, None, "model comparison"),
|
|
6679
|
+
])
|
|
6385
6680
|
|
|
6386
6681
|
#%% Other parameters
|
|
6387
6682
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -6586,13 +6881,29 @@ def f_heat_PFU_Eich(P_sol, B_pol, R, eps, theta_deg,
|
|
|
6586
6881
|
|
|
6587
6882
|
|
|
6588
6883
|
if __name__ == "__main__":
|
|
6589
|
-
# SOL width anchor
|
|
6590
|
-
#
|
|
6591
|
-
#
|
|
6592
|
-
# (
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
|
|
6884
|
+
# ── SOL width: published anchor and ITER chain (10/12) ──────────────
|
|
6885
|
+
# Published anchor: Eich et al., NF 53 (2013) 093031, Table 6,
|
|
6886
|
+
# regression #15: lambda_q [mm] = 1.35 P_SOL^-0.02 R^0.04 Bpol^-0.92
|
|
6887
|
+
# (a/R)^0.42. Closed worked example: the paper's own ITER evaluation
|
|
6888
|
+
# gives lambda_q = 0.73 mm (P_SOL = 100 MW, Bpol,MP = 1.185 T,
|
|
6889
|
+
# R = 6.2 m, a = 2 m); tolerance 8 % (rounding of the quoted
|
|
6890
|
+
# midplane poloidal field).
|
|
6891
|
+
# Chain: B_pol from the q-inversion at the frozen q95 (closed by
|
|
6892
|
+
# chain 12) and lambda_q at the chain P_sep.
|
|
6893
|
+
_lam_pub, _, _ = f_heat_PFU_Eich(100., 1.185, 6.2, 2 / 6.2, 3.0, B0=5.3)
|
|
6894
|
+
_Bpol = f_Bpol(FROZEN['q95'], ITER['B0'], ITER['a'], ITER['R0'],
|
|
6895
|
+
kappa=ITER['kappa'])
|
|
6896
|
+
_lam, _, _ = f_heat_PFU_Eich(ITER['P_sep'], _Bpol, ITER['R0'],
|
|
6897
|
+
ITER['a'] / ITER['R0'], FROZEN['q95'],
|
|
6898
|
+
B0=ITER['B0'])
|
|
6899
|
+
_bench("ITER chain 10/12 - SOL heat-flux width (Eich #15)", [
|
|
6900
|
+
("lambda_q paper inputs [mm]", _lam_pub * 1e3, 0.73, 0.08,
|
|
6901
|
+
"Eich 2013"),
|
|
6902
|
+
("B_pol outer midplane [T]", _Bpol, FROZEN['B_pol'], 2e-3,
|
|
6903
|
+
"deck frozen"),
|
|
6904
|
+
("lambda_q chain point [mm]", _lam * 1e3, FROZEN['lambda_q_mm'],
|
|
6905
|
+
2e-3, "deck frozen"),
|
|
6906
|
+
])
|
|
6596
6907
|
|
|
6597
6908
|
# =============================================================================
|
|
6598
6909
|
# Refined divertor exhaust — two-point model (Stangeby 2018)
|
|
@@ -7274,24 +7585,36 @@ def f_Get_parameter_scaling_law(Scaling_Law):
|
|
|
7274
7585
|
# ── Global energy and confinement descriptors ─────────────────────────────────
|
|
7275
7586
|
|
|
7276
7587
|
if __name__ == "__main__":
|
|
7277
|
-
#
|
|
7588
|
+
# ── Published anchors - confinement-scaling registry ─────────────────
|
|
7589
|
+
# The registry must store the published exponents EXACTLY:
|
|
7278
7590
|
# IPB98(y,2): ITER Physics Basis, NF 39 (1999) 2175 / Doyle, NF 47
|
|
7279
7591
|
# (2007) S18. ITPA20: Verdoolaege, NF 61 (2021) 076006. ITER89-P:
|
|
7280
|
-
# Yushmanov, NF 30 (1990) 1999
|
|
7281
|
-
#
|
|
7592
|
+
# Yushmanov, NF 30 (1990) 1999, with the prefactor converted to the
|
|
7593
|
+
# D0FUS n19 convention: C(n19) = 0.048 x 10^-0.1 = 0.0381.
|
|
7594
|
+
# Exact tuple equality is asserted (structural identity).
|
|
7282
7595
|
assert f_Get_parameter_scaling_law('IPB98(y,2)') == \
|
|
7283
7596
|
(0.0562, 0, 0.19, 0.78, 0.58, 1.97, 0.15, 0.41, 0.93, -0.69)
|
|
7284
7597
|
assert f_Get_parameter_scaling_law('ITPA20') == \
|
|
7285
7598
|
(0.053, 0.36, 0.2, 0.8, 0.35, 1.71, 0.22, 0.24, 0.98, -0.669)
|
|
7286
|
-
|
|
7287
|
-
#
|
|
7288
|
-
#
|
|
7289
|
-
#
|
|
7290
|
-
|
|
7291
|
-
|
|
7292
|
-
|
|
7293
|
-
|
|
7294
|
-
|
|
7599
|
+
# IPB98 at the ITER Q=10 point with the PUBLISHED loss-power
|
|
7600
|
+
# convention (P = 87 MW, radiation NOT subtracted; kappa_x = 1.70,
|
|
7601
|
+
# n19 = 10.1): tolerance 8 % covers the kappa-convention and P_loss
|
|
7602
|
+
# definition spread between sources. The production (PROCESS-like)
|
|
7603
|
+
# convention, which subtracts the core radiation, is exercised by
|
|
7604
|
+
# chain 11.
|
|
7605
|
+
_Csl, _ad, _aM, _ak, _ae, _aR, _aB, _an, _aI, _aP = \
|
|
7606
|
+
f_Get_parameter_scaling_law('IPB98(y,2)')
|
|
7607
|
+
_tau98 = (_Csl * 15**_aI * 5.3**_aB * 10.1**_an * 2.5**_aM * 6.2**_aR
|
|
7608
|
+
* (2 / 6.2)**_ae * 1.70**_ak * 87.0**_aP)
|
|
7609
|
+
_bench("Published anchors - confinement scaling laws", [
|
|
7610
|
+
("IPB98(y,2) / ITPA20 exponents", "exact", "published", None,
|
|
7611
|
+
"registry assert"),
|
|
7612
|
+
("ITER89-P prefactor C(n19) [-]",
|
|
7613
|
+
f_Get_parameter_scaling_law('ITER89-P')[0], 0.048 * 10**-0.1, 5e-3,
|
|
7614
|
+
"Yushmanov 1990"),
|
|
7615
|
+
("tau_IPB98, published convention [s]", _tau98, 3.7, 0.08,
|
|
7616
|
+
"IPB 1999"),
|
|
7617
|
+
])
|
|
7295
7618
|
|
|
7296
7619
|
def f_tauE(pbar, V, P_Alpha, P_Aux, P_Ohm, P_rad):
|
|
7297
7620
|
"""
|
|
@@ -7441,6 +7764,57 @@ def f_Q(P_fus, P_CD, P_Ohm):
|
|
|
7441
7764
|
return P_fus / P_heat
|
|
7442
7765
|
|
|
7443
7766
|
|
|
7767
|
+
if __name__ == "__main__":
|
|
7768
|
+
# ── ITER chain (11/12) - stored energy, tau_E and plasma current ────
|
|
7769
|
+
# Loss-power convention: D0FUS subtracts the CORE radiated power from
|
|
7770
|
+
# the heating power, P_loss = P_alpha + P_aux + P_Ohm - P_rad,core,
|
|
7771
|
+
# in BOTH tau_E and the scaling-law inversion for Ip (PROCESS-like
|
|
7772
|
+
# convention; see the note in f_tauE). With this convention and the
|
|
7773
|
+
# chain radiation budget, the module-level tau_E and Ip must land on
|
|
7774
|
+
# the full-device values. The published tau_E = 3.7 s instead uses
|
|
7775
|
+
# P_loss = 87 MW with no radiation subtracted: the same stored energy
|
|
7776
|
+
# then gives W_th/87 = 3.91 s, bracketing the published value.
|
|
7777
|
+
# The IPB98 inversion uses the area elongation kappa_a = V/(2 pi^2 R0
|
|
7778
|
+
# a^2) and the LINE-averaged density (fitting conventions of the law).
|
|
7779
|
+
_W = f_W_th(ITER['pbar'], ITER['V']) / 1e6 # [MJ]
|
|
7780
|
+
_tau = f_tauE(ITER['pbar'], ITER['V'], ITER['P_alpha'], ITER['P_aux'],
|
|
7781
|
+
ITER['P_Ohm'], ITER['P_rad_core'])
|
|
7782
|
+
_Ploss = (ITER['P_alpha'] + ITER['P_aux'] + ITER['P_Ohm']
|
|
7783
|
+
- ITER['P_rad_core'])
|
|
7784
|
+
_Csl, _ad, _aM, _ak, _ae, _aR, _aB, _an, _aI, _aP = \
|
|
7785
|
+
f_Get_parameter_scaling_law('IPB98(y,2)')
|
|
7786
|
+
_Ip = f_Ip(_tau, ITER['R0'], ITER['a'], ITER['kappa_a'], ITER['delta'],
|
|
7787
|
+
ITER['nbar_line'], ITER['B0'], ITER['M'],
|
|
7788
|
+
ITER['P_alpha'], ITER['P_Ohm'], ITER['P_aux'],
|
|
7789
|
+
ITER['P_rad_core'], ITER['H'], _Csl,
|
|
7790
|
+
_ad, _aM, _ak, _ae, _aR, _aB, _an, _aI, _aP)
|
|
7791
|
+
_nG = f_nG(_Ip, ITER['a'])
|
|
7792
|
+
ITER.update(tauE=_tau, Ip=_Ip, W_th=_W)
|
|
7793
|
+
_bench("ITER chain 11/12 - tau_E and plasma current (IPB98 inversion)", [
|
|
7794
|
+
("W_th [MJ]", _W, FROZEN['W_th'], 1e-3, "deck frozen"),
|
|
7795
|
+
("P_loss = P_heat - P_rad,core [MW]", _Ploss, None, None,
|
|
7796
|
+
"convention"),
|
|
7797
|
+
("tau_E, core radiation subtracted [s]", _tau, FROZEN['tauE'], 1e-3,
|
|
7798
|
+
"deck frozen"),
|
|
7799
|
+
("energy-balance closure W/(tau P_loss)",
|
|
7800
|
+
f_W_th(ITER['pbar'], ITER['V']) / 1e6 / (_tau * _Ploss), 1.0, 1e-9,
|
|
7801
|
+
"identity"),
|
|
7802
|
+
("tau_E, published convention [s]", _W / 87.0, "3.7", None,
|
|
7803
|
+
"IPB 1999 (P=87 MW)"),
|
|
7804
|
+
("Ip from IPB98 inversion [MA]", _Ip, FROZEN['Ip'], 5e-3,
|
|
7805
|
+
"deck frozen"),
|
|
7806
|
+
("Ip [MA]", _Ip, 15.0, 0.01, "Shimada 2007"),
|
|
7807
|
+
("n_GW at chain Ip [1e20 m-3]", _nG, FROZEN['nG'], 2e-3,
|
|
7808
|
+
"deck frozen"),
|
|
7809
|
+
("f_GW = n_line/n_GW [-]", ITER['nbar_line'] / _nG, 0.85, 5e-3,
|
|
7810
|
+
"deck target"),
|
|
7811
|
+
], notes=[
|
|
7812
|
+
"Neither Ip nor the density is imposed: the deck receives the "
|
|
7813
|
+
"geometry, B at the front face, P_fus, P_aux and f_GW = 0.85, and "
|
|
7814
|
+
"the chain closes on the published 15 MA / 1.01e20 m-3 point.",
|
|
7815
|
+
])
|
|
7816
|
+
|
|
7817
|
+
|
|
7444
7818
|
# ── Helium ash accumulation model ─────────────────────────────────────────────
|
|
7445
7819
|
|
|
7446
7820
|
def _sigmav_vol(T_bar, nu_T, rho_ped=1.0, T_ped_frac=0.0, N=200,
|
|
@@ -8483,64 +8857,93 @@ def compute_RE_indicators(Ip, nbar, Tbar, a, R0, κ, Z_eff, li,
|
|
|
8483
8857
|
# ── Validation ────────────────────────────────────────────────────────────────
|
|
8484
8858
|
|
|
8485
8859
|
if __name__ == "__main__":
|
|
8486
|
-
#
|
|
8487
|
-
#
|
|
8488
|
-
#
|
|
8489
|
-
|
|
8490
|
-
#
|
|
8491
|
-
#
|
|
8492
|
-
#
|
|
8493
|
-
|
|
8494
|
-
|
|
8495
|
-
|
|
8496
|
-
|
|
8497
|
-
|
|
8498
|
-
|
|
8499
|
-
|
|
8500
|
-
|
|
8501
|
-
|
|
8502
|
-
|
|
8503
|
-
|
|
8504
|
-
|
|
8505
|
-
|
|
8506
|
-
|
|
8507
|
-
|
|
8508
|
-
|
|
8509
|
-
|
|
8510
|
-
|
|
8511
|
-
|
|
8512
|
-
|
|
8513
|
-
|
|
8514
|
-
|
|
8515
|
-
|
|
8516
|
-
|
|
8517
|
-
|
|
8518
|
-
|
|
8519
|
-
|
|
8520
|
-
|
|
8521
|
-
|
|
8522
|
-
|
|
8523
|
-
|
|
8524
|
-
|
|
8525
|
-
|
|
8526
|
-
|
|
8527
|
-
|
|
8528
|
-
|
|
8529
|
-
|
|
8530
|
-
|
|
8531
|
-
|
|
8532
|
-
|
|
8533
|
-
|
|
8534
|
-
|
|
8535
|
-
|
|
8536
|
-
|
|
8537
|
-
|
|
8860
|
+
# ── ITER chain (12/12) - q95, wall load and helium ash closure ───────
|
|
8861
|
+
# q95: the Sauter (2016) formula at the chain Ip and LCFS shaping
|
|
8862
|
+
# closes the forward reference used by chains 4, 9 and 10; the
|
|
8863
|
+
# ITER-1989 guideline formula at the PUBLISHED 95 % shaping and 15 MA
|
|
8864
|
+
# reproduces the ITER design value q95 = 3.0.
|
|
8865
|
+
# Helium ash: f_alpha solves the Sarazin steady-state balance with
|
|
8866
|
+
# the deck removal efficiency C_alpha = 5 (tau*_alpha = C_alpha
|
|
8867
|
+
# tau_E) at the chain tau_E. The value must close on the f_alpha
|
|
8868
|
+
# forward reference injected in chain 2, which is the convergence
|
|
8869
|
+
# criterion of the production solver. The production solver evaluates
|
|
8870
|
+
# the ash balance with the cylindrical volume weight even in refined
|
|
8871
|
+
# geometry (see D0FUS_run.py); the chain mirrors that call exactly.
|
|
8872
|
+
_q95 = f_q95(ITER['B0'], ITER['Ip'], ITER['R0'], ITER['a'],
|
|
8873
|
+
ITER['kappa'], ITER['delta'], ITER['kappa95'],
|
|
8874
|
+
ITER['delta95'], Option_q95='Sauter')
|
|
8875
|
+
_q95_pub = f_q95(5.3, 15.0, 6.2, 2.0, 1.85, 0.485, 1.70, 0.33,
|
|
8876
|
+
Option_q95='ITER_1989')
|
|
8877
|
+
_Gam = f_Gamma_n(ITER['a'], ITER['P_fus'], ITER['R0'], ITER['kappa'],
|
|
8878
|
+
S_wall=ITER['S'])
|
|
8879
|
+
_fa = f_He_fraction(ITER['nbar'], ITER['Tbar'], ITER['tauE'],
|
|
8880
|
+
ITER['C_Alpha'], ITER['nu_T'],
|
|
8881
|
+
rho_ped=ITER['rho_ped'],
|
|
8882
|
+
T_ped_frac=ITER['T_ped_frac'], tau_i_e=1.0)
|
|
8883
|
+
_ta = f_tau_alpha(ITER['nbar'], ITER['Tbar'], ITER['tauE'],
|
|
8884
|
+
ITER['C_Alpha'], ITER['nu_T'],
|
|
8885
|
+
rho_ped=ITER['rho_ped'],
|
|
8886
|
+
T_ped_frac=ITER['T_ped_frac'])
|
|
8887
|
+
_IOhm = f_I_Ohm(ITER['Ip'], ITER['Ib'], ITER['I_CD'])
|
|
8888
|
+
_bench("ITER chain 12/12 - q95, neutron wall load, helium ash", [
|
|
8889
|
+
("q95 Sauter, chain Ip [-]", _q95, FROZEN['q95'], 2e-3,
|
|
8890
|
+
"deck frozen"),
|
|
8891
|
+
("q95 ITER-1989, published point [-]", _q95_pub, 3.0, 0.01,
|
|
8892
|
+
"Uckan 1990"),
|
|
8893
|
+
("Gamma_n, Miller wall [MW/m2]", _Gam, FROZEN['Gamma_n'], 2e-3,
|
|
8894
|
+
"deck frozen"),
|
|
8895
|
+
("Gamma_n [MW/m2]", _Gam, 0.57, 0.05, "Shimada 2007"),
|
|
8896
|
+
("f_He closure (C_alpha = 5) [-]", _fa, FROZEN['f_alpha'], 1e-3,
|
|
8897
|
+
"solver fixed point"),
|
|
8898
|
+
("f_He, IPB exhaust assumption [%]", _fa * 100, "4.4", None,
|
|
8899
|
+
"IPB 1999"),
|
|
8900
|
+
("tau*_alpha = C_alpha tau_E [s]", _ta, FROZEN['tau_alpha'], 1e-3,
|
|
8901
|
+
"deck frozen"),
|
|
8902
|
+
("I_Ohm = Ip - I_bs - I_CD [MA]", _IOhm, FROZEN['I_Ohm'], 5e-3,
|
|
8903
|
+
"deck frozen"),
|
|
8904
|
+
], notes=[
|
|
8905
|
+
"The f_He closure is the global loop of the chain: chain 2 "
|
|
8906
|
+
"injected the frozen f_alpha into the density solve, and the same "
|
|
8907
|
+
"value re-emerges from the ash balance at the chain tau_E.",
|
|
8908
|
+
"The IPB exhaust value (4.4 %) corresponds to the ITER Physics "
|
|
8909
|
+
"Basis assumption at its own operating point.",
|
|
8910
|
+
"The I_Ohm row closes the forward reference of chain 7 "
|
|
8911
|
+
"(0.2 % residual inherited from the Picard q-profile cache of "
|
|
8912
|
+
"the bootstrap step).",
|
|
8913
|
+
])
|
|
8914
|
+
|
|
8915
|
+
if __name__ == "__main__":
|
|
8916
|
+
# ── Published anchors - runaway electrons ────────────────────────────
|
|
8917
|
+
# Hot-tail seed: Smith & Verwichte model evaluated at the Stahl
|
|
8918
|
+
# (2016) Fig. 2(b) point (informative: the figure quotes 4-5e-4).
|
|
8919
|
+
# Avalanche: Breizman et al., NF 59 (2019) 083001, Eq. 99 at the
|
|
8920
|
+
# Fig. 17 conditions (li = 1, Z = 4, Ip = 15 MA); the implementation
|
|
8921
|
+
# must reproduce the analytic Eq. 99 values, and the Fig. 17 ranges
|
|
8922
|
+
# are quoted for context.
|
|
8923
|
+
_f_RE = _hot_tail_fraction_local(2.8e19, 3.1e3, 1.4e6, Te_final_eV=31.0,
|
|
8924
|
+
tau_TQ=0.3e-3, Z_eff=1.0)
|
|
8925
|
+
_re_rows = [
|
|
8926
|
+
("hot-tail f_RE (local) [-]", float(_f_RE), "4-5e-4", None,
|
|
8927
|
+
"Stahl 2016 Fig. 2b"),
|
|
8928
|
+
("relativistic lnLambda (1e20, 5 eV)",
|
|
8929
|
+
float(_coulomb_log_relativistic(1e20, 5.0)), None, None,
|
|
8930
|
+
"Breizman 2019"),
|
|
8931
|
+
]
|
|
8932
|
+
for _ire0, _ref99, _fig17 in ((1.0, 3.306, "~2-3"), (1e3, 7.999, "~7-9"),
|
|
8933
|
+
(1e6, 13.002, "~12-14")):
|
|
8934
|
+
_ire = f_RE_avalanche(15e6, _ire0, 1e20, 5.0, 1.0, 4) / 1e6
|
|
8935
|
+
_re_rows.append((f"I_RE avalanche, seed {_ire0:.0e} A [MA]",
|
|
8936
|
+
float(_ire), _ref99, 1e-3, "Breizman Eq. 99"))
|
|
8937
|
+
_re_rows.append((f" Fig. 17 range, seed {_ire0:.0e} A [MA]",
|
|
8938
|
+
float(_ire), _fig17, None, "Breizman Fig. 17"))
|
|
8939
|
+
_bench("Published anchors - runaway electrons (hot tail, avalanche)",
|
|
8940
|
+
_re_rows)
|
|
8538
8941
|
|
|
8539
8942
|
import D0FUS_BIB.D0FUS_figures as figs
|
|
8540
8943
|
# plot_He_fraction takes separate ITER/DEMO removal efficiencies
|
|
8541
|
-
# (C_Alpha_ITER=5.0, C_Alpha_DEMO=7.0 by default);
|
|
8542
|
-
#
|
|
8543
|
-
figs.plot_He_fraction(C_Alpha_ITER=
|
|
8944
|
+
# (C_Alpha_ITER=5.0, C_Alpha_DEMO=7.0 by default); defaults match the
|
|
8945
|
+
# chain above (C_alpha = 5).
|
|
8946
|
+
figs.plot_He_fraction(C_Alpha_ITER=ITER['C_Alpha'])
|
|
8544
8947
|
|
|
8545
8948
|
#%%
|
|
8546
8949
|
|
|
@@ -8549,21 +8952,35 @@ if __name__ == "__main__":
|
|
|
8549
8952
|
|
|
8550
8953
|
if __name__ == "__main__":
|
|
8551
8954
|
# ─────────────────────────────────────────────────────────────────────
|
|
8552
|
-
#
|
|
8553
|
-
# frozen 2026-06 values (anti-drift guard; intentional physics
|
|
8554
|
-
# must update these anchors
|
|
8555
|
-
#
|
|
8955
|
+
# Full-device regression: the shipped reference deck must reproduce
|
|
8956
|
+
# the frozen 2026-06 values (anti-drift guard; intentional physics
|
|
8957
|
+
# changes must update these anchors AND the FROZEN dict at the top of
|
|
8958
|
+
# this file). Skipped gracefully if the deck is absent. Indices
|
|
8959
|
+
# follow the save_run_output tuple map of D0FUS_EXE/D0FUS_run.py.
|
|
8556
8960
|
# ─────────────────────────────────────────────────────────────────────
|
|
8557
8961
|
try:
|
|
8558
8962
|
from D0FUS_EXE.D0FUS_run import load_config_from_file, run
|
|
8559
8963
|
_deck = os.path.join(os.path.dirname(os.path.dirname(
|
|
8560
8964
|
os.path.abspath(__file__))), 'D0FUS_INPUTS', '1_run_ITER.txt')
|
|
8561
8965
|
_res = run(load_config_from_file(_deck), verbose=0)
|
|
8562
|
-
|
|
8563
|
-
|
|
8564
|
-
|
|
8565
|
-
|
|
8566
|
-
|
|
8567
|
-
|
|
8966
|
+
_frozen_idx = [
|
|
8967
|
+
(0, "B0 [T]", 5.300), (3, "tau_E [s]", 3.144),
|
|
8968
|
+
(5, "Q [-]", 9.982), (8, "Ip [MA]", 14.968),
|
|
8969
|
+
(9, "I_bs [MA]", 4.816), (13, "n_line [1e20 m-3]", 1.012),
|
|
8970
|
+
(14, "n_GW [1e20 m-3]", 1.191), (16, "beta_N [-]", 1.635),
|
|
8971
|
+
(20, "q95 [-]", 3.598), (23, "P_LH [MW]", 73.522),
|
|
8972
|
+
(40, "f_alpha [-]", 0.029762),
|
|
8973
|
+
]
|
|
8974
|
+
_rows = [(f"deck[{_i}] {_nm}", float(_res[_i]), _v, 5e-3,
|
|
8975
|
+
"frozen 2026-06") for _i, _nm, _v in _frozen_idx]
|
|
8976
|
+
_rows.append(("deck[4] W_th [MJ]", float(_res[4]) / 1e6, 340.15,
|
|
8977
|
+
5e-3, "frozen 2026-06"))
|
|
8978
|
+
_bench("Full-device regression - shipped ITER deck", _rows, notes=[
|
|
8979
|
+
"Closes the chain: every forward reference (f_alpha, "
|
|
8980
|
+
"f_imp_dil, q95, I_Ohm) and every chain output is "
|
|
8981
|
+
"re-produced by the assembled solver on the shipped deck.",
|
|
8982
|
+
])
|
|
8568
8983
|
except FileNotFoundError:
|
|
8569
|
-
print("--
|
|
8984
|
+
print("-- ITER deck not found: full-device regression skipped")
|
|
8985
|
+
|
|
8986
|
+
_bench_summary()
|