d0fus 2.2.0__tar.gz → 2.2.2__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.2.0 → d0fus-2.2.2}/D0FUS_BIB/D0FUS_figures.py +326 -108
- {d0fus-2.2.0 → d0fus-2.2.2}/D0FUS_BIB/D0FUS_parameterization.py +143 -5
- {d0fus-2.2.0 → d0fus-2.2.2}/D0FUS_BIB/D0FUS_physical_functions.py +919 -758
- {d0fus-2.2.0 → d0fus-2.2.2}/D0FUS_BIB/D0FUS_radial_build_functions.py +133 -68
- {d0fus-2.2.0 → d0fus-2.2.2}/D0FUS_EXE/D0FUS_run.py +292 -129
- {d0fus-2.2.0 → d0fus-2.2.2}/PKG-INFO +21 -21
- {d0fus-2.2.0 → d0fus-2.2.2}/README.md +20 -20
- {d0fus-2.2.0 → d0fus-2.2.2}/d0fus.egg-info/PKG-INFO +21 -21
- {d0fus-2.2.0 → d0fus-2.2.2}/pyproject.toml +1 -1
- {d0fus-2.2.0 → d0fus-2.2.2}/D0FUS.py +0 -0
- {d0fus-2.2.0 → d0fus-2.2.2}/D0FUS_BIB/D0FUS_cost_data.py +0 -0
- {d0fus-2.2.0 → d0fus-2.2.2}/D0FUS_BIB/D0FUS_cost_functions.py +0 -0
- {d0fus-2.2.0 → d0fus-2.2.2}/D0FUS_BIB/D0FUS_import.py +0 -0
- {d0fus-2.2.0 → d0fus-2.2.2}/D0FUS_EXE/D0FUS_genetic.py +0 -0
- {d0fus-2.2.0 → d0fus-2.2.2}/D0FUS_EXE/D0FUS_scan.py +0 -0
- {d0fus-2.2.0 → d0fus-2.2.2}/LICENSE +0 -0
- {d0fus-2.2.0 → d0fus-2.2.2}/d0fus.egg-info/SOURCES.txt +0 -0
- {d0fus-2.2.0 → d0fus-2.2.2}/d0fus.egg-info/dependency_links.txt +0 -0
- {d0fus-2.2.0 → d0fus-2.2.2}/d0fus.egg-info/entry_points.txt +0 -0
- {d0fus-2.2.0 → d0fus-2.2.2}/d0fus.egg-info/requires.txt +0 -0
- {d0fus-2.2.0 → d0fus-2.2.2}/d0fus.egg-info/top_level.txt +0 -0
- {d0fus-2.2.0 → d0fus-2.2.2}/setup.cfg +0 -0
|
@@ -48,14 +48,15 @@ if __name__ != "__main__":
|
|
|
48
48
|
get_Lz,
|
|
49
49
|
f_He_fraction,
|
|
50
50
|
f_q_profile,
|
|
51
|
+
f_sigmav,
|
|
51
52
|
)
|
|
52
53
|
from .D0FUS_radial_build_functions import (
|
|
53
54
|
J_non_Cu_NbTi, J_non_Cu_Nb3Sn, J_non_Cu_REBCO,
|
|
54
55
|
calculate_cable_current_density,
|
|
55
56
|
eta_old, eta_spitzer, eta_sauter, eta_redl,
|
|
56
|
-
f_TF_academic,
|
|
57
|
-
|
|
58
|
-
f_CS_ACAD,
|
|
57
|
+
f_TF_academic, f_TF_refined,
|
|
58
|
+
Winding_Pack_refined, gamma_func, _last_graded_profile,
|
|
59
|
+
f_CS_ACAD, f_CS_refined, f_CS_CIRCE,
|
|
59
60
|
F_CIRCE0D, compute_von_mises_stress,
|
|
60
61
|
calculate_E_mag_TF,
|
|
61
62
|
)
|
|
@@ -76,14 +77,15 @@ else:
|
|
|
76
77
|
get_Lz,
|
|
77
78
|
f_He_fraction,
|
|
78
79
|
f_q_profile,
|
|
80
|
+
f_sigmav,
|
|
79
81
|
)
|
|
80
82
|
from D0FUS_BIB.D0FUS_radial_build_functions import (
|
|
81
83
|
J_non_Cu_NbTi, J_non_Cu_Nb3Sn, J_non_Cu_REBCO,
|
|
82
84
|
calculate_cable_current_density,
|
|
83
85
|
eta_old, eta_spitzer, eta_sauter, eta_redl,
|
|
84
|
-
f_TF_academic,
|
|
85
|
-
|
|
86
|
-
f_CS_ACAD,
|
|
86
|
+
f_TF_academic, f_TF_refined,
|
|
87
|
+
Winding_Pack_refined, gamma_func, _last_graded_profile,
|
|
88
|
+
f_CS_ACAD, f_CS_refined, f_CS_CIRCE,
|
|
87
89
|
F_CIRCE0D, compute_von_mises_stress,
|
|
88
90
|
calculate_E_mag_TF,
|
|
89
91
|
)
|
|
@@ -193,9 +195,9 @@ def plot_shaping_profiles(
|
|
|
193
195
|
"""
|
|
194
196
|
Plot radial elongation κ(ρ) and triangularity δ(ρ) profiles.
|
|
195
197
|
|
|
196
|
-
Both the 'Academic' (constant-κ, δ=0) and the '
|
|
197
|
-
shown. For δ, both positive and negative triangularity
|
|
198
|
-
superimposed.
|
|
198
|
+
Both the 'Academic' (constant-κ, δ=0) and the 'Refined PCHIP'
|
|
199
|
+
profiles are shown. For δ, both positive and negative triangularity
|
|
200
|
+
cases are superimposed.
|
|
199
201
|
|
|
200
202
|
Parameters
|
|
201
203
|
----------
|
|
@@ -206,7 +208,7 @@ def plot_shaping_profiles(
|
|
|
206
208
|
|
|
207
209
|
References
|
|
208
210
|
----------
|
|
209
|
-
|
|
211
|
+
Fritsch & Carlson, SIAM J. Numer. Anal. 17, 238 (1980) — PCHIP scheme.
|
|
210
212
|
Ball & Parra, PPCF 57, 045006 (2015) — κ core penetration.
|
|
211
213
|
"""
|
|
212
214
|
rho = np.linspace(1e-4, 1.0, 500)
|
|
@@ -223,7 +225,7 @@ def plot_shaping_profiles(
|
|
|
223
225
|
label=f"Academic: κ = κ_edge = {kappa_edge} (const)")
|
|
224
226
|
ax.plot(rho, kappa_profile(rho, kappa_edge, kappa_95),
|
|
225
227
|
color="tab:red", lw=2,
|
|
226
|
-
label="
|
|
228
|
+
label="Refined: flat core at κ₉₅, edge rise (PCHIP)")
|
|
227
229
|
ax.axvline(rho_95, color="gray", lw=1, ls="--", alpha=0.7)
|
|
228
230
|
ax.axhline(kappa_95, color="gray", lw=1, ls="--", alpha=0.7)
|
|
229
231
|
ax.plot(rho_95, kappa_95, "o", color="tab:red", ms=7, zorder=5,
|
|
@@ -243,10 +245,10 @@ def plot_shaping_profiles(
|
|
|
243
245
|
ax.axhline(0, color="tab:blue", lw=2, ls="-", label="Academic: δ = 0")
|
|
244
246
|
ax.plot(rho, delta_profile(rho, delta_edge, delta_95),
|
|
245
247
|
color="tab:red", lw=2,
|
|
246
|
-
label=f"
|
|
248
|
+
label=f"Refined PCHIP: δ_edge = +{delta_edge} (D-shape)")
|
|
247
249
|
ax.plot(rho, delta_profile(rho, delta_edge_neg, delta_95_neg),
|
|
248
250
|
color="tab:purple", lw=2,
|
|
249
|
-
label=f"
|
|
251
|
+
label=f"Refined PCHIP: δ_edge = {delta_edge_neg} (neg. triang.)")
|
|
250
252
|
ax.axvline(rho_95, color="gray", lw=1, ls="--", alpha=0.7)
|
|
251
253
|
ax.axhline(delta_95, color="tab:red", lw=1, ls="--", alpha=0.6)
|
|
252
254
|
ax.plot(rho_95, delta_95, "o", color="tab:red", ms=7, zorder=5,
|
|
@@ -283,9 +285,9 @@ def plot_miller_surfaces(
|
|
|
283
285
|
) -> None:
|
|
284
286
|
"""
|
|
285
287
|
Plot Miller flux surfaces for three geometry configurations side by side:
|
|
286
|
-
1.
|
|
287
|
-
2. Academic
|
|
288
|
-
3.
|
|
288
|
+
1. Refined PCHIP — positive triangularity
|
|
289
|
+
2. Academic — circular (κ const, δ = 0)
|
|
290
|
+
3. Refined PCHIP — negative triangularity
|
|
289
291
|
|
|
290
292
|
Parameters
|
|
291
293
|
----------
|
|
@@ -309,15 +311,15 @@ def plot_miller_surfaces(
|
|
|
309
311
|
colors_fs = cm.Blues(np.linspace(0.20, 0.95, n_levels))
|
|
310
312
|
|
|
311
313
|
configs = [
|
|
312
|
-
("
|
|
313
|
-
f"
|
|
314
|
+
("refined",
|
|
315
|
+
f"Refined PCHIP — positive δ\n"
|
|
314
316
|
f"(κ₉₅ = {kappa_95:.3f}, δ_edge = +{delta_edge}, δ₉₅ = +{delta_95:.3f})",
|
|
315
317
|
kappa_edge, delta_edge, kappa_95, delta_95),
|
|
316
318
|
("Academic",
|
|
317
319
|
f"Academic (κ = {kappa_edge} const, δ = 0)",
|
|
318
320
|
kappa_edge, 0.0, kappa_95, 0.0),
|
|
319
|
-
("
|
|
320
|
-
f"
|
|
321
|
+
("refined",
|
|
322
|
+
f"Refined PCHIP — negative δ\n"
|
|
321
323
|
f"(κ₉₅ = {kappa_95:.3f}, δ_edge = {delta_edge_neg}, δ₉₅ = {delta_95_neg:.3f})",
|
|
322
324
|
kappa_edge, delta_edge_neg, kappa_95, delta_95_neg),
|
|
323
325
|
]
|
|
@@ -348,7 +350,7 @@ def plot_miller_surfaces(
|
|
|
348
350
|
ax.grid(True, alpha=0.3)
|
|
349
351
|
|
|
350
352
|
plt.suptitle(
|
|
351
|
-
f"Miller flux surfaces — Academic |
|
|
353
|
+
f"Miller flux surfaces — Academic | Refined (+δ) | Refined (−δ)\n"
|
|
352
354
|
f"(R₀ = {R0} m, a = {a} m, κ = {kappa_edge}, |δ| = {delta_edge})",
|
|
353
355
|
fontsize=12, fontweight="bold"
|
|
354
356
|
)
|
|
@@ -461,7 +463,7 @@ def plot_first_wall_surface(
|
|
|
461
463
|
) -> None:
|
|
462
464
|
"""
|
|
463
465
|
Compare first-wall surface area from the Academic (Ramanujan ellipse)
|
|
464
|
-
and
|
|
466
|
+
and refined (Miller LCFS numerical) models.
|
|
465
467
|
|
|
466
468
|
Left panel : S vs κ (δ fixed).
|
|
467
469
|
Right panel : S vs δ (κ fixed, including negative triangularity).
|
|
@@ -483,11 +485,11 @@ def plot_first_wall_surface(
|
|
|
483
485
|
|
|
484
486
|
S_ac_k = [f_first_wall_surface(R0, a, k, delta_fix, "Academic")
|
|
485
487
|
for k in kappa_scan]
|
|
486
|
-
S_d0_k = [f_first_wall_surface(R0, a, k, delta_fix, "
|
|
488
|
+
S_d0_k = [f_first_wall_surface(R0, a, k, delta_fix, "refined")
|
|
487
489
|
for k in kappa_scan]
|
|
488
490
|
S_ac_d = [f_first_wall_surface(R0, a, kappa_fix, d, "Academic")
|
|
489
491
|
for d in delta_scan]
|
|
490
|
-
S_d0_d = [f_first_wall_surface(R0, a, kappa_fix, d, "
|
|
492
|
+
S_d0_d = [f_first_wall_surface(R0, a, kappa_fix, d, "refined")
|
|
491
493
|
for d in delta_scan]
|
|
492
494
|
|
|
493
495
|
fig, axes = plt.subplots(1, 2, figsize=(13, 5))
|
|
@@ -501,7 +503,7 @@ def plot_first_wall_surface(
|
|
|
501
503
|
f"First wall surface vs δ (κ = {kappa_fix})"),
|
|
502
504
|
]:
|
|
503
505
|
ax.plot(xarr, S_ac, "b-", lw=2, label="Academic (Ramanujan ellipse)")
|
|
504
|
-
ax.plot(xarr, S_d0, "r--", lw=2, label="
|
|
506
|
+
ax.plot(xarr, S_d0, "r--", lw=2, label="Refined (Miller LCFS)")
|
|
505
507
|
ax.set_xlabel(xlabel, fontsize=12)
|
|
506
508
|
ax.set_ylabel("S [m²]", fontsize=12)
|
|
507
509
|
ax.set_title(title, fontsize=11)
|
|
@@ -639,6 +641,96 @@ def plot_density_line_vol(
|
|
|
639
641
|
# 3. Nuclear / radiation physics
|
|
640
642
|
# =============================================================================
|
|
641
643
|
|
|
644
|
+
def plot_DT_reactivity(
|
|
645
|
+
T_min_keV: float = 1.0,
|
|
646
|
+
T_max_keV: float = 100.0,
|
|
647
|
+
n_points: int = 600,
|
|
648
|
+
T_op_min: float = 10.0,
|
|
649
|
+
T_op_max: float = 25.0,
|
|
650
|
+
save_dir: str | None = None,
|
|
651
|
+
) -> None:
|
|
652
|
+
"""
|
|
653
|
+
Plot the D-T Maxwellian reactivity ⟨σv⟩(T) used by D0FUS, together with
|
|
654
|
+
the pressure-limited fusion power density metric ⟨σv⟩/T².
|
|
655
|
+
|
|
656
|
+
Left panel:
|
|
657
|
+
⟨σv⟩(T) computed from the Bosch & Hale (1992) parameterisation,
|
|
658
|
+
with the power plant operating window [T_op_min, T_op_max] shaded
|
|
659
|
+
and the reactivity maximum marked.
|
|
660
|
+
|
|
661
|
+
Right panel:
|
|
662
|
+
Pressure-limited figure of merit ⟨σv⟩/T² (arbitrary units).
|
|
663
|
+
At fixed plasma pressure p = nT, the fuel ion density scales as
|
|
664
|
+
n ∝ 1/T, so the volumetric fusion power density p_fus ∝ n² ⟨σv⟩
|
|
665
|
+
is proportional to ⟨σv⟩/T². This identifies the optimal operating
|
|
666
|
+
temperature for a β-limited tokamak near 14 keV.
|
|
667
|
+
|
|
668
|
+
Parameters
|
|
669
|
+
----------
|
|
670
|
+
T_min_keV, T_max_keV : float Ion temperature scan range [keV].
|
|
671
|
+
n_points : int Number of temperature grid points.
|
|
672
|
+
T_op_min, T_op_max : float Power plant operating window bounds [keV].
|
|
673
|
+
save_dir : str or None
|
|
674
|
+
|
|
675
|
+
References
|
|
676
|
+
----------
|
|
677
|
+
Bosch & Hale, Nucl. Fusion 32, 611 (1992) — DT reactivity fit (Table IV).
|
|
678
|
+
Freidberg, Plasma Physics and Fusion Energy (2007) — pressure-limited optimum.
|
|
679
|
+
"""
|
|
680
|
+
# Temperature grid (linear, since the operating window lies in the rapid-rise zone)
|
|
681
|
+
T_arr = np.linspace(T_min_keV, T_max_keV, n_points)
|
|
682
|
+
sv_arr = f_sigmav(T_arr)
|
|
683
|
+
|
|
684
|
+
# Reactivity maximum
|
|
685
|
+
i_peak = int(np.argmax(sv_arr))
|
|
686
|
+
T_peak, sv_peak = T_arr[i_peak], sv_arr[i_peak]
|
|
687
|
+
|
|
688
|
+
# Pressure-limited metric ⟨σv⟩/T² (units arbitrary, normalised below)
|
|
689
|
+
metric = sv_arr / T_arr**2
|
|
690
|
+
i_opt = int(np.argmax(metric))
|
|
691
|
+
T_opt, m_opt = T_arr[i_opt], metric[i_opt]
|
|
692
|
+
|
|
693
|
+
# --- Figure ------------------------------------------------------------
|
|
694
|
+
fig, axes = plt.subplots(1, 2, figsize=(11, 4.5))
|
|
695
|
+
|
|
696
|
+
# Left panel: reactivity curve on log scale
|
|
697
|
+
ax = axes[0]
|
|
698
|
+
ax.semilogy(T_arr, sv_arr, color="tab:red", lw=2.0,
|
|
699
|
+
label="Bosch & Hale (1992)")
|
|
700
|
+
ax.axvspan(T_op_min, T_op_max, color="goldenrod", alpha=0.20,
|
|
701
|
+
label=f"Operating window\n{T_op_min:.0f}–{T_op_max:.0f} keV")
|
|
702
|
+
ax.axvline(T_peak, color="k", lw=1.0, ls="--",
|
|
703
|
+
label=f"peak: T = {T_peak:.0f} keV")
|
|
704
|
+
ax.set_xlabel(r"Ion temperature $T$ [keV]", fontsize=11)
|
|
705
|
+
ax.set_ylabel(r"$\langle\sigma v\rangle_{DT}$ [m$^3$ s$^{-1}$]", fontsize=11)
|
|
706
|
+
ax.set_title("D-T Maxwellian reactivity", fontsize=11)
|
|
707
|
+
ax.set_xlim(T_min_keV, T_max_keV)
|
|
708
|
+
ax.set_ylim(1e-25, 3e-21)
|
|
709
|
+
ax.grid(True, which="both", alpha=0.25)
|
|
710
|
+
ax.legend(fontsize=9, loc="lower right")
|
|
711
|
+
|
|
712
|
+
# Right panel: pressure-limited figure of merit
|
|
713
|
+
ax = axes[1]
|
|
714
|
+
ax.plot(T_arr, metric / m_opt, color="tab:blue", lw=2.0,
|
|
715
|
+
label=r"$\langle\sigma v\rangle / T^2$ (normalised)")
|
|
716
|
+
ax.axvspan(T_op_min, T_op_max, color="goldenrod", alpha=0.20,
|
|
717
|
+
label=f"Operating window\n{T_op_min:.0f}–{T_op_max:.0f} keV")
|
|
718
|
+
ax.axvline(T_opt, color="k", lw=1.0, ls="--",
|
|
719
|
+
label=f"optimum: T = {T_opt:.1f} keV")
|
|
720
|
+
ax.set_xlabel(r"Ion temperature $T$ [keV]", fontsize=11)
|
|
721
|
+
ax.set_ylabel(r"$\langle\sigma v\rangle / T^2$ [normalised]", fontsize=11)
|
|
722
|
+
ax.set_title(r"Pressure-limited fusion power figure of merit", fontsize=11)
|
|
723
|
+
ax.set_xlim(0, 50)
|
|
724
|
+
ax.set_ylim(0, 1.08)
|
|
725
|
+
ax.grid(True, alpha=0.25)
|
|
726
|
+
ax.legend(fontsize=9, loc="lower right")
|
|
727
|
+
|
|
728
|
+
plt.suptitle("Fusion reactivity and operating temperature window",
|
|
729
|
+
fontsize=12, fontweight="bold")
|
|
730
|
+
plt.tight_layout()
|
|
731
|
+
_save_or_show(fig, save_dir, "DT_reactivity")
|
|
732
|
+
|
|
733
|
+
|
|
642
734
|
def plot_Lz_cooling(
|
|
643
735
|
Te_min_keV: float = 0.05,
|
|
644
736
|
Te_max_keV: float = 100.0,
|
|
@@ -711,7 +803,8 @@ def plot_He_fraction(
|
|
|
711
803
|
nbar: float = 1.0,
|
|
712
804
|
Tbar: float = 8.9,
|
|
713
805
|
tauE: float = 3.7,
|
|
714
|
-
|
|
806
|
+
C_Alpha_ITER: float = 5.0,
|
|
807
|
+
C_Alpha_DEMO: float = 7.0,
|
|
715
808
|
nu_T: float = 1.0,
|
|
716
809
|
save_dir: str | None = None,
|
|
717
810
|
) -> None:
|
|
@@ -720,16 +813,25 @@ def plot_He_fraction(
|
|
|
720
813
|
C_α = τ_α / τ_E for ITER and EU-DEMO reference parameters, comparing
|
|
721
814
|
academic (no pedestal) and H-mode pedestal profile assumptions.
|
|
722
815
|
|
|
816
|
+
Two D0FUS default values for C_α are highlighted with vertical dotted
|
|
817
|
+
lines: C_α = 5 for ITER (consistent with Progress in the ITER Physics
|
|
818
|
+
Basis projections) and C_α = 7 for EU-DEMO 2017 (PROCESS reference run).
|
|
819
|
+
|
|
723
820
|
Parameters
|
|
724
821
|
----------
|
|
725
822
|
nbar, Tbar, tauE : float Reference plasma parameters [10²⁰ m⁻³, keV, s].
|
|
726
|
-
|
|
823
|
+
C_Alpha_ITER : float D0FUS default C_α for ITER (vertical line) [-].
|
|
824
|
+
C_Alpha_DEMO : float D0FUS default C_α for EU-DEMO 2017 (vertical line) [-].
|
|
727
825
|
nu_T : float Temperature peaking exponent [-].
|
|
728
826
|
save_dir : str or None
|
|
729
827
|
|
|
730
828
|
References
|
|
731
829
|
----------
|
|
732
830
|
ITER Physics Basis, Nucl. Fusion 39, §2.4 (1999).
|
|
831
|
+
Shimada et al., Progress in the ITER Physics Basis, Ch. 1,
|
|
832
|
+
Nucl. Fusion 47, S1 (2007).
|
|
833
|
+
Kovari et al., Fus. Eng. Des. 89, 3054 (2014) — PROCESS systems code.
|
|
834
|
+
Reiter, Wolf and Kever, Nucl. Fusion 30, 2141 (1990) — ignition bound on C_α.
|
|
733
835
|
"""
|
|
734
836
|
C_arr = np.linspace(2, 15, 150)
|
|
735
837
|
|
|
@@ -739,16 +841,22 @@ def plot_He_fraction(
|
|
|
739
841
|
for C in C_arr]
|
|
740
842
|
fa_DEMO = [f_He_fraction(1.2, 12.5, 4.6, C, nu_T) * 100 for C in C_arr]
|
|
741
843
|
|
|
742
|
-
fig, ax = plt.subplots(figsize=(
|
|
743
|
-
ax.plot(C_arr, fa_ITER, "b-", lw=
|
|
744
|
-
ax.plot(C_arr, fa_ITER_ped, "b--", lw=1.
|
|
745
|
-
ax.plot(C_arr, fa_DEMO, "r-", lw=
|
|
746
|
-
|
|
747
|
-
ax.
|
|
748
|
-
|
|
749
|
-
ax.
|
|
750
|
-
|
|
751
|
-
ax.
|
|
844
|
+
fig, ax = plt.subplots(figsize=(8.0, 5.2))
|
|
845
|
+
ax.plot(C_arr, fa_ITER, "b-", lw=2.0, label="ITER — academic (no pedestal)")
|
|
846
|
+
ax.plot(C_arr, fa_ITER_ped, "b--", lw=1.6, label="ITER — Refined H-mode pedestal")
|
|
847
|
+
ax.plot(C_arr, fa_DEMO, "r-", lw=2.0, label="EU-DEMO — academic")
|
|
848
|
+
# Two D0FUS default operating points: ITER and EU-DEMO 2017
|
|
849
|
+
ax.axvline(C_Alpha_ITER, color="tab:blue", lw=1.6, ls=":",
|
|
850
|
+
label=f"ITER default $C_\\alpha$ = {C_Alpha_ITER:.0f}")
|
|
851
|
+
ax.axvline(C_Alpha_DEMO, color="tab:red", lw=1.6, ls=":",
|
|
852
|
+
label=f"EU-DEMO 2017 default $C_\\alpha$ = {C_Alpha_DEMO:.0f}")
|
|
853
|
+
ax.axhspan(4, 6, color="grey", alpha=0.12, label="ITER target 4–6 %")
|
|
854
|
+
ax.set_xlabel(r"Removal efficiency $C_\alpha = \tau_\alpha / \tau_E$", fontsize=14)
|
|
855
|
+
ax.set_ylabel(r"Helium ash fraction $f_\alpha$ [%]", fontsize=14)
|
|
856
|
+
ax.set_title("He ash fraction — academic vs refined H-mode pedestal",
|
|
857
|
+
fontsize=13)
|
|
858
|
+
ax.legend(fontsize=11, loc="upper left")
|
|
859
|
+
ax.tick_params(axis="both", labelsize=12)
|
|
752
860
|
ax.set_xlim(2, 15)
|
|
753
861
|
ax.set_ylim(0, 25)
|
|
754
862
|
ax.grid(True, alpha=0.3)
|
|
@@ -761,12 +869,84 @@ def plot_He_fraction(
|
|
|
761
869
|
# 4. Superconductor / cable engineering
|
|
762
870
|
# =============================================================================
|
|
763
871
|
|
|
872
|
+
# -----------------------------------------------------------------------------
|
|
873
|
+
# NHMFL engineering current density reference data (T = 4.2 K)
|
|
874
|
+
# -----------------------------------------------------------------------------
|
|
875
|
+
# Source: National High Magnetic Field Laboratory (NHMFL / MagLab) "Engineering
|
|
876
|
+
# Current Density Plot", file Je_vs_B-041118a (updated 2021-01-04). Values are
|
|
877
|
+
# whole-strand / whole-tape engineering current density Je [A/mm²] vs applied
|
|
878
|
+
# field B [T]. Only the three series directly comparable to the D0FUS strand
|
|
879
|
+
# scalings (NbTi, Nb3Sn bronze, REBCO worst-orientation) are kept here.
|
|
880
|
+
#
|
|
881
|
+
# Per-series provenance:
|
|
882
|
+
# * REBCO B perp tape plane : SuperPower SP26, 50 µm substrate, 7.5%Zr,
|
|
883
|
+
# measured at NHMFL (Braccini, Jaroszynski, Xu) - DOI 10.1088/0953-2048/24/3/035001
|
|
884
|
+
# * Nb3Sn High Sn Bronze : Miyazaki et al., MT-18 (IEEE TASC 14:2, 2004)
|
|
885
|
+
# DOI 10.1109/TASC.2004.830344
|
|
886
|
+
# * NbTi LHC 4.2 K : Boutboul et al., MT-19 (IEEE TASC 16:2, 2006)
|
|
887
|
+
# DOI 10.1109/TASC.2006.870777
|
|
888
|
+
_NHMFL_JE_REFERENCE = {
|
|
889
|
+
"NbTi": {
|
|
890
|
+
"label": "NbTi LHC strand",
|
|
891
|
+
"B": np.array([0.61, 0.95, 1.34, 1.68, 2.23, 3.18, 4.15, 5.12, 6.10]),
|
|
892
|
+
"Je": np.array([5106.88, 4054.15, 3180.60, 2710.23, 2217.46,
|
|
893
|
+
1724.69, 1411.11, 1187.12, 918.34]),
|
|
894
|
+
},
|
|
895
|
+
"Nb3Sn": {
|
|
896
|
+
"label": "Nb₃Sn high-Sn bronze",
|
|
897
|
+
"B": np.array([18.00, 19.01, 20.01, 21.01, 22.02, 23.00, 24.00, 25.00]),
|
|
898
|
+
"Je": np.array([166.64, 137.81, 111.10, 84.38, 60.82, 40.08, 19.34, 8.09]),
|
|
899
|
+
},
|
|
900
|
+
"REBCO": {
|
|
901
|
+
"label": "REBCO tape, B⊥ (worst case)",
|
|
902
|
+
"B": np.array([1.0, 1.5, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0,
|
|
903
|
+
10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 18.0,
|
|
904
|
+
20.0, 22.0, 24.0, 25.0, 26.0, 28.0, 30.0, 31.0]),
|
|
905
|
+
"Je": np.array([3665.00, 2920.00, 2380.00, 1796.15, 1447.69, 1188.46,
|
|
906
|
+
1034.37, 956.92, 851.83, 780.84, 720.00, 679.44,
|
|
907
|
+
626.09, 593.24, 563.48, 535.38, 516.52, 469.56,
|
|
908
|
+
433.08, 406.96, 391.30, 367.69, 360.00, 344.35,
|
|
909
|
+
328.70, 322.31]),
|
|
910
|
+
},
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
|
|
914
|
+
def _overlay_nhmfl_reference(ax, color_map: dict, alpha: float = 0.30) -> None:
|
|
915
|
+
"""
|
|
916
|
+
Overlay the NHMFL engineering current density reference (4.2 K) on ``ax``
|
|
917
|
+
as transparent markers connected by a thin line, color-matched per material.
|
|
918
|
+
|
|
919
|
+
Parameters
|
|
920
|
+
----------
|
|
921
|
+
ax : matplotlib Axes Target axes (assumed log-y in A/mm²).
|
|
922
|
+
color_map : dict Mapping {material_key: hex_color} for NbTi,
|
|
923
|
+
Nb3Sn, REBCO; the same colours used for the
|
|
924
|
+
D0FUS curves so the visual pairing is direct.
|
|
925
|
+
alpha : float Transparency for both markers and connecting
|
|
926
|
+
line (default 0.30, "background" appearance).
|
|
927
|
+
"""
|
|
928
|
+
for key, ref in _NHMFL_JE_REFERENCE.items():
|
|
929
|
+
col = color_map.get(key, "#808080")
|
|
930
|
+
ax.plot(
|
|
931
|
+
ref["B"], ref["Je"],
|
|
932
|
+
linestyle="-", linewidth=1.0,
|
|
933
|
+
marker="o", markersize=4.5,
|
|
934
|
+
color=col, alpha=alpha,
|
|
935
|
+
zorder=1,
|
|
936
|
+
)
|
|
937
|
+
# Single neutral legend handle for the whole NHMFL reference set.
|
|
938
|
+
ax.plot([], [], linestyle="-", linewidth=1.0, marker="o", markersize=4.5,
|
|
939
|
+
color="#555555", alpha=alpha,
|
|
940
|
+
label="NHMFL strand/tape data @ 4.2 K (2011 vintage, ref.)")
|
|
941
|
+
|
|
942
|
+
|
|
764
943
|
def plot_Jc_scaling(
|
|
765
944
|
B_min: float = 0.5,
|
|
766
945
|
B_max: float = 45.0,
|
|
767
946
|
T_op: float = 4.2,
|
|
768
947
|
f_non_Cu_LTS: float = 0.50,
|
|
769
948
|
f_non_Cu_HTS: float = 0.60,
|
|
949
|
+
show_nhmfl_ref: bool = True,
|
|
770
950
|
save_dir: str | None = None,
|
|
771
951
|
) -> None:
|
|
772
952
|
"""
|
|
@@ -776,29 +956,47 @@ def plot_Jc_scaling(
|
|
|
776
956
|
* Nb₃Sn (LTS, ITER/EU-DEMO TF/CS)
|
|
777
957
|
* REBCO (HTS, ARC / SPARC)
|
|
778
958
|
|
|
959
|
+
When ``show_nhmfl_ref`` is True, the NHMFL/MagLab experimental engineering
|
|
960
|
+
current density data at 4.2 K is overlaid as a transparent background
|
|
961
|
+
reference (color-matched markers + thin line per material).
|
|
962
|
+
|
|
779
963
|
Parameters
|
|
780
964
|
----------
|
|
781
|
-
B_min, B_max
|
|
782
|
-
T_op
|
|
783
|
-
f_non_Cu_LTS
|
|
784
|
-
f_non_Cu_HTS
|
|
785
|
-
|
|
965
|
+
B_min, B_max : float Field scan range [T].
|
|
966
|
+
T_op : float Operating temperature [K].
|
|
967
|
+
f_non_Cu_LTS : float Non-copper fraction for LTS strands [-].
|
|
968
|
+
f_non_Cu_HTS : float Non-copper fraction for HTS tapes [-].
|
|
969
|
+
show_nhmfl_ref : bool Overlay NHMFL 4.2 K reference data (default True).
|
|
970
|
+
save_dir : str or None
|
|
786
971
|
|
|
787
972
|
References
|
|
788
973
|
----------
|
|
789
974
|
ITER TF strand specifications; Nijhuis (2008); Fleiter & Ballarino (2014).
|
|
975
|
+
NHMFL/MagLab Engineering Current Density Plot, updated 2021-01-04.
|
|
790
976
|
"""
|
|
791
977
|
B_vals = np.linspace(B_min, B_max, 300)
|
|
792
978
|
J_NbTi = J_non_Cu_NbTi(B_vals, T_op) * f_non_Cu_LTS / 1e6 # [A/mm²]
|
|
793
979
|
J_Nb3Sn = J_non_Cu_Nb3Sn(B_vals, T_op, Eps=-0.003) * f_non_Cu_LTS / 1e6
|
|
794
980
|
J_REBCO = J_non_Cu_REBCO(B_vals, T_op, Tet=0) * f_non_Cu_HTS / 1e6
|
|
795
981
|
|
|
982
|
+
# Colour palette shared between D0FUS curves and NHMFL reference overlay.
|
|
983
|
+
col_NbTi, col_Nb3Sn, col_REBCO = "#A06AB4", "#E06C75", "#D4B000"
|
|
984
|
+
|
|
796
985
|
fig, ax = plt.subplots(figsize=(7, 5))
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
986
|
+
|
|
987
|
+
# NHMFL background reference (drawn first → lower z-order).
|
|
988
|
+
if show_nhmfl_ref:
|
|
989
|
+
_overlay_nhmfl_reference(
|
|
990
|
+
ax,
|
|
991
|
+
color_map={"NbTi": col_NbTi, "Nb3Sn": col_Nb3Sn, "REBCO": col_REBCO},
|
|
992
|
+
alpha=0.30,
|
|
993
|
+
)
|
|
994
|
+
|
|
995
|
+
ax.plot(B_vals, J_NbTi, lw=2, color=col_NbTi, label="NbTi strand", zorder=3)
|
|
996
|
+
ax.plot(B_vals, J_Nb3Sn, lw=2, color=col_Nb3Sn, label="Nb₃Sn strand", zorder=3)
|
|
997
|
+
ax.plot(B_vals, J_REBCO, lw=2, color=col_REBCO, label="REBCO tape", zorder=3)
|
|
800
998
|
ax.set_xlabel("Magnetic field B [T]", fontsize=12)
|
|
801
|
-
ax.set_ylabel("
|
|
999
|
+
ax.set_ylabel("Strand/Tape current density [A/mm²]", fontsize=12)
|
|
802
1000
|
ax.set_title(f"Superconductor Jc scalings @ {T_op} K", fontsize=12)
|
|
803
1001
|
ax.legend(loc="upper right", fontsize=10)
|
|
804
1002
|
ax.grid(True, alpha=0.3)
|
|
@@ -937,7 +1135,7 @@ def plot_TF_thickness_vs_field(
|
|
|
937
1135
|
) -> None:
|
|
938
1136
|
"""
|
|
939
1137
|
Plot TF coil total inboard thickness vs peak magnetic field for four
|
|
940
|
-
mechanical models (Academic/
|
|
1138
|
+
mechanical models (Academic/refined × Wedging/Bucking).
|
|
941
1139
|
|
|
942
1140
|
An optional MADE benchmark scatter dataset is superimposed on the Wedging
|
|
943
1141
|
panel for EU-DEMO validation.
|
|
@@ -949,7 +1147,7 @@ def plot_TF_thickness_vs_field(
|
|
|
949
1147
|
sigma_TF : float Allowable TF coil stress [Pa].
|
|
950
1148
|
Default 867 MPa (SDC-IC Pm+Pb, austenitic steel at 4 K).
|
|
951
1149
|
J_max_TF : float Current density on the non-steel area [A/m²].
|
|
952
|
-
Default 60 MA/m². The
|
|
1150
|
+
Default 60 MA/m². The refined cable model (Maddock
|
|
953
1151
|
adiabatic hotspot) predicts ~40 MA/m² for HTS at
|
|
954
1152
|
20 T,
|
|
955
1153
|
From Giannini 2023 Fig. 20 (validated FEM
|
|
@@ -983,11 +1181,11 @@ def plot_TF_thickness_vs_field(
|
|
|
983
1181
|
"Bucking", cfg.coef_inboard_tension,
|
|
984
1182
|
cfg.F_CClamp)[0])
|
|
985
1183
|
# c_BP = 0: MADE total radial build = WP + case vault (no backplate)
|
|
986
|
-
d0_w.append(
|
|
1184
|
+
d0_w.append(f_TF_refined(a, b, R0, sigma_TF, J_max_TF, B,
|
|
987
1185
|
"Wedging", 0.5, 1,
|
|
988
1186
|
0.0, cfg.coef_inboard_tension,
|
|
989
1187
|
cfg.F_CClamp)[0])
|
|
990
|
-
d0_b.append(
|
|
1188
|
+
d0_b.append(f_TF_refined(a, b, R0, sigma_TF, J_max_TF, B,
|
|
991
1189
|
"Bucking", 1.0, 1,
|
|
992
1190
|
0.0, cfg.coef_inboard_tension,
|
|
993
1191
|
cfg.F_CClamp)[0])
|
|
@@ -1007,7 +1205,7 @@ def plot_TF_thickness_vs_field(
|
|
|
1007
1205
|
# Wedging panel
|
|
1008
1206
|
ax = axes[0]
|
|
1009
1207
|
ax.plot(B_vals, acad_w, color=colors[0], lw=2, label="Academic — Wedging")
|
|
1010
|
-
ax.plot(B_vals, d0_w, color=colors[1], lw=2, label="
|
|
1208
|
+
ax.plot(B_vals, d0_w, color=colors[1], lw=2, label="Refined — Wedging")
|
|
1011
1209
|
ax.scatter(x_made, y_made, color="k", marker="x", s=80, label="MADE")
|
|
1012
1210
|
ax.set_xlabel("Peak field B_max [T]", fontsize=12)
|
|
1013
1211
|
ax.set_ylabel("TF total inboard thickness [m]", fontsize=12)
|
|
@@ -1018,7 +1216,7 @@ def plot_TF_thickness_vs_field(
|
|
|
1018
1216
|
# Bucking panel
|
|
1019
1217
|
ax = axes[1]
|
|
1020
1218
|
ax.plot(B_vals, acad_b, color=colors[0], lw=2, label="Academic — Bucking")
|
|
1021
|
-
ax.plot(B_vals, d0_b, color=colors[1], lw=2, label="
|
|
1219
|
+
ax.plot(B_vals, d0_b, color=colors[1], lw=2, label="Refined — Bucking")
|
|
1022
1220
|
ax.set_xlabel("Peak field B_max [T]", fontsize=12)
|
|
1023
1221
|
ax.set_ylabel("TF total inboard thickness [m]", fontsize=12)
|
|
1024
1222
|
ax.set_title("Bucking configuration", fontsize=12)
|
|
@@ -1067,9 +1265,9 @@ def plot_TF_grading_thickness_vs_field(
|
|
|
1067
1265
|
c_g = np.full_like(B_scan, np.nan)
|
|
1068
1266
|
|
|
1069
1267
|
for i, Bm in enumerate(B_scan):
|
|
1070
|
-
ru =
|
|
1268
|
+
ru = Winding_Pack_refined(R0, a, b, sigma_TF, J_max_TF, Bm,
|
|
1071
1269
|
0.5, n, grading=False)
|
|
1072
|
-
rg =
|
|
1270
|
+
rg = Winding_Pack_refined(R0, a, b, sigma_TF, J_max_TF, Bm,
|
|
1073
1271
|
0.5, n, grading=True)
|
|
1074
1272
|
if np.isfinite(ru[0]):
|
|
1075
1273
|
c_u[i] = ru[0] * 100
|
|
@@ -1116,9 +1314,9 @@ def plot_TF_grading_reduction(
|
|
|
1116
1314
|
c_g = np.full_like(B_scan, np.nan)
|
|
1117
1315
|
|
|
1118
1316
|
for i, Bm in enumerate(B_scan):
|
|
1119
|
-
ru =
|
|
1317
|
+
ru = Winding_Pack_refined(R0, a, b, sigma_TF, J_max_TF, Bm,
|
|
1120
1318
|
0.5, n, grading=False)
|
|
1121
|
-
rg =
|
|
1319
|
+
rg = Winding_Pack_refined(R0, a, b, sigma_TF, J_max_TF, Bm,
|
|
1122
1320
|
0.5, n, grading=True)
|
|
1123
1321
|
if np.isfinite(ru[0]):
|
|
1124
1322
|
c_u[i] = ru[0] * 100
|
|
@@ -1164,9 +1362,9 @@ def plot_TF_grading_alpha_profile(
|
|
|
1164
1362
|
cfg = DEFAULT_CONFIG
|
|
1165
1363
|
|
|
1166
1364
|
# Run graded (populates _last_graded_profile) then ungraded
|
|
1167
|
-
rg =
|
|
1365
|
+
rg = Winding_Pack_refined(R0, a, b, sigma_TF, J_max_TF, B_max,
|
|
1168
1366
|
0.5, n, grading=True)
|
|
1169
|
-
ru =
|
|
1367
|
+
ru = Winding_Pack_refined(R0, a, b, sigma_TF, J_max_TF, B_max,
|
|
1170
1368
|
0.5, n, grading=False)
|
|
1171
1369
|
|
|
1172
1370
|
if not np.isfinite(rg[0]) or not np.isfinite(ru[0]):
|
|
@@ -1221,7 +1419,7 @@ def plot_CS_thickness_vs_flux(
|
|
|
1221
1419
|
) -> None:
|
|
1222
1420
|
"""
|
|
1223
1421
|
Plot CS coil winding-pack thickness and peak field vs volt-second budget
|
|
1224
|
-
for three mechanical models (Academic,
|
|
1422
|
+
for three mechanical models (Academic, refined, CIRCE) and three
|
|
1225
1423
|
configuration types (Wedging, Bucking, Plug).
|
|
1226
1424
|
|
|
1227
1425
|
For the Wedging configuration, a MADE benchmark scatter dataset
|
|
@@ -1234,7 +1432,7 @@ def plot_CS_thickness_vs_flux(
|
|
|
1234
1432
|
Geometry: a_cs=3 m, b_cs=1.2 m, c_cs=2 m, R0=9 m, Gap=0.1 m
|
|
1235
1433
|
→ R_CS_ext = 9 - 3 - 1.2 - 2 - 0.1 = 2.7 m
|
|
1236
1434
|
|
|
1237
|
-
Stress: σ_CS = 600 MPa.
|
|
1435
|
+
Stress: σ_CS = 600 MPa. The refined model applies fatigue_CS = 2 internally
|
|
1238
1436
|
→ σ_eff = 300 MPa as the fig.2 source
|
|
1239
1437
|
|
|
1240
1438
|
J_wost = 85 MA/m², derived from Sarasola 2020 Sec. II-B:
|
|
@@ -1281,7 +1479,7 @@ def plot_CS_thickness_vs_flux(
|
|
|
1281
1479
|
# subtracted). The MADE reference data published by Sarasola et al.
|
|
1282
1480
|
# corresponds to the CS hardware design output at full bipolar swing
|
|
1283
1481
|
# (the curve is parameterised on the total volt-second capacity of
|
|
1284
|
-
# the coil, not on the plasma inductive demand). To compare
|
|
1482
|
+
# the coil, not on the plasma inductive demand). To compare the refined model to
|
|
1285
1483
|
# MADE on the same footing, we feed the full-bipolar abscissa to the
|
|
1286
1484
|
# solver and disable the additional 1/f_swing_usable rescaling. The
|
|
1287
1485
|
# design-mode default (0.75 = 25% control reserve) is restored
|
|
@@ -1299,8 +1497,8 @@ def plot_CS_thickness_vs_flux(
|
|
|
1299
1497
|
|
|
1300
1498
|
psi_values = np.linspace(0, psi_max, n_psi)
|
|
1301
1499
|
mech_confs = ["Wedging", "Bucking", "Plug"]
|
|
1302
|
-
model_funcs = {"Academic": f_CS_ACAD, "
|
|
1303
|
-
colors = {"Academic": "blue", "
|
|
1500
|
+
model_funcs = {"Academic": f_CS_ACAD, "refined": f_CS_refined, "CIRCE": f_CS_CIRCE}
|
|
1501
|
+
colors = {"Academic": "blue", "refined": "green", "CIRCE": "red"}
|
|
1304
1502
|
|
|
1305
1503
|
# Storage: results[model][config] = {"thickness": [], "B": []}
|
|
1306
1504
|
results = {m: {c: {"thickness": [], "B": []} for c in mech_confs}
|
|
@@ -1653,10 +1851,11 @@ def plot_shaping_run(
|
|
|
1653
1851
|
Plot radial elongation κ(ρ) and triangularity δ(ρ) profiles for the
|
|
1654
1852
|
geometry used in a D0FUS run.
|
|
1655
1853
|
|
|
1656
|
-
In '
|
|
1657
|
-
|
|
1658
|
-
In 'Academic' mode,
|
|
1659
|
-
consistent with the
|
|
1854
|
+
In 'refined' geometry mode, three-point PCHIP profiles are used for
|
|
1855
|
+
κ(ρ) and δ(ρ) (C¹ continuous globally, monotonicity-preserving,
|
|
1856
|
+
with radially varying core penetration). In 'Academic' mode,
|
|
1857
|
+
κ(ρ) = κ_edge = const and δ(ρ) = 0 everywhere, consistent with the
|
|
1858
|
+
cylindrical-torus approximation.
|
|
1660
1859
|
|
|
1661
1860
|
Parameters
|
|
1662
1861
|
----------
|
|
@@ -1666,11 +1865,11 @@ def plot_shaping_run(
|
|
|
1666
1865
|
|
|
1667
1866
|
References
|
|
1668
1867
|
----------
|
|
1669
|
-
|
|
1868
|
+
Fritsch & Carlson, SIAM J. Numer. Anal. 17, 238 (1980) — PCHIP scheme.
|
|
1670
1869
|
Ball & Parra, PPCF 57, 045006 (2015) — κ core penetration.
|
|
1671
1870
|
"""
|
|
1672
1871
|
R0, a, kappa_edge, delta_edge, kappa_95, delta_95, _ = _resolve_geometry(run)
|
|
1673
|
-
geom_mode = run.get("Plasma_geometry", "
|
|
1872
|
+
geom_mode = run.get("Plasma_geometry", "refined")
|
|
1674
1873
|
rho = np.linspace(1e-4, 1.0, n_rho)
|
|
1675
1874
|
|
|
1676
1875
|
if geom_mode == "Academic":
|
|
@@ -1678,7 +1877,7 @@ def plot_shaping_run(
|
|
|
1678
1877
|
kap_arr = np.full_like(rho, kappa_edge)
|
|
1679
1878
|
del_arr = np.zeros_like(rho)
|
|
1680
1879
|
else:
|
|
1681
|
-
#
|
|
1880
|
+
# Refined Miller PCHIP profiles with radial variation
|
|
1682
1881
|
kap_arr = kappa_profile(rho, kappa_edge, kappa_95)
|
|
1683
1882
|
del_arr = delta_profile(rho, delta_edge, delta_95)
|
|
1684
1883
|
|
|
@@ -1690,7 +1889,7 @@ def plot_shaping_run(
|
|
|
1690
1889
|
ax.axhline(kappa_edge, color="tab:blue", lw=2.2,
|
|
1691
1890
|
label=f"Academic: κ = κ_edge = {kappa_edge:.3f} (const)")
|
|
1692
1891
|
else:
|
|
1693
|
-
ax.plot(rho, kap_arr, "tab:blue", lw=2.2, label="
|
|
1892
|
+
ax.plot(rho, kap_arr, "tab:blue", lw=2.2, label="Refined PCHIP")
|
|
1694
1893
|
ax.axhline(kappa_edge, color="tab:orange", lw=1.4, ls="--",
|
|
1695
1894
|
label=f"κ_edge = {kappa_edge:.3f}")
|
|
1696
1895
|
ax.axhline(kappa_95, color="tab:gray", lw=1.0, ls=":",
|
|
@@ -1709,7 +1908,7 @@ def plot_shaping_run(
|
|
|
1709
1908
|
ax.axhline(0, color="tab:blue", lw=2.2,
|
|
1710
1909
|
label="Academic: δ = 0 (const)")
|
|
1711
1910
|
else:
|
|
1712
|
-
ax.plot(rho, del_arr, "tab:red", lw=2.2, label="
|
|
1911
|
+
ax.plot(rho, del_arr, "tab:red", lw=2.2, label="Refined PCHIP")
|
|
1713
1912
|
ax.axhline(delta_edge, color="tab:orange", lw=1.4, ls="--",
|
|
1714
1913
|
label=f"δ_edge = {delta_edge:.3f}")
|
|
1715
1914
|
ax.axhline(delta_95, color="tab:gray", lw=1.0, ls=":",
|
|
@@ -1723,7 +1922,7 @@ def plot_shaping_run(
|
|
|
1723
1922
|
ax.grid(True, alpha=0.3)
|
|
1724
1923
|
ax.set_xlim(0, 1)
|
|
1725
1924
|
|
|
1726
|
-
_mode_str = "Academic" if geom_mode == "Academic" else "
|
|
1925
|
+
_mode_str = "Academic" if geom_mode == "Academic" else "Refined PCHIP"
|
|
1727
1926
|
plt.suptitle(
|
|
1728
1927
|
f"Run shaping profiles ({_mode_str}) — R₀={R0} m, a={a} m\n"
|
|
1729
1928
|
f"κ_edge={kappa_edge:.3f}, δ_edge={delta_edge:.3f}",
|
|
@@ -1746,8 +1945,9 @@ def plot_flux_surfaces_run(
|
|
|
1746
1945
|
"""
|
|
1747
1946
|
Plot nested flux surfaces for the actual geometry of a D0FUS run.
|
|
1748
1947
|
|
|
1749
|
-
When Plasma_geometry == '
|
|
1750
|
-
parameterisation with radially varying κ(ρ) and δ(ρ)
|
|
1948
|
+
When Plasma_geometry == 'refined', surfaces follow the Miller
|
|
1949
|
+
parameterisation with radially varying κ(ρ) and δ(ρ) built from
|
|
1950
|
+
three-point PCHIP profiles (C¹ continuous, monotonicity-preserving).
|
|
1751
1951
|
When Plasma_geometry == 'Academic' (or unset), surfaces are concentric
|
|
1752
1952
|
ellipses with constant κ = κ_edge and δ = 0, consistent with the
|
|
1753
1953
|
cylindrical-torus approximation used in the Academic solver branch.
|
|
@@ -1767,7 +1967,7 @@ def plot_flux_surfaces_run(
|
|
|
1767
1967
|
Miller et al., Phys. Plasmas 5, 973 (1998).
|
|
1768
1968
|
"""
|
|
1769
1969
|
R0, a, kappa_edge, delta_edge, kappa_95, delta_95, _ = _resolve_geometry(run)
|
|
1770
|
-
geom_mode = run.get("Plasma_geometry", "
|
|
1970
|
+
geom_mode = run.get("Plasma_geometry", "refined")
|
|
1771
1971
|
|
|
1772
1972
|
theta = np.linspace(0, 2 * np.pi, n_theta)
|
|
1773
1973
|
rho_levels = np.linspace(0.1, 1.0, n_levels)
|
|
@@ -1781,7 +1981,7 @@ def plot_flux_surfaces_run(
|
|
|
1781
1981
|
R_surf = R0 + rho_val * a * np.cos(theta)
|
|
1782
1982
|
Z_surf = kappa_edge * rho_val * a * np.sin(theta)
|
|
1783
1983
|
else:
|
|
1784
|
-
#
|
|
1984
|
+
# Refined Miller PCHIP surfaces with radially varying κ(ρ), δ(ρ)
|
|
1785
1985
|
R_surf, Z_surf = miller_RZ(rho_val, theta,
|
|
1786
1986
|
R0, a, kappa_edge, delta_edge,
|
|
1787
1987
|
kappa_95, delta_95)
|
|
@@ -1802,7 +2002,7 @@ def plot_flux_surfaces_run(
|
|
|
1802
2002
|
if geom_mode == "Academic":
|
|
1803
2003
|
_geom_label = "Academic (elliptic, δ = 0)"
|
|
1804
2004
|
else:
|
|
1805
|
-
_geom_label = f"
|
|
2005
|
+
_geom_label = f"Refined PCHIP (δ_edge = {delta_edge:.3f})"
|
|
1806
2006
|
|
|
1807
2007
|
ax.set_title(
|
|
1808
2008
|
f"Flux surfaces — {_geom_label}\n"
|
|
@@ -1827,16 +2027,16 @@ def plot_q_profile(
|
|
|
1827
2027
|
"""
|
|
1828
2028
|
Plot the safety factor profile q(rho).
|
|
1829
2029
|
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
2030
|
+
Picks data from run['_q_sc'], which is populated by the q,j dispatcher
|
|
2031
|
+
(f_q_profile_academic or f_q_profile_refined according to
|
|
2032
|
+
config.q_profile_mode). When the dict is unavailable, falls back to
|
|
2033
|
+
the analytical parametric q(rho) from f_q_profile() with alpha_J = 1.5.
|
|
1833
2034
|
|
|
1834
|
-
The current density decomposition
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
(alpha_J, q_0, l_i, f_bs) are reported in the figure title.
|
|
2035
|
+
The current density decomposition j_Ohm + j_CD + j_bs is meaningful
|
|
2036
|
+
only in 'refined' mode (in 'academic' mode those components are zero
|
|
2037
|
+
by construction). The figure stays focused on q(rho) and reports the
|
|
2038
|
+
integral diagnostic l_i(3) along with q_0 and either q(0.95) or the
|
|
2039
|
+
imposed q95.
|
|
1840
2040
|
|
|
1841
2041
|
Parameters
|
|
1842
2042
|
----------
|
|
@@ -1852,18 +2052,20 @@ def plot_q_profile(
|
|
|
1852
2052
|
"""
|
|
1853
2053
|
q95 = float(run["q95"])
|
|
1854
2054
|
_q_sc = run.get("_q_sc", None)
|
|
2055
|
+
q_mode = str(run.get("q_profile_mode", "refined")) # default for legacy runs
|
|
1855
2056
|
|
|
1856
|
-
# ── Data source:
|
|
2057
|
+
# ── Data source: dispatcher solution or analytical fallback ──────────
|
|
1857
2058
|
if _q_sc is not None and 'q_arr' in _q_sc:
|
|
1858
2059
|
rho = _q_sc['rho']
|
|
1859
2060
|
q_arr = _q_sc['q_arr']
|
|
1860
2061
|
li = _q_sc['li']
|
|
1861
|
-
|
|
1862
|
-
suptitle_str =
|
|
2062
|
+
q_at_95 = float(_q_sc.get('q_at_95', np.interp(0.95, rho, q_arr)))
|
|
2063
|
+
suptitle_str = (rf"Safety factor profile $q(\rho)$ "
|
|
2064
|
+
rf"({q_mode} mode)")
|
|
1863
2065
|
else:
|
|
1864
|
-
# Analytical fallback (no
|
|
2066
|
+
# Analytical fallback (no run output available — used by tests).
|
|
1865
2067
|
rho = np.linspace(0.0, 0.99, n_rho)
|
|
1866
|
-
alpha_J = 1.5
|
|
2068
|
+
alpha_J = float(run.get("alpha_J", 1.5))
|
|
1867
2069
|
q_arr = f_q_profile(rho, q95=q95, rho95=0.95, alpha_J=alpha_J)
|
|
1868
2070
|
|
|
1869
2071
|
# Cylindrical li estimate from the analytical form
|
|
@@ -1874,7 +2076,9 @@ def plot_q_profile(
|
|
|
1874
2076
|
li_integ = np.where(rho > 1e-8, I_norm**2 / rho_s, 0.0)
|
|
1875
2077
|
li = 2.0 * np.trapezoid(li_integ, rho)
|
|
1876
2078
|
|
|
1877
|
-
|
|
2079
|
+
q_at_95 = float(np.interp(0.95, rho, q_arr))
|
|
2080
|
+
suptitle_str = (rf"Safety factor profile $q(\rho)$ "
|
|
2081
|
+
rf"(analytical fallback, $\alpha_J = {alpha_J:.2f}$)")
|
|
1878
2082
|
|
|
1879
2083
|
# ── Figure ────────────────────────────────────────────────────────
|
|
1880
2084
|
fig, ax = plt.subplots(1, 1, figsize=(7, 5))
|
|
@@ -1883,12 +2087,23 @@ def plot_q_profile(
|
|
|
1883
2087
|
ax.axhline(q95, color="gray", ls="--", lw=0.8, alpha=0.5)
|
|
1884
2088
|
ax.axvline(0.95, color="gray", ls=":", lw=0.8, alpha=0.4)
|
|
1885
2089
|
ax.plot(rho[0], q_arr[0], "o", color="tab:blue", ms=7, zorder=5)
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
2090
|
+
# In academic mode, q(rho_95) = q95 by construction; in refined mode,
|
|
2091
|
+
# q(rho_95) generally differs and is a meaningful diagnostic.
|
|
2092
|
+
if q_mode == "refined":
|
|
2093
|
+
ax.plot(0.95, q_at_95, "o", color="tab:blue", ms=7, zorder=5)
|
|
2094
|
+
ax.plot(0.95, q95, "s", color="tab:red", ms=7, zorder=5,
|
|
2095
|
+
label=rf"$q_{{95}}^{{\rm scaling}} = {q95:.2f}$")
|
|
2096
|
+
info_text = (rf"$q_0 = {q_arr[0]:.2f}$" + "\n"
|
|
2097
|
+
rf"$q(\rho_{{95}}) = {q_at_95:.2f}$" + "\n"
|
|
2098
|
+
rf"$l_i(3) = {li:.2f}$" + "\n"
|
|
2099
|
+
rf"$q(1) = {q_arr[-1]:.2f}$")
|
|
2100
|
+
else: # academic mode: q95 is imposed
|
|
2101
|
+
ax.plot(0.95, q95, "s", color="tab:red", ms=7, zorder=5,
|
|
2102
|
+
label=rf"$q_{{95}} = {q95:.2f}$ (imposed)")
|
|
2103
|
+
info_text = (rf"$q_0 = {q_arr[0]:.2f}$" + "\n"
|
|
2104
|
+
rf"$l_i(3) = {li:.2f}$" + "\n"
|
|
2105
|
+
rf"$q(1) = {q_arr[-1]:.2f}$")
|
|
2106
|
+
ax.text(0.04, 0.95, info_text,
|
|
1892
2107
|
transform=ax.transAxes, fontsize=10, va="top",
|
|
1893
2108
|
bbox=dict(boxstyle="round,pad=0.3", fc="lightyellow", alpha=0.85))
|
|
1894
2109
|
ax.set_xlabel(r"$\rho$", fontsize=12)
|
|
@@ -2646,10 +2861,10 @@ def plot_all(
|
|
|
2646
2861
|
|
|
2647
2862
|
── Plasma shaping [ 1– 7]
|
|
2648
2863
|
── Kinetic profiles [ 8–11]
|
|
2649
|
-
── Transport & current [12–
|
|
2650
|
-
── Radiation & impurities [
|
|
2651
|
-
── Superconductor eng. [
|
|
2652
|
-
── Coil sizing & mechanics [
|
|
2864
|
+
── Transport & current [12–13]
|
|
2865
|
+
── Radiation & impurities [14–16]
|
|
2866
|
+
── Superconductor eng. [17–19]
|
|
2867
|
+
── Coil sizing & mechanics [20–29]
|
|
2653
2868
|
· TF grading [21–23]
|
|
2654
2869
|
· CS / CIRCE / geometry [24–27]
|
|
2655
2870
|
· Benchmarks [28–29]
|
|
@@ -2715,6 +2930,9 @@ def plot_all(
|
|
|
2715
2930
|
_p(14, "Coronal cooling coefficient L_z(T)")
|
|
2716
2931
|
plot_Lz_cooling(save_dir=save_dir)
|
|
2717
2932
|
|
|
2933
|
+
_p(15, "D-T reactivity ⟨σv⟩(T)")
|
|
2934
|
+
plot_DT_reactivity(save_dir=save_dir)
|
|
2935
|
+
|
|
2718
2936
|
_p(16, "Helium ash fraction")
|
|
2719
2937
|
plot_He_fraction(save_dir=save_dir)
|
|
2720
2938
|
|
|
@@ -3121,8 +3339,8 @@ def plot_TF_benchmark_table(cfg=None, save_dir=None) -> None:
|
|
|
3121
3339
|
return f_TF_academic(a, b, R0, sigma, J_wost, B_max,
|
|
3122
3340
|
conf, cfg.coef_inboard_tension, cfg.F_CClamp)
|
|
3123
3341
|
else:
|
|
3124
|
-
#
|
|
3125
|
-
return
|
|
3342
|
+
# f_TF_refined returns the same tuple layout
|
|
3343
|
+
return f_TF_refined(a, b, R0, sigma, J_wost, B_max,
|
|
3126
3344
|
conf, omega, n_frac,
|
|
3127
3345
|
cfg.c_BP, cfg.coef_inboard_tension, cfg.F_CClamp)
|
|
3128
3346
|
|
|
@@ -3133,7 +3351,7 @@ def plot_TF_benchmark_table(cfg=None, save_dir=None) -> None:
|
|
|
3133
3351
|
# consistent with the CS benchmark.
|
|
3134
3352
|
model_specs = [
|
|
3135
3353
|
("Academic", "#9C27B0"),
|
|
3136
|
-
("
|
|
3354
|
+
("refined", "#4CAF50"),
|
|
3137
3355
|
]
|
|
3138
3356
|
|
|
3139
3357
|
cols = ["Machine", "SC", "Config", "B_max [T]", "J [MA/m²]", "σ [MPa]", "c [m]"]
|
|
@@ -3428,7 +3646,7 @@ def plot_CS_benchmark_table(cfg=None, save_dir=None) -> None:
|
|
|
3428
3646
|
# Model definitions: (label, function, header colour)
|
|
3429
3647
|
model_specs = [
|
|
3430
3648
|
("Academic", f_CS_ACAD, "#9C27B0"),
|
|
3431
|
-
("
|
|
3649
|
+
("refined", f_CS_refined, "#2196F3"),
|
|
3432
3650
|
("CIRCE", f_CS_CIRCE, "#F44336"),
|
|
3433
3651
|
]
|
|
3434
3652
|
|
|
@@ -3994,7 +4212,7 @@ if __name__ == "__main__":
|
|
|
3994
4212
|
# Plasma geometry
|
|
3995
4213
|
"R0": 6.2,
|
|
3996
4214
|
"a": 2.0,
|
|
3997
|
-
"Plasma_geometry": "
|
|
4215
|
+
"Plasma_geometry": "refined",
|
|
3998
4216
|
"kappa_edge": 1.85,
|
|
3999
4217
|
"delta_edge": 0.33,
|
|
4000
4218
|
"Vprime_data": None,
|