d0fus 2.2.2__tar.gz → 2.3.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.2 → d0fus-2.3.2}/D0FUS.py +30 -3
- {d0fus-2.2.2 → d0fus-2.3.2}/D0FUS_BIB/D0FUS_figures.py +293 -19
- {d0fus-2.2.2 → d0fus-2.3.2}/D0FUS_BIB/D0FUS_import.py +5 -1
- {d0fus-2.2.2 → d0fus-2.3.2}/D0FUS_BIB/D0FUS_parameterization.py +51 -5
- {d0fus-2.2.2 → d0fus-2.3.2}/D0FUS_BIB/D0FUS_physical_functions.py +239 -29
- {d0fus-2.2.2 → d0fus-2.3.2}/D0FUS_BIB/D0FUS_radial_build_functions.py +258 -66
- d0fus-2.3.2/D0FUS_EXE/D0FUS_genetic.py +2987 -0
- {d0fus-2.2.2 → d0fus-2.3.2}/D0FUS_EXE/D0FUS_run.py +168 -28
- {d0fus-2.2.2 → d0fus-2.3.2}/D0FUS_EXE/D0FUS_scan.py +15 -5
- {d0fus-2.2.2 → d0fus-2.3.2}/PKG-INFO +1 -1
- {d0fus-2.2.2 → d0fus-2.3.2}/d0fus.egg-info/PKG-INFO +1 -1
- {d0fus-2.2.2 → d0fus-2.3.2}/pyproject.toml +1 -1
- d0fus-2.2.2/D0FUS_EXE/D0FUS_genetic.py +0 -1335
- {d0fus-2.2.2 → d0fus-2.3.2}/D0FUS_BIB/D0FUS_cost_data.py +0 -0
- {d0fus-2.2.2 → d0fus-2.3.2}/D0FUS_BIB/D0FUS_cost_functions.py +0 -0
- {d0fus-2.2.2 → d0fus-2.3.2}/LICENSE +0 -0
- {d0fus-2.2.2 → d0fus-2.3.2}/README.md +0 -0
- {d0fus-2.2.2 → d0fus-2.3.2}/d0fus.egg-info/SOURCES.txt +0 -0
- {d0fus-2.2.2 → d0fus-2.3.2}/d0fus.egg-info/dependency_links.txt +0 -0
- {d0fus-2.2.2 → d0fus-2.3.2}/d0fus.egg-info/entry_points.txt +0 -0
- {d0fus-2.2.2 → d0fus-2.3.2}/d0fus.egg-info/requires.txt +0 -0
- {d0fus-2.2.2 → d0fus-2.3.2}/d0fus.egg-info/top_level.txt +0 -0
- {d0fus-2.2.2 → d0fus-2.3.2}/setup.cfg +0 -0
|
@@ -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
|
-
|
|
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
|
-
║
|
|
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,
|
|
@@ -59,6 +60,7 @@ if __name__ != "__main__":
|
|
|
59
60
|
f_CS_ACAD, f_CS_refined, f_CS_CIRCE,
|
|
60
61
|
F_CIRCE0D, compute_von_mises_stress,
|
|
61
62
|
calculate_E_mag_TF,
|
|
63
|
+
Number_TF_coils,
|
|
62
64
|
)
|
|
63
65
|
from .D0FUS_parameterization import DEFAULT_CONFIG, E_ELEM
|
|
64
66
|
|
|
@@ -78,6 +80,7 @@ else:
|
|
|
78
80
|
f_He_fraction,
|
|
79
81
|
f_q_profile,
|
|
80
82
|
f_sigmav,
|
|
83
|
+
_two_point_core, M_F_DT,
|
|
81
84
|
)
|
|
82
85
|
from D0FUS_BIB.D0FUS_radial_build_functions import (
|
|
83
86
|
J_non_Cu_NbTi, J_non_Cu_Nb3Sn, J_non_Cu_REBCO,
|
|
@@ -88,6 +91,7 @@ else:
|
|
|
88
91
|
f_CS_ACAD, f_CS_refined, f_CS_CIRCE,
|
|
89
92
|
F_CIRCE0D, compute_von_mises_stress,
|
|
90
93
|
calculate_E_mag_TF,
|
|
94
|
+
Number_TF_coils,
|
|
91
95
|
)
|
|
92
96
|
from D0FUS_BIB.D0FUS_parameterization import DEFAULT_CONFIG, E_ELEM
|
|
93
97
|
|
|
@@ -2214,6 +2218,85 @@ def plot_radiation_profile(
|
|
|
2214
2218
|
_save_or_show(fig, save_dir, "run_radiation_profile")
|
|
2215
2219
|
|
|
2216
2220
|
|
|
2221
|
+
def plot_divertor_two_point(
|
|
2222
|
+
run: dict,
|
|
2223
|
+
n_pts: int = 300,
|
|
2224
|
+
save_dir: str | None = None,
|
|
2225
|
+
) -> None:
|
|
2226
|
+
"""
|
|
2227
|
+
Simple two-point-model view: target temperature versus SOL dissipation.
|
|
2228
|
+
|
|
2229
|
+
A single panel shows the target electron temperature T_et as a function of
|
|
2230
|
+
the SOL power-loss fraction f_cooling, at the converged upstream conditions
|
|
2231
|
+
(q_par_u, p_u read from the run). The dashed line is the detachment
|
|
2232
|
+
threshold T_et = 10 eV (Stangeby 2018). The flat plateau at high T_et is the
|
|
2233
|
+
attached sheath-limited branch where the two-point model no longer applies.
|
|
2234
|
+
The minimum dissipation required for target survival (Eq. 14) is given in
|
|
2235
|
+
the title and marked by the dotted vertical line.
|
|
2236
|
+
|
|
2237
|
+
Parameters
|
|
2238
|
+
----------
|
|
2239
|
+
run : dict D0FUS run output; reads the 'divertor' sub-dict produced
|
|
2240
|
+
by f_heat_two_point.
|
|
2241
|
+
n_pts : int Number of f_cooling samples.
|
|
2242
|
+
save_dir : str or None
|
|
2243
|
+
|
|
2244
|
+
References
|
|
2245
|
+
----------
|
|
2246
|
+
P.C. Stangeby, Plasma Phys. Control. Fusion 60 (2018) 044022.
|
|
2247
|
+
"""
|
|
2248
|
+
div = run.get("divertor", {}) or {}
|
|
2249
|
+
if not {"q_par_u", "T_eu", "n_sep"}.issubset(div):
|
|
2250
|
+
print(" [skip] no two-point-model divertor solution in run dict")
|
|
2251
|
+
return
|
|
2252
|
+
|
|
2253
|
+
q_par_u = div["q_par_u"] # [MW/m^2]
|
|
2254
|
+
T_eu = div["T_eu"] # [eV]
|
|
2255
|
+
n_sep = div["n_sep"] # [m^-3]
|
|
2256
|
+
f_c_op = div.get("f_cooling", 0.0)
|
|
2257
|
+
f_m_op = div.get("f_mom", 0.0)
|
|
2258
|
+
T_et_op = div.get("T_et", np.nan)
|
|
2259
|
+
f_pwr_req = div.get("f_pwr_loss_req", np.nan)
|
|
2260
|
+
R0 = run.get("R0", "?")
|
|
2261
|
+
|
|
2262
|
+
q_par_u_SI = q_par_u * 1e6
|
|
2263
|
+
p_u = 2.0 * n_sep * E_ELEM * T_eu
|
|
2264
|
+
|
|
2265
|
+
f_c = np.linspace(0.0, 0.995, n_pts)
|
|
2266
|
+
T_et = np.array([min(_two_point_core(q_par_u_SI, p_u, fc, f_m_op, 7.0, M_F_DT)[0],
|
|
2267
|
+
T_eu) for fc in f_c])
|
|
2268
|
+
|
|
2269
|
+
fig, ax = plt.subplots(figsize=(7, 4.5))
|
|
2270
|
+
ax.plot(f_c, T_et, color="#4477AA", lw=2.4)
|
|
2271
|
+
ax.axhline(10.0, color="#EE6677", lw=1.3, ls="--")
|
|
2272
|
+
ax.text(0.015, 11.0, "detachment (10 eV)", color="#EE6677",
|
|
2273
|
+
fontsize=9, va="bottom")
|
|
2274
|
+
if np.isfinite(f_pwr_req):
|
|
2275
|
+
ax.axvline(f_pwr_req, color="0.55", lw=1.0, ls=":")
|
|
2276
|
+
if np.isfinite(T_et_op):
|
|
2277
|
+
ax.plot([f_c_op], [T_et_op], "o", ms=9, color="k", zorder=5)
|
|
2278
|
+
near_left = f_c_op < 0.5
|
|
2279
|
+
near_top = T_et_op > 0.3 * T_eu
|
|
2280
|
+
ax.annotate(f"operating point\n$T_{{e,t}}$ = {T_et_op:.1f} eV",
|
|
2281
|
+
(f_c_op, T_et_op), textcoords="offset points",
|
|
2282
|
+
xytext=(14 if near_left else -12, -16 if near_top else 14),
|
|
2283
|
+
ha="left" if near_left else "right",
|
|
2284
|
+
va="top" if near_top else "bottom", fontsize=9)
|
|
2285
|
+
|
|
2286
|
+
ax.set_yscale("log")
|
|
2287
|
+
ax.set_xlim(0, 1)
|
|
2288
|
+
ax.set_xlabel(r"SOL power-loss fraction $f_{\rm cooling}$", fontsize=12)
|
|
2289
|
+
ax.set_ylabel(r"Target electron temperature $T_{e,t}$ [eV]", fontsize=12)
|
|
2290
|
+
ttl = (rf"Divertor two-point model: $R_0$={R0} m, "
|
|
2291
|
+
rf"$q_{{\parallel u}}$={q_par_u/1e3:.2f} GW/m$^2$")
|
|
2292
|
+
if np.isfinite(f_pwr_req):
|
|
2293
|
+
ttl += f", required dissipation = {f_pwr_req:.2f}"
|
|
2294
|
+
ax.set_title(ttl, fontsize=11)
|
|
2295
|
+
ax.grid(True, which="both", alpha=0.3)
|
|
2296
|
+
plt.tight_layout()
|
|
2297
|
+
_save_or_show(fig, save_dir, "run_divertor_two_point")
|
|
2298
|
+
|
|
2299
|
+
|
|
2217
2300
|
# ---------------------------------------------------------------------------
|
|
2218
2301
|
# A — Convenience wrapper
|
|
2219
2302
|
# ---------------------------------------------------------------------------
|
|
@@ -2998,23 +3081,24 @@ def plot_run(
|
|
|
2998
3081
|
save_dir: str | None = None,
|
|
2999
3082
|
) -> None:
|
|
3000
3083
|
"""
|
|
3001
|
-
Render the run-specific figure set (
|
|
3084
|
+
Render the run-specific figure set (11 figures).
|
|
3002
3085
|
|
|
3003
3086
|
This is the subset called after each D0FUS run. It contains only the
|
|
3004
3087
|
figures that depend on the current run configuration and results —
|
|
3005
3088
|
no validation curves, no benchmarks, no scaling-law surveys.
|
|
3006
3089
|
|
|
3007
3090
|
Figures produced:
|
|
3008
|
-
[ 1/
|
|
3009
|
-
[ 2/
|
|
3010
|
-
[ 3/
|
|
3011
|
-
[ 4/
|
|
3012
|
-
[ 5/
|
|
3013
|
-
[ 6/
|
|
3014
|
-
[ 7/
|
|
3015
|
-
[ 8/
|
|
3016
|
-
[ 9/
|
|
3017
|
-
[10/
|
|
3091
|
+
[ 1/11] Tokamak LCFS comparison (with D0FUS overlay)
|
|
3092
|
+
[ 2/11] Miller flux surfaces (run geometry)
|
|
3093
|
+
[ 3/11] Shaping profiles κ(ρ), δ(ρ)
|
|
3094
|
+
[ 4/11] Kinetic profiles n(ρ), T(ρ), p(ρ)
|
|
3095
|
+
[ 5/11] Safety factor q(ρ) and current decomposition
|
|
3096
|
+
[ 6/11] Radiation profiles
|
|
3097
|
+
[ 7/11] Divertor two-point model (detachment vs SOL dissipation)
|
|
3098
|
+
[ 8/11] TF coil side view
|
|
3099
|
+
[ 9/11] CICC TF conductor
|
|
3100
|
+
[10/11] CS cross-section
|
|
3101
|
+
[11/11] CICC CS conductor
|
|
3018
3102
|
|
|
3019
3103
|
Parameters
|
|
3020
3104
|
----------
|
|
@@ -3023,7 +3107,7 @@ def plot_run(
|
|
|
3023
3107
|
If provided, figures are saved as PNG files.
|
|
3024
3108
|
Pass ``None`` to display interactively.
|
|
3025
3109
|
"""
|
|
3026
|
-
N =
|
|
3110
|
+
N = 11
|
|
3027
3111
|
|
|
3028
3112
|
def _p(i, label):
|
|
3029
3113
|
print(f" [{i:2d}/{N}] {label}")
|
|
@@ -3051,17 +3135,20 @@ def plot_run(
|
|
|
3051
3135
|
_p(6, "Radiation profiles")
|
|
3052
3136
|
plot_radiation_profile(run, save_dir=save_dir)
|
|
3053
3137
|
|
|
3138
|
+
_p(7, "Divertor two-point model")
|
|
3139
|
+
plot_divertor_two_point(run, save_dir=save_dir)
|
|
3140
|
+
|
|
3054
3141
|
# ── Coils & conductors ────────────────────────────────────────────
|
|
3055
|
-
_p(
|
|
3142
|
+
_p(8, "TF coil side view")
|
|
3056
3143
|
plot_TF_side_view(run, save_dir=save_dir)
|
|
3057
3144
|
|
|
3058
|
-
_p(
|
|
3145
|
+
_p(9, "CICC TF conductor")
|
|
3059
3146
|
plot_CICC_cross_section(build_conductor_from_run(run, coil="TF"), save_dir=save_dir)
|
|
3060
3147
|
|
|
3061
|
-
_p(
|
|
3148
|
+
_p(10, "CS cross-section")
|
|
3062
3149
|
plot_CS_cross_section(run, save_dir=save_dir)
|
|
3063
3150
|
|
|
3064
|
-
_p(
|
|
3151
|
+
_p(11, "CICC CS conductor")
|
|
3065
3152
|
plot_CICC_cross_section(build_conductor_from_run(run, coil="CS"), save_dir=save_dir)
|
|
3066
3153
|
|
|
3067
3154
|
print("Done.")
|
|
@@ -3308,11 +3395,11 @@ def plot_TF_benchmark_table(cfg=None, save_dir=None) -> None:
|
|
|
3308
3395
|
"ARC": {"a": 1.10, "b": 0.89, "R0": 3.30, "σ": 1000e6, "T_op": 20.0,
|
|
3309
3396
|
"B_max": 23.0, "n_TF": 1, "sc": "REBCO", "config": "Plug",
|
|
3310
3397
|
"κ": 1.84, "I_cond": 50e3, "V_max": 10e3, "N_sub": 6,
|
|
3311
|
-
"tau_h": 20, "J_wost":
|
|
3398
|
+
"tau_h": 20, "J_wost": 200e6},
|
|
3312
3399
|
"SPARC": {"a": 0.57, "b": 0.18, "R0": 1.85, "σ": 1000e6, "T_op": 20.0,
|
|
3313
3400
|
"B_max": 20.0, "n_TF": 1, "sc": "REBCO", "config": "Bucking",
|
|
3314
|
-
"κ": 1.75, "I_cond": 40.5e3,"V_max": 10e3, "N_sub":
|
|
3315
|
-
"tau_h": 20, "J_wost":
|
|
3401
|
+
"κ": 1.75, "I_cond": 40.5e3,"V_max": 10e3, "N_sub": 6,
|
|
3402
|
+
"tau_h": 20, "J_wost": 200e6},
|
|
3316
3403
|
}
|
|
3317
3404
|
|
|
3318
3405
|
def _clean(val):
|
|
@@ -4193,6 +4280,193 @@ def plot_CICC_cross_section(
|
|
|
4193
4280
|
# Stand-alone execution — smoke test
|
|
4194
4281
|
# =============================================================================
|
|
4195
4282
|
|
|
4283
|
+
|
|
4284
|
+
# =============================================================================
|
|
4285
|
+
# 7. TF coil number - ripple and port-access constraints
|
|
4286
|
+
# =============================================================================
|
|
4287
|
+
|
|
4288
|
+
# D0FUS-consistent palette (matches the hex codes used elsewhere in this file)
|
|
4289
|
+
_RIP_C_PLASMA = "#f4a582" # plasma fill
|
|
4290
|
+
_RIP_C_COIL = "#2166ac" # TF coil footprint
|
|
4291
|
+
_RIP_C_ACCESS = "#4dac26" # maintenance / heating access sector
|
|
4292
|
+
|
|
4293
|
+
|
|
4294
|
+
def plot_tf_ripple(
|
|
4295
|
+
R0: float = 6.2,
|
|
4296
|
+
a: float = 2.0,
|
|
4297
|
+
b: float = 1.2,
|
|
4298
|
+
ripple_adm: float = 0.01,
|
|
4299
|
+
L_min: float = 3.6,
|
|
4300
|
+
grid: int = 460,
|
|
4301
|
+
save_dir: str | None = None,
|
|
4302
|
+
) -> None:
|
|
4303
|
+
"""
|
|
4304
|
+
Top-view map of the toroidal field magnitude produced by the discrete TF
|
|
4305
|
+
coils, illustrating the field ripple.
|
|
4306
|
+
|
|
4307
|
+
The coil number, ripple and outboard standoff (N_coil, ripple, Delta_ext)
|
|
4308
|
+
are taken from the package routine ``Number_TF_coils`` so the figure stays
|
|
4309
|
+
consistent with the D0FUS sizing. Each coil is modelled as a pair of
|
|
4310
|
+
infinite straight filaments (inner leg at R0 - a - b carrying +I, outer leg
|
|
4311
|
+
at R0 + a + b + Delta_ext carrying -I, at the same toroidal angle); the field
|
|
4312
|
+
magnitude |B| is evaluated on a Cartesian grid in the toroidal mid-plane.
|
|
4313
|
+
The prefactor mu0*I/(2*pi) is dropped, so |B| is in arbitrary units. The
|
|
4314
|
+
iso-|B| contours scallop near the coils: that scalloping is the ripple.
|
|
4315
|
+
|
|
4316
|
+
Parameters
|
|
4317
|
+
----------
|
|
4318
|
+
R0, a : float Major and minor radius [m].
|
|
4319
|
+
b : float Aggregated inboard build (FW + blanket + shield + gaps) [m].
|
|
4320
|
+
ripple_adm : float Admissible ripple fraction (default 1 %).
|
|
4321
|
+
L_min : float Minimum toroidal access per coil [m] (default 3.6 m, the
|
|
4322
|
+
ITER-reproducing value; the manuscript also uses 3.0 m).
|
|
4323
|
+
grid : int Number of grid points per axis for the field map.
|
|
4324
|
+
save_dir : str or None
|
|
4325
|
+
|
|
4326
|
+
References
|
|
4327
|
+
----------
|
|
4328
|
+
Wesson, Tokamaks, 4th ed., p.169 - ripple model.
|
|
4329
|
+
Goldston & Rutherford, Introduction to Plasma Physics, IOP (1995), ch.14.
|
|
4330
|
+
"""
|
|
4331
|
+
n_coil, ripple_sel, Delta_ext = Number_TF_coils(R0, a, b, ripple_adm, L_min)
|
|
4332
|
+
R_in = R0 - a - b
|
|
4333
|
+
R_out = R0 + a + b + Delta_ext
|
|
4334
|
+
theta = 2.0 * np.pi * np.arange(n_coil) / n_coil
|
|
4335
|
+
|
|
4336
|
+
# filament positions (inner + outer legs) and signed currents
|
|
4337
|
+
fx = np.concatenate([R_in * np.cos(theta), R_out * np.cos(theta)])
|
|
4338
|
+
fy = np.concatenate([R_in * np.sin(theta), R_out * np.sin(theta)])
|
|
4339
|
+
cur = np.concatenate([np.ones(n_coil), -np.ones(n_coil)])
|
|
4340
|
+
|
|
4341
|
+
# |B| on a Cartesian grid (loop over filaments to limit memory)
|
|
4342
|
+
L = R_out + 0.7
|
|
4343
|
+
gx = np.linspace(-L, L, grid)
|
|
4344
|
+
X, Y = np.meshgrid(gx, gx)
|
|
4345
|
+
Bx = np.zeros_like(X)
|
|
4346
|
+
By = np.zeros_like(X)
|
|
4347
|
+
dmin = np.full(X.shape, np.inf)
|
|
4348
|
+
for xi, yi, ci in zip(fx, fy, cur):
|
|
4349
|
+
dx = X - xi
|
|
4350
|
+
dy = Y - yi
|
|
4351
|
+
d2 = dx * dx + dy * dy
|
|
4352
|
+
Bx += ci * (-dy) / d2
|
|
4353
|
+
By += ci * (dx) / d2
|
|
4354
|
+
dmin = np.minimum(dmin, np.sqrt(d2))
|
|
4355
|
+
rr = np.sqrt(X ** 2 + Y ** 2)
|
|
4356
|
+
Bmag = np.sqrt(Bx ** 2 + By ** 2)
|
|
4357
|
+
# cap the colour scale to the plasma-region field, then hide near-wire
|
|
4358
|
+
# spikes and the central column for legibility
|
|
4359
|
+
vmax = np.percentile(Bmag[(rr > R0 - a) & (rr < R0 + a) & (dmin > 0.22)], 99)
|
|
4360
|
+
Bmag = np.ma.array(Bmag, mask=(dmin < 0.22) | (rr < R0 - a))
|
|
4361
|
+
|
|
4362
|
+
th = np.linspace(0, 2 * np.pi, 400)
|
|
4363
|
+
fig, ax = plt.subplots(figsize=(6.4, 5.9))
|
|
4364
|
+
ax.set_aspect("equal")
|
|
4365
|
+
pcm = ax.pcolormesh(X, Y, Bmag, shading="auto", cmap="magma", vmin=0, vmax=vmax)
|
|
4366
|
+
ax.contour(X, Y, Bmag, levels=np.linspace(0.35 * vmax, vmax, 7),
|
|
4367
|
+
colors="white", linewidths=0.45, alpha=0.55)
|
|
4368
|
+
ax.add_patch(mpatches.Circle((0, 0), R0 - a, facecolor="0.75",
|
|
4369
|
+
edgecolor="0.5", lw=0.8, zorder=4))
|
|
4370
|
+
ax.text(0, 0, "central\ncolumn", ha="center", va="center", fontsize=8,
|
|
4371
|
+
color="0.25", zorder=5)
|
|
4372
|
+
ax.plot((R0 + a) * np.cos(th), (R0 + a) * np.sin(th), color="cyan",
|
|
4373
|
+
lw=1.3, ls="--", alpha=0.9, zorder=4)
|
|
4374
|
+
ax.plot(R_out * np.cos(th), R_out * np.sin(th), color="white", lw=0.7,
|
|
4375
|
+
ls=":", alpha=0.5, zorder=4)
|
|
4376
|
+
ax.plot(R_out * np.cos(theta), R_out * np.sin(theta), "o", color="white",
|
|
4377
|
+
ms=5, mec="k", mew=0.5, zorder=5)
|
|
4378
|
+
ax.text(0, (R0 + a) + 0.15, "plasma edge", color="cyan", fontsize=8,
|
|
4379
|
+
ha="center", va="bottom", zorder=6)
|
|
4380
|
+
ax.text(-L + 0.3, L - 0.5,
|
|
4381
|
+
rf"$\delta_\mathrm{{ripple}} = {ripple_sel * 100:.2f}\,\%$",
|
|
4382
|
+
color="white", fontsize=11, va="top")
|
|
4383
|
+
ax.set_xlim(-L, L)
|
|
4384
|
+
ax.set_ylim(-L, L)
|
|
4385
|
+
ax.set_xlabel("x [m]", fontsize=12)
|
|
4386
|
+
ax.set_ylabel("y [m]", fontsize=12)
|
|
4387
|
+
ax.set_title(rf"Toroidal field magnitude, top view ($N_\mathrm{{coil}} = {n_coil}$)",
|
|
4388
|
+
fontsize=11)
|
|
4389
|
+
fig.colorbar(pcm, ax=ax, shrink=0.85, label=r"$|B|$ (arb. units)")
|
|
4390
|
+
plt.tight_layout()
|
|
4391
|
+
_save_or_show(fig, save_dir, "tf_ripple")
|
|
4392
|
+
|
|
4393
|
+
|
|
4394
|
+
def plot_port_access(
|
|
4395
|
+
R0: float = 6.2,
|
|
4396
|
+
a: float = 2.0,
|
|
4397
|
+
b: float = 1.2,
|
|
4398
|
+
ripple_adm: float = 0.01,
|
|
4399
|
+
L_min: float = 3.6,
|
|
4400
|
+
save_dir: str | None = None,
|
|
4401
|
+
) -> None:
|
|
4402
|
+
"""
|
|
4403
|
+
Clean top view of the tokamak showing the TF coils and the angular sectors
|
|
4404
|
+
left free between them for maintenance and heating access.
|
|
4405
|
+
|
|
4406
|
+
The coil number and outboard standoff come from ``Number_TF_coils`` (same
|
|
4407
|
+
call as plot_tf_ripple), so the layout matches the D0FUS sizing.
|
|
4408
|
+
|
|
4409
|
+
Parameters
|
|
4410
|
+
----------
|
|
4411
|
+
R0, a, b : float Plasma geometry and aggregated inboard build [m].
|
|
4412
|
+
ripple_adm : float Admissible ripple fraction (for the N_coil selection).
|
|
4413
|
+
L_min : float Minimum toroidal pitch per coil for maintenance [m].
|
|
4414
|
+
save_dir : str or None
|
|
4415
|
+
"""
|
|
4416
|
+
n_coil, ripple_sel, Delta_ext = Number_TF_coils(R0, a, b, ripple_adm, L_min)
|
|
4417
|
+
R_out = R0 + a + b + Delta_ext
|
|
4418
|
+
theta_deg = np.degrees(2.0 * np.pi * np.arange(n_coil) / n_coil)
|
|
4419
|
+
pitch = 360.0 / n_coil
|
|
4420
|
+
coil_hw = 0.30 * pitch # illustrative coil half-width
|
|
4421
|
+
band = (R_out + 0.6) - (R0 + a)
|
|
4422
|
+
|
|
4423
|
+
fig, ax = plt.subplots(figsize=(6.2, 6.0))
|
|
4424
|
+
ax.set_aspect("equal")
|
|
4425
|
+
|
|
4426
|
+
# plasma annulus + central column
|
|
4427
|
+
ax.add_patch(mpatches.Wedge((0, 0), R0 + a, 0, 360, width=2 * a,
|
|
4428
|
+
facecolor=_RIP_C_PLASMA, alpha=0.45, lw=0, zorder=1))
|
|
4429
|
+
ax.add_patch(mpatches.Circle((0, 0), R0 - a, facecolor="0.85",
|
|
4430
|
+
edgecolor="0.6", lw=0.8, zorder=2))
|
|
4431
|
+
ax.text(0, 0, "central\ncolumn", ha="center", va="center", fontsize=8.5,
|
|
4432
|
+
color="0.3", zorder=3)
|
|
4433
|
+
ax.text(0, -R0, "plasma", ha="center", va="center", fontsize=9,
|
|
4434
|
+
color="#9c4a2f", zorder=3,
|
|
4435
|
+
bbox=dict(boxstyle="round,pad=0.15", fc="white", ec="none", alpha=0.7))
|
|
4436
|
+
|
|
4437
|
+
# access corridors (gaps) and coil footprints
|
|
4438
|
+
for t in theta_deg:
|
|
4439
|
+
ax.add_patch(mpatches.Wedge((0, 0), R_out + 0.6, t + coil_hw,
|
|
4440
|
+
t + pitch - coil_hw, width=band,
|
|
4441
|
+
facecolor=_RIP_C_ACCESS, alpha=0.16, lw=0,
|
|
4442
|
+
zorder=2))
|
|
4443
|
+
ax.add_patch(mpatches.Wedge((0, 0), R_out + 0.6, t - coil_hw,
|
|
4444
|
+
t + coil_hw, width=1.7,
|
|
4445
|
+
facecolor=_RIP_C_COIL, edgecolor="none",
|
|
4446
|
+
alpha=0.95, zorder=3))
|
|
4447
|
+
|
|
4448
|
+
# single neutral label on one access sector
|
|
4449
|
+
tlab = theta_deg[0] + pitch / 2
|
|
4450
|
+
r_lab = R0 + a + 0.9
|
|
4451
|
+
ax.annotate("maintenance /\nheating access",
|
|
4452
|
+
xy=(r_lab * np.cos(np.radians(tlab)), r_lab * np.sin(np.radians(tlab))),
|
|
4453
|
+
xytext=(1.12 * R_out, 1.02 * R_out), fontsize=9, color="#2f6b16",
|
|
4454
|
+
ha="left", arrowprops=dict(arrowstyle="->", color="#2f6b16", lw=1.0))
|
|
4455
|
+
|
|
4456
|
+
ax.plot([], [], color=_RIP_C_COIL, lw=6, label=rf"TF coils ($N = {n_coil}$)")
|
|
4457
|
+
ax.plot([], [], color=_RIP_C_ACCESS, lw=6, alpha=0.4, label="access sectors")
|
|
4458
|
+
lim = R_out + 1.6
|
|
4459
|
+
ax.set_xlim(-lim, lim)
|
|
4460
|
+
ax.set_ylim(-lim, lim)
|
|
4461
|
+
ax.set_xlabel("x [m]", fontsize=12)
|
|
4462
|
+
ax.set_ylabel("y [m]", fontsize=12)
|
|
4463
|
+
ax.set_title(rf"TF coils and maintenance access, top view ($N_\mathrm{{coil}} = {n_coil}$)",
|
|
4464
|
+
fontsize=11)
|
|
4465
|
+
ax.legend(fontsize=9, loc="upper left")
|
|
4466
|
+
plt.tight_layout()
|
|
4467
|
+
_save_or_show(fig, save_dir, "port_access")
|
|
4468
|
+
|
|
4469
|
+
|
|
4196
4470
|
if __name__ == "__main__":
|
|
4197
4471
|
|
|
4198
4472
|
parser = argparse.ArgumentParser(
|
|
@@ -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,7 +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
|
|
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),
|
|
101
104
|
H : float = 1.0 # Confinement enhancement factor (H-factor) [-]
|
|
102
105
|
|
|
103
106
|
# ── 2. Physics and operation ─────────────────────────────────────────────
|
|
@@ -122,7 +125,7 @@ class GlobalConfig:
|
|
|
122
125
|
# 'Sauter' — uses LCFS values (κ_edge, δ_edge). Sauter, FED 112 (2016) Eq. 30.
|
|
123
126
|
# 'ITER_1989' — uses ψ_N = 0.95 values (κ₉₅, δ₉₅). Uckan (1989), also Johner (2011).
|
|
124
127
|
Option_q95 : str = 'Sauter' # q₉₅ formula: 'Sauter' (default) or 'ITER_1989'
|
|
125
|
-
Option_Kappa : str = 'Wenninger'
|
|
128
|
+
Option_Kappa : str = 'Wenninger' # Elongation model: 'Wenninger', 'Stambaugh', 'Freidberg', 'Manual'
|
|
126
129
|
κ_manual : float = 1.9 # Elongation (Manual mode only) [-]
|
|
127
130
|
|
|
128
131
|
# ── 2a. Safety factor and current density profiles ────────────────────────
|
|
@@ -169,11 +172,20 @@ class GlobalConfig:
|
|
|
169
172
|
# kink_parameter='q95' → q_limit ≈ 3.0–3.5 (ITER/EU-DEMO practice)
|
|
170
173
|
q_limit : float = 3.0 # Kink safety factor threshold [-]
|
|
171
174
|
Greenwald_limit : float = 1.0 # Greenwald density fraction limit [-]
|
|
175
|
+
Ip_limit : float = None # Upper bound on plasma current [MA]; None = no ceiling (GA)
|
|
172
176
|
ms : float = 0.3 # Vertical stability margin parameter [-]
|
|
173
177
|
|
|
174
178
|
# ── 4. Plasma composition ────────────────────────────────────────────────
|
|
175
179
|
Atomic_mass : float = 2.5 # Volume-averaged ionic mass [AMU] (D-T: 2.5)
|
|
176
|
-
Zeff : float =
|
|
180
|
+
Zeff : float = None # Effective plasma charge [-].
|
|
181
|
+
# None -> computed self-consistently from the impurity
|
|
182
|
+
# inventory (impurity_species + f_imp_core) and the
|
|
183
|
+
# helium ash fraction (see _compute_Zeff_effective):
|
|
184
|
+
# Z_eff = 1 + 2 f_He - sum_j <Z_j> c_j
|
|
185
|
+
# + sum_j <Z_j>^2 c_j
|
|
186
|
+
# An empty impurity inventory therefore yields a clean
|
|
187
|
+
# D-T+He plasma with Z_eff ~ 1 + 2 f_He (~1.1).
|
|
188
|
+
# float -> manual override (legacy behaviour, e.g. Zeff = 2.0).
|
|
177
189
|
r_synch : float = 0.5 # Synchrotron radiation wall reflectivity [-]
|
|
178
190
|
C_Alpha : float = 7.0 # Helium ash dilution tuning parameter [-] (default taken from PROCESS)
|
|
179
191
|
# Impurity line radiation (0D bulk-plasma estimate).
|
|
@@ -215,6 +227,26 @@ class GlobalConfig:
|
|
|
215
227
|
Young_modul_Steel : float = 200e9 # Steel Young's modulus [Pa] (only used in CIRCE model)
|
|
216
228
|
Young_modul_GF : float = 90e9 # S-glass fiber Young's modulus [Pa] (only used in CIRCE model)
|
|
217
229
|
fatigue_CS : float = 2.0 # CS fatigue knockdown factor (pulsed & wedging only) [-]
|
|
230
|
+
# ── Steel allowable safety factors ───────────────────────────────────────
|
|
231
|
+
# Dimensionless knockdown applied to the steel mechanical allowable inside
|
|
232
|
+
# the TF and CS thickness solvers: σ_eff = σ_allowable / SF.
|
|
233
|
+
# Captures effects not represented by the idealised CICC area model:
|
|
234
|
+
# – Realistic winding-pack filling factor: round/square cables inside a
|
|
235
|
+
# rectangular envelope leave 15–30% of the WP cross-section occupied
|
|
236
|
+
# by ground insulation, inter-pancake plates, helium manifolds,
|
|
237
|
+
# terminations and assembly clearances. The structural jacket carries
|
|
238
|
+
# the electromagnetic load through a section reduced by this factor.
|
|
239
|
+
# – Stress concentrations at jacket corners and transitions (K_t ≈ 1.2–1.5).
|
|
240
|
+
# – Weld efficiency on jacket seams (η_w ≈ 0.85–0.90).
|
|
241
|
+
# – Manufacturing thickness tolerances (≈ ±5%).
|
|
242
|
+
# Default 1.0 preserves backward compatibility (raw material allowable used
|
|
243
|
+
# directly). Realistic engineering value for both TF and CS large-magnet
|
|
244
|
+
# CICC designs is ≈ 1.5 (composition of the four contributions above).
|
|
245
|
+
# The CS fatigue knockdown (fatigue_CS) multiplies on top of SF_CS, since
|
|
246
|
+
# the two factors address disjoint phenomena: primary static stress (SF)
|
|
247
|
+
# versus cyclic damage accumulation (fatigue_CS).
|
|
248
|
+
SF_TF : float = 1.0 # Safety factor on TF steel allowable [-] (suggested ≈ 1.5 for realistic CICC packing)
|
|
249
|
+
SF_CS : float = 1.0 # Safety factor on CS steel allowable [-] (suggested ≈ 1.5 for realistic CICC packing)
|
|
218
250
|
|
|
219
251
|
# ── 7. TF coil engineering ───────────────────────────────────────────────
|
|
220
252
|
Radial_build_model : str = 'refined' # Stress model: 'academic', 'refined', 'CIRCE'
|
|
@@ -257,7 +289,11 @@ class GlobalConfig:
|
|
|
257
289
|
f_void : float = None # Interstitial void fraction in strand bundle [-]
|
|
258
290
|
# None = auto: 0.33 (LTS) / 0.00 (REBCO).
|
|
259
291
|
# Set explicitly (e.g. 0.30) to override.
|
|
260
|
-
f_In : float = 0.
|
|
292
|
+
f_In : float = 0.05 # Insulation area fraction [-]
|
|
293
|
+
# Winding-pack overhead fraction in wost, treated exactly like the
|
|
294
|
+
# helium fraction: ground and inter-pancake insulation plus assembly
|
|
295
|
+
# clearances that occupy volume but carry neither current nor load.
|
|
296
|
+
f_gap : float = 0.15 # WP insulation + clearance fraction in wost [-]
|
|
261
297
|
|
|
262
298
|
# Temperature margins above T_helium defining T_operating [K]
|
|
263
299
|
# Conservative baseline: Corato et al., "Common operating values for DEMO…" (2016)
|
|
@@ -297,7 +333,7 @@ class GlobalConfig:
|
|
|
297
333
|
# P_CD_total = P_LH + P_ECRH + P_NBI + P_ICRH (P_aux_input ignored).
|
|
298
334
|
P_LH : float = 0.0 # LHCD plasma power [MW]
|
|
299
335
|
P_ECRH : float = 0.0 # ECRH plasma power [MW]
|
|
300
|
-
P_NBI : float = 0.0 # NBI injected power
|
|
336
|
+
P_NBI : float = 0.0 # NBI injected power[MW]
|
|
301
337
|
P_ICRH : float = 0.0 # ICRH plasma power [MW] (heating only, no current drive)
|
|
302
338
|
# --- Steady-State mode: power fractions per source [-] ---
|
|
303
339
|
# Used when Operation_mode = 'Steady-State' and CD_source = 'Multi'
|
|
@@ -325,6 +361,16 @@ class GlobalConfig:
|
|
|
325
361
|
# ── 12. Plasma-facing components ─────────────────────────────────────────
|
|
326
362
|
theta_deg : float = 2.7 # Divertor strike-point grazing angle [deg]
|
|
327
363
|
|
|
364
|
+
# Refined divertor exhaust (two-point model, Stangeby 2018). All used only
|
|
365
|
+
# in the post-convergence f_heat_two_point evaluation; they do not feed the
|
|
366
|
+
# core power balance.
|
|
367
|
+
f_n_sep : float = 0.20 # n_sep / volume-average density n̄ [-].
|
|
368
|
+
# Groth value taken from https://arxiv.org/pdf/2406.15693
|
|
369
|
+
f_cooling_div : float = 0.0 # SOL volumetric power-loss fraction [-] (2PM input)
|
|
370
|
+
f_mom_div : float = 0.0 # SOL volumetric momentum-loss fraction [-] (2PM input)
|
|
371
|
+
q_dep_limit : float = 5.0 # Tolerable deposited target heat flux [MW/m²]
|
|
372
|
+
flux_expansion: float = 1.0 # Target/upstream flux expansion R_t/R_u [-]
|
|
373
|
+
|
|
328
374
|
# ── 13. Maintenance constraints ──────────────────────────────────────────
|
|
329
375
|
ripple_adm : float = 0.01 # Admissible toroidal field ripple [-] (default 1%)
|
|
330
376
|
L_min : float = 3.00 # Minimum toroidal maintenance access width [m]
|