From f6b18839105afbbc6fa37ab0cadb34449628e6cd Mon Sep 17 00:00:00 2001 From: mn3981 Date: Thu, 1 Oct 2026 14:47:03 +0100 Subject: [PATCH 01/16] Add STEP Stacked Tape option to superconducting TF coil turn types --- process/core/input.py | 2 +- process/models/tfcoil/superconducting.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/process/core/input.py b/process/core/input.py index 31b6c6f6cd..b911b00511 100644 --- a/process/core/input.py +++ b/process/core/input.py @@ -1070,7 +1070,7 @@ def bounds(self) -> tuple[NumberType | None, NumberType | None]: "lsa": InputVariable("costs", int, range=(1, 4)), "m_res": InputVariable("stellarator", int, range=(1, 10)), "n_tf_wp_layers": InputVariable("tfcoil", int, range=(1, 100)), - "i_tf_turn_type": InputVariable("superconducting_tfcoil", int, choices=[1, 2]), + "i_tf_turn_type": InputVariable("superconducting_tfcoil", int, choices=[1, 2, 3]), "n_liq_recirc": InputVariable("fwbs", int, range=(1, 50)), "n_tf_wp_pancakes": InputVariable("tfcoil", int, range=(1, 100)), "n_rad_per_layer": InputVariable("tfcoil", int, range=(1, 500)), diff --git a/process/models/tfcoil/superconducting.py b/process/models/tfcoil/superconducting.py index 9bcc29b344..87b6adfcb6 100644 --- a/process/models/tfcoil/superconducting.py +++ b/process/models/tfcoil/superconducting.py @@ -35,6 +35,7 @@ class SuperconductingTFTurnType(IntEnum): CABLE_IN_CONDUIT = (1, "CICC", "Cable-in-Conduit Conductor") CROSS_CONDUCTOR = (2, "CroCo", "Cross Conductor") + STEP_STACKED_TAPE = (3, "STEP", "STEP Stacked Tape") def __new__(cls, value: int, abbreviation: str, full_name: str): """Create a new SuperconductingTFTurnType enum member From 8495f35c0c5829d2d280139373d688235df9bc67 Mon Sep 17 00:00:00 2001 From: mn3981 Date: Thu, 1 Oct 2026 15:34:38 +0100 Subject: [PATCH 02/16] Add STEP vertically stacked tape superconducting TF coil model --- process/core/caller.py | 2 + .../superconducting_tf_coil_variables.py | 24 + process/main.py | 3 + process/models/tfcoil/superconducting.py | 900 ++++++++++++++++++ 4 files changed, 929 insertions(+) diff --git a/process/core/caller.py b/process/core/caller.py index b2283e1516..5d9a326f38 100644 --- a/process/core/caller.py +++ b/process/core/caller.py @@ -306,6 +306,8 @@ def _call_models_once(self, xc: np.ndarray): self.models.cicc_sctfcoil.run() case SuperconductingTFTurnType.CROSS_CONDUCTOR: self.models.croco_sctfcoil.run() + case SuperconductingTFTurnType.STEP_STACKED_TAPE: + self.models.step_sctfcoil.run() case TFConductorModel.HELIUM_COOLED_ALUMINIUM: self.models.aluminium_tf_coil.run() diff --git a/process/data_structure/superconducting_tf_coil_variables.py b/process/data_structure/superconducting_tf_coil_variables.py index 34faf78cf8..13b8b3f47d 100644 --- a/process/data_structure/superconducting_tf_coil_variables.py +++ b/process/data_structure/superconducting_tf_coil_variables.py @@ -275,6 +275,30 @@ class SuperconductingTFData: a_tf_turn_croco_hastelloy: float = 0.0 """Area of the Hastelloy in the CroCo cable space of the TF turn (includes tapes and outer tube) (m²)""" + dx_tf_turn_tape_stack: float = 0.0 + """Thickness of the tape stack in the TF turn [m]""" + + dr_tf_turn_tape_stack: float = 0.0 + """Radial thickness of the tape stack in the TF turn [m]""" + + a_tf_turn_tape_stack: float = 0.0 + """Cross-sectional area of the tape stack in the TF turn [m²]""" + + x_tf_turn_coolant_channel_centre: float = 0.0 + """Vertical centre position of the coolant channel in the TF turn [m]""" + + dr_tf_turn_stabiliser: float = 0.0 + """Radial thickness of the stabiliser in the TF turn [m]""" + + dx_tf_turn_stabiliser: float = 0.0 + """Toroidal thickness of the stabiliser in the TF turn [m]""" + + a_tf_turn_stabiliser: float = 0.0 + """Cross-sectional area of the stabiliser in the TF turn [m²]""" + + n_tf_turn_superconducting_strands: int = 0.0 + """Number of superconducting strands in the TF turn""" + t1: float = 0.0 time2: float = 0.0 diff --git a/process/main.py b/process/main.py index d99959837f..7fec801f15 100644 --- a/process/main.py +++ b/process/main.py @@ -124,6 +124,7 @@ from process.models.tfcoil.superconducting import ( CICCSuperconductingTFCoil, CROCOSuperconductingTFCoil, + STEPSuperconductingTFCoil, SuperconductingTFCoil, SuperconductingTFTurnType, ) @@ -657,6 +658,7 @@ def __init__(self, data: DataStructure): self.sctfcoil = SuperconductingTFCoil() self.cicc_sctfcoil = CICCSuperconductingTFCoil() self.croco_sctfcoil = CROCOSuperconductingTFCoil() + self.step_sctfcoil = STEPSuperconductingTFCoil() self.tfcoil = TFCoil() self.resistive_tf_coil = ResistiveTFCoil() self.copper_tf_coil = CopperTFCoil() @@ -809,6 +811,7 @@ def models(self) -> tuple[Model, ...]: self.copper_tf_coil, self.cicc_sctfcoil, self.croco_sctfcoil, + self.step_sctfcoil, self.tfcoil, self.build, self.shield, diff --git a/process/models/tfcoil/superconducting.py b/process/models/tfcoil/superconducting.py index 87b6adfcb6..dfd09db1c6 100644 --- a/process/models/tfcoil/superconducting.py +++ b/process/models/tfcoil/superconducting.py @@ -4856,6 +4856,906 @@ def output_croco_info(self) -> None: ) +@dataclass +class STEPIntegerTurnGeometry: + """Geometry of a STEP integer turn in the superconducting TF coil.""" + + dr_tf_turn: float + dx_tf_turn: float + c_tf_turn: float + n_tf_coil_turns: int + dr_tf_turn_stabiliser: float + dx_tf_turn_stabiliser: float + x_tf_turn_coolant_channel_centre: float + dr_tf_turn_tape_stack: float + dx_tf_turn_tape_stack: float + a_tf_turn_tape_stack: float + a_tf_turn_insulation: float + a_tf_turn_stabiliser: float + + +class STEPSuperconductingTFCoil(SuperconductingTFCoil): + """STEP Vertically Stacked Tapes Superconducting TF Coil class. + + + Raises + ------ + ProcessValueError + + References + ---------- + [1] E. Nasr, S. C. Wimbush, P. Noonan, P. Harris, R. Gowland, and A. Petrov, + “The magnetic cage,” Philosophical Transactions of the Royal Society + A Mathematical Physical and Engineering Sciences, + vol. 382, no. 2280, Aug. 2024, doi: https://doi.org/10.1098/rsta.2023.0407. + + """ + + def run(self, output: bool = False): + """Run the superconducting TF coil model for a STEP vertically stacked tapes + conductor with HTS tape. + + Parameters + ---------- + output : bool + If True, print the results of the calculations. + + """ + self.run_base_superconducting_tf() + + d_sc_tf = self.data.superconducting_tfcoil + + match TFWPIntegerTurnType(self.data.tfcoil.i_tf_turns_integer): + # Setting the WP turn geometry / areas + case TFWPIntegerTurnType.INTEGER: + integer_turn_geometry: STEPIntegerTurnGeometry = self.tf_step_vertical_tape_integer_turn_geometry( + dr_tf_wp_with_insulation=self.data.tfcoil.dr_tf_wp_with_insulation, + dx_tf_wp_insulation=self.data.tfcoil.dx_tf_wp_insulation, + dx_tf_wp_insertion_gap=self.data.tfcoil.dx_tf_wp_insertion_gap, + n_tf_wp_layers=self.data.tfcoil.n_tf_wp_layers, + dx_tf_wp_toroidal_min=self.data.superconducting_tfcoil.dx_tf_wp_toroidal_min, + n_tf_wp_pancakes=self.data.tfcoil.n_tf_wp_pancakes, + c_tf_coil=self.data.superconducting_tfcoil.c_tf_coil, + dx_tf_turn_insulation=self.data.tfcoil.dx_tf_turn_insulation, + dia_tf_turn_coolant_channel=self.data.tfcoil.dia_tf_turn_coolant_channel, + ) + + d_sc_tf.dr_tf_turn = integer_turn_geometry.dr_tf_turn + d_sc_tf.dx_tf_turn = integer_turn_geometry.dx_tf_turn + self.data.tfcoil.c_tf_turn = integer_turn_geometry.c_tf_turn + self.data.tfcoil.n_tf_coil_turns = integer_turn_geometry.n_tf_coil_turns + d_sc_tf.dr_tf_turn_stabiliser = ( + integer_turn_geometry.dr_tf_turn_stabiliser + ) + d_sc_tf.dx_tf_turn_stabiliser = ( + integer_turn_geometry.dx_tf_turn_stabiliser + ) + d_sc_tf.x_tf_turn_coolant_channel_centre = ( + integer_turn_geometry.x_tf_turn_coolant_channel_centre + ) + d_sc_tf.dr_tf_turn_tape_stack = ( + integer_turn_geometry.dr_tf_turn_tape_stack + ) + d_sc_tf.dx_tf_turn_tape_stack = ( + integer_turn_geometry.dx_tf_turn_tape_stack + ) + d_sc_tf.a_tf_turn_tape_stack = integer_turn_geometry.a_tf_turn_tape_stack + self.data.tfcoil.a_tf_turn_insulation = ( + integer_turn_geometry.a_tf_turn_insulation + ) + d_sc_tf.a_tf_turn_stabiliser = integer_turn_geometry.a_tf_turn_stabiliser + + d_sc_tf.dr_tf_hts_tape = ( + self.data.superconducting_tfcoil.dr_tf_turn_tape_stack + ) + + case TFWPIntegerTurnType.NON_INTEGER: + raise ProcessValueError( + "Non integer turn geometry not implemented for STEP conductor." + ) + + self.data.superconducting_tfcoil.n_tf_turn_superconducting_strands = ( + self.calculate_stacked_tape_strand_count( + dr_tape_stack=self.data.superconducting_tfcoil.dx_tf_turn_tape_stack, + dr_hts_tape=self.data.superconducting_tfcoil.dx_tf_hts_tape_total, + ) + ) + + # Areas and fractions + # ------------------- + # Central helium channel down the conductor core [m2] + self.data.tfcoil.a_tf_wp_coolant_channels = ( + 0.25e0 + * self.data.tfcoil.n_tf_coil_turns + * np.pi + * self.data.tfcoil.dia_tf_turn_coolant_channel**2 + ) + + # Total conductor cross-sectional area, taking account of void area + # and central helium channel [m2] + self.data.tfcoil.a_tf_wp_conductor = ( + self.data.superconducting_tfcoil.a_tf_turn_tape_stack + * self.data.tfcoil.n_tf_coil_turns + ) + + # Void area in conductor for He, not including central channel [m2] + self.data.tfcoil.a_tf_wp_extra_void = 0.0 + + # Area of inter-turn insulation: total [m2] + self.data.tfcoil.a_tf_coil_wp_turn_insulation = ( + self.data.tfcoil.n_tf_coil_turns * self.data.tfcoil.a_tf_turn_insulation + ) + + self.data.tfcoil.a_tf_turn_steel = 0.0 + + # Area of steel structure in winding pack [m2] + self.data.tfcoil.a_tf_wp_steel = ( + self.data.tfcoil.n_tf_coil_turns * self.data.tfcoil.a_tf_turn_steel + ) + + # Inboard coil steel area [m2] + self.data.superconducting_tfcoil.a_tf_coil_inboard_steel = ( + self.data.tfcoil.a_tf_coil_inboard_case + self.data.tfcoil.a_tf_wp_steel + ) + + # Inboard coil steel fraction [-] + self.data.superconducting_tfcoil.f_a_tf_coil_inboard_steel = ( + self.data.tfcoil.n_tf_coils + * self.data.superconducting_tfcoil.a_tf_coil_inboard_steel + / self.data.tfcoil.a_tf_inboard_total + ) + + # Inboard coil insulation cross-section [m2] + self.data.superconducting_tfcoil.a_tf_coil_inboard_insulation = ( + self.data.tfcoil.a_tf_coil_wp_turn_insulation + + self.data.superconducting_tfcoil.a_tf_wp_ground_insulation + ) + + # Inboard coil insulation fraction [-] + self.data.superconducting_tfcoil.f_a_tf_coil_inboard_insulation = ( + self.data.tfcoil.n_tf_coils + * self.data.superconducting_tfcoil.a_tf_coil_inboard_insulation + / self.data.tfcoil.a_tf_inboard_total + ) + + # Cross-sectional area per turn + self.data.tfcoil.a_tf_turn = self.data.tfcoil.c_tf_total / ( + self.data.tfcoil.j_tf_wp + * self.data.tfcoil.n_tf_coils + * self.data.tfcoil.n_tf_coil_turns + ) + + superconductor_critical_properties = self.tf_step_superconductor_properties( + a_tf_turn=self.data.tfcoil.a_tf_turn, + b_tf_inboard_peak=self.data.tfcoil.b_tf_inboard_peak_with_ripple, + cur_tf_turn=self.data.tfcoil.c_tf_turn, + temp_tf_peak=self.data.tfcoil.tftmp, + i_tf_superconductor=self.data.tfcoil.i_tf_sc_mat, + dr_tf_hts_tape=self.data.superconducting_tfcoil.dr_tf_hts_tape, + dx_tf_hts_tape_rebco=self.data.superconducting_tfcoil.dx_tf_hts_tape_rebco, + dx_tf_hts_tape_total=self.data.superconducting_tfcoil.dx_tf_hts_tape_total, + ) + + self.data.tfcoil.j_tf_wp_critical = ( + superconductor_critical_properties.j_tf_wp_critical + ) + self.data.superconducting_tfcoil.j_tf_superconductor_critical = ( + superconductor_critical_properties.j_superconductor_critical + ) + self.data.superconducting_tfcoil.f_c_tf_turn_operating_critical = ( + superconductor_critical_properties.f_c_tf_turn_operating_critical + ) + self.data.superconducting_tfcoil.j_tf_superconductor = ( + superconductor_critical_properties.j_superconductor + ) + self.data.superconducting_tfcoil.j_tf_coil_turn = ( + superconductor_critical_properties.j_tf_coil_turn + ) + + d_sc_tf.b_tf_superconductor_critical_zero_temp_strain = ( + superconductor_critical_properties.bc20m + ) + d_sc_tf.temp_tf_superconductor_critical_zero_field_strain = ( + superconductor_critical_properties.tc0m + ) + d_sc_tf.c_tf_turn_cables_critical = ( + superconductor_critical_properties.c_turn_cables_critical + ) + + # if self.data.tfcoil.i_str_wp == 0: + # strain = self.data.tfcoil.str_tf_con_res + # else: + # strain = self.data.tfcoil.str_wp + + # self.data.tfcoil.temp_tf_superconductor_margin = self.calculate_superconductor_temperature_margin( + # i_tf_superconductor=self.data.tfcoil.i_tf_sc_mat, + # j_superconductor=self.data.superconducting_tfcoil.j_tf_superconductor, + # b_tf_inboard_peak=self.data.tfcoil.b_tf_inboard_peak_with_ripple, + # strain=strain, + # bc20m=self.data.superconducting_tfcoil.b_tf_superconductor_critical_zero_temp_strain, + # tc0m=self.data.superconducting_tfcoil.temp_tf_superconductor_critical_zero_field_strain, + # c0=1.0e10, + # temp_tf_coolant_peak_field=self.data.tfcoil.tftmp, + # data=self.data, + # ) + + # TFC Quench voltage in kV + + # Negative areas or fractions error reporting + if ( + self.data.tfcoil.a_tf_wp_conductor <= 0.0e0 + or self.data.tfcoil.a_tf_wp_extra_void <= 0.0e0 + or self.data.tfcoil.a_tf_coil_wp_turn_insulation <= 0.0e0 + or self.data.tfcoil.a_tf_wp_steel <= 0.0e0 + or self.data.superconducting_tfcoil.a_tf_coil_inboard_steel <= 0.0e0 + or self.data.superconducting_tfcoil.f_a_tf_coil_inboard_steel <= 0.0e0 + or self.data.superconducting_tfcoil.a_tf_coil_inboard_insulation <= 0.0e0 + or self.data.superconducting_tfcoil.f_a_tf_coil_inboard_insulation <= 0.0e0 + ): + logger.error( + "One of the areas or fractions is negative in the internal SC TF " + "coil geometry " + f"{self.data.tfcoil.a_tf_wp_conductor=} " + f"{self.data.tfcoil.a_tf_wp_extra_void=} " + f"{self.data.tfcoil.a_tf_coil_wp_turn_insulation=} " + f"{self.data.tfcoil.a_tf_wp_steel=} " + f"{self.data.superconducting_tfcoil.a_tf_coil_inboard_steel=} " + f"{self.data.superconducting_tfcoil.f_a_tf_coil_inboard_steel=} " + f"{self.data.superconducting_tfcoil.a_tf_coil_inboard_insulation=} " + f"{self.data.superconducting_tfcoil.f_a_tf_coil_inboard_insulation=}" + ) + + # Calculate TF coil areas and masses + self.generic_tf_coil_area_and_masses() + self.superconducting_tf_coil_areas_and_masses() + + # # Do stress calculations (writes the stress output) + # if output: + # self.data.tfcoil.n_rad_per_layer = 500 + + # try: + # ( + # sig_tf_r_max, + # sig_tf_t_max, + # sig_tf_z_max, + # sig_tf_vmises_max, + # s_shear_tf_peak, + # deflect, + # eyoung_axial, + # eyoung_trans, + # eyoung_wp_axial, + # eyoung_wp_trans, + # poisson_wp_trans, + # radial_array, + # s_shear_cea_tf_cond, + # poisson_wp_axial, + # sig_tf_r, + # sig_tf_smeared_r, + # sig_tf_smeared_t, + # sig_tf_smeared_z, + # sig_tf_t, + # s_shear_tf, + # sig_tf_vmises, + # sig_tf_z, + # str_tf_r, + # str_tf_t, + # str_tf_z, + # n_radial_array, + # n_tf_bucking, + # self.data.tfcoil.sig_tf_wp, + # sig_tf_case, + # sig_tf_cs_bucked, + # str_wp, + # casestr, + # insstrain, + # sig_tf_wp_av_z, + # ) = self.stresscl( + # int(self.data.tfcoil.n_tf_stress_layers), + # int(self.data.tfcoil.n_rad_per_layer), + # int(self.data.tfcoil.n_tf_wp_stress_layers), + # int(self.data.tfcoil.i_tf_bucking), + # float(self.data.build.r_tf_inboard_in), + # self.data.build.dr_bore, + # self.data.build.z_tf_inside_half, + # self.data.pf_coil.f_z_cs_tf_internal, + # self.data.build.dr_cs, + # self.data.build.i_tf_inside_cs, + # self.data.build.dr_tf_inboard, + # self.data.build.dr_cs_tf_gap, + # self.data.pf_coil.i_pf_conductor, + # self.data.pf_coil.j_cs_flat_top_end, + # self.data.pf_coil.j_cs_pulse_start, + # self.data.pf_coil.c_pf_coil_turn_peak_input, + # self.data.pf_coil.n_pf_coils_in_group, + # self.data.pf_coil.f_dr_dz_cs_turn, + # self.data.pf_coil.radius_cs_turn_corners, + # self.data.pf_coil.f_a_cs_turn_steel, + # self.data.tfcoil.eyoung_steel, + # self.data.tfcoil.poisson_steel, + # self.data.tfcoil.eyoung_cond_axial, + # self.data.tfcoil.poisson_cond_axial, + # self.data.tfcoil.eyoung_cond_trans, + # self.data.tfcoil.poisson_cond_trans, + # self.data.tfcoil.eyoung_ins, + # self.data.tfcoil.poisson_ins, + # self.data.tfcoil.dx_tf_turn_insulation, + # self.data.tfcoil.eyoung_copper, + # self.data.tfcoil.poisson_copper, + # self.data.tfcoil.i_tf_sup, + # self.data.tfcoil.eyoung_res_tf_buck, + # self.data.superconducting_tfcoil.r_tf_wp_inboard_inner, + # self.data.superconducting_tfcoil.tan_theta_coil, + # self.data.superconducting_tfcoil.rad_tf_coil_inboard_toroidal_half, + # self.data.superconducting_tfcoil.r_tf_wp_inboard_outer, + # self.data.superconducting_tfcoil.a_tf_coil_inboard_steel, + # self.data.superconducting_tfcoil.a_tf_plasma_case, + # self.data.superconducting_tfcoil.a_tf_coil_nose_case, + # self.data.tfcoil.dx_tf_wp_insertion_gap, + # self.data.tfcoil.dx_tf_wp_insulation, + # self.data.tfcoil.n_tf_coil_turns, + # int(self.data.tfcoil.i_tf_turns_integer), + # self.data.superconducting_tfcoil.dx_tf_turn_cable_space_average, + # self.data.superconducting_tfcoil.dr_tf_turn_cable_space, + # self.data.tfcoil.dia_tf_turn_coolant_channel, + # self.data.tfcoil.f_a_tf_turn_cable_copper, + # self.data.tfcoil.dx_tf_turn_steel, + # self.data.superconducting_tfcoil.dx_tf_side_case_average, + # self.data.superconducting_tfcoil.dx_tf_wp_toroidal_average, + # self.data.superconducting_tfcoil.a_tf_coil_inboard_insulation, + # self.data.tfcoil.a_tf_wp_steel, + # self.data.tfcoil.a_tf_wp_conductor, + # self.data.superconducting_tfcoil.a_tf_wp_with_insulation, + # self.data.tfcoil.eyoung_al, + # self.data.tfcoil.poisson_al, + # self.data.tfcoil.fcoolcp, + # self.data.tfcoil.n_tf_graded_layers, + # self.data.tfcoil.c_tf_total, + # self.data.tfcoil.dr_tf_plasma_case, + # self.data.tfcoil.i_tf_stress_model, + # self.data.superconducting_tfcoil.vforce_inboard_tot, + # self.data.tfcoil.i_tf_tresca, + # self.data.tfcoil.a_tf_coil_inboard_case, + # self.data.tfcoil.vforce, + # self.data.tfcoil.a_tf_turn_steel, + # ) + + # self.data.tfcoil.sig_tf_case = ( + # self.data.tfcoil.sig_tf_case + # if self.data.tfcoil.sig_tf_case is None + # else sig_tf_case + # ) + + # self.data.tfcoil.sig_tf_cs_bucked = ( + # self.data.tfcoil.sig_tf_cs_bucked + # if self.data.tfcoil.sig_tf_cs_bucked is None + # else sig_tf_cs_bucked + # ) + + # self.data.tfcoil.str_wp = ( + # self.data.tfcoil.str_wp if self.data.tfcoil.str_wp is None else str_wp + # ) + + # self.data.tfcoil.casestr = ( + # self.data.tfcoil.casestr if self.data.tfcoil.casestr is None else casestr + # ) + + # self.data.tfcoil.insstrain = ( + # self.data.tfcoil.insstrain + # if self.data.tfcoil.insstrain is None + # else insstrain + # ) + # if output: + # self.out_stress( + # sig_tf_r_max, + # sig_tf_t_max, + # sig_tf_z_max, + # sig_tf_vmises_max, + # s_shear_tf_peak, + # deflect, + # eyoung_axial, + # eyoung_trans, + # eyoung_wp_axial, + # eyoung_wp_trans, + # poisson_wp_trans, + # radial_array, + # s_shear_cea_tf_cond, + # poisson_wp_axial, + # sig_tf_r, + # sig_tf_smeared_r, + # sig_tf_smeared_t, + # sig_tf_smeared_z, + # sig_tf_t, + # s_shear_tf, + # sig_tf_vmises, + # sig_tf_z, + # str_tf_r, + # str_tf_t, + # str_tf_z, + # n_radial_array, + # n_tf_bucking, + # sig_tf_wp_av_z, + # ) + # except ValueError as e: + # if e.args[1] == 245 and e.args[2] == 0: + # logger.warning( + # "Invalid stress model (r_tf_inboard = 0), stress constraint " + # "switched off" + # ) + # self.data.tfcoil.sig_tf_case = 0.0e0 + # self.data.tfcoil.sig_tf_wp = 0.0e0 + + self.vv_stress_on_quench() + + if output: + self.output_general_tf_info() + self.output_croco_info() + + def output(self) -> None: + """Output the results of the superconducting TF coil model for a STEP vertically + stacked tapes conductor. + """ + self.output_general_tf_info() + self.output_general_superconducting_tf_info() + self.output_step_turn_info() + self.output_tf_superconductor_info() + # self.run_and_output_stress() + + @staticmethod + def tf_step_vertical_tape_integer_turn_geometry( + dr_tf_wp_with_insulation: float, + dx_tf_wp_insulation: float, + dx_tf_wp_insertion_gap: float, + n_tf_wp_layers: int, + dx_tf_wp_toroidal_min: float, + n_tf_wp_pancakes: int, + c_tf_coil: float, + dx_tf_turn_insulation: float, + dia_tf_turn_coolant_channel: float, + ) -> STEPIntegerTurnGeometry: + """ + Calculate the integer-turn geometry for TF coil turns using a vertical + stacked tape (HTS-style) conductor. + + Parameters + ---------- + dr_tf_wp_with_insulation: + Radial thickness of the winding pack including insulation [m]. + dx_tf_wp_insulation: + Thickness of winding-pack insulation [m]. + dx_tf_wp_insertion_gap: + Insertion gap thickness inside the winding pack [m]. + n_tf_wp_layers: + Number of radial layers (turn rows). + dx_tf_wp_toroidal_min: + Minimum toroidal thickness of the winding pack [m]. + n_tf_wp_pancakes: + Number of toroidal pancakes per radial layer. + c_tf_coil: + Total TF coil current [A]. + dx_tf_turn_insulation: + Thickness of the turn (intra-turn) insulation [m]. + dia_tf_turn_coolant_channel: + Diameter of the coolant channel inside the conductor [m]. + + Returns + ------- + : + STEPIntegerTurnGeometry + + Notes + ----- + - Assumes rectangular turns and places the coolant channel near the + bottom of the conductor with a small clearance from the tape stack. + - Basic consistency checks are emitted via logger.error() if + calculated dimensions are non-positive or if the coolant channel + conflicts with the conductor geometry. + + References + ---------- + [1] E. Nasr, S. C. Wimbush, P. Noonan, P. Harris, R. Gowland, and A. Petrov, + “The magnetic cage,” Philosophical Transactions of the Royal Society + A Mathematical Physical and Engineering Sciences, + vol. 382, no. 2280, Aug. 2024, doi: https://doi.org/10.1098/rsta.2023.0407. + ‌ + """ + # Radial turn dimension [m] + dr_tf_turn = ( + dr_tf_wp_with_insulation + - 2.0e0 * (dx_tf_wp_insulation + dx_tf_wp_insertion_gap) + ) / n_tf_wp_layers + + if dr_tf_turn <= (2.0e0 * dx_tf_turn_insulation): + logger.error( + "Negative cable space dimension; reduce conduit thicknesses or raise " + "c_tf_turn. " + f"{dr_tf_turn=} {dx_tf_turn_insulation=}" + ) + + # Toroidal turn dimension [m] + dx_tf_turn = ( + dx_tf_wp_toroidal_min + - 2.0e0 * (dx_tf_wp_insulation + dx_tf_wp_insertion_gap) + ) / n_tf_wp_pancakes + + if dx_tf_turn <= (2.0e0 * dx_tf_turn_insulation): + logger.error( + "Negative cable space dimension; reduce conduit thicknesses or raise " + "c_tf_turn. " + f"{dx_tf_turn=} {dx_tf_turn_insulation=}" + ) + + # Number of TF turns + n_tf_coil_turns = np.double(n_tf_wp_layers * n_tf_wp_pancakes) + + # Current per turn [A/turn] + c_tf_turn = c_tf_coil / n_tf_coil_turns + + # Radial and toroidal dimension of conductor region containing tape stack and + # cooling pipe [m] + dr_tf_turn_stabiliser = dr_tf_turn - 2.0e0 * dx_tf_turn_insulation + dx_tf_turn_stabiliser = dx_tf_turn - 2.0e0 * dx_tf_turn_insulation + + # Place coolant channel at bottom of turn with a gap equal to 10% of conductor + # height + x_tf_turn_coolant_channel_centre = ( + dx_tf_turn_insulation + + (0.1 * dx_tf_turn_stabiliser) + + (dia_tf_turn_coolant_channel / 2) + ) + + # Check to make sure coolant channel leaves some gap to the tape stack + if x_tf_turn_coolant_channel_centre > (dx_tf_turn_stabiliser / 2) - ( + 0.1 * dx_tf_turn_stabiliser + ) - (dia_tf_turn_coolant_channel / 2): + logger.error( + "Coolant channel too big for turn conductor dimension; reduce coolant " + "channel diameter or increase turn dimensions." + f"{x_tf_turn_coolant_channel_centre=} {dx_tf_turn_stabiliser=}" + ) + + # Width of the tape stack allows for 10% of copper stabiliser on each side + dr_tf_turn_tape_stack = dr_tf_turn_stabiliser * 0.8 + + # Bottom of tape stack starts at the centre of the turn and allows for 10% of + # conductor height above + dx_tf_turn_tape_stack = (dx_tf_turn / 2) - ( + dx_tf_turn_insulation + (0.1 * dx_tf_turn_stabiliser) + ) + + # Cross-sectional area of tape stack per turn [m²] + a_tf_turn_tape_stack = dr_tf_turn_tape_stack * dx_tf_turn_tape_stack + + # Area of inter-turn insulation: single turn [m²] + a_tf_turn_insulation = (dr_tf_turn * dx_tf_turn) - ( + dr_tf_turn_stabiliser * dx_tf_turn_stabiliser + ) + + # Area of stabiliser region per turn [m²] + a_tf_turn_stabiliser = ( + dr_tf_turn_stabiliser * dx_tf_turn_stabiliser + - a_tf_turn_tape_stack + - (np.pi / 4.0e0) * dia_tf_turn_coolant_channel * dia_tf_turn_coolant_channel + ) + + return STEPIntegerTurnGeometry( + dr_tf_turn=dr_tf_turn, + dx_tf_turn=dx_tf_turn, + c_tf_turn=c_tf_turn, + n_tf_coil_turns=n_tf_coil_turns, + dr_tf_turn_stabiliser=dr_tf_turn_stabiliser, + dx_tf_turn_stabiliser=dx_tf_turn_stabiliser, + x_tf_turn_coolant_channel_centre=x_tf_turn_coolant_channel_centre, + dr_tf_turn_tape_stack=dr_tf_turn_tape_stack, + dx_tf_turn_tape_stack=dx_tf_turn_tape_stack, + a_tf_turn_tape_stack=a_tf_turn_tape_stack, + a_tf_turn_insulation=a_tf_turn_insulation, + a_tf_turn_stabiliser=a_tf_turn_stabiliser, + ) + + def tf_step_superconductor_properties( + self, + a_tf_turn: float, + b_tf_inboard_peak: float, + cur_tf_turn: float, + temp_tf_peak: float, + i_tf_superconductor: int, + dr_tf_hts_tape: float, + dx_tf_hts_tape_rebco: float, + dx_tf_hts_tape_total: float, + ) -> TFSuperconductorLimits: + """TF superconducting STEP turn using HTS tape + + Parameters + ---------- + a_tf_turn : + Cross-sectional area of the TF turn [m²] + b_tf_inboard_peak : + Peak field at conductor [T] + cur_tf_turn : + Operating current per turn [A] + temp_tf_peak : + Coil temperature at peak field point [K] + i_tf_superconductor : + Integer identifier for the superconductor material model to use. + dr_tf_hts_tape : + Thickness of the HTS tape [m] + dx_tf_hts_tape_rebco : + Width of the REBCO layer in the HTS tape [m] + dx_tf_hts_tape_total : + Total width of the HTS tape [m] + + Returns + ------- + : + TFSuperconductorLimits + + """ + if SuperconductorModel(i_tf_superconductor).sc_shape != SuperconductorShape.TAPE: + raise ProcessValueError( + "Cannot calculate tape superconductor properties for " + "non-tape superconductors. Change `i_tf_sc_mat` to a tape " + "superconductor or use a different TF coil class for non-tape " + "superconductors." + ) + + if self.data.tfcoil.i_str_wp == 0: + strain = self.data.tfcoil.str_tf_con_res + else: + strain = self.data.tfcoil.str_wp + + # ================================================================= + + if i_tf_superconductor == SuperconductorModel.CROCO_REBCO: + # 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 + ) + + # ================================================================= + + # Durham Ginzburg-Landau critical surface model for REBCO + elif i_tf_superconductor == SuperconductorModel.DURHAM_REBCO: + bc20m = 430 # [T] + tc0m = 185 # [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 + + 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 + elif i_tf_superconductor == SuperconductorModel.HAZELTON_ZHAI_REBCO: + bc20m = 138 # [T] + tc0m = 92 # [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 + + # '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 + # f_a_tf_turn_cable_copper here + self.data.tfcoil.j_crit_str_tf = j_superconductor_critical + + # REBCO measurements from 2 T to 14 T, extrapolating outside this + if (b_tf_inboard_peak) >= 14.0: + logger.error( + "Field on superconductor > 14 T (outside of interpolation range)" + ) + + a_tf_strand = dr_tf_hts_tape * dx_tf_hts_tape_total + + cur_tf_turn_strand_critical = j_superconductor_critical * a_tf_strand + + # Critical current density in winding pack + # a_tf_turn : Area per turn (i.e. entire jacketed conductor with insulation) (m2) + j_tf_wp_critical = cur_tf_turn_strand_critical / a_tf_turn + + # Ratio of operating / critical current + f_c_tf_turn_operating_critical = cur_tf_turn / cur_tf_turn_strand_critical + + # Operating current density + j_tf_coil_turn = cur_tf_turn / a_tf_turn + + # Actual current density in superconductor, + # which should be equal to jcrit(thelium+tmarg) + # when we have found the desired value of tmarg + j_superconductor = f_c_tf_turn_operating_critical * j_superconductor_critical + + # Temperature margin + current_sharing_t = superconductors.current_sharing_rebco( + b_tf_inboard_peak, j_superconductor + ) + tmarg = current_sharing_t - temp_tf_peak + self.data.tfcoil.temp_margin = ( + tmarg # Only used in the availabilty routine - see comment to Issue #526 + ) + + return TFSuperconductorLimits( + j_tf_wp_critical=j_tf_wp_critical, + j_superconductor_critical=j_superconductor_critical, + f_c_tf_turn_operating_critical=f_c_tf_turn_operating_critical, + j_superconductor=j_superconductor, + j_tf_coil_turn=j_tf_coil_turn, + bc20m=bc20m, + tc0m=tc0m, + c_turn_cables_critical=cur_tf_turn_strand_critical, + ) + + @staticmethod + def calculate_stacked_tape_strand_count( + dr_tape_stack: float, + dr_hts_tape: float, + ) -> int: + """ + Calculate how many HTS tapes can be placed in a vertical stacked tape conductor. + The function returns the maximum whole number of tapes that fit within the + available radial tape stack width by taking the floor of the ratio + dr_tape_stack / dr_hts_tape. + + Parameters + ---------- + dr_tape_stack : float + Available radial width for the tape stack (m). Must be >= 0. + dr_hts_tape : float + Width of a single HTS tape (m). Must be > 0. + + Returns + ------- + int + Maximum number of tapes that fit. + + Raises + ------ + ValueError + If dr_hts_tape <= 0 or dr_tape_stack < 0. + """ + if dr_hts_tape <= 0: + raise ValueError("dr_hts_tape must be greater than 0") + if dr_tape_stack < 0: + raise ValueError("dr_tape_stack must be non-negative") + + # Use floor to get the maximum whole number of tapes that fit + return int(np.floor(dr_tape_stack / dr_hts_tape)) + + def output_step_turn_info(self) -> None: + """Output the STEP conductor geometry and properties.""" + po.oheadr(self.outfile, "TF STEP Cable Space Information") + + po.ovarre( + self.outfile, + "Thickness of REBCO layer in tape (m)", + "(dx_tf_hts_tape_rebco)", + self.data.superconducting_tfcoil.dx_tf_hts_tape_rebco, + ) + po.ovarre( + self.outfile, + "Thickness of copper layer in tape (m)", + "(dx_tf_hts_tape_copper)", + self.data.superconducting_tfcoil.dx_tf_hts_tape_copper, + ) + po.ovarre( + self.outfile, + "Thickness of Hastelloy layer in tape (m) ", + "(dx_tf_hts_tape_hastelloy)", + self.data.superconducting_tfcoil.dx_tf_hts_tape_hastelloy, + ) + po.ovarre( + self.outfile, + "Thickness of each HTS tape ", + "(dx_tf_hts_tape_total)", + self.data.superconducting_tfcoil.dx_tf_hts_tape_total, + "OP ", + ) + po.ovarre( + self.outfile, + "Mean width of tape (m)", + "(dr_tf_hts_tape)", + self.data.superconducting_tfcoil.dr_tf_hts_tape, + "OP ", + ) + po.oblnkl(self.outfile) + + po.ovarre( + self.outfile, + "Diameter of central helium channel in cable", + "(dia_tf_turn_coolant_channel)", + self.data.tfcoil.dia_tf_turn_coolant_channel, + ) + po.ovarre( + self.outfile, + "Number of superconducting strands per turn", + "(n_tf_turn_superconducting_strands)", + self.data.superconducting_tfcoil.n_tf_turn_superconducting_strands, + ) + po.ovarre( + self.outfile, + "Length of superconductor in TF coil (m)", + "(len_tf_coil_superconductor)", + self.data.superconducting_tfcoil.len_tf_coil_superconductor, + ) + po.ovarre( + self.outfile, + "Total length of superconductor in all TF coils (m)", + "(len_tf_superconductor_total)", + self.data.superconducting_tfcoil.len_tf_superconductor_total, + ) + po.ovarre( + self.outfile, + "Radial width of tape stack in TF turn (m)", + "(dr_tf_turn_tape_stack)", + self.data.superconducting_tfcoil.dr_tf_turn_tape_stack, + ) + po.ovarre( + self.outfile, + "Vertical width of tape stack in TF turn (m)", + "(dx_tf_turn_tape_stack)", + self.data.superconducting_tfcoil.dx_tf_turn_tape_stack, + ) + po.ovarre( + self.outfile, + "Area of tape stack in TF turn (m)", + "(a_tf_turn_tape_stack)", + self.data.superconducting_tfcoil.a_tf_turn_tape_stack, + ) + po.ovarre( + self.outfile, + "Vertical position of coolant channel centre in TF turn (m)", + "(x_tf_turn_coolant_channel_centre)", + self.data.superconducting_tfcoil.x_tf_turn_coolant_channel_centre, + ) + po.ovarre( + self.outfile, + "Radial width of stabiliser in TF turn (m)", + "(dr_tf_turn_stabiliser)", + self.data.superconducting_tfcoil.dr_tf_turn_stabiliser, + ) + po.ovarre( + self.outfile, + "Toroidal width of stabiliser in TF turn (m)", + "(dx_tf_turn_stabiliser)", + self.data.superconducting_tfcoil.dx_tf_turn_stabiliser, + ) + po.ovarre( + self.outfile, + "Area of stabiliser in TF turn (m)", + "(a_tf_turn_stabiliser)", + self.data.superconducting_tfcoil.a_tf_turn_stabiliser, + ) + + @staticmethod def lambda_term(tau: float, omega: float) -> float: """The lambda function used inegral in inductance calcuation found From 1ccb03e6355d50c2c709fef083dcc6ca4258d494 Mon Sep 17 00:00:00 2001 From: mn3981 Date: Thu, 1 Oct 2026 16:00:48 +0100 Subject: [PATCH 03/16] Add function to plot TF coil STEP styled vertical tape turn structure --- process/core/io/plot/summary.py | 282 ++++++++++++++++++++++++++++++-- 1 file changed, 270 insertions(+), 12 deletions(-) diff --git a/process/core/io/plot/summary.py b/process/core/io/plot/summary.py index 6d1379d3d5..355f05a61f 100644 --- a/process/core/io/plot/summary.py +++ b/process/core/io/plot/summary.py @@ -13321,6 +13321,234 @@ def plot_tf_corc_cable_summary_box(axis, fig, mfile: MFile, scan: int): ) +def plot_tf_step_vertical_tape_turn( + axis: plt.Axes, + fig: plt.Figure, + mfile: MFile, + scan: int, + dr_tf_turn: float, + dx_tf_turn: float, + dia_tf_turn_coolant_channel: float, + dx_tf_turn_insulation: float, + dr_tf_turn_tape_stack: float, + dx_tf_turn_tape_stack: float, + x_tf_turn_coolant_channel_centre: float, + dr_tf_turn_stabiliser: float, +) -> None: + """ + Plots TF coil STEP styled vertical tape turn structure. + """ + axis.add_patch( + Rectangle( + [0, 0], + dr_tf_turn, + dx_tf_turn, + facecolor="red", + edgecolor="black", + ), + ) + # Plot the steel conduit + axis.add_patch( + Rectangle( + [dx_tf_turn_insulation, dx_tf_turn_insulation], + (dr_tf_turn - 2 * dx_tf_turn_insulation), + (dx_tf_turn - 2 * dx_tf_turn_insulation), + facecolor="#b87333", + edgecolor="#8B4000", + ), + ) + + # Plot the coolant channel + axis.add_patch( + Circle( + [(dr_tf_turn / 2), x_tf_turn_coolant_channel_centre], + dia_tf_turn_coolant_channel / 2, + facecolor="white", + edgecolor="black", + ), + ) + + # Plot the tape stack + axis.add_patch( + Rectangle( + [(dr_tf_turn_stabiliser * 0.1 + dx_tf_turn_insulation), (dx_tf_turn * 0.5)], + (dr_tf_turn_tape_stack), + (dx_tf_turn_tape_stack), + facecolor="royalblue", + ), + ) + + # Slice the tape stack into layers + for i in range(int(mfile.get("n_tf_turn_superconducting_strands", scan=scan))): + y_start = (dx_tf_turn * 0.5) + i * ( + mfile.get("dx_tf_turn_tape_stack", scan=scan) + / mfile.get("n_tf_turn_superconducting_strands", scan=scan) + ) + plot_hts_tape_geometry( + axis=axis, + r_left=(mfile.get("dr_tf_turn", scan=scan) / 2) + - (mfile.get("dr_tf_hts_tape", scan=scan) / 2), + z_bottom=y_start, + dr_hts_tape=mfile.get("dr_tf_hts_tape", scan=scan), + dx_hts_tape_rebco=mfile.get("dx_tf_hts_tape_rebco", scan=scan), + dx_hts_tape_copper=mfile.get("dx_tf_hts_tape_copper", scan=scan), + dx_hts_tape_hastelloy=mfile.get("dx_tf_hts_tape_hastelloy", scan=scan), + show_legend=False, + ) + + axis.set_xlim(-dr_tf_turn * 0.05, dr_tf_turn * 1.05) + axis.set_ylim(-dx_tf_turn * 0.05, dx_tf_turn * 1.05) + + axis.minorticks_on() + axis.set_title("WP Turn Structure") + axis.set_xlabel("r [m]") + axis.set_ylabel("x [m]") + + # Add info about the steel casing surrounding the WP + textstr_turn_insulation = f"$\\mathbf{{Turn \\ Insulation:}}$\n\n$\\Delta r:${mfile.get('t_tf_superconductor_quench', scan=scan):.3e} m" + + axis.text( + 0.4, + 0.9, + textstr_turn_insulation, + fontsize=9, + verticalalignment="top", + horizontalalignment="left", + transform=fig.transFigure, + bbox={ + "boxstyle": "round", + "facecolor": "red", + "alpha": 1.0, + "linewidth": 2, + }, + ) + + textstr_turn = ( + f"$\\mathbf{{Turn:}}$\n\n" + f"$\\Delta r$: {mfile.get('dr_tf_turn', scan=scan):.3e} m\n" + f"$\\Delta x$: {mfile.get('dx_tf_turn', scan=scan):.3e} m" + ) + + axis.text( + 0.525, + 0.9, + textstr_turn, + fontsize=9, + verticalalignment="top", + horizontalalignment="left", + transform=fig.transFigure, + bbox={ + "boxstyle": "round", + "facecolor": "wheat", + "alpha": 1.0, + "linewidth": 2, + }, + ) + + textstr_turn_strand_space = ( + f"$\\mathbf{{Strand \\ Space:}}$\n\n" + f"$\\Delta r:$ {mfile.get('dr_tf_turn_tape_stack', scan=scan):.3e} m\n" + f"$\\Delta x:$ {mfile.get('dx_tf_turn_tape_stack', scan=scan):.3e} m\n" + f"Tape stack space area: {mfile.get('a_tf_turn_tape_stack', scan=scan):.3e} m$^2$" + ) + + axis.text( + 0.5, + 0.7, + textstr_turn_strand_space, + fontsize=9, + verticalalignment="top", + horizontalalignment="left", + transform=fig.transFigure, + bbox={ + "boxstyle": "round", + "facecolor": "royalblue", + "alpha": 1.0, + "linewidth": 2, + }, + ) + + textstr_stabiliser = ( + f"$\\mathbf{{Stabiliser:}}$\n\n" + f"$\\Delta r$: {mfile.get('dr_tf_turn_stabiliser', scan=scan):.3e} m\n" + f"$\\Delta x$: {mfile.get('dx_tf_turn_stabiliser', scan=scan):.3e} m\n" + f"Stabiliser area: {mfile.get('a_tf_turn_stabiliser', scan=scan):.3e} m$^2$" + ) + + axis.text( + 0.5, + 0.6, + textstr_stabiliser, + fontsize=9, + verticalalignment="top", + horizontalalignment="left", + transform=fig.transFigure, + bbox={ + "boxstyle": "round", + "facecolor": "#b87333", + "alpha": 1.0, + "linewidth": 2, + }, + ) + + # Add info about the steel casing surrounding the WP + textstr_turn_cooling = ( + f"$\\mathbf{{Cooling:}}$\n\n" + f"$\\varnothing$: {mfile.get('dia_tf_turn_coolant_channel', scan=scan):.3e} m\n" + f"Total area of all coolant channels: {mfile.get('a_tf_wp_coolant_channels', scan=scan):.4f} m$^2$" + ) + + axis.text( + 0.45, + 0.8, + textstr_turn_cooling, + fontsize=9, + verticalalignment="top", + horizontalalignment="left", + transform=fig.transFigure, + bbox={ + "boxstyle": "round", + "facecolor": "white", + "alpha": 1.0, + "linewidth": 2, + }, + ) + + textstr_superconductor = ( + f"$\\mathbf{{Superconductor:}}$\n \n" + f"Superconductor used: {SuperconductorModel(mfile.get('i_tf_sc_mat', scan=scan)).full_name}\n" + f"Critical field at zero \ntemperature and strain: {mfile.get('b_tf_superconductor_critical_zero_temp_strain', scan=scan):.4f} T\n" + f"Critical temperature at \nzero field and strain: {mfile.get('temp_tf_superconductor_critical_zero_field_strain', scan=scan):.4f} K\n" + f"Temperature at conductor: {mfile.get('tftmp', scan=scan):.4f} K\n" + f"Field at conductor: {mfile.get('b_tf_inboard_peak_with_ripple', scan=scan):.4f} T\n" + f"Superconductor critical current density at \noperating conditions: {mfile.get('j_tf_superconductor_critical', scan=scan):.2e} A/m$^2$\n" + f"$I_{{\\text{{TF,turn critical}}}}$: {mfile.get('c_turn_cables_critical', scan=scan):,.2f} A\n" + f"$I_{{\\text{{TF,turn}}}}$: {mfile.get('c_tf_turn', scan=scan):,.2f} A\n" + f"Critcal current ratio: {mfile.get('f_c_tf_turn_operating_critical', scan=scan):,.4f}\n" + f"Superconductor temperature \nmargin: {mfile.get('temp_tf_superconductor_margin', scan=scan):,.4f} K\n" + f"\n$\\mathbf{{Quench:}}$\n \n" + f"Quench dump time: {mfile.get('t_tf_superconductor_quench', scan=scan):.4e} s\n" + f"Quench detection time: {mfile.get('t_tf_quench_detection', scan=scan):.4e} s\n" + f"User input max temperature \nduring quench: {mfile.get('temp_tf_conductor_quench_max', scan=scan):.2f} K\n" + f"Required maxium WP current \ndensity for heat protection:\n{mfile.get('j_tf_wp_quench_heat_max', scan=scan):.2e} A/m$^2$\n" + ) + axis.text( + 0.75, + 0.9, + textstr_superconductor, + fontsize=9, + verticalalignment="top", + horizontalalignment="left", + transform=fig.transFigure, + bbox={ + "boxstyle": "round", + "facecolor": "#6dd3f7", # light blue for superconductors + "alpha": 1.0, + "linewidth": 2, + }, + ) + + def reaction_plot_grid( rminor, rmajor, @@ -17220,6 +17448,30 @@ def _add_page(name: str | None = None): ax20 = pages["cable"].add_subplot(325, aspect="equal") ax20.set_position([0.025, 0.5, 0.4, 0.4]) plot_tf_cable_in_conduit_turn(ax20, pages["cable"], m_file, scan) + elif ( + m_file.get("i_tf_turn_type", scan=scan) + == SuperconductingTFTurnType.STEP_STACKED_TAPE + ): + ax20 = pages["cable"].add_subplot(325, aspect="equal") + ax20.set_position([0.025, 0.5, 0.4, 0.4]) + plot_tf_step_vertical_tape_turn( + axis=ax20, + fig=pages["cable"], + mfile=m_file, + scan=scan, + dr_tf_turn=m_file.get("dr_tf_turn", scan=scan), + dx_tf_turn=m_file.get("dx_tf_turn", scan=scan), + dia_tf_turn_coolant_channel=m_file.get( + "dia_tf_turn_coolant_channel", scan=scan + ), + dx_tf_turn_insulation=m_file.get("dx_tf_turn_insulation", scan=scan), + dr_tf_turn_tape_stack=m_file.get("dr_tf_turn_tape_stack", scan=scan), + dx_tf_turn_tape_stack=m_file.get("dx_tf_turn_tape_stack", scan=scan), + x_tf_turn_coolant_channel_centre=m_file.get( + "x_tf_turn_coolant_channel_centre", scan=scan + ), + dr_tf_turn_stabiliser=m_file.get("dr_tf_turn_stabiliser", scan=scan), + ) if ( m_file.get("i_tf_turn_type", scan=scan) @@ -17248,18 +17500,7 @@ def _add_page(name: str | None = None): show_legend=True, ) plot_tf_corc_cable_summary_box(plot_205, pages["cable"], m_file, scan) - ax_hts_tape = pages["cable"].add_subplot(339) - ax_hts_tape.set_position([0.75, 0.1, 0.2, 0.2]) - plot_hts_tape_geometry( - axis=ax_hts_tape, - r_left=0.0, - z_bottom=0.0, - dr_hts_tape=m_file.get("dr_tf_hts_tape", scan=scan), - dx_hts_tape_rebco=m_file.get("dx_tf_hts_tape_rebco", scan=scan), - dx_hts_tape_copper=m_file.get("dx_tf_hts_tape_copper", scan=scan), - dx_hts_tape_hastelloy=m_file.get("dx_tf_hts_tape_hastelloy", scan=scan), - show_legend=True, - ) + elif ( m_file.get("i_tf_turn_type", scan=scan) == SuperconductingTFTurnType.CABLE_IN_CONDUIT @@ -17287,6 +17528,23 @@ def _add_page(name: str | None = None): axes_1=_add_page("quench_time_evo").add_subplot(211), axes_2=pages["quench_time_evo"].add_subplot(212), ) + if ( + m_file.get("i_tf_turn_type", scan=scan) + == SuperconductingTFTurnType.STEP_STACKED_TAPE + or SuperconductingTFTurnType.CROSS_CONDUCTOR + ): + ax_hts_tape = pages["cable"].add_subplot(339) + ax_hts_tape.set_position([0.75, 0.1, 0.2, 0.2]) + plot_hts_tape_geometry( + axis=ax_hts_tape, + r_left=0.0, + z_bottom=0.0, + dr_hts_tape=m_file.get("dr_tf_hts_tape", scan=scan), + dx_hts_tape_rebco=m_file.get("dx_tf_hts_tape_rebco", scan=scan), + dx_hts_tape_copper=m_file.get("dx_tf_hts_tape_copper", scan=scan), + dx_hts_tape_hastelloy=m_file.get("dx_tf_hts_tape_hastelloy", scan=scan), + show_legend=True, + ) else: ax19 = _add_page("tf_wp").add_subplot(211, aspect="equal") ax19.set_position([0.06, 0.55, 0.675, 0.4]) From b59f5c1a947e40d0a00c6efdf6f4831462ddba12 Mon Sep 17 00:00:00 2001 From: mn3981 Date: Thu, 1 Oct 2026 16:20:50 +0100 Subject: [PATCH 04/16] Enhance TF coil summary plot with strands per turn and adjust text positioning --- process/core/io/plot/summary.py | 5 +++-- process/models/tfcoil/superconducting.py | 5 ++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/process/core/io/plot/summary.py b/process/core/io/plot/summary.py index 355f05a61f..d3f21d519a 100644 --- a/process/core/io/plot/summary.py +++ b/process/core/io/plot/summary.py @@ -13449,7 +13449,8 @@ def plot_tf_step_vertical_tape_turn( f"$\\mathbf{{Strand \\ Space:}}$\n\n" f"$\\Delta r:$ {mfile.get('dr_tf_turn_tape_stack', scan=scan):.3e} m\n" f"$\\Delta x:$ {mfile.get('dx_tf_turn_tape_stack', scan=scan):.3e} m\n" - f"Tape stack space area: {mfile.get('a_tf_turn_tape_stack', scan=scan):.3e} m$^2$" + f"Tape stack space area: {mfile.get('a_tf_turn_tape_stack', scan=scan):.3e} m$^2$\n" + f"Strands per turn: {mfile.get('n_tf_turn_superconducting_strands', scan=scan)}" ) axis.text( @@ -13477,7 +13478,7 @@ def plot_tf_step_vertical_tape_turn( axis.text( 0.5, - 0.6, + 0.55, textstr_stabiliser, fontsize=9, verticalalignment="top", diff --git a/process/models/tfcoil/superconducting.py b/process/models/tfcoil/superconducting.py index dfd09db1c6..74b0621c6e 100644 --- a/process/models/tfcoil/superconducting.py +++ b/process/models/tfcoil/superconducting.py @@ -5288,7 +5288,6 @@ def run(self, output: bool = False): if output: self.output_general_tf_info() - self.output_croco_info() def output(self) -> None: """Output the results of the superconducting TF coil model for a STEP vertically @@ -5390,8 +5389,8 @@ def tf_step_vertical_tape_integer_turn_geometry( # Current per turn [A/turn] c_tf_turn = c_tf_coil / n_tf_coil_turns - # Radial and toroidal dimension of conductor region containing tape stack and - # cooling pipe [m] + # Radial and toroidal dimension of conduit/stabiliser region containing tape + # stack and cooling pipe [m] dr_tf_turn_stabiliser = dr_tf_turn - 2.0e0 * dx_tf_turn_insulation dx_tf_turn_stabiliser = dx_tf_turn - 2.0e0 * dx_tf_turn_insulation From fbed2fe50012af10685e63ed526ffbb39e476fcc Mon Sep 17 00:00:00 2001 From: mn3981 Date: Thu, 1 Oct 2026 16:28:10 +0100 Subject: [PATCH 05/16] Refactor STEPSuperconductingTFCoil to use match-case for superconductor model handling and improve comments --- process/models/tfcoil/superconducting.py | 114 +++++++++++++---------- 1 file changed, 65 insertions(+), 49 deletions(-) diff --git a/process/models/tfcoil/superconducting.py b/process/models/tfcoil/superconducting.py index 74b0621c6e..1bf1ffdfbe 100644 --- a/process/models/tfcoil/superconducting.py +++ b/process/models/tfcoil/superconducting.py @@ -5389,7 +5389,7 @@ def tf_step_vertical_tape_integer_turn_geometry( # Current per turn [A/turn] c_tf_turn = c_tf_coil / n_tf_coil_turns - # Radial and toroidal dimension of conduit/stabiliser region containing tape + # Radial and toroidal dimension of conduit/stabiliser region containing tape # stack and cooling pipe [m] dr_tf_turn_stabiliser = dr_tf_turn - 2.0e0 * dx_tf_turn_insulation dx_tf_turn_stabiliser = dx_tf_turn - 2.0e0 * dx_tf_turn_insulation @@ -5502,64 +5502,80 @@ def tf_step_superconductor_properties( else: strain = self.data.tfcoil.str_wp - # ================================================================= + match SuperconductorModel(i_tf_superconductor): + # ================================================================= - if i_tf_superconductor == SuperconductorModel.CROCO_REBCO: - # 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 - ) + 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, + ) - # ================================================================= + # ================================================================= - # Durham Ginzburg-Landau critical surface model for REBCO - elif i_tf_superconductor == SuperconductorModel.DURHAM_REBCO: - bc20m = 430 # [T] - tc0m = 185 # [K] + # 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] - # 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." + # 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 + + 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] - # Hazelton experimental data + Zhai conceptual model for REBCO - elif i_tf_superconductor == SuperconductorModel.HAZELTON_ZHAI_REBCO: - bc20m = 138 # [T] - tc0m = 92 # [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." + # '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, - ) + + # ================================================================= # ================================================================= From 4e385eb2299ac89084092437c121fe82fd8b307f Mon Sep 17 00:00:00 2001 From: mn3981 Date: Fri, 2 Oct 2026 11:33:10 +0100 Subject: [PATCH 06/16] :bug: Fix turn line plotting spacing problem --- process/core/io/plot/summary.py | 120 +++++++++----------------------- 1 file changed, 32 insertions(+), 88 deletions(-) diff --git a/process/core/io/plot/summary.py b/process/core/io/plot/summary.py index d3f21d519a..e0fb36e2ba 100644 --- a/process/core/io/plot/summary.py +++ b/process/core/io/plot/summary.py @@ -102,7 +102,10 @@ _build_cumulative_quench_integral, calculate_quench_protection_current_density, ) -from process.models.tfcoil.superconducting import SuperconductingTFTurnType +from process.models.tfcoil.superconducting import ( + SuperconductingTFTurnType, + SuperconductingTFWPShapeType, +) @dataclass @@ -6207,10 +6210,13 @@ def plot_superconducting_tf_wp(axis: plt.Axes, mfile: MFile, scan: int, fig): # ================================================================ # Plot the rectangular WP - if i_tf_wp_geom == 0: + if ( + SuperconductingTFWPShapeType(i_tf_wp_geom) + == SuperconductingTFWPShapeType.RECTANGULAR + ): if TFWPIntegerTurnType(i_tf_turns_integer) == TFWPIntegerTurnType.INTEGER: long_turns = round(turn_layers) - short_turns = round(turn_pancakes) + n_turns_toroidal = round(turn_pancakes) else: wp_side_ratio = ( dr_tf_wp_with_insulation @@ -6222,7 +6228,7 @@ def plot_superconducting_tf_wp(axis: plt.Axes, mfile: MFile, scan: int, fig): side_unit = n_tf_coil_turns / wp_side_ratio root_turns = round(np.sqrt(side_unit), 1) long_turns = round(root_turns * wp_side_ratio) - short_turns = round(root_turns) + n_turns_toroidal = round(root_turns) # Plots the surrounding insualtion axis.add_patch( @@ -6255,94 +6261,26 @@ def plot_superconducting_tf_wp(axis: plt.Axes, mfile: MFile, scan: int, fig): color="blue", ) ) - # Dvides the WP up into the turn segments - for i in range(1, long_turns): + # Divides the WP up into the turn segments + wp_inner_margin = dx_tf_wp_insulation + dx_tf_wp_insertion_gap + wp_r_min = r_tf_wp_inboard_inner + wp_inner_margin + wp_r_max = wp_r_min + dr_tf_wp_with_insulation - 2 * wp_inner_margin + wp_t_min = -0.5 * dx_tf_wp_primary_toroidal + wp_inner_margin + wp_t_max = -wp_t_min + + for r_line in np.linspace(wp_r_min, wp_r_max, long_turns + 1)[1:-1]: axis.plot( - [ - ( - r_tf_wp_inboard_inner - + dx_tf_wp_insulation - + dx_tf_wp_insertion_gap - ) - + i - * ( - ( - dr_tf_wp_with_insulation - - dx_tf_wp_insulation - - dx_tf_wp_insertion_gap - ) - / long_turns - ), - ( - r_tf_wp_inboard_inner - + dx_tf_wp_insulation - + dx_tf_wp_insertion_gap - ) - + i - * ( - ( - dr_tf_wp_with_insulation - - dx_tf_wp_insulation - - dx_tf_wp_insertion_gap - ) - / long_turns - ), - ], - [ - -0.5 * dx_tf_wp_primary_toroidal - + (dx_tf_wp_insulation + dx_tf_wp_insertion_gap), - 0.5 * dx_tf_wp_primary_toroidal - - (dx_tf_wp_insulation + dx_tf_wp_insertion_gap), - ], + [r_line, r_line], + [wp_t_min, wp_t_max], color="white", linewidth="0.25", linestyle="dashed", ) - for i in range(1, short_turns): + for t_line in np.linspace(wp_t_min, wp_t_max, n_turns_toroidal + 1)[1:-1]: axis.plot( - [ - ( - r_tf_wp_inboard_inner - + dx_tf_wp_insulation - + dx_tf_wp_insertion_gap - ), - ( - r_tf_wp_inboard_outer - - dx_tf_wp_insulation - - dx_tf_wp_insertion_gap - ), - ], - [ - ( - -0.5 * dx_tf_wp_primary_toroidal - + dx_tf_wp_insulation - + dx_tf_wp_insertion_gap - ) - + ( - i - * ( - dx_tf_wp_primary_toroidal - - dx_tf_wp_insulation - - dx_tf_wp_insertion_gap - ) - / short_turns - ), - ( - -0.5 * dx_tf_wp_primary_toroidal - + dx_tf_wp_insulation - + dx_tf_wp_insertion_gap - ) - + ( - i - * ( - dx_tf_wp_primary_toroidal - - dx_tf_wp_insulation - - dx_tf_wp_insertion_gap - ) - / short_turns - ), - ], + [wp_r_min, wp_r_max], + [t_line, t_line], color="white", linewidth="0.25", linestyle="dashed", @@ -6351,7 +6289,10 @@ def plot_superconducting_tf_wp(axis: plt.Axes, mfile: MFile, scan: int, fig): # ================================================================ # Plot the double rectangle winding pack - if i_tf_wp_geom == 1: + if ( + SuperconductingTFWPShapeType(i_tf_wp_geom) + == SuperconductingTFWPShapeType.DOUBLE_RECTANGULAR + ): # Inner WP insulation axis.add_patch( Rectangle( @@ -6417,7 +6358,10 @@ def plot_superconducting_tf_wp(axis: plt.Axes, mfile: MFile, scan: int, fig): # ================================================================ # Trapezium WP - if i_tf_wp_geom == 2: + if ( + SuperconductingTFWPShapeType(i_tf_wp_geom) + == SuperconductingTFWPShapeType.TRAPEZOIDAL + ): # WP insulation x = [ r_tf_wp_inboard_inner, @@ -17557,7 +17501,7 @@ def _add_page(name: str | None = None): plot_plasma_outboard_toroidal_ripple_map(_add_page(), m_file, scan) - plot_tf_stress(_add_page().subplots(nrows=3, ncols=1, sharex=True).flatten(), m_file) + # plot_tf_stress(_add_page().subplots(nrows=3, ncols=1, sharex=True).flatten(), m_file) plot_pf_dimensions( axis=_add_page("pf_dimensions").add_subplot(121, aspect="equal"), From b44f8ffba66b913ff1d8e8ba6ac1c10113c09059 Mon Sep 17 00:00:00 2001 From: mn3981 Date: Fri, 2 Oct 2026 11:44:37 +0100 Subject: [PATCH 07/16] Add dia_tf_turn_coolant_channel to STEPIntegerTurnGeometry and update related calculations --- process/models/tfcoil/superconducting.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/process/models/tfcoil/superconducting.py b/process/models/tfcoil/superconducting.py index 1bf1ffdfbe..8213795986 100644 --- a/process/models/tfcoil/superconducting.py +++ b/process/models/tfcoil/superconducting.py @@ -4872,6 +4872,7 @@ class STEPIntegerTurnGeometry: a_tf_turn_tape_stack: float a_tf_turn_insulation: float a_tf_turn_stabiliser: float + dia_tf_turn_coolant_channel: float class STEPSuperconductingTFCoil(SuperconductingTFCoil): @@ -4917,7 +4918,6 @@ def run(self, output: bool = False): n_tf_wp_pancakes=self.data.tfcoil.n_tf_wp_pancakes, c_tf_coil=self.data.superconducting_tfcoil.c_tf_coil, dx_tf_turn_insulation=self.data.tfcoil.dx_tf_turn_insulation, - dia_tf_turn_coolant_channel=self.data.tfcoil.dia_tf_turn_coolant_channel, ) d_sc_tf.dr_tf_turn = integer_turn_geometry.dr_tf_turn @@ -4948,6 +4948,9 @@ def run(self, output: bool = False): d_sc_tf.dr_tf_hts_tape = ( self.data.superconducting_tfcoil.dr_tf_turn_tape_stack ) + self.data.tfcoil.dia_tf_turn_coolant_channel = ( + integer_turn_geometry.dia_tf_turn_coolant_channel + ) case TFWPIntegerTurnType.NON_INTEGER: raise ProcessValueError( @@ -5309,7 +5312,6 @@ def tf_step_vertical_tape_integer_turn_geometry( n_tf_wp_pancakes: int, c_tf_coil: float, dx_tf_turn_insulation: float, - dia_tf_turn_coolant_channel: float, ) -> STEPIntegerTurnGeometry: """ Calculate the integer-turn geometry for TF coil turns using a vertical @@ -5333,8 +5335,6 @@ def tf_step_vertical_tape_integer_turn_geometry( Total TF coil current [A]. dx_tf_turn_insulation: Thickness of the turn (intra-turn) insulation [m]. - dia_tf_turn_coolant_channel: - Diameter of the coolant channel inside the conductor [m]. Returns ------- @@ -5394,11 +5394,15 @@ def tf_step_vertical_tape_integer_turn_geometry( dr_tf_turn_stabiliser = dr_tf_turn - 2.0e0 * dx_tf_turn_insulation dx_tf_turn_stabiliser = dx_tf_turn - 2.0e0 * dx_tf_turn_insulation + dia_tf_turn_coolant_channel = min( + 0.3 * dx_tf_turn_stabiliser, 0.8 * dr_tf_turn_stabiliser + ) + # Place coolant channel at bottom of turn with a gap equal to 10% of conductor # height x_tf_turn_coolant_channel_centre = ( dx_tf_turn_insulation - + (0.1 * dx_tf_turn_stabiliser) + + (0.15 * dx_tf_turn_stabiliser) + (dia_tf_turn_coolant_channel / 2) ) @@ -5449,6 +5453,7 @@ def tf_step_vertical_tape_integer_turn_geometry( a_tf_turn_tape_stack=a_tf_turn_tape_stack, a_tf_turn_insulation=a_tf_turn_insulation, a_tf_turn_stabiliser=a_tf_turn_stabiliser, + dia_tf_turn_coolant_channel=dia_tf_turn_coolant_channel, ) def tf_step_superconductor_properties( From 077afdc7389debd53f381ae6024667495e14786e Mon Sep 17 00:00:00 2001 From: mn3981 Date: Fri, 2 Oct 2026 11:51:55 +0100 Subject: [PATCH 08/16] Refactor tape stack plotting in plot_tf_step_vertical_tape_turn for improved clarity and accuracy --- process/core/io/plot/summary.py | 35 ++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/process/core/io/plot/summary.py b/process/core/io/plot/summary.py index e0fb36e2ba..234bbf10e7 100644 --- a/process/core/io/plot/summary.py +++ b/process/core/io/plot/summary.py @@ -13312,31 +13312,38 @@ def plot_tf_step_vertical_tape_turn( ), ) - # Plot the tape stack + # Outline of the space available for the tape stack axis.add_patch( Rectangle( [(dr_tf_turn_stabiliser * 0.1 + dx_tf_turn_insulation), (dx_tf_turn * 0.5)], (dr_tf_turn_tape_stack), (dx_tf_turn_tape_stack), - facecolor="royalblue", + facecolor="none", + edgecolor="black", + linewidth=1, ), ) - # Slice the tape stack into layers + # Tapes are packed from the bottom of the stack at the pitch used by the model + # to count them. Layer thicknesses are scaled so each tape sums to that pitch. + dx_hts_tape_total = mfile.get("dx_tf_hts_tape_total", scan=scan) + dx_hts_tape_rebco = mfile.get("dx_tf_hts_tape_rebco", scan=scan) + dx_hts_tape_copper = mfile.get("dx_tf_hts_tape_copper", scan=scan) + dx_hts_tape_hastelloy = mfile.get("dx_tf_hts_tape_hastelloy", scan=scan) + tape_scale = dx_hts_tape_total / ( + dx_hts_tape_rebco + dx_hts_tape_copper + dx_hts_tape_hastelloy + ) + dr_hts_tape = mfile.get("dr_tf_hts_tape", scan=scan) + for i in range(int(mfile.get("n_tf_turn_superconducting_strands", scan=scan))): - y_start = (dx_tf_turn * 0.5) + i * ( - mfile.get("dx_tf_turn_tape_stack", scan=scan) - / mfile.get("n_tf_turn_superconducting_strands", scan=scan) - ) plot_hts_tape_geometry( axis=axis, - r_left=(mfile.get("dr_tf_turn", scan=scan) / 2) - - (mfile.get("dr_tf_hts_tape", scan=scan) / 2), - z_bottom=y_start, - dr_hts_tape=mfile.get("dr_tf_hts_tape", scan=scan), - dx_hts_tape_rebco=mfile.get("dx_tf_hts_tape_rebco", scan=scan), - dx_hts_tape_copper=mfile.get("dx_tf_hts_tape_copper", scan=scan), - dx_hts_tape_hastelloy=mfile.get("dx_tf_hts_tape_hastelloy", scan=scan), + r_left=(dr_tf_turn / 2) - (dr_hts_tape / 2), + z_bottom=(dx_tf_turn * 0.5) + i * dx_hts_tape_total, + dr_hts_tape=dr_hts_tape, + dx_hts_tape_rebco=dx_hts_tape_rebco * tape_scale, + dx_hts_tape_copper=dx_hts_tape_copper * tape_scale, + dx_hts_tape_hastelloy=dx_hts_tape_hastelloy * tape_scale, show_legend=False, ) From 536cec62604394025f7b3bbd4d0b6191cd5de044 Mon Sep 17 00:00:00 2001 From: mn3981 Date: Fri, 2 Oct 2026 12:06:45 +0100 Subject: [PATCH 09/16] :bug: Add n_tf_turn_superconducting_strands parameter to STEPSuperconductingTFCoil for accurate critical current calculations --- process/models/tfcoil/superconducting.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/process/models/tfcoil/superconducting.py b/process/models/tfcoil/superconducting.py index 8213795986..fdb4c0bcd2 100644 --- a/process/models/tfcoil/superconducting.py +++ b/process/models/tfcoil/superconducting.py @@ -5037,6 +5037,7 @@ def run(self, output: bool = False): dr_tf_hts_tape=self.data.superconducting_tfcoil.dr_tf_hts_tape, dx_tf_hts_tape_rebco=self.data.superconducting_tfcoil.dx_tf_hts_tape_rebco, dx_tf_hts_tape_total=self.data.superconducting_tfcoil.dx_tf_hts_tape_total, + n_tf_turn_superconducting_strands=self.data.superconducting_tfcoil.n_tf_turn_superconducting_strands, ) self.data.tfcoil.j_tf_wp_critical = ( @@ -5466,6 +5467,7 @@ def tf_step_superconductor_properties( dr_tf_hts_tape: float, dx_tf_hts_tape_rebco: float, dx_tf_hts_tape_total: float, + n_tf_turn_superconducting_strands: int, ) -> TFSuperconductorLimits: """TF superconducting STEP turn using HTS tape @@ -5487,6 +5489,8 @@ def tf_step_superconductor_properties( Width of the REBCO layer in the HTS tape [m] dx_tf_hts_tape_total : Total width of the HTS tape [m] + n_tf_turn_superconducting_strands : + Number of HTS tapes stacked in the turn Returns ------- @@ -5597,7 +5601,9 @@ def tf_step_superconductor_properties( a_tf_strand = dr_tf_hts_tape * dx_tf_hts_tape_total - cur_tf_turn_strand_critical = j_superconductor_critical * a_tf_strand + cur_tf_turn_strand_critical = ( + j_superconductor_critical * a_tf_strand * n_tf_turn_superconducting_strands + ) # Critical current density in winding pack # a_tf_turn : Area per turn (i.e. entire jacketed conductor with insulation) (m2) From 1b3e226e4a0f65caf911f945421cf9465fb92b9b Mon Sep 17 00:00:00 2001 From: mn3981 Date: Fri, 2 Oct 2026 12:19:39 +0100 Subject: [PATCH 10/16] Fix some labels and placement --- process/core/io/plot/summary.py | 39 +++++++++++++++------------------ 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/process/core/io/plot/summary.py b/process/core/io/plot/summary.py index 234bbf10e7..84a617cba0 100644 --- a/process/core/io/plot/summary.py +++ b/process/core/io/plot/summary.py @@ -6539,7 +6539,7 @@ def plot_superconducting_tf_wp(axis: plt.Axes, mfile: MFile, scan: int, fig): f"Max $\\Delta r$: {mfile.get('dx_tf_side_case_peak', scan=scan):.3f} m" ) axis.text( - 0.55, + 0.525, 0.975, textstr_casing, fontsize=9, @@ -6563,7 +6563,7 @@ def plot_superconducting_tf_wp(axis: plt.Axes, mfile: MFile, scan: int, fig): f"$\\Delta r$: {mfile.get('dx_tf_wp_insertion_gap', scan=scan):.4f} m" ) axis.text( - 0.55, + 0.525, 0.575, textstr_wp_insulation, fontsize=9, @@ -6599,7 +6599,7 @@ def plot_superconducting_tf_wp(axis: plt.Axes, mfile: MFile, scan: int, fig): ) axis.text( - 0.775, + 0.75, 0.95, textstr_wp, fontsize=9, @@ -6625,10 +6625,10 @@ def plot_superconducting_tf_wp(axis: plt.Axes, mfile: MFile, scan: int, fig): f"Total area of steel in coil: {mfile.get('a_tf_coil_inboard_steel', scan=scan):.4f} $\\mathrm{{m}}^2$\n" f"Total area fraction of steel: {mfile.get('f_a_tf_coil_inboard_steel', scan=scan):.4f}\n" f"Total area fraction of insulation: {mfile.get('f_a_tf_coil_inboard_insulation', scan=scan):.4f}\n" - f"$A$, all insulation in coil: {mfile.get('a_tf_coil_inboard_insulation', scan=scan):.4f} $\\mathrm{{m}}^2$\n" + f"$A$, all insulation in coil: {mfile.get('a_tf_coil_inboard_insulation', scan=scan):.4f} $\\mathrm{{m}}^2$" ) axis.text( - 0.775, + 0.75, 0.58, textstr_general_info, fontsize=9, @@ -13359,7 +13359,7 @@ def plot_tf_step_vertical_tape_turn( textstr_turn_insulation = f"$\\mathbf{{Turn \\ Insulation:}}$\n\n$\\Delta r:${mfile.get('t_tf_superconductor_quench', scan=scan):.3e} m" axis.text( - 0.4, + 0.5, 0.9, textstr_turn_insulation, fontsize=9, @@ -13381,7 +13381,7 @@ def plot_tf_step_vertical_tape_turn( ) axis.text( - 0.525, + 0.65, 0.9, textstr_turn, fontsize=9, @@ -13405,7 +13405,7 @@ def plot_tf_step_vertical_tape_turn( ) axis.text( - 0.5, + 0.525, 0.7, textstr_turn_strand_space, fontsize=9, @@ -13428,7 +13428,7 @@ def plot_tf_step_vertical_tape_turn( ) axis.text( - 0.5, + 0.575, 0.55, textstr_stabiliser, fontsize=9, @@ -13451,7 +13451,7 @@ def plot_tf_step_vertical_tape_turn( ) axis.text( - 0.45, + 0.5, 0.8, textstr_turn_cooling, fontsize=9, @@ -17375,14 +17375,13 @@ def _add_page(name: str | None = None): # Can only plot WP and turn structure if superconducting coil at the moment if m_file.get("i_tf_sup", scan=scan) == TFConductorModel.SUPERCONDUCTING: # TF coil with WP - ax19 = _add_page("tf_wp").add_subplot(221, aspect="equal") - ax19.set_position([ - 0.025, - 0.45, - 0.5, - 0.5, - ]) # Half height, a bit wider, top left - plot_superconducting_tf_wp(ax19, m_file, scan, pages["tf_wp"]) + + plot_superconducting_tf_wp( + _add_page("tf_wp").add_subplot(221, aspect="equal"), + m_file, + scan, + pages["tf_wp"], + ) _add_page("cable") if ( @@ -17404,10 +17403,8 @@ def _add_page(name: str | None = None): m_file.get("i_tf_turn_type", scan=scan) == SuperconductingTFTurnType.STEP_STACKED_TAPE ): - ax20 = pages["cable"].add_subplot(325, aspect="equal") - ax20.set_position([0.025, 0.5, 0.4, 0.4]) plot_tf_step_vertical_tape_turn( - axis=ax20, + axis=pages["cable"].add_subplot(221, aspect="equal"), fig=pages["cable"], mfile=m_file, scan=scan, From d9609b648166e0059b82a3fbbc86587638364cf8 Mon Sep 17 00:00:00 2001 From: mn3981 Date: Fri, 2 Oct 2026 14:07:21 +0100 Subject: [PATCH 11/16] Update legend position in plot_superconducting_tf_wp and set f_b_tf_inboard_peak_ripple_symmetric in SuperconductingTFCoil --- process/core/io/plot/summary.py | 2 +- process/models/tfcoil/superconducting.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/process/core/io/plot/summary.py b/process/core/io/plot/summary.py index 84a617cba0..b2057d2a00 100644 --- a/process/core/io/plot/summary.py +++ b/process/core/io/plot/summary.py @@ -6650,7 +6650,7 @@ def plot_superconducting_tf_wp(axis: plt.Axes, mfile: MFile, scan: int, fig): axis.set_title("Top-down view of inboard TF coil at midplane") axis.set_xlabel("Radial distance [m]") axis.set_ylabel("Toroidal distance [m]") - axis.legend(loc="upper left") + axis.legend(loc="lower center", bbox_to_anchor=(0.5, -0.4)) def plot_resistive_tf_wp(axis: plt.Axes, mfile: MFile, scan: int, fig): diff --git a/process/models/tfcoil/superconducting.py b/process/models/tfcoil/superconducting.py index fdb4c0bcd2..e9cbe741e4 100644 --- a/process/models/tfcoil/superconducting.py +++ b/process/models/tfcoil/superconducting.py @@ -1530,6 +1530,9 @@ def peak_b_tf_inboard_with_ripple( a[2] = -0.85031e0 a[3] = 0.89808e0 case _: + self.data.superconducting_tfcoil.f_b_tf_inboard_peak_ripple_symmetric = ( + 1.09e0 + ) return 1.09e0 * b_tf_inboard_peak_symmetric # Maximum winding pack width before adjacent packs touch From 1c8cab102c75c08eae43ff96f0bbef67ab782f78 Mon Sep 17 00:00:00 2001 From: mn3981 Date: Fri, 2 Oct 2026 15:04:03 +0100 Subject: [PATCH 12/16] Add SuperconTFAreasAndMasses dataclass and refactor area/mass calculations in STEPSuperconductingTFCoil --- process/models/tfcoil/superconducting.py | 153 +++++++++++++++++------ 1 file changed, 118 insertions(+), 35 deletions(-) diff --git a/process/models/tfcoil/superconducting.py b/process/models/tfcoil/superconducting.py index e9cbe741e4..a6cad134a1 100644 --- a/process/models/tfcoil/superconducting.py +++ b/process/models/tfcoil/superconducting.py @@ -145,6 +145,24 @@ class TFSuperconductorLimits: """Critical current in the TF turn cables (A).""" +@dataclass(slots=True) +class SuperconTFAreasAndMasses: + """Superconducting TF coil winding pack areas and masses.""" + + m_tf_coil_wp_insulation: float + cplen: float + m_tf_coil_case: float + m_tf_coil_superconductor: float + m_tf_coil_copper: float + m_tf_wp_steel_conduit: float + m_tf_coil_wp_turn_insulation: float + m_tf_coil_conductor: float + m_tf_coil: float + m_tf_coils_total: float + whtcp: float + whttflgs: float + + class SuperconductingTFCoil(TFCoil): """Class for superconducting TF coil model, inheriting from the base TFCoil class. @@ -1989,7 +2007,7 @@ def tf_wp_currents(data: DataStructure): ), ) - def superconducting_tf_coil_areas_and_masses(self): + def superconducting_tf_coil_areas_and_masses(self) -> SuperconTFAreasAndMasses: """Calculate areas and masses of superconducting TF coil components.""" # Mass of case [kg] # *** @@ -1998,7 +2016,7 @@ def superconducting_tf_coil_areas_and_masses(self): # (assumed to be same density/material as turn insulation) d_sc_tf = self.data.superconducting_tfcoil - self.data.tfcoil.m_tf_coil_wp_insulation = ( + m_tf_coil_wp_insulation = ( self.data.tfcoil.len_tf_coil * (d_sc_tf.a_tf_wp_with_insulation - d_sc_tf.a_tf_wp_no_insulation) * self.data.tfcoil.den_tf_wp_turn_insulation @@ -2006,7 +2024,7 @@ def superconducting_tf_coil_areas_and_masses(self): # The length of the vertical section is that of the first (inboard) segment # = height of TF coil inner edge + (2 * coil thickness) - self.data.tfcoil.cplen = (2.0e0 * self.data.build.z_tf_inside_half) + ( + cplen = (2.0e0 * self.data.build.z_tf_inside_half) + ( 2.0e0 * self.data.build.dr_tf_inboard ) @@ -2015,22 +2033,22 @@ def superconducting_tf_coil_areas_and_masses(self): if self.data.physics.itart == 1: # self.data.tfcoil.len_tf_coil does not include inboard leg # ('centrepost') length in TART - self.data.tfcoil.m_tf_coil_case = ( + m_tf_coil_case = ( 2.2e0 * self.data.tfcoil.den_tf_coil_case * ( - self.data.tfcoil.cplen * self.data.tfcoil.a_tf_coil_inboard_case + cplen * self.data.tfcoil.a_tf_coil_inboard_case + self.data.tfcoil.len_tf_coil * self.data.tfcoil.a_tf_coil_outboard_case ) ) else: - self.data.tfcoil.m_tf_coil_case = ( + m_tf_coil_case = ( 2.2e0 * self.data.tfcoil.den_tf_coil_case * ( - self.data.tfcoil.cplen * self.data.tfcoil.a_tf_coil_inboard_case - + (self.data.tfcoil.len_tf_coil - self.data.tfcoil.cplen) + cplen * self.data.tfcoil.a_tf_coil_inboard_case + + (self.data.tfcoil.len_tf_coil - cplen) * self.data.tfcoil.a_tf_coil_outboard_case ) ) @@ -2042,7 +2060,7 @@ def superconducting_tf_coil_areas_and_masses(self): # Superconductor mass [kg] # Includes space allowance for central helium channel, area # self.data.tfcoil.a_tf_wp_coolant_channels - self.data.tfcoil.m_tf_coil_superconductor = ( + m_tf_coil_superconductor = ( self.data.tfcoil.len_tf_coil * self.data.tfcoil.n_tf_coil_turns * self.data.tfcoil.a_tf_turn_cable_space_no_void @@ -2052,7 +2070,7 @@ def superconducting_tf_coil_areas_and_masses(self): ) * self.data.tfcoil.dcond[self.data.tfcoil.i_tf_sc_mat - 1] # Copper mass [kg] - self.data.tfcoil.m_tf_coil_copper = ( + m_tf_coil_copper = ( self.data.tfcoil.len_tf_coil * self.data.tfcoil.n_tf_coil_turns * self.data.tfcoil.a_tf_turn_cable_space_no_void @@ -2060,10 +2078,10 @@ def superconducting_tf_coil_areas_and_masses(self): * self.data.tfcoil.f_a_tf_turn_cable_copper - self.data.tfcoil.len_tf_coil * self.data.tfcoil.a_tf_wp_coolant_channels ) * constants.DEN_COPPER - self.data.tfcoil.m_tf_coil_copper = max(0.0e0, self.data.tfcoil.m_tf_coil_copper) + m_tf_coil_copper = max(0.0e0, m_tf_coil_copper) # Steel conduit (sheath) mass [kg] - self.data.tfcoil.m_tf_wp_steel_conduit = ( + m_tf_wp_steel_conduit = ( self.data.tfcoil.len_tf_coil * self.data.tfcoil.n_tf_coil_turns * self.data.tfcoil.a_tf_turn_steel @@ -2073,44 +2091,52 @@ def superconducting_tf_coil_areas_and_masses(self): # Conduit insulation mass [kg] # (self.data.tfcoil.a_tf_coil_wp_turn_insulation already contains # self.data.tfcoil.n_tf_coil_turns) - self.data.tfcoil.m_tf_coil_wp_turn_insulation = ( + m_tf_coil_wp_turn_insulation = ( self.data.tfcoil.len_tf_coil * self.data.tfcoil.a_tf_coil_wp_turn_insulation * self.data.tfcoil.den_tf_wp_turn_insulation ) # Total conductor mass [kg] - self.data.tfcoil.m_tf_coil_conductor = ( - self.data.tfcoil.m_tf_coil_superconductor - + self.data.tfcoil.m_tf_coil_copper - + self.data.tfcoil.m_tf_wp_steel_conduit - + self.data.tfcoil.m_tf_coil_wp_turn_insulation + m_tf_coil_conductor = ( + m_tf_coil_superconductor + + m_tf_coil_copper + + m_tf_wp_steel_conduit + + m_tf_coil_wp_turn_insulation ) # --------------------------------- # Total TF coil mass [kg] (all coils) - self.data.tfcoil.m_tf_coil = ( - self.data.tfcoil.m_tf_coil_case - + self.data.tfcoil.m_tf_coil_conductor - + self.data.tfcoil.m_tf_coil_wp_insulation - ) + m_tf_coil = m_tf_coil_case + m_tf_coil_conductor + m_tf_coil_wp_insulation # Total TF coil mass [kg] (all coils) - self.data.tfcoil.m_tf_coils_total = ( - self.data.tfcoil.m_tf_coil * self.data.tfcoil.n_tf_coils - ) + m_tf_coils_total = m_tf_coil * self.data.tfcoil.n_tf_coils # If spherical tokamak, distribute between centrepost and outboard legs # (in this case, total TF coil length = inboard `cplen` + # outboard `len_tf_coil`) if self.data.physics.itart == 1: tfleng_sph = self.data.tfcoil.cplen + self.data.tfcoil.len_tf_coil - self.data.tfcoil.whtcp = self.data.tfcoil.m_tf_coils_total * ( - self.data.tfcoil.cplen / tfleng_sph - ) - self.data.tfcoil.whttflgs = self.data.tfcoil.m_tf_coils_total * ( - self.data.tfcoil.len_tf_coil / tfleng_sph - ) + whtcp = m_tf_coils_total * (self.data.tfcoil.cplen / tfleng_sph) + whttflgs = m_tf_coils_total * (self.data.tfcoil.len_tf_coil / tfleng_sph) + else: + whtcp = 0.0 + whttflgs = 0.0 + + return SuperconTFAreasAndMasses( + m_tf_coil_wp_insulation=m_tf_coil_wp_insulation, + cplen=cplen, + m_tf_coil_case=m_tf_coil_case, + m_tf_coil_superconductor=m_tf_coil_superconductor, + m_tf_coil_copper=m_tf_coil_copper, + m_tf_wp_steel_conduit=m_tf_wp_steel_conduit, + m_tf_coil_conductor=m_tf_coil_conductor, + m_tf_coil_wp_turn_insulation=m_tf_coil_wp_turn_insulation, + m_tf_coil=m_tf_coil, + m_tf_coils_total=m_tf_coils_total, + whtcp=whtcp, + whttflgs=whttflgs, + ) def run_and_output_stress(self) -> None: """ @@ -2517,7 +2543,25 @@ def run(self, output: bool = False): # Calculate TF coil areas and masses self.generic_tf_coil_area_and_masses() - self.superconducting_tf_coil_areas_and_masses() + tf_areas_masses = self.superconducting_tf_coil_areas_and_masses() + self.data.tfcoil.m_tf_coil_wp_insulation = ( + tf_areas_masses.m_tf_coil_wp_insulation + ) + self.data.tfcoil.cplen = tf_areas_masses.cplen + self.data.tfcoil.m_tf_coil_case = tf_areas_masses.m_tf_coil_case + self.data.tfcoil.m_tf_coil_superconductor = ( + tf_areas_masses.m_tf_coil_superconductor + ) + self.data.tfcoil.m_tf_coil_copper = tf_areas_masses.m_tf_coil_copper + self.data.tfcoil.m_tf_wp_steel_conduit = tf_areas_masses.m_tf_wp_steel_conduit + self.data.tfcoil.m_tf_coil_wp_turn_insulation = ( + tf_areas_masses.m_tf_coil_wp_turn_insulation + ) + self.data.tfcoil.m_tf_coil = tf_areas_masses.m_tf_coil + self.data.tfcoil.m_tf_coil_conductor = tf_areas_masses.m_tf_coil_conductor + self.data.tfcoil.m_tf_coils_total = tf_areas_masses.m_tf_coils_total + self.data.tfcoil.whtcp = tf_areas_masses.whtcp + self.data.tfcoil.whttflgs = tf_areas_masses.whttflgs # Do stress calculations (writes the stress output) if output: @@ -4046,7 +4090,25 @@ def run(self, output: bool = False): # Calculate TF coil areas and masses self.generic_tf_coil_area_and_masses() - self.superconducting_tf_coil_areas_and_masses() + tf_areas_masses = self.superconducting_tf_coil_areas_and_masses() + self.data.tfcoil.m_tf_coil_wp_insulation = ( + tf_areas_masses.m_tf_coil_wp_insulation + ) + self.data.tfcoil.cplen = tf_areas_masses.cplen + self.data.tfcoil.m_tf_coil_case = tf_areas_masses.m_tf_coil_case + self.data.tfcoil.m_tf_coil_superconductor = ( + tf_areas_masses.m_tf_coil_superconductor + ) + self.data.tfcoil.m_tf_coil_copper = tf_areas_masses.m_tf_coil_copper + self.data.tfcoil.m_tf_wp_steel_conduit = tf_areas_masses.m_tf_wp_steel_conduit + self.data.tfcoil.m_tf_coil_wp_turn_insulation = ( + tf_areas_masses.m_tf_coil_wp_turn_insulation + ) + self.data.tfcoil.m_tf_coil_conductor = tf_areas_masses.m_tf_coil_conductor + self.data.tfcoil.m_tf_coil = tf_areas_masses.m_tf_coil + self.data.tfcoil.m_tf_coils_total = tf_areas_masses.m_tf_coils_total + self.data.tfcoil.whtcp = tf_areas_masses.whtcp + self.data.tfcoil.whttflgs = tf_areas_masses.whttflgs # Do stress calculations (writes the stress output) if output: @@ -5114,8 +5176,29 @@ def run(self, output: bool = False): # Calculate TF coil areas and masses self.generic_tf_coil_area_and_masses() - self.superconducting_tf_coil_areas_and_masses() + tf_areas_masses = self.superconducting_tf_coil_areas_and_masses() + self.data.tfcoil.m_tf_coil_wp_insulation = ( + tf_areas_masses.m_tf_coil_wp_insulation + ) + self.data.tfcoil.cplen = tf_areas_masses.cplen + self.data.tfcoil.m_tf_coil_case = tf_areas_masses.m_tf_coil_case + self.data.tfcoil.m_tf_coil_superconductor = ( + tf_areas_masses.m_tf_coil_superconductor + ) + self.data.tfcoil.m_tf_coil_copper = tf_areas_masses.m_tf_coil_copper + self.data.tfcoil.m_tf_wp_steel_conduit = tf_areas_masses.m_tf_wp_steel_conduit + self.data.tfcoil.m_tf_coil_wp_turn_insulation = ( + tf_areas_masses.m_tf_coil_wp_turn_insulation + ) + self.data.tfcoil.m_tf_coil_conductor = tf_areas_masses.m_tf_coil_conductor + self.data.tfcoil.m_tf_coil_inboard_steel = ( + tf_areas_masses.m_tf_coil_inboard_steel + ) + self.data.tfcoil.m_tf_coil = tf_areas_masses.m_tf_coil + self.data.tfcoil.m_tf_coils_total = tf_areas_masses.m_tf_coils_total + self.data.tfcoil.whtcp = tf_areas_masses.whtcp + self.data.tfcoil.whttflgs = tf_areas_masses.whttflgs # # Do stress calculations (writes the stress output) # if output: # self.data.tfcoil.n_rad_per_layer = 500 From 9cc6771beaf7dd1d2fcb27381136555a3ac8bbe8 Mon Sep 17 00:00:00 2001 From: mn3981 Date: Fri, 2 Oct 2026 15:11:05 +0100 Subject: [PATCH 13/16] Refactor area and mass calculations in CICCSuperconductingTFCoil and add tf_cicc_areas_and_masses method for improved accuracy --- process/models/tfcoil/superconducting.py | 134 ++++++++++++++++++++++- 1 file changed, 130 insertions(+), 4 deletions(-) diff --git a/process/models/tfcoil/superconducting.py b/process/models/tfcoil/superconducting.py index a6cad134a1..8ca26ebcc6 100644 --- a/process/models/tfcoil/superconducting.py +++ b/process/models/tfcoil/superconducting.py @@ -2543,7 +2543,7 @@ def run(self, output: bool = False): # Calculate TF coil areas and masses self.generic_tf_coil_area_and_masses() - tf_areas_masses = self.superconducting_tf_coil_areas_and_masses() + tf_areas_masses = self.tf_cicc_areas_and_masses() self.data.tfcoil.m_tf_coil_wp_insulation = ( tf_areas_masses.m_tf_coil_wp_insulation ) @@ -3714,6 +3714,135 @@ def tf_cicc_inboard_areas_and_fractions( f_a_tf_coil_inboard_insulation=f_a_tf_coil_inboard_insulation, ) + def tf_cicc_areas_and_masses(self) -> SuperconTFAreasAndMasses: + """Calculate areas and masses of superconducting TF coil components.""" + + # Mass of ground-wall insulation [kg] + # (assumed to be same density/material as turn insulation) + d_sc_tf = self.data.superconducting_tfcoil + + m_tf_coil_wp_insulation = ( + self.data.tfcoil.len_tf_coil + * (d_sc_tf.a_tf_wp_with_insulation - d_sc_tf.a_tf_wp_no_insulation) + * self.data.tfcoil.den_tf_wp_turn_insulation + ) + + # The length of the vertical section is that of the first (inboard) segment + # = height of TF coil inner edge + (2 * coil thickness) + cplen = (2.0e0 * self.data.build.z_tf_inside_half) + ( + 2.0e0 * self.data.build.dr_tf_inboard + ) + + # The 2.2 factor is used as a scaling factor to fit + # to the ITER-FDR value of 450 tonnes; see CCFE note T&M/PKNIGHT/PROCESS/026 + if self.data.physics.itart == 1: + # self.data.tfcoil.len_tf_coil does not include inboard leg + # ('centrepost') length in TART + m_tf_coil_case = ( + 2.2e0 + * self.data.tfcoil.den_tf_coil_case + * ( + cplen * self.data.tfcoil.a_tf_coil_inboard_case + + self.data.tfcoil.len_tf_coil + * self.data.tfcoil.a_tf_coil_outboard_case + ) + ) + else: + m_tf_coil_case = ( + 2.2e0 + * self.data.tfcoil.den_tf_coil_case + * ( + cplen * self.data.tfcoil.a_tf_coil_inboard_case + + (self.data.tfcoil.len_tf_coil - cplen) + * self.data.tfcoil.a_tf_coil_outboard_case + ) + ) + + # *** + + # Masses of conductor constituents + # --------------------------------- + # Superconductor mass [kg] + # Includes space allowance for central helium channel, area + # self.data.tfcoil.a_tf_wp_coolant_channels + m_tf_coil_superconductor = ( + self.data.tfcoil.len_tf_coil + * self.data.tfcoil.n_tf_coil_turns + * self.data.tfcoil.a_tf_turn_cable_space_no_void + * (1.0e0 - self.data.tfcoil.f_a_tf_turn_cable_space_extra_void) + * (1.0e0 - self.data.tfcoil.f_a_tf_turn_cable_copper) + - self.data.tfcoil.len_tf_coil * self.data.tfcoil.a_tf_wp_coolant_channels + ) * self.data.tfcoil.dcond[self.data.tfcoil.i_tf_sc_mat - 1] + + # Copper mass [kg] + m_tf_coil_copper = ( + self.data.tfcoil.len_tf_coil + * self.data.tfcoil.n_tf_coil_turns + * self.data.tfcoil.a_tf_turn_cable_space_no_void + * (1.0e0 - self.data.tfcoil.f_a_tf_turn_cable_space_extra_void) + * self.data.tfcoil.f_a_tf_turn_cable_copper + - self.data.tfcoil.len_tf_coil * self.data.tfcoil.a_tf_wp_coolant_channels + ) * constants.DEN_COPPER + m_tf_coil_copper = max(0.0e0, m_tf_coil_copper) + + # Steel conduit (sheath) mass [kg] + m_tf_wp_steel_conduit = ( + self.data.tfcoil.len_tf_coil + * self.data.tfcoil.n_tf_coil_turns + * self.data.tfcoil.a_tf_turn_steel + * self.data.fwbs.den_steel + ) + + # Conduit insulation mass [kg] + # (self.data.tfcoil.a_tf_coil_wp_turn_insulation already contains + # self.data.tfcoil.n_tf_coil_turns) + m_tf_coil_wp_turn_insulation = ( + self.data.tfcoil.len_tf_coil + * self.data.tfcoil.a_tf_coil_wp_turn_insulation + * self.data.tfcoil.den_tf_wp_turn_insulation + ) + + # Total conductor mass [kg] + m_tf_coil_conductor = ( + m_tf_coil_superconductor + + m_tf_coil_copper + + m_tf_wp_steel_conduit + + m_tf_coil_wp_turn_insulation + ) + # --------------------------------- + + # Total TF coil mass [kg] (all coils) + m_tf_coil = m_tf_coil_case + m_tf_coil_conductor + m_tf_coil_wp_insulation + + # Total TF coil mass [kg] (all coils) + m_tf_coils_total = m_tf_coil * self.data.tfcoil.n_tf_coils + + # If spherical tokamak, distribute between centrepost and outboard legs + # (in this case, total TF coil length = inboard `cplen` + + # outboard `len_tf_coil`) + if self.data.physics.itart == 1: + tfleng_sph = self.data.tfcoil.cplen + self.data.tfcoil.len_tf_coil + whtcp = m_tf_coils_total * (self.data.tfcoil.cplen / tfleng_sph) + whttflgs = m_tf_coils_total * (self.data.tfcoil.len_tf_coil / tfleng_sph) + else: + whtcp = 0.0 + whttflgs = 0.0 + + return SuperconTFAreasAndMasses( + m_tf_coil_wp_insulation=m_tf_coil_wp_insulation, + cplen=cplen, + m_tf_coil_case=m_tf_coil_case, + m_tf_coil_superconductor=m_tf_coil_superconductor, + m_tf_coil_copper=m_tf_coil_copper, + m_tf_wp_steel_conduit=m_tf_wp_steel_conduit, + m_tf_coil_conductor=m_tf_coil_conductor, + m_tf_coil_wp_turn_insulation=m_tf_coil_wp_turn_insulation, + m_tf_coil=m_tf_coil, + m_tf_coils_total=m_tf_coils_total, + whtcp=whtcp, + whttflgs=whttflgs, + ) + def output_cable_in_conduit_cable_info(self) -> None: """ Outputs the calculated cable in conduit cable space geometry information @@ -5192,9 +5321,6 @@ def run(self, output: bool = False): tf_areas_masses.m_tf_coil_wp_turn_insulation ) self.data.tfcoil.m_tf_coil_conductor = tf_areas_masses.m_tf_coil_conductor - self.data.tfcoil.m_tf_coil_inboard_steel = ( - tf_areas_masses.m_tf_coil_inboard_steel - ) self.data.tfcoil.m_tf_coil = tf_areas_masses.m_tf_coil self.data.tfcoil.m_tf_coils_total = tf_areas_masses.m_tf_coils_total self.data.tfcoil.whtcp = tf_areas_masses.whtcp From 68cb2a77e5e4329beed4103350aa693df9eb452c Mon Sep 17 00:00:00 2001 From: mn3981 Date: Fri, 2 Oct 2026 15:33:52 +0100 Subject: [PATCH 14/16] Refactor area and mass calculations in STEPSuperconductingTFCoil and add tf_step_inboard_areas_and_fractions method for improved accuracy --- process/models/tfcoil/superconducting.py | 155 ++++++++++++++--------- 1 file changed, 98 insertions(+), 57 deletions(-) diff --git a/process/models/tfcoil/superconducting.py b/process/models/tfcoil/superconducting.py index 8ca26ebcc6..1a2a2fd747 100644 --- a/process/models/tfcoil/superconducting.py +++ b/process/models/tfcoil/superconducting.py @@ -3716,7 +3716,6 @@ def tf_cicc_inboard_areas_and_fractions( def tf_cicc_areas_and_masses(self) -> SuperconTFAreasAndMasses: """Calculate areas and masses of superconducting TF coil components.""" - # Mass of ground-wall insulation [kg] # (assumed to be same density/material as turn insulation) d_sc_tf = self.data.superconducting_tfcoil @@ -3842,7 +3841,7 @@ def tf_cicc_areas_and_masses(self) -> SuperconTFAreasAndMasses: whtcp=whtcp, whttflgs=whttflgs, ) - + def output_cable_in_conduit_cable_info(self) -> None: """ Outputs the calculated cable in conduit cable space geometry information @@ -5151,6 +5150,13 @@ def run(self, output: bool = False): "Non integer turn geometry not implemented for STEP conductor." ) + # Cross-sectional area per turn + self.data.tfcoil.a_tf_turn = self.data.tfcoil.c_tf_total / ( + self.data.tfcoil.j_tf_wp + * self.data.tfcoil.n_tf_coils + * self.data.tfcoil.n_tf_coil_turns + ) + self.data.superconducting_tfcoil.n_tf_turn_superconducting_strands = ( self.calculate_stacked_tape_strand_count( dr_tape_stack=self.data.superconducting_tfcoil.dx_tf_turn_tape_stack, @@ -5158,68 +5164,38 @@ def run(self, output: bool = False): ) ) - # Areas and fractions - # ------------------- - # Central helium channel down the conductor core [m2] - self.data.tfcoil.a_tf_wp_coolant_channels = ( - 0.25e0 - * self.data.tfcoil.n_tf_coil_turns - * np.pi - * self.data.tfcoil.dia_tf_turn_coolant_channel**2 - ) - - # Total conductor cross-sectional area, taking account of void area - # and central helium channel [m2] - self.data.tfcoil.a_tf_wp_conductor = ( - self.data.superconducting_tfcoil.a_tf_turn_tape_stack - * self.data.tfcoil.n_tf_coil_turns - ) - - # Void area in conductor for He, not including central channel [m2] - self.data.tfcoil.a_tf_wp_extra_void = 0.0 - - # Area of inter-turn insulation: total [m2] - self.data.tfcoil.a_tf_coil_wp_turn_insulation = ( - self.data.tfcoil.n_tf_coil_turns * self.data.tfcoil.a_tf_turn_insulation - ) - - self.data.tfcoil.a_tf_turn_steel = 0.0 + self.data.tfcoil.f_a_tf_turn_cable_space_extra_void = 0.0 - # Area of steel structure in winding pack [m2] - self.data.tfcoil.a_tf_wp_steel = ( - self.data.tfcoil.n_tf_coil_turns * self.data.tfcoil.a_tf_turn_steel + inboard_areas_fractions = self.tf_step_inboard_areas_and_fractions( + dia_tf_turn_coolant_channel=self.data.tfcoil.dia_tf_turn_coolant_channel, + n_tf_coil_turns=self.data.tfcoil.n_tf_coil_turns, + a_tf_turn_tape_stack=d_sc_tf.a_tf_turn_tape_stack, + a_tf_turn_insulation=self.data.tfcoil.a_tf_turn_insulation, + a_tf_turn_steel=self.data.tfcoil.a_tf_turn_steel, + n_tf_coils=self.data.tfcoil.n_tf_coils, + a_tf_inboard_total=self.data.tfcoil.a_tf_inboard_total, + a_tf_coil_inboard_case=self.data.tfcoil.a_tf_coil_inboard_case, + a_tf_wp_ground_insulation=d_sc_tf.a_tf_wp_ground_insulation, ) - # Inboard coil steel area [m2] - self.data.superconducting_tfcoil.a_tf_coil_inboard_steel = ( - self.data.tfcoil.a_tf_coil_inboard_case + self.data.tfcoil.a_tf_wp_steel + self.data.tfcoil.a_tf_wp_coolant_channels = ( + inboard_areas_fractions.a_tf_wp_coolant_channels ) - - # Inboard coil steel fraction [-] - self.data.superconducting_tfcoil.f_a_tf_coil_inboard_steel = ( - self.data.tfcoil.n_tf_coils - * self.data.superconducting_tfcoil.a_tf_coil_inboard_steel - / self.data.tfcoil.a_tf_inboard_total + self.data.tfcoil.a_tf_wp_conductor = inboard_areas_fractions.a_tf_wp_conductor + self.data.tfcoil.a_tf_wp_extra_void = inboard_areas_fractions.a_tf_wp_extra_void + self.data.tfcoil.a_tf_coil_wp_turn_insulation = ( + inboard_areas_fractions.a_tf_coil_wp_turn_insulation ) - - # Inboard coil insulation cross-section [m2] - self.data.superconducting_tfcoil.a_tf_coil_inboard_insulation = ( - self.data.tfcoil.a_tf_coil_wp_turn_insulation - + self.data.superconducting_tfcoil.a_tf_wp_ground_insulation + self.data.tfcoil.a_tf_wp_steel = inboard_areas_fractions.a_tf_wp_steel + d_sc_tf.a_tf_coil_inboard_steel = inboard_areas_fractions.a_tf_coil_inboard_steel + d_sc_tf.f_a_tf_coil_inboard_steel = ( + inboard_areas_fractions.f_a_tf_coil_inboard_steel ) - - # Inboard coil insulation fraction [-] - self.data.superconducting_tfcoil.f_a_tf_coil_inboard_insulation = ( - self.data.tfcoil.n_tf_coils - * self.data.superconducting_tfcoil.a_tf_coil_inboard_insulation - / self.data.tfcoil.a_tf_inboard_total + d_sc_tf.a_tf_coil_inboard_insulation = ( + inboard_areas_fractions.a_tf_coil_inboard_insulation ) - - # Cross-sectional area per turn - self.data.tfcoil.a_tf_turn = self.data.tfcoil.c_tf_total / ( - self.data.tfcoil.j_tf_wp - * self.data.tfcoil.n_tf_coils - * self.data.tfcoil.n_tf_coil_turns + d_sc_tf.f_a_tf_coil_inboard_insulation = ( + inboard_areas_fractions.f_a_tf_coil_inboard_insulation ) superconductor_critical_properties = self.tf_step_superconductor_properties( @@ -5669,6 +5645,71 @@ def tf_step_vertical_tape_integer_turn_geometry( dia_tf_turn_coolant_channel=dia_tf_turn_coolant_channel, ) + @staticmethod + def tf_step_inboard_areas_and_fractions( + dia_tf_turn_coolant_channel: float, + n_tf_coil_turns: int, + a_tf_turn_tape_stack: float, + a_tf_turn_insulation: float, + a_tf_turn_steel: float, + a_tf_coil_inboard_case: float, + n_tf_coils: int, + a_tf_inboard_total: float, + a_tf_wp_ground_insulation: float, + ) -> SuperconTFAreasFractions: + + # Areas and fractions + # ------------------- + # Central helium channel down the conductor core [m2] + a_tf_wp_coolant_channels = ( + 0.25e0 * n_tf_coil_turns * np.pi * dia_tf_turn_coolant_channel**2 + ) + + # Total conductor cross-sectional area, taking account of void area + # and central helium channel [m2] + a_tf_wp_conductor = a_tf_turn_tape_stack * n_tf_coil_turns + + # Void area in conductor for He, not including central channel [m2] + a_tf_wp_extra_void = 0.0 + + # Area of inter-turn insulation: total [m2] + a_tf_coil_wp_turn_insulation = n_tf_coil_turns * a_tf_turn_insulation + + a_tf_turn_steel = 0.0 + + # Area of steel structure in winding pack [m2] + a_tf_wp_steel = n_tf_coil_turns * a_tf_turn_steel + + # Inboard coil steel area [m2] + a_tf_coil_inboard_steel = a_tf_coil_inboard_case + a_tf_wp_steel + + # Inboard coil steel fraction [-] + f_a_tf_coil_inboard_steel = ( + n_tf_coils * a_tf_coil_inboard_steel / a_tf_inboard_total + ) + + # Inboard coil insulation cross-section [m2] + a_tf_coil_inboard_insulation = ( + a_tf_coil_wp_turn_insulation + a_tf_wp_ground_insulation + ) + + # Inboard coil insulation fraction [-] + f_a_tf_coil_inboard_insulation = ( + n_tf_coils * a_tf_coil_inboard_insulation / a_tf_inboard_total + ) + + return SuperconTFAreasFractions( + a_tf_wp_coolant_channels=a_tf_wp_coolant_channels, + a_tf_wp_conductor=a_tf_wp_conductor, + a_tf_wp_extra_void=a_tf_wp_extra_void, + a_tf_coil_wp_turn_insulation=a_tf_coil_wp_turn_insulation, + a_tf_wp_steel=a_tf_wp_steel, + a_tf_coil_inboard_steel=a_tf_coil_inboard_steel, + f_a_tf_coil_inboard_steel=f_a_tf_coil_inboard_steel, + a_tf_coil_inboard_insulation=a_tf_coil_inboard_insulation, + f_a_tf_coil_inboard_insulation=f_a_tf_coil_inboard_insulation, + ) + def tf_step_superconductor_properties( self, a_tf_turn: float, From f259da407f589106a67ab5d2371f7d32fcea94ac Mon Sep 17 00:00:00 2001 From: mn3981 Date: Fri, 2 Oct 2026 17:11:57 +0100 Subject: [PATCH 15/16] Implement tf_croco_areas_and_masses method and refactor area/mass calculations in CROCOSuperconductingTFCoil --- process/models/tfcoil/superconducting.py | 345 +++++++++++++++++++++-- 1 file changed, 321 insertions(+), 24 deletions(-) diff --git a/process/models/tfcoil/superconducting.py b/process/models/tfcoil/superconducting.py index 1a2a2fd747..841d4993b8 100644 --- a/process/models/tfcoil/superconducting.py +++ b/process/models/tfcoil/superconducting.py @@ -4218,7 +4218,7 @@ def run(self, output: bool = False): # Calculate TF coil areas and masses self.generic_tf_coil_area_and_masses() - tf_areas_masses = self.superconducting_tf_coil_areas_and_masses() + tf_areas_masses = self.tf_croco_areas_and_masses() self.data.tfcoil.m_tf_coil_wp_insulation = ( tf_areas_masses.m_tf_coil_wp_insulation ) @@ -4854,6 +4854,127 @@ def tf_croco_inboard_areas_and_fractions( f_a_tf_coil_inboard_insulation=f_a_tf_coil_inboard_insulation, ) + def tf_croco_areas_and_masses(self) -> SuperconTFAreasAndMasses: + """Calculate areas and masses of superconducting TF coil components.""" + # Mass of ground-wall insulation [kg] + # (assumed to be same density/material as turn insulation) + d_sc_tf = self.data.superconducting_tfcoil + + m_tf_coil_wp_insulation = ( + self.data.tfcoil.len_tf_coil + * (d_sc_tf.a_tf_wp_with_insulation - d_sc_tf.a_tf_wp_no_insulation) + * self.data.tfcoil.den_tf_wp_turn_insulation + ) + + # The length of the vertical section is that of the first (inboard) segment + # = height of TF coil inner edge + (2 * coil thickness) + cplen = (2.0e0 * self.data.build.z_tf_inside_half) + ( + 2.0e0 * self.data.build.dr_tf_inboard + ) + + # The 2.2 factor is used as a scaling factor to fit + # to the ITER-FDR value of 450 tonnes; see CCFE note T&M/PKNIGHT/PROCESS/026 + if self.data.physics.itart == 1: + # self.data.tfcoil.len_tf_coil does not include inboard leg + # ('centrepost') length in TART + m_tf_coil_case = ( + 2.2e0 + * self.data.tfcoil.den_tf_coil_case + * ( + cplen * self.data.tfcoil.a_tf_coil_inboard_case + + self.data.tfcoil.len_tf_coil + * self.data.tfcoil.a_tf_coil_outboard_case + ) + ) + else: + m_tf_coil_case = ( + 2.2e0 + * self.data.tfcoil.den_tf_coil_case + * ( + cplen * self.data.tfcoil.a_tf_coil_inboard_case + + (self.data.tfcoil.len_tf_coil - cplen) + * self.data.tfcoil.a_tf_coil_outboard_case + ) + ) + + # Masses of conductor constituents + # --------------------------------- + # Superconductor mass [kg] + m_tf_coil_superconductor = ( + self.data.tfcoil.len_tf_coil + * self.data.tfcoil.n_tf_coil_turns + * d_sc_tf.a_tf_croco_strand_rebco + * N_CROCO_STRANDS_TURN + ) * self.data.tfcoil.dcond[self.data.tfcoil.i_tf_sc_mat - 1] + + # Copper mass [kg] + # Copper around the tape stacks in each strand plus the copper in the tapes + # and the central core of the cable space [kg] + m_tf_coil_copper = ( + self.data.tfcoil.len_tf_coil + * self.data.tfcoil.n_tf_coil_turns + * ( + d_sc_tf.a_tf_croco_strand_copper_total * N_CROCO_STRANDS_TURN + + d_sc_tf.a_tf_croco_strand + ) + ) * constants.DEN_COPPER + + # Steel conduit (sheath) mass [kg] + m_tf_wp_steel_conduit = ( + self.data.tfcoil.len_tf_coil + * self.data.tfcoil.n_tf_coil_turns + * self.data.tfcoil.a_tf_turn_steel + * self.data.fwbs.den_steel + ) + + # Conduit insulation mass [kg] + m_tf_coil_wp_turn_insulation = ( + self.data.tfcoil.len_tf_coil + * self.data.tfcoil.a_tf_coil_wp_turn_insulation + * self.data.tfcoil.den_tf_wp_turn_insulation + ) + + # Total conductor mass [kg] + m_tf_coil_conductor = ( + m_tf_coil_superconductor + + m_tf_coil_copper + + m_tf_wp_steel_conduit + + m_tf_coil_wp_turn_insulation + ) + # --------------------------------- + + # Total TF coil mass [kg] (all coils) + m_tf_coil = m_tf_coil_case + m_tf_coil_conductor + m_tf_coil_wp_insulation + + # Total TF coil mass [kg] (all coils) + m_tf_coils_total = m_tf_coil * self.data.tfcoil.n_tf_coils + + # If spherical tokamak, distribute between centrepost and outboard legs + # (in this case, total TF coil length = inboard `cplen` + + # outboard `len_tf_coil`) + if self.data.physics.itart == 1: + tfleng_sph = self.data.tfcoil.cplen + self.data.tfcoil.len_tf_coil + whtcp = m_tf_coils_total * (self.data.tfcoil.cplen / tfleng_sph) + whttflgs = m_tf_coils_total * (self.data.tfcoil.len_tf_coil / tfleng_sph) + else: + whtcp = 0.0 + whttflgs = 0.0 + + return SuperconTFAreasAndMasses( + m_tf_coil_wp_insulation=m_tf_coil_wp_insulation, + cplen=cplen, + m_tf_coil_case=m_tf_coil_case, + m_tf_coil_superconductor=m_tf_coil_superconductor, + m_tf_coil_copper=m_tf_coil_copper, + m_tf_wp_steel_conduit=m_tf_wp_steel_conduit, + m_tf_coil_conductor=m_tf_coil_conductor, + m_tf_coil_wp_turn_insulation=m_tf_coil_wp_turn_insulation, + m_tf_coil=m_tf_coil, + m_tf_coils_total=m_tf_coils_total, + whtcp=whtcp, + whttflgs=whttflgs, + ) + def croco_voltage(self) -> float: """Calculates CROCO voltage""" d_sc_tf = self.data.superconducting_tfcoil @@ -5049,23 +5170,20 @@ def output_croco_info(self) -> None: ) -@dataclass -class STEPIntegerTurnGeometry: +@dataclass(slots=True) +class STEPIntegerTurnGeometry(TFGeneralTurnGeometry): """Geometry of a STEP integer turn in the superconducting TF coil.""" - dr_tf_turn: float - dx_tf_turn: float - c_tf_turn: float - n_tf_coil_turns: int dr_tf_turn_stabiliser: float dx_tf_turn_stabiliser: float x_tf_turn_coolant_channel_centre: float dr_tf_turn_tape_stack: float dx_tf_turn_tape_stack: float a_tf_turn_tape_stack: float - a_tf_turn_insulation: float a_tf_turn_stabiliser: float dia_tf_turn_coolant_channel: float + dr_tf_turn_conduit_full: float + dx_tf_turn_conduit_full_toroidal: float class STEPSuperconductingTFCoil(SuperconductingTFCoil): @@ -5144,6 +5262,42 @@ def run(self, output: bool = False): self.data.tfcoil.dia_tf_turn_coolant_channel = ( integer_turn_geometry.dia_tf_turn_coolant_channel ) + self.data.tfcoil.a_tf_turn_cable_space_no_void = ( + integer_turn_geometry.a_tf_turn_cable_space_no_void + ) + + self.data.tfcoil.a_tf_turn_steel = integer_turn_geometry.a_tf_turn_steel + + self.data.tfcoil.dx_tf_turn_general = ( + integer_turn_geometry.dx_tf_turn_general + ) + + self.data.tfcoil.dx_tf_turn_conduit_full_average = ( + integer_turn_geometry.dx_tf_turn_conduit_full_average + ) + + d_sc_tf.dx_tf_turn_cable_space_average = ( + integer_turn_geometry.dx_tf_turn_cable_space_average + ) + + d_sc_tf.dr_tf_turn_cable_space = ( + integer_turn_geometry.dr_tf_turn_tape_stack + ) + d_sc_tf.dx_tf_turn_cable_space = ( + integer_turn_geometry.dx_tf_turn_tape_stack + ) + d_sc_tf.a_tf_turn_cable_space_effective = ( + integer_turn_geometry.a_tf_turn_tape_stack + ) + + d_sc_tf.dr_tf_turn_conduit_full = ( + integer_turn_geometry.dr_tf_turn_conduit_full + ) + d_sc_tf.dx_tf_turn_conduit_full_toroidal = ( + integer_turn_geometry.dx_tf_turn_conduit_full_toroidal + ) + + self.data.tfcoil.dx_tf_turn_steel = 0.0 case TFWPIntegerTurnType.NON_INTEGER: raise ProcessValueError( @@ -5236,22 +5390,22 @@ def run(self, output: bool = False): superconductor_critical_properties.c_turn_cables_critical ) - # if self.data.tfcoil.i_str_wp == 0: - # strain = self.data.tfcoil.str_tf_con_res - # else: - # strain = self.data.tfcoil.str_wp + if self.data.tfcoil.i_str_wp == 0: + strain = self.data.tfcoil.str_tf_con_res + else: + strain = self.data.tfcoil.str_wp - # self.data.tfcoil.temp_tf_superconductor_margin = self.calculate_superconductor_temperature_margin( - # i_tf_superconductor=self.data.tfcoil.i_tf_sc_mat, - # j_superconductor=self.data.superconducting_tfcoil.j_tf_superconductor, - # b_tf_inboard_peak=self.data.tfcoil.b_tf_inboard_peak_with_ripple, - # strain=strain, - # bc20m=self.data.superconducting_tfcoil.b_tf_superconductor_critical_zero_temp_strain, - # tc0m=self.data.superconducting_tfcoil.temp_tf_superconductor_critical_zero_field_strain, - # c0=1.0e10, - # temp_tf_coolant_peak_field=self.data.tfcoil.tftmp, - # data=self.data, - # ) + self.data.tfcoil.temp_tf_superconductor_margin = self.calculate_superconductor_temperature_margin( + i_tf_superconductor=self.data.tfcoil.i_tf_sc_mat, + j_superconductor=self.data.superconducting_tfcoil.j_tf_superconductor, + b_tf_inboard_peak=self.data.tfcoil.b_tf_inboard_peak_with_ripple, + strain=strain, + bc20m=self.data.superconducting_tfcoil.b_tf_superconductor_critical_zero_temp_strain, + tc0m=self.data.superconducting_tfcoil.temp_tf_superconductor_critical_zero_field_strain, + c0=1.0e10, + temp_tf_coolant_peak_field=self.data.tfcoil.tftmp, + data=self.data, + ) # TFC Quench voltage in kV @@ -5282,7 +5436,7 @@ def run(self, output: bool = False): # Calculate TF coil areas and masses self.generic_tf_coil_area_and_masses() - tf_areas_masses = self.superconducting_tf_coil_areas_and_masses() + tf_areas_masses = self.tf_step_areas_and_masses() self.data.tfcoil.m_tf_coil_wp_insulation = ( tf_areas_masses.m_tf_coil_wp_insulation ) @@ -5572,6 +5726,8 @@ def tf_step_vertical_tape_integer_turn_geometry( f"{dx_tf_turn=} {dx_tf_turn_insulation=}" ) + dx_tf_turn_general = dx_tf_turn + # Number of TF turns n_tf_coil_turns = np.double(n_tf_wp_layers * n_tf_wp_pancakes) @@ -5583,6 +5739,11 @@ def tf_step_vertical_tape_integer_turn_geometry( dr_tf_turn_stabiliser = dr_tf_turn - 2.0e0 * dx_tf_turn_insulation dx_tf_turn_stabiliser = dx_tf_turn - 2.0e0 * dx_tf_turn_insulation + dr_tf_turn_conduit_full = dr_tf_turn_stabiliser + dx_tf_turn_conduit_full_toroidal = dx_tf_turn_stabiliser + + dx_tf_turn_conduit_full_average = dx_tf_turn_stabiliser + dia_tf_turn_coolant_channel = min( 0.3 * dx_tf_turn_stabiliser, 0.8 * dr_tf_turn_stabiliser ) @@ -5614,9 +5775,17 @@ def tf_step_vertical_tape_integer_turn_geometry( dx_tf_turn_insulation + (0.1 * dx_tf_turn_stabiliser) ) + dx_tf_turn_cable_space_average = dx_tf_turn_tape_stack + # Cross-sectional area of tape stack per turn [m²] a_tf_turn_tape_stack = dr_tf_turn_tape_stack * dx_tf_turn_tape_stack + # The tape stack is perfectly packed with no voids + a_tf_turn_cable_space_no_void = a_tf_turn_tape_stack + + # Area of steel conduit per turn [m²] (none in STEP design) + a_tf_turn_steel = 0.0 + # Area of inter-turn insulation: single turn [m²] a_tf_turn_insulation = (dr_tf_turn * dx_tf_turn) - ( dr_tf_turn_stabiliser * dx_tf_turn_stabiliser @@ -5643,6 +5812,13 @@ def tf_step_vertical_tape_integer_turn_geometry( a_tf_turn_insulation=a_tf_turn_insulation, a_tf_turn_stabiliser=a_tf_turn_stabiliser, dia_tf_turn_coolant_channel=dia_tf_turn_coolant_channel, + a_tf_turn_cable_space_no_void=a_tf_turn_cable_space_no_void, + a_tf_turn_steel=a_tf_turn_steel, + dx_tf_turn_cable_space_average=dx_tf_turn_cable_space_average, + dx_tf_turn_conduit_full_average=dx_tf_turn_conduit_full_average, + dx_tf_turn_general=dx_tf_turn_general, + dr_tf_turn_conduit_full=dr_tf_turn_conduit_full, + dx_tf_turn_conduit_full_toroidal=dx_tf_turn_conduit_full_toroidal, ) @staticmethod @@ -5929,6 +6105,127 @@ def calculate_stacked_tape_strand_count( # Use floor to get the maximum whole number of tapes that fit return int(np.floor(dr_tape_stack / dr_hts_tape)) + def tf_step_areas_and_masses(self) -> SuperconTFAreasAndMasses: + """Calculate areas and masses of superconducting TF coil components.""" + # Mass of ground-wall insulation [kg] + # (assumed to be same density/material as turn insulation) + d_sc_tf = self.data.superconducting_tfcoil + + m_tf_coil_wp_insulation = ( + self.data.tfcoil.len_tf_coil + * (d_sc_tf.a_tf_wp_with_insulation - d_sc_tf.a_tf_wp_no_insulation) + * self.data.tfcoil.den_tf_wp_turn_insulation + ) + + # The length of the vertical section is that of the first (inboard) segment + # = height of TF coil inner edge + (2 * coil thickness) + cplen = (2.0e0 * self.data.build.z_tf_inside_half) + ( + 2.0e0 * self.data.build.dr_tf_inboard + ) + + # The 2.2 factor is used as a scaling factor to fit + # to the ITER-FDR value of 450 tonnes; see CCFE note T&M/PKNIGHT/PROCESS/026 + if self.data.physics.itart == 1: + # self.data.tfcoil.len_tf_coil does not include inboard leg + # ('centrepost') length in TART + m_tf_coil_case = ( + 2.2e0 + * self.data.tfcoil.den_tf_coil_case + * ( + cplen * self.data.tfcoil.a_tf_coil_inboard_case + + self.data.tfcoil.len_tf_coil + * self.data.tfcoil.a_tf_coil_outboard_case + ) + ) + else: + m_tf_coil_case = ( + 2.2e0 + * self.data.tfcoil.den_tf_coil_case + * ( + cplen * self.data.tfcoil.a_tf_coil_inboard_case + + (self.data.tfcoil.len_tf_coil - cplen) + * self.data.tfcoil.a_tf_coil_outboard_case + ) + ) + + # Masses of conductor constituents + # --------------------------------- + # Superconductor mass [kg] + m_tf_coil_superconductor = ( + self.data.tfcoil.len_tf_coil + * self.data.tfcoil.n_tf_coil_turns + * d_sc_tf.dx_tf_hts_tape_rebco + * d_sc_tf.dr_tf_hts_tape + * d_sc_tf.n_tf_turn_superconducting_strands + ) * self.data.tfcoil.dcond[self.data.tfcoil.i_tf_sc_mat - 1] + + # Copper mass [kg] + # Copper around the tape stacks and the surrounding stabiliser [kg] + m_tf_coil_copper = ( + self.data.tfcoil.len_tf_coil + * self.data.tfcoil.n_tf_coil_turns + * ( + d_sc_tf.a_tf_turn_stabiliser + + ( + d_sc_tf.dx_tf_hts_tape_copper + * d_sc_tf.dr_tf_hts_tape + * d_sc_tf.n_tf_turn_superconducting_strands + ) + ) + ) * constants.DEN_COPPER + + # Steel conduit (sheath) mass [kg] + # No steel conduit in the STEP design it is all copper stabiliser + m_tf_wp_steel_conduit = 0.0 + + # Conduit insulation mass [kg] + m_tf_coil_wp_turn_insulation = ( + self.data.tfcoil.len_tf_coil + * self.data.tfcoil.a_tf_coil_wp_turn_insulation + * self.data.tfcoil.den_tf_wp_turn_insulation + ) + + # Total conductor mass [kg] + m_tf_coil_conductor = ( + m_tf_coil_superconductor + + m_tf_coil_copper + + m_tf_wp_steel_conduit + + m_tf_coil_wp_turn_insulation + ) + # --------------------------------- + + # Total TF coil mass [kg] (all coils) + m_tf_coil = m_tf_coil_case + m_tf_coil_conductor + m_tf_coil_wp_insulation + + # Total TF coil mass [kg] (all coils) + m_tf_coils_total = m_tf_coil * self.data.tfcoil.n_tf_coils + + # If spherical tokamak, distribute between centrepost and outboard legs + # (in this case, total TF coil length = inboard `cplen` + + # outboard `len_tf_coil`) + if self.data.physics.itart == 1: + tfleng_sph = self.data.tfcoil.cplen + self.data.tfcoil.len_tf_coil + whtcp = m_tf_coils_total * (self.data.tfcoil.cplen / tfleng_sph) + whttflgs = m_tf_coils_total * (self.data.tfcoil.len_tf_coil / tfleng_sph) + else: + whtcp = 0.0 + whttflgs = 0.0 + + return SuperconTFAreasAndMasses( + m_tf_coil_wp_insulation=m_tf_coil_wp_insulation, + cplen=cplen, + m_tf_coil_case=m_tf_coil_case, + m_tf_coil_superconductor=m_tf_coil_superconductor, + m_tf_coil_copper=m_tf_coil_copper, + m_tf_wp_steel_conduit=m_tf_wp_steel_conduit, + m_tf_coil_conductor=m_tf_coil_conductor, + m_tf_coil_wp_turn_insulation=m_tf_coil_wp_turn_insulation, + m_tf_coil=m_tf_coil, + m_tf_coils_total=m_tf_coils_total, + whtcp=whtcp, + whttflgs=whttflgs, + ) + def output_step_turn_info(self) -> None: """Output the STEP conductor geometry and properties.""" po.oheadr(self.outfile, "TF STEP Cable Space Information") From cc1638e470621eeb66ae579af8c43519dfce7194 Mon Sep 17 00:00:00 2001 From: mn3981 Date: Fri, 2 Oct 2026 17:17:28 +0100 Subject: [PATCH 16/16] Add calculate_tf_step_superconductor_length method and update STEPSuperconductingTFCoil to compute superconductor lengths --- process/models/tfcoil/superconducting.py | 49 ++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/process/models/tfcoil/superconducting.py b/process/models/tfcoil/superconducting.py index 841d4993b8..385adef161 100644 --- a/process/models/tfcoil/superconducting.py +++ b/process/models/tfcoil/superconducting.py @@ -5318,6 +5318,16 @@ def run(self, output: bool = False): ) ) + ( + d_sc_tf.len_tf_coil_superconductor, + d_sc_tf.len_tf_superconductor_total, + ) = self.calculate_tf_step_superconductor_length( + n_tf_coils=self.data.tfcoil.n_tf_coils, + n_tf_coil_turns=self.data.tfcoil.n_tf_coil_turns, + len_tf_coil=self.data.tfcoil.len_tf_coil, + n_tf_turn_superconducting_strands=d_sc_tf.n_tf_turn_superconducting_strands, + ) + self.data.tfcoil.f_a_tf_turn_cable_space_extra_void = 0.0 inboard_areas_fractions = self.tf_step_inboard_areas_and_fractions( @@ -6226,6 +6236,45 @@ def tf_step_areas_and_masses(self) -> SuperconTFAreasAndMasses: whttflgs=whttflgs, ) + @staticmethod + def calculate_tf_step_superconductor_length( + n_tf_coils: int, + n_tf_coil_turns: int, + len_tf_coil: float, + n_tf_turn_superconducting_strands: int, + ) -> float: + """Calculates the total length of superconducting material required for the + TF coils. + + Parameters + ---------- + n_tf_coils: int : + n_tf_coils: Number of TF coils. + n_tf_coil_turns: int : + n_tf_coil_turns: Total number of turns in the TF coil winding pack. + len_tf_coil: + len_tf_coil: Length of a single TF coil (in meters). + n_tf_turn_superconducting_strands: int : + n_tf_turn_superconducting_strands: Number of superconducting tapes/strands + per turn in the TF coil. + + Returns + ------- + tuple[float, float] + Tuple containing: + - Length of superconductor in one TF coil (in meters). + - Total length of superconductor in all TF coils (in meters). + """ + # Length of superconductor in one TF coil + len_tf_coil_superconductor = ( + n_tf_coil_turns * len_tf_coil * n_tf_turn_superconducting_strands + ) + + # Total length of superconductor in all TF coils + len_tf_superconductor_total = len_tf_coil_superconductor * n_tf_coils + + return len_tf_coil_superconductor, len_tf_superconductor_total + def output_step_turn_info(self) -> None: """Output the STEP conductor geometry and properties.""" po.oheadr(self.outfile, "TF STEP Cable Space Information")