From 7f64497e107f8a97209a7bd3e68889ed734acb83 Mon Sep 17 00:00:00 2001 From: mn3981 Date: Mon, 8 Jun 2026 11:23:45 +0100 Subject: [PATCH 01/17] Create initial poloidal power plotting function --- process/core/io/plot/summary.py | 308 +++++++++++++++++++++++++++++++- 1 file changed, 304 insertions(+), 4 deletions(-) diff --git a/process/core/io/plot/summary.py b/process/core/io/plot/summary.py index a4ef14263..9619e1626 100644 --- a/process/core/io/plot/summary.py +++ b/process/core/io/plot/summary.py @@ -14660,6 +14660,298 @@ def plot_blkt_structure( ) +def plot_poloidal_power_distribution( + ax: plt.Axes, + m_file: MFile, + scan: int, + radial_build: dict[str, float], + colour_scheme: Literal[1, 2], +): + """Plot the poloidal power distribution on the first wall and blanket and annotate relevant angles""" + # MFILE variables needed to plot the blkt structure and angles + rmajor = m_file.get("rmajor", scan=scan) + rminor = m_file.get("rminor", scan=scan) + dr_fw_plasma_gap_outboard = m_file.get("dr_fw_plasma_gap_outboard", scan=scan) + dr_fw_plasma_gap_inboard = m_file.get("dr_fw_plasma_gap_inboard", scan=scan) + dr_fw_inboard = m_file.get("dr_fw_inboard", scan=scan) + dr_fw_outboard = m_file.get("dr_fw_outboard", scan=scan) + dr_blkt_outboard = m_file.get("dr_blkt_outboard", scan=scan) + dr_blkt_inboard = m_file.get("dr_blkt_inboard", scan=scan) + dz_blkt_half = m_file.get("dz_blkt_half", scan=scan) + deg_blkt_outboard_poloidal_plasma = m_file.get( + "deg_blkt_outboard_poloidal_plasma", scan=scan + ) + deg_blkt_inboard_poloidal_plasma = m_file.get( + "deg_blkt_inboard_poloidal_plasma", scan=scan + ) + f_deg_blkt_outboard_poloidal_plasma = m_file.get( + "f_deg_blkt_outboard_poloidal_plasma", scan=scan + ) + f_deg_blkt_inboard_poloidal_plasma = m_file.get( + "f_deg_blkt_inboard_poloidal_plasma", scan=scan + ) + deg_div_poloidal_plasma = m_file.get("deg_div_poloidal_plasma", scan=scan) + f_ster_div_single = m_file.get("f_ster_div_single", scan=scan) + i_single_null = m_file.get("i_single_null", scan=scan) + + # ====================== + + plot_blanket(ax, m_file, scan, radial_build, colour_scheme) + plot_plasma(ax, m_file, scan, colour_scheme) + plot_firstwall(ax, m_file, scan, radial_build, colour_scheme) + + ax.set_xlabel("Radial position [m]") + ax.set_ylabel("Vertical position [m]") + ax.set_title("Blanket and First Wall Poloidal Cross-Section") + ax.minorticks_on() + ax.grid(which="minor", linestyle=":", linewidth=0.5, alpha=0.5) + + r_blkt_outboard_out = ( + rmajor + rminor + dr_fw_outboard + dr_fw_plasma_gap_outboard + dr_blkt_outboard + ) + r_blkt_inboard_in = ( + rmajor - rminor - dr_fw_plasma_gap_inboard - dr_fw_inboard - dr_blkt_inboard + ) + r_fw_outboard_in = r_blkt_outboard_out - dr_blkt_outboard - dr_fw_outboard + r_fw_inboard_out = r_blkt_inboard_in + dr_blkt_inboard + dr_fw_inboard + + # Plot a horizontal line at dz_blkt_half (blanket half height) + ax.axhline( + dz_blkt_half, + color="purple", + linestyle="--", + linewidth=1.5, + label="Blanket Half Height", + ) + ax.axhline( + -dz_blkt_half, + color="purple", + linestyle="--", + linewidth=1.5, + label="Blanket Half Height", + ) + + if i_single_null == 0: + # Plot arrows for the outboard blanket angles + ax.annotate( + "", + xy=(rmajor, 0), + xytext=(rmajor, dz_blkt_half), + arrowprops={"arrowstyle": "<-", "color": "purple"}, + zorder=5, + ) + # If single null then only plot the lower arrow for the outboard blanket angle + ax.annotate( + "", + xy=(rmajor, 0), + xytext=(rmajor, -dz_blkt_half), + arrowprops={"arrowstyle": "<-", "color": "purple"}, + zorder=5, + ) + + # Plot arc showing the angle between the two outboard blanket arrows + arc_radius = 1.0 + + # 3 to 6 o'clock position is -90 degrees, + angle_start = -90.0 + if i_single_null == 1: + angle_end = 90.0 + deg_div_poloidal_plasma + elif i_single_null == 0: + # 3 to 12 o'clock position is +90 degrees + angle_end = 90.0 + + theta = np.linspace(np.deg2rad(angle_start), np.deg2rad(angle_end), 50) + arc_x = rmajor + arc_radius * np.cos(theta) + arc_y = arc_radius * np.sin(theta) + + ax.plot(arc_x, arc_y, color="purple", linewidth=2) + + # Add angle label at the arc + mid_angle = np.deg2rad((angle_start + angle_end) / 2) + label_radius = arc_radius * 1.8 + label_x = rmajor + label_radius * np.cos(mid_angle) + label_y = label_radius * np.sin(mid_angle) + + # Plot the info box for the outboard blanket + ax.text( + label_x, + label_y, + f"{deg_blkt_outboard_poloidal_plasma:.1f}°\n({f_deg_blkt_outboard_poloidal_plasma * 100:.1f}%)", + fontsize=7, + color="purple", + ha="center", + va="center", + weight="bold", + bbox={ + "boxstyle": "round", + "facecolor": "white", + "alpha": 0.8, + "edgecolor": "purple", + "linewidth": 1.5, + }, + ) + + # Plot arrows for the inboard blanket angles + ax.annotate( + "", + xy=(rmajor, 0), + xytext=(r_fw_inboard_out, dz_blkt_half), + arrowprops={"arrowstyle": "<-", "color": "green"}, + zorder=5, + ) + + ax.annotate( + "", + xy=(rmajor, 0), + xytext=(r_fw_inboard_out, -dz_blkt_half), + arrowprops={"arrowstyle": "<-", "color": "green"}, + zorder=5, + ) + + # Plot arc showing the angle between the two inboard blanket arrows + arc_radius = 1.0 + angle_start = -deg_blkt_inboard_poloidal_plasma / 2 + angle_end = deg_blkt_inboard_poloidal_plasma / 2 + + theta = np.linspace(np.deg2rad(angle_start), np.deg2rad(angle_end), 50) + arc_x = rmajor - arc_radius * np.cos(theta) + arc_y = arc_radius * np.sin(theta) + + ax.plot(arc_x, arc_y, color="green", linewidth=2) + + # Add angle label at the arc + mid_angle = np.deg2rad((angle_start + angle_end) / 2) + label_radius = arc_radius * 1.8 + label_x = rmajor - label_radius * np.cos(mid_angle) + label_y = label_radius * np.sin(mid_angle) + + # Plot the info box for the inboard blanket + ax.text( + label_x, + label_y, + f"{deg_blkt_inboard_poloidal_plasma:.1f}°\n({f_deg_blkt_inboard_poloidal_plasma * 100:.1f}%)", + fontsize=7, + color="green", + ha="center", + va="center", + weight="bold", + bbox={ + "boxstyle": "round", + "facecolor": "white", + "alpha": 0.8, + "edgecolor": "green", + "linewidth": 1.5, + }, + zorder=5, + ) + + # Plot arrows for the divertor angles + # If double null then plot the upper also + if i_single_null == 0: + # Plot arc showing the angle between the two arrows (divertor angle) + arc_radius = 1.5 + # 3 to 12 o'clock position is +90 degrees, + angle_start = 90.0 + angle_end = 90.0 + deg_div_poloidal_plasma + + theta = np.linspace(np.deg2rad(angle_start), np.deg2rad(angle_end), 50) + arc_x = rmajor + arc_radius * np.cos(theta) + arc_y = arc_radius * np.sin(theta) + + ax.plot(arc_x, arc_y, color="black", linewidth=2) + + # Add angle label at the arc + mid_angle = np.deg2rad((angle_start + angle_end) / 2) + label_radius = arc_radius * 1.8 + label_x = rmajor + label_radius * np.cos(mid_angle) + label_y = label_radius * np.sin(mid_angle) + + ax.text( + label_x, + label_y, + f"{deg_div_poloidal_plasma:.1f}°\n({f_ster_div_single * 100:.1f}%)", + fontsize=7, + color="black", + ha="center", + va="center", + weight="bold", + bbox={ + "boxstyle": "round", + "facecolor": "white", + "alpha": 0.8, + "edgecolor": "black", + "linewidth": 1.5, + }, + zorder=5, + ) + + # Plot arc showing the angle between the two arrows for the lower divertor (divertor angle) + arc_radius = 1.5 + # 3 to 6 o'clock is -90 degrees + angle_start = -90.0 + angle_end = -90.0 - deg_div_poloidal_plasma + + theta = np.linspace(np.deg2rad(angle_start), np.deg2rad(angle_end), 50) + arc_x = rmajor + arc_radius * np.cos(theta) + arc_y = arc_radius * np.sin(theta) + + ax.plot(arc_x, arc_y, color="black", linewidth=2) + + # Add angle label at the arc + mid_angle = np.deg2rad((angle_start + angle_end) / 2) + label_radius = arc_radius * 1.8 + label_x = rmajor + label_radius * np.cos(mid_angle) + label_y = label_radius * np.sin(mid_angle) + + # Plot the info box for the lower divertor angle + ax.text( + label_x, + label_y, + f"{deg_div_poloidal_plasma:.1f}°\n({f_ster_div_single * 100:.1f}%)", + fontsize=7, + color="black", + ha="center", + va="center", + weight="bold", + bbox={ + "boxstyle": "round", + "facecolor": "white", + "alpha": 0.8, + "edgecolor": "black", + "linewidth": 1.5, + }, + zorder=5, + ) + + # Plot vertical lines at the inner and outer radial boundaries of the blanket + ax.axvline( + r_blkt_inboard_in, color="black", linestyle="--", linewidth=1.5, zorder=10 + ) + ax.axvline( + r_blkt_outboard_out, color="black", linestyle="--", linewidth=1.5, zorder=10 + ) + ax.axvline(r_fw_inboard_out, color="black", linestyle="--", linewidth=1.5, zorder=10) + + ax.axvline(r_fw_outboard_in, color="black", linestyle="--", linewidth=1.5, zorder=10) + + ax.axvline( + rmajor, + color="black", + linestyle="--", + linewidth=1.5, + label="Major Radius $R_0$", + ) + + # Plot midplane line (horizontal dashed line at Z=0) + ax.axhline( + 0.0, + color="black", + linestyle="--", + linewidth=1.5, + label="Midplane", + ) + + def plot_detailed_plasma_parameters(axis: plt.Axes, fig, mfile: MFile, scan: int): """Function to plot detailed plasma parameters from physics data. @@ -15608,14 +15900,22 @@ def main_plot( ax_blanket = figs[34].add_subplot(122, aspect="equal") plot_blkt_structure(ax_blanket, figs[34], m_file, scan, radial_build, colour_scheme) + plot_poloidal_power_distribution( + ax=figs[35].add_subplot(111, aspect="equal"), + m_file=m_file, + scan=scan, + radial_build=radial_build, + colour_scheme=colour_scheme, + ) + plot_main_power_flow( - figs[35].add_subplot(111, aspect="equal"), m_file, scan, figs[35] + figs[36].add_subplot(111, aspect="equal"), m_file, scan, figs[35] ) - ax24 = figs[36].add_subplot(111) + ax24 = figs[37].add_subplot(111) # set_position([left, bottom, width, height]) -> height ~ 0.66 => ~2/3 of page height ax24.set_position([0.08, 0.35, 0.84, 0.57]) - plot_system_power_profiles_over_time(ax24, m_file, scan, figs[36]) + plot_system_power_profiles_over_time(ax24, m_file, scan, figs[37]) def create_thickness_builds(m_file, scan: int): @@ -15697,7 +15997,7 @@ def plot_summary( ): # create main plot # Increase range when adding new page - pages = [plt.figure(figsize=(12, 9), dpi=80) for i in range(37)] + pages = [plt.figure(figsize=(12, 9), dpi=80) for i in range(38)] # run main_plot main_plot( From 394228adf79c6e8903d13916e8199c8540c4e980 Mon Sep 17 00:00:00 2001 From: mn3981 Date: Mon, 8 Jun 2026 11:49:24 +0100 Subject: [PATCH 02/17] Add radiation power variables for inboard and outboard first wall in FWBSData --- process/data_structure/fwbs_variables.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/process/data_structure/fwbs_variables.py b/process/data_structure/fwbs_variables.py index 0bc6208d9..d484eb03d 100644 --- a/process/data_structure/fwbs_variables.py +++ b/process/data_structure/fwbs_variables.py @@ -379,6 +379,12 @@ class FWBSData: p_fw_rad_total_mw: float = 0.0 """Radiation power incident on the first wall (MW)""" + p_fw_inboard_rad_mw: float = 0.0 + """Radiation power incident on the inboard first wall [MW]""" + + p_fw_outboard_rad_mw: float = 0.0 + """Radiation power incident on the outboard first wall [MW]""" + p_fw_hcd_rad_total_mw: float = 0.0 """Radiation power incident on the heating and current drive systems on the first wall (MW)""" From fb4f8f3af99969398acf65440595d89a6b029a6e Mon Sep 17 00:00:00 2001 From: mn3981 Date: Mon, 8 Jun 2026 12:10:10 +0100 Subject: [PATCH 03/17] Refactor radiation power calculations for first wall in DCLL and CCFE_HCPB; add new calculations in FirstWall model --- process/models/blankets/dcll.py | 7 ------- process/models/blankets/hcpb.py | 7 ------- process/models/fw.py | 19 +++++++++++++++++++ tests/unit/models/blankets/test_ccfe_hcpb.py | 8 -------- tests/unit/models/test_dcll.py | 8 -------- 5 files changed, 19 insertions(+), 30 deletions(-) diff --git a/process/models/blankets/dcll.py b/process/models/blankets/dcll.py index 25b49073e..a8a97bb7b 100644 --- a/process/models/blankets/dcll.py +++ b/process/models/blankets/dcll.py @@ -212,13 +212,6 @@ def dcll_neutronics_and_power(self, output: bool): # FW - # Radiation power incident on first wall (MW) - self.data.fwbs.p_fw_rad_total_mw = ( - self.data.physics.p_plasma_rad_mw - - self.data.fwbs.p_div_rad_total_mw - - self.data.fwbs.p_fw_hcd_rad_total_mw - ) - # Surface heat flux on first wall (MW) # All of the fast particle losses go to the outer wall. self.data.fwbs.psurffwo = ( diff --git a/process/models/blankets/hcpb.py b/process/models/blankets/hcpb.py index b62ecc5d6..6f329adcd 100644 --- a/process/models/blankets/hcpb.py +++ b/process/models/blankets/hcpb.py @@ -767,13 +767,6 @@ def powerflow_calc(self, output: bool): self.data.physics.p_plasma_rad_mw * self.data.fwbs.f_a_fw_outboard_hcd ) - # Radiation power incident on first wall (MW) - self.data.fwbs.p_fw_rad_total_mw = ( - self.data.physics.p_plasma_rad_mw - - self.data.fwbs.p_div_rad_total_mw - - self.data.fwbs.p_fw_hcd_rad_total_mw - ) - # If we have chosen pressurised water as the blanket coolant, set the # coolant outlet temperature as 20 deg C below the boiling point if self.data.fwbs.i_blkt_coolant_type == 2: diff --git a/process/models/fw.py b/process/models/fw.py index be146e78a..3c23de7ac 100644 --- a/process/models/fw.py +++ b/process/models/fw.py @@ -119,6 +119,25 @@ def run(self): self.data.physics.p_neutron_total_mw / self.data.first_wall.a_fw_total ) + # Radiation power incident on first wall (MW) + # Set based on the total radiation power and the angular fractions taken up by + # the inboard and outboard first wall, which are calculated based on the + # geometry of the first wall and the plasma. + self.data.fwbs.p_fw_rad_total_mw = self.data.physics.p_plasma_rad_mw * ( + self.data.blanket.f_deg_blkt_outboard_poloidal_plasma + + self.data.blanket.f_deg_blkt_inboard_poloidal_plasma + ) + + self.data.fwbs.p_fw_inboard_rad_mw = ( + self.data.physics.p_plasma_rad_mw + * self.data.blanket.f_deg_blkt_inboard_poloidal_plasma + ) + + self.data.fwbs.p_fw_outboard_rad_mw = ( + self.data.physics.p_plasma_rad_mw + * self.data.blanket.f_deg_blkt_outboard_poloidal_plasma + ) + if self.data.physics.i_pflux_fw_neutron == 1: self.data.physics.pflux_fw_rad_mw = ( self.data.physics.ffwal diff --git a/tests/unit/models/blankets/test_ccfe_hcpb.py b/tests/unit/models/blankets/test_ccfe_hcpb.py index 8e9f8ae9b..44fb0ae12 100644 --- a/tests/unit/models/blankets/test_ccfe_hcpb.py +++ b/tests/unit/models/blankets/test_ccfe_hcpb.py @@ -748,8 +748,6 @@ class PowerflowCalcParam(NamedTuple): expected_p_div_rad_total_mw: Any = None - expected_p_fw_rad_total_mw: Any = None - expected_psurffwi: Any = None expected_psurffwo: Any = None @@ -803,7 +801,6 @@ class PowerflowCalcParam(NamedTuple): t_in_bb=573.13, t_out_bb=773.13, p_fw_blkt_coolant_pump_mw=0, - expected_p_fw_rad_total_mw=254.39207240222791, expected_psurffwi=97.271629070225231, expected_psurffwo=176.95628839065773, expected_p_shld_coolant_pump_mw=0.0068056297940224456, @@ -849,7 +846,6 @@ class PowerflowCalcParam(NamedTuple): t_in_bb=573.13, t_out_bb=773.13, p_fw_blkt_coolant_pump_mw=202.00455086503842, - expected_p_fw_rad_total_mw=254.39207240222791, expected_psurffwi=97.271629070225259, expected_psurffwo=176.95009681558912, expected_p_shld_coolant_pump_mw=0.007019085478296147, @@ -1059,10 +1055,6 @@ def test_powerflow_calc(powerflowcalcparam, monkeypatch, ccfe_hcpb): ccfe_hcpb.powerflow_calc(False) - assert ccfe_hcpb.data.fwbs.p_fw_rad_total_mw == pytest.approx( - powerflowcalcparam.expected_p_fw_rad_total_mw - ) - assert ccfe_hcpb.data.fwbs.psurffwi == pytest.approx( powerflowcalcparam.expected_psurffwi ) diff --git a/tests/unit/models/test_dcll.py b/tests/unit/models/test_dcll.py index 08c0d1585..cf73986cc 100644 --- a/tests/unit/models/test_dcll.py +++ b/tests/unit/models/test_dcll.py @@ -64,8 +64,6 @@ class DcllNeutronicsAndPowerParam(NamedTuple): p_fw_alpha_mw: Any = None - expected_p_fw_rad_total_mw: Any = None - expected_p_fw_nuclear_heat_total_mw: Any = None expected_p_blkt_nuclear_heat_total_mw: Any = None @@ -101,7 +99,6 @@ class DcllNeutronicsAndPowerParam(NamedTuple): p_neutron_total_mw=1587.7386535917431, p_plasma_rad_mw=287.44866938104849, p_fw_alpha_mw=19.835845058655043, - expected_p_fw_rad_total_mw=254.39207240222791, expected_p_fw_nuclear_heat_total_mw=196.72081918001697, expected_p_blkt_nuclear_heat_total_mw=1533.4949914565693, expected_p_blkt_multiplication_mw=325.06710220789364, @@ -131,7 +128,6 @@ class DcllNeutronicsAndPowerParam(NamedTuple): p_neutron_total_mw=1587.2430556964196, p_plasma_rad_mw=287.44866938104849, p_fw_alpha_mw=19.829653483586444, - expected_p_fw_rad_total_mw=254.39207240222791, expected_p_fw_nuclear_heat_total_mw=196.65941460078642, expected_p_blkt_nuclear_heat_total_mw=1533.0163252173013, expected_p_blkt_multiplication_mw=324.96563552675644, @@ -281,10 +277,6 @@ def test_dcll_neutronics_and_power(dcllneutronicsandpowerparam, monkeypatch, dcl dcll.dcll_neutronics_and_power(False) - assert dcll.data.fwbs.p_fw_rad_total_mw == pytest.approx( - dcllneutronicsandpowerparam.expected_p_fw_rad_total_mw - ) - assert dcll.data.fwbs.p_fw_nuclear_heat_total_mw == pytest.approx( dcllneutronicsandpowerparam.expected_p_fw_nuclear_heat_total_mw ) From ac5cffe09b759e027f78c2b5a36c6fbefa7f7542 Mon Sep 17 00:00:00 2001 From: mn3981 Date: Mon, 8 Jun 2026 12:45:29 +0100 Subject: [PATCH 04/17] Rename 'psurffwi' to 'p_fw_inboard_surface_heat' for clarity and consistency across models and tests --- process/data_structure/fwbs_variables.py | 4 ++-- process/models/blankets/blanket_library.py | 15 +++++++++------ process/models/blankets/dcll.py | 7 ++++--- process/models/blankets/hcpb.py | 9 +++++---- process/models/engineering/ivc_functions.py | 6 +++--- process/models/stellarator/stellarator.py | 4 ++-- tests/unit/models/blankets/test_ccfe_hcpb.py | 14 +++++++++----- tests/unit/models/test_dcll.py | 12 ++++++++---- 8 files changed, 42 insertions(+), 29 deletions(-) diff --git a/process/data_structure/fwbs_variables.py b/process/data_structure/fwbs_variables.py index d484eb03d..b064a3774 100644 --- a/process/data_structure/fwbs_variables.py +++ b/process/data_structure/fwbs_variables.py @@ -170,8 +170,8 @@ class FWBSData: f_a_fw_coolant_outboard: float = 0.0 """Outboard FW coolant cross-sectional area void fraction""" - psurffwi: float = 0.0 - """Surface heat flux on first wall [MW] (sum = p_fw_rad_total_mw)""" + p_fw_inboard_surface_heat_mw: float = 0.0 + """Surface heat flux on inboard first wall [MW] """ psurffwo: float = 0.0 """Surface heat flux on first wall [MW] (sum = p_fw_rad_total_mw)""" diff --git a/process/models/blankets/blanket_library.py b/process/models/blankets/blanket_library.py index c310e831e..55b90aee7 100644 --- a/process/models/blankets/blanket_library.py +++ b/process/models/blankets/blanket_library.py @@ -2195,10 +2195,10 @@ def thermo_hydraulic_model(self, output: bool): if self.data.fwbs.i_blkt_dual_coolant == 2: f_nuc_fwi = ( self.data.blanket.p_fw_inboard_nuclear_heat_mw - + self.data.fwbs.psurffwi + + self.data.fwbs.p_fw_inboard_surface_heat_mw ) / ( self.data.blanket.p_fw_inboard_nuclear_heat_mw - + self.data.fwbs.psurffwi + + self.data.fwbs.p_fw_inboard_surface_heat_mw + pnucblkti_struct ) f_nuc_fwo = ( @@ -2212,10 +2212,10 @@ def thermo_hydraulic_model(self, output: bool): else: f_nuc_fwi = ( self.data.blanket.p_fw_inboard_nuclear_heat_mw - + self.data.fwbs.psurffwi + + self.data.fwbs.p_fw_inboard_surface_heat_mw ) / ( self.data.blanket.p_fw_inboard_nuclear_heat_mw - + self.data.fwbs.psurffwi + + self.data.fwbs.p_fw_inboard_surface_heat_mw + self.data.blanket.p_blkt_nuclear_heat_inboard_mw ) f_nuc_fwo = ( @@ -2267,7 +2267,7 @@ def thermo_hydraulic_model(self, output: bool): self.data.fwbs.radius_fw_channel, self.data.build.dr_fw_inboard, self.data.first_wall.a_fw_inboard, - self.data.fwbs.psurffwi, + self.data.fwbs.p_fw_inboard_surface_heat_mw, self.data.blanket.p_fw_inboard_nuclear_heat_mw, "Inboard first wall", ) @@ -2295,7 +2295,10 @@ def thermo_hydraulic_model(self, output: bool): # Total mass flow rate to remove inboard FW power (kg/s) self.data.blanket.mflow_fw_inboard_coolant_total = ( 1.0e6 - * (self.data.blanket.p_fw_inboard_nuclear_heat_mw + self.data.fwbs.psurffwi) + * ( + self.data.blanket.p_fw_inboard_nuclear_heat_mw + + self.data.fwbs.p_fw_inboard_surface_heat_mw + ) / (self.data.fwbs.cp_fw * (fwoutleti - self.data.fwbs.temp_fw_coolant_in)) ) # Total mass flow rate to remove outboard FW power (kg/s) diff --git a/process/models/blankets/dcll.py b/process/models/blankets/dcll.py index a8a97bb7b..7cd4e47cc 100644 --- a/process/models/blankets/dcll.py +++ b/process/models/blankets/dcll.py @@ -221,8 +221,9 @@ def dcll_neutronics_and_power(self, output: bool): + self.data.current_drive.p_beam_orbit_loss_mw + self.data.physics.p_fw_alpha_mw ) - self.data.fwbs.psurffwi = self.data.fwbs.p_fw_rad_total_mw * ( - 1 - self.data.first_wall.a_fw_outboard / self.data.first_wall.a_fw_total + self.data.fwbs.p_fw_inboard_surface_heat_mw = ( + self.data.fwbs.p_fw_rad_total_mw + * (1 - self.data.first_wall.a_fw_outboard / self.data.first_wall.a_fw_total) ) if output: @@ -332,7 +333,7 @@ def dcll_power_and_heating(self, output: bool): f_p_shld_coolant_pump_total_heat=self.data.heat_transport.f_p_shld_coolant_pump_total_heat, f_p_div_coolant_pump_total_heat=self.data.heat_transport.f_p_div_coolant_pump_total_heat, p_fw_nuclear_heat_total_mw=self.data.fwbs.p_fw_nuclear_heat_total_mw, - psurffwi=self.data.fwbs.psurffwi, + p_fw_inboard_surface_heat_mw=self.data.fwbs.p_fw_inboard_surface_heat_mw, psurffwo=self.data.fwbs.psurffwo, p_blkt_nuclear_heat_total_mw=self.data.fwbs.p_blkt_nuclear_heat_total_mw, p_shld_nuclear_heat_mw=self.data.fwbs.p_shld_nuclear_heat_mw, diff --git a/process/models/blankets/hcpb.py b/process/models/blankets/hcpb.py index 6f329adcd..5b2a57797 100644 --- a/process/models/blankets/hcpb.py +++ b/process/models/blankets/hcpb.py @@ -788,8 +788,9 @@ def powerflow_calc(self, output: bool): + self.data.current_drive.p_beam_orbit_loss_mw + self.data.physics.p_fw_alpha_mw ) - self.data.fwbs.psurffwi = self.data.fwbs.p_fw_rad_total_mw * ( - 1 - self.data.first_wall.a_fw_outboard / self.data.first_wall.a_fw_total + self.data.fwbs.p_fw_inboard_surface_heat_mw = ( + self.data.fwbs.p_fw_rad_total_mw + * (1 - self.data.first_wall.a_fw_outboard / self.data.first_wall.a_fw_total) ) i_p_coolant_pumping = PumpingPowerModelTypes(self.data.fwbs.i_p_coolant_pumping) @@ -806,7 +807,7 @@ def powerflow_calc(self, output: bool): f_p_shld_coolant_pump_total_heat=self.data.heat_transport.f_p_shld_coolant_pump_total_heat, f_p_div_coolant_pump_total_heat=self.data.heat_transport.f_p_div_coolant_pump_total_heat, p_fw_nuclear_heat_total_mw=self.data.fwbs.p_fw_nuclear_heat_total_mw, - psurffwi=self.data.fwbs.psurffwi, + p_fw_inboard_surface_heat_mw=self.data.fwbs.p_fw_inboard_surface_heat_mw, psurffwo=self.data.fwbs.psurffwo, p_blkt_nuclear_heat_total_mw=self.data.fwbs.p_blkt_nuclear_heat_total_mw, p_shld_nuclear_heat_mw=self.data.heat_transport.p_shld_nuclear_heat_mw, @@ -866,7 +867,7 @@ def powerflow_calc(self, output: bool): fpump = t_in_compressor / (self.data.fwbs.etaiso * dt_he) * (pfactor - 1) p_plasma = ( self.data.fwbs.p_fw_nuclear_heat_total_mw - + self.data.fwbs.psurffwi + + self.data.fwbs.p_fw_inboard_surface_heat_mw + self.data.fwbs.psurffwo + self.data.fwbs.p_blkt_nuclear_heat_total_mw ) diff --git a/process/models/engineering/ivc_functions.py b/process/models/engineering/ivc_functions.py index b5aafc191..8e7c7b3f2 100644 --- a/process/models/engineering/ivc_functions.py +++ b/process/models/engineering/ivc_functions.py @@ -30,7 +30,7 @@ def pumping_powers_as_fractions( f_p_shld_coolant_pump_total_heat: float, f_p_div_coolant_pump_total_heat: float, p_fw_nuclear_heat_total_mw: float, - psurffwi: float, + p_fw_inboard_surface_heat_mw: float, psurffwo: float, p_blkt_nuclear_heat_total_mw: float, p_shld_nuclear_heat_mw: float, @@ -53,7 +53,7 @@ def pumping_powers_as_fractions( Fraction for divertor coolant pump. p_fw_nuclear_heat_total_mw : float Total FW nuclear heating (MW). - psurffwi : float + p_fw_inboard_surface_heat_mw : float Inboard FW surface heating (MW). psurffwo : float Outboard FW surface heating (MW). @@ -76,7 +76,7 @@ def pumping_powers_as_fractions( Tuple of pumping powers (MW) for FW, blanket, shield, and divertor. """ p_fw_coolant_pump_mw = f_p_fw_coolant_pump_total_heat * ( - p_fw_nuclear_heat_total_mw + psurffwi + psurffwo + p_fw_nuclear_heat_total_mw + p_fw_inboard_surface_heat_mw + psurffwo ) p_blkt_coolant_pump_mw = ( f_p_blkt_coolant_pump_total_heat * p_blkt_nuclear_heat_total_mw diff --git a/process/models/stellarator/stellarator.py b/process/models/stellarator/stellarator.py index a35695a1e..11cb9e052 100644 --- a/process/models/stellarator/stellarator.py +++ b/process/models/stellarator/stellarator.py @@ -829,7 +829,7 @@ def st_fwbs(self, output: bool): # Surface heat flux on first wall (MW) (sum = self.data.fwbs.p_fw_rad_total_mw) - psurffwi = ( + p_fw_inboard_surface_heat_mw = ( self.data.fwbs.p_fw_rad_total_mw * self.data.first_wall.a_fw_inboard / self.data.first_wall.a_fw_total @@ -894,7 +894,7 @@ def st_fwbs(self, output: bool): * ( p_fw_inboard_nuclear_heat_mw + p_fw_outboard_nuclear_heat_mw - + psurffwi + + p_fw_inboard_surface_heat_mw + psurffwo + self.data.current_drive.p_beam_orbit_loss_mw ) diff --git a/tests/unit/models/blankets/test_ccfe_hcpb.py b/tests/unit/models/blankets/test_ccfe_hcpb.py index 44fb0ae12..bde8f8f6e 100644 --- a/tests/unit/models/blankets/test_ccfe_hcpb.py +++ b/tests/unit/models/blankets/test_ccfe_hcpb.py @@ -706,7 +706,7 @@ class PowerflowCalcParam(NamedTuple): p_cp_shield_nuclear_heat_mw: Any = None - psurffwi: Any = None + p_fw_inboard_surface_heat_mw: Any = None psurffwo: Any = None @@ -781,7 +781,7 @@ class PowerflowCalcParam(NamedTuple): p_shld_nuclear_heat_mw=1.3611259588044891, etaiso=0.90000000000000002, p_cp_shield_nuclear_heat_mw=0, - psurffwi=0, + p_fw_inboard_surface_heat_mw=0, psurffwo=0, p_fw_coolant_pump_mw=0, f_p_fw_coolant_pump_total_heat=0.0050000000000000001, @@ -826,7 +826,7 @@ class PowerflowCalcParam(NamedTuple): p_shld_nuclear_heat_mw=1.4038170956592293, etaiso=0.90000000000000002, p_cp_shield_nuclear_heat_mw=0, - psurffwi=97.271629070225231, + p_fw_inboard_surface_heat_mw=97.271629070225231, psurffwo=176.95628839065773, p_fw_coolant_pump_mw=0, f_p_fw_coolant_pump_total_heat=0.0050000000000000001, @@ -959,7 +959,11 @@ def test_powerflow_calc(powerflowcalcparam, monkeypatch, ccfe_hcpb): powerflowcalcparam.p_cp_shield_nuclear_heat_mw, ) - monkeypatch.setattr(ccfe_hcpb.data.fwbs, "psurffwi", powerflowcalcparam.psurffwi) + monkeypatch.setattr( + ccfe_hcpb.data.fwbs, + "p_fw_inboard_surface_heat_mw", + powerflowcalcparam.p_fw_inboard_surface_heat_mw, + ) monkeypatch.setattr(ccfe_hcpb.data.fwbs, "psurffwo", powerflowcalcparam.psurffwo) @@ -1055,7 +1059,7 @@ def test_powerflow_calc(powerflowcalcparam, monkeypatch, ccfe_hcpb): ccfe_hcpb.powerflow_calc(False) - assert ccfe_hcpb.data.fwbs.psurffwi == pytest.approx( + assert ccfe_hcpb.data.fwbs.p_fw_inboard_surface_heat_mw == pytest.approx( powerflowcalcparam.expected_psurffwi ) diff --git a/tests/unit/models/test_dcll.py b/tests/unit/models/test_dcll.py index cf73986cc..4269c92cd 100644 --- a/tests/unit/models/test_dcll.py +++ b/tests/unit/models/test_dcll.py @@ -40,7 +40,7 @@ class DcllNeutronicsAndPowerParam(NamedTuple): p_fw_nuclear_heat_total_mw: Any = None - psurffwi: Any = None + p_fw_inboard_surface_heat_mw: Any = None psurffwo: Any = None @@ -87,7 +87,7 @@ class DcllNeutronicsAndPowerParam(NamedTuple): p_shld_nuclear_heat_mw=0, p_fw_rad_total_mw=0, p_fw_nuclear_heat_total_mw=0, - psurffwi=0, + p_fw_inboard_surface_heat_mw=0, psurffwo=0, p_blkt_nuclear_heat_total_mw=0, pnuc_fw_ratio_dcll=0.14000000000000001, @@ -116,7 +116,7 @@ class DcllNeutronicsAndPowerParam(NamedTuple): p_shld_nuclear_heat_mw=0, p_fw_rad_total_mw=254.39207240222791, p_fw_nuclear_heat_total_mw=196.72081918001697, - psurffwi=97.271629070225231, + p_fw_inboard_surface_heat_mw=97.271629070225231, psurffwo=176.95628839065773, p_blkt_nuclear_heat_total_mw=1533.4949914565693, pnuc_fw_ratio_dcll=0.14000000000000001, @@ -215,7 +215,11 @@ def test_dcll_neutronics_and_power(dcllneutronicsandpowerparam, monkeypatch, dcl dcllneutronicsandpowerparam.p_fw_nuclear_heat_total_mw, ) - monkeypatch.setattr(dcll.data.fwbs, "psurffwi", dcllneutronicsandpowerparam.psurffwi) + monkeypatch.setattr( + dcll.data.fwbs, + "p_fw_inboard_surface_heat_mw", + dcllneutronicsandpowerparam.p_fw_inboard_surface_heat_mw, + ) monkeypatch.setattr(dcll.data.fwbs, "psurffwo", dcllneutronicsandpowerparam.psurffwo) From 21d1bf4832d62720cab5e891bbf5ad7235eb0fb6 Mon Sep 17 00:00:00 2001 From: mn3981 Date: Mon, 8 Jun 2026 13:09:01 +0100 Subject: [PATCH 05/17] Rename 'psurffwo' to 'p_fw_outboard_surface_heat_mw' for clarity and consistency across models and tests --- process/data_structure/fwbs_variables.py | 4 ++-- process/models/blankets/blanket_library.py | 15 +++++++++------ process/models/blankets/dcll.py | 4 ++-- process/models/blankets/hcpb.py | 6 +++--- process/models/engineering/ivc_functions.py | 8 +++++--- process/models/stellarator/stellarator.py | 4 ++-- tests/unit/models/blankets/test_ccfe_hcpb.py | 14 +++++++++----- tests/unit/models/test_dcll.py | 12 ++++++++---- 8 files changed, 40 insertions(+), 27 deletions(-) diff --git a/process/data_structure/fwbs_variables.py b/process/data_structure/fwbs_variables.py index b064a3774..ade3c5bf8 100644 --- a/process/data_structure/fwbs_variables.py +++ b/process/data_structure/fwbs_variables.py @@ -173,8 +173,8 @@ class FWBSData: p_fw_inboard_surface_heat_mw: float = 0.0 """Surface heat flux on inboard first wall [MW] """ - psurffwo: float = 0.0 - """Surface heat flux on first wall [MW] (sum = p_fw_rad_total_mw)""" + p_fw_outboard_surface_heat_mw: float = 0.0 + """Surface heat flux on outboard first wall [MW]""" vol_fw_total: float = 0.0 """First wall volume [m3]""" diff --git a/process/models/blankets/blanket_library.py b/process/models/blankets/blanket_library.py index 55b90aee7..feb132e0b 100644 --- a/process/models/blankets/blanket_library.py +++ b/process/models/blankets/blanket_library.py @@ -2203,10 +2203,10 @@ def thermo_hydraulic_model(self, output: bool): ) f_nuc_fwo = ( self.data.blanket.p_fw_outboard_nuclear_heat_mw - + self.data.fwbs.psurffwo + + self.data.fwbs.p_fw_outboard_surface_heat_mw ) / ( self.data.blanket.p_fw_outboard_nuclear_heat_mw - + self.data.fwbs.psurffwo + + self.data.fwbs.p_fw_outboard_surface_heat_mw + pnucblkto_struct ) else: @@ -2220,10 +2220,10 @@ def thermo_hydraulic_model(self, output: bool): ) f_nuc_fwo = ( self.data.blanket.p_fw_outboard_nuclear_heat_mw - + self.data.fwbs.psurffwo + + self.data.fwbs.p_fw_outboard_surface_heat_mw ) / ( self.data.blanket.p_fw_outboard_nuclear_heat_mw - + self.data.fwbs.psurffwo + + self.data.fwbs.p_fw_outboard_surface_heat_mw + self.data.blanket.p_blkt_nuclear_heat_outboard_mw ) @@ -2281,7 +2281,7 @@ def thermo_hydraulic_model(self, output: bool): self.data.fwbs.radius_fw_channel, self.data.build.dr_fw_outboard, self.data.first_wall.a_fw_outboard, - self.data.fwbs.psurffwo, + self.data.fwbs.p_fw_outboard_surface_heat_mw, self.data.blanket.p_fw_outboard_nuclear_heat_mw, "Outboard first wall", ) @@ -2304,7 +2304,10 @@ def thermo_hydraulic_model(self, output: bool): # Total mass flow rate to remove outboard FW power (kg/s) self.data.blanket.mflow_fw_outboard_coolant_total = ( 1.0e6 - * (self.data.blanket.p_fw_outboard_nuclear_heat_mw + self.data.fwbs.psurffwo) + * ( + self.data.blanket.p_fw_outboard_nuclear_heat_mw + + self.data.fwbs.p_fw_outboard_surface_heat_mw + ) / (self.data.fwbs.cp_fw * (fwoutleto - self.data.fwbs.temp_fw_coolant_in)) ) diff --git a/process/models/blankets/dcll.py b/process/models/blankets/dcll.py index 7cd4e47cc..c6c958b90 100644 --- a/process/models/blankets/dcll.py +++ b/process/models/blankets/dcll.py @@ -214,7 +214,7 @@ def dcll_neutronics_and_power(self, output: bool): # Surface heat flux on first wall (MW) # All of the fast particle losses go to the outer wall. - self.data.fwbs.psurffwo = ( + self.data.fwbs.p_fw_outboard_surface_heat_mw = ( self.data.fwbs.p_fw_rad_total_mw * self.data.first_wall.a_fw_outboard / self.data.first_wall.a_fw_total @@ -334,7 +334,7 @@ def dcll_power_and_heating(self, output: bool): f_p_div_coolant_pump_total_heat=self.data.heat_transport.f_p_div_coolant_pump_total_heat, p_fw_nuclear_heat_total_mw=self.data.fwbs.p_fw_nuclear_heat_total_mw, p_fw_inboard_surface_heat_mw=self.data.fwbs.p_fw_inboard_surface_heat_mw, - psurffwo=self.data.fwbs.psurffwo, + p_fw_outboard_surface_heat_mw=self.data.fwbs.p_fw_outboard_surface_heat_mw, p_blkt_nuclear_heat_total_mw=self.data.fwbs.p_blkt_nuclear_heat_total_mw, p_shld_nuclear_heat_mw=self.data.fwbs.p_shld_nuclear_heat_mw, p_cp_shield_nuclear_heat_mw=self.data.fwbs.p_cp_shield_nuclear_heat_mw, diff --git a/process/models/blankets/hcpb.py b/process/models/blankets/hcpb.py index 5b2a57797..2b454f039 100644 --- a/process/models/blankets/hcpb.py +++ b/process/models/blankets/hcpb.py @@ -781,7 +781,7 @@ def powerflow_calc(self, output: bool): # Surface heat flux on first wall (outboard and inboard) (MW) # All of the fast particle losses go to the outer wall. - self.data.fwbs.psurffwo = ( + self.data.fwbs.p_fw_outboard_surface_heat_mw = ( self.data.fwbs.p_fw_rad_total_mw * self.data.first_wall.a_fw_outboard / self.data.first_wall.a_fw_total @@ -808,7 +808,7 @@ def powerflow_calc(self, output: bool): f_p_div_coolant_pump_total_heat=self.data.heat_transport.f_p_div_coolant_pump_total_heat, p_fw_nuclear_heat_total_mw=self.data.fwbs.p_fw_nuclear_heat_total_mw, p_fw_inboard_surface_heat_mw=self.data.fwbs.p_fw_inboard_surface_heat_mw, - psurffwo=self.data.fwbs.psurffwo, + p_fw_outboard_surface_heat_mw=self.data.fwbs.p_fw_outboard_surface_heat_mw, p_blkt_nuclear_heat_total_mw=self.data.fwbs.p_blkt_nuclear_heat_total_mw, p_shld_nuclear_heat_mw=self.data.heat_transport.p_shld_nuclear_heat_mw, p_cp_shield_nuclear_heat_mw=self.data.fwbs.p_cp_shield_nuclear_heat_mw, @@ -868,7 +868,7 @@ def powerflow_calc(self, output: bool): p_plasma = ( self.data.fwbs.p_fw_nuclear_heat_total_mw + self.data.fwbs.p_fw_inboard_surface_heat_mw - + self.data.fwbs.psurffwo + + self.data.fwbs.p_fw_outboard_surface_heat_mw + self.data.fwbs.p_blkt_nuclear_heat_total_mw ) self.data.primary_pumping.p_fw_blkt_coolant_pump_mw = ( diff --git a/process/models/engineering/ivc_functions.py b/process/models/engineering/ivc_functions.py index 8e7c7b3f2..a22122d21 100644 --- a/process/models/engineering/ivc_functions.py +++ b/process/models/engineering/ivc_functions.py @@ -31,7 +31,7 @@ def pumping_powers_as_fractions( f_p_div_coolant_pump_total_heat: float, p_fw_nuclear_heat_total_mw: float, p_fw_inboard_surface_heat_mw: float, - psurffwo: float, + p_fw_outboard_surface_heat_mw: float, p_blkt_nuclear_heat_total_mw: float, p_shld_nuclear_heat_mw: float, p_cp_shield_nuclear_heat_mw: float, @@ -55,7 +55,7 @@ def pumping_powers_as_fractions( Total FW nuclear heating (MW). p_fw_inboard_surface_heat_mw : float Inboard FW surface heating (MW). - psurffwo : float + p_fw_outboard_surface_heat_mw : float Outboard FW surface heating (MW). p_blkt_nuclear_heat_total_mw : float Total blanket nuclear heating (MW). @@ -76,7 +76,9 @@ def pumping_powers_as_fractions( Tuple of pumping powers (MW) for FW, blanket, shield, and divertor. """ p_fw_coolant_pump_mw = f_p_fw_coolant_pump_total_heat * ( - p_fw_nuclear_heat_total_mw + p_fw_inboard_surface_heat_mw + psurffwo + p_fw_nuclear_heat_total_mw + + p_fw_inboard_surface_heat_mw + + p_fw_outboard_surface_heat_mw ) p_blkt_coolant_pump_mw = ( f_p_blkt_coolant_pump_total_heat * p_blkt_nuclear_heat_total_mw diff --git a/process/models/stellarator/stellarator.py b/process/models/stellarator/stellarator.py index 11cb9e052..ce21d2591 100644 --- a/process/models/stellarator/stellarator.py +++ b/process/models/stellarator/stellarator.py @@ -834,7 +834,7 @@ def st_fwbs(self, output: bool): * self.data.first_wall.a_fw_inboard / self.data.first_wall.a_fw_total ) - psurffwo = ( + p_fw_outboard_surface_heat_mw = ( self.data.fwbs.p_fw_rad_total_mw * self.data.first_wall.a_fw_outboard / self.data.first_wall.a_fw_total @@ -895,7 +895,7 @@ def st_fwbs(self, output: bool): p_fw_inboard_nuclear_heat_mw + p_fw_outboard_nuclear_heat_mw + p_fw_inboard_surface_heat_mw - + psurffwo + + p_fw_outboard_surface_heat_mw + self.data.current_drive.p_beam_orbit_loss_mw ) ) diff --git a/tests/unit/models/blankets/test_ccfe_hcpb.py b/tests/unit/models/blankets/test_ccfe_hcpb.py index bde8f8f6e..eece3154f 100644 --- a/tests/unit/models/blankets/test_ccfe_hcpb.py +++ b/tests/unit/models/blankets/test_ccfe_hcpb.py @@ -708,7 +708,7 @@ class PowerflowCalcParam(NamedTuple): p_fw_inboard_surface_heat_mw: Any = None - psurffwo: Any = None + p_fw_outboard_surface_heat_mw: Any = None p_fw_coolant_pump_mw: Any = None @@ -782,7 +782,7 @@ class PowerflowCalcParam(NamedTuple): etaiso=0.90000000000000002, p_cp_shield_nuclear_heat_mw=0, p_fw_inboard_surface_heat_mw=0, - psurffwo=0, + p_fw_outboard_surface_heat_mw=0, p_fw_coolant_pump_mw=0, f_p_fw_coolant_pump_total_heat=0.0050000000000000001, p_blkt_coolant_pump_mw=0, @@ -827,7 +827,7 @@ class PowerflowCalcParam(NamedTuple): etaiso=0.90000000000000002, p_cp_shield_nuclear_heat_mw=0, p_fw_inboard_surface_heat_mw=97.271629070225231, - psurffwo=176.95628839065773, + p_fw_outboard_surface_heat_mw=176.95628839065773, p_fw_coolant_pump_mw=0, f_p_fw_coolant_pump_total_heat=0.0050000000000000001, p_blkt_coolant_pump_mw=0, @@ -965,7 +965,11 @@ def test_powerflow_calc(powerflowcalcparam, monkeypatch, ccfe_hcpb): powerflowcalcparam.p_fw_inboard_surface_heat_mw, ) - monkeypatch.setattr(ccfe_hcpb.data.fwbs, "psurffwo", powerflowcalcparam.psurffwo) + monkeypatch.setattr( + ccfe_hcpb.data.fwbs, + "p_fw_outboard_surface_heat_mw", + powerflowcalcparam.p_fw_outboard_surface_heat_mw, + ) monkeypatch.setattr( ccfe_hcpb.data.heat_transport, @@ -1063,7 +1067,7 @@ def test_powerflow_calc(powerflowcalcparam, monkeypatch, ccfe_hcpb): powerflowcalcparam.expected_psurffwi ) - assert ccfe_hcpb.data.fwbs.psurffwo == pytest.approx( + assert ccfe_hcpb.data.fwbs.p_fw_outboard_surface_heat_mw == pytest.approx( powerflowcalcparam.expected_psurffwo ) diff --git a/tests/unit/models/test_dcll.py b/tests/unit/models/test_dcll.py index 4269c92cd..34c8b421d 100644 --- a/tests/unit/models/test_dcll.py +++ b/tests/unit/models/test_dcll.py @@ -42,7 +42,7 @@ class DcllNeutronicsAndPowerParam(NamedTuple): p_fw_inboard_surface_heat_mw: Any = None - psurffwo: Any = None + p_fw_outboard_surface_heat_mw: Any = None p_blkt_nuclear_heat_total_mw: Any = None @@ -88,7 +88,7 @@ class DcllNeutronicsAndPowerParam(NamedTuple): p_fw_rad_total_mw=0, p_fw_nuclear_heat_total_mw=0, p_fw_inboard_surface_heat_mw=0, - psurffwo=0, + p_fw_outboard_surface_heat_mw=0, p_blkt_nuclear_heat_total_mw=0, pnuc_fw_ratio_dcll=0.14000000000000001, pnuc_blkt_ratio_dcll=0.85999999999999999, @@ -117,7 +117,7 @@ class DcllNeutronicsAndPowerParam(NamedTuple): p_fw_rad_total_mw=254.39207240222791, p_fw_nuclear_heat_total_mw=196.72081918001697, p_fw_inboard_surface_heat_mw=97.271629070225231, - psurffwo=176.95628839065773, + p_fw_outboard_surface_heat_mw=176.95628839065773, p_blkt_nuclear_heat_total_mw=1533.4949914565693, pnuc_fw_ratio_dcll=0.14000000000000001, pnuc_blkt_ratio_dcll=0.85999999999999999, @@ -221,7 +221,11 @@ def test_dcll_neutronics_and_power(dcllneutronicsandpowerparam, monkeypatch, dcl dcllneutronicsandpowerparam.p_fw_inboard_surface_heat_mw, ) - monkeypatch.setattr(dcll.data.fwbs, "psurffwo", dcllneutronicsandpowerparam.psurffwo) + monkeypatch.setattr( + dcll.data.fwbs, + "p_fw_outboard_surface_heat_mw", + dcllneutronicsandpowerparam.p_fw_outboard_surface_heat_mw, + ) monkeypatch.setattr( dcll.data.fwbs, From c5deea1bddaa69e78339bf5baefa49c9bd7b128c Mon Sep 17 00:00:00 2001 From: mn3981 Date: Mon, 8 Jun 2026 13:16:54 +0100 Subject: [PATCH 06/17] Rename 'p_fw_alpha_mw' to 'p_fw_alpha_surface_total_mw' for clarity and consistency across models and tests --- .../source/eng-models/power-requirements.md | 2 +- process/core/io/plot/summary.py | 6 +++--- process/data_structure/physics_variables.py | 4 ++-- process/models/blankets/dcll.py | 2 +- process/models/blankets/hcpb.py | 2 +- process/models/fw.py | 9 +++++---- process/models/power.py | 10 +++++----- process/models/stellarator/stellarator.py | 5 +++-- tests/unit/models/blankets/test_ccfe_hcpb.py | 10 ++++++---- tests/unit/models/test_dcll.py | 10 ++++++---- tests/unit/models/test_power.py | 12 ++++++++---- 11 files changed, 41 insertions(+), 31 deletions(-) diff --git a/documentation/source/eng-models/power-requirements.md b/documentation/source/eng-models/power-requirements.md index 214989efe..c538b42c0 100644 --- a/documentation/source/eng-models/power-requirements.md +++ b/documentation/source/eng-models/power-requirements.md @@ -214,7 +214,7 @@ $$ - $P_{\text{FW, nuclear}}$ & $P_{\text{Blkt, nuclear}}$ is the nuclear heating from neutron interaction (which includes the energy multiplication (`f_p_blkt_multiplication`) for the blanket.) - $P_{\text{FW,}\gamma}$ is the photon radiation incident on the FW (`p_fw_rad_total_mw`). -- $P_{\alpha,\text{loss}}$ is the plasma lost alpha power (`p_fw_alpha_mw`) +- $P_{\alpha,\text{loss}}$ is the plasma lost alpha power (`p_fw_alpha_surface_total_mw`) 8: The thermal power deposited in the shields is calculated: diff --git a/process/core/io/plot/summary.py b/process/core/io/plot/summary.py index 9619e1626..d3030f120 100644 --- a/process/core/io/plot/summary.py +++ b/process/core/io/plot/summary.py @@ -520,7 +520,7 @@ def plot_main_power_flow(axis: plt.Axes, mfile: MFile, scan: int, fig: plt.Figur axis.text( 0.22, 0.81, - f"$P_{{\\alpha,{{loss}}}}$\n{mfile.get('p_fw_alpha_mw', scan=scan):,.2f} MW", + f"$P_{{\\alpha,{{loss}}}}$\n{mfile.get('p_fw_alpha_surface_total_mw', scan=scan):,.2f} MW", transform=fig.transFigure, horizontalalignment="left", verticalalignment="bottom", @@ -1364,7 +1364,7 @@ def plot_main_power_flow(axis: plt.Axes, mfile: MFile, scan: int, fig: plt.Figur axis.text( 0.46, 0.85, - f"$P_{{\\text{{FW, }}\\alpha}}$:\n{mfile.get('p_fw_alpha_mw', scan=scan):.2f} MW", + f"$P_{{\\text{{FW, }}\\alpha}}$:\n{mfile.get('p_fw_alpha_surface_total_mw', scan=scan):.2f} MW", fontsize=9, verticalalignment="bottom", horizontalalignment="left", @@ -2870,7 +2870,7 @@ def plot_main_plasma_information( ) textstr_alpha = ( - f"$P_{{\\alpha,\\text{{loss}}}}$ {mfile.get('p_fw_alpha_mw', scan=scan):.2f} MW \n" + f"$P_{{\\alpha,\\text{{loss}}}}$ {mfile.get('p_fw_alpha_surface_total_mw', scan=scan):.2f} MW \n" f"$f_{{\\alpha,\\text{{coupled}}}}$ {mfile.get('f_p_alpha_plasma_deposited', scan=scan):.2f}" ) diff --git a/process/data_structure/physics_variables.py b/process/data_structure/physics_variables.py index 43af54e6a..d7c284984 100644 --- a/process/data_structure/physics_variables.py +++ b/process/data_structure/physics_variables.py @@ -658,8 +658,8 @@ class PhysicsData: f_pden_alpha_electron_mw: float = 0.0 """Alpha power per volume to electrons [MW/m3]""" - p_fw_alpha_mw: float = 0.0 - """alpha power escaping plasma and reaching first wall (MW)""" + p_fw_alpha_surface_total_mw: float = 0.0 + """Total alpha power escaping plasma and reaching first walls (MW)""" f_pden_alpha_ions_mw: float = 0.0 """alpha power per volume to ions (MW/m3)""" diff --git a/process/models/blankets/dcll.py b/process/models/blankets/dcll.py index c6c958b90..b23a53ac2 100644 --- a/process/models/blankets/dcll.py +++ b/process/models/blankets/dcll.py @@ -219,7 +219,7 @@ def dcll_neutronics_and_power(self, output: bool): * self.data.first_wall.a_fw_outboard / self.data.first_wall.a_fw_total + self.data.current_drive.p_beam_orbit_loss_mw - + self.data.physics.p_fw_alpha_mw + + self.data.physics.p_fw_alpha_surface_total_mw ) self.data.fwbs.p_fw_inboard_surface_heat_mw = ( self.data.fwbs.p_fw_rad_total_mw diff --git a/process/models/blankets/hcpb.py b/process/models/blankets/hcpb.py index 2b454f039..8f0e0112e 100644 --- a/process/models/blankets/hcpb.py +++ b/process/models/blankets/hcpb.py @@ -786,7 +786,7 @@ def powerflow_calc(self, output: bool): * self.data.first_wall.a_fw_outboard / self.data.first_wall.a_fw_total + self.data.current_drive.p_beam_orbit_loss_mw - + self.data.physics.p_fw_alpha_mw + + self.data.physics.p_fw_alpha_surface_total_mw ) self.data.fwbs.p_fw_inboard_surface_heat_mw = ( self.data.fwbs.p_fw_rad_total_mw diff --git a/process/models/fw.py b/process/models/fw.py index 3c23de7ac..5984dc34d 100644 --- a/process/models/fw.py +++ b/process/models/fw.py @@ -154,8 +154,9 @@ def run(self): ) # Power transported to the first wall by escaped alpha particles - self.data.physics.p_fw_alpha_mw = self.data.physics.p_alpha_total_mw * ( - 1.0e0 - self.data.physics.f_p_alpha_plasma_deposited + self.data.physics.p_fw_alpha_surface_total_mw = ( + self.data.physics.p_alpha_total_mw + * (1.0e0 - self.data.physics.f_p_alpha_plasma_deposited) ) @staticmethod @@ -839,8 +840,8 @@ def output_fw_surface_loads(self): po.ovarre( self.outfile, "Fast alpha particle power incident on the first-wall (MW)", - "(p_fw_alpha_mw)", - self.data.physics.p_fw_alpha_mw, + "(p_fw_alpha_surface_total_mw)", + self.data.physics.p_fw_alpha_surface_total_mw, "OP ", ) po.ovarre( diff --git a/process/models/power.py b/process/models/power.py index ede6e6f0e..60afd88f3 100644 --- a/process/models/power.py +++ b/process/models/power.py @@ -879,7 +879,7 @@ def component_thermal_powers(self): + self.data.heat_transport.p_blkt_breeder_pump_mw + self.data.primary_pumping.p_fw_blkt_coolant_pump_mw + self.data.current_drive.p_beam_orbit_loss_mw - + self.data.physics.p_fw_alpha_mw + + self.data.physics.p_fw_alpha_surface_total_mw + self.data.current_drive.p_beam_shine_through_mw ) else: @@ -890,7 +890,7 @@ def component_thermal_powers(self): + self.data.fwbs.p_blkt_nuclear_heat_total_mw + self.data.primary_pumping.p_fw_blkt_coolant_pump_mw + self.data.current_drive.p_beam_orbit_loss_mw - + self.data.physics.p_fw_alpha_mw + + self.data.physics.p_fw_alpha_surface_total_mw + self.data.current_drive.p_beam_shine_through_mw ) @@ -900,7 +900,7 @@ def component_thermal_powers(self): + self.data.fwbs.p_fw_rad_total_mw + self.data.heat_transport.p_fw_coolant_pump_mw + self.data.current_drive.p_beam_orbit_loss_mw - + self.data.physics.p_fw_alpha_mw + + self.data.physics.p_fw_alpha_surface_total_mw + self.data.current_drive.p_beam_shine_through_mw ) @@ -1110,8 +1110,8 @@ def output_plant_thermal_powers(self): po.ovarre( self.outfile, "Lost alpha-particle heat deposited in FW [MW]", - "(p_fw_alpha_mw)", - self.data.physics.p_fw_alpha_mw, + "(p_fw_alpha_surface_total_mw)", + self.data.physics.p_fw_alpha_surface_total_mw, ) po.ovarre( self.outfile, diff --git a/process/models/stellarator/stellarator.py b/process/models/stellarator/stellarator.py index ce21d2591..adbfbc4b5 100644 --- a/process/models/stellarator/stellarator.py +++ b/process/models/stellarator/stellarator.py @@ -2229,8 +2229,9 @@ def st_phys(self, output): # Power transported to the first wall by escaped alpha particles - self.data.physics.p_fw_alpha_mw = self.data.physics.p_alpha_total_mw * ( - 1.0e0 - self.data.physics.f_p_alpha_plasma_deposited + self.data.physics.p_fw_alpha_surface_total_mw = ( + self.data.physics.p_alpha_total_mw + * (1.0e0 - self.data.physics.f_p_alpha_plasma_deposited) ) # Nominal mean photon wall load diff --git a/tests/unit/models/blankets/test_ccfe_hcpb.py b/tests/unit/models/blankets/test_ccfe_hcpb.py index eece3154f..3ae6aaabb 100644 --- a/tests/unit/models/blankets/test_ccfe_hcpb.py +++ b/tests/unit/models/blankets/test_ccfe_hcpb.py @@ -730,7 +730,7 @@ class PowerflowCalcParam(NamedTuple): p_plasma_rad_mw: Any = None - p_fw_alpha_mw: Any = None + p_fw_alpha_surface_total_mw: Any = None p_plasma_separatrix_mw: Any = None @@ -793,7 +793,7 @@ class PowerflowCalcParam(NamedTuple): f_p_div_coolant_pump_total_heat=0.0050000000000000001, n_divertors=1, p_plasma_rad_mw=287.44866938104849, - p_fw_alpha_mw=19.835845058655043, + p_fw_alpha_surface_total_mw=19.835845058655043, p_plasma_separatrix_mw=143.6315222649435, p_he=8000000, dp_he=550000, @@ -838,7 +838,7 @@ class PowerflowCalcParam(NamedTuple): f_p_div_coolant_pump_total_heat=0.0050000000000000001, n_divertors=1, p_plasma_rad_mw=287.44866938104849, - p_fw_alpha_mw=19.829653483586444, + p_fw_alpha_surface_total_mw=19.829653483586444, p_plasma_separatrix_mw=143.51338080047339, p_he=8000000, dp_he=550000, @@ -1028,7 +1028,9 @@ def test_powerflow_calc(powerflowcalcparam, monkeypatch, ccfe_hcpb): ) monkeypatch.setattr( - ccfe_hcpb.data.physics, "p_fw_alpha_mw", powerflowcalcparam.p_fw_alpha_mw + ccfe_hcpb.data.physics, + "p_fw_alpha_surface_total_mw", + powerflowcalcparam.p_fw_alpha_surface_total_mw, ) monkeypatch.setattr( diff --git a/tests/unit/models/test_dcll.py b/tests/unit/models/test_dcll.py index 34c8b421d..4e7fd23bc 100644 --- a/tests/unit/models/test_dcll.py +++ b/tests/unit/models/test_dcll.py @@ -62,7 +62,7 @@ class DcllNeutronicsAndPowerParam(NamedTuple): p_plasma_rad_mw: Any = None - p_fw_alpha_mw: Any = None + p_fw_alpha_surface_total_mw: Any = None expected_p_fw_nuclear_heat_total_mw: Any = None @@ -98,7 +98,7 @@ class DcllNeutronicsAndPowerParam(NamedTuple): n_divertors=1, p_neutron_total_mw=1587.7386535917431, p_plasma_rad_mw=287.44866938104849, - p_fw_alpha_mw=19.835845058655043, + p_fw_alpha_surface_total_mw=19.835845058655043, expected_p_fw_nuclear_heat_total_mw=196.72081918001697, expected_p_blkt_nuclear_heat_total_mw=1533.4949914565693, expected_p_blkt_multiplication_mw=325.06710220789364, @@ -127,7 +127,7 @@ class DcllNeutronicsAndPowerParam(NamedTuple): n_divertors=1, p_neutron_total_mw=1587.2430556964196, p_plasma_rad_mw=287.44866938104849, - p_fw_alpha_mw=19.829653483586444, + p_fw_alpha_surface_total_mw=19.829653483586444, expected_p_fw_nuclear_heat_total_mw=196.65941460078642, expected_p_blkt_nuclear_heat_total_mw=1533.0163252173013, expected_p_blkt_multiplication_mw=324.96563552675644, @@ -280,7 +280,9 @@ def test_dcll_neutronics_and_power(dcllneutronicsandpowerparam, monkeypatch, dcl ) monkeypatch.setattr( - dcll.data.physics, "p_fw_alpha_mw", dcllneutronicsandpowerparam.p_fw_alpha_mw + dcll.data.physics, + "p_fw_alpha_surface_total_mw", + dcllneutronicsandpowerparam.p_fw_alpha_surface_total_mw, ) dcll.dcll_neutronics_and_power(False) diff --git a/tests/unit/models/test_power.py b/tests/unit/models/test_power.py index 8ee838c97..fa1846481 100644 --- a/tests/unit/models/test_power.py +++ b/tests/unit/models/test_power.py @@ -2191,7 +2191,7 @@ class Power2Param(NamedTuple): p_plasma_separatrix_mw: Any = None - p_fw_alpha_mw: Any = None + p_fw_alpha_surface_total_mw: Any = None n_divertors: Any = None @@ -2334,7 +2334,7 @@ class Power2Param(NamedTuple): p_plasma_rad_mw=287.99550050743289, itart=0, p_plasma_separatrix_mw=143.03180561618876, - p_fw_alpha_mw=19.833077403424262, + p_fw_alpha_surface_total_mw=19.833077403424262, n_divertors=1, p_plasma_ohmic_mw=0.61391840981850698, i_rad_loss=1, @@ -2436,7 +2436,7 @@ class Power2Param(NamedTuple): p_plasma_rad_mw=287.99550050743289, itart=0, p_plasma_separatrix_mw=142.91368967092416, - p_fw_alpha_mw=19.826887164528632, + p_fw_alpha_surface_total_mw=19.826887164528632, n_divertors=1, p_plasma_ohmic_mw=0.61391840981850698, i_rad_loss=1, @@ -2788,7 +2788,11 @@ def test_power2(power2param, monkeypatch, power): power.data.physics, "p_plasma_separatrix_mw", power2param.p_plasma_separatrix_mw ) - monkeypatch.setattr(power.data.physics, "p_fw_alpha_mw", power2param.p_fw_alpha_mw) + monkeypatch.setattr( + power.data.physics, + "p_fw_alpha_surface_total_mw", + power2param.p_fw_alpha_surface_total_mw, + ) monkeypatch.setattr(power.data.divertor, "n_divertors", power2param.n_divertors) From 158ebe17ada819825bf7861cae3b8ded631e535b Mon Sep 17 00:00:00 2001 From: mn3981 Date: Mon, 8 Jun 2026 13:19:01 +0100 Subject: [PATCH 07/17] Add alpha particle heat flux variables for inboard and outboard first wall --- process/data_structure/fwbs_variables.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/process/data_structure/fwbs_variables.py b/process/data_structure/fwbs_variables.py index ade3c5bf8..6fc22575a 100644 --- a/process/data_structure/fwbs_variables.py +++ b/process/data_structure/fwbs_variables.py @@ -176,6 +176,12 @@ class FWBSData: p_fw_outboard_surface_heat_mw: float = 0.0 """Surface heat flux on outboard first wall [MW]""" + p_fw_inboard_alpha_surface_mw: float = 0.0 + """Alpha particle heat flux on inboard first wall [MW]""" + + p_fw_outboard_alpha_surface_mw: float = 0.0 + """Alpha particle heat flux on outboard first wall [MW]""" + vol_fw_total: float = 0.0 """First wall volume [m3]""" From 233c22f96b7b4432bf6b08a29b72c79a33f4a909 Mon Sep 17 00:00:00 2001 From: mn3981 Date: Mon, 8 Jun 2026 13:24:15 +0100 Subject: [PATCH 08/17] Add alpha power deposition calculations for first wall surfaces --- process/models/fw.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/process/models/fw.py b/process/models/fw.py index 5984dc34d..57ccbd9c4 100644 --- a/process/models/fw.py +++ b/process/models/fw.py @@ -158,6 +158,11 @@ def run(self): self.data.physics.p_alpha_total_mw * (1.0e0 - self.data.physics.f_p_alpha_plasma_deposited) ) + + # Will assume that all alpha power reaching the first wall is deposited on the + # outboard side. + self.data.fwbs.p_fw_inboard_alpha_surface_mw = 0.0 + self.data.fwbs.p_fw_outboard_alpha_surface_mw = self.data.physics.p_fw_alpha_surface_total_mw @staticmethod def calculate_first_wall_half_height( From bfec0a2b9221c6fc381973739573b319b33f6f65 Mon Sep 17 00:00:00 2001 From: mn3981 Date: Mon, 8 Jun 2026 13:37:36 +0100 Subject: [PATCH 09/17] Refactor heat flux calculations by removing redundant code and updating test parameters for HCPB model --- process/models/blankets/dcll.py | 14 ----------- process/models/blankets/hcpb.py | 14 ----------- process/models/fw.py | 25 +++++++++++++++++--- tests/unit/models/blankets/test_ccfe_hcpb.py | 20 ++-------------- 4 files changed, 24 insertions(+), 49 deletions(-) diff --git a/process/models/blankets/dcll.py b/process/models/blankets/dcll.py index b23a53ac2..d38aa4489 100644 --- a/process/models/blankets/dcll.py +++ b/process/models/blankets/dcll.py @@ -212,20 +212,6 @@ def dcll_neutronics_and_power(self, output: bool): # FW - # Surface heat flux on first wall (MW) - # All of the fast particle losses go to the outer wall. - self.data.fwbs.p_fw_outboard_surface_heat_mw = ( - self.data.fwbs.p_fw_rad_total_mw - * self.data.first_wall.a_fw_outboard - / self.data.first_wall.a_fw_total - + self.data.current_drive.p_beam_orbit_loss_mw - + self.data.physics.p_fw_alpha_surface_total_mw - ) - self.data.fwbs.p_fw_inboard_surface_heat_mw = ( - self.data.fwbs.p_fw_rad_total_mw - * (1 - self.data.first_wall.a_fw_outboard / self.data.first_wall.a_fw_total) - ) - if output: po.osubhd( self.outfile, "DCLL model: Nuclear and Radiation Heating of Components" diff --git a/process/models/blankets/hcpb.py b/process/models/blankets/hcpb.py index 8f0e0112e..dc5f552fb 100644 --- a/process/models/blankets/hcpb.py +++ b/process/models/blankets/hcpb.py @@ -779,20 +779,6 @@ def powerflow_calc(self, output: bool): outlet_saturated_fluid_properties.temperature - 20.0 ) # in K - # Surface heat flux on first wall (outboard and inboard) (MW) - # All of the fast particle losses go to the outer wall. - self.data.fwbs.p_fw_outboard_surface_heat_mw = ( - self.data.fwbs.p_fw_rad_total_mw - * self.data.first_wall.a_fw_outboard - / self.data.first_wall.a_fw_total - + self.data.current_drive.p_beam_orbit_loss_mw - + self.data.physics.p_fw_alpha_surface_total_mw - ) - self.data.fwbs.p_fw_inboard_surface_heat_mw = ( - self.data.fwbs.p_fw_rad_total_mw - * (1 - self.data.first_wall.a_fw_outboard / self.data.first_wall.a_fw_total) - ) - i_p_coolant_pumping = PumpingPowerModelTypes(self.data.fwbs.i_p_coolant_pumping) if i_p_coolant_pumping == PumpingPowerModelTypes.FRACTION_OF_HEAT: # User sets mechanical pumping power directly diff --git a/process/models/fw.py b/process/models/fw.py index 57ccbd9c4..d7335f0a4 100644 --- a/process/models/fw.py +++ b/process/models/fw.py @@ -158,11 +158,30 @@ def run(self): self.data.physics.p_alpha_total_mw * (1.0e0 - self.data.physics.f_p_alpha_plasma_deposited) ) - - # Will assume that all alpha power reaching the first wall is deposited on the + + # Will assume that all alpha power reaching the first wall is deposited on the # outboard side. self.data.fwbs.p_fw_inboard_alpha_surface_mw = 0.0 - self.data.fwbs.p_fw_outboard_alpha_surface_mw = self.data.physics.p_fw_alpha_surface_total_mw + self.data.fwbs.p_fw_outboard_alpha_surface_mw = ( + self.data.physics.p_fw_alpha_surface_total_mw + ) + + # Surface heat flux on first wall (MW) + # All of the fast particle losses go to the outer wall, as do all beam losses + # and shine through. + # Some power is lost to HCD and ports on the outboard wall, so this is + # taken into account with a coverage factor. + self.data.fwbs.p_fw_outboard_surface_heat_mw = ( + self.data.fwbs.p_fw_outboard_rad_mw + + self.data.current_drive.p_beam_orbit_loss_mw + + self.data.fwbs.p_fw_outboard_alpha_surface_mw + + self.data.current_drive.p_beam_shine_through_mw + ) * (1.0 - self.data.fwbs.f_a_fw_outboard_hcd) + + self.data.fwbs.p_fw_inboard_surface_heat_mw = ( + self.data.fwbs.p_fw_inboard_rad_mw + + self.data.fwbs.p_fw_inboard_alpha_surface_mw + ) @staticmethod def calculate_first_wall_half_height( diff --git a/tests/unit/models/blankets/test_ccfe_hcpb.py b/tests/unit/models/blankets/test_ccfe_hcpb.py index 3ae6aaabb..155b2bc37 100644 --- a/tests/unit/models/blankets/test_ccfe_hcpb.py +++ b/tests/unit/models/blankets/test_ccfe_hcpb.py @@ -748,10 +748,6 @@ class PowerflowCalcParam(NamedTuple): expected_p_div_rad_total_mw: Any = None - expected_psurffwi: Any = None - - expected_psurffwo: Any = None - expected_p_shld_coolant_pump_mw: Any = None expected_p_div_coolant_pump_mw: Any = None @@ -801,11 +797,9 @@ class PowerflowCalcParam(NamedTuple): t_in_bb=573.13, t_out_bb=773.13, p_fw_blkt_coolant_pump_mw=0, - expected_psurffwi=97.271629070225231, - expected_psurffwo=176.95628839065773, expected_p_shld_coolant_pump_mw=0.0068056297940224456, expected_p_div_coolant_pump_mw=1.7970292653352464, - expected_p_fw_blkt_coolant_pump_mw=202.00455086503842, + expected_p_fw_blkt_coolant_pump_mw=175.06074627472202, ), PowerflowCalcParam( a_fw_outboard=1168.1172772224481, @@ -846,11 +840,9 @@ class PowerflowCalcParam(NamedTuple): t_in_bb=573.13, t_out_bb=773.13, p_fw_blkt_coolant_pump_mw=202.00455086503842, - expected_psurffwi=97.271629070225259, - expected_psurffwo=176.95009681558912, expected_p_shld_coolant_pump_mw=0.007019085478296147, expected_p_div_coolant_pump_mw=1.7961533897828594, - expected_p_fw_blkt_coolant_pump_mw=201.94492795635171, + expected_p_fw_blkt_coolant_pump_mw=201.94553629918676, ), ], ) @@ -1065,14 +1057,6 @@ def test_powerflow_calc(powerflowcalcparam, monkeypatch, ccfe_hcpb): ccfe_hcpb.powerflow_calc(False) - assert ccfe_hcpb.data.fwbs.p_fw_inboard_surface_heat_mw == pytest.approx( - powerflowcalcparam.expected_psurffwi - ) - - assert ccfe_hcpb.data.fwbs.p_fw_outboard_surface_heat_mw == pytest.approx( - powerflowcalcparam.expected_psurffwo - ) - assert ccfe_hcpb.data.heat_transport.p_shld_coolant_pump_mw == pytest.approx( powerflowcalcparam.expected_p_shld_coolant_pump_mw ) From 6128fa84576ced0c3504f5810ac1c2fa45b53cb9 Mon Sep 17 00:00:00 2001 From: mn3981 Date: Mon, 8 Jun 2026 14:38:53 +0100 Subject: [PATCH 10/17] Update radiation and alpha particle heat flux descriptions for first wall surfaces --- process/core/io/plot/summary.py | 8 ++++--- process/models/fw.py | 40 ++++++++++++++++++++++++++++----- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/process/core/io/plot/summary.py b/process/core/io/plot/summary.py index d3030f120..b43f9499a 100644 --- a/process/core/io/plot/summary.py +++ b/process/core/io/plot/summary.py @@ -14776,7 +14776,8 @@ def plot_poloidal_power_distribution( ax.text( label_x, label_y, - f"{deg_blkt_outboard_poloidal_plasma:.1f}°\n({f_deg_blkt_outboard_poloidal_plasma * 100:.1f}%)", + f"$P_{{\\gamma}}$={m_file.get('p_fw_outboard_rad_mw', scan=scan):.1f}MW\n" + f"$P_{{\\alpha}}$={m_file.get('p_fw_outboard_alpha_surface_mw', scan=scan):.1f}MW", fontsize=7, color="purple", ha="center", @@ -14829,7 +14830,8 @@ def plot_poloidal_power_distribution( ax.text( label_x, label_y, - f"{deg_blkt_inboard_poloidal_plasma:.1f}°\n({f_deg_blkt_inboard_poloidal_plasma * 100:.1f}%)", + f"$P_{{\\gamma}}$={m_file.get('p_fw_inboard_rad_mw', scan=scan):.1f}MW\n" + f"$P_{{\\alpha}}$={m_file.get('p_fw_inboard_alpha_surface_mw', scan=scan):.1f}MW", fontsize=7, color="green", ha="center", @@ -15909,7 +15911,7 @@ def main_plot( ) plot_main_power_flow( - figs[36].add_subplot(111, aspect="equal"), m_file, scan, figs[35] + figs[36].add_subplot(111, aspect="equal"), m_file, scan, figs[36] ) ax24 = figs[37].add_subplot(111) diff --git a/process/models/fw.py b/process/models/fw.py index d7335f0a4..9f03152a6 100644 --- a/process/models/fw.py +++ b/process/models/fw.py @@ -835,7 +835,7 @@ def output_fw_surface_loads(self): po.ovarre( self.outfile, - "Nominal mean radiation load on vessel first-wall (MW/m^2)", + "Nominal mean radiation load on vessel first-wall [MW/m²]", "(pflux_fw_rad_mw)", self.data.physics.pflux_fw_rad_mw, "OP ", @@ -849,28 +849,58 @@ def output_fw_surface_loads(self): ) po.ovarre( self.outfile, - "Maximum permitted radiation first-wall load (MW/m^2)", + "Maximum permitted radiation first-wall load [MW/m²]", "(pflux_fw_rad_max)", self.data.constraints.pflux_fw_rad_max, "IP ", ) po.ovarre( self.outfile, - "Peak radiation wall load (MW/m^2)", + "Peak radiation wall load [MW/m²]", "(pflux_fw_rad_max_mw)", self.data.constraints.pflux_fw_rad_max_mw, "OP ", ) po.ovarre( self.outfile, - "Fast alpha particle power incident on the first-wall (MW)", + "Radiation heat flux on inboard first wall [MW]", + "(p_fw_inboard_rad_mw)", + self.data.fwbs.p_fw_inboard_rad_mw, + "OP ", + ) + po.ovarre( + self.outfile, + "Radiation heat flux on outboard first wall [MW]", + "(p_fw_outboard_rad_mw)", + self.data.fwbs.p_fw_outboard_rad_mw, + "OP ", + ) + po.oblnkl(self.outfile) + po.ovarre( + self.outfile, + "Alpha particle heat flux on inboard first wall [MW]", + "(p_fw_inboard_alpha_surface_mw)", + self.data.fwbs.p_fw_inboard_alpha_surface_mw, + "OP ", + ) + po.ovarre( + self.outfile, + "Alpha particle heat flux on outboard first wall [MW]", + "(p_fw_outboard_alpha_surface_mw)", + self.data.fwbs.p_fw_outboard_alpha_surface_mw, + "OP ", + ) + po.ovarre( + self.outfile, + "Fast alpha particle power incident on the first-wall [MW]", "(p_fw_alpha_surface_total_mw)", self.data.physics.p_fw_alpha_surface_total_mw, "OP ", ) + po.oblnkl(self.outfile) po.ovarre( self.outfile, - "Nominal mean neutron load on vessel first-wall (MW/m^2)", + "Nominal mean neutron load on vessel first-wall [MW/m²]", "(pflux_fw_neutron_mw)", self.data.physics.pflux_fw_neutron_mw, "OP ", From 067941675afb12270c4d53ef02d169aa1e7220d9 Mon Sep 17 00:00:00 2001 From: mn3981 Date: Mon, 8 Jun 2026 15:04:16 +0100 Subject: [PATCH 11/17] Change FW load logic --- process/models/fw.py | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/process/models/fw.py b/process/models/fw.py index 9f03152a6..8b26cdd09 100644 --- a/process/models/fw.py +++ b/process/models/fw.py @@ -119,23 +119,25 @@ def run(self): self.data.physics.p_neutron_total_mw / self.data.first_wall.a_fw_total ) - # Radiation power incident on first wall (MW) - # Set based on the total radiation power and the angular fractions taken up by - # the inboard and outboard first wall, which are calculated based on the - # geometry of the first wall and the plasma. - self.data.fwbs.p_fw_rad_total_mw = self.data.physics.p_plasma_rad_mw * ( - self.data.blanket.f_deg_blkt_outboard_poloidal_plasma - + self.data.blanket.f_deg_blkt_inboard_poloidal_plasma - ) - self.data.fwbs.p_fw_inboard_rad_mw = ( self.data.physics.p_plasma_rad_mw * self.data.blanket.f_deg_blkt_inboard_poloidal_plasma ) + # Some power is lost to HCD and ports on the outboard wall, so this is taken + # into account with a coverage factor. self.data.fwbs.p_fw_outboard_rad_mw = ( self.data.physics.p_plasma_rad_mw * self.data.blanket.f_deg_blkt_outboard_poloidal_plasma + * (1.0 - self.data.fwbs.f_a_fw_outboard_hcd) + ) + + # Radiation power incident on first wall (MW) + # Set based on the total radiation power and the angular fractions taken up by + # the inboard and outboard first wall, which are calculated based on the + # geometry of the first wall and the plasma. + self.data.fwbs.p_fw_rad_total_mw = ( + self.data.fwbs.p_fw_inboard_rad_mw + self.data.fwbs.p_fw_outboard_rad_mw ) if self.data.physics.i_pflux_fw_neutron == 1: @@ -154,16 +156,21 @@ def run(self): ) # Power transported to the first wall by escaped alpha particles - self.data.physics.p_fw_alpha_surface_total_mw = ( + # Some is lost to HCD and ports on the outboard wall, so this is taken into + # account with a coverage factor. + + self.data.fwbs.p_fw_outboard_alpha_surface_mw = ( self.data.physics.p_alpha_total_mw * (1.0e0 - self.data.physics.f_p_alpha_plasma_deposited) + * (1.0 - self.data.fwbs.f_a_fw_outboard_hcd) ) - # Will assume that all alpha power reaching the first wall is deposited on the # outboard side. self.data.fwbs.p_fw_inboard_alpha_surface_mw = 0.0 - self.data.fwbs.p_fw_outboard_alpha_surface_mw = ( - self.data.physics.p_fw_alpha_surface_total_mw + + self.data.physics.p_fw_alpha_surface_total_mw = ( + self.data.fwbs.p_fw_outboard_alpha_surface_mw + + self.data.fwbs.p_fw_inboard_alpha_surface_mw ) # Surface heat flux on first wall (MW) @@ -176,7 +183,7 @@ def run(self): + self.data.current_drive.p_beam_orbit_loss_mw + self.data.fwbs.p_fw_outboard_alpha_surface_mw + self.data.current_drive.p_beam_shine_through_mw - ) * (1.0 - self.data.fwbs.f_a_fw_outboard_hcd) + ) self.data.fwbs.p_fw_inboard_surface_heat_mw = ( self.data.fwbs.p_fw_inboard_rad_mw From 976dc42d6ffa65de32e4abc2111767bd3f4e3abf Mon Sep 17 00:00:00 2001 From: mn3981 Date: Mon, 8 Jun 2026 15:30:50 +0100 Subject: [PATCH 12/17] Add new surface flux variables --- process/data_structure/fwbs_variables.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/process/data_structure/fwbs_variables.py b/process/data_structure/fwbs_variables.py index 6fc22575a..64c1ce534 100644 --- a/process/data_structure/fwbs_variables.py +++ b/process/data_structure/fwbs_variables.py @@ -182,6 +182,18 @@ class FWBSData: p_fw_outboard_alpha_surface_mw: float = 0.0 """Alpha particle heat flux on outboard first wall [MW]""" + pflux_fw_inboard_neutron_surface_average_mw: float = 0.0 + """Average neutron flux on inboard first wall surface [MW/m²]""" + + pflux_fw_outboard_neutron_surface_average_mw: float = 0.0 + """Average neutron flux on outboard first wall surface [MW/m²]""" + + pflux_fw_inboard_rad_surface_average_mw: float = 0.0 + """Average radiation flux on inboard first wall surface [MW/m²]""" + + pflux_fw_outboard_rad_surface_average_mw: float = 0.0 + """Average radiation flux on outboard first wall surface [MW/m²]""" + vol_fw_total: float = 0.0 """First wall volume [m3]""" From 6c49c1fc2caf54721aace952316def8e8e2104fc Mon Sep 17 00:00:00 2001 From: mn3981 Date: Mon, 8 Jun 2026 16:45:56 +0100 Subject: [PATCH 13/17] Add radiation outboard surface values --- process/models/fw.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/process/models/fw.py b/process/models/fw.py index 8b26cdd09..1feff052d 100644 --- a/process/models/fw.py +++ b/process/models/fw.py @@ -140,6 +140,21 @@ def run(self): self.data.fwbs.p_fw_inboard_rad_mw + self.data.fwbs.p_fw_outboard_rad_mw ) + # Radiation surface heat flux on first wall (MW/m²) + # The full area is used as the radiation is assumed to be uniformly distributed + # across the first wall, so the coverage factors are not applied here. + self.data.fwbs.pflux_fw_inboard_rad_surface_average_mw = ( + self.data.physics.p_plasma_rad_mw + * self.data.blanket.f_deg_blkt_inboard_poloidal_plasma + / self.data.first_wall.a_fw_inboard_full_coverage + ) + + self.data.fwbs.pflux_fw_outboard_rad_surface_average_mw = ( + self.data.physics.p_plasma_rad_mw + * self.data.blanket.f_deg_blkt_outboard_poloidal_plasma + / self.data.first_wall.a_fw_outboard_full_coverage + ) + if self.data.physics.i_pflux_fw_neutron == 1: self.data.physics.pflux_fw_rad_mw = ( self.data.physics.ffwal @@ -875,6 +890,13 @@ def output_fw_surface_loads(self): self.data.fwbs.p_fw_inboard_rad_mw, "OP ", ) + po.ovarre( + self.outfile, + "Radiation surface heat flux on inboard first wall [MW/m²]", + "(pflux_fw_inboard_rad_surface_average_mw)", + self.data.fwbs.pflux_fw_inboard_rad_surface_average_mw, + "OP ", + ) po.ovarre( self.outfile, "Radiation heat flux on outboard first wall [MW]", @@ -882,6 +904,13 @@ def output_fw_surface_loads(self): self.data.fwbs.p_fw_outboard_rad_mw, "OP ", ) + po.ovarre( + self.outfile, + "Radiation surface heat flux on outboard first wall [MW/m²]", + "(pflux_fw_outboard_rad_surface_average_mw)", + self.data.fwbs.pflux_fw_outboard_rad_surface_average_mw, + "OP ", + ) po.oblnkl(self.outfile) po.ovarre( self.outfile, From 16a6e3527a3a3c6ffc0be89dab75dc87da4626bb Mon Sep 17 00:00:00 2001 From: mn3981 Date: Tue, 9 Jun 2026 11:26:27 +0100 Subject: [PATCH 14/17] Add neutron surface heat flux calculations for first wall components --- process/core/io/plot/summary.py | 76 ++++++++++++++++++++++++--------- process/models/fw.py | 30 ++++++++++++- 2 files changed, 85 insertions(+), 21 deletions(-) diff --git a/process/core/io/plot/summary.py b/process/core/io/plot/summary.py index b43f9499a..876dea9d8 100644 --- a/process/core/io/plot/summary.py +++ b/process/core/io/plot/summary.py @@ -14766,18 +14766,13 @@ def plot_poloidal_power_distribution( ax.plot(arc_x, arc_y, color="purple", linewidth=2) - # Add angle label at the arc - mid_angle = np.deg2rad((angle_start + angle_end) / 2) - label_radius = arc_radius * 1.8 - label_x = rmajor + label_radius * np.cos(mid_angle) - label_y = label_radius * np.sin(mid_angle) - # Plot the info box for the outboard blanket ax.text( - label_x, - label_y, - f"$P_{{\\gamma}}$={m_file.get('p_fw_outboard_rad_mw', scan=scan):.1f}MW\n" - f"$P_{{\\alpha}}$={m_file.get('p_fw_outboard_alpha_surface_mw', scan=scan):.1f}MW", + rmajor * 1.75, + 0.0, + f"$P_{{\\gamma}}$={m_file.get('p_fw_outboard_rad_mw', scan=scan):.3f} MW\n" + f"$\\Gamma_{{\\gamma}}$={m_file.get('pflux_fw_outboard_rad_surface_average_mw', scan=scan):.3f} MW/m²\n\n" + f"$P_{{\\alpha}}$={m_file.get('p_fw_outboard_alpha_surface_mw', scan=scan):.3f} MW", fontsize=7, color="purple", ha="center", @@ -14786,12 +14781,21 @@ def plot_poloidal_power_distribution( bbox={ "boxstyle": "round", "facecolor": "white", - "alpha": 0.8, + "alpha": 1.0, "edgecolor": "purple", "linewidth": 1.5, }, ) + # Plot arrow for the outboard blanket + ax.annotate( + "", + xy=(rmajor, 0), + xytext=(rmajor + rminor, 0), + arrowprops={"arrowstyle": "<-", "color": "purple", "linewidth": 1.5}, + zorder=5, + ) + # Plot arrows for the inboard blanket angles ax.annotate( "", @@ -14801,6 +14805,28 @@ def plot_poloidal_power_distribution( zorder=5, ) + # Plot the total loads at the centre of the plasma + ax.text( + rmajor, + 0.0, + f"$P_{{\\gamma}}$={m_file.get('p_plasma_rad_mw', scan=scan):.1f}MW\n" + f"$P_{{\\alpha}}$={m_file.get('p_fw_outboard_alpha_surface_mw', scan=scan):.1f}MW\n" + f"$P_n$={m_file.get('p_neutron_total_mw', scan=scan):.1f}MW", + fontsize=7, + color="black", + ha="center", + va="center", + weight="bold", + bbox={ + "boxstyle": "round", + "facecolor": "wheat", + "alpha": 1.0, + "edgecolor": "black", + "linewidth": 1.5, + }, + zorder=20, + ) + ax.annotate( "", xy=(rmajor, 0), @@ -14826,12 +14852,13 @@ def plot_poloidal_power_distribution( label_x = rmajor - label_radius * np.cos(mid_angle) label_y = label_radius * np.sin(mid_angle) - # Plot the info box for the inboard blanket + # Plot the info box for the inboard blanket/FW ax.text( - label_x, - label_y, - f"$P_{{\\gamma}}$={m_file.get('p_fw_inboard_rad_mw', scan=scan):.1f}MW\n" - f"$P_{{\\alpha}}$={m_file.get('p_fw_inboard_alpha_surface_mw', scan=scan):.1f}MW", + rmajor / 3, + 0.0, + f"$P_{{\\gamma}}$={m_file.get('p_fw_inboard_rad_mw', scan=scan):.2f} MW\n" + f"$\\Gamma_{{\\gamma}}$={m_file.get('pflux_fw_inboard_rad_surface_average_mw', scan=scan):.3f} MW/m²\n\n" + f"$P_{{\\alpha}}$={m_file.get('p_fw_inboard_alpha_surface_mw', scan=scan):.2f}MW", fontsize=7, color="green", ha="center", @@ -14840,13 +14867,22 @@ def plot_poloidal_power_distribution( bbox={ "boxstyle": "round", "facecolor": "white", - "alpha": 0.8, + "alpha": 1.0, "edgecolor": "green", "linewidth": 1.5, }, zorder=5, ) + # Plot arrow for the inboard blanket + ax.annotate( + "", + xy=(rmajor, 0), + xytext=(rmajor - rminor, 0), + arrowprops={"arrowstyle": "<-", "color": "green", "linewidth": 1.5}, + zorder=5, + ) + # Plot arrows for the divertor angles # If double null then plot the upper also if i_single_null == 0: @@ -14880,7 +14916,7 @@ def plot_poloidal_power_distribution( bbox={ "boxstyle": "round", "facecolor": "white", - "alpha": 0.8, + "alpha": 1.0, "edgecolor": "black", "linewidth": 1.5, }, @@ -14918,7 +14954,7 @@ def plot_poloidal_power_distribution( bbox={ "boxstyle": "round", "facecolor": "white", - "alpha": 0.8, + "alpha": 1.0, "edgecolor": "black", "linewidth": 1.5, }, @@ -14953,6 +14989,8 @@ def plot_poloidal_power_distribution( label="Midplane", ) + ax.set_xlim(-rmajor / 4, 2 * rmajor) + def plot_detailed_plasma_parameters(axis: plt.Axes, fig, mfile: MFile, scan: int): """Function to plot detailed plasma parameters from physics data. diff --git a/process/models/fw.py b/process/models/fw.py index 1feff052d..0dfbf4c6d 100644 --- a/process/models/fw.py +++ b/process/models/fw.py @@ -141,14 +141,14 @@ def run(self): ) # Radiation surface heat flux on first wall (MW/m²) - # The full area is used as the radiation is assumed to be uniformly distributed + # The full area is used as the radiation is assumed to be uniformly distributed # across the first wall, so the coverage factors are not applied here. self.data.fwbs.pflux_fw_inboard_rad_surface_average_mw = ( self.data.physics.p_plasma_rad_mw * self.data.blanket.f_deg_blkt_inboard_poloidal_plasma / self.data.first_wall.a_fw_inboard_full_coverage ) - + self.data.fwbs.pflux_fw_outboard_rad_surface_average_mw = ( self.data.physics.p_plasma_rad_mw * self.data.blanket.f_deg_blkt_outboard_poloidal_plasma @@ -205,6 +205,18 @@ def run(self): + self.data.fwbs.p_fw_inboard_alpha_surface_mw ) + self.data.fwbs.pflux_fw_outboard_neutron_surface_average_mw = ( + self.data.physics.p_neutron_total_mw + * self.data.blanket.f_deg_blkt_outboard_poloidal_plasma + / self.data.first_wall.a_fw_outboard_full_coverage + ) + + self.data.fwbs.pflux_fw_inboard_neutron_surface_average_mw = ( + self.data.physics.p_neutron_total_mw + * self.data.blanket.f_deg_blkt_inboard_poloidal_plasma + / self.data.first_wall.a_fw_inboard_full_coverage + ) + @staticmethod def calculate_first_wall_half_height( z_plasma_xpoint_lower: float, @@ -941,3 +953,17 @@ def output_fw_surface_loads(self): self.data.physics.pflux_fw_neutron_mw, "OP ", ) + po.ovarre( + self.outfile, + "Nominal mean neutron load on inboard first-wall [MW/m²]", + "(pflux_fw_inboard_neutron_mw)", + self.data.fwbs.pflux_fw_inboard_neutron_surface_average_mw, + "OP ", + ) + po.ovarre( + self.outfile, + "Nominal mean neutron load on outboard first-wall [MW/m²]", + "(pflux_fw_outboard_neutron_mw)", + self.data.fwbs.pflux_fw_outboard_neutron_surface_average_mw, + "OP ", + ) From e15d5ec167e1ddfbeb16d7b08c482680f92d717b Mon Sep 17 00:00:00 2001 From: mn3981 Date: Tue, 9 Jun 2026 13:47:28 +0100 Subject: [PATCH 15/17] Add neutron heat flux variables for inboard and outboard first wall components --- process/core/io/plot/summary.py | 19 +++++++++++----- process/data_structure/fwbs_variables.py | 6 +++++ process/models/fw.py | 28 ++++++++++++++++++++++-- 3 files changed, 45 insertions(+), 8 deletions(-) diff --git a/process/core/io/plot/summary.py b/process/core/io/plot/summary.py index 876dea9d8..71b2892f7 100644 --- a/process/core/io/plot/summary.py +++ b/process/core/io/plot/summary.py @@ -14671,6 +14671,8 @@ def plot_poloidal_power_distribution( # MFILE variables needed to plot the blkt structure and angles rmajor = m_file.get("rmajor", scan=scan) rminor = m_file.get("rminor", scan=scan) + triang = m_file.get("triang", scan=scan) + kappa = m_file.get("kappa", scan=scan) dr_fw_plasma_gap_outboard = m_file.get("dr_fw_plasma_gap_outboard", scan=scan) dr_fw_plasma_gap_inboard = m_file.get("dr_fw_plasma_gap_inboard", scan=scan) dr_fw_inboard = m_file.get("dr_fw_inboard", scan=scan) @@ -14772,7 +14774,9 @@ def plot_poloidal_power_distribution( 0.0, f"$P_{{\\gamma}}$={m_file.get('p_fw_outboard_rad_mw', scan=scan):.3f} MW\n" f"$\\Gamma_{{\\gamma}}$={m_file.get('pflux_fw_outboard_rad_surface_average_mw', scan=scan):.3f} MW/m²\n\n" - f"$P_{{\\alpha}}$={m_file.get('p_fw_outboard_alpha_surface_mw', scan=scan):.3f} MW", + f"$P_{{\\alpha}}$={m_file.get('p_fw_outboard_alpha_surface_mw', scan=scan):.3f} MW\n\n" + f"$P_n$={m_file.get('p_fw_outboard_neutron_incident_mw', scan=scan):.3f} MW\n" + f"$\\Gamma_{{n}}$={m_file.get('pflux_fw_outboard_neutron_surface_average_mw', scan=scan):.3f} MW/m²\n", fontsize=7, color="purple", ha="center", @@ -14856,9 +14860,11 @@ def plot_poloidal_power_distribution( ax.text( rmajor / 3, 0.0, - f"$P_{{\\gamma}}$={m_file.get('p_fw_inboard_rad_mw', scan=scan):.2f} MW\n" + f"$P_{{\\gamma}}$={m_file.get('p_fw_inboard_rad_mw', scan=scan):.3f} MW\n" f"$\\Gamma_{{\\gamma}}$={m_file.get('pflux_fw_inboard_rad_surface_average_mw', scan=scan):.3f} MW/m²\n\n" - f"$P_{{\\alpha}}$={m_file.get('p_fw_inboard_alpha_surface_mw', scan=scan):.2f}MW", + f"$P_{{\\alpha}}$={m_file.get('p_fw_inboard_alpha_surface_mw', scan=scan):.3f} MW\n\n" + f"$P_n$={m_file.get('p_fw_inboard_neutron_incident_mw', scan=scan):.3f} MW\n" + f"$\\Gamma_{{n}}$={m_file.get('pflux_fw_inboard_neutron_surface_average_mw', scan=scan):.3f} MW/m²\n\n", fontsize=7, color="green", ha="center", @@ -14943,9 +14949,10 @@ def plot_poloidal_power_distribution( # Plot the info box for the lower divertor angle ax.text( - label_x, - label_y, - f"{deg_div_poloidal_plasma:.1f}°\n({f_ster_div_single * 100:.1f}%)", + rmajor - (triang * rminor), + -(rminor * kappa), + f"$P_{{\\gamma}}$={m_file.get('p_div_rad_total_mw', scan=scan):.3f} MW\n\n" + f"$P_n$={m_file.get('p_div_nuclear_heat_total_mw', scan=scan):.3f} MW\n", fontsize=7, color="black", ha="center", diff --git a/process/data_structure/fwbs_variables.py b/process/data_structure/fwbs_variables.py index 64c1ce534..4163290c9 100644 --- a/process/data_structure/fwbs_variables.py +++ b/process/data_structure/fwbs_variables.py @@ -182,6 +182,12 @@ class FWBSData: p_fw_outboard_alpha_surface_mw: float = 0.0 """Alpha particle heat flux on outboard first wall [MW]""" + p_fw_inboard_neutron_incident_mw: float = 0.0 + """Neutron heat flux incident on inboard first wall [MW]""" + + p_fw_outboard_neutron_incident_mw: float = 0.0 + """Neutron heat flux incident on outboard first wall [MW]""" + pflux_fw_inboard_neutron_surface_average_mw: float = 0.0 """Average neutron flux on inboard first wall surface [MW/m²]""" diff --git a/process/models/fw.py b/process/models/fw.py index 0dfbf4c6d..30ae6c07d 100644 --- a/process/models/fw.py +++ b/process/models/fw.py @@ -205,6 +205,16 @@ def run(self): + self.data.fwbs.p_fw_inboard_alpha_surface_mw ) + self.data.fwbs.p_fw_inboard_neutron_incident_mw = ( + self.data.physics.p_neutron_total_mw + * self.data.blanket.f_deg_blkt_inboard_poloidal_plasma + ) + + self.data.fwbs.p_fw_outboard_neutron_incident_mw = ( + self.data.physics.p_neutron_total_mw + * self.data.blanket.f_deg_blkt_outboard_poloidal_plasma + ) + self.data.fwbs.pflux_fw_outboard_neutron_surface_average_mw = ( self.data.physics.p_neutron_total_mw * self.data.blanket.f_deg_blkt_outboard_poloidal_plasma @@ -953,17 +963,31 @@ def output_fw_surface_loads(self): self.data.physics.pflux_fw_neutron_mw, "OP ", ) + po.ovarre( + self.outfile, + "Neutron heat flux on inboard first wall [MW]", + "(p_fw_inboard_neutron_incident_mw)", + self.data.fwbs.p_fw_inboard_neutron_incident_mw, + "OP ", + ) po.ovarre( self.outfile, "Nominal mean neutron load on inboard first-wall [MW/m²]", - "(pflux_fw_inboard_neutron_mw)", + "(pflux_fw_inboard_neutron_surface_average_mw)", self.data.fwbs.pflux_fw_inboard_neutron_surface_average_mw, "OP ", ) + po.ovarre( + self.outfile, + "Neutron heat flux on outboard first wall [MW]", + "(p_fw_outboard_neutron_incident_mw)", + self.data.fwbs.p_fw_outboard_neutron_incident_mw, + "OP ", + ) po.ovarre( self.outfile, "Nominal mean neutron load on outboard first-wall [MW/m²]", - "(pflux_fw_outboard_neutron_mw)", + "(pflux_fw_outboard_neutron_surface_average_mw)", self.data.fwbs.pflux_fw_outboard_neutron_surface_average_mw, "OP ", ) From 0594a0616afcc577e1b8d9a7a1f1fa716723f4ea Mon Sep 17 00:00:00 2001 From: mn3981 Date: Tue, 9 Jun 2026 14:24:48 +0100 Subject: [PATCH 16/17] Refactor first wall radiation calculations and add unit tests for surface load calculations --- process/models/fw.py | 139 +++++++++++++++++++++++------------ tests/unit/models/test_fw.py | 75 +++++++++++++++++++ 2 files changed, 168 insertions(+), 46 deletions(-) diff --git a/process/models/fw.py b/process/models/fw.py index 30ae6c07d..c13fff19f 100644 --- a/process/models/fw.py +++ b/process/models/fw.py @@ -119,17 +119,29 @@ def run(self): self.data.physics.p_neutron_total_mw / self.data.first_wall.a_fw_total ) - self.data.fwbs.p_fw_inboard_rad_mw = ( - self.data.physics.p_plasma_rad_mw - * self.data.blanket.f_deg_blkt_inboard_poloidal_plasma + # Radiation surface heat flux on first wall (MW/m²) + # The full area is used as the radiation is assumed to be uniformly distributed + # across the first wall, so the coverage factors are not applied here. + ( + self.data.fwbs.p_fw_inboard_rad_mw, + self.data.fwbs.pflux_fw_inboard_rad_surface_average_mw, + ) = self.calculate_fw_surface_load( + p_plasma_source_mw=self.data.physics.p_plasma_rad_mw, + f_deg_blkt_poloidal_plasma=self.data.blanket.f_deg_blkt_inboard_poloidal_plasma, + a_fw_full_coverage=self.data.first_wall.a_fw_inboard_full_coverage, + f_a_fw_hcd_ports=0.0, # Inboard is toroidally continous with no ports or + # HCD, so no coverage factor applied ) - # Some power is lost to HCD and ports on the outboard wall, so this is taken - # into account with a coverage factor. - self.data.fwbs.p_fw_outboard_rad_mw = ( - self.data.physics.p_plasma_rad_mw - * self.data.blanket.f_deg_blkt_outboard_poloidal_plasma - * (1.0 - self.data.fwbs.f_a_fw_outboard_hcd) + ( + self.data.fwbs.p_fw_outboard_rad_mw, + self.data.fwbs.pflux_fw_outboard_rad_surface_average_mw, + ) = self.calculate_fw_surface_load( + p_plasma_source_mw=self.data.physics.p_plasma_rad_mw, + f_deg_blkt_poloidal_plasma=self.data.blanket.f_deg_blkt_outboard_poloidal_plasma, + a_fw_full_coverage=self.data.first_wall.a_fw_outboard_full_coverage, + f_a_fw_hcd_ports=self.data.fwbs.f_a_fw_outboard_hcd, # Coverage factor + # applied to outboard wall to account for HCD and ports ) # Radiation power incident on first wall (MW) @@ -140,21 +152,6 @@ def run(self): self.data.fwbs.p_fw_inboard_rad_mw + self.data.fwbs.p_fw_outboard_rad_mw ) - # Radiation surface heat flux on first wall (MW/m²) - # The full area is used as the radiation is assumed to be uniformly distributed - # across the first wall, so the coverage factors are not applied here. - self.data.fwbs.pflux_fw_inboard_rad_surface_average_mw = ( - self.data.physics.p_plasma_rad_mw - * self.data.blanket.f_deg_blkt_inboard_poloidal_plasma - / self.data.first_wall.a_fw_inboard_full_coverage - ) - - self.data.fwbs.pflux_fw_outboard_rad_surface_average_mw = ( - self.data.physics.p_plasma_rad_mw - * self.data.blanket.f_deg_blkt_outboard_poloidal_plasma - / self.data.first_wall.a_fw_outboard_full_coverage - ) - if self.data.physics.i_pflux_fw_neutron == 1: self.data.physics.pflux_fw_rad_mw = ( self.data.physics.ffwal @@ -174,11 +171,20 @@ def run(self): # Some is lost to HCD and ports on the outboard wall, so this is taken into # account with a coverage factor. - self.data.fwbs.p_fw_outboard_alpha_surface_mw = ( - self.data.physics.p_alpha_total_mw - * (1.0e0 - self.data.physics.f_p_alpha_plasma_deposited) - * (1.0 - self.data.fwbs.f_a_fw_outboard_hcd) + ( + self.data.fwbs.p_fw_outboard_alpha_surface_mw, + _, + ) = self.calculate_fw_surface_load( + p_plasma_source_mw=( + self.data.physics.p_alpha_total_mw + * (1.0e0 - self.data.physics.f_p_alpha_plasma_deposited) + ), + f_deg_blkt_poloidal_plasma=1.0, + a_fw_full_coverage=self.data.first_wall.a_fw_outboard_full_coverage, + f_a_fw_hcd_ports=self.data.fwbs.f_a_fw_outboard_hcd, # Coverage factor + # applied to outboard wall to account for HCD and ports ) + # Will assume that all alpha power reaching the first wall is deposited on the # outboard side. self.data.fwbs.p_fw_inboard_alpha_surface_mw = 0.0 @@ -205,26 +211,26 @@ def run(self): + self.data.fwbs.p_fw_inboard_alpha_surface_mw ) - self.data.fwbs.p_fw_inboard_neutron_incident_mw = ( - self.data.physics.p_neutron_total_mw - * self.data.blanket.f_deg_blkt_inboard_poloidal_plasma - ) - - self.data.fwbs.p_fw_outboard_neutron_incident_mw = ( - self.data.physics.p_neutron_total_mw - * self.data.blanket.f_deg_blkt_outboard_poloidal_plasma - ) - - self.data.fwbs.pflux_fw_outboard_neutron_surface_average_mw = ( - self.data.physics.p_neutron_total_mw - * self.data.blanket.f_deg_blkt_outboard_poloidal_plasma - / self.data.first_wall.a_fw_outboard_full_coverage + ( + self.data.fwbs.p_fw_inboard_neutron_incident_mw, + self.data.fwbs.pflux_fw_inboard_neutron_surface_average_mw, + ) = self.calculate_fw_surface_load( + p_plasma_source_mw=self.data.physics.p_neutron_total_mw, + f_deg_blkt_poloidal_plasma=self.data.blanket.f_deg_blkt_inboard_poloidal_plasma, + a_fw_full_coverage=self.data.first_wall.a_fw_inboard_full_coverage, + f_a_fw_hcd_ports=0.0, # Inboard is toroidally continous with no ports + # or HCD, so no coverage factor applied ) - self.data.fwbs.pflux_fw_inboard_neutron_surface_average_mw = ( - self.data.physics.p_neutron_total_mw - * self.data.blanket.f_deg_blkt_inboard_poloidal_plasma - / self.data.first_wall.a_fw_inboard_full_coverage + ( + self.data.fwbs.p_fw_outboard_neutron_incident_mw, + self.data.fwbs.pflux_fw_outboard_neutron_surface_average_mw, + ) = self.calculate_fw_surface_load( + p_plasma_source_mw=self.data.physics.p_neutron_total_mw, + f_deg_blkt_poloidal_plasma=self.data.blanket.f_deg_blkt_outboard_poloidal_plasma, + a_fw_full_coverage=self.data.first_wall.a_fw_outboard_full_coverage, + f_a_fw_hcd_ports=self.data.fwbs.f_a_fw_outboard_hcd, # Coverage factor + # applied to outboard wall to account for HCD and ports ) @staticmethod @@ -732,6 +738,47 @@ def fw_temp( mflow_fw_coolant, ) + @staticmethod + def calculate_fw_surface_load( + p_plasma_source_mw: float, + f_deg_blkt_poloidal_plasma: float, + a_fw_full_coverage: float, + f_a_fw_hcd_ports: float = 0.0, + ) -> tuple[float, float]: + """Calculate the surface load on the first wall due to some incident power + + Parameters + ---------- + p_plasma_source_mw: + Total plasma power source, can be neutrons or other forms of energy etc [MW] + f_deg_blkt_poloidal_plasma: + Fraction of the plasma poloidal circumference covered by the first wall. + a_fw_full_coverage: + Area of the first wall with full coverage [m²]. + f_a_fw_hcd_ports: + Fraction of the first wall area occupied by HCD and ports. + This is a loss term that reduces the effective area for power deposition on + the first wall. Default is 0.0 (no loss). + + Returns + ------- + tuple + Power incident on the first wall [MW] and the surface heat flux on the + first wall [MW/m²]. + + Notes + ----- + We use the full coverage area here as the surface is toroidally continous and + the radiation is assumed to be uniformly distributed across the first wall, + so the coverage factors are not applied here. + """ + p_fw_incident_mw = ( + p_plasma_source_mw * f_deg_blkt_poloidal_plasma * (1.0 - f_a_fw_hcd_ports) + ) + pflux_fw_inboard_surface_average_mw = p_fw_incident_mw / a_fw_full_coverage + + return p_fw_incident_mw, pflux_fw_inboard_surface_average_mw + @staticmethod def calculate_total_fw_channels( a_fw_inboard: float, diff --git a/tests/unit/models/test_fw.py b/tests/unit/models/test_fw.py index 0d609f00a..58c2779af 100644 --- a/tests/unit/models/test_fw.py +++ b/tests/unit/models/test_fw.py @@ -161,3 +161,78 @@ def test_fw_temp(fwtempparam, monkeypatch, fw): assert rhofmean == pytest.approx(fwtempparam.expected_rhofmean, rel=1e-4) assert massrate == pytest.approx(fwtempparam.expected_massrate, rel=1e-4) + + +class CalculateFwSurfaceLoadParam(NamedTuple): + p_plasma_source_mw: float + f_deg_blkt_poloidal_plasma: float + a_fw_full_coverage: float + f_a_fw_hcd_ports: float = 0.0 + expected_p_fw_incident_mw: float = None + expected_pflux_fw_surface_mw_m2: float = None + label: str = None + + +@pytest.mark.parametrize( + "calculatefwsurfaceloadparam", + [ + CalculateFwSurfaceLoadParam( + p_plasma_source_mw=1000.0, + f_deg_blkt_poloidal_plasma=0.8, + a_fw_full_coverage=100.0, + f_a_fw_hcd_ports=0.0, + expected_p_fw_incident_mw=800.0, + expected_pflux_fw_surface_mw_m2=8.0, + label="No HCD ports loss", + ), + CalculateFwSurfaceLoadParam( + p_plasma_source_mw=1000.0, + f_deg_blkt_poloidal_plasma=0.8, + a_fw_full_coverage=100.0, + f_a_fw_hcd_ports=0.1, + expected_p_fw_incident_mw=720.0, + expected_pflux_fw_surface_mw_m2=7.2, + label="With HCD ports loss", + ), + CalculateFwSurfaceLoadParam( + p_plasma_source_mw=500.0, + f_deg_blkt_poloidal_plasma=1.0, + a_fw_full_coverage=50.0, + f_a_fw_hcd_ports=0.2, + expected_p_fw_incident_mw=400.0, + expected_pflux_fw_surface_mw_m2=8.0, + label="Full poloidal coverage with ports loss", + ), + CalculateFwSurfaceLoadParam( + p_plasma_source_mw=2000.0, + f_deg_blkt_poloidal_plasma=0.5, + a_fw_full_coverage=200.0, + f_a_fw_hcd_ports=0.0, + expected_p_fw_incident_mw=1000.0, + expected_pflux_fw_surface_mw_m2=5.0, + label="Lower poloidal coverage", + ), + ], +) +def test_calculate_fw_surface_load(calculatefwsurfaceloadparam, fw): + """ + Unit test for calculate_fw_surface_load. + + :param calculatefwsurfaceloadparam: the data used to assert in this test. + :type calculatefwsurfaceloadparam: CalculateFwSurfaceLoadParam + """ + + p_fw_incident_mw, pflux_fw_surface_mw_m2 = fw.calculate_fw_surface_load( + p_plasma_source_mw=calculatefwsurfaceloadparam.p_plasma_source_mw, + f_deg_blkt_poloidal_plasma=calculatefwsurfaceloadparam.f_deg_blkt_poloidal_plasma, + a_fw_full_coverage=calculatefwsurfaceloadparam.a_fw_full_coverage, + f_a_fw_hcd_ports=calculatefwsurfaceloadparam.f_a_fw_hcd_ports, + ) + + assert p_fw_incident_mw == pytest.approx( + calculatefwsurfaceloadparam.expected_p_fw_incident_mw + ) + + assert pflux_fw_surface_mw_m2 == pytest.approx( + calculatefwsurfaceloadparam.expected_pflux_fw_surface_mw_m2 + ) From 232c993ee6baf2abdc40e3113a3e30b992fcfc8c Mon Sep 17 00:00:00 2001 From: mn3981 Date: Wed, 10 Jun 2026 09:16:19 +0100 Subject: [PATCH 17/17] Add calculation method for outboard surface heat loads and corresponding unit tests --- process/models/fw.py | 48 +++++++++++++++++++++++--- tests/unit/models/test_fw.py | 66 ++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 5 deletions(-) diff --git a/process/models/fw.py b/process/models/fw.py index c13fff19f..fe4556b9f 100644 --- a/process/models/fw.py +++ b/process/models/fw.py @@ -199,11 +199,11 @@ def run(self): # and shine through. # Some power is lost to HCD and ports on the outboard wall, so this is # taken into account with a coverage factor. - self.data.fwbs.p_fw_outboard_surface_heat_mw = ( - self.data.fwbs.p_fw_outboard_rad_mw - + self.data.current_drive.p_beam_orbit_loss_mw - + self.data.fwbs.p_fw_outboard_alpha_surface_mw - + self.data.current_drive.p_beam_shine_through_mw + self.data.fwbs.p_fw_outboard_surface_heat_mw = self.calculate_fw_outboard_surface_loads( + p_fw_outboard_rad_mw=self.data.fwbs.p_fw_outboard_rad_mw, + p_beam_orbit_loss_mw=self.data.current_drive.p_beam_orbit_loss_mw, + p_fw_outboard_alpha_surface_mw=self.data.fwbs.p_fw_outboard_alpha_surface_mw, + p_beam_shine_through_mw=self.data.current_drive.p_beam_shine_through_mw, ) self.data.fwbs.p_fw_inboard_surface_heat_mw = ( @@ -1038,3 +1038,41 @@ def output_fw_surface_loads(self): self.data.fwbs.pflux_fw_outboard_neutron_surface_average_mw, "OP ", ) + + @staticmethod + def calculate_fw_outboard_surface_loads( + p_fw_outboard_rad_mw: float, + p_beam_orbit_loss_mw: float, + p_fw_outboard_alpha_surface_mw: float, + p_beam_shine_through_mw: float, + ) -> float: + """Calculate the surface loads on the first wall. + + Parameters + ---------- + p_fw_outboard_rad_mw: + Radiation heat flux on outboard first wall [MW]. + p_beam_orbit_loss_mw: + Beam orbit loss power [MW]. + p_fw_outboard_alpha_surface_mw: + Alpha particle heat flux on outboard first wall [MW]. + p_beam_shine_through_mw: + Beam shine through power [MW]. + + Returns + ------- + p_fw_outboard_surface_heat_mw: + Total surface heat flux on the outboard first wall [MW]. + + """ + # Surface heat flux on first wall (MW) + # All of the fast particle losses go to the outer wall, as do all beam losses + # and shine through. + # Some power is lost to HCD and ports on the outboard wall, so this is + # taken into account with a coverage factor. + return ( + p_fw_outboard_rad_mw + + p_beam_orbit_loss_mw + + p_fw_outboard_alpha_surface_mw + + p_beam_shine_through_mw + ) diff --git a/tests/unit/models/test_fw.py b/tests/unit/models/test_fw.py index 58c2779af..f20ee6e8a 100644 --- a/tests/unit/models/test_fw.py +++ b/tests/unit/models/test_fw.py @@ -236,3 +236,69 @@ def test_calculate_fw_surface_load(calculatefwsurfaceloadparam, fw): assert pflux_fw_surface_mw_m2 == pytest.approx( calculatefwsurfaceloadparam.expected_pflux_fw_surface_mw_m2 ) + + +class CalculateFwOutboardSurfaceLoadsParam(NamedTuple): + p_fw_outboard_rad_mw: float + p_beam_orbit_loss_mw: float + p_fw_outboard_alpha_surface_mw: float + p_beam_shine_through_mw: float + expected_p_fw_outboard_surface_heat_mw: float + label: str = None + + +@pytest.mark.parametrize( + "calculatefwoutboardsurfaceloadsparam", + [ + CalculateFwOutboardSurfaceLoadsParam( + p_fw_outboard_rad_mw=100.0, + p_beam_orbit_loss_mw=50.0, + p_fw_outboard_alpha_surface_mw=75.0, + p_beam_shine_through_mw=25.0, + expected_p_fw_outboard_surface_heat_mw=250.0, + label="Equal power contributions", + ), + CalculateFwOutboardSurfaceLoadsParam( + p_fw_outboard_rad_mw=500.0, + p_beam_orbit_loss_mw=100.0, + p_fw_outboard_alpha_surface_mw=150.0, + p_beam_shine_through_mw=50.0, + expected_p_fw_outboard_surface_heat_mw=800.0, + label="Dominant radiation load", + ), + CalculateFwOutboardSurfaceLoadsParam( + p_fw_outboard_rad_mw=0.0, + p_beam_orbit_loss_mw=0.0, + p_fw_outboard_alpha_surface_mw=0.0, + p_beam_shine_through_mw=0.0, + expected_p_fw_outboard_surface_heat_mw=0.0, + label="Zero power contributions", + ), + CalculateFwOutboardSurfaceLoadsParam( + p_fw_outboard_rad_mw=200.0, + p_beam_orbit_loss_mw=75.0, + p_fw_outboard_alpha_surface_mw=100.0, + p_beam_shine_through_mw=25.0, + expected_p_fw_outboard_surface_heat_mw=400.0, + label="Mixed power contributions", + ), + ], +) +def test_calculate_fw_outboard_surface_loads(calculatefwoutboardsurfaceloadsparam, fw): + """ + Unit test for calculate_fw_outboard_surface_loads. + + :param calculatefwoutboardsurfaceloadsparam: the data used to assert in this test. + :type calculatefwoutboardsurfaceloadsparam: CalculateFwOutboardSurfaceLoadsParam + """ + + p_fw_outboard_surface_heat_mw = fw.calculate_fw_outboard_surface_loads( + p_fw_outboard_rad_mw=calculatefwoutboardsurfaceloadsparam.p_fw_outboard_rad_mw, + p_beam_orbit_loss_mw=calculatefwoutboardsurfaceloadsparam.p_beam_orbit_loss_mw, + p_fw_outboard_alpha_surface_mw=calculatefwoutboardsurfaceloadsparam.p_fw_outboard_alpha_surface_mw, + p_beam_shine_through_mw=calculatefwoutboardsurfaceloadsparam.p_beam_shine_through_mw, + ) + + assert p_fw_outboard_surface_heat_mw == pytest.approx( + calculatefwoutboardsurfaceloadsparam.expected_p_fw_outboard_surface_heat_mw + )