d0fus 2.2.2__tar.gz → 2.3.0__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.
@@ -170,12 +170,39 @@ def detect_mode_from_input(input_file):
170
170
  #%% Main functions
171
171
 
172
172
  def print_banner():
173
- """Display D0FUS banner"""
174
- banner = """
173
+ """Display the D0FUS startup banner.
174
+
175
+ Simple double-line framed banner with title, tagline, version, author,
176
+ license and repository URL. Can be silenced with the environment variable
177
+ ``D0FUS_NO_BANNER`` (useful for batch scans, HPC jobs, CI).
178
+ """
179
+ if os.environ.get("D0FUS_NO_BANNER", "").strip() not in ("", "0", "false", "False"):
180
+ return
181
+
182
+ # Resolve installed package version, fall back to a static string
183
+ try:
184
+ from importlib.metadata import version, PackageNotFoundError
185
+ try:
186
+ v = version("d0fus")
187
+ except PackageNotFoundError:
188
+ v = "dev"
189
+ except ImportError:
190
+ v = "dev"
191
+
192
+ # The version is the only dynamic line; the others are hardcoded for clarity.
193
+ # Inside width is 51 characters; the version is centered programmatically.
194
+ version_line = f"Version {v}".center(51)
195
+
196
+ banner = f"""
175
197
  ╔═══════════════════════════════════════════════════╗
176
198
  ║ ║
177
199
  ║ D0FUS ║
178
- Design 0-dimensional for Fusion Systems
200
+ Design 0-dimensional for Fusion Systems
201
+ ║ ║
202
+ ╠═══════════════════════════════════════════════════╣
203
+ ║{version_line}║
204
+ ║ T. Auclair, CEA-IRFM | CeCILL-C ║
205
+ ║ https://github.com/IRFM/D0FUS ║
179
206
  ║ ║
180
207
  ╚═══════════════════════════════════════════════════╝
181
208
  """
@@ -49,6 +49,7 @@ if __name__ != "__main__":
49
49
  f_He_fraction,
50
50
  f_q_profile,
51
51
  f_sigmav,
52
+ _two_point_core, M_F_DT,
52
53
  )
53
54
  from .D0FUS_radial_build_functions import (
54
55
  J_non_Cu_NbTi, J_non_Cu_Nb3Sn, J_non_Cu_REBCO,
@@ -78,6 +79,7 @@ else:
78
79
  f_He_fraction,
79
80
  f_q_profile,
80
81
  f_sigmav,
82
+ _two_point_core, M_F_DT,
81
83
  )
82
84
  from D0FUS_BIB.D0FUS_radial_build_functions import (
83
85
  J_non_Cu_NbTi, J_non_Cu_Nb3Sn, J_non_Cu_REBCO,
@@ -2214,6 +2216,85 @@ def plot_radiation_profile(
2214
2216
  _save_or_show(fig, save_dir, "run_radiation_profile")
2215
2217
 
2216
2218
 
2219
+ def plot_divertor_two_point(
2220
+ run: dict,
2221
+ n_pts: int = 300,
2222
+ save_dir: str | None = None,
2223
+ ) -> None:
2224
+ """
2225
+ Simple two-point-model view: target temperature versus SOL dissipation.
2226
+
2227
+ A single panel shows the target electron temperature T_et as a function of
2228
+ the SOL power-loss fraction f_cooling, at the converged upstream conditions
2229
+ (q_par_u, p_u read from the run). The dashed line is the detachment
2230
+ threshold T_et = 10 eV (Stangeby 2018). The flat plateau at high T_et is the
2231
+ attached sheath-limited branch where the two-point model no longer applies.
2232
+ The minimum dissipation required for target survival (Eq. 14) is given in
2233
+ the title and marked by the dotted vertical line.
2234
+
2235
+ Parameters
2236
+ ----------
2237
+ run : dict D0FUS run output; reads the 'divertor' sub-dict produced
2238
+ by f_heat_two_point.
2239
+ n_pts : int Number of f_cooling samples.
2240
+ save_dir : str or None
2241
+
2242
+ References
2243
+ ----------
2244
+ P.C. Stangeby, Plasma Phys. Control. Fusion 60 (2018) 044022.
2245
+ """
2246
+ div = run.get("divertor", {}) or {}
2247
+ if not {"q_par_u", "T_eu", "n_sep"}.issubset(div):
2248
+ print(" [skip] no two-point-model divertor solution in run dict")
2249
+ return
2250
+
2251
+ q_par_u = div["q_par_u"] # [MW/m^2]
2252
+ T_eu = div["T_eu"] # [eV]
2253
+ n_sep = div["n_sep"] # [m^-3]
2254
+ f_c_op = div.get("f_cooling", 0.0)
2255
+ f_m_op = div.get("f_mom", 0.0)
2256
+ T_et_op = div.get("T_et", np.nan)
2257
+ f_pwr_req = div.get("f_pwr_loss_req", np.nan)
2258
+ R0 = run.get("R0", "?")
2259
+
2260
+ q_par_u_SI = q_par_u * 1e6
2261
+ p_u = 2.0 * n_sep * E_ELEM * T_eu
2262
+
2263
+ f_c = np.linspace(0.0, 0.995, n_pts)
2264
+ T_et = np.array([min(_two_point_core(q_par_u_SI, p_u, fc, f_m_op, 7.0, M_F_DT)[0],
2265
+ T_eu) for fc in f_c])
2266
+
2267
+ fig, ax = plt.subplots(figsize=(7, 4.5))
2268
+ ax.plot(f_c, T_et, color="#4477AA", lw=2.4)
2269
+ ax.axhline(10.0, color="#EE6677", lw=1.3, ls="--")
2270
+ ax.text(0.015, 11.0, "detachment (10 eV)", color="#EE6677",
2271
+ fontsize=9, va="bottom")
2272
+ if np.isfinite(f_pwr_req):
2273
+ ax.axvline(f_pwr_req, color="0.55", lw=1.0, ls=":")
2274
+ if np.isfinite(T_et_op):
2275
+ ax.plot([f_c_op], [T_et_op], "o", ms=9, color="k", zorder=5)
2276
+ near_left = f_c_op < 0.5
2277
+ near_top = T_et_op > 0.3 * T_eu
2278
+ ax.annotate(f"operating point\n$T_{{e,t}}$ = {T_et_op:.1f} eV",
2279
+ (f_c_op, T_et_op), textcoords="offset points",
2280
+ xytext=(14 if near_left else -12, -16 if near_top else 14),
2281
+ ha="left" if near_left else "right",
2282
+ va="top" if near_top else "bottom", fontsize=9)
2283
+
2284
+ ax.set_yscale("log")
2285
+ ax.set_xlim(0, 1)
2286
+ ax.set_xlabel(r"SOL power-loss fraction $f_{\rm cooling}$", fontsize=12)
2287
+ ax.set_ylabel(r"Target electron temperature $T_{e,t}$ [eV]", fontsize=12)
2288
+ ttl = (rf"Divertor two-point model: $R_0$={R0} m, "
2289
+ rf"$q_{{\parallel u}}$={q_par_u/1e3:.2f} GW/m$^2$")
2290
+ if np.isfinite(f_pwr_req):
2291
+ ttl += f", required dissipation = {f_pwr_req:.2f}"
2292
+ ax.set_title(ttl, fontsize=11)
2293
+ ax.grid(True, which="both", alpha=0.3)
2294
+ plt.tight_layout()
2295
+ _save_or_show(fig, save_dir, "run_divertor_two_point")
2296
+
2297
+
2217
2298
  # ---------------------------------------------------------------------------
2218
2299
  # A — Convenience wrapper
2219
2300
  # ---------------------------------------------------------------------------
@@ -2998,23 +3079,24 @@ def plot_run(
2998
3079
  save_dir: str | None = None,
2999
3080
  ) -> None:
3000
3081
  """
3001
- Render the run-specific figure set (10 figures).
3082
+ Render the run-specific figure set (11 figures).
3002
3083
 
3003
3084
  This is the subset called after each D0FUS run. It contains only the
3004
3085
  figures that depend on the current run configuration and results —
3005
3086
  no validation curves, no benchmarks, no scaling-law surveys.
3006
3087
 
3007
3088
  Figures produced:
3008
- [ 1/10] Tokamak LCFS comparison (with D0FUS overlay)
3009
- [ 2/10] Miller flux surfaces (run geometry)
3010
- [ 3/10] Shaping profiles κ(ρ), δ(ρ)
3011
- [ 4/10] Kinetic profiles n(ρ), T(ρ), p(ρ)
3012
- [ 5/10] Safety factor q(ρ) and current decomposition
3013
- [ 6/10] Radiation profiles
3014
- [ 7/10] TF coil side view
3015
- [ 8/10] CICC TF conductor
3016
- [ 9/10] CS cross-section
3017
- [10/10] CICC CS conductor
3089
+ [ 1/11] Tokamak LCFS comparison (with D0FUS overlay)
3090
+ [ 2/11] Miller flux surfaces (run geometry)
3091
+ [ 3/11] Shaping profiles κ(ρ), δ(ρ)
3092
+ [ 4/11] Kinetic profiles n(ρ), T(ρ), p(ρ)
3093
+ [ 5/11] Safety factor q(ρ) and current decomposition
3094
+ [ 6/11] Radiation profiles
3095
+ [ 7/11] Divertor two-point model (detachment vs SOL dissipation)
3096
+ [ 8/11] TF coil side view
3097
+ [ 9/11] CICC TF conductor
3098
+ [10/11] CS cross-section
3099
+ [11/11] CICC CS conductor
3018
3100
 
3019
3101
  Parameters
3020
3102
  ----------
@@ -3023,7 +3105,7 @@ def plot_run(
3023
3105
  If provided, figures are saved as PNG files.
3024
3106
  Pass ``None`` to display interactively.
3025
3107
  """
3026
- N = 10
3108
+ N = 11
3027
3109
 
3028
3110
  def _p(i, label):
3029
3111
  print(f" [{i:2d}/{N}] {label}")
@@ -3051,17 +3133,20 @@ def plot_run(
3051
3133
  _p(6, "Radiation profiles")
3052
3134
  plot_radiation_profile(run, save_dir=save_dir)
3053
3135
 
3136
+ _p(7, "Divertor two-point model")
3137
+ plot_divertor_two_point(run, save_dir=save_dir)
3138
+
3054
3139
  # ── Coils & conductors ────────────────────────────────────────────
3055
- _p(7, "TF coil side view")
3140
+ _p(8, "TF coil side view")
3056
3141
  plot_TF_side_view(run, save_dir=save_dir)
3057
3142
 
3058
- _p(8, "CICC TF conductor")
3143
+ _p(9, "CICC TF conductor")
3059
3144
  plot_CICC_cross_section(build_conductor_from_run(run, coil="TF"), save_dir=save_dir)
3060
3145
 
3061
- _p(9, "CS cross-section")
3146
+ _p(10, "CS cross-section")
3062
3147
  plot_CS_cross_section(run, save_dir=save_dir)
3063
3148
 
3064
- _p(10, "CICC CS conductor")
3149
+ _p(11, "CICC CS conductor")
3065
3150
  plot_CICC_cross_section(build_conductor_from_run(run, coil="CS"), save_dir=save_dir)
3066
3151
 
3067
3152
  print("Done.")
@@ -3308,11 +3393,11 @@ def plot_TF_benchmark_table(cfg=None, save_dir=None) -> None:
3308
3393
  "ARC": {"a": 1.10, "b": 0.89, "R0": 3.30, "σ": 1000e6, "T_op": 20.0,
3309
3394
  "B_max": 23.0, "n_TF": 1, "sc": "REBCO", "config": "Plug",
3310
3395
  "κ": 1.84, "I_cond": 50e3, "V_max": 10e3, "N_sub": 6,
3311
- "tau_h": 20, "J_wost": 120e6},
3396
+ "tau_h": 20, "J_wost": 200e6},
3312
3397
  "SPARC": {"a": 0.57, "b": 0.18, "R0": 1.85, "σ": 1000e6, "T_op": 20.0,
3313
3398
  "B_max": 20.0, "n_TF": 1, "sc": "REBCO", "config": "Bucking",
3314
- "κ": 1.75, "I_cond": 40.5e3,"V_max": 10e3, "N_sub": 4,
3315
- "tau_h": 20, "J_wost": 120e6},
3399
+ "κ": 1.75, "I_cond": 40.5e3,"V_max": 10e3, "N_sub": 6,
3400
+ "tau_h": 20, "J_wost": 200e6},
3316
3401
  }
3317
3402
 
3318
3403
  def _clean(val):
@@ -18,11 +18,13 @@ os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"
18
18
 
19
19
  import json
20
20
  import math
21
+ import multiprocessing
21
22
  import random
22
23
  import re
23
24
  import shutil
24
25
  import sys
25
26
  import time
27
+ import traceback
26
28
  import warnings
27
29
  import importlib
28
30
  from datetime import datetime
@@ -34,7 +36,7 @@ import numpy as np
34
36
  import pandas as pd
35
37
  import sympy as sp
36
38
  from typing import List, Tuple
37
- from dataclasses import dataclass
39
+ from dataclasses import dataclass, replace, asdict
38
40
 
39
41
  #%% Scipy - Optimization and Numerical Methods
40
42
 
@@ -66,11 +68,13 @@ import matplotlib.colors as mcolors
66
68
  import matplotlib.lines as mlines
67
69
  import matplotlib.patches as mpatches
68
70
  import matplotlib.cm as cm
71
+ from matplotlib.animation import FuncAnimation, PillowWriter
69
72
  from matplotlib.colors import Normalize
70
73
  from matplotlib.gridspec import GridSpec
71
74
  from matplotlib.patches import Circle, Rectangle, Patch
72
75
  from matplotlib.ticker import MultipleLocator
73
76
  from mpl_toolkits.axes_grid1 import make_axes_locatable
77
+ from mpl_toolkits.mplot3d import Axes3D # noqa: F401 (registers the '3d' projection)
74
78
  from pandas.plotting import table
75
79
  from tqdm import tqdm
76
80
  from dataclasses import dataclass
@@ -97,8 +97,10 @@ class GlobalConfig:
97
97
  Bmax_TF : float = 12.0 # Peak magnetic field on TF conductor [T]
98
98
  Bmax_CS_adm : float = 25.0 # Peak magnetic field allowed on the CS [T]
99
99
  P_fus : float = 2000.0 # Total fusion power [MW]
100
- Tbar : float = 14.0 # Volume-averaged ion temperature [keV]
101
- H : float = 1.0 # Confinement enhancement factor (H-factor) [-]
100
+ Tbar : float = 14.0 # Volume-averaged electron temperature T_e [keV]
101
+ tau_i_e : float = 1.0 # Ion-to-electron temperature ratio T_i/T_e [-]
102
+ # 1.0 -> single-temperature plasma (T_i = T_e).
103
+ # Prescribed: T_i(rho) = tau_i_e * T_e(rho),
102
104
 
103
105
  # ── 2. Physics and operation ─────────────────────────────────────────────
104
106
  Operation_mode : str = 'Pulsed' # 'Steady-State' or 'Pulsed'
@@ -122,7 +124,7 @@ class GlobalConfig:
122
124
  # 'Sauter' — uses LCFS values (κ_edge, δ_edge). Sauter, FED 112 (2016) Eq. 30.
123
125
  # 'ITER_1989' — uses ψ_N = 0.95 values (κ₉₅, δ₉₅). Uckan (1989), also Johner (2011).
124
126
  Option_q95 : str = 'Sauter' # q₉₅ formula: 'Sauter' (default) or 'ITER_1989'
125
- Option_Kappa : str = 'Wenninger' # Elongation model: 'Wenninger', 'Stambaugh', 'Freidberg', 'Manual'
127
+ Option_Kappa : str = 'Wenninger' # Elongation model: 'Wenninger', 'Stambaugh', 'Freidberg', 'Manual'
126
128
  κ_manual : float = 1.9 # Elongation (Manual mode only) [-]
127
129
 
128
130
  # ── 2a. Safety factor and current density profiles ────────────────────────
@@ -173,7 +175,15 @@ class GlobalConfig:
173
175
 
174
176
  # ── 4. Plasma composition ────────────────────────────────────────────────
175
177
  Atomic_mass : float = 2.5 # Volume-averaged ionic mass [AMU] (D-T: 2.5)
176
- Zeff : float = 2.0 # Effective plasma charge [-]
178
+ Zeff : float = None # Effective plasma charge [-].
179
+ # None -> computed self-consistently from the impurity
180
+ # inventory (impurity_species + f_imp_core) and the
181
+ # helium ash fraction (see _compute_Zeff_effective):
182
+ # Z_eff = 1 + 2 f_He - sum_j <Z_j> c_j
183
+ # + sum_j <Z_j>^2 c_j
184
+ # An empty impurity inventory therefore yields a clean
185
+ # D-T+He plasma with Z_eff ~ 1 + 2 f_He (~1.1).
186
+ # float -> manual override (legacy behaviour, e.g. Zeff = 2.0).
177
187
  r_synch : float = 0.5 # Synchrotron radiation wall reflectivity [-]
178
188
  C_Alpha : float = 7.0 # Helium ash dilution tuning parameter [-] (default taken from PROCESS)
179
189
  # Impurity line radiation (0D bulk-plasma estimate).
@@ -215,6 +225,26 @@ class GlobalConfig:
215
225
  Young_modul_Steel : float = 200e9 # Steel Young's modulus [Pa] (only used in CIRCE model)
216
226
  Young_modul_GF : float = 90e9 # S-glass fiber Young's modulus [Pa] (only used in CIRCE model)
217
227
  fatigue_CS : float = 2.0 # CS fatigue knockdown factor (pulsed & wedging only) [-]
228
+ # ── Steel allowable safety factors ───────────────────────────────────────
229
+ # Dimensionless knockdown applied to the steel mechanical allowable inside
230
+ # the TF and CS thickness solvers: σ_eff = σ_allowable / SF.
231
+ # Captures effects not represented by the idealised CICC area model:
232
+ # – Realistic winding-pack filling factor: round/square cables inside a
233
+ # rectangular envelope leave 15–30% of the WP cross-section occupied
234
+ # by ground insulation, inter-pancake plates, helium manifolds,
235
+ # terminations and assembly clearances. The structural jacket carries
236
+ # the electromagnetic load through a section reduced by this factor.
237
+ # – Stress concentrations at jacket corners and transitions (K_t ≈ 1.2–1.5).
238
+ # – Weld efficiency on jacket seams (η_w ≈ 0.85–0.90).
239
+ # – Manufacturing thickness tolerances (≈ ±5%).
240
+ # Default 1.0 preserves backward compatibility (raw material allowable used
241
+ # directly). Realistic engineering value for both TF and CS large-magnet
242
+ # CICC designs is ≈ 1.5 (composition of the four contributions above).
243
+ # The CS fatigue knockdown (fatigue_CS) multiplies on top of SF_CS, since
244
+ # the two factors address disjoint phenomena: primary static stress (SF)
245
+ # versus cyclic damage accumulation (fatigue_CS).
246
+ SF_TF : float = 1.0 # Safety factor on TF steel allowable [-] (suggested ≈ 1.5 for realistic CICC packing)
247
+ SF_CS : float = 1.0 # Safety factor on CS steel allowable [-] (suggested ≈ 1.5 for realistic CICC packing)
218
248
 
219
249
  # ── 7. TF coil engineering ───────────────────────────────────────────────
220
250
  Radial_build_model : str = 'refined' # Stress model: 'academic', 'refined', 'CIRCE'
@@ -257,7 +287,11 @@ class GlobalConfig:
257
287
  f_void : float = None # Interstitial void fraction in strand bundle [-]
258
288
  # None = auto: 0.33 (LTS) / 0.00 (REBCO).
259
289
  # Set explicitly (e.g. 0.30) to override.
260
- f_In : float = 0.15 # Insulation area fraction [-]
290
+ f_In : float = 0.05 # Insulation area fraction [-]
291
+ # Winding-pack overhead fraction in wost, treated exactly like the
292
+ # helium fraction: ground and inter-pancake insulation plus assembly
293
+ # clearances that occupy volume but carry neither current nor load.
294
+ f_gap : float = 0.15 # WP insulation + clearance fraction in wost [-]
261
295
 
262
296
  # Temperature margins above T_helium defining T_operating [K]
263
297
  # Conservative baseline: Corato et al., "Common operating values for DEMO…" (2016)
@@ -297,7 +331,7 @@ class GlobalConfig:
297
331
  # P_CD_total = P_LH + P_ECRH + P_NBI + P_ICRH (P_aux_input ignored).
298
332
  P_LH : float = 0.0 # LHCD plasma power [MW]
299
333
  P_ECRH : float = 0.0 # ECRH plasma power [MW]
300
- P_NBI : float = 0.0 # NBI injected power [MW] (5 % losses applied internally)
334
+ P_NBI : float = 0.0 # NBI injected power[MW]
301
335
  P_ICRH : float = 0.0 # ICRH plasma power [MW] (heating only, no current drive)
302
336
  # --- Steady-State mode: power fractions per source [-] ---
303
337
  # Used when Operation_mode = 'Steady-State' and CD_source = 'Multi'
@@ -325,6 +359,16 @@ class GlobalConfig:
325
359
  # ── 12. Plasma-facing components ─────────────────────────────────────────
326
360
  theta_deg : float = 2.7 # Divertor strike-point grazing angle [deg]
327
361
 
362
+ # Refined divertor exhaust (two-point model, Stangeby 2018). All used only
363
+ # in the post-convergence f_heat_two_point evaluation; they do not feed the
364
+ # core power balance.
365
+ f_n_sep : float = 0.20 # n_sep / volume-average density n̄ [-].
366
+ # Groth value taken from https://arxiv.org/pdf/2406.15693
367
+ f_cooling_div : float = 0.0 # SOL volumetric power-loss fraction [-] (2PM input)
368
+ f_mom_div : float = 0.0 # SOL volumetric momentum-loss fraction [-] (2PM input)
369
+ q_dep_limit : float = 5.0 # Tolerable deposited target heat flux [MW/m²]
370
+ flux_expansion: float = 1.0 # Target/upstream flux expansion R_t/R_u [-]
371
+
328
372
  # ── 13. Maintenance constraints ──────────────────────────────────────────
329
373
  ripple_adm : float = 0.01 # Admissible toroidal field ripple [-] (default 1%)
330
374
  L_min : float = 3.00 # Minimum toroidal maintenance access width [m]