diff --git a/documentation/source/development/standards.md b/documentation/source/development/standards.md index ef0feaeda9..8479267753 100644 --- a/documentation/source/development/standards.md +++ b/documentation/source/development/standards.md @@ -658,8 +658,10 @@ def function_name(param1: Any, param2: Any) -> Any: References ---------- - - Reference 1: Description of the reference. - - Reference 2: Description of the reference. + [1] Description of reference 1. Preferably in IEEE reference format with a + DOI link (if applicable). + [2] Description of reference 2. Preferably in IEEE reference format with a + DOI link (if applicable). """ ``` diff --git a/process/core/caller.py b/process/core/caller.py index dc743b0d45..b2283e1516 100644 --- a/process/core/caller.py +++ b/process/core/caller.py @@ -293,28 +293,22 @@ def _call_models_once(self, xc: np.ndarray): # Toroidal field coil model # Toroidal field coil resistive model - if self.data.tfcoil.i_tf_sup == TFConductorModel.WATER_COOLED_COPPER: - self.models.copper_tf_coil.run() + match TFConductorModel(self.data.tfcoil.i_tf_sup): + case TFConductorModel.WATER_COOLED_COPPER: + self.models.copper_tf_coil.run() - # Toroidal field coil superconductor model - if self.data.tfcoil.i_tf_sup == TFConductorModel.SUPERCONDUCTING: - if ( - SuperconductingTFTurnType( + # Toroidal field coil superconductor model + case TFConductorModel.SUPERCONDUCTING: + match SuperconductingTFTurnType( self.data.superconducting_tfcoil.i_tf_turn_type - ) - == SuperconductingTFTurnType.CABLE_IN_CONDUIT - ): - self.models.cicc_sctfcoil.run() - elif ( - SuperconductingTFTurnType( - self.data.superconducting_tfcoil.i_tf_turn_type - ) - == SuperconductingTFTurnType.CROSS_CONDUCTOR - ): - self.models.croco_sctfcoil.run() + ): + case SuperconductingTFTurnType.CABLE_IN_CONDUIT: + self.models.cicc_sctfcoil.run() + case SuperconductingTFTurnType.CROSS_CONDUCTOR: + self.models.croco_sctfcoil.run() - if self.data.tfcoil.i_tf_sup == TFConductorModel.HELIUM_COOLED_ALUMINIUM: - self.models.aluminium_tf_coil.run() + case TFConductorModel.HELIUM_COOLED_ALUMINIUM: + self.models.aluminium_tf_coil.run() # Poloidal field and central solenoid model self.models.pfcoil.run() @@ -341,13 +335,13 @@ def _call_models_once(self, xc: np.ndarray): 4 | KIT HCLL model 5 | DCLL model """ - if self.data.fwbs.i_blanket_type == BlktModelTypes.CCFE_HCPB: - # CCFE HCPB model - self.models.ccfe_hcpb.run() - - elif self.data.fwbs.i_blanket_type == BlktModelTypes.DCLL: - # DCLL model - self.models.dcll.run() + match BlktModelTypes(self.data.fwbs.i_blanket_type): + case BlktModelTypes.CCFE_HCPB: + # CCFE HCPB model + self.models.ccfe_hcpb.run() + case BlktModelTypes.DCLL: + # DCLL model + self.models.dcll.run() self.models.cryostat.run() diff --git a/process/core/init.py b/process/core/init.py index 4df54e6664..de40dd0d64 100644 --- a/process/core/init.py +++ b/process/core/init.py @@ -638,16 +638,19 @@ def check_process(inputs, data): # noqa: ARG001 stacklevel=2, ) i_single_null = DivertorNumberModels(data.physics.i_single_null) - if i_single_null == DivertorNumberModels.DOUBLE_NULL: - data.divertor.n_divertors = 2 - data.build.dz_fw_plasma_gap = data.build.dz_xpoint_divertor - data.build.dz_shld_upper = data.build.dz_shld_lower - data.build.dz_vv_upper = data.build.dz_vv_lower - logger.warning( - "Double-null: Upper vertical build forced to match lower", stacklevel=2 - ) - else: # i_single_null == DivertorNumberModels.SINGLE_NULL - data.divertor.n_divertors = 1 + match i_single_null: + case DivertorNumberModels.DOUBLE_NULL: + data.divertor.n_divertors = 2 + data.build.dz_fw_plasma_gap = data.build.dz_xpoint_divertor + data.build.dz_shld_upper = data.build.dz_shld_lower + data.build.dz_vv_upper = data.build.dz_vv_lower + logger.warning( + "Double-null: Upper vertical build forced to match lower", stacklevel=2 + ) + case ( + DivertorNumberModels.SINGLE_NULL + ): # i_single_null == DivertorNumberModels.SINGLE_NULL + data.divertor.n_divertors = 1 # Tight aspect ratio options (ST) if data.physics.itart == 1: @@ -675,73 +678,76 @@ def check_process(inputs, data): # noqa: ARG001 data.pf_coil.i_pf_location[1] = PFLocationTypes.OUTSIDE_TF data.pf_coil.i_pf_location[2] = PFLocationTypes.OUTSIDE_TF - # Water cooled copper magnets initialisation / checks - if data.tfcoil.i_tf_sup == TFConductorModel.WATER_COOLED_COPPER: - # Check if the initial centrepost coolant loop adapted to the - # magnet technology - # Ice cannot flow so temp_cp_coolant_inlet > 273.15 K - if data.tfcoil.temp_cp_coolant_inlet < 273.15: - raise ProcessValidationError( - "Coolant temperature (temp_cp_coolant_inlet) cannot be < 0 C" - " (273.15 K) for water cooled copper magents" - ) - - # Temperature of the TF legs cannot be cooled down - if ( - data.tfcoil.temp_tf_legs_outboard > 0 - and data.tfcoil.temp_tf_legs_outboard < 273.15 - ): - raise ProcessValidationError( - "TF legs conductor temperature (temp_tf_legs_outboard) cannot be" - " < 0 C (273.15 K) for water cooled magents" - ) + match TFConductorModel(data.tfcoil.i_tf_sup): + # Water cooled copper magnets initialisation / checks + case TFConductorModel.WATER_COOLED_COPPER: + # Check if the initial centrepost coolant loop adapted to the + # magnet technology + # Ice cannot flow so temp_cp_coolant_inlet > 273.15 K + if data.tfcoil.temp_cp_coolant_inlet < 273.15: + raise ProcessValidationError( + "Coolant temperature (temp_cp_coolant_inlet) cannot be < 0 C" + " (273.15 K) for water cooled copper magents" + ) - # Check if conductor upper limit is properly set to 50 K or below - if ( - data.numerics.ixc[: data.numerics.n_iteration_variables] == 20 - ).any() and data.numerics.boundu[19] < 273.15: - raise ProcessValidationError( - "Too low CP conductor temperature (temp_cp_average)." - " Lower limit for copper > 273.15 K" - ) + # Temperature of the TF legs cannot be cooled down + if ( + data.tfcoil.temp_tf_legs_outboard > 0 + and data.tfcoil.temp_tf_legs_outboard < 273.15 + ): + raise ProcessValidationError( + "TF legs conductor temperature (temp_tf_legs_outboard) cannot be" + " < 0 C (273.15 K) for water cooled magents" + ) - # Call a lvl 3 error if superconductor magnets are used - elif data.tfcoil.i_tf_sup == TFConductorModel.SUPERCONDUCTING: - logger.warning( - "Joints res not cal. for SC (itart = 1) TF (data.tfcoil.i_tf_sup = 1)", - stacklevel=2, - ) + # Check if conductor upper limit is properly set to 50 K or below + if ( + data.numerics.ixc[: data.numerics.n_iteration_variables] == 20 + ).any() and data.numerics.boundu[19] < 273.15: + raise ProcessValidationError( + "Too low CP conductor temperature (temp_cp_average)." + " Lower limit for copper > 273.15 K" + ) - # Aluminium magnets initialisation / checks - # Initialize the CP conductor temperature to cryogenic temperature for - # cryo-al magnets (20 K) - elif data.tfcoil.i_tf_sup == TFConductorModel.HELIUM_COOLED_ALUMINIUM: - # Call a lvl 3 error if the inlet coolant temperature is too large - # Motivation : ill-defined aluminium resistivity fit for T > 40-50 K - if data.tfcoil.temp_cp_coolant_inlet > 40.0: - raise ProcessValidationError( - "Coolant temperature (temp_cp_coolant_inlet) should be < 40 K for" - " the cryo-al resistivity to be defined" + # Call a lvl 3 error if superconductor magnets are used + case TFConductorModel.SUPERCONDUCTING: + logger.warning( + "Joints res not cal. for SC (itart = 1) TF " + "(data.tfcoil.i_tf_sup = 1)", + stacklevel=2, ) - # Check if the leg average temperature is low enough for the resisitivity fit - if data.tfcoil.temp_tf_legs_outboard > 50.0: - raise ProcessValidationError( - "TF legs conductor temperature (temp_tf_legs_outboard) should be" - " < 40 K for the cryo-al resistivity to be defined" - ) + # Aluminium magnets initialisation / checks + # Initialize the CP conductor temperature to cryogenic temperature for + # cryo-al magnets (20 K) + case TFConductorModel.HELIUM_COOLED_ALUMINIUM: + # Call a lvl 3 error if the inlet coolant temperature is too large + # Motivation : ill-defined aluminium resistivity fit for T > 40-50 K + if data.tfcoil.temp_cp_coolant_inlet > 40.0: + raise ProcessValidationError( + "Coolant temperature (temp_cp_coolant_inlet) should be " + "< 40 K for the cryo-al resistivity to be defined" + ) - # Check if conductor upper limit is properly set to 50 K or below - if ( - data.numerics.ixc[: data.numerics.n_iteration_variables] == 20 - ).any() and data.numerics.boundu[19] > 50.0: - raise ProcessValidationError( - "Too large CP conductor temperature (temp_cp_average). Upper limit" - " for cryo-al < 50 K" - ) + # Check if the leg average temperature is low enough for the + # resisitivity fit + if data.tfcoil.temp_tf_legs_outboard > 50.0: + raise ProcessValidationError( + "TF legs conductor temperature (temp_tf_legs_outboard) should be" + " < 40 K for the cryo-al resistivity to be defined" + ) + + # Check if conductor upper limit is properly set to 50 K or below + if ( + data.numerics.ixc[: data.numerics.n_iteration_variables] == 20 + ).any() and data.numerics.boundu[19] > 50.0: + raise ProcessValidationError( + "Too large CP conductor temperature (temp_cp_average). " + "Upper limit for cryo-al < 50 K" + ) - # Otherwise intitialise the average conductor temperature at - data.tfcoil.temp_cp_average = data.tfcoil.temp_cp_coolant_inlet + # Otherwise intitialise the average conductor temperature at + data.tfcoil.temp_cp_average = data.tfcoil.temp_cp_coolant_inlet # Check if the boostrap current selection is addapted to ST if data.physics.i_bootstrap_current == 1: @@ -965,12 +971,13 @@ def check_process(inputs, data): # noqa: ARG001 # Setting the default cryo-plants efficiencies if abs(data.tfcoil.eff_tf_cryo + 1) < 1e-6: # The ITER cyoplant efficiency is used for SC - if data.tfcoil.i_tf_sup == TFConductorModel.SUPERCONDUCTING: - data.tfcoil.eff_tf_cryo = 0.13 + match TFConductorModel(data.tfcoil.i_tf_sup): + case TFConductorModel.SUPERCONDUCTING: + data.tfcoil.eff_tf_cryo = 0.13 - # Strawbrige plot extrapolation is used for Cryo-Al - elif data.tfcoil.i_tf_sup == TFConductorModel.HELIUM_COOLED_ALUMINIUM: - data.tfcoil.eff_tf_cryo = 0.40 + # Strawbrige plot extrapolation is used for Cryo-Al + case TFConductorModel.HELIUM_COOLED_ALUMINIUM: + data.tfcoil.eff_tf_cryo = 0.40 # Cryo-plane efficiency must be in [0-1.0] elif data.tfcoil.eff_tf_cryo > 1.0 or data.tfcoil.eff_tf_cryo < 0.0: @@ -995,17 +1002,15 @@ def check_process(inputs, data): # noqa: ARG001 if data.tfcoil.eyoung_ins <= 1.0e8: # Copper magnets, no insulation material defined # But use the ITER design by default - if data.tfcoil.i_tf_sup in { - TFConductorModel.WATER_COOLED_COPPER, - TFConductorModel.SUPERCONDUCTING, - }: - # SC magnets - # Value from DDD11-2 v2 2 (2009) - data.tfcoil.eyoung_ins = 20.0e9 - - # Cryo-aluminum magnets (Kapton polymer) - elif data.tfcoil.i_tf_sup == TFConductorModel.HELIUM_COOLED_ALUMINIUM: - data.tfcoil.eyoung_ins = 2.5e9 + match TFConductorModel(data.tfcoil.i_tf_sup): + case TFConductorModel.WATER_COOLED_COPPER | TFConductorModel.SUPERCONDUCTING: + # SC magnets + # Value from DDD11-2 v2 2 (2009) + data.tfcoil.eyoung_ins = 20.0e9 + + # Cryo-aluminum magnets (Kapton polymer) + case TFConductorModel.HELIUM_COOLED_ALUMINIUM: + data.tfcoil.eyoung_ins = 2.5e9 # Setting the default WP geometry i_tf_wp_geom = SuperconductingTFWPShapeType(data.tfcoil.i_tf_wp_geom) @@ -1022,49 +1027,39 @@ def check_process(inputs, data): # noqa: ARG001 data.tfcoil.i_tf_wp_geom = SuperconductingTFWPShapeType.RECTANGULAR # Setting the TF coil conductor elastic properties - - if data.tfcoil.i_tf_cond_eyoung_axial == 0: - # Conductor stiffness is not considered - data.tfcoil.eyoung_cond_axial = 0 - data.tfcoil.eyoung_cond_trans = 0 - elif data.tfcoil.i_tf_cond_eyoung_axial == 2: - # Select sensible defaults from the literature - if ( - SuperconductorModel(data.tfcoil.i_tf_sc_mat).material - == SuperconductorMaterial.NB3SN - ): - # Nb3Sn: Nyilas, A et. al, Superconductor Science and Technology 16, - # no. 9 (2003): 1036-42. https://doi.org/10.1088/0953-2048/16/9/313. - data.tfcoil.eyoung_cond_axial = 32e9 - elif ( - SuperconductorModel(data.tfcoil.i_tf_sc_mat).material - == SuperconductorMaterial.BI2212 - ): - # Bi-2212: Brown, M. et al, IOP Conference Series: Materials Science - # and Engineering 279 (2017): 012022. - # https://doi.org/10.1088/1757-899X/279/1/012022. - data.tfcoil.eyoung_cond_axial = 80e9 - elif ( - SuperconductorModel(data.tfcoil.i_tf_sc_mat).material - == SuperconductorMaterial.NBTI - ): - # NbTi: Vedrine, P. et. al, IEEE Transactions on Applied Superconductivity - # 9, no. 2 (1999): 236-39. https://doi.org/10.1109/77.783280. - data.tfcoil.eyoung_cond_axial = 6.8e9 - elif ( - SuperconductorModel(data.tfcoil.i_tf_sc_mat).material - == SuperconductorMaterial.REBCO - ): - # REBCO: Fujishiro, H. et. al, Physica C: Superconductivity, - # 426-431 (2005): 699-704. https://doi.org/10.1016/j.physc.2005.01.045. - data.tfcoil.eyoung_cond_axial = 145e9 - - if data.tfcoil.i_tf_cond_eyoung_trans == 0: - # Transverse stiffness is not considered + match data.tfcoil.i_tf_cond_eyoung_axial: + case 0: + # Conductor stiffness is not considered + data.tfcoil.eyoung_cond_axial = 0 data.tfcoil.eyoung_cond_trans = 0 - else: - # Transverse stiffness is significant - data.tfcoil.eyoung_cond_trans = data.tfcoil.eyoung_cond_axial + case 2: + # Select sensible defaults from the literature + match SuperconductorModel(data.tfcoil.i_tf_sc_mat).material: + case SuperconductorMaterial.NB3SN: + # Nb3Sn: Nyilas, A et. al, Superconductor Science and Technology 16, + # no. 9 (2003): 1036-42. https://doi.org/10.1088/0953-2048/16/9/313. + data.tfcoil.eyoung_cond_axial = 32e9 + case SuperconductorMaterial.BI2212: + # Bi-2212: Brown, M. et al, IOP Conference Series: Materials Science + # and Engineering 279 (2017): 012022. + # https://doi.org/10.1088/1757-899X/279/1/012022. + data.tfcoil.eyoung_cond_axial = 80e9 + case SuperconductorMaterial.NBTI: + # NbTi: Vedrine, P. et. al, IEEE Transactions on + # Applied Superconductivity 9, no. 2 (1999): 236-39. + # https://doi.org/10.1109/77.783280. + data.tfcoil.eyoung_cond_axial = 6.8e9 + case SuperconductorMaterial.REBCO: + # REBCO: Fujishiro, H. et. al, Physica C: Superconductivity, + # 426-431 (2005): 699-704. https://doi.org/10.1016/j.physc.2005.01.045. + data.tfcoil.eyoung_cond_axial = 145e9 + + if data.tfcoil.i_tf_cond_eyoung_trans == 0: + # Transverse stiffness is not considered + data.tfcoil.eyoung_cond_trans = 0 + else: + # Transverse stiffness is significant + data.tfcoil.eyoung_cond_trans = data.tfcoil.eyoung_cond_axial # Check if the user has set the critical current density and temperature for # non-user-defined superconductors @@ -1086,30 +1081,36 @@ def check_process(inputs, data): # noqa: ARG001 # Rem : Only verified if the WP thickness is used if (data.numerics.ixc[: data.numerics.n_iteration_variables] == 140).any(): # Minimal WP thickness - if data.tfcoil.i_tf_sup == TFConductorModel.SUPERCONDUCTING: - dr_tf_wp_min = 2.0 * ( - data.tfcoil.dx_tf_wp_insulation - + data.tfcoil.dx_tf_wp_insertion_gap - + data.tfcoil.dx_tf_turn_insulation - + data.tfcoil.dia_tf_turn_coolant_channel - ) + match TFConductorModel(data.tfcoil.i_tf_sup): + case TFConductorModel.SUPERCONDUCTING: + dr_tf_wp_min = 2.0 * ( + data.tfcoil.dx_tf_wp_insulation + + data.tfcoil.dx_tf_wp_insertion_gap + + data.tfcoil.dx_tf_turn_insulation + + data.tfcoil.dia_tf_turn_coolant_channel + ) - # Steel conduit thickness (can be an iteration variable) - if (data.numerics.ixc[: data.numerics.n_iteration_variables] == 58).any(): - dr_tf_wp_min += 2.0 * data.numerics.boundl[57] - else: - dr_tf_wp_min += 2.0 * data.tfcoil.dx_tf_turn_steel - - # Minimal conductor layer thickness - elif data.tfcoil.i_tf_sup in { - TFConductorModel.WATER_COOLED_COPPER, - TFConductorModel.HELIUM_COOLED_ALUMINIUM, - }: - dr_tf_wp_min = ( - 2.0 - * (data.tfcoil.dx_tf_turn_insulation + data.tfcoil.dx_tf_wp_insulation) - + 4.0 * data.tfcoil.radius_cp_coolant_channel - ) + # Steel conduit thickness (can be an iteration variable) + if ( + data.numerics.ixc[: data.numerics.n_iteration_variables] == 58 + ).any(): + dr_tf_wp_min += 2.0 * data.numerics.boundl[57] + else: + dr_tf_wp_min += 2.0 * data.tfcoil.dx_tf_turn_steel + + # Minimal conductor layer thickness + case ( + TFConductorModel.WATER_COOLED_COPPER + | TFConductorModel.HELIUM_COOLED_ALUMINIUM + ): + dr_tf_wp_min = ( + 2.0 + * ( + data.tfcoil.dx_tf_turn_insulation + + data.tfcoil.dx_tf_wp_insulation + ) + + 4.0 * data.tfcoil.radius_cp_coolant_channel + ) if data.numerics.boundl[139] < dr_tf_wp_min: raise ProcessValidationError( diff --git a/process/core/io/plot/summary.py b/process/core/io/plot/summary.py index a611ac63c2..93b7390f56 100644 --- a/process/core/io/plot/summary.py +++ b/process/core/io/plot/summary.py @@ -199,6 +199,12 @@ def plot_plasma( colour scheme to use for plots mirror_negative_x : if True, mirror the plot to the negative x-axis (Default value = False) + + + Raises + ------ + ValueError + If an unsupported plasma shape model type is encountered. """ r_0, a, triang, kappa, i_single_null, i_plasma_shape, plasma_square = ( mfile.get_variables( @@ -226,35 +232,38 @@ def plot_plasma( # Apply mirror transformation if requested x_scale = -1 if mirror_negative_x else 1 - if i_plasma_shape == PlasmaShapeModelType.PROCESS_ORIGINAL: - # Plot the 2 plasma outline arcs. - axis.plot(x_scale * np.array(pg.rs[0]), pg.zs[0], color="black") - axis.plot(x_scale * np.array(pg.rs[1]), pg.zs[1], color="black") + match PlasmaShapeModelType(i_plasma_shape): + case PlasmaShapeModelType.PROCESS_ORIGINAL: + # Plot the 2 plasma outline arcs. + axis.plot(x_scale * np.array(pg.rs[0]), pg.zs[0], color="black") + axis.plot(x_scale * np.array(pg.rs[1]), pg.zs[1], color="black") - # Set triang_95 to stop plotting plasma past boundary - # Assume IPDG scaling - triang_95 = triang / 1.5 + # Set triang_95 to stop plotting plasma past boundary + # Assume IPDG scaling + triang_95 = triang / 1.5 - # Colour in right side of plasma - axis.fill_between( - x=x_scale * np.array(pg.rs[0]), - y1=pg.zs[0], - where=(pg.rs[0] > r_0 - (triang_95 * a * 1.5)), - color=PLASMA_COLOUR[colour_scheme - 1], - ) - # Colour in left side of plasma - axis.fill_between( - x=x_scale * np.array(pg.rs[1]), - y1=pg.zs[1], - where=(pg.rs[1] < r_0 - (triang_95 * a * 1.5)), - color=PLASMA_COLOUR[colour_scheme - 1], - ) + # Colour in right side of plasma + axis.fill_between( + x=x_scale * np.array(pg.rs[0]), + y1=pg.zs[0], + where=(pg.rs[0] > r_0 - (triang_95 * a * 1.5)), + color=PLASMA_COLOUR[colour_scheme - 1], + ) + # Colour in left side of plasma + axis.fill_between( + x=x_scale * np.array(pg.rs[1]), + y1=pg.zs[1], + where=(pg.rs[1] < r_0 - (triang_95 * a * 1.5)), + color=PLASMA_COLOUR[colour_scheme - 1], + ) - elif i_plasma_shape == PlasmaShapeModelType.SAUTER: - axis.plot(x_scale * np.array(pg.rs), pg.zs, color="black") - axis.fill( - x_scale * np.array(pg.rs), pg.zs, color=PLASMA_COLOUR[colour_scheme - 1] - ) + case PlasmaShapeModelType.SAUTER: + axis.plot(x_scale * np.array(pg.rs), pg.zs, color="black") + axis.fill( + x_scale * np.array(pg.rs), pg.zs, color=PLASMA_COLOUR[colour_scheme - 1] + ) + case _: + raise ValueError(f"Unsupported plasma shape model type: {i_plasma_shape}") def plot_centre_cross( @@ -4963,167 +4972,168 @@ def plot_vacuum_vessel_and_divertor( z_divertor_lower_top = (-kappa * rminor) - dz_xpoint_divertor z_divertor_lower_bottom = z_divertor_lower_top - dz_divertor - if i_single_null == 0: - z_divertor_upper_bottom = (kappa * rminor) + dz_xpoint_divertor - z_divertor_upper_top = z_divertor_upper_bottom + dz_divertor - # Apply mirror transformation if requested x_scale = -1 if mirror_negative_x else 1 - if i_single_null == 1: - vvg_single_null = vacuum_vessel_geometry_single_null( - cumulative_upper=cumulative_upper, - upper=upper, - triang=triang_95, - radx_outer=radx_outer, - rminx_outer=rminx_outer, - radx_inner=radx_inner, - rminx_inner=rminx_inner, - cumulative_lower=cumulative_lower, - lower=lower, - ) + match DivertorNumberModels(i_single_null): + case DivertorNumberModels.SINGLE_NULL: + z_divertor_upper_bottom = None + z_divertor_upper_top = None + vvg_single_null = vacuum_vessel_geometry_single_null( + cumulative_upper=cumulative_upper, + upper=upper, + triang=triang_95, + radx_outer=radx_outer, + rminx_outer=rminx_outer, + radx_inner=radx_inner, + rminx_inner=rminx_inner, + cumulative_lower=cumulative_lower, + lower=lower, + ) - axis.plot( - x_scale * np.array(vvg_single_null.rs), - vvg_single_null.zs, - color="black", - lw=thin, - zorder=5, - ) + axis.plot( + x_scale * np.array(vvg_single_null.rs), + vvg_single_null.zs, + color="black", + lw=thin, + zorder=5, + ) - axis.fill( - x_scale * np.array(vvg_single_null.rs), - vvg_single_null.zs, - color=VESSEL_COLOUR[colour_scheme - 1], - lw=0.01, - zorder=5, - ) + axis.fill( + x_scale * np.array(vvg_single_null.rs), + vvg_single_null.zs, + color=VESSEL_COLOUR[colour_scheme - 1], + lw=0.01, + zorder=5, + ) - # Find indices where vessel boundary is between z_divertor_bottom and z_divertor_top - # Find the min and max R values of the vessel boundary between the divertor lines - mask = (vvg_single_null.zs >= z_divertor_lower_bottom) & ( - vvg_single_null.zs <= z_divertor_lower_top - ) - # Get the min/max R for the region between the divertor lines - r_min = ( - np.min(vvg_single_null.rs[mask]) - + dr_vv_inboard - + dr_shld_inboard - + (dr_blkt_inboard * 0.5) - ) - r_max = ( - np.max(vvg_single_null.rs[mask]) - - dr_vv_outboard - - dr_shld_outboard - - (dr_blkt_outboard * 0.5) - ) - # Draw a rectangle (box) between the two lines and inside the vessel - axis.add_patch( - patches.Rectangle( - ( - x_scale * r_min, - z_divertor_lower_bottom, - ), - x_scale * (r_max - r_min), - z_divertor_lower_top - z_divertor_lower_bottom, - facecolor="black", - alpha=0.8, - zorder=1, + # Find indices where vessel boundary is between z_divertor_bottom and z_divertor_top + # Find the min and max R values of the vessel boundary between the divertor lines + mask = (vvg_single_null.zs >= z_divertor_lower_bottom) & ( + vvg_single_null.zs <= z_divertor_lower_top + ) + # Get the min/max R for the region between the divertor lines + r_min = ( + np.min(vvg_single_null.rs[mask]) + + dr_vv_inboard + + dr_shld_inboard + + (dr_blkt_inboard * 0.5) + ) + r_max = ( + np.max(vvg_single_null.rs[mask]) + - dr_vv_outboard + - dr_shld_outboard + - (dr_blkt_outboard * 0.5) + ) + # Draw a rectangle (box) between the two lines and inside the vessel + axis.add_patch( + patches.Rectangle( + ( + x_scale * r_min, + z_divertor_lower_bottom, + ), + x_scale * (r_max - r_min), + z_divertor_lower_top - z_divertor_lower_bottom, + facecolor="black", + alpha=0.8, + zorder=1, + ) ) - ) - if i_single_null == 0: - vvg_double_null = vacuum_vessel_geometry_double_null( - cumulative_lower=cumulative_lower, - lower=lower, - radx_inner=radx_inner, - radx_outer=radx_outer, - rminx_inner=rminx_inner, - rminx_outer=rminx_outer, - triang=triang_95, - ) - axis.plot( - x_scale * np.array(vvg_double_null.rs), - vvg_double_null.zs, - color="black", - lw=thin, - zorder=5, - ) + case DivertorNumberModels.DOUBLE_NULL: + z_divertor_upper_bottom = (kappa * rminor) + dz_xpoint_divertor + z_divertor_upper_top = z_divertor_upper_bottom + dz_divertor + vvg_double_null = vacuum_vessel_geometry_double_null( + cumulative_lower=cumulative_lower, + lower=lower, + radx_inner=radx_inner, + radx_outer=radx_outer, + rminx_inner=rminx_inner, + rminx_outer=rminx_outer, + triang=triang_95, + ) + axis.plot( + x_scale * np.array(vvg_double_null.rs), + vvg_double_null.zs, + color="black", + lw=thin, + zorder=5, + ) - axis.fill( - x_scale * np.array(vvg_double_null.rs), - vvg_double_null.zs, - color=VESSEL_COLOUR[colour_scheme - 1], - lw=0.01, - zorder=5, - ) + axis.fill( + x_scale * np.array(vvg_double_null.rs), + vvg_double_null.zs, + color=VESSEL_COLOUR[colour_scheme - 1], + lw=0.01, + zorder=5, + ) - # Plot lower divertor - # Find indices where vessel boundary is between z_divertor_bottom and z_divertor_top - # Find the min and max R values of the vessel boundary between the divertor lines - mask = (vvg_double_null.zs >= z_divertor_lower_bottom) & ( - vvg_double_null.zs <= z_divertor_lower_top - ) - # Get the min/max R for the region between the divertor lines - r_min = ( - np.min(vvg_double_null.rs[mask]) - + dr_vv_inboard - + dr_shld_inboard - + (dr_blkt_inboard * 0.5) - ) - r_max = ( - np.max(vvg_double_null.rs[mask]) - - dr_vv_outboard - - dr_shld_outboard - - (dr_blkt_outboard * 0.5) - ) - # Draw a rectangle (box) between the two lines and inside the vessel - axis.add_patch( - patches.Rectangle( - ( - x_scale * r_min, - z_divertor_lower_bottom, - ), - x_scale * (r_max - r_min), - z_divertor_lower_top - z_divertor_lower_bottom, - facecolor="black", - alpha=0.8, - zorder=1, + # Plot lower divertor + # Find indices where vessel boundary is between z_divertor_bottom and z_divertor_top + # Find the min and max R values of the vessel boundary between the divertor lines + mask = (vvg_double_null.zs >= z_divertor_lower_bottom) & ( + vvg_double_null.zs <= z_divertor_lower_top ) - ) - # Plot upper divertor - # Find indices where vessel boundary is between z_divertor_bottom and z_divertor_top - # Find the min and max R values of the vessel boundary between the divertor lines - mask = (vvg_double_null.zs >= z_divertor_upper_bottom) & ( - vvg_double_null.zs <= z_divertor_upper_top - ) - # Get the min/max R for the region between the divertor lines - r_min = ( - np.min(vvg_double_null.rs[mask]) - + dr_vv_inboard - + dr_shld_inboard - + (dr_blkt_inboard * 0.5) - ) - r_max = ( - np.max(vvg_double_null.rs[mask]) - - dr_vv_outboard - - dr_shld_outboard - - (dr_blkt_outboard * 0.5) - ) - # Draw a rectangle (box) between the two lines and inside the vessel - axis.add_patch( - patches.Rectangle( - ( - x_scale * r_min, - z_divertor_upper_bottom, - ), - x_scale * (r_max - r_min), - z_divertor_upper_top - z_divertor_upper_bottom, - facecolor="black", - alpha=0.8, - zorder=1, + # Get the min/max R for the region between the divertor lines + r_min = ( + np.min(vvg_double_null.rs[mask]) + + dr_vv_inboard + + dr_shld_inboard + + (dr_blkt_inboard * 0.5) + ) + r_max = ( + np.max(vvg_double_null.rs[mask]) + - dr_vv_outboard + - dr_shld_outboard + - (dr_blkt_outboard * 0.5) + ) + # Draw a rectangle (box) between the two lines and inside the vessel + axis.add_patch( + patches.Rectangle( + ( + x_scale * r_min, + z_divertor_lower_bottom, + ), + x_scale * (r_max - r_min), + z_divertor_lower_top - z_divertor_lower_bottom, + facecolor="black", + alpha=0.8, + zorder=1, + ) + ) + # Plot upper divertor + # Find indices where vessel boundary is between z_divertor_bottom and z_divertor_top + # Find the min and max R values of the vessel boundary between the divertor lines + mask = (vvg_double_null.zs >= z_divertor_upper_bottom) & ( + vvg_double_null.zs <= z_divertor_upper_top + ) + # Get the min/max R for the region between the divertor lines + r_min = ( + np.min(vvg_double_null.rs[mask]) + + dr_vv_inboard + + dr_shld_inboard + + (dr_blkt_inboard * 0.5) + ) + r_max = ( + np.max(vvg_double_null.rs[mask]) + - dr_vv_outboard + - dr_shld_outboard + - (dr_blkt_outboard * 0.5) + ) + # Draw a rectangle (box) between the two lines and inside the vessel + axis.add_patch( + patches.Rectangle( + ( + x_scale * r_min, + z_divertor_upper_bottom, + ), + x_scale * (r_max - r_min), + z_divertor_upper_top - z_divertor_upper_bottom, + facecolor="black", + alpha=0.8, + zorder=1, + ) ) - ) def plot_shield( @@ -5181,25 +5191,26 @@ def plot_shield( # Apply mirror transformation if requested x_scale = -1 if mirror_negative_x else 1 - if i_single_null == 1: - shield_geometry = shield_geometry_single_null( - cumulative_upper=cumulative_upper, - radx_far=radx_far, - rminx_far=rminx_far, - radx_near=radx_near, - rminx_near=rminx_near, - triang=triang_95, - cumulative_lower=cumulative_lower, - ) - else: - shield_geometry = shield_geometry_double_null( - cumulative_lower=cumulative_lower, - radx_far=radx_far, - radx_near=radx_near, - rminx_far=rminx_far, - rminx_near=rminx_near, - triang=triang_95, - ) + match DivertorNumberModels(i_single_null): + case DivertorNumberModels.SINGLE_NULL: + shield_geometry = shield_geometry_single_null( + cumulative_upper=cumulative_upper, + radx_far=radx_far, + rminx_far=rminx_far, + radx_near=radx_near, + rminx_near=rminx_near, + triang=triang_95, + cumulative_lower=cumulative_lower, + ) + case DivertorNumberModels.DOUBLE_NULL: + shield_geometry = shield_geometry_double_null( + cumulative_lower=cumulative_lower, + radx_far=radx_far, + radx_near=radx_near, + rminx_far=rminx_far, + rminx_near=rminx_near, + triang=triang_95, + ) axis.plot( x_scale * np.array(shield_geometry.rs), @@ -5260,98 +5271,99 @@ def plot_blanket( # Apply mirror transformation if requested x_scale = -1 if mirror_negative_x else 1 - if i_single_null == 1: - # Upper blanket: outer surface - radx_outer = ( - cumulative_radial_build("dr_blkt_outboard", mfile, scan) - + cumulative_radial_build("vvblgapi", mfile, scan) - ) / 2.0 - rminx_outer = ( - cumulative_radial_build("dr_blkt_outboard", mfile, scan) - - cumulative_radial_build("vvblgapi", mfile, scan) - ) / 2.0 - - # Upper blanket: inner surface - radx_inner = ( - cumulative_radial_build("dr_fw_outboard", mfile, scan) - + cumulative_radial_build("dr_blkt_inboard", mfile, scan) - ) / 2.0 - rminx_inner = ( - cumulative_radial_build("dr_fw_outboard", mfile, scan) - - cumulative_radial_build("dr_blkt_inboard", mfile, scan) - ) / 2.0 - bg_single_null = blanket_geometry_single_null( - radx_outer=radx_outer, - rminx_outer=rminx_outer, - radx_inner=radx_inner, - rminx_inner=rminx_inner, - cumulative_upper=cumulative_upper, - triang=triang_95, - cumulative_lower=cumulative_lower, - dz_blkt_upper=dz_blkt_upper, - c_shldith=c_shldith, - c_blnkoth=c_blnkoth, - dr_blkt_inboard=dr_blkt_inboard, - dr_blkt_outboard=dr_blkt_outboard, - ) + match DivertorNumberModels(i_single_null): + case DivertorNumberModels.SINGLE_NULL: + # Upper blanket: outer surface + radx_outer = ( + cumulative_radial_build("dr_blkt_outboard", mfile, scan) + + cumulative_radial_build("vvblgapi", mfile, scan) + ) / 2.0 + rminx_outer = ( + cumulative_radial_build("dr_blkt_outboard", mfile, scan) + - cumulative_radial_build("vvblgapi", mfile, scan) + ) / 2.0 + + # Upper blanket: inner surface + radx_inner = ( + cumulative_radial_build("dr_fw_outboard", mfile, scan) + + cumulative_radial_build("dr_blkt_inboard", mfile, scan) + ) / 2.0 + rminx_inner = ( + cumulative_radial_build("dr_fw_outboard", mfile, scan) + - cumulative_radial_build("dr_blkt_inboard", mfile, scan) + ) / 2.0 + bg_single_null = blanket_geometry_single_null( + radx_outer=radx_outer, + rminx_outer=rminx_outer, + radx_inner=radx_inner, + rminx_inner=rminx_inner, + cumulative_upper=cumulative_upper, + triang=triang_95, + cumulative_lower=cumulative_lower, + dz_blkt_upper=dz_blkt_upper, + c_shldith=c_shldith, + c_blnkoth=c_blnkoth, + dr_blkt_inboard=dr_blkt_inboard, + dr_blkt_outboard=dr_blkt_outboard, + ) - # Plot blanket - axis.plot( - x_scale * np.array(bg_single_null.rs), - bg_single_null.zs, - color="black", - lw=thin, - zorder=5, - ) + # Plot blanket + axis.plot( + x_scale * np.array(bg_single_null.rs), + bg_single_null.zs, + color="black", + lw=thin, + zorder=5, + ) - axis.fill( - x_scale * np.array(bg_single_null.rs), - bg_single_null.zs, - color=BLANKET_COLOUR[colour_scheme - 1], - lw=0.01, - zorder=5, - ) + axis.fill( + x_scale * np.array(bg_single_null.rs), + bg_single_null.zs, + color=BLANKET_COLOUR[colour_scheme - 1], + lw=0.01, + zorder=5, + ) - if i_single_null == 0: - bg_double_null = blanket_geometry_double_null( - cumulative_lower=cumulative_lower, - triang=triang_95, - dz_blkt_upper=dz_blkt_upper, - c_shldith=c_shldith, - c_blnkoth=c_blnkoth, - dr_blkt_inboard=dr_blkt_inboard, - dr_blkt_outboard=dr_blkt_outboard, - ) - # Plot blanket - axis.plot( - x_scale * np.array(bg_double_null.rs[0]), - bg_double_null.zs[0], - color="black", - lw=thin, - ) - axis.fill( - x_scale * np.array(bg_double_null.rs[0]), - bg_double_null.zs[0], - color=BLANKET_COLOUR[colour_scheme - 1], - lw=0.01, - zorder=5, - ) - if dr_blkt_inboard > 0.0: - # only plot inboard blanket if inboard blanket thickness > 0 + case DivertorNumberModels.DOUBLE_NULL: + bg_double_null = blanket_geometry_double_null( + cumulative_lower=cumulative_lower, + triang=triang_95, + dz_blkt_upper=dz_blkt_upper, + c_shldith=c_shldith, + c_blnkoth=c_blnkoth, + dr_blkt_inboard=dr_blkt_inboard, + dr_blkt_outboard=dr_blkt_outboard, + ) + # Plot blanket axis.plot( - x_scale * np.array(bg_double_null.rs[1]), - bg_double_null.zs[1], + x_scale * np.array(bg_double_null.rs[0]), + bg_double_null.zs[0], color="black", lw=thin, - zorder=5, ) axis.fill( - x_scale * np.array(bg_double_null.rs[1]), - bg_double_null.zs[1], + x_scale * np.array(bg_double_null.rs[0]), + bg_double_null.zs[0], color=BLANKET_COLOUR[colour_scheme - 1], lw=0.01, zorder=5, ) + if dr_blkt_inboard > 0.0: + # only plot inboard blanket if inboard blanket thickness > 0 + axis.plot( + x_scale * np.array(bg_double_null.rs[1]), + bg_double_null.zs[1], + color="black", + lw=thin, + zorder=5, + ) + axis.fill( + x_scale * np.array(bg_double_null.rs[1]), + bg_double_null.zs[1], + color=BLANKET_COLOUR[colour_scheme - 1], + lw=0.01, + zorder=5, + ) def plot_first_wall_top_down_cross_section(axis: plt.Axes, mfile: MFile, scan: int): @@ -5633,93 +5645,94 @@ def plot_firstwall( # Apply mirror transformation if requested x_scale = -1 if mirror_negative_x else 1 - if i_single_null == 1: - # Upper first wall: outer surface - radx_outer = ( - cumulative_radial_build("dr_fw_outboard", mfile, scan) - + cumulative_radial_build("dr_blkt_inboard", mfile, scan) - ) / 2.0 - rminx_outer = ( - cumulative_radial_build("dr_fw_outboard", mfile, scan) - - cumulative_radial_build("dr_blkt_inboard", mfile, scan) - ) / 2.0 - - # Upper first wall: inner surface - radx_inner = ( - cumulative_radial_build("dr_fw_plasma_gap_outboard", mfile, scan) - + cumulative_radial_build("dr_fw_inboard", mfile, scan) - ) / 2.0 - rminx_inner = ( - cumulative_radial_build("dr_fw_plasma_gap_outboard", mfile, scan) - - cumulative_radial_build("dr_fw_inboard", mfile, scan) - ) / 2.0 - - fwg_single_null = first_wall_geometry_single_null( - radx_outer=radx_outer, - rminx_outer=rminx_outer, - radx_inner=radx_inner, - rminx_inner=rminx_inner, - cumulative_upper=cumulative_upper, - triang=triang_95, - cumulative_lower=cumulative_lower, - dz_blkt_upper=dz_blkt_upper, - c_blnkith=c_blnkith, - c_fwoth=c_fwoth, - dr_fw_inboard=dr_fw_inboard, - dr_fw_outboard=dr_fw_outboard, - tfwvt=tfwvt, - ) + match DivertorNumberModels(i_single_null): + case DivertorNumberModels.SINGLE_NULL: + # Upper first wall: outer surface + radx_outer = ( + cumulative_radial_build("dr_fw_outboard", mfile, scan) + + cumulative_radial_build("dr_blkt_inboard", mfile, scan) + ) / 2.0 + rminx_outer = ( + cumulative_radial_build("dr_fw_outboard", mfile, scan) + - cumulative_radial_build("dr_blkt_inboard", mfile, scan) + ) / 2.0 + + # Upper first wall: inner surface + radx_inner = ( + cumulative_radial_build("dr_fw_plasma_gap_outboard", mfile, scan) + + cumulative_radial_build("dr_fw_inboard", mfile, scan) + ) / 2.0 + rminx_inner = ( + cumulative_radial_build("dr_fw_plasma_gap_outboard", mfile, scan) + - cumulative_radial_build("dr_fw_inboard", mfile, scan) + ) / 2.0 + + fwg_single_null = first_wall_geometry_single_null( + radx_outer=radx_outer, + rminx_outer=rminx_outer, + radx_inner=radx_inner, + rminx_inner=rminx_inner, + cumulative_upper=cumulative_upper, + triang=triang_95, + cumulative_lower=cumulative_lower, + dz_blkt_upper=dz_blkt_upper, + c_blnkith=c_blnkith, + c_fwoth=c_fwoth, + dr_fw_inboard=dr_fw_inboard, + dr_fw_outboard=dr_fw_outboard, + tfwvt=tfwvt, + ) - # Plot first wall - axis.plot( - x_scale * np.array(fwg_single_null.rs), - fwg_single_null.zs, - color="black", - lw=thin, - ) - axis.fill( - x_scale * np.array(fwg_single_null.rs), - fwg_single_null.zs, - color=FIRSTWALL_COLOUR[colour_scheme - 1], - lw=0.01, - ) + # Plot first wall + axis.plot( + x_scale * np.array(fwg_single_null.rs), + fwg_single_null.zs, + color="black", + lw=thin, + ) + axis.fill( + x_scale * np.array(fwg_single_null.rs), + fwg_single_null.zs, + color=FIRSTWALL_COLOUR[colour_scheme - 1], + lw=0.01, + ) - if i_single_null == 0: - fwg_double_null = first_wall_geometry_double_null( - cumulative_lower=cumulative_lower, - triang=triang_95, - dz_blkt_upper=dz_blkt_upper, - c_blnkith=c_blnkith, - c_fwoth=c_fwoth, - dr_fw_inboard=dr_fw_inboard, - dr_fw_outboard=dr_fw_outboard, - tfwvt=tfwvt, - ) - # Plot first wall - axis.plot( - x_scale * np.array(fwg_double_null.rs[0]), - fwg_double_null.zs[0], - color="black", - lw=thin, - ) - axis.plot( - x_scale * np.array(fwg_double_null.rs[1]), - fwg_double_null.zs[1], - color="black", - lw=thin, - ) - axis.fill( - x_scale * np.array(fwg_double_null.rs[0]), - fwg_double_null.zs[0], - color=FIRSTWALL_COLOUR[colour_scheme - 1], - lw=0.01, - ) - axis.fill( - x_scale * np.array(fwg_double_null.rs[1]), - fwg_double_null.zs[1], - color=FIRSTWALL_COLOUR[colour_scheme - 1], - lw=0.01, - ) + case DivertorNumberModels.DOUBLE_NULL: + fwg_double_null = first_wall_geometry_double_null( + cumulative_lower=cumulative_lower, + triang=triang_95, + dz_blkt_upper=dz_blkt_upper, + c_blnkith=c_blnkith, + c_fwoth=c_fwoth, + dr_fw_inboard=dr_fw_inboard, + dr_fw_outboard=dr_fw_outboard, + tfwvt=tfwvt, + ) + # Plot first wall + axis.plot( + x_scale * np.array(fwg_double_null.rs[0]), + fwg_double_null.zs[0], + color="black", + lw=thin, + ) + axis.plot( + x_scale * np.array(fwg_double_null.rs[1]), + fwg_double_null.zs[1], + color="black", + lw=thin, + ) + axis.fill( + x_scale * np.array(fwg_double_null.rs[0]), + fwg_double_null.zs[0], + color=FIRSTWALL_COLOUR[colour_scheme - 1], + lw=0.01, + ) + axis.fill( + x_scale * np.array(fwg_double_null.rs[1]), + fwg_double_null.zs[1], + color=FIRSTWALL_COLOUR[colour_scheme - 1], + lw=0.01, + ) def plot_tf_coils( @@ -14890,7 +14903,7 @@ def plot_blkt_structure( label="Blanket Half Height", ) - if i_single_null == 0: + if DivertorNumberModels(i_single_null) == DivertorNumberModels.DOUBLE_NULL: # Plot arrows for the outboard blanket angles ax.annotate( "", @@ -14913,11 +14926,12 @@ def plot_blkt_structure( # 3 to 6 o'clock position is -90 degrees, angle_start = -90.0 - if i_single_null == 1: - angle_end = 90.0 + deg_div_poloidal_plasma - elif i_single_null == 0: - # 3 to 12 o'clock position is +90 degrees - angle_end = 90.0 + match DivertorNumberModels(i_single_null): + case DivertorNumberModels.SINGLE_NULL: + angle_end = 90.0 + deg_div_poloidal_plasma + case DivertorNumberModels.DOUBLE_NULL: + # 3 to 12 o'clock position is +90 degrees + angle_end = 90.0 theta = np.linspace(np.deg2rad(angle_start), np.deg2rad(angle_end), 50) arc_x = rmajor + arc_radius * np.cos(theta) @@ -15006,7 +15020,7 @@ def plot_blkt_structure( # Plot arrows for the divertor angles # If double null then plot the upper also - if i_single_null == 0: + if DivertorNumberModels(i_single_null) == DivertorNumberModels.DOUBLE_NULL: # Plot arc showing the angle between the two arrows (divertor angle) arc_radius = 1.5 # 3 to 12 o'clock position is +90 degrees, @@ -16707,7 +16721,7 @@ def _add_page(name: str | None = None): plot_magnetic_fields_in_plasma( _add_page("beta").add_subplot(122, aspect="equal"), m_file, scan ) - plot_beta_profiles(pages["beta"].add_subplot(221), m_file, scan) + plot_beta_profiles(pages["beta"].add_subplot(321), m_file, scan) plot_ebw_ecrh_coupling_graph(_add_page().add_subplot(111), m_file, scan) diff --git a/process/core/solver/constraints.py b/process/core/solver/constraints.py index c224110cde..4ffebd4373 100644 --- a/process/core/solver/constraints.py +++ b/process/core/solver/constraints.py @@ -12,6 +12,7 @@ from process.core.data_structure.base import DataStructure from process.core.exceptions import ProcessError, ProcessValueError from process.data_structure.build_variables import TFCSRadialConfiguration +from process.data_structure.physics_variables import ConfinementRadiationLossModel from process.data_structure.stellarator_variables import StellaratorModel from process.models.physics.density_limit import DensityLimitModel from process.models.physics.exhaust import PlasmaExhaust @@ -286,6 +287,12 @@ def constraint_equation_2(constraint_registration, data): p_hcd_injected_total_mw: total auxiliary injected power (MW) vol_plasma: plasma volume (m3) + + Raises + ------ + ValueError + If an unknown ConfinementRadiationLossModel or PlasmaIgnitionModel is + encountered. """ # pscaling: total transport power per volume (MW/m3) @@ -293,32 +300,38 @@ def constraint_equation_2(constraint_registration, data): data.physics.pden_electron_transport_loss_mw + data.physics.pden_ion_transport_loss_mw ) - # Total power lost is scaling power plus radiation: - if data.physics.i_rad_loss == 0: - pnumerator = pscaling + data.physics.pden_plasma_rad_mw - elif data.physics.i_rad_loss == 1: - pnumerator = pscaling + data.physics.pden_plasma_core_rad_mw - else: - pnumerator = pscaling + match ConfinementRadiationLossModel(data.physics.i_rad_loss): + case ConfinementRadiationLossModel.FULL_RADIATION: + pnumerator = pscaling + data.physics.pden_plasma_rad_mw + case ConfinementRadiationLossModel.CORE_ONLY: + pnumerator = pscaling + data.physics.pden_plasma_core_rad_mw + case ConfinementRadiationLossModel.NO_RADIATION: + pnumerator = pscaling + case _: + raise ValueError( + f"Unknown ConfinementRadiationLossModel: {data.physics.i_rad_loss}" + ) - # if plasma not ignited include injected power - if ( - PlasmaIgnitionModel(data.physics.i_plasma_ignited) - == PlasmaIgnitionModel.NON_IGNITED - ): - pdenom = ( - data.physics.f_p_alpha_plasma_deposited * data.physics.pden_alpha_total_mw - + data.physics.pden_non_alpha_charged_mw - + data.physics.pden_plasma_ohmic_mw - + data.current_drive.p_hcd_injected_total_mw / data.physics.vol_plasma - ) - else: - # if plasma ignited - pdenom = ( - data.physics.f_p_alpha_plasma_deposited * data.physics.pden_alpha_total_mw - + data.physics.pden_non_alpha_charged_mw - + data.physics.pden_plasma_ohmic_mw - ) + match PlasmaIgnitionModel(data.physics.i_plasma_ignited): + case PlasmaIgnitionModel.NON_IGNITED: + pdenom = ( + data.physics.f_p_alpha_plasma_deposited + * data.physics.pden_alpha_total_mw + + data.physics.pden_non_alpha_charged_mw + + data.physics.pden_plasma_ohmic_mw + + data.current_drive.p_hcd_injected_total_mw / data.physics.vol_plasma + ) + case PlasmaIgnitionModel.IGNITED: + pdenom = ( + data.physics.f_p_alpha_plasma_deposited + * data.physics.pden_alpha_total_mw + + data.physics.pden_non_alpha_charged_mw + + data.physics.pden_plasma_ohmic_mw + ) + case _: + raise ValueError( + f"Unknown PlasmaIgnitionModel: {data.physics.i_plasma_ignited}" + ) return eq(pnumerator, pdenom, constraint_registration) @@ -799,24 +812,33 @@ def constraint_equation_24(constraint_registration, data): beta_beam: neutral beam beta component b_plasma_toroidal_on_axis: toroidal field b_plasma_total: total field - """ + + Raises + ------ + ValueError + If an unknown beta component limit is specified. + """ + match data.physics.i_beta_component: + case BetaComponentLimits.TOTAL: + value = data.physics.beta_total_vol_avg + case BetaComponentLimits.THERMAL: + # Here, the beta limit applies to only the thermal component, + # not the fast alpha or neutral beam parts + value = data.physics.beta_thermal_vol_avg + case BetaComponentLimits.THERMAL_AND_BEAM: + # Beta limit applies to thermal + neutral beam: components of the total beta, + # i.e. excludes alphas + value = data.physics.beta_thermal_vol_avg + data.physics.beta_beam + case BetaComponentLimits.TOROIDAL: + # Beta limit applies to toroidal beta + value = data.physics.beta_toroidal_vol_avg + case _: + raise ValueError( + f"Unknown BetaComponentLimits: {data.physics.i_beta_component}" + ) # Include all beta components: relevant for both tokamaks and stellarators - if ( - data.physics.i_beta_component == BetaComponentLimits.TOTAL - or data.stellarator.istell != StellaratorModel.DISABLED - ): + if data.stellarator.istell != StellaratorModel.DISABLED: value = data.physics.beta_total_vol_avg - # Here, the beta limit applies to only the thermal component, - # not the fast alpha or neutral beam parts - elif data.physics.i_beta_component == BetaComponentLimits.THERMAL: - value = data.physics.beta_thermal_vol_avg - # Beta limit applies to thermal + neutral beam: components of the total beta, - # i.e. excludes alphas - elif data.physics.i_beta_component == BetaComponentLimits.THERMAL_AND_BEAM: - value = data.physics.beta_thermal_vol_avg + data.physics.beta_beam - # Beta limit applies to toroidal beta - elif data.physics.i_beta_component == BetaComponentLimits.TOROIDAL: - value = data.physics.beta_toroidal_vol_avg return leq( value, @@ -1818,21 +1840,24 @@ def constraint_equation_85(constraint_registration, data): life_div_fpy: calculated divertor power year lifetime (years) i_cp_lifetime: switch chosing which plant element the CP the CP lifetime must equate - """ - # The CP lifetime is equal to the the divertor one - if data.costs.i_cp_lifetime == 0: - bound = data.costs.cplife_input - - elif data.costs.i_cp_lifetime == 1: - bound = data.costs.life_div_fpy - - # The CP lifetime is equal to the tritium breeding blankets / FW one - elif data.costs.i_cp_lifetime == 2: - bound = data.fwbs.life_blkt_fpy - elif data.costs.i_cp_lifetime == 3: - bound = data.costs.life_plant + Raises + ------ + ValueError + If an unknown i_cp_lifetime option is specified. + """ + match data.costs.i_cp_lifetime: + case 0: + bound = data.costs.cplife_input + case 1: + bound = data.costs.life_div_fpy + case 2: + bound = data.fwbs.life_blkt_fpy + case 3: + bound = data.costs.life_plant + case _: + raise ValueError(f"Unknown i_cp_lifetime: {data.costs.i_cp_lifetime}") return eq(data.costs.cplife, bound, constraint_registration) diff --git a/process/core/solver/objectives.py b/process/core/solver/objectives.py index 5536b1fd15..72367d32c4 100644 --- a/process/core/solver/objectives.py +++ b/process/core/solver/objectives.py @@ -52,54 +52,56 @@ def objective_function(i_figure_merit: int, data: DataStructure) -> float: # -1 = maximise # +1 = minimise objective_sign = np.sign(i_figure_merit) - - if figure_of_merit == FiguresOfMerit.MAJOR_RADIUS: - objective_metric = 0.2 * data.physics.rmajor - elif figure_of_merit == FiguresOfMerit.NEUTRON_WALL_LOAD: - objective_metric = data.physics.pflux_fw_neutron_mw - elif figure_of_merit == FiguresOfMerit.P_TF_PLUS_P_PF: - objective_metric = (data.tfcoil.tfcmw + 1e-3 * data.pf_power.srcktpm) / 10.0 - elif figure_of_merit == FiguresOfMerit.FUSION_GAIN_Q: - objective_metric = data.current_drive.big_q_plasma - elif figure_of_merit == FiguresOfMerit.COST_OF_ELECTRICITY: - objective_metric = data.costs.coe / 100.0 - elif figure_of_merit == FiguresOfMerit.CAPITAL_COST: - objective_metric = ( - data.costs.cdirt / 1.0e3 - if data.costs.ireactor == 0 - else data.costs.concost / 1.0e4 - ) - elif figure_of_merit == FiguresOfMerit.ASPECT_RATIO: - objective_metric = data.physics.aspect - elif figure_of_merit == FiguresOfMerit.DIVERTOR_HEAT_LOAD: - objective_metric = data.divertor.pflux_div_heat_load_mw - elif figure_of_merit == FiguresOfMerit.TOROIDAL_FIELD: - objective_metric = data.physics.b_plasma_toroidal_on_axis - elif figure_of_merit == FiguresOfMerit.TOTAL_INJECTED_POWER: - objective_metric = data.current_drive.p_hcd_injected_total_mw - elif figure_of_merit == FiguresOfMerit.PULSE_LENGTH: - objective_metric = data.times.t_plant_pulse_burn / 2.0e4 - elif figure_of_merit == FiguresOfMerit.PLANT_AVAILABILITY_FACTOR: - if ( - AvailabilityModel(data.costs.i_plant_availability) - == AvailabilityModel.USER_INPUT - ): - raise ProcessValueError( - "i_figure_merit=15 requires `f_t_plant_available` to be calculated, not " - "user input" + match FiguresOfMerit(figure_of_merit): + case FiguresOfMerit.MAJOR_RADIUS: + objective_metric = 0.2 * data.physics.rmajor + case FiguresOfMerit.NEUTRON_WALL_LOAD: + objective_metric = data.physics.pflux_fw_neutron_mw + case FiguresOfMerit.P_TF_PLUS_P_PF: + objective_metric = (data.tfcoil.tfcmw + 1e-3 * data.pf_power.srcktpm) / 10.0 + case FiguresOfMerit.FUSION_GAIN_Q: + objective_metric = data.current_drive.big_q_plasma + case FiguresOfMerit.COST_OF_ELECTRICITY: + objective_metric = data.costs.coe / 100.0 + case FiguresOfMerit.CAPITAL_COST: + objective_metric = ( + data.costs.cdirt / 1.0e3 + if data.costs.ireactor == 0 + else data.costs.concost / 1.0e4 + ) + case FiguresOfMerit.ASPECT_RATIO: + objective_metric = data.physics.aspect + case FiguresOfMerit.DIVERTOR_HEAT_LOAD: + objective_metric = data.divertor.pflux_div_heat_load_mw + case FiguresOfMerit.TOROIDAL_FIELD: + objective_metric = data.physics.b_plasma_toroidal_on_axis + case FiguresOfMerit.TOTAL_INJECTED_POWER: + objective_metric = data.current_drive.p_hcd_injected_total_mw + case FiguresOfMerit.PULSE_LENGTH: + objective_metric = data.times.t_plant_pulse_burn / 2.0e4 + case FiguresOfMerit.PLANT_AVAILABILITY_FACTOR: + if ( + AvailabilityModel(data.costs.i_plant_availability) + == AvailabilityModel.USER_INPUT + ): + raise ProcessValueError( + "i_figure_merit=15 requires `f_t_plant_available` to be calculated, " + "not user input" + ) + objective_metric = data.costs.f_t_plant_available + case FiguresOfMerit.MIN_R0_MAX_TAU_BURN: + objective_metric = 0.95 * (data.physics.rmajor / 9.0) - 0.05 * ( + data.times.t_plant_pulse_burn / 7200.0 + ) + case FiguresOfMerit.NET_ELECTRICAL_OUTPUT: + objective_metric = data.heat_transport.p_plant_electric_net_mw / 500.0 + case FiguresOfMerit.NULL_FIGURE_OF_MERIT: + objective_metric = 1.0 + case FiguresOfMerit.MAX_Q_MAX_T_PLANT_PULSE_BURN: + objective_metric = -0.5 * (data.current_drive.big_q_plasma / 20.0) - 0.5 * ( + data.times.t_plant_pulse_burn / 7200.0 ) - objective_metric = data.costs.f_t_plant_available - elif figure_of_merit == FiguresOfMerit.MIN_R0_MAX_TAU_BURN: - objective_metric = 0.95 * (data.physics.rmajor / 9.0) - 0.05 * ( - data.times.t_plant_pulse_burn / 7200.0 - ) - elif figure_of_merit == FiguresOfMerit.NET_ELECTRICAL_OUTPUT: - objective_metric = data.heat_transport.p_plant_electric_net_mw / 500.0 - elif figure_of_merit == FiguresOfMerit.NULL_FIGURE_OF_MERIT: - objective_metric = 1.0 - elif figure_of_merit == FiguresOfMerit.MAX_Q_MAX_T_PLANT_PULSE_BURN: - objective_metric = -0.5 * (data.current_drive.big_q_plasma / 20.0) - 0.5 * ( - data.times.t_plant_pulse_burn / 7200.0 - ) + case _: + raise ProcessValueError(f"Unknown figure_of_merit: {figure_of_merit}") return objective_sign * objective_metric diff --git a/process/data_structure/physics_variables.py b/process/data_structure/physics_variables.py index b09ce1a7cf..83409542c1 100644 --- a/process/data_structure/physics_variables.py +++ b/process/data_structure/physics_variables.py @@ -2,10 +2,102 @@ from dataclasses import dataclass, field from enum import IntEnum, unique +from types import DynamicClassAttribute import numpy as np +@unique +class PlasmaConfinementTransitionModel(IntEnum): + """Enum for plasma L -> H and L -> I transition power threshold models.""" + + ITER1996_NOMINAL = (1, "ITER-1996 Nominal") + ITER1996_UPPER = (2, "ITER-1996 Upper") + ITER1996_LOWER = (3, "ITER-1996 Lower") + SNIPES1997_ITER = (4, "Snipes 1997 ITER Scaling I") + SNIPES1997_KAPPA = (5, "Snipes 1997 ITER Scaling II") + MARTIN08_NOMINAL = (6, "Martin 2008 Nominal") + MARTIN08_UPPER = (7, "Martin 2008 Upper") + MARTIN08_LOWER = (8, "Martin 2008 Lower") + SNIPES2000_NOMINAL = (9, "Snipes 2000 Nominal") + SNIPES2000_UPPER = (10, "Snipes 2000 Upper") + SNIPES2000_LOWER = (11, "Snipes 2000 Lower") + SNIPES2000_CLOSED_DIVERTOR_NOMINAL = (12, "Snipes 2000 Closed Divertor Nominal") + SNIPES2000_CLOSED_DIVERTOR_UPPER = (13, "Snipes 2000 Closed Divertor Upper") + SNIPES2000_CLOSED_DIVERTOR_LOWER = (14, "Snipes 2000 Closed Divertor Lower") + HUBBARD2012_NOMINAL = (15, "Hubbard 2012 Nominal") + HUBBARD2012_LOWER = (16, "Hubbard 2012 Lower") + HUBBARD2012_UPPER = (17, "Hubbard 2012 Upper") + HUBBARD2017_I_MODE = (18, "Hubbard 2017 I-Mode") + MARTIN08_ASPECT_NOMINAL = (19, "Martin 2008 Aspect Corrected Nominal") + MARTIN08_ASPECT_UPPER = (20, "Martin 2008 Aspect Corrected Upper") + MARTIN08_ASPECT_LOWER = (21, "Martin 2008 Aspect Corrected Lower") + + def __new__(cls, value: int, full_name: str): + """Create a new PlasmaConfinementTransitionModel instance. + + Parameters + ---------- + value : int + The integer value of the enum member. + full_name : str + The full descriptive name of the enum member. + + Returns + ------- + PlasmaConfinementTransitionModel + A new instance of PlasmaConfinementTransitionModel. + """ + obj = int.__new__(cls, value) + obj._value_ = value + obj.full_name = full_name + return obj + + +@unique +class PlasmaCurrentModel(IntEnum): + """Enumeration of plasma current scaling models available for calculations. + + Each model represents a different scaling law used to calculate plasma + current based on various plasma and machine parameters. + """ + + PENG_ANALYTIC_FIT = (1, "Peng analytic fit") + PENG_DIVERTOR_SCALING = (2, "Peng divertor scaling") + ITER_SCALING = (3, "Simple ITER scaling (cylindrical case)") + IPDG89_SCALING = (4, "IPDG89 scaling") + TODD_EMPIRICAL_SCALING_I = (5, "Todd empirical scaling I") + TODD_EMPIRICAL_SCALING_II = (6, "Todd empirical scaling II") + CONNOR_HASTIE_MODEL = (7, "Connor-Hastie model") + SAUTER_SCALING = (8, "Sauter scaling") + FIESTA_ST_SCALING = (9, "FIESTA ST scaling") + + def __new__(cls, value: int, full_name: str): + """Create a new PlasmaCurrentModel enum member with value and full_name. + + Parameters + ---------- + value : int + The numeric value of the enum member. + full_name : str + The full name description of the plasma current model. + + Returns + ------- + PlasmaCurrentModel + A new enum member with the specified value and full_name. + """ + obj = int.__new__(cls, value) + obj._value_ = value + obj._full_name_ = full_name + return obj + + @DynamicClassAttribute + def full_name(self): + """The full name of the plasma current model.""" + return self._full_name_ + + @unique class OutbordSOLPowerDecayLengthModel(IntEnum): """Enum for outboard scrape off layer power decay length models with descriptions.""" diff --git a/process/models/blankets/hcpb.py b/process/models/blankets/hcpb.py index 15895cdca6..2a257962df 100644 --- a/process/models/blankets/hcpb.py +++ b/process/models/blankets/hcpb.py @@ -803,197 +803,199 @@ def powerflow_calc(self, output: bool): 1 - self.data.first_wall.a_fw_outboard / self.data.first_wall.a_fw_total ) - i_p_coolant_pumping = PumpingPowerModelTypes(self.data.fwbs.i_p_coolant_pumping) - if i_p_coolant_pumping == PumpingPowerModelTypes.FRACTION_OF_HEAT: - # User sets mechanical pumping power directly - ( - self.data.heat_transport.p_fw_coolant_pump_mw, - self.data.heat_transport.p_blkt_coolant_pump_mw, - self.data.heat_transport.p_shld_coolant_pump_mw, - self.data.heat_transport.p_div_coolant_pump_mw, - ) = pumping_powers_as_fractions( - f_p_fw_coolant_pump_total_heat=self.data.heat_transport.f_p_fw_coolant_pump_total_heat, - f_p_blkt_coolant_pump_total_heat=self.data.heat_transport.f_p_blkt_coolant_pump_total_heat, - f_p_shld_coolant_pump_total_heat=self.data.heat_transport.f_p_shld_coolant_pump_total_heat, - f_p_div_coolant_pump_total_heat=self.data.heat_transport.f_p_div_coolant_pump_total_heat, - p_fw_nuclear_heat_total_mw=self.data.fwbs.p_fw_nuclear_heat_total_mw, - psurffwi=self.data.fwbs.psurffwi, - psurffwo=self.data.fwbs.psurffwo, - p_blkt_nuclear_heat_total_mw=self.data.fwbs.p_blkt_nuclear_heat_total_mw, - p_shld_nuclear_heat_mw=self.data.heat_transport.p_shld_nuclear_heat_mw, - p_cp_shield_nuclear_heat_mw=self.data.fwbs.p_cp_shield_nuclear_heat_mw, - p_plasma_separatrix_mw=self.data.physics.p_plasma_separatrix_mw, - p_div_nuclear_heat_total_mw=self.data.fwbs.p_div_nuclear_heat_total_mw, - p_div_rad_total_mw=self.data.fwbs.p_div_rad_total_mw, - ) - - elif i_p_coolant_pumping == PumpingPowerModelTypes.MECHANICAL: - # 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 - self.thermo_hydraulic_model(output) - - # For divertor and shield, mechanical pumping power is a fraction of thermal - # power removed by coolant - self.data.heat_transport.p_shld_coolant_pump_mw = ( - self.data.heat_transport.f_p_shld_coolant_pump_total_heat - * ( - self.data.fwbs.p_shld_nuclear_heat_mw - + self.data.fwbs.p_cp_shield_nuclear_heat_mw - ) - ) - self.data.heat_transport.p_div_coolant_pump_mw = ( - self.data.heat_transport.f_p_div_coolant_pump_total_heat - * ( - self.data.physics.p_plasma_separatrix_mw - + self.data.fwbs.p_div_nuclear_heat_total_mw - + self.data.fwbs.p_div_rad_total_mw + match PumpingPowerModelTypes(self.data.fwbs.i_p_coolant_pumping): + case PumpingPowerModelTypes.FRACTION_OF_HEAT: + # User sets mechanical pumping power directly + ( + self.data.heat_transport.p_fw_coolant_pump_mw, + self.data.heat_transport.p_blkt_coolant_pump_mw, + self.data.heat_transport.p_shld_coolant_pump_mw, + self.data.heat_transport.p_div_coolant_pump_mw, + ) = pumping_powers_as_fractions( + f_p_fw_coolant_pump_total_heat=self.data.heat_transport.f_p_fw_coolant_pump_total_heat, + f_p_blkt_coolant_pump_total_heat=self.data.heat_transport.f_p_blkt_coolant_pump_total_heat, + f_p_shld_coolant_pump_total_heat=self.data.heat_transport.f_p_shld_coolant_pump_total_heat, + f_p_div_coolant_pump_total_heat=self.data.heat_transport.f_p_div_coolant_pump_total_heat, + p_fw_nuclear_heat_total_mw=self.data.fwbs.p_fw_nuclear_heat_total_mw, + psurffwi=self.data.fwbs.psurffwi, + psurffwo=self.data.fwbs.psurffwo, + p_blkt_nuclear_heat_total_mw=self.data.fwbs.p_blkt_nuclear_heat_total_mw, + p_shld_nuclear_heat_mw=self.data.heat_transport.p_shld_nuclear_heat_mw, + p_cp_shield_nuclear_heat_mw=self.data.fwbs.p_cp_shield_nuclear_heat_mw, + p_plasma_separatrix_mw=self.data.physics.p_plasma_separatrix_mw, + p_div_nuclear_heat_total_mw=self.data.fwbs.p_div_nuclear_heat_total_mw, + p_div_rad_total_mw=self.data.fwbs.p_div_rad_total_mw, ) - ) - - elif i_p_coolant_pumping == PumpingPowerModelTypes.MECHANICAL_WITH_PRESSURE_DROP: - # Issue #503 - # Mechanical pumping power is calculated using specified pressure drop for - # first wall and blanket circuit, including heat exchanger and pipes - pfactor = ( - self.data.primary_pumping.p_he - / (self.data.primary_pumping.p_he - self.data.primary_pumping.dp_he) - ) ** ( - (self.data.primary_pumping.gamma_he - 1) - / self.data.primary_pumping.gamma_he - ) - # N.B. Currenlty i_p_coolant_pumping==3 uses separate variables found in - # primary_pumping_variables rather than self.data.fwbs. - # The pressure (p_he) is assumed to be the pressure at the - # blanket inlet/pump oulet. - # The pressures (found in fwbs_variables) for coolants using - # i_p_coolant_pumping==2 are assumed to be the pressure at the - # blanket oulet/pump inlet. - # The equation below is used for i_p_coolant_pumping==2: - # pfactor = ((pressure+deltap)/pressure)**((gamma-1.0d0)/gamma) - t_in_compressor = self.data.primary_pumping.t_in_bb / pfactor - dt_he = ( - self.data.primary_pumping.t_out_bb - self.data.primary_pumping.t_in_bb - ) - fpump = t_in_compressor / (self.data.fwbs.etaiso * dt_he) * (pfactor - 1) - p_plasma = ( - self.data.fwbs.p_fw_nuclear_heat_total_mw - + self.data.fwbs.psurffwi - + self.data.fwbs.psurffwo - + self.data.fwbs.p_blkt_nuclear_heat_total_mw - ) - self.data.primary_pumping.p_fw_blkt_coolant_pump_mw = ( - self.data.primary_pumping.f_p_fw_blkt_pump - * fpump - / (1 - fpump) - * p_plasma - ) - # For divertor and shield, mechanical pumping power is a fraction of thermal - # power removed by coolant - self.data.heat_transport.p_shld_coolant_pump_mw = ( - self.data.heat_transport.f_p_shld_coolant_pump_total_heat - * ( - self.data.fwbs.p_shld_nuclear_heat_mw - + self.data.fwbs.p_cp_shield_nuclear_heat_mw - ) - ) - self.data.heat_transport.p_div_coolant_pump_mw = ( - self.data.heat_transport.f_p_div_coolant_pump_total_heat - * ( - self.data.physics.p_plasma_separatrix_mw - + self.data.fwbs.p_div_nuclear_heat_total_mw - + self.data.fwbs.p_div_rad_total_mw - ) - ) - if output: - po.oheadr(self.outfile, "Pumping for primary coolant (helium)") - po.ovarre( - self.outfile, - "Pressure drop in FW and blanket coolant incl. hx and pipes (Pa)", - "(dp_he)", - self.data.primary_pumping.dp_he, - ) - po.ovarre( - self.outfile, - "Fraction of FW and blanket thermal power required for pumping", - "(fpump)", - fpump, - "OP ", - ) - po.ovarre( - self.outfile, - "Total power absorbed by FW & blanket (MW)", - "(p_plasma)", - p_plasma, - "OP ", + case PumpingPowerModelTypes.MECHANICAL: + # 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 + self.thermo_hydraulic_model(output) + + # For divertor and shield, mechanical pumping power is a fraction + # of thermal power removed by coolant + self.data.heat_transport.p_shld_coolant_pump_mw = ( + self.data.heat_transport.f_p_shld_coolant_pump_total_heat + * ( + self.data.fwbs.p_shld_nuclear_heat_mw + + self.data.fwbs.p_cp_shield_nuclear_heat_mw + ) ) - po.ovarre( - self.outfile, - "Inlet temperature of FW & blanket coolant pump (K)", - "(t_in_compressor)", - t_in_compressor, - "OP ", + self.data.heat_transport.p_div_coolant_pump_mw = ( + self.data.heat_transport.f_p_div_coolant_pump_total_heat + * ( + self.data.physics.p_plasma_separatrix_mw + + self.data.fwbs.p_div_nuclear_heat_total_mw + + self.data.fwbs.p_div_rad_total_mw + ) ) - po.ovarre( - self.outfile, - "Coolant pump outlet/Inlet temperature of FW & blanket (K)", - "(t_in_bb)", - self.data.primary_pumping.t_in_bb, - ) - po.ovarre( - self.outfile, - "Outlet temperature of FW & blanket (K)", - "(t_out_bb)", - self.data.primary_pumping.t_out_bb, - ) - 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 ", + + case PumpingPowerModelTypes.MECHANICAL_WITH_PRESSURE_DROP: + # Issue #503 + # Mechanical pumping power is calculated using specified pressure drop + # for first wall and blanket circuit, including heat exchanger and pipes + pfactor = ( + self.data.primary_pumping.p_he + / (self.data.primary_pumping.p_he - self.data.primary_pumping.dp_he) + ) ** ( + (self.data.primary_pumping.gamma_he - 1) + / self.data.primary_pumping.gamma_he ) - 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 ", + # N.B. Currenlty i_p_coolant_pumping==3 uses separate variables found in + # primary_pumping_variables rather than self.data.fwbs. + # The pressure (p_he) is assumed to be the pressure at the + # blanket inlet/pump oulet. + # The pressures (found in fwbs_variables) for coolants using + # i_p_coolant_pumping==2 are assumed to be the pressure at the + # blanket oulet/pump inlet. + # The equation below is used for i_p_coolant_pumping==2: + # pfactor = ((pressure+deltap)/pressure)**((gamma-1.0d0)/gamma) + t_in_compressor = self.data.primary_pumping.t_in_bb / pfactor + dt_he = ( + self.data.primary_pumping.t_out_bb + - self.data.primary_pumping.t_in_bb ) - 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 ", + fpump = t_in_compressor / (self.data.fwbs.etaiso * dt_he) * (pfactor - 1) + p_plasma = ( + self.data.fwbs.p_fw_nuclear_heat_total_mw + + self.data.fwbs.psurffwi + + self.data.fwbs.psurffwo + + self.data.fwbs.p_blkt_nuclear_heat_total_mw ) - po.ovarre( - self.outfile, - "Radius of blanket cooling channels (m)", - "(radius_blkt_channel)", - self.data.fwbs.radius_blkt_channel, + self.data.primary_pumping.p_fw_blkt_coolant_pump_mw = ( + self.data.primary_pumping.f_p_fw_blkt_pump + * fpump + / (1 - fpump) + * p_plasma ) - 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, + + # For divertor and shield, mechanical pumping power is a fraction of + # thermal power removed by coolant + self.data.heat_transport.p_shld_coolant_pump_mw = ( + self.data.heat_transport.f_p_shld_coolant_pump_total_heat + * ( + self.data.fwbs.p_shld_nuclear_heat_mw + + self.data.fwbs.p_cp_shield_nuclear_heat_mw + ) ) - 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, + self.data.heat_transport.p_div_coolant_pump_mw = ( + self.data.heat_transport.f_p_div_coolant_pump_total_heat + * ( + self.data.physics.p_plasma_separatrix_mw + + self.data.fwbs.p_div_nuclear_heat_total_mw + + self.data.fwbs.p_div_rad_total_mw + ) ) + if output: + po.oheadr(self.outfile, "Pumping for primary coolant (helium)") + po.ovarre( + self.outfile, + "Pressure drop in FW and blanket coolant incl. hx and " + "pipes [Pa]", + "(dp_he)", + self.data.primary_pumping.dp_he, + ) + po.ovarre( + self.outfile, + "Fraction of FW and blanket thermal power required for pumping", + "(fpump)", + fpump, + "OP ", + ) + po.ovarre( + self.outfile, + "Total power absorbed by FW & blanket (MW)", + "(p_plasma)", + p_plasma, + "OP ", + ) + po.ovarre( + self.outfile, + "Inlet temperature of FW & blanket coolant pump (K)", + "(t_in_compressor)", + t_in_compressor, + "OP ", + ) + po.ovarre( + self.outfile, + "Coolant pump outlet/Inlet temperature of FW & blanket (K)", + "(t_in_bb)", + self.data.primary_pumping.t_in_bb, + ) + po.ovarre( + self.outfile, + "Outlet temperature of FW & blanket (K)", + "(t_out_bb)", + self.data.primary_pumping.t_out_bb, + ) + 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, + ) @staticmethod def st_cp_angle_fraction(z_cp_top, r_cp_mid, r_cp_top, rmajor): @@ -1415,19 +1417,20 @@ def write_output(self): # ST centre post if self.data.physics.itart == 1: - if self.data.tfcoil.i_tf_sup == TFConductorModel.WATER_COOLED_COPPER: - po.osubhd(self.outfile, "(Copper resistive centrepost used)") - elif self.data.tfcoil.i_tf_sup == TFConductorModel.SUPERCONDUCTING: - po.osubhd(self.outfile, "(Superdonducting magnet centrepost used)") - po.ovarre( - self.outfile, - "ST centrepost TF fast neutron fllux (E > 0.1 MeV) (m^(-2).s^(-1))", - "(neut_flux_cp)", - self.data.fwbs.neut_flux_cp, - "OP ", - ) - elif self.data.tfcoil.i_tf_sup == TFConductorModel.HELIUM_COOLED_ALUMINIUM: - po.osubhd(self.outfile, "(Aluminium magnet centrepost used)") + match TFConductorModel(self.data.tfcoil.i_tf_sup): + case TFConductorModel.WATER_COOLED_COPPER: + po.osubhd(self.outfile, "(Copper resistive centrepost used)") + case TFConductorModel.SUPERCONDUCTING: + po.osubhd(self.outfile, "(Superconducting magnet centrepost used)") + po.ovarre( + self.outfile, + "ST centrepost TF fast neutron fllux (E > 0.1 MeV) [m⁻²s⁻¹]", + "(neut_flux_cp)", + self.data.fwbs.neut_flux_cp, + "OP ", + ) + case TFConductorModel.HELIUM_COOLED_ALUMINIUM: + po.osubhd(self.outfile, "(Aluminium magnet centrepost used)") po.ovarre( self.outfile, diff --git a/process/models/build.py b/process/models/build.py index 2c1d86c057..146002c33c 100644 --- a/process/models/build.py +++ b/process/models/build.py @@ -20,7 +20,7 @@ CurrentDriveMethodType, CurrentDriveModel, ) -from process.models.tfcoil.base import TFCoilShapeModel +from process.models.tfcoil.base import TFCoilShapeModel, TFConductorModel from process.models.tfcoil.superconducting import SuperconductingTFWPShapeType logger = logging.getLogger(__name__) @@ -495,29 +495,30 @@ def calculate_vertical_build(self, output: bool): ) # Vertical locations of divertor coils - if i_single_null == DivertorNumberModels.DOUBLE_NULL: - self.data.build.z_tf_top = ( - self.data.build.z_tf_inside_half + self.data.build.dr_tf_inboard - ) - self.data.build.dz_tf_upper_lower_midplane = 0.0e0 - else: - self.data.build.z_tf_top = ( - self.data.build.dr_tf_inboard - + self.data.build.dr_tf_shld_gap - + self.data.build.dz_shld_thermal - + self.data.build.dz_shld_vv_gap - + self.data.build.dz_vv_upper - + self.data.build.dz_shld_upper - + self.data.build.dr_shld_blkt_gap - + self.data.build.dz_blkt_upper - + 0.5e0 - * (self.data.build.dr_fw_inboard + self.data.build.dr_fw_outboard) - + self.data.build.dz_fw_plasma_gap - + self.data.build.z_plasma_xpoint_upper - ) - self.data.build.dz_tf_upper_lower_midplane = self.data.build.z_tf_top - ( - self.data.build.z_tf_inside_half + self.data.build.dr_tf_inboard - ) + match DivertorNumberModels(i_single_null): + case DivertorNumberModels.DOUBLE_NULL: + self.data.build.z_tf_top = ( + self.data.build.z_tf_inside_half + self.data.build.dr_tf_inboard + ) + self.data.build.dz_tf_upper_lower_midplane = 0.0e0 + case DivertorNumberModels.SINGLE_NULL: + self.data.build.z_tf_top = ( + self.data.build.dr_tf_inboard + + self.data.build.dr_tf_shld_gap + + self.data.build.dz_shld_thermal + + self.data.build.dz_shld_vv_gap + + self.data.build.dz_vv_upper + + self.data.build.dz_shld_upper + + self.data.build.dr_shld_blkt_gap + + self.data.build.dz_blkt_upper + + 0.5e0 + * (self.data.build.dr_fw_inboard + self.data.build.dr_fw_outboard) + + self.data.build.dz_fw_plasma_gap + + self.data.build.z_plasma_xpoint_upper + ) + self.data.build.dz_tf_upper_lower_midplane = self.data.build.z_tf_top - ( + self.data.build.z_tf_inside_half + self.data.build.dr_tf_inboard + ) def divgeom(self, output: bool): """Divertor geometry calculation @@ -904,6 +905,12 @@ def plasma_outboard_edge_toroidal_ripple( maximum ripple (m) - flag: Applicability flag (0 = OK, non-zero = fitted-range concern) + + Raises + ------ + ProcessValueError + If the TF coil parameters are not properly defined. + Notes ----- - Fitted coefficients originate from parametric MAGINT runs (M. Kovari, 2014). @@ -912,35 +919,36 @@ def plasma_outboard_edge_toroidal_ripple( - The routine sets an applicability flag when fitted-range assumptions are exceeded. """ - if i_tf_sup == 1: - # Minimal inboard WP radius [m] - r_wp_min = r_tf_wp_inboard_inner - - i_tf_wp_geom = SuperconductingTFWPShapeType(i_tf_wp_geom) - - # Rectangular WP - if i_tf_wp_geom == SuperconductingTFWPShapeType.RECTANGULAR: - r_wp_max = r_wp_min - - # Double rectangle WP - elif i_tf_wp_geom == SuperconductingTFWPShapeType.DOUBLE_RECTANGULAR: - r_wp_max = r_tf_wp_inboard_centre + match TFConductorModel(i_tf_sup): + case TFConductorModel.SUPERCONDUCTING: + # Minimal inboard WP radius [m] + r_wp_min = r_tf_wp_inboard_inner + + match SuperconductingTFWPShapeType(i_tf_wp_geom): + case SuperconductingTFWPShapeType.RECTANGULAR: + r_wp_max = r_wp_min + case SuperconductingTFWPShapeType.DOUBLE_RECTANGULAR: + r_wp_max = r_tf_wp_inboard_centre + case SuperconductingTFWPShapeType.TRAPEZOIDAL: + r_wp_max = r_tf_wp_inboard_outer + + # Calculated maximum toroidal WP toroidal thickness [m] + dx_tf_wp_conductor_max = dx_tf_wp_primary_toroidal - 2.0 * ( + dx_tf_wp_insulation + dx_tf_wp_insertion_gap + ) - # Trapezoidal WP - elif i_tf_wp_geom == SuperconductingTFWPShapeType.TRAPEZOIDAL: + # Resistive magnet case + case ( + TFConductorModel.WATER_COOLED_COPPER + | TFConductorModel.HELIUM_COOLED_ALUMINIUM + ): + # Radius used to define the dx_tf_wp_conductor_max [m] r_wp_max = r_tf_wp_inboard_outer + # Calculated maximum toroidal WP toroidal thickness [m] + dx_tf_wp_conductor_max = 2.0e0 * r_wp_max * np.tan(np.pi / n_tf_coils) - # Calculated maximum toroidal WP toroidal thickness [m] - dx_tf_wp_conductor_max = dx_tf_wp_primary_toroidal - 2.0 * ( - dx_tf_wp_insulation + dx_tf_wp_insertion_gap - ) - - # Resistive magnet case - else: - # Radius used to define the dx_tf_wp_conductor_max [m] - r_wp_max = r_tf_wp_inboard_outer - # Calculated maximum toroidal WP toroidal thickness [m] - dx_tf_wp_conductor_max = 2.0e0 * r_wp_max * np.tan(np.pi / n_tf_coils) + case _: + raise ProcessValueError("Unsupported TF conductor model.") flag = 0 if i_tf_shape == TFCoilShapeModel.PICTURE_FRAME: @@ -1315,29 +1323,30 @@ def _ripple_flag_validation(self): "Ripple result may be inaccurate, as the fit has been extrapolated" ) - if self.data.build.ripflag == 1: - warning_str = ( - "(TF coil ripple calculation) " - "Dimensionless coil width X out of fitted range. %s" - ) - diagnostic = ( - self.data.tfcoil.dx_tf_wp_primary_toroidal - * self.data.tfcoil.n_tf_coils - / self.data.physics.rmajor - ) - elif self.data.build.ripflag == 2: - warning_str = ( - "(TF coil ripple calculation) " - "No. of TF coils not between 16 and 20 inclusive " - ) - diagnostic = f"{self.data.tfcoil.n_tf_coils=}" - else: - diagnostic = ( - self.data.physics.rmajor + self.data.physics.rminor - ) / self.data.build.r_tf_outboard_mid - warning_str = ( - "(TF coil ripple calculation) (R+a)/rtot=%s out of fitted range.", - ) + match self.data.build.ripflag: + case 1: + warning_str = ( + "(TF coil ripple calculation) " + "Dimensionless coil width X out of fitted range. %s" + ) + diagnostic = ( + self.data.tfcoil.dx_tf_wp_primary_toroidal + * self.data.tfcoil.n_tf_coils + / self.data.physics.rmajor + ) + case 2: + warning_str = ( + "(TF coil ripple calculation) " + "No. of TF coils not between 16 and 20 inclusive " + ) + diagnostic = f"{self.data.tfcoil.n_tf_coils=}" + case _: + diagnostic = ( + self.data.physics.rmajor + self.data.physics.rminor + ) / self.data.build.r_tf_outboard_mid + warning_str = ( + "(TF coil ripple calculation) (R+a)/rtot=%s out of fitted range.", + ) logger.warning(warning_str, diagnostic) diff --git a/process/models/divertor.py b/process/models/divertor.py index 2723bd8c20..81b5c1c306 100644 --- a/process/models/divertor.py +++ b/process/models/divertor.py @@ -219,11 +219,12 @@ def divtart( # Total divertor area # Single null case - if i_single_null == DivertorNumberModels.SINGLE_NULL: - areadv = a1 + a2 + a3 - # Double null case - elif i_single_null == DivertorNumberModels.DOUBLE_NULL: - areadv = 2.0 * (a1 + a2 + a3) + match DivertorNumberModels(i_single_null): + case DivertorNumberModels.SINGLE_NULL: + areadv = a1 + a2 + a3 + # Double null case + case DivertorNumberModels.DOUBLE_NULL: + areadv = 2.0 * (a1 + a2 + a3) if ( DivertorHeatLoadModel(self.data.divertor.i_div_heat_load) diff --git a/process/models/geometry/plasma.py b/process/models/geometry/plasma.py index cd8c3f8f1d..139cec932e 100644 --- a/process/models/geometry/plasma.py +++ b/process/models/geometry/plasma.py @@ -83,18 +83,22 @@ def plasma_geometry( theta2 = np.arcsin((kappa * rminor) / r2) inang = 1.0 / r1 outang = 1.5 / r2 - if i_single_null == DivertorNumberModels.DOUBLE_NULL: - angs1 = np.linspace( - -(inang + theta1) + np.pi, (inang + theta1) + np.pi, 500, endpoint=True - ) - angs2 = np.linspace( - -(outang + theta2), (outang + theta2), 500, endpoint=True - ) - else: - angs1 = np.linspace( - -theta1 + np.pi, (inang + theta1) + np.pi, 500, endpoint=True - ) - angs2 = np.linspace(-(outang + theta2), theta2, 500, endpoint=True) + match DivertorNumberModels(i_single_null): + case DivertorNumberModels.DOUBLE_NULL: + angs1 = np.linspace( + -(inang + theta1) + np.pi, + (inang + theta1) + np.pi, + 500, + endpoint=True, + ) + angs2 = np.linspace( + -(outang + theta2), (outang + theta2), 500, endpoint=True + ) + case DivertorNumberModels.SINGLE_NULL: + angs1 = np.linspace( + -theta1 + np.pi, (inang + theta1) + np.pi, 500, endpoint=True + ) + angs2 = np.linspace(-(outang + theta2), theta2, 500, endpoint=True) xs1 = -(r1 * np.cos(angs1) - x1) ys1 = r1 * np.sin(angs1) diff --git a/process/models/pfcoil.py b/process/models/pfcoil.py index 0baf378283..cac752e19a 100644 --- a/process/models/pfcoil.py +++ b/process/models/pfcoil.py @@ -247,113 +247,111 @@ def pfcoil(self): # N.B. Problems here if coil=n_pf_coils_in_group(group) is greater than 2. for group in range(self.data.pf_coil.n_pf_coil_groups): - if self.data.pf_coil.i_pf_location[group] == PFLocationTypes.ABOVE_CS: - # PF coil is stacked on top of the Central Solenoid - # Use a helper function to compute r_pf_coil_middle_group_array and - # z_pf_coil_middle_group_array arrays for this group + match self.data.pf_coil.i_pf_location[group]: + case PFLocationTypes.ABOVE_CS: + # PF coil is stacked on top of the Central Solenoid + # Use a helper function to compute r_pf_coil_middle_group_array and + # z_pf_coil_middle_group_array arrays for this group + + r_pf_coil_middle_group_array, z_pf_coil_middle_group_array = ( + self.place_pf_above_cs( + n_pf_coils_in_group=self.data.pf_coil.n_pf_coils_in_group, + n_pf_group=group, + r_cs_middle=self.data.pf_coil.r_cs_middle, + dr_pf_cs_middle_offset=self.data.pf_coil.dr_pf_cs_middle_offset, + z_tf_inside_half=self.data.build.z_tf_inside_half, + dr_tf_inboard=self.data.build.dr_tf_inboard, + z_cs_coil_upper=self.data.pf_coil.dz_cs_full / 2, + ) + ) + for coil in range(self.data.pf_coil.n_pf_coils_in_group[group]): + self.data.pf_coil.r_pf_coil_middle_group_array[group, coil] = ( + r_pf_coil_middle_group_array[group, coil] + ) + self.data.pf_coil.z_pf_coil_middle_group_array[group, coil] = ( + z_pf_coil_middle_group_array[group, coil] + ) - r_pf_coil_middle_group_array, z_pf_coil_middle_group_array = ( - self.place_pf_above_cs( + case PFLocationTypes.ABOVE_TF: + # PF coil is on top of the TF coil + ( + r_pf_coil_middle_group_array, + z_pf_coil_middle_group_array, + top_bottom, + ) = self.place_pf_above_tf( n_pf_coils_in_group=self.data.pf_coil.n_pf_coils_in_group, n_pf_group=group, - r_cs_middle=self.data.pf_coil.r_cs_middle, - dr_pf_cs_middle_offset=self.data.pf_coil.dr_pf_cs_middle_offset, + rmajor=self.data.physics.rmajor, + triang=self.data.physics.triang, + rminor=self.data.physics.rminor, + itart=self.data.physics.itart, + itartpf=self.data.physics.itartpf, z_tf_inside_half=self.data.build.z_tf_inside_half, - dr_tf_inboard=self.data.build.dr_tf_inboard, - z_cs_coil_upper=self.data.pf_coil.dz_cs_full / 2, - ) - ) - for coil in range(self.data.pf_coil.n_pf_coils_in_group[group]): - self.data.pf_coil.r_pf_coil_middle_group_array[group, coil] = ( - r_pf_coil_middle_group_array[group, coil] - ) - self.data.pf_coil.z_pf_coil_middle_group_array[group, coil] = ( - z_pf_coil_middle_group_array[group, coil] + dz_tf_upper_lower_midplane=self.data.build.dz_tf_upper_lower_midplane, + z_tf_top=self.data.build.z_tf_top, + top_bottom=top_bottom, + rpf2=self.data.pf_coil.rpf2, + zref=self.data.pf_coil.zref, ) - elif self.data.pf_coil.i_pf_location[group] == PFLocationTypes.ABOVE_TF: - # PF coil is on top of the TF coil - ( - r_pf_coil_middle_group_array, - z_pf_coil_middle_group_array, - top_bottom, - ) = self.place_pf_above_tf( - n_pf_coils_in_group=self.data.pf_coil.n_pf_coils_in_group, - n_pf_group=group, - rmajor=self.data.physics.rmajor, - triang=self.data.physics.triang, - rminor=self.data.physics.rminor, - itart=self.data.physics.itart, - itartpf=self.data.physics.itartpf, - z_tf_inside_half=self.data.build.z_tf_inside_half, - dz_tf_upper_lower_midplane=self.data.build.dz_tf_upper_lower_midplane, - z_tf_top=self.data.build.z_tf_top, - top_bottom=top_bottom, - rpf2=self.data.pf_coil.rpf2, - zref=self.data.pf_coil.zref, - ) + for coil in range(self.data.pf_coil.n_pf_coils_in_group[group]): + self.data.pf_coil.r_pf_coil_middle_group_array[group, coil] = ( + r_pf_coil_middle_group_array[group, coil] + ) + self.data.pf_coil.z_pf_coil_middle_group_array[group, coil] = ( + z_pf_coil_middle_group_array[group, coil] + ) - for coil in range(self.data.pf_coil.n_pf_coils_in_group[group]): - self.data.pf_coil.r_pf_coil_middle_group_array[group, coil] = ( - r_pf_coil_middle_group_array[group, coil] - ) - self.data.pf_coil.z_pf_coil_middle_group_array[group, coil] = ( - z_pf_coil_middle_group_array[group, coil] + case PFLocationTypes.OUTSIDE_TF: + # PF coil is radially outside the TF coil + ( + r_pf_coil_middle_group_array, + z_pf_coil_middle_group_array, + ) = self.place_pf_outside_tf( + n_pf_coils_in_group=self.data.pf_coil.n_pf_coils_in_group, + n_pf_group=group, + rminor=self.data.physics.rminor, + zref=self.data.pf_coil.zref, + i_tf_shape=self.data.tfcoil.i_tf_shape, + i_r_pf_outside_tf_placement=self.data.pf_coil.i_r_pf_outside_tf_placement, + r_pf_outside_tf_midplane=self.data.pf_coil.r_pf_outside_tf_midplane, ) - elif self.data.pf_coil.i_pf_location[group] == PFLocationTypes.OUTSIDE_TF: - # PF coil is radially outside the TF coil - ( - r_pf_coil_middle_group_array, - z_pf_coil_middle_group_array, - ) = self.place_pf_outside_tf( - n_pf_coils_in_group=self.data.pf_coil.n_pf_coils_in_group, - n_pf_group=group, - rminor=self.data.physics.rminor, - zref=self.data.pf_coil.zref, - i_tf_shape=self.data.tfcoil.i_tf_shape, - i_r_pf_outside_tf_placement=self.data.pf_coil.i_r_pf_outside_tf_placement, - r_pf_outside_tf_midplane=self.data.pf_coil.r_pf_outside_tf_midplane, - ) + for coil in range(self.data.pf_coil.n_pf_coils_in_group[group]): + self.data.pf_coil.r_pf_coil_middle_group_array[group, coil] = ( + r_pf_coil_middle_group_array[group, coil] + ) + self.data.pf_coil.z_pf_coil_middle_group_array[group, coil] = ( + z_pf_coil_middle_group_array[group, coil] + ) - for coil in range(self.data.pf_coil.n_pf_coils_in_group[group]): - self.data.pf_coil.r_pf_coil_middle_group_array[group, coil] = ( - r_pf_coil_middle_group_array[group, coil] - ) - self.data.pf_coil.z_pf_coil_middle_group_array[group, coil] = ( - z_pf_coil_middle_group_array[group, coil] + case PFLocationTypes.GENERALLY_PLACED: + ( + r_pf_coil_middle_group_array, + z_pf_coil_middle_group_array, + ) = self.place_pf_generally( + n_pf_coils_in_group=self.data.pf_coil.n_pf_coils_in_group, + n_pf_group=group, + rminor=self.data.physics.rminor, + rmajor=self.data.physics.rmajor, + zref=self.data.pf_coil.zref, + rref=self.data.pf_coil.rref, ) - elif ( - self.data.pf_coil.i_pf_location[group] - == PFLocationTypes.GENERALLY_PLACED - ): - ( - r_pf_coil_middle_group_array, - z_pf_coil_middle_group_array, - ) = self.place_pf_generally( - n_pf_coils_in_group=self.data.pf_coil.n_pf_coils_in_group, - n_pf_group=group, - rminor=self.data.physics.rminor, - rmajor=self.data.physics.rmajor, - zref=self.data.pf_coil.zref, - rref=self.data.pf_coil.rref, - ) + for coil in range(self.data.pf_coil.n_pf_coils_in_group[group]): + self.data.pf_coil.r_pf_coil_middle_group_array[group, coil] = ( + r_pf_coil_middle_group_array[group, coil] + ) + self.data.pf_coil.z_pf_coil_middle_group_array[group, coil] = ( + z_pf_coil_middle_group_array[group, coil] + ) - for coil in range(self.data.pf_coil.n_pf_coils_in_group[group]): - self.data.pf_coil.r_pf_coil_middle_group_array[group, coil] = ( - r_pf_coil_middle_group_array[group, coil] + case _: + raise ProcessValueError( + "Illegal i_pf_location value", + group=group, + i_pf_location=self.data.pf_coil.i_pf_location[group], ) - self.data.pf_coil.z_pf_coil_middle_group_array[group, coil] = ( - z_pf_coil_middle_group_array[group, coil] - ) - - else: - raise ProcessValueError( - "Illegal i_pf_location value", - group=group, - i_pf_location=self.data.pf_coil.i_pf_location[group], - ) # Allocate current to the PF coils: # "Flux swing coils" participate in cancellation of the CS @@ -412,35 +410,34 @@ def pfcoil(self): # Bypasses SVD solver if self.data.physics.itart == 1 and self.data.physics.itartpf == 0: for i in range(self.data.pf_coil.n_pf_coil_groups): - if self.data.pf_coil.i_pf_location[i] == PFLocationTypes.ABOVE_CS: - # PF coil is stacked on top of the Central Solenoid - self.data.pf_coil.ccls[i] = 0.0e0 - raise ProcessValueError( - "i_pf_location(i) should not be 1 if itart=1", i=i - ) + match self.data.pf_coil.i_pf_location[i]: + case PFLocationTypes.ABOVE_CS: + # PF coil is stacked on top of the Central Solenoid + self.data.pf_coil.ccls[i] = 0.0e0 + raise ProcessValueError( + "i_pf_location(i) should not be 1 if itart=1", i=i + ) - if self.data.pf_coil.i_pf_location[i] == PFLocationTypes.ABOVE_TF: - # PF coil is on top of the TF coil - self.data.pf_coil.ccls[i] = ( - 0.3e0 - * self.data.physics.aspect**1.6e0 - * self.data.physics.plasma_current - ) + case PFLocationTypes.ABOVE_TF: + # PF coil is on top of the TF coil + self.data.pf_coil.ccls[i] = ( + 0.3e0 + * self.data.physics.aspect**1.6e0 + * self.data.physics.plasma_current + ) - elif ( - self.data.pf_coil.i_pf_location[i] == PFLocationTypes.OUTSIDE_TF - ): - # PF coil is radially outside the TF coil - self.data.pf_coil.ccls[i] = ( - -0.4e0 * self.data.physics.plasma_current - ) + case PFLocationTypes.OUTSIDE_TF: + # PF coil is radially outside the TF coil + self.data.pf_coil.ccls[i] = ( + -0.4e0 * self.data.physics.plasma_current + ) - else: - raise ProcessValueError( - "Illegal value of i_pf_location(i)", - i=i, - i_pf_location=self.data.pf_coil.i_pf_location[i], - ) + case _: + raise ProcessValueError( + "Illegal value of i_pf_location(i)", + i=i, + i_pf_location=self.data.pf_coil.i_pf_location[i], + ) # Vertical field (T) self.data.physics.b_plasma_vertical_required = ( @@ -461,84 +458,97 @@ def pfcoil(self): ngrp0 = 0 nocoil = 0 for i in range(self.data.pf_coil.n_pf_coil_groups): - if self.data.pf_coil.i_pf_location[i] == PFLocationTypes.ABOVE_CS: - # Do not allow if no central solenoid - if self.data.build.iohcl == 0: - raise ProcessValueError( - "i_pf_location(i) should not be 1 if iohcl=0" - ) - # PF coil is stacked on top of the Central Solenoid - # This coil is to balance Central Solenoid flux and should - # not be involved in equilibrium calculation -- RK 07/12 - self.data.pf_coil.ccls[i] = 0.0e0 - nfxf0 += self.data.pf_coil.n_pf_coils_in_group[i] - for ccount in range(self.data.pf_coil.n_pf_coils_in_group[i]): - self.data.pf_coil.r_pf_cs_current_filaments[nocoil] = ( - self.data.pf_coil.r_pf_coil_middle_group_array[i, ccount] - ) - self.data.pf_coil.z_pf_cs_current_filaments[nocoil] = ( - self.data.pf_coil.z_pf_coil_middle_group_array[i, ccount] - ) - self.data.pf_coil.c_pf_cs_current_filaments[nocoil] = ( - self.data.pf_coil.ccls[i] - ) - nocoil += 1 - - elif self.data.pf_coil.i_pf_location[i] == PFLocationTypes.ABOVE_TF: - # PF coil is on top of the TF coil; divertor coil - # This is a fixed current for this calculation -- RK 07/12 - - self.data.pf_coil.ccls[i] = ( - self.data.physics.plasma_current - * 2.0e0 - * ( - 1.0e0 - - (self.data.physics.kappa * self.data.physics.rminor) - / abs( - self.data.pf_coil.z_pf_coil_middle_group_array[i, 0] + match self.data.pf_coil.i_pf_location[i]: + case PFLocationTypes.ABOVE_CS: + # Do not allow if no central solenoid + if self.data.build.iohcl == 0: + raise ProcessValueError( + "i_pf_location(i) should not be 1 if iohcl=0" + ) + # PF coil is stacked on top of the Central Solenoid + # This coil is to balance Central Solenoid flux and should + # not be involved in equilibrium calculation -- RK 07/12 + self.data.pf_coil.ccls[i] = 0.0e0 + nfxf0 += self.data.pf_coil.n_pf_coils_in_group[i] + for ccount in range( + self.data.pf_coil.n_pf_coils_in_group[i] + ): + self.data.pf_coil.r_pf_cs_current_filaments[nocoil] = ( + self.data.pf_coil.r_pf_coil_middle_group_array[ + i, ccount + ] + ) + self.data.pf_coil.z_pf_cs_current_filaments[nocoil] = ( + self.data.pf_coil.z_pf_coil_middle_group_array[ + i, ccount + ] + ) + self.data.pf_coil.c_pf_cs_current_filaments[nocoil] = ( + self.data.pf_coil.ccls[i] + ) + nocoil += 1 + + case PFLocationTypes.ABOVE_TF: + # PF coil is on top of the TF coil; divertor coil + # This is a fixed current for this calculation -- RK 07/12 + + self.data.pf_coil.ccls[i] = ( + self.data.physics.plasma_current + * 2.0e0 + * ( + 1.0e0 + - ( + self.data.physics.kappa + * self.data.physics.rminor + ) + / abs( + self.data.pf_coil.z_pf_coil_middle_group_array[ + i, 0 + ] + ) ) ) - ) - nfxf0 += self.data.pf_coil.n_pf_coils_in_group[i] - for ccount in range(self.data.pf_coil.n_pf_coils_in_group[i]): - self.data.pf_coil.r_pf_cs_current_filaments[nocoil] = ( - self.data.pf_coil.r_pf_coil_middle_group_array[i, ccount] - ) - self.data.pf_coil.z_pf_cs_current_filaments[nocoil] = ( - self.data.pf_coil.z_pf_coil_middle_group_array[i, ccount] - ) - self.data.pf_coil.c_pf_cs_current_filaments[nocoil] = ( - self.data.pf_coil.ccls[i] - ) - nocoil += 1 + nfxf0 += self.data.pf_coil.n_pf_coils_in_group[i] + for ccount in range( + self.data.pf_coil.n_pf_coils_in_group[i] + ): + self.data.pf_coil.r_pf_cs_current_filaments[nocoil] = ( + self.data.pf_coil.r_pf_coil_middle_group_array[ + i, ccount + ] + ) + self.data.pf_coil.z_pf_cs_current_filaments[nocoil] = ( + self.data.pf_coil.z_pf_coil_middle_group_array[ + i, ccount + ] + ) + self.data.pf_coil.c_pf_cs_current_filaments[nocoil] = ( + self.data.pf_coil.ccls[i] + ) + nocoil += 1 - elif ( - self.data.pf_coil.i_pf_location[i] == PFLocationTypes.OUTSIDE_TF - ): - # PF coil is radially outside the TF coil - # This is an equilibrium coil, current must be solved for + case PFLocationTypes.OUTSIDE_TF: + # PF coil is radially outside the TF coil + # This is an equilibrium coil, current must be solved for - pcls0[ngrp0] = i + 1 - ngrp0 += 1 + pcls0[ngrp0] = i + 1 + ngrp0 += 1 - elif ( - self.data.pf_coil.i_pf_location[i] - == PFLocationTypes.GENERALLY_PLACED - ): - # PF coil is generally placed - # See issue 1418 - # https://git.ccfe.ac.uk/process/process/-/issues/1418 - # This is an equilibrium coil, current must be solved for + case PFLocationTypes.GENERALLY_PLACED: + # PF coil is generally placed + # See issue 1418 + # https://git.ccfe.ac.uk/process/process/-/issues/1418 + # This is an equilibrium coil, current must be solved for - pcls0[ngrp0] = i + 1 - ngrp0 += 1 + pcls0[ngrp0] = i + 1 + ngrp0 += 1 - else: - raise ProcessValueError( - "Illegal value of i_pf_location(i)", - i=i, - i_pf_location=self.data.pf_coil.i_pf_location[i], - ) + case _: + raise ProcessValueError( + "Illegal value of i_pf_location(i)", + i=i, + i_pf_location=self.data.pf_coil.i_pf_location[i], + ) for ccount in range(ngrp0): ncls0[ccount] = 2 @@ -4723,176 +4733,180 @@ def j_crit_cable_frac(j_crit_sc, fcu, fhe): """ return j_crit_sc * (1.0e0 - fcu) * (1.0e0 - fhe) - # Find critical current density in superconducting strand, jcritstr - if isumat == SuperconductorModel.ITER_NB3SN: - # ITER Nb3Sn critical surface parameterization - bc20m = ( - SuperconductorModel.ITER_NB3SN.b_crit_zero_field_strain - ) # [T] critical field at 0 K and 0 strain - tc0m = ( - SuperconductorModel.ITER_NB3SN.temp_crit_zero_field_strain - ) # [K] critical temperature at 0 T and 0 strain - - # j_crit_sc returned by superconductors.itersc is - # the critical current density in the superconductor - # - not the whole strand, which contains copper - - j_crit_sc, _, _ = superconductors.itersc( - temp_conductor=temp_pf_peak_field, - b_conductor=b_pf_peak, - strain=strain, - b_c20max=bc20m, - temp_c0max=tc0m, - ) - j_crit_cable = j_crit_cable_frac(j_crit_sc, fcu, fhe) - - elif isumat == SuperconductorModel.BI2212: - # Bi-2212 high temperature superconductor parameterization - - # Current density in a strand of Bi-2212 conductor - # N.B. jcrit returned by superconductors.bi2212 is the critical current density - # in the strand, not just the superconducting portion. - # The parameterization for j_crit_cable assumes a particular strand - # composition that does not require a user-defined copper fraction, - # so this is irrelevant in this model - - # j_pf_wp / conductor fraction of cable - jstrand = j_pf_wp / (1.0e0 - fhe) - j_crit_cable, tmarg = superconductors.bi2212( - b_conductor=b_pf_peak, - jstrand=jstrand, - temp_conductor=temp_pf_peak_field, - f_strain=fhts, - ) - # j_crit_cable / non-copper fraction of conductor - j_crit_sc = j_crit_cable / (1.0e0 - fcu) - - elif isumat == SuperconductorModel.OLD_LUBELL_NBTI: - # NbTi data - bc20m = ( - SuperconductorModel.OLD_LUBELL_NBTI.b_crit_zero_field_strain - ) # [T] critical field at 0 K and 0 strain - tc0m = ( - SuperconductorModel.OLD_LUBELL_NBTI.temp_crit_zero_field_strain - ) # [K] critical temperature at 0 T and 0 strain - c0 = 1.0e10 # # [A/m²] - j_crit_sc, _ = superconductors.jcrit_nbti( - temp_conductor=temp_pf_peak_field, - b_conductor=b_pf_peak, - c0=c0, - b_c20max=bc20m, - temp_c0max=tc0m, - ) - j_crit_cable = j_crit_cable_frac(j_crit_sc, fcu, fhe) - - elif isumat == SuperconductorModel.USER_DEFINED_NB3SN: - # As (1), but user-defined parameters - bc20m = bcritsc - tc0m = tcritsc - j_crit_sc, _, _ = superconductors.itersc( - temp_conductor=temp_pf_peak_field, - b_conductor=b_pf_peak, - strain=strain, - b_c20max=bc20m, - temp_c0max=tc0m, - ) - j_crit_cable = j_crit_cable_frac(j_crit_sc, fcu, fhe) - - elif isumat == SuperconductorModel.WST_NB3SN: - # WST Nb3Sn parameterisation - bc20m = ( - SuperconductorModel.WST_NB3SN.b_crit_zero_field_strain - ) # [T] critical field at 0 K and 0 strain - tc0m = ( - SuperconductorModel.WST_NB3SN.temp_crit_zero_field_strain - ) # [K] critical temperature at 0 T and 0 strain - - # j_crit_sc returned by superconductors.itersc is the critical current density - # in the superconductor - not the whole strand, which contains copper - - j_crit_sc, _, _ = superconductors.western_superconducting_nb3sn( - temp_conductor=temp_pf_peak_field, - b_conductor=b_pf_peak, - strain=strain, - b_c20max=bc20m, - temp_c0max=tc0m, - ) - j_crit_cable = j_crit_cable_frac(j_crit_sc, fcu, fhe) - - elif isumat == SuperconductorModel.CROCO_REBCO: - # "REBCO" 2nd generation HTS superconductor in CrCo strand - b_c20m = ( - SuperconductorModel.CROCO_REBCO.b_crit_zero_field_strain - ) # [T] critical field at 0 K and 0 strain - t_c0m = ( - SuperconductorModel.CROCO_REBCO.temp_crit_zero_field_strain - ) # [K] critical temperature at 0 T and 0 strain - j_crit_sc, _, _, _ = superconductors.jcrit_rebco( - temp_conductor=temp_pf_peak_field, - b_conductor=b_pf_peak, - b_c20_max=b_c20m, - temp_c0_max=t_c0m, - ) - j_crit_cable = j_crit_cable_frac(j_crit_sc, fcu, fhe) - - elif isumat == SuperconductorModel.DURHAM_NBTI: - # Durham Ginzburg-Landau critical surface model for Nb-Ti - bc20m = ( - SuperconductorModel.DURHAM_NBTI.b_crit_zero_field_strain - ) # [T] critical field at 0 K and 0 strain - tc0m = ( - SuperconductorModel.DURHAM_NBTI.temp_crit_zero_field_strain - ) # [K] critical temperature at 0 T and 0 strain - j_crit_sc, _, _ = superconductors.gl_nbti( - temp_conductor=temp_pf_peak_field, - b_conductor=b_pf_peak, - strain=strain, - b_c20max=bc20m, - t_c0=tc0m, - ) - j_crit_cable = j_crit_cable_frac(j_crit_sc, fcu, fhe) - - elif isumat == SuperconductorModel.DURHAM_REBCO: - # Durham Ginzburg-Landau critical surface model for REBCO - bc20m = ( - SuperconductorModel.DURHAM_REBCO.b_crit_zero_field_strain - ) # [T] critical field at 0 K and 0 strain - tc0m = ( - SuperconductorModel.DURHAM_REBCO.temp_crit_zero_field_strain - ) # [K] critical temperature at 0 T and 0 strain - j_crit_sc, _, _ = superconductors.gl_rebco( - temp_conductor=temp_pf_peak_field, - b_conductor=b_pf_peak, - strain=strain, - b_c20max=bc20m, - t_c0=tc0m, - ) - # A0 calculated for tape cross section already - j_crit_cable = j_crit_cable_frac(j_crit_sc, fcu, fhe) - - elif isumat == SuperconductorModel.HAZELTON_ZHAI_REBCO: - # Hazelton experimental data + Zhai conceptual model for REBCO - bc20m = ( - SuperconductorModel.HAZELTON_ZHAI_REBCO.b_crit_zero_field_strain - ) # [T] critical field at 0 K and 0 strain - tc0m = ( - SuperconductorModel.HAZELTON_ZHAI_REBCO.temp_crit_zero_field_strain - ) # [K] critical temperature at 0 T and 0 strain - j_crit_sc, _, _ = superconductors.hijc_rebco( - temp_conductor=temp_pf_peak_field, - b_conductor=b_pf_peak, - b_c20max=bc20m, - t_c0=tc0m, - dr_hts_tape=dr_hts_tape, - dx_hts_tape_rebco=dx_hts_tape_rebco, - dx_hts_tape_total=dx_hts_tape_total, - ) - # A0 calculated for tape cross section already - j_crit_cable = j_crit_cable_frac(j_crit_sc, fcu, fhe) - - else: - # Error condition - raise ProcessValueError("Illegal value for i_pf_superconductor", isumat=isumat) + match SuperconductorModel(isumat): + case SuperconductorModel.ITER_NB3SN: + # ITER Nb3Sn critical surface parameterization + # ITER Nb3Sn critical surface parameterization + bc20m = ( + SuperconductorModel.ITER_NB3SN.b_crit_zero_field_strain + ) # [T] critical field at 0 K and 0 strain + tc0m = ( + SuperconductorModel.ITER_NB3SN.temp_crit_zero_field_strain + ) # [K] critical temperature at 0 T and 0 strain + + # j_crit_sc returned by superconductors.itersc is + # the critical current density in the superconductor + # - not the whole strand, which contains copper + + j_crit_sc, _, _ = superconductors.itersc( + temp_conductor=temp_pf_peak_field, + b_conductor=b_pf_peak, + strain=strain, + b_c20max=bc20m, + temp_c0max=tc0m, + ) + j_crit_cable = j_crit_cable_frac(j_crit_sc, fcu, fhe) + + case SuperconductorModel.BI2212: + # Bi-2212 high temperature superconductor parameterization + # Bi-2212 high temperature superconductor parameterization + + # Current density in a strand of Bi-2212 conductor + # N.B. jcrit returned by superconductors.bi2212 is the critical current + # density in the strand, not just the superconducting portion. + # The parameterization for j_crit_cable assumes a particular strand + # composition that does not require a user-defined copper fraction, + # so this is irrelevant in this model + + # j_pf_wp / conductor fraction of cable + jstrand = j_pf_wp / (1.0e0 - fhe) + j_crit_cable, tmarg = superconductors.bi2212( + b_conductor=b_pf_peak, + jstrand=jstrand, + temp_conductor=temp_pf_peak_field, + f_strain=fhts, + ) + # j_crit_cable / non-copper fraction of conductor + j_crit_sc = j_crit_cable / (1.0e0 - fcu) + + case SuperconductorModel.OLD_LUBELL_NBTI: + # NbTi data + bc20m = ( + SuperconductorModel.OLD_LUBELL_NBTI.b_crit_zero_field_strain + ) # [T] critical field at 0 K and 0 strain + tc0m = ( + SuperconductorModel.OLD_LUBELL_NBTI.temp_crit_zero_field_strain + ) # [K] critical temperature at 0 T and 0 strain + c0 = 1.0e10 # # [A/m²] + j_crit_sc, _ = superconductors.jcrit_nbti( + temp_conductor=temp_pf_peak_field, + b_conductor=b_pf_peak, + c0=c0, + b_c20max=bc20m, + temp_c0max=tc0m, + ) + j_crit_cable = j_crit_cable_frac(j_crit_sc, fcu, fhe) + + case SuperconductorModel.USER_DEFINED_NB3SN: + # As (1), but user-defined parameters + bc20m = bcritsc + tc0m = tcritsc + j_crit_sc, _, _ = superconductors.itersc( + temp_conductor=temp_pf_peak_field, + b_conductor=b_pf_peak, + strain=strain, + b_c20max=bc20m, + temp_c0max=tc0m, + ) + j_crit_cable = j_crit_cable_frac(j_crit_sc, fcu, fhe) + + case SuperconductorModel.WST_NB3SN: + # WST Nb3Sn parameterisation + bc20m = ( + SuperconductorModel.WST_NB3SN.b_crit_zero_field_strain + ) # [T] critical field at 0 K and 0 strain + tc0m = ( + SuperconductorModel.WST_NB3SN.temp_crit_zero_field_strain + ) # [K] critical temperature at 0 T and 0 strain + + # j_crit_sc returned by superconductors.itersc is the critical current + # density in the superconductor - not the whole strand, which contains copper + + j_crit_sc, _, _ = superconductors.western_superconducting_nb3sn( + temp_conductor=temp_pf_peak_field, + b_conductor=b_pf_peak, + strain=strain, + b_c20max=bc20m, + temp_c0max=tc0m, + ) + j_crit_cable = j_crit_cable_frac(j_crit_sc, fcu, fhe) + + case SuperconductorModel.CROCO_REBCO: + # "REBCO" 2nd generation HTS superconductor in CrCo strand + b_c20m = ( + SuperconductorModel.CROCO_REBCO.b_crit_zero_field_strain + ) # [T] critical field at 0 K and 0 strain + t_c0m = ( + SuperconductorModel.CROCO_REBCO.temp_crit_zero_field_strain + ) # [K] critical temperature at 0 T and 0 strain + j_crit_sc, _, _, _ = superconductors.jcrit_rebco( + temp_conductor=temp_pf_peak_field, + b_conductor=b_pf_peak, + b_c20_max=b_c20m, + temp_c0_max=t_c0m, + ) + j_crit_cable = j_crit_cable_frac(j_crit_sc, fcu, fhe) + + case SuperconductorModel.DURHAM_NBTI: + # Durham Ginzburg-Landau critical surface model for Nb-Ti + bc20m = ( + SuperconductorModel.DURHAM_NBTI.b_crit_zero_field_strain + ) # [T] critical field at 0 K and 0 strain + tc0m = ( + SuperconductorModel.DURHAM_NBTI.temp_crit_zero_field_strain + ) # [K] critical temperature at 0 T and 0 strain + j_crit_sc, _, _ = superconductors.gl_nbti( + temp_conductor=temp_pf_peak_field, + b_conductor=b_pf_peak, + strain=strain, + b_c20max=bc20m, + t_c0=tc0m, + ) + j_crit_cable = j_crit_cable_frac(j_crit_sc, fcu, fhe) + + case SuperconductorModel.DURHAM_REBCO: + # Durham Ginzburg-Landau critical surface model for REBCO + bc20m = ( + SuperconductorModel.DURHAM_REBCO.b_crit_zero_field_strain + ) # [T] critical field at 0 K and 0 strain + tc0m = ( + SuperconductorModel.DURHAM_REBCO.temp_crit_zero_field_strain + ) # [K] critical temperature at 0 T and 0 strain + j_crit_sc, _, _ = superconductors.gl_rebco( + temp_conductor=temp_pf_peak_field, + b_conductor=b_pf_peak, + strain=strain, + b_c20max=bc20m, + t_c0=tc0m, + ) + # A0 calculated for tape cross section already + j_crit_cable = j_crit_cable_frac(j_crit_sc, fcu, fhe) + + case SuperconductorModel.HAZELTON_ZHAI_REBCO: + # Hazelton experimental data + Zhai conceptual model for REBCO + bc20m = ( + SuperconductorModel.HAZELTON_ZHAI_REBCO.b_crit_zero_field_strain + ) # [T] critical field at 0 K and 0 strain + tc0m = ( + SuperconductorModel.HAZELTON_ZHAI_REBCO.temp_crit_zero_field_strain + ) # [K] critical temperature at 0 T and 0 strain + j_crit_sc, _, _ = superconductors.hijc_rebco( + temp_conductor=temp_pf_peak_field, + b_conductor=b_pf_peak, + b_c20max=bc20m, + t_c0=tc0m, + dr_hts_tape=dr_hts_tape, + dx_hts_tape_rebco=dx_hts_tape_rebco, + dx_hts_tape_total=dx_hts_tape_total, + ) + # A0 calculated for tape cross section already + j_crit_cable = j_crit_cable_frac(j_crit_sc, fcu, fhe) + + case _: + # Error condition + raise ProcessValueError( + "Illegal value for i_pf_superconductor", isumat=isumat + ) # Critical current density in winding pack jcritwp = j_crit_cable diff --git a/process/models/physics/confinement_time.py b/process/models/physics/confinement_time.py index 8840ed8cce..aa871d7477 100644 --- a/process/models/physics/confinement_time.py +++ b/process/models/physics/confinement_time.py @@ -164,14 +164,21 @@ def calculate_confinement_time( # noqa: PLR0917 p_plasma_loss_mw += p_hcd_injected_total_mw # Include the radiation as a loss term based on radiation model - try: + try: # noqa: PLW0717 model = ConfinementRadiationLossModel(int(self.data.physics.i_rad_loss)) - if model == ConfinementRadiationLossModel.FULL_RADIATION: - p_plasma_loss_mw -= self.data.physics.pden_plasma_rad_mw * vol_plasma - elif model == ConfinementRadiationLossModel.CORE_ONLY: - p_plasma_loss_mw -= pden_plasma_core_rad_mw * vol_plasma - # NO_RADIATION: do not adjust p_plasma_loss_mw for radiation + match model: + case ConfinementRadiationLossModel.FULL_RADIATION: + p_plasma_loss_mw -= self.data.physics.pden_plasma_rad_mw * vol_plasma + case ConfinementRadiationLossModel.CORE_ONLY: + p_plasma_loss_mw -= pden_plasma_core_rad_mw * vol_plasma + case ConfinementRadiationLossModel.NO_RADIATION: + pass + case _: + raise ProcessValueError( + "Illegal value for i_rad_loss", + i_rad_loss=self.data.physics.i_rad_loss, + ) except ValueError as e: raise ProcessValueError( "Illegal value of i_rad_loss", @@ -215,201 +222,180 @@ def calculate_confinement_time( # noqa: PLR0917 i_confinement_time=i_confinement_time, ) from e - # ======================================================================== + match model: + # ==================================================================== - # User defined confinement time - if ( - model == ConfinementTimeModel.USER_INPUT - ): # t_electron_energy_confinement is an input - t_electron_confinement = self.data.physics.tauee_in + # User defined confinement time + case ConfinementTimeModel.USER_INPUT: + # t_electron_energy_confinement is an input + t_electron_confinement = self.data.physics.tauee_in - # ======================================================================== + # ==================================================================== - # Nec-Alcator(NA) OH scaling - if ( - model == ConfinementTimeModel.NEO_ALCATOR - ): # t_electron_energy_confinement is an input - t_electron_confinement = self.neo_alcator_confinement_time( - n20, rminor, rmajor, qstar - ) - - # ======================================================================== - - # "Mirnov"-like scaling (H-mode) - elif model == ConfinementTimeModel.MIRNOV: # Mirnov scaling (H-mode) - t_electron_confinement = self.mirnov_confinement_time( - rminor, kappa95, cur_plasma_ma - ) - - # ======================================================================== + # Nec-Alcator(NA) OH scaling + case ConfinementTimeModel.NEO_ALCATOR: + # t_electron_energy_confinement is an input + t_electron_confinement = self.neo_alcator_confinement_time( + n20, rminor, rmajor, qstar + ) - # Merezhkin-Mukhovatov (MM) OH/L-mode scaling - elif model == ConfinementTimeModel.MEREZHKIN_MUHKOVATOV: - t_electron_confinement = self.merezhkin_muhkovatov_confinement_time( - rmajor, - rminor, - kappa95, - qstar, - nd_plasma_electron_line_20, - m_fuel_amu, - temp_plasma_electron_density_weighted_kev, - ) + # ==================================================================== - # ======================================================================== + # "Mirnov"-like scaling (H-mode) + case ConfinementTimeModel.MIRNOV: # Mirnov scaling (H-mode) + t_electron_confinement = self.mirnov_confinement_time( + rminor, kappa95, cur_plasma_ma + ) - # Shimomura (S) optimized H-mode scaling - elif model == ConfinementTimeModel.SHIMOMURA: - t_electron_confinement = self.shimomura_confinement_time( - rmajor, rminor, b_plasma_toroidal_on_axis, kappa95, m_fuel_amu - ) + # ==================================================================== - # ======================================================================== + # Merezhkin-Mukhovatov (MM) OH/L-mode scaling + case ConfinementTimeModel.MEREZHKIN_MUHKOVATOV: + t_electron_confinement = self.merezhkin_muhkovatov_confinement_time( + rmajor, + rminor, + kappa95, + qstar, + nd_plasma_electron_line_20, + m_fuel_amu, + temp_plasma_electron_density_weighted_kev, + ) - # Kaye-Goldston scaling (L-mode) - elif model == ConfinementTimeModel.KAYE_GOLDSTON: - t_electron_confinement = self.kaye_goldston_confinement_time( - cur_plasma_ma, - rmajor, - rminor, - kappa, - nd_plasma_electron_line_20, - b_plasma_toroidal_on_axis, - m_fuel_amu, - p_plasma_loss_mw, - ) + # ==================================================================== - # ======================================================================== + # Shimomura (S) optimized H-mode scaling + case ConfinementTimeModel.SHIMOMURA: + t_electron_confinement = self.shimomura_confinement_time( + rmajor, rminor, b_plasma_toroidal_on_axis, kappa95, m_fuel_amu + ) - # ITER Power scaling - ITER 89-P (L-mode) - elif model == ConfinementTimeModel.ITER_89P: - t_electron_confinement = self.iter_89p_confinement_time( - cur_plasma_ma, - rmajor, - rminor, - kappa, - nd_plasma_electron_line_20, - b_plasma_toroidal_on_axis, - m_fuel_amu, - p_plasma_loss_mw, - ) + # ==================================================================== - # ======================================================================== + # Kaye-Goldston scaling (L-mode) + case ConfinementTimeModel.KAYE_GOLDSTON: + t_electron_confinement = self.kaye_goldston_confinement_time( + cur_plasma_ma, + rmajor, + rminor, + kappa, + nd_plasma_electron_line_20, + b_plasma_toroidal_on_axis, + m_fuel_amu, + p_plasma_loss_mw, + ) - # ITER Offset linear scaling - ITER 89-O (L-mode) - elif model == ConfinementTimeModel.ITER_89_0: - t_electron_confinement = self.iter_89_0_confinement_time( - cur_plasma_ma, - rmajor, - rminor, - kappa, - nd_plasma_electron_line_20, - b_plasma_toroidal_on_axis, - m_fuel_amu, - p_plasma_loss_mw, - ) - # ======================================================================== + # ==================================================================== - # Rebut-Lallia offset linear scaling (L-mode) - elif model == ConfinementTimeModel.REBUT_LALLIA: - t_electron_confinement = self.rebut_lallia_confinement_time( - rminor, - rmajor, - kappa, - m_fuel_amu, - cur_plasma_ma, - zeff, - nd_plasma_electron_line_20, - b_plasma_toroidal_on_axis, - p_plasma_loss_mw, - ) + # ITER Power scaling - ITER 89-P (L-mode) + case ConfinementTimeModel.ITER_89P: + t_electron_confinement = self.iter_89p_confinement_time( + cur_plasma_ma, + rmajor, + rminor, + kappa, + nd_plasma_electron_line_20, + b_plasma_toroidal_on_axis, + m_fuel_amu, + p_plasma_loss_mw, + ) - # ======================================================================== + # ==================================================================== - # Goldston scaling (L-mode) - elif model == ConfinementTimeModel.GOLDSTON: # Goldston scaling (L-mode) - t_electron_confinement = self.goldston_confinement_time( - cur_plasma_ma, rmajor, rminor, kappa95, m_fuel_amu, p_plasma_loss_mw - ) + # ITER Offset linear scaling - ITER 89-O (L-mode) + case ConfinementTimeModel.ITER_89_0: + t_electron_confinement = self.iter_89_0_confinement_time( + cur_plasma_ma, + rmajor, + rminor, + kappa, + nd_plasma_electron_line_20, + b_plasma_toroidal_on_axis, + m_fuel_amu, + p_plasma_loss_mw, + ) - # ======================================================================== + # ==================================================================== - # T-10 scaling (L-mode) - elif model == ConfinementTimeModel.T_10: - t_electron_confinement = self.t10_confinement_time( - nd_plasma_electron_line_20, - rmajor, - qstar, - b_plasma_toroidal_on_axis, - rminor, - kappa95, - p_plasma_loss_mw, - zeff, - cur_plasma_ma, - ) + # Rebut-Lallia offset linear scaling (L-mode) + case ConfinementTimeModel.REBUT_LALLIA: + t_electron_confinement = self.rebut_lallia_confinement_time( + rminor, + rmajor, + kappa, + m_fuel_amu, + cur_plasma_ma, + zeff, + nd_plasma_electron_line_20, + b_plasma_toroidal_on_axis, + p_plasma_loss_mw, + ) - # ======================================================================== + # ==================================================================== - # JAERI / Odajima-Shimomura L-mode scaling - elif model == ConfinementTimeModel.JAERI: # JAERI scaling - t_electron_confinement = self.jaeri_confinement_time( - kappa95, - rminor, - m_fuel_amu, - n20, - cur_plasma_ma, - b_plasma_toroidal_on_axis, - rmajor, - qstar, - zeff, - p_plasma_loss_mw, - ) + # Goldston scaling (L-mode) + case ConfinementTimeModel.GOLDSTON: # Goldston scaling (L-mode) + t_electron_confinement = self.goldston_confinement_time( + cur_plasma_ma, + rmajor, + rminor, + kappa95, + m_fuel_amu, + p_plasma_loss_mw, + ) - # ======================================================================== + # ==================================================================== - # Kaye "big" L-mode scaling (based only on big tokamak data) - elif model == ConfinementTimeModel.KAYE_BIG: - t_electron_confinement = self.kaye_big_confinement_time( - rmajor, - rminor, - b_plasma_toroidal_on_axis, - kappa95, - cur_plasma_ma, - n20, - m_fuel_amu, - p_plasma_loss_mw, - ) + # T-10 scaling (L-mode) + case ConfinementTimeModel.T_10: + t_electron_confinement = self.t10_confinement_time( + nd_plasma_electron_line_20, + rmajor, + qstar, + b_plasma_toroidal_on_axis, + rminor, + kappa95, + p_plasma_loss_mw, + zeff, + cur_plasma_ma, + ) - # ======================================================================== + # ==================================================================== - # ITER H90-P H-mode scaling - elif model == ConfinementTimeModel.ITER_H90_P: - t_electron_confinement = self.iter_h90_p_confinement_time( - cur_plasma_ma, - rmajor, - rminor, - kappa, - nd_plasma_electron_line_20, - b_plasma_toroidal_on_axis, - m_fuel_amu, - p_plasma_loss_mw, - ) + # JAERI / Odajima-Shimomura L-mode scaling + case ConfinementTimeModel.JAERI: # JAERI scaling + t_electron_confinement = self.jaeri_confinement_time( + kappa95, + rminor, + m_fuel_amu, + n20, + cur_plasma_ma, + b_plasma_toroidal_on_axis, + rmajor, + qstar, + zeff, + p_plasma_loss_mw, + ) - # ======================================================================== + # ==================================================================== - # Minimum of ITER 89-P and ITER 89-O - elif model == ConfinementTimeModel.MINIMUM_OF_ITER_89P_AND_ITER_89_0: - t_electron_confinement = min( - self.iter_89p_confinement_time( - cur_plasma_ma, + # Kaye "big" L-mode scaling (based only on big tokamak data) + case ConfinementTimeModel.KAYE_BIG: + t_electron_confinement = self.kaye_big_confinement_time( rmajor, rminor, - kappa, - nd_plasma_electron_line_20, b_plasma_toroidal_on_axis, + kappa95, + cur_plasma_ma, + n20, m_fuel_amu, p_plasma_loss_mw, - ), - self.iter_89_0_confinement_time( + ) + + # ==================================================================== + + # ITER H90-P H-mode scaling + case ConfinementTimeModel.ITER_H90_P: + t_electron_confinement = self.iter_h90_p_confinement_time( cur_plasma_ma, rmajor, rminor, @@ -418,542 +404,572 @@ def calculate_confinement_time( # noqa: PLR0917 b_plasma_toroidal_on_axis, m_fuel_amu, p_plasma_loss_mw, - ), - ) - - # ======================================================================== - - # Riedel scaling (L-mode) - elif model == ConfinementTimeModel.RIEDEL_L: - t_electron_confinement = self.riedel_l_confinement_time( - cur_plasma_ma, - rmajor, - rminor, - kappa95, - nd_plasma_electron_line_20, - b_plasma_toroidal_on_axis, - p_plasma_loss_mw, - ) + ) - # ======================================================================== + # ==================================================================== + + # Minimum of ITER 89-P and ITER 89-O + case ConfinementTimeModel.MINIMUM_OF_ITER_89P_AND_ITER_89_0: + t_electron_confinement = min( + self.iter_89p_confinement_time( + cur_plasma_ma, + rmajor, + rminor, + kappa, + nd_plasma_electron_line_20, + b_plasma_toroidal_on_axis, + m_fuel_amu, + p_plasma_loss_mw, + ), + self.iter_89_0_confinement_time( + cur_plasma_ma, + rmajor, + rminor, + kappa, + nd_plasma_electron_line_20, + b_plasma_toroidal_on_axis, + m_fuel_amu, + p_plasma_loss_mw, + ), + ) - # Christiansen et al scaling (L-mode) - elif model == ConfinementTimeModel.CHRISTIANSEN: - t_electron_confinement = self.christiansen_confinement_time( - cur_plasma_ma, - rmajor, - rminor, - kappa95, - nd_plasma_electron_line_20, - b_plasma_toroidal_on_axis, - p_plasma_loss_mw, - m_fuel_amu, - ) + # ==================================================================== - # ======================================================================== + # Riedel scaling (L-mode) + case ConfinementTimeModel.RIEDEL_L: + t_electron_confinement = self.riedel_l_confinement_time( + cur_plasma_ma, + rmajor, + rminor, + kappa95, + nd_plasma_electron_line_20, + b_plasma_toroidal_on_axis, + p_plasma_loss_mw, + ) - # Lackner-Gottardi scaling (L-mode) - elif model == ConfinementTimeModel.LACKNER_GOTTARDI: - t_electron_confinement = self.lackner_gottardi_confinement_time( - cur_plasma_ma, - rmajor, - rminor, - kappa95, - nd_plasma_electron_line_20, - b_plasma_toroidal_on_axis, - p_plasma_loss_mw, - ) + # ==================================================================== - # ======================================================================== + # Christiansen et al scaling (L-mode) + case ConfinementTimeModel.CHRISTIANSEN: + t_electron_confinement = self.christiansen_confinement_time( + cur_plasma_ma, + rmajor, + rminor, + kappa95, + nd_plasma_electron_line_20, + b_plasma_toroidal_on_axis, + p_plasma_loss_mw, + m_fuel_amu, + ) - # Neo-Kaye scaling (L-mode) - elif model == ConfinementTimeModel.NEO_KAYE: - t_electron_confinement = self.neo_kaye_confinement_time( - cur_plasma_ma, - rmajor, - rminor, - kappa95, - nd_plasma_electron_line_20, - b_plasma_toroidal_on_axis, - p_plasma_loss_mw, - ) + # ==================================================================== - # ======== ================================================================ + # Lackner-Gottardi scaling (L-mode) + case ConfinementTimeModel.LACKNER_GOTTARDI: + t_electron_confinement = self.lackner_gottardi_confinement_time( + cur_plasma_ma, + rmajor, + rminor, + kappa95, + nd_plasma_electron_line_20, + b_plasma_toroidal_on_axis, + p_plasma_loss_mw, + ) - # Riedel scaling (H-mode) - elif model == ConfinementTimeModel.RIEDEL_H: - t_electron_confinement = self.riedel_h_confinement_time( - cur_plasma_ma, - rmajor, - rminor, - kappa95, - nd_plasma_electron_line_20, - b_plasma_toroidal_on_axis, - m_fuel_amu, - p_plasma_loss_mw, - ) + # ==================================================================== - # ======================================================================== + # Neo-Kaye scaling (L-mode) + case ConfinementTimeModel.NEO_KAYE: + t_electron_confinement = self.neo_kaye_confinement_time( + cur_plasma_ma, + rmajor, + rminor, + kappa95, + nd_plasma_electron_line_20, + b_plasma_toroidal_on_axis, + p_plasma_loss_mw, + ) - # Amended version of ITER H90-P law - elif model == ConfinementTimeModel.ITER_H90_P_AMENDED: - t_electron_confinement = self.iter_h90_p_amended_confinement_time( - cur_plasma_ma, - b_plasma_toroidal_on_axis, - m_fuel_amu, - rmajor, - p_plasma_loss_mw, - kappa, - ) + # ======== ================================================================ - # ========================================================================== + # Riedel scaling (H-mode) + case ConfinementTimeModel.RIEDEL_H: + t_electron_confinement = self.riedel_h_confinement_time( + cur_plasma_ma, + rmajor, + rminor, + kappa95, + nd_plasma_electron_line_20, + b_plasma_toroidal_on_axis, + m_fuel_amu, + p_plasma_loss_mw, + ) - # Sudo et al. scaling (stellarators/heliotron) - elif model == ConfinementTimeModel.SUDO_ET_AL: - t_electron_confinement = self.sudo_et_al_confinement_time( - rmajor, - rminor, - nd_plasma_electron_line_20, - b_plasma_toroidal_on_axis, - p_plasma_loss_mw, - ) + # ==================================================================== - # ========================================================================== + # Amended version of ITER H90-P law + case ConfinementTimeModel.ITER_H90_P_AMENDED: + t_electron_confinement = self.iter_h90_p_amended_confinement_time( + cur_plasma_ma, + b_plasma_toroidal_on_axis, + m_fuel_amu, + rmajor, + p_plasma_loss_mw, + kappa, + ) - # Gyro-reduced Bohm scaling - elif model == ConfinementTimeModel.GYRO_REDUCED_BOHM: - t_electron_confinement = self.gyro_reduced_bohm_confinement_time( - b_plasma_toroidal_on_axis, - nd_plasma_electron_line_20, - p_plasma_loss_mw, - rminor, - rmajor, - ) + # ==================================================================== - # ========================================================================== + # Sudo et al. scaling (stellarators/heliotron) + case ConfinementTimeModel.SUDO_ET_AL: + t_electron_confinement = self.sudo_et_al_confinement_time( + rmajor, + rminor, + nd_plasma_electron_line_20, + b_plasma_toroidal_on_axis, + p_plasma_loss_mw, + ) - # Lackner-Gottardi stellarator scaling - elif model == ConfinementTimeModel.LACKNER_GOTTARDI_STELLARATOR: - t_electron_confinement = self.lackner_gottardi_stellarator_confinement_time( - rmajor, - rminor, - nd_plasma_electron_line_20, - b_plasma_toroidal_on_axis, - p_plasma_loss_mw, - q95, - ) + # ==================================================================== - # ========================================================================== + # Gyro-reduced Bohm scaling + case ConfinementTimeModel.GYRO_REDUCED_BOHM: + t_electron_confinement = self.gyro_reduced_bohm_confinement_time( + b_plasma_toroidal_on_axis, + nd_plasma_electron_line_20, + p_plasma_loss_mw, + rminor, + rmajor, + ) - # ITER_93 ELM-free H-mode scaling - elif model == ConfinementTimeModel.ITER_93H: - t_electron_confinement = self.iter_93h_confinement_time( - cur_plasma_ma, - b_plasma_toroidal_on_axis, - p_plasma_loss_mw, - m_fuel_amu, - rmajor, - nd_plasma_electron_line_20, - aspect, - kappa, - ) + # ==================================================================== + + # Lackner-Gottardi stellarator scaling + case ConfinementTimeModel.LACKNER_GOTTARDI_STELLARATOR: + t_electron_confinement = ( + self.lackner_gottardi_stellarator_confinement_time( + rmajor, + rminor, + nd_plasma_electron_line_20, + b_plasma_toroidal_on_axis, + p_plasma_loss_mw, + q95, + ) + ) - # ========================================================================== - # Scaling removed - elif model == ConfinementTimeModel.TITAN_REMOVED: - raise ProcessValueError("Scaling removed") - # ========================================================================== + # ==================================================================== - # ELM-free: ITERH-97P - elif model == ConfinementTimeModel.ITER_H97P: - t_electron_confinement = self.iter_h97p_confinement_time( - cur_plasma_ma, - b_plasma_toroidal_on_axis, - p_plasma_loss_mw, - nd_plasma_electron_line_19, - rmajor, - aspect, - kappa, - m_fuel_amu, - ) + # ITER_93 ELM-free H-mode scaling + case ConfinementTimeModel.ITER_93H: + t_electron_confinement = self.iter_93h_confinement_time( + cur_plasma_ma, + b_plasma_toroidal_on_axis, + p_plasma_loss_mw, + m_fuel_amu, + rmajor, + nd_plasma_electron_line_20, + aspect, + kappa, + ) - # ========================================================================== + # ==================================================================== + # Scaling removed + case ConfinementTimeModel.TITAN_REMOVED: + raise ProcessValueError("Scaling removed") + # ==================================================================== - # ELMy: ITERH-97P(y) - elif model == ConfinementTimeModel.ITER_H97P_ELMY: - t_electron_confinement = self.iter_h97p_elmy_confinement_time( - cur_plasma_ma, - b_plasma_toroidal_on_axis, - p_plasma_loss_mw, - nd_plasma_electron_line_19, - rmajor, - aspect, - kappa, - m_fuel_amu, - ) + # ELM-free: ITERH-97P + case ConfinementTimeModel.ITER_H97P: + t_electron_confinement = self.iter_h97p_confinement_time( + cur_plasma_ma, + b_plasma_toroidal_on_axis, + p_plasma_loss_mw, + nd_plasma_electron_line_19, + rmajor, + aspect, + kappa, + m_fuel_amu, + ) - # ========================================================================== + # ==================================================================== - # ITER-96P (= ITER-97L) L-mode scaling - elif model == ConfinementTimeModel.ITER_96P: - t_electron_confinement = self.iter_96p_confinement_time( - cur_plasma_ma, - b_plasma_toroidal_on_axis, - kappa95, - rmajor, - aspect, - nd_plasma_electron_line_19, - m_fuel_amu, - p_plasma_loss_mw, - ) + # ELMy: ITERH-97P(y) + case ConfinementTimeModel.ITER_H97P_ELMY: + t_electron_confinement = self.iter_h97p_elmy_confinement_time( + cur_plasma_ma, + b_plasma_toroidal_on_axis, + p_plasma_loss_mw, + nd_plasma_electron_line_19, + rmajor, + aspect, + kappa, + m_fuel_amu, + ) - # ========================================================================== + # ==================================================================== - # Valovic modified ELMy-H mode scaling - # WARNING: No reference found for this scaling. This may not be its real name - elif model == ConfinementTimeModel.VALOVIC_ELMY: - t_electron_confinement = self.valovic_elmy_confinement_time( - cur_plasma_ma, - b_plasma_toroidal_on_axis, - nd_plasma_electron_line_19, - m_fuel_amu, - rmajor, - rminor, - kappa, - p_plasma_loss_mw, - ) + # ITER-96P (= ITER-97L) L-mode scaling + case ConfinementTimeModel.ITER_96P: + t_electron_confinement = self.iter_96p_confinement_time( + cur_plasma_ma, + b_plasma_toroidal_on_axis, + kappa95, + rmajor, + aspect, + nd_plasma_electron_line_19, + m_fuel_amu, + p_plasma_loss_mw, + ) - # ========================================================================== + # ==================================================================== - # Kaye PPPL Workshop April 1998 L-mode scaling - # WARNING: No reference found for this scaling. This may not be its real name - elif model == ConfinementTimeModel.KAYE: - t_electron_confinement = self.kaye_confinement_time( - cur_plasma_ma, - b_plasma_toroidal_on_axis, - kappa, - rmajor, - aspect, - nd_plasma_electron_line_19, - m_fuel_amu, - p_plasma_loss_mw, - ) + # Valovic modified ELMy-H mode scaling + # WARNING: No reference found for this scaling. This may not be its real name + case ConfinementTimeModel.VALOVIC_ELMY: + t_electron_confinement = self.valovic_elmy_confinement_time( + cur_plasma_ma, + b_plasma_toroidal_on_axis, + nd_plasma_electron_line_19, + m_fuel_amu, + rmajor, + rminor, + kappa, + p_plasma_loss_mw, + ) - # ========================================================================== + # ==================================================================== - # ITERH-PB98P(y), ELMy H-mode scaling - # WARNING: No reference found for this scaling. This may not be its real name - elif model == ConfinementTimeModel.ITER_PB98P_Y: - t_electron_confinement = self.iter_pb98py_confinement_time( - cur_plasma_ma, - b_plasma_toroidal_on_axis, - nd_plasma_electron_line_19, - p_plasma_loss_mw, - rmajor, - self.data.physics.kappa_ipb, - aspect, - m_fuel_amu, - ) + # Kaye PPPL Workshop April 1998 L-mode scaling + # WARNING: No reference found for this scaling. This may not be its real name + case ConfinementTimeModel.KAYE: + t_electron_confinement = self.kaye_confinement_time( + cur_plasma_ma, + b_plasma_toroidal_on_axis, + kappa, + rmajor, + aspect, + nd_plasma_electron_line_19, + m_fuel_amu, + p_plasma_loss_mw, + ) - # ========================================================================== + # ==================================================================== - # IPB98(y), ELMy H-mode scaling - elif model == ConfinementTimeModel.IPB98_Y: - t_electron_confinement = self.iter_ipb98y_confinement_time( - cur_plasma_ma, - b_plasma_toroidal_on_axis, - nd_plasma_electron_line_19, - p_plasma_loss_mw, - rmajor, - kappa, - aspect, - m_fuel_amu, - ) + # ITERH-PB98P(y), ELMy H-mode scaling + # WARNING: No reference found for this scaling. This may not be its real name + case ConfinementTimeModel.ITER_PB98P_Y: + t_electron_confinement = self.iter_pb98py_confinement_time( + cur_plasma_ma, + b_plasma_toroidal_on_axis, + nd_plasma_electron_line_19, + p_plasma_loss_mw, + rmajor, + self.data.physics.kappa_ipb, + aspect, + m_fuel_amu, + ) - # ========================================================================== + # ==================================================================== - # IPB98(y,1), ELMy H-mode scaling - elif model == ConfinementTimeModel.ITER_IPB98Y1: - t_electron_confinement = self.iter_ipb98y1_confinement_time( - cur_plasma_ma, - b_plasma_toroidal_on_axis, - nd_plasma_electron_line_19, - p_plasma_loss_mw, - rmajor, - self.data.physics.kappa_ipb, - aspect, - m_fuel_amu, - ) + # IPB98(y), ELMy H-mode scaling + case ConfinementTimeModel.IPB98_Y: + t_electron_confinement = self.iter_ipb98y_confinement_time( + cur_plasma_ma, + b_plasma_toroidal_on_axis, + nd_plasma_electron_line_19, + p_plasma_loss_mw, + rmajor, + kappa, + aspect, + m_fuel_amu, + ) - # ========================================================================== + # ==================================================================== - # IPB98(y,2), ELMy H-mode scaling - elif model == ConfinementTimeModel.ITER_IPB98Y2: - t_electron_confinement = self.iter_ipb98y2_confinement_time( - cur_plasma_ma, - b_plasma_toroidal_on_axis, - nd_plasma_electron_line_19, - p_plasma_loss_mw, - rmajor, - self.data.physics.kappa_ipb, - aspect, - m_fuel_amu, - ) + # IPB98(y,1), ELMy H-mode scaling + case ConfinementTimeModel.ITER_IPB98Y1: + t_electron_confinement = self.iter_ipb98y1_confinement_time( + cur_plasma_ma, + b_plasma_toroidal_on_axis, + nd_plasma_electron_line_19, + p_plasma_loss_mw, + rmajor, + self.data.physics.kappa_ipb, + aspect, + m_fuel_amu, + ) - # ========================================================================== + # ==================================================================== - # IPB98(y,3), ELMy H-mode scaling - elif model == ConfinementTimeModel.ITER_IPB98Y3: - t_electron_confinement = self.iter_ipb98y3_confinement_time( - cur_plasma_ma, - b_plasma_toroidal_on_axis, - nd_plasma_electron_line_19, - p_plasma_loss_mw, - rmajor, - self.data.physics.kappa_ipb, - aspect, - m_fuel_amu, - ) + # IPB98(y,2), ELMy H-mode scaling + case ConfinementTimeModel.ITER_IPB98Y2: + t_electron_confinement = self.iter_ipb98y2_confinement_time( + cur_plasma_ma, + b_plasma_toroidal_on_axis, + nd_plasma_electron_line_19, + p_plasma_loss_mw, + rmajor, + self.data.physics.kappa_ipb, + aspect, + m_fuel_amu, + ) - # ========================================================================== + # ==================================================================== - # IPB98(y,4), ELMy H-mode scaling - elif model == ConfinementTimeModel.ITER_IPB98Y4: - t_electron_confinement = self.iter_ipb98y4_confinement_time( - cur_plasma_ma, - b_plasma_toroidal_on_axis, - nd_plasma_electron_line_19, - p_plasma_loss_mw, - rmajor, - self.data.physics.kappa_ipb, - aspect, - m_fuel_amu, - ) + # IPB98(y,3), ELMy H-mode scaling + case ConfinementTimeModel.ITER_IPB98Y3: + t_electron_confinement = self.iter_ipb98y3_confinement_time( + cur_plasma_ma, + b_plasma_toroidal_on_axis, + nd_plasma_electron_line_19, + p_plasma_loss_mw, + rmajor, + self.data.physics.kappa_ipb, + aspect, + m_fuel_amu, + ) - # ========================================================================== + # ==================================================================== - # ISS95 stellarator scaling - elif model == ConfinementTimeModel.ISS95_STELLARATOR: - # dummy argument q95 is actual argument iotabar for stellarators - iotabar = q95 - t_electron_confinement = self.iss95_stellarator_confinement_time( - rminor, - rmajor, - nd_plasma_electron_line_19, - b_plasma_toroidal_on_axis, - p_plasma_loss_mw, - iotabar, - ) + # IPB98(y,4), ELMy H-mode scaling + case ConfinementTimeModel.ITER_IPB98Y4: + t_electron_confinement = self.iter_ipb98y4_confinement_time( + cur_plasma_ma, + b_plasma_toroidal_on_axis, + nd_plasma_electron_line_19, + p_plasma_loss_mw, + rmajor, + self.data.physics.kappa_ipb, + aspect, + m_fuel_amu, + ) - # ========================================================================== + # ==================================================================== - # ISS04 stellarator scaling - elif model == ConfinementTimeModel.ISS04_STELLARATOR: - # dummy argument q95 is actual argument iotabar for stellarators - iotabar = q95 - t_electron_confinement = self.iss04_stellarator_confinement_time( - rminor, - rmajor, - nd_plasma_electron_line_19, - b_plasma_toroidal_on_axis, - p_plasma_loss_mw, - iotabar, - ) + # ISS95 stellarator scaling + case ConfinementTimeModel.ISS95_STELLARATOR: + # dummy argument q95 is actual argument iotabar for stellarators + iotabar = q95 + t_electron_confinement = self.iss95_stellarator_confinement_time( + rminor, + rmajor, + nd_plasma_electron_line_19, + b_plasma_toroidal_on_axis, + p_plasma_loss_mw, + iotabar, + ) - # ========================================================================== + # ==================================================================== - # DS03 beta-independent H-mode scaling - elif model == ConfinementTimeModel.DS03: - t_electron_confinement = self.ds03_confinement_time( - cur_plasma_ma, - b_plasma_toroidal_on_axis, - nd_plasma_electron_line_19, - p_plasma_loss_mw, - rmajor, - kappa95, - aspect, - m_fuel_amu, - ) + # ISS04 stellarator scaling + case ConfinementTimeModel.ISS04_STELLARATOR: + # dummy argument q95 is actual argument iotabar for stellarators + iotabar = q95 + t_electron_confinement = self.iss04_stellarator_confinement_time( + rminor, + rmajor, + nd_plasma_electron_line_19, + b_plasma_toroidal_on_axis, + p_plasma_loss_mw, + iotabar, + ) - # ========================================================================== + # ==================================================================== - # Murari "Non-power law" scaling - elif model == ConfinementTimeModel.MURARI: - t_electron_confinement = self.murari_confinement_time( - cur_plasma_ma, - rmajor, - self.data.physics.kappa_ipb, - nd_plasma_electron_line_19, - b_plasma_toroidal_on_axis, - p_plasma_loss_mw, - ) + # DS03 beta-independent H-mode scaling + case ConfinementTimeModel.DS03: + t_electron_confinement = self.ds03_confinement_time( + cur_plasma_ma, + b_plasma_toroidal_on_axis, + nd_plasma_electron_line_19, + p_plasma_loss_mw, + rmajor, + kappa95, + aspect, + m_fuel_amu, + ) - # ========================================================================== + # ==================================================================== - # Petty08, beta independent dimensionless scaling - elif model == ConfinementTimeModel.PETTY08: - t_electron_confinement = self.petty08_confinement_time( - cur_plasma_ma, - b_plasma_toroidal_on_axis, - nd_plasma_electron_line_19, - p_plasma_loss_mw, - rmajor, - self.data.physics.kappa_ipb, - aspect, - ) + # Murari "Non-power law" scaling + case ConfinementTimeModel.MURARI: + t_electron_confinement = self.murari_confinement_time( + cur_plasma_ma, + rmajor, + self.data.physics.kappa_ipb, + nd_plasma_electron_line_19, + b_plasma_toroidal_on_axis, + p_plasma_loss_mw, + ) - # ========================================================================== + # ==================================================================== - # Lang high density relevant confinement scaling - elif model == ConfinementTimeModel.LANG_HIGH_DENSITY: - t_electron_confinement = self.lang_high_density_confinement_time( - plasma_current, - b_plasma_toroidal_on_axis, - nd_plasma_electron_line, - p_plasma_loss_mw, - rmajor, - rminor, - q95, - qstar, - aspect, - m_fuel_amu, - self.data.physics.kappa_ipb, - ) + # Petty08, beta independent dimensionless scaling + case ConfinementTimeModel.PETTY08: + t_electron_confinement = self.petty08_confinement_time( + cur_plasma_ma, + b_plasma_toroidal_on_axis, + nd_plasma_electron_line_19, + p_plasma_loss_mw, + rmajor, + self.data.physics.kappa_ipb, + aspect, + ) - # ========================================================================== + # ==================================================================== - # Hubbard 2017 I-mode confinement time scaling - nominal - elif model == ConfinementTimeModel.HUBBARD_NOMINAL: - t_electron_confinement = self.hubbard_nominal_confinement_time( - cur_plasma_ma, - b_plasma_toroidal_on_axis, - nd_plasma_electron_line_20, - p_plasma_loss_mw, - ) + # Lang high density relevant confinement scaling + case ConfinementTimeModel.LANG_HIGH_DENSITY: + t_electron_confinement = self.lang_high_density_confinement_time( + plasma_current, + b_plasma_toroidal_on_axis, + nd_plasma_electron_line, + p_plasma_loss_mw, + rmajor, + rminor, + q95, + qstar, + aspect, + m_fuel_amu, + self.data.physics.kappa_ipb, + ) - # ========================================================================== + # ==================================================================== - # Hubbard 2017 I-mode confinement time scaling - lower - elif model == ConfinementTimeModel.HUBBARD_LOWER: - t_electron_confinement = self.hubbard_lower_confinement_time( - cur_plasma_ma, - b_plasma_toroidal_on_axis, - nd_plasma_electron_line_20, - p_plasma_loss_mw, - ) + # Hubbard 2017 I-mode confinement time scaling - nominal + case ConfinementTimeModel.HUBBARD_NOMINAL: + t_electron_confinement = self.hubbard_nominal_confinement_time( + cur_plasma_ma, + b_plasma_toroidal_on_axis, + nd_plasma_electron_line_20, + p_plasma_loss_mw, + ) - # ========================================================================== + # ==================================================================== - # Hubbard 2017 I-mode confinement time scaling - upper - elif model == ConfinementTimeModel.HUBBARD_UPPER: - t_electron_confinement = self.hubbard_upper_confinement_time( - cur_plasma_ma, - b_plasma_toroidal_on_axis, - nd_plasma_electron_line_20, - p_plasma_loss_mw, - ) + # Hubbard 2017 I-mode confinement time scaling - lower + case ConfinementTimeModel.HUBBARD_LOWER: + t_electron_confinement = self.hubbard_lower_confinement_time( + cur_plasma_ma, + b_plasma_toroidal_on_axis, + nd_plasma_electron_line_20, + p_plasma_loss_mw, + ) - # ========================================================================== + # ==================================================================== - # Menard NSTX, ELMy H-mode scaling - elif model == ConfinementTimeModel.MENARD_NSTX: - t_electron_confinement = self.menard_nstx_confinement_time( - cur_plasma_ma, - b_plasma_toroidal_on_axis, - nd_plasma_electron_line_19, - p_plasma_loss_mw, - rmajor, - self.data.physics.kappa_ipb, - aspect, - m_fuel_amu, - ) + # Hubbard 2017 I-mode confinement time scaling - upper + case ConfinementTimeModel.HUBBARD_UPPER: + t_electron_confinement = self.hubbard_upper_confinement_time( + cur_plasma_ma, + b_plasma_toroidal_on_axis, + nd_plasma_electron_line_20, + p_plasma_loss_mw, + ) - # ========================================================================== + # ==================================================================== - # Menard NSTX-Petty08 Hybrid - elif model == ConfinementTimeModel.MENARD_NSTX_PETTY08_HYBRID: - t_electron_confinement = self.menard_nstx_petty08_hybrid_confinement_time( - cur_plasma_ma, - b_plasma_toroidal_on_axis, - nd_plasma_electron_line_19, - p_plasma_loss_mw, - rmajor, - self.data.physics.kappa_ipb, - aspect, - m_fuel_amu, - ) + # Menard NSTX, ELMy H-mode scaling + case ConfinementTimeModel.MENARD_NSTX: + t_electron_confinement = self.menard_nstx_confinement_time( + cur_plasma_ma, + b_plasma_toroidal_on_axis, + nd_plasma_electron_line_19, + p_plasma_loss_mw, + rmajor, + self.data.physics.kappa_ipb, + aspect, + m_fuel_amu, + ) - # ========================================================================== + # ==================================================================== + + # Menard NSTX-Petty08 Hybrid + case ConfinementTimeModel.MENARD_NSTX_PETTY08_HYBRID: + t_electron_confinement = ( + self.menard_nstx_petty08_hybrid_confinement_time( + cur_plasma_ma, + b_plasma_toroidal_on_axis, + nd_plasma_electron_line_19, + p_plasma_loss_mw, + rmajor, + self.data.physics.kappa_ipb, + aspect, + m_fuel_amu, + ) + ) - # NSTX gyro-Bohm (Buxton) - elif model == ConfinementTimeModel.NSTX_GYRO_BOHM: - t_electron_confinement = self.nstx_gyro_bohm_confinement_time( - cur_plasma_ma, - b_plasma_toroidal_on_axis, - p_plasma_loss_mw, - rmajor, - nd_plasma_electron_line_20, - ) + # ==================================================================== + + # NSTX gyro-Bohm (Buxton) + case ConfinementTimeModel.NSTX_GYRO_BOHM: + t_electron_confinement = self.nstx_gyro_bohm_confinement_time( + cur_plasma_ma, + b_plasma_toroidal_on_axis, + p_plasma_loss_mw, + rmajor, + nd_plasma_electron_line_20, + ) - # ========================================================================== + # ==================================================================== - # ITPA20 H-mode scaling - elif model == ConfinementTimeModel.ITPA20: - t_electron_confinement = self.itpa20_confinement_time( - cur_plasma_ma, - b_plasma_toroidal_on_axis, - nd_plasma_electron_line_19, - p_plasma_loss_mw, - rmajor, - self.data.physics.triang, - self.data.physics.kappa_ipb, - eps, - self.data.physics.m_ions_total_amu, - ) + # ITPA20 H-mode scaling + case ConfinementTimeModel.ITPA20: + t_electron_confinement = self.itpa20_confinement_time( + cur_plasma_ma, + b_plasma_toroidal_on_axis, + nd_plasma_electron_line_19, + p_plasma_loss_mw, + rmajor, + self.data.physics.triang, + self.data.physics.kappa_ipb, + eps, + self.data.physics.m_ions_total_amu, + ) - # ========================================================================== + # ==================================================================== - # ITPA20-IL confinement time scaling - elif model == ConfinementTimeModel.ITPA20_IL: - t_electron_confinement = self.itpa20_il_confinement_time( - cur_plasma_ma, - b_plasma_toroidal_on_axis, - p_plasma_loss_mw, - nd_plasma_electron_line_19, - self.data.physics.m_ions_total_amu, - rmajor, - self.data.physics.triang, - self.data.physics.kappa_ipb, - ) + # ITPA20-IL confinement time scaling + case ConfinementTimeModel.ITPA20_IL: + t_electron_confinement = self.itpa20_il_confinement_time( + cur_plasma_ma, + b_plasma_toroidal_on_axis, + p_plasma_loss_mw, + nd_plasma_electron_line_19, + self.data.physics.m_ions_total_amu, + rmajor, + self.data.physics.triang, + self.data.physics.kappa_ipb, + ) - # ========================================================================== - # NCST spherical tokamak L-mode confinement time scaling - elif model == ConfinementTimeModel.NCST: - t_electron_confinement = self.ncst_confinement_time( - cur_plasma_ma=cur_plasma_ma, - b_plasma_toroidal_on_axis=b_plasma_toroidal_on_axis, - p_plasma_loss_mw=p_plasma_loss_mw, - nd_plasma_electron_line_19=nd_plasma_electron_line_19, - ) + # ========================================================================== + # NCST spherical tokamak L-mode confinement time scaling + case ConfinementTimeModel.NCST: + t_electron_confinement = self.ncst_confinement_time( + cur_plasma_ma=cur_plasma_ma, + b_plasma_toroidal_on_axis=b_plasma_toroidal_on_axis, + p_plasma_loss_mw=p_plasma_loss_mw, + nd_plasma_electron_line_19=nd_plasma_electron_line_19, + ) - # ========================================================================== + # ==================================================================== - # Paz-Soldan negative triangularity confinement time scaling - elif model == ConfinementTimeModel.PAZ_SOLDAN_NT: - t_electron_confinement = self.paz_soldan_nt_confinement_time( - cur_plasma_ma=cur_plasma_ma, - b_plasma_toroidal_on_axis=b_plasma_toroidal_on_axis, - p_plasma_loss_mw=p_plasma_loss_mw, - nd_plasma_electron_line_19=nd_plasma_electron_line_19, - ) + # Paz-Soldan negative triangularity confinement time scaling + case ConfinementTimeModel.PAZ_SOLDAN_NT: + t_electron_confinement = self.paz_soldan_nt_confinement_time( + cur_plasma_ma=cur_plasma_ma, + b_plasma_toroidal_on_axis=b_plasma_toroidal_on_axis, + p_plasma_loss_mw=p_plasma_loss_mw, + nd_plasma_electron_line_19=nd_plasma_electron_line_19, + ) - # ========================================================================== + # ==================================================================== - else: - raise ProcessValueError( - "Illegal value for i_confinement_time", - i_confinement_time=i_confinement_time, - ) + case _: + raise ProcessValueError( + "Illegal value for i_confinement_time", + i_confinement_time=i_confinement_time, + ) # Apply H-factor correction to chosen scaling t_electron_energy_confinement = hfact * t_electron_confinement @@ -963,38 +979,39 @@ def calculate_confinement_time( # noqa: PLR0917 # Calculate H* non-radiation corrected H factor # Note: we will assume the IPB-98y2 scaling. - if ( - ConfinementRadiationLossModel(self.data.physics.i_rad_loss) - == ConfinementRadiationLossModel.CORE_ONLY - ): - hstar = ( - hfact - * ( - p_plasma_loss_mw - / ( + match ConfinementRadiationLossModel(self.data.physics.i_rad_loss): + case ConfinementRadiationLossModel.CORE_ONLY: + hstar = ( + hfact + * ( p_plasma_loss_mw - + self.data.physics.pden_plasma_sync_mw * vol_plasma - + self.data.physics.p_plasma_inner_rad_mw + / ( + p_plasma_loss_mw + + self.data.physics.pden_plasma_sync_mw * vol_plasma + + self.data.physics.p_plasma_inner_rad_mw + ) ) + ** 0.31 ) - ** 0.31 - ) - elif ( - self.data.physics.i_rad_loss == ConfinementRadiationLossModel.FULL_RADIATION - ): - hstar = ( - hfact - * ( - p_plasma_loss_mw - / ( + case ConfinementRadiationLossModel.FULL_RADIATION: + hstar = ( + hfact + * ( p_plasma_loss_mw - + self.data.physics.pden_plasma_rad_mw * vol_plasma + / ( + p_plasma_loss_mw + + self.data.physics.pden_plasma_rad_mw * vol_plasma + ) ) + ** 0.31 + ) + case ConfinementRadiationLossModel.NO_RADIATION: + hstar = hfact + case _: + raise ProcessValueError( + "Illegal value for i_rad_loss", + i_rad_loss=self.data.physics.i_rad_loss, ) - ** 0.31 - ) - elif self.data.physics.i_rad_loss == ConfinementRadiationLossModel.NO_RADIATION: - hstar = hfact # Calculation of the transport power loss terms # Transport losses in Watts/m3 are 3/2 * n.e.T / tau , with T in eV @@ -1148,13 +1165,11 @@ def fhz(hfact: float) -> float: ) # Include the radiation power if requested - if ( - self.data.physics.i_rad_loss - == ConfinementRadiationLossModel.FULL_RADIATION - ): - fhz_value += self.data.physics.pden_plasma_rad_mw - elif self.data.physics.i_rad_loss == ConfinementRadiationLossModel.CORE_ONLY: - fhz_value += self.data.physics.pden_plasma_core_rad_mw + match ConfinementRadiationLossModel(self.data.physics.i_rad_loss): + case ConfinementRadiationLossModel.FULL_RADIATION: + fhz_value += self.data.physics.pden_plasma_rad_mw + case ConfinementRadiationLossModel.CORE_ONLY: + fhz_value += self.data.physics.pden_plasma_core_rad_mw return fhz_value diff --git a/process/models/physics/current_drive.py b/process/models/physics/current_drive.py index 61c1df7ff7..554fbc4893 100644 --- a/process/models/physics/current_drive.py +++ b/process/models/physics/current_drive.py @@ -145,17 +145,17 @@ def output(self): def run(self): """NeutralBeam model doesn't need to be run""" - def iternb(self): + def iternb(self) -> tuple[float, float, float]: """Routine to calculate ITER Neutral Beam current drive parameters Returns ------- effnbss: - neutral beam current drive efficiency (A/W) + neutral beam current drive efficiency [A/W] f_p_beam_injected_ions: - fraction of NB power given to ions + fraction of NB power given to ions [-] fshine: - shine-through fraction of beam + shine-through fraction of beam [-] Raises ------ @@ -190,13 +190,13 @@ def iternb(self): # Calculate beam stopping cross-section sigstop = self.sigbeam( - self.data.current_drive.e_beam_kev / self.data.physics.m_beam_amu, - self.data.physics.temp_plasma_electron_vol_avg_kev, - self.data.physics.nd_plasma_electrons_vol_avg, - self.data.physics.f_nd_alpha_thermal_electron, - self.data.physics.f_nd_plasma_carbon_electron, - self.data.physics.f_nd_plasma_oxygen_electron, - self.data.physics.f_nd_plasma_iron_argon_electron, + eb=self.data.current_drive.e_beam_kev / self.data.physics.m_beam_amu, + te=self.data.physics.temp_plasma_electron_vol_avg_kev, + ne=self.data.physics.nd_plasma_electrons_vol_avg, + rnhe=self.data.physics.f_nd_alpha_thermal_electron, + rnc=self.data.physics.f_nd_plasma_carbon_electron, + rno=self.data.physics.f_nd_plasma_oxygen_electron, + rnfe=self.data.physics.f_nd_plasma_iron_argon_electron, ) # Calculate number of decay lengths to centre @@ -222,15 +222,15 @@ def iternb(self): # Current drive efficiency effnbss = self.data.current_drive.f_radius_beam_tangency_rmajor * self.etanb( - self.data.physics.m_beam_amu, - self.data.physics.alphan, - self.data.physics.alphat, - self.data.physics.aspect, - self.data.physics.nd_plasma_electrons_vol_avg, - self.data.current_drive.e_beam_kev, - self.data.physics.rmajor, - self.data.physics.temp_plasma_electron_density_weighted_kev, - self.data.physics.n_charge_plasma_effective_vol_avg, + m_beam_amu=self.data.physics.m_beam_amu, + alphan=self.data.physics.alphan, + alphat=self.data.physics.alphat, + aspect=self.data.physics.aspect, + nd_plasma_electrons_vol_avg=self.data.physics.nd_plasma_electrons_vol_avg, + ebeam=self.data.current_drive.e_beam_kev, + rmajor=self.data.physics.rmajor, + temp_plasma_electron_density_weighted_kev=self.data.physics.temp_plasma_electron_density_weighted_kev, + zeff=self.data.physics.n_charge_plasma_effective_vol_avg, ) return effnbss, f_p_beam_injected_ions, fshine @@ -241,11 +241,11 @@ def culnbi(self) -> tuple[float, float, float]: Returns ------- effnbss: - neutral beam current drive efficiency (A/W) + neutral beam current drive efficiency [A/W] f_p_beam_injected_ions: - fraction of NB power given to ions + fraction of NB power given to ions [-] fshine: - shine-through fraction of beam + shine-through fraction of beam [-] Raises ------ @@ -256,11 +256,14 @@ def culnbi(self) -> tuple[float, float, float]: Notes ----- - This routine calculates Neutral Beam current drive parameters + - This routine calculates Neutral Beam current drive parameters using the corrections outlined in AEA FUS 172 to the ITER method. -

The result cannot be guaranteed for devices with aspect ratios far + - The result cannot be guaranteed for devices with aspect ratios far from that of ITER (approx. 2.8). - AEA FUS 172: Physics Assessment for the European Reactor Study + + References + ---------- + [1] AEA FUS 172: Physics Assessment for the European Reactor Study """ if ( 1.0e0 + self.data.physics.eps @@ -282,13 +285,13 @@ def culnbi(self) -> tuple[float, float, float]: # Calculate beam stopping cross-section sigstop = self.sigbeam( - self.data.current_drive.e_beam_kev / self.data.physics.m_beam_amu, - self.data.physics.temp_plasma_electron_vol_avg_kev, - self.data.physics.nd_plasma_electrons_vol_avg, - self.data.physics.f_nd_alpha_thermal_electron, - self.data.physics.f_nd_plasma_carbon_electron, - self.data.physics.f_nd_plasma_oxygen_electron, - self.data.physics.f_nd_plasma_iron_argon_electron, + eb=self.data.current_drive.e_beam_kev / self.data.physics.m_beam_amu, + te=self.data.physics.temp_plasma_electron_vol_avg_kev, + ne=self.data.physics.nd_plasma_electrons_vol_avg, + rnhe=self.data.physics.f_nd_alpha_thermal_electron, + rnc=self.data.physics.f_nd_plasma_carbon_electron, + rno=self.data.physics.f_nd_plasma_oxygen_electron, + rnfe=self.data.physics.f_nd_plasma_iron_argon_electron, ) # Calculate number of decay lengths to centre @@ -318,19 +321,19 @@ def culnbi(self) -> tuple[float, float, float]: # Current drive efficiency effnbss = self.etanb2( - self.data.physics.m_beam_amu, - self.data.physics.alphan, - self.data.physics.alphat, - self.data.physics.aspect, - self.data.physics.nd_plasma_electrons_vol_avg, - self.data.physics.nd_plasma_electron_line, - self.data.current_drive.e_beam_kev, - self.data.current_drive.f_radius_beam_tangency_rmajor, - fshine, - self.data.physics.rmajor, - self.data.physics.rminor, - self.data.physics.temp_plasma_electron_density_weighted_kev, - self.data.physics.n_charge_plasma_effective_vol_avg, + m_beam_amu=self.data.physics.m_beam_amu, + alphan=self.data.physics.alphan, + alphat=self.data.physics.alphat, + aspect=self.data.physics.aspect, + nd_plasma_electrons_vol_avg=self.data.physics.nd_plasma_electrons_vol_avg, + nd_plasma_electron_line=self.data.physics.nd_plasma_electron_line, + e_beam_kev=self.data.current_drive.e_beam_kev, + f_radius_beam_tangency_rmajor=self.data.current_drive.f_radius_beam_tangency_rmajor, + fshine=fshine, + rmajor=self.data.physics.rmajor, + rminor=self.data.physics.rminor, + temp_plasma_electron_density_weighted_kev=self.data.physics.temp_plasma_electron_density_weighted_kev, + zeff=self.data.physics.n_charge_plasma_effective_vol_avg, ) return effnbss, f_p_beam_injected_ions, fshine @@ -359,39 +362,34 @@ def etanb2( system, based on the 1990 ITER model, plus correction terms outlined in Culham Report AEA FUS 172. - The formulae are from AEA FUS 172, unless denoted by IPDG89. - AEA FUS 172: Physics Assessment for the European Reactor Study - ITER Physics Design Guidelines: 1989 [IPDG89], N. A. Uckan et al, - ITER Documentation Series No.10, IAEA/ITER/DS/10, IAEA, Vienna, 1990 - Parameters ---------- m_beam_amu : float - Beam ion mass (amu) + Beam ion mass [amu] alphan : float - Density profile factor + Density profile factor [-] alphat : float - Temperature profile factor + Temperature profile factor [-] aspect : float - Aspect ratio + Aspect ratio [-] nd_plasma_electrons_vol_avg : float - Volume averaged electron density (m**-3) + Volume averaged electron density [m⁻³] nd_plasma_electron_line : float - Line averaged electron density (m**-3) + Line averaged electron density [m⁻³] e_beam_kev : float - Neutral beam energy (keV) + Neutral beam energy [keV] f_radius_beam_tangency_rmajor : float - R_tangent / R_major for neutral beam injection + R_tangent / R_major for neutral beam injection [-] fshine : float - Shine-through fraction of beam + Shine-through fraction of beam [-] rmajor : float - Plasma major radius (m) + Plasma major radius [m] rminor : float - Plasma minor radius (m) + Plasma minor radius [m] temp_plasma_electron_density_weighted_kev : float - Density weighted average electron temperature (keV) + Density weighted average electron temperature [keV] zeff : float - Plasma effective charge + Plasma effective charge [-] Returns ------- @@ -405,6 +403,14 @@ def etanb2( If the beam tangency radius is greater than the plasma major radius, which would lead to an imminent negative square root argument and the NBI missing the plasma completely. + + References + ---------- + [1] AEA FUS 172: Physics Assessment for the European Reactor Study + + [2] ITER Physics Design Guidelines: 1989 [IPDG89], N. A. Uckan et al, + + [3] ITER Documentation Series No.10, IAEA/ITER/DS/10, IAEA, Vienna, 1990 """ # Charge of beam ions zbeam = 1.0 @@ -508,39 +514,46 @@ def etanb( rmajor: float, temp_plasma_electron_density_weighted_kev: float, zeff: float, - ): + ) -> float: """Routine to find neutral beam current drive efficiency using the ITER 1990 formulation Parameters ---------- m_beam_amu: - beam ion mass (amu) + beam ion mass [amu] alphan: - density profile factor + density profile factor [-] alphat: - temperature profile factor + temperature profile factor [-] aspect: - aspect ratio + aspect ratio [-] nd_plasma_electrons_vol_avg: - volume averaged electron density (m**-3) + volume averaged electron density [m⁻³] ebeam: - neutral beam energy (keV) + neutral beam energy [keV] rmajor: - plasma major radius (m) + plasma major radius [m] temp_plasma_electron_density_weighted_kev: - density weighted average electron temp. (keV) + density weighted average electron temp. [keV] zeff: - plasma effective charge + plasma effective charge [-] + + + Returns + ------- + float + neutral beam current drive efficiency [A/W] Notes ----- This routine calculates the current drive efficiency of - a neutral beam system, based on the 1990 ITER model. - ITER Physics Design Guidelines: 1989 [IPDG89], N. A. Uckan et al, - ITER Documentation Series No.10, IAEA/ITER/DS/10, IAEA, Vienna, 1990 - + a neutral beam system, based on the 1990 ITER model [1] + References + ---------- + [1] ITER Physics Design Guidelines: 1989 [IPDG89], N. A. Uckan et al, + ITER Documentation Series No.10, IAEA/ITER/DS/10, IAEA, Vienna, 1990 """ zbeam = 1.0 @@ -581,32 +594,41 @@ def etanb( @staticmethod def sigbeam( eb: float, te: float, ne: float, rnhe: float, rnc: float, rno: float, rnfe: float - ): + ) -> float: """Calculates the stopping cross-section for a hydrogen beam in a fusion plasma Parameters ---------- eb: - beam energy (kev/amu) + beam energy [keV/amu] te: - electron temperature (keV) + electron temperature [keV] ne: - electron density (10^20m-3) + electron density [10²⁰m⁻³] rnhe: - alpha density / ne + alpha density / ne [-] rnc: - carbon density /ne + carbon density /ne [-] rno: - oxygen density /ne + oxygen density /ne [-] rnfe: - iron density /ne + iron density /ne [-] + + + Returns + ------- + float + stopping cross-section [m²] Notes ----- - This function calculates the stopping cross-section (m^2) + This function calculates the stopping cross-section [m²] for a hydrogen beam in a fusion plasma. - Janev, Boley and Post, Nuclear Fusion 29 (1989) 2125 + + References + ---------- + [1] Janev, Boley and Post, Nuclear Fusion 29 (1989) 2125 """ a = np.array([ [ @@ -699,17 +721,17 @@ def cfnbi( Parameters ---------- afast: - mass of fast particle (units of proton mass) + mass of fast particle [units of proton mass] efast: - energy of fast particle (keV) + energy of fast particle [keV] te: - density weighted average electron temp. (keV) + density weighted average electron temp. [keV] ne: - volume averaged electron density (m**-3) + volume averaged electron density [m⁻³] n_charge_plasma_effective_mass_weighted_vol_avg: - mass weighted plasma effective charge + mass weighted plasma effective charge [-] xlmbda: - ion-electron coulomb logarithm + ion-electron coulomb logarithm [-] Returns ------- @@ -759,26 +781,35 @@ def cfnbi( return (t1 + t2) / (3.0e0 * x * x) @staticmethod - def xlmbdabi(mb, mth, eb, t, nelec): + def xlmbdabi(mb: float, mth: float, eb: float, t: float, nelec: float) -> float: """Calculates the Coulomb logarithm for ion-ion collisions This function calculates the Coulomb logarithm for ion-ion collisions where the relative velocity may be large compared with the background ('mt') thermal velocity. - Mikkelson and Singer, Nuc Tech/Fus, 4, 237 (1983) Parameters ---------- mb: - mass of fast particle (units of proton mass) + mass of fast particle [units of proton mass] mth: - mass of background ions (units of proton mass) + mass of background ions [units of proton mass] eb: - energy of fast particle (keV) + energy of fast particle [keV] t: - density weighted average electron temp. (keV) + density weighted average electron temp. [keV] nelec: - volume averaged electron density (m**-3) + volume averaged electron density [m⁻³] + + Returns + ------- + float + Coulomb logarithm for ion-ion collisions [-] + + References + ---------- + [1] Mikkelson and Singer, Nuc Tech/Fus, 4, 237 (1983) + """ x1 = (t / 10.0) * (eb / 1000.0) * mb / (nelec / 1e20) x2 = mth / (mth + mb) @@ -799,7 +830,7 @@ def run(self): def output(self): """ElectronCyclotron model has no output""" - def culecd(self): + def culecd(self) -> float: """Routine to calculate Electron Cyclotron current drive efficiency This routine calculates the current drive parameters for a @@ -808,8 +839,12 @@ def culecd(self): Returns ------- - : - electron cyclotron current drive efficiency (A/W) + float + electron cyclotron current drive efficiency [A/W] + + References + ---------- + [1] AEA FUS 172: Physics Assessment for the European Reactor Study """ rrr = 1.0e0 / 3.0e0 @@ -863,7 +898,9 @@ def culecd(self): # Current drive efficiency (A/W) return ecgam / (dlocal * self.data.physics.rmajor) - def eccdef(self, tlocal, epsloc, zlocal, cosang, coulog): + def eccdef( + self, tlocal: float, epsloc: float, zlocal: float, cosang: float, coulog: float + ) -> float: """Calculate Electron Cyclotron current drive efficiency. This routine calculates the current drive parameters for an @@ -871,11 +908,11 @@ def eccdef(self, tlocal, epsloc, zlocal, cosang, coulog): It works out the ECCD efficiency using the formula due to Cohen quoted in the ITER Physics Design Guidelines: 1989 (but including division by the Coulomb Logarithm omitted from - IPDG89). We have assumed gamma**2-1 << 1, where gamma is the + IPDG89). We have assumed γ²-1 << 1, where gamma is the relativistic factor. The notation follows that in IPDG89. The answer ECGAM is the normalised efficiency nIR/P with n the - local density in 10**20 /m**3, I the driven current in MAmps, + local density in 10²⁰ /m³, I the driven current in MA, R the major radius in metres, and P the absorbed power in MWatts. @@ -883,13 +920,13 @@ def eccdef(self, tlocal, epsloc, zlocal, cosang, coulog): Parameters ---------- tlocal : float - Local electron temperature (keV). + Local electron temperature [keV]. epsloc : float Local inverse aspect ratio. zlocal : float Local plasma effective charge. cosang : float - Cosine of the poloidal angle at which ECCD takes place + Cosine of the poloidal angle at which ECCD takes place. (+1 outside, -1 inside). coulog : float Local coulomb logarithm for ion-electron collisions. @@ -897,7 +934,7 @@ def eccdef(self, tlocal, epsloc, zlocal, cosang, coulog): Returns ------- float - Normalised current drive efficiency (A/W m**-2). + Normalised current drive efficiency [A/W m⁻²]. Raises ------ @@ -906,9 +943,11 @@ def eccdef(self, tlocal, epsloc, zlocal, cosang, coulog): References ---------- - AEA FUS 172: Physics Assessment for the European Reactor Study - ITER Physics Design Guidelines: 1989 [IPDG89], N. A. Uckan et al, - ITER Documentation Series No.10, IAEA/ITER/DS/10, IAEA, Vienna, 1990 + [1] AEA FUS 172: Physics Assessment for the European Reactor Study. + + [2] ITER Physics Design Guidelines: 1989 [IPDG89], N. A. Uckan et al, + + [3]ITER Documentation Series No.10, IAEA/ITER/DS/10, IAEA, Vienna, 1990 """ mcsq = ( constants.ELECTRON_MASS * 2.9979e8**2 / (1.0e3 * constants.ELECTRON_VOLT) @@ -916,7 +955,7 @@ def eccdef(self, tlocal, epsloc, zlocal, cosang, coulog): f = 16.0e0 * (tlocal / mcsq) ** 2 # fp is the derivative of f with respect to gamma, the relativistic - # factor, taken equal to 1 + 2T/(m c**2) + # factor, taken equal to 1 + 2T/(m c²) fp = 16.0e0 * tlocal / mcsq @@ -968,23 +1007,23 @@ def electron_cyclotron_fenstermacher( Parameters ---------- temp_plasma_electron_density_weighted_kev: float - Density weighted average electron temperature keV. + Density weighted average electron temperature [keV]. rmajor: float - Major radius of the plasma in meters. + Major radius of the plasma [m]. dene20: float - Volume averaged electron density in 1x10^20 m^-3. + Volume averaged electron density [10²⁰ m⁻³]. dlamee: float - Electron collision frequency in 1/s. + Electron collision frequency [s⁻¹]. Returns ------- float - The calculated electron cyclotron heating efficiency in A/W. + The calculated electron cyclotron heating efficiency [A/W]. References ---------- - - T.C. Hender et al., 'Physics Assessment of the European Reactor Study', - AEA FUS 172, 1992. + [1] T.C. Hender et al., 'Physics Assessment of the European Reactor Study', + AEA FUS 172, 1992. """ return (0.21e0 * temp_plasma_electron_density_weighted_kev) / ( rmajor * dene20 * dlamee @@ -1010,31 +1049,30 @@ def electron_cyclotron_freethy( Parameters ---------- te: float - Volume averaged electron temperature in keV. + Volume averaged electron temperature [keV]. zeff: float - Plasma effective charge. + Plasma effective charge [-]. rmajor: float - Major radius of the plasma in meters. + Major radius of the plasma [m]. nd_plasma_electrons_vol_avg: float - Volume averaged electron density in m^-3. + Volume averaged electron density [m⁻³]. b_plasma_toroidal_on_axis: float - Toroidal magnetic field in Tesla. + Toroidal magnetic field [T]. n_ecrh_harmonic: int - Cyclotron harmonic number (fundamental used as default). + Cyclotron harmonic number [-] (fundamental used as default). i_ecrh_wave_mode: int - Wave mode switch (0 for O-mode, 1 for X-mode). + Wave mode switch [-] (0 for O-mode, 1 for X-mode). Returns ------- float - The calculated absolute ECCD efficiency in A/W. + The calculated absolute ECCD efficiency [A/W]. Raises ------ ValueError If the wave mode is invalid (not 0 for O-mode or 1 for X-mode). - Notes ----- - Plasma coupling only occurs if the plasma cut-off is below the cyclotron @@ -1043,7 +1081,7 @@ def electron_cyclotron_freethy( References ---------- - - Freethy, S., PROCESS issue #2994. + [1] Freethy, S., PROCESS issue #2994. """ # Cyclotron frequency fc = ( @@ -1187,18 +1225,18 @@ def ion_cyclotron_ipdg89( Parameters ---------- temp_plasma_electron_density_weighted_kev: float - Density weighted average electron temperature keV. + Density weighted average electron temperature [keV]. zeff: float - Plasma effective charge. + Plasma effective charge [-] rmajor: float - Major radius of the plasma in meters. + Major radius of the plasma [m]. dene20: float - Volume averaged electron density in 1x10^20 m^-3. + Volume averaged electron density [10²⁰ m⁻³]. Returns ------- float - The calculated ion cyclotron heating efficiency in A/W. + The calculated ion cyclotron heating efficiency [A/W]. Notes ----- @@ -1209,11 +1247,11 @@ def ion_cyclotron_ipdg89( References ---------- - - N.A. Uckan and ITER Physics Group, 'ITER Physics Design Guidelines: 1989', - https://inis.iaea.org/collection/NCLCollectionStore/_Public/21/068/21068960.pdf + [1] N.A. Uckan and ITER Physics Group, 'ITER Physics Design Guidelines: 1989', + https://inis.iaea.org/collection/NCLCollectionStore/_Public/21/068/21068960.pdf - - T.C. Hender et al., 'Physics Assessment of the European Reactor Study', - AEA FUS 172, 1992. + [2] T.C. Hender et al., 'Physics Assessment of the European Reactor Study', + AEA FUS 172, 1992. """ return ( (0.63e0 * 0.1e0 * temp_plasma_electron_density_weighted_kev) / (2.0e0 + zeff) @@ -1246,13 +1284,13 @@ def electron_bernstein_freethy( Parameters ---------- te: float - Volume averaged electron temperature in keV. + Volume averaged electron temperature [keV]. rmajor: float - Major radius of the plasma in meters. + Major radius of the plasma [m]. dene20: float - Volume averaged electron density in units of 10^20 m^-3. + Volume averaged electron density [10²⁰ m⁻³]. b_plasma_toroidal_on_axis: float - Toroidal magnetic field in Tesla. + Toroidal magnetic field [T]. n_ecrh_harmonic: int Cyclotron harmonic number (fundamental used as default). xi_ebw: float @@ -1261,7 +1299,7 @@ def electron_bernstein_freethy( Returns ------- float - The calculated absolute EBW current drive efficiency in A/W. + The calculated absolute EBW current drive efficiency [A/W]. Notes ----- @@ -1271,7 +1309,7 @@ def electron_bernstein_freethy( References ---------- - - Freethy, S., PROCESS issue #1262. + [1] Freethy, S., PROCESS issue #1262. """ # Normalised current drive efficiency gamma eta_cd_norm = (xi_ebw / 32.7e0) * te @@ -1322,7 +1360,7 @@ def run(self): def output(self): """LowerHybrid model has no output""" - def cullhy(self): + def cullhy(self) -> float: """Calculate Culham Lower Hybrid current drive efficiency. This routine calculates the current drive parameters for a @@ -1335,7 +1373,7 @@ def cullhy(self): Returns ------- float - Lower hybrid current drive efficiency (A/W) + Lower hybrid current drive efficiency [A/W] Raises ------ @@ -1343,9 +1381,9 @@ def cullhy(self): If the normalised LH efficiency is negative, which may indicate an issue with the input parameters - Notes - ----- - AEA FUS 172: Physics Assessment for the European Reactor Study + References + ---------- + [1] AEA FUS 172: Physics Assessment for the European Reactor Study """ rratio = self.lhrad() rpenet = rratio * self.data.physics.rminor @@ -1410,13 +1448,18 @@ def cullhy(self): return gamlh / ((0.1e0 * dlocal) * self.data.physics.rmajor) - def lhrad(self): - """Routine to calculate Lower Hybrid wave absorption radius + def lhrad(self) -> float: + """Routine to calculate Lower Hybrid wave absorption radius [m]. rratio: output real: minor radius of penetration / rminor This routine determines numerically the minor radius at which the damping of Lower Hybrid waves occurs, using a Newton-Raphson method. AEA FUS 172: Physics Assessment for the European Reactor Study + + + References + ---------- + [1] AEA FUS 172: Physics Assessment for the European Reactor Study, 1992. """ # Correction to refractive index (kept within valid bounds) drfind = min( @@ -1472,9 +1515,9 @@ def lhrad(self): return rat0 - def lheval(self, drfind, rratio): + def lheval(self, drfind: float, rratio: float) -> float: """Routine to evaluate the difference between electron energy - expressions required to find the Lower Hybrid absorption radius + expressions required to find the Lower Hybrid absorption radius [m]. Parameters ---------- @@ -1486,16 +1529,18 @@ def lheval(self, drfind, rratio): Returns ------- ediff: - difference between the E values (keV) + difference between the E values [keV] Notes ----- - This routine evaluates the difference between the values calculated + - This routine evaluates the difference between the values calculated from the two equations for the electron energy E, given in AEA FUS 172, p.58. This difference is used to locate the Lower Hybrid wave absorption radius via a Newton-Raphson method, in calling - routine lhrad. - AEA FUS 172: Physics Assessment for the European Reactor Study + + References + ---------- + [1] AEA FUS 172: Physics Assessment for the European Reactor Study, 1992. """ dlocal = 1.0e-19 * self.plasma_profile.neprofile.calculate_profile_y( rratio, @@ -1556,28 +1601,28 @@ def lower_hybrid_fenstermacher(te: float, rmajor: float, dene20: float) -> float Parameters ---------- te: float - Volume averaged electron temperature in keV. + Volume averaged electron temperature [keV]. rmajor: float - Major radius of the plasma in meters. + Major radius of the plasma [m]. dene20: float - Volume averaged electron density in units of 10^20 m^-3. + Volume averaged electron density [10²⁰ m⁻³]. Returns ------- float - The calculated absolute current drive efficiency in A/W. + The calculated absolute current drive efficiency [A/W]. Notes ----- - This formula was originally in the Oak RidgeSystems Code, attributed to - Fenstermacher and is used in the AEA FUS 172 report. + Fenstermacher and is used in the AEA FUS 172 report. References ---------- - - T.C. Hender et al., 'Physics Assessment of the European Reactor Study', - AEA FUS 172, 1992. + [1] T.C. Hender et al., 'Physics Assessment of the European Reactor Study', + AEA FUS 172, 1992. - - R.L.Reid et al, Oak Ridge Report ORNL/FEDC-87-7, 1988 + [2] R.L.Reid et al, Oak Ridge Report ORNL/FEDC-87-7, 1988 """ return (0.36e0 * (1.0e0 + (te / 25.0e0) ** 1.16e0)) / (rmajor * dene20) @@ -1593,26 +1638,26 @@ def lower_hybrid_ehst( Parameters ---------- te: float - Volume averaged electron temperature in keV. + Volume averaged electron temperature [keV]. beta: float - Plasma beta value (ratio of plasma pressure to magnetic pressure). + Plasma beta value (ratio of plasma pressure to magnetic pressure) [-] rmajor: float - Major radius of the plasma in meters. + Major radius of the plasma [m]. dene20: float - Volume averaged electron density in units of 10^20 m^-3. + Volume averaged electron density [10²⁰ m⁻³]. zeff: float - Plasma effective charge. + Plasma effective charge [-] Returns ------- float - The calculated absolute current drive efficiency in A/W. + The calculated absolute current drive efficiency [A/W]. References ---------- - - Ehst, D.A., and Karney, C.F.F., "Lower Hybrid Current Drive in Tokamaks", - Nuclear Fusion, 31(10), 1933-1949, 1991. + [1] Ehst, D.A., and Karney, C.F.F., "Lower Hybrid Current Drive in Tokamaks", + Nuclear Fusion, 31(10), 1933-1949, 1991. """ return ( ((te**0.77 * (0.034 + 0.196 * beta)) / (rmajor * dene20)) @@ -1680,7 +1725,10 @@ def current_drive(self): # To stop issues with input file we force # zero secondary heating if no injection method - if self.data.current_drive.i_hcd_secondary == 0: + if ( + CurrentDriveModel(self.data.current_drive.i_hcd_secondary) + == CurrentDriveModel.NO_CURRENT_DRIVE + ): self.data.current_drive.p_hcd_secondary_extra_heat_mw = 0.0 # i_hcd_calculations | switch for current drive calculation @@ -1776,7 +1824,8 @@ def current_drive(self): if secondary_cdm.method == CurrentDriveMethodType.NEUTRAL_BEAM: _, f_p_beam_injected_ions, f_p_beam_shine_through = ( self.neutral_beam.iternb() - if self.data.current_drive.i_hcd_secondary == 5 + if CurrentDriveModel(self.data.current_drive.i_hcd_secondary) + == CurrentDriveModel.ITER_NEUTRAL_BEAM else self.neutral_beam.culnbi() ) self.data.current_drive.f_p_beam_injected_ions = f_p_beam_injected_ions @@ -1884,382 +1933,385 @@ def current_drive(self): # ============================================================== # Lower hybrid cases - if secondary_cdm.method == CurrentDriveMethodType.LOWER_HYBRID: - # Injected power - p_hcd_secondary_electrons_mw = ( - self.data.current_drive.p_hcd_secondary_injected_mw - + self.data.current_drive.p_hcd_secondary_extra_heat_mw - ) - - # Wall plug power - self.data.heat_transport.p_hcd_secondary_electric_mw = ( - self.data.current_drive.p_hcd_secondary_injected_mw - + self.data.current_drive.p_hcd_secondary_extra_heat_mw - ) / self.data.current_drive.eta_lowhyb_injector_wall_plug - - # Wall plug to injector efficiency - self.data.current_drive.eta_hcd_secondary_injector_wall_plug = ( - self.data.current_drive.eta_lowhyb_injector_wall_plug - ) + match secondary_cdm.method: + case CurrentDriveMethodType.LOWER_HYBRID: + # Injected power + p_hcd_secondary_electrons_mw = ( + self.data.current_drive.p_hcd_secondary_injected_mw + + self.data.current_drive.p_hcd_secondary_extra_heat_mw + ) - self.data.current_drive.p_hcd_lowhyb_injected_total_mw += ( - self.data.current_drive.p_hcd_secondary_injected_mw - + self.data.current_drive.p_hcd_secondary_extra_heat_mw - ) + # Wall plug power + self.data.heat_transport.p_hcd_secondary_electric_mw = ( + self.data.current_drive.p_hcd_secondary_injected_mw + + self.data.current_drive.p_hcd_secondary_extra_heat_mw + ) / self.data.current_drive.eta_lowhyb_injector_wall_plug - # ========================================================== + # Wall plug to injector efficiency + self.data.current_drive.eta_hcd_secondary_injector_wall_plug = ( + self.data.current_drive.eta_lowhyb_injector_wall_plug + ) - # Ion cyclotron cases - if secondary_cdm.method == CurrentDriveMethodType.ION_CYCLOTRON: - # Injected power - p_hcd_secondary_ions_mw = ( - self.data.current_drive.p_hcd_secondary_injected_mw - + self.data.current_drive.p_hcd_secondary_extra_heat_mw - ) + self.data.current_drive.p_hcd_lowhyb_injected_total_mw += ( + self.data.current_drive.p_hcd_secondary_injected_mw + + self.data.current_drive.p_hcd_secondary_extra_heat_mw + ) - # Wall plug power - self.data.heat_transport.p_hcd_secondary_electric_mw = ( - self.data.current_drive.p_hcd_secondary_injected_mw - + self.data.current_drive.p_hcd_secondary_extra_heat_mw - ) / self.data.current_drive.eta_icrh_injector_wall_plug + # ========================================================== - # Wall plug to injector efficiency - self.data.current_drive.eta_hcd_secondary_injector_wall_plug = ( - self.data.current_drive.eta_icrh_injector_wall_plug - ) + # Ion cyclotron cases + case CurrentDriveMethodType.ION_CYCLOTRON: + # Injected power + p_hcd_secondary_ions_mw = ( + self.data.current_drive.p_hcd_secondary_injected_mw + + self.data.current_drive.p_hcd_secondary_extra_heat_mw + ) - self.data.current_drive.p_hcd_icrh_injected_total_mw += ( - self.data.current_drive.p_hcd_secondary_injected_mw - + self.data.current_drive.p_hcd_secondary_extra_heat_mw - ) + # Wall plug power + self.data.heat_transport.p_hcd_secondary_electric_mw = ( + self.data.current_drive.p_hcd_secondary_injected_mw + + self.data.current_drive.p_hcd_secondary_extra_heat_mw + ) / self.data.current_drive.eta_icrh_injector_wall_plug - # ========================================================== + # Wall plug to injector efficiency + self.data.current_drive.eta_hcd_secondary_injector_wall_plug = ( + self.data.current_drive.eta_icrh_injector_wall_plug + ) - # Electron cyclotron cases - if secondary_cdm.method == CurrentDriveMethodType.ELECTRON_CYCLOTRON: - # Injected power - p_hcd_secondary_electrons_mw = ( - self.data.current_drive.p_hcd_secondary_injected_mw - + self.data.current_drive.p_hcd_secondary_extra_heat_mw - ) + self.data.current_drive.p_hcd_icrh_injected_total_mw += ( + self.data.current_drive.p_hcd_secondary_injected_mw + + self.data.current_drive.p_hcd_secondary_extra_heat_mw + ) - # Wall plug power - self.data.heat_transport.p_hcd_secondary_electric_mw = ( - self.data.current_drive.p_hcd_secondary_injected_mw - + self.data.current_drive.p_hcd_secondary_extra_heat_mw - ) / self.data.current_drive.eta_ecrh_injector_wall_plug + # ========================================================== - # Wall plug to injector efficiency - self.data.current_drive.eta_hcd_secondary_injector_wall_plug = ( - self.data.current_drive.eta_ecrh_injector_wall_plug - ) + # Electron cyclotron cases + case CurrentDriveMethodType.ELECTRON_CYCLOTRON: + # Injected power + p_hcd_secondary_electrons_mw = ( + self.data.current_drive.p_hcd_secondary_injected_mw + + self.data.current_drive.p_hcd_secondary_extra_heat_mw + ) - self.data.current_drive.p_hcd_ecrh_injected_total_mw += ( - self.data.current_drive.p_hcd_secondary_injected_mw - + self.data.current_drive.p_hcd_secondary_extra_heat_mw - ) + # Wall plug power + self.data.heat_transport.p_hcd_secondary_electric_mw = ( + self.data.current_drive.p_hcd_secondary_injected_mw + + self.data.current_drive.p_hcd_secondary_extra_heat_mw + ) / self.data.current_drive.eta_ecrh_injector_wall_plug - # ========================================================== + # Wall plug to injector efficiency + self.data.current_drive.eta_hcd_secondary_injector_wall_plug = ( + self.data.current_drive.eta_ecrh_injector_wall_plug + ) - # Electron berstein cases - if secondary_cdm.method == CurrentDriveMethodType.ELECTRON_BERNSTEIN: - # Injected power - p_hcd_secondary_electrons_mw = ( - self.data.current_drive.p_hcd_secondary_injected_mw - + self.data.current_drive.p_hcd_secondary_extra_heat_mw - ) + self.data.current_drive.p_hcd_ecrh_injected_total_mw += ( + self.data.current_drive.p_hcd_secondary_injected_mw + + self.data.current_drive.p_hcd_secondary_extra_heat_mw + ) - # Wall plug power - self.data.heat_transport.p_hcd_secondary_electric_mw = ( - self.data.current_drive.p_hcd_secondary_injected_mw - + self.data.current_drive.p_hcd_secondary_extra_heat_mw - ) / self.data.current_drive.eta_ebw_injector_wall_plug + # ========================================================== - # Wall plug to injector efficiency - self.data.current_drive.eta_hcd_secondary_injector_wall_plug = ( - self.data.current_drive.eta_ebw_injector_wall_plug - ) + # Electron berstein cases + case CurrentDriveMethodType.ELECTRON_BERNSTEIN: + # Injected power + p_hcd_secondary_electrons_mw = ( + self.data.current_drive.p_hcd_secondary_injected_mw + + self.data.current_drive.p_hcd_secondary_extra_heat_mw + ) - self.data.current_drive.p_hcd_ebw_injected_total_mw += ( - self.data.current_drive.p_hcd_secondary_injected_mw - + self.data.current_drive.p_hcd_secondary_extra_heat_mw - ) + # Wall plug power + self.data.heat_transport.p_hcd_secondary_electric_mw = ( + self.data.current_drive.p_hcd_secondary_injected_mw + + self.data.current_drive.p_hcd_secondary_extra_heat_mw + ) / self.data.current_drive.eta_ebw_injector_wall_plug - # ========================================================== + # Wall plug to injector efficiency + self.data.current_drive.eta_hcd_secondary_injector_wall_plug = ( + self.data.current_drive.eta_ebw_injector_wall_plug + ) - # Neutral beam cases - elif secondary_cdm.method == CurrentDriveMethodType.NEUTRAL_BEAM: - # Account for first orbit losses - # (power due to particles that are ionised but not thermalised) [MW]: - # This includes a second order term in shinethrough*(first orbit loss) - - self.data.current_drive.f_p_beam_orbit_loss = min( - 0.999, self.data.current_drive.f_p_beam_orbit_loss - ) # Should never be needed - - # Shinethrough power (atoms that are not ionised) [MW]: - self.data.current_drive.p_beam_shine_through_mw = ( - self.data.current_drive.p_hcd_secondary_injected_mw - + self.data.current_drive.p_hcd_secondary_extra_heat_mw - ) * (self.data.current_drive.f_p_beam_shine_through) - - # First orbit loss - self.data.current_drive.p_beam_orbit_loss_mw = ( - self.data.current_drive.f_p_beam_orbit_loss - * ( + self.data.current_drive.p_hcd_ebw_injected_total_mw += ( self.data.current_drive.p_hcd_secondary_injected_mw + self.data.current_drive.p_hcd_secondary_extra_heat_mw - - self.data.current_drive.p_beam_shine_through_mw ) - ) - # Power deposited - self.data.current_drive.p_beam_plasma_coupled_mw = ( - self.data.current_drive.p_hcd_secondary_injected_mw - + self.data.current_drive.p_hcd_secondary_extra_heat_mw - - self.data.current_drive.p_beam_shine_through_mw - - self.data.current_drive.p_beam_orbit_loss_mw - ) + # ========================================================== - p_hcd_secondary_ions_mw = ( - self.data.current_drive.p_beam_plasma_coupled_mw - * self.data.current_drive.f_p_beam_injected_ions - ) + # Neutral beam cases + case CurrentDriveMethodType.NEUTRAL_BEAM: + # Account for first orbit losses + # (power due to particles that are ionised but not thermalised) [MW]: + # This includes a second order term in + # shinethrough*(first orbit loss) - p_hcd_secondary_electrons_mw = ( - self.data.current_drive.p_beam_plasma_coupled_mw - * (1.0e0 - self.data.current_drive.f_p_beam_injected_ions) - ) + self.data.current_drive.f_p_beam_orbit_loss = min( + 0.999, self.data.current_drive.f_p_beam_orbit_loss + ) # Should never be needed - self.data.current_drive.pwpnb = ( - ( + # Shinethrough power (atoms that are not ionised) [MW]: + self.data.current_drive.p_beam_shine_through_mw = ( self.data.current_drive.p_hcd_secondary_injected_mw + self.data.current_drive.p_hcd_secondary_extra_heat_mw + ) * (self.data.current_drive.f_p_beam_shine_through) + + # First orbit loss + self.data.current_drive.p_beam_orbit_loss_mw = ( + self.data.current_drive.f_p_beam_orbit_loss + * ( + self.data.current_drive.p_hcd_secondary_injected_mw + + self.data.current_drive.p_hcd_secondary_extra_heat_mw + - self.data.current_drive.p_beam_shine_through_mw + ) ) - / self.data.current_drive.eta_beam_injector_wall_plug - ) # neutral beam wall plug power - self.data.heat_transport.p_hcd_secondary_electric_mw = ( - self.data.current_drive.pwpnb - ) + # Power deposited + self.data.current_drive.p_beam_plasma_coupled_mw = ( + self.data.current_drive.p_hcd_secondary_injected_mw + + self.data.current_drive.p_hcd_secondary_extra_heat_mw + - self.data.current_drive.p_beam_shine_through_mw + - self.data.current_drive.p_beam_orbit_loss_mw + ) - self.data.current_drive.eta_hcd_secondary_injector_wall_plug = ( - self.data.current_drive.eta_beam_injector_wall_plug - ) + p_hcd_secondary_ions_mw = ( + self.data.current_drive.p_beam_plasma_coupled_mw + * self.data.current_drive.f_p_beam_injected_ions + ) + + p_hcd_secondary_electrons_mw = ( + self.data.current_drive.p_beam_plasma_coupled_mw + * (1.0e0 - self.data.current_drive.f_p_beam_injected_ions) + ) - self.data.current_drive.c_beam_total = ( - 1.0e-3 - * ( + self.data.current_drive.pwpnb = ( ( self.data.current_drive.p_hcd_secondary_injected_mw + self.data.current_drive.p_hcd_secondary_extra_heat_mw ) - * 1.0e6 + / self.data.current_drive.eta_beam_injector_wall_plug + ) # neutral beam wall plug power + + self.data.heat_transport.p_hcd_secondary_electric_mw = ( + self.data.current_drive.pwpnb ) - / self.data.current_drive.e_beam_kev - ) # Neutral beam current (A) - self.data.current_drive.p_hcd_beam_injected_total_mw += ( - self.data.current_drive.p_hcd_secondary_injected_mw - + self.data.current_drive.p_hcd_secondary_extra_heat_mw - ) + self.data.current_drive.eta_hcd_secondary_injector_wall_plug = ( + self.data.current_drive.eta_beam_injector_wall_plug + ) + + self.data.current_drive.c_beam_total = ( + 1.0e-3 + * ( + ( + self.data.current_drive.p_hcd_secondary_injected_mw + + self.data.current_drive.p_hcd_secondary_extra_heat_mw + ) + * 1.0e6 + ) + / self.data.current_drive.e_beam_kev + ) # Neutral beam current (A) + + self.data.current_drive.p_hcd_beam_injected_total_mw += ( + self.data.current_drive.p_hcd_secondary_injected_mw + + self.data.current_drive.p_hcd_secondary_extra_heat_mw + ) # ========================================================== # Lower hybrid cases - if primary_cdm.method == CurrentDriveMethodType.LOWER_HYBRID: - p_hcd_primary_electrons_mw = ( - self.data.current_drive.p_hcd_primary_injected_mw - + self.data.current_drive.p_hcd_primary_extra_heat_mw - ) + match primary_cdm.method: + case CurrentDriveMethodType.LOWER_HYBRID: + p_hcd_primary_electrons_mw = ( + self.data.current_drive.p_hcd_primary_injected_mw + + self.data.current_drive.p_hcd_primary_extra_heat_mw + ) - self.data.current_drive.p_hcd_lowhyb_injected_total_mw += ( - self.data.current_drive.p_hcd_primary_injected_mw - + self.data.current_drive.p_hcd_primary_extra_heat_mw - ) + self.data.current_drive.p_hcd_lowhyb_injected_total_mw += ( + self.data.current_drive.p_hcd_primary_injected_mw + + self.data.current_drive.p_hcd_primary_extra_heat_mw + ) - # Wall plug power - self.data.heat_transport.p_hcd_primary_electric_mw = ( - self.data.current_drive.p_hcd_primary_injected_mw - + self.data.current_drive.p_hcd_primary_extra_heat_mw - ) / self.data.current_drive.eta_lowhyb_injector_wall_plug + # Wall plug power + self.data.heat_transport.p_hcd_primary_electric_mw = ( + self.data.current_drive.p_hcd_primary_injected_mw + + self.data.current_drive.p_hcd_primary_extra_heat_mw + ) / self.data.current_drive.eta_lowhyb_injector_wall_plug - # Wall plug to injector efficiency - self.data.current_drive.eta_hcd_primary_injector_wall_plug = ( - self.data.current_drive.eta_lowhyb_injector_wall_plug - ) + # Wall plug to injector efficiency + self.data.current_drive.eta_hcd_primary_injector_wall_plug = ( + self.data.current_drive.eta_lowhyb_injector_wall_plug + ) - # Wall plug power - self.data.current_drive.p_hcd_lowhyb_electric_mw = ( - self.data.current_drive.p_hcd_lowhyb_injected_total_mw - / self.data.current_drive.eta_lowhyb_injector_wall_plug - ) + # Wall plug power + self.data.current_drive.p_hcd_lowhyb_electric_mw = ( + self.data.current_drive.p_hcd_lowhyb_injected_total_mw + / self.data.current_drive.eta_lowhyb_injector_wall_plug + ) - # =========================================================== + # =========================================================== - # Ion cyclotron cases - if primary_cdm.method == CurrentDriveMethodType.ION_CYCLOTRON: - p_hcd_primary_ions_mw = ( - self.data.current_drive.p_hcd_primary_injected_mw - + self.data.current_drive.p_hcd_primary_extra_heat_mw - ) + # Ion cyclotron cases + case CurrentDriveMethodType.ION_CYCLOTRON: + p_hcd_primary_ions_mw = ( + self.data.current_drive.p_hcd_primary_injected_mw + + self.data.current_drive.p_hcd_primary_extra_heat_mw + ) - # Wall plug power - self.data.heat_transport.p_hcd_primary_electric_mw = ( - self.data.current_drive.p_hcd_primary_injected_mw - + self.data.current_drive.p_hcd_primary_extra_heat_mw - ) / self.data.current_drive.eta_icrh_injector_wall_plug + # Wall plug power + self.data.heat_transport.p_hcd_primary_electric_mw = ( + self.data.current_drive.p_hcd_primary_injected_mw + + self.data.current_drive.p_hcd_primary_extra_heat_mw + ) / self.data.current_drive.eta_icrh_injector_wall_plug - # Wall plug to injector efficiency - self.data.current_drive.eta_hcd_primary_injector_wall_plug = ( - self.data.current_drive.eta_icrh_injector_wall_plug - ) + # Wall plug to injector efficiency + self.data.current_drive.eta_hcd_primary_injector_wall_plug = ( + self.data.current_drive.eta_icrh_injector_wall_plug + ) - self.data.current_drive.p_hcd_icrh_injected_total_mw += ( - self.data.current_drive.p_hcd_primary_injected_mw - + self.data.current_drive.p_hcd_primary_extra_heat_mw - ) + self.data.current_drive.p_hcd_icrh_injected_total_mw += ( + self.data.current_drive.p_hcd_primary_injected_mw + + self.data.current_drive.p_hcd_primary_extra_heat_mw + ) - # Wall plug power - self.data.current_drive.p_hcd_icrh_electric_mw = ( - self.data.current_drive.p_hcd_icrh_injected_total_mw - / self.data.current_drive.eta_icrh_injector_wall_plug - ) + # Wall plug power + self.data.current_drive.p_hcd_icrh_electric_mw = ( + self.data.current_drive.p_hcd_icrh_injected_total_mw + / self.data.current_drive.eta_icrh_injector_wall_plug + ) - # =========================================================== + # =========================================================== - # Electron cyclotron cases + # Electron cyclotron cases + case CurrentDriveMethodType.ELECTRON_CYCLOTRON: + p_hcd_primary_electrons_mw = ( + self.data.current_drive.p_hcd_primary_injected_mw + + self.data.current_drive.p_hcd_primary_extra_heat_mw + ) - if primary_cdm.method == CurrentDriveMethodType.ELECTRON_CYCLOTRON: - p_hcd_primary_electrons_mw = ( - self.data.current_drive.p_hcd_primary_injected_mw - + self.data.current_drive.p_hcd_primary_extra_heat_mw - ) + # Wall plug to injector efficiency + self.data.heat_transport.p_hcd_primary_electric_mw = ( + self.data.current_drive.p_hcd_primary_injected_mw + + self.data.current_drive.p_hcd_primary_extra_heat_mw + ) / self.data.current_drive.eta_ecrh_injector_wall_plug - # Wall plug to injector efficiency - self.data.heat_transport.p_hcd_primary_electric_mw = ( - self.data.current_drive.p_hcd_primary_injected_mw - + self.data.current_drive.p_hcd_primary_extra_heat_mw - ) / self.data.current_drive.eta_ecrh_injector_wall_plug + self.data.current_drive.eta_hcd_primary_injector_wall_plug = ( + self.data.current_drive.eta_ecrh_injector_wall_plug + ) - self.data.current_drive.eta_hcd_primary_injector_wall_plug = ( - self.data.current_drive.eta_ecrh_injector_wall_plug - ) + self.data.current_drive.p_hcd_ecrh_injected_total_mw += ( + self.data.current_drive.p_hcd_primary_injected_mw + + self.data.current_drive.p_hcd_primary_extra_heat_mw + ) - self.data.current_drive.p_hcd_ecrh_injected_total_mw += ( - self.data.current_drive.p_hcd_primary_injected_mw - + self.data.current_drive.p_hcd_primary_extra_heat_mw - ) + # Wall plug power + self.data.current_drive.p_hcd_ecrh_electric_mw = ( + self.data.current_drive.p_hcd_ecrh_injected_total_mw + / self.data.current_drive.eta_ecrh_injector_wall_plug + ) - # Wall plug power - self.data.current_drive.p_hcd_ecrh_electric_mw = ( - self.data.current_drive.p_hcd_ecrh_injected_total_mw - / self.data.current_drive.eta_ecrh_injector_wall_plug - ) + # =========================================================== - # =========================================================== + # Electron bernstein cases + case CurrentDriveMethodType.ELECTRON_BERNSTEIN: + p_hcd_primary_electrons_mw = ( + self.data.current_drive.p_ebw_injected_mw + + self.data.current_drive.p_hcd_primary_extra_heat_mw + ) - # Electron bernstein cases + # Wall plug to injector efficiency + self.data.heat_transport.p_hcd_primary_electric_mw = ( + self.data.current_drive.p_hcd_primary_injected_mw + + self.data.current_drive.p_hcd_primary_extra_heat_mw + ) / self.data.current_drive.eta_ebw_injector_wall_plug - if primary_cdm.method == CurrentDriveMethodType.ELECTRON_BERNSTEIN: - p_hcd_primary_electrons_mw = ( - self.data.current_drive.p_ebw_injected_mw - + self.data.current_drive.p_hcd_primary_extra_heat_mw - ) + self.data.current_drive.eta_hcd_primary_injector_wall_plug = ( + self.data.current_drive.eta_ebw_injector_wall_plug + ) - # Wall plug to injector efficiency - self.data.heat_transport.p_hcd_primary_electric_mw = ( - self.data.current_drive.p_hcd_primary_injected_mw - + self.data.current_drive.p_hcd_primary_extra_heat_mw - ) / self.data.current_drive.eta_ebw_injector_wall_plug + self.data.current_drive.p_hcd_ebw_injected_total_mw += ( + self.data.current_drive.p_hcd_primary_injected_mw + + self.data.current_drive.p_hcd_primary_extra_heat_mw + ) - self.data.current_drive.eta_hcd_primary_injector_wall_plug = ( - self.data.current_drive.eta_ebw_injector_wall_plug - ) + # Wall plug power + self.data.current_drive.p_hcd_ebw_electric_mw = ( + self.data.current_drive.p_ebw_injected_mw + / self.data.current_drive.eta_ebw_injector_wall_plug + ) - self.data.current_drive.p_hcd_ebw_injected_total_mw += ( - self.data.current_drive.p_hcd_primary_injected_mw - + self.data.current_drive.p_hcd_primary_extra_heat_mw - ) + # =========================================================== - # Wall plug power - self.data.current_drive.p_hcd_ebw_electric_mw = ( - self.data.current_drive.p_ebw_injected_mw - / self.data.current_drive.eta_ebw_injector_wall_plug - ) + case CurrentDriveMethodType.NEUTRAL_BEAM: + # Account for first orbit losses + # (power due to particles that are ionised but + # not thermalised) [MW]: + # This includes a second order term in + # shinethrough*(first orbit loss) + self.data.current_drive.f_p_beam_orbit_loss = min( + 0.999, self.data.current_drive.f_p_beam_orbit_loss + ) # Should never be needed - # =========================================================== + # Shinethrough power (atoms that are not ionised) [MW]: + self.data.current_drive.p_beam_shine_through_mw = ( + self.data.current_drive.p_hcd_primary_injected_mw + + self.data.current_drive.p_hcd_primary_extra_heat_mw + ) * (self.data.current_drive.f_p_beam_shine_through) - elif primary_cdm.method == CurrentDriveMethodType.NEUTRAL_BEAM: - # Account for first orbit losses - # (power due to particles that are ionised but not thermalised) [MW]: - # This includes a second order term in shinethrough*(first orbit loss) - self.data.current_drive.f_p_beam_orbit_loss = min( - 0.999, self.data.current_drive.f_p_beam_orbit_loss - ) # Should never be needed - - # Shinethrough power (atoms that are not ionised) [MW]: - self.data.current_drive.p_beam_shine_through_mw = ( - self.data.current_drive.p_hcd_primary_injected_mw - + self.data.current_drive.p_hcd_primary_extra_heat_mw - ) * (self.data.current_drive.f_p_beam_shine_through) - - # First orbit loss - self.data.current_drive.p_beam_orbit_loss_mw = ( - self.data.current_drive.f_p_beam_orbit_loss - * ( + # First orbit loss + self.data.current_drive.p_beam_orbit_loss_mw = ( + self.data.current_drive.f_p_beam_orbit_loss + * ( + self.data.current_drive.p_hcd_primary_injected_mw + + self.data.current_drive.p_hcd_primary_extra_heat_mw + - self.data.current_drive.p_beam_shine_through_mw + ) + ) + + # Power deposited + self.data.current_drive.p_beam_plasma_coupled_mw = ( self.data.current_drive.p_hcd_primary_injected_mw + self.data.current_drive.p_hcd_primary_extra_heat_mw - self.data.current_drive.p_beam_shine_through_mw + - self.data.current_drive.p_beam_orbit_loss_mw ) - ) - - # Power deposited - self.data.current_drive.p_beam_plasma_coupled_mw = ( - self.data.current_drive.p_hcd_primary_injected_mw - + self.data.current_drive.p_hcd_primary_extra_heat_mw - - self.data.current_drive.p_beam_shine_through_mw - - self.data.current_drive.p_beam_orbit_loss_mw - ) - p_hcd_primary_ions_mw = ( - self.data.current_drive.p_beam_plasma_coupled_mw - * self.data.current_drive.f_p_beam_injected_ions - ) - p_hcd_primary_electrons_mw = ( - self.data.current_drive.p_beam_plasma_coupled_mw - * (1.0e0 - self.data.current_drive.f_p_beam_injected_ions) - ) + p_hcd_primary_ions_mw = ( + self.data.current_drive.p_beam_plasma_coupled_mw + * self.data.current_drive.f_p_beam_injected_ions + ) + p_hcd_primary_electrons_mw = ( + self.data.current_drive.p_beam_plasma_coupled_mw + * (1.0e0 - self.data.current_drive.f_p_beam_injected_ions) + ) - self.data.current_drive.pwpnb = ( - self.data.current_drive.p_hcd_primary_injected_mw - + self.data.current_drive.p_hcd_primary_extra_heat_mw - ) / self.data.current_drive.eta_beam_injector_wall_plug + self.data.current_drive.pwpnb = ( + self.data.current_drive.p_hcd_primary_injected_mw + + self.data.current_drive.p_hcd_primary_extra_heat_mw + ) / self.data.current_drive.eta_beam_injector_wall_plug - # Neutral beam wall plug power - self.data.heat_transport.p_hcd_primary_electric_mw = ( - self.data.current_drive.pwpnb - ) - self.data.current_drive.eta_hcd_primary_injector_wall_plug = ( - self.data.current_drive.eta_beam_injector_wall_plug - ) + # Neutral beam wall plug power + self.data.heat_transport.p_hcd_primary_electric_mw = ( + self.data.current_drive.pwpnb + ) + self.data.current_drive.eta_hcd_primary_injector_wall_plug = ( + self.data.current_drive.eta_beam_injector_wall_plug + ) - self.data.current_drive.c_beam_total = ( - 1.0e-3 - * ( - ( - self.data.current_drive.p_hcd_primary_injected_mw - + self.data.current_drive.p_hcd_primary_extra_heat_mw + self.data.current_drive.c_beam_total = ( + 1.0e-3 + * ( + ( + self.data.current_drive.p_hcd_primary_injected_mw + + self.data.current_drive.p_hcd_primary_extra_heat_mw + ) + * 1.0e6 ) - * 1.0e6 - ) - / self.data.current_drive.e_beam_kev - ) # Neutral beam current (A) + / self.data.current_drive.e_beam_kev + ) # Neutral beam current (A) - self.data.current_drive.p_hcd_beam_injected_total_mw += ( - self.data.current_drive.p_hcd_primary_injected_mw - + self.data.current_drive.p_hcd_primary_extra_heat_mw - ) + self.data.current_drive.p_hcd_beam_injected_total_mw += ( + self.data.current_drive.p_hcd_primary_injected_mw + + self.data.current_drive.p_hcd_primary_extra_heat_mw + ) # =========================================================== @@ -2466,13 +2518,10 @@ def output(self): self.data.current_drive.eta_cd_dimensionless_hcd_primary, "OP ", ) - po.ovarre( - self.mfile, - "EBW coupling efficiency", - "(xi_ebw)", - self.data.current_drive.xi_ebw, - ) - if self.data.current_drive.i_hcd_primary == 10: + if ( + CurrentDriveModel(self.data.current_drive.i_hcd_primary) + == CurrentDriveModel.USER_INPUT_ELECTRON_CYCLOTRON + ): po.ovarre( self.outfile, "ECRH plasma heating efficiency", @@ -2481,21 +2530,21 @@ def output(self): ) po.ovarre( self.outfile, - "Power injected into plasma by primary system for current drive (MW)", + "Power injected into plasma by primary system for current drive [MW]", "(p_hcd_primary_injected_mw)", self.data.current_drive.p_hcd_primary_injected_mw, "OP ", ) po.ovarre( self.outfile, - "Extra power injected into plasma by primary system (MW)", + "Extra power injected into plasma by primary system [MW]", "(p_hcd_primary_extra_heat_mw)", self.data.current_drive.p_hcd_primary_extra_heat_mw, "OP ", ) po.ovarre( self.outfile, - "Current driven in plasma by primary system (A)", + "Current driven in plasma by primary system [A]", "(c_hcd_primary_driven)", self.data.current_drive.c_hcd_primary_driven, "OP ", @@ -2522,7 +2571,10 @@ def output(self): "OP ", ) - if self.data.current_drive.i_hcd_primary in {12, 13}: + if CurrentDriveModel(self.data.current_drive.i_hcd_primary) in { + CurrentDriveModel.USER_INPUT_ELECTRON_BERNSTEIN, + CurrentDriveModel.FREETHY_ELECTRON_CYCLOTRON, + }: po.oblnkl(self.outfile) po.ovarre( self.outfile, @@ -2536,7 +2588,10 @@ def output(self): "(xi_ebw)", self.data.current_drive.xi_ebw, ) - if self.data.current_drive.i_hcd_primary == 13: + if ( + CurrentDriveModel(self.data.current_drive.i_hcd_primary) + == CurrentDriveModel.FREETHY_ELECTRON_CYCLOTRON + ): po.ovarre( self.outfile, "Electron cyclotron cutoff wave mode switch", @@ -2556,7 +2611,7 @@ def output(self): po.ovarre( self.outfile, - "Neutral beam energy (keV)", + "Neutral beam energy [keV]", "(e_beam_kev)", self.data.current_drive.e_beam_kev, ) @@ -2566,7 +2621,7 @@ def output(self): ): po.ovarre( self.outfile, - "Neutral beam current (A)", + "Neutral beam current [A]", "(c_beam_total)", self.data.current_drive.c_beam_total, "OP ", @@ -2580,7 +2635,7 @@ def output(self): ) po.ovarre( self.outfile, - "Beam decay lengths to centre", + "Beam decay lengths to centre [m]", "(n_beam_decay_lengths_core)", self.data.current_drive.n_beam_decay_lengths_core, "OP ", @@ -2599,7 +2654,7 @@ def output(self): ): po.ovarre( self.outfile, - "Beam first orbit loss power (MW)", + "Beam first orbit loss power [MW]", "(p_beam_orbit_loss_mw)", self.data.current_drive.p_beam_orbit_loss_mw, "OP ", @@ -2613,14 +2668,14 @@ def output(self): ) po.ovarre( self.outfile, - "Maximum allowable beam power (MW)", + "Maximum allowable beam power [MW]", "(p_hcd_injected_max)", self.data.current_drive.p_hcd_injected_max, ) po.oblnkl(self.outfile) po.ovarre( self.outfile, - "Beam power entering vacuum vessel (MW)", + "Beam power entering vacuum vessel [MW]", "(p_beam_injected_mw)", self.data.current_drive.p_beam_injected_mw, "OP ", @@ -2634,7 +2689,7 @@ def output(self): ) po.ovarre( self.outfile, - "Beam duct shielding thickness (m)", + "Beam duct shielding thickness [m]", "(dx_beam_shield)", self.data.current_drive.dx_beam_shield, ) @@ -2646,14 +2701,14 @@ def output(self): ) po.ovarre( self.outfile, - "Beam centreline tangency radius (m)", + "Beam centreline tangency radius [m]", "(radius_beam_tangency)", self.data.current_drive.radius_beam_tangency, "OP ", ) po.ovarre( self.outfile, - "Maximum possible tangency radius (m)", + "Maximum possible tangency radius [m]", "(radius_beam_tangency_max)", self.data.current_drive.radius_beam_tangency_max, "OP ", @@ -2695,7 +2750,10 @@ def output(self): self.data.current_drive.eta_cd_dimensionless_hcd_secondary, "OP ", ) - if self.data.current_drive.i_hcd_secondary == 10: + if ( + CurrentDriveModel(self.data.current_drive.i_hcd_secondary) + == CurrentDriveModel.USER_INPUT_ELECTRON_CYCLOTRON + ): po.ovarre( self.outfile, "ECRH plasma heating efficiency", @@ -2705,21 +2763,21 @@ def output(self): po.ovarre( self.outfile, - "Power injected into plasma by secondary system (MW)", + "Power injected into plasma by secondary system [MW]", "(p_hcd_secondary_injected_mw)", self.data.current_drive.p_hcd_secondary_injected_mw, "OP ", ) po.ovarre( self.outfile, - "Extra power injected into plasma by secondary system (MW)", + "Extra power injected into plasma by secondary system [MW]", "(p_hcd_secondary_extra_heat_mw)", self.data.current_drive.p_hcd_secondary_extra_heat_mw, "OP ", ) po.ovarre( self.outfile, - "Current driven in plasma by secondary system (A)", + "Current driven in plasma by secondary system [A]", "(c_hcd_secondary_driven)", self.data.current_drive.c_hcd_secondary_driven, "OP ", @@ -2740,7 +2798,7 @@ def output(self): ) po.ovarre( self.outfile, - "Wall plug electric power of secondary system", + "Wall plug electric power of secondary system [MW]", "(p_hcd_secondary_electric_mw)", self.data.heat_transport.p_hcd_secondary_electric_mw, "OP ", @@ -2758,7 +2816,7 @@ def output(self): po.ovarre( self.outfile, - "Neutral beam energy (keV)", + "Neutral beam energy [keV]", "(e_beam_kev)", self.data.current_drive.e_beam_kev, ) @@ -2768,7 +2826,7 @@ def output(self): ): po.ovarre( self.outfile, - "Neutral beam current (A)", + "Neutral beam current [A]", "(c_beam_total)", self.data.current_drive.c_beam_total, "OP ", @@ -2801,7 +2859,7 @@ def output(self): ): po.ovarre( self.outfile, - "Beam first orbit loss power (MW)", + "Beam first orbit loss power [MW]", "(p_beam_orbit_loss_mw)", self.data.current_drive.p_beam_orbit_loss_mw, "OP ", @@ -2815,14 +2873,14 @@ def output(self): ) po.ovarre( self.outfile, - "Maximum allowable beam power (MW)", + "Maximum allowable beam power [MW]", "(p_hcd_injected_max)", self.data.current_drive.p_hcd_injected_max, ) po.oblnkl(self.outfile) po.ovarre( self.outfile, - "Beam power entering vacuum vessel (MW)", + "Beam power entering vacuum vessel [MW]", "(p_beam_injected_mw)", self.data.current_drive.p_beam_injected_mw, "OP ", @@ -2836,7 +2894,7 @@ def output(self): ) po.ovarre( self.outfile, - "Beam duct shielding thickness (m)", + "Beam duct shielding thickness [m]", "(dx_beam_shield)", self.data.current_drive.dx_beam_shield, ) @@ -2848,14 +2906,14 @@ def output(self): ) po.ovarre( self.outfile, - "Beam centreline tangency radius (m)", + "Beam centreline tangency radius [m]", "(radius_beam_tangency)", self.data.current_drive.radius_beam_tangency, "OP ", ) po.ovarre( self.outfile, - "Maximum possible tangency radius (m)", + "Maximum possible tangency radius [m]", "(radius_beam_tangency_max)", self.data.current_drive.radius_beam_tangency_max, "OP ", @@ -2867,27 +2925,27 @@ def output(self): po.ovarre( self.outfile, - "Total injected heating power that drove plasma current (MW)", + "Total injected heating power that drove plasma current [MW]", "(p_hcd_injected_current_total_mw)", self.data.current_drive.p_hcd_injected_current_total_mw, ) po.ovarre( self.outfile, - "Total injected heating power across all systems (MW)", + "Total injected heating power across all systems [MW]", "(p_hcd_injected_total_mw)", self.data.current_drive.p_hcd_injected_total_mw, "OP ", ) po.ovarre( self.outfile, - "Total injected heating power given to the electrons (MW)", + "Total injected heating power given to the electrons [MW]", "(p_hcd_injected_electrons_mw)", self.data.current_drive.p_hcd_injected_electrons_mw, "OP ", ) po.ovarre( self.outfile, - "Total injected heating power given to the ions (MW)", + "Total injected heating power given to the ions [MW]", "(p_hcd_injected_ions_mw)", self.data.current_drive.p_hcd_injected_ions_mw, "OP ", @@ -2895,7 +2953,7 @@ def output(self): po.ovarre( self.outfile, - "Upper limit on total plasma injected power (MW)", + "Upper limit on total plasma injected power [MW]", "(p_hcd_injected_max)", self.data.current_drive.p_hcd_injected_max, "OP ", @@ -2905,35 +2963,35 @@ def output(self): po.ovarre( self.outfile, - "Injected power into plasma from lower hybrid systems (MW)", + "Injected power into plasma from lower hybrid systems [MW]", "(p_hcd_lowhyb_injected_total_mw)", self.data.current_drive.p_hcd_lowhyb_injected_total_mw, "OP ", ) po.ovarre( self.outfile, - "Injected power into plasma from ion cyclotron systems (MW)", + "Injected power into plasma from ion cyclotron systems [MW]", "(p_hcd_icrh_injected_total_mw)", self.data.current_drive.p_hcd_icrh_injected_total_mw, "OP ", ) po.ovarre( self.outfile, - "Injected power into plasma from electron cyclotron systems (MW)", + "Injected power into plasma from electron cyclotron systems [MW]", "(p_hcd_ecrh_injected_total_mw)", self.data.current_drive.p_hcd_ecrh_injected_total_mw, "OP ", ) po.ovarre( self.outfile, - "Injected power into plasma from neutral beam systems (MW)", + "Injected power into plasma from neutral beam systems [MW]", "(p_hcd_beam_injected_total_mw)", self.data.current_drive.p_hcd_beam_injected_total_mw, "OP ", ) po.ovarre( self.outfile, - "Injected power into plasma from lower hybrid systems (MW)", + "Injected power into plasma from electron Bernstein wave systems [MW]", "(p_hcd_ebw_injected_total_mw)", self.data.current_drive.p_hcd_ebw_injected_total_mw, "OP ", diff --git a/process/models/physics/exhaust.py b/process/models/physics/exhaust.py index 377f58d3c7..3b3d23988c 100644 --- a/process/models/physics/exhaust.py +++ b/process/models/physics/exhaust.py @@ -52,7 +52,7 @@ def output(self): po.oheadr(self.outfile, "Plasma Exhaust") po.ovarre( self.outfile, - "Plasma separatrix power (Pₛₑₚ) (MW)", + "Plasma separatrix power (Pₛₑₚ) [MW]", "(p_plasma_separatrix_mw)", self.data.physics.p_plasma_separatrix_mw, "OP ", @@ -75,7 +75,10 @@ def output(self): ) po.oblnkl(self.outfile) - if self.data.divertor.n_divertors == 2: + if ( + DivertorNumberModels(self.data.physics.i_single_null) + == DivertorNumberModels.DOUBLE_NULL + ): # Double null divertor configuration po.ovarre( self.outfile, @@ -129,22 +132,22 @@ def calculate_separatrix_power( Parameters ---------- f_p_alpha_plasma_deposited : float - Fraction of alpha power deposited in plasma. + Fraction of alpha power deposited in plasma [-] p_alpha_total_mw : float - Total alpha power produced (MW). + Total alpha power produced [MW]. p_non_alpha_charged_mw : float - Power from non-alpha charged particles (MW). + Power from non-alpha charged particles [MW]. p_hcd_injected_total_mw : float - Total power injected by heating and current drive (MW). + Total power injected by heating and current drive [MW]. p_plasma_ohmic_mw : float - Ohmic heating power (MW). + Ohmic heating power [MW]. p_plasma_rad_mw : float - Radiated power from plasma (MW). + Radiated power from plasma [MW]. Returns ------- float - Power crossing the separatrix (MW). + Power crossing the separatrix [MW]. """ return ( f_p_alpha_plasma_deposited * p_alpha_total_mw @@ -230,9 +233,9 @@ def calculate_radiation_fraction( Parameters ---------- p_plasma_rad_mw : float - Radiated power from plasma (MW). + Radiated power from plasma [MW]. p_plasma_heating_mw : float - Total plasma heating power (MW). + Total plasma heating power [MW]. Returns ------- @@ -272,18 +275,18 @@ def output_brunner_divertor_power_splits(self): ), None, ( - "Outboard side heat flux decay length (m)", + "Outboard side heat flux decay length [m]", "(len_sol_outboard_power_decay)", self.data.physics.len_sol_outboard_power_decay, ), None, ( - "Fraction of separatrix power on the inner target(s)", + "Fraction of separatrix power on the inner target", "(f_p_div_inboard_separatrix)", self.data.physics.f_p_div_inboard_separatrix, ), ( - "Fraction of separatrix power on the outer target(s)", + "Fraction of separatrix power on the outer target", "(f_p_div_outboard_separatrix)", self.data.physics.f_p_div_outboard_separatrix, ), @@ -294,7 +297,7 @@ def output_brunner_divertor_power_splits(self): self.data.physics.f_p_div_lower_inboard_separatrix, ), ( - "Separatrix power on the inner lower target", + "Separatrix power on the inner lower target [MW]", "(p_div_lower_inboard_separatrix_mw)", self.data.physics.p_div_lower_inboard_separatrix_mw, ), @@ -305,7 +308,7 @@ def output_brunner_divertor_power_splits(self): self.data.physics.f_p_div_lower_outboard_separatrix, ), ( - "Separatrix power on the outer lower target", + "Separatrix power on the outer lower target [MW]", "(p_div_lower_outboard_separatrix_mw)", self.data.physics.p_div_lower_outboard_separatrix_mw, ), @@ -331,7 +334,7 @@ def output_brunner_divertor_power_splits(self): self.data.physics.f_p_div_upper_inboard_separatrix, ), ( - "Separatrix power on the inner upper target", + "Separatrix power on the inner upper target [MW]", "(p_div_upper_inboard_separatrix_mw)", self.data.physics.p_div_upper_inboard_separatrix_mw, ), @@ -342,7 +345,7 @@ def output_brunner_divertor_power_splits(self): self.data.physics.f_p_div_upper_outboard_separatrix, ), ( - "Separatrix power on the outer upper target", + "Separatrix power on the outer upper target [MW]", "(p_div_upper_outboard_separatrix_mw)", self.data.physics.p_div_upper_outboard_separatrix_mw, ), diff --git a/process/models/physics/impurity_radiation.py b/process/models/physics/impurity_radiation.py index f6b192fbc8..c1b93aa578 100644 --- a/process/models/physics/impurity_radiation.py +++ b/process/models/physics/impurity_radiation.py @@ -276,7 +276,7 @@ def init_imp_element( len_tab: int, error: int, data: DataStructure, -): +) -> None: """Initialise the impurity radiation data for a species. This routine initialises the impurity radiation data structure @@ -292,13 +292,15 @@ def init_imp_element( z : int Species charge number m_species_amu : float - Species atomic mass (amu) + Species atomic mass [amu] f_nd_species_electron : float Number density / electron density len_tab : int Length of temperature and Lz tables error : int Error flag; 0 = okay, 1 = missing impurity data + data : DataStructure + Data structure containing impurity radiation information Raises ------ @@ -418,14 +420,14 @@ def calculate_average_charge_at_temp( imp_element_index: Impurity element index temp_electron_kev: - electron temperature in keV + electron temperature [keV] data: DataStructure containing impurity radiation data Returns ------- numpy.array - zav_of_te - electron temperature dependent average atomic charge + zav_of_te - electron temperature dependent average atomic charge [-] """ return _calculate_average_charge_at_temp_compiled( imp_element_index=imp_element_index, @@ -450,23 +452,23 @@ def _calculate_average_charge_at_temp_compiled( Parameters ---------- imp_element_index: - Impurity element index + Impurity element index [-] temp_electron_kev: - electron temperature in keV + electron temperature [keV] temp_impurity_keV_array: - 2D array of impurity temperatures in keV for each impurity element + 2D array of impurity temperatures [keV] for each impurity element impurity_arr_zav: 2D array of average charge values for each impurity element at the corresponding - temperatures in temp_impurity_keV_array + temperatures in temp_impurity_keV_array [-] impurity_arr_len_tab: 1D array of the length of the temperature and average charge tables for each - impurity element + impurity element [-] Returns ------- n_charge_impurity_average: electron temperature dependent average atomic charge of impurity element at the - given temperature(s) + given temperature(s) [-] """ bins = temp_impurity_keV_array[imp_element_index] @@ -604,13 +606,19 @@ def calculate_impurity_radiation_power_density( return pden_impurity_profile -def element2index(element: str, data: DataStructure): +def element2index(element: str, data: DataStructure) -> int: """Returns the index of the `element` in the impurity array with a given name Parameters ---------- element: str : + Name of the impurity element [-] + + Returns + ------- + int + Index of the element in the impurity array [-] Raises ------ @@ -701,7 +709,7 @@ def imprad_profile(self, imp_element_index: int) -> None: self.pden_impurity_radiation_profile, pden_impurity_radiation_profile ) - def calculate_radiation_loss_profiles(self): + def calculate_radiation_loss_profiles(self) -> None: """Calculate the Bremsstrahlung (radb), line radiation (radl), total impurity radiation from the core (pden_impurity_core_rad_total_mw) and total impurity radiation (pden_impurity_rad_total_mw). Update the stored arrays with the @@ -727,7 +735,7 @@ def calculate_radiation_loss_profiles(self): self.pden_impurity_core_rad_profile, pden_impurity_core_rad_total ) - def integrate_radiation_loss_profiles(self): + def integrate_radiation_loss_profiles(self) -> None: """Integrate the radiation loss profiles using the Simpson rule. Store the total values for each aspect of impurity radiation loss. """ @@ -747,7 +755,7 @@ def integrate_radiation_loss_profiles(self): dx=self.plasma_profile.neprofile.profile_dx, ) - def calculate_imprad(self): + def calculate_imprad(self) -> None: """Call the map function to calculate impurity radiation parameters for each impurity element. Calculate the radiation loss profiles, and integrate them to find the total values for radiation loss. diff --git a/process/models/physics/l_h_transition.py b/process/models/physics/l_h_transition.py index dac95c23d3..2bf32d638b 100644 --- a/process/models/physics/l_h_transition.py +++ b/process/models/physics/l_h_transition.py @@ -1,62 +1,15 @@ """Module for plasma L-H and L-I transition power threshold calculations.""" import logging -from enum import IntEnum, unique from process.core import constants from process.core import process_output as po from process.core.model import Model +from process.data_structure.physics_variables import PlasmaConfinementTransitionModel logger = logging.getLogger(__name__) -@unique -class PlasmaConfinementTransitionModel(IntEnum): - """Enum for plasma L -> H and L -> I transition power threshold models.""" - - ITER1996_NOMINAL = (1, "ITER-1996 Nominal") - ITER1996_UPPER = (2, "ITER-1996 Upper") - ITER1996_LOWER = (3, "ITER-1996 Lower") - SNIPES1997_ITER = (4, "Snipes 1997 ITER Scaling I") - SNIPES1997_KAPPA = (5, "Snipes 1997 ITER Scaling II") - MARTIN08_NOMINAL = (6, "Martin 2008 Nominal") - MARTIN08_UPPER = (7, "Martin 2008 Upper") - MARTIN08_LOWER = (8, "Martin 2008 Lower") - SNIPES2000_NOMINAL = (9, "Snipes 2000 Nominal") - SNIPES2000_UPPER = (10, "Snipes 2000 Upper") - SNIPES2000_LOWER = (11, "Snipes 2000 Lower") - SNIPES2000_CLOSED_DIVERTOR_NOMINAL = (12, "Snipes 2000 Closed Divertor Nominal") - SNIPES2000_CLOSED_DIVERTOR_UPPER = (13, "Snipes 2000 Closed Divertor Upper") - SNIPES2000_CLOSED_DIVERTOR_LOWER = (14, "Snipes 2000 Closed Divertor Lower") - HUBBARD2012_NOMINAL = (15, "Hubbard 2012 Nominal") - HUBBARD2012_LOWER = (16, "Hubbard 2012 Lower") - HUBBARD2012_UPPER = (17, "Hubbard 2012 Upper") - HUBBARD2017_I_MODE = (18, "Hubbard 2017 I-Mode") - MARTIN08_ASPECT_NOMINAL = (19, "Martin 2008 Aspect Corrected Nominal") - MARTIN08_ASPECT_UPPER = (20, "Martin 2008 Aspect Corrected Upper") - MARTIN08_ASPECT_LOWER = (21, "Martin 2008 Aspect Corrected Lower") - - def __new__(cls, value: int, full_name: str): - """Create a new PlasmaConfinementTransitionModel instance. - - Parameters - ---------- - value : int - The integer value of the enum member. - full_name : str - The full descriptive name of the enum member. - - Returns - ------- - PlasmaConfinementTransitionModel - A new instance of PlasmaConfinementTransitionModel. - """ - obj = int.__new__(cls, value) - obj._value_ = value - obj.full_name = full_name - return obj - - class PlasmaConfinementTransition(Model): """Class to calculate plasma L -> H and L -> I transition power thresholds.""" @@ -72,15 +25,15 @@ def run(self) -> None: """ # Calculate L- to H-mode power threshold for different scalings self.data.physics.l_h_threshold_powers = self.l_h_threshold_power( - self.data.physics.nd_plasma_electron_line, - self.data.physics.b_plasma_toroidal_on_axis, - self.data.physics.rmajor, - self.data.physics.rminor, - self.data.physics.kappa, - self.data.physics.a_plasma_surface, - self.data.physics.m_ions_total_amu, - self.data.physics.aspect, - self.data.physics.plasma_current, + nd_plasma_electron_line=self.data.physics.nd_plasma_electron_line, + b_plasma_toroidal_on_axis=self.data.physics.b_plasma_toroidal_on_axis, + rmajor=self.data.physics.rmajor, + rminor=self.data.physics.rminor, + kappa=self.data.physics.kappa, + a_plasma_surface=self.data.physics.a_plasma_surface, + m_ions_total_amu=self.data.physics.m_ions_total_amu, + aspect=self.data.physics.aspect, + plasma_current=self.data.physics.plasma_current, ) # Enforced L-H power threshold value (if constraint 15 is turned on) @@ -105,23 +58,23 @@ def l_h_threshold_power( Parameters ---------- nd_plasma_electron_line : float - Line-averaged electron density (/m3) + Line-averaged electron density [m⁻³] b_plasma_toroidal_on_axis : float - Toroidal field on axis (T) + Toroidal field on axis [T] rmajor : float - Plasma major radius (m) + Plasma major radius [m] rminor : float - Plasma minor radius (m) + Plasma minor radius [m] kappa : float Plasma elongation a_plasma_surface : float - Plasma surface area (m2) + Plasma surface area [m²] m_ions_total_amu : float - Average mass of all ions (amu) + Average mass of all ions [amu] aspect : float Aspect ratio plasma_current : float - Plasma current (A) + Plasma current [A] Returns ------- @@ -138,19 +91,25 @@ def l_h_threshold_power( # i_l_h_threshold = 1 iterdd = self.calculate_iter1996_nominal( - dnla20, b_plasma_toroidal_on_axis, rmajor + dnla20=dnla20, + b_plasma_toroidal_on_axis=b_plasma_toroidal_on_axis, + rmajor=rmajor, ) # Fit to 1996 H-mode power threshold database: upper bound # i_l_h_threshold = 2 iterdd_ub = self.calculate_iter1996_upper( - dnla20, b_plasma_toroidal_on_axis, rmajor + dnla20=dnla20, + b_plasma_toroidal_on_axis=b_plasma_toroidal_on_axis, + rmajor=rmajor, ) # Fit to 1996 H-mode power threshold database: lower bound # i_l_h_threshold = 3 iterdd_lb = self.calculate_iter1996_lower( - dnla20, b_plasma_toroidal_on_axis, rmajor + dnla20=dnla20, + b_plasma_toroidal_on_axis=b_plasma_toroidal_on_axis, + rmajor=rmajor, ) # ======================================================================== @@ -159,12 +118,17 @@ def l_h_threshold_power( # i_l_h_threshold = 4 snipes_1997 = self.calculate_snipes1997_iter( - dnla20, b_plasma_toroidal_on_axis, rmajor + dnla20=dnla20, + b_plasma_toroidal_on_axis=b_plasma_toroidal_on_axis, + rmajor=rmajor, ) # i_l_h_threshold = 5 snipes_1997_kappa = self.calculate_snipes1997_kappa( - dnla20, b_plasma_toroidal_on_axis, rmajor, kappa + dnla20=dnla20, + b_plasma_toroidal_on_axis=b_plasma_toroidal_on_axis, + rmajor=rmajor, + kappa=kappa, ) # ======================================================================== @@ -174,17 +138,26 @@ def l_h_threshold_power( # i_l_h_threshold = 6 martin_nominal = self.calculate_martin08_nominal( - dnla20, b_plasma_toroidal_on_axis, a_plasma_surface, m_ions_total_amu + dnla20=dnla20, + b_plasma_toroidal_on_axis=b_plasma_toroidal_on_axis, + a_plasma_surface=a_plasma_surface, + m_ions_total_amu=m_ions_total_amu, ) # i_l_h_threshold = 7 martin_ub = self.calculate_martin08_upper( - dnla20, b_plasma_toroidal_on_axis, a_plasma_surface, m_ions_total_amu + dnla20=dnla20, + b_plasma_toroidal_on_axis=b_plasma_toroidal_on_axis, + a_plasma_surface=a_plasma_surface, + m_ions_total_amu=m_ions_total_amu, ) # i_l_h_threshold = 8 martin_lb = self.calculate_martin08_lower( - dnla20, b_plasma_toroidal_on_axis, a_plasma_surface, m_ions_total_amu + dnla20=dnla20, + b_plasma_toroidal_on_axis=b_plasma_toroidal_on_axis, + a_plasma_surface=a_plasma_surface, + m_ions_total_amu=m_ions_total_amu, ) # ======================================================================== @@ -194,17 +167,29 @@ def l_h_threshold_power( # i_l_h_threshold = 9 snipes_2000 = self.calculate_snipes2000_nominal( - dnla20, b_plasma_toroidal_on_axis, rmajor, rminor, m_ions_total_amu + dnla20=dnla20, + b_plasma_toroidal_on_axis=b_plasma_toroidal_on_axis, + rmajor=rmajor, + rminor=rminor, + m_ions_total_amu=m_ions_total_amu, ) # i_l_h_threshold = 10 snipes_2000_ub = self.calculate_snipes2000_upper( - dnla20, b_plasma_toroidal_on_axis, rmajor, rminor, m_ions_total_amu + dnla20=dnla20, + b_plasma_toroidal_on_axis=b_plasma_toroidal_on_axis, + rmajor=rmajor, + rminor=rminor, + m_ions_total_amu=m_ions_total_amu, ) # i_l_h_threshold = 11 snipes_2000_lb = self.calculate_snipes2000_lower( - dnla20, b_plasma_toroidal_on_axis, rmajor, rminor, m_ions_total_amu + dnla20=dnla20, + b_plasma_toroidal_on_axis=b_plasma_toroidal_on_axis, + rmajor=rmajor, + rminor=rminor, + m_ions_total_amu=m_ions_total_amu, ) # ======================================================================== @@ -214,17 +199,26 @@ def l_h_threshold_power( # i_l_h_threshold = 12 snipes_2000_cd = self.calculate_snipes2000_closed_divertor_nominal( - dnla20, b_plasma_toroidal_on_axis, rmajor, m_ions_total_amu + dnla20=dnla20, + b_plasma_toroidal_on_axis=b_plasma_toroidal_on_axis, + rmajor=rmajor, + m_ions_total_amu=m_ions_total_amu, ) # i_l_h_threshold = 13 snipes_2000_cd_ub = self.calculate_snipes2000_closed_divertor_upper( - dnla20, b_plasma_toroidal_on_axis, rmajor, m_ions_total_amu + dnla20=dnla20, + b_plasma_toroidal_on_axis=b_plasma_toroidal_on_axis, + rmajor=rmajor, + m_ions_total_amu=m_ions_total_amu, ) # i_l_h_threshold = 14 snipes_2000_cd_lb = self.calculate_snipes2000_closed_divertor_lower( - dnla20, b_plasma_toroidal_on_axis, rmajor, m_ions_total_amu + dnla20=dnla20, + b_plasma_toroidal_on_axis=b_plasma_toroidal_on_axis, + rmajor=rmajor, + m_ions_total_amu=m_ions_total_amu, ) # ======================================================================== @@ -232,13 +226,19 @@ def l_h_threshold_power( # Hubbard et al. 2012 L-I threshold scaling # i_l_h_threshold = 15 - hubbard_2012 = self.calculate_hubbard2012_nominal(plasma_current, dnla20) + hubbard_2012 = self.calculate_hubbard2012_nominal( + plasma_current=plasma_current, dnla20=dnla20 + ) # i_l_h_threshold = 16 - hubbard_2012_lb = self.calculate_hubbard2012_lower(plasma_current, dnla20) + hubbard_2012_lb = self.calculate_hubbard2012_lower( + plasma_current=plasma_current, dnla20=dnla20 + ) # i_l_h_threshold = 17 - hubbard_2012_ub = self.calculate_hubbard2012_upper(plasma_current, dnla20) + hubbard_2012_ub = self.calculate_hubbard2012_upper( + plasma_current=plasma_current, dnla20=dnla20 + ) # ======================================================================== @@ -246,7 +246,9 @@ def l_h_threshold_power( # i_l_h_threshold = 18 hubbard_2017 = self.calculate_hubbard2017( - dnla20, a_plasma_surface, b_plasma_toroidal_on_axis + dnla20=dnla20, + a_plasma_surface=a_plasma_surface, + b_plasma_toroidal_on_axis=b_plasma_toroidal_on_axis, ) # ======================================================================== @@ -255,17 +257,29 @@ def l_h_threshold_power( # i_l_h_threshold = 19 martin_nominal_aspect = self.calculate_martin08_aspect_nominal( - dnla20, b_plasma_toroidal_on_axis, a_plasma_surface, m_ions_total_amu, aspect + dnla20=dnla20, + b_plasma_toroidal_on_axis=b_plasma_toroidal_on_axis, + a_plasma_surface=a_plasma_surface, + m_ions_total_amu=m_ions_total_amu, + aspect=aspect, ) # i_l_h_threshold = 20 martin_ub_aspect = self.calculate_martin08_aspect_upper( - dnla20, b_plasma_toroidal_on_axis, a_plasma_surface, m_ions_total_amu, aspect + dnla20=dnla20, + b_plasma_toroidal_on_axis=b_plasma_toroidal_on_axis, + a_plasma_surface=a_plasma_surface, + m_ions_total_amu=m_ions_total_amu, + aspect=aspect, ) # i_l_h_threshold = 21 martin_lb_aspect = self.calculate_martin08_aspect_lower( - dnla20, b_plasma_toroidal_on_axis, a_plasma_surface, m_ions_total_amu, aspect + dnla20=dnla20, + b_plasma_toroidal_on_axis=b_plasma_toroidal_on_axis, + a_plasma_surface=a_plasma_surface, + m_ions_total_amu=m_ions_total_amu, + aspect=aspect, ) # ======================================================================== @@ -476,7 +490,11 @@ def output(self) -> None: "OP ", ) po.oblnkl(self.outfile) - if self.data.physics.i_l_h_threshold in {9, 10, 11}: + if PlasmaConfinementTransitionModel(self.data.physics.i_l_h_threshold) in { + PlasmaConfinementTransitionModel.SNIPES2000_NOMINAL, + PlasmaConfinementTransitionModel.SNIPES2000_UPPER, + PlasmaConfinementTransitionModel.SNIPES2000_LOWER, + }: if (self.data.physics.b_plasma_toroidal_on_axis < 0.78e0) or ( self.data.physics.b_plasma_toroidal_on_axis > 7.94e0 ): @@ -528,7 +546,11 @@ def output(self) -> None: po.ocmmnt(self.outfile, "(triang outside Snipes 2000 fitted range)") logger.warning("triang outside Snipes 2000 fitted range") - if self.data.physics.i_l_h_threshold in {12, 13, 14}: + if PlasmaConfinementTransitionModel(self.data.physics.i_l_h_threshold) in { + PlasmaConfinementTransitionModel.SNIPES2000_CLOSED_DIVERTOR_NOMINAL, + PlasmaConfinementTransitionModel.SNIPES2000_CLOSED_DIVERTOR_UPPER, + PlasmaConfinementTransitionModel.SNIPES2000_CLOSED_DIVERTOR_LOWER, + }: po.ocmmnt( self.outfile, "(L-H threshold for closed divertor only. Limited data used in Snipes " @@ -547,7 +569,7 @@ def calculate_iter1996_nominal( Parameters ---------- dnla20 : float - Line averaged electron density in units of 10^20 m^-3. + Line averaged electron density in units of [10²⁰m⁻³]. b_plasma_toroidal_on_axis : float Toroidal magnetic field [T] rmajor : float @@ -560,13 +582,13 @@ def calculate_iter1996_nominal( References ---------- - - T. Takizuka and International Atomic Energy Agency, Vienna (Austria), - "Threshold power and energy confinement for ITER". 1996. + [1] T. Takizuka and International Atomic Energy Agency, Vienna (Austria), + "Threshold power and energy confinement for ITER". 1996. - - J. C. Wesley, “International Thermonuclear Experimental Reactor: Physics - issues, capabilities and physics program plans,” - Physics of Plasmas, vol. 4, no. 7, pp. 2642-2652, Jul. 1997, - doi: https://doi.org/10.1063/1.872406. + [2] J. C. Wesley, “International Thermonuclear Experimental Reactor: Physics + issues, capabilities and physics program plans,” + Physics of Plasmas, vol. 4, no. 7, pp. 2642-2652, Jul. 1997, + doi: https://doi.org/10.1063/1.872406. """ return 0.45 * dnla20**0.75 * b_plasma_toroidal_on_axis * rmajor**2 @@ -579,7 +601,7 @@ def calculate_iter1996_upper( Parameters ---------- dnla20 : float - Line averaged electron density in units of 10^20 m^-3. + Line averaged electron density in units of [10²⁰m⁻³]. b_plasma_toroidal_on_axis : float Toroidal magnetic field [T] rmajor : float @@ -592,13 +614,13 @@ def calculate_iter1996_upper( References ---------- - - T. Takizuka and International Atomic Energy Agency, Vienna (Austria), - "Threshold power and energy confinement for ITER". 1996. + [1] T. Takizuka and International Atomic Energy Agency, Vienna (Austria), + "Threshold power and energy confinement for ITER". 1996. - - J. C. Wesley, “International Thermonuclear Experimental Reactor: Physics - issues, capabilities and physics program plans,” - Physics of Plasmas, vol. 4, no. 7, pp. 2642-2652, Jul. 1997, - doi: https://doi.org/10.1063/1.872406. + [2] J. C. Wesley, “International Thermonuclear Experimental Reactor: Physics + issues, capabilities and physics program plans,” + Physics of Plasmas, vol. 4, no. 7, pp. 2642-2652, Jul. 1997, + doi: https://doi.org/10.1063/1.872406. """ return 0.3960502816 * dnla20 * b_plasma_toroidal_on_axis * rmajor**2.5 @@ -611,7 +633,7 @@ def calculate_iter1996_lower( Parameters ---------- dnla20 : float - Line averaged electron density in units of 10^20 m^-3. + Line averaged electron density in units of [10²⁰m⁻³]. b_plasma_toroidal_on_axis : float Toroidal magnetic field [T] rmajor : float @@ -624,13 +646,13 @@ def calculate_iter1996_lower( References ---------- - - T. Takizuka and International Atomic Energy Agency, Vienna (Austria), - "Threshold power and energy confinement for ITER". 1996. + [1] T. Takizuka and International Atomic Energy Agency, Vienna (Austria), + "Threshold power and energy confinement for ITER". 1996. - - J. C. Wesley, “International Thermonuclear Experimental Reactor: Physics - issues, capabilities and physics program plans,” - Physics of Plasmas, vol. 4, no. 7, pp. 2642-2652, Jul. 1997, - doi: https://doi.org/10.1063/1.872406. + [2] J. C. Wesley, “International Thermonuclear Experimental Reactor: Physics + issues, capabilities and physics program plans,” + Physics of Plasmas, vol. 4, no. 7, pp. 2642-2652, Jul. 1997, + doi: https://doi.org/10.1063/1.872406. """ return 0.5112987149 * dnla20**0.5 * b_plasma_toroidal_on_axis * rmajor**1.5 @@ -643,7 +665,7 @@ def calculate_snipes1997_iter( Parameters ---------- dnla20 : float - Line averaged electron density in units of 10^20 m^-3. + Line averaged electron density in units of [10²⁰m⁻³]. b_plasma_toroidal_on_axis : float Toroidal magnetic field [T] rmajor : float @@ -656,12 +678,12 @@ def calculate_snipes1997_iter( References ---------- - - J. A. Snipes and the ITER H-mode Threshold Database Working Group, - "An Analysis of the H-mode Threshold in ITER," - Controlled Fusion and Plasma Physics, 24th EPS Conference, - Berchtesgaden, June 9th-13th 1997, vol.21A, part III, p.961. - url:https://library.ipp.mpg.de/EPS_24_Vol3_1997.pdf. - *This is a conference poster* + [1] J. A. Snipes and the ITER H-mode Threshold Database Working Group, + "An Analysis of the H-mode Threshold in ITER," + Controlled Fusion and Plasma Physics, 24th EPS Conference, + Berchtesgaden, June 9th-13th 1997, vol.21A, part III, p.961. + url:https://library.ipp.mpg.de/EPS_24_Vol3_1997.pdf. + *This is a conference poster* """ return 0.65 * dnla20**0.93 * b_plasma_toroidal_on_axis**0.86 * rmajor**2.15 @@ -675,7 +697,7 @@ def calculate_snipes1997_kappa( Parameters ---------- dnla20 : float - Line averaged electron density in units of 10^20 m^-3. + Line averaged electron density in units of [10²⁰m⁻³]. b_plasma_toroidal_on_axis : float Toroidal magnetic field [T] rmajor : float @@ -690,12 +712,12 @@ def calculate_snipes1997_kappa( References ---------- - - J. A. Snipes and the ITER H-mode Threshold Database Working Group, - "An Analysis of the H-mode Threshold in ITER," - Controlled Fusion and Plasma Physics, 24th EPS Conference, - Berchtesgaden, June 9th-13th 1997, vol.21A, part III, p.961. - url:https://library.ipp.mpg.de/EPS_24_Vol3_1997.pdf. - *This is a conference poster* + [1] J. A. Snipes and the ITER H-mode Threshold Database Working Group, + "An Analysis of the H-mode Threshold in ITER," + Controlled Fusion and Plasma Physics, 24th EPS Conference, + Berchtesgaden, June 9th-13th 1997, vol.21A, part III, p.961. + url:https://library.ipp.mpg.de/EPS_24_Vol3_1997.pdf. + *This is a conference poster* """ return ( 0.42 @@ -717,11 +739,11 @@ def calculate_martin08_nominal( Parameters ---------- dnla20 : float - Line averaged electron density in units of 10^20 m^-3. + Line averaged electron density in units of [10²⁰m⁻³]. b_plasma_toroidal_on_axis : float Toroidal magnetic field [T] a_plasma_surface : float - Plasma surface area [m^2] + Plasma surface area [m²] m_ions_total_amu : float Total ion mass in atomic mass units [amu] @@ -732,20 +754,20 @@ def calculate_martin08_nominal( Notes ----- - - A scaling with the total ion mass is used in this model. Martin 08 shows - that P_LH scales with 1/m_i. It is stated; "When this mass dependence is - applied to the deuterium-tritium discharges for ITER, the above predicted - values of P_LH can be reduced by ~ 20%". We thus apply a (2/m_i) addition so - that for a 50/50 D-T mixture (M_i = 2.5 amu), the predicted values is - 20% lower. + - A scaling with the total ion mass is used in this model. Martin 08 shows + that P_LH scales with 1/m_i. It is stated; "When this mass dependence is + applied to the deuterium-tritium discharges for ITER, the above predicted + values of P_LH can be reduced by ~ 20%". We thus apply a (2/m_i) addition so + that for a 50/50 D-T mixture (M_i = 2.5 amu), the predicted values is + 20% lower. References ---------- - - Y. R. Martin, T. Takizuka, and the I. C. H-mode. T. D. Group, - “Power requirement for accessing the H-mode in ITER,” - Journal of Physics: Conference Series, vol. 123, p. 012033, Jul. 2008, - doi: https://doi.org/10.1088/1742-6596/123/1/012033. + [1] Y. R. Martin, T. Takizuka, and the I. C. H-mode. T. D. Group, + “Power requirement for accessing the H-mode in ITER,” + Journal of Physics: Conference Series, vol. 123, p. 012033, Jul. 2008, + doi: https://doi.org/10.1088/1742-6596/123/1/012033. """ return ( 0.0488 @@ -767,11 +789,11 @@ def calculate_martin08_upper( Parameters ---------- dnla20 : float - Line averaged electron density in units of 10^20 m^-3. + Line averaged electron density in units of [10²⁰m⁻³]. b_plasma_toroidal_on_axis : float Toroidal magnetic field [T] a_plasma_surface : float - Plasma surface area [m^2] + Plasma surface area [m²] m_ions_total_amu : float Total ion mass in atomic mass units [amu] @@ -782,19 +804,19 @@ def calculate_martin08_upper( Notes ----- - - A scaling with the total ion mass is used in this model. Martin 08 shows - that P_LH scales with 1/m_i. It is stated; "When this mass dependence is - applied to the deuterium-tritium discharges for ITER, the above predicted - values of P_LH can be reduced by ~ 20%". We thus apply a (2/m_i) addition - so that for a 50/50 D-T mixture (M_i = 2.5 amu), the predicted values is - 20% lower. + - A scaling with the total ion mass is used in this model. Martin 08 shows + that P_LH scales with 1/m_i. It is stated; "When this mass dependence is + applied to the deuterium-tritium discharges for ITER, the above predicted + values of P_LH can be reduced by ~ 20%". We thus apply a (2/m_i) addition + so that for a 50/50 D-T mixture (M_i = 2.5 amu), the predicted values is + 20% lower. References ---------- - - Y. R. Martin, T. Takizuka, and the I. C. H-mode. T. D. Group, “Power - requirement for accessing the H-mode in ITER,” - Journal of Physics: Conference Series, vol. 123, p. 012033, Jul. 2008, - doi: https://doi.org/10.1088/1742-6596/123/1/012033. + [1] Y. R. Martin, T. Takizuka, and the I. C. H-mode. T. D. Group, “Power + requirement for accessing the H-mode in ITER,” + Journal of Physics: Conference Series, vol. 123, p. 012033, Jul. 2008, + doi: https://doi.org/10.1088/1742-6596/123/1/012033. """ return ( 0.05166240355 @@ -816,11 +838,11 @@ def calculate_martin08_lower( Parameters ---------- dnla20 : float - Line averaged electron density in units of 10^20 m^-3. + Line averaged electron density in units of [10²⁰m⁻³] b_plasma_toroidal_on_axis : float Toroidal magnetic field [T] a_plasma_surface : float - Plasma surface area [m^2] + Plasma surface area [m²] m_ions_total_amu : float Total ion mass in atomic mass units [amu] @@ -831,19 +853,19 @@ def calculate_martin08_lower( Notes ----- - - A scaling with the total ion mass is used in this model. Martin 08 shows - that P_LH scales with 1/m_i. It is stated; "When this mass dependence is - applied to the deuterium-tritium discharges for ITER, the above predicted - values of P_LH can be reduced by ~ 20%". We thus apply a (2/m_i) addition - so that for a 50/50 D-T mixture (M_i = 2.5 amu), the predicted values is - 20% lower. + - A scaling with the total ion mass is used in this model. Martin 08 shows + that P_LH scales with 1/m_i. It is stated; "When this mass dependence is + applied to the deuterium-tritium discharges for ITER, the above predicted + values of P_LH can be reduced by ~ 20%". We thus apply a (2/m_i) addition + so that for a 50/50 D-T mixture (M_i = 2.5 amu), the predicted values is + 20% lower. References ---------- - - Y. R. Martin, T. Takizuka, and the I. C. H-mode. T. D. Group, - “Power requirement for accessing the H-mode in ITER,” - Journal of Physics: Conference Series, vol. 123, p. 012033, Jul. 2008, - doi: https://doi.org/10.1088/1742-6596/123/1/012033. + [1] Y. R. Martin, T. Takizuka, and the I. C. H-mode. T. D. Group, + “Power requirement for accessing the H-mode in ITER,” + Journal of Physics: Conference Series, vol. 123, p. 012033, Jul. 2008, + doi: https://doi.org/10.1088/1742-6596/123/1/012033. """ return ( 0.04609619059 @@ -866,7 +888,7 @@ def calculate_snipes2000_nominal( Parameters ---------- dnla20 : float - Line averaged electron density in units of 10^20 m^-3. + Line averaged electron density in units of [10²⁰m⁻³] b_plasma_toroidal_on_axis : float Toroidal magnetic field [T] rmajor : float @@ -883,18 +905,18 @@ def calculate_snipes2000_nominal( Notes ----- - - A scaling with the total ion mass is used in this model. Snipes cites - that P_LH scales with 1/m_i. It is stated; "This results in a 20% reduction - in the threshold power for a 50/50 D-T mixture compared with the pure - deuterium results above". We thus apply a (2/m_i) addition so that for a - 50/50 D-T mixture (M_i = 2.5 amu), the predicted values is 20% lower. + - A scaling with the total ion mass is used in this model. Snipes cites + that P_LH scales with 1/m_i. It is stated; "This results in a 20% reduction + in the threshold power for a 50/50 D-T mixture compared with the pure + deuterium results above". We thus apply a (2/m_i) addition so that for a + 50/50 D-T mixture (M_i = 2.5 amu), the predicted values is 20% lower. References ---------- - - J. A. Snipes and the I. H-mode. T. Group, “Latest results on the H-mode - threshold using the international H-mode threshold database,” - Plasma Physics and Controlled Fusion, vol. 42, no. 5A, pp. A299-A308, - May 2000, doi: https://doi.org/10.1088/0741-3335/42/5a/336. + [1] J. A. Snipes and the I. H-mode. T. Group, “Latest results on the H-mode + threshold using the international H-mode threshold database,” + Plasma Physics and Controlled Fusion, vol. 42, no. 5A, pp. A299-A308, + May 2000, doi: https://doi.org/10.1088/0741-3335/42/5a/336. """ return ( 1.42 @@ -918,7 +940,7 @@ def calculate_snipes2000_upper( Parameters ---------- dnla20 : float - Line averaged electron density in units of 10^20 m^-3. + Line averaged electron density in units of [10²⁰m⁻³] b_plasma_toroidal_on_axis : float Toroidal magnetic field [T] rmajor : float @@ -935,18 +957,18 @@ def calculate_snipes2000_upper( Notes ----- - - A scaling with the total ion mass is used in this model. Snipes cites - that P_LH scales with 1/m_i. It is stated; "This results in a 20% reduction - in the threshold power for a 50/50 D-T mixture compared with the pure - deuterium results above". We thus apply a (2/m_i) addition so that for a - 50/50 D-T mixture (M_i = 2.5 amu), the predicted values is 20% lower. + - A scaling with the total ion mass is used in this model. Snipes cites + that P_LH scales with 1/m_i. It is stated; "This results in a 20% reduction + in the threshold power for a 50/50 D-T mixture compared with the pure + deuterium results above". We thus apply a (2/m_i) addition so that for a + 50/50 D-T mixture (M_i = 2.5 amu), the predicted values is 20% lower. References ---------- - - J. A. Snipes and the I. H-mode. T. Group, “Latest results on the H-mode - threshold using the international H-mode threshold database,” - Plasma Physics and Controlled Fusion, vol. 42, no. 5A, pp. A299-A308, - May 2000, doi: https://doi.org/10.1088/0741-3335/42/5a/336. + [1] J. A. Snipes and the I. H-mode. T. Group, “Latest results on the H-mode + threshold using the international H-mode threshold database,” + Plasma Physics and Controlled Fusion, vol. 42, no. 5A, pp. A299-A308, + May 2000, doi: https://doi.org/10.1088/0741-3335/42/5a/336. """ return ( @@ -971,7 +993,7 @@ def calculate_snipes2000_lower( Parameters ---------- dnla20 : float - Line averaged electron density in units of 10^20 m^-3. + Line averaged electron density in units of [10²⁰m⁻³]. b_plasma_toroidal_on_axis : float Toroidal magnetic field [T] rmajor : float @@ -988,18 +1010,18 @@ def calculate_snipes2000_lower( Notes ----- - - A scaling with the total ion mass is used in this model. Snipes cites - that P_LH scales with 1/m_i. It is stated; "This results in a 20% reduction - in the threshold power for a 50/50 D-T mixture compared with the pure - deuterium results above". We thus apply a (2/m_i) addition so that for a - 50/50 D-T mixture (M_i = 2.5 amu), the predicted values is 20% lower. + - A scaling with the total ion mass is used in this model. Snipes cites + that P_LH scales with 1/m_i. It is stated; "This results in a 20% reduction + in the threshold power for a 50/50 D-T mixture compared with the pure + deuterium results above". We thus apply a (2/m_i) addition so that for a + 50/50 D-T mixture (M_i = 2.5 amu), the predicted values is 20% lower. References ---------- - - J. A. Snipes and the I. H-mode. T. Group, “Latest results on the H-mode - threshold using the international H-mode threshold database,” - Plasma Physics and Controlled Fusion, vol. 42, no. 5A, pp. A299-A308, - May 2000, doi: https://doi.org/10.1088/0741-3335/42/5a/336. + [1] J. A. Snipes and the I. H-mode. T. Group, “Latest results on the H-mode + threshold using the international H-mode threshold database,” + Plasma Physics and Controlled Fusion, vol. 42, no. 5A, pp. A299-A308, + May 2000, doi: https://doi.org/10.1088/0741-3335/42/5a/336. """ return ( @@ -1024,7 +1046,7 @@ def calculate_snipes2000_closed_divertor_nominal( Parameters ---------- dnla20 : float - Line averaged electron density in units of 10^20 m^-3. + Line averaged electron density in units of [10²⁰m⁻³]. b_plasma_toroidal_on_axis : float Toroidal magnetic field [T] rmajor : float @@ -1039,18 +1061,18 @@ def calculate_snipes2000_closed_divertor_nominal( Notes ----- - - A scaling with the total ion mass is used in this model. Snipes cites - that P_LH scales with 1/m_i. It is stated;m"This results in a 20% reduction - in the threshold power for a 50/50 D-T mixture compared with the pure - deuterium results above". We thus apply a (2/m_i) addition so that for a - 50/50 D-T mixture (M_i = 2.5 amu), the predicted values is 20% lower. + - A scaling with the total ion mass is used in this model. Snipes cites + that P_LH scales with 1/m_i. It is stated;m"This results in a 20% reduction + in the threshold power for a 50/50 D-T mixture compared with the pure + deuterium results above". We thus apply a (2/m_i) addition so that for a + 50/50 D-T mixture (M_i = 2.5 amu), the predicted values is 20% lower. References ---------- - - J. A. Snipes and the I. H-mode. T. Group, “Latest results on the H-mode - threshold using the international H-mode threshold database,” - Plasma Physics and Controlled Fusion, vol. 42, no. 5A, pp. A299-A308, - May 2000, doi: https://doi.org/10.1088/0741-3335/42/5a/336. + [1] J. A. Snipes and the I. H-mode. T. Group, “Latest results on the H-mode + threshold using the international H-mode threshold database,” + Plasma Physics and Controlled Fusion, vol. 42, no. 5A, pp. A299-A308, + May 2000, doi: https://doi.org/10.1088/0741-3335/42/5a/336. """ return ( @@ -1074,7 +1096,7 @@ def calculate_snipes2000_closed_divertor_upper( Parameters ---------- dnla20 : float - Line averaged electron density in units of 10^20 m^-3. + Line averaged electron density in units of [10²⁰m⁻³]. b_plasma_toroidal_on_axis : float Toroidal magnetic field [T] rmajor : float @@ -1089,18 +1111,18 @@ def calculate_snipes2000_closed_divertor_upper( Notes ----- - - A scaling with the total ion mass is used in this model. Snipes cites that - P_LH scales with 1/m_i. It is stated; "This results in a 20% reduction in - the threshold power for a 50/50 D-T mixture compared with the pure deuterium - results above". We thus apply a (2/m_i) addition so that for a 50/50 D-T - mixture (M_i = 2.5 amu), the predicted values is 20% lower. + - A scaling with the total ion mass is used in this model. Snipes cites that + P_LH scales with 1/m_i. It is stated; "This results in a 20% reduction in + the threshold power for a 50/50 D-T mixture compared with the pure deuterium + results above". We thus apply a (2/m_i) addition so that for a 50/50 D-T + mixture (M_i = 2.5 amu), the predicted values is 20% lower. References ---------- - - J. A. Snipes and the I. H-mode. T. Group, “Latest results on the H-mode - threshold using the international H-mode threshold database,” - Plasma Physics and Controlled Fusion, vol. 42, no. 5A, pp. A299-A308, - May 2000, doi: https://doi.org/10.1088/0741-3335/42/5a/336. + [1] J. A. Snipes and the I. H-mode. T. Group, “Latest results on the H-mode + threshold using the international H-mode threshold database,” + Plasma Physics and Controlled Fusion, vol. 42, no. 5A, pp. A299-A308, + May 2000, doi: https://doi.org/10.1088/0741-3335/42/5a/336. """ return ( @@ -1124,7 +1146,7 @@ def calculate_snipes2000_closed_divertor_lower( Parameters ---------- dnla20 : float - Line averaged electron density in units of 10^20 m^-3. + Line averaged electron density in units of [10²⁰m⁻³]. b_plasma_toroidal_on_axis : float Toroidal magnetic field [T] rmajor : float @@ -1139,18 +1161,18 @@ def calculate_snipes2000_closed_divertor_lower( Notes ----- - - A scaling with the total ion mass is used in this model. Snipes cites that - P_LH scales with 1/m_i. It is stated; "This results in a 20% reduction in - the threshold power for a 50/50 D-T mixture compared with the pure - deuterium results above". We thus apply a (2/m_i) addition so that for a - 50/50 D-T mixture (M_i = 2.5 amu), the predicted values is 20% lower. + - A scaling with the total ion mass is used in this model. Snipes cites that + P_LH scales with 1/m_i. It is stated; "This results in a 20% reduction in + the threshold power for a 50/50 D-T mixture compared with the pure + deuterium results above". We thus apply a (2/m_i) addition so that for a + 50/50 D-T mixture (M_i = 2.5 amu), the predicted values is 20% lower. References ---------- - - J. A. Snipes and the I. H-mode. T. Group, “Latest results on the H-mode - threshold using the international H-mode threshold database,” - Plasma Physics and Controlled Fusion, vol. 42, no. 5A, pp. A299-A308, - May 2000, doi: https://doi.org/10.1088/0741-3335/42/5a/336. + [1] J. A. Snipes and the I. H-mode. T. Group, “Latest results on the H-mode + threshold using the international H-mode threshold database,” + Plasma Physics and Controlled Fusion, vol. 42, no. 5A, pp. A299-A308, + May 2000, doi: https://doi.org/10.1088/0741-3335/42/5a/336. """ return ( @@ -1170,7 +1192,7 @@ def calculate_hubbard2012_nominal(plasma_current: float, dnla20: float) -> float plasma_current : float Plasma current [A] dnla20 : float - Line averaged electron density in units of 10^20 m^-3. + Line averaged electron density in units of [10²⁰m⁻³]. Returns ------- @@ -1179,10 +1201,10 @@ def calculate_hubbard2012_nominal(plasma_current: float, dnla20: float) -> float References ---------- - - A. E. Hubbard et al., “Threshold conditions for transitions to I-mode and - H-mode with unfavourable ion grad B drift direction,” - Nuclear Fusion, vol. 52, no. 11, pp. 114009-114009, Oct. 2012, - doi: https://doi.org/10.1088/0029-5515/52/11/114009. + [1] A. E. Hubbard et al., “Threshold conditions for transitions to I-mode and + H-mode with unfavourable ion grad B drift direction,” + Nuclear Fusion, vol. 52, no. 11, pp. 114009-114009, Oct. 2012, + doi: https://doi.org/10.1088/0029-5515/52/11/114009. """ return 2.11 * (plasma_current / 1e6) ** 0.94 * dnla20**0.65 @@ -1196,7 +1218,7 @@ def calculate_hubbard2012_upper(plasma_current: float, dnla20: float) -> float: plasma_current : float Plasma current [A] dnla20 : float - Line averaged electron density in units of 10^20 m^-3. + Line averaged electron density in units of [10²⁰m⁻³]. Returns ------- @@ -1205,10 +1227,10 @@ def calculate_hubbard2012_upper(plasma_current: float, dnla20: float) -> float: References ---------- - - A. E. Hubbard et al., “Threshold conditions for transitions to I-mode and - H-mode with unfavourable ion grad B drift direction,” - Nuclear Fusion, vol. 52, no. 11, pp. 114009-114009, Oct. 2012, - doi: https://doi.org/10.1088/0029-5515/52/11/114009. + [1] A. E. Hubbard et al., “Threshold conditions for transitions to I-mode and + H-mode with unfavourable ion grad B drift direction,” + Nuclear Fusion, vol. 52, no. 11, pp. 114009-114009, Oct. 2012, + doi: https://doi.org/10.1088/0029-5515/52/11/114009. """ return 2.11 * (plasma_current / 1e6) ** 1.18 * dnla20**0.83 @@ -1222,7 +1244,7 @@ def calculate_hubbard2012_lower(plasma_current: float, dnla20: float) -> float: plasma_current : float Plasma current [A] dnla20 : float - Line averaged electron density in units of 10^20 m^-3. + Line averaged electron density in units of [10²⁰m⁻³]. Returns ------- @@ -1231,10 +1253,10 @@ def calculate_hubbard2012_lower(plasma_current: float, dnla20: float) -> float: References ---------- - - A. E. Hubbard et al., “Threshold conditions for transitions to I-mode and - H-mode with unfavourable ion grad B drift direction,” - Nuclear Fusion, vol. 52, no. 11, pp. 114009-114009, Oct. 2012, - doi: https://doi.org/10.1088/0029-5515/52/11/114009. + [1] A. E. Hubbard et al., “Threshold conditions for transitions to I-mode and + H-mode with unfavourable ion grad B drift direction,” + Nuclear Fusion, vol. 52, no. 11, pp. 114009-114009, Oct. 2012, + doi: https://doi.org/10.1088/0029-5515/52/11/114009. """ return 2.11 * (plasma_current / 1e6) ** 0.7 * dnla20**0.47 @@ -1248,9 +1270,9 @@ def calculate_hubbard2017( Parameters ---------- dnla20 : float - Line averaged electron density in units of 10^20 m^-3. + Line averaged electron density in units of [10²⁰m⁻³]. a_plasma_surface : float - Plasma surface area [m^2] + Plasma surface area [m²] b_plasma_toroidal_on_axis : float Toroidal magnetic field [T] @@ -1265,9 +1287,9 @@ def calculate_hubbard2017( References ---------- - - A. E. Hubbard et al., “Physics and performance of the I-mode regime over an - expanded operating space on Alcator C-Mod,” Nuclear Fusion, vol. 57, no. 12, - p. 126039, Oct. 2017, doi: https://doi.org/10.1088/1741-4326/aa8570. + [1] A. E. Hubbard et al., “Physics and performance of the I-mode regime over an + expanded operating space on Alcator C-Mod,” Nuclear Fusion, vol. 57, no. 12, + p. 126039, Oct. 2017, doi: https://doi.org/10.1088/1741-4326/aa8570. """ return 0.162 * dnla20 * a_plasma_surface * b_plasma_toroidal_on_axis**0.26 @@ -1286,11 +1308,11 @@ def calculate_martin08_aspect_nominal( Parameters ---------- dnla20 : float - Line averaged electron density in units of 10^20 m^-3. + Line averaged electron density in units of [10²⁰m⁻³]. b_plasma_toroidal_on_axis : float Toroidal magnetic field [T] a_plasma_surface : float - Plasma surface area [m^2] + Plasma surface area [m²] m_ions_total_amu : float Total ion mass in atomic mass units [amu] aspect : float @@ -1303,27 +1325,27 @@ def calculate_martin08_aspect_nominal( Notes ----- - - Thus will return an aspect ratio correction of the aspect ratio is less - than or equal to 2.7. If not the usual Martin 2008 scaling will be returned. + - Thus will return an aspect ratio correction of the aspect ratio is less + than or equal to 2.7. If not the usual Martin 2008 scaling will be returned. - - A scaling with the total ion mass is used in this model. Martin 08 shows - that P_LH scales with 1/m_i. It is stated; "When this mass dependence is - applied to the deuterium-tritium discharges for ITER, the above predicted - values of P_LH can be reduced by ~ 20%". We thus apply a (2/m_i) addition - so that for a 50/50 D-T mixture (M_i = 2.5 amu), the predicted values is - 20% lower. + - A scaling with the total ion mass is used in this model. Martin 08 shows + that P_LH scales with 1/m_i. It is stated; "When this mass dependence is + applied to the deuterium-tritium discharges for ITER, the above predicted + values of P_LH can be reduced by ~ 20%". We thus apply a (2/m_i) addition + so that for a 50/50 D-T mixture (M_i = 2.5 amu), the predicted values is + 20% lower. References ---------- - - Y. R. Martin, T. Takizuka, and the I. C. H-mode. T. D. Group, “Power - requirement for accessing the H-mode in ITER,” - Journal of Physics: Conference Series, vol. 123, p. 012033, Jul. 2008, - doi: https://doi.org/10.1088/1742-6596/123/1/012033. + [1] Y. R. Martin, T. Takizuka, and the I. C. H-mode. T. D. Group, “Power + requirement for accessing the H-mode in ITER,” + Journal of Physics: Conference Series, vol. 123, p. 012033, Jul. 2008, + doi: https://doi.org/10.1088/1742-6596/123/1/012033. - - T. Takizuka et.al, “Roles of aspect ratio, absolute B and effective Z of - the H-mode power threshold in tokamaks of the ITPA database,” - Plasma Physics and Controlled Fusion, vol. 46, no. 5A, pp. A227-A233, - Apr. 2004, doi: https://doi.org/10.1088/0741-3335/46/5a/024. + [2] T. Takizuka et.al, “Roles of aspect ratio, absolute B and effective Z of + the H-mode power threshold in tokamaks of the ITPA database,” + Plasma Physics and Controlled Fusion, vol. 46, no. 5A, pp. A227-A233, + Apr. 2004, doi: https://doi.org/10.1088/0741-3335/46/5a/024. """ if aspect <= 2.7: @@ -1354,15 +1376,15 @@ def calculate_martin08_aspect_upper( Parameters ---------- dnla20 : float - Line averaged electron density in units of 10^20 m^-3. + Line averaged electron density in units of [10²⁰m⁻³]. b_plasma_toroidal_on_axis : float - Toroidal magnetic field [T] + Toroidal magnetic field [T]. a_plasma_surface : float - Plasma surface area [m^2] + Plasma surface area [m²]. m_ions_total_amu : float - Total ion mass in atomic mass units [amu] + Total ion mass in atomic mass units [amu]. aspect : float - Plasma aspect ratio + Plasma aspect ratio [-]. Returns ------- @@ -1371,27 +1393,27 @@ def calculate_martin08_aspect_upper( Notes ----- - - Thus will return an aspect ratio correction of the aspect ratio is less - than or equal to 2.7. If not the usual Martin 2008 scaling will be returned. + - Thus will return an aspect ratio correction of the aspect ratio is less + than or equal to 2.7. If not the usual Martin 2008 scaling will be returned. - - A scaling with the total ion mass is used in this model. Martin 08 shows - that P_LH scales with 1/m_i. It is stated; "When this mass dependence is - applied to the deuterium-tritium discharges for ITER, the above predicted - values of P_LH can be reduced by ~ 20%". We thus apply a (2/m_i) addition - so that for a 50/50 D-T mixture (M_i = 2.5 amu), the predicted values is - 20% lower. + - A scaling with the total ion mass is used in this model. Martin 08 shows + that P_LH scales with 1/m_i. It is stated; "When this mass dependence is + applied to the deuterium-tritium discharges for ITER, the above predicted + values of P_LH can be reduced by ~ 20%". We thus apply a (2/m_i) addition + so that for a 50/50 D-T mixture (M_i = 2.5 amu), the predicted values is + 20% lower. References ---------- - - Y. R. Martin, T. Takizuka, and the I. C. H-mode. T. D. Group, “Power - requirement for accessing the H-mode in ITER,” - Journal of Physics: Conference Series, vol. 123, p. 012033, Jul. 2008, - doi: https://doi.org/10.1088/1742-6596/123/1/012033. + [1] Y. R. Martin, T. Takizuka, and the I. C. H-mode. T. D. Group, “Power + requirement for accessing the H-mode in ITER,” + Journal of Physics: Conference Series, vol. 123, p. 012033, Jul. 2008, + doi: https://doi.org/10.1088/1742-6596/123/1/012033. - - T. Takizuka et.al, “Roles of aspect ratio, absolute B and effective Z of - the H-mode power threshold in tokamaks of the ITPA database,” - Plasma Physics and Controlled Fusion, vol. 46, no. 5A, pp. A227-A233, - Apr. 2004, doi: https://doi.org/10.1088/0741-3335/46/5a/024. + [2] T. Takizuka et.al, “Roles of aspect ratio, absolute B and effective Z of + the H-mode power threshold in tokamaks of the ITPA database,” + Plasma Physics and Controlled Fusion, vol. 46, no. 5A, pp. A227-A233, + Apr. 2004, doi: https://doi.org/10.1088/0741-3335/46/5a/024. """ if aspect <= 2.7: @@ -1422,15 +1444,15 @@ def calculate_martin08_aspect_lower( Parameters ---------- dnla20 : float - Line averaged electron density in units of 10^20 m^-3. + Line averaged electron density in units of [10²⁰m⁻³]. b_plasma_toroidal_on_axis : float - Toroidal magnetic field [T] + Toroidal magnetic field [T]. a_plasma_surface : float - Plasma surface area [m^2] + Plasma surface area [m²]. m_ions_total_amu : float - Total ion mass in atomic mass units [amu] + Total ion mass in atomic mass units [amu]. aspect : float - Plasma aspect ratio + Plasma aspect ratio [-]. Returns ------- @@ -1439,27 +1461,27 @@ def calculate_martin08_aspect_lower( Notes ----- - - Thus will return an aspect ratio correction of the aspect ratio is less - than or equal to 2.7. if not the usual Martin 2008 scaling will be returned. + - Thus will return an aspect ratio correction of the aspect ratio is less + than or equal to 2.7. if not the usual Martin 2008 scaling will be returned. - - A scaling with the total ion mass is used in this model. Martin 08 shows - that P_LH scales with 1/m_i. It is stated; "When this mass dependence is - applied to the deuterium-tritium discharges for ITER, the above predicted - values of P_LH can be reduced by ~ 20%". We thus apply a (2/m_i) addition - so that for a 50/50 D-T mixture (M_i = 2.5 amu), the predicted values is - 20% lower. + - A scaling with the total ion mass is used in this model. Martin 08 shows + that P_LH scales with 1/m_i. It is stated; "When this mass dependence is + applied to the deuterium-tritium discharges for ITER, the above predicted + values of P_LH can be reduced by ~ 20%". We thus apply a (2/m_i) addition + so that for a 50/50 D-T mixture (M_i = 2.5 amu), the predicted values is + 20% lower. References ---------- - - Y. R. Martin, T. Takizuka, and the I. C. H-mode. T. D. Group, “Power - requirement for accessing the H-mode in ITER,” - Journal of Physics: Conference Series, vol. 123, p. 012033, Jul. 2008, - doi: https://doi.org/10.1088/1742-6596/123/1/012033. - - - T. Takizuka et.al, “Roles of aspect ratio, absolute B and effective Z of - the H-mode power threshold in tokamaks of the ITPA database,” - Plasma Physics and Controlled Fusion, vol. 46, no. 5A, pp. A227-A233, - Apr. 2004, doi: https://doi.org/10.1088/0741-3335/46/5a/024. + [1] Y. R. Martin, T. Takizuka, and the I. C. H-mode. T. D. Group, “Power + requirement for accessing the H-mode in ITER,” + Journal of Physics: Conference Series, vol. 123, p. 012033, Jul. 2008, + doi: https://doi.org/10.1088/1742-6596/123/1/012033. + + [2] T. Takizuka et.al, “Roles of aspect ratio, absolute B and effective Z of + the H-mode power threshold in tokamaks of the ITPA database,” + Plasma Physics and Controlled Fusion, vol. 46, no. 5A, pp. A227-A233, + Apr. 2004, doi: https://doi.org/10.1088/0741-3335/46/5a/024. """ if aspect <= 2.7: diff --git a/process/models/physics/physics.py b/process/models/physics/physics.py index e9ae123cb4..2961e820a4 100644 --- a/process/models/physics/physics.py +++ b/process/models/physics/physics.py @@ -333,21 +333,16 @@ def run(self): qstar=self.data.physics.qstar, q0=self.data.physics.q0 ) - if ( - CurrentProfileIndexModel(self.data.physics.i_alphaj) - == CurrentProfileIndexModel.USER_INPUT - ): - self.data.physics.alphaj = self.data.physics.alphaj - elif ( - CurrentProfileIndexModel(self.data.physics.i_alphaj) - == CurrentProfileIndexModel.WESSON - ): - self.data.physics.alphaj = self.data.physics.alphaj_wesson - else: - raise ProcessValueError( - "Illegal value of i_alphaj", - i_alphaj=self.data.physics.i_alphaj, - ) + match CurrentProfileIndexModel(self.data.physics.i_alphaj): + case CurrentProfileIndexModel.USER_INPUT: + self.data.physics.alphaj = self.data.physics.alphaj + case CurrentProfileIndexModel.WESSON: + self.data.physics.alphaj = self.data.physics.alphaj_wesson + case _: + raise ProcessValueError( + "Illegal value of i_alphaj", + i_alphaj=self.data.physics.i_alphaj, + ) # ================================================== @@ -2939,6 +2934,7 @@ def output_temperature_density_profile_info(self) -> None: "(radius_plasma_pedestal_density_norm)", self.data.physics.radius_plasma_pedestal_density_norm, ) + if ( self.data.physics.i_nd_plasma_pedestal_separatrix == DensityProfilePedestalType.USER_INPUT @@ -4329,39 +4325,39 @@ def output_beta_information(self): f"{BetaComponentLimits(self.data.physics.i_beta_component).full_name} ", ) po.oblnkl(self.outfile) - - if self.data.physics.i_beta_component == BetaComponentLimits.TOTAL: - po.ovarre( - self.outfile, - "Upper limit on volume averaged total beta (⟨β⟩<)", - "(beta_vol_avg_max)", - self.data.physics.beta_vol_avg_max, - "OP ", - ) - elif self.data.physics.i_beta_component == BetaComponentLimits.THERMAL: - po.ovarre( - self.outfile, - "Upper limit on volume averaged thermal beta (⟨βₜₕ⟩<)", - "(beta_vol_avg_max)", - self.data.physics.beta_vol_avg_max, - "OP ", - ) - elif self.data.physics.i_beta_component == BetaComponentLimits.THERMAL_AND_BEAM: - po.ovarre( - self.outfile, - "Upper limit on volume averaged thermal + NB beta (⟨βₜₕ+βₙᵦ⟩<)", - "(beta_vol_avg_max)", - self.data.physics.beta_vol_avg_max, - "OP ", - ) - elif self.data.physics.i_beta_component == BetaComponentLimits.TOROIDAL: - po.ovarre( - self.outfile, - "Upper limit on volume averaged toroidal beta (⟨βₜ⟩<)", - "(beta_vol_avg_max)", - self.data.physics.beta_vol_avg_max, - "OP ", - ) + match BetaComponentLimits(self.data.physics.i_beta_component): + case BetaComponentLimits.TOTAL: + po.ovarre( + self.outfile, + "Upper limit on volume averaged total beta (⟨β⟩<)", + "(beta_vol_avg_max)", + self.data.physics.beta_vol_avg_max, + "OP ", + ) + case BetaComponentLimits.THERMAL: + po.ovarre( + self.outfile, + "Upper limit on volume averaged thermal beta (⟨βₜₕ⟩<)", + "(beta_vol_avg_max)", + self.data.physics.beta_vol_avg_max, + "OP ", + ) + case BetaComponentLimits.THERMAL_AND_BEAM: + po.ovarre( + self.outfile, + "Upper limit on volume averaged thermal + NB beta (⟨βₜₕ+βₙᵦ⟩<)", + "(beta_vol_avg_max)", + self.data.physics.beta_vol_avg_max, + "OP ", + ) + case BetaComponentLimits.TOROIDAL: + po.ovarre( + self.outfile, + "Upper limit on volume averaged toroidal beta (⟨βₜ⟩<)", + "(beta_vol_avg_max)", + self.data.physics.beta_vol_avg_max, + "OP ", + ) po.ovarre( self.outfile, diff --git a/process/models/physics/plasma_current.py b/process/models/physics/plasma_current.py index ed11a60ef3..3675596b8a 100644 --- a/process/models/physics/plasma_current.py +++ b/process/models/physics/plasma_current.py @@ -16,56 +16,13 @@ from process.core import process_output as po from process.core.exceptions import ProcessValueError from process.core.model import Model +from process.data_structure.physics_variables import PlasmaCurrentModel from process.data_structure.stellarator_variables import StellaratorModel from process.models.physics.plasma_geometry import PlasmaGeometryModelType logger = logging.getLogger(__name__) -@unique -class PlasmaCurrentModel(IntEnum): - """Enumeration of plasma current scaling models available for calculations. - - Each model represents a different scaling law used to calculate plasma - current based on various plasma and machine parameters. - """ - - PENG_ANALYTIC_FIT = (1, "Peng analytic fit") - PENG_DIVERTOR_SCALING = (2, "Peng divertor scaling") - ITER_SCALING = (3, "Simple ITER scaling (cylindrical case)") - IPDG89_SCALING = (4, "IPDG89 scaling") - TODD_EMPIRICAL_SCALING_I = (5, "Todd empirical scaling I") - TODD_EMPIRICAL_SCALING_II = (6, "Todd empirical scaling II") - CONNOR_HASTIE_MODEL = (7, "Connor-Hastie model") - SAUTER_SCALING = (8, "Sauter scaling") - FIESTA_ST_SCALING = (9, "FIESTA ST scaling") - - def __new__(cls, value: int, full_name: str): - """Create a new PlasmaCurrentModel enum member with value and full_name. - - Parameters - ---------- - value : int - The numeric value of the enum member. - full_name : str - The full name description of the plasma current model. - - Returns - ------- - PlasmaCurrentModel - A new enum member with the specified value and full_name. - """ - obj = int.__new__(cls, value) - obj._value_ = value - obj._full_name_ = full_name - return obj - - @DynamicClassAttribute - def full_name(self): - """The full name of the plasma current model.""" - return self._full_name_ - - class PlasmaCurrent(Model): """Class to hold plasma current calculations for plasma processing.""" @@ -220,13 +177,13 @@ def calculate_plasma_current( Parameters ---------- alphaj : float - Current profile index. + Current profile index . alphap : float - Pressure profile index. + Pressure profile index . b_plasma_toroidal_on_axis : float - Toroidal field on axis (T). + Toroidal field on axis [T]. eps : float - Inverse aspect ratio. + Inverse aspect ratio . i_plasma_current : int Current scaling model to use. 1 = Peng analytic fit @@ -239,19 +196,19 @@ def calculate_plasma_current( 8 = Sauter scaling (allowing negative triangularity) 9 = FIESTA ST scaling kappa : float - Plasma elongation. + Plasma elongation . kappa95 : float - Plasma elongation at 95% surface. + Plasma elongation at 95% surface . pres_plasma_on_axis : float - Central plasma pressure (Pa). + Central plasma pressure [Pa]. len_plasma_poloidal : float - Plasma perimeter length (m). + Plasma perimeter length [m]. q95 : float - Plasma safety factor at 95% flux. + Plasma safety factor at 95% flux . rmajor : float - Major radius (m). + Major radius [m]. rminor : float - Minor radius (m). + Minor radius [m]. triang : float Plasma triangularity. triang95 : float @@ -272,7 +229,7 @@ def calculate_plasma_current( Notes ----- - This routine calculates the plasma current based on the edge safety factor + - This routine calculates the plasma current based on the edge safety factor q95. It will also make the current profile parameters consistent with the q-profile if required. @@ -304,85 +261,88 @@ def calculate_plasma_current( # Only the Sauter scaling (i_plasma_current=8) is suitable for negative # triangularity: - if i_plasma_current != 8 and triang < 0.0: + if ( + PlasmaCurrentModel(i_plasma_current) != PlasmaCurrentModel.SAUTER_SCALING + and triang < 0.0 + ): raise ProcessValueError( f"Triangularity is negative without i_plasma_current = 8 selected:" f" {triang=}, {i_plasma_current=}" ) + try: # noqa: PLW0717 model = PlasmaCurrentModel(int(i_plasma_current)) - # Calculate the function Fq that scales the edge q from the - # circular cross-section cylindrical case - - # Peng analytical fit - if model == PlasmaCurrentModel.PENG_ANALYTIC_FIT: - fq = self.calculate_current_coefficient_peng( - eps=eps, len_plasma_poloidal=len_plasma_poloidal, rminor=rminor - ) + match model: + # Calculate the function Fq that scales the edge q from the + # circular cross-section cylindrical case + + # Peng analytical fit + case PlasmaCurrentModel.PENG_ANALYTIC_FIT: + fq = self.calculate_current_coefficient_peng( + eps=eps, len_plasma_poloidal=len_plasma_poloidal, rminor=rminor + ) - # Peng scaling for double null divertor; TARTs [STAR Code] - elif model == PlasmaCurrentModel.PENG_DIVERTOR_SCALING: - plasma_current = 1.0e6 * self.calculate_plasma_current_peng( - q95=q95, - aspect=aspect_ratio, - rminor=rminor, - b_plasma_toroidal_on_axis=b_plasma_toroidal_on_axis, - kappa=kappa, - triang=triang, - ) + # Peng scaling for double null divertor; TARTs [STAR Code] + case PlasmaCurrentModel.PENG_DIVERTOR_SCALING: + plasma_current = 1.0e6 * self.calculate_plasma_current_peng( + q95=q95, + aspect=aspect_ratio, + rminor=rminor, + b_plasma_toroidal_on_axis=b_plasma_toroidal_on_axis, + kappa=kappa, + triang=triang, + ) - # Simple ITER scaling (simply the cylindrical case) - elif model == PlasmaCurrentModel.ITER_SCALING: - fq = 1.0 + # Simple ITER scaling (simply the cylindrical case) + case PlasmaCurrentModel.ITER_SCALING: + fq = 1.0 - # ITER formula (IPDG89) - elif model == PlasmaCurrentModel.IPDG89_SCALING: - fq = self.calculate_current_coefficient_ipdg89( - eps=eps, kappa95=kappa95, triang95=triang95 - ) + # ITER formula (IPDG89) + case PlasmaCurrentModel.IPDG89_SCALING: + fq = self.calculate_current_coefficient_ipdg89( + eps=eps, kappa95=kappa95, triang95=triang95 + ) - # Todd empirical scalings - # D.C.Robinson and T.N.Todd, Plasma and Contr Fusion 28 (1986) 1181 - elif model in { - PlasmaCurrentModel.TODD_EMPIRICAL_SCALING_I, - PlasmaCurrentModel.TODD_EMPIRICAL_SCALING_II, - }: - fq = self.calculate_current_coefficient_todd( - eps=eps, kappa95=kappa95, triang95=triang95, model=1 - ) + # Todd empirical scalings + # D.C.Robinson and T.N.Todd, Plasma and Contr Fusion 28 (1986) 1181 + case PlasmaCurrentModel.TODD_EMPIRICAL_SCALING_I: + fq = self.calculate_current_coefficient_todd( + eps=eps, kappa95=kappa95, triang95=triang95, model=1 + ) - if model == PlasmaCurrentModel.TODD_EMPIRICAL_SCALING_II: + case PlasmaCurrentModel.TODD_EMPIRICAL_SCALING_II: fq = self.calculate_current_coefficient_todd( eps=eps, kappa95=kappa95, triang95=triang95, model=2 ) - # Connor-Hastie asymptotically-correct expression - elif model == PlasmaCurrentModel.CONNOR_HASTIE_MODEL: - fq = self.calculate_current_coefficient_hastie( - alphaj=alphaj, - alphap=alphap, - b_plasma_toroidal_on_axis=b_plasma_toroidal_on_axis, - triang95=triang95, - eps=eps, - kappa95=kappa95, - pres_plasma_on_axis=pres_plasma_on_axis, - rmu0=constants.RMU0, - ) + # Connor-Hastie asymptotically-correct expression + case PlasmaCurrentModel.CONNOR_HASTIE_MODEL: + fq = self.calculate_current_coefficient_hastie( + alphaj=alphaj, + alphap=alphap, + b_plasma_toroidal_on_axis=b_plasma_toroidal_on_axis, + triang95=triang95, + eps=eps, + kappa95=kappa95, + pres_plasma_on_axis=pres_plasma_on_axis, + rmu0=constants.RMU0, + ) - # Sauter scaling allowing negative triangularity [FED May 2016] - # https://doi.org/10.1016/j.fusengdes.2016.04.033. - elif model == PlasmaCurrentModel.SAUTER_SCALING: - # Assumes zero squareness, note takes kappa, delta at separatrix not _95 - fq = self.calculate_current_coefficient_sauter( - eps=eps, kappa=kappa, triang=triang - ) + # Sauter scaling allowing negative triangularity [FED May 2016] + # https://doi.org/10.1016/j.fusengdes.2016.04.033. + case PlasmaCurrentModel.SAUTER_SCALING: + # Assumes zero squareness, note takes kappa, delta at + # separatrix not _95 + fq = self.calculate_current_coefficient_sauter( + eps=eps, kappa=kappa, triang=triang + ) - # FIESTA ST scaling - # https://doi.org/10.1016/j.fusengdes.2020.111530. - elif model == PlasmaCurrentModel.FIESTA_ST_SCALING: - fq = self.calculate_current_coefficient_fiesta( - eps=eps, kappa=kappa, triang=triang - ) + # FIESTA ST scaling + # https://doi.org/10.1016/j.fusengdes.2020.111530. + case PlasmaCurrentModel.FIESTA_ST_SCALING: + fq = self.calculate_current_coefficient_fiesta( + eps=eps, kappa=kappa, triang=triang + ) except ValueError as e: raise ProcessValueError( @@ -428,31 +388,31 @@ def calculate_all_plasma_current_models( Parameters ---------- alphaj : - current profile index + current profile index. alphap : - pressure profile index + pressure profile index. b_plasma_toroidal_on_axis : - toroidal field on axis (T) + toroidal field on axis [T]. eps : - inverse aspect ratio + inverse aspect ratio. kappa : - plasma elongation + plasma elongation. kappa95 : - plasma elongation at 95% surface + plasma elongation at 95% surface. pres_plasma_on_axis : - central plasma pressure (Pa) + central plasma pressure [Pa]. len_plasma_poloidal : - plasma perimeter length (m) + plasma perimeter length [m]. q95 : - plasma safety factor at 95% flux + plasma safety factor at 95% flux. rmajor : - major radius (m) + major radius [m]. rminor : - minor radius (m) + minor radius [m]. triang : - plasma triangularity + plasma triangularity. triang95 : - plasma triangularity at 95% surface + plasma triangularity at 95% surface. Returns ------- @@ -603,18 +563,18 @@ def calculate_cyclindrical_plasma_current( Parameters ---------- rminor : - plasma minor radius (m) + plasma minor radius [m]. rmajor : - plasma major radius (m) + plasma major radius [m]. q95 : - plasma safety factor at 95% flux + plasma safety factor at 95% flux [-]. b_plasma_toroidal_on_axis : - toroidal field on axis (T) + toroidal field on axis [T]. Returns ------- float - plasma current (A) + plasma current [A]. """ return ( @@ -638,13 +598,13 @@ def plascar_bpol( Parameters ---------- aspect : - plasma aspect ratio + plasma aspect ratio. eps : - inverse aspect ratio + inverse aspect ratio. kappa : - plasma elongation + plasma elongation. triang : - plasma triangularity + plasma triangularity. Returns ------- @@ -700,11 +660,11 @@ def calculate_current_coefficient_peng( Parameters ---------- eps: - Plasma inverse aspect ratio. + Plasma inverse aspect ratio [-]. len_plasma_poloidal: - Plasma poloidal perimeter length (m). + Plasma poloidal perimeter length [m]. rminor: - Plasma minor radius (m). + Plasma minor radius [m]. Returns ------- @@ -735,16 +695,23 @@ def calculate_plasma_current_peng( Parameters ---------- - - q95: float, 95% flux surface safety factor - - aspect: float, plasma aspect ratio - - rminor: float, plasma minor radius (m) - - b_plasma_toroidal_on_axis: float, toroidal field on axis (T) - - kappa: float, plasma elongation - - triang: float, plasma triangularity + - q95: + 95% flux surface safety factor. + - aspect: + plasma aspect ratio. + - rminor: + plasma minor radius [m]. + - b_plasma_toroidal_on_axis: + toroidal field on axis [T]. + - kappa: + plasma elongation. + - triang: + plasma triangularity. Returns ------- - - float, plasma current in MA + : + plasma current [MA]. This function calculates the plasma current in MA, using a scaling from Peng, Galambos and Shipe (1992). @@ -794,16 +761,17 @@ def calculate_current_coefficient_ipdg89( Parameters ---------- - - eps: float, plasma inverse aspect ratio - - kappa95: float, plasma elongation 95% - - triang95: float, plasma triangularity 95% + eps : float + plasma inverse aspect ratio. + kappa95 : float + plasma elongation 95%. + triang95 : float + plasma triangularity 95%. Returns ------- - - float, the fq plasma current coefficient - - This function calculates the fq coefficient used in the IPDG89 plasma current - scaling, based on the given plasma parameters. + float + the fq plasma current coefficient References ---------- @@ -828,13 +796,17 @@ def calculate_current_coefficient_todd( Parameters ---------- - - eps: float, plasma inverse aspect ratio - - kappa95: float, plasma elongation 95% - - triang95: float, plasma triangularity 95% + eps: float + plasma inverse aspect ratio. + kappa95: float + plasma elongation 95%. + triang95: float + plasma triangularity 95%. Returns ------- - - float, the fq plasma current coefficient + float + the fq plasma current coefficient Raises ------ @@ -885,18 +857,27 @@ def calculate_current_coefficient_hastie( Parameters ---------- - - alphaj: float, the current profile index - - alphap: float, the pressure profile index - - b_plasma_toroidal_on_axis: float, the toroidal field on axis (T) - - triang95: float, the plasma triangularity 95% - - eps: float, the inverse aspect ratio - - kappa95: float, the plasma elongation 95% - - pres_plasma_on_axis: float, the central plasma pressure (Pa) - - rmu0: float, the vacuum permeability (H/m) + alphaj: float + the current profile index [-]. + alphap: float + the pressure profile index [-]. + b_plasma_toroidal_on_axis: float + the toroidal field on axis [T]. + triang95: float + the plasma triangularity 95% [-]. + eps: float + the inverse aspect ratio [-]. + kappa95: float + the plasma elongation 95% [-]. + pres_plasma_on_axis: float + the central plasma pressure (Pa) [Pa]. + rmu0: float + the vacuum permeability [H/m]. Returns ------- - - float, the F coefficient + float + the F coefficient This routine calculates the f_q coefficient used for scaling the plasma current, using the Connor-Hastie scaling @@ -967,13 +948,17 @@ def calculate_current_coefficient_sauter( Parameters ---------- - - eps: float, inverse aspect ratio - - kappa: float, plasma elongation at the separatrix - - triang: float, plasma triangularity at the separatrix + eps: float + inverse aspect ratio [-]. + kappa: float + plasma elongation at the separatrix [-]. + triang: float + plasma triangularity at the separatrix [-]. Returns ------- - - float, the fq coefficient + float + the fq coefficient References ---------- @@ -1001,13 +986,17 @@ def calculate_current_coefficient_fiesta( Parameters ---------- - - eps: float, plasma inverse aspect ratio - - kappa: float, plasma elongation at the separatrix - - triang: float, plasma triangularity at the separatrix + eps: float + plasma inverse aspect ratio. + kappa: float + plasma elongation at the separatrix. + triang: float + plasma triangularity at the separatrix. Returns ------- - - float, the fq plasma current coefficient + :float + the fq plasma current coefficient This function calculates the fq coefficient based on the given plasma parameters for the FIESTA scaling. @@ -1083,21 +1072,15 @@ def run(self): self.data.physics.q0, ) ) - - if ( - self.data.physics.i_diamagnetic_current - == PlasmaDiamagneticCurrentModel.HENDER_ST_FIT - ): - self.data.current_drive.f_c_plasma_diamagnetic = ( - self.data.current_drive.f_c_plasma_diamagnetic_hender - ) - elif ( - self.data.physics.i_diamagnetic_current - == PlasmaDiamagneticCurrentModel.SCENE_FIT - ): - self.data.current_drive.f_c_plasma_diamagnetic = ( - self.data.current_drive.f_c_plasma_diamagnetic_scene - ) + match PlasmaDiamagneticCurrentModel(self.data.physics.i_diamagnetic_current): + case PlasmaDiamagneticCurrentModel.HENDER_ST_FIT: + self.data.current_drive.f_c_plasma_diamagnetic = ( + self.data.current_drive.f_c_plasma_diamagnetic_hender + ) + case PlasmaDiamagneticCurrentModel.SCENE_FIT: + self.data.current_drive.f_c_plasma_diamagnetic = ( + self.data.current_drive.f_c_plasma_diamagnetic_scene + ) def output(self): """Output the plasma diamagnetic current model results.""" @@ -1167,15 +1150,15 @@ def diamagnetic_fraction_scene(beta: float, q95: float, q0: float) -> float: Parameters ---------- beta : - the plasma beta value + the plasma beta value. q95 : - the normalized safety factor at 95% of the plasma radius + the normalized safety factor at 95% of the plasma radius. q0 : - the normalized safety factor at the magnetic axis + the normalized safety factor at the magnetic axis. Returns ------- - float + : the diamagnetic fraction """ return beta * (0.1 * q95 / q0 + 0.44) * 0.414 diff --git a/process/models/physics/plasma_fields.py b/process/models/physics/plasma_fields.py index 71ea77bd7c..fdd5163fc9 100644 --- a/process/models/physics/plasma_fields.py +++ b/process/models/physics/plasma_fields.py @@ -8,7 +8,8 @@ from process.core import constants from process.core import process_output as po from process.core.model import Model -from process.models.physics.plasma_current import PlasmaCurrent, PlasmaCurrentModel +from process.data_structure.physics_variables import PlasmaCurrentModel +from process.models.physics.plasma_current import PlasmaCurrent logger = logging.getLogger(__name__) diff --git a/process/models/physics/plasma_geometry.py b/process/models/physics/plasma_geometry.py index c42b9f0609..7c7d3bd116 100644 --- a/process/models/physics/plasma_geometry.py +++ b/process/models/physics/plasma_geometry.py @@ -10,6 +10,7 @@ from process.core import process_output as po from process.core.exceptions import ProcessValueError from process.core.model import Model +from process.data_structure.physics_variables import PlasmaCurrentModel from process.data_structure.stellarator_variables import StellaratorModel logger = logging.getLogger(__name__) @@ -210,12 +211,10 @@ def run(self): terms and input values. It updates the `physics_variables` with calculated values for kappa, triangularity, surface area, volume, etc. - References - ---------- - - J D Galambos, STAR Code : Spherical Tokamak Analysis and Reactor Code, - unpublished internal Oak Ridge document - - H. Zohm et al, On the Physics Guidelines for a Tokamak DEMO, - FTP/3-3, Proc. IAEA Fusion Energy Conference, October 2012, San Diego + Raises + ------ + ProcessValueError + If the value of `i_plasma_geometry` is illegal. """ xsi = 0.0e0 xso = 0.0e0 @@ -232,209 +231,216 @@ def run(self): # ====================================================================== - if ( - self.data.physics.i_plasma_geometry == PlasmaGeometryModelType.IPDG89_X_POINT - ): # Use input kappa, self.data.physics.triang values - # Rough estimate of 95% values - # ITER Physics Design Guidelines: 1989 (Uckan et al. 1990) - # (close to previous estimate of (self.data.physics.kappa - 0.04) / 1.1 - # over a large self.data.physics.kappa range) - - self.data.physics.kappa95 = self.data.physics.kappa / 1.12e0 - self.data.physics.triang95 = self.data.physics.triang / 1.50e0 - - # ====================================================================== - - if ( - self.data.physics.i_plasma_geometry == PlasmaGeometryModelType.STAR_FIESTA - ): # ST scaling with self.data.physics.aspect ratio [STAR Code] - self.data.physics.q95_min = 3.0e0 * ( - 1.0e0 + 2.6e0 * self.data.physics.eps**2.8e0 - ) - - self.data.physics.kappa = 2.05e0 * ( - 1.0e0 + 0.44e0 * self.data.physics.eps**2.1e0 - ) - self.data.physics.triang = 0.53e0 * ( - 1.0e0 + 0.77e0 * self.data.physics.eps**3 - ) - - # SIM 10/09/2020: Switched to FIESTA ST scaling from IPDG89 - self.data.physics.kappa95 = ( - self.data.physics.kappa - 0.39467e0 - ) / 0.90698e0 # Fit to FIESTA (Issue #1086) - self.data.physics.triang95 = ( - self.data.physics.triang - 0.048306e0 - ) / 1.3799e0 - - # ====================================================================== - - if ( - self.data.physics.i_plasma_geometry - == PlasmaGeometryModelType.ZOHM_ITER_X_POINT - ): # Zohm et al. ITER scaling for elongation, input self.data.physics.triang - self.data.physics.kappa = self.data.physics.fkzohm * min( - 2.0e0, 1.5e0 + 0.5e0 / (self.data.physics.aspect - 1.0e0) - ) - - # ITER Physics Design Guidelines: 1989 (Uckan et al. 1990) - self.data.physics.kappa95 = self.data.physics.kappa / 1.12e0 - self.data.physics.triang95 = self.data.physics.triang / 1.50e0 - - # ====================================================================== - - if ( - self.data.physics.i_plasma_geometry == PlasmaGeometryModelType.ZOHM_ITER_95 - ): # Zohm et al. ITER scaling for elongation, input self.data.physics.triang95 - self.data.physics.kappa = self.data.physics.fkzohm * min( - 2.0e0, 1.5e0 + 0.5e0 / (self.data.physics.aspect - 1.0e0) - ) + try: + model = PlasmaGeometryModelType(self.data.physics.i_plasma_geometry) + except ValueError as e: + raise ProcessValueError( + "Illegal value for i_plasma_geometry", + i_plasma_geometry=self.data.physics.i_plasma_geometry, + ) from e + + match model: + case PlasmaGeometryModelType.IPDG89_X_POINT: + # Use input kappa, self.data.physics.triang values + # Rough estimate of 95% values + # ITER Physics Design Guidelines: 1989 (Uckan et al. 1990) + # (close to previous estimate of (self.data.physics.kappa - 0.04) / 1.1 + # over a large self.data.physics.kappa range) + + self.data.physics.kappa95 = self.data.physics.kappa / 1.12e0 + self.data.physics.triang95 = self.data.physics.triang / 1.50e0 + + # ====================================================================== + + case PlasmaGeometryModelType.STAR_FIESTA: + # ST scaling with self.data.physics.aspect ratio [STAR Code] + self.data.physics.q95_min = 3.0e0 * ( + 1.0e0 + 2.6e0 * self.data.physics.eps**2.8e0 + ) - # ITER Physics Design Guidelines: 1989 (Uckan et al. 1990) - self.data.physics.triang = 1.5e0 * self.data.physics.triang95 + self.data.physics.kappa = 2.05e0 * ( + 1.0e0 + 0.44e0 * self.data.physics.eps**2.1e0 + ) + self.data.physics.triang = 0.53e0 * ( + 1.0e0 + 0.77e0 * self.data.physics.eps**3 + ) - self.data.physics.kappa95 = self.data.physics.kappa / 1.12e0 + # SIM 10/09/2020: Switched to FIESTA ST scaling from IPDG89 + self.data.physics.kappa95 = ( + self.data.physics.kappa - 0.39467e0 + ) / 0.90698e0 # Fit to FIESTA (Issue #1086) + self.data.physics.triang95 = ( + self.data.physics.triang - 0.048306e0 + ) / 1.3799e0 + + # ====================================================================== + + case PlasmaGeometryModelType.ZOHM_ITER_X_POINT: + # Zohm et al. ITER scaling for elongation, input self.data.physics.triang + self.data.physics.kappa = self.data.physics.fkzohm * min( + 2.0e0, 1.5e0 + 0.5e0 / (self.data.physics.aspect - 1.0e0) + ) - # ====================================================================== + # ITER Physics Design Guidelines: 1989 (Uckan et al. 1990) + self.data.physics.kappa95 = self.data.physics.kappa / 1.12e0 + self.data.physics.triang95 = self.data.physics.triang / 1.50e0 - if ( - self.data.physics.i_plasma_geometry == PlasmaGeometryModelType.IPDG89_95 - ): # Use input kappa95, self.data.physics.triang95 values - # ITER Physics Design Guidelines: 1989 (Uckan et al. 1990) - self.data.physics.kappa = 1.12e0 * self.data.physics.kappa95 - self.data.physics.triang = 1.5e0 * self.data.physics.triang95 + # ====================================================================== - # ====================================================================== + case PlasmaGeometryModelType.ZOHM_ITER_95: + # Zohm et al. ITER scaling for elongation, + # input self.data.physics.triang95 + self.data.physics.kappa = self.data.physics.fkzohm * min( + 2.0e0, 1.5e0 + 0.5e0 / (self.data.physics.aspect - 1.0e0) + ) - if ( - self.data.physics.i_plasma_geometry == PlasmaGeometryModelType.MAST_DATA_95 - ): # Use input kappa95, self.data.physics.triang95 values - # Fit to MAST data (Issue #1086) - self.data.physics.kappa = 0.91300e0 * self.data.physics.kappa95 + 0.38654e0 - self.data.physics.triang = 0.77394e0 * self.data.physics.triang95 + 0.18515e0 + # ITER Physics Design Guidelines: 1989 (Uckan et al. 1990) + self.data.physics.triang = 1.5e0 * self.data.physics.triang95 - # ====================================================================== + self.data.physics.kappa95 = self.data.physics.kappa / 1.12e0 - if ( - self.data.physics.i_plasma_geometry - == PlasmaGeometryModelType.MAST_DATA_X_POINT - ): # Use input kappa, self.data.physics.triang values - # Fit to MAST data (Issue #1086) - self.data.physics.kappa95 = (self.data.physics.kappa - 0.38654e0) / 0.91300e0 - self.data.physics.triang95 = ( - self.data.physics.triang - 0.18515e0 - ) / 0.77394e0 + # ====================================================================== - # ====================================================================== + case PlasmaGeometryModelType.IPDG89_95: + # Use input kappa95, self.data.physics.triang95 values + # ITER Physics Design Guidelines: 1989 (Uckan et al. 1990) + self.data.physics.kappa = 1.12e0 * self.data.physics.kappa95 + self.data.physics.triang = 1.5e0 * self.data.physics.triang95 - if ( - self.data.physics.i_plasma_geometry == PlasmaGeometryModelType.FIESTA_RUNS_95 - ): # Use input kappa95, self.data.physics.triang95 values - # Fit to FIESTA (Issue #1086) - self.data.physics.kappa = 0.90698e0 * self.data.physics.kappa95 + 0.39467e0 - self.data.physics.triang = 1.3799e0 * self.data.physics.triang95 + 0.048306e0 + # ====================================================================== - # ====================================================================== - - if ( - self.data.physics.i_plasma_geometry - == PlasmaGeometryModelType.FIESTA_RUNS_X_POINT - ): # Use input kappa, self.data.physics.triang values - # Fit to FIESTA (Issue #1086) - self.data.physics.kappa95 = (self.data.physics.kappa - 0.39467e0) / 0.90698e0 - self.data.physics.triang95 = ( - self.data.physics.triang - 0.048306e0 - ) / 1.3799e0 + case PlasmaGeometryModelType.MAST_DATA_95: + # Use input kappa95, self.data.physics.triang95 values + # Fit to MAST data (Issue #1086) + self.data.physics.kappa = ( + 0.91300e0 * self.data.physics.kappa95 + 0.38654e0 + ) + self.data.physics.triang = ( + 0.77394e0 * self.data.physics.triang95 + 0.18515e0 + ) - # ====================================================================== + # ====================================================================== - if ( - self.data.physics.i_plasma_geometry - == PlasmaGeometryModelType.INDUCTANCE_SCALING_X_POINT - ): # Use input triang, self.data.physics.ind_plasma_internal_norm values - # self.data.physics.kappa found from self.data.physics.aspect ratio and - # plasma internal inductance li(3) - self.data.physics.kappa = ( - 1.09e0 + 0.26e0 / self.data.physics.ind_plasma_internal_norm - ) * (1.5e0 / self.data.physics.aspect) ** 0.4e0 - - self.data.physics.kappa95 = self.data.physics.kappa / 1.12e0 - self.data.physics.triang95 = self.data.physics.triang / 1.50e0 + case PlasmaGeometryModelType.MAST_DATA_X_POINT: + # Use input kappa, self.data.physics.triang values + # Fit to MAST data (Issue #1086) + self.data.physics.kappa95 = ( + self.data.physics.kappa - 0.38654e0 + ) / 0.91300e0 + self.data.physics.triang95 = ( + self.data.physics.triang - 0.18515e0 + ) / 0.77394e0 + + # ====================================================================== + + case PlasmaGeometryModelType.FIESTA_RUNS_95: + # Use input kappa95, self.data.physics.triang95 values + # Fit to FIESTA (Issue #1086) + self.data.physics.kappa = ( + 0.90698e0 * self.data.physics.kappa95 + 0.39467e0 + ) + self.data.physics.triang = ( + 1.3799e0 * self.data.physics.triang95 + 0.048306e0 + ) - # ====================================================================== + # ====================================================================== - if ( - self.data.physics.i_plasma_geometry - == PlasmaGeometryModelType.CREATE_DATA_EU_DEMO_X_POINT - ): - # self.data.physics.kappa95 found from self.data.physics.aspect ratio and - # stability margin Based on fit to CREATE data. ref Issue #1399 - # valid for EU-DEMO like machine - self.data.physics.aspect ratio 2.6 - 3.6 - # Model updated see Issue #1648 - a = 3.68436807e0 - b = -0.27706527e0 - c = 0.87040251e0 - d = -18.83740952e0 - e = -0.27267618e0 - f = 20.5141261e0 - - self.data.physics.kappa95 = ( - -d - - c * self.data.physics.aspect - - np.sqrt( - (c**2.0e0 - 4.0e0 * a * b) * self.data.physics.aspect**2.0e0 - + (2.0e0 * d * c - 4.0e0 * a * e) * self.data.physics.aspect - + d**2.0e0 - - 4.0e0 * a * f - + 4.0e0 * a * self.data.physics.m_s_limit - ) - ) / (2.0e0 * a) + case PlasmaGeometryModelType.FIESTA_RUNS_X_POINT: + # Use input kappa, self.data.physics.triang values + # Fit to FIESTA (Issue #1086) + self.data.physics.kappa95 = ( + self.data.physics.kappa - 0.39467e0 + ) / 0.90698e0 + self.data.physics.triang95 = ( + self.data.physics.triang - 0.048306e0 + ) / 1.3799e0 + + # ====================================================================== + + case PlasmaGeometryModelType.INDUCTANCE_SCALING_X_POINT: + # Use input triang, self.data.physics.ind_plasma_internal_norm values + # self.data.physics.kappa found from self.data.physics.aspect ratio and + # plasma internal inductance li(3) + self.data.physics.kappa = ( + 1.09e0 + 0.26e0 / self.data.physics.ind_plasma_internal_norm + ) * (1.5e0 / self.data.physics.aspect) ** 0.4e0 + + self.data.physics.kappa95 = self.data.physics.kappa / 1.12e0 + self.data.physics.triang95 = self.data.physics.triang / 1.50e0 + + # ====================================================================== + + case PlasmaGeometryModelType.CREATE_DATA_EU_DEMO_X_POINT: + # self.data.physics.kappa95 found from self.data.physics.aspect ratio and + # stability margin Based on fit to CREATE data. ref Issue #1399 + # valid for EU-DEMO like machine - self.data.physics.aspect + # ratio 2.6 - 3.6 + # Model updated see Issue #1648 + a = 3.68436807e0 + b = -0.27706527e0 + c = 0.87040251e0 + d = -18.83740952e0 + e = -0.27267618e0 + f = 20.5141261e0 - if self.data.physics.kappa95 > 1.77: - ratio = 1.77 / self.data.physics.kappa95 - corner_fudge = 0.3 * (self.data.physics.kappa95 - 1.77) / ratio self.data.physics.kappa95 = ( - self.data.physics.kappa95 ** (ratio) + corner_fudge + -d + - c * self.data.physics.aspect + - np.sqrt( + (c**2.0e0 - 4.0e0 * a * b) * self.data.physics.aspect**2.0e0 + + (2.0e0 * d * c - 4.0e0 * a * e) * self.data.physics.aspect + + d**2.0e0 + - 4.0e0 * a * f + + 4.0e0 * a * self.data.physics.m_s_limit + ) + ) / (2.0e0 * a) + + if self.data.physics.kappa95 > 1.77: + ratio = 1.77 / self.data.physics.kappa95 + corner_fudge = 0.3 * (self.data.physics.kappa95 - 1.77) / ratio + self.data.physics.kappa95 = ( + self.data.physics.kappa95 ** (ratio) + corner_fudge + ) + + self.data.physics.kappa = 1.12e0 * self.data.physics.kappa95 + self.data.physics.triang95 = self.data.physics.triang / 1.50e0 + + # ====================================================================== + + case PlasmaGeometryModelType.MENARD_2016_X_POINT: + # See Issue #1439 + # self.data.physics.triang is an input + # self.data.physics.kappa found from self.data.physics.aspect ratio + # scaling on p32 of Menard: Menard, et al. "Fusion Nuclear Science + # Facilities and Pilot Plants Based on the Spherical Tokamak." + # Nucl. Fusion, 2016, 44. + + self.data.physics.kappa = 0.95e0 * ( + 1.9e0 + 1.9e0 / self.data.physics.aspect**1.4e0 ) + self.data.physics.kappa95 = self.data.physics.kappa / 1.12e0 + self.data.physics.triang95 = self.data.physics.triang / 1.50e0 - self.data.physics.kappa = 1.12e0 * self.data.physics.kappa95 - self.data.physics.triang95 = self.data.physics.triang / 1.50e0 - - # ====================================================================== - - if ( - self.data.physics.i_plasma_geometry - == PlasmaGeometryModelType.MENARD_2016_X_POINT - ): - # See Issue #1439 - # self.data.physics.triang is an input - # self.data.physics.kappa found from self.data.physics.aspect ratio scaling - # on p32 of Menard: Menard, et al. "Fusion Nuclear Science Facilities - # and Pilot Plants Based on the Spherical Tokamak." Nucl. Fusion, 2016, 44. - - self.data.physics.kappa = 0.95e0 * ( - 1.9e0 + 1.9e0 / self.data.physics.aspect**1.4e0 - ) - self.data.physics.kappa95 = self.data.physics.kappa / 1.12e0 - self.data.physics.triang95 = self.data.physics.triang / 1.50e0 + # ====================================================================== - # ====================================================================== + case PlasmaGeometryModelType.MENARD_1997_X_POINT: + # self.data.physics.triang is an input + # self.data.physics.kappa found from self.data.physics.aspect ratio + # scaling from J.E. Menard et al 1997 Nucl. Fusion 37 595 and + # assume max controllable kappa and assume lᵢ(3) is held constant - if ( - self.data.physics.i_plasma_geometry - == PlasmaGeometryModelType.MENARD_1997_X_POINT - ): - # self.data.physics.triang is an input - # self.data.physics.kappa found from self.data.physics.aspect ratio scaling - # from J.E. Menard et al 1997 Nucl. Fusion 37 595 and - # assume max controllable kappa and assume lᵢ(3) is held constant + self.data.physics.kappa = ( + 2.93e0 * (1.8e0 / self.data.physics.aspect) ** 0.4e0 + ) - self.data.physics.kappa = ( - 2.93e0 * (1.8e0 / self.data.physics.aspect) ** 0.4e0 - ) + self.data.physics.kappa95 = self.data.physics.kappa / 1.12e0 + self.data.physics.triang95 = self.data.physics.triang / 1.50e0 - self.data.physics.kappa95 = self.data.physics.kappa / 1.12e0 - self.data.physics.triang95 = self.data.physics.triang / 1.50e0 + case _: + raise ProcessValueError( + "Illegal value for plasma geometry model type", + i_plasma_geom=self.data.physics.i_plasma_geom, + ) # ====================================================================== @@ -447,20 +453,20 @@ def run(self): # Find parameters of arcs describing plasma surfaces xi, thetai, xo, thetao = self.plasma_angles_arcs( - self.data.physics.rminor, - self.data.physics.kappa, - self.data.physics.triang, + a=self.data.physics.rminor, + kappa=self.data.physics.kappa, + triang=self.data.physics.triang, ) # Surface area - inboard and outboard. These are not given by Sauter but # the outboard area is required by DCLL and divertor xsi, xso = self.plasma_surface_area( - self.data.physics.rmajor, - self.data.physics.rminor, - xi, - thetai, - xo, - thetao, + rmajor=self.data.physics.rmajor, + rminor=self.data.physics.rminor, + xi=xi, + thetai=thetai, + xo=xo, + thetao=thetao, ) self.data.physics.a_plasma_surface_outboard = xso @@ -469,7 +475,8 @@ def run(self): # i_plasma_current = 8 specifies use of the Sauter geometry as well as plasma # current. if ( - self.data.physics.i_plasma_current == 8 + PlasmaCurrentModel(self.data.physics.i_plasma_current) + == PlasmaCurrentModel.SAUTER_SCALING or self.data.physics.i_plasma_shape == PlasmaShapeModelType.SAUTER ): ( @@ -478,35 +485,35 @@ def run(self): self.data.physics.a_plasma_poloidal, self.data.physics.vol_plasma, ) = self.sauter_geometry( - self.data.physics.rminor, - self.data.physics.rmajor, - self.data.physics.kappa, - self.data.physics.triang, - self.data.physics.plasma_square, + a=self.data.physics.rminor, + r0=self.data.physics.rmajor, + kappa=self.data.physics.kappa, + triang=self.data.physics.triang, + square=self.data.physics.plasma_square, ) else: # Poloidal perimeter self.data.physics.len_plasma_poloidal = self.plasma_poloidal_perimeter( - xi, thetai, xo, thetao + xi=xi, thetai=thetai, xo=xo, thetao=thetao ) # Volume self.data.physics.vol_plasma = ( self.data.physics.f_vol_plasma * self.plasma_volume( - self.data.physics.rmajor, - self.data.physics.rminor, - xi, - thetai, - xo, - thetao, + rmajor=self.data.physics.rmajor, + rminor=self.data.physics.rminor, + xi=xi, + thetai=thetai, + xo=xo, + thetao=thetao, ) ) # Cross-sectional area self.data.physics.a_plasma_poloidal = self.plasma_cross_section( - xi, thetai, xo, thetao + xi=xi, thetai=thetai, xo=xo, thetao=thetao ) # Surface area - sum of inboard and outboard. @@ -525,37 +532,43 @@ def output(self): po.oheadr(self.outfile, "Plasma Geometry") if self.data.stellarator.istell == StellaratorModel.DISABLED: - if self.data.divertor.n_divertors == 0: - po.ocmmnt(self.outfile, "Plasma configuration = limiter") - elif self.data.divertor.n_divertors == 1: - po.ocmmnt(self.outfile, "Plasma configuration = single null divertor") - elif self.data.divertor.n_divertors == 2: - po.ocmmnt(self.outfile, "Plasma configuration = double null divertor") - else: - raise ProcessValueError( - "Illegal value of n_divertors", - n_divertors=self.data.divertor.n_divertors, - ) + match self.data.divertor.n_divertors: + case 0: + po.ocmmnt(self.outfile, "Plasma configuration = limiter") + case 1: + po.ocmmnt( + self.outfile, "Plasma configuration = single null divertor" + ) + case 2: + po.ocmmnt( + self.outfile, "Plasma configuration = double null divertor" + ) + case _: + raise ProcessValueError( + "Illegal value of n_divertors", + n_divertors=self.data.divertor.n_divertors, + ) else: po.ocmmnt(self.outfile, "Plasma configuration = stellarator") if self.data.stellarator.istell == StellaratorModel.DISABLED: - if self.data.physics.itart == 0: - self.data.physics.itart_r = self.data.physics.itart - po.ovarre( - self.outfile, - "Tokamak aspect ratio = Conventional, itart = 0", - "(itart)", - self.data.physics.itart_r, - ) - elif self.data.physics.itart == 1: - self.data.physics.itart_r = self.data.physics.itart - po.ovarre( - self.outfile, - "Tokamak aspect ratio = Spherical, itart = 1", - "(itart)", - self.data.physics.itart_r, - ) + match self.data.physics.itart: + case 0: + self.data.physics.itart_r = self.data.physics.itart + po.ovarre( + self.outfile, + "Tokamak aspect ratio = Conventional, itart = 0", + "(itart)", + self.data.physics.itart_r, + ) + case 1: + self.data.physics.itart_r = self.data.physics.itart + po.ovarre( + self.outfile, + "Tokamak aspect ratio = Spherical, itart = 1", + "(itart)", + self.data.physics.itart_r, + ) po.ovarre( self.outfile, diff --git a/process/models/physics/profiles.py b/process/models/physics/profiles.py index e05afdfa8a..67af404cff 100644 --- a/process/models/physics/profiles.py +++ b/process/models/physics/profiles.py @@ -306,72 +306,66 @@ def set_pedestal_and_separatrix_values(self): """Sets the pedestal and separatrix density values based on the user input or greenwald fraction method. """ - i_nd_plasma_pedestal_separatrix = DensityProfilePedestalType( + match DensityProfilePedestalType( self.data.physics.i_nd_plasma_pedestal_separatrix - ) + ): + case DensityProfilePedestalType.USER_INPUT: + self.data.physics.f_nd_plasma_pedestal_greenwald = ( + self.data.physics.nd_plasma_pedestal_electron + / ( + PlasmaDensityLimit.calculate_greenwald_density_limit( + c_plasma=self.data.physics.plasma_current, + rminor=self.data.physics.rminor, + ) + ) + ) - if i_nd_plasma_pedestal_separatrix == DensityProfilePedestalType.USER_INPUT: - self.data.physics.f_nd_plasma_pedestal_greenwald = ( - self.data.physics.nd_plasma_pedestal_electron - / ( - PlasmaDensityLimit.calculate_greenwald_density_limit( + self.data.physics.f_nd_plasma_separatrix_greenwald = ( + self.data.physics.nd_plasma_separatrix_electron + / ( + PlasmaDensityLimit.calculate_greenwald_density_limit( + c_plasma=self.data.physics.plasma_current, + rminor=self.data.physics.rminor, + ) + ) + ) + case DensityProfilePedestalType.GREENWALD_FRACTION: + self.data.physics.nd_plasma_pedestal_electron = ( + self.data.physics.f_nd_plasma_pedestal_greenwald + * PlasmaDensityLimit.calculate_greenwald_density_limit( c_plasma=self.data.physics.plasma_current, rminor=self.data.physics.rminor, ) ) - ) - - self.data.physics.f_nd_plasma_separatrix_greenwald = ( - self.data.physics.nd_plasma_separatrix_electron - / ( - PlasmaDensityLimit.calculate_greenwald_density_limit( + self.data.physics.nd_plasma_separatrix_electron = ( + self.data.physics.f_nd_plasma_separatrix_greenwald + * PlasmaDensityLimit.calculate_greenwald_density_limit( c_plasma=self.data.physics.plasma_current, rminor=self.data.physics.rminor, ) ) - ) - elif ( - i_nd_plasma_pedestal_separatrix - == DensityProfilePedestalType.GREENWALD_FRACTION - ): - self.data.physics.nd_plasma_pedestal_electron = ( - self.data.physics.f_nd_plasma_pedestal_greenwald - * PlasmaDensityLimit.calculate_greenwald_density_limit( - c_plasma=self.data.physics.plasma_current, - rminor=self.data.physics.rminor, - ) - ) - self.data.physics.nd_plasma_separatrix_electron = ( - self.data.physics.f_nd_plasma_separatrix_greenwald - * PlasmaDensityLimit.calculate_greenwald_density_limit( - c_plasma=self.data.physics.plasma_current, - rminor=self.data.physics.rminor, - ) - ) def set_physics_variables(self): """Calculates and sets physics variables required for the profile.""" - if ( - PlasmaProfileShapeType(self.data.physics.i_plasma_pedestal) - == PlasmaProfileShapeType.PARABOLIC_PROFILE + match DensityProfilePedestalType( + self.data.physics.i_nd_plasma_pedestal_separatrix ): - self.data.physics.nd_plasma_electron_on_axis = ( - self.calculate_parabolic_profile_on_axis_density( + case PlasmaProfileShapeType.PARABOLIC_PROFILE: + self.data.physics.nd_plasma_electron_on_axis = ( + self.calculate_parabolic_profile_on_axis_density( + nd_vol_average=self.data.physics.nd_plasma_electrons_vol_avg, + alphan=self.data.physics.alphan, + ) + ) + + case PlasmaProfileShapeType.PEDESTAL_PROFILE: + self.data.physics.nd_plasma_electron_on_axis = self.calculate_pedestal_profile_on_axis_density( # noqa: E501 + radius_plasma_pedestal_density_norm=self.data.physics.radius_plasma_pedestal_density_norm, + nd_pedestal=self.data.physics.nd_plasma_pedestal_electron, + nd_separatrix=self.data.physics.nd_plasma_separatrix_electron, nd_vol_average=self.data.physics.nd_plasma_electrons_vol_avg, alphan=self.data.physics.alphan, ) - ) - elif ( - PlasmaProfileShapeType(self.data.physics.i_plasma_pedestal) - == PlasmaProfileShapeType.PEDESTAL_PROFILE - ): - self.data.physics.nd_plasma_electron_on_axis = self.calculate_pedestal_profile_on_axis_density( # noqa: E501 - radius_plasma_pedestal_density_norm=self.data.physics.radius_plasma_pedestal_density_norm, - nd_pedestal=self.data.physics.nd_plasma_pedestal_electron, - nd_separatrix=self.data.physics.nd_plasma_separatrix_electron, - nd_vol_average=self.data.physics.nd_plasma_electrons_vol_avg, - alphan=self.data.physics.alphan, - ) self.data.physics.nd_plasma_ions_on_axis = ( self.data.physics.nd_plasma_ions_total_vol_avg / self.data.physics.nd_plasma_electrons_vol_avg @@ -555,28 +549,21 @@ def calculate_parabolic_profile_on_axis_temperature( def set_physics_variables(self): """Calculates and sets physics variables required for the temperature profile.""" - if ( - PlasmaProfileShapeType(self.data.physics.i_plasma_pedestal) - == PlasmaProfileShapeType.PARABOLIC_PROFILE - ): - self.data.physics.temp_plasma_electron_on_axis_kev = ( - self.calculate_parabolic_profile_on_axis_temperature( + match PlasmaProfileShapeType(self.data.physics.i_plasma_pedestal): + case PlasmaProfileShapeType.PARABOLIC_PROFILE: + self.data.physics.temp_plasma_electron_on_axis_kev = self.calculate_parabolic_profile_on_axis_temperature( # noqa: E501 temp_vol_avg_kev=self.data.physics.temp_plasma_electron_vol_avg_kev, alphat=self.data.physics.alphat, ) - ) - elif ( - PlasmaProfileShapeType(self.data.physics.i_plasma_pedestal) - == PlasmaProfileShapeType.PEDESTAL_PROFILE - ): - self.data.physics.temp_plasma_electron_on_axis_kev = self.calculate_pedestal_profile_on_axis_temperature( # noqa: E501 - radius_plasma_pedestal_temp_norm=self.data.physics.radius_plasma_pedestal_temp_norm, - temp_pedestal_kev=self.data.physics.temp_plasma_pedestal_kev, - temp_separatrix_kev=self.data.physics.temp_plasma_separatrix_kev, - temp_vol_avg_kev=self.data.physics.temp_plasma_electron_vol_avg_kev, - alphat=self.data.physics.alphat, - tbeta=self.data.physics.tbeta, - ) + case PlasmaProfileShapeType.PEDESTAL_PROFILE: + self.data.physics.temp_plasma_electron_on_axis_kev = self.calculate_pedestal_profile_on_axis_temperature( # noqa: E501 + radius_plasma_pedestal_temp_norm=self.data.physics.radius_plasma_pedestal_temp_norm, + temp_pedestal_kev=self.data.physics.temp_plasma_pedestal_kev, + temp_separatrix_kev=self.data.physics.temp_plasma_separatrix_kev, + temp_vol_avg_kev=self.data.physics.temp_plasma_electron_vol_avg_kev, + alphat=self.data.physics.alphat, + tbeta=self.data.physics.tbeta, + ) self.data.physics.temp_plasma_ion_on_axis_kev = ( self.data.physics.temp_plasma_ion_vol_avg_kev diff --git a/process/models/physics/radiation_power.py b/process/models/physics/radiation_power.py index 0fba6e92d2..8ade84ca89 100644 --- a/process/models/physics/radiation_power.py +++ b/process/models/physics/radiation_power.py @@ -42,7 +42,7 @@ def calculate_radiation_powers( vol_plasma: float, data_structure: DataStructure, ) -> RadpwrData: - """Calculate the radiation powers in MW/m^3 by calling relevant routines. + """Calculate the radiation powers in [MW/m³] by calling relevant routines. This function computes the radiation power densities for the plasma, including impurity radiation and synchrotron radiation. It returns a dataclass containing @@ -53,52 +53,45 @@ def calculate_radiation_powers( plasma_profile : PlasmaProfile The parameterized temperature and density profiles of the plasma. nd_plasma_electron_on_axis : float - Central electron density (m^-3). + Central electron density [m⁻³]. rminor : float - Minor radius of the plasma (m). + Minor radius of the plasma [m]. b_plasma_toroidal_on_axis : float - Toroidal magnetic field (T). + Toroidal magnetic field [T]. aspect : float - Aspect ratio of the plasma. + Aspect ratio of the plasma [-]. alphan : float - Alpha parameter for density profile. + Alpha parameter for density profile [-]. alphat : float - Alpha parameter for temperature profile. + Alpha parameter for temperature profile [-]. tbeta : float - Beta parameter for temperature profile. + Beta parameter for temperature profile [-]. temp_plasma_electron_on_axis_kev : float - Central electron temperature (keV). + Central electron temperature [keV]. f_sync_reflect : float Fraction of synchrotron radiation reflected. rmajor : float - Major radius of the plasma (m). + Major radius of the plasma [m]. kappa : float - Elongation of the plasma. + Elongation of the plasma [-]. vol_plasma : float - Plasma volume (m^3). + Plasma volume [m³]. Returns ------- RadpwrData - A dataclass containing the following radiation power densities: - - pden_plasma_sync_mw (float): Synchrotron radiation power per unit - volume (MW/m^3). - - pden_plasma_core_rad_mw (float): Total core radiation power per unit - volume (MW/m^3). - - pden_plasma_outer_rad_mw (float): Edge radiation power per unit - volume (MW/m^3). - - pden_plasma_rad_mw (float): Total radiation power per unit volume (MW/m^3). + A dataclass containing the radiation power densities in [MW/m³]. References ---------- - - F. Albajar, J. Johner, and G. Granata, “Improved calculation of synchrotron - radiation losses in realistic tokamak plasmas,” Nuclear Fusion, vol. 41, - no. 6, pp. 665-678, Jun. 2001, - doi: https://doi.org/10.1088/0029-5515/41/6/301. - - - I. Fidone, G Giruzzi, and G. Granata, “Synchrotron radiation loss in tokamaks - of arbitrary geometry,” Nuclear Fusion, vol. 41, no. 12, pp. 1755-1758, - Dec. 2001, doi: https://doi.org/10.1088/0029-5515/41/12/102. + [1] F. Albajar, J. Johner, and G. Granata, “Improved calculation of synchrotron + radiation losses in realistic tokamak plasmas,” Nuclear Fusion, vol. 41, + no. 6, pp. 665-678, Jun. 2001, + doi: https://doi.org/10.1088/0029-5515/41/6/301. + + [2] I. Fidone, G Giruzzi, and G. Granata, “Synchrotron radiation loss in tokamaks + of arbitrary geometry,” Nuclear Fusion, vol. 41, no. 12, pp. 1755-1758, + Dec. 2001, doi: https://doi.org/10.1088/0029-5515/41/12/102. """ imp_rad = impurity.ImpurityRadiation(plasma_profile, data_structure) imp_rad.calculate_imprad() @@ -109,18 +102,18 @@ def calculate_radiation_powers( # Synchrotron radiation power/volume; assumed to be from core only. pden_plasma_sync_mw = psync_albajar_fidone( - nd_plasma_electron_on_axis, - rminor, - b_plasma_toroidal_on_axis, - aspect, - alphan, - alphat, - tbeta, - temp_plasma_electron_on_axis_kev, - f_sync_reflect, - rmajor, - kappa, - vol_plasma, + nd_plasma_electron_on_axis=nd_plasma_electron_on_axis, + rminor=rminor, + b_plasma_toroidal_on_axis=b_plasma_toroidal_on_axis, + aspect=aspect, + alphan=alphan, + alphat=alphat, + tbeta=tbeta, + temp_plasma_electron_on_axis_kev=temp_plasma_electron_on_axis_kev, + f_sync_reflect=f_sync_reflect, + rmajor=rmajor, + kappa=kappa, + vol_plasma=vol_plasma, ) # Total core radiation power/volume. @@ -132,10 +125,10 @@ def calculate_radiation_powers( pden_plasma_rad_mw = imp_rad.pden_impurity_rad_total_mw + pden_plasma_sync_mw return RadpwrData( - pden_plasma_sync_mw, - pden_plasma_core_rad_mw, - pden_plasma_outer_rad_mw, - pden_plasma_rad_mw, + pden_plasma_sync_mw=pden_plasma_sync_mw, + pden_plasma_core_rad_mw=pden_plasma_core_rad_mw, + pden_plasma_outer_rad_mw=pden_plasma_outer_rad_mw, + pden_plasma_rad_mw=pden_plasma_rad_mw, ) @@ -153,7 +146,7 @@ def psync_albajar_fidone( kappa: float, vol_plasma: float, ) -> float: - """Calculate the synchrotron radiation power in MW/m^3. + """Calculate the synchrotron radiation power in [MW/m³]. This function computes the synchrotron radiation power density for the plasma based on the plasma shape, major and minor radii, electron density, and temperature @@ -162,44 +155,44 @@ def psync_albajar_fidone( Parameters ---------- nd_plasma_electron_on_axis : float - Central electron density (m^-3). + Central electron density [m⁻³]. rminor : float - Minor radius of the plasma (m). + Minor radius of the plasma [m]. b_plasma_toroidal_on_axis : float - Toroidal magnetic field (T). + Toroidal magnetic field [T]. aspect : float - Aspect ratio of the plasma. + Aspect ratio of the plasma [-]. alphan : float - Alpha parameter for density profile. + Alpha parameter for density profile [-]. alphat : float - Alpha parameter for temperature profile. + Alpha parameter for temperature profile [-]. tbeta : float - Beta parameter for temperature profile. + Beta parameter for temperature profile [-]. temp_plasma_electron_on_axis_kev : float - Central electron temperature (keV). + Central electron temperature [keV]. f_sync_reflect : float - Fraction of synchrotron radiation reflected. + Fraction of synchrotron radiation reflected [-]. rmajor : float - Major radius of the plasma (m). + Major radius of the plasma [m]. kappa : float - Elongation of the plasma. + Elongation of the plasma [-]. vol_plasma : float - Plasma volume (m^3). + Plasma volume [m³]. Returns ------- float - Synchrotron radiation power per unit volume (MW/m^3). + Synchrotron radiation power per unit volume [MW/m³]. References ---------- - - F. Albajar, J. Johner, and G. Granata, “Improved calculation of synchrotron - radiation losses in realistic tokamak plasmas,” Nuclear Fusion, vol. 41, no. 6, - pp. 665-678, Jun. 2001, doi: https://doi.org/10.1088/0029-5515/41/6/301. + [1] F. Albajar, J. Johner, and G. Granata, “Improved calculation of synchrotron + radiation losses in realistic tokamak plasmas,” Nuclear Fusion, vol. 41, no. 6, + pp. 665-678, Jun. 2001, doi: https://doi.org/10.1088/0029-5515/41/6/301. - - I. Fidone, G Giruzzi, and G. Granata, “Synchrotron radiation loss in tokamaks of - arbitrary geometry,” Nuclear Fusion, vol. 41, no. 12, pp. 1755-1758, Dec. 2001, - doi: https://doi.org/10.1088/0029-5515/41/12/102. + [2] I. Fidone, G Giruzzi, and G. Granata, “Synchrotron radiation loss in tokamaks of + arbitrary geometry,” Nuclear Fusion, vol. 41, no. 12, pp. 1755-1758, Dec. 2001, + doi: https://doi.org/10.1088/0029-5515/41/12/102. """ # Variable names are created to closely match those from the reference papers. diff --git a/process/models/physics/scrape_off_layer.py b/process/models/physics/scrape_off_layer.py index b7ea9ebf40..a2072ef7b8 100644 --- a/process/models/physics/scrape_off_layer.py +++ b/process/models/physics/scrape_off_layer.py @@ -59,52 +59,29 @@ def run(self): ) # Set to user input if OutbordSOLPowerDecayLengthModel = 1/USER_INUT - - if ( - OutbordSOLPowerDecayLengthModel( - self.data.physics.i_len_sol_outboard_power_decay - ) - == OutbordSOLPowerDecayLengthModel.EICH_2013 - ): - self.data.physics.len_sol_outboard_power_decay = ( - self.data.physics.len_plasma_sol_eich13_power_decay - ) - elif ( - OutbordSOLPowerDecayLengthModel( - self.data.physics.i_len_sol_outboard_power_decay - ) - == OutbordSOLPowerDecayLengthModel.MAST_2014_1 - ): - self.data.physics.len_sol_outboard_power_decay = ( - self.data.physics.len_plasma_sol_mast14_power_decay_1 - ) - elif ( - OutbordSOLPowerDecayLengthModel( - self.data.physics.i_len_sol_outboard_power_decay - ) - == OutbordSOLPowerDecayLengthModel.MAST_2014_2 - ): - self.data.physics.len_sol_outboard_power_decay = ( - self.data.physics.len_plasma_sol_mast14_power_decay_2 - ) - elif ( - OutbordSOLPowerDecayLengthModel( - self.data.physics.i_len_sol_outboard_power_decay - ) - == OutbordSOLPowerDecayLengthModel.EICH_2011_JET + match OutbordSOLPowerDecayLengthModel( + self.data.physics.i_len_sol_outboard_power_decay ): - self.data.physics.len_sol_outboard_power_decay = ( - self.data.physics.len_plasma_sol_eich11_jet_power_decay - ) - elif ( - OutbordSOLPowerDecayLengthModel( - self.data.physics.i_len_sol_outboard_power_decay - ) - == OutbordSOLPowerDecayLengthModel.EICH_2011_JET_ASDEX - ): - self.data.physics.len_sol_outboard_power_decay = ( - self.data.physics.len_plasma_sol_eich11_jet_asdex_power_decay - ) + case OutbordSOLPowerDecayLengthModel.EICH_2013: + self.data.physics.len_sol_outboard_power_decay = ( + self.data.physics.len_plasma_sol_eich13_power_decay + ) + case OutbordSOLPowerDecayLengthModel.MAST_2014_1: + self.data.physics.len_sol_outboard_power_decay = ( + self.data.physics.len_plasma_sol_mast14_power_decay_1 + ) + case OutbordSOLPowerDecayLengthModel.MAST_2014_2: + self.data.physics.len_sol_outboard_power_decay = ( + self.data.physics.len_plasma_sol_mast14_power_decay_2 + ) + case OutbordSOLPowerDecayLengthModel.EICH_2011_JET: + self.data.physics.len_sol_outboard_power_decay = ( + self.data.physics.len_plasma_sol_eich11_jet_power_decay + ) + case OutbordSOLPowerDecayLengthModel.EICH_2011_JET_ASDEX: + self.data.physics.len_sol_outboard_power_decay = ( + self.data.physics.len_plasma_sol_eich11_jet_asdex_power_decay + ) self.data.physics.len_sol_inboard_power_decay = ( self.data.physics.f_len_sol_power_decay_inboard_outboard diff --git a/process/models/power.py b/process/models/power.py index 99a3f4d296..b1eb59308c 100644 --- a/process/models/power.py +++ b/process/models/power.py @@ -1958,118 +1958,109 @@ def plant_thermal_efficiency(self, eta_turbine: float) -> float: eta_turbine : """ - i_thermal_electric_conversion = ElectricConversionModelTypes( - self.data.fwbs.i_thermal_electric_conversion - ) i_blanket_type = BlktModelTypes(self.data.fwbs.i_blanket_type) - if i_thermal_electric_conversion == ElectricConversionModelTypes.CCFE_HCPB_VALUE: - # CCFE HCPB Model - if i_blanket_type == BlktModelTypes.CCFE_HCPB: - # HCPB, efficiency taken from M. Kovari 2016 - # "PROCESS": A systems code for fusion power plants - - # Part 2: Engineering - # https://www.sciencedirect.com/science/article/pii/S0920379616300072 - # Feedheat & reheat cycle assumed - eta_turbine = 0.411e0 - else: - logger.log(f"{'i_blanket_type is not equal to 1'}") + match ElectricConversionModelTypes(self.data.fwbs.i_thermal_electric_conversion): + case ElectricConversionModelTypes.CCFE_HCPB_VALUE: + # CCFE HCPB Model + if i_blanket_type == BlktModelTypes.CCFE_HCPB: + # HCPB, efficiency taken from M. Kovari 2016 + # "PROCESS": A systems code for fusion power plants - + # Part 2: Engineering + # https://www.sciencedirect.com/science/article/pii/S0920379616300072 + # Feedheat & reheat cycle assumed + eta_turbine = 0.411e0 + else: + logger.log(f"{'i_blanket_type is not equal to 1'}") # Etath from reference. Div power to primary - elif ( - i_thermal_electric_conversion - == ElectricConversionModelTypes.CCFE_HCPB_VALUE_WITH_DIVERTOR - ): - # CCFE HCPB Model - if self.data.fwbs.i_blanket_type == BlktModelTypes.CCFE_HCPB: - # HCPB, efficiency taken from M. Kovari 2016 - # "PROCESS": A systems code for fusion power plants - - # Part 2: Engineering - # https://www.sciencedirect.com/science/article/pii/S0920379616300072 - # Feedheat & reheat cycle assumed - eta_turbine = 0.411e0 - self.data.power.delta_eta - else: - logger.log(f"{'i_blanket_type is not equal to 1.'}") + case ElectricConversionModelTypes.CCFE_HCPB_VALUE_WITH_DIVERTOR: + # CCFE HCPB Model + if self.data.fwbs.i_blanket_type == BlktModelTypes.CCFE_HCPB: + # HCPB, efficiency taken from M. Kovari 2016 + # "PROCESS": A systems code for fusion power plants - + # Part 2: Engineering + # https://www.sciencedirect.com/science/article/pii/S0920379616300072 + # Feedheat & reheat cycle assumed + eta_turbine = 0.411e0 - self.data.power.delta_eta + else: + logger.log(f"{'i_blanket_type is not equal to 1.'}") # User input used, eta_turbine not changed - elif i_thermal_electric_conversion == ElectricConversionModelTypes.USER_INPUT: - return eta_turbine - # Do nothing + case ElectricConversionModelTypes.USER_INPUT: + return eta_turbine + # Do nothing # Steam Rankine cycle to be used - elif ( - i_thermal_electric_conversion - == ElectricConversionModelTypes.STEAM_RANKINE_CYCLE - ): - # CCFE HCPB Model - if self.data.fwbs.i_blanket_type == BlktModelTypes.CCFE_HCPB: - # If coolant is helium, the steam cycle is assumed to be superheated - # and a different correlation is used. The turbine inlet temperature - # is assumed to be 20 degrees below the primary coolant outlet - # temperature, as was stated for steam rankine cycle for Helium in - # M. Kovari 2016, "PROCESS": A systems code for fusion power plants - # - Part 2: Engineering - # https://www.sciencedirect.com/science/article/pii/S0920379616300072 - - # Superheated steam Rankine cycle correlation (C. Harrington) - # Range of validity: 657 K - # < self.data.heat_transport.temp_turbine_coolant_in < 915 K + case ElectricConversionModelTypes.STEAM_RANKINE_CYCLE: + # CCFE HCPB Model + if self.data.fwbs.i_blanket_type == BlktModelTypes.CCFE_HCPB: + # If coolant is helium, the steam cycle is assumed to be superheated + # and a different correlation is used. The turbine inlet temperature + # is assumed to be 20 degrees below the primary coolant outlet + # temperature, as was stated for steam rankine cycle for Helium in + # M. Kovari 2016, "PROCESS": A systems code for fusion power plants + # - Part 2: Engineering + # https://www.sciencedirect.com/science/article/pii/S0920379616300072 + + # Superheated steam Rankine cycle correlation (C. Harrington) + # Range of validity: 657 K + # < self.data.heat_transport.temp_turbine_coolant_in < 915 K + self.data.heat_transport.temp_turbine_coolant_in = ( + self.data.fwbs.temp_blkt_coolant_out - 20.0e0 + ) + if (self.data.heat_transport.temp_turbine_coolant_in < 657.0e0) or ( + self.data.heat_transport.temp_turbine_coolant_in > 915.0e0 + ): + logger.warning( + "Turbine temperature temp_turbine_coolant_in out of range " + f"of validity: " + f"{self.data.heat_transport.temp_turbine_coolant_in=}" + ) + + eta_turbine = ( + 0.1802e0 + * np.log(self.data.heat_transport.temp_turbine_coolant_in) + - 0.7823 + - self.data.power.delta_eta + ) + + else: + logger.log(f"{'i_blanket_type is not equal to 1.'}") + + # Supercritical CO2 cycle to be used + case ElectricConversionModelTypes.SUPERCRITICAL_CO2_CYCLE: + # The same temperature/efficiency correlation is used regardless of + # primary coolant choice. The turbine inlet temperature is assumed to + # be 20 degrees below the primary coolant outlet temperature. + # s-CO2 can in theory be used for both helium and water primary coolants + # so no differentiation is made, but for water the efficiency will be + # very low and the correlation will reflect this. + + # Supercritical CO2 cycle correlation (C. Harrington) + # Range of validity: 408 K + # < self.data.heat_transport.temp_turbine_coolant_in < 1023 K self.data.heat_transport.temp_turbine_coolant_in = ( self.data.fwbs.temp_blkt_coolant_out - 20.0e0 ) - if (self.data.heat_transport.temp_turbine_coolant_in < 657.0e0) or ( - self.data.heat_transport.temp_turbine_coolant_in > 915.0e0 + if (self.data.heat_transport.temp_turbine_coolant_in < 408.0e0) or ( + self.data.heat_transport.temp_turbine_coolant_in > 1023.0e0 ): logger.warning( "Turbine temperature temp_turbine_coolant_in out of range " - f"of validity: " + "of validity: " f"{self.data.heat_transport.temp_turbine_coolant_in=}" ) eta_turbine = ( - 0.1802e0 * np.log(self.data.heat_transport.temp_turbine_coolant_in) - - 0.7823 - - self.data.power.delta_eta + 0.4347e0 * np.log(self.data.heat_transport.temp_turbine_coolant_in) + - 2.5043e0 ) - else: - logger.log(f"{'i_blanket_type is not equal to 1.'}") - - # Supercritical CO2 cycle to be used - elif ( - i_thermal_electric_conversion - == ElectricConversionModelTypes.SUPERCRITICAL_CO2_CYCLE - ): - # The same temperature/efficiency correlation is used regardless of - # primary coolant choice. The turbine inlet temperature is assumed to - # be 20 degrees below the primary coolant outlet temperature. - # s-CO2 can in theory be used for both helium and water primary coolants - # so no differentiation is made, but for water the efficiency will be - # very low and the correlation will reflect this. - - # Supercritical CO2 cycle correlation (C. Harrington) - # Range of validity: 408 K - # < self.data.heat_transport.temp_turbine_coolant_in < 1023 K - self.data.heat_transport.temp_turbine_coolant_in = ( - self.data.fwbs.temp_blkt_coolant_out - 20.0e0 - ) - if (self.data.heat_transport.temp_turbine_coolant_in < 408.0e0) or ( - self.data.heat_transport.temp_turbine_coolant_in > 1023.0e0 - ): + case _: logger.warning( - "Turbine temperature temp_turbine_coolant_in out of range " - f"of validity: {self.data.heat_transport.temp_turbine_coolant_in=}" + "i_thermal_electric_conversion does not appear to have a value" + "within its range (0-4)" ) - - eta_turbine = ( - 0.4347e0 * np.log(self.data.heat_transport.temp_turbine_coolant_in) - - 2.5043e0 - ) - - else: - logger.warning( - "i_thermal_electric_conversion does not appear to have a value" - "within its range (0-4)" - ) return eta_turbine def plant_thermal_efficiency_2(self, etath_liq: float) -> float: diff --git a/process/models/tfcoil/base.py b/process/models/tfcoil/base.py index 3303657618..1c4371d3f7 100644 --- a/process/models/tfcoil/base.py +++ b/process/models/tfcoil/base.py @@ -306,18 +306,19 @@ def tf_global_geometry( tan_theta_coil = np.tan(rad_tf_coil_inboard_toroidal_half) # TF coil inboard legs total mid-plane cross-section area [m^2] - if i_tf_case_geom == TFPlasmaCaseType.CIRCULAR: - # Circular plasma facing front case - a_tf_inboard_total = np.pi * (r_tf_inboard_out**2 - r_tf_inboard_in**2) - elif i_tf_case_geom == TFPlasmaCaseType.STRAIGHT: - # Straight plasma facing front case - a_tf_inboard_total = ( - n_tf_coils - * np.sin(rad_tf_coil_inboard_toroidal_half) - * np.cos(rad_tf_coil_inboard_toroidal_half) - * r_tf_inboard_out**2 - - np.pi * r_tf_inboard_in**2 - ) + match TFPlasmaCaseType(i_tf_case_geom): + case TFPlasmaCaseType.CIRCULAR: + # Circular plasma facing front case + a_tf_inboard_total = np.pi * (r_tf_inboard_out**2 - r_tf_inboard_in**2) + case TFPlasmaCaseType.STRAIGHT: + # Straight plasma facing front case + a_tf_inboard_total = ( + n_tf_coils + * np.sin(rad_tf_coil_inboard_toroidal_half) + * np.cos(rad_tf_coil_inboard_toroidal_half) + * r_tf_inboard_out**2 + - np.pi * r_tf_inboard_in**2 + ) # TF coil width in toroidal direction at inboard leg outer edge [m] @@ -522,87 +523,93 @@ def tf_coil_shape_inner( tfa = np.zeros(4) tfb = np.zeros(4) - if i_tf_shape == TFCoilShapeModel.D_SHAPE and itart == 0: - # PROCESS D-shape parameterisation - r_tf_arc[0] = r_tf_inboard_out - r_tf_arc[1] = rmajor - 0.2e0 * rminor - r_tf_arc[2] = r_tf_outboard_in - r_tf_arc[3] = r_tf_arc[1] - r_tf_arc[4] = r_tf_arc[0] - - if i_single_null == DivertorNumberModels.DOUBLE_NULL: - z_tf_arc[0] = FSTRAIGHT * z_tf_inside_half - z_tf_arc[1] = z_tf_inside_half + match TFCoilShapeModel(i_tf_shape): + case TFCoilShapeModel.D_SHAPE if itart == 0: + # PROCESS D-shape parameterisation + r_tf_arc[0] = r_tf_inboard_out + r_tf_arc[1] = rmajor - 0.2e0 * rminor + r_tf_arc[2] = r_tf_outboard_in + r_tf_arc[3] = r_tf_arc[1] + r_tf_arc[4] = r_tf_arc[0] + + match DivertorNumberModels(i_single_null): + case DivertorNumberModels.DOUBLE_NULL: + z_tf_arc[0] = FSTRAIGHT * z_tf_inside_half + z_tf_arc[1] = z_tf_inside_half + z_tf_arc[2] = 0 + z_tf_arc[3] = -z_tf_inside_half + z_tf_arc[4] = -FSTRAIGHT * z_tf_inside_half + case DivertorNumberModels.SINGLE_NULL: + z_tf_arc[0] = FSTRAIGHT * (z_tf_top - dr_tf_inboard) + z_tf_arc[1] = z_tf_top - dr_tf_inboard + z_tf_arc[2] = 0 + z_tf_arc[3] = -z_tf_inside_half + z_tf_arc[4] = -FSTRAIGHT * z_tf_inside_half + + len_tf_coil = z_tf_arc[0] - z_tf_arc[4] + + for ii in range(4): + tfa[ii] = abs(r_tf_arc[ii + 1] - r_tf_arc[ii]) + tfb[ii] = abs(z_tf_arc[ii + 1] - z_tf_arc[ii]) + aa = tfa[ii] + 0.5e0 * dr_tf_inboard + bb = tfb[ii] + 0.5e0 * dr_tf_inboard + len_tf_coil += 0.25e0 * self.circumference(aa, bb) + + case TFCoilShapeModel.D_SHAPE if itart == 1: + # Centrepost with D-shaped + r_tf_arc[0] = r_cp_top + r_tf_arc[1] = rmajor - 0.2e0 * rminor + r_tf_arc[2] = r_tf_outboard_in + r_tf_arc[3] = r_tf_arc[1] + r_tf_arc[4] = r_tf_arc[0] + + z_tf_arc[0] = z_tf_top - dr_tf_inboard + z_tf_arc[1] = z_tf_top - dr_tf_inboard z_tf_arc[2] = 0 z_tf_arc[3] = -z_tf_inside_half - z_tf_arc[4] = -FSTRAIGHT * z_tf_inside_half - else: - z_tf_arc[0] = FSTRAIGHT * (z_tf_top - dr_tf_inboard) + z_tf_arc[4] = -z_tf_inside_half + + len_tf_coil = 2 * (r_tf_arc[1] - r_tf_arc[0]) + + for ii in range(1, 3): + tfa[ii] = abs(r_tf_arc[ii + 1] - r_tf_arc[ii]) + tfb[ii] = abs(z_tf_arc[ii + 1] - z_tf_arc[ii]) + aa = tfa[ii] + 0.5e0 * dr_tf_outboard + bb = tfb[ii] + 0.5e0 * dr_tf_outboard + len_tf_coil += 0.25e0 * self.circumference(aa, bb) + + case TFCoilShapeModel.PICTURE_FRAME: + # Picture frame coil + match itart: + case 0: + r_tf_arc[0] = r_tf_inboard_out + case 1: + r_tf_arc[0] = r_cp_top + r_tf_arc[1] = r_tf_outboard_in + r_tf_arc[2] = r_tf_arc[1] + r_tf_arc[3] = r_tf_arc[1] + r_tf_arc[4] = r_tf_arc[0] + + z_tf_arc[0] = z_tf_top - dr_tf_inboard z_tf_arc[1] = z_tf_top - dr_tf_inboard z_tf_arc[2] = 0 z_tf_arc[3] = -z_tf_inside_half - z_tf_arc[4] = -FSTRAIGHT * z_tf_inside_half - - len_tf_coil = z_tf_arc[0] - z_tf_arc[4] - - for ii in range(4): - tfa[ii] = abs(r_tf_arc[ii + 1] - r_tf_arc[ii]) - tfb[ii] = abs(z_tf_arc[ii + 1] - z_tf_arc[ii]) - aa = tfa[ii] + 0.5e0 * dr_tf_inboard - bb = tfb[ii] + 0.5e0 * dr_tf_inboard - len_tf_coil += 0.25e0 * self.circumference(aa, bb) - - elif i_tf_shape == TFCoilShapeModel.D_SHAPE and itart == 1: - # Centrepost with D-shaped - r_tf_arc[0] = r_cp_top - r_tf_arc[1] = rmajor - 0.2e0 * rminor - r_tf_arc[2] = r_tf_outboard_in - r_tf_arc[3] = r_tf_arc[1] - r_tf_arc[4] = r_tf_arc[0] - - z_tf_arc[0] = z_tf_top - dr_tf_inboard - z_tf_arc[1] = z_tf_top - dr_tf_inboard - z_tf_arc[2] = 0 - z_tf_arc[3] = -z_tf_inside_half - z_tf_arc[4] = -z_tf_inside_half - - len_tf_coil = 2 * (r_tf_arc[1] - r_tf_arc[0]) - - for ii in range(1, 3): - tfa[ii] = abs(r_tf_arc[ii + 1] - r_tf_arc[ii]) - tfb[ii] = abs(z_tf_arc[ii + 1] - z_tf_arc[ii]) - aa = tfa[ii] + 0.5e0 * dr_tf_outboard - bb = tfb[ii] + 0.5e0 * dr_tf_outboard - len_tf_coil += 0.25e0 * self.circumference(aa, bb) - - elif i_tf_shape == TFCoilShapeModel.PICTURE_FRAME: - # Picture frame coil - if itart == 0: - r_tf_arc[0] = r_tf_inboard_out - if itart == 1: - r_tf_arc[0] = r_cp_top - r_tf_arc[1] = r_tf_outboard_in - r_tf_arc[2] = r_tf_arc[1] - r_tf_arc[3] = r_tf_arc[1] - r_tf_arc[4] = r_tf_arc[0] - - z_tf_arc[0] = z_tf_top - dr_tf_inboard - z_tf_arc[1] = z_tf_top - dr_tf_inboard - z_tf_arc[2] = 0 - z_tf_arc[3] = -z_tf_inside_half - z_tf_arc[4] = -z_tf_inside_half - - if itart == 0: - len_tf_coil = 2.0e0 * ( - 2.0e0 * z_tf_inside_half - + dr_tf_inboard - + r_tf_outboard_mid - - r_tf_inboard_mid - ) - elif itart == 1: - len_tf_coil = ( - z_tf_inside_half + z_tf_top + 2.0e0 * (r_tf_outboard_mid - r_cp_top) - ) + z_tf_arc[4] = -z_tf_inside_half + + match itart: + case 0: + len_tf_coil = 2.0e0 * ( + 2.0e0 * z_tf_inside_half + + dr_tf_inboard + + r_tf_outboard_mid + - r_tf_inboard_mid + ) + case 1: + len_tf_coil = ( + z_tf_inside_half + + z_tf_top + + 2.0e0 * (r_tf_outboard_mid - r_cp_top) + ) return len_tf_coil, tfa, tfb, r_tf_arc, z_tf_arc @@ -689,22 +696,27 @@ def output_general_tf_info(self) -> None: "(i_tf_shape)", self.data.tfcoil.i_tf_shape, ) - if self.data.tfcoil.i_tf_shape == TFCoilShapeModel.D_SHAPE: - po.oblnkl(self.outfile) - po.ocmmnt(self.outfile, "D-shape coil, inner surface shape approximated by") - po.ocmmnt( - self.outfile, - "by a straight segment and elliptical arcs between the " - "following points:", - ) - po.oblnkl(self.outfile) - elif self.data.tfcoil.i_tf_shape == TFCoilShapeModel.PICTURE_FRAME: - po.oblnkl(self.outfile) - po.ocmmnt(self.outfile, "Picture frame coil, inner surface approximated by") - po.ocmmnt( - self.outfile, "by a straight segment between the following points:" - ) - po.oblnkl(self.outfile) + match TFCoilShapeModel(self.data.tfcoil.i_tf_shape): + case TFCoilShapeModel.D_SHAPE: + po.oblnkl(self.outfile) + po.ocmmnt( + self.outfile, "D-shape coil, inner surface shape approximated by" + ) + po.ocmmnt( + self.outfile, + "by a straight segment and elliptical arcs between the " + "following points:", + ) + po.oblnkl(self.outfile) + case TFCoilShapeModel.PICTURE_FRAME: + po.oblnkl(self.outfile) + po.ocmmnt( + self.outfile, "Picture frame coil, inner surface approximated by" + ) + po.ocmmnt( + self.outfile, "by a straight segment between the following points:" + ) + po.oblnkl(self.outfile) po.write(self.outfile, " Point r(m) z(m)") for ii in range(5): @@ -770,37 +782,35 @@ def output_general_tf_info(self) -> None: "(i_tf_bucking)", self.data.tfcoil.i_tf_bucking, ) + match self.data.tfcoil.i_tf_bucking: + case 0: + po.ocmmnt(self.outfile, " -> No support structure") + case 1: + if self.data.tfcoil.i_tf_sup == TFConductorModel.SUPERCONDUCTING: + po.ocmmnt(self.outfile, " -> Steel casing") + elif ( + abs(self.data.tfcoil.eyoung_res_tf_buck - 205.0e9) + < np.finfo(float(self.data.tfcoil.eyoung_res_tf_buck)).eps + ): + po.ocmmnt(self.outfile, " -> Steel bucking cylinder") + else: + po.ocmmnt(self.outfile, " -> Bucking cylinder") - if self.data.tfcoil.i_tf_bucking == 0: - po.ocmmnt(self.outfile, " -> No support structure") - elif self.data.tfcoil.i_tf_bucking == 1: - if self.data.tfcoil.i_tf_sup == TFConductorModel.SUPERCONDUCTING: - po.ocmmnt(self.outfile, " -> Steel casing") - elif ( - abs(self.data.tfcoil.eyoung_res_tf_buck - 205.0e9) - < np.finfo(float(self.data.tfcoil.eyoung_res_tf_buck)).eps + case 2 | 3 if ( + self.data.build.i_tf_inside_cs == TFCSRadialConfiguration.TF_INSIDE_CS ): - po.ocmmnt(self.outfile, " -> Steel bucking cylinder") - else: - po.ocmmnt(self.outfile, " -> Bucking cylinder") - - elif ( - self.data.tfcoil.i_tf_bucking in {2, 3} - and self.data.build.i_tf_inside_cs == TFCSRadialConfiguration.TF_INSIDE_CS - ): - po.ocmmnt( - self.outfile, - " -> TF in contact with dr_bore filler support " - "(bucked and weged design)", - ) + po.ocmmnt( + self.outfile, + " -> TF in contact with dr_bore filler support " + "(bucked and weged design)", + ) - elif ( - self.data.tfcoil.i_tf_bucking in {2, 3} - and self.data.build.i_tf_inside_cs == TFCSRadialConfiguration.TF_OUTSIDE_CS - ): - po.ocmmnt( - self.outfile, " -> TF in contact with CS (bucked and weged design)" - ) + case 2 | 3 if ( + self.data.build.i_tf_inside_cs == TFCSRadialConfiguration.TF_OUTSIDE_CS + ): + po.ocmmnt( + self.outfile, " -> TF in contact with CS (bucked and wedged design)" + ) po.oblnkl(self.outfile) po.ocmmnt(self.outfile, "----------------------------") @@ -1241,6 +1251,11 @@ def cntrpst(self, output: bool = False): This subroutine evaluates the parameters of the centrepost for a tight aspect ratio tokamak. The centrepost is assumed to be tapered, i.e. narrowest on the midplane (z=0). + + Raises + ------ + ProcessValueError + If the TART centrepost parameters are not properly defined. """ # Vertical distance from the midplane to the top of the tapered section [m] if self.data.physics.itart == 1: @@ -1296,57 +1311,60 @@ def cntrpst(self, output: bool = False): # ********************************************** # Water coollant # -------------- - if self.data.tfcoil.i_tf_sup == TFConductorModel.WATER_COOLED_COPPER: - # Water coolant physical properties - coolant_density = constants.DENH2O - coolant_cp = constants.CPH2O - coolant_visco = constants.MUH2O - coolant_th_cond = constants.KH2O - - # Mass flow rate [kg/s] - cool_mass_flow = ( - acool * coolant_density * self.data.tfcoil.vel_cp_coolant_midplane - ) + match TFConductorModel(self.data.tfcoil.i_tf_sup): + case TFConductorModel.WATER_COOLED_COPPER: + # Water coolant physical properties + coolant_density = constants.DENH2O + coolant_cp = constants.CPH2O + coolant_visco = constants.MUH2O + coolant_th_cond = constants.KH2O + + # Mass flow rate [kg/s] + cool_mass_flow = ( + acool * coolant_density * self.data.tfcoil.vel_cp_coolant_midplane + ) - # Water temperature rise - self.data.tfcoil.dtemp_cp_coolant = ptot / (cool_mass_flow * coolant_cp) + # Water temperature rise + self.data.tfcoil.dtemp_cp_coolant = ptot / (cool_mass_flow * coolant_cp) - # Constant coolant velocity - vcool_max = self.data.tfcoil.vel_cp_coolant_midplane - # -------------- + # Constant coolant velocity + vcool_max = self.data.tfcoil.vel_cp_coolant_midplane + # -------------- - # Helium coolant - # -------------- - elif self.data.tfcoil.i_tf_sup == TFConductorModel.HELIUM_COOLED_ALUMINIUM: - # Inlet coolant density [kg/m3] - coolant_density = self.he_density(self.data.tfcoil.temp_cp_coolant_inlet) + # Helium coolant + # -------------- + case TFConductorModel.HELIUM_COOLED_ALUMINIUM: + # Inlet coolant density [kg/m3] + coolant_density = self.he_density(self.data.tfcoil.temp_cp_coolant_inlet) - # Mass flow rate [kg/s] - cool_mass_flow = ( - acool * coolant_density * self.data.tfcoil.vel_cp_coolant_midplane - ) + # Mass flow rate [kg/s] + cool_mass_flow = ( + acool * coolant_density * self.data.tfcoil.vel_cp_coolant_midplane + ) - # Infinitesimal power deposition used in the integral - dptot = ptot / n_tcool_it + # Infinitesimal power deposition used in the integral + dptot = ptot / n_tcool_it - tcool_calc = copy.copy(self.data.tfcoil.temp_cp_coolant_inlet) # K - for _i in range(n_tcool_it): - # Thermal capacity Cp - coolant_cp = self.he_cp(tcool_calc) + tcool_calc = copy.copy(self.data.tfcoil.temp_cp_coolant_inlet) # K + for _i in range(n_tcool_it): + # Thermal capacity Cp + coolant_cp = self.he_cp(tcool_calc) - # Temperature infinitesimal increase - tcool_calc += dptot / (cool_mass_flow * coolant_cp) + # Temperature infinitesimal increase + tcool_calc += dptot / (cool_mass_flow * coolant_cp) - # Outlet coolant density (minimal coolant density value) - coolant_density = self.he_density(tcool_calc) + # Outlet coolant density (minimal coolant density value) + coolant_density = self.he_density(tcool_calc) - # Maxium coolant velocity - vcool_max = cool_mass_flow / (acool * coolant_density) + # Maxium coolant velocity + vcool_max = cool_mass_flow / (acool * coolant_density) - # Getting the global in-outlet temperature increase - self.data.tfcoil.dtemp_cp_coolant = ( - tcool_calc - self.data.tfcoil.temp_cp_coolant_inlet - ) + # Getting the global in-outlet temperature increase + self.data.tfcoil.dtemp_cp_coolant = ( + tcool_calc - self.data.tfcoil.temp_cp_coolant_inlet + ) + case _: + raise ProcessValueError("Unknown TF conductor model") # -------------- # Average coolant temperature @@ -1404,12 +1422,13 @@ def cntrpst(self, output: bool = False): # Conductor thermal conductivity # ****** # Copper conductor - if self.data.tfcoil.i_tf_sup == TFConductorModel.WATER_COOLED_COPPER: - conductor_th_cond = constants.K_COPPER - - # Aluminium - elif self.data.tfcoil.i_tf_sup == TFConductorModel.HELIUM_COOLED_ALUMINIUM: - conductor_th_cond = self.al_th_cond(tcool_film) + match TFConductorModel(self.data.tfcoil.i_tf_sup): + case TFConductorModel.WATER_COOLED_COPPER: + conductor_th_cond = constants.K_COPPER + case TFConductorModel.HELIUM_COOLED_ALUMINIUM: + conductor_th_cond = self.al_th_cond(tcool_film) + case _: + raise ProcessValueError("Unknown TF conductor model") # ****** # Average temperature rise : To be changed with Garry Voss' better documented @@ -1488,14 +1507,11 @@ def cntrpst(self, output: bool = False): ) # Pumping efficiency - if ( - self.data.tfcoil.i_tf_sup == TFConductorModel.WATER_COOLED_COPPER - ): # Water cooled - self.data.tfcoil.etapump = 0.8e0 - elif ( - self.data.tfcoil.i_tf_sup == TFConductorModel.HELIUM_COOLED_ALUMINIUM - ): # Cryogenic helium - self.data.tfcoil.etapump = 0.6e0 + match TFConductorModel(self.data.tfcoil.i_tf_sup): + case TFConductorModel.WATER_COOLED_COPPER: + self.data.tfcoil.etapump = 0.8e0 + case TFConductorModel.HELIUM_COOLED_ALUMINIUM: + self.data.tfcoil.etapump = 0.6e0 # Pressure drop calculation dpres = ( @@ -3408,48 +3424,51 @@ def table_format_arrays(a, mult=1, delim="\t\t"): "Please use utilities/plot_stress_tf.py for radial plots plots summary", ) - if self.data.tfcoil.i_tf_bucking == 0: - if self.data.tfcoil.i_tf_sup == TFConductorModel.SUPERCONDUCTING: - po.write(self.outfile, " Layers \t\t\t\t WP \t\t Outer case") - else: - po.write(self.outfile, " Layers \t\t\t\t conductor \t\t Outer case") + match self.data.tfcoil.i_tf_bucking: + case 0: + if self.data.tfcoil.i_tf_sup == TFConductorModel.SUPERCONDUCTING: + po.write(self.outfile, " Layers \t\t\t\t WP \t\t Outer case") + else: + po.write(self.outfile, " Layers \t\t\t\t conductor \t\t Outer case") - elif self.data.tfcoil.i_tf_bucking == 1: - if self.data.tfcoil.i_tf_sup == TFConductorModel.SUPERCONDUCTING: - po.write( - self.outfile, " Layers \t\t\t\t Steel case \t\t WP \t\t Outer case" - ) - else: - po.write( - self.outfile, - " Layers \t\t\t\t bucking \t\t conductor \t\t Outer case", - ) + case 1: + if self.data.tfcoil.i_tf_sup == TFConductorModel.SUPERCONDUCTING: + po.write( + self.outfile, + " Layers \t\t\t\t Steel case \t\t WP \t\t Outer case", + ) + else: + po.write( + self.outfile, + " Layers \t\t\t\t bucking \t\t conductor \t\t Outer case", + ) - elif self.data.tfcoil.i_tf_bucking == 2: - if self.data.tfcoil.i_tf_sup == TFConductorModel.SUPERCONDUCTING: - po.write( - self.outfile, - " Layers \t\t\t\t CS \t\t Steel case \t\t WP \t\t Outer case", - ) - else: - po.write( - self.outfile, - " Layers \t\t\t\t CS \t\t bucking \t\t conductor \t\t Outer case", - ) + case 2: + if self.data.tfcoil.i_tf_sup == TFConductorModel.SUPERCONDUCTING: + po.write( + self.outfile, + " Layers \t\t\t\t CS \t\t Steel case \t\t WP \t\t Outer case", + ) + else: + po.write( + self.outfile, + " Layers \t\t\t\t CS \t\t bucking \t\t conductor \t\t " + "Outer case", + ) - elif self.data.tfcoil.i_tf_bucking == 3: - if self.data.tfcoil.i_tf_sup == TFConductorModel.SUPERCONDUCTING: - po.write( - self.outfile, - " Layers \t\t\t\t CS \t\t interface \t\t Steel case \t\t WP" - "\t\t Outer case", - ) - else: - po.write( - self.outfile, - " Layers \t\t\t\t CS \t\t interface \t\t bucking \t\t conductor" - "\t\t Outer case", - ) + case 3: + if self.data.tfcoil.i_tf_sup == TFConductorModel.SUPERCONDUCTING: + po.write( + self.outfile, + " Layers \t\t\t\t CS \t\t interface \t\t Steel case \t\t WP" + "\t\t Outer case", + ) + else: + po.write( + self.outfile, + " Layers \t\t\t\t CS \t\t interface \t\t bucking \t\t conductor" + "\t\t Outer case", + ) po.write( self.outfile, diff --git a/process/models/tfcoil/resistive.py b/process/models/tfcoil/resistive.py index 7d29d18bda..82cbcfd15f 100644 --- a/process/models/tfcoil/resistive.py +++ b/process/models/tfcoil/resistive.py @@ -457,22 +457,25 @@ def tf_res_heating(self): - https://cirris.com/temperature-coefficient-of-copper/ """ # Resistivity of the Glidcop copper centerpost - if self.data.tfcoil.i_tf_sup == TFConductorModel.WATER_COOLED_COPPER: - self.data.tfcoil.rho_cp = ( - # 1.86 is the resistivity at `20°C` for GLIDCOP AL-15 - # 0.00393 is the coefficient of resistivity for copper - self.data.tfcoil.frhocp - * (1.86e0 + 0.00393e0 * (self.data.tfcoil.temp_cp_average - 293.15e0)) - * 1.0e-8 - ) + match TFConductorModel(self.data.tfcoil.i_tf_sup): + case TFConductorModel.WATER_COOLED_COPPER: + self.data.tfcoil.rho_cp = ( + # 1.86 is the resistivity at `20°C` for GLIDCOP AL-15 + # 0.00393 is the coefficient of resistivity for copper + self.data.tfcoil.frhocp + * ( + 1.86e0 + + 0.00393e0 * (self.data.tfcoil.temp_cp_average - 293.15e0) + ) + * 1.0e-8 + ) - # Resistivity of the aluminium centerpost - if self.data.tfcoil.i_tf_sup == TFConductorModel.HELIUM_COOLED_ALUMINIUM: - self.data.tfcoil.rho_cp = self.data.tfcoil.frhocp * ( - 2.00016e-14 * self.data.tfcoil.temp_cp_average**3 - - 6.75384e-13 * self.data.tfcoil.temp_cp_average**2 - + 8.89159e-12 * self.data.tfcoil.temp_cp_average - ) + case TFConductorModel.HELIUM_COOLED_ALUMINIUM: + self.data.tfcoil.rho_cp = self.data.tfcoil.frhocp * ( + 2.00016e-14 * self.data.tfcoil.temp_cp_average**3 + - 6.75384e-13 * self.data.tfcoil.temp_cp_average**2 + + 8.89159e-12 * self.data.tfcoil.temp_cp_average + ) # Calculations dedicated for configurations with CP if self.data.physics.itart == 1: @@ -485,21 +488,23 @@ def tf_res_heating(self): self.data.tfcoil.temp_tf_legs_outboard = self.data.tfcoil.temp_cp_average # Leg resistivity (different leg temperature as separate cooling channels) - if self.data.tfcoil.i_tf_sup == TFConductorModel.WATER_COOLED_COPPER: - self.data.tfcoil.rho_tf_leg = ( - self.data.tfcoil.frholeg - * ( - 1.86e0 - + 0.00393e0 * (self.data.tfcoil.temp_tf_legs_outboard - 293.15e0) + match TFConductorModel(self.data.tfcoil.i_tf_sup): + case TFConductorModel.WATER_COOLED_COPPER: + self.data.tfcoil.rho_tf_leg = ( + self.data.tfcoil.frholeg + * ( + 1.86e0 + + 0.00393e0 + * (self.data.tfcoil.temp_tf_legs_outboard - 293.15e0) + ) + * 1.0e-8 + ) + case TFConductorModel.HELIUM_COOLED_ALUMINIUM: + self.data.tfcoil.rho_tf_leg = self.data.tfcoil.frholeg * ( + 2.00016e-14 * self.data.tfcoil.temp_tf_legs_outboard**3 + - 6.75384e-13 * self.data.tfcoil.temp_tf_legs_outboard**2 + + 8.89159e-12 * self.data.tfcoil.temp_tf_legs_outboard ) - * 1.0e-8 - ) - elif self.data.tfcoil.i_tf_sup == TFConductorModel.HELIUM_COOLED_ALUMINIUM: - self.data.tfcoil.rho_tf_leg = self.data.tfcoil.frholeg * ( - 2.00016e-14 * self.data.tfcoil.temp_tf_legs_outboard**3 - - 6.75384e-13 * self.data.tfcoil.temp_tf_legs_outboard**2 - + 8.89159e-12 * self.data.tfcoil.temp_tf_legs_outboard - ) # Tricky trick to make the leg / CP temperatures the same if self.data.superconducting_tfcoil.is_leg_cp_temp_same == 1: @@ -737,68 +742,69 @@ def resistive_tf_coil_areas_and_masses(self): ) # Copper magnets casing/conductor weights per coil [kg] - if self.data.tfcoil.i_tf_sup == TFConductorModel.WATER_COOLED_COPPER: - self.data.tfcoil.m_tf_coil_case = ( - self.data.fwbs.den_steel * vol_case / self.data.tfcoil.n_tf_coils - ) # Per TF leg, no casing for outer leg - self.data.tfcoil.m_tf_coil_copper = ( - constants.DEN_COPPER * vol_cond / self.data.tfcoil.n_tf_coils - ) - self.data.tfcoil.whtconal = 0.0e0 - - # Outer legs/CP weights - if self.data.physics.itart == 1: - # Weight of all the TF legs - self.data.tfcoil.whttflgs = self.data.tfcoil.n_tf_coils * ( - constants.DEN_COPPER * vol_cond_leg - + self.data.tfcoil.den_tf_wp_turn_insulation - * (vol_ins_leg + vol_gr_ins_leg) + match TFConductorModel(self.data.tfcoil.i_tf_sup): + case TFConductorModel.WATER_COOLED_COPPER: + self.data.tfcoil.m_tf_coil_case = ( + self.data.fwbs.den_steel * vol_case / self.data.tfcoil.n_tf_coils + ) # Per TF leg, no casing for outer leg + self.data.tfcoil.m_tf_coil_copper = ( + constants.DEN_COPPER * vol_cond / self.data.tfcoil.n_tf_coils ) + self.data.tfcoil.whtconal = 0.0e0 + + # Outer legs/CP weights + if self.data.physics.itart == 1: + # Weight of all the TF legs + self.data.tfcoil.whttflgs = self.data.tfcoil.n_tf_coils * ( + constants.DEN_COPPER * vol_cond_leg + + self.data.tfcoil.den_tf_wp_turn_insulation + * (vol_ins_leg + vol_gr_ins_leg) + ) - # CP weight - self.data.tfcoil.whtcp = ( - constants.DEN_COPPER * self.data.tfcoil.vol_cond_cp - + self.data.tfcoil.den_tf_wp_turn_insulation - * ( - self.data.superconducting_tfcoil.vol_ins_cp - + self.data.superconducting_tfcoil.vol_gr_ins_cp + # CP weight + self.data.tfcoil.whtcp = ( + constants.DEN_COPPER * self.data.tfcoil.vol_cond_cp + + self.data.tfcoil.den_tf_wp_turn_insulation + * ( + self.data.superconducting_tfcoil.vol_ins_cp + + self.data.superconducting_tfcoil.vol_gr_ins_cp + ) + + self.data.superconducting_tfcoil.vol_case_cp + * self.data.fwbs.den_steel ) - + self.data.superconducting_tfcoil.vol_case_cp - * self.data.fwbs.den_steel - ) - # Cryo-aluminium conductor weights - # Casing made of re-inforced aluminium alloy - elif self.data.tfcoil.i_tf_sup == TFConductorModel.HELIUM_COOLED_ALUMINIUM: - # Casing weight (CP only if self.data.physics.itart = 1)bper leg/coil - self.data.tfcoil.m_tf_coil_case = ( - constants.DEN_ALUMINIUM * vol_case / self.data.tfcoil.n_tf_coils - ) - self.data.tfcoil.m_tf_coil_copper = 0.0e0 - self.data.tfcoil.whtconal = ( - constants.DEN_ALUMINIUM * vol_cond / self.data.tfcoil.n_tf_coils - ) - - # Outer legs/CP weights - if self.data.physics.itart == 1: - # Weight of all the TF legs - self.data.tfcoil.whttflgs = self.data.tfcoil.n_tf_coils * ( - constants.DEN_ALUMINIUM * vol_cond_leg - + self.data.tfcoil.den_tf_wp_turn_insulation - * (vol_ins_leg + vol_gr_ins_leg) + # Cryo-aluminium conductor weights + # Casing made of re-inforced aluminium alloy + case TFConductorModel.HELIUM_COOLED_ALUMINIUM: + # Casing weight (CP only if self.data.physics.itart = 1)bper leg/coil + self.data.tfcoil.m_tf_coil_case = ( + constants.DEN_ALUMINIUM * vol_case / self.data.tfcoil.n_tf_coils + ) + self.data.tfcoil.m_tf_coil_copper = 0.0e0 + self.data.tfcoil.whtconal = ( + constants.DEN_ALUMINIUM * vol_cond / self.data.tfcoil.n_tf_coils ) - # CP weight - self.data.tfcoil.whtcp = ( - constants.DEN_ALUMINIUM * self.data.tfcoil.vol_cond_cp - + self.data.tfcoil.den_tf_wp_turn_insulation - * ( - self.data.superconducting_tfcoil.vol_ins_cp - + self.data.superconducting_tfcoil.vol_gr_ins_cp + # Outer legs/CP weights + if self.data.physics.itart == 1: + # Weight of all the TF legs + self.data.tfcoil.whttflgs = self.data.tfcoil.n_tf_coils * ( + constants.DEN_ALUMINIUM * vol_cond_leg + + self.data.tfcoil.den_tf_wp_turn_insulation + * (vol_ins_leg + vol_gr_ins_leg) + ) + + # CP weight + self.data.tfcoil.whtcp = ( + constants.DEN_ALUMINIUM * self.data.tfcoil.vol_cond_cp + + self.data.tfcoil.den_tf_wp_turn_insulation + * ( + self.data.superconducting_tfcoil.vol_ins_cp + + self.data.superconducting_tfcoil.vol_gr_ins_cp + ) + + self.data.superconducting_tfcoil.vol_case_cp + * self.data.fwbs.den_steel ) - + self.data.superconducting_tfcoil.vol_case_cp - * self.data.fwbs.den_steel - ) # Turn insulation mass [kg] self.data.tfcoil.m_tf_coil_wp_turn_insulation = ( diff --git a/process/models/tfcoil/superconducting.py b/process/models/tfcoil/superconducting.py index 7015032802..4e04dfac9b 100644 --- a/process/models/tfcoil/superconducting.py +++ b/process/models/tfcoil/superconducting.py @@ -1099,7 +1099,7 @@ def output_tf_superconductor_info(self): "(temp_tf_conductor_quench_max)", self.data.tfcoil.temp_tf_conductor_quench_max, ) - elif self.data.tfcoil == 6: + elif self.data.tfcoil.i_tf_sc_mat == 6: po.ocmmnt(self.outfile, "CroCo cable with jacket: ") if 75 in self.data.numerics.icc: @@ -1229,81 +1229,84 @@ def calculate_superconductor_temperature_margin( # Temperature margin (already calculated in superconductors.bi2212 for # i_tf_superconductor=2) - - if SuperconductorModel(i_tf_superconductor) == SuperconductorModel.BI2212: - # Bi-2212: temperature margin already calculated elsewhere - temp_tf_superconductor_margin = 0.0 - # Find temperature at which current density margin = 0 - elif SuperconductorModel(i_tf_superconductor) in { - SuperconductorModel.ITER_NB3SN, - SuperconductorModel.OLD_LUBELL_NBTI, - SuperconductorModel.USER_DEFINED_NB3SN, - SuperconductorModel.WST_NB3SN, - SuperconductorModel.DURHAM_NBTI, - SuperconductorModel.DURHAM_REBCO, - SuperconductorModel.HAZELTON_ZHAI_REBCO, - }: - if ( - SuperconductorModel(i_tf_superconductor) - == SuperconductorModel.OLD_LUBELL_NBTI + match SuperconductorModel(i_tf_superconductor): + case SuperconductorModel.BI2212: + # Bi-2212: temperature margin already calculated elsewhere + temp_tf_superconductor_margin = 0.0 + # Find temperature at which current density margin = 0 + case ( + SuperconductorModel.ITER_NB3SN + | SuperconductorModel.OLD_LUBELL_NBTI + | SuperconductorModel.USER_DEFINED_NB3SN + | SuperconductorModel.WST_NB3SN + | SuperconductorModel.DURHAM_NBTI + | SuperconductorModel.DURHAM_REBCO + | SuperconductorModel.HAZELTON_ZHAI_REBCO ): - arguments = ( - i_tf_superconductor, - j_superconductor, - b_tf_inboard_peak, - strain, - bc20m, - tc0m, - data.superconducting_tfcoil.dr_tf_hts_tape, - data.superconducting_tfcoil.dx_tf_hts_tape_rebco, - data.superconducting_tfcoil.dx_tf_hts_tape_total, - c0, + if ( + SuperconductorModel(i_tf_superconductor) + == SuperconductorModel.OLD_LUBELL_NBTI + ): + arguments = ( + i_tf_superconductor, + j_superconductor, + b_tf_inboard_peak, + strain, + bc20m, + tc0m, + data.superconducting_tfcoil.dr_tf_hts_tape, + data.superconducting_tfcoil.dx_tf_hts_tape_rebco, + data.superconducting_tfcoil.dx_tf_hts_tape_total, + c0, + ) + else: + arguments = ( + i_tf_superconductor, + j_superconductor, + b_tf_inboard_peak, + strain, + bc20m, + tc0m, + data.superconducting_tfcoil.dr_tf_hts_tape, + data.superconducting_tfcoil.dx_tf_hts_tape_rebco, + data.superconducting_tfcoil.dx_tf_hts_tape_total, + ) + + another_estimate = 2 * temp_tf_coolant_peak_field + ( + t_zero_margin, + _, + ) = optimize.newton( + superconductors.superconductor_current_density_margin, + temp_tf_coolant_peak_field, + fprime=None, + args=arguments, + tol=1.0e-06, + maxiter=50, + fprime2=None, + x1=another_estimate, + rtol=1.0e-6, + full_output=True, + disp=True, ) - else: - arguments = ( - i_tf_superconductor, - j_superconductor, - b_tf_inboard_peak, - strain, - bc20m, - tc0m, - data.superconducting_tfcoil.dr_tf_hts_tape, - data.superconducting_tfcoil.dx_tf_hts_tape_rebco, - data.superconducting_tfcoil.dx_tf_hts_tape_total, + temp_tf_superconductor_margin = ( + t_zero_margin - temp_tf_coolant_peak_field ) - - another_estimate = 2 * temp_tf_coolant_peak_field - ( - t_zero_margin, - _, - ) = optimize.newton( - superconductors.superconductor_current_density_margin, - temp_tf_coolant_peak_field, - fprime=None, - args=arguments, - tol=1.0e-06, - maxiter=50, - fprime2=None, - x1=another_estimate, - rtol=1.0e-6, - full_output=True, - disp=True, - ) - temp_tf_superconductor_margin = t_zero_margin - temp_tf_coolant_peak_field - data.tfcoil.temp_margin = temp_tf_superconductor_margin - - if temp_tf_superconductor_margin <= 0.0e0: - logger.error( - """Negative TFC temperature margin - temp_tf_superconductor_margin: {temp_tf_superconductor_margin} - b_tf_inboard_peak: {b_tf_inboard_peak} - j_superconductor: {j_superconductor} - """ + data.tfcoil.temp_margin = temp_tf_superconductor_margin + + if temp_tf_superconductor_margin <= 0.0e0: + logger.error( + f"""Negative TFC temperature margin + temp_tf_superconductor_margin: {temp_tf_superconductor_margin} + b_tf_inboard_peak: {b_tf_inboard_peak} + j_superconductor: {j_superconductor} + """ + ) + case _: + raise ProcessValueError( + f"Unknown superconductor type: " + f"i_tf_superconductor={i_tf_superconductor}" ) - else: - raise ProcessValueError( - f"Unknown superconductor type: i_tf_superconductor={i_tf_superconductor}" - ) return temp_tf_superconductor_margin @@ -1509,25 +1512,24 @@ def peak_b_tf_inboard_with_ripple( # Set fitting coefficients for different numbers of TF coils int_n_tf = np.round(n_tf_coils) - - if int_n_tf == 16: - a[0] = 0.28101e0 - a[1] = 1.8481e0 - a[2] = -0.88159e0 - a[3] = 0.93834e0 - elif int_n_tf == 18: - a[0] = 0.29153e0 - a[1] = 1.81600e0 - a[2] = -0.84178e0 - a[3] = 0.90426e0 - elif int_n_tf == 20: - a[0] = 0.29853e0 - a[1] = 1.82130e0 - a[2] = -0.85031e0 - a[3] = 0.89808e0 - - else: - return 1.09e0 * b_tf_inboard_peak_symmetric + match int_n_tf: + case 16: + a[0] = 0.28101e0 + a[1] = 1.8481e0 + a[2] = -0.88159e0 + a[3] = 0.93834e0 + case 18: + a[0] = 0.29153e0 + a[1] = 1.81600e0 + a[2] = -0.84178e0 + a[3] = 0.90426e0 + case 20: + a[0] = 0.29853e0 + a[1] = 1.82130e0 + a[2] = -0.85031e0 + a[3] = 0.89808e0 + case _: + return 1.09e0 * b_tf_inboard_peak_symmetric # Maximum winding pack width before adjacent packs touch # (ignoring the external case and ground wall thicknesses) @@ -1631,153 +1633,152 @@ def superconducting_tf_wp_geometry( dx_tf_wp_insulation + dx_tf_wp_insertion_gap ) - i_tf_wp_geom = SuperconductingTFWPShapeType(i_tf_wp_geom) - - # Rectangular WP - # -------------- - if i_tf_wp_geom == SuperconductingTFWPShapeType.RECTANGULAR: - # Outer WP layer toroidal thickness [m] - dx_tf_wp_primary_toroidal = dx_tf_wp_toroidal_min - - # No secondary WP here but will set for consistency - dx_tf_wp_secondary_toroidal = dx_tf_wp_toroidal_min - - # Averaged toroidal thickness of of winding pack [m] - dx_tf_wp_toroidal_average = dx_tf_wp_toroidal_min + match SuperconductingTFWPShapeType(i_tf_wp_geom): + # Rectangular WP + # -------------- + case SuperconductingTFWPShapeType.RECTANGULAR: + # Outer WP layer toroidal thickness [m] + dx_tf_wp_primary_toroidal = dx_tf_wp_toroidal_min - # Total cross-sectional area of winding pack [m²] - a_tf_wp_with_insulation = ( - dr_tf_wp_with_insulation * dx_tf_wp_primary_toroidal - ) + # No secondary WP here but will set for consistency + dx_tf_wp_secondary_toroidal = dx_tf_wp_toroidal_min - # WP cross-section without insertion gap and ground insulation [m²] - a_tf_wp_no_insulation = ( - dr_tf_wp_with_insulation - - 2.0e0 * (dx_tf_wp_insulation + dx_tf_wp_insertion_gap) - ) * ( - dx_tf_wp_primary_toroidal - - 2.0e0 * (dx_tf_wp_insulation + dx_tf_wp_insertion_gap) - ) + # Averaged toroidal thickness of of winding pack [m] + dx_tf_wp_toroidal_average = dx_tf_wp_toroidal_min - # Cross-section area of the WP ground insulation [m²] - a_tf_wp_ground_insulation = ( - dr_tf_wp_with_insulation - 2.0e0 * dx_tf_wp_insertion_gap - ) * ( - dx_tf_wp_primary_toroidal - 2.0e0 * dx_tf_wp_insertion_gap - ) - a_tf_wp_no_insulation - - # Double rectangular WP - # --------------------- - elif i_tf_wp_geom == SuperconductingTFWPShapeType.DOUBLE_RECTANGULAR: - # Thickness of winding pack section at R > - # d_sc_tf.r_tf_wp_inboard_centre [m] - dx_tf_wp_primary_toroidal = 2.0e0 * ( - r_tf_wp_inboard_centre * tan_theta_coil - dx_tf_side_case_min - ) + # Total cross-sectional area of winding pack [m²] + a_tf_wp_with_insulation = ( + dr_tf_wp_with_insulation * dx_tf_wp_primary_toroidal + ) - # Thickness of winding pack section at R < - # d_sc_tf.r_tf_wp_inboard_centre [m] - dx_tf_wp_secondary_toroidal = 2.0e0 * ( - r_tf_wp_inboard_inner * tan_theta_coil - dx_tf_side_case_min - ) + # WP cross-section without insertion gap and ground insulation [m²] + a_tf_wp_no_insulation = ( + dr_tf_wp_with_insulation + - 2.0e0 * (dx_tf_wp_insulation + dx_tf_wp_insertion_gap) + ) * ( + dx_tf_wp_primary_toroidal + - 2.0e0 * (dx_tf_wp_insulation + dx_tf_wp_insertion_gap) + ) - # Averaged toroidal thickness of of winding pack [m] - dx_tf_wp_toroidal_average = 0.5e0 * ( - dx_tf_wp_primary_toroidal + dx_tf_wp_secondary_toroidal - ) + # Cross-section area of the WP ground insulation [m²] + a_tf_wp_ground_insulation = ( + dr_tf_wp_with_insulation - 2.0e0 * dx_tf_wp_insertion_gap + ) * ( + dx_tf_wp_primary_toroidal - 2.0e0 * dx_tf_wp_insertion_gap + ) - a_tf_wp_no_insulation + + # Double rectangular WP + # --------------------- + case SuperconductingTFWPShapeType.DOUBLE_RECTANGULAR: + # Thickness of winding pack section at R > + # d_sc_tf.r_tf_wp_inboard_centre [m] + dx_tf_wp_primary_toroidal = 2.0e0 * ( + r_tf_wp_inboard_centre * tan_theta_coil - dx_tf_side_case_min + ) - # Total cross-sectional area of winding pack [m²] - # Including ground insulation and insertion gap - a_tf_wp_with_insulation = ( - dr_tf_wp_with_insulation * dx_tf_wp_toroidal_average - ) + # Thickness of winding pack section at R < + # d_sc_tf.r_tf_wp_inboard_centre [m] + dx_tf_wp_secondary_toroidal = 2.0e0 * ( + r_tf_wp_inboard_inner * tan_theta_coil - dx_tf_side_case_min + ) - # WP cross-section without insertion gap and ground insulation [m²] - a_tf_wp_no_insulation = ( - 0.5e0 - * ( - dr_tf_wp_with_insulation - - 2.0e0 * (dx_tf_wp_insulation + dx_tf_wp_insertion_gap) + # Averaged toroidal thickness of of winding pack [m] + dx_tf_wp_toroidal_average = 0.5e0 * ( + dx_tf_wp_primary_toroidal + dx_tf_wp_secondary_toroidal ) - * ( - dx_tf_wp_primary_toroidal - + dx_tf_wp_secondary_toroidal - - 4.0e0 * (dx_tf_wp_insulation + dx_tf_wp_insertion_gap) + + # Total cross-sectional area of winding pack [m²] + # Including ground insulation and insertion gap + a_tf_wp_with_insulation = ( + dr_tf_wp_with_insulation * dx_tf_wp_toroidal_average ) - ) - # Cross-section area of the WP ground insulation [m²] - a_tf_wp_ground_insulation = ( - 0.5e0 - * (dr_tf_wp_with_insulation - 2.0e0 * dx_tf_wp_insertion_gap) - * ( - dx_tf_wp_primary_toroidal - + dx_tf_wp_secondary_toroidal - - 4.0e0 * dx_tf_wp_insertion_gap + # WP cross-section without insertion gap and ground insulation [m²] + a_tf_wp_no_insulation = ( + 0.5e0 + * ( + dr_tf_wp_with_insulation + - 2.0e0 * (dx_tf_wp_insulation + dx_tf_wp_insertion_gap) + ) + * ( + dx_tf_wp_primary_toroidal + + dx_tf_wp_secondary_toroidal + - 4.0e0 * (dx_tf_wp_insulation + dx_tf_wp_insertion_gap) + ) ) - - a_tf_wp_no_insulation - ) - # Trapezoidal WP - # -------------- - elif i_tf_wp_geom == SuperconductingTFWPShapeType.TRAPEZOIDAL: - # Thickness of winding pack section at r_tf_wp_inboard_outer [m] - dx_tf_wp_primary_toroidal = 2.0e0 * ( - r_tf_wp_inboard_outer * tan_theta_coil - dx_tf_side_case_min - ) + # Cross-section area of the WP ground insulation [m²] + a_tf_wp_ground_insulation = ( + 0.5e0 + * (dr_tf_wp_with_insulation - 2.0e0 * dx_tf_wp_insertion_gap) + * ( + dx_tf_wp_primary_toroidal + + dx_tf_wp_secondary_toroidal + - 4.0e0 * dx_tf_wp_insertion_gap + ) + - a_tf_wp_no_insulation + ) - # Thickness of winding pack section at r_tf_wp_inboard_inner [m] - dx_tf_wp_secondary_toroidal = 2.0e0 * ( - r_tf_wp_inboard_inner * tan_theta_coil - dx_tf_side_case_min - ) + # Trapezoidal WP + # -------------- + case SuperconductingTFWPShapeType.TRAPEZOIDAL: + # Thickness of winding pack section at r_tf_wp_inboard_outer [m] + dx_tf_wp_primary_toroidal = 2.0e0 * ( + r_tf_wp_inboard_outer * tan_theta_coil - dx_tf_side_case_min + ) - # Averaged toroidal thickness of of winding pack [m] - dx_tf_wp_toroidal_average = 0.5e0 * ( - dx_tf_wp_primary_toroidal + dx_tf_wp_secondary_toroidal - ) + # Thickness of winding pack section at r_tf_wp_inboard_inner [m] + dx_tf_wp_secondary_toroidal = 2.0e0 * ( + r_tf_wp_inboard_inner * tan_theta_coil - dx_tf_side_case_min + ) - # Total cross-sectional area of winding pack [m²] - # Including ground insulation and insertion gap - a_tf_wp_with_insulation = ( - dr_tf_wp_with_insulation - * 0.5 - * (dx_tf_wp_primary_toroidal + dx_tf_wp_secondary_toroidal) - ) + # Averaged toroidal thickness of of winding pack [m] + dx_tf_wp_toroidal_average = 0.5e0 * ( + dx_tf_wp_primary_toroidal + dx_tf_wp_secondary_toroidal + ) - # WP cross-section without insertion gap and ground insulation [m²] - a_tf_wp_no_insulation = ( - ( + # Total cross-sectional area of winding pack [m²] + # Including ground insulation and insertion gap + a_tf_wp_with_insulation = ( dr_tf_wp_with_insulation - - 2.0e0 * (dx_tf_wp_insulation + dx_tf_wp_insertion_gap) + * 0.5 + * (dx_tf_wp_primary_toroidal + dx_tf_wp_secondary_toroidal) ) - * ( + + # WP cross-section without insertion gap and ground insulation [m²] + a_tf_wp_no_insulation = ( ( - dx_tf_wp_secondary_toroidal + dr_tf_wp_with_insulation - 2.0e0 * (dx_tf_wp_insulation + dx_tf_wp_insertion_gap) ) - + ( - dx_tf_wp_primary_toroidal - - 2.0e0 * (dx_tf_wp_insulation + dx_tf_wp_insertion_gap) + * ( + ( + dx_tf_wp_secondary_toroidal + - 2.0e0 * (dx_tf_wp_insulation + dx_tf_wp_insertion_gap) + ) + + ( + dx_tf_wp_primary_toroidal + - 2.0e0 * (dx_tf_wp_insulation + dx_tf_wp_insertion_gap) + ) ) + / 2 ) - / 2 - ) - # Cross-section area of the WP ground insulation [m²] - a_tf_wp_ground_insulation = ( - dr_tf_wp_with_insulation - 2.0e0 * dx_tf_wp_insertion_gap - ) * ( - ( - (dx_tf_wp_primary_toroidal - 2.0e0 * dx_tf_wp_insertion_gap) - + (dx_tf_wp_secondary_toroidal - 2.0e0 * dx_tf_wp_insertion_gap) - ) - / 2 - ) - a_tf_wp_no_insulation + # Cross-section area of the WP ground insulation [m²] + a_tf_wp_ground_insulation = ( + dr_tf_wp_with_insulation - 2.0e0 * dx_tf_wp_insertion_gap + ) * ( + ( + (dx_tf_wp_primary_toroidal - 2.0e0 * dx_tf_wp_insertion_gap) + + (dx_tf_wp_secondary_toroidal - 2.0e0 * dx_tf_wp_insertion_gap) + ) + / 2 + ) - a_tf_wp_no_insulation - else: - raise ProcessValueError( - f"Invalid winding pack geometry index: {i_tf_wp_geom}" - ) + case _: + raise ProcessValueError( + f"Invalid winding pack geometry index: {i_tf_wp_geom}" + ) # -------------- # Negative WP area error reporting @@ -1883,17 +1884,18 @@ def superconducting_tf_case_geometry( a_tf_coil_outboard_case = a_tf_leg_outboard - a_tf_wp_with_insulation # Front casing area [m²] - if i_tf_case_geom == TFPlasmaCaseType.CIRCULAR: - # Circular front case - a_tf_plasma_case = ( - rad_tf_coil_inboard_toroidal_half * r_tf_inboard_out**2 - ) - (tan_theta_coil * r_tf_wp_inboard_outer**2) - elif i_tf_case_geom == TFPlasmaCaseType.STRAIGHT: - # Straight front case [m²] - a_tf_plasma_case = ( - (r_tf_wp_inboard_outer + dr_tf_plasma_case) ** 2 - - r_tf_wp_inboard_outer**2 - ) * tan_theta_coil + match TFPlasmaCaseType(i_tf_case_geom): + case TFPlasmaCaseType.CIRCULAR: + # Circular front case + a_tf_plasma_case = ( + rad_tf_coil_inboard_toroidal_half * r_tf_inboard_out**2 + ) - (tan_theta_coil * r_tf_wp_inboard_outer**2) + case TFPlasmaCaseType.STRAIGHT: + # Straight front case [m²] + a_tf_plasma_case = ( + (r_tf_wp_inboard_outer + dr_tf_plasma_case) ** 2 + - r_tf_wp_inboard_outer**2 + ) * tan_theta_coil # Nose casing area [m²] a_tf_coil_nose_case = ( @@ -1912,49 +1914,51 @@ def superconducting_tf_case_geometry( # Average lateral casing thickness [m] # -------------- # Rectangular casing - if i_tf_wp_geom == SuperconductingTFWPShapeType.RECTANGULAR: - dx_tf_side_case_average = ( - dx_tf_side_case_min + 0.5e0 * tan_theta_coil * dr_tf_wp_with_insulation - ) + match SuperconductingTFWPShapeType(i_tf_wp_geom): + case SuperconductingTFWPShapeType.RECTANGULAR: + dx_tf_side_case_average = ( + dx_tf_side_case_min + + 0.5e0 * tan_theta_coil * dr_tf_wp_with_insulation + ) - # Double rectangular WP - elif i_tf_wp_geom == SuperconductingTFWPShapeType.DOUBLE_RECTANGULAR: - dx_tf_side_case_average = ( - dx_tf_side_case_min + 0.25e0 * tan_theta_coil * dr_tf_wp_with_insulation - ) + # Double rectangular WP + case SuperconductingTFWPShapeType.DOUBLE_RECTANGULAR: + dx_tf_side_case_average = ( + dx_tf_side_case_min + + 0.25e0 * tan_theta_coil * dr_tf_wp_with_insulation + ) - # Trapezoidal WP - elif i_tf_wp_geom == SuperconductingTFWPShapeType.TRAPEZOIDAL: - dx_tf_side_case_average = dx_tf_side_case_min - else: - raise ProcessValueError( - "Unsupported TF winding pack geometry for average lateral " - "casing thickness", - {"i_tf_wp_geom": i_tf_wp_geom}, - ) + # Trapezoidal WP + case SuperconductingTFWPShapeType.TRAPEZOIDAL: + dx_tf_side_case_average = dx_tf_side_case_min + case _: + raise ProcessValueError( + "Unsupported TF winding pack geometry for average lateral " + "casing thickness", + {"i_tf_wp_geom": i_tf_wp_geom}, + ) # Peak lateral casing thickness [m] # -------------- # Rectangular casing + match SuperconductingTFWPShapeType(i_tf_wp_geom): + case SuperconductingTFWPShapeType.RECTANGULAR: + dx_tf_side_case_peak = ( + dx_tf_side_case_min + tan_theta_coil * dr_tf_wp_with_insulation + ) - if i_tf_wp_geom == SuperconductingTFWPShapeType.RECTANGULAR: - dx_tf_side_case_peak = ( - dx_tf_side_case_min + tan_theta_coil * dr_tf_wp_with_insulation - ) - # Double rectangular WP - elif i_tf_wp_geom == SuperconductingTFWPShapeType.DOUBLE_RECTANGULAR: - dx_tf_side_case_peak = ( - dx_tf_side_case_min + 0.5 * tan_theta_coil * dr_tf_wp_with_insulation - ) + case SuperconductingTFWPShapeType.DOUBLE_RECTANGULAR: + dx_tf_side_case_peak = ( + dx_tf_side_case_min + 0.5 * tan_theta_coil * dr_tf_wp_with_insulation + ) - # Trapezoidal WP - # Constant thickness so min = average - elif i_tf_wp_geom == SuperconductingTFWPShapeType.TRAPEZOIDAL: - dx_tf_side_case_peak = dx_tf_side_case_min - else: - raise ProcessValueError( - f"Unsupported TF winding pack geometry: {i_tf_wp_geom}" - ) + case SuperconductingTFWPShapeType.TRAPEZOIDAL: + dx_tf_side_case_peak = dx_tf_side_case_min + + case _: + raise ProcessValueError( + f"Unsupported TF winding pack geometry: {i_tf_wp_geom}" + ) return ( a_tf_coil_inboard_case, @@ -2875,218 +2879,237 @@ def tf_cable_in_conduit_superconductor_properties( strain = data.tfcoil.str_wp # ================================================================= + match SuperconductorModel(i_tf_superconductor): + # ITER Nb3Sn critical surface parameterization + case SuperconductorModel.ITER_NB3SN: + # Peak field and temperature at zero strain + bc20m = SuperconductorModel.ITER_NB3SN.b_crit_zero_field_strain # [T] + tc0m = SuperconductorModel.ITER_NB3SN.temp_crit_zero_field_strain # [K] + + # If strain limit achieved, throw a warning and use the lower strain + if abs(strain) > 0.5e-2: + logger.error( + f"TF strain={strain} was outside the region of applicability. " + f"Used lower strain." + ) + strain = np.sign(strain) * 0.5e-2 - # ITER Nb3Sn critical surface parameterization - if i_tf_superconductor == SuperconductorModel.ITER_NB3SN: - # Peak field and temperature at zero strain - bc20m = SuperconductorModel.ITER_NB3SN.b_crit_zero_field_strain # [T] - tc0m = SuperconductorModel.ITER_NB3SN.temp_crit_zero_field_strain # [K] - - # If strain limit achieved, throw a warning and use the lower strain - if abs(strain) > 0.5e-2: - logger.error( - f"TF strain={strain} was outside the region of applicability. " - f"Used lower strain." + # j_superconductor_critical returned by superconductors.itersc is the + # critical current density in the superconductor - not the whole strand, + # which contains copper + j_superconductor_critical, _, _ = superconductors.itersc( + temp_conductor=temp_tf_coolant_peak_field, + b_conductor=b_tf_inboard_peak, + strain=strain, + b_c20max=bc20m, + temp_c0max=tc0m, ) - strain = np.sign(strain) * 0.5e-2 - - # j_superconductor_critical returned by superconductors.itersc is the - # critical current density in the superconductor - not the whole strand, - # which contains copper - j_superconductor_critical, _, _ = superconductors.itersc( - temp_conductor=temp_tf_coolant_peak_field, - b_conductor=b_tf_inboard_peak, - strain=strain, - b_c20max=bc20m, - temp_c0max=tc0m, - ) - # Scale for the copper area fraction of the cable - j_cables_critical = j_superconductor_critical * ( - 1.0e0 - f_a_tf_turn_cable_copper - ) + # Scale for the copper area fraction of the cable + j_cables_critical = j_superconductor_critical * ( + 1.0e0 - f_a_tf_turn_cable_copper + ) - # Critical current in turn all turn cables - c_turn_cables_critical = j_cables_critical * a_tf_turn_cable_space_effective + # Critical current in turn all turn cables + c_turn_cables_critical = ( + j_cables_critical * a_tf_turn_cable_space_effective + ) - # Strand critical current calculation for costing in $/kAm - # = Superconducting filaments jc * (1 - strand copper fraction) - data.tfcoil.j_crit_str_tf = j_superconductor_critical * ( - 1.0e0 - f_a_tf_turn_cable_copper - ) + # Strand critical current calculation for costing in $/kAm + # = Superconducting filaments jc * (1 - strand copper fraction) + data.tfcoil.j_crit_str_tf = j_superconductor_critical * ( + 1.0e0 - f_a_tf_turn_cable_copper + ) - # ================================================================= + # ================================================================= + + # Bi-2212 high temperature superconductor parameterization + case SuperconductorModel.BI2212: + # Current density in a strand of Bi-2212 conductor + # N.B. jcrit returned by superconductors.bi2212 is the critical + # current density in the strand, not just the superconducting portion. + # The parameterization for j_crit_cable assumes a particular strand + # composition that does not require a user-defined copper fraction, + # so this is irrelevant in this model + j_strand = ( + j_tf_wp + * a_tf_turn + / (a_tf_turn_cable_space * f_a_tf_turn_cable_space_conductor) + ) - # Bi-2212 high temperature superconductor parameterization - elif i_tf_superconductor == SuperconductorModel.BI2212: - # Current density in a strand of Bi-2212 conductor - # N.B. jcrit returned by superconductors.bi2212 is the critical - # current density in the strand, not just the superconducting portion. - # The parameterization for j_crit_cable assumes a particular strand - # composition that does not require a user-defined copper fraction, - # so this is irrelevant in this model - j_strand = ( - j_tf_wp - * a_tf_turn - / (a_tf_turn_cable_space * f_a_tf_turn_cable_space_conductor) - ) + j_crit_cable, _ = superconductors.bi2212( + b_conductor=b_tf_inboard_peak, + jstrand=j_strand, + temp_conductor=temp_tf_coolant_peak_field, + f_strain=f_strain_scale, + ) + j_superconductor_critical = j_crit_cable / ( + 1.0e0 - f_a_tf_turn_cable_copper + ) + # Critical current in cable + c_turn_cables_critical = ( + j_crit_cable + * a_tf_turn_cable_space + * f_a_tf_turn_cable_space_conductor + ) - j_crit_cable, _ = superconductors.bi2212( - b_conductor=b_tf_inboard_peak, - jstrand=j_strand, - temp_conductor=temp_tf_coolant_peak_field, - f_strain=f_strain_scale, - ) - j_superconductor_critical = j_crit_cable / (1.0e0 - f_a_tf_turn_cable_copper) - # Critical current in cable - c_turn_cables_critical = ( - j_crit_cable * a_tf_turn_cable_space * f_a_tf_turn_cable_space_conductor - ) + # Strand critical current calulation for costing in $ / kAm + # Copper in the strand is already accounted for + data.tfcoil.j_crit_str_tf = j_superconductor_critical + # ================================================================= + + # NbTi data + case SuperconductorModel.OLD_LUBELL_NBTI: + bc20m = ( + SuperconductorModel.OLD_LUBELL_NBTI.b_crit_zero_field_strain + ) # [T] + tc0m = ( + SuperconductorModel.OLD_LUBELL_NBTI.temp_crit_zero_field_strain + ) # [K] + c0 = 1.0e10 # [A/m²] + + j_superconductor_critical, _ = superconductors.jcrit_nbti( + temp_conductor=temp_tf_coolant_peak_field, + b_conductor=b_tf_inboard_peak, + c0=c0, + b_c20max=bc20m, + temp_c0max=tc0m, + ) - # Strand critical current calulation for costing in $ / kAm - # Copper in the strand is already accounted for - data.tfcoil.j_crit_str_tf = j_superconductor_critical - # ================================================================= + # Scale for the copper area fraction of the cable + j_cables_critical = j_superconductor_critical * ( + 1.0e0 - f_a_tf_turn_cable_copper + ) - # NbTi data - elif i_tf_superconductor == SuperconductorModel.OLD_LUBELL_NBTI: - bc20m = SuperconductorModel.OLD_LUBELL_NBTI.b_crit_zero_field_strain # [T] - tc0m = SuperconductorModel.OLD_LUBELL_NBTI.temp_crit_zero_field_strain # [K] - c0 = 1.0e10 # [A/m²] - - j_superconductor_critical, _ = superconductors.jcrit_nbti( - temp_conductor=temp_tf_coolant_peak_field, - b_conductor=b_tf_inboard_peak, - c0=c0, - b_c20max=bc20m, - temp_c0max=tc0m, - ) + # Critical current in turn all turn cables + c_turn_cables_critical = ( + j_cables_critical * a_tf_turn_cable_space_effective + ) - # Scale for the copper area fraction of the cable - j_cables_critical = j_superconductor_critical * ( - 1.0e0 - f_a_tf_turn_cable_copper - ) + # Strand critical current calulation for costing in $ / kAm + # = superconducting filaments jc * (1 -strand copper fraction) + data.tfcoil.j_crit_str_tf = j_superconductor_critical * ( + 1.0e0 - f_a_tf_turn_cable_copper + ) - # Critical current in turn all turn cables - c_turn_cables_critical = j_cables_critical * a_tf_turn_cable_space_effective + # ================================================================= - # Strand critical current calulation for costing in $ / kAm - # = superconducting filaments jc * (1 -strand copper fraction) - data.tfcoil.j_crit_str_tf = j_superconductor_critical * ( - 1.0e0 - f_a_tf_turn_cable_copper - ) + # ITER Nb3Sn parameterization, but user-defined parameters + case SuperconductorModel.USER_DEFINED_NB3SN: + bc20m = bcritsc # [T] + tc0m = tcritsc # [K] - # ================================================================= + # If strain limit achieved, throw a warning and use the lower strain + if abs(strain) > 0.5e-2: + logger.error( + f"TF strain={strain} was outside the region of applicability. " + f"Used lower strain." + ) + strain = np.sign(strain) * 0.5e-2 - # ITER Nb3Sn parameterization, but user-defined parameters - elif i_tf_superconductor == SuperconductorModel.USER_DEFINED_NB3SN: - bc20m = bcritsc # [T] - tc0m = tcritsc # [K] + j_superconductor_critical, _, _ = superconductors.itersc( + temp_conductor=temp_tf_coolant_peak_field, + b_conductor=b_tf_inboard_peak, + strain=strain, + b_c20max=bc20m, + temp_c0max=tc0m, + ) + # Scale for the copper area fraction of the cable + j_cables_critical = j_superconductor_critical * ( + 1.0e0 - f_a_tf_turn_cable_copper + ) - # If strain limit achieved, throw a warning and use the lower strain - if abs(strain) > 0.5e-2: - logger.error( - f"TF strain={strain} was outside the region of applicability. " - f"Used lower strain." + # Critical current in turn all turn cables + c_turn_cables_critical = ( + j_cables_critical * a_tf_turn_cable_space_effective ) - strain = np.sign(strain) * 0.5e-2 - j_superconductor_critical, _, _ = superconductors.itersc( - temp_conductor=temp_tf_coolant_peak_field, - b_conductor=b_tf_inboard_peak, - strain=strain, - b_c20max=bc20m, - temp_c0max=tc0m, - ) - # Scale for the copper area fraction of the cable - j_cables_critical = j_superconductor_critical * ( - 1.0e0 - f_a_tf_turn_cable_copper - ) + # Strand critical current calulation for costing in $ / kAm + # = superconducting filaments jc * (1 -strand copper fraction) + data.tfcoil.j_crit_str_tf = j_superconductor_critical * ( + 1.0e0 - f_a_tf_turn_cable_copper + ) - # Critical current in turn all turn cables - c_turn_cables_critical = j_cables_critical * a_tf_turn_cable_space_effective + # ================================================================= - # Strand critical current calulation for costing in $ / kAm - # = superconducting filaments jc * (1 -strand copper fraction) - data.tfcoil.j_crit_str_tf = j_superconductor_critical * ( - 1.0e0 - f_a_tf_turn_cable_copper - ) + # WST Nb3Sn parameterisation + case SuperconductorModel.WST_NB3SN: + bc20m = SuperconductorModel.WST_NB3SN.b_crit_zero_field_strain # [T] + tc0m = SuperconductorModel.WST_NB3SN.temp_crit_zero_field_strain # [K] - # ================================================================= + # If strain limit achieved, throw a warning and use the lower strain + if abs(strain) > 0.5e-2: + logger.error( + f"TF strain={strain} was outside the region of applicability. " + f"Used lower strain." + ) + strain = np.sign(strain) * 0.5e-2 + + # j_superconductor_critical returned by superconductors.itersc is the + # critical current density in the superconductor - not the whole strand, + # which contains copper + j_superconductor_critical, _, _ = ( + superconductors.western_superconducting_nb3sn( + temp_conductor=temp_tf_coolant_peak_field, + b_conductor=b_tf_inboard_peak, + strain=strain, + b_c20max=bc20m, + temp_c0max=tc0m, + ) + ) + # Scale for the copper area fraction of the cable + j_cables_critical = j_superconductor_critical * ( + 1.0e0 - f_a_tf_turn_cable_copper + ) - # WST Nb3Sn parameterisation - elif i_tf_superconductor == SuperconductorModel.WST_NB3SN: - bc20m = SuperconductorModel.WST_NB3SN.b_crit_zero_field_strain # [T] - tc0m = SuperconductorModel.WST_NB3SN.temp_crit_zero_field_strain # [K] + # Critical current in turn all turn cables + c_turn_cables_critical = ( + j_cables_critical * a_tf_turn_cable_space_effective + ) - # If strain limit achieved, throw a warning and use the lower strain - if abs(strain) > 0.5e-2: - logger.error( - f"TF strain={strain} was outside the region of applicability. " - f"Used lower strain." + # Strand critical current calulation for costing in $ / kAm + # = superconducting filaments jc * (1 -strand copper fraction) + data.tfcoil.j_crit_str_tf = j_superconductor_critical * ( + 1.0e0 - f_a_tf_turn_cable_copper ) - strain = np.sign(strain) * 0.5e-2 - # j_superconductor_critical returned by superconductors.itersc is the - # critical current density in the superconductor - not the whole strand, - # which contains copper - j_superconductor_critical, _, _ = ( - superconductors.western_superconducting_nb3sn( + # ================================================================= + + # Durham Ginzburg-Landau Nb-Ti parameterisation + case SuperconductorModel.DURHAM_NBTI: + bc20m = SuperconductorModel.DURHAM_NBTI.b_crit_zero_field_strain # [T] + tc0m = SuperconductorModel.DURHAM_NBTI.temp_crit_zero_field_strain # [K] + + j_superconductor_critical, _, _ = superconductors.gl_nbti( temp_conductor=temp_tf_coolant_peak_field, b_conductor=b_tf_inboard_peak, strain=strain, b_c20max=bc20m, - temp_c0max=tc0m, + t_c0=tc0m, + ) + # Scale for the copper area fraction of the cable + j_cables_critical = j_superconductor_critical * ( + 1.0e0 - f_a_tf_turn_cable_copper ) - ) - # Scale for the copper area fraction of the cable - j_cables_critical = j_superconductor_critical * ( - 1.0e0 - f_a_tf_turn_cable_copper - ) - - # Critical current in turn all turn cables - c_turn_cables_critical = j_cables_critical * a_tf_turn_cable_space_effective - - # Strand critical current calulation for costing in $ / kAm - # = superconducting filaments jc * (1 -strand copper fraction) - data.tfcoil.j_crit_str_tf = j_superconductor_critical * ( - 1.0e0 - f_a_tf_turn_cable_copper - ) - - # ================================================================= - - # Durham Ginzburg-Landau Nb-Ti parameterisation - elif i_tf_superconductor == SuperconductorModel.DURHAM_NBTI: - bc20m = SuperconductorModel.DURHAM_NBTI.b_crit_zero_field_strain # [T] - tc0m = SuperconductorModel.DURHAM_NBTI.temp_crit_zero_field_strain # [K] - - j_superconductor_critical, _, _ = superconductors.gl_nbti( - temp_conductor=temp_tf_coolant_peak_field, - b_conductor=b_tf_inboard_peak, - strain=strain, - b_c20max=bc20m, - t_c0=tc0m, - ) - # Scale for the copper area fraction of the cable - j_cables_critical = j_superconductor_critical * ( - 1.0e0 - f_a_tf_turn_cable_copper - ) - # Critical current in turn all turn cables - c_turn_cables_critical = j_cables_critical * a_tf_turn_cable_space_effective + # Critical current in turn all turn cables + c_turn_cables_critical = ( + j_cables_critical * a_tf_turn_cable_space_effective + ) - # Strand critical current calulation for costing in $ / kAm - # = superconducting filaments jc * (1 -strand copper fraction) - data.tfcoil.j_crit_str_tf = j_superconductor_critical * ( - 1.0e0 - f_a_tf_turn_cable_copper - ) + # Strand critical current calulation for costing in $ / kAm + # = superconducting filaments jc * (1 -strand copper fraction) + data.tfcoil.j_crit_str_tf = j_superconductor_critical * ( + 1.0e0 - f_a_tf_turn_cable_copper + ) - # ================================================================= + # ================================================================= - else: - raise ProcessValueError( - "Illegal value for i_tf_sc_mat", i_tf_superconductor=i_tf_superconductor - ) + case _: + raise ProcessValueError( + "Illegal value for i_tf_superconductor", + i_tf_superconductor=i_tf_superconductor, + ) # ================================================================= @@ -3760,53 +3783,51 @@ def run(self, output: bool = False): d_sc_tf = self.data.superconducting_tfcoil - # Setting the WP turn geometry / areas - if ( - TFWPIntegerTurnType(self.data.tfcoil.i_tf_turns_integer) - == TFWPIntegerTurnType.NON_INTEGER - ): - # Non-ingeger number of turns - - avg_turn_geometry = self.tf_croco_averaged_turn_geometry( - j_tf_wp=self.data.tfcoil.j_tf_wp, - dx_tf_turn_steel=self.data.tfcoil.dx_tf_turn_steel, - dx_tf_turn_insulation=self.data.tfcoil.dx_tf_turn_insulation, - dx_tf_turn_general=self.data.tfcoil.dx_tf_turn_general, - c_tf_turn=self.data.tfcoil.c_tf_turn, - i_dx_tf_turn_general_input=self.data.tfcoil.i_dx_tf_turn_general_input, - i_dx_tf_turn_cable_space_general_input=self.data.tfcoil.i_dx_tf_turn_cable_space_general_input, - dx_tf_turn_cable_space_general=self.data.tfcoil.dx_tf_turn_cable_space_general, - layer_ins=self.data.tfcoil.layer_ins, - a_tf_wp_no_insulation=d_sc_tf.a_tf_wp_no_insulation, - ) + match TFWPIntegerTurnType(self.data.tfcoil.i_tf_turns_integer): + # Setting the WP turn geometry / areas + # Setting the WP turn geometry / areas + case TFWPIntegerTurnType.NON_INTEGER: + avg_turn_geometry = self.tf_croco_averaged_turn_geometry( + j_tf_wp=self.data.tfcoil.j_tf_wp, + dx_tf_turn_steel=self.data.tfcoil.dx_tf_turn_steel, + dx_tf_turn_insulation=self.data.tfcoil.dx_tf_turn_insulation, + dx_tf_turn_general=self.data.tfcoil.dx_tf_turn_general, + c_tf_turn=self.data.tfcoil.c_tf_turn, + i_dx_tf_turn_general_input=self.data.tfcoil.i_dx_tf_turn_general_input, + i_dx_tf_turn_cable_space_general_input=self.data.tfcoil.i_dx_tf_turn_cable_space_general_input, + dx_tf_turn_cable_space_general=self.data.tfcoil.dx_tf_turn_cable_space_general, + layer_ins=self.data.tfcoil.layer_ins, + a_tf_wp_no_insulation=d_sc_tf.a_tf_wp_no_insulation, + ) - self.data.tfcoil.a_tf_turn_cable_space_no_void = ( - avg_turn_geometry.a_tf_turn_cable_space_no_void - ) - self.data.tfcoil.a_tf_turn_steel = avg_turn_geometry.a_tf_turn_steel - self.data.tfcoil.a_tf_turn_insulation = ( - avg_turn_geometry.a_tf_turn_insulation - ) - self.data.tfcoil.n_tf_coil_turns = avg_turn_geometry.n_tf_coil_turns - self.data.tfcoil.dx_tf_turn_general = avg_turn_geometry.dx_tf_turn_general - self.data.tfcoil.c_tf_turn = avg_turn_geometry.c_tf_turn - self.data.tfcoil.dx_tf_turn_general = avg_turn_geometry.dx_tf_turn_general - d_sc_tf.dr_tf_turn = avg_turn_geometry.dr_tf_turn - d_sc_tf.dx_tf_turn = avg_turn_geometry.dx_tf_turn - self.data.tfcoil.dx_tf_turn_conduit_full_average = ( - avg_turn_geometry.dx_tf_turn_conduit_full_average - ) - d_sc_tf.dx_tf_turn_cable_space_average = ( - avg_turn_geometry.dx_tf_turn_cable_space_average - ) + self.data.tfcoil.a_tf_turn_cable_space_no_void = ( + avg_turn_geometry.a_tf_turn_cable_space_no_void + ) + self.data.tfcoil.a_tf_turn_steel = avg_turn_geometry.a_tf_turn_steel + self.data.tfcoil.a_tf_turn_insulation = ( + avg_turn_geometry.a_tf_turn_insulation + ) + self.data.tfcoil.n_tf_coil_turns = avg_turn_geometry.n_tf_coil_turns + self.data.tfcoil.dx_tf_turn_general = ( + avg_turn_geometry.dx_tf_turn_general + ) + self.data.tfcoil.c_tf_turn = avg_turn_geometry.c_tf_turn + self.data.tfcoil.dx_tf_turn_general = ( + avg_turn_geometry.dx_tf_turn_general + ) + d_sc_tf.dr_tf_turn = avg_turn_geometry.dr_tf_turn + d_sc_tf.dx_tf_turn = avg_turn_geometry.dx_tf_turn + self.data.tfcoil.dx_tf_turn_conduit_full_average = ( + avg_turn_geometry.dx_tf_turn_conduit_full_average + ) + d_sc_tf.dx_tf_turn_cable_space_average = ( + avg_turn_geometry.dx_tf_turn_cable_space_average + ) - elif ( - TFWPIntegerTurnType(self.data.tfcoil.i_tf_turns_integer) - == TFWPIntegerTurnType.INTEGER - ): - raise ProcessValueError( - "Integer turn geometry not implemented for CroCo conductor." - ) + case TFWPIntegerTurnType.INTEGER: + raise ProcessValueError( + "Integer turn geometry not implemented for CroCo conductor." + ) croco_cable_space_geometry = self.tf_turn_croco_cable_space_properties( dx_tf_turn_conduit_full_average=self.data.tfcoil.dx_tf_turn_conduit_full_average, @@ -4395,75 +4416,80 @@ def tf_croco_superconductor_properties( else: strain = self.data.tfcoil.str_wp - # ================================================================= + match SuperconductorModel(i_tf_superconductor): + # ================================================================= + + case SuperconductorModel.CROCO_REBCO: + b_c20_max = ( + SuperconductorModel.CROCO_REBCO.b_crit_zero_field_strain + ) # [T] + t_c0 = SuperconductorModel.CROCO_REBCO.temp_crit_zero_field_strain # [K] + # Find critical current density in superconducting cable, j_crit_cable + j_superconductor_critical, _, bc20m, tc0m = superconductors.jcrit_rebco( + temp_conductor=temp_tf_peak, + b_conductor=b_tf_inboard_peak, + temp_c0_max=t_c0, + b_c20_max=b_c20_max, + ) - if i_tf_superconductor == SuperconductorModel.CROCO_REBCO: - b_c20_max = SuperconductorModel.CROCO_REBCO.b_crit_zero_field_strain # [T] - t_c0 = SuperconductorModel.CROCO_REBCO.temp_crit_zero_field_strain # [K] - # Find critical current density in superconducting cable, j_crit_cable - j_superconductor_critical, _, bc20m, tc0m = superconductors.jcrit_rebco( - temp_conductor=temp_tf_peak, - b_conductor=b_tf_inboard_peak, - temp_c0_max=t_c0, - b_c20_max=b_c20_max, - ) + # ================================================================= - # ================================================================= + # Durham Ginzburg-Landau critical surface model for REBCO + case SuperconductorModel.DURHAM_REBCO: + bc20m = SuperconductorModel.DURHAM_REBCO.b_crit_zero_field_strain # [T] + tc0m = ( + SuperconductorModel.DURHAM_REBCO.temp_crit_zero_field_strain + ) # [K] - # Durham Ginzburg-Landau critical surface model for REBCO - elif i_tf_superconductor == SuperconductorModel.DURHAM_REBCO: - bc20m = SuperconductorModel.DURHAM_REBCO.b_crit_zero_field_strain # [T] - tc0m = SuperconductorModel.DURHAM_REBCO.temp_crit_zero_field_strain # [K] + # If strain limit achieved, throw a warning and use the lower strain + if abs(strain) > 0.7e-2: + logger.error( + f"TF strain={strain} was outside the region of applicability. " + f"Used lower strain." + ) + strain = np.sign(strain) * 0.7e-2 - # If strain limit achieved, throw a warning and use the lower strain - if abs(strain) > 0.7e-2: - logger.error( - f"TF strain={strain} was outside the region of applicability. " - f"Used lower strain." + j_superconductor_critical, _, _ = superconductors.gl_rebco( + temp_conductor=temp_tf_peak, + b_conductor=b_tf_inboard_peak, + strain=strain, + b_c20max=bc20m, + t_c0=tc0m, ) - strain = np.sign(strain) * 0.7e-2 - j_superconductor_critical, _, _ = superconductors.gl_rebco( - temp_conductor=temp_tf_peak, - b_conductor=b_tf_inboard_peak, - strain=strain, - b_c20max=bc20m, - t_c0=tc0m, - ) - - # ================================================================= + # ================================================================= + + # Hazelton experimental data + Zhai conceptual model for REBCO + case SuperconductorModel.HAZELTON_ZHAI_REBCO: + bc20m = ( + SuperconductorModel.HAZELTON_ZHAI_REBCO.b_crit_zero_field_strain + ) # [T] + tc0m = ( + SuperconductorModel.HAZELTON_ZHAI_REBCO.temp_crit_zero_field_strain + ) # [K] + + # If strain limit achieved, throw a warning and use the lower strain + if abs(strain) > 0.7e-2: + logger.error( + f"TF strain={strain} was outside the region of applicability. " + f"Used lower strain." + ) + strain = np.sign(strain) * 0.7e-2 - # Hazelton experimental data + Zhai conceptual model for REBCO - elif i_tf_superconductor == SuperconductorModel.HAZELTON_ZHAI_REBCO: - bc20m = ( - SuperconductorModel.HAZELTON_ZHAI_REBCO.b_crit_zero_field_strain - ) # [T] - tc0m = ( - SuperconductorModel.HAZELTON_ZHAI_REBCO.temp_crit_zero_field_strain - ) # [K] - - # If strain limit achieved, throw a warning and use the lower strain - if abs(strain) > 0.7e-2: - logger.error( - f"TF strain={strain} was outside the region of applicability. " - f"Used lower strain." + # 'high current density' as per parameterisation described in Wolf, + # and based on Hazelton experimental data and Zhai conceptual model; + # see subroutine for full references + j_superconductor_critical, _, _ = superconductors.hijc_rebco( + temp_conductor=temp_tf_peak, + b_conductor=b_tf_inboard_peak, + b_c20max=bc20m, + t_c0=tc0m, + dr_hts_tape=dr_tf_hts_tape, + dx_hts_tape_rebco=dx_tf_hts_tape_rebco, + dx_hts_tape_total=dx_tf_hts_tape_total, ) - strain = np.sign(strain) * 0.7e-2 - - # 'high current density' as per parameterisation described in Wolf, - # and based on Hazelton experimental data and Zhai conceptual model; - # see subroutine for full references - j_superconductor_critical, _, _ = superconductors.hijc_rebco( - temp_conductor=temp_tf_peak, - b_conductor=b_tf_inboard_peak, - b_c20max=bc20m, - t_c0=tc0m, - dr_hts_tape=dr_tf_hts_tape, - dx_hts_tape_rebco=dx_tf_hts_tape_rebco, - dx_hts_tape_total=dx_tf_hts_tape_total, - ) - # ================================================================= + # ================================================================= # Strand critical current calulation for costing in $ / kAm # Already includes buffer and support layers so no need to include @@ -4634,30 +4660,31 @@ def croco_voltage(self) -> float: """Calculates CROCO voltage""" d_sc_tf = self.data.superconducting_tfcoil - if self.data.tfcoil.quench_model == "linear": - d_sc_tf.time2 = self.data.tfcoil.t_tf_superconductor_quench - croco_voltage = ( - 2.0e0 - / d_sc_tf.time2 - * ( - self.data.tfcoil.e_tf_magnetic_stored_total - / self.data.tfcoil.n_tf_coils + match self.data.tfcoil.quench_model: + case "linear": + d_sc_tf.time2 = self.data.tfcoil.t_tf_superconductor_quench + croco_voltage = ( + 2.0e0 + / d_sc_tf.time2 + * ( + self.data.tfcoil.e_tf_magnetic_stored_total + / self.data.tfcoil.n_tf_coils + ) + / self.data.tfcoil.c_tf_turn ) - / self.data.tfcoil.c_tf_turn - ) - elif self.data.tfcoil.quench_model == "exponential": - d_sc_tf.tau2 = self.data.tfcoil.t_tf_superconductor_quench - croco_voltage = ( - 2.0e0 - / d_sc_tf.tau2 - * ( - self.data.tfcoil.e_tf_magnetic_stored_total - / self.data.tfcoil.n_tf_coils + case "exponential": + d_sc_tf.tau2 = self.data.tfcoil.t_tf_superconductor_quench + croco_voltage = ( + 2.0e0 + / d_sc_tf.tau2 + * ( + self.data.tfcoil.e_tf_magnetic_stored_total + / self.data.tfcoil.n_tf_coils + ) + / self.data.tfcoil.c_tf_turn ) - / self.data.tfcoil.c_tf_turn - ) - else: - return 0.0 + case _: + return 0.0 return croco_voltage diff --git a/process/models/vacuum.py b/process/models/vacuum.py index e64ddfbec7..4c55cd17cd 100644 --- a/process/models/vacuum.py +++ b/process/models/vacuum.py @@ -7,6 +7,7 @@ from process.core import constants, process_output from process.core import process_output as po +from process.core.exceptions import ProcessValueError from process.core.model import Model from process.data_structure.vacuum_variables import VacuumPumpType from process.models.build import FwBlktVVShape @@ -58,42 +59,44 @@ def run(self, output: bool = False): bld = self.data.build phy = self.data.physics - if vp.i_vacuum_pumping == "old": - ( - pumpn, - vp.n_vv_vacuum_ducts, - vp.dlscal, - vp.m_vv_vacuum_duct_shield, - vp.dia_vv_vacuum_ducts, - ) = self.vacuum( - phy.p_fusion_total_mw, - phy.rmajor, - phy.rminor, - 0.5e0 * (bld.dr_fw_plasma_gap_inboard + bld.dr_fw_plasma_gap_outboard), - phy.a_plasma_surface, - phy.vol_plasma, - bld.dr_shld_outboard, - bld.dr_shld_inboard, - bld.dr_tf_inboard, - bld.r_shld_inboard_inner - - bld.dr_shld_vv_gap_inboard - - bld.dr_vv_inboard, - self.data.tfcoil.n_tf_coils, - self.data.times.t_plant_pulse_dwell, - phy.nd_plasma_electrons_vol_avg, - self.data.divertor.n_divertors, - qtorus, - gasld, - output=output, - ) - # MDK pumpn is real: convert to integer by rounding. - vp.n_vac_pumps_high = math.floor(pumpn + 0.5e0) - elif vp.i_vacuum_pumping == "simple": - vp.n_iter_vacuum_pumps = self.vacuum_simple(output=output) - else: - logger.error( - f"i_vacuum_pumping is invalid: {self.data.vacuum.i_vacuum_pumping}" - ) + match vp.i_vacuum_pumping: + case "old": + ( + pumpn, + vp.n_vv_vacuum_ducts, + vp.dlscal, + vp.m_vv_vacuum_duct_shield, + vp.dia_vv_vacuum_ducts, + ) = self.vacuum( + phy.p_fusion_total_mw, + phy.rmajor, + phy.rminor, + 0.5e0 + * (bld.dr_fw_plasma_gap_inboard + bld.dr_fw_plasma_gap_outboard), + phy.a_plasma_surface, + phy.vol_plasma, + bld.dr_shld_outboard, + bld.dr_shld_inboard, + bld.dr_tf_inboard, + bld.r_shld_inboard_inner + - bld.dr_shld_vv_gap_inboard + - bld.dr_vv_inboard, + self.data.tfcoil.n_tf_coils, + self.data.times.t_plant_pulse_dwell, + phy.nd_plasma_electrons_vol_avg, + self.data.divertor.n_divertors, + qtorus, + gasld, + output=output, + ) + # MDK pumpn is real: convert to integer by rounding. + vp.n_vac_pumps_high = math.floor(pumpn + 0.5e0) + case "simple": + vp.n_iter_vacuum_pumps = self.vacuum_simple(output=output) + case _: + ProcessValueError( + f"i_vacuum_pumping is invalid: {self.data.vacuum.i_vacuum_pumping}" + ) def vacuum_simple(self, output) -> float: """Simple model of vacuum pumping system @@ -334,12 +337,21 @@ def vacuum( volume = plasma_vol * (aw + dsol) * (aw + dsol) / (aw * aw) # dwell pumping options - if (self.data.vacuum.i_vac_pump_dwell == 1) or (t_plant_pulse_dwell == 0): - tpump = self.data.times.t_plant_pulse_coil_precharge - elif self.data.vacuum.i_vac_pump_dwell == 2: - tpump = t_plant_pulse_dwell + self.data.times.t_plant_pulse_coil_precharge - else: - tpump = t_plant_pulse_dwell + match self.data.vacuum.i_vac_pump_dwell: + case 1: + tpump = self.data.times.t_plant_pulse_coil_precharge + case 2: + tpump = ( + t_plant_pulse_dwell + self.data.times.t_plant_pulse_coil_precharge + ) + case _: + tpump = t_plant_pulse_dwell + + tpump = ( + self.data.times.t_plant_pulse_coil_precharge + if t_plant_pulse_dwell == 0 + else t_plant_pulse_dwell + ) s.append(volume / tpump * math.log(pend / pstart)) @@ -688,20 +700,23 @@ def _write_to_outfile( # noqa: PLR0917 self.outfile, "The vacuum pumping system size is governed by the" ) - if imax == 1: - process_output.ocmmnt( - self.outfile, "requirements for pumpdown to base pressure." - ) - elif imax == 2: - process_output.ocmmnt( - self.outfile, "requirements for pumpdown between burns." - ) - elif imax == 3: - process_output.ocmmnt(self.outfile, "requirements for helium ash removal.") - else: - process_output.ocmmnt( - self.outfile, "requirements for D-T removal at fuelling rate." - ) + match imax: + case 1: + process_output.ocmmnt( + self.outfile, "requirements for pumpdown to base pressure." + ) + case 2: + process_output.ocmmnt( + self.outfile, "requirements for pumpdown between burns." + ) + case 3: + process_output.ocmmnt( + self.outfile, "requirements for helium ash removal." + ) + case _: + process_output.ocmmnt( + self.outfile, "requirements for D-T removal at fuelling rate." + ) process_output.oblnkl(self.outfile) process_output.ovarre( diff --git a/tests/unit/models/physics/test_plasma_current.py b/tests/unit/models/physics/test_plasma_current.py index 4ed331e10a..88167e3b45 100644 --- a/tests/unit/models/physics/test_plasma_current.py +++ b/tests/unit/models/physics/test_plasma_current.py @@ -3,7 +3,8 @@ import numpy as np import pytest -from process.models.physics.plasma_current import PlasmaCurrent, PlasmaCurrentModel +from process.data_structure.physics_variables import PlasmaCurrentModel +from process.models.physics.plasma_current import PlasmaCurrent from process.models.physics.plasma_fields import PlasmaFields