diff --git a/documentation/source/physics-models/plasma_scrape_off_layer.md b/documentation/source/physics-models/plasma_scrape_off_layer.md index a85fd981f6..b10f2780b6 100644 --- a/documentation/source/physics-models/plasma_scrape_off_layer.md +++ b/documentation/source/physics-models/plasma_scrape_off_layer.md @@ -25,7 +25,43 @@ $$ A_{\parallel,u} = 2\pi\lambda_{\text{q,u}}R_{\text{u}}\frac{B_{\text{p,u}}}{B_{\text{Tot,u}}} $$ +--------------- +## Upstream radial decay | `calculate_outboard_midplane_near_sol_radial_profile()` + +The radial decay length $\lambda_{\text{q}}$ of the scrape-off layer (SOL) at the outer midplane of a tokamak is defined as the e-folding distance over which plasma heat and particle fluxes decay exponentially outside the last closed flux surface. Therefore the decay of the total heat flux outside the separatrix towards the vessel walls can be modelled as [^eich_2011] [^eich_2013]: + +$$ +q_{\text{u}}(r) = q_{\parallel,\text{u}}e^{\frac{-r}{\lambda_{\text{q}}}} +$$ + +where $r = R - R_{\text{sep}}$, $R_{\text{sep}}$ being the major radius of the separatrix, $\lambda_{\text{q}}$ the [power decay length](#power-decay-lengths) and $q_{\parallel}$ the [upstream energy flux density](#upstream-radial-decay--calculate_outboard_midplane_near_sol_radial_profile) + +---------------- + +## Eich parallel flux at target | `calculate_eich_target_heat_flux_profile()` + +The Eich formula (often called the standard SOL heat flux profile) is the primary mathematical model used to describe the distribution of heat target loads on tokamak divertor plates. It convolutionally connects the physics of the plasma edge at the outer midplane with the geometric projection of the heat hitting the divertor surface [^eich_2011] [^eich_2013]. + +Heat transport into the private flux region is modeled by convolving the power profile $q_{\text{u}}(r)$ with a Gaussian function of width $S$ known as the [spreading parameter](#spreading-parameter). + +$$ +q_{\parallel,t}(s) = \frac{q_0}{2}\times \exp\left[\left(\frac{S}{2\lambda_{\text{q}}f_x}\right)^2- \frac{s-s_0}{\lambda_q f_x}\right] \times \operatorname{erfc}\left(\frac{S}{2\lambda_{\text{q}}f_x}- \frac{s-s_0}{S}\right) + q_{\text{BG}} +$$ + +where $s$ is the coordinate along the divertor target, $s_0$ is the strike-point location on the target, $\operatorname{erfc}$ is the complementary error function, $q_{\text{BG}}$ is the background heat flux, $\lambda_{\text{q}}$ is the [power decay length](#power-decay-lengths), $f_x$ is the effective flux expansion in the region, + +A compact equivalent form is: + +$$ +q_{\parallel,t}(\overline{s}) = \frac{q_0}{2}\times \exp\left[\left(\frac{S}{2\lambda_{\text{q}}f_x}\right)^2- \frac{\overline{s}}{\lambda_q f_x}\right] \times \operatorname{erfc}\left(\frac{S}{2\lambda_{\text{q}}f_x}- \frac{\overline{s}}{S}\right) + q_{\text{BG}} +$$ + +The connection to upstream midplane coordinates is usually: + +$$ +\overline{s} = f_x(R-R_{\text{sep}}) +$$ ------------------ @@ -83,6 +119,33 @@ 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. [^mast_2014]: A. J. Thornton and A. Kirk, “Scaling of the scrape-off layer width during inter-ELM H modes on MAST as measured by infrared thermography,” @@ -90,4 +153,8 @@ 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. + +[^eich_2011]: T. Eich, B. Sieglin, A. Scarabosio, W. Fundamenski, R. J. Goldston, and A. Herrmann, “Inter-ELM Power Decay Length for JET and ASDEX Upgrade: Measurement and Comparison with Heuristic Drift-Based Model,” Physical Review Letters, vol. 107, no. 21, Nov. 2011, doi: https://doi.org/10.1103/PhysRevLett.107.215001 \ No newline at end of file diff --git a/process/core/io/plot/summary.py b/process/core/io/plot/summary.py index bd34580d43..7e25108f51 100644 --- a/process/core/io/plot/summary.py +++ b/process/core/io/plot/summary.py @@ -84,6 +84,7 @@ PlasmaShapeModelType, ) from process.models.physics.profiles import PlasmaProfileShapeType +from process.models.physics.scrape_off_layer import ScrapeOffLayer from process.models.pulse import PulseTimings from process.models.superconductors import SuperconductorModel from process.models.tfcoil.base import ( @@ -9349,6 +9350,160 @@ def make_bbox_props(power: float) -> dict[str, Any]: axis.get_yaxis().set_ticks([]) +def plot_midplane_near_sol_radial_profile( + axis: plt.Axes, mfile: MFile, scan: int, colour_scheme: int +): + """Function to plot the radial profile of the near SOL at the midplane.""" + rmajor = mfile.get("rmajor", scan=scan) + rminor = mfile.get("rminor", scan=scan) + len_sol_outboard_power_decay = mfile.get("len_sol_outboard_power_decay", scan=scan) + r = np.linspace( + (rmajor + rminor), (rmajor + rminor) + (7 * len_sol_outboard_power_decay), 100 + ) + + radial_profile = ( + ScrapeOffLayer().calculate_outboard_midplane_near_sol_radial_profile( + rmajor=rmajor, + rminor=rminor, + len_plasma_sol_power_decay=len_sol_outboard_power_decay, + pflux_plasma_outboard_sol_parallel_mw=mfile.get( + "pflux_plasma_outboard_sol_parallel_mw", scan=scan + ), + r=r, + ) + ) + + x_ref = rmajor + rminor + len_sol_outboard_power_decay + plasma_boundary = rmajor + rminor + + axis.axvspan( + 0, + plasma_boundary, + color=PLASMA_COLOUR[colour_scheme - 1], + alpha=0.35, + label="Plasma", + ) + axis.plot(r, radial_profile, label=r"$q_{||}$ profile") + axis.axvline( + x_ref, + color="black", + linestyle="--", + linewidth=1, + label=r"$\lambda_{q,\mathrm{out}}$", + ) + axis.grid() + axis.legend() + axis.set_xlim( + (rmajor + rminor - (3 * len_sol_outboard_power_decay)), + (rmajor + rminor) + (7 * len_sol_outboard_power_decay), + ) + axis.set_title(r"Midplane Near SOL Radial Profile") + axis.minorticks_on() + axis.tick_params(axis="x", labelbottom=False) + axis.set_ylabel(r"$q_{||}$ [MW/m$^2$]") + + +def plot_div_lower_outboard_eich_target_profile(axis: plt.Axes, mfile: MFile, scan: int): + """Function to plot the Eich target profile at the lower outboard divertor.""" + rmajor = mfile.get("rmajor", scan=scan) + rminor = mfile.get("rminor", scan=scan) + len_plasma_sol_power_decay = mfile.get( + "len_plasma_sol_eich13_power_decay", scan=scan + ) + len_div_outboard_lower_power_spreading = mfile.get( + "len_div_outboard_lower_power_spreading", scan=scan + ) + f_b_flux_expansion = mfile.get("f_b_div_outboard_lower_flux_expansion", scan=scan) + r = np.linspace( + (rmajor + rminor) + - ((f_b_flux_expansion / 2) * len_plasma_sol_power_decay) * f_b_flux_expansion, + (rmajor + rminor) + (3 * len_plasma_sol_power_decay) * f_b_flux_expansion, + 200, + ) + + pflux_target_profile = ScrapeOffLayer().calculate_eich_target_heat_flux_profile( + rmajor=rmajor, + rminor=rminor, + pflux_plasma_sol_parallel_mw=mfile.get( + "pflux_plasma_outboard_sol_parallel_mw", scan=scan + ), + len_plasma_sol_power_decay=mfile.get("len_sol_outboard_power_decay", scan=scan), + f_b_div_flux_expansion=f_b_flux_expansion, + len_plasma_sol_power_spreading=len_div_outboard_lower_power_spreading, + pflux_target_background_heat_flux_mw=0.0, + r=r, + ) + peak_idx = np.argmax(pflux_target_profile) + peak_r = r[peak_idx] + peak_q = pflux_target_profile[peak_idx] + + axis.plot(r, pflux_target_profile) + axis.axvline(peak_r, color="black", linestyle="--", linewidth=1) + axis.axhline(peak_q, color="black", linestyle="--", linewidth=1) + axis.text( + 0.8, + 0.9, + f"$f_x$ = {f_b_flux_expansion:.2f}\n$S$ = {len_div_outboard_lower_power_spreading * 1e3:.3f} mm", + transform=axis.transAxes, + ha="left", + va="top", + bbox={"boxstyle": "round", "facecolor": "white", "alpha": 1.0}, + ) + axis.grid() + axis.minorticks_on() + axis.set_title(r"Lower Outboard Eich Target Parallel Heat Flux Profile") + axis.set_xlabel("Radial Position [m]") + axis.set_xlim(r[0], r[-1]) + axis.set_ylabel(r"$q_{||,t}$ [MW/m$^2$]") + + +def plot_sol_power_flux_profiles(axis: plt.Axes, mfile: MFile, scan: int, colour_scheme): + """Plot separatrix power split fractions as a bar chart.""" + plot_plasma(axis=axis, mfile=mfile, scan=scan, colour_scheme=colour_scheme) + rmajor, rminor, kappa = mfile.get_variables( + "rmajor", + "rminor", + "kappa", + scan=scan, + ) + len_sol_outboard_power_decay = mfile.get("len_sol_outboard_power_decay", scan=scan) + a_plasma_outboard_sol_parallel = mfile.get( + "a_plasma_outboard_sol_parallel", scan=scan + ) + pflux_plasma_outboard_sol_parallel_mw = mfile.get( + "pflux_plasma_outboard_sol_parallel_mw", scan=scan + ) + plasma_scale = max(rminor, abs(kappa * rminor), 1e-6) + scale_factor = min(max(plasma_scale / 2.0, 0.7), 1.0) + text_fontsize = 9 * scale_factor + + outboard_pos = (rmajor + rminor, 0.0) + + axis.text( + *outboard_pos, + f"$\\lambda_q = {len_sol_outboard_power_decay * 1e3:.3f}$ mm\n" + f"$A_{{||}} = {a_plasma_outboard_sol_parallel:.4f}$ m$^2$\n" + f"$q_{{||}} = {pflux_plasma_outboard_sol_parallel_mw:,.2f}$ MW/m$^2$", + fontsize=text_fontsize, + verticalalignment="center", + horizontalalignment="center", + bbox={ + "boxstyle": f"round,pad={0.3 * scale_factor:.3f}", + "alpha": 1.0, + "linewidth": 2 * scale_factor, + "edgecolor": "black", + }, + zorder=101, + ) + + axis.spines["top"].set_visible(False) + axis.spines["right"].set_visible(False) + axis.spines["bottom"].set_visible(False) + axis.spines["left"].set_visible(False) + axis.get_xaxis().set_ticks([]) + axis.get_yaxis().set_ticks([]) + + def plot_h_threshold_comparison(axis: plt.Axes, mfile: MFile, scan: int, u_seed=None): """Function to plot a scatter box plot of L-H threshold power comparisons. @@ -16731,6 +16886,20 @@ def _add_page(name: str | None = None): pages["plasma_exhaust"].add_subplot(122), m_file, scan, colour_scheme ) + plot_sol_power_flux_profiles( + _add_page("sol_powerfluxes").add_subplot(121), m_file, scan, colour_scheme + ) + + ax_midplane_near_sol = pages["sol_powerfluxes"].add_subplot(336) + plot_midplane_near_sol_radial_profile( + ax_midplane_near_sol, m_file, scan, colour_scheme + ) + + ax_div_lower_outboard = pages["sol_powerfluxes"].add_subplot( + 339, sharex=ax_midplane_near_sol + ) + plot_div_lower_outboard_eich_target_profile(ax_div_lower_outboard, m_file, scan) + plot_debye_length_profile( _add_page("microscopic_quantities").add_subplot(232), m_file, scan ) diff --git a/process/data_structure/physics_variables.py b/process/data_structure/physics_variables.py index 53d7bf343a..f41d4f0241 100644 --- a/process/data_structure/physics_variables.py +++ b/process/data_structure/physics_variables.py @@ -1761,6 +1761,15 @@ class PhysicsData: - =3 MAST 2014 scaling 2 """ + f_b_div_outboard_lower_flux_expansion: float = 5.0 + """Outboard lower divertor flux expansion factor for the divertor targets (fₓ)""" + + len_div_outboard_lower_scarabosio15_power_spreading: float = 0.0 + """Scarabosio 2015 H-mode power spreading length/factor in the scrape-off layer scaling (S) [m]""" + + len_div_outboard_lower_power_spreading: float = 0.0 + """Power spreading length/factor at the outboard lower divertor target (S) [m]""" + dt_power_density_plasma: float = 0.0 sigmav_dt_average: float = 0.0 dhe3_power_density: float = 0.0 diff --git a/process/models/physics/scrape_off_layer.py b/process/models/physics/scrape_off_layer.py index 4388d79914..554d20c307 100644 --- a/process/models/physics/scrape_off_layer.py +++ b/process/models/physics/scrape_off_layer.py @@ -3,6 +3,7 @@ import logging import numpy as np +import scipy from process.core import constants from process.core import process_output as po @@ -94,6 +95,7 @@ def run(self): self.data.physics.pflux_plasma_outboard_sol_parallel_mw = ( self.data.physics.p_plasma_separatrix_mw + * self.data.physics.f_p_div_outboard_separatrix / self.data.physics.a_plasma_outboard_sol_parallel ) @@ -102,6 +104,18 @@ def run(self): / self.data.physics.a_plasma_outboard_sol_eich13_parallel ) + self.data.physics.len_div_outboard_lower_scarabosio15_power_spreading = self.calculate_scarabosio2015_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, + ) + + self.data.physics.len_div_outboard_lower_power_spreading = ( + self.data.physics.len_div_outboard_lower_scarabosio15_power_spreading + ) + def output(self) -> None: """Output plasma scrape off layer physics information.""" po.oheadr(self.outfile, "Plasma Scrape Off Layer") @@ -187,6 +201,32 @@ def output(self) -> None: "(pflux_plasma_outboard_sol_eich13_parallel_mw)", self.data.physics.pflux_plasma_outboard_sol_eich13_parallel_mw, ) + po.oblnkl(self.outfile) + po.ocmmnt(self.outfile, "----------------------------") + po.osubhd(self.outfile, "Power Spreading Factors (S):") + + po.ovarre( + self.outfile, + "Outboard lower divertor power spreading factor (S) [m]", + "(len_div_outboard_lower_power_spreading)", + self.data.physics.len_div_outboard_lower_power_spreading, + ) + po.ovarre( + self.outfile, + "Scarabosio 2015 H-mode power spreading factor (S) [m]", + "(len_div_outboard_lower_scarabosio15_power_spreading)", + self.data.physics.len_div_outboard_lower_scarabosio15_power_spreading, + ) + po.oblnkl(self.outfile) + po.ocmmnt(self.outfile, "----------------------------") + po.oblnkl(self.outfile) + po.ovarre( + self.outfile, + "Outboard lower divertor flux expansion factor for the divertor targets " + "(fₓ)", + "(f_b_div_outboard_lower_flux_expansion)", + self.data.physics.f_b_div_outboard_lower_flux_expansion, + ) @staticmethod def calculate_eich2013_sol_power_decay_length( @@ -344,3 +384,186 @@ def calculate_upstream_sol_outboard_parallel_area( * len_plasma_sol_power_decay * (b_plasma_surface_poloidal_average / b_plasma_outboard_total) ) + + @staticmethod + def calculate_outboard_midplane_near_sol_radial_profile( + rmajor: float, + rminor: float, + len_plasma_sol_power_decay: float, + pflux_plasma_outboard_sol_parallel_mw: float, + r: float | np.ndarray, + ) -> float | np.ndarray: + """Calculate the outboard midplane near SOL radial profile (qₗₗ(r)) [MW/m²]. + + Parameters + ---------- + rmajor : float + Major radius of the plasma (R₀) [m] + rminor : float + Minor radius of the plasma (a) [m] + len_plasma_sol_power_decay : float + Power decay length (λ_q) [m] + pflux_plasma_outboard_sol_parallel_mw : float + Parallel power flux at the outboard midplane (qₗₗ,ᵤ) [MW/m²] + r : float|np.ndarray + Radial position(s) at which to calculate the SOL profile [m] + + Returns + ------- + float|np.ndarray + Outboard midplane SOL radial profile (qₗₗ(r)) [MW/m²] + + Raises + ------ + ValueError + If any radial position r is inside the plasma edge (r < rmajor + rminor) + + Notes + ----- + - The exponential model is highly valid in the "near-SOL" (typically the first + few millimeters to a centimeter outside the separatrix). In this region, parallel + heat transport is dominated by classical electron heat conduction + (Spitzer-Härm conductivity), which is vastly faster than perpendicular diffusion. + This competition between fast parallel conduction and slow perpendicular + diffusion naturally produces an exponential radial profile. + + - The midplane exponential assumes steady-state H-mode conditions without the + massive, transient convective bursts caused by ELMs, which momentarily + flatten the entire midplane profile. + + References + ---------- + [1] 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. + + """ + if np.any(r < (rmajor + rminor)): + raise ValueError( + f"Radial position r={r} must be greater than or equal to the plasma " + f"edge (rmajor + rminor)={rmajor + rminor}." + ) + + return pflux_plasma_outboard_sol_parallel_mw * np.exp( + -(r - (rmajor + rminor)) / len_plasma_sol_power_decay + ) + + @staticmethod + def calculate_eich_target_heat_flux_profile( + rmajor: float, + rminor: float, + pflux_plasma_sol_parallel_mw: float, + len_plasma_sol_power_decay: float, + f_b_div_flux_expansion: float, + len_plasma_sol_power_spreading: float, + pflux_target_background_heat_flux_mw: float, + r: float | np.ndarray, + ) -> float | np.ndarray: + """Calculate the Eich parallel target heat flux profile (qₗₗ,ₜ(r)) [MW/m²]. + + Parameters + ---------- + rmajor : float + Major radius of the plasma (R₀) [m] + rminor : float + Minor radius of the plasma (a) [m] + pflux_plasma_sol_parallel_mw : float + Parallel power flux at the outboard midplane (qₗₗ,ᵤ) [MW/m²] + len_plasma_sol_power_decay : float + Power decay length (λ_q) [m] + f_b_div_flux_expansion : float + Divertor flux expansion factor (fₓ) [-] + len_plasma_sol_power_spreading : float + Power spreading length in the divertor (S) [m] + pflux_target_background_heat_flux_mw : float + Background heat flux at the divertor target [MW/m²] + r : float|np.ndarray + Radial position(s) at which to calculate the target heat flux profile [m] + + Returns + ------- + float|np.ndarray + Eich parallel target heat flux profile (qₗₗ,ₜ(r)) [MW/m²] + + Notes + ----- + - The Eich parallel target heat flux profile is derived from the midplane + exponential profile, taking into account the magnetic geometry and flux expansion + between the midplane and the divertor target. The profile is typically + characterized by a combination of an exponential decay and a Gaussian spreading + due to cross-field transport in the divertor leg. + + References + ---------- + [1] T. Eich, B. Sieglin, A. Scarabosio, W. Fundamenski, R. J. Goldston, and + A. Herrmann, “Inter-ELM Power Decay Length for JET and ASDEX Upgrade: Measurement + and Comparison with Heuristic Drift-Based Model,” Physical Review Letters, + vol. 107, no. 21, Nov. 2011, doi: https://doi.org/10.1103/PhysRevLett.107.215001 + + [2] 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. + + """ + return (pflux_plasma_sol_parallel_mw / 2) * np.exp( + ( + (len_plasma_sol_power_spreading) + / (2 * len_plasma_sol_power_decay * f_b_div_flux_expansion) + ) + ** 2 + - ( + (r - (rmajor + rminor)) + / (len_plasma_sol_power_decay * f_b_div_flux_expansion) + ) + ) * scipy.special.erfc( + ( + len_plasma_sol_power_spreading + / (2 * len_plasma_sol_power_decay * f_b_div_flux_expansion) + ) + - ((r - (rmajor + rminor)) / (len_plasma_sol_power_spreading)) + ) + pflux_target_background_heat_flux_mw + + @staticmethod + def calculate_scarabosio2015_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 Scarabosio 2015 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 + Scarabosio 2015 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/unit/models/physics/test_scrape_off_layer.py b/tests/unit/models/physics/test_scrape_off_layer.py index bbcd78a553..7f7c843a18 100644 --- a/tests/unit/models/physics/test_scrape_off_layer.py +++ b/tests/unit/models/physics/test_scrape_off_layer.py @@ -1,3 +1,4 @@ +import numpy as np import pytest from process.models.physics.scrape_off_layer import ScrapeOffLayer @@ -112,3 +113,91 @@ def test_calculate_upstream_sol_outboard_parallel_area_exact(): ) assert isinstance(result, float) assert pytest.approx(result) == 0.006283185307179587 + + +@pytest.mark.parametrize( + "r", + [ + 8.001, + 8.01, + 8.1, + ], +) +def test_calculate_outboard_midplane_near_sol_radial_profile(r): + """Test outboard midplane near SOL radial profile with various parameters.""" + result = ScrapeOffLayer.calculate_outboard_midplane_near_sol_radial_profile( + rmajor=6.0, + rminor=2.0, + len_plasma_sol_power_decay=0.001, + pflux_plasma_outboard_sol_parallel_mw=10.0, + r=r, + ) + assert isinstance(result, float) + assert result > 0 + + +def test_calculate_outboard_midplane_near_sol_radial_profile_exact(): + """Test outboard midplane near SOL radial profile with exact value check.""" + result = ScrapeOffLayer.calculate_outboard_midplane_near_sol_radial_profile( + rmajor=6.0, + rminor=2.0, + len_plasma_sol_power_decay=0.001, + pflux_plasma_outboard_sol_parallel_mw=10.0, + r=8.001, + ) + assert isinstance(result, float) + assert pytest.approx(result) == 3.678794411714423 + + +def test_calculate_outboard_midplane_near_sol_radial_profile_array(): + """Test outboard midplane near SOL radial profile with array input.""" + r = np.array([8.001, 8.002, 8.003]) + result = ScrapeOffLayer.calculate_outboard_midplane_near_sol_radial_profile( + rmajor=6.0, + rminor=2.0, + len_plasma_sol_power_decay=0.001, + pflux_plasma_outboard_sol_parallel_mw=10.0, + r=r, + ) + assert isinstance(result, np.ndarray) + assert np.all(result > 0) + + +@pytest.mark.parametrize( + "r", + [ + 8.001, + 8.01, + 8.1, + ], +) +def test_calculate_eich_target_heat_flux_profile(r): + """Test Eich target heat flux profile with various parameters.""" + result = ScrapeOffLayer.calculate_eich_target_heat_flux_profile( + rmajor=6.0, + rminor=2.0, + pflux_plasma_sol_parallel_mw=10.0, + len_plasma_sol_power_decay=0.001, + f_b_div_flux_expansion=2.0, + len_plasma_sol_power_spreading=0.001, + pflux_target_background_heat_flux_mw=0.01, + r=r, + ) + assert isinstance(result, float) + assert result > 0 + + +def test_calculate_eich_target_heat_flux_profile_exact(): + """Test Eich target heat flux profile with exact value check.""" + result = ScrapeOffLayer.calculate_eich_target_heat_flux_profile( + rmajor=6.0, + rminor=2.0, + pflux_plasma_sol_parallel_mw=10.0, + len_plasma_sol_power_decay=0.001, + f_b_div_flux_expansion=2.0, + len_plasma_sol_power_spreading=0.001, + pflux_target_background_heat_flux_mw=0.01, + r=8.001, + ) + assert isinstance(result, float) + assert pytest.approx(result) == 5.534025566786268