diff --git a/documentation/source/development/standards.md b/documentation/source/development/standards.md index 8479267753..beec712ac0 100644 --- a/documentation/source/development/standards.md +++ b/documentation/source/development/standards.md @@ -259,6 +259,14 @@ This should be used for units of $\text{kg} \cdot \text{m}^{-2}\text{s}^{-1}$ --------------------- +##### Specific Heat Capacities + +- Specific heat capacities for materials $[\text{J/kg/K}]$ should start with the `heatcap_` prefix +- Specific heat capacities at constant volume should start with the `heatcap_vol_` pefix +- Specific heat capacities at constant pressure should start with the `heatcap_pres_` pefix + +--------------------- + ##### Pressures - Pressures should start with the `pres_` prefix diff --git a/documentation/source/eng-models/blanket_overview.md b/documentation/source/eng-models/blanket_overview.md index 66a84b15f7..97bd9dc6dc 100644 --- a/documentation/source/eng-models/blanket_overview.md +++ b/documentation/source/eng-models/blanket_overview.md @@ -5,100 +5,3 @@ -------------------- -### Coolant mechanical pumping power | `coolant_pumping_power()` - -To calculate the coolant pumping power we use the change in enthalpies of the coolant as it goes through the pump. -**We assume the pump is isentropic so the entropy change of the coolant is 0**. - -The mechanical pumping power is defined as: - -$$ -P = \frac{\frac{\dot{m} \times \left(H_{\text{out}}-H_{\text{in}}\right)}{\eta}}{\left(1-fp\right)} -$$ - -where $\dot{m}$ is the coolant mass flow rate, $H$ is the coolant enthalpy, $\eta$ is the isentropic efficiency of the pump and $\gamma$ is the adiabatic index of the coolant. - -$$ -fp = \frac{T_{\text{pump,out}}\left(\frac{P_{\text{pump,out}}}{P_{\text{pump,in}}}\right)^{-\frac{\gamma -1}{\gamma}}}{\eta \left(T_{\text{pump,in}}-T_{\text{pump,out}}\right)} -$$ - ------------------- - -### Coolant pressure drop | `coolant_friction_pressure_drop()` - -The pressure drop in the coolant is given by the [Darcy-Weisbach Equation](https://en.wikipedia.org/wiki/Darcy%E2%80%93Weisbach_equation) - -For a cylindrical pipe of uniform diameter the pressure loss due to viscous effects can be characterized by: - -$$ -\Delta P = L\left[f_{\text{D}}\frac{\rho}{2}\frac{\langle v \rangle^2}{D_{\text{H}}}\right] -$$ - -where $L$ is the pipe length, $f_{\text{D}}$ is the [Darcy friction factor](https://en.wikipedia.org/wiki/Darcy_friction_factor_formulae), $\rho$ is the coolant density, $\langle v \rangle$ is the mean flow coolant velocity and $D_{\text{H}}$ is the hydraulic diameter or the pipe diameter in this case. - -To find the Darcy friction factor we need to know the Reynolds number given by: - -$$ -\text{Re} = \frac{\rho v L}{\mu} -$$ - -here $L$ is the characteristic length which we set to be the pipe diameter and $\mu$ is the coolant dynamic viscosity. - -Using the Reynolds number we calculate the Darcy friction factor using the Haaland approximation calculated by [`darcy_friction_haaland()`](../eng-models/generic_methods/pumping.md#pumping-coolant-friction--darcy_friction_haaland). - -For the radius of the pipe bend we assume it to be 3 times the radius of the coolant channel. - -The elbow coefficients for the 90 and 180 degree bends $\left(f_{\text{90,elbow}}, f_{\text{180,elbow}}\right)$ are calculated via [`elbow_coeff()`](#pipe-bend-elbow-coefficient--elbow_coeff). - -The pressure drop for the straights along the entire pipe length is the same as above: - -$$ -\Delta P = L\left[f_{\text{D}}\frac{\rho}{2}\frac{\langle v \rangle^2}{D_{\text{H}}}\right] -$$ - -where we define $\frac{f_{\text{D}}L}{D_{\text{H}}}$ as our straight section coefficient. - -The pressure drop for the 90 and 180 degree bends are: - -$$ -\Delta P = N_{\text{90}} \left[f_{\text{90,elbow}} \frac{\rho \langle v \rangle^2}{2}\right] -$$ - -$$ -\Delta P = N_{\text{180}} \left[f_{\text{180,elbow}} \frac{\rho \langle v \rangle^2}{2}\right] -$$ - -where $N_{\text{90}}$ and $N_{\text{180}}$ are the number of 90 and 180 degree bends in the system. - -The total returned pressure drop is simply: - -$$ -\Delta P = L\left[f_{\text{D}}\frac{\rho}{2}\frac{\langle v \rangle^2}{D_{\text{H}}}\right] + N_{\text{90}} \left[f_{\text{90,elbow}} \frac{\rho \langle v \rangle^2}{2}\right] + N_{\text{180}} \left[f_{\text{180,elbow}} \frac{\rho \langle v \rangle^2}{2}\right] -$$ - -------------------- - -### Pipe bend elbow coefficient | `elbow_coeff()` - -This function calculates the elbow bend coefficients for pressure drop calculations. - -$$ -a = 1.0 \quad \text{if} \ \theta = 90^{\circ} \\ -a = 0.9 \times \sin{\left(\frac{\theta \pi}{180^{\circ}}\right)} \quad \text{if} \ \theta < 70^{\circ} \\ -a = 0.7 + 0.35 \times \sin{\left(\frac{\theta}{90^{\circ}} \times \frac{\pi}{180^{\circ}}\right)} \quad \text{if} \ \theta > 90^{\circ} \\ -$$ - -where $\theta$ is the angle of the pipe bend. - -$$ -b = \frac{0.21}{\sqrt{\frac{R_{\text{elbow}}}{D_{\text{pipe}}}}}\quad \text{if} \ \frac{R_{\text{elbow}}}{D_{\text{pipe}}} \ge 1 \\ -b = \frac{0.21}{\left(\frac{R_{\text{elbow}}}{D_{\text{pipe}}}\right)^{2.5}}\quad \text{if} \ \frac{R_{\text{elbow}}}{D_{\text{pipe}}} \le 1 \\ -\text{else} \quad b =0.21 -$$ - -The elbow coefficient is given by: - -$$ -ab + \left( f_{\text{D}} \times \frac{R_{\text{elbow}}}{D_{\text{pipe}}}\right) \times \theta \times \left(\frac{\pi}{180^{\circ}}\right) -$$ - diff --git a/documentation/source/eng-models/generic_methods/pumping.md b/documentation/source/eng-models/generic_methods/pumping.md index a4822ba00b..829f36bf8e 100644 --- a/documentation/source/eng-models/generic_methods/pumping.md +++ b/documentation/source/eng-models/generic_methods/pumping.md @@ -1,5 +1,82 @@ # Pumping Methods + +## Coolant mechanical pumping power | `coolant_pumping_power()` + +To calculate the coolant pumping power we use the change in enthalpies of the coolant as it goes through the pump. +**We assume the pump is isentropic so the entropy change of the coolant is 0**. + +The mechanical pumping power is defined as: + +$$ +P = \frac{\frac{\dot{m} \times \left(H_{\text{out}}-H_{\text{in}}\right)}{\eta}}{\left(1-f_p\right)} +$$ + +where $\dot{m}$ is the coolant mass flow rate, $H$ is the coolant enthalpy, $\eta$ is the isentropic efficiency of the pump and $\gamma$ is the adiabatic index of the coolant. + +$f_p$ is a seocondary correction accounting for the fact that some of the pump's mechanical work reheats the coolant, and that reheat is already implicitly present in the prescribed $\left(T_{\text{pump,in}}-T_{\text{pump,out}}\right)$ used elsewhere in the plant energy balance, without this correction that portion of the pump work would be double counted. The term uses the classical isentropic ideal-gas relation $T_2/T_1 = (P_2/P_1)^{(\gamma-1)/\gamma}$ + +$$ +f_p = \frac{T_{\text{pump,out}}\left(\frac{P_{\text{pump,out}}}{P_{\text{pump,in}}}\right)^{-\frac{\gamma -1}{\gamma}}}{\eta \left(T_{\text{pump,in}}-T_{\text{pump,out}}\right)} +$$ + +------------------ + +## Coolant pressure drop | `coolant_friction_pressure_drop()` + +The pressure drop in the coolant is given by the [Darcy-Weisbach Equation](https://en.wikipedia.org/wiki/Darcy%E2%80%93Weisbach_equation) + +For a cylindrical pipe of uniform diameter the pressure loss due to viscous effects can be characterized by: + +$$ +\Delta P = L\left[f_{\text{D}}\frac{\rho}{2}\frac{\langle v \rangle^2}{D_{\text{H}}}\right] +$$ + +where $L$ is the pipe length, $f_{\text{D}}$ is the [Darcy friction factor](https://en.wikipedia.org/wiki/Darcy_friction_factor_formulae), $\rho$ is the coolant density, $\langle v \rangle$ is the mean flow coolant velocity and $D_{\text{H}}$ is the hydraulic diameter or the pipe diameter in this case. + +To find the Darcy friction factor we need to know the Reynolds number given by: + +$$ +\text{Re} = \frac{\rho v L}{\mu} +$$ + +here $L$ is the characteristic length which we set to be the pipe diameter and $\mu$ is the coolant dynamic viscosity. + +Using the Reynolds number we calculate the Darcy friction factor using the Haaland approximation calculated by [`darcy_friction_haaland()`](../eng-models/generic_methods/pumping.md#pumping-coolant-friction--darcy_friction_haaland). + +For the radius of the pipe bend we assume it to be 3 times the radius of the coolant channel. + +The elbow coefficients for the 90 and 180 degree bends $\left(f_{\text{90,elbow}}, f_{\text{180,elbow}}\right)$ are calculated via [`elbow_coeff()`](#pipe-bend-elbow-coefficient--elbow_coeff). + +The pressure drop for the straights along the entire pipe length is the same as above: + +$$ +\Delta P = L\left[f_{\text{D}}\frac{\rho}{2}\frac{\langle v \rangle^2}{D_{\text{H}}}\right] +$$ + +where we define $\frac{f_{\text{D}}L}{D_{\text{H}}}$ as our straight section coefficient. + +The pressure drop for the 90 and 180 degree bends are: + +$$ +\Delta P = N_{\text{90}} \left[f_{\text{90,elbow}} \frac{\rho \langle v \rangle^2}{2}\right] +$$ + +$$ +\Delta P = N_{\text{180}} \left[f_{\text{180,elbow}} \frac{\rho \langle v \rangle^2}{2}\right] +$$ + +where $N_{\text{90}}$ and $N_{\text{180}}$ are the number of 90 and 180 degree bends in the system. + +The total returned pressure drop is simply: + +$$ +\Delta P = L\left[f_{\text{D}}\frac{\rho}{2}\frac{\langle v \rangle^2}{D_{\text{H}}}\right] + N_{\text{90}} \left[f_{\text{90,elbow}} \frac{\rho \langle v \rangle^2}{2}\right] + N_{\text{180}} \left[f_{\text{180,elbow}} \frac{\rho \langle v \rangle^2}{2}\right] +$$ + +------------------- + + ## Pumping coolant friction | `darcy_friction_haaland()` The pressure drop is based on the Darcy friction factor, using the [Haaland equation](https://en.wikipedia.org/wiki/Darcy_friction_factor_formulae#Haaland_equation), an approximation to the implicit Colebrook–White equation. @@ -61,4 +138,62 @@ where $\rho$ is the coolant density and $\mu$ is the coolant viscosity. $$ h = \frac{\mathrm{Nu_D}k}{2r_{\text{channel}}} - $$ \ No newline at end of file + $$ + +------------------------- + +## Pipe bend elbow coefficient | `elbow_coeff()` + +This function calculates the elbow bend coefficients for pressure drop calculations. + +$$ +a = 1.0 \quad \text{if} \ \theta = 90^{\circ} \\ +a = 0.9 \times \sin{\left(\frac{\theta \pi}{180^{\circ}}\right)} \quad \text{if} \ \theta < 70^{\circ} \\ +a = 0.7 + 0.35 \times \sin{\left(\frac{\theta}{90^{\circ}} \times \frac{\pi}{180^{\circ}}\right)} \quad \text{if} \ \theta > 90^{\circ} \\ +$$ + +where $\theta$ is the angle of the pipe bend. + +$$ +b = \frac{0.21}{\sqrt{\frac{R_{\text{elbow}}}{D_{\text{pipe}}}}}\quad \text{if} \ \frac{R_{\text{elbow}}}{D_{\text{pipe}}} \ge 1 \\ +b = \frac{0.21}{\left(\frac{R_{\text{elbow}}}{D_{\text{pipe}}}\right)^{2.5}}\quad \text{if} \ \frac{R_{\text{elbow}}}{D_{\text{pipe}}} \le 1 \\ +\text{else} \quad b =0.21 +$$ + +The elbow coefficient is given by: + +$$ +ab + \left( f_{\text{D}} \times \frac{R_{\text{elbow}}}{D_{\text{pipe}}}\right) \times \theta \times \left(\frac{\pi}{180^{\circ}}\right) +$$ + +-------------- + +## Required mass flow rate | `calculate_required_mass_flow_rate()` + +The required mass flow rate of a coolant is given simply by the fundamental heat transfer equation: + +$$ +\dot{m} = \frac{P}{c_{\text{p}}(T)\times \Delta T} +$$ + +where $\dot{m}$ is the required mass flow rate in, $P$ is the heating power to be removed, $c_{\text{p}}$ is the coolant specific heat capacity for constant pressure and $\Delta T$ is the temperature change in the coolant. + +!!! note "Variation specific heat capacity" + + The heat capacity itself is a function of temperature. Therefore it is common to use the heat capacity value at the simple average between the initial and final temperature. + This however assumes a linear relationship. Ideally the equation should be solves as: + + $$ + \dot{m} = \frac{P}{\int_{T_{\text{in}}}^{T_{\text{in}}}c_{\text{p}}(T) dT} + $$ + + +!!! info "Choice of specific heat capacity" + + For pumping, the specific heat capacity for constant pressure $(c_{\text{p}})$ is used because + cooling loops are open, steady-flow systems: coolant flows continuously through pipes, heat + exchangers and pumps at a system pressure that is actively maintained by the pump's operating + point and any pressure-control components (e.g. pressurisers, control valves), rather than + varying freely with the coolant's volume. + + You would only use the specific heat capacity for constant volume $(c_{\text{v}})$ if the coolant was completely sealed inside a rigid, unyielding container with zero flow, where heating it would cause the pressure to spike but the volume to stay exactly the same. \ No newline at end of file diff --git a/process/core/io/plot/summary.py b/process/core/io/plot/summary.py index 6d1379d3d5..6471685831 100644 --- a/process/core/io/plot/summary.py +++ b/process/core/io/plot/summary.py @@ -91,6 +91,7 @@ PlasmaProfileShapeType, calculate_profile_shell_contributions, ) +from process.models.power import PumpingPowerModelTypes from process.models.pulse import PulseTimings from process.models.superconductors import SuperconductorModel from process.models.tfcoil.base import ( @@ -16813,6 +16814,55 @@ def plot_cumulative_plasma_thermal_energy_profiles(axis, m_file: MFile, scan: in ) +def plot_blanket_coolant_properties(fig: plt.Figure, m_file: MFile, scan: int): + """Combined plot of blanket coolant channel structure and properties.""" + for side, x_position in (("inboard", 0.1), ("outboard", 0.5)): + + def get(variable: str): + return m_file.get(variable, scan=scan) + + text = ( + f"$\\mathbf{{{side.capitalize()} \\ blanket:}}$\n \n" + f"Radius of blanket channel: {m_file.get('radius_blkt_channel', scan=scan):.4f} m\n" + f"Channel roughness ($\\epsilon$): {m_file.get('roughness_fw_channel', scan=scan):.4e} m\n\n" + f"Radial coolant channel length: {get(f'len_blkt_{side}_coolant_channel_radial'):.4f} m\n" + f"Poloidal coolant channel length: {get(f'len_blkt_{side}_segment_poloidal'):.4f} m\n" + f"Number of radial channels: {get(f'n_blkt_{side}_module_coolant_sections_radial')}\n" + f"Number of poloidal channels: {get(f'n_blkt_{side}_module_coolant_sections_poloidal')}\n" + f"Total length of coolant channel straight sections: {get(f'len_blkt_{side}_channel_total'):.4f} m\n\n" + f"Pressure drop for straight sections: {get(f'dpres_blkt_{side}_coolant_channel_straight_total'):,.2f} Pa\n" + f"Pressure drop for 90° bends: {get(f'dpres_blkt_{side}_coolant_channel_90_bend'):,.2f} Pa\n" + f"Total pressure drop for 90° bends: {get(f'dpres_blkt_{side}_coolant_channel_90_bends_total'):,.2f} Pa\n" + f"Pressure drop for 180° bends: {get(f'dpres_blkt_{side}_coolant_channel_180_bend'):,.2f} Pa\n" + f"Total pressure drop for 180° bends: {get(f'dpres_blkt_{side}_coolant_channel_180_bends_total'):,.2f} Pa\n" + f"Total pressure drop for all bends: {get(f'dpres_blkt_{side}_bends_total'):,.2f} Pa\n\n" + f"Reynolds number ($Re$): {get(f'reynolds_blkt_{side}_coolant'):,.4f}\n" + f"Darcy Friction factor ($f$): {get(f'darcy_frict_blkt_{side}_coolant'):.4f}\n\n" + f"Friction drop coefficient for straight sections: {get(f'f_straight_blkt_{side}_coolant'):.4f}\n" + f"Friction drop coefficient for 90° bends: {get(f'f_elbow_blkt_{side}_90_bend'):.4f}\n" + f"Friction drop coefficient for 180° bends: {get(f'f_elbow_blkt_{side}_180_bend'):.4f}\n\n" + f"Total coolant mass flow rate: {get(f'mflow_blkt_{side}_coolant'):.4f} kg/s\n" + f"Coolant mass flow rate in single channel: {get(f'mflow_blkt_{side}_coolant_channel'):.4f} kg/s\n" + f"Coolant velocity in single channel: {get(f'vel_blkt_{side}_coolant'):.4f} m/s" + ) + + fig.text( + x_position, + 0.5, + text, + fontsize=9, + verticalalignment="top", + horizontalalignment="left", + transform=fig.transFigure, + bbox={ + "boxstyle": "round", + "facecolor": "wheat", + "alpha": 1.0, + "linewidth": 2, + }, + ) + + def main_plot( m_file: MFile, scan: int, @@ -17449,17 +17499,18 @@ def _add_page(name: str | None = None): ) plot_fw_90_deg_pipe_bend(pages["fw_td_cross_section"].add_subplot(337), m_file, scan) - plot_blkt_pipe_bends(_add_page("blkt_pipe_bends"), m_file, scan) - ax_blanket = pages["blkt_pipe_bends"].add_subplot(122, aspect="equal") + ax_blanket = _add_page("blkt_structure").add_subplot(122, aspect="equal") plot_blkt_structure( - ax_blanket, - pages["blkt_pipe_bends"], - m_file, - scan, - radial_build, - colour_scheme, + ax_blanket, pages["blkt_structure"], m_file, scan, radial_build, colour_scheme ) + plot_blkt_pipe_bends(_add_page("blkt_cooling"), m_file, scan) + if ( + m_file.get("i_p_coolant_pumping", scan=scan) + == PumpingPowerModelTypes.CALCULATE_PRESSURE_DROP + ): + plot_blanket_coolant_properties(pages["blkt_cooling"], m_file, scan) + plot_main_power_flow( _add_page("main_power_flow").add_subplot(111, aspect="equal"), m_file, @@ -17578,8 +17629,6 @@ def add_page_footer( color="dimgray", ) - # create main plot - # Increase range when adding new page # run main_plot mfile_obj = MFile(mfile) if mfile else MFile("MFILE.DAT") run_label = f"{mfile_obj.get('fileprefix', scan=-1)} | scan {scan or -1} | {mfile_obj.get('date', scan=-1)} {mfile_obj.get('time', scan=-1)} | {mfile_obj.get('tagno', scan=-1)} | Branch: {mfile_obj.get('branch_name', scan=-1)} " diff --git a/process/data_structure/blanket_variables.py b/process/data_structure/blanket_variables.py index 05d2b9c7cf..ee28b9b8b7 100644 --- a/process/data_structure/blanket_variables.py +++ b/process/data_structure/blanket_variables.py @@ -149,10 +149,10 @@ class BlanketData: n_blkt_outboard_channels: float = 0.0 """Outboard total number of blanket coolant pipes""" - mfblktpi: float = 0.0 + mflow_blkt_inboard_coolant_channel: float = 0.0 """Inboard mass flow rate per coolant pipe (kg/s)""" - mfblktpo: float = 0.0 + mflow_blkt_outboard_coolant_channel: float = 0.0 """Outboard mass flow rate per coolant pipe (kg/s)""" vel_blkt_inboard_coolant: float = 0.0 @@ -200,5 +200,77 @@ class BlanketData: f_deg_blkt_inboard_poloidal_plasma: float = 0.0 """Fraction of inboard blanket poloidal angle subtended by plasma (degrees)""" + reynolds_blkt_inboard_coolant: float = 0.0 + """Inboard blanket coolant Reynolds number""" + + reynolds_blkt_outboard_coolant: float = 0.0 + """Outboard blanket coolant Reynolds number""" + + darcy_frict_blkt_inboard_coolant: float = 0.0 + """Inboard blanket coolant Darcy friction factor""" + + darcy_frict_blkt_outboard_coolant: float = 0.0 + """Outboard blanket coolant Darcy friction factor""" + + f_elbow_blkt_inboard_90_bend: float = 0.0 + """Inboard blanket coolant 90 degree bend loss coefficient""" + + f_elbow_blkt_outboard_90_bend: float = 0.0 + """Outboard blanket coolant 90 degree bend loss coefficient""" + + f_elbow_blkt_inboard_180_bend: float = 0.0 + """Inboard blanket coolant 180 degree bend loss coefficient""" + + f_elbow_blkt_outboard_180_bend: float = 0.0 + """Outboard blanket coolant 180 degree bend loss coefficient""" + + f_straight_blkt_inboard_coolant: float = 0.0 + """Inboard blanket coolant straight length loss coefficient""" + + f_straight_blkt_outboard_coolant: float = 0.0 + """Outboard blanket coolant straight length loss coefficient""" + + dpres_blkt_inboard_coolant_channel_straight_total: float = 0.0 + """Total pressure drop in inboard blanket coolant channel straight sections (Pa)""" + + dpres_blkt_outboard_coolant_channel_straight_total: float = 0.0 + """Total pressure drop in outboard blanket coolant channel straight sections (Pa)""" + + dpres_blkt_inboard_coolant_channel_90_bends_total: float = 0.0 + """Total pressure drop in inboard blanket coolant channel 90 degree bends (Pa)""" + + dpres_blkt_outboard_coolant_channel_90_bends_total: float = 0.0 + """Total pressure drop in outboard blanket coolant channel 90 degree bends (Pa)""" + + dpres_blkt_inboard_coolant_channel_180_bends_total: float = 0.0 + """Total pressure drop in inboard blanket coolant channel 180 degree bends (Pa)""" + + dpres_blkt_outboard_coolant_channel_180_bends_total: float = 0.0 + """Total pressure drop in outboard blanket coolant channel 180 degree bends (Pa)""" + + dpres_blkt_inboard_coolant_channel_90_bend: float = 0.0 + """Pressure drop in inboard blanket coolant channel 90 degree bend (Pa)""" + + dpres_blkt_outboard_coolant_channel_90_bend: float = 0.0 + """Pressure drop in outboard blanket coolant channel 90 degree bend (Pa)""" + + dpres_blkt_inboard_coolant_channel_180_bend: float = 0.0 + """Pressure drop in inboard blanket coolant channel 180 degree bend (Pa)""" + + dpres_blkt_outboard_coolant_channel_180_bend: float = 0.0 + """Pressure drop in outboard blanket coolant channel 180 degree bend (Pa)""" + + dpres_blkt_inboard_bends_total: float = 0.0 + """Total pressure drop in inboard blanket coolant channel bends (Pa)""" + + dpres_blkt_outboard_bends_total: float = 0.0 + """Total pressure drop in outboard blanket coolant channel bends (Pa)""" + + len_blkt_inboard_coolant_channel_straight_total: float = 0.0 + """Total length of inboard blanket coolant channel straight sections (m)""" + + len_blkt_outboard_coolant_channel_straight_total: float = 0.0 + """Total length of outboard blanket coolant channel straight sections (m)""" + CREATE_DICTS_FROM_DATACLASS = BlanketData diff --git a/process/data_structure/fwbs_variables.py b/process/data_structure/fwbs_variables.py index acd52894d6..c1ae7cfe75 100644 --- a/process/data_structure/fwbs_variables.py +++ b/process/data_structure/fwbs_variables.py @@ -310,7 +310,7 @@ class FWBSData: """peak first wall temperature [K]""" roughness_fw_channel: float = 1.0e-6 - """first wall channel roughness epsilon [m]""" + """First wall channel roughness (ε) [m]""" len_fw_channel: float = 4.0 """Length of a single first wall channel (all in parallel) [m] @@ -635,17 +635,17 @@ class FWBSData: visc_blkt_coolant: float = 0.0 """Viscosity of the blanket primary coolant""" - cp_fw: float = 0.0 - """Spesific heat for FW and blanket primary coolant(s)""" + heatcap_pres_fw_coolant_average: float = 0.0 + """FW coolant average specific heat capacity at constant pressure [J/kg/K]""" - cv_fw: float = 0.0 - """Spesific heat for FW and blanket primary coolant(s)""" + heatcap_vol_fw_coolant_average: float = 0.0 + """FW coolant average specific heat capacity at constant volume [J/kg/K]""" - cp_bl: float = 0.0 - """Spesific heat for FW and blanket primary coolant(s)""" + heatcap_pres_blkt_coolant_average: float = 0.0 + """Blanket coolant average specific heat capacity at constant pressure [J/kg/K]""" - cv_bl: float = 0.0 - """Spesific heat for FW and blanket primary coolant(s)""" + heatcap_vol_blkt_coolant_average: float = 0.0 + """Blanket coolant average specific heat capacity at constant volume [J/kg/K]""" f_nuc_pow_bz_struct: float = 0.34 """For a dual-coolant blanket, fraction of BZ power cooled by primary coolant""" diff --git a/process/main.py b/process/main.py index d99959837f..843fbebe60 100644 --- a/process/main.py +++ b/process/main.py @@ -50,6 +50,7 @@ from process.core.process_output import OutputFileManager, oheadr from process.core.scan import Scan from process.data_structure.blanket_variables import BlktModelTypes +from process.data_structure.build_variables import InboardBlanketConfiguration from process.data_structure.cost_variables import CostModels from process.data_structure.numerics import PROCESSRunMode from process.data_structure.stellarator_variables import StellaratorModel @@ -109,7 +110,7 @@ IonTemperatureProfile, ) from process.models.physics.scrape_off_layer import ScrapeOffLayer -from process.models.power import Power +from process.models.power import Power, PumpingPowerModelTypes from process.models.pulse import Pulse from process.models.shield import Shield from process.models.stellarator.neoclassics import Neoclassics @@ -965,6 +966,18 @@ def write(self, data, _outfile): # DCLL model self.dcll.output() + if ( + data.fwbs.i_p_coolant_pumping + == PumpingPowerModelTypes.CALCULATE_PRESSURE_DROP + ): + if ( + data.build.i_blkt_inboard + == InboardBlanketConfiguration.INBOARD_BLANKET_PRESENT + ): + self.blanket_library.output_inboard_blkt_pumping_variables() + + self.blanket_library.output_outboard_blkt_pumping_variables() + # FISPACT and LOCA model (not used)- removed # Power model diff --git a/process/models/blankets/blanket_library.py b/process/models/blankets/blanket_library.py index ec293bed12..106aae93c3 100644 --- a/process/models/blankets/blanket_library.py +++ b/process/models/blankets/blanket_library.py @@ -21,10 +21,14 @@ eshellvol, ) from process.models.engineering.pumping import ( + CoolantFrictionLossParameters, CoolantType, - calculate_reynolds_number, - darcy_friction_haaland, + calculate_required_mass_flow_rate, + coolant_friction_pressure_drop, + coolant_pumping_power, + pipe_hydraulic_diameter, ) +from process.models.fw import N_FW_PIPE_90_DEG_BENDS, N_FW_PIPE_180_DEG_BENDS from process.models.power import PumpingPowerModelTypes logger = logging.getLogger(__name__) @@ -733,14 +737,22 @@ def primary_coolant_properties(self, output: bool): pressure=self.data.fwbs.pres_fw_coolant, ) self.data.fwbs.den_fw_coolant = fw_bb_fluid_properties.density - self.data.fwbs.cp_fw = fw_bb_fluid_properties.specific_heat_const_p - self.data.fwbs.cv_fw = fw_bb_fluid_properties.specific_heat_const_v + self.data.fwbs.heatcap_pres_fw_coolant_average = ( + fw_bb_fluid_properties.specific_heat_const_p + ) + self.data.fwbs.heatcap_vol_fw_coolant_average = ( + fw_bb_fluid_properties.specific_heat_const_v + ) self.data.fwbs.visc_fw_coolant = fw_bb_fluid_properties.viscosity self.data.fwbs.den_blkt_coolant = self.data.fwbs.den_fw_coolant self.data.fwbs.visc_blkt_coolant = self.data.fwbs.visc_fw_coolant - self.data.fwbs.cp_bl = self.data.fwbs.cp_fw - self.data.fwbs.cv_bl = self.data.fwbs.cv_fw + self.data.fwbs.heatcap_pres_blkt_coolant_average = ( + self.data.fwbs.heatcap_pres_fw_coolant_average + ) + self.data.fwbs.heatcap_vol_blkt_coolant_average = ( + self.data.fwbs.heatcap_vol_fw_coolant_average + ) # If FW and BB have different coolants... else: @@ -754,8 +766,12 @@ def primary_coolant_properties(self, output: bool): pressure=self.data.fwbs.pres_fw_coolant, ) self.data.fwbs.den_fw_coolant = fw_fluid_properties.density - self.data.fwbs.cp_fw = fw_fluid_properties.specific_heat_const_p - self.data.fwbs.cv_fw = fw_fluid_properties.specific_heat_const_v + self.data.fwbs.heatcap_pres_fw_coolant_average = ( + fw_fluid_properties.specific_heat_const_p + ) + self.data.fwbs.heatcap_vol_fw_coolant_average = ( + fw_fluid_properties.specific_heat_const_v + ) self.data.fwbs.visc_fw_coolant = fw_fluid_properties.viscosity # BB @@ -769,8 +785,12 @@ def primary_coolant_properties(self, output: bool): pressure=self.data.fwbs.pres_blkt_coolant, ) self.data.fwbs.den_blkt_coolant = bb_fluid_properties.density - self.data.fwbs.cp_bl = bb_fluid_properties.specific_heat_const_p - self.data.fwbs.cv_bl = bb_fluid_properties.specific_heat_const_v + self.data.fwbs.heatcap_pres_blkt_coolant_average = ( + bb_fluid_properties.specific_heat_const_p + ) + self.data.fwbs.heatcap_vol_blkt_coolant_average = ( + bb_fluid_properties.specific_heat_const_v + ) self.data.fwbs.visc_blkt_coolant = bb_fluid_properties.viscosity if ( @@ -1124,12 +1144,6 @@ def thermo_hydraulic_model_pressure_drop_calculations(self, output: bool): # Coolant channel bends - # Number of angle turns in FW and blanket flow channels, n.b. these are the - # same for CCFE HCPB and KIT DCLL. FW is also be the same for DCLL MMS ans SMS. - - N_FW_PIPE_90_DEG_BENDS = 2 - N_FW_PIPE_180_DEG_BENDS = 0 - # N.B. This is for BZ only, does not include MF/BSS. if self.data.fwbs.i_blkt_dual_coolant in {1, 2}: N_BLKT_PIPE_90_DEG_BENDS = 4 @@ -1187,7 +1201,7 @@ def thermo_hydraulic_model_pressure_drop_calculations(self, output: bool): ) # Mass flow rate per coolant pipe - self.data.blanket.mfblktpo = ( + self.data.blanket.mflow_blkt_outboard_coolant_channel = ( self.data.blanket.mflow_blkt_outboard_coolant / self.data.blanket.n_blkt_outboard_channels ) @@ -1196,7 +1210,7 @@ def thermo_hydraulic_model_pressure_drop_calculations(self, output: bool): # Assume BZ structure has same channel width as FW self.data.blanket.vel_blkt_outboard_coolant = self.flow_velocity( i_channel_shape=1, - mass_flow_rate=self.data.blanket.mfblktpo, + mass_flow_rate=self.data.blanket.mflow_blkt_outboard_coolant_channel, flow_density=self.data.fwbs.den_blkt_coolant, ) velblkto_liq = self.flow_velocity( @@ -1229,7 +1243,7 @@ def thermo_hydraulic_model_pressure_drop_calculations(self, output: bool): ) # Mass flow rate per coolant pipe - self.data.blanket.mfblktpi = ( + self.data.blanket.mflow_blkt_inboard_coolant_channel = ( self.data.blanket.mflow_blkt_inboard_coolant / self.data.blanket.n_blkt_inboard_channels ) @@ -1241,7 +1255,7 @@ def thermo_hydraulic_model_pressure_drop_calculations(self, output: bool): # Assume BZ structure has same channel width as FW self.data.blanket.vel_blkt_inboard_coolant = self.flow_velocity( i_channel_shape=1, - mass_flow_rate=self.data.blanket.mfblktpi, + mass_flow_rate=self.data.blanket.mflow_blkt_inboard_coolant_channel, flow_density=self.data.fwbs.den_blkt_coolant, ) velblkti_liq = self.flow_velocity( @@ -1271,7 +1285,7 @@ def thermo_hydraulic_model_pressure_drop_calculations(self, output: bool): ) # Mass flow rate per coolant pipe - self.data.blanket.mfblktpo = ( + self.data.blanket.mflow_blkt_outboard_coolant_channel = ( self.data.blanket.mflow_blkt_outboard_coolant / self.data.blanket.n_blkt_outboard_channels ) @@ -1280,7 +1294,7 @@ def thermo_hydraulic_model_pressure_drop_calculations(self, output: bool): # Assume BZ structure has same channel width as FW self.data.blanket.vel_blkt_outboard_coolant = self.flow_velocity( i_channel_shape=1, - mass_flow_rate=self.data.blanket.mfblktpo, + mass_flow_rate=self.data.blanket.mflow_blkt_outboard_coolant_channel, flow_density=self.data.fwbs.den_blkt_coolant, ) @@ -1323,7 +1337,7 @@ def thermo_hydraulic_model_pressure_drop_calculations(self, output: bool): ) # Mass flow rate per coolant pipe - self.data.blanket.mfblktpi = ( + self.data.blanket.mflow_blkt_inboard_coolant_channel = ( self.data.blanket.mflow_blkt_inboard_coolant / self.data.blanket.n_blkt_inboard_channels ) @@ -1332,7 +1346,7 @@ def thermo_hydraulic_model_pressure_drop_calculations(self, output: bool): # Assume BZ structure has same channel width as FW self.data.blanket.vel_blkt_inboard_coolant = self.flow_velocity( i_channel_shape=1, - mass_flow_rate=self.data.blanket.mfblktpi, + mass_flow_rate=self.data.blanket.mflow_blkt_inboard_coolant_channel, flow_density=self.data.fwbs.den_blkt_coolant, ) @@ -1369,7 +1383,7 @@ def thermo_hydraulic_model_pressure_drop_calculations(self, output: bool): ) # Mass flow rate per coolant pipe - self.data.blanket.mfblktpo = ( + self.data.blanket.mflow_blkt_outboard_coolant_channel = ( self.data.blanket.mflow_blkt_outboard_coolant / self.data.blanket.n_blkt_outboard_channels ) @@ -1378,7 +1392,7 @@ def thermo_hydraulic_model_pressure_drop_calculations(self, output: bool): # Assume BZ structure has same channel width as FW self.data.blanket.vel_blkt_outboard_coolant = self.flow_velocity( i_channel_shape=1, - mass_flow_rate=self.data.blanket.mfblktpo, + mass_flow_rate=self.data.blanket.mflow_blkt_outboard_coolant_channel, flow_density=self.data.fwbs.den_blkt_coolant, ) @@ -1400,7 +1414,7 @@ def thermo_hydraulic_model_pressure_drop_calculations(self, output: bool): ) # Mass flow rate per coolant pipe - self.data.blanket.mfblktpi = ( + self.data.blanket.mflow_blkt_inboard_coolant_channel = ( self.data.blanket.mflow_blkt_inboard_coolant / self.data.blanket.n_blkt_inboard_channels ) @@ -1409,7 +1423,7 @@ def thermo_hydraulic_model_pressure_drop_calculations(self, output: bool): # Assume BZ structure has same channel width as FW self.data.blanket.vel_blkt_inboard_coolant = self.flow_velocity( i_channel_shape=1, - mass_flow_rate=self.data.blanket.mfblktpi, + mass_flow_rate=self.data.blanket.mflow_blkt_inboard_coolant_channel, flow_density=self.data.fwbs.den_blkt_coolant, ) @@ -1424,7 +1438,7 @@ def thermo_hydraulic_model_pressure_drop_calculations(self, output: bool): b_bz_liq=self.data.fwbs.b_bz_liq, ) - dpres_fw_inboard_coolant = self.total_pressure_drop( + dpres_fw_inboard_coolant, _ = self.total_pressure_drop( output, icoolpump=1, vel_coolant=vel_fw_inboard_coolant, @@ -1439,7 +1453,7 @@ def thermo_hydraulic_model_pressure_drop_calculations(self, output: bool): label="Inboard first wall", ) - dpres_fw_outboard_coolant = self.total_pressure_drop( + dpres_fw_outboard_coolant, _ = self.total_pressure_drop( output, icoolpump=1, vel_coolant=vel_fw_outboard_coolant, @@ -1472,43 +1486,123 @@ def thermo_hydraulic_model_pressure_drop_calculations(self, output: bool): npoltoti = self.data.fwbs.nopol * npblkti_liq npoltoto = self.data.fwbs.nopol * npblkto_liq - dpres_blkt_outboard_coolant = self.total_pressure_drop( - output, - icoolpump=1, - vel_coolant=self.data.blanket.vel_blkt_outboard_coolant, - len_pipe=self.data.blanket.len_blkt_outboard_channel_total, - n_pipe_90_deg_bends=N_BLKT_PIPE_90_DEG_BENDS, - n_pipe_180_deg_bends=N_BLKT_PIPE_180_DEG_BENDS, - den_coolant=self.data.fwbs.den_blkt_coolant, - visc_coolant_dynamic=self.data.fwbs.visc_blkt_coolant, - coolant_electrical_conductivity=0.0e0, - pol_channel_length=pollengo, - nopolchan=npoltoto, - label="Outboard blanket", - ) - - if ( - self.data.build.i_blkt_inboard - == InboardBlanketConfiguration.INBOARD_BLANKET_PRESENT - ): - dpres_blkt_inboard_coolant = self.total_pressure_drop( + dpres_blkt_outboard_coolant, blkt_outboard_friction_params = ( + self.total_pressure_drop( output, icoolpump=1, - vel_coolant=self.data.blanket.vel_blkt_inboard_coolant, - len_pipe=self.data.blanket.len_blkt_inboard_channel_total, + vel_coolant=self.data.blanket.vel_blkt_outboard_coolant, + len_pipe=self.data.blanket.len_blkt_outboard_channel_total, n_pipe_90_deg_bends=N_BLKT_PIPE_90_DEG_BENDS, n_pipe_180_deg_bends=N_BLKT_PIPE_180_DEG_BENDS, den_coolant=self.data.fwbs.den_blkt_coolant, visc_coolant_dynamic=self.data.fwbs.visc_blkt_coolant, coolant_electrical_conductivity=0.0e0, - pol_channel_length=pollengi, - nopolchan=npoltoti, - label="Inboard blanket", + pol_channel_length=pollengo, + nopolchan=npoltoto, + label="Outboard blanket", ) + ) + + self.data.blanket.dpres_blkt_outboard_coolant_channel_straight_total = ( + blkt_outboard_friction_params.dpres_straight + ) + self.data.blanket.dpres_blkt_outboard_coolant_channel_90_bend = ( + blkt_outboard_friction_params.dpres_90 + ) + self.data.blanket.dpres_blkt_outboard_coolant_channel_90_bends_total = ( + blkt_outboard_friction_params.dpres_90_total + ) + self.data.blanket.dpres_blkt_outboard_coolant_channel_180_bend = ( + blkt_outboard_friction_params.dpres_180 + ) + self.data.blanket.dpres_blkt_outboard_coolant_channel_180_bends_total = ( + blkt_outboard_friction_params.dpres_180_total + ) + self.data.blanket.dpres_blkt_outboard_bends_total = ( + blkt_outboard_friction_params.dpres_bends_total + ) + + self.data.blanket.reynolds_blkt_outboard_coolant = ( + blkt_outboard_friction_params.reynolds_number + ) + self.data.blanket.darcy_frict_blkt_outboard_coolant = ( + blkt_outboard_friction_params.darcy_friction_factor + ) + self.data.blanket.f_straight_blkt_outboard_coolant = ( + blkt_outboard_friction_params.f_straight + ) + self.data.blanket.len_blkt_outboard_coolant_channel_straight_total = ( + blkt_outboard_friction_params.len_straight + ) + self.data.blanket.f_elbow_blkt_outboard_90_bend = ( + blkt_outboard_friction_params.f_elbow_90 + ) + self.data.blanket.f_elbow_blkt_outboard_180_bend = ( + blkt_outboard_friction_params.f_elbow_180 + ) + + if ( + self.data.build.i_blkt_inboard + == InboardBlanketConfiguration.INBOARD_BLANKET_PRESENT + ): + dpres_blkt_inboard_coolant, blkt_inboard_friction_params = ( + self.total_pressure_drop( + output, + icoolpump=1, + vel_coolant=self.data.blanket.vel_blkt_inboard_coolant, + len_pipe=self.data.blanket.len_blkt_inboard_channel_total, + n_pipe_90_deg_bends=N_BLKT_PIPE_90_DEG_BENDS, + n_pipe_180_deg_bends=N_BLKT_PIPE_180_DEG_BENDS, + den_coolant=self.data.fwbs.den_blkt_coolant, + visc_coolant_dynamic=self.data.fwbs.visc_blkt_coolant, + coolant_electrical_conductivity=0.0e0, + pol_channel_length=pollengi, + nopolchan=npoltoti, + label="Inboard blanket", + ) + ) + + self.data.blanket.dpres_blkt_inboard_coolant_channel_straight_total = ( + blkt_inboard_friction_params.dpres_straight + ) + self.data.blanket.dpres_blkt_inboard_coolant_channel_90_bend = ( + blkt_inboard_friction_params.dpres_90 + ) + self.data.blanket.dpres_blkt_inboard_coolant_channel_90_bends_total = ( + blkt_inboard_friction_params.dpres_90_total + ) + self.data.blanket.dpres_blkt_inboard_coolant_channel_180_bend = ( + blkt_inboard_friction_params.dpres_180 + ) + self.data.blanket.dpres_blkt_inboard_coolant_channel_180_bends_total = ( + blkt_inboard_friction_params.dpres_180_total + ) + self.data.blanket.dpres_blkt_inboard_bends_total = ( + blkt_inboard_friction_params.dpres_bends_total + ) + + self.data.blanket.reynolds_blkt_inboard_coolant = ( + blkt_inboard_friction_params.reynolds_number + ) + self.data.blanket.darcy_frict_blkt_inboard_coolant = ( + blkt_inboard_friction_params.darcy_friction_factor + ) + self.data.blanket.f_straight_blkt_inboard_coolant = ( + blkt_inboard_friction_params.f_straight + ) + self.data.blanket.len_blkt_inboard_coolant_channel_straight_total = ( + blkt_inboard_friction_params.len_straight + ) + self.data.blanket.f_elbow_blkt_inboard_90_bend = ( + blkt_inboard_friction_params.f_elbow_90 + ) + self.data.blanket.f_elbow_blkt_inboard_180_bend = ( + blkt_inboard_friction_params.f_elbow_180 + ) # If the blanket has a liquid metal breeder... if self.data.fwbs.i_blkt_dual_coolant > 0: - deltap_blo_liq = self.total_pressure_drop( + deltap_blo_liq, _ = self.total_pressure_drop( output, icoolpump=2, vel_coolant=velblkto_liq, @@ -1526,7 +1620,7 @@ def thermo_hydraulic_model_pressure_drop_calculations(self, output: bool): self.data.build.i_blkt_inboard == InboardBlanketConfiguration.INBOARD_BLANKET_PRESENT ): - deltap_bli_liq = self.total_pressure_drop( + deltap_bli_liq, _ = self.total_pressure_drop( output, icoolpump=2, vel_coolant=velblkti_liq, @@ -2276,26 +2370,26 @@ def thermo_hydraulic_model(self, output: bool): self.data.build.i_blkt_inboard == InboardBlanketConfiguration.INBOARD_BLANKET_PRESENT ): - fwoutleti = (f_nuc_fwi * self.data.fwbs.temp_blkt_coolant_out) + ( - 1 - f_nuc_fwi - ) * self.data.fwbs.temp_fw_coolant_in - inlet_tempi = fwoutleti + temp_fw_coolant_out = ( + f_nuc_fwi * self.data.fwbs.temp_blkt_coolant_out + ) + (1 - f_nuc_fwi) * self.data.fwbs.temp_fw_coolant_in + temp_blkt_coolant_in = temp_fw_coolant_out else: - fwoutleti = self.data.fwbs.temp_fw_coolant_out + temp_fw_coolant_out = self.data.fwbs.temp_fw_coolant_out - fwoutleto = (f_nuc_fwo * self.data.fwbs.temp_blkt_coolant_out) + ( + temp_fw_coolant_out = (f_nuc_fwo * self.data.fwbs.temp_blkt_coolant_out) + ( 1 - f_nuc_fwo ) * self.data.fwbs.temp_fw_coolant_in - inlet_tempo = fwoutleto + temp_blkt_coolant_in = temp_fw_coolant_out elif ( self.data.fwbs.i_fw_blkt_shared_coolant == FWBlktCoolantLoopTypes.SEPARATE_LOOPS ): - fwoutleti = self.data.fwbs.temp_fw_coolant_out - inlet_tempi = self.data.fwbs.temp_blkt_coolant_in - fwoutleto = self.data.fwbs.temp_fw_coolant_out - inlet_tempo = self.data.fwbs.temp_blkt_coolant_in + temp_fw_coolant_out = self.data.fwbs.temp_fw_coolant_out + temp_blkt_coolant_in = self.data.fwbs.temp_blkt_coolant_in + temp_fw_coolant_out = self.data.fwbs.temp_fw_coolant_out + temp_blkt_coolant_in = self.data.fwbs.temp_blkt_coolant_in # Maximum FW temperature. (27/11/2015) Issue #348 # First wall flow is just along the first wall, with no allowance for radial @@ -2338,35 +2432,49 @@ 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.fwbs.cp_fw * (fwoutleti - self.data.fwbs.temp_fw_coolant_in)) + calculate_required_mass_flow_rate( + p_heat_total=1.0e6 + * ( + self.data.blanket.p_fw_inboard_nuclear_heat_mw + + self.data.fwbs.psurffwi + ), + heatcap_coolant=self.data.fwbs.heatcap_pres_fw_coolant_average, + temp_in_coolant=self.data.fwbs.temp_fw_coolant_in, + temp_out_coolant=temp_fw_coolant_out, + ) ) + # 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.fwbs.cp_fw * (fwoutleto - self.data.fwbs.temp_fw_coolant_in)) + calculate_required_mass_flow_rate( + p_heat_total=1.0e6 + * ( + self.data.blanket.p_fw_outboard_nuclear_heat_mw + + self.data.fwbs.psurffwo + ), + heatcap_coolant=self.data.fwbs.heatcap_pres_fw_coolant_average, + temp_in_coolant=self.data.fwbs.temp_fw_coolant_in, + temp_out_coolant=temp_fw_coolant_out, + ) ) # If the blanket is dual-coolant... if self.data.fwbs.i_blkt_dual_coolant == 2: # Mass flow rates for outboard blanket coolants (kg/s) self.data.blanket.mflow_blkt_outboard_coolant = ( - 1.0e6 - * (pnucblkto_struct) - / ( - self.data.fwbs.cp_bl - * (self.data.fwbs.temp_blkt_coolant_out - inlet_tempo) + calculate_required_mass_flow_rate( + p_heat_total=1.0e6 * pnucblkto_struct, + heatcap_coolant=self.data.fwbs.heatcap_pres_blkt_coolant_average, + temp_in_coolant=temp_blkt_coolant_in, + temp_out_coolant=self.data.fwbs.temp_blkt_coolant_out, ) ) - self.data.blanket.mfblkto_liq = ( - 1.0e6 - * (pnucblkto_liq) - / ( - self.data.fwbs.specific_heat_liq - * (self.data.fwbs.outlet_temp_liq - self.data.fwbs.inlet_temp_liq) - ) + + self.data.blanket.mfblkto_liq = calculate_required_mass_flow_rate( + p_heat_total=1.0e6 * pnucblkto_liq, + heatcap_coolant=self.data.fwbs.specific_heat_liq, + temp_in_coolant=self.data.fwbs.inlet_temp_liq, + temp_out_coolant=self.data.fwbs.outlet_temp_liq, ) # If there is an IB blanket... @@ -2376,34 +2484,31 @@ def thermo_hydraulic_model(self, output: bool): ): # Mass flow rates for inboard blanket coolants (kg/s) self.data.blanket.mflow_blkt_inboard_coolant = ( - 1.0e6 - * (pnucblkti_struct) - / ( - self.data.fwbs.cp_bl - * (self.data.fwbs.temp_blkt_coolant_out - inlet_tempi) + calculate_required_mass_flow_rate( + p_heat_total=1.0e6 * pnucblkti_struct, + heatcap_coolant=self.data.fwbs.heatcap_pres_blkt_coolant_average, + temp_in_coolant=temp_blkt_coolant_in, + temp_out_coolant=self.data.fwbs.temp_blkt_coolant_out, ) ) - self.data.blanket.mfblkti_liq = ( - 1.0e6 - * (pnucblkti_liq) - / ( - self.data.fwbs.specific_heat_liq - * ( - self.data.fwbs.outlet_temp_liq - - self.data.fwbs.inlet_temp_liq - ) - ) + + self.data.blanket.mfblkti_liq = calculate_required_mass_flow_rate( + p_heat_total=1.0e6 * pnucblkti_liq, + heatcap_coolant=self.data.fwbs.specific_heat_liq, + temp_in_coolant=self.data.fwbs.inlet_temp_liq, + temp_out_coolant=self.data.fwbs.outlet_temp_liq, ) # If the blanket is single-coolant with liquid metal breeder... elif self.data.fwbs.i_blkt_dual_coolant == 1: # Mass flow rate for outboard blanket coolant (kg/s) self.data.blanket.mflow_blkt_outboard_coolant = ( - 1.0e6 - * (self.data.blanket.p_blkt_nuclear_heat_outboard_mw) - / ( - self.data.fwbs.cp_bl - * (self.data.fwbs.temp_blkt_coolant_out - inlet_tempo) + calculate_required_mass_flow_rate( + p_heat_total=1.0e6 + * self.data.blanket.p_blkt_nuclear_heat_outboard_mw, + heatcap_coolant=self.data.fwbs.heatcap_pres_blkt_coolant_average, + temp_in_coolant=temp_blkt_coolant_in, + temp_out_coolant=self.data.fwbs.temp_blkt_coolant_out, ) ) @@ -2423,13 +2528,15 @@ def thermo_hydraulic_model(self, output: bool): ): # Mass flow rate for inboard blanket coolant (kg/s) self.data.blanket.mflow_blkt_inboard_coolant = ( - 1.0e6 - * (self.data.blanket.p_blkt_nuclear_heat_inboard_mw) - / ( - self.data.fwbs.cp_bl - * (self.data.fwbs.temp_blkt_coolant_out - inlet_tempi) + calculate_required_mass_flow_rate( + p_heat_total=1.0e6 + * self.data.blanket.p_blkt_nuclear_heat_inboard_mw, + heatcap_coolant=self.data.fwbs.heatcap_pres_blkt_coolant_average, + temp_in_coolant=temp_blkt_coolant_in, + temp_out_coolant=self.data.fwbs.temp_blkt_coolant_out, ) ) + # Mass flow rate for inboard breeder flow (kg/s) self.data.fwbs.mfblkti_liq = ( self.data.fwbs.n_liq_recirc * self.data.fwbs.wht_liq_ib @@ -2439,11 +2546,12 @@ def thermo_hydraulic_model(self, output: bool): else: # Mass flow rate for inboard blanket coolant (kg/s) self.data.blanket.mflow_blkt_outboard_coolant = ( - 1.0e6 - * (self.data.blanket.p_blkt_nuclear_heat_outboard_mw) - / ( - self.data.fwbs.cp_bl - * (self.data.fwbs.temp_blkt_coolant_out - inlet_tempo) + calculate_required_mass_flow_rate( + p_heat_total=1.0e6 + * self.data.blanket.p_blkt_nuclear_heat_outboard_mw, + heatcap_coolant=self.data.fwbs.heatcap_pres_blkt_coolant_average, + temp_in_coolant=temp_blkt_coolant_in, + temp_out_coolant=self.data.fwbs.temp_blkt_coolant_out, ) ) @@ -2454,11 +2562,12 @@ def thermo_hydraulic_model(self, output: bool): == InboardBlanketConfiguration.INBOARD_BLANKET_PRESENT ): self.data.blanket.mflow_blkt_inboard_coolant = ( - 1.0e6 - * (self.data.blanket.p_blkt_nuclear_heat_inboard_mw) - / ( - self.data.fwbs.cp_bl - * (self.data.fwbs.temp_blkt_coolant_out - inlet_tempi) + calculate_required_mass_flow_rate( + p_heat_total=1.0e6 + * self.data.blanket.p_blkt_nuclear_heat_inboard_mw, + heatcap_coolant=self.data.fwbs.heatcap_pres_blkt_coolant_average, + temp_in_coolant=temp_blkt_coolant_in, + temp_out_coolant=self.data.fwbs.temp_blkt_coolant_out, ) ) @@ -2468,19 +2577,19 @@ def thermo_hydraulic_model(self, output: bool): # load in pressures if primary pumping == 2 i_p_coolant_pumping = PumpingPowerModelTypes(self.data.fwbs.i_p_coolant_pumping) - if i_p_coolant_pumping == PumpingPowerModelTypes.MECHANICAL: + if i_p_coolant_pumping == PumpingPowerModelTypes.CALCULATE_PRESSURE_DROP: deltap = self.thermo_hydraulic_model_pressure_drop_calculations( output=output ) - deltap_fwi = deltap[0] - deltap_fwo = deltap[1] - deltap_blo = deltap[2] + dpres_fw_inboard_coolant = deltap[0] + dpres_fw_outboard_coolant = deltap[1] + dpres_blkt_outboard_coolant = deltap[2] if self.data.fwbs.i_blkt_dual_coolant > 0: if ( self.data.build.i_blkt_inboard == InboardBlanketConfiguration.INBOARD_BLANKET_PRESENT ): - deltap_bli = deltap[3] + dpres_blkt_inboard_coolant = deltap[3] deltap_blo_liq = deltap[4] deltap_bli_liq = deltap[5] else: @@ -2489,27 +2598,33 @@ def thermo_hydraulic_model(self, output: bool): self.data.build.i_blkt_inboard == InboardBlanketConfiguration.INBOARD_BLANKET_PRESENT ): - deltap_bli = deltap[3] + dpres_blkt_inboard_coolant = deltap[3] # Pumping Power # If FW and BB have the same coolant... if self.data.fwbs.i_fw_blkt_shared_coolant == FWBlktCoolantLoopTypes.SHARED_LOOP: # Total pressure drop in the first wall/blanket (Pa) - if i_p_coolant_pumping == PumpingPowerModelTypes.MECHANICAL: + if i_p_coolant_pumping == PumpingPowerModelTypes.CALCULATE_PRESSURE_DROP: if ( self.data.build.i_blkt_inboard == InboardBlanketConfiguration.INBOARD_BLANKET_PRESENT ): - deltap_fw_blkt = deltap_fwi + deltap_bli + deltap_fwo + deltap_blo + deltap_fw_blkt = ( + dpres_fw_inboard_coolant + + dpres_blkt_inboard_coolant + + dpres_fw_outboard_coolant + + dpres_blkt_outboard_coolant + ) if ( self.data.build.i_blkt_inboard == InboardBlanketConfiguration.NO_INBOARD_BLANKET ): - deltap_fw_blkt = deltap_fwi + deltap_fwo + deltap_blo - elif ( - i_p_coolant_pumping - == PumpingPowerModelTypes.MECHANICAL_WITH_PRESSURE_DROP - ): + deltap_fw_blkt = ( + dpres_fw_inboard_coolant + + dpres_fw_outboard_coolant + + dpres_blkt_outboard_coolant + ) + elif i_p_coolant_pumping == PumpingPowerModelTypes.INPUT_PRESSURE_DROP: deltap_fw_blkt = self.data.primary_pumping.dp_fw_blkt # Total coolant mass flow rate in the first wall/blanket (kg/s) self.data.blanket.mftotal = ( @@ -2518,19 +2633,17 @@ def thermo_hydraulic_model(self, output: bool): ) # Total mechanical pumping power (MW) - self.data.primary_pumping.p_fw_blkt_coolant_pump_mw = ( - self.coolant_pumping_power( - output=output, - i_liquid_breeder=1, - temp_coolant_pump_outlet=self.data.fwbs.temp_fw_coolant_in, - temp_coolant_pump_inlet=self.data.fwbs.temp_blkt_coolant_out, - pres_coolant_pump_inlet=self.data.fwbs.pres_fw_coolant, - dpres_coolant=deltap_fw_blkt, - mflow_coolant_total=self.data.blanket.mftotal, - i_coolant_type=self.data.fwbs.i_fw_coolant_type, - den_coolant=self.data.fwbs.den_fw_coolant, - label="First Wall and Blanket", - ) + self.data.primary_pumping.p_fw_blkt_coolant_pump_mw = coolant_pumping_power( + i_liquid_breeder=1, + temp_coolant_pump_outlet=self.data.fwbs.temp_fw_coolant_in, + temp_coolant_pump_inlet=self.data.fwbs.temp_blkt_coolant_out, + pres_coolant_pump_inlet=self.data.fwbs.pres_fw_coolant, + dpres_coolant=deltap_fw_blkt, + mflow_coolant_total=self.data.blanket.mftotal, + i_coolant_type=self.data.fwbs.i_fw_coolant_type, + den_coolant=self.data.fwbs.den_fw_coolant, + etaiso=self.data.fwbs.etaiso, + etaiso_liq=self.data.fwbs.etaiso_liq, ) # If FW and BB have different coolants... @@ -2538,27 +2651,28 @@ def thermo_hydraulic_model(self, output: bool): self.data.fwbs.i_fw_blkt_shared_coolant == FWBlktCoolantLoopTypes.SEPARATE_LOOPS ): - if i_p_coolant_pumping == PumpingPowerModelTypes.MECHANICAL: + if i_p_coolant_pumping == PumpingPowerModelTypes.CALCULATE_PRESSURE_DROP: # Total pressure drop in the first wall (Pa) - deltap_fw = deltap_fwi + deltap_fwo + dpres_fw_coolant_total = ( + dpres_fw_inboard_coolant + dpres_fw_outboard_coolant + ) # Total pressure drop in the blanket (Pa) if ( self.data.build.i_blkt_inboard == InboardBlanketConfiguration.INBOARD_BLANKET_PRESENT ): - deltap_blkt = deltap_bli + deltap_blo + dpres_blkt_coolant_total = ( + dpres_blkt_inboard_coolant + dpres_blkt_outboard_coolant + ) if ( self.data.build.i_blkt_inboard == InboardBlanketConfiguration.NO_INBOARD_BLANKET ): - deltap_blkt = deltap_blo - elif ( - i_p_coolant_pumping - == PumpingPowerModelTypes.MECHANICAL_WITH_PRESSURE_DROP - ): - deltap_fw = self.data.primary_pumping.dp_fw - deltap_blkt = self.data.primary_pumping.dp_blkt + dpres_blkt_coolant_total = dpres_blkt_outboard_coolant + elif i_p_coolant_pumping == PumpingPowerModelTypes.INPUT_PRESSURE_DROP: + dpres_fw_coolant_total = self.data.primary_pumping.dp_fw + dpres_blkt_coolant_total = self.data.primary_pumping.dp_blkt # Total coolant mass flow rate in the first wall (kg/s) self.data.blanket.mflow_fw_coolant_total = ( @@ -2572,31 +2686,31 @@ def thermo_hydraulic_model(self, output: bool): ) # Mechanical pumping power for the first wall (MW) - self.data.heat_transport.p_fw_coolant_pump_mw = self.coolant_pumping_power( - output=output, + self.data.heat_transport.p_fw_coolant_pump_mw = coolant_pumping_power( i_liquid_breeder=1, temp_coolant_pump_outlet=self.data.fwbs.temp_fw_coolant_in, temp_coolant_pump_inlet=self.data.fwbs.temp_fw_coolant_out, pres_coolant_pump_inlet=self.data.fwbs.pres_fw_coolant, - dpres_coolant=deltap_fw, + dpres_coolant=dpres_fw_coolant_total, mflow_coolant_total=self.data.blanket.mflow_fw_coolant_total, i_coolant_type=self.data.fwbs.i_fw_coolant_type, den_coolant=self.data.fwbs.den_fw_coolant, - label="First Wall", + etaiso=self.data.fwbs.etaiso, + etaiso_liq=self.data.fwbs.etaiso_liq, ) # Mechanical pumping power for the blanket (MW) - self.data.heat_transport.p_blkt_coolant_pump_mw = self.coolant_pumping_power( - output=output, + self.data.heat_transport.p_blkt_coolant_pump_mw = coolant_pumping_power( i_liquid_breeder=1, temp_coolant_pump_outlet=self.data.fwbs.temp_blkt_coolant_in, temp_coolant_pump_inlet=self.data.fwbs.temp_blkt_coolant_out, pres_coolant_pump_inlet=self.data.fwbs.pres_blkt_coolant, - dpres_coolant=deltap_blkt, + dpres_coolant=dpres_blkt_coolant_total, mflow_coolant_total=self.data.blanket.mflow_blkt_coolant_total, i_coolant_type=(self.data.fwbs.i_blkt_coolant_type), den_coolant=self.data.fwbs.den_blkt_coolant, - label="Blanket", + etaiso=self.data.fwbs.etaiso, + etaiso_liq=self.data.fwbs.etaiso_liq, ) # Total mechanical pumping power (MW) @@ -2608,7 +2722,7 @@ def thermo_hydraulic_model(self, output: bool): # If the blanket has a liquid metal breeder... if self.data.fwbs.i_blkt_dual_coolant > 0: # Total pressure drop in the blanket (Pa) - if i_p_coolant_pumping == PumpingPowerModelTypes.MECHANICAL: + if i_p_coolant_pumping == PumpingPowerModelTypes.CALCULATE_PRESSURE_DROP: if ( self.data.build.i_blkt_inboard == InboardBlanketConfiguration.INBOARD_BLANKET_PRESENT @@ -2619,10 +2733,7 @@ def thermo_hydraulic_model(self, output: bool): == InboardBlanketConfiguration.NO_INBOARD_BLANKET ): deltap_bl_liq = deltap_blo_liq - elif ( - i_p_coolant_pumping - == PumpingPowerModelTypes.MECHANICAL_WITH_PRESSURE_DROP - ): + elif i_p_coolant_pumping == PumpingPowerModelTypes.INPUT_PRESSURE_DROP: deltap_bl_liq = self.data.primary_pumping.dp_liq # Total liquid metal breeder/coolant mass flow rate in the blanket (kg/s) self.data.blanket.mfblkt_liq = ( @@ -2630,8 +2741,7 @@ def thermo_hydraulic_model(self, output: bool): ) # Mechanical pumping power for the blanket (MW) - self.data.heat_transport.p_blkt_breeder_pump_mw = self.coolant_pumping_power( - output=output, + self.data.heat_transport.p_blkt_breeder_pump_mw = coolant_pumping_power( i_liquid_breeder=2, temp_coolant_pump_outlet=self.data.fwbs.inlet_temp_liq, temp_coolant_pump_inlet=self.data.fwbs.outlet_temp_liq, @@ -2640,7 +2750,8 @@ def thermo_hydraulic_model(self, output: bool): mflow_coolant_total=self.data.blanket.mfblkt_liq, i_coolant_type=(self.data.fwbs.i_blkt_coolant_type), den_coolant=self.data.fwbs.den_liq, - label="Liquid Metal Breeder/Coolant", + etaiso=self.data.fwbs.etaiso, + etaiso_liq=self.data.fwbs.etaiso_liq, ) self.data.heat_transport.htpmw_blkt_tot = ( @@ -2902,9 +3013,9 @@ def total_pressure_drop( pol_channel_length: float, nopolchan: int, label: str, - ) -> float: - """Calculate the total pressure drop (Pa) for coolant flow in the - first wall (FW) and breeding blanket (BZ). + ) -> tuple[float, CoolantFrictionLossParameters]: + """Calculate the total pressure drop (Pa) for coolant flow in the first wall + (FW) and breeding blanket (BZ). This includes frictional losses and, for liquid breeder coolants, magnetohydrodynamic (MHD) losses. @@ -2938,8 +3049,8 @@ def total_pressure_drop( Returns ------- - float - Total pressure drop (Pa). + tuple + Total pressure drop (Pa) and friction loss parameters. """ radius_pipe_90_deg_bend, radius_pipe_180_deg_bend = calculate_pipe_bend_radius( i_ps=icoolpump, @@ -2948,7 +3059,7 @@ def total_pressure_drop( ) # Friction - for all coolants - dpres_friction = self.coolant_friction_pressure_drop( + friction_params = coolant_friction_pressure_drop( i_ps=icoolpump, radius_pipe_90_deg_bend=radius_pipe_90_deg_bend, radius_pipe_180_deg_bend=radius_pipe_180_deg_bend, @@ -2958,9 +3069,12 @@ def total_pressure_drop( den_coolant=den_coolant, visc_coolant=visc_coolant_dynamic, vel_coolant=vel_coolant, - label=label, - output=output, + roughness_channel=self.data.fwbs.roughness_fw_channel, + radius_channel=self.data.fwbs.radius_fw_channel, + a_bz_liq=self.data.fwbs.a_bz_liq, + b_bz_liq=self.data.fwbs.b_bz_liq, ) + dpres_friction = friction_params.dpres_total if icoolpump == 2: dpres_mhd = self.liquid_breeder_mhd_pressure_drop( @@ -2978,22 +3092,7 @@ def total_pressure_drop( # Total pressure drop (Pa) dpres_total = dpres_friction + dpres_mhd - if output: - po.osubhd(self.outfile, f"Total pressure drop for {label}") - - po.ocmmnt(self.outfile, "Friction drops plus MHD drops if applicaple") - po.ovarre( - self.outfile, "Total pressure drop (Pa)", "(deltap)", dpres_total, "OP " - ) - po.ovarre( - self.outfile, - "Coolant flow velocity (m/s)", - "(flow_velocity, formerly vv)", - vel_coolant, - "OP ", - ) - - return dpres_total + return dpres_total, friction_params def liquid_breeder_mhd_pressure_drop( self, @@ -3156,466 +3255,338 @@ def liquid_breeder_mhd_pressure_drop( return liquid_breeder_pressure_drop_mhd - def coolant_friction_pressure_drop( - self, - i_ps: int, - radius_pipe_90_deg_bend: float, - radius_pipe_180_deg_bend: float, - n_pipe_90_deg_bends: float, - n_pipe_180_deg_bends: float, - len_pipe: float, - den_coolant: float, - visc_coolant: float, - vel_coolant: float, - label: str, - output: bool = False, - ): - """Pressure drops are calculated for a pipe with a number of 90 - and 180 degree bends. The pressure drop due to frictional forces along - the total straight length of the pipe is calculated, then the pressure - drop due to the bends is calculated. The total pressure drop is the sum - of all contributions. - - Parameters - ---------- - i_ps : - switch for primary or secondary coolant - radius_pipe_90_deg_bend : - radius of 90 degree bend in pipe (m) - radius_pipe_180_deg_bend : - radius of 180 degree bend in pipe (m) - n_pipe_90_deg_bends : - number of 90 degree bends in the pipe - n_pipe_180_deg_bends : - number of 180 degree bends in the pipe - len_pipe : - total flow length along pipe (m) - den_coolant : - coolant density (kg/m³) - visc_coolant : - coolant viscosity (Pa s) - vel_coolant : - coolant flow velocity (m/s) - label : - component name - output : - boolean of whether to write data to output file - - :Notes: - Darcy-Weisbach Equation (straight pipe): - - ΔP = λ * L/D * (p 〈v〉²) / 2 - - λ - Darcy friction factor, L - pipe length, D - hydraulic diameter, - p - fluid density, 〈v〉 - fluid flow average velocity - - This function also calculates pressure drop equations for elbow bends, - with modified coefficients. + def output_outboard_blkt_pumping_variables(self): + """Output the outboard blanket pumping variables to the output file.""" + po.oheadr(self.outfile, "Outboard Blanket pumping variables") - N.B. Darcy friction factor is estimated from the Haaland approximation. - """ - # Calculate hydraulic dimater for round or retancular pipe (m) - dia_pipe = self.pipe_hydraulic_diameter(i_ps) - - # Reynolds number - reynolds_number = calculate_reynolds_number( - den_coolant=den_coolant, - vel_coolant=vel_coolant, - radius_channel=dia_pipe / 2, - visc_coolant=visc_coolant, + po.ovarre( + self.outfile, + "Outboard blanket coolant channel length (radial direction) (m)", + "(len_blkt_outboard_coolant_channel_radial)", + self.data.blanket.len_blkt_outboard_coolant_channel_radial, + "OP ", ) - - # Calculate Darcy friction factor - # N.B. friction function Uses Haaland approx. - # which assumes a filled circular pipe. - # Use dh which allows us to do fluid calculations for non-cicular tubes - # (dh is estimate appropriate for fully developed flow). - - darcy_friction_factor = darcy_friction_haaland( - reynolds=reynolds_number, - roughness_channel=self.data.fwbs.roughness_fw_channel, - radius_channel=self.data.fwbs.radius_fw_channel, + po.ovarre( + self.outfile, + "Outboard blanket coolant channel length (poloidal direction) (m)", + "(len_blkt_outboard_segment_poloidal)", + self.data.blanket.len_blkt_outboard_segment_poloidal, + "OP ", ) - - # Pressure drop coefficient - - # Straight section - f_straight = darcy_friction_factor * len_pipe / dia_pipe - - # 90 degree elbow pressure drop coefficient - f_elbow_90 = self.elbow_coeff( - radius_pipe_elbow=radius_pipe_90_deg_bend, - deg_pipe_elbow=90.0, - darcy_friction=darcy_friction_factor, - dia_pipe=dia_pipe, + po.oblnkl(self.outfile) + po.ovarre( + self.outfile, + "Number of outboard blanket coolant sections in the radial direction", + "(n_blkt_outboard_module_coolant_sections_radial)", + self.data.fwbs.n_blkt_outboard_module_coolant_sections_radial, + "OP ", ) - - # 180 degree elbow pressure drop coefficient - f_elbow_180 = self.elbow_coeff( - radius_pipe_elbow=radius_pipe_180_deg_bend, - deg_pipe_elbow=180.0, - darcy_friction=darcy_friction_factor, - dia_pipe=dia_pipe, + po.ovarre( + self.outfile, + "Number of outboard blanket coolant sections in the poloidal direction", + "(n_blkt_outboard_module_coolant_sections_poloidal)", + self.data.fwbs.n_blkt_outboard_module_coolant_sections_poloidal, + "OP ", ) - - # Pressure drop due to friction in straight sections - dpres_straight = f_straight * 0.5 * den_coolant * vel_coolant**2 - - # Pressure drop due to 90 and 180 degree bends - dpres_90 = n_pipe_90_deg_bends * f_elbow_90 * 0.5 * den_coolant * vel_coolant**2 - dpres_180 = ( - n_pipe_180_deg_bends * f_elbow_180 * 0.5 * den_coolant * vel_coolant**2 + po.oblnkl(self.outfile) + po.ovarre( + self.outfile, + "Total length of outboard blanket coolant channel straight sections (m)", + "(len_blkt_outboard_channel_total)", + self.data.blanket.len_blkt_outboard_channel_total, + "OP ", ) - - # Total pressure drop (Pa) - dpres_total = dpres_straight + dpres_90 + dpres_180 - - if output: - po.osubhd(self.outfile, f"Pressure drop (friction) for {label}") - po.ovarre(self.outfile, "Reynolds number", "(reyn)", reynolds_number, "OP ") - po.ovarre( - self.outfile, - "Darcy friction factor", - "(lambda)", - darcy_friction_factor, - "OP ", - ) - po.ovarre( - self.outfile, - "Pressure drop (Pa)", - "(pressure_drop)", - dpres_total, - "OP ", - ) - po.ocmmnt(self.outfile, "This is the sum of the following:") - po.ovarre( - self.outfile, - " Straight sections (Pa)", - "(pdropstraight)", - dpres_straight, - "OP ", - ) - po.ovarre( - self.outfile, - " 90 degree bends (Pa)", - "(pdrop90)", - dpres_90, - "OP ", - ) - po.ovarre( - self.outfile, - " 180 degree bends (Pa)", - "(pdrop180)", - dpres_180, - "OP ", - ) - - # TN: always write verbose stuff, it has no harm - po.ovarre( - self.outfile, - "Straight section pressure drop coefficient", - "(kstrght)", - f_straight, - "OP ", - ) - po.ovarre( - self.outfile, - "90 degree elbow coefficient", - "(kelbwn)", - f_elbow_90, - "OP ", - ) - po.ovarre( - self.outfile, - "180 degree elbow coefficient coefficient", - "(kelbwt)", - f_elbow_180, - "OP ", - ) - - return dpres_total - - def pipe_hydraulic_diameter(self, i_channel_shape): - """Caculate the hydraulic diameter (m) for a given coolant pipe size/shape. - - - Parameters - ---------- - i_channel_shape : - switch for circular or rectangular channel crossection. - Shape depends on whether primary or secondary coolant - - Raises - ------ - ProcessValueError - If i_channel_shape is an invalid option (not in [1,2]) - """ - # If primary coolant then circular channels assumed - if i_channel_shape == 1: - return 2.0 * self.data.fwbs.radius_fw_channel - - # If secondary coolant then rectangular channels assumed - if i_channel_shape == 2: - return ( - 2 - * self.data.fwbs.a_bz_liq - * self.data.fwbs.b_bz_liq - / (self.data.fwbs.a_bz_liq + self.data.fwbs.b_bz_liq) - ) - - raise ProcessValueError( - f"i_channel_shape ={i_channel_shape} is an invalid option." + po.oblnkl(self.outfile) + po.ocmmnt(self.outfile, "----------------------------") + po.oblnkl(self.outfile) + po.ovarre( + self.outfile, + "Pressure drop for straight sections of outboard blanket (Pa)", + "(dpres_blkt_outboard_coolant_channel_straight_total)", + self.data.blanket.dpres_blkt_outboard_coolant_channel_straight_total, + "OP ", + ) + po.oblnkl(self.outfile) + po.ovarre( + self.outfile, + "Pressure drop for 90° bends of outboard blanket (Pa)", + "(dpres_blkt_outboard_coolant_channel_90_bend)", + self.data.blanket.dpres_blkt_outboard_coolant_channel_90_bend, + "OP ", + ) + po.ovarre( + self.outfile, + "Total pressure drop for 90° bends of outboard blanket (Pa)", + "(dpres_blkt_outboard_coolant_channel_90_bends_total)", + self.data.blanket.dpres_blkt_outboard_coolant_channel_90_bends_total, + "OP ", + ) + po.oblnkl(self.outfile) + po.ovarre( + self.outfile, + "Pressure drop for 180° bends of outboard blanket (Pa)", + "(dpres_blkt_outboard_coolant_channel_180_bend)", + self.data.blanket.dpres_blkt_outboard_coolant_channel_180_bend, + "OP ", + ) + po.ovarre( + self.outfile, + "Total pressure drop for 180° bends of outboard blanket (Pa)", + "(dpres_blkt_outboard_coolant_channel_180_bends_total)", + self.data.blanket.dpres_blkt_outboard_coolant_channel_180_bends_total, + "OP ", ) - @staticmethod - def elbow_coeff( - radius_pipe_elbow: float, - deg_pipe_elbow: float, - darcy_friction: float, - dia_pipe: float, - ) -> float: - """Calculates elbow bend coefficients for pressure drop calculations. - - Parameters - ---------- - radius_pipe_elbow : float - Pipe elbow radius (m) - deg_pipe_elbow : float - Pipe elbow angle (degrees) - darcy_friction : float - Darcy friction factor - dia_pipe : float - Pipe diameter (m) - - Returns - ------- - float - Elbow coefficient for pressure drop calculation - - Raises - ------ - ProcessValueError - If 70 <= elbow angle(deg) <= 100 as there is no formula - for this range - - References - ---------- - - [Ide1969] Idel'Cik, I. E. (1969), Memento des pertes de charge, - Collection de la Direction des Etudes et Recherches d'Electricité de France. - """ - if deg_pipe_elbow == 90: - a = 1.0 - elif deg_pipe_elbow < 70: - a = 0.9 * np.sin(deg_pipe_elbow * np.pi / 180.0) - elif deg_pipe_elbow > 100: - a = 0.7 + (0.35 * np.sin((deg_pipe_elbow / 90.0) * (np.pi / 180.0))) - else: - raise ProcessValueError( - "No formula for 70 <= elbow angle(deg) <= 100, " - "only 90 deg option available in this range." - ) - - r_ratio = radius_pipe_elbow / dia_pipe - - if r_ratio > 1: - b = 0.21 / r_ratio**0.5 - elif r_ratio < 1: - b = 0.21 / r_ratio**2.5 - else: - b = 0.21 - - # Singularity - ximt = a * b - - # Friction - xift = ( - (np.pi / 180.0) - * darcy_friction - * (radius_pipe_elbow / dia_pipe) - * deg_pipe_elbow + po.oblnkl(self.outfile) + po.ovarre( + self.outfile, + "Total pressure drop for all bends (Pa)", + "(dpres_blkt_outboard_bends_total)", + self.data.blanket.dpres_blkt_outboard_bends_total, + "OP ", ) - # Elbow Coefficient - return ximt + xift + po.oblnkl(self.outfile) + po.ovarre( + self.outfile, + "Reynolds number of outboard blanket coolant", + "(reynolds_blkt_outboard_coolant)", + self.data.blanket.reynolds_blkt_outboard_coolant, + "OP ", + ) - def coolant_pumping_power( - self, - output: bool, - i_liquid_breeder: int, - temp_coolant_pump_outlet: float, - temp_coolant_pump_inlet: float, - pres_coolant_pump_inlet: float, - dpres_coolant: float, - mflow_coolant_total: float, - i_coolant_type: int, - den_coolant: float, - label: str, - ) -> float: - """Calculate the coolant pumping power in MW for the first wall (FW) or - breeding blanket (BZ) coolant. + po.ovarre( + self.outfile, + "Darcy friction factor of outboard blanket coolant", + "(darcy_frict_blkt_outboard_coolant)", + self.data.blanket.darcy_frict_blkt_outboard_coolant, + "OP ", + ) + po.oblnkl(self.outfile) - Parameters - ---------- - output : bool - Whether to write data to output file. - i_liquid_breeder : int - Switch for primary coolant or secondary coolant/breeder - (1=primary He/H2O, 2=secondary PbLi/Li). - temp_coolant_pump_outlet : float - Pump outlet temperature (K). - temp_coolant_pump_inlet : float - Pump inlet temperature (K). - pres_coolant_pump_inlet : float - Outlet (pump inlet) coolant pressure (Pa). - dpres_coolant : float - Coolant pressure drop (Pa). - mflow_coolant_total : float - Total coolant mass flow rate in (kg/s). - i_coolant_type : int - Type of FW/blanket coolant (e.g., 1=Helium, 2=Water) - den_coolant : float - Density of coolant or liquid breeder (kg/m³). - label : str - Description label for output. + po.ovarre( + self.outfile, + "Pressure drop coefficient for straight sections of outboard blanket", + "(f_straight_blkt_outboard_coolant)", + self.data.blanket.f_straight_blkt_outboard_coolant, + "OP ", + ) - Returns - ------- - float - Pumping power in MW. + po.ovarre( + self.outfile, + "Total length of straight sections of outboard blanket coolant channels (m)", + "(len_blkt_outboard_coolant_channel_straight_total)", + self.data.blanket.len_blkt_outboard_coolant_channel_straight_total, + "OP ", + ) - Raises - ------ - ProcessValueError - If calculated pressure drops in the coolant are too large to be feasible + po.ovarre( + self.outfile, + "Pressure drop coefficient for 90° bends in outboard blanket", + "(f_elbow_blkt_outboard_90_bend)", + self.data.blanket.f_elbow_blkt_outboard_90_bend, + "OP ", + ) - References - ---------- - - Idel'Cik, I. E. (1969), Memento des pertes de charge - - S.P. Sukhatme (2005), A Textbook on Heat Transfer - """ - # Pump outlet pressure (Pa) - # The pump adds the pressure lost going through the coolant channels back - pres_coolant_pump_outlet = pres_coolant_pump_inlet + dpres_coolant + po.ovarre( + self.outfile, + "Pressure drop coefficient for 180° bends in outboard blanket", + "(f_elbow_blkt_outboard_180_bend)", + self.data.blanket.f_elbow_blkt_outboard_180_bend, + "OP ", + ) + po.oblnkl(self.outfile) + po.ocmmnt(self.outfile, "----------------------------") + po.oblnkl(self.outfile) - # Adiabatic index for helium or water - gamma = (5 / 3) if i_coolant_type == CoolantType.HELIUM else (4 / 3) + po.ovarre( + self.outfile, + "Mass flow rate of outboard blanket coolant (kg/s)", + "(mflow_blkt_outboard_coolant)", + self.data.blanket.mflow_blkt_outboard_coolant, + "OP ", + ) + po.ovarre( + self.outfile, + "Mass flow rate of outboard blanket coolant in single channel (kg/s)", + "(mflow_blkt_outboard_coolant_channel)", + self.data.blanket.mflow_blkt_outboard_coolant_channel, + "OP ", + ) + po.ovarre( + self.outfile, + "Velocity of outboard blanket coolant in single channel (m/s)", + "(vel_blkt_outboard_coolant)", + self.data.blanket.vel_blkt_outboard_coolant, + "OP ", + ) - # If calculating for primary coolant - if i_liquid_breeder == 1: - # The pumping power is be calculated in the most general way, - # using enthalpies before and after the pump. + def output_inboard_blkt_pumping_variables(self): + """Output the inboard blanket pumping variables to the output file.""" + po.oheadr(self.outfile, "Inboard Blanket pumping variables") - pump_outlet_fluid_properties = FluidProperties.of( - fluid_name=CoolantType(i_coolant_type).full_name, - temperature=temp_coolant_pump_outlet, - pressure=pres_coolant_pump_outlet, - ) + po.ovarre( + self.outfile, + "Inboard blanket coolant channel length (radial direction) (m)", + "(len_blkt_inboard_coolant_channel_radial)", + self.data.blanket.len_blkt_inboard_coolant_channel_radial, + "OP ", + ) + po.ovarre( + self.outfile, + "Inboard blanket coolant channel length (poloidal direction) (m)", + "(len_blkt_inboard_segment_poloidal)", + self.data.blanket.len_blkt_inboard_segment_poloidal, + "OP ", + ) + po.oblnkl(self.outfile) + po.ovarre( + self.outfile, + "Number of inboard blanket coolant sections in the radial direction", + "(n_blkt_inboard_module_coolant_sections_radial)", + self.data.fwbs.n_blkt_inboard_module_coolant_sections_radial, + "OP ", + ) + po.ovarre( + self.outfile, + "Number of inboard blanket coolant sections in the poloidal direction", + "(n_blkt_inboard_module_coolant_sections_poloidal)", + self.data.fwbs.n_blkt_inboard_module_coolant_sections_poloidal, + "OP ", + ) + po.oblnkl(self.outfile) + po.ovarre( + self.outfile, + "Total length of inboard blanket coolant channel straight sections (m)", + "(len_blkt_inboard_channel_total)", + self.data.blanket.len_blkt_inboard_channel_total, + "OP ", + ) + po.oblnkl(self.outfile) + po.ocmmnt(self.outfile, "----------------------------") + po.oblnkl(self.outfile) + po.ovarre( + self.outfile, + "Pressure drop for straight sections of inboard blanket (Pa)", + "(dpres_blkt_inboard_coolant_channel_straight_total)", + self.data.blanket.dpres_blkt_inboard_coolant_channel_straight_total, + "OP ", + ) + po.oblnkl(self.outfile) + po.ovarre( + self.outfile, + "Pressure drop for 90° bends of inboard blanket (Pa)", + "(dpres_blkt_inboard_coolant_channel_90_bend)", + self.data.blanket.dpres_blkt_inboard_coolant_channel_90_bend, + "OP ", + ) + po.ovarre( + self.outfile, + "Total pressure drop for 90° bends of inboard blanket (Pa)", + "(dpres_blkt_inboard_coolant_channel_90_bends_total)", + self.data.blanket.dpres_blkt_inboard_coolant_channel_90_bends_total, + "OP ", + ) + po.oblnkl(self.outfile) + po.ovarre( + self.outfile, + "Pressure drop for 180° bends of inboard blanket (Pa)", + "(dpres_blkt_inboard_coolant_channel_180_bend)", + self.data.blanket.dpres_blkt_inboard_coolant_channel_180_bend, + "OP ", + ) + po.ovarre( + self.outfile, + "Total pressure drop for 180° bends of inboard blanket (Pa)", + "(dpres_blkt_inboard_coolant_channel_180_bends_total)", + self.data.blanket.dpres_blkt_inboard_coolant_channel_180_bends_total, + "OP ", + ) - # Assume isentropic pump so that s1 = s2 - s1 = pump_outlet_fluid_properties.entropy + po.oblnkl(self.outfile) + po.ovarre( + self.outfile, + "Total pressure drop for all bends (Pa)", + "(dpres_blkt_inboard_bends_total)", + self.data.blanket.dpres_blkt_inboard_bends_total, + "OP ", + ) - # Get specific enthalpy at the outlet (J/kg) before pump using - # pressure and entropy s1 - pump_inlet_fluid_properties = FluidProperties.of( - fluid_name=CoolantType(i_coolant_type).full_name, - pressure=pres_coolant_pump_inlet, - entropy=s1, - ) + po.oblnkl(self.outfile) + po.ovarre( + self.outfile, + "Reynolds number of inboard blanket coolant", + "(reynolds_blkt_inboard_coolant)", + self.data.blanket.reynolds_blkt_inboard_coolant, + "OP ", + ) - # Pumping power (MW) is given by enthalpy change, with a correction for - # the isentropic efficiency of the pump. - fp = ( - temp_coolant_pump_outlet - * ( - 1 - - (pres_coolant_pump_outlet / pres_coolant_pump_inlet) - ** -((gamma - 1) / gamma) - ) - / ( - self.data.fwbs.etaiso - * (temp_coolant_pump_inlet - temp_coolant_pump_outlet) - ) - ) - pumppower = ( - 1e-6 - * mflow_coolant_total - * ( - pump_outlet_fluid_properties.enthalpy - - pump_inlet_fluid_properties.enthalpy - ) - / self.data.fwbs.etaiso - ) / (1 - fp) + po.ovarre( + self.outfile, + "Darcy friction factor of inboard blanket coolant", + "(darcy_frict_blkt_inboard_coolant)", + self.data.blanket.darcy_frict_blkt_inboard_coolant, + "OP ", + ) + po.oblnkl(self.outfile) - # If calculating for secondary coolant/breeder... - else: - # Calculate specific volume - spec_vol = 1 / den_coolant + po.ovarre( + self.outfile, + "Pressure drop coefficient for straight sections of inboard blanket", + "(f_straight_blkt_inboard_coolant)", + self.data.blanket.f_straight_blkt_inboard_coolant, + "OP ", + ) - # Pumping power (MW) is given by pressure change, with a correction for - # the isentropic efficiency of the pump. - fp = ( - temp_coolant_pump_outlet - * ( - 1 - - (pres_coolant_pump_outlet / pres_coolant_pump_inlet) - ** -((gamma - 1) / gamma) - ) - / ( - self.data.fwbs.etaiso_liq - * (temp_coolant_pump_inlet - temp_coolant_pump_outlet) - ) - ) - pumppower = ( - 1e-6 - * mflow_coolant_total - * spec_vol - * dpres_coolant - / self.data.fwbs.etaiso_liq - ) / (1 - fp) + po.ovarre( + self.outfile, + "Total length of straight sections of inboard blanket coolant channels (m)", + "(len_blkt_inboard_coolant_channel_straight_total)", + self.data.blanket.len_blkt_inboard_coolant_channel_straight_total, + "OP ", + ) - # Error for dpres_coolant too large - if fp >= 1: - raise ProcessValueError( - "Pressure drops in coolant are too large to be feasible" - ) + po.ovarre( + self.outfile, + "Pressure drop coefficient for 90° bends in inboard blanket", + "(f_elbow_blkt_inboard_90_bend)", + self.data.blanket.f_elbow_blkt_inboard_90_bend, + "OP ", + ) - if output: - po.oheadr(self.outfile, "Mechanical Pumping Power for " + label) - po.osubhd(self.outfile, "Pumping power for " + label) + po.ovarre( + self.outfile, + "Pressure drop coefficient for 180° bends in inboard blanket", + "(f_elbow_blkt_inboard_180_bend)", + self.data.blanket.f_elbow_blkt_inboard_180_bend, + "OP ", + ) - po.ovarre( - self.outfile, "Pumping power (MW)", "(pumppower)", pumppower, "OP " - ) - po.ovarre( - self.outfile, - "FW or Blanket inlet (pump oulet) pressure (Pa)", - "(coolpin)", - pres_coolant_pump_outlet, - "OP ", - ) - po.ovarre( - self.outfile, - "FW or Blanket oulet (pump inlet) pressure (Pa)", - "(pres_coolant_pump_inlet)", - pres_coolant_pump_inlet, - "OP ", - ) - po.ovarre( - self.outfile, - "FW or Blanket total pressure drop (Pa)", - "(dpres_coolant)", - dpres_coolant, - "OP ", - ) - po.ovarre( - self.outfile, - "Mass flow rate in (kg/s) = ", - "(mf)", - mflow_coolant_total, - "OP ", - ) + po.oblnkl(self.outfile) + po.ocmmnt(self.outfile, "----------------------------") + po.oblnkl(self.outfile) - return pumppower + po.ovarre( + self.outfile, + "Mass flow rate of inboard blanket coolant (kg/s)", + "(mflow_blkt_inboard_coolant)", + self.data.blanket.mflow_blkt_inboard_coolant, + "OP ", + ) + po.ovarre( + self.outfile, + "Mass flow rate of inboard blanket coolant in single channel (kg/s)", + "(mflow_blkt_inboard_coolant_channel)", + self.data.blanket.mflow_blkt_inboard_coolant_channel, + "OP ", + ) + po.ovarre( + self.outfile, + "Velocity of inboard blanket coolant in single channel (m/s)", + "(vel_blkt_inboard_coolant)", + self.data.blanket.vel_blkt_inboard_coolant, + "OP ", + ) class OutboardBlanket(BlanketLibrary): @@ -3625,7 +3596,12 @@ def calculate_basic_geometry(self): """Calculate basic outboard blanket geometry""" self.component_volumes() - dia_blkt_channel = self.pipe_hydraulic_diameter(i_channel_shape=1) + dia_blkt_channel = pipe_hydraulic_diameter( + i_channel_shape=1, + radius_fw_channel=self.data.fwbs.radius_fw_channel, + a_bz_liq=self.data.fwbs.a_bz_liq, + b_bz_liq=self.data.fwbs.b_bz_liq, + ) self.data.fwbs.radius_blkt_channel = dia_blkt_channel / 2 ( self.data.fwbs.radius_blkt_channel_90_bend, @@ -3756,7 +3732,12 @@ def calculate_basic_geometry(self): """Calculate basic inboard blanket geometry""" self.component_volumes() - dia_blkt_channel = self.pipe_hydraulic_diameter(i_channel_shape=1) + dia_blkt_channel = pipe_hydraulic_diameter( + i_channel_shape=1, + radius_fw_channel=self.data.fwbs.radius_fw_channel, + a_bz_liq=self.data.fwbs.a_bz_liq, + b_bz_liq=self.data.fwbs.b_bz_liq, + ) self.data.fwbs.radius_blkt_channel = dia_blkt_channel / 2 ( self.data.fwbs.radius_blkt_channel_90_bend, diff --git a/process/models/blankets/dcll.py b/process/models/blankets/dcll.py index 82616d4293..e2cfa26c7c 100644 --- a/process/models/blankets/dcll.py +++ b/process/models/blankets/dcll.py @@ -10,6 +10,9 @@ calculate_pipe_bend_radius, pumping_powers_as_fractions, ) +from process.models.engineering.pumping import ( + pipe_hydraulic_diameter, +) from process.models.power import PumpingPowerModelTypes @@ -126,7 +129,12 @@ def run(self, output: bool = False): self.data.blanket.deg_blkt_inboard_poloidal_plasma / 360.0 ) - dia_blkt_channel = self.pipe_hydraulic_diameter(i_channel_shape=1) + dia_blkt_channel = pipe_hydraulic_diameter( + i_channel_shape=1, + radius_fw_channel=self.data.fwbs.radius_fw_channel, + a_bz_liq=self.data.fwbs.a_bz_liq, + b_bz_liq=self.data.fwbs.b_bz_liq, + ) self.data.fwbs.radius_blkt_channel = dia_blkt_channel / 2 ( self.data.fwbs.radius_blkt_channel_90_bend, @@ -370,8 +378,8 @@ def dcll_power_and_heating(self, output: bool): ) elif i_p_coolant_pumping in { - PumpingPowerModelTypes.MECHANICAL, - PumpingPowerModelTypes.MECHANICAL_WITH_PRESSURE_DROP, + PumpingPowerModelTypes.CALCULATE_PRESSURE_DROP, + PumpingPowerModelTypes.INPUT_PRESSURE_DROP, }: # Mechanical pumping power is calculated for first wall and blanket self.thermo_hydraulic_model(output=output) @@ -396,8 +404,8 @@ def dcll_power_and_heating(self, output: bool): po.osubhd(self.outfile, "DCLL model: Thermal-hydraulics Component Totals") if self.data.fwbs.i_p_coolant_pumping not in { - PumpingPowerModelTypes.MECHANICAL, - PumpingPowerModelTypes.MECHANICAL_WITH_PRESSURE_DROP, + PumpingPowerModelTypes.CALCULATE_PRESSURE_DROP, + PumpingPowerModelTypes.INPUT_PRESSURE_DROP, }: po.ovarre( self.outfile, diff --git a/process/models/blankets/hcpb.py b/process/models/blankets/hcpb.py index 5a300557f4..ee7cc67ad3 100644 --- a/process/models/blankets/hcpb.py +++ b/process/models/blankets/hcpb.py @@ -15,7 +15,10 @@ calculate_pipe_bend_radius, pumping_powers_as_fractions, ) -from process.models.engineering.pumping import CoolantType +from process.models.engineering.pumping import ( + CoolantType, + pipe_hydraulic_diameter, +) from process.models.power import PumpingPowerModelTypes from process.models.tfcoil.base import TFConductorModel @@ -73,7 +76,12 @@ def run(self, output: bool = False): self.data.blanket.deg_blkt_inboard_poloidal_plasma / 360.0 ) - dia_blkt_channel = self.pipe_hydraulic_diameter(i_channel_shape=1) + dia_blkt_channel = pipe_hydraulic_diameter( + i_channel_shape=1, + radius_fw_channel=self.data.fwbs.radius_fw_channel, + a_bz_liq=self.data.fwbs.a_bz_liq, + b_bz_liq=self.data.fwbs.b_bz_liq, + ) self.data.fwbs.radius_blkt_channel = dia_blkt_channel / 2 ( self.data.fwbs.radius_blkt_channel_90_bend, @@ -829,7 +837,7 @@ def powerflow_calc(self, output: bool): p_div_rad_total_mw=self.data.fwbs.p_div_rad_total_mw, ) - case PumpingPowerModelTypes.MECHANICAL: + case PumpingPowerModelTypes.CALCULATE_PRESSURE_DROP: # Calculate the required material properties of the FW and BB coolant. self.primary_coolant_properties(output=output) # Mechanical pumping power is calculated for first wall and blanket @@ -852,8 +860,56 @@ def powerflow_calc(self, output: bool): + self.data.fwbs.p_div_rad_total_mw ) ) + if output: + po.ovarre( + self.outfile, + "Mechanical pumping power for FW and blanket cooling loop " + "including heat exchanger (MW)", + "(p_fw_blkt_coolant_pump_mw)", + self.data.primary_pumping.p_fw_blkt_coolant_pump_mw, + "OP ", + ) + po.ovarre( + self.outfile, + "Pumping power for FW and Blanket multiplier factor", + "(f_p_fw_blkt_pump)", + self.data.primary_pumping.f_p_fw_blkt_pump, + "IP ", + ) + po.ovarre( + self.outfile, + "Mechanical pumping power for divertor (MW)", + "(p_div_coolant_pump_mw)", + self.data.heat_transport.p_div_coolant_pump_mw, + "OP ", + ) + po.ovarre( + self.outfile, + "Mechanical pumping power for shield and vacuum vessel (MW)", + "(p_shld_coolant_pump_mw)", + self.data.heat_transport.p_shld_coolant_pump_mw, + "OP ", + ) + po.ovarre( + self.outfile, + "Radius of blanket cooling channels (m)", + "(radius_blkt_channel)", + self.data.fwbs.radius_blkt_channel, + ) + po.ovarre( + self.outfile, + "Radius of 90 degree coolant channel bend (m)", + "(radius_blkt_channel_90_bend)", + self.data.fwbs.radius_blkt_channel_90_bend, + ) + po.ovarre( + self.outfile, + "Radius of 180 degree coolant channel bend (m)", + "(radius_blkt_channel_180_bend)", + self.data.fwbs.radius_blkt_channel_180_bend, + ) - case PumpingPowerModelTypes.MECHANICAL_WITH_PRESSURE_DROP: + case PumpingPowerModelTypes.INPUT_PRESSURE_DROP: # Issue #503 # Mechanical pumping power is calculated using specified pressure drop # for first wall and blanket circuit, including heat exchanger and pipes @@ -1551,7 +1607,7 @@ def write_output(self): if ( self.data.fwbs.i_p_coolant_pumping - != PumpingPowerModelTypes.MECHANICAL_WITH_PRESSURE_DROP + != PumpingPowerModelTypes.INPUT_PRESSURE_DROP ): po.ovarre( self.outfile, diff --git a/process/models/engineering/pumping.py b/process/models/engineering/pumping.py index ea6c6fb0c8..fd134fc9ca 100644 --- a/process/models/engineering/pumping.py +++ b/process/models/engineering/pumping.py @@ -1,11 +1,17 @@ """Engineering models for pumping system analysis.""" import logging +from dataclasses import dataclass from enum import IntEnum, unique from types import DynamicClassAttribute import numpy as np +from process.core.coolprop_interface import FluidProperties +from process.core.exceptions import ProcessValueError + +logger = logging.getLogger(__name__) + @unique class CoolantType(IntEnum): @@ -41,11 +47,234 @@ def full_name(self): return self._full_name_ -logger = logging.getLogger(__name__) +@dataclass +class CoolantFrictionLossParameters: + """Parameters for calculating coolant friction losses.""" + + dpres_total: float + """Total pressure drop across the coolant channel [Pa]""" + dpres_straight: float + """Pressure drop due to straight length of the coolant channel [Pa]""" + dpres_90: float + """Pressure drop due to 90 degree bends in the coolant channel [Pa]""" + dpres_90_total: float + """Total pressure drop due to 90 degree bends in the coolant channel [Pa]""" + dpres_180: float + """Pressure drop due to 180 degree bends in the coolant channel [Pa]""" + dpres_180_total: float + """Total pressure drop due to 180 degree bends in the coolant channel [Pa]""" + dpres_bends_total: float + """Total pressure drop due to bends in the coolant channel [Pa]""" + reynolds_number: float + """Reynolds number of the coolant flow in the channel""" + darcy_friction_factor: float + """Darcy friction factor for the coolant flow in the channel""" + f_straight: float + """Friction factor for straight length of the coolant channel""" + len_straight: float + """Length of straight sections of the coolant channel [m]""" + f_elbow_90: float + """Friction factor for 90 degree bends in the coolant channel""" + f_elbow_180: float + """Friction factor for 180 degree bends in the coolant channel""" + + +def coolant_friction_pressure_drop( + i_ps: int, + radius_pipe_90_deg_bend: float, + radius_pipe_180_deg_bend: float, + n_pipe_90_deg_bends: float, + n_pipe_180_deg_bends: float, + len_pipe: float, + den_coolant: float, + visc_coolant: float, + vel_coolant: float, + roughness_channel: float, + radius_channel: float, + a_bz_liq: float, + b_bz_liq: float, +) -> CoolantFrictionLossParameters: + """Pressure drops are calculated for a pipe with a number of 90 + and 180 degree bends. The pressure drop due to frictional forces along + the total straight length of the pipe is calculated, then the pressure + drop due to the bends is calculated. The total pressure drop is the sum + of all contributions. + + Parameters + ---------- + i_ps : + switch for primary or secondary coolant + radius_pipe_90_deg_bend : + radius of 90 degree bend in pipe [m] + radius_pipe_180_deg_bend : + radius of 180 degree bend in pipe [m] + n_pipe_90_deg_bends : + number of 90 degree bends in the pipe + n_pipe_180_deg_bends : + number of 180 degree bends in the pipe + len_pipe : + total flow length along pipe [m] + den_coolant : + coolant density [kg/m³] + visc_coolant : + coolant viscosity [Pa s] + vel_coolant : + coolant flow velocity [m/s] + roughness_channel : + roughness of the channel wall (ε) [m] + radius_channel : + radius of the channel [m] + a_bz_liq : + width of the breeding blanket coolant channel [m] + b_bz_liq : + height of the breeding blanket coolant channel [m] + + + Returns + ------- + : + `CoolantFrictionLossParameters` dataclass containing: + - Total pressure drop due to friction (Pa) + - Pressure drop due to straight sections (Pa) + - Pressure drop due to 90 degree bends (Pa) + - Pressure drop due to 180 degree bends (Pa) + - Reynolds number + - Darcy friction factor + - Pressure drop coefficient for straight sections + - Pressure drop coefficient for 90 degree bends + - Pressure drop coefficient for 180 degree bends + + Notes + ----- + Darcy-Weisbach Equation (straight pipe): + + ΔP = λ * L/D * (p 〈v〉²) / 2 + + λ - Darcy friction factor, L - pipe length, D - hydraulic diameter, + p - fluid density, 〈v〉 - fluid flow average velocity + + This function also calculates pressure drop equations for elbow bends, + with modified coefficients. + + N.B. Darcy friction factor is estimated from the Haaland approximation. + """ + # Calculate hydraulic dimater for round or retancular pipe (m) + dia_pipe = pipe_hydraulic_diameter( + i_channel_shape=i_ps, + radius_fw_channel=radius_channel, + a_bz_liq=a_bz_liq, + b_bz_liq=b_bz_liq, + ) + + # Reynolds number (Re) + reynolds_number = calculate_reynolds_number( + den_coolant=den_coolant, + vel_coolant=vel_coolant, + radius_channel=dia_pipe / 2, + visc_coolant=visc_coolant, + ) + + # Calculate Darcy friction factor + # N.B. friction function Uses Haaland approx. which assumes a filled + # circular pipe. + # Use dh which allows us to do fluid calculations for non-circular tubes + # (dh is estimate appropriate for fully developed flow). + + darcy_friction_factor = darcy_friction_haaland( + reynolds=reynolds_number, + roughness_channel=roughness_channel, + dia_channel=dia_pipe, + ) + + # Pressure drop coefficient + + # Straight section + f_straight = darcy_friction_factor * len_pipe / dia_pipe + + # 90 degree elbow pressure drop coefficient + f_elbow_90 = elbow_coeff( + radius_pipe_elbow=radius_pipe_90_deg_bend, + deg_pipe_elbow=90.0, + darcy_friction=darcy_friction_factor, + dia_pipe=dia_pipe, + ) + + # 180 degree elbow pressure drop coefficient + f_elbow_180 = elbow_coeff( + radius_pipe_elbow=radius_pipe_180_deg_bend, + deg_pipe_elbow=180.0, + darcy_friction=darcy_friction_factor, + dia_pipe=dia_pipe, + ) + + # Pressure drop due to friction in straight sections + dpres_straight = f_straight * 0.5 * den_coolant * vel_coolant**2 + + # Pressure drop due to 90 and 180 degree bends + dpres_90 = f_elbow_90 * 0.5 * den_coolant * vel_coolant**2 + dpres_90_total = n_pipe_90_deg_bends * dpres_90 + dpres_180 = f_elbow_180 * 0.5 * den_coolant * vel_coolant**2 + dpres_180_total = n_pipe_180_deg_bends * dpres_180 + + dpres_bends_total = dpres_90_total + dpres_180_total + + # Total pressure drop (Pa) + dpres_total = dpres_straight + dpres_bends_total + + return CoolantFrictionLossParameters( + dpres_total=dpres_total, + dpres_straight=dpres_straight, + dpres_90=dpres_90, + dpres_90_total=dpres_90_total, + dpres_180=dpres_180, + dpres_180_total=dpres_180_total, + dpres_bends_total=dpres_bends_total, + reynolds_number=reynolds_number, + darcy_friction_factor=darcy_friction_factor, + f_straight=f_straight, + len_straight=len_pipe, + f_elbow_90=f_elbow_90, + f_elbow_180=f_elbow_180, + ) + + +def pipe_hydraulic_diameter( + i_channel_shape, radius_fw_channel: float, a_bz_liq: float, b_bz_liq: float +) -> float: + """Caculate the hydraulic diameter [m] for a given coolant pipe size/shape. + + + Parameters + ---------- + i_channel_shape : + switch for circular or rectangular channel crossection. + Shape depends on whether primary or secondary coolant is used. + 1 = circular channel (primary coolant) + 2 = rectangular channel (secondary coolant) + + Returns + ------- + : + Hydraulic diameter of the coolant channel [m]. + + Raises + ------ + ProcessValueError + If i_channel_shape is not 1 or 2. + """ + # If primary coolant then circular channels assumed + if i_channel_shape == 1: + return 2.0 * radius_fw_channel + + # If secondary coolant then rectangular channels assumed + if i_channel_shape == 2: + return 2 * a_bz_liq * b_bz_liq / (a_bz_liq + b_bz_liq) + + raise ProcessValueError(f"i_channel_shape ={i_channel_shape} is an invalid option.") def darcy_friction_haaland( - reynolds: float, roughness_channel: float, radius_channel: float + reynolds: float, roughness_channel: float, dia_channel: float ) -> float: """Calculate Darcy friction factor using the Haaland equation. @@ -54,9 +283,9 @@ def darcy_friction_haaland( reynolds: Reynolds number. roughness_channel: - Roughness of the first wall coolant channel (m). - radius_channel: - Radius of the first wall coolant channel (m). + Roughness of the coolant channel [m]. + dia_channel: + Hydraulic diameter of the coolant channel [m]. Returns ------- @@ -65,20 +294,147 @@ def darcy_friction_haaland( Notes ----- - The Haaland equation is an approximation to the implicit Colebrook-White equation. - It is used to calculate the Darcy friction factor for turbulent flow in pipes. + - The Haaland equation is an approximation to the implicit Colebrook-White equation. + - It is used to calculate the Darcy friction factor for turbulent flow in pipes. References ---------- [1] https://en.wikipedia.org/wiki/Darcy_friction_factor_formulae#Haaland_equation """ # Bracketed term in Haaland equation - bracket = (roughness_channel / radius_channel / 3.7) ** 1.11 + 6.9 / reynolds + bracket = (roughness_channel / dia_channel / 3.7) ** 1.11 + 6.9 / reynolds # Calculate Darcy friction factor return (1.8 * np.log10(bracket)) ** (-2) +def coolant_pumping_power( + i_liquid_breeder: int, + temp_coolant_pump_outlet: float, + temp_coolant_pump_inlet: float, + pres_coolant_pump_inlet: float, + dpres_coolant: float, + mflow_coolant_total: float, + i_coolant_type: int, + den_coolant: float, + etaiso: float, + etaiso_liq: float, +) -> float: + """Calculate the coolant pumping power in MW for the first wall (FW) or breeding + blanket (BZ) coolant. + + Parameters + ---------- + i_liquid_breeder : int + Switch for primary coolant or secondary coolant/breeder + (1=primary He/H2O, 2=secondary PbLi/Li). + temp_coolant_pump_outlet : float + Pump outlet temperature [K]. + temp_coolant_pump_inlet : float + Pump inlet temperature [K]. + pres_coolant_pump_inlet : float + Pump inlet pressure [Pa]. + dpres_coolant : float + Coolant pressure drop [Pa]. + mflow_coolant_total : float + Total coolant mass flow rate [kg/s]. + i_coolant_type : int + Type of FW/blanket coolant (e.g., 1=Helium, 2=Water) + den_coolant : float + Density of coolant or liquid breeder [kg/m³]. + etaiso : float + Isentropic efficiency of the pump for primary coolant. + etaiso_liq : float + Isentropic efficiency of the pump for secondary coolant/breeder. + + Returns + ------- + :float + Pumping power [MW]. + + Raises + ------ + ProcessValueError + If the pressure drops in the coolant are too large to be feasible. + + References + ---------- + [1] Idel'Cik, I. E. (1969), Memento des pertes de charge + [2] S.P. Sukhatme (2005), A Textbook on Heat Transfer + """ + # Pump outlet pressure [Pa] + # The pump adds the pressure lost going through the coolant channels back + pres_coolant_pump_outlet = pres_coolant_pump_inlet + dpres_coolant + + # If calculating for primary coolant + if i_liquid_breeder == 1: + # The pumping power is be calculated in the most general way, + # using enthalpies before and after the pump. + + pump_outlet_fluid_properties = FluidProperties.of( + fluid_name=CoolantType(i_coolant_type).full_name, + temperature=temp_coolant_pump_outlet, + pressure=pres_coolant_pump_outlet, + ) + + # Real gamma (cp/cv) at the pump outlet state, so liquid water (γ~1) # noqa: RUF003 + # and superheated steam (γ~1.3) are both handled correctly # noqa: RUF003 + gamma = ( + pump_outlet_fluid_properties.specific_heat_const_p + / pump_outlet_fluid_properties.specific_heat_const_v + ) + + # Assume isentropic pump so that s1 = s2 + s1 = pump_outlet_fluid_properties.entropy + + # Get specific enthalpy at the outlet [J/kg] before pump using pressure and + # entropy s1 + pump_inlet_fluid_properties = FluidProperties.of( + fluid_name=CoolantType(i_coolant_type).full_name, + pressure=pres_coolant_pump_inlet, + entropy=s1, + ) + + # Pumping power [MW] is given by enthalpy change, with a correction for + # the isentropic efficiency of the pump. + fp = ( + temp_coolant_pump_outlet + * ( + 1 + - (pres_coolant_pump_outlet / pres_coolant_pump_inlet) + ** -((gamma - 1) / gamma) + ) + / (etaiso * (temp_coolant_pump_inlet - temp_coolant_pump_outlet)) + ) + pumppower = ( + 1e-6 + * mflow_coolant_total + * ( + pump_outlet_fluid_properties.enthalpy + - pump_inlet_fluid_properties.enthalpy + ) + / etaiso + ) / (1 - fp) + + # If calculating for secondary coolant/breeder... + else: + # Calculate specific volume + spec_vol = 1 / den_coolant + + # Liquid metal breeders (PbLi/Li) are incompressible, so there is no + # gas-law reheat effect from compression: fp is negligible (gamma~1). + fp = 0.0 + pumppower = ( + 1e-6 * mflow_coolant_total * spec_vol * dpres_coolant / etaiso_liq + ) / (1 - fp) + + # Error for dpres_coolant too large + if fp >= 1: + raise ProcessValueError("Pressure drops in coolant are too large to be feasible") + + return pumppower + + def gnielinski_heat_transfer_coefficient( mflux_coolant: float, den_coolant: float, @@ -93,28 +449,28 @@ def gnielinski_heat_transfer_coefficient( Parameters ---------- mflux_coolant: - Coolant mass flux in a single channel (kg/m²/s). + Coolant mass flux in a single channel [kg/m²/s]. den_coolant: - Coolant density (average of inlet and outlet) (kg/m³). + Coolant density (average of inlet and outlet) [kg/m³]. radius_channel: - Coolant pipe radius (m). + Coolant pipe radius [m]. heatcap_coolant: - Coolant specific heat capacity (average of inlet and outlet) (J/kg/K). + Coolant specific heat capacity (average of inlet and outlet) [J/kg/K]. visc_coolant: - Coolant viscosity (average of inlet and outlet) (Pa.s). + Coolant viscosity (average of inlet and outlet) [Pa.s]. thermcond_coolant: - Thermal conductivity of coolant (average of inlet and outlet) (W/m.K). + Thermal conductivity of coolant (average of inlet and outlet) [W/m.K]. roughness_channel: - Roughness of the coolant channel (m). + Roughness of the coolant channel [m]. Returns ------- : - Heat transfer coefficient (W/m²K). + Heat transfer coefficient [W/m²K]. Notes ----- - Gnielinski correlation. Ignore the distinction between wall and + - Gnielinski correlation. Ignore the distinction between wall and bulk temperatures. Valid for: 3000 < Re < 5e6, 0.5 < Pr < 2000 References @@ -128,7 +484,7 @@ def gnielinski_heat_transfer_coefficient( # Calculate flow velocity (m/s) vel_coolant = mflux_coolant / den_coolant - # Calculate Reynolds number + # Calculate Reynolds number (Re) reynolds = calculate_reynolds_number( den_coolant=den_coolant, vel_coolant=vel_coolant, @@ -136,17 +492,17 @@ def gnielinski_heat_transfer_coefficient( visc_coolant=visc_coolant, ) - # Calculate Prandtl number + # Calculate Prandtl number (Pr) pr = heatcap_coolant * visc_coolant / thermcond_coolant # Calculate Darcy friction factor, using Haaland equation f = darcy_friction_haaland( reynolds=reynolds, roughness_channel=roughness_channel, - radius_channel=radius_channel, + dia_channel=2 * radius_channel, ) - # Calculate the Nusselt number + # Calculate the Nusselt number (Nu) nusselt = ( (f / 8.0) * (reynolds - 1000.0) @@ -154,7 +510,7 @@ def gnielinski_heat_transfer_coefficient( / (1 + 12.7 * np.sqrt(f / 8.0) * (pr ** (2 / 3) - 1.0)) ) - # Calculate the heat transfer coefficient (W/m^2K) + # Calculate the heat transfer coefficient [W/m²K] heat_transfer_coefficient = nusselt * thermcond_coolant / (2.0 * radius_channel) # Check that Reynolds number is in valid range for the Gnielinski correlation @@ -174,25 +530,28 @@ def gnielinski_heat_transfer_coefficient( def calculate_reynolds_number( - den_coolant: float, vel_coolant: float, radius_channel: float, visc_coolant: float + den_coolant: float, + vel_coolant: float, + radius_channel: float, + visc_coolant: float, ) -> float: - """Calculate Reynolds number for flow in a pipe. + """Calculate Reynolds number (Re) for flow in a pipe. Parameters ---------- den_coolant: - Coolant density (average of inlet and outlet) (kg/m³). + Coolant density (average of inlet and outlet) [kg/m³]. vel_coolant: - Coolant velocity in a single channel (m/s). + Coolant velocity in a single channel [m/s]. radius_channel: - Coolant pipe radius (m). + Coolant pipe radius [m]. visc_coolant: - Coolant viscosity (average of inlet and outlet) (Pa.s). + Coolant viscosity (average of inlet and outlet) [Pa.s]. Returns ------- : - Reynolds number. + Reynolds number (Re). """ # Calculate pipe diameter (m) @@ -200,3 +559,108 @@ def calculate_reynolds_number( # Calculate Reynolds number return den_coolant * vel_coolant * diameter / visc_coolant + + +def elbow_coeff( + radius_pipe_elbow: float, + deg_pipe_elbow: float, + darcy_friction: float, + dia_pipe: float, +) -> float: + """Calculates elbow bend coefficients for pressure drop calculations. + + Parameters + ---------- + radius_pipe_elbow : float + Pipe elbow radius [m]. + deg_pipe_elbow : float + Pipe elbow angle [degrees] + darcy_friction : float + Darcy friction factor + dia_pipe : float + Pipe diameter [m] + + Returns + ------- + :float + Elbow coefficient for pressure drop calculation + + Raises + ------ + ProcessValueError + If deg_pipe_elbow is between 70 and 100 degrees, as no formula is + available for this range. + + References + ---------- + [1] Idel'Cik, I. E. (1969), Memento des pertes de charge, + Collection de la Direction des Etudes et Recherches d'Electricité de France. + """ + if deg_pipe_elbow == 90: + a = 1.0 + elif deg_pipe_elbow < 70: + a = 0.9 * np.sin(deg_pipe_elbow * np.pi / 180.0) + elif deg_pipe_elbow > 100: + a = 0.7 + (0.35 * np.sin((deg_pipe_elbow / 90.0) * (np.pi / 180.0))) + else: + raise ProcessValueError( + "No formula for 70 <= elbow angle(deg) <= 100, only 90 deg option available " + "in this range." + ) + + r_ratio = radius_pipe_elbow / dia_pipe + + if r_ratio > 1: + b = 0.21 / r_ratio**0.5 + elif r_ratio < 1: + b = 0.21 / r_ratio**2.5 + else: + b = 0.21 + + # Singularity + ximt = a * b + + # Friction + xift = ( + (np.pi / 180.0) + * darcy_friction + * (radius_pipe_elbow / dia_pipe) + * deg_pipe_elbow + ) + + # Elbow Coefficient + return ximt + xift + + +def calculate_required_mass_flow_rate( + p_heat_total: float, + heatcap_coolant: float, + temp_in_coolant: float, + temp_out_coolant: float, +) -> float: + """Calculate the required mass flow rate of coolant to remove the specified heat + load, due to the fundamental energy balance formula. + + Parameters + ---------- + p_heat_total: + Total heat load to be removed [W]. + heatcap_coolant: + Specific heat capacity of the coolant [J/kg/K]. + temp_in_coolant: + Inlet temperature of the coolant [K]. + temp_out_coolant: + Outlet temperature of the coolant [K]. + + Returns + ------- + :float + Required mass flow rate of the coolant [kg/s]. + + Notes + ----- + - The heat capacity is assumed to be constant over the temperature range of the + coolant. + + """ + return p_heat_total / (heatcap_coolant * (temp_out_coolant - temp_in_coolant)) diff --git a/process/models/fw.py b/process/models/fw.py index 44fb8c1503..2f134dced1 100644 --- a/process/models/fw.py +++ b/process/models/fw.py @@ -24,6 +24,12 @@ logger = logging.getLogger(__name__) +N_FW_PIPE_90_DEG_BENDS = 2 +"Number of 90 degree bends in first wall coolant channels." +N_FW_PIPE_180_DEG_BENDS = 0 +"Number of 180 degree bends in first wall coolant channels." + + class FirstWall(Model): """Calculate the first wall parameters""" @@ -782,7 +788,13 @@ def output_fw_geometry(self): def output_fw_pumping(self): """Outputs the first wall pumping details to the output file.""" po.oheadr(self.outfile, "First wall pumping") - + po.ovarre( + self.outfile, + "Pumping power calculation option", + "(i_p_coolant_pumping)", + self.data.fwbs.i_p_coolant_pumping, + "OP ", + ) po.ovarre( self.outfile, "First wall coolant type", diff --git a/process/models/power.py b/process/models/power.py index b1eb59308c..2c8bfeec08 100644 --- a/process/models/power.py +++ b/process/models/power.py @@ -22,8 +22,8 @@ class PumpingPowerModelTypes(IntEnum): USER_INPUT = 0 FRACTION_OF_HEAT = 1 - MECHANICAL = 2 - MECHANICAL_WITH_PRESSURE_DROP = 3 + CALCULATE_PRESSURE_DROP = 2 + INPUT_PRESSURE_DROP = 3 logger = logging.getLogger(__name__) @@ -822,8 +822,8 @@ def component_thermal_powers(self): """ i_p_coolant_pumping = PumpingPowerModelTypes(self.data.fwbs.i_p_coolant_pumping) if i_p_coolant_pumping not in { - PumpingPowerModelTypes.MECHANICAL, - PumpingPowerModelTypes.MECHANICAL_WITH_PRESSURE_DROP, + PumpingPowerModelTypes.CALCULATE_PRESSURE_DROP, + PumpingPowerModelTypes.INPUT_PRESSURE_DROP, }: self.data.primary_pumping.p_fw_blkt_coolant_pump_mw = ( self.data.heat_transport.p_fw_coolant_pump_mw @@ -956,7 +956,7 @@ def component_thermal_powers(self): # Heat removal from first wall and divertor (MW) (only used in costs.f90) i_p_coolant_pumping = PumpingPowerModelTypes(self.data.fwbs.i_p_coolant_pumping) - if i_p_coolant_pumping != PumpingPowerModelTypes.MECHANICAL_WITH_PRESSURE_DROP: + if i_p_coolant_pumping != PumpingPowerModelTypes.INPUT_PRESSURE_DROP: self.data.heat_transport.p_fw_div_heat_deposited_mw = ( self.data.power.p_fw_heat_deposited_mw + self.data.power.p_div_heat_deposited_mw @@ -1694,7 +1694,7 @@ def plant_electric_production(self): ) if ( self.data.fwbs.i_blkt_dual_coolant > 0 - and i_p_coolant_pumping == PumpingPowerModelTypes.MECHANICAL + and i_p_coolant_pumping == PumpingPowerModelTypes.CALCULATE_PRESSURE_DROP ): self.data.heat_transport.p_plant_electric_gross_mw = ( ( diff --git a/tests/unit/models/blankets/test_blanket_library.py b/tests/unit/models/blankets/test_blanket_library.py index 3f05117eff..c30b798dcb 100644 --- a/tests/unit/models/blankets/test_blanket_library.py +++ b/tests/unit/models/blankets/test_blanket_library.py @@ -18,6 +18,16 @@ def blanket_library(process_models): return process_models.blanket_library +@pytest.fixture +def pumping(process_models): + """Fixture to get the Physics instance from process_models. + + :returns: initialised Physics object + :rtype: process.physics.Physics + """ + return process_models.pumping + + class PrimaryCoolantPropertiesParam(NamedTuple): i_fw_coolant_type: Any = None @@ -29,9 +39,9 @@ class PrimaryCoolantPropertiesParam(NamedTuple): den_fw_coolant: Any = None - cp_fw: Any = None + heatcap_pres_fw_coolant_average: Any = None - cv_fw: Any = None + heatcap_vol_fw_coolant_average: Any = None i_blkt_coolant_type: Any = None @@ -47,9 +57,9 @@ class PrimaryCoolantPropertiesParam(NamedTuple): visc_blkt_coolant: Any = None - cp_bl: Any = None + heatcap_pres_blkt_coolant_average: Any = None - cv_bl: Any = None + heatcap_vol_blkt_coolant_average: Any = None visc_fw_coolant: Any = None @@ -81,8 +91,8 @@ class PrimaryCoolantPropertiesParam(NamedTuple): temp_fw_coolant_out=773, pres_fw_coolant=8000000, den_fw_coolant=0, - cp_fw=0, - cv_fw=0, + heatcap_pres_fw_coolant_average=0, + heatcap_vol_fw_coolant_average=0, i_blkt_coolant_type=CoolantType.HELIUM, temp_blkt_coolant_in=573, temp_blkt_coolant_out=773, @@ -90,8 +100,8 @@ class PrimaryCoolantPropertiesParam(NamedTuple): den_blkt_coolant=0, i_blkt_dual_coolant=2, visc_blkt_coolant=0, - cp_bl=0, - cv_bl=0, + heatcap_pres_blkt_coolant_average=0, + heatcap_vol_blkt_coolant_average=0, visc_fw_coolant=0, i_fw_blkt_shared_coolant=0, expected_den_fw_coolant=5.6389735407435868, @@ -109,8 +119,8 @@ class PrimaryCoolantPropertiesParam(NamedTuple): temp_fw_coolant_out=773, pres_fw_coolant=8000000, den_fw_coolant=5.6389735407435868, - cp_fw=5188.5588430173211, - cv_fw=3123.5687263525392, + heatcap_pres_fw_coolant_average=5188.5588430173211, + heatcap_vol_fw_coolant_average=3123.5687263525392, i_blkt_coolant_type=CoolantType.HELIUM, temp_blkt_coolant_in=573, temp_blkt_coolant_out=773, @@ -118,8 +128,8 @@ class PrimaryCoolantPropertiesParam(NamedTuple): den_blkt_coolant=5.6389735407435868, i_blkt_dual_coolant=2, visc_blkt_coolant=3.5036293160410249e-05, - cp_bl=5188.5588430173211, - cv_bl=3123.5687263525392, + heatcap_pres_blkt_coolant_average=5188.5588430173211, + heatcap_vol_blkt_coolant_average=3123.5687263525392, visc_fw_coolant=3.5036293160410249e-05, i_fw_blkt_shared_coolant=0, expected_den_fw_coolant=5.6389735407435868, @@ -179,11 +189,15 @@ def test_primary_coolant_properties( ) monkeypatch.setattr( - blanket_library.data.fwbs, "cp_fw", primarycoolantpropertiesparam.cp_fw + blanket_library.data.fwbs, + "heatcap_pres_fw_coolant_average", + primarycoolantpropertiesparam.heatcap_pres_fw_coolant_average, ) monkeypatch.setattr( - blanket_library.data.fwbs, "cv_fw", primarycoolantpropertiesparam.cv_fw + blanket_library.data.fwbs, + "heatcap_vol_fw_coolant_average", + primarycoolantpropertiesparam.heatcap_vol_fw_coolant_average, ) monkeypatch.setattr( @@ -229,11 +243,15 @@ def test_primary_coolant_properties( ) monkeypatch.setattr( - blanket_library.data.fwbs, "cp_bl", primarycoolantpropertiesparam.cp_bl + blanket_library.data.fwbs, + "heatcap_pres_blkt_coolant_average", + primarycoolantpropertiesparam.heatcap_pres_blkt_coolant_average, ) monkeypatch.setattr( - blanket_library.data.fwbs, "cv_bl", primarycoolantpropertiesparam.cv_bl + blanket_library.data.fwbs, + "heatcap_vol_blkt_coolant_average", + primarycoolantpropertiesparam.heatcap_vol_blkt_coolant_average, ) monkeypatch.setattr( @@ -254,11 +272,11 @@ def test_primary_coolant_properties( primarycoolantpropertiesparam.expected_den_fw_coolant, rel=1e-4 ) - assert blanket_library.data.fwbs.cp_fw == pytest.approx( + assert blanket_library.data.fwbs.heatcap_pres_fw_coolant_average == pytest.approx( primarycoolantpropertiesparam.expected_cp_fw, rel=1e-4 ) - assert blanket_library.data.fwbs.cv_fw == pytest.approx( + assert blanket_library.data.fwbs.heatcap_vol_fw_coolant_average == pytest.approx( primarycoolantpropertiesparam.expected_cv_fw, rel=1e-4 ) @@ -270,11 +288,11 @@ def test_primary_coolant_properties( primarycoolantpropertiesparam.expected_visc_blkt_coolant, rel=1e-4 ) - assert blanket_library.data.fwbs.cp_bl == pytest.approx( + assert blanket_library.data.fwbs.heatcap_pres_blkt_coolant_average == pytest.approx( primarycoolantpropertiesparam.expected_cp_bl, rel=1e-4 ) - assert blanket_library.data.fwbs.cv_bl == pytest.approx( + assert blanket_library.data.fwbs.heatcap_vol_blkt_coolant_average == pytest.approx( primarycoolantpropertiesparam.expected_cv_bl, rel=1e-4 ) @@ -301,10 +319,8 @@ def test_deltap_tot_inboard_first_wall(monkeypatch, blanket_library): "label": "Inboard first wall", } - assert ( - pytest.approx(blanket_library.total_pressure_drop(False, **data)) - == 5884.982168510442 - ) + dpres_total, _ = blanket_library.total_pressure_drop(False, **data) + assert dpres_total == pytest.approx(5839.298437354936) def test_deltap_tot_outboard_blanket_breeder_liquid(monkeypatch, blanket_library): @@ -332,54 +348,8 @@ def test_deltap_tot_outboard_blanket_breeder_liquid(monkeypatch, blanket_library "label": "Outboard blanket breeder liquid", } - assert ( - pytest.approx(blanket_library.total_pressure_drop(False, **data)) - == 56.95922064419226 - ) - - -def test_pumppower_primary_helium(monkeypatch, blanket_library): - monkeypatch.setattr(blanket_library.data.fwbs, "etaiso", 0.9) - monkeypatch.setattr(blanket_library.data.fwbs, "etaiso_liq", 0.85) - - data = { - "i_liquid_breeder": 2, - "temp_coolant_pump_outlet": 570, - "temp_coolant_pump_inlet": 720, - "pres_coolant_pump_inlet": 1700000, - "dpres_coolant": 303517.3, - "mflow_coolant_total": 35677.7, - "i_coolant_type": 1, - "den_coolant": 9753.25, - "label": "Liquid Metal Breeder/Coolant", - } - - assert ( - pytest.approx(blanket_library.coolant_pumping_power(False, **data)) - == 1.8251284651310427 - ) - - -def test_pumppower_secondary_pb_li(monkeypatch, blanket_library): - monkeypatch.setattr(blanket_library.data.fwbs, "etaiso", 0.9) - monkeypatch.setattr(blanket_library.data.fwbs, "etaiso_liq", 0.85) - - data = { - "i_liquid_breeder": 1, - "temp_coolant_pump_outlet": 573, - "temp_coolant_pump_inlet": 773, - "pres_coolant_pump_inlet": 8000000, - "dpres_coolant": 20088.23, - "mflow_coolant_total": 956.3, - "i_coolant_type": CoolantType.HELIUM, - "den_coolant": 5.64, - "label": "First Wall and Blanket", - } - - assert ( - pytest.approx(blanket_library.coolant_pumping_power(False, **data), rel=1e-4) - == 3.2374845432302464 - ) + dpres_total, _ = blanket_library.total_pressure_drop(False, **data) + assert dpres_total == pytest.approx(56.66392220444523) class ComponentHalfHeightParam(NamedTuple): @@ -1018,114 +988,6 @@ def test_liquid_breeder_properties( ) -class PressureDropParam(NamedTuple): - radius_fw_channel: Any = None - radius_pipe_90_deg_bend: Any = None - radius_pipe_180_deg_bend: Any = None - a_bz_liq: Any = None - b_bz_liq: Any = None - roughness_fw_channel: Any = None - ip: Any = None - i_ps: Any = None - num_90: Any = None - num_180: Any = None - l_pipe: Any = None - den: Any = None - vsc: Any = None - vv: Any = None - label: Any = None - expected_pressure_drop_out: Any = None - - -@pytest.mark.parametrize( - "pressuredropparam", - [ - PressureDropParam( - radius_fw_channel=0.0060000000000000001, - radius_pipe_90_deg_bend=0.018, - radius_pipe_180_deg_bend=0.09, - a_bz_liq=0.20000000000000001, - b_bz_liq=0.20000000000000001, - roughness_fw_channel=9.9999999999999995e-07, - ip=0, - i_ps=1, - num_90=2, - num_180=0, - l_pipe=4, - den=10.405276820718059, - vsc=3.604452999475736e-05, - vv=32.753134225223164, - label="Inboard first wall", - expected_pressure_drop_out=36213.58989742931, - ), - PressureDropParam( - radius_fw_channel=1.0, - radius_pipe_90_deg_bend=1.0, - radius_pipe_180_deg_bend=1.0, - a_bz_liq=1.0, - b_bz_liq=1.0, - roughness_fw_channel=1e-6, - ip=0, - i_ps=2, - num_90=1.0, - num_180=1.0, - l_pipe=1.0, - den=1.0, - vsc=1.0, - vv=1.0, - label="label", - expected_pressure_drop_out=1.4325633520224854, - ), - ], -) -def test_pressure_drop(pressuredropparam, monkeypatch, blanket_library): - """ - Automatically generated Unit Test for pressure_drop. - - This test was generated using data from - blanket_files/large_tokamak_primary_pumping2.IN.DAT. - - :param pressuredropparam: the data used to mock and assert in this test. - :type pressuredropparam: pressuredropparam - - :param monkeypatch: pytest fixture used to mock module/class variables - :type monkeypatch: _pytest.monkeypatch.monkeypatch - """ - monkeypatch.setattr( - blanket_library.data.fwbs, - "radius_fw_channel", - pressuredropparam.radius_fw_channel, - ) - monkeypatch.setattr( - blanket_library.data.fwbs, "a_bz_liq", pressuredropparam.a_bz_liq - ) - monkeypatch.setattr( - blanket_library.data.fwbs, "b_bz_liq", pressuredropparam.b_bz_liq - ) - monkeypatch.setattr( - blanket_library.data.fwbs, - "roughness_fw_channel", - pressuredropparam.roughness_fw_channel, - ) - - pressure_drop_out = blanket_library.coolant_friction_pressure_drop( - i_ps=pressuredropparam.i_ps, - radius_pipe_90_deg_bend=pressuredropparam.radius_pipe_90_deg_bend, - radius_pipe_180_deg_bend=pressuredropparam.radius_pipe_180_deg_bend, - n_pipe_90_deg_bends=pressuredropparam.num_90, - n_pipe_180_deg_bends=pressuredropparam.num_180, - len_pipe=pressuredropparam.l_pipe, - den_coolant=pressuredropparam.den, - visc_coolant=pressuredropparam.vsc, - vel_coolant=pressuredropparam.vv, - label=pressuredropparam.label, - ) - - assert pressure_drop_out == pytest.approx( - pressuredropparam.expected_pressure_drop_out - ) - - class LiquidBreederPressureDropMhdParam(NamedTuple): i_blkt_liquid_breeder_channel_type: Any = None a_bz_liq: Any = None @@ -1590,43 +1452,6 @@ def test_calculate_elliptical_blkt_volumes( ) -def test_hydraulic_diameter(monkeypatch, blanket_library): - """ - Test for hydraulic_diameter function. - """ - # Set var values - monkeypatch.setattr(blanket_library.data.fwbs, "radius_fw_channel", 1.0) - monkeypatch.setattr(blanket_library.data.fwbs, "a_bz_liq", 1.0) - monkeypatch.setattr(blanket_library.data.fwbs, "b_bz_liq", 1.0) - - # hydraulic_diameter input = i_channel_shape: 1 = circle, 2 = rectangle - # 2.0D0*radius_fw_channel - assert blanket_library.pipe_hydraulic_diameter(1) == pytest.approx(2.0) - # 2*a_bz_liq*b_bz_liq/(a_bz_liq+b_bz_liq) - assert blanket_library.pipe_hydraulic_diameter(2) == pytest.approx(1.0) - - -def test_elbow_coeff(blanket_library): - """ - Test for elbow_coeff function. - """ - # input = r_elbow, ang_elbow, lambda, dh - assert blanket_library.elbow_coeff(1, 0, 1, 1) == pytest.approx(0.0, rel=1e-3) - assert blanket_library.elbow_coeff(1, 90, 1, 1) == pytest.approx( - 1.7807963267948965, rel=1e-3 - ) - assert blanket_library.elbow_coeff(1, 180, 1, 1) == pytest.approx( - 3.291157766597427, rel=1e-3 - ) - assert blanket_library.elbow_coeff(1, 90, 1, 0.1) == pytest.approx( - 15.774371098812502, rel=1e-3 - ) - assert blanket_library.elbow_coeff(0.1, 90, 1, 1) == pytest.approx(66.57, rel=1e-3) - assert blanket_library.elbow_coeff(1, 90, 0.1, 1) == pytest.approx( - 0.3670796326794896, rel=1e-3 - ) - - def test_flow_velocity(monkeypatch, blanket_library): """ Test for flow_velocity function. diff --git a/tests/unit/models/engineering/test_pumping.py b/tests/unit/models/engineering/test_pumping.py index 6b1898bb1a..7371edb3c8 100644 --- a/tests/unit/models/engineering/test_pumping.py +++ b/tests/unit/models/engineering/test_pumping.py @@ -1,15 +1,34 @@ +from typing import Any, NamedTuple + import pytest from process.models.engineering.pumping import ( + CoolantFrictionLossParameters, + CoolantType, + calculate_required_mass_flow_rate, calculate_reynolds_number, + coolant_friction_pressure_drop, + coolant_pumping_power, darcy_friction_haaland, + elbow_coeff, gnielinski_heat_transfer_coefficient, + pipe_hydraulic_diameter, ) +@pytest.fixture +def blanket_library(process_models): + """Provides BlanketLibrary object for testing. + + :returns: initialised BlanketLibrary object + :rtype: process.blanket_library.BlanketLibrary + """ + return process_models.blanket_library + + def test_darcy_friction_haaland(): assert darcy_friction_haaland( - reynolds=5500, roughness_channel=1e-6, radius_channel=0.1 + reynolds=5500, roughness_channel=1e-6, dia_channel=0.1 ) == pytest.approx(0.0366668931278784) @@ -22,7 +41,7 @@ def test_gnielinski_heat_transfer_coefficient(): visc_coolant=4.0416219836935569e-05, thermcond_coolant=0.3211653052986152, roughness_channel=6e-8, - ) == pytest.approx(1929.2042015869506) + ) == pytest.approx(1928.3502714369304) def test_calculate_reynolds_number(): @@ -33,3 +52,198 @@ def test_calculate_reynolds_number(): radius_channel=0.0060000000000000001, visc_coolant=4.0416219836935569e-05, ) == pytest.approx(33302.602975971815) + + +def test_elbow_coeff(): + """ + Test for elbow_coeff function. + """ + # input = r_elbow, ang_elbow, lambda, dh + assert elbow_coeff(1, 0, 1, 1) == pytest.approx(0.0, rel=1e-3) + assert elbow_coeff(1, 90, 1, 1) == pytest.approx(1.7807963267948965, rel=1e-3) + assert elbow_coeff(1, 180, 1, 1) == pytest.approx(3.291157766597427, rel=1e-3) + assert elbow_coeff(1, 90, 1, 0.1) == pytest.approx(15.774371098812502, rel=1e-3) + assert elbow_coeff(0.1, 90, 1, 1) == pytest.approx(66.57, rel=1e-3) + assert elbow_coeff(1, 90, 0.1, 1) == pytest.approx(0.3670796326794896, rel=1e-3) + + +def test_calculate_required_mass_flow_rate(): + assert calculate_required_mass_flow_rate( + p_heat_total=1000.0, + heatcap_coolant=100.0, + temp_in_coolant=300.0, + temp_out_coolant=310.0, + ) == pytest.approx(1.0) + + +def test_calculate_required_mass_flow_rate_with_realistic_values(): + assert calculate_required_mass_flow_rate( + p_heat_total=50000.0, + heatcap_coolant=4180.0, + temp_in_coolant=293.15, + temp_out_coolant=313.15, + ) == pytest.approx(0.5980861244019139) + + +def test_calculate_required_mass_flow_rate_zero_temperature_rise(): + with pytest.raises(ZeroDivisionError): + calculate_required_mass_flow_rate( + p_heat_total=1000.0, + heatcap_coolant=4180.0, + temp_in_coolant=300.0, + temp_out_coolant=300.0, + ) + + +def test_hydraulic_diameter(): + """ + Test for hydraulic_diameter function. + """ + + # hydraulic_diameter input = i_channel_shape: 1 = circle, 2 = rectangle + # 2.0D0*radius_fw_channel + assert pipe_hydraulic_diameter( + 1, radius_fw_channel=1.0, a_bz_liq=1.0, b_bz_liq=1.0 + ) == pytest.approx(2.0) + # 2*a_bz_liq*b_bz_liq/(a_bz_liq+b_bz_liq) + assert pipe_hydraulic_diameter( + 2, radius_fw_channel=1.0, a_bz_liq=1.0, b_bz_liq=1.0 + ) == pytest.approx(1.0) + + +class CoolantFrictionLossParam(NamedTuple): + radius_channel: Any = None + radius_pipe_90_deg_bend: Any = None + radius_pipe_180_deg_bend: Any = None + a_bz_liq: Any = None + b_bz_liq: Any = None + roughness_channel: Any = None + i_ps: Any = None + n_pipe_90_deg_bends: Any = None + n_pipe_180_deg_bends: Any = None + len_pipe: Any = None + den_coolant: Any = None + visc_coolant: Any = None + vel_coolant: Any = None + label: Any = None + expected_pressure_drop_out: Any = None + + +@pytest.mark.parametrize( + "coolantfrictionlossparam", + [ + CoolantFrictionLossParam( + radius_channel=0.0060000000000000001, + radius_pipe_90_deg_bend=0.018, + radius_pipe_180_deg_bend=0.09, + a_bz_liq=0.20000000000000001, + b_bz_liq=0.20000000000000001, + roughness_channel=9.9999999999999995e-07, + i_ps=1, + n_pipe_90_deg_bends=2, + n_pipe_180_deg_bends=0, + len_pipe=4, + den_coolant=10.405276820718059, + visc_coolant=3.604452999475736e-05, + vel_coolant=32.753134225223164, + label="Inboard first wall", + expected_pressure_drop_out=35416.899017164345, + ), + CoolantFrictionLossParam( + radius_channel=1.0, + radius_pipe_90_deg_bend=1.0, + radius_pipe_180_deg_bend=1.0, + a_bz_liq=1.0, + b_bz_liq=1.0, + roughness_channel=1e-6, + i_ps=2, + n_pipe_90_deg_bends=1.0, + n_pipe_180_deg_bends=1.0, + len_pipe=1.0, + den_coolant=1.0, + visc_coolant=1.0, + vel_coolant=1.0, + label="label", + expected_pressure_drop_out=1.4325633520224854, + ), + ], +) +def test_coolant_friction_loss(coolantfrictionlossparam, monkeypatch, blanket_library): + """ + Automatically generated Regression Unit Test for coolant_friction_loss. + + This test was generated using data from + blanket_files/large_tokamak_primary_pumping2.IN.DAT. + + Parameters + ---------- + coolantfrictionlossparam : CoolantFrictionLossParam + the data used to mock and assert in this test. + monkeypatch : _pytest.monkeypatch.monkeypatch + pytest fixture used to mock module/class variables + blanket_library : BlanketLibrary + the blanket library instance used in this test. + + """ + monkeypatch.setattr( + blanket_library.data.fwbs, "a_bz_liq", coolantfrictionlossparam.a_bz_liq + ) + monkeypatch.setattr( + blanket_library.data.fwbs, "b_bz_liq", coolantfrictionlossparam.b_bz_liq + ) + + pressure_params: CoolantFrictionLossParameters = coolant_friction_pressure_drop( + i_ps=coolantfrictionlossparam.i_ps, + radius_pipe_90_deg_bend=(coolantfrictionlossparam.radius_pipe_90_deg_bend), + radius_pipe_180_deg_bend=(coolantfrictionlossparam.radius_pipe_180_deg_bend), + n_pipe_90_deg_bends=coolantfrictionlossparam.n_pipe_90_deg_bends, + n_pipe_180_deg_bends=coolantfrictionlossparam.n_pipe_180_deg_bends, + len_pipe=coolantfrictionlossparam.len_pipe, + den_coolant=coolantfrictionlossparam.den_coolant, + visc_coolant=coolantfrictionlossparam.visc_coolant, + vel_coolant=coolantfrictionlossparam.vel_coolant, + roughness_channel=coolantfrictionlossparam.roughness_channel, + radius_channel=coolantfrictionlossparam.radius_channel, + a_bz_liq=coolantfrictionlossparam.a_bz_liq, + b_bz_liq=coolantfrictionlossparam.b_bz_liq, + ) + + assert pressure_params.dpres_total == pytest.approx( + coolantfrictionlossparam.expected_pressure_drop_out + ) + + +def test_pumppower_secondary_pb_li(): + + data = { + "i_liquid_breeder": 2, + "temp_coolant_pump_outlet": 570, + "temp_coolant_pump_inlet": 720, + "pres_coolant_pump_inlet": 1700000, + "dpres_coolant": 303517.3, + "mflow_coolant_total": 35677.7, + "i_coolant_type": CoolantType.HELIUM, + "den_coolant": 9753.25, + "etaiso": 0.9, + "etaiso_liq": 0.85, + } + + assert pytest.approx(coolant_pumping_power(**data)) == 1.3062070319498325 + + +def test_pumppower_primary_helium(): + + data = { + "i_liquid_breeder": 1, + "temp_coolant_pump_outlet": 573, + "temp_coolant_pump_inlet": 773, + "pres_coolant_pump_inlet": 8000000, + "dpres_coolant": 20088.23, + "mflow_coolant_total": 956.3, + "i_coolant_type": CoolantType.HELIUM, + "den_coolant": 5.64, + "etaiso": 0.9, + "etaiso_liq": 0.85, + } + + assert pytest.approx(coolant_pumping_power(**data), rel=1e-4) == 3.2374845432302464 diff --git a/tests/unit/models/test_fw.py b/tests/unit/models/test_fw.py index 88ec3e88ae..7c5e620d4d 100644 --- a/tests/unit/models/test_fw.py +++ b/tests/unit/models/test_fw.py @@ -77,7 +77,7 @@ class FwTempParam(NamedTuple): area=612.23369764444396, prad_incident=97.271629070225231, label="Inboard first wall", - expected_tpeakfw=846.4965128389427, + expected_tpeakfw=846.6822343235608, expected_cfmean=5188.6731857247905, expected_rhofmean=5.7616377393642955, expected_massrate=0.0054299309578952218, @@ -99,7 +99,7 @@ class FwTempParam(NamedTuple): area=988.92586580655245, prad_incident=176.95628839065773, label="Outboard first wall", - expected_tpeakfw=850.851622254886, + expected_tpeakfw=851.051204263975, expected_cfmean=5188.6731857247905, expected_rhofmean=5.7616377393642955, expected_massrate=0.005816503189155049,