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.

@@ -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, f_TF_D0FUS,
57
- Winding_Pack_D0FUS, gamma_func, _last_graded_profile,
58
- f_CS_ACAD, f_CS_D0FUS, f_CS_CIRCE,
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, f_TF_D0FUS,
85
- Winding_Pack_D0FUS, gamma_func, _last_graded_profile,
86
- f_CS_ACAD, f_CS_D0FUS, f_CS_CIRCE,
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 'D0FUS PCHIP' profiles are
197
- shown. For δ, both positive and negative triangularity cases are
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
- Christiansen et al., Nucl. Fusion 32, 291 (1992) — PCHIP parameterisation.
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="D0FUS: flat core at κ₉₅, edge rise (PCHIP)")
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"D0FUS PCHIP: δ_edge = +{delta_edge} (D-shape)")
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"D0FUS PCHIP: δ_edge = {delta_edge_neg} (neg. triang.)")
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. D0FUS PCHIP — positive triangularity
287
- 2. Academic — circular (κ const, δ = 0)
288
- 3. D0FUS PCHIP — negative triangularity
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
- ("D0FUS",
313
- f"D0FUS PCHIP — positive δ\n"
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
- ("D0FUS",
320
- f"D0FUS PCHIP — negative δ\n"
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 | D0FUS (+δ) | D0FUS (−δ)\n"
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 D0FUS (Miller LCFS numerical) models.
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, "D0FUS")
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, "D0FUS")
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="D0FUS (Miller LCFS)")
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 — D0FUS H-mode pedestal")
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 D0FUS H-mode pedestal", fontsize=10)
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/D0FUS × Wedging/Bucking).
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 D0FUS cable model (Maddock
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(f_TF_D0FUS(a, b, R0, sigma_TF, J_max_TF, B,
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(f_TF_D0FUS(a, b, R0, sigma_TF, J_max_TF, B,
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="D0FUS — Wedging")
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="D0FUS — Bucking")
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 = Winding_Pack_D0FUS(R0, a, b, sigma_TF, J_max_TF, Bm,
1070
+ ru = Winding_Pack_refined(R0, a, b, sigma_TF, J_max_TF, Bm,
1071
1071
  0.5, n, grading=False)
1072
- rg = Winding_Pack_D0FUS(R0, a, b, sigma_TF, J_max_TF, Bm,
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 = Winding_Pack_D0FUS(R0, a, b, sigma_TF, J_max_TF, Bm,
1119
+ ru = Winding_Pack_refined(R0, a, b, sigma_TF, J_max_TF, Bm,
1120
1120
  0.5, n, grading=False)
1121
- rg = Winding_Pack_D0FUS(R0, a, b, sigma_TF, J_max_TF, Bm,
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 = Winding_Pack_D0FUS(R0, a, b, sigma_TF, J_max_TF, B_max,
1167
+ rg = Winding_Pack_refined(R0, a, b, sigma_TF, J_max_TF, B_max,
1168
1168
  0.5, n, grading=True)
1169
- ru = Winding_Pack_D0FUS(R0, a, b, sigma_TF, J_max_TF, B_max,
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, D0FUS, CIRCE) and three
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. D0FUS applies fatigue_CS = 2 internally
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 D0FUS to
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, "D0FUS": f_CS_D0FUS, "CIRCE": f_CS_CIRCE}
1303
- colors = {"Academic": "blue", "D0FUS": "green", "CIRCE": "red"}
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 'D0FUS' geometry mode, the Christiansen PCHIP parameterisation is
1657
- used (radially varying κ and δ with core penetration).
1658
- In 'Academic' mode, κ(ρ) = κ_edge = const and δ(ρ) = 0 everywhere,
1659
- consistent with the cylindrical-torus approximation.
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
- Christiansen et al., Nucl. Fusion 32, 291 (1992).
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", "D0FUS")
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
- # D0FUS PCHIP profiles with radial variation
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="D0FUS PCHIP")
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="D0FUS PCHIP")
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 "D0FUS PCHIP"
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 == 'D0FUS', surfaces follow the Miller PCHIP
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", "D0FUS")
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
- # D0FUS PCHIP Miller surfaces with radially varying κ(ρ), δ(ρ)
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"D0FUS PCHIP (δ_edge = {delta_edge:.3f})"
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
- Uses the parametric q-profile from f_q_profile_selfconsistent() when
1831
- available in run['_q_sc']. Falls back to the analytical f_q_profile()
1832
- model with a prescribed alpha_J otherwise.
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 j_total = j_Ohm + j_CD + j_bs is
1835
- deliberately NOT plotted. Those profiles are built inside
1836
- f_q_profile_selfconsistent() only to close the self-consistency loop
1837
- on the integral scalar l_i(3); they are not calibrated quantities
1838
- that downstream D0FUS modules consume. The scalars that do propagate
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: parametric solve or analytical fallback ──────────
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 = r"Safety factor profile $q(\rho)$"
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 self-consistent data available)
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 # IPDG89 reference
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
- suptitle_str = r"Safety factor profile $q(\rho)$ (analytical fallback)"
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
- ax.plot(0.95, q95, "s", color="tab:red", ms=7, zorder=5,
1887
- label=rf"$q_{{95}} = {q95:.2f}$")
1888
- ax.text(0.04, 0.95,
1889
- rf"$q_0 = {q_arr[0]:.2f}$" + "\n"
1890
- rf"$l_i(3) = {li:.2f}$" + "\n"
1891
- rf"$q(1) = {q_arr[-1]:.2f}$",
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
- # f_TF_D0FUS returns same tuple layout
3125
- return f_TF_D0FUS(a, b, R0, sigma, J_wost, B_max,
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
- ("D0FUS", "#4CAF50"),
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
- ("D0FUS", f_CS_D0FUS, "#2196F3"),
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": "D0FUS",
4014
+ "Plasma_geometry": "refined",
3998
4015
  "kappa_edge": 1.85,
3999
4016
  "delta_edge": 0.33,
4000
4017
  "Vprime_data": None,