diff --git a/process/data_structure/superconducting_tf_coil_variables.py b/process/data_structure/superconducting_tf_coil_variables.py index af280c4243..95bdfa5405 100644 --- a/process/data_structure/superconducting_tf_coil_variables.py +++ b/process/data_structure/superconducting_tf_coil_variables.py @@ -216,6 +216,33 @@ j_tf_superconductor: float = None """Current density in the superconducting cable [A/m^2]""" +i_tf_turn_type: int = None +"""Switch for TF turn geometry type""" + +dx_tf_turn_tape_stack: float = None +"""Thickness of the tape stack in the TF turn [m]""" + +dr_tf_turn_tape_stack: float = None +"""Radial thickness of the tape stack in the TF turn [m]""" + +a_tf_turn_tape_stack: float = None +"""Cross-sectional area of the tape stack in the TF turn [m^2]""" + +n_tf_turn_superconducting_strands: int = None +"""Number of superconducting strands in the TF turn""" + +x_tf_turn_coolant_channel_centre: float = None +"""Vertical centre position of the coolant channel in the TF turn [m]""" + +dr_tf_turn_stabiliser: float = None +"""Radial thickness of the stabiliser in the TF turn [m]""" + +dx_tf_turn_stabiliser: float = None +"""Toroidal thickness of the stabiliser in the TF turn [m]""" + +a_tf_turn_stabiliser: float = None +"""Cross-sectional area of the stabiliser in the TF turn [m^2]""" + # Vacuum Vessel stress on TF coil quench vv_stress_quench: float = None @@ -320,6 +347,15 @@ def init_superconducting_tf_coil_variables(): global f_a_tf_turn_cable_space_cooling global c_tf_turn_cables_critical global j_tf_superconductor + global i_tf_turn_type + global dx_tf_turn_tape_stack + global dr_tf_turn_tape_stack + global a_tf_turn_tape_stack + global n_tf_turn_superconducting_strands + global x_tf_turn_coolant_channel_centre + global dr_tf_turn_stabiliser + global dx_tf_turn_stabiliser + global a_tf_turn_stabiliser is_leg_cp_temp_same = 0 tf_fit_t = 0.0 @@ -380,3 +416,12 @@ def init_superconducting_tf_coil_variables(): f_a_tf_turn_cable_space_cooling = 0.0 c_tf_turn_cables_critical = 0.0 j_tf_superconductor = 0.0 + i_tf_turn_type = 0 + dx_tf_turn_tape_stack = 0.0 + dr_tf_turn_tape_stack = 0.0 + a_tf_turn_tape_stack = 0.0 + n_tf_turn_superconducting_strands = 0 + x_tf_turn_coolant_channel_centre = 0.0 + dr_tf_turn_stabiliser = 0.0 + dx_tf_turn_stabiliser = 0.0 + a_tf_turn_stabiliser = 0.0 diff --git a/process/init.py b/process/init.py index 15b7598cc0..72477f35c5 100644 --- a/process/init.py +++ b/process/init.py @@ -8,6 +8,7 @@ import process import process.iteration_variables as iteration_variables import process.process_output as process_output +import process.superconducting_tf_coil as superconducting_tf_coil_module from process import constants, data_structure from process.constraints import ConstraintManager from process.data_structure.blanket_library import init_blanket_library @@ -811,6 +812,15 @@ def check_process(inputs): # noqa: ARG001 "Constraint equation 10 (CP lifetime) to used with ST desing (itart=1)" ) + if ( + data_structure.superconducting_tf_coil_variables.i_tf_turn_type == 0 + and data_structure.tfcoil_variables.i_tf_sup + not in superconducting_tf_coil_module.CABLE_SUPERCONDUCTORS + ): + raise ProcessValidationError( + "i_tf_turn_type = 0 can only be used with cable type superconductors" + ) + # Pulsed power plant model if data_structure.pulse_variables.i_pulsed_plant == 1: data_structure.global_variables.icase = "Pulsed tokamak model" diff --git a/process/input.py b/process/input.py index 91c7eebfc1..efce39b3b1 100644 --- a/process/input.py +++ b/process/input.py @@ -2203,6 +2203,9 @@ def __post_init__(self): "n_tf_wp_pancakes": InputVariable( data_structure.tfcoil_variables, int, range=(1, 100) ), + "i_tf_turn_type": InputVariable( + data_structure.superconducting_tf_coil_variables, int, choices=[0, 1, 2] + ), "n_rad_per_layer": InputVariable( data_structure.tfcoil_variables, int, range=(1, 500) ), diff --git a/process/io/plot_proc.py b/process/io/plot_proc.py index 17cbe646f5..b9c6b6980f 100644 --- a/process/io/plot_proc.py +++ b/process/io/plot_proc.py @@ -6926,6 +6926,339 @@ def plot_cable_in_conduit_cable(axis, fig, mfile_data, scan: int) -> None: axis.set_ylabel("Y [mm]") +def plot_tf_step_vertical_tape_turn( + axis, + fig, + mfile_data, + scan: int, + dr_tf_turn, + dx_tf_turn, + dia_tf_turn_coolant_channel, + dx_tf_turn_insulation, + dr_tf_turn_tape_stack, + dx_tf_turn_tape_stack, + x_tf_turn_coolant_channel_centre, + dr_tf_turn_stabiliser, +) -> None: + """ + Plots TF coil step 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", + ), + ) + + 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("X [m]") + axis.set_ylabel("Y [m]") + + # Add info about the steel casing surrounding the WP + textstr_turn_insulation = f"$\\mathbf{{Turn \\ Insulation:}}$\n\n$\\Delta r:${mfile_data.data['t_tf_superconductor_quench'].get_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_data.data['dr_tf_turn'].get_scan(scan):.3e} m\n" + f"$\\Delta x$: {mfile_data.data['dx_tf_turn'].get_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_data.data['dr_tf_turn_tape_stack'].get_scan(scan):.3e} m\n" + f"$\\Delta x:$ {mfile_data.data['dx_tf_turn_tape_stack'].get_scan(scan):.3e} m\n" + f"Tape stack space area: {mfile_data.data['a_tf_turn_tape_stack'].get_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_data.data['dr_tf_turn_stabiliser'].get_scan(scan):.3e} m\n" + f"$\\Delta x$: {mfile_data.data['dx_tf_turn_stabiliser'].get_scan(scan):.3e} m\n" + f"Stabiliser area: {mfile_data.data['a_tf_turn_stabiliser'].get_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_data.data['dia_tf_turn_coolant_channel'].get_scan(scan):.3e} m\n" + f"Total area of all coolant channels: {mfile_data.data['a_tf_wp_coolant_channels'].get_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: {sctf.SUPERCONDUCTING_TF_TYPES[mfile_data.data['i_tf_sc_mat'].get_scan(scan)]}\n" + f"Critical field at zero \ntemperature and strain: {mfile_data.data['b_tf_superconductor_critical_zero_temp_strain'].get_scan(scan):.4f} T\n" + f"Critical temperature at \nzero field and strain: {mfile_data.data['temp_tf_superconductor_critical_zero_field_strain'].get_scan(scan):.4f} K\n" + f"Temperature at conductor: {mfile_data.data['tftmp'].get_scan(scan):.4f} K\n" + f"$I_{{\\text{{TF,turn critical}}}}$: {mfile_data.data['c_turn_cables_critical'].get_scan(scan):,.2f} A\n" + f"$I_{{\\text{{TF,turn}}}}$: {mfile_data.data['c_tf_turn'].get_scan(scan):,.2f} A\n" + f"Critcal current ratio: {mfile_data.data['f_c_tf_turn_operating_critical'].get_scan(scan):,.4f}\n" + f"Superconductor temperature \nmargin: {mfile_data.data['temp_tf_superconductor_margin'].get_scan(scan):,.4f} K\n" + f"\n$\\mathbf{{Quench:}}$\n \n" + f"Quench dump time: {mfile_data.data['t_tf_superconductor_quench'].get_scan(scan):.4e} s\n" + f"Quench detection time: {mfile_data.data['t_tf_quench_detection'].get_scan(scan):.4e} s\n" + f"User input max temperature \nduring quench: {mfile_data.data['temp_tf_conductor_quench_max'].get_scan(scan):.2f} K\n" + f"Required maxium WP current \ndensity for heat protection:\n{mfile_data.data['j_tf_wp_quench_heat_max'].get_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 plot_hts_tape( + axis, + fig, + mfile_data, + scan: int, + dx_hts_tape_rebco, + dx_hts_tape_copper, + dx_hts_tape_hastelloy, + dx_hts_tape_total, + dr_hts_tape, +) -> None: + """ + Plot the TF coils HTS tape compositions and dimensions + """ + + # convert all dimensions from metres to micrometres for plotting + um = 1e6 + dx_cu_um = dx_hts_tape_copper * um + dx_hast_um = dx_hts_tape_hastelloy * um + dx_rebco_um = dx_hts_tape_rebco * um + dx_total_um = dx_hts_tape_total * um + dr_um = dr_hts_tape * um + + # left copper (half), left hastelloy (half), REBCO, then mirrored right side + axis.add_patch( + Rectangle( + [0, 0], + dx_cu_um / 2, + dr_um, + facecolor="#b87333", # copper color + edgecolor="#8B4000", # darker copper edge + ), + ) + + axis.add_patch( + Rectangle( + [dx_cu_um / 2, 0], + dx_hast_um / 2, + dr_um, + facecolor="silver", + edgecolor="#A9A9A9", + ), + ) + + rebco_x = (dx_cu_um + dx_hast_um) / 2 + axis.add_patch( + Rectangle( + [rebco_x, 0], + dx_rebco_um, + dr_um, + facecolor="#D4AF37", # gold fill + edgecolor="#B8860B", + ), + ) + + # mirror Hastelloy and copper on the right side of the REBCO + rebco_end = rebco_x + dx_rebco_um + axis.add_patch( + Rectangle( + [rebco_end, 0], + dx_hast_um / 2, + dr_um, + facecolor="silver", + edgecolor="#A9A9A9", + ), + ) + + axis.add_patch( + Rectangle( + [rebco_end + dx_hast_um / 2, 0], + dx_cu_um / 2, + dr_um, + facecolor="#b87333", + edgecolor="#8B4000", + ), + ) + + # Convert lengths from meters to kilometers for display + len_tf_coil_superconductor_km = ( + mfile_data.data["len_tf_coil_superconductor"].get_scan(scan) / 1000.0 + ) + len_tf_superconductor_total_km = ( + mfile_data.data["len_tf_superconductor_total"].get_scan(scan) / 1000.0 + ) + + # Annotate component widths (in µm) + textstr_components = ( + f"$\\mathbf{{Tapes:}}$\n \n" + f"Number of strands per turn: {int(mfile_data.data['n_tf_turn_superconducting_strands'].get_scan(scan)):,}\n" + f"Length of superconductor per coil: {len_tf_coil_superconductor_km:,.2f} km\n" + f"Total length of superconductor in all coils: {len_tf_superconductor_total_km:,.2f} km\n" + f"Copper (each side half): {dx_cu_um / 2:.1f} µm\n" + f"Hastelloy (each side half): {dx_hast_um / 2:.1f} µm\n" + f"REBCO: {dx_rebco_um:.1f} µm\n" + f"Total tape thickness: {dx_total_um:.1f} µm\n" + f"Tape width: {dr_um:.1f} µm" + ) + axis.text( + 0.4, + 0.3, + textstr_components, + fontsize=9, + verticalalignment="top", + horizontalalignment="left", + transform=fig.transFigure, + bbox={ + "boxstyle": "round", + "facecolor": "#eeeeee", + "alpha": 0.9, + "linewidth": 1, + }, + ) + + axis.set_xlim(-dx_total_um * 0.1, dx_total_um * 1.1) + axis.set_ylim(-dr_um * 0.1, dr_um * 1.1) + axis.set_title("TF HTS Tape Cross-Section (µm)") + axis.minorticks_on() + # remove legend if nothing to show (keeps plot clean) + axis.legend(loc="upper right") + axis.grid(True, which="both", linestyle="--", linewidth=0.5, alpha=0.2) + axis.set_xlabel("X [µm]") + axis.set_ylabel("Y [µm]") + + def plot_pf_coils(axis, mfile_data, scan, colour_scheme): """Function to plot PF coils @@ -12354,10 +12687,57 @@ def main_plot( # TF coil turn structure ax20 = fig14.add_subplot(325, aspect="equal") ax20.set_position([0.025, 0.5, 0.4, 0.4]) - plot_tf_cable_in_conduit_turn(ax20, fig14, m_file_data, scan) - plot_205 = fig14.add_subplot(223, aspect="equal") - plot_205.set_position([0.075, 0.1, 0.3, 0.3]) - plot_cable_in_conduit_cable(plot_205, fig14, m_file_data, scan) + + if m_file_data.data["i_tf_turn_type"].get_scan(scan) == 0: + plot_205 = fig14.add_subplot(223, aspect="equal") + plot_205.set_position([0.075, 0.1, 0.3, 0.3]) + plot_tf_cable_in_conduit_turn(ax20, fig14, m_file_data, scan) + + plot_cable_in_conduit_cable(plot_205, fig14, m_file_data, scan) + elif m_file_data.data["i_tf_turn_type"].get_scan(scan) == 2: + plot_205 = fig14.add_subplot(223) + plot_205.set_position([0.075, 0.1, 0.3, 0.3]) + plot_tf_step_vertical_tape_turn( + ax20, + fig14, + m_file_data, + scan, + dr_tf_turn=m_file_data.data["dr_tf_turn"].get_scan(scan), + dx_tf_turn=m_file_data.data["dx_tf_turn"].get_scan(scan), + dia_tf_turn_coolant_channel=m_file_data.data[ + "dia_tf_turn_coolant_channel" + ].get_scan(scan), + dx_tf_turn_insulation=m_file_data.data[ + "dx_tf_turn_insulation" + ].get_scan(scan), + dr_tf_turn_tape_stack=m_file_data.data[ + "dr_tf_turn_tape_stack" + ].get_scan(scan), + dx_tf_turn_tape_stack=m_file_data.data[ + "dx_tf_turn_tape_stack" + ].get_scan(scan), + x_tf_turn_coolant_channel_centre=m_file_data.data[ + "x_tf_turn_coolant_channel_centre" + ].get_scan(scan), + dr_tf_turn_stabiliser=m_file_data.data[ + "dr_tf_turn_stabiliser" + ].get_scan(scan), + ) + plot_hts_tape( + plot_205, + fig14, + m_file_data, + scan, + dx_hts_tape_rebco=m_file_data.data["dx_hts_tape_rebco"].get_scan(scan), + dx_hts_tape_copper=m_file_data.data["dx_hts_tape_copper"].get_scan( + scan + ), + dx_hts_tape_hastelloy=m_file_data.data[ + "dx_hts_tape_hastelloy" + ].get_scan(scan), + dx_hts_tape_total=m_file_data.data["dx_hts_tape_total"].get_scan(scan), + dr_hts_tape=m_file_data.data["dr_hts_tape"].get_scan(scan), + ) else: ax19 = fig13.add_subplot(211, aspect="equal") ax19.set_position([0.06, 0.55, 0.675, 0.4]) diff --git a/process/superconducting_tf_coil.py b/process/superconducting_tf_coil.py index 0d4fbe3794..48b3b2f933 100644 --- a/process/superconducting_tf_coil.py +++ b/process/superconducting_tf_coil.py @@ -37,6 +37,9 @@ 9: "REBCO Hazelton-Zhai", } +CABLE_SUPERCONDUCTORS = {1, 2, 3, 4, 5, 7} +TAPE_SUPERCONDUCTORS = {6, 8, 9} + class SuperconductingTFCoil(TFCoil): def __init__(self): @@ -55,6 +58,7 @@ def run(self, output: bool): tfcoil_variables.i_tf_wp_geom, tfcoil_variables.i_tf_case_geom, tfcoil_variables.i_tf_turns_integer, + superconducting_tf_coil_variables.i_tf_turn_type, ) tfcoil_variables.ind_tf_coil = self.tf_coil_self_inductance( @@ -111,143 +115,9 @@ def run(self, output: bool): # Do stress calculations (writes the stress output) if output: tfcoil_variables.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, - tfcoil_variables.sig_tf_wp, - sig_tf_case, - sig_tf_cs_bucked, - str_wp, - casestr, - insstrain, - sig_tf_wp_av_z, - ) = self.stresscl( - int(tfcoil_variables.n_tf_stress_layers), - int(tfcoil_variables.n_rad_per_layer), - int(tfcoil_variables.n_tf_wp_stress_layers), - int(tfcoil_variables.i_tf_bucking), - float(build_variables.r_tf_inboard_in), - build_variables.dr_bore, - build_variables.z_tf_inside_half, - pfcoil_variables.f_z_cs_tf_internal, - build_variables.dr_cs, - build_variables.i_tf_inside_cs, - build_variables.dr_tf_inboard, - build_variables.dr_cs_tf_gap, - pfcoil_variables.i_pf_conductor, - pfcoil_variables.j_cs_flat_top_end, - pfcoil_variables.j_cs_pulse_start, - pfcoil_variables.c_pf_coil_turn_peak_input, - pfcoil_variables.n_pf_coils_in_group, - pfcoil_variables.f_dr_dz_cs_turn, - pfcoil_variables.radius_cs_turn_corners, - pfcoil_variables.f_a_cs_turn_steel, - tfcoil_variables.eyoung_steel, - tfcoil_variables.poisson_steel, - tfcoil_variables.eyoung_cond_axial, - tfcoil_variables.poisson_cond_axial, - tfcoil_variables.eyoung_cond_trans, - tfcoil_variables.poisson_cond_trans, - tfcoil_variables.eyoung_ins, - tfcoil_variables.poisson_ins, - tfcoil_variables.dx_tf_turn_insulation, - tfcoil_variables.eyoung_copper, - tfcoil_variables.poisson_copper, - tfcoil_variables.i_tf_sup, - tfcoil_variables.eyoung_res_tf_buck, - superconducting_tf_coil_variables.r_tf_wp_inboard_inner, - superconducting_tf_coil_variables.tan_theta_coil, - superconducting_tf_coil_variables.rad_tf_coil_inboard_toroidal_half, - superconducting_tf_coil_variables.r_tf_wp_inboard_outer, - superconducting_tf_coil_variables.a_tf_coil_inboard_steel, - superconducting_tf_coil_variables.a_tf_plasma_case, - superconducting_tf_coil_variables.a_tf_coil_nose_case, - tfcoil_variables.dx_tf_wp_insertion_gap, - tfcoil_variables.dx_tf_wp_insulation, - tfcoil_variables.n_tf_coil_turns, - int(tfcoil_variables.i_tf_turns_integer), - superconducting_tf_coil_variables.dx_tf_turn_cable_space_average, - superconducting_tf_coil_variables.dr_tf_turn_cable_space, - tfcoil_variables.dia_tf_turn_coolant_channel, - tfcoil_variables.f_a_tf_turn_cable_copper, - tfcoil_variables.dx_tf_turn_steel, - superconducting_tf_coil_variables.dx_tf_side_case_average, - superconducting_tf_coil_variables.dx_tf_wp_toroidal_average, - superconducting_tf_coil_variables.a_tf_coil_inboard_insulation, - tfcoil_variables.a_tf_wp_steel, - tfcoil_variables.a_tf_wp_conductor, - superconducting_tf_coil_variables.a_tf_wp_with_insulation, - tfcoil_variables.eyoung_al, - tfcoil_variables.poisson_al, - tfcoil_variables.fcoolcp, - tfcoil_variables.n_tf_graded_layers, - tfcoil_variables.c_tf_total, - tfcoil_variables.dr_tf_plasma_case, - tfcoil_variables.i_tf_stress_model, - superconducting_tf_coil_variables.vforce_inboard_tot, - tfcoil_variables.i_tf_tresca, - tfcoil_variables.a_tf_coil_inboard_case, - tfcoil_variables.vforce, - tfcoil_variables.a_tf_turn_steel, - ) - - tfcoil_variables.sig_tf_case = ( - tfcoil_variables.sig_tf_case - if tfcoil_variables.sig_tf_case is None - else sig_tf_case - ) - - tfcoil_variables.sig_tf_cs_bucked = ( - tfcoil_variables.sig_tf_cs_bucked - if tfcoil_variables.sig_tf_cs_bucked is None - else sig_tf_cs_bucked - ) - - tfcoil_variables.str_wp = ( - tfcoil_variables.str_wp if tfcoil_variables.str_wp is None else str_wp - ) - - tfcoil_variables.casestr = ( - tfcoil_variables.casestr - if tfcoil_variables.casestr is None - else casestr - ) - - tfcoil_variables.insstrain = ( - tfcoil_variables.insstrain - if tfcoil_variables.insstrain is None - else insstrain - ) - - if output: - self.out_stress( + if superconducting_tf_coil_variables.i_tf_turn_type == 0: + try: + ( sig_tf_r_max, sig_tf_t_max, sig_tf_z_max, @@ -275,15 +145,151 @@ def run(self, output: bool): str_tf_z, n_radial_array, n_tf_bucking, + tfcoil_variables.sig_tf_wp, + sig_tf_case, + sig_tf_cs_bucked, + str_wp, + casestr, + insstrain, sig_tf_wp_av_z, + ) = self.stresscl( + int(tfcoil_variables.n_tf_stress_layers), + int(tfcoil_variables.n_rad_per_layer), + int(tfcoil_variables.n_tf_wp_stress_layers), + int(tfcoil_variables.i_tf_bucking), + float(build_variables.r_tf_inboard_in), + build_variables.dr_bore, + build_variables.z_tf_inside_half, + pfcoil_variables.f_z_cs_tf_internal, + build_variables.dr_cs, + build_variables.i_tf_inside_cs, + build_variables.dr_tf_inboard, + build_variables.dr_cs_tf_gap, + pfcoil_variables.i_pf_conductor, + pfcoil_variables.j_cs_flat_top_end, + pfcoil_variables.j_cs_pulse_start, + pfcoil_variables.c_pf_coil_turn_peak_input, + pfcoil_variables.n_pf_coils_in_group, + pfcoil_variables.f_dr_dz_cs_turn, + pfcoil_variables.radius_cs_turn_corners, + pfcoil_variables.f_a_cs_turn_steel, + tfcoil_variables.eyoung_steel, + tfcoil_variables.poisson_steel, + tfcoil_variables.eyoung_cond_axial, + tfcoil_variables.poisson_cond_axial, + tfcoil_variables.eyoung_cond_trans, + tfcoil_variables.poisson_cond_trans, + tfcoil_variables.eyoung_ins, + tfcoil_variables.poisson_ins, + tfcoil_variables.dx_tf_turn_insulation, + tfcoil_variables.eyoung_copper, + tfcoil_variables.poisson_copper, + tfcoil_variables.i_tf_sup, + tfcoil_variables.eyoung_res_tf_buck, + superconducting_tf_coil_variables.r_tf_wp_inboard_inner, + superconducting_tf_coil_variables.tan_theta_coil, + superconducting_tf_coil_variables.rad_tf_coil_inboard_toroidal_half, + superconducting_tf_coil_variables.r_tf_wp_inboard_outer, + superconducting_tf_coil_variables.a_tf_coil_inboard_steel, + superconducting_tf_coil_variables.a_tf_plasma_case, + superconducting_tf_coil_variables.a_tf_coil_nose_case, + tfcoil_variables.dx_tf_wp_insertion_gap, + tfcoil_variables.dx_tf_wp_insulation, + tfcoil_variables.n_tf_coil_turns, + int(tfcoil_variables.i_tf_turns_integer), + superconducting_tf_coil_variables.dx_tf_turn_cable_space_average, + superconducting_tf_coil_variables.dr_tf_turn_cable_space, + tfcoil_variables.dia_tf_turn_coolant_channel, + tfcoil_variables.f_a_tf_turn_cable_copper, + tfcoil_variables.dx_tf_turn_steel, + superconducting_tf_coil_variables.dx_tf_side_case_average, + superconducting_tf_coil_variables.dx_tf_wp_toroidal_average, + superconducting_tf_coil_variables.a_tf_coil_inboard_insulation, + tfcoil_variables.a_tf_wp_steel, + tfcoil_variables.a_tf_wp_conductor, + superconducting_tf_coil_variables.a_tf_wp_with_insulation, + tfcoil_variables.eyoung_al, + tfcoil_variables.poisson_al, + tfcoil_variables.fcoolcp, + tfcoil_variables.n_tf_graded_layers, + tfcoil_variables.c_tf_total, + tfcoil_variables.dr_tf_plasma_case, + tfcoil_variables.i_tf_stress_model, + superconducting_tf_coil_variables.vforce_inboard_tot, + tfcoil_variables.i_tf_tresca, + tfcoil_variables.a_tf_coil_inboard_case, + tfcoil_variables.vforce, + tfcoil_variables.a_tf_turn_steel, + ) + + tfcoil_variables.sig_tf_case = ( + tfcoil_variables.sig_tf_case + if tfcoil_variables.sig_tf_case is None + else sig_tf_case + ) + + tfcoil_variables.sig_tf_cs_bucked = ( + tfcoil_variables.sig_tf_cs_bucked + if tfcoil_variables.sig_tf_cs_bucked is None + else sig_tf_cs_bucked + ) + + tfcoil_variables.str_wp = ( + tfcoil_variables.str_wp + if tfcoil_variables.str_wp is None + else str_wp ) - 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" + + tfcoil_variables.casestr = ( + tfcoil_variables.casestr + if tfcoil_variables.casestr is None + else casestr ) - tfcoil_variables.sig_tf_case = 0.0e0 - tfcoil_variables.sig_tf_wp = 0.0e0 + + tfcoil_variables.insstrain = ( + tfcoil_variables.insstrain + if tfcoil_variables.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" + ) + tfcoil_variables.sig_tf_case = 0.0e0 + tfcoil_variables.sig_tf_wp = 0.0e0 self.vv_stress_on_quench() @@ -322,7 +328,10 @@ def run(self, output: bool): self.croco_voltage() / 1.0e3 ) # TFC Quench voltage in kV - else: + elif ( + tfcoil_variables.i_tf_sc_mat in CABLE_SUPERCONDUCTORS + and superconducting_tf_coil_variables.i_tf_turn_type == 0 + ): ( tfcoil_variables.j_tf_wp_critical, superconducting_tf_coil_variables.j_tf_superconductor_critical, @@ -347,6 +356,30 @@ def run(self, output: bool): bcritsc=tfcoil_variables.bcritsc, tcritsc=tfcoil_variables.tcritsc, ) + elif ( + tfcoil_variables.i_tf_sc_mat in TAPE_SUPERCONDUCTORS + and superconducting_tf_coil_variables.i_tf_turn_type == 2 + ): + ( + tfcoil_variables.j_tf_wp_critical, + superconducting_tf_coil_variables.j_tf_superconductor_critical, + superconducting_tf_coil_variables.f_c_tf_turn_operating_critical, + superconducting_tf_coil_variables.j_tf_superconductor, + superconducting_tf_coil_variables.j_tf_coil_turn, + superconducting_tf_coil_variables.b_tf_superconductor_critical_zero_temp_strain, + superconducting_tf_coil_variables.temp_tf_superconductor_critical_zero_field_strain, + superconducting_tf_coil_variables.c_tf_turn_cables_critical, + ) = self.tf_step_vertical_stack_superconductor_properties( + a_tf_turn=a_tf_turn, + b_tf_inboard_peak=tfcoil_variables.b_tf_inboard_peak_with_ripple, + temp_tf_coolant_peak_field=tfcoil_variables.tftmp, + i_tf_superconductor=tfcoil_variables.i_tf_sc_mat, + dr_hts_tape=rebco_variables.dr_hts_tape, + dx_hts_tape_rebco=rebco_variables.dx_hts_tape_rebco, + dx_hts_tape_total=rebco_variables.dx_hts_tape_total, + a_tf_turn_tape_stack=superconducting_tf_coil_variables.a_tf_turn_tape_stack, + c_tf_turn=tfcoil_variables.c_tf_turn, + ) if tfcoil_variables.i_str_wp == 0: strain = tfcoil_variables.str_tf_con_res @@ -372,28 +405,29 @@ def run(self, output: bool): ) # Find the current density limited by the protection limit # At present only valid for LTS windings (Nb3Sn properties assumed) - tfcoil_variables.j_tf_wp_quench_heat_max, v_tf_coil_dump_quench = ( - self.quench_heat_protection_current_density( - c_tf_turn=tfcoil_variables.c_tf_turn, - e_tf_coil_magnetic_stored=tfcoil_variables.e_tf_coil_magnetic_stored, - a_tf_turn_cable_space=tfcoil_variables.a_tf_turn_cable_space_no_void, - a_tf_turn=a_tf_turn, - t_tf_quench_dump=tfcoil_variables.t_tf_superconductor_quench, - f_a_tf_turn_cable_space_conductor=1.0e0 - - superconducting_tf_coil_variables.f_a_tf_turn_cable_space_cooling, - f_a_tf_turn_cable_copper=tfcoil_variables.f_a_tf_turn_cable_copper, - temp_tf_coolant_peak_field=tfcoil_variables.tftmp, - temp_tf_conductor_quench_max=tfcoil_variables.temp_tf_conductor_quench_max, - b_tf_inboard_peak=tfcoil_variables.b_tf_inboard_peak_with_ripple, - cu_rrr=tfcoil_variables.rrr_tf_cu, - t_tf_quench_detection=tfcoil_variables.t_tf_quench_detection, - nflutfmax=constraint_variables.nflutfmax, + if tfcoil_variables.i_tf_sc_mat in {1, 4, 5}: + tfcoil_variables.j_tf_wp_quench_heat_max, v_tf_coil_dump_quench = ( + self.quench_heat_protection_current_density( + c_tf_turn=tfcoil_variables.c_tf_turn, + e_tf_coil_magnetic_stored=tfcoil_variables.e_tf_coil_magnetic_stored, + a_tf_turn_cable_space=tfcoil_variables.a_tf_turn_cable_space_no_void, + a_tf_turn=a_tf_turn, + t_tf_quench_dump=tfcoil_variables.t_tf_superconductor_quench, + f_a_tf_turn_cable_space_conductor=1.0e0 + - superconducting_tf_coil_variables.f_a_tf_turn_cable_space_cooling, + f_a_tf_turn_cable_copper=tfcoil_variables.f_a_tf_turn_cable_copper, + temp_tf_coolant_peak_field=tfcoil_variables.tftmp, + temp_tf_conductor_quench_max=tfcoil_variables.temp_tf_conductor_quench_max, + b_tf_inboard_peak=tfcoil_variables.b_tf_inboard_peak_with_ripple, + cu_rrr=tfcoil_variables.rrr_tf_cu, + t_tf_quench_detection=tfcoil_variables.t_tf_quench_detection, + nflutfmax=constraint_variables.nflutfmax, + ) ) - ) - tfcoil_variables.v_tf_coil_dump_quench_kv = ( - v_tf_coil_dump_quench / 1.0e3 - ) # TFC Quench voltage in kV + tfcoil_variables.v_tf_coil_dump_quench_kv = ( + v_tf_coil_dump_quench / 1.0e3 + ) # TFC Quench voltage in kV if output: self.outtf() @@ -1315,6 +1349,173 @@ def tf_cable_in_conduit_superconductor_properties( c_turn_cables_critical, ) + def tf_step_vertical_stack_superconductor_properties( + self, + a_tf_turn, + b_tf_inboard_peak, + temp_tf_coolant_peak_field, + i_tf_superconductor, + dr_hts_tape, + dx_hts_tape_rebco, + dx_hts_tape_total, + a_tf_turn_tape_stack, + c_tf_turn, + ) -> tuple[float, float, float, float, float, float, float, float]: + """ + Calculates the properties of the TF superconducting conductor. + + :param float a_tf_turn: + Area per turn (i.e. entire jacketed conductor) (m²). + :param float b_tf_inboard_peak: + Peak field at conductor (T). + :param float temp_tf_coolant_peak_field: + He temperature at peak field point (K). + :param int i_tf_superconductor: + Switch for conductor type: + - 8: Durham Ginzburg-Landau critical surface model for REBCO + - 9: Hazelton experimental data + Zhai conceptual model for REBCO + :param float dr_hts_tape: + Thickness of HTS tape (m). + :param float dx_hts_tape_rebco: + Width of REBCO layer in HTS tape (m). + :param float dx_hts_tape_total: + Total width of HTS tape (m). + :param float a_tf_turn_tape_stack: + Area of tape stack per turn (m²). + :param float c_tf_turn: + Operating current per turn (A). + + :returns: tuple (float, float, float, float, float, float, float, float) + - j_tf_wp_critical (float): Critical winding pack current density (A/m²). + - j_superconductor_critical (float): Critical current density in superconductor (A/m²). + - f_c_tf_turn_operating_critical (float): Ratio of operating / critical current. + - j_superconductor_turn (float): Actual current density in superconductor (A/m²). + - j_tf_coil_turn (float): Actual current density in superconductor (A/m²). + - b_tf_superconductor_critical_zero_temp_strain (float): Critical field at zero temperature and strain (T). + - temp_tf_superconductor_critical_zero_field_strain (float): Critical temperature at zero field and strain (K). + - c_turn_strands_critical (float): Critical current in cable (A). + + :notes: + This routine calculates the superconductor properties for the TF coils with step vertical stack conductor. + """ + if i_tf_superconductor not in TAPE_SUPERCONDUCTORS: + raise ProcessValueError( + "Illegal value for i_tf_sc_mat", i_tf_superconductor=i_tf_superconductor + ) + + if tfcoil_variables.i_str_wp == 0: + strain = tfcoil_variables.str_tf_con_res + else: + strain = tfcoil_variables.str_wp + + # ================================================================= + + # Durham Ginzburg-Landau critical surface model for REBCO + if i_tf_superconductor == 8: + 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. Used lower strain." + ) + strain = np.sign(strain) * 0.7e-2 + + j_superconductor_critical, _, _ = superconductors.gl_rebco( + temp_conductor=temp_tf_coolant_peak_field, + b_conductor=b_tf_inboard_peak, + strain=strain, + b_c20max=bc20m, + t_c0=tc0m, + ) + + # Scale for the copper area fraction of the strand + j_strand_critical = j_superconductor_critical * ( + (dr_hts_tape * dx_hts_tape_total) / (dr_hts_tape * dx_hts_tape_rebco) + ) + + # Critical current in turn all turn cables + c_turn_strands_critical = j_strand_critical * a_tf_turn_tape_stack + + # 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 + tfcoil_variables.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)" + ) + + # ================================================================= + + # Hazelton experimental data + Zhai conceptual model for REBCO + if i_tf_superconductor == 9: + 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. 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_coolant_peak_field, + b_conductor=b_tf_inboard_peak, + b_c20max=bc20m, + t_c0=tc0m, + dr_hts_tape=dr_hts_tape, + dx_hts_tape_rebco=dx_hts_tape_rebco, + dx_hts_tape_total=dx_hts_tape_total, + ) + # Scale for the copper area fraction of the cable + j_strand_critical = j_superconductor_critical * ( + (dr_hts_tape * dx_hts_tape_total) / (dr_hts_tape * dx_hts_tape_rebco) + ) + + # Critical current in turn all turn cables + c_turn_strands_critical = j_strand_critical * a_tf_turn_tape_stack + + elif i_tf_superconductor not in TAPE_SUPERCONDUCTORS: + raise ProcessValueError( + "Illegal value for i_tf_sc_mat", i_tf_superconductor=i_tf_superconductor + ) + + # ================================================================= + + # Critical current density in winding pack + # a_tf_turn : Area per turn (i.e. entire jacketed conductor with insulation) (m2) + j_tf_wp_critical = c_turn_strands_critical / a_tf_turn + + # Ratio of operating / critical current + f_c_tf_turn_operating_critical = c_tf_turn / c_turn_strands_critical + + # Operating current density + j_tf_coil_turn = c_tf_turn / a_tf_turn + + # Actual current density in superconductor, not including copper + + j_superconductor = f_c_tf_turn_operating_critical * j_superconductor_critical + + # ================================================================= + + return ( + j_tf_wp_critical, + j_superconductor_critical, + f_c_tf_turn_operating_critical, + j_superconductor, + j_tf_coil_turn, + bc20m, + tc0m, + c_turn_strands_critical, + ) + def calculate_cable_in_conduit_strand_count( self, a_cable_space: float, @@ -1342,7 +1543,7 @@ def calculate_cable_in_conduit_strand_count( # Number of strands that fit return int(effective_area / strand_area) - def calculate_cable_in_conduit_superconductor_length( + def calculate_tf_superconductor_length( self, n_tf_coils: int, n_tf_coil_turns: int, @@ -1913,7 +2114,9 @@ def peak_b_tf_inboard_with_ripple( * b_tf_inboard_peak_symmetric ) - def sc_tf_internal_geom(self, i_tf_wp_geom, i_tf_case_geom, i_tf_turns_integer): + def sc_tf_internal_geom( + self, i_tf_wp_geom, i_tf_case_geom, i_tf_turns_integer, i_tf_turn_type: int + ): """ Author : S. Kahn, CCFE Seting the WP, case and turns geometry for SC magnets @@ -1972,167 +2175,326 @@ def sc_tf_internal_geom(self, i_tf_wp_geom, i_tf_case_geom, i_tf_turns_integer): # WP/trun currents self.tf_wp_currents() - # Setting the WP turn geometry / areas - if i_tf_turns_integer == 0: - # Non-ingeger number of turns - ( - tfcoil_variables.a_tf_turn_cable_space_no_void, - tfcoil_variables.a_tf_turn_steel, - tfcoil_variables.a_tf_turn_insulation, - tfcoil_variables.n_tf_coil_turns, - tfcoil_variables.dx_tf_turn_general, - tfcoil_variables.c_tf_turn, - tfcoil_variables.dx_tf_turn_general, - superconducting_tf_coil_variables.dr_tf_turn, - superconducting_tf_coil_variables.dx_tf_turn, - tfcoil_variables.t_conductor, - superconducting_tf_coil_variables.radius_tf_turn_cable_space_corners, - superconducting_tf_coil_variables.dx_tf_turn_cable_space_average, - superconducting_tf_coil_variables.a_tf_turn_cable_space_effective, - superconducting_tf_coil_variables.f_a_tf_turn_cable_space_cooling, - ) = self.tf_cable_in_conduit_averaged_turn_geometry( - j_tf_wp=tfcoil_variables.j_tf_wp, - dx_tf_turn_steel=tfcoil_variables.dx_tf_turn_steel, - dx_tf_turn_insulation=tfcoil_variables.dx_tf_turn_insulation, - i_tf_sc_mat=tfcoil_variables.i_tf_sc_mat, - dx_tf_turn_general=tfcoil_variables.dx_tf_turn_general, - c_tf_turn=tfcoil_variables.c_tf_turn, - i_dx_tf_turn_general_input=tfcoil_variables.i_dx_tf_turn_general_input, - i_dx_tf_turn_cable_space_general_input=tfcoil_variables.i_dx_tf_turn_cable_space_general_input, - dx_tf_turn_cable_space_general=tfcoil_variables.dx_tf_turn_cable_space_general, - layer_ins=tfcoil_variables.layer_ins, - a_tf_wp_no_insulation=superconducting_tf_coil_variables.a_tf_wp_no_insulation, - dia_tf_turn_coolant_channel=tfcoil_variables.dia_tf_turn_coolant_channel, - f_a_tf_turn_cable_space_extra_void=tfcoil_variables.f_a_tf_turn_cable_space_extra_void, + # Cable in conduit conductor geometry + if i_tf_turn_type == 0: + if i_tf_turns_integer == 0: + # Non-integer number of turns + ( + tfcoil_variables.a_tf_turn_cable_space_no_void, + tfcoil_variables.a_tf_turn_steel, + tfcoil_variables.a_tf_turn_insulation, + tfcoil_variables.n_tf_coil_turns, + tfcoil_variables.dx_tf_turn_general, + tfcoil_variables.c_tf_turn, + tfcoil_variables.dx_tf_turn_general, + superconducting_tf_coil_variables.dr_tf_turn, + superconducting_tf_coil_variables.dx_tf_turn, + tfcoil_variables.t_conductor, + superconducting_tf_coil_variables.radius_tf_turn_cable_space_corners, + superconducting_tf_coil_variables.dx_tf_turn_cable_space_average, + superconducting_tf_coil_variables.a_tf_turn_cable_space_effective, + superconducting_tf_coil_variables.f_a_tf_turn_cable_space_cooling, + ) = self.tf_cable_in_conduit_averaged_turn_geometry( + j_tf_wp=tfcoil_variables.j_tf_wp, + dx_tf_turn_steel=tfcoil_variables.dx_tf_turn_steel, + dx_tf_turn_insulation=tfcoil_variables.dx_tf_turn_insulation, + i_tf_sc_mat=tfcoil_variables.i_tf_sc_mat, + dx_tf_turn_general=tfcoil_variables.dx_tf_turn_general, + c_tf_turn=tfcoil_variables.c_tf_turn, + i_dx_tf_turn_general_input=tfcoil_variables.i_dx_tf_turn_general_input, + i_dx_tf_turn_cable_space_general_input=tfcoil_variables.i_dx_tf_turn_cable_space_general_input, + dx_tf_turn_cable_space_general=tfcoil_variables.dx_tf_turn_cable_space_general, + layer_ins=tfcoil_variables.layer_ins, + a_tf_wp_no_insulation=superconducting_tf_coil_variables.a_tf_wp_no_insulation, + dia_tf_turn_coolant_channel=tfcoil_variables.dia_tf_turn_coolant_channel, + f_a_tf_turn_cable_space_extra_void=tfcoil_variables.f_a_tf_turn_cable_space_extra_void, + ) + + else: + # Integer number of turns + ( + superconducting_tf_coil_variables.radius_tf_turn_cable_space_corners, + superconducting_tf_coil_variables.dr_tf_turn, + superconducting_tf_coil_variables.dx_tf_turn, + tfcoil_variables.a_tf_turn_cable_space_no_void, + tfcoil_variables.a_tf_turn_steel, + tfcoil_variables.a_tf_turn_insulation, + tfcoil_variables.c_tf_turn, + tfcoil_variables.n_tf_coil_turns, + superconducting_tf_coil_variables.t_conductor_radial, + superconducting_tf_coil_variables.t_conductor_toroidal, + tfcoil_variables.t_conductor, + superconducting_tf_coil_variables.dr_tf_turn_cable_space, + superconducting_tf_coil_variables.dx_tf_turn_cable_space, + superconducting_tf_coil_variables.dx_tf_turn_cable_space_average, + ) = self.tf_cable_in_conduit_integer_turn_geometry( + dr_tf_wp_with_insulation=tfcoil_variables.dr_tf_wp_with_insulation, + dx_tf_wp_insulation=tfcoil_variables.dx_tf_wp_insulation, + dx_tf_wp_insertion_gap=tfcoil_variables.dx_tf_wp_insertion_gap, + n_tf_wp_layers=tfcoil_variables.n_tf_wp_layers, + dx_tf_wp_toroidal_min=superconducting_tf_coil_variables.dx_tf_wp_toroidal_min, + n_tf_wp_pancakes=tfcoil_variables.n_tf_wp_pancakes, + c_tf_coil=superconducting_tf_coil_variables.c_tf_coil, + dx_tf_turn_steel=tfcoil_variables.dx_tf_turn_steel, + dx_tf_turn_insulation=tfcoil_variables.dx_tf_turn_insulation, + ) + # Calculate number of cables in turn if CICC conductor + # --------------------------------------------------- + if tfcoil_variables.i_tf_sc_mat != 6: + superconducting_tf_coil_variables.n_tf_turn_superconducting_cables = self.calculate_cable_in_conduit_strand_count( + a_cable_space=superconducting_tf_coil_variables.a_tf_turn_cable_space_effective, + dia_superconductor_strand=superconducting_tf_coil_variables.dia_tf_turn_superconducting_cable, + ) + + ( + superconducting_tf_coil_variables.len_tf_coil_superconductor, + superconducting_tf_coil_variables.len_tf_superconductor_total, + ) = self.calculate_tf_superconductor_length( + n_tf_coils=tfcoil_variables.n_tf_coils, + n_tf_coil_turns=tfcoil_variables.n_tf_coil_turns, + len_tf_coil=tfcoil_variables.len_tf_coil, + n_tf_turn_superconducting_cables=superconducting_tf_coil_variables.n_tf_turn_superconducting_cables, + ) + + # Areas and fractions + # ------------------- + # Central helium channel down the conductor core [m2] + tfcoil_variables.a_tf_wp_coolant_channels = ( + 0.25e0 + * tfcoil_variables.n_tf_coil_turns + * np.pi + * tfcoil_variables.dia_tf_turn_coolant_channel**2 + ) + + # Total conductor cross-sectional area, taking account of void area + # and central helium channel [m2] + tfcoil_variables.a_tf_wp_conductor = ( + tfcoil_variables.a_tf_turn_cable_space_no_void + * tfcoil_variables.n_tf_coil_turns + * (1.0e0 - tfcoil_variables.f_a_tf_turn_cable_space_extra_void) + - tfcoil_variables.a_tf_wp_coolant_channels + ) + + # Void area in conductor for He, not including central channel [m2] + tfcoil_variables.a_tf_wp_extra_void = ( + tfcoil_variables.a_tf_turn_cable_space_no_void + * tfcoil_variables.n_tf_coil_turns + * tfcoil_variables.f_a_tf_turn_cable_space_extra_void ) - else: - # Integer number of turns - ( - superconducting_tf_coil_variables.radius_tf_turn_cable_space_corners, - superconducting_tf_coil_variables.dr_tf_turn, - superconducting_tf_coil_variables.dx_tf_turn, - tfcoil_variables.a_tf_turn_cable_space_no_void, - tfcoil_variables.a_tf_turn_steel, - tfcoil_variables.a_tf_turn_insulation, - tfcoil_variables.c_tf_turn, - tfcoil_variables.n_tf_coil_turns, - superconducting_tf_coil_variables.t_conductor_radial, - superconducting_tf_coil_variables.t_conductor_toroidal, - tfcoil_variables.t_conductor, - superconducting_tf_coil_variables.dr_tf_turn_cable_space, - superconducting_tf_coil_variables.dx_tf_turn_cable_space, - superconducting_tf_coil_variables.dx_tf_turn_cable_space_average, - ) = self.tf_cable_in_conduit_integer_turn_geometry( - dr_tf_wp_with_insulation=tfcoil_variables.dr_tf_wp_with_insulation, - dx_tf_wp_insulation=tfcoil_variables.dx_tf_wp_insulation, - dx_tf_wp_insertion_gap=tfcoil_variables.dx_tf_wp_insertion_gap, - n_tf_wp_layers=tfcoil_variables.n_tf_wp_layers, - dx_tf_wp_toroidal_min=superconducting_tf_coil_variables.dx_tf_wp_toroidal_min, - n_tf_wp_pancakes=tfcoil_variables.n_tf_wp_pancakes, - c_tf_coil=superconducting_tf_coil_variables.c_tf_coil, - dx_tf_turn_steel=tfcoil_variables.dx_tf_turn_steel, - dx_tf_turn_insulation=tfcoil_variables.dx_tf_turn_insulation, - ) - - # Calculate number of cables in turn if CICC conductor - # --------------------------------------------------- - if tfcoil_variables.i_tf_sc_mat != 6: - superconducting_tf_coil_variables.n_tf_turn_superconducting_cables = self.calculate_cable_in_conduit_strand_count( - a_cable_space=superconducting_tf_coil_variables.a_tf_turn_cable_space_effective, - dia_superconductor_strand=superconducting_tf_coil_variables.dia_tf_turn_superconducting_cable, + # Area of inter-turn insulation: total [m2] + tfcoil_variables.a_tf_coil_wp_turn_insulation = ( + tfcoil_variables.n_tf_coil_turns * tfcoil_variables.a_tf_turn_insulation ) - ( - superconducting_tf_coil_variables.len_tf_coil_superconductor, - superconducting_tf_coil_variables.len_tf_superconductor_total, - ) = self.calculate_cable_in_conduit_superconductor_length( - n_tf_coils=tfcoil_variables.n_tf_coils, - n_tf_coil_turns=tfcoil_variables.n_tf_coil_turns, - len_tf_coil=tfcoil_variables.len_tf_coil, - n_tf_turn_superconducting_cables=superconducting_tf_coil_variables.n_tf_turn_superconducting_cables, - ) - - # Areas and fractions - # ------------------- - # Central helium channel down the conductor core [m2] - tfcoil_variables.a_tf_wp_coolant_channels = ( - 0.25e0 - * tfcoil_variables.n_tf_coil_turns - * np.pi - * tfcoil_variables.dia_tf_turn_coolant_channel**2 - ) + # Area of steel structure in winding pack [m2] + tfcoil_variables.a_tf_wp_steel = ( + tfcoil_variables.n_tf_coil_turns * tfcoil_variables.a_tf_turn_steel + ) - # Total conductor cross-sectional area, taking account of void area - # and central helium channel [m2] - tfcoil_variables.a_tf_wp_conductor = ( - tfcoil_variables.a_tf_turn_cable_space_no_void - * tfcoil_variables.n_tf_coil_turns - * (1.0e0 - tfcoil_variables.f_a_tf_turn_cable_space_extra_void) - - tfcoil_variables.a_tf_wp_coolant_channels - ) + # Inboard coil steel area [m2] + superconducting_tf_coil_variables.a_tf_coil_inboard_steel = ( + tfcoil_variables.a_tf_coil_inboard_case + tfcoil_variables.a_tf_wp_steel + ) - # Void area in conductor for He, not including central channel [m2] - tfcoil_variables.a_tf_wp_extra_void = ( - tfcoil_variables.a_tf_turn_cable_space_no_void - * tfcoil_variables.n_tf_coil_turns - * tfcoil_variables.f_a_tf_turn_cable_space_extra_void - ) + # Inboard coil steel fraction [-] + superconducting_tf_coil_variables.f_a_tf_coil_inboard_steel = ( + tfcoil_variables.n_tf_coils + * superconducting_tf_coil_variables.a_tf_coil_inboard_steel + / tfcoil_variables.a_tf_inboard_total + ) - # Area of inter-turn insulation: total [m2] - tfcoil_variables.a_tf_coil_wp_turn_insulation = ( - tfcoil_variables.n_tf_coil_turns * tfcoil_variables.a_tf_turn_insulation - ) + # Inboard coil insulation cross-section [m2] + superconducting_tf_coil_variables.a_tf_coil_inboard_insulation = ( + tfcoil_variables.a_tf_coil_wp_turn_insulation + + superconducting_tf_coil_variables.a_tf_wp_ground_insulation + ) - # Area of steel structure in winding pack [m2] - tfcoil_variables.a_tf_wp_steel = ( - tfcoil_variables.n_tf_coil_turns * tfcoil_variables.a_tf_turn_steel - ) + # Inboard coil insulation fraction [-] + superconducting_tf_coil_variables.f_a_tf_coil_inboard_insulation = ( + tfcoil_variables.n_tf_coils + * superconducting_tf_coil_variables.a_tf_coil_inboard_insulation + / tfcoil_variables.a_tf_inboard_total + ) - # Inboard coil steel area [m2] - superconducting_tf_coil_variables.a_tf_coil_inboard_steel = ( - tfcoil_variables.a_tf_coil_inboard_case + tfcoil_variables.a_tf_wp_steel - ) + # Negative areas or fractions error reporting + if ( + tfcoil_variables.a_tf_wp_conductor <= 0.0e0 + or tfcoil_variables.a_tf_wp_extra_void <= 0.0e0 + or tfcoil_variables.a_tf_coil_wp_turn_insulation <= 0.0e0 + or tfcoil_variables.a_tf_wp_steel <= 0.0e0 + or superconducting_tf_coil_variables.a_tf_coil_inboard_steel <= 0.0e0 + or superconducting_tf_coil_variables.f_a_tf_coil_inboard_steel <= 0.0e0 + or superconducting_tf_coil_variables.a_tf_coil_inboard_insulation + <= 0.0e0 + or superconducting_tf_coil_variables.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"{tfcoil_variables.a_tf_wp_conductor=} {tfcoil_variables.a_tf_wp_extra_void=}" + f"{tfcoil_variables.a_tf_coil_wp_turn_insulation=} {tfcoil_variables.a_tf_wp_steel=}" + f"{superconducting_tf_coil_variables.a_tf_coil_inboard_steel=} {superconducting_tf_coil_variables.f_a_tf_coil_inboard_steel=}" + f"{superconducting_tf_coil_variables.a_tf_coil_inboard_insulation=} {superconducting_tf_coil_variables.f_a_tf_coil_inboard_insulation=}" + ) - # Inboard coil steel fraction [-] - superconducting_tf_coil_variables.f_a_tf_coil_inboard_steel = ( - tfcoil_variables.n_tf_coils - * superconducting_tf_coil_variables.a_tf_coil_inboard_steel - / tfcoil_variables.a_tf_inboard_total - ) + if i_tf_turn_type == 2: + if i_tf_turns_integer == 0: + # Non-integer number of turns + ( + tfcoil_variables.a_tf_turn_cable_space_no_void, + tfcoil_variables.a_tf_turn_steel, + tfcoil_variables.a_tf_turn_insulation, + tfcoil_variables.n_tf_coil_turns, + tfcoil_variables.dx_tf_turn_general, + tfcoil_variables.c_tf_turn, + tfcoil_variables.dx_tf_turn_general, + superconducting_tf_coil_variables.dr_tf_turn, + superconducting_tf_coil_variables.dx_tf_turn, + tfcoil_variables.t_conductor, + superconducting_tf_coil_variables.radius_tf_turn_cable_space_corners, + superconducting_tf_coil_variables.dx_tf_turn_cable_space_average, + superconducting_tf_coil_variables.a_tf_turn_cable_space_effective, + superconducting_tf_coil_variables.f_a_tf_turn_cable_space_cooling, + ) = self.tf_step_vertical_tape_averaged_turn_geometry( + j_tf_wp=tfcoil_variables.j_tf_wp, + dx_tf_turn_steel=tfcoil_variables.dx_tf_turn_steel, + dx_tf_turn_insulation=tfcoil_variables.dx_tf_turn_insulation, + i_tf_sc_mat=tfcoil_variables.i_tf_sc_mat, + dx_tf_turn_general=tfcoil_variables.dx_tf_turn_general, + c_tf_turn=tfcoil_variables.c_tf_turn, + i_dx_tf_turn_general_input=tfcoil_variables.i_dx_tf_turn_general_input, + i_dx_tf_turn_cable_space_general_input=tfcoil_variables.i_dx_tf_turn_cable_space_general_input, + dx_tf_turn_cable_space_general=tfcoil_variables.dx_tf_turn_cable_space_general, + layer_ins=tfcoil_variables.layer_ins, + a_tf_wp_no_insulation=superconducting_tf_coil_variables.a_tf_wp_no_insulation, + dia_tf_turn_coolant_channel=tfcoil_variables.dia_tf_turn_coolant_channel, + f_a_tf_turn_cable_space_extra_void=tfcoil_variables.f_a_tf_turn_cable_space_extra_void, + ) - # Inboard coil insulation cross-section [m2] - superconducting_tf_coil_variables.a_tf_coil_inboard_insulation = ( - tfcoil_variables.a_tf_coil_wp_turn_insulation - + superconducting_tf_coil_variables.a_tf_wp_ground_insulation - ) + else: + # Integer number of turns + ( + superconducting_tf_coil_variables.dr_tf_turn, + superconducting_tf_coil_variables.dx_tf_turn, + tfcoil_variables.c_tf_turn, + tfcoil_variables.n_tf_coil_turns, + superconducting_tf_coil_variables.dr_tf_turn_stabiliser, + superconducting_tf_coil_variables.dx_tf_turn_stabiliser, + superconducting_tf_coil_variables.x_tf_turn_coolant_channel_centre, + superconducting_tf_coil_variables.dr_tf_turn_tape_stack, + superconducting_tf_coil_variables.dx_tf_turn_tape_stack, + superconducting_tf_coil_variables.a_tf_turn_tape_stack, + tfcoil_variables.a_tf_turn_insulation, + superconducting_tf_coil_variables.a_tf_turn_stabiliser, + ) = self.tf_step_vertical_tape_integer_turn_geometry( + dr_tf_wp_with_insulation=tfcoil_variables.dr_tf_wp_with_insulation, + dx_tf_wp_insulation=tfcoil_variables.dx_tf_wp_insulation, + dx_tf_wp_insertion_gap=tfcoil_variables.dx_tf_wp_insertion_gap, + n_tf_wp_layers=tfcoil_variables.n_tf_wp_layers, + dx_tf_wp_toroidal_min=superconducting_tf_coil_variables.dx_tf_wp_toroidal_min, + n_tf_wp_pancakes=tfcoil_variables.n_tf_wp_pancakes, + c_tf_coil=superconducting_tf_coil_variables.c_tf_coil, + dx_tf_turn_insulation=tfcoil_variables.dx_tf_turn_insulation, + dia_tf_turn_coolant_channel=tfcoil_variables.dia_tf_turn_coolant_channel, + ) + rebco_variables.dr_hts_tape = ( + superconducting_tf_coil_variables.dr_tf_turn_tape_stack + ) - # Inboard coil insulation fraction [-] - superconducting_tf_coil_variables.f_a_tf_coil_inboard_insulation = ( - tfcoil_variables.n_tf_coils - * superconducting_tf_coil_variables.a_tf_coil_inboard_insulation - / tfcoil_variables.a_tf_inboard_total - ) + superconducting_tf_coil_variables.n_tf_turn_superconducting_strands = self.calculate_stacked_tape_strand_count( + dr_tape_stack=superconducting_tf_coil_variables.dx_tf_turn_tape_stack, + dr_hts_tape=rebco_variables.dx_hts_tape_total, + ) - # Negative areas or fractions error reporting - if ( - tfcoil_variables.a_tf_wp_conductor <= 0.0e0 - or tfcoil_variables.a_tf_wp_extra_void <= 0.0e0 - or tfcoil_variables.a_tf_coil_wp_turn_insulation <= 0.0e0 - or tfcoil_variables.a_tf_wp_steel <= 0.0e0 - or superconducting_tf_coil_variables.a_tf_coil_inboard_steel <= 0.0e0 - or superconducting_tf_coil_variables.f_a_tf_coil_inboard_steel <= 0.0e0 - or superconducting_tf_coil_variables.a_tf_coil_inboard_insulation <= 0.0e0 - or superconducting_tf_coil_variables.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"{tfcoil_variables.a_tf_wp_conductor=} {tfcoil_variables.a_tf_wp_extra_void=}" - f"{tfcoil_variables.a_tf_coil_wp_turn_insulation=} {tfcoil_variables.a_tf_wp_steel=}" - f"{superconducting_tf_coil_variables.a_tf_coil_inboard_steel=} {superconducting_tf_coil_variables.f_a_tf_coil_inboard_steel=}" - f"{superconducting_tf_coil_variables.a_tf_coil_inboard_insulation=} {superconducting_tf_coil_variables.f_a_tf_coil_inboard_insulation=}" + ( + superconducting_tf_coil_variables.len_tf_coil_superconductor, + superconducting_tf_coil_variables.len_tf_superconductor_total, + ) = self.calculate_tf_superconductor_length( + n_tf_coils=tfcoil_variables.n_tf_coils, + n_tf_coil_turns=tfcoil_variables.n_tf_coil_turns, + len_tf_coil=tfcoil_variables.len_tf_coil, + n_tf_turn_superconducting_cables=superconducting_tf_coil_variables.n_tf_turn_superconducting_strands, + ) + + # Areas and fractions + # ------------------- + # Central helium channel down the conductor core [m2] + tfcoil_variables.a_tf_wp_coolant_channels = ( + 0.25e0 + * tfcoil_variables.n_tf_coil_turns + * np.pi + * tfcoil_variables.dia_tf_turn_coolant_channel**2 + ) + + # Total conductor cross-sectional area, taking account of void area + # and central helium channel [m2] + tfcoil_variables.a_tf_wp_conductor = ( + superconducting_tf_coil_variables.a_tf_turn_tape_stack + * tfcoil_variables.n_tf_coil_turns + ) + + # Void area in conductor for He, not including central channel [m2] + tfcoil_variables.a_tf_wp_extra_void = 0.0 + + # Area of inter-turn insulation: total [m2] + tfcoil_variables.a_tf_coil_wp_turn_insulation = ( + tfcoil_variables.n_tf_coil_turns * tfcoil_variables.a_tf_turn_insulation + ) + + tfcoil_variables.a_tf_turn_steel = 0.0 + + # Area of steel structure in winding pack [m2] + tfcoil_variables.a_tf_wp_steel = ( + tfcoil_variables.n_tf_coil_turns * tfcoil_variables.a_tf_turn_steel + ) + + # Inboard coil steel area [m2] + superconducting_tf_coil_variables.a_tf_coil_inboard_steel = ( + tfcoil_variables.a_tf_coil_inboard_case + tfcoil_variables.a_tf_wp_steel ) + # Inboard coil steel fraction [-] + superconducting_tf_coil_variables.f_a_tf_coil_inboard_steel = ( + tfcoil_variables.n_tf_coils + * superconducting_tf_coil_variables.a_tf_coil_inboard_steel + / tfcoil_variables.a_tf_inboard_total + ) + + # Inboard coil insulation cross-section [m2] + superconducting_tf_coil_variables.a_tf_coil_inboard_insulation = ( + tfcoil_variables.a_tf_coil_wp_turn_insulation + + superconducting_tf_coil_variables.a_tf_wp_ground_insulation + ) + + # Inboard coil insulation fraction [-] + superconducting_tf_coil_variables.f_a_tf_coil_inboard_insulation = ( + tfcoil_variables.n_tf_coils + * superconducting_tf_coil_variables.a_tf_coil_inboard_insulation + / tfcoil_variables.a_tf_inboard_total + ) + + # Negative areas or fractions error reporting + if ( + tfcoil_variables.a_tf_wp_conductor <= 0.0e0 + or tfcoil_variables.a_tf_wp_extra_void <= 0.0e0 + or tfcoil_variables.a_tf_coil_wp_turn_insulation <= 0.0e0 + or tfcoil_variables.a_tf_wp_steel <= 0.0e0 + or superconducting_tf_coil_variables.a_tf_coil_inboard_steel <= 0.0e0 + or superconducting_tf_coil_variables.f_a_tf_coil_inboard_steel <= 0.0e0 + or superconducting_tf_coil_variables.a_tf_coil_inboard_insulation + <= 0.0e0 + or superconducting_tf_coil_variables.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"{tfcoil_variables.a_tf_wp_conductor=} {tfcoil_variables.a_tf_wp_extra_void=}" + f"{tfcoil_variables.a_tf_coil_wp_turn_insulation=} {tfcoil_variables.a_tf_wp_steel=}" + f"{superconducting_tf_coil_variables.a_tf_coil_inboard_steel=} {superconducting_tf_coil_variables.f_a_tf_coil_inboard_steel=}" + f"{superconducting_tf_coil_variables.a_tf_coil_inboard_insulation=} {superconducting_tf_coil_variables.f_a_tf_coil_inboard_insulation=}" + ) + def superconducting_tf_wp_geometry( self, i_tf_wp_geom: int, @@ -2712,6 +3074,203 @@ def tf_cable_in_conduit_integer_turn_geometry( # ------------- + def tf_step_vertical_tape_integer_turn_geometry( + self, + 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, + ) -> tuple[ + float, # dr_tf_turn + float, # dx_tf_turn + float, # c_tf_turn + float, # n_tf_coil_turns + float, # 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 + ]: + """ + Calculate the integer-turn geometry for TF coil turns using a vertical + stacked tape (REBCO-style) conductor. + + :param float dr_tf_wp_with_insulation: + Radial thickness of the winding pack including insulation (m). + :param float dx_tf_wp_insulation: + Thickness of winding-pack insulation (m). + :param float dx_tf_wp_insertion_gap: + Insertion gap thickness inside the winding pack (m). + :param int n_tf_wp_layers: + Number of radial layers (turn rows). + :param float dx_tf_wp_toroidal_min: + Minimum toroidal thickness of the winding pack (m). + :param int n_tf_wp_pancakes: + Number of toroidal pancakes per radial layer. + :param float c_tf_coil: + Total TF coil current (A). + :param float dx_tf_turn_insulation: + Thickness of the turn (intra-turn) insulation (m). + :param float dia_tf_turn_coolant_channel: + Diameter of the coolant channel inside the conductor (m). + + :returns: Tuple containing: + - dr_tf_turn (float): Radial turn dimension (m). + - dx_tf_turn (float): Toroidal turn dimension (m). + - c_tf_turn (float): Current per turn (A). + - n_tf_coil_turns (float): Total number of turns in a TF coil + (float, integer-valued). + - dr_tf_turn_stabiliser (float): Radial dimension of conductor + stabiliser region (m). + - dx_tf_turn_stabiliser (float): Toroidal dimension of conductor + stabiliser region (m). + - x_tf_turn_coolant_channel_centre (float): Centre position of the + coolant channel measured from the inner face (m). + - dr_tf_turn_tape_stack (float): Width of the tape stack in the + radial direction (m). + - dx_tf_turn_tape_stack (float): Height of the tape stack in the + toroidal direction (m). + - a_tf_turn_tape_stack (float): Cross-sectional area of the tape + stack per turn (m^2). + :rtype: tuple[float, float, float, float, float, float, float, float, float, float] + + :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: + - 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 ( + dr_tf_turn, + dx_tf_turn, + c_tf_turn, + n_tf_coil_turns, + dr_tf_turn_stabiliser, + dx_tf_turn_stabiliser, + x_tf_turn_coolant_channel_centre, + dr_tf_turn_tape_stack, + dx_tf_turn_tape_stack, + a_tf_turn_tape_stack, + a_tf_turn_insulation, + a_tf_turn_stabiliser, + ) + + # ------------- + + def calculate_stacked_tape_strand_count( + self, + 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. + + :param dr_tape_stack: Available radial width for the tape stack (m). Must be >= 0. + :param dr_hts_tape: Width of a single HTS tape (m). Must be > 0. + :return: Maximum number of tapes that fit (int). + :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 tf_wp_currents(self): """ Author : S. Kahn, CCFE @@ -2885,6 +3444,190 @@ def tf_cable_in_conduit_averaged_turn_geometry( f_a_tf_turn_cable_space_cooling, ) + def tf_step_vertical_tape_averaged_turn_geometry( + self, + j_tf_wp: float, + dx_tf_turn_insulation: float, + dx_tf_turn_general: float, + c_tf_turn: float, + i_dx_tf_turn_general_input: bool, + dia_tf_turn_coolant_channel: float, + c_tf_coil: float, + a_tf_wp_no_insulation: float, + ) -> tuple[ + float, # dr_tf_turn + float, # dx_tf_turn + float, # c_tf_turn + float, # n_tf_coil_turns + float, # 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 + ]: + """ + Calculate the integer-turn geometry for TF coil turns using a vertical + stacked tape (REBCO-style) conductor. + + :param float dr_tf_wp_with_insulation: + Radial thickness of the winding pack including insulation (m). + :param float dx_tf_wp_insulation: + Thickness of winding-pack insulation (m). + :param float dx_tf_wp_insertion_gap: + Insertion gap thickness inside the winding pack (m). + :param int n_tf_wp_layers: + Number of radial layers (turn rows). + :param float dx_tf_wp_toroidal_min: + Minimum toroidal thickness of the winding pack (m). + :param int n_tf_wp_pancakes: + Number of toroidal pancakes per radial layer. + :param float c_tf_coil: + Total TF coil current (A). + :param float dx_tf_turn_insulation: + Thickness of the turn (intra-turn) insulation (m). + :param float dia_tf_turn_coolant_channel: + Diameter of the coolant channel inside the conductor (m). + + :returns: Tuple containing: + - dr_tf_turn (float): Radial turn dimension (m). + - dx_tf_turn (float): Toroidal turn dimension (m). + - c_tf_turn (float): Current per turn (A). + - n_tf_coil_turns (float): Total number of turns in a TF coil + (float, integer-valued). + - dr_tf_turn_stabiliser (float): Radial dimension of conductor + stabiliser region (m). + - dx_tf_turn_stabiliser (float): Toroidal dimension of conductor + stabiliser region (m). + - x_tf_turn_coolant_channel_centre (float): Centre position of the + coolant channel measured from the inner face (m). + - dr_tf_turn_tape_stack (float): Width of the tape stack in the + radial direction (m). + - dx_tf_turn_tape_stack (float): Height of the tape stack in the + toroidal direction (m). + - a_tf_turn_tape_stack (float): Cross-sectional area of the tape + stack per turn (m^2). + :rtype: tuple[float, float, float, float, float, float, float, float, float, float] + + :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: + - 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. + ‌ + """ + + # Turn dimension is a an input + if i_dx_tf_turn_general_input: + # Turn area [m2] + a_tf_turn = dx_tf_turn_general**2 + + # Current per turn [A] + c_tf_turn = a_tf_turn * j_tf_wp + + # Current per turn is an input + else: + # Turn area [m2] + # Allow for additional inter-layer insulation MDK 13/11/18 + # Area of turn including conduit and inter-layer insulation + a_tf_turn = c_tf_turn / j_tf_wp + + # Dimension of square cross-section of each turn including inter-turn insulation [m] + dx_tf_turn_general = np.sqrt(a_tf_turn) + + # Square turn assumption + dr_tf_turn = dx_tf_turn_general + + 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=}" + ) + + dx_tf_turn = dx_tf_turn_general + + 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=}" + ) + + # Total number of turns per TF coil (not required to be an integer) + n_tf_coil_turns = a_tf_wp_no_insulation / a_tf_turn + + # 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 ( + dr_tf_turn, + dx_tf_turn, + c_tf_turn, + n_tf_coil_turns, + dr_tf_turn_stabiliser, + dx_tf_turn_stabiliser, + x_tf_turn_coolant_channel_centre, + dr_tf_turn_tape_stack, + dx_tf_turn_tape_stack, + a_tf_turn_tape_stack, + a_tf_turn_insulation, + a_tf_turn_stabiliser, + ) + + # ------------- + def superconducting_tf_coil_areas_and_masses(self): # Mass of case [kg] # *** diff --git a/process/tf_coil.py b/process/tf_coil.py index 9927b2e541..b676d50771 100644 --- a/process/tf_coil.py +++ b/process/tf_coil.py @@ -534,6 +534,12 @@ def outtf(self): "(i_tf_sup)", tfcoil_variables.i_tf_sup, ) + po.ovarin( + self.outfile, + "Superconducting TF coil turn type", + "(i_tf_turn_type)", + superconducting_tf_coil_variables.i_tf_turn_type, + ) if tfcoil_variables.i_tf_sup == 0: po.ocmmnt( @@ -1193,7 +1199,10 @@ def outtf(self): tfcoil_variables.dx_tf_turn_insulation, ) - if tfcoil_variables.i_tf_sc_mat in (1, 2, 3, 4, 5, 7, 8, 9): + if ( + tfcoil_variables.i_tf_sc_mat in (1, 2, 3, 4, 5, 7) + and superconducting_tf_coil_variables.i_tf_turn_type == 0 + ): po.osubhd(self.outfile, "Conductor information:") po.ovarre( self.outfile, @@ -1342,6 +1351,254 @@ def outtf(self): tfcoil_variables.temp_tf_superconductor_margin, ) + po.ovarin( + self.outfile, + "Elastic properties behavior", + "(i_tf_cond_eyoung_axial)", + tfcoil_variables.i_tf_cond_eyoung_axial, + ) + if tfcoil_variables.i_tf_cond_eyoung_axial == 0: + po.ocmmnt(self.outfile, " Conductor stiffness neglected") + elif tfcoil_variables.i_tf_cond_eyoung_axial == 1: + po.ocmmnt(self.outfile, " Conductor stiffness is user-input") + elif tfcoil_variables.i_tf_cond_eyoung_axial == 2: + po.ocmmnt( + self.outfile, + " Conductor stiffness is set by material-specific default", + ) + + po.ovarre( + self.outfile, + "Conductor axial Youngs modulus", + "(eyoung_cond_axial)", + tfcoil_variables.eyoung_cond_axial, + ) + po.ovarre( + self.outfile, + "Conductor transverse Youngs modulus", + "(eyoung_cond_trans)", + tfcoil_variables.eyoung_cond_trans, + ) + if ( + tfcoil_variables.i_tf_sc_mat in (6, 8, 9) + and superconducting_tf_coil_variables.i_tf_turn_type == 2 + ): + po.osubhd(self.outfile, "Conductor information:") + po.ovarre( + self.outfile, + "Diameter of central helium channel in cable", + "(dia_tf_turn_coolant_channel)", + tfcoil_variables.dia_tf_turn_coolant_channel, + ) + po.ovarre( + self.outfile, + "Number of superconducting strands per turn", + "(n_tf_turn_superconducting_strands)", + superconducting_tf_coil_variables.n_tf_turn_superconducting_strands, + ) + po.ovarre( + self.outfile, + "Length of superconductor in TF coil (m)", + "(len_tf_coil_superconductor)", + superconducting_tf_coil_variables.len_tf_coil_superconductor, + ) + po.ovarre( + self.outfile, + "Total length of superconductor in all TF coils (m)", + "(len_tf_superconductor_total)", + superconducting_tf_coil_variables.len_tf_superconductor_total, + ) + po.ovarre( + self.outfile, + "Radial width of tape stack in TF turn (m)", + "(dr_tf_turn_tape_stack)", + superconducting_tf_coil_variables.dr_tf_turn_tape_stack, + ) + po.ovarre( + self.outfile, + "Vertical width of tape stack in TF turn (m)", + "(dx_tf_turn_tape_stack)", + superconducting_tf_coil_variables.dx_tf_turn_tape_stack, + ) + po.ovarre( + self.outfile, + "Area of tape stack in TF turn (m)", + "(a_tf_turn_tape_stack)", + superconducting_tf_coil_variables.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)", + superconducting_tf_coil_variables.x_tf_turn_coolant_channel_centre, + ) + po.ovarre( + self.outfile, + "Radial width of stabiliser in TF turn (m)", + "(dr_tf_turn_stabiliser)", + superconducting_tf_coil_variables.dr_tf_turn_stabiliser, + ) + po.ovarre( + self.outfile, + "Toroidal width of stabiliser in TF turn (m)", + "(dx_tf_turn_stabiliser)", + superconducting_tf_coil_variables.dx_tf_turn_stabiliser, + ) + po.ovarre( + self.outfile, + "Area of stabiliser in TF turn (m)", + "(a_tf_turn_stabiliser)", + superconducting_tf_coil_variables.a_tf_turn_stabiliser, + ) + po.ovarre( + self.outfile, + "Thickness of REBCO strand in TF turn HTS tape (m)", + "(dx_hts_tape_rebco)", + rebco_variables.dx_hts_tape_rebco, + ) + po.ovarre( + self.outfile, + "Thickness of copper strand in TF turn HTS tape (m)", + "(dx_hts_tape_copper)", + rebco_variables.dx_hts_tape_copper, + ) + po.ovarre( + self.outfile, + "Thickness of hastelloy strand in TF turn HTS tape (m)", + "(dx_hts_tape_hastelloy)", + rebco_variables.dx_hts_tape_hastelloy, + ) + po.ovarre( + self.outfile, + "Total thickness of HTS tape in TF turn (m)", + "(dx_hts_tape_total)", + rebco_variables.dx_hts_tape_total, + ) + po.ovarre( + self.outfile, + "Width of HTS tape in TF turn (m)", + "(dr_hts_tape)", + rebco_variables.dr_hts_tape, + ) + po.ovarre( + self.outfile, + "Number of superconducting strands per turn", + "(n_tf_turn_superconducting_strands)", + superconducting_tf_coil_variables.n_tf_turn_superconducting_strands, + ) + + po.ocmmnt(self.outfile, "Fractions by area") + po.ovarre( + self.outfile, + "internal area of the cable space", + "(a_tf_turn_cable_space_no_void)", + tfcoil_variables.a_tf_turn_cable_space_no_void, + ) + po.ovarre( + self.outfile, + "True area of turn cable space with gaps and channels removed", + "(a_tf_turn_cable_space_effective)", + superconducting_tf_coil_variables.a_tf_turn_cable_space_effective, + ) + + po.ovarre( + self.outfile, + "Coolant fraction in conductor excluding central channel", + "(f_a_tf_turn_cable_space_extra_void)", + tfcoil_variables.f_a_tf_turn_cable_space_extra_void, + ) + po.ovarre( + self.outfile, + "Area of steel in turn", + "(a_tf_turn_steel)", + tfcoil_variables.a_tf_turn_steel, + ) + po.ovarre( + self.outfile, + "Area of all turn insulation in WP", + "(a_tf_coil_wp_turn_insulation)", + tfcoil_variables.a_tf_coil_wp_turn_insulation, + ) + po.ovarre( + self.outfile, + "Total insulation area in TF coil (turn and WP)", + "(a_tf_coil_inboard_insulation)", + superconducting_tf_coil_variables.a_tf_coil_inboard_insulation, + ) + po.ovarre( + self.outfile, + "Total steel area in inboard TF coil (turn and case)", + "(a_tf_coil_inboard_steel)", + superconducting_tf_coil_variables.a_tf_coil_inboard_steel, + ) + po.ovarre( + self.outfile, + "Total conductor area in WP", + "(a_tf_wp_conductor)", + tfcoil_variables.a_tf_wp_conductor, + ) + po.ovarre( + self.outfile, + "Total additional void area in WP", + "(a_tf_wp_extra_void)", + tfcoil_variables.a_tf_wp_extra_void, + ) + + po.ovarre( + self.outfile, + "Area of all coolant channels in WP", + "(a_tf_wp_coolant_channels)", + tfcoil_variables.a_tf_wp_coolant_channels, + ) + + po.ovarre( + self.outfile, + "Copper fraction of conductor", + "(f_a_tf_turn_cable_copper)", + tfcoil_variables.f_a_tf_turn_cable_copper, + ) + po.ovarre( + self.outfile, + "Superconductor fraction of conductor", + "(1-f_a_tf_turn_cable_copper)", + 1 - tfcoil_variables.f_a_tf_turn_cable_copper, + ) + + ap = ( + tfcoil_variables.a_tf_wp_conductor + + tfcoil_variables.n_tf_coil_turns + * tfcoil_variables.a_tf_turn_steel + + tfcoil_variables.a_tf_coil_wp_turn_insulation + + tfcoil_variables.a_tf_wp_extra_void + + tfcoil_variables.a_tf_wp_coolant_channels + ) + po.ovarrf( + self.outfile, + "Check total area fractions in winding pack = 1", + "", + ( + tfcoil_variables.a_tf_wp_conductor + + tfcoil_variables.n_tf_coil_turns + * tfcoil_variables.a_tf_turn_steel + + tfcoil_variables.a_tf_coil_wp_turn_insulation + + tfcoil_variables.a_tf_wp_extra_void + + tfcoil_variables.a_tf_wp_coolant_channels + ) + / ap, + ) + po.ovarrf( + self.outfile, + "minimum TF conductor temperature margin (K)", + "(temp_tf_superconductor_margin_min)", + tfcoil_variables.temp_tf_superconductor_margin_min, + ) + po.ovarrf( + self.outfile, + "TF conductor temperature margin (K)", + "(temp_tf_superconductor_margin)", + tfcoil_variables.temp_tf_superconductor_margin, + ) + po.ovarin( self.outfile, "Elastic properties behavior", diff --git a/tests/unit/test_sctfcoil.py b/tests/unit/test_sctfcoil.py index 993f476d14..3ea742c795 100644 --- a/tests/unit/test_sctfcoil.py +++ b/tests/unit/test_sctfcoil.py @@ -2246,3 +2246,420 @@ def test_calculate_superconductor_temperature_margin( ) # The expected_margin values are illustrative; in real tests, use values from reference calculations. assert margin == pytest.approx(expected_margin) + + +@pytest.mark.parametrize( + "params, mode", + [ + ( + { + "dr_tf_wp_with_insulation": 0.6, + "dx_tf_wp_insulation": 0.01, + "dx_tf_wp_insertion_gap": 0.005, + "n_tf_wp_layers": 3, + "dx_tf_wp_toroidal_min": 0.4, + "n_tf_wp_pancakes": 5, + "c_tf_coil": 1e6, + "dx_tf_turn_insulation": 0.002, + "dia_tf_turn_coolant_channel": 0.01, + }, + "full", + ), + # Edge case: very small turn dimensions which trigger the logger.error checks + ( + { + "dr_tf_wp_with_insulation": 0.03, + "dx_tf_wp_insulation": 0.01, + "dx_tf_wp_insertion_gap": 0.01, + "n_tf_wp_layers": 2, + "dx_tf_wp_toroidal_min": 0.04, + "n_tf_wp_pancakes": 2, + "c_tf_coil": 1e5, + "dx_tf_turn_insulation": 0.02, + "dia_tf_turn_coolant_channel": 0.02, + }, + "edge", + ), + ], +) +def test_tf_step_vertical_tape_integer_turn_geometry(params, mode): + """ + Parameterised tests for tf_step_vertical_tape_integer_turn_geometry. + + - 'full' mode: assert returned values match the analytical formulas used in the implementation. + - 'edge' mode: check that problematic geometry is detected via the numerical relationships + (function logs errors but does not raise), and return values remain finite. + """ + obj = SuperconductingTFCoil() + + # Call the method under test + outputs = obj.tf_step_vertical_tape_integer_turn_geometry( + dr_tf_wp_with_insulation=params["dr_tf_wp_with_insulation"], + dx_tf_wp_insulation=params["dx_tf_wp_insulation"], + dx_tf_wp_insertion_gap=params["dx_tf_wp_insertion_gap"], + n_tf_wp_layers=params["n_tf_wp_layers"], + dx_tf_wp_toroidal_min=params["dx_tf_wp_toroidal_min"], + n_tf_wp_pancakes=params["n_tf_wp_pancakes"], + c_tf_coil=params["c_tf_coil"], + dx_tf_turn_insulation=params["dx_tf_turn_insulation"], + dia_tf_turn_coolant_channel=params["dia_tf_turn_coolant_channel"], + ) + + # Unpack outputs according to implementation + ( + dr_tf_turn, + dx_tf_turn, + c_tf_turn, + n_tf_coil_turns, + dr_tf_turn_stabiliser, + dx_tf_turn_stabiliser, + x_tf_turn_coolant_channel_centre, + dr_tf_turn_tape_stack, + dx_tf_turn_tape_stack, + a_tf_turn_tape_stack, + a_tf_turn_insulation, + a_tf_turn_stabiliser, + ) = outputs + + # Basic invariants that should hold for both cases + assert isinstance(dr_tf_turn, float) + assert isinstance(dx_tf_turn, float) + assert isinstance(c_tf_turn, float) + assert isinstance(n_tf_coil_turns, float) + assert np.isfinite(dr_tf_turn) + assert np.isfinite(dx_tf_turn) + assert np.isfinite(c_tf_turn) + assert np.isfinite(n_tf_coil_turns) + + # Recompute expected intermediate values using same formulas as implementation + expected_dr_tf_turn = ( + params["dr_tf_wp_with_insulation"] + - 2.0 * (params["dx_tf_wp_insulation"] + params["dx_tf_wp_insertion_gap"]) + ) / params["n_tf_wp_layers"] + + expected_dx_tf_turn = ( + params["dx_tf_wp_toroidal_min"] + - 2.0 * (params["dx_tf_wp_insulation"] + params["dx_tf_wp_insertion_gap"]) + ) / params["n_tf_wp_pancakes"] + + expected_n_tf_coil_turns = np.double( + params["n_tf_wp_layers"] * params["n_tf_wp_pancakes"] + ) + expected_c_tf_turn = params["c_tf_coil"] / expected_n_tf_coil_turns + + # Assert primary computed values match + assert dr_tf_turn == pytest.approx(expected_dr_tf_turn, rel=1e-9, abs=1e-12) + assert dx_tf_turn == pytest.approx(expected_dx_tf_turn, rel=1e-9, abs=1e-12) + assert n_tf_coil_turns == pytest.approx(expected_n_tf_coil_turns, rel=1e-12) + assert c_tf_turn == pytest.approx(expected_c_tf_turn, rel=1e-9) + + if mode == "full": + # Continue checking derived quantities for the normal case + + expected_dr_tf_turn_stabiliser = ( + expected_dr_tf_turn - 2.0 * params["dx_tf_turn_insulation"] + ) + expected_dx_tf_turn_stabiliser = ( + expected_dx_tf_turn - 2.0 * params["dx_tf_turn_insulation"] + ) + + expected_x_tf_turn_coolant_channel_centre = ( + params["dx_tf_turn_insulation"] + + (0.1 * expected_dx_tf_turn_stabiliser) + + (params["dia_tf_turn_coolant_channel"] / 2) + ) + + expected_dr_tf_turn_tape_stack = expected_dr_tf_turn_stabiliser * 0.8 + expected_dx_tf_turn_tape_stack = (expected_dx_tf_turn / 2) - ( + params["dx_tf_turn_insulation"] + (0.1 * expected_dx_tf_turn_stabiliser) + ) + expected_a_tf_turn_tape_stack = ( + expected_dr_tf_turn_tape_stack * expected_dx_tf_turn_tape_stack + ) + + expected_a_tf_turn_insulation = (expected_dr_tf_turn * expected_dx_tf_turn) - ( + expected_dr_tf_turn_stabiliser * expected_dx_tf_turn_stabiliser + ) + + expected_a_tf_turn_stabiliser = ( + expected_dr_tf_turn_stabiliser * expected_dx_tf_turn_stabiliser + - expected_a_tf_turn_tape_stack + - (np.pi / 4.0) * params["dia_tf_turn_coolant_channel"] ** 2 + ) + + assert dr_tf_turn_stabiliser == pytest.approx( + expected_dr_tf_turn_stabiliser, rel=1e-9 + ) + assert dx_tf_turn_stabiliser == pytest.approx( + expected_dx_tf_turn_stabiliser, rel=1e-9 + ) + assert x_tf_turn_coolant_channel_centre == pytest.approx( + expected_x_tf_turn_coolant_channel_centre, rel=1e-9 + ) + assert dr_tf_turn_tape_stack == pytest.approx( + expected_dr_tf_turn_tape_stack, rel=1e-9 + ) + assert dx_tf_turn_tape_stack == pytest.approx( + expected_dx_tf_turn_tape_stack, rel=1e-9 + ) + assert a_tf_turn_tape_stack == pytest.approx( + expected_a_tf_turn_tape_stack, rel=1e-9 + ) + assert a_tf_turn_insulation == pytest.approx( + expected_a_tf_turn_insulation, rel=1e-9 + ) + assert a_tf_turn_stabiliser == pytest.approx( + expected_a_tf_turn_stabiliser, rel=1e-9 + ) + + else: + # Edge mode: expect that at least one of the geometry checks in the implementation would + # indicate problematic dimensions (they are logged as errors but not raised). We assert + # the numerical condition that would have triggered the log. + # Either radial stabiliser calculation leads to dr_tf_turn <= 2*insulation OR + # toroidal turn dimension too small. + cond_radial_issue = expected_dr_tf_turn <= 2.0 * params["dx_tf_turn_insulation"] + cond_toroidal_issue = ( + expected_dx_tf_turn <= 2.0 * params["dx_tf_turn_insulation"] + ) + assert cond_radial_issue or cond_toroidal_issue + + # Also sanity-check returned stabiliser dims are consistent with primary dims + assert dr_tf_turn_stabiliser == pytest.approx( + dr_tf_turn - 2.0 * params["dx_tf_turn_insulation"], rel=1e-9 + ) + assert dx_tf_turn_stabiliser == pytest.approx( + dx_tf_turn - 2.0 * params["dx_tf_turn_insulation"], rel=1e-9 + ) + + +@pytest.mark.parametrize( + "i_tf_superconductor, dr_hts_tape, dx_hts_tape_rebco, dx_hts_tape_total, a_tf_turn_tape_stack, a_tf_turn, b_tf_inboard_peak, temp_tf_coolant_peak_field, c_tf_turn, expected_bc20m, expected_tc0m", + ( + # Durham Ginzburg-Landau REBCO (i_tf_superconductor == 8) + ( + 8, + 0.010, # dr_hts_tape + 0.002, # dx_hts_tape_rebco + 0.010, # dx_hts_tape_total + 0.0005, # a_tf_turn_tape_stack + 0.0032, # a_tf_turn + 12.49, # b_tf_inboard_peak + 4.75, # temp_tf_coolant_peak_field + 1500.0, # c_tf_turn + 430, # expected bc20m for i=8 + 185, # expected tc0m for i=8 + ), + # Hazelton + Zhai REBCO (i_tf_superconductor == 9) + ( + 9, + 0.012, # dr_hts_tape + 0.0015, # dx_hts_tape_rebco + 0.009, # dx_hts_tape_total + 0.0006, # a_tf_turn_tape_stack + 0.0035, # a_tf_turn + 11.0, # b_tf_inboard_peak + 4.2, # temp_tf_coolant_peak_field + 1200.0, # c_tf_turn + 138, # expected bc20m for i=9 + 92, # expected tc0m for i=9 + ), + ), +) +def test_tf_step_vertical_stack_superconductor_properties_relations( + i_tf_superconductor, + dr_hts_tape, + dx_hts_tape_rebco, + dx_hts_tape_total, + a_tf_turn_tape_stack, + a_tf_turn, + b_tf_inboard_peak, + temp_tf_coolant_peak_field, + c_tf_turn, + expected_bc20m, + expected_tc0m, +): + """ + Parametrized tests for tf_step_vertical_stack_superconductor_properties. + Verify internal consistency of returned quantities and that BC20M/TC0M + are set to the expected values for the selected models. + """ + + sctf = SuperconductingTFCoil() + + ( + j_tf_wp_critical, + j_superconductor_critical, + f_c_tf_turn_operating_critical, + j_superconductor, + j_tf_coil_turn, + bc20m, + tc0m, + c_turn_strands_critical, + ) = sctf.tf_step_vertical_stack_superconductor_properties( + a_tf_turn=a_tf_turn, + b_tf_inboard_peak=b_tf_inboard_peak, + temp_tf_coolant_peak_field=temp_tf_coolant_peak_field, + i_tf_superconductor=i_tf_superconductor, + dr_hts_tape=dr_hts_tape, + dx_hts_tape_rebco=dx_hts_tape_rebco, + dx_hts_tape_total=dx_hts_tape_total, + a_tf_turn_tape_stack=a_tf_turn_tape_stack, + c_tf_turn=c_tf_turn, + ) + + # Basic sanity checks + assert j_tf_wp_critical > 0.0 + assert j_superconductor_critical > 0.0 + assert c_turn_strands_critical > 0.0 + assert j_tf_coil_turn > 0.0 + + # Consistency relations as implemented in the routine + assert j_tf_wp_critical == pytest.approx(c_turn_strands_critical / a_tf_turn) + assert f_c_tf_turn_operating_critical == pytest.approx( + c_tf_turn / c_turn_strands_critical + ) + assert j_tf_coil_turn == pytest.approx(c_tf_turn / a_tf_turn) + assert j_superconductor == pytest.approx( + f_c_tf_turn_operating_critical * j_superconductor_critical + ) + + # Check the model-specific BC20M and TC0M values are as documented + assert bc20m == expected_bc20m + assert tc0m == expected_tc0m + + +@pytest.mark.parametrize( + "params", + [ + # Case A: dx input mode (i_dx_tf_turn_general_input=True) + { + "j_tf_wp": 5e5, + "dx_tf_turn_insulation": 0.001, + "dx_tf_turn_general": 0.02, + "c_tf_turn_input": 0.0, # ignored/overwritten + "i_dx_tf_turn_general_input": True, + "dia_tf_turn_coolant_channel": 0.004, + # choose a_tf_wp_no_insulation and c_tf_coil so that final c_tf_turn equals the a*j value + "a_tf_wp_no_insulation": 0.5, + }, + # Case B: current-per-turn input mode (i_dx_tf_turn_general_input=False) + { + "j_tf_wp": 1.0e6, + "dx_tf_turn_insulation": 0.0015, + "dx_tf_turn_general": 0.0, # ignored in this mode + "c_tf_turn_input": 500.0, + "i_dx_tf_turn_general_input": False, + "dia_tf_turn_coolant_channel": 0.006, + "a_tf_wp_no_insulation": 0.5, + }, + ], +) +def test_tf_step_vertical_tape_averaged_turn_geometry_consistency(params): + """ + Sanity tests for tf_step_vertical_tape_averaged_turn_geometry. + + The routine can be called in two modes: + - i_dx_tf_turn_general_input=True: dx_tf_turn_general provided, c_tf_turn is computed from j_tf_wp. + Later c_tf_turn is overwritten with c_tf_coil / n_tf_coil_turns, so we set c_tf_coil to keep consistency. + - i_dx_tf_turn_general_input=False: c_tf_turn provided and dx is computed from c_tf_turn and j_tf_wp. + Later c_tf_turn is overwritten with c_tf_coil / n_tf_coil_turns, so we set c_tf_coil to keep consistency. + + For both modes we assert internal relations (areas, dimensions and simple derived quantities). + """ + + sc = SuperconductingTFCoil() + + j_tf_wp = params["j_tf_wp"] + dx_ins = params["dx_tf_turn_insulation"] + dx_general = params["dx_tf_turn_general"] + c_tf_turn_input = params["c_tf_turn_input"] + mode = params["i_dx_tf_turn_general_input"] + dia = params["dia_tf_turn_coolant_channel"] + a_wp = params["a_tf_wp_no_insulation"] + + # Prepare c_tf_coil so that after the function recomputes c_tf_turn = c_tf_coil / n_tf_coil_turns + if mode: + # compute a_tf_turn, n_tf_coil_turns and set c_tf_coil so final c_tf_turn equals a*j + a_tf_turn = dx_general**2 + n_expected = a_wp / a_tf_turn + c_per_turn_target = a_tf_turn * j_tf_wp + c_tf_coil = c_per_turn_target * n_expected + else: + # current-per-turn input: compute a_tf_turn from provided c_tf_turn_input then set c_tf_coil so final c_tf_turn unchanged + a_tf_turn = c_tf_turn_input / j_tf_wp + n_expected = a_wp / a_tf_turn + c_tf_coil = c_tf_turn_input * n_expected + + out = sc.tf_step_vertical_tape_averaged_turn_geometry( + j_tf_wp=j_tf_wp, + dx_tf_turn_insulation=dx_ins, + dx_tf_turn_general=dx_general, + c_tf_turn=c_tf_turn_input, + i_dx_tf_turn_general_input=mode, + dia_tf_turn_coolant_channel=dia, + c_tf_coil=c_tf_coil, + a_tf_wp_no_insulation=a_wp, + ) + + # Unpack outputs + ( + dr_tf_turn, + dx_tf_turn, + c_tf_turn_final, + n_tf_coil_turns, + dr_tf_turn_stabiliser, + dx_tf_turn_stabiliser, + x_tf_turn_coolant_channel_centre, + dr_tf_turn_tape_stack, + dx_tf_turn_tape_stack, + a_tf_turn_tape_stack, + a_tf_turn_insulation, + a_tf_turn_stabiliser, + ) = out + + # Basic geometric relations + # square-turn assumption: dr == dx + assert dr_tf_turn == pytest.approx(dx_tf_turn, rel=1e-12) + + # a_tf_turn used in internal calcs + a_tf_turn_computed = dx_tf_turn**2 + + # number of turns + assert n_tf_coil_turns == pytest.approx(a_wp / a_tf_turn_computed, rel=1e-12) + + # final c per turn should equal c_tf_coil / n_tf_coil_turns + assert c_tf_turn_final == pytest.approx(c_tf_coil / n_tf_coil_turns, rel=1e-12) + + # stabiliser dims + assert dr_tf_turn_stabiliser == pytest.approx(dr_tf_turn - 2.0 * dx_ins, rel=1e-12) + assert dx_tf_turn_stabiliser == pytest.approx(dx_tf_turn - 2.0 * dx_ins, rel=1e-12) + + # coolant channel centre calculation + expected_x = dx_ins + (0.1 * dx_tf_turn_stabiliser) + (dia / 2.0) + assert x_tf_turn_coolant_channel_centre == pytest.approx(expected_x, rel=1e-12) + + # tape stack dims and area relation + assert dr_tf_turn_tape_stack == pytest.approx( + dr_tf_turn_stabiliser * 0.8, rel=1e-12 + ) + expected_dx_tape = (dx_tf_turn / 2.0) - (dx_ins + (0.1 * dx_tf_turn_stabiliser)) + assert dx_tf_turn_tape_stack == pytest.approx(expected_dx_tape, rel=1e-12) + assert a_tf_turn_tape_stack == pytest.approx( + dr_tf_turn_tape_stack * dx_tf_turn_tape_stack, rel=1e-12 + ) + + # a_tf_turn_insulation computed as difference between full turn and stabiliser region + expected_a_turn_insulation = (dr_tf_turn * dx_tf_turn) - ( + dr_tf_turn_stabiliser * dx_tf_turn_stabiliser + ) + assert a_tf_turn_insulation == pytest.approx(expected_a_turn_insulation, rel=1e-12) + + # a_tf_turn_stabiliser consistency check (non-negative) + # a_tf_turn_stabiliser = dr_stab*dx_stab - a_tf_turn_tape_stack - (pi/4)*dia^2 + expected_a_stabiliser = ( + dr_tf_turn_stabiliser * dx_tf_turn_stabiliser + - a_tf_turn_tape_stack + - (np.pi / 4.0) * dia * dia + ) + assert a_tf_turn_stabiliser == pytest.approx(expected_a_stabiliser, rel=1e-12)