diff --git a/documentation/source/eng-models/divertor.md b/documentation/source/eng-models/divertor.md index 7eb1d26c5d..b8bc20f4f6 100644 --- a/documentation/source/eng-models/divertor.md +++ b/documentation/source/eng-models/divertor.md @@ -169,21 +169,11 @@ The interactive graph below can be used to investigate how changing the key para !!! Note "" `i_div_heat_load == 2` -A divertor heat flux model is provided in Appendix A.II. of [^2]. This uses the Eich scaling -[^3] and S-factor [^4] to calculate the SOL width at the outboard divertor, mapped to the midplane: +A divertor heat flux model is provided in Appendix A.II. of [^wade_21]. This model originally uses the [Eich scaling](../physics-models/plasma_scrape_off_layer.md#eich-2013-model--calculate_eich2013_sol_power_decay_length) +and the [Scrabosio S-factor](../physics-models/plasma_scrape_off_layer.md#scarabosio-2015--calculate_scarabosio2015_power_spreading_factor) to calculate the SOL width at the outboard divertor, mapped to the midplane: $$ -\lambda_{int} = \lambda_{q,Eich} + 1.64S -$$ - -where - -$$ -\lambda_{q,Eich} = 1.35 \, P_{\mathrm{SOL}}^{-0.02} \, R_{o}^{0.04} \, B_{p}^{-0.92} \, \epsilon^{0.42} -$$ - -$$ -S = 0.12(n_{e,mid}/10^{19})^{-0.02} \, P_{\mathrm{SOL}}^{-0.21} \, R_{o}^{0.71} \, B_{p}^{-0.82}. +\lambda_{\text{int}} = \lambda_{\text{q,Eich}} + 1.64S $$ This is then used to calculate the wetted area in the divertor @@ -196,44 +186,40 @@ where $N_{div}$ is the number of divertors (1 or 2), $F_{exp}$ is the relevant f $\theta_{div}$ is the tilt of the separatrix relative to the target in the poloidal plane, and has the form $$ -\theta_{div} = \sin^{-1} [(1+1/\alpha_{div}^{2})\sin\beta_{div}], +\theta_{\text{div}} = \sin^{-1} [(1+1/\alpha_{\text{div}}^{2})\sin(\beta_{\text{div}})], $$ where $$ -\alpha_{div} = F_{exp}\alpha_{mid} +\alpha_{\text{div}} = F_{\text{exp}}\alpha_{\text{mid}} $$ $$ -\alpha_{mid} = \tan^{-1}\frac{B_{p,mid}}{B_{T,mid}} +\alpha_{\text{mid}} = \tan^{-1}\frac{B_{\text{p,mid}}}{B_{\text{T,mid}}} $$ -where $B_{p,mid}$ and $B_{T,mid}$ are the poloidal and toroidal fields on the outer midplane. The +where $B_{\text{p,mid}}$ and $B_{\text{T,mid}}$ are the poloidal and toroidal fields on the outer midplane. The parameter $\beta_{div}$ is the angle of incidence between the field line and the target. -The divertor heat flux in $\mathrm{MW}/\mathrm{m^{2}}$ is then +The divertor heat flux in $[\mathrm{MW}/\mathrm{m^{2}}]$ is then $$ -q_{div} = P_{\mathrm{SOL}}(1-f_{rad,div})/A_{wetted} +q_{\text{div}} = P_{\mathrm{SOL}}(1-f_{\text{rad,div}})/A_{\text{wetted}} $$ -where $f_{rad,div}$ is the SOL radiative fraction. +where $f_{\text{rad,div}}$ is the SOL radiative fraction. For the purposes of this model, the following are inputs: -- Flux expansion $F_{exp}$ (`f_div_flux_expansion`, default = 2) -- Field line angle with respect to divertor target plate (degrees) $\beta_{div}$ (`deg_div_field_plate`), also +- Flux expansion $F_{\text{exp}}$ (`f_div_flux_expansion`, default = 2) +- Field line angle with respect to divertor target plate (degrees) $\beta_{\text{div}}$ (`deg_b_div_lower_outboard_grazing`), also available as an iteration variable (170) -- SOL radiative fraction, $f_{rad,div}$ (`rad_fraction_sol`). +- SOL radiative fraction, $f_{\text{rad,div}}$ (`rad_fraction_sol`). [^1]: N.A. Uckan and ITER Physics Group, 'ITER Physics Design Guidelines: 1989', ITER Documentation Series, No. 10, IAEA/ITER/DS/10 (1990) -[^2]: M.R. Wade & J.A. Leuer, 'Cost Drivers for a Tokamak-Based Compact Pilot Plant, Fusion Science and Technology, 77:2, 119-143 (2021) - -[^3]: T. Eich et al, 'Scaling of the tokamak near the scrape-off layer H-mode power width and implications for ITER', Nucl. Fusion 53 093031 (2013) - -[^4]: A. Scarabosio et al, 'Scaling of the divertor power spreading (S-factor) in open and closed divertor operation in JET and ASDEX Upgrade, Journal of Nuclear Materials, Vol. 463, 49-54 (2015) +[^wade_21]: M. R. Wade and J. A. Leuer, “Cost Drivers for a Tokamak-Based Compact Pilot Plant,” Fusion Science and Technology, vol. 77, no. 2, pp. 119–143, Feb. 2021, doi: 10.1080/15361055.2020.1858670. [^5]: Y.-K. M. Peng, J. B. Hicksand AEA Fusion, Culham (UK), "Engineering feasibility of tight aspect ratio Tokamak (spherical torus) reactors". 1990. https://inis.iaea.org/records/ey2rf-dah04 diff --git a/documentation/source/physics-models/plasma_scrape_off_layer.md b/documentation/source/physics-models/plasma_scrape_off_layer.md index a85fd981f6..8092c2f72e 100644 --- a/documentation/source/physics-models/plasma_scrape_off_layer.md +++ b/documentation/source/physics-models/plasma_scrape_off_layer.md @@ -81,7 +81,34 @@ This can be found in Table 2 and Equation 4 from Thornton et.al [^mast_2014] The $R^2$ value for this fit is 0.55 --------- +------------- + +## Spreading Parameter + +The scrape-off layer (SOL) spreading parameter $S$ represents a Gaussian width that quantifies additional perpendicular heat spreading in the divertor leg. It works alongside the upstream heat flux decay length $\lambda_{q}$ to determine total target heat loads on the divertor. + +Unlike $\lambda_{q}$, which is governed by robust upstream parallel and perpendicular transport physics at the plasma midplane, $S$ is inherently a "local" divertor parameter. Deriving a single, absolute multi-machine formula for $S$ is incredibly difficult due to several overlapping regional variables: + +- Divertor Geometry: The path length from the X-point to the target tile heavily impacts how much the heat spreads radially. + +- Plasma Recycling Regimes: Low-recycling, high-recycling, and detached plasma conditions completely alter the cross-field diffusion rates. + +- Localized Radiation: Impurity seeding and neutral gas interactions dissipate power unevenly along the divertor leg, altering the effective Gaussian profile width. + +----------- + +### Scarabosio 2015 | `calculate_scarabosio2015_power_spreading_factor()` + +The H-mode SOL spreading factor, $S$ is given in $\text{m}$ by[^scarabosio_2015]: + +$$ +S = (0.12(\pm0.07)\times 10^{-3}) P_{\text{sep}}^{0.21(\pm0.11)}R_0^{0.71(\pm0.5)}B_{\text{p}}(a)^{-0.82(\pm0.27)}n_{\text{sep}}^{0.71(\pm0.5)} +$$ + +- This was fitted from ASDEX Upgrade and JET outer target data +- The $R^2$ value of the regression fit was 0.65 + +------------ [^eich_2013]: T. Eich et al., “Scaling of the tokamak near the scrape-off layer H-mode power width and implications for ITER,” Nuclear Fusion, vol. 53, no. 9 p. 093031, Aug. 2013, doi: 10.1088/0029-5515/53/9/093031. @@ -90,4 +117,6 @@ Plasma Physics and Controlled Fusion, vol. 56, no. 5, p. 055008, Apr. 2014, doi: [^stangeby_boundary]: P. C. Stangeby, “The Plasma Boundary of Magnetic Fusion Devices,” Jan. 2000, doi: 10.1201/9780367801489. -[^henderson_step]: S. S. Henderson et al., “An overview of the STEP divertor design and the simple models driving the plasma exhaust scenario,” Nuclear Fusion, vol. 65, no. 1, pp. 016033–016033, Nov. 2024, doi: 10.1088/1741-4326/ad93e7. \ No newline at end of file +[^henderson_step]: S. S. Henderson et al., “An overview of the STEP divertor design and the simple models driving the plasma exhaust scenario,” Nuclear Fusion, vol. 65, no. 1, pp. 016033–016033, Nov. 2024, doi: 10.1088/1741-4326/ad93e7. + +[^scarabosio_2015]: A. Scarabosio et al., “Scaling of the divertor power spreading (S-factor) in open and closed divertor operation in JET and ASDEX Upgrade,” Journal of Nuclear Materials, vol. 463, pp. 49-54, Aug. 2015, doi: 10.1016/j.jnucmat.2014.11.076. \ No newline at end of file diff --git a/process/core/input.py b/process/core/input.py index 1d7c132b6a..e16e77af8d 100644 --- a/process/core/input.py +++ b/process/core/input.py @@ -217,7 +217,9 @@ def bounds(self) -> tuple[NumberType | None, NumberType | None]: "bctmp": InputVariable("pulse", float, range=(1.0, 800.0)), "e_beam_kev": InputVariable("current_drive", float, range=(1.0, 1000000.0)), "dx_beam_duct": InputVariable("current_drive", float, range=(0.001, 5.0)), - "deg_div_field_plate": InputVariable("divertor", float, range=(0.0, 360.0)), + "deg_b_div_lower_outboard_grazing": InputVariable( + "divertor", float, range=(0.0, 360.0) + ), "beta_poloidal_max": InputVariable("constraints", float, range=(0.01, 2.0)), "betai": InputVariable("divertor", float, range=(0.0, 1.5707)), "betao": InputVariable("divertor", float, range=(0.0, 1.5707)), diff --git a/process/core/io/obsolete_vars.py b/process/core/io/obsolete_vars.py index 2b34765c7e..9c66c4b327 100644 --- a/process/core/io/obsolete_vars.py +++ b/process/core/io/obsolete_vars.py @@ -302,7 +302,7 @@ "wave_mode": "i_ecrh_wave_mode", "pheatfix": "p_hcd_secondary_extra_heat_mw", "ftar": "f_p_div_lower", - "beta_div": "deg_div_field_plate", + "beta_div": "deg_b_div_lower_outboard_grazing", "divclfr": "f_vol_div_coolant", "divdens": "den_div_structure", "divplt": "dx_div_plate", @@ -473,6 +473,7 @@ "i_cs_stress": None, "f_nd_alpha_electron": "f_nd_alpha_thermal_electron", "cost_model": "i_cost_model", + "deg_div_field_plate": "deg_b_div_lower_outboard_grazing", } OBS_VARS_HELP = { diff --git a/process/core/io/plot/summary.py b/process/core/io/plot/summary.py index d5b3e71109..c234a64064 100644 --- a/process/core/io/plot/summary.py +++ b/process/core/io/plot/summary.py @@ -13,7 +13,7 @@ import matplotlib.pyplot as plt import numpy as np from matplotlib import patches -from matplotlib.patches import Circle, Rectangle +from matplotlib.patches import Arc, Circle, Polygon, Rectangle from matplotlib.path import Path as mplPath from scipy.interpolate import interp1d @@ -16201,6 +16201,394 @@ def plot_pf_dimensions( axis.set_aspect("equal", adjustable="box") +def plot_divertor_geometry( + theta_div_deg, alpha_div, beta_div_deg, wetted_width=3.5, plate_angle_deg=20 +): + """ + Plots the divertor geometry using calculated angle values. + + Parameters + ---------- + theta_div_deg : float + Separatrix tilt angle in degrees (poloidal plane). + alpha_div : float + Magnetic pitch / flux expansion factor (F_exp * alpha_mid). + beta_div_deg : float + Total magnetic field grazing angle in degrees (3D). + wetted_width : float + Distance along the plate between strike points (in arbitrary units/cm). + plate_angle_deg : float + Orientation angle of the plate relative to the horizontal plane. + """ + # --- 1. Geometric Angle Calculations --- + L = 7.5 # Length of incoming flux lines + + # Orientation of vectors + plate_tangent_deg = plate_angle_deg + 180 # Along plate face (leftward) + sep_deg = plate_tangent_deg - theta_div_deg # Direction of incoming flux lines + + plate_tangent_rad = np.radians(plate_tangent_deg) + sep_rad = np.radians(sep_deg) + + # --- 2. Target Plate & Strike Point Coordinates --- + sp1 = np.array([0.0, 0.0]) # Strike point 1 (origin) + sp2 = sp1 + wetted_width * np.array([ + np.cos(plate_tangent_rad), + np.sin(plate_tangent_rad), + ]) + + # Endpoints of flux lines + fl1_end = sp1 + L * np.array([np.cos(sep_rad), np.sin(sep_rad)]) + fl2_end = sp2 + L * np.array([np.cos(sep_rad), np.sin(sep_rad)]) + + # Extend target plate beyond the strike points + plate_start = sp1 - 2.0 * np.array([ + np.cos(plate_tangent_rad), + np.sin(plate_tangent_rad), + ]) + plate_end = sp2 + 2.0 * np.array([ + np.cos(plate_tangent_rad), + np.sin(plate_tangent_rad), + ]) + + # --- 3. Figure Setup --- + fig, ax = plt.subplots(figsize=(11, 7.5)) + + # Target Plate + ax.plot( + [plate_start[0], plate_end[0]], + [plate_start[1], plate_end[1]], + color="dimgray", + linewidth=8, + solid_capstyle="round", + label="Divertor Target Plate", + ) + + # Highlight Wetted Area (Strike Zone) + ax.plot( + [sp1[0], sp2[0]], + [sp1[1], sp2[1]], + color="gold", + linewidth=10, + zorder=3, + label="Wetted Area (Strike Zone)", + ) + + # Shaded Scrape-Off Layer (SOL) Heat Flow Region + sol_region = Polygon( + [sp1, fl1_end, fl2_end, sp2], + closed=True, + color="red", + alpha=0.15, + label="Heat Flow Corridor (SOL)", + ) + ax.add_patch(sol_region) + + # --- 4. Plot Separatrix Lines --- + ax.plot( + [sp1[0], fl1_end[0]], + [sp1[1], fl1_end[1]], + color="red", + linewidth=3, + linestyle="-", + label="Separatrix Line 1", + ) + ax.plot( + [sp2[0], fl2_end[0]], + [sp2[1], fl2_end[1]], + color="darkred", + linewidth=3, + linestyle="--", + label="Flux Line 2 (SOL Edge)", + ) + + # Strike Points + ax.plot(sp1[0], sp1[1], "ko", markersize=7) + ax.plot(sp2[0], sp2[1], "ko", markersize=7) + + # --- 5. Dynamic Angle Arc for theta_div --- + arc_radius = 2.8 + arc = Arc( + sp1, + arc_radius * 2, + arc_radius * 2, + angle=0, + theta1=sep_deg, + theta2=plate_tangent_deg, + color="red", + linewidth=2.5, + ) + ax.add_patch(arc) + + # Label showing the dynamic value of theta_div + mid_angle_rad = np.radians((sep_deg + plate_tangent_deg) / 2) + label_x = sp1[0] + (arc_radius * 1.3) * np.cos(mid_angle_rad) + label_y = sp1[1] + (arc_radius * 1.3) * np.sin(mid_angle_rad) + ax.text( + label_x, + label_y, + rf"$\theta_{{div}} = {theta_div_deg:.1f}^\circ$", + color="red", + fontsize=13, + fontweight="bold", + ha="center", + va="center", + ) + + # --- 6. Wetted Area Dimension Marker --- + normal_vec = np.array([-np.sin(plate_tangent_rad), np.cos(plate_tangent_rad)]) + offset = 1.0 + dim_sp1 = sp1 - normal_vec * offset + dim_sp2 = sp2 - normal_vec * offset + + ax.annotate( + "", + xy=dim_sp1, + xytext=dim_sp2, + arrowprops=dict(arrowstyle="<->", color="darkorange", lw=2.5), + ) + + dim_mid = (dim_sp1 + dim_sp2) / 2 - normal_vec * 0.5 + ax.text( + dim_mid[0], + dim_mid[1], + f"Wetted Width = {wetted_width:.2f}", + color="darkorange", + fontsize=11, + fontweight="bold", + ha="center", + va="center", + rotation=plate_angle_deg, + ) + + # --- 7. Overlay Box displaying Calculated Code Values --- + info_text = ( + r"$\mathbf{Calculated\ Divertor\ Parameters:}$" + "\n" + "---------------------------------------\n" + rf"• $\mathbf{{\theta_{{div}}}}$ (Separatrix Tilt): {theta_div_deg:.2f}°" + "\n" + rf"• $\mathbf{{\alpha_{{div}}}}$ (Pitch / Flux Factor): {alpha_div:.2f}" + "\n" + rf"• $\mathbf{{\beta_{{div}}}}$ (3D Grazing Angle): {beta_div_deg:.2f}°" + "\n" + "---------------------------------------\n" + r"Note: $\beta_{div}$ is a 3D skimming angle" + "\n" + r"between total field $\mathbf{B}$ & target face." + ) + props = dict( + boxstyle="round,pad=0.6", facecolor="whitesmoke", alpha=0.9, edgecolor="gray" + ) + ax.text( + 0.03, + 0.95, + info_text, + transform=ax.transAxes, + fontsize=11, + verticalalignment="top", + bbox=props, + ) + + # --- 8. Formatting & Cleanup --- + ax.set_aspect("equal") + ax.set_xlim(-9, 4) + ax.set_ylim(-4, 8) + ax.axis("off") + ax.legend(loc="upper right", fontsize=11) + plt.title( + "Divertor Geometry with Calculated Field Angles", fontsize=15, fontweight="bold" + ) + plt.tight_layout() + plt.show() + + +def plot_wade_divertor_dimensions( + axis: plt.Axes, mfile: MFile, scan: int, colour_scheme: Literal[1, 2] = 1 +) -> None: + """Plot the Wade divertor dimensions on the given axis.""" + # --- 1. Parameters --- + alpha_div = mfile.get( + "deg_b_div_lower_flux", scan=scan + ) # Magnetic pitch / flux expansion factor (F_exp * alpha_mid) + beta_div_deg = mfile.get( + "deg_b_div_lower_outboard_grazing", scan=scan + ) # Total magnetic field grazing angle (3D) + plate_angle_deg = 20 # Visual orientation of the plate in the room + theta_div_deg = np.degrees( + mfile.get("deg_div_lower_outboard_plate_separatrix_poloidal", scan=scan) + ) # Separatrix tilt angle relative to the target face + wetted_width = mfile.get( + "dx_div_lower_outboard_strike", scan=scan + ) # Distance along plate between the two strike points + L = 0.5 # Length of flux lines + + # --- 1. Geometric Angle Calculations --- + L = 3 * wetted_width # Length of incoming flux lines + + # Orientation of vectors + plate_tangent_deg = plate_angle_deg + 180 # Along plate face (leftward) + sep_deg = plate_tangent_deg - theta_div_deg # Direction of incoming flux lines + + plate_tangent_rad = np.radians(plate_tangent_deg) + sep_rad = np.radians(sep_deg) + + # --- 2. Target Plate & Strike Point Coordinates --- + sp1 = np.array([0.0, 0.0]) # Strike point 1 (origin) + sp2 = sp1 + wetted_width * np.array([ + np.cos(plate_tangent_rad), + np.sin(plate_tangent_rad), + ]) + + # Endpoints of flux lines + fl1_end = sp1 + L * np.array([np.cos(sep_rad), np.sin(sep_rad)]) + fl2_end = sp2 + L * np.array([np.cos(sep_rad), np.sin(sep_rad)]) + + # Extend target plate beyond the strike points + plate_start = sp1 - (2 * wetted_width) * np.array([ + np.cos(plate_tangent_rad), + np.sin(plate_tangent_rad), + ]) + plate_end = sp2 + (2 * wetted_width) * np.array([ + np.cos(plate_tangent_rad), + np.sin(plate_tangent_rad), + ]) + + # Target Plate + axis.plot( + [plate_start[0], plate_end[0]], + [plate_start[1], plate_end[1]], + color="dimgray", + linewidth=8, + solid_capstyle="round", + label="Divertor Target Plate", + ) + + # Highlight Wetted Area (Strike Zone) + axis.plot( + [sp1[0], sp2[0]], + [sp1[1], sp2[1]], + color="gold", + linewidth=10, + zorder=3, + label="Wetted Area (Strike Zone)", + ) + + # Shaded Scrape-Off Layer (SOL) Heat Flow Region + sol_region = Polygon( + [sp1, fl1_end, fl2_end, sp2], + closed=True, + color="red", + alpha=0.15, + label="Heat Flow Corridor (SOL)", + ) + axis.add_patch(sol_region) + + # --- 4. Plot Separatrix Lines --- + axis.plot( + [sp1[0], fl1_end[0]], + [sp1[1], fl1_end[1]], + color="red", + linewidth=3, + linestyle="-", + label="Separatrix Line 1", + ) + axis.plot( + [sp2[0], fl2_end[0]], + [sp2[1], fl2_end[1]], + color="darkred", + linewidth=3, + linestyle="--", + label="Flux Line 2 (SOL Edge)", + ) + + # Strike Points + axis.plot(sp1[0], sp1[1], "ko", markersize=7) + axis.plot(sp2[0], sp2[1], "ko", markersize=7) + + # --- 5. Dynamic Angle Arc for theta_div --- + arc_radius = 2 * wetted_width + arc = Arc( + sp1, + arc_radius * 2, + arc_radius * 2, + angle=0, + theta1=sep_deg, + theta2=plate_tangent_deg, + color="red", + linewidth=2.5, + ) + axis.add_patch(arc) + + # Label showing the dynamic value of theta_div + mid_angle_rad = np.radians((sep_deg + plate_tangent_deg) / 2) + label_x = sp1[0] + (arc_radius * 1.3) * np.cos(mid_angle_rad) + label_y = sp1[1] + (arc_radius * 1.3) * np.sin(mid_angle_rad) + axis.text( + label_x, + label_y, + rf"$\theta_{{div}} = {theta_div_deg:.1f}^\circ$", + color="red", + fontsize=13, + fontweight="bold", + ha="center", + va="center", + ) + + # --- 6. Wetted Area Dimension Marker --- + normal_vec = np.array([-np.sin(plate_tangent_rad), np.cos(plate_tangent_rad)]) + offset = 1.0 + dim_sp1 = sp1 - normal_vec * offset + dim_sp2 = sp2 - normal_vec * offset + + axis.annotate( + "", + xy=dim_sp1, + xytext=dim_sp2, + arrowprops=dict(arrowstyle="<->", color="darkorange", lw=2.5), + ) + + dim_mid = (dim_sp1 + dim_sp2) / 2 - normal_vec * 0.5 + axis.text( + dim_mid[0], + dim_mid[1], + f"Wetted Width = {wetted_width:.3e}", + color="darkorange", + fontsize=11, + fontweight="bold", + ha="center", + va="center", + rotation=plate_angle_deg, + ) + + # --- 7. Overlay Box displaying Calculated Code Values --- + info_text = ( + r"$\mathbf{Calculated\ Divertor\ Parameters:}$" + "\n" + "---------------------------------------\n" + rf"• $\mathbf{{\theta_{{div}}}}$ (Separatrix Tilt): {theta_div_deg:.2f}°" + "\n" + rf"• $\mathbf{{\alpha_{{div}}}}$ (Pitch / Flux Factor): {alpha_div:.2f}" + "\n" + rf"• $\mathbf{{\beta_{{div}}}}$ (3D Grazing Angle): {beta_div_deg:.2f}°" + "\n" + "---------------------------------------\n" + r"Note: $\beta_{div}$ is a 3D skimming angle" + "\n" + r"between total field $\mathbf{B}$ & target face." + ) + props = dict( + boxstyle="round,pad=0.6", facecolor="whitesmoke", alpha=0.9, edgecolor="gray" + ) + axis.text( + 0.03, + 0.95, + info_text, + transform=axis.transAxes, + fontsize=11, + verticalalignment="top", + bbox=props, + ) + + # --- 8. Formatting & Cleanup --- + axis.set_aspect("equal") + axis.legend(loc="upper right", fontsize=11) + plt.title( + "Divertor Geometry with Calculated Field Angles", fontsize=15, fontweight="bold" + ) + plt.tight_layout() + + def main_plot( m_file: MFile, scan: int, @@ -16498,6 +16886,10 @@ def _add_page(name: str | None = None): pages["freq"].subplots_adjust(hspace=0.5) + plot_wade_divertor_dimensions( + _add_page("wade_divertor").add_subplot(111), m_file, scan + ) + # Plot poloidal cross-section poloidal_cross_section( _add_page("tokamak_cross_section").add_subplot(121, aspect="equal"), diff --git a/process/core/solver/iteration_variables.py b/process/core/solver/iteration_variables.py index c40f932ec1..e89092316b 100644 --- a/process/core/solver/iteration_variables.py +++ b/process/core/solver/iteration_variables.py @@ -231,7 +231,7 @@ class IterationVariable: ), 162: IterationVariable("r_cp_top", "build", 0.0010, 10.0), 169: IterationVariable("te0_ecrh_achievable", "stellarator", 1.0, 1.0e3), - 170: IterationVariable("deg_div_field_plate", "divertor", 0.49, 5.01), + 170: IterationVariable("deg_b_div_lower_outboard_grazing", "divertor", 0.49, 5.01), 171: IterationVariable("casths_fraction", "tfcoil", 0.01, 0.99), 172: IterationVariable("dx_tf_side_case_min", "tfcoil", 0.001, 1.0), 173: IterationVariable("f_plasma_fuel_tritium", "physics", 0.000, 1.000), diff --git a/process/data_structure/divertor_variables.py b/process/data_structure/divertor_variables.py index 29934185b4..e3af28ff81 100644 --- a/process/data_structure/divertor_variables.py +++ b/process/data_structure/divertor_variables.py @@ -24,8 +24,8 @@ class DivertorData: anginc: float = 0.262 """angle of incidence of field line on plate (rad)""" - deg_div_field_plate: float = 1.0 - """field line angle wrt divertor target plate (degrees)""" + deg_b_div_lower_outboard_grazing: float = 1.0 + """3D grazing field line angle with respect to divertor target plate [degrees]""" betai: float = 1.0 """poloidal plane angle between divertor plate and leg, inboard (rad)""" @@ -97,5 +97,20 @@ class DivertorData: deg_div_poloidal_plasma: float = 0.0 """Divertor poloidal angle subtended by plasma (degrees)""" + dx_div_lower_outboard_strike: float = 0.0 + """Lower divertor outboard strike point width [m]""" + + f_div_lower_flux_expansion: float = 0.0 + """Lower divertor flux expansion factor (fₓ)""" + + deg_b_div_lower_flux: float = 0.0 + """Lower divertor flux angle [degrees]""" + + a_div_lower_outboard_wetted: float = 0.0 + """Lower divertor outboard wetted area [m²]""" + + deg_div_lower_outboard_plate_separatrix_poloidal: float = 0.0 + """Lower divertor outboard plate-separatrix poloidal angle [degrees]""" + CREATE_DICTS_FROM_DATACLASS = DivertorData diff --git a/process/data_structure/numerics.py b/process/data_structure/numerics.py index 57505f1c39..a56ad53a80 100644 --- a/process/data_structure/numerics.py +++ b/process/data_structure/numerics.py @@ -566,7 +566,7 @@ class NumericsData: * (167) NOT USED * (168) NOT USED * (169) te0_ecrh_achievable: Max. achievable electron temperature at ignition point - * (170) deg_div_field_plate : field line angle wrt divertor target plate (degrees) + * (170) deg_b_div_lower_outboard_grazing : field line angle wrt divertor target plate (degrees) * (171) casths_fraction : TF side case thickness as fraction of toridal case thickness * (172) dx_tf_side_case_min : TF side case thickness [m] * (173) f_plasma_fuel_deuterium : Deuterium fraction in fuel diff --git a/process/data_structure/physics_variables.py b/process/data_structure/physics_variables.py index 22f742e3ad..c21af1c091 100644 --- a/process/data_structure/physics_variables.py +++ b/process/data_structure/physics_variables.py @@ -1671,6 +1671,10 @@ class PhysicsData: len_plasma_sol_mast14_power_decay_2: float = 0.0 """MAST 2014 power decay length in the scrape-off layer scaling 2 (λ_q) [m]""" + len_plasma_sol_scrabosio14_power_spreading: float = 0.0 + """Scrabosio 2014 H-mode power spreading length in the scrape-off layer scaling + (S) [m]""" + a_plasma_outboard_sol_eich13_parallel: float = 0.0 """Plasma outboard midplane (upstream) Eich 2013 SOL parallel area (Aₗₗ,ᵤ) [m]""" @@ -1678,6 +1682,9 @@ class PhysicsData: """Plasma outboard midplane (upstream) Eich 2013 SOL parallel power flux (qₗₗ,ᵤ) [MW/m²]""" + deg_b_plasma_outboard_flux_midplane: float = 0.0 + """Plasma outboard midplane flux angle [deg]""" + dt_power_density_plasma: float = 0.0 sigmav_dt_average: float = 0.0 dhe3_power_density: float = 0.0 diff --git a/process/models/divertor.py b/process/models/divertor.py index a9563e4383..4afd1ef6ce 100644 --- a/process/models/divertor.py +++ b/process/models/divertor.py @@ -1,6 +1,7 @@ """Module containing divertor routines""" import math +from dataclasses import dataclass import numpy as np @@ -12,6 +13,26 @@ from process.data_structure.physics_variables import DivertorNumberModels +@dataclass +class WadeDivertorMetrics: + """Dataclass to hold the output metrics for the Wade divertor model.""" + + pflux_div_heat_load_mw: float + """Divertor heat load [MW/m²]""" + dx_div_lower_outboard_strike: float + """Lower outboard divertor strike point width [m]""" + a_div_lower_outboard_wetted: float + """Lower divertor outboard wetted area [m²]""" + f_div_lower_flux_expansion: float + """Lower divertor flux expansion factor (fₓ)""" + deg_b_div_lower_flux: float + """Lower divertor flux angle [degrees]""" + deg_div_lower_outboard_plate_separatrix_poloidal: float + """Lower divertor outboard plate-separatrix poloidal angle [degrees]""" + deg_b_div_lower_outboard_grazing: float + """Lower divertor outboard grazing angle [degrees]""" + + class Divertor(Model): """Module containing divertor routines @@ -87,20 +108,41 @@ def run(self, output: bool = False): DivertorHeatLoadModel(self.data.divertor.i_div_heat_load) == DivertorHeatLoadModel.WADE ): - self.divwade( - self.data.physics.rmajor, - self.data.physics.rminor, - self.data.physics.aspect, - self.data.physics.b_plasma_toroidal_on_axis, - self.data.physics.b_plasma_surface_poloidal_average, - self.data.physics.p_plasma_separatrix_mw, - self.data.divertor.f_div_flux_expansion, - self.data.physics.nd_plasma_separatrix_electron, - self.data.divertor.deg_div_field_plate, - self.data.physics.rad_fraction_sol, - self.data.physics.f_p_div_lower, - output=output, + wade_divertor = self.divwade( + rmajor=self.data.physics.rmajor, + p_plasma_separatrix_mw=self.data.physics.p_plasma_separatrix_mw, + len_plasma_sol_power_decay=self.data.physics.len_plasma_sol_eich13_power_decay, + len_plasma_sol_power_spreading=self.data.physics.len_plasma_sol_scrabosio14_power_spreading, + f_div_flux_expansion=self.data.divertor.f_div_flux_expansion, + deg_b_div_lower_outboard_grazing=self.data.divertor.deg_b_div_lower_outboard_grazing, + rad_fraction_sol=self.data.physics.rad_fraction_sol, + f_p_div_lower=self.data.physics.f_p_div_lower, + deg_b_plasma_outboard_flux_midplane=self.data.physics.deg_b_plasma_outboard_flux_midplane, ) + + self.data.divertor.pflux_div_heat_load_mw = ( + wade_divertor.pflux_div_heat_load_mw + ) + self.data.divertor.dx_div_lower_outboard_strike = ( + wade_divertor.dx_div_lower_outboard_strike + ) + self.data.divertor.a_div_lower_outboard_wetted = ( + wade_divertor.a_div_lower_outboard_wetted + ) + self.data.divertor.f_div_lower_flux_expansion = ( + wade_divertor.f_div_lower_flux_expansion + ) + self.data.divertor.deg_b_div_lower_flux = wade_divertor.deg_b_div_lower_flux + self.data.divertor.deg_div_lower_outboard_plate_separatrix_poloidal = ( + wade_divertor.deg_div_lower_outboard_plate_separatrix_poloidal + ) + self.data.divertor.deg_b_div_lower_outboard_grazing = ( + wade_divertor.deg_b_div_lower_outboard_grazing + ) + + if output: + self.output_wade_divertor_model() + return @property @@ -272,18 +314,15 @@ def divtart( def divwade( self, rmajor: float, - rminor: float, - aspect: float, - b_plasma_toroidal_on_axis: float, - b_plasma_poloidal_average: float, p_plasma_separatrix_mw: float, + len_plasma_sol_power_decay: float, + len_plasma_sol_power_spreading: float, f_div_flux_expansion: float, - nd_plasma_separatrix_electron: float, - deg_div_field_plate: float, + deg_b_div_lower_outboard_grazing: float, rad_fraction_sol: float, f_p_div_lower: float, - output: bool, - ) -> float: + deg_b_plasma_outboard_flux_midplane: float, + ) -> WadeDivertorMetrics: """Divertor heat load model (Wade 2020) This subroutine calculates the divertor heat flux for any machine, @@ -298,21 +337,15 @@ def divwade( ---------- rmajor : float plasma major radius (m) - rminor : float - plasma minor radius (m) - aspect : float - tokamak aspect ratio - b_plasma_toroidal_on_axis : float - toroidal field (T) - b_plasma_poloidal_average : float - poloidal field (T) p_plasma_separatrix_mw : float power to divertor (MW) + len_plasma_sol_power_decay : float + SOL power decay length (λ_q) [m] + len_plasma_sol_power_spreading : float + SOL power spreading factor (S) [m] f_div_flux_expansion : float plasma flux expansion in divertor - nd_plasma_separatrix_electron : float - electron density at separatrix (m-3) - deg_div_field_plate : float + deg_b_div_lower_outboard_grazing : float field line angle wrt divertor target plate (degrees) rad_fraction_sol : float SOL radiation fraction @@ -321,47 +354,26 @@ def divwade( Returns ------- - float - divertor heat load for a tight aspect ratio machine - """ - # Radius on midplane - r_omp = rmajor + rminor + WadeDivertorMetrics - # B fields on midplane - Bp_omp = -b_plasma_poloidal_average * rmajor / r_omp - Bt_omp = -b_plasma_toroidal_on_axis * rmajor / r_omp - - # Eich scaling for lambda_q - lambda_eich = ( - 1.35 - * p_plasma_separatrix_mw**-0.02 - * rmajor**0.04 - * b_plasma_poloidal_average**-0.92 - * aspect**0.42 - ) - - # Spreading factor - spread_fact = ( - 0.12 - * (nd_plasma_separatrix_electron / 1e19) ** -0.02 - * p_plasma_separatrix_mw**-0.21 - * rmajor**0.71 - * b_plasma_poloidal_average**-0.82 - ) - - # SOL width - lambda_int = lambda_eich + 1.64 * spread_fact - - # Flux angle on midplane - alpha_mid = math.degrees(math.atan(Bp_omp / Bt_omp)) + References + ---------- + [1] M. R. Wade and J. A. Leuer, “Cost Drivers for a Tokamak-Based Compact Pilot + Plant,” Fusion Science and Technology, vol. 77, no. 2, pp. 119-143, Feb. 2021, + doi: 10.1080/15361055.2020.1858670. + """ + # SOL width at divertor plates (λ_int) [m] + # λ_int = λ_q + 1.64 * S + lambda_int = len_plasma_sol_power_decay + 1.64 * len_plasma_sol_power_spreading # Flux angle in the divertor - alpha_div = f_div_flux_expansion * alpha_mid + alpha_div = f_div_flux_expansion * deg_b_plasma_outboard_flux_midplane # Tilt of the separatrix relative to the target in the poloidal plane theta_div = math.asin( - (1 + 1 / alpha_div**2) * math.sin(math.radians(deg_div_field_plate)) + (1 + 1 / math.radians(alpha_div) ** 2) + * math.sin(math.radians(deg_b_div_lower_outboard_grazing)) ) # Wetted area @@ -369,6 +381,8 @@ def divwade( 2 * np.pi * rmajor * lambda_int * f_div_flux_expansion * math.sin(theta_div) ) + strike_width = lambda_int * f_div_flux_expansion * math.sin(theta_div) + # Divertor heat load hldiv_base = p_plasma_separatrix_mw * (1 - rad_fraction_sol) / area_wetted @@ -377,33 +391,63 @@ def divwade( if self.data.divertor.n_divertors == 2: hldiv_lower = f_p_div_lower * hldiv_base hldiv_upper = (1.0 - f_p_div_lower) * hldiv_base - self.data.divertor.pflux_div_heat_load_mw = max(hldiv_lower, hldiv_upper) + pflux_div_heat_load_mw = max(hldiv_lower, hldiv_upper) else: - self.data.divertor.pflux_div_heat_load_mw = hldiv_base + pflux_div_heat_load_mw = hldiv_base + + return WadeDivertorMetrics( + pflux_div_heat_load_mw=pflux_div_heat_load_mw, + dx_div_lower_outboard_strike=strike_width, + a_div_lower_outboard_wetted=area_wetted, + f_div_lower_flux_expansion=f_div_flux_expansion, + deg_b_div_lower_flux=deg_b_div_lower_outboard_grazing, + deg_div_lower_outboard_plate_separatrix_poloidal=theta_div, + deg_b_div_lower_outboard_grazing=deg_b_div_lower_outboard_grazing, + ) - if output: - po.osubhd(self.outfile, "Divertor Heat Load") - po.ocmmnt(self.outfile, "Assume an expanded divertor with a gaseous target") - po.oblnkl(self.outfile) - po.ovarre( - self.outfile, - "Flux expansion", - "(f_div_flux_expansion)", - f_div_flux_expansion, - ) - po.ovarre( - self.outfile, - "Field line angle wrt to target divertor plate (degrees)", - "(deg_div_field_plate)", - deg_div_field_plate, - ) - po.ovarre( - self.outfile, - "Divertor heat load (MW/m²)", - "(pflux_div_heat_load_mw)", - self.data.divertor.pflux_div_heat_load_mw, - ) - return self.data.divertor.pflux_div_heat_load_mw + def output_wade_divertor_model(self): + + po.oheadr(self.outfile, "Wade Divertor Heat Load Model") + po.ocmmnt(self.outfile, "Assume an expanded divertor with a gaseous target") + po.oblnkl(self.outfile) + po.ovarre( + self.outfile, + "Flux expansion factor (fₓ)", + "(f_div_flux_expansion)", + self.data.divertor.f_div_lower_flux_expansion, + ) + po.ovarre( + self.outfile, + "Field line angle wrt to target divertor plate [degrees]", + "(deg_b_div_lower_outboard_grazing)", + self.data.divertor.deg_b_div_lower_outboard_grazing, + ) + po.ovarre( + self.outfile, + "Lower divertor flux angle [degrees]", + "(deg_b_div_lower_flux)", + self.data.divertor.deg_b_div_lower_flux, + ) + po.ovarre( + self.outfile, + "Lower divertor outboard plate-separatrix poloidal angle [degrees]", + "(deg_div_lower_outboard_plate_separatrix_poloidal)", + self.data.divertor.deg_div_lower_outboard_plate_separatrix_poloidal, + ) + + po.ovarre( + self.outfile, + "Strike point width (m)", + "(dx_div_lower_outboard_strike)", + self.data.divertor.dx_div_lower_outboard_strike, + ) + po.oblnkl(self.outfile) + po.ovarre( + self.outfile, + "Divertor heat load [MW/m²]", + "(pflux_div_heat_load_mw)", + self.data.divertor.pflux_div_heat_load_mw, + ) @staticmethod def incident_radiation_power( diff --git a/process/models/physics/physics.py b/process/models/physics/physics.py index cc94f885fb..178034d2d5 100644 --- a/process/models/physics/physics.py +++ b/process/models/physics/physics.py @@ -419,6 +419,13 @@ def run(self): ) ) + self.data.physics.deg_b_plasma_outboard_flux_midplane = ( + self.fields.calculate_plasma_magnetic_flux_angle( + b_plasma_toroidal=self.data.physics.b_plasma_outboard_toroidal, + b_plasma_poloidal=self.data.physics.b_plasma_surface_poloidal_average, + ) + ) + # ============================================ # ----------------------------------------------------- diff --git a/process/models/physics/plasma_fields.py b/process/models/physics/plasma_fields.py index e178ebf2e3..6444aecf9b 100644 --- a/process/models/physics/plasma_fields.py +++ b/process/models/physics/plasma_fields.py @@ -197,6 +197,27 @@ def calculate_total_magnetic_field( """ return np.sqrt(b_plasma_toroidal**2 + b_plasma_poloidal**2) + @staticmethod + def calculate_plasma_magnetic_flux_angle( + b_plasma_toroidal: float, b_plasma_poloidal: float + ) -> float: + """Calculate the magnetic flux angle in the plasma based on the toroidal and + poloidal magnetic fields strengths + + Parameters + ---------- + b_plasma_toroidal : + toroidal field at point of interest [T] + b_plasma_poloidal : + poloidal field at point of interest [T] + + Returns + ------- + : + magnetic flux angle at the plasma edge (degrees) + """ + return np.degrees(np.arctan(b_plasma_poloidal / b_plasma_toroidal)) + def output(self): """Output plasma magnetic fields data.""" po.oheadr(self.outfile, "Plasma magnetic fields") @@ -266,3 +287,11 @@ def output(self): self.data.physics.b_plasma_outboard_total, "OP ", ) + po.oblnkl(self.outfile) + po.ovarre( + self.outfile, + "Outboard plasma magnetic flux angle from midplane [deg]", + "(deg_b_plasma_outboard_flux_midplane)", + self.data.physics.deg_b_plasma_outboard_flux_midplane, + "OP ", + ) diff --git a/process/models/physics/scrape_off_layer.py b/process/models/physics/scrape_off_layer.py index 7b2ea9055f..a0194e960d 100644 --- a/process/models/physics/scrape_off_layer.py +++ b/process/models/physics/scrape_off_layer.py @@ -53,6 +53,14 @@ def run(self): / self.data.physics.a_plasma_outboard_sol_eich13_parallel ) + self.data.physics.len_plasma_sol_scrabosio14_power_spreading = self.calculate_scarabosio2014_power_spreading_factor( # noqa: E501 + p_plasma_separatrix_mw=self.data.physics.p_plasma_separatrix_mw, + b_plasma_surface_poloidal_average=self.data.physics.b_plasma_surface_poloidal_average, + nd_plasma_separatrix_electron_19=self.data.physics.nd_plasma_separatrix_electron + / 1e19, + rmajor=self.data.physics.rmajor, + ) + def output(self) -> None: """Output plasma scrape off layer physics information.""" po.oheadr(self.outfile, "Plasma Scrape Off Layer") @@ -94,6 +102,14 @@ def output(self) -> None: self.data.physics.pflux_plasma_outboard_sol_eich13_parallel_mw, ) + po.osubhd(self.outfile, "Power Spreading Factors (S):") + po.ovarre( + self.outfile, + "Scrabosio 2014 H-mode power spreading factor (S) [m]", + "(len_plasma_sol_scrabosio14_power_spreading)", + self.data.physics.len_plasma_sol_scrabosio14_power_spreading, + ) + @staticmethod def calculate_eich2013_sol_power_decay_length( p_plasma_separatrix_mw: float, @@ -250,3 +266,48 @@ def calculate_upstream_sol_outboard_parallel_area( * len_plasma_sol_power_decay * (b_plasma_surface_poloidal_average / b_plasma_outboard_total) ) + + @staticmethod + def calculate_scarabosio2014_power_spreading_factor( + p_plasma_separatrix_mw: float, + b_plasma_surface_poloidal_average: float, + nd_plasma_separatrix_electron_19: float, + rmajor: float, + ) -> float: + """Calculate the Scrabosio 2014 H-mode power spreading factor (S). + + Parameters + ---------- + p_plasma_separatrix_mw : float + Power crossing the separatrix (Pₛₑₚ) [MW] + b_plasma_surface_poloidal_average : float + Poloidal magnetic field at the plasma surface (Bₚₒₗ(a)) [T] + nd_plasma_separatrix_electron_19 : float + Electron density at the separatrix (nₑ,ₛₑₚ) [10¹⁹ m⁻³] + rmajor : float + Major radius of the plasma (R₀) [m] + + Returns + ------- + float + Scrabosio 2014 H-mode power spreading factor (S) [m] + + Notes + ----- + - The R² for the fit is 0.65 + + References + ---------- + [1] A. Scarabosio et al., “Scaling of the divertor power spreading (S-factor) in + open and closed divertor operation in JET and ASDEX Upgrade,” + Journal of Nuclear Materials, vol. 463, pp. 49-54, Aug. 2015, + doi: 10.1016/j.jnucmat.2014.11.076. + + """ + return ( + 0.12e-3 + * p_plasma_separatrix_mw**0.21 + * b_plasma_surface_poloidal_average**-0.82 + * nd_plasma_separatrix_electron_19**-0.02 + * rmajor**0.71 + ) diff --git a/tests/regression/input_files/st_regression.IN.DAT b/tests/regression/input_files/st_regression.IN.DAT index 78f4028107..ec8b3a4b59 100644 --- a/tests/regression/input_files/st_regression.IN.DAT +++ b/tests/regression/input_files/st_regression.IN.DAT @@ -748,7 +748,7 @@ p_plasma_separatrix_rmajor_max_mw = 40.0 * DESCRIPTION: Angle of incidence of field line on plate (rad) (default = 0.262) * JUSTIFICATION: Not yet set -*deg_div_field_plate = +*deg_b_div_lower_outboard_grazing = * DESCRIPTION: Field line angle wrt divertor target plate (degrees) (default = 1.0) * JUSTIFICATION: Not yet set diff --git a/tests/unit/models/test_divertor.py b/tests/unit/models/test_divertor.py index 803df7cfc7..c2caaf17ba 100644 --- a/tests/unit/models/test_divertor.py +++ b/tests/unit/models/test_divertor.py @@ -84,7 +84,7 @@ def test_divwade(monkeypatch, divertor): p_plasma_separatrix_mw = 1.0e2 f_div_flux_expansion = 2 nd_plasma_separatrix_electron = 1.0e19 - deg_div_field_plate = 5.0 + deg_b_div_lower_outboard_grazing = 5.0 rad_fraction_sol = 8.0e-1 f_p_div_lower = 1.0 @@ -99,7 +99,7 @@ def test_divwade(monkeypatch, divertor): p_plasma_separatrix_mw, f_div_flux_expansion, nd_plasma_separatrix_electron, - deg_div_field_plate, + deg_b_div_lower_outboard_grazing, rad_fraction_sol, f_p_div_lower, False,