From 43db2162d8672260fd819025adf789171794403f Mon Sep 17 00:00:00 2001 From: mn3981 Date: Fri, 24 Jul 2026 14:49:30 +0100 Subject: [PATCH 1/7] Add ScrapeOffLayer to physics models --- process/main.py | 3 +++ process/models/physics/physics.py | 3 +++ process/models/physics/scrape_off_layer.py | 22 ++++++++++++++++++++++ 3 files changed, 28 insertions(+) create mode 100644 process/models/physics/scrape_off_layer.py diff --git a/process/main.py b/process/main.py index 551fe9f852..f7cb406b6d 100644 --- a/process/main.py +++ b/process/main.py @@ -103,6 +103,7 @@ from process.models.physics.plasma_geometry import PlasmaGeom from process.models.physics.plasma_profiles import PlasmaProfile from process.models.physics.profiles import NeProfile, TeProfile +from process.models.physics.scrape_off_layer import ScrapeOffLayer from process.models.power import Power from process.models.pulse import Pulse from process.models.shield import Shield @@ -679,6 +680,7 @@ def __init__(self, data: DataStructure): self.plasma_current = PlasmaCurrent() self.plasma_fields = PlasmaFields() self.plasma_dia_current = PlasmaDiamagneticCurrent() + self.scrape_off_layer = ScrapeOffLayer() self.physics = Physics( plasma_profile=self.plasma_profile, current_drive=self.current_drive, @@ -693,6 +695,7 @@ def __init__(self, data: DataStructure): plasma_fields=self.plasma_fields, plasma_dia_current=self.plasma_dia_current, plasma_geometry=self.plasma_geom, + scrape_off_layer=self.scrape_off_layer, ) self.physics_detailed = DetailedPhysics( plasma_profile=self.plasma_profile, diff --git a/process/models/physics/physics.py b/process/models/physics/physics.py index ccd54551d4..59b5373269 100644 --- a/process/models/physics/physics.py +++ b/process/models/physics/physics.py @@ -45,6 +45,7 @@ from process.models.physics.plasma_fields import PlasmaFields from process.models.physics.plasma_geometry import PlasmaGeom from process.models.physics.plasma_profiles import PlasmaProfile + from process.models.physics.scrape_off_layer import ScrapeOffLayer logger = logging.getLogger(__name__) @@ -195,6 +196,7 @@ def __init__( plasma_fields: PlasmaFields, plasma_dia_current: PlasmaDiamagneticCurrent, plasma_geometry: PlasmaGeom, + scrape_off_layer: ScrapeOffLayer, ): self.outfile = constants.NOUT self.mfile = constants.MFILE @@ -211,6 +213,7 @@ def __init__( self.fields = plasma_fields self.dia_current = plasma_dia_current self.geometry = plasma_geometry + self.scrape_off_layer = scrape_off_layer def output(self) -> None: """Output plasma physics information.""" diff --git a/process/models/physics/scrape_off_layer.py b/process/models/physics/scrape_off_layer.py new file mode 100644 index 0000000000..c5a84d1ae1 --- /dev/null +++ b/process/models/physics/scrape_off_layer.py @@ -0,0 +1,22 @@ +"""Module for calculating plasma scrape off layers physics""" + +import logging + +from process.core import constants +from process.core.model import Model + +logger = logging.getLogger(__name__) + + +class ScrapeOffLayer(Model): + """Model for calculating plasma scrape off layers physics.""" + + def __init__(self): + self.outfile = constants.NOUT + self.mfile = constants.MFILE + + def run(self): + """Run the model. This model cannot yet be 'run'.""" + + def output(self) -> None: + """Output plasma scrape off layer physics information.""" \ No newline at end of file From a4881badff83bc7488b0ebf042c4e8e5dd2c49cb Mon Sep 17 00:00:00 2001 From: mn3981 Date: Fri, 24 Jul 2026 16:14:30 +0100 Subject: [PATCH 2/7] Add calculate_eich2013_sol_power_decay_length method and corresponding tests --- process/models/physics/scrape_off_layer.py | 46 ++++++++++++- .../models/physics/test_scrape_off_layer.py | 65 +++++++++++++++++++ 2 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 tests/unit/models/physics/test_scrape_off_layer.py diff --git a/process/models/physics/scrape_off_layer.py b/process/models/physics/scrape_off_layer.py index c5a84d1ae1..cecc4083ed 100644 --- a/process/models/physics/scrape_off_layer.py +++ b/process/models/physics/scrape_off_layer.py @@ -19,4 +19,48 @@ def run(self): """Run the model. This model cannot yet be 'run'.""" def output(self) -> None: - """Output plasma scrape off layer physics information.""" \ No newline at end of file + """Output plasma scrape off layer physics information.""" + + @staticmethod + def calculate_eich2013_sol_power_decay_length( + p_plasma_separatrix_mw: float, + rmajor: float, + b_plasma_surface_poloidal_average: float, + aspect: float, + ) -> float: + """Calculate the Eich 2013 SOL power decay length (λ_q). + + Parameters + ---------- + p_plasma_separatrix_mw : float + Power crossing the separatrix (Pₛₑₚ) (MW) + rmajor : float + Major radius of the plasma (R₀) [m] + b_plasma_surface_poloidal_average : float + Poloidal magnetic field at the plasma surface (⟨Bₚₒₗ(a)⟩) [T] + aspect : float + Aspect ratio of the plasma (A) + + Returns + ------- + float + Eich 2013 SOL power decay length (λ_q) [m] + + Notes + ----- + - The paper states that the poloidal field terms is for the outer midplane + Bₚₒₗ(a), we are using the outer surface average + + 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. + + """ + return ( + 1.35e-3 * p_plasma_separatrix_mw**-0.02 + + rmajor**0.04 + + b_plasma_surface_poloidal_average**-0.92 + + aspect**-0.42 + ) diff --git a/tests/unit/models/physics/test_scrape_off_layer.py b/tests/unit/models/physics/test_scrape_off_layer.py new file mode 100644 index 0000000000..afca46b297 --- /dev/null +++ b/tests/unit/models/physics/test_scrape_off_layer.py @@ -0,0 +1,65 @@ +from process.models.physics.scrape_off_layer import ScrapeOffLayer + + +class TestScrapeOffLayer: + """Test suite for ScrapeOffLayer class.""" + + @staticmethod + def test_calculate_eich2013_sol_power_decay_length_nominal(): + """Test Eich 2013 SOL power decay length with nominal values.""" + result = ScrapeOffLayer.calculate_eich2013_sol_power_decay_length( + p_plasma_separatrix_mw=100.0, + rmajor=3.0, + b_plasma_surface_poloidal_average=0.5, + aspect=3.0, + ) + assert isinstance(result, float) + assert result > 0 + + @staticmethod + def test_calculate_eich2013_sol_power_decay_length_low_power(): + """Test with low plasma separatrix power.""" + result = ScrapeOffLayer.calculate_eich2013_sol_power_decay_length( + p_plasma_separatrix_mw=10.0, + rmajor=3.0, + b_plasma_surface_poloidal_average=0.5, + aspect=3.0, + ) + assert isinstance(result, float) + assert result > 0 + + @staticmethod + def test_calculate_eich2013_sol_power_decay_length_high_power(): + """Test with high plasma separatrix power.""" + result = ScrapeOffLayer.calculate_eich2013_sol_power_decay_length( + p_plasma_separatrix_mw=500.0, + rmajor=3.0, + b_plasma_surface_poloidal_average=0.5, + aspect=3.0, + ) + assert isinstance(result, float) + assert result > 0 + + @staticmethod + def test_calculate_eich2013_sol_power_decay_length_large_rmajor(): + """Test with large major radius.""" + result = ScrapeOffLayer.calculate_eich2013_sol_power_decay_length( + p_plasma_separatrix_mw=100.0, + rmajor=10.0, + b_plasma_surface_poloidal_average=0.5, + aspect=3.0, + ) + assert isinstance(result, float) + assert result > 0 + + @staticmethod + def test_calculate_eich2013_sol_power_decay_length_small_rmajor(): + """Test with small major radius.""" + result = ScrapeOffLayer.calculate_eich2013_sol_power_decay_length( + p_plasma_separatrix_mw=100.0, + rmajor=1.0, + b_plasma_surface_poloidal_average=0.5, + aspect=3.0, + ) + assert isinstance(result, float) + assert result > 0 From 2149e8c31fff738be17ea896a8eeb64a3aa356b8 Mon Sep 17 00:00:00 2001 From: mn3981 Date: Fri, 24 Jul 2026 16:31:30 +0100 Subject: [PATCH 3/7] Add MAST 2014 SOL power decay length calculations and corresponding tests --- process/models/physics/scrape_off_layer.py | 67 ++++++++++++++++ .../models/physics/test_scrape_off_layer.py | 80 +++++++++++++++++++ 2 files changed, 147 insertions(+) diff --git a/process/models/physics/scrape_off_layer.py b/process/models/physics/scrape_off_layer.py index cecc4083ed..0330b9a5b4 100644 --- a/process/models/physics/scrape_off_layer.py +++ b/process/models/physics/scrape_off_layer.py @@ -64,3 +64,70 @@ def calculate_eich2013_sol_power_decay_length( + b_plasma_surface_poloidal_average**-0.92 + aspect**-0.42 ) + + @staticmethod + def calculate_mast2014_sol_power_decay_length_1( + p_plasma_separatrix_mw: float, + b_plasma_surface_poloidal_average: float, + ) -> float: + """Calculate the MAST 2014 SOL power decay length (λ_q). + + 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] + + Returns + ------- + float + MAST 2014 SOL power decay length (λ_q) [m] + + Notes + ----- + - The paper states that the poloidal field terms is for the outer midplane + Bₚₒₗ(a), we are using the outer surface average + + References + ---------- + [1] 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,” + Plasma Physics and Controlled Fusion, vol. 56, no. 5, p. 055008, Apr. 2014, + doi: 10.1088/0741-3335/56/5/055008. + + """ + return ( + 1.84e-3 * p_plasma_separatrix_mw**0.18 + + b_plasma_surface_poloidal_average**-0.68 + ) + + @staticmethod + def calculate_mast2014_sol_power_decay_length_2( + p_plasma_separatrix_mw: float, + cur_plasma_ma: float, + ) -> float: + """Calculate the MAST 2014 SOL power decay length (λ_q). + + Parameters + ---------- + p_plasma_separatrix_mw : float + Power crossing the separatrix (Pₛₑₚ) (MW) + cur_plasma_ma : float + Plasma current (Iₚ) [MA] + + Returns + ------- + float + MAST 2014 SOL power decay length (λ_q) [m] + + + References + ---------- + [1] 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,” + Plasma Physics and Controlled Fusion, vol. 56, no. 5, p. 055008, Apr. 2014, + doi: 10.1088/0741-3335/56/5/055008. + + """ + return 4.57e-3 * p_plasma_separatrix_mw**0.22 + cur_plasma_ma**-0.64 diff --git a/tests/unit/models/physics/test_scrape_off_layer.py b/tests/unit/models/physics/test_scrape_off_layer.py index afca46b297..d232384219 100644 --- a/tests/unit/models/physics/test_scrape_off_layer.py +++ b/tests/unit/models/physics/test_scrape_off_layer.py @@ -63,3 +63,83 @@ def test_calculate_eich2013_sol_power_decay_length_small_rmajor(): ) assert isinstance(result, float) assert result > 0 + + @staticmethod + def test_calculate_mast2014_sol_power_decay_length_1_nominal(): + """Test MAST 2014 SOL power decay length 1 with nominal values.""" + result = ScrapeOffLayer.calculate_mast2014_sol_power_decay_length_1( + p_plasma_separatrix_mw=100.0, + b_plasma_surface_poloidal_average=0.5, + ) + assert isinstance(result, float) + assert result > 0 + + @staticmethod + def test_calculate_mast2014_sol_power_decay_length_1_low_power(): + """Test MAST 2014 SOL power decay length 1 with low power.""" + result = ScrapeOffLayer.calculate_mast2014_sol_power_decay_length_1( + p_plasma_separatrix_mw=10.0, + b_plasma_surface_poloidal_average=0.5, + ) + assert isinstance(result, float) + assert result > 0 + + @staticmethod + def test_calculate_mast2014_sol_power_decay_length_1_high_power(): + """Test MAST 2014 SOL power decay length 1 with high power.""" + result = ScrapeOffLayer.calculate_mast2014_sol_power_decay_length_1( + p_plasma_separatrix_mw=500.0, + b_plasma_surface_poloidal_average=0.5, + ) + assert isinstance(result, float) + assert result > 0 + + @staticmethod + def test_calculate_mast2014_sol_power_decay_length_1_high_bpol(): + """Test MAST 2014 SOL power decay length 1 with high poloidal field.""" + result = ScrapeOffLayer.calculate_mast2014_sol_power_decay_length_1( + p_plasma_separatrix_mw=100.0, + b_plasma_surface_poloidal_average=2.0, + ) + assert isinstance(result, float) + assert result > 0 + + @staticmethod + def test_calculate_mast2014_sol_power_decay_length_2_nominal(): + """Test MAST 2014 SOL power decay length 2 with nominal values.""" + result = ScrapeOffLayer.calculate_mast2014_sol_power_decay_length_2( + p_plasma_separatrix_mw=100.0, + cur_plasma_ma=1.0, + ) + assert isinstance(result, float) + assert result > 0 + + @staticmethod + def test_calculate_mast2014_sol_power_decay_length_2_low_power(): + """Test MAST 2014 SOL power decay length 2 with low power.""" + result = ScrapeOffLayer.calculate_mast2014_sol_power_decay_length_2( + p_plasma_separatrix_mw=10.0, + cur_plasma_ma=1.0, + ) + assert isinstance(result, float) + assert result > 0 + + @staticmethod + def test_calculate_mast2014_sol_power_decay_length_2_high_power(): + """Test MAST 2014 SOL power decay length 2 with high power.""" + result = ScrapeOffLayer.calculate_mast2014_sol_power_decay_length_2( + p_plasma_separatrix_mw=500.0, + cur_plasma_ma=1.0, + ) + assert isinstance(result, float) + assert result > 0 + + @staticmethod + def test_calculate_mast2014_sol_power_decay_length_2_high_current(): + """Test MAST 2014 SOL power decay length 2 with high plasma current.""" + result = ScrapeOffLayer.calculate_mast2014_sol_power_decay_length_2( + p_plasma_separatrix_mw=100.0, + cur_plasma_ma=3.0, + ) + assert isinstance(result, float) + assert result > 0 From fc0e433ce722a575ff2efb57471c4c4e0dc94bbb Mon Sep 17 00:00:00 2001 From: mn3981 Date: Fri, 24 Jul 2026 16:55:26 +0100 Subject: [PATCH 4/7] Add power decay length variables for Eich 2013 and MAST 2014 in PhysicsData class --- process/data_structure/physics_variables.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/process/data_structure/physics_variables.py b/process/data_structure/physics_variables.py index edc5b4f3fc..941b8310b4 100644 --- a/process/data_structure/physics_variables.py +++ b/process/data_structure/physics_variables.py @@ -1651,6 +1651,15 @@ class PhysicsData: res_plasma_fuel_spitzer_vol_avg: float = 0.0 """Volume averaged plasma Spitzer resistivity due to fuel ions (ohm m)""" + len_plasma_sol_eich13_power_decay: float = 0.0 + """Eich 2013 power decay length in the scrape-off layer scaling (λ_q) [m]""" + + len_plasma_sol_mast14_power_decay_1: float = 0.0 + """MAST 2014 power decay length in the scrape-off layer scaling 1 (λ_q) [m]""" + + len_plasma_sol_mast14_power_decay_2: float = 0.0 + """MAST 2014 power decay length in the scrape-off layer scaling 2 (λ_q) [m]""" + dt_power_density_plasma: float = 0.0 sigmav_dt_average: float = 0.0 dhe3_power_density: float = 0.0 From 38dd4acffb614b75faf604fccc2448a0f4c3123a Mon Sep 17 00:00:00 2001 From: mn3981 Date: Fri, 24 Jul 2026 17:26:19 +0100 Subject: [PATCH 5/7] Add scrape-off layer calculations and output to physics model --- process/main.py | 1 + process/models/physics/physics.py | 9 ++++ process/models/physics/scrape_off_layer.py | 59 +++++++++++++++++++--- 3 files changed, 61 insertions(+), 8 deletions(-) diff --git a/process/main.py b/process/main.py index f7cb406b6d..0bd541c08f 100644 --- a/process/main.py +++ b/process/main.py @@ -785,6 +785,7 @@ def models(self) -> tuple[Model, ...]: self.plasma_bootstrap_current, self.plasma_exhaust, self.plasma_current, + self.scrape_off_layer, self.neoclassics, self.plasma_inductance, self.ne_profile, diff --git a/process/models/physics/physics.py b/process/models/physics/physics.py index 59b5373269..a529d3efcf 100644 --- a/process/models/physics/physics.py +++ b/process/models/physics/physics.py @@ -816,6 +816,13 @@ def run(self): ) ) + # =============================== + + # Calculate scrape-off layer physics + + self.scrape_off_layer.run() + + # =============================== self.data.physics.pflux_plasma_surface_neutron_avg_mw = ( self.data.physics.p_neutron_total_mw / self.data.physics.a_plasma_surface ) @@ -2337,6 +2344,8 @@ def outplas(self): self.exhaust.output() + self.scrape_off_layer.output() + if self.data.stellarator.istell == 0: self.plasma_transition.output() diff --git a/process/models/physics/scrape_off_layer.py b/process/models/physics/scrape_off_layer.py index 0330b9a5b4..e54a626de9 100644 --- a/process/models/physics/scrape_off_layer.py +++ b/process/models/physics/scrape_off_layer.py @@ -3,6 +3,7 @@ import logging from process.core import constants +from process.core import process_output as po from process.core.model import Model logger = logging.getLogger(__name__) @@ -16,10 +17,50 @@ def __init__(self): self.mfile = constants.MFILE def run(self): - """Run the model. This model cannot yet be 'run'.""" + """Calculate the scrape off layer physics and update the physics variables.""" + self.data.physics.len_plasma_sol_eich13_power_decay = self.calculate_eich2013_sol_power_decay_length( + p_plasma_separatrix_mw=self.data.physics.p_plasma_separatrix_mw, + rmajor=self.data.physics.rmajor, + b_plasma_surface_poloidal_average=self.data.physics.b_plasma_surface_poloidal_average, + aspect=self.data.physics.aspect, + ) + + self.data.physics.len_plasma_sol_mast14_power_decay_1 = self.calculate_mast2014_sol_power_decay_length_1( + p_plasma_separatrix_mw=self.data.physics.p_plasma_separatrix_mw, + b_plasma_surface_poloidal_average=self.data.physics.b_plasma_surface_poloidal_average, + ) + + self.data.physics.len_plasma_sol_mast14_power_decay_2 = ( + self.calculate_mast2014_sol_power_decay_length_2( + p_plasma_separatrix_mw=self.data.physics.p_plasma_separatrix_mw, + cur_plasma_ma=self.data.physics.plasma_current / 1e6, # Convert A to MA + ) + ) def output(self) -> None: """Output plasma scrape off layer physics information.""" + po.oheadr(self.outfile, "Plasma Scrape Off Layer") + + po.osubhd(self.outfile, "Power Decay Lengths (λ_q)") + + po.ovarre( + self.outfile, + "Eich 2013 SOL power decay length (λ_q) [m]", + "(len_plasma_sol_eich13_power_decay)", + self.data.physics.len_plasma_sol_eich13_power_decay, + ) + po.ovarre( + self.outfile, + "MAST 2014 SOL power decay length 1 (λ_q) [m]", + "(len_plasma_sol_mast14_power_decay_1)", + self.data.physics.len_plasma_sol_mast14_power_decay_1, + ) + po.ovarre( + self.outfile, + "MAST 2014 SOL power decay length 2 (λ_q) [m]", + "(len_plasma_sol_mast14_power_decay_2)", + self.data.physics.len_plasma_sol_mast14_power_decay_2, + ) @staticmethod def calculate_eich2013_sol_power_decay_length( @@ -59,10 +100,11 @@ def calculate_eich2013_sol_power_decay_length( """ return ( - 1.35e-3 * p_plasma_separatrix_mw**-0.02 - + rmajor**0.04 - + b_plasma_surface_poloidal_average**-0.92 - + aspect**-0.42 + 1.35e-3 + * p_plasma_separatrix_mw**-0.02 + * rmajor**0.04 + * b_plasma_surface_poloidal_average**-0.92 + * aspect**-0.42 ) @staticmethod @@ -98,8 +140,9 @@ def calculate_mast2014_sol_power_decay_length_1( """ return ( - 1.84e-3 * p_plasma_separatrix_mw**0.18 - + b_plasma_surface_poloidal_average**-0.68 + 1.84e-3 + * p_plasma_separatrix_mw**0.18 + * b_plasma_surface_poloidal_average**-0.68 ) @staticmethod @@ -130,4 +173,4 @@ def calculate_mast2014_sol_power_decay_length_2( doi: 10.1088/0741-3335/56/5/055008. """ - return 4.57e-3 * p_plasma_separatrix_mw**0.22 + cur_plasma_ma**-0.64 + return 4.57e-3 * p_plasma_separatrix_mw**0.22 * cur_plasma_ma**-0.64 From bc5497abddfb145357d99324a1576f17d558a083 Mon Sep 17 00:00:00 2001 From: mn3981 Date: Fri, 24 Jul 2026 17:32:52 +0100 Subject: [PATCH 6/7] Add SOL power decay length comparison plotting function to summary output --- process/core/io/plot/summary.py | 83 +++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/process/core/io/plot/summary.py b/process/core/io/plot/summary.py index dc5b6052db..efc1e9fcd4 100644 --- a/process/core/io/plot/summary.py +++ b/process/core/io/plot/summary.py @@ -8914,6 +8914,85 @@ def plot_bootstrap_comparison(axis: plt.Axes, mfile: MFile, scan: int): axis.set_facecolor("#f0f0f0") +def plot_sol_power_decay_length_comparison(axis: plt.Axes, mfile: MFile, scan: int): + """Function to plot a scatter box plot of SOL power decay lengths (λ_q). + + Parameters + ---------- + axis : + axis object to plot to + mfile : + MFILE data object + scan : + scan number to use + """ + len_plasma_sol_eich13_power_decay_mm = ( + mfile.get("len_plasma_sol_eich13_power_decay", scan=scan) * 1e3 + ) + len_plasma_sol_mast14_power_decay_1_mm = ( + mfile.get("len_plasma_sol_mast14_power_decay_1", scan=scan) * 1e3 + ) + len_plasma_sol_mast14_power_decay_2_mm = ( + mfile.get("len_plasma_sol_mast14_power_decay_2", scan=scan) * 1e3 + ) + + # Data for the box plot + data = { + "Eich 2013": len_plasma_sol_eich13_power_decay_mm, + "MAST 2014 (1)": len_plasma_sol_mast14_power_decay_1_mm, + "MAST 2014 (2)": len_plasma_sol_mast14_power_decay_2_mm, + } + # Create the violin plot + axis.violinplot(data.values(), showextrema=False) + + # Create the box plot + axis.boxplot( + data.values(), showfliers=True, showmeans=True, meanline=True, widths=0.3 + ) + + # Scatter plot for each data point + colors = plt.cm.plasma(np.linspace(0, 1, len(data.values()))) + for index, (key, value) in enumerate(data.items()): + axis.scatter(1, value, color=colors[index], label=key, alpha=1.0) + axis.legend(loc="upper left", bbox_to_anchor=(1, 1)) + + # Calculate average, standard deviation, and median + data_values = list(data.values()) + avg_decay_length = np.mean(data_values) + std_decay_length = np.std(data_values) + median_decay_length = np.median(data_values) + + # Plot average, standard deviation, and median as text + axis.text( + 1.02, + 0.2, + f"Average: {avg_decay_length:.4f}", + transform=axis.transAxes, + fontsize=9, + ) + axis.text( + 1.02, + 0.15, + f"Standard Dev: {std_decay_length:.4f}", + transform=axis.transAxes, + fontsize=9, + ) + axis.text( + 1.02, + 0.1, + f"Median: {median_decay_length:.4f}", + transform=axis.transAxes, + fontsize=9, + ) + + axis.set_title("SOL Power Decay Length ($\\lambda_q$) Comparison") + axis.set_ylabel("Power Decay Length [mm]") + axis.set_xlim([0.5, 1.5]) + axis.set_xticks([]) + axis.set_xticklabels([]) + axis.set_facecolor("#f0f0f0") + + 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. @@ -16090,6 +16169,10 @@ def _add_page(name: str | None = None): pages["plasma_compare_2"].add_subplot(224), m_file, scan ) + plot_sol_power_decay_length_comparison( + _add_page("plasma_compare_3").add_subplot(221), m_file, scan + ) + plot_debye_length_profile( _add_page("microscopic_quantities").add_subplot(232), m_file, scan ) From 53b76ab9debeb73281e59639776257ba04e079c3 Mon Sep 17 00:00:00 2001 From: mn3981 Date: Mon, 27 Jul 2026 13:13:34 +0100 Subject: [PATCH 7/7] Add documentation for Plasma Scrape-off Layer model and update navigation --- .../physics-models/plasma_scrape_off_layer.md | 60 +++++++++++++++++++ mkdocs.yml | 1 + 2 files changed, 61 insertions(+) create mode 100644 documentation/source/physics-models/plasma_scrape_off_layer.md diff --git a/documentation/source/physics-models/plasma_scrape_off_layer.md b/documentation/source/physics-models/plasma_scrape_off_layer.md new file mode 100644 index 0000000000..1f9c009797 --- /dev/null +++ b/documentation/source/physics-models/plasma_scrape_off_layer.md @@ -0,0 +1,60 @@ +# Scrape off Layer | `ScrapeOffLayer` + +## Power Decay Lengths + +In tokamak scrape-off layer (SOL) physics, $\lambda_q$ is the radial heat flux e-folding length, representing the narrow width over which exhaust power drops exponentially. It measures how tightly thermal energy exiting the core plasma is compressed onto open magnetic field lines. + +----------- + +### Eich 2013 Model | `calculate_eich2013_sol_power_decay_length()` + +The power decay length in metres is given by[^eich_2013]: + +$$ +\lambda_q = 1.35 \times 10^{-3} P_{\text{sep}}^{-0.02}R_0^{0.04}B_{\text{p}}(a)^{-0.92}\epsilon^{0.42} +$$ + +Here $P_{\text{sep}}$ is the plasma separatrix power in $\text{MW}$, $R_0$ is the plasma major radius in $\text{m}$, $B_{\text{p}}(a)$ is the plasma poloidal field at the outboard mid-plane measured in $\text{T}$ and $\epsilon$ is the plasma inverse aspect ratio. + +This can be found in Table 3 from Eich et.al [^eich_2013] + +The $R^2$ value for this fit is 0.88 + +---------- + +### MAST 2014 I | `calculate_mast2014_sol_power_decay_length_1()` + +The power decay length in metres is given by[^mast_2014]: + +$$ +\lambda_q = 1.84(\pm0.48) \times 10^{-3} P_{\text{sep}}^{0.18(\pm0.07)}B_{\text{p}}(a)^{-0.68(\pm0.14)} +$$ + +Here $P_{\text{sep}}$ is the plasma separatrix power in $\text{MW}$, and $B_{\text{p}}(a)$ is the plasma poloidal field at the outboard mid-plane measured in $\text{T}$. + +This can be found in Table 2 and Equation 3 from Thornton et.al [^mast_2014] + +The $R^2$ value for this fit is 0.56 + +---------- + +### MAST 2014 II | `calculate_mast2014_sol_power_decay_length_2()` + +The power decay length in metres is given by[^mast_2014]: + +$$ +\lambda_q = 4.57(\pm0.54) \times 10^{-3} P_{\text{sep}}^{0.22(\pm0.08)}I_{\text{p}}^{-0.64(\pm0.15)} +$$ + +Here $P_{\text{sep}}$ is the plasma separatrix power in $\text{MW}$, and $I_{\text{p}}$ is the total plasma current measured in $\text{MA}$. + +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 + +-------- + +[^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,” +Plasma Physics and Controlled Fusion, vol. 56, no. 5, p. 055008, Apr. 2014, doi: 10.1088/0741-3335/56/5/055008. \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 42595c58bf..1c80b5d1de 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -67,6 +67,7 @@ nav: - Confinement time: physics-models/plasma_confinement.md - L-H transition: physics-models/plasma_h_mode.md - Plasma Core Power Balance: physics-models/plasma_power_balance.md + - Plasma Scrape-off Layer: physics-models/plasma_scrape_off_layer.md - Detailed Plasma Physics: physics-models/detailed_physics.md - Pulsed Plant Operation: physics-models/pulsed-plant.md - Engineering Models: