d0fus 2.2.0__tar.gz → 2.2.1__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.
Potentially problematic release.
This version of d0fus might be problematic. Click here for more details.
- {d0fus-2.2.0 → d0fus-2.2.1}/D0FUS_BIB/D0FUS_figures.py +102 -85
- {d0fus-2.2.0 → d0fus-2.2.1}/D0FUS_BIB/D0FUS_parameterization.py +143 -5
- {d0fus-2.2.0 → d0fus-2.2.1}/D0FUS_BIB/D0FUS_physical_functions.py +919 -758
- {d0fus-2.2.0 → d0fus-2.2.1}/D0FUS_BIB/D0FUS_radial_build_functions.py +22 -21
- {d0fus-2.2.0 → d0fus-2.2.1}/D0FUS_EXE/D0FUS_run.py +292 -129
- {d0fus-2.2.0 → d0fus-2.2.1}/PKG-INFO +21 -21
- {d0fus-2.2.0 → d0fus-2.2.1}/README.md +20 -20
- {d0fus-2.2.0 → d0fus-2.2.1}/d0fus.egg-info/PKG-INFO +21 -21
- {d0fus-2.2.0 → d0fus-2.2.1}/pyproject.toml +1 -1
- {d0fus-2.2.0 → d0fus-2.2.1}/D0FUS.py +0 -0
- {d0fus-2.2.0 → d0fus-2.2.1}/D0FUS_BIB/D0FUS_cost_data.py +0 -0
- {d0fus-2.2.0 → d0fus-2.2.1}/D0FUS_BIB/D0FUS_cost_functions.py +0 -0
- {d0fus-2.2.0 → d0fus-2.2.1}/D0FUS_BIB/D0FUS_import.py +0 -0
- {d0fus-2.2.0 → d0fus-2.2.1}/D0FUS_EXE/D0FUS_genetic.py +0 -0
- {d0fus-2.2.0 → d0fus-2.2.1}/D0FUS_EXE/D0FUS_scan.py +0 -0
- {d0fus-2.2.0 → d0fus-2.2.1}/LICENSE +0 -0
- {d0fus-2.2.0 → d0fus-2.2.1}/d0fus.egg-info/SOURCES.txt +0 -0
- {d0fus-2.2.0 → d0fus-2.2.1}/d0fus.egg-info/dependency_links.txt +0 -0
- {d0fus-2.2.0 → d0fus-2.2.1}/d0fus.egg-info/entry_points.txt +0 -0
- {d0fus-2.2.0 → d0fus-2.2.1}/d0fus.egg-info/requires.txt +0 -0
- {d0fus-2.2.0 → d0fus-2.2.1}/d0fus.egg-info/top_level.txt +0 -0
- {d0fus-2.2.0 → d0fus-2.2.1}/setup.cfg +0 -0
|
@@ -53,9 +53,9 @@ if __name__ != "__main__":
|
|
|
53
53
|
J_non_Cu_NbTi, J_non_Cu_Nb3Sn, J_non_Cu_REBCO,
|
|
54
54
|
calculate_cable_current_density,
|
|
55
55
|
eta_old, eta_spitzer, eta_sauter, eta_redl,
|
|
56
|
-
f_TF_academic,
|
|
57
|
-
|
|
58
|
-
f_CS_ACAD,
|
|
56
|
+
f_TF_academic, f_TF_refined,
|
|
57
|
+
Winding_Pack_refined, gamma_func, _last_graded_profile,
|
|
58
|
+
f_CS_ACAD, f_CS_refined, f_CS_CIRCE,
|
|
59
59
|
F_CIRCE0D, compute_von_mises_stress,
|
|
60
60
|
calculate_E_mag_TF,
|
|
61
61
|
)
|
|
@@ -81,9 +81,9 @@ else:
|
|
|
81
81
|
J_non_Cu_NbTi, J_non_Cu_Nb3Sn, J_non_Cu_REBCO,
|
|
82
82
|
calculate_cable_current_density,
|
|
83
83
|
eta_old, eta_spitzer, eta_sauter, eta_redl,
|
|
84
|
-
f_TF_academic,
|
|
85
|
-
|
|
86
|
-
f_CS_ACAD,
|
|
84
|
+
f_TF_academic, f_TF_refined,
|
|
85
|
+
Winding_Pack_refined, gamma_func, _last_graded_profile,
|
|
86
|
+
f_CS_ACAD, f_CS_refined, f_CS_CIRCE,
|
|
87
87
|
F_CIRCE0D, compute_von_mises_stress,
|
|
88
88
|
calculate_E_mag_TF,
|
|
89
89
|
)
|
|
@@ -193,9 +193,9 @@ def plot_shaping_profiles(
|
|
|
193
193
|
"""
|
|
194
194
|
Plot radial elongation κ(ρ) and triangularity δ(ρ) profiles.
|
|
195
195
|
|
|
196
|
-
Both the 'Academic' (constant-κ, δ=0) and the '
|
|
197
|
-
shown. For δ, both positive and negative triangularity
|
|
198
|
-
superimposed.
|
|
196
|
+
Both the 'Academic' (constant-κ, δ=0) and the 'Refined PCHIP'
|
|
197
|
+
profiles are shown. For δ, both positive and negative triangularity
|
|
198
|
+
cases are superimposed.
|
|
199
199
|
|
|
200
200
|
Parameters
|
|
201
201
|
----------
|
|
@@ -206,7 +206,7 @@ def plot_shaping_profiles(
|
|
|
206
206
|
|
|
207
207
|
References
|
|
208
208
|
----------
|
|
209
|
-
|
|
209
|
+
Fritsch & Carlson, SIAM J. Numer. Anal. 17, 238 (1980) — PCHIP scheme.
|
|
210
210
|
Ball & Parra, PPCF 57, 045006 (2015) — κ core penetration.
|
|
211
211
|
"""
|
|
212
212
|
rho = np.linspace(1e-4, 1.0, 500)
|
|
@@ -223,7 +223,7 @@ def plot_shaping_profiles(
|
|
|
223
223
|
label=f"Academic: κ = κ_edge = {kappa_edge} (const)")
|
|
224
224
|
ax.plot(rho, kappa_profile(rho, kappa_edge, kappa_95),
|
|
225
225
|
color="tab:red", lw=2,
|
|
226
|
-
label="
|
|
226
|
+
label="Refined: flat core at κ₉₅, edge rise (PCHIP)")
|
|
227
227
|
ax.axvline(rho_95, color="gray", lw=1, ls="--", alpha=0.7)
|
|
228
228
|
ax.axhline(kappa_95, color="gray", lw=1, ls="--", alpha=0.7)
|
|
229
229
|
ax.plot(rho_95, kappa_95, "o", color="tab:red", ms=7, zorder=5,
|
|
@@ -243,10 +243,10 @@ def plot_shaping_profiles(
|
|
|
243
243
|
ax.axhline(0, color="tab:blue", lw=2, ls="-", label="Academic: δ = 0")
|
|
244
244
|
ax.plot(rho, delta_profile(rho, delta_edge, delta_95),
|
|
245
245
|
color="tab:red", lw=2,
|
|
246
|
-
label=f"
|
|
246
|
+
label=f"Refined PCHIP: δ_edge = +{delta_edge} (D-shape)")
|
|
247
247
|
ax.plot(rho, delta_profile(rho, delta_edge_neg, delta_95_neg),
|
|
248
248
|
color="tab:purple", lw=2,
|
|
249
|
-
label=f"
|
|
249
|
+
label=f"Refined PCHIP: δ_edge = {delta_edge_neg} (neg. triang.)")
|
|
250
250
|
ax.axvline(rho_95, color="gray", lw=1, ls="--", alpha=0.7)
|
|
251
251
|
ax.axhline(delta_95, color="tab:red", lw=1, ls="--", alpha=0.6)
|
|
252
252
|
ax.plot(rho_95, delta_95, "o", color="tab:red", ms=7, zorder=5,
|
|
@@ -283,9 +283,9 @@ def plot_miller_surfaces(
|
|
|
283
283
|
) -> None:
|
|
284
284
|
"""
|
|
285
285
|
Plot Miller flux surfaces for three geometry configurations side by side:
|
|
286
|
-
1.
|
|
287
|
-
2. Academic
|
|
288
|
-
3.
|
|
286
|
+
1. Refined PCHIP — positive triangularity
|
|
287
|
+
2. Academic — circular (κ const, δ = 0)
|
|
288
|
+
3. Refined PCHIP — negative triangularity
|
|
289
289
|
|
|
290
290
|
Parameters
|
|
291
291
|
----------
|
|
@@ -309,15 +309,15 @@ def plot_miller_surfaces(
|
|
|
309
309
|
colors_fs = cm.Blues(np.linspace(0.20, 0.95, n_levels))
|
|
310
310
|
|
|
311
311
|
configs = [
|
|
312
|
-
("
|
|
313
|
-
f"
|
|
312
|
+
("refined",
|
|
313
|
+
f"Refined PCHIP — positive δ\n"
|
|
314
314
|
f"(κ₉₅ = {kappa_95:.3f}, δ_edge = +{delta_edge}, δ₉₅ = +{delta_95:.3f})",
|
|
315
315
|
kappa_edge, delta_edge, kappa_95, delta_95),
|
|
316
316
|
("Academic",
|
|
317
317
|
f"Academic (κ = {kappa_edge} const, δ = 0)",
|
|
318
318
|
kappa_edge, 0.0, kappa_95, 0.0),
|
|
319
|
-
("
|
|
320
|
-
f"
|
|
319
|
+
("refined",
|
|
320
|
+
f"Refined PCHIP — negative δ\n"
|
|
321
321
|
f"(κ₉₅ = {kappa_95:.3f}, δ_edge = {delta_edge_neg}, δ₉₅ = {delta_95_neg:.3f})",
|
|
322
322
|
kappa_edge, delta_edge_neg, kappa_95, delta_95_neg),
|
|
323
323
|
]
|
|
@@ -348,7 +348,7 @@ def plot_miller_surfaces(
|
|
|
348
348
|
ax.grid(True, alpha=0.3)
|
|
349
349
|
|
|
350
350
|
plt.suptitle(
|
|
351
|
-
f"Miller flux surfaces — Academic |
|
|
351
|
+
f"Miller flux surfaces — Academic | Refined (+δ) | Refined (−δ)\n"
|
|
352
352
|
f"(R₀ = {R0} m, a = {a} m, κ = {kappa_edge}, |δ| = {delta_edge})",
|
|
353
353
|
fontsize=12, fontweight="bold"
|
|
354
354
|
)
|
|
@@ -461,7 +461,7 @@ def plot_first_wall_surface(
|
|
|
461
461
|
) -> None:
|
|
462
462
|
"""
|
|
463
463
|
Compare first-wall surface area from the Academic (Ramanujan ellipse)
|
|
464
|
-
and
|
|
464
|
+
and refined (Miller LCFS numerical) models.
|
|
465
465
|
|
|
466
466
|
Left panel : S vs κ (δ fixed).
|
|
467
467
|
Right panel : S vs δ (κ fixed, including negative triangularity).
|
|
@@ -483,11 +483,11 @@ def plot_first_wall_surface(
|
|
|
483
483
|
|
|
484
484
|
S_ac_k = [f_first_wall_surface(R0, a, k, delta_fix, "Academic")
|
|
485
485
|
for k in kappa_scan]
|
|
486
|
-
S_d0_k = [f_first_wall_surface(R0, a, k, delta_fix, "
|
|
486
|
+
S_d0_k = [f_first_wall_surface(R0, a, k, delta_fix, "refined")
|
|
487
487
|
for k in kappa_scan]
|
|
488
488
|
S_ac_d = [f_first_wall_surface(R0, a, kappa_fix, d, "Academic")
|
|
489
489
|
for d in delta_scan]
|
|
490
|
-
S_d0_d = [f_first_wall_surface(R0, a, kappa_fix, d, "
|
|
490
|
+
S_d0_d = [f_first_wall_surface(R0, a, kappa_fix, d, "refined")
|
|
491
491
|
for d in delta_scan]
|
|
492
492
|
|
|
493
493
|
fig, axes = plt.subplots(1, 2, figsize=(13, 5))
|
|
@@ -501,7 +501,7 @@ def plot_first_wall_surface(
|
|
|
501
501
|
f"First wall surface vs δ (κ = {kappa_fix})"),
|
|
502
502
|
]:
|
|
503
503
|
ax.plot(xarr, S_ac, "b-", lw=2, label="Academic (Ramanujan ellipse)")
|
|
504
|
-
ax.plot(xarr, S_d0, "r--", lw=2, label="
|
|
504
|
+
ax.plot(xarr, S_d0, "r--", lw=2, label="Refined (Miller LCFS)")
|
|
505
505
|
ax.set_xlabel(xlabel, fontsize=12)
|
|
506
506
|
ax.set_ylabel("S [m²]", fontsize=12)
|
|
507
507
|
ax.set_title(title, fontsize=11)
|
|
@@ -741,13 +741,13 @@ def plot_He_fraction(
|
|
|
741
741
|
|
|
742
742
|
fig, ax = plt.subplots(figsize=(6.5, 4.2))
|
|
743
743
|
ax.plot(C_arr, fa_ITER, "b-", lw=1.8, label="ITER — academic (no pedestal)")
|
|
744
|
-
ax.plot(C_arr, fa_ITER_ped, "b--", lw=1.4, label="ITER —
|
|
744
|
+
ax.plot(C_arr, fa_ITER_ped, "b--", lw=1.4, label="ITER — Refined H-mode pedestal")
|
|
745
745
|
ax.plot(C_arr, fa_DEMO, "r-", lw=1.8, label="EU-DEMO — academic")
|
|
746
746
|
ax.axvline(C_Alpha, color="k", lw=0.9, ls=":", label=f"$C_\\alpha$ = {C_Alpha:.0f}")
|
|
747
747
|
ax.axhspan(4, 6, color="grey", alpha=0.12, label="ITER target 5 %")
|
|
748
748
|
ax.set_xlabel(r"Removal efficiency $C_\alpha = \tau_\alpha / \tau_E$", fontsize=11)
|
|
749
749
|
ax.set_ylabel(r"Helium ash fraction $f_\alpha$ [%]", fontsize=11)
|
|
750
|
-
ax.set_title("He ash fraction — academic vs
|
|
750
|
+
ax.set_title("He ash fraction — academic vs refined H-mode pedestal", fontsize=10)
|
|
751
751
|
ax.legend(fontsize=8)
|
|
752
752
|
ax.set_xlim(2, 15)
|
|
753
753
|
ax.set_ylim(0, 25)
|
|
@@ -937,7 +937,7 @@ def plot_TF_thickness_vs_field(
|
|
|
937
937
|
) -> None:
|
|
938
938
|
"""
|
|
939
939
|
Plot TF coil total inboard thickness vs peak magnetic field for four
|
|
940
|
-
mechanical models (Academic/
|
|
940
|
+
mechanical models (Academic/refined × Wedging/Bucking).
|
|
941
941
|
|
|
942
942
|
An optional MADE benchmark scatter dataset is superimposed on the Wedging
|
|
943
943
|
panel for EU-DEMO validation.
|
|
@@ -949,7 +949,7 @@ def plot_TF_thickness_vs_field(
|
|
|
949
949
|
sigma_TF : float Allowable TF coil stress [Pa].
|
|
950
950
|
Default 867 MPa (SDC-IC Pm+Pb, austenitic steel at 4 K).
|
|
951
951
|
J_max_TF : float Current density on the non-steel area [A/m²].
|
|
952
|
-
Default 60 MA/m². The
|
|
952
|
+
Default 60 MA/m². The refined cable model (Maddock
|
|
953
953
|
adiabatic hotspot) predicts ~40 MA/m² for HTS at
|
|
954
954
|
20 T,
|
|
955
955
|
From Giannini 2023 Fig. 20 (validated FEM
|
|
@@ -983,11 +983,11 @@ def plot_TF_thickness_vs_field(
|
|
|
983
983
|
"Bucking", cfg.coef_inboard_tension,
|
|
984
984
|
cfg.F_CClamp)[0])
|
|
985
985
|
# c_BP = 0: MADE total radial build = WP + case vault (no backplate)
|
|
986
|
-
d0_w.append(
|
|
986
|
+
d0_w.append(f_TF_refined(a, b, R0, sigma_TF, J_max_TF, B,
|
|
987
987
|
"Wedging", 0.5, 1,
|
|
988
988
|
0.0, cfg.coef_inboard_tension,
|
|
989
989
|
cfg.F_CClamp)[0])
|
|
990
|
-
d0_b.append(
|
|
990
|
+
d0_b.append(f_TF_refined(a, b, R0, sigma_TF, J_max_TF, B,
|
|
991
991
|
"Bucking", 1.0, 1,
|
|
992
992
|
0.0, cfg.coef_inboard_tension,
|
|
993
993
|
cfg.F_CClamp)[0])
|
|
@@ -1007,7 +1007,7 @@ def plot_TF_thickness_vs_field(
|
|
|
1007
1007
|
# Wedging panel
|
|
1008
1008
|
ax = axes[0]
|
|
1009
1009
|
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="
|
|
1010
|
+
ax.plot(B_vals, d0_w, color=colors[1], lw=2, label="Refined — Wedging")
|
|
1011
1011
|
ax.scatter(x_made, y_made, color="k", marker="x", s=80, label="MADE")
|
|
1012
1012
|
ax.set_xlabel("Peak field B_max [T]", fontsize=12)
|
|
1013
1013
|
ax.set_ylabel("TF total inboard thickness [m]", fontsize=12)
|
|
@@ -1018,7 +1018,7 @@ def plot_TF_thickness_vs_field(
|
|
|
1018
1018
|
# Bucking panel
|
|
1019
1019
|
ax = axes[1]
|
|
1020
1020
|
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="
|
|
1021
|
+
ax.plot(B_vals, d0_b, color=colors[1], lw=2, label="Refined — Bucking")
|
|
1022
1022
|
ax.set_xlabel("Peak field B_max [T]", fontsize=12)
|
|
1023
1023
|
ax.set_ylabel("TF total inboard thickness [m]", fontsize=12)
|
|
1024
1024
|
ax.set_title("Bucking configuration", fontsize=12)
|
|
@@ -1067,9 +1067,9 @@ def plot_TF_grading_thickness_vs_field(
|
|
|
1067
1067
|
c_g = np.full_like(B_scan, np.nan)
|
|
1068
1068
|
|
|
1069
1069
|
for i, Bm in enumerate(B_scan):
|
|
1070
|
-
ru =
|
|
1070
|
+
ru = Winding_Pack_refined(R0, a, b, sigma_TF, J_max_TF, Bm,
|
|
1071
1071
|
0.5, n, grading=False)
|
|
1072
|
-
rg =
|
|
1072
|
+
rg = Winding_Pack_refined(R0, a, b, sigma_TF, J_max_TF, Bm,
|
|
1073
1073
|
0.5, n, grading=True)
|
|
1074
1074
|
if np.isfinite(ru[0]):
|
|
1075
1075
|
c_u[i] = ru[0] * 100
|
|
@@ -1116,9 +1116,9 @@ def plot_TF_grading_reduction(
|
|
|
1116
1116
|
c_g = np.full_like(B_scan, np.nan)
|
|
1117
1117
|
|
|
1118
1118
|
for i, Bm in enumerate(B_scan):
|
|
1119
|
-
ru =
|
|
1119
|
+
ru = Winding_Pack_refined(R0, a, b, sigma_TF, J_max_TF, Bm,
|
|
1120
1120
|
0.5, n, grading=False)
|
|
1121
|
-
rg =
|
|
1121
|
+
rg = Winding_Pack_refined(R0, a, b, sigma_TF, J_max_TF, Bm,
|
|
1122
1122
|
0.5, n, grading=True)
|
|
1123
1123
|
if np.isfinite(ru[0]):
|
|
1124
1124
|
c_u[i] = ru[0] * 100
|
|
@@ -1164,9 +1164,9 @@ def plot_TF_grading_alpha_profile(
|
|
|
1164
1164
|
cfg = DEFAULT_CONFIG
|
|
1165
1165
|
|
|
1166
1166
|
# Run graded (populates _last_graded_profile) then ungraded
|
|
1167
|
-
rg =
|
|
1167
|
+
rg = Winding_Pack_refined(R0, a, b, sigma_TF, J_max_TF, B_max,
|
|
1168
1168
|
0.5, n, grading=True)
|
|
1169
|
-
ru =
|
|
1169
|
+
ru = Winding_Pack_refined(R0, a, b, sigma_TF, J_max_TF, B_max,
|
|
1170
1170
|
0.5, n, grading=False)
|
|
1171
1171
|
|
|
1172
1172
|
if not np.isfinite(rg[0]) or not np.isfinite(ru[0]):
|
|
@@ -1221,7 +1221,7 @@ def plot_CS_thickness_vs_flux(
|
|
|
1221
1221
|
) -> None:
|
|
1222
1222
|
"""
|
|
1223
1223
|
Plot CS coil winding-pack thickness and peak field vs volt-second budget
|
|
1224
|
-
for three mechanical models (Academic,
|
|
1224
|
+
for three mechanical models (Academic, refined, CIRCE) and three
|
|
1225
1225
|
configuration types (Wedging, Bucking, Plug).
|
|
1226
1226
|
|
|
1227
1227
|
For the Wedging configuration, a MADE benchmark scatter dataset
|
|
@@ -1234,7 +1234,7 @@ def plot_CS_thickness_vs_flux(
|
|
|
1234
1234
|
Geometry: a_cs=3 m, b_cs=1.2 m, c_cs=2 m, R0=9 m, Gap=0.1 m
|
|
1235
1235
|
→ R_CS_ext = 9 - 3 - 1.2 - 2 - 0.1 = 2.7 m
|
|
1236
1236
|
|
|
1237
|
-
Stress: σ_CS = 600 MPa.
|
|
1237
|
+
Stress: σ_CS = 600 MPa. The refined model applies fatigue_CS = 2 internally
|
|
1238
1238
|
→ σ_eff = 300 MPa as the fig.2 source
|
|
1239
1239
|
|
|
1240
1240
|
J_wost = 85 MA/m², derived from Sarasola 2020 Sec. II-B:
|
|
@@ -1281,7 +1281,7 @@ def plot_CS_thickness_vs_flux(
|
|
|
1281
1281
|
# subtracted). The MADE reference data published by Sarasola et al.
|
|
1282
1282
|
# corresponds to the CS hardware design output at full bipolar swing
|
|
1283
1283
|
# (the curve is parameterised on the total volt-second capacity of
|
|
1284
|
-
# the coil, not on the plasma inductive demand). To compare
|
|
1284
|
+
# the coil, not on the plasma inductive demand). To compare the refined model to
|
|
1285
1285
|
# MADE on the same footing, we feed the full-bipolar abscissa to the
|
|
1286
1286
|
# solver and disable the additional 1/f_swing_usable rescaling. The
|
|
1287
1287
|
# design-mode default (0.75 = 25% control reserve) is restored
|
|
@@ -1299,8 +1299,8 @@ def plot_CS_thickness_vs_flux(
|
|
|
1299
1299
|
|
|
1300
1300
|
psi_values = np.linspace(0, psi_max, n_psi)
|
|
1301
1301
|
mech_confs = ["Wedging", "Bucking", "Plug"]
|
|
1302
|
-
model_funcs = {"Academic": f_CS_ACAD, "
|
|
1303
|
-
colors = {"Academic": "blue", "
|
|
1302
|
+
model_funcs = {"Academic": f_CS_ACAD, "refined": f_CS_refined, "CIRCE": f_CS_CIRCE}
|
|
1303
|
+
colors = {"Academic": "blue", "refined": "green", "CIRCE": "red"}
|
|
1304
1304
|
|
|
1305
1305
|
# Storage: results[model][config] = {"thickness": [], "B": []}
|
|
1306
1306
|
results = {m: {c: {"thickness": [], "B": []} for c in mech_confs}
|
|
@@ -1653,10 +1653,11 @@ def plot_shaping_run(
|
|
|
1653
1653
|
Plot radial elongation κ(ρ) and triangularity δ(ρ) profiles for the
|
|
1654
1654
|
geometry used in a D0FUS run.
|
|
1655
1655
|
|
|
1656
|
-
In '
|
|
1657
|
-
|
|
1658
|
-
In 'Academic' mode,
|
|
1659
|
-
consistent with the
|
|
1656
|
+
In 'refined' geometry mode, three-point PCHIP profiles are used for
|
|
1657
|
+
κ(ρ) and δ(ρ) (C¹ continuous globally, monotonicity-preserving,
|
|
1658
|
+
with radially varying core penetration). In 'Academic' mode,
|
|
1659
|
+
κ(ρ) = κ_edge = const and δ(ρ) = 0 everywhere, consistent with the
|
|
1660
|
+
cylindrical-torus approximation.
|
|
1660
1661
|
|
|
1661
1662
|
Parameters
|
|
1662
1663
|
----------
|
|
@@ -1666,11 +1667,11 @@ def plot_shaping_run(
|
|
|
1666
1667
|
|
|
1667
1668
|
References
|
|
1668
1669
|
----------
|
|
1669
|
-
|
|
1670
|
+
Fritsch & Carlson, SIAM J. Numer. Anal. 17, 238 (1980) — PCHIP scheme.
|
|
1670
1671
|
Ball & Parra, PPCF 57, 045006 (2015) — κ core penetration.
|
|
1671
1672
|
"""
|
|
1672
1673
|
R0, a, kappa_edge, delta_edge, kappa_95, delta_95, _ = _resolve_geometry(run)
|
|
1673
|
-
geom_mode = run.get("Plasma_geometry", "
|
|
1674
|
+
geom_mode = run.get("Plasma_geometry", "refined")
|
|
1674
1675
|
rho = np.linspace(1e-4, 1.0, n_rho)
|
|
1675
1676
|
|
|
1676
1677
|
if geom_mode == "Academic":
|
|
@@ -1678,7 +1679,7 @@ def plot_shaping_run(
|
|
|
1678
1679
|
kap_arr = np.full_like(rho, kappa_edge)
|
|
1679
1680
|
del_arr = np.zeros_like(rho)
|
|
1680
1681
|
else:
|
|
1681
|
-
#
|
|
1682
|
+
# Refined Miller PCHIP profiles with radial variation
|
|
1682
1683
|
kap_arr = kappa_profile(rho, kappa_edge, kappa_95)
|
|
1683
1684
|
del_arr = delta_profile(rho, delta_edge, delta_95)
|
|
1684
1685
|
|
|
@@ -1690,7 +1691,7 @@ def plot_shaping_run(
|
|
|
1690
1691
|
ax.axhline(kappa_edge, color="tab:blue", lw=2.2,
|
|
1691
1692
|
label=f"Academic: κ = κ_edge = {kappa_edge:.3f} (const)")
|
|
1692
1693
|
else:
|
|
1693
|
-
ax.plot(rho, kap_arr, "tab:blue", lw=2.2, label="
|
|
1694
|
+
ax.plot(rho, kap_arr, "tab:blue", lw=2.2, label="Refined PCHIP")
|
|
1694
1695
|
ax.axhline(kappa_edge, color="tab:orange", lw=1.4, ls="--",
|
|
1695
1696
|
label=f"κ_edge = {kappa_edge:.3f}")
|
|
1696
1697
|
ax.axhline(kappa_95, color="tab:gray", lw=1.0, ls=":",
|
|
@@ -1709,7 +1710,7 @@ def plot_shaping_run(
|
|
|
1709
1710
|
ax.axhline(0, color="tab:blue", lw=2.2,
|
|
1710
1711
|
label="Academic: δ = 0 (const)")
|
|
1711
1712
|
else:
|
|
1712
|
-
ax.plot(rho, del_arr, "tab:red", lw=2.2, label="
|
|
1713
|
+
ax.plot(rho, del_arr, "tab:red", lw=2.2, label="Refined PCHIP")
|
|
1713
1714
|
ax.axhline(delta_edge, color="tab:orange", lw=1.4, ls="--",
|
|
1714
1715
|
label=f"δ_edge = {delta_edge:.3f}")
|
|
1715
1716
|
ax.axhline(delta_95, color="tab:gray", lw=1.0, ls=":",
|
|
@@ -1723,7 +1724,7 @@ def plot_shaping_run(
|
|
|
1723
1724
|
ax.grid(True, alpha=0.3)
|
|
1724
1725
|
ax.set_xlim(0, 1)
|
|
1725
1726
|
|
|
1726
|
-
_mode_str = "Academic" if geom_mode == "Academic" else "
|
|
1727
|
+
_mode_str = "Academic" if geom_mode == "Academic" else "Refined PCHIP"
|
|
1727
1728
|
plt.suptitle(
|
|
1728
1729
|
f"Run shaping profiles ({_mode_str}) — R₀={R0} m, a={a} m\n"
|
|
1729
1730
|
f"κ_edge={kappa_edge:.3f}, δ_edge={delta_edge:.3f}",
|
|
@@ -1746,8 +1747,9 @@ def plot_flux_surfaces_run(
|
|
|
1746
1747
|
"""
|
|
1747
1748
|
Plot nested flux surfaces for the actual geometry of a D0FUS run.
|
|
1748
1749
|
|
|
1749
|
-
When Plasma_geometry == '
|
|
1750
|
-
parameterisation with radially varying κ(ρ) and δ(ρ)
|
|
1750
|
+
When Plasma_geometry == 'refined', surfaces follow the Miller
|
|
1751
|
+
parameterisation with radially varying κ(ρ) and δ(ρ) built from
|
|
1752
|
+
three-point PCHIP profiles (C¹ continuous, monotonicity-preserving).
|
|
1751
1753
|
When Plasma_geometry == 'Academic' (or unset), surfaces are concentric
|
|
1752
1754
|
ellipses with constant κ = κ_edge and δ = 0, consistent with the
|
|
1753
1755
|
cylindrical-torus approximation used in the Academic solver branch.
|
|
@@ -1767,7 +1769,7 @@ def plot_flux_surfaces_run(
|
|
|
1767
1769
|
Miller et al., Phys. Plasmas 5, 973 (1998).
|
|
1768
1770
|
"""
|
|
1769
1771
|
R0, a, kappa_edge, delta_edge, kappa_95, delta_95, _ = _resolve_geometry(run)
|
|
1770
|
-
geom_mode = run.get("Plasma_geometry", "
|
|
1772
|
+
geom_mode = run.get("Plasma_geometry", "refined")
|
|
1771
1773
|
|
|
1772
1774
|
theta = np.linspace(0, 2 * np.pi, n_theta)
|
|
1773
1775
|
rho_levels = np.linspace(0.1, 1.0, n_levels)
|
|
@@ -1781,7 +1783,7 @@ def plot_flux_surfaces_run(
|
|
|
1781
1783
|
R_surf = R0 + rho_val * a * np.cos(theta)
|
|
1782
1784
|
Z_surf = kappa_edge * rho_val * a * np.sin(theta)
|
|
1783
1785
|
else:
|
|
1784
|
-
#
|
|
1786
|
+
# Refined Miller PCHIP surfaces with radially varying κ(ρ), δ(ρ)
|
|
1785
1787
|
R_surf, Z_surf = miller_RZ(rho_val, theta,
|
|
1786
1788
|
R0, a, kappa_edge, delta_edge,
|
|
1787
1789
|
kappa_95, delta_95)
|
|
@@ -1802,7 +1804,7 @@ def plot_flux_surfaces_run(
|
|
|
1802
1804
|
if geom_mode == "Academic":
|
|
1803
1805
|
_geom_label = "Academic (elliptic, δ = 0)"
|
|
1804
1806
|
else:
|
|
1805
|
-
_geom_label = f"
|
|
1807
|
+
_geom_label = f"Refined PCHIP (δ_edge = {delta_edge:.3f})"
|
|
1806
1808
|
|
|
1807
1809
|
ax.set_title(
|
|
1808
1810
|
f"Flux surfaces — {_geom_label}\n"
|
|
@@ -1827,16 +1829,16 @@ def plot_q_profile(
|
|
|
1827
1829
|
"""
|
|
1828
1830
|
Plot the safety factor profile q(rho).
|
|
1829
1831
|
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1832
|
+
Picks data from run['_q_sc'], which is populated by the q,j dispatcher
|
|
1833
|
+
(f_q_profile_academic or f_q_profile_refined according to
|
|
1834
|
+
config.q_profile_mode). When the dict is unavailable, falls back to
|
|
1835
|
+
the analytical parametric q(rho) from f_q_profile() with alpha_J = 1.5.
|
|
1833
1836
|
|
|
1834
|
-
The current density decomposition
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
(alpha_J, q_0, l_i, f_bs) are reported in the figure title.
|
|
1837
|
+
The current density decomposition j_Ohm + j_CD + j_bs is meaningful
|
|
1838
|
+
only in 'refined' mode (in 'academic' mode those components are zero
|
|
1839
|
+
by construction). The figure stays focused on q(rho) and reports the
|
|
1840
|
+
integral diagnostic l_i(3) along with q_0 and either q(0.95) or the
|
|
1841
|
+
imposed q95.
|
|
1840
1842
|
|
|
1841
1843
|
Parameters
|
|
1842
1844
|
----------
|
|
@@ -1852,18 +1854,20 @@ def plot_q_profile(
|
|
|
1852
1854
|
"""
|
|
1853
1855
|
q95 = float(run["q95"])
|
|
1854
1856
|
_q_sc = run.get("_q_sc", None)
|
|
1857
|
+
q_mode = str(run.get("q_profile_mode", "refined")) # default for legacy runs
|
|
1855
1858
|
|
|
1856
|
-
# ── Data source:
|
|
1859
|
+
# ── Data source: dispatcher solution or analytical fallback ──────────
|
|
1857
1860
|
if _q_sc is not None and 'q_arr' in _q_sc:
|
|
1858
1861
|
rho = _q_sc['rho']
|
|
1859
1862
|
q_arr = _q_sc['q_arr']
|
|
1860
1863
|
li = _q_sc['li']
|
|
1861
|
-
|
|
1862
|
-
suptitle_str =
|
|
1864
|
+
q_at_95 = float(_q_sc.get('q_at_95', np.interp(0.95, rho, q_arr)))
|
|
1865
|
+
suptitle_str = (rf"Safety factor profile $q(\rho)$ "
|
|
1866
|
+
rf"({q_mode} mode)")
|
|
1863
1867
|
else:
|
|
1864
|
-
# Analytical fallback (no
|
|
1868
|
+
# Analytical fallback (no run output available — used by tests).
|
|
1865
1869
|
rho = np.linspace(0.0, 0.99, n_rho)
|
|
1866
|
-
alpha_J = 1.5
|
|
1870
|
+
alpha_J = float(run.get("alpha_J", 1.5))
|
|
1867
1871
|
q_arr = f_q_profile(rho, q95=q95, rho95=0.95, alpha_J=alpha_J)
|
|
1868
1872
|
|
|
1869
1873
|
# Cylindrical li estimate from the analytical form
|
|
@@ -1874,7 +1878,9 @@ def plot_q_profile(
|
|
|
1874
1878
|
li_integ = np.where(rho > 1e-8, I_norm**2 / rho_s, 0.0)
|
|
1875
1879
|
li = 2.0 * np.trapezoid(li_integ, rho)
|
|
1876
1880
|
|
|
1877
|
-
|
|
1881
|
+
q_at_95 = float(np.interp(0.95, rho, q_arr))
|
|
1882
|
+
suptitle_str = (rf"Safety factor profile $q(\rho)$ "
|
|
1883
|
+
rf"(analytical fallback, $\alpha_J = {alpha_J:.2f}$)")
|
|
1878
1884
|
|
|
1879
1885
|
# ── Figure ────────────────────────────────────────────────────────
|
|
1880
1886
|
fig, ax = plt.subplots(1, 1, figsize=(7, 5))
|
|
@@ -1883,12 +1889,23 @@ def plot_q_profile(
|
|
|
1883
1889
|
ax.axhline(q95, color="gray", ls="--", lw=0.8, alpha=0.5)
|
|
1884
1890
|
ax.axvline(0.95, color="gray", ls=":", lw=0.8, alpha=0.4)
|
|
1885
1891
|
ax.plot(rho[0], q_arr[0], "o", color="tab:blue", ms=7, zorder=5)
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
+
# In academic mode, q(rho_95) = q95 by construction; in refined mode,
|
|
1893
|
+
# q(rho_95) generally differs and is a meaningful diagnostic.
|
|
1894
|
+
if q_mode == "refined":
|
|
1895
|
+
ax.plot(0.95, q_at_95, "o", color="tab:blue", ms=7, zorder=5)
|
|
1896
|
+
ax.plot(0.95, q95, "s", color="tab:red", ms=7, zorder=5,
|
|
1897
|
+
label=rf"$q_{{95}}^{{\rm scaling}} = {q95:.2f}$")
|
|
1898
|
+
info_text = (rf"$q_0 = {q_arr[0]:.2f}$" + "\n"
|
|
1899
|
+
rf"$q(\rho_{{95}}) = {q_at_95:.2f}$" + "\n"
|
|
1900
|
+
rf"$l_i(3) = {li:.2f}$" + "\n"
|
|
1901
|
+
rf"$q(1) = {q_arr[-1]:.2f}$")
|
|
1902
|
+
else: # academic mode: q95 is imposed
|
|
1903
|
+
ax.plot(0.95, q95, "s", color="tab:red", ms=7, zorder=5,
|
|
1904
|
+
label=rf"$q_{{95}} = {q95:.2f}$ (imposed)")
|
|
1905
|
+
info_text = (rf"$q_0 = {q_arr[0]:.2f}$" + "\n"
|
|
1906
|
+
rf"$l_i(3) = {li:.2f}$" + "\n"
|
|
1907
|
+
rf"$q(1) = {q_arr[-1]:.2f}$")
|
|
1908
|
+
ax.text(0.04, 0.95, info_text,
|
|
1892
1909
|
transform=ax.transAxes, fontsize=10, va="top",
|
|
1893
1910
|
bbox=dict(boxstyle="round,pad=0.3", fc="lightyellow", alpha=0.85))
|
|
1894
1911
|
ax.set_xlabel(r"$\rho$", fontsize=12)
|
|
@@ -3121,8 +3138,8 @@ def plot_TF_benchmark_table(cfg=None, save_dir=None) -> None:
|
|
|
3121
3138
|
return f_TF_academic(a, b, R0, sigma, J_wost, B_max,
|
|
3122
3139
|
conf, cfg.coef_inboard_tension, cfg.F_CClamp)
|
|
3123
3140
|
else:
|
|
3124
|
-
#
|
|
3125
|
-
return
|
|
3141
|
+
# f_TF_refined returns the same tuple layout
|
|
3142
|
+
return f_TF_refined(a, b, R0, sigma, J_wost, B_max,
|
|
3126
3143
|
conf, omega, n_frac,
|
|
3127
3144
|
cfg.c_BP, cfg.coef_inboard_tension, cfg.F_CClamp)
|
|
3128
3145
|
|
|
@@ -3133,7 +3150,7 @@ def plot_TF_benchmark_table(cfg=None, save_dir=None) -> None:
|
|
|
3133
3150
|
# consistent with the CS benchmark.
|
|
3134
3151
|
model_specs = [
|
|
3135
3152
|
("Academic", "#9C27B0"),
|
|
3136
|
-
("
|
|
3153
|
+
("refined", "#4CAF50"),
|
|
3137
3154
|
]
|
|
3138
3155
|
|
|
3139
3156
|
cols = ["Machine", "SC", "Config", "B_max [T]", "J [MA/m²]", "σ [MPa]", "c [m]"]
|
|
@@ -3428,7 +3445,7 @@ def plot_CS_benchmark_table(cfg=None, save_dir=None) -> None:
|
|
|
3428
3445
|
# Model definitions: (label, function, header colour)
|
|
3429
3446
|
model_specs = [
|
|
3430
3447
|
("Academic", f_CS_ACAD, "#9C27B0"),
|
|
3431
|
-
("
|
|
3448
|
+
("refined", f_CS_refined, "#2196F3"),
|
|
3432
3449
|
("CIRCE", f_CS_CIRCE, "#F44336"),
|
|
3433
3450
|
]
|
|
3434
3451
|
|
|
@@ -3994,7 +4011,7 @@ if __name__ == "__main__":
|
|
|
3994
4011
|
# Plasma geometry
|
|
3995
4012
|
"R0": 6.2,
|
|
3996
4013
|
"a": 2.0,
|
|
3997
|
-
"Plasma_geometry": "
|
|
4014
|
+
"Plasma_geometry": "refined",
|
|
3998
4015
|
"kappa_edge": 1.85,
|
|
3999
4016
|
"delta_edge": 0.33,
|
|
4000
4017
|
"Vprime_data": None,
|