Skip to content

Create specific turn tf classes#4152

Open
chris-ashe wants to merge 9 commits intomainfrom
create_specific_turn_tf_classes
Open

Create specific turn tf classes#4152
chris-ashe wants to merge 9 commits intomainfrom
create_specific_turn_tf_classes

Conversation

@chris-ashe
Copy link
Copy Markdown
Collaborator

@chris-ashe chris-ashe commented Mar 30, 2026

This pull request introduces support for multiple toroidal field (TF) coil superconductor turn geometry types by refactoring the codebase to use specific model classes for each geometry and updating the data structure and input handling accordingly. The changes also add new input variables, update the main process flow, and extend the test suite to cover the new model classes.

The most important changes are:

Core Model and Process Flow Updates:

  • Refactored the TF coil superconductor model logic in caller.py and output.py to select and run/output the appropriate model (CICCSuperconductingTFCoil or CROCOSuperconductingTFCoil) based on the new i_tf_turn_type variable, instead of always using the generic SuperconductingTFCoil model.
  • Updated main.py to instantiate and store the new turn-type-specific model classes (CICCSuperconductingTFCoil, CROCOSuperconductingTFCoil) in the Models class.

Input and Data Structure Enhancements:

  • Added the i_tf_turn_type variable to superconducting_tf_coil_variables.py and registered it as an input variable, allowing users to select the TF coil turn geometry type. Set a default value in the initialization function.
  • Updated the base TF coil output to include the new turn type variable in the output file.

Testing Improvements:

  • Added fixtures for the new model classes in the test suite and updated relevant tests to use the new CICCSuperconductingTFCoil class instead of the generic SuperconductingTFCoil, ensuring coverage of the refactored logic.

Imports and Code Organization:

  • Updated imports in several files to reference the new model classes and the SuperconductingTFTurnType enum where appropriate.

These changes collectively improve the flexibility and maintainability of the TF coil modeling code by supporting multiple turn geometries and ensuring the correct model is used throughout the process.

Checklist

I confirm that I have completed the following checks:

  • My changes follow the PROCESS style guide
  • I have justified any large differences in the regression tests caused by this pull request in the comments.
  • I have added new tests where appropriate for the changes I have made.
  • If I have had to change any existing unit or integration tests, I have justified this change in the pull request comments.
  • If I have made documentation changes, I have checked they render correctly.
  • I have added documentation for my change, if appropriate.

@chris-ashe chris-ashe added TF Coil Toroidal field coil Refactor labels Mar 30, 2026
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Mar 30, 2026

Codecov Report

❌ Patch coverage is 47.05882% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 47.98%. Comparing base (5960a7f) to head (252bfdb).
⚠️ Report is 6 commits behind head on main.

Files with missing lines Patch % Lines
process/core/caller.py 20.00% 4 Missing ⚠️
process/core/output.py 20.00% 4 Missing ⚠️
process/models/tfcoil/base.py 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4152      +/-   ##
==========================================
- Coverage   48.02%   47.98%   -0.05%     
==========================================
  Files         143      144       +1     
  Lines       30088    30334     +246     
==========================================
+ Hits        14451    14555     +104     
- Misses      15637    15779     +142     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@chris-ashe chris-ashe force-pushed the create_specific_turn_tf_classes branch from 3473018 to b023ab3 Compare March 31, 2026 09:20
@chris-ashe chris-ashe marked this pull request as ready for review March 31, 2026 09:20
@chris-ashe chris-ashe requested a review from a team as a code owner March 31, 2026 09:20
@je-cook je-cook self-assigned this Mar 31, 2026
Copy link
Copy Markdown
Collaborator

@je-cook je-cook left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a huge fan of the large number of float returns in this. It would be preferable to return a dataclass so the variable names are propagated and hard coded so most variable name order issues are avoided eg superconducting_tf_case_geometry and others. Obviously this would need to be then fed through. Rough metric >= 5 float outputs probably should be dataclass.

output: bool

"""
self.iprint = 0
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont think iprint does anything anymore

Comment on lines +2762 to +2763
def __init__(self):
super().__init__()
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does nothing

Suggested change
def __init__(self):
super().__init__()

)
== SuperconductingTFTurnType.CABLE_IN_CONDUIT
):
self.models.cicc_sctfcoil.run()
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont know whether this should be organised differently in future eg self.models.sctfcoil.cicc.run().

this if check would then be moved into some sctfcoil object. Right know I'm not going to hold this up but before we add a future cable design we have to reoganise this. I'm not yet sure what that will look like.

Copilot AI review requested due to automatic review settings April 9, 2026 08:04
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the superconducting TF coil workflow to support multiple turn geometry types by introducing turn-type-specific model classes and routing execution/output based on a new i_tf_turn_type input.

Changes:

  • Added SuperconductingTFTurnType and new TF coil model classes (CICCSuperconductingTFCoil, CROCOSuperconductingTFCoil) and updated the main model registry.
  • Added i_tf_turn_type to the data structure, input handling, and TF output.
  • Updated unit tests to use the new model classes and adapted a geometry helper to return a dataclass.

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
process/models/tfcoil/superconducting.py Introduces turn-type enum, geometry dataclass, and new per-turn-type model classes; refactors common setup.
process/core/caller.py Selects which TF superconducting model to run based on i_tf_turn_type.
process/core/output.py Selects which TF superconducting model to output based on i_tf_turn_type.
process/core/input.py Registers new input variable i_tf_turn_type.
process/data_structure/superconducting_tf_coil_variables.py Adds and initializes i_tf_turn_type.
process/models/tfcoil/base.py Adds i_tf_turn_type to TF output block.
process/main.py Instantiates and stores the new TF superconducting model classes.
tests/unit/test_sctfcoil.py Adds fixtures for new model classes and updates tests for the refactor.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

self.superconducting_tf_coil_areas_and_masses()
def output(self):
self.outtf()
self.run_base_superconducting_tf()
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SuperconductingTFCoil.output() calls run_base_superconducting_tf() directly, which bypasses subclass run() implementations (CICCSuperconductingTFCoil.run / CROCOSuperconductingTFCoil.run). This means turn-geometry + superconductor-property calculations may never run before output/stress, leading to stale/None values in the output. Consider making output() call self.run(output=True) (polymorphic) and keeping shared setup inside run_base_superconducting_tf().

Suggested change
self.run_base_superconducting_tf()
self.run(output=True)

Copilot uses AI. Check for mistakes.
b_tf_inboard_peak: {b_tf_inboard_peak}
j_superconductor: {j_superconductor}
"""
)
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

calculate_superconductor_temperature_margin() returns temp_tf_superconductor_margin, but that variable is only assigned inside the if i_tf_superconductor in (...) block. For other values (e.g. 2 or 6) this will raise UnboundLocalError at return. Add an else path (or initialize a default) and/or raise a clear error for unsupported conductor types.

Suggested change
)
)
else:
raise ProcessValueError(
f"Unsupported TF superconductor type for temperature margin "
f"calculation: i_tf_superconductor={i_tf_superconductor}"
)

Copilot uses AI. Check for mistakes.
Comment on lines +669 to +676
if temp_tf_superconductor_margin <= 0.0e0:
logger.error(
"""Negative TFC temperature margin
temp_tf_superconductor_margin: {temp_tf_superconductor_margin}
b_tf_inboard_peak: {b_tf_inboard_peak}
j_superconductor: {j_superconductor}
"""
)
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error log string is missing an f prefix, so {temp_tf_superconductor_margin}, {b_tf_inboard_peak}, and {j_superconductor} will not be interpolated and the message will be misleading. Make this an f-string (or use structured logging args).

Copilot uses AI. Check for mistakes.
Comment on lines +3179 to +3188
tfcoil_variables.temp_tf_superconductor_margin = self.calculate_superconductor_temperature_margin(
i_tf_superconductor=tfcoil_variables.i_tf_sc_mat,
j_superconductor=superconducting_tf_coil_variables.j_tf_superconductor,
b_tf_inboard_peak=tfcoil_variables.b_tf_inboard_peak_with_ripple,
strain=strain,
bc20m=superconducting_tf_coil_variables.b_tf_superconductor_critical_zero_temp_strain,
tc0m=superconducting_tf_coil_variables.temp_tf_superconductor_critical_zero_field_strain,
c0=1.0e10,
temp_tf_coolant_peak_field=tfcoil_variables.tftmp,
)
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In CROCOSuperconductingTFCoil.run(), calculate_superconductor_temperature_margin() is called unconditionally using superconducting_tf_coil_variables.j_tf_superconductor / critical-surface values, but those are not set by the CroCo path (supercon_croco) and may still be None. This can crash (and also conflicts with the fact CroCo already computes tmarg). Consider skipping this call for CROCO_REBCO and/or setting the required variables in supercon_croco.

Suggested change
tfcoil_variables.temp_tf_superconductor_margin = self.calculate_superconductor_temperature_margin(
i_tf_superconductor=tfcoil_variables.i_tf_sc_mat,
j_superconductor=superconducting_tf_coil_variables.j_tf_superconductor,
b_tf_inboard_peak=tfcoil_variables.b_tf_inboard_peak_with_ripple,
strain=strain,
bc20m=superconducting_tf_coil_variables.b_tf_superconductor_critical_zero_temp_strain,
tc0m=superconducting_tf_coil_variables.temp_tf_superconductor_critical_zero_field_strain,
c0=1.0e10,
temp_tf_coolant_peak_field=tfcoil_variables.tftmp,
)
if (
SuperconductorModel(tfcoil_variables.i_tf_sc_mat)
!= SuperconductorModel.CROCO_REBCO
):
tfcoil_variables.temp_tf_superconductor_margin = self.calculate_superconductor_temperature_margin(
i_tf_superconductor=tfcoil_variables.i_tf_sc_mat,
j_superconductor=superconducting_tf_coil_variables.j_tf_superconductor,
b_tf_inboard_peak=tfcoil_variables.b_tf_inboard_peak_with_ripple,
strain=strain,
bc20m=superconducting_tf_coil_variables.b_tf_superconductor_critical_zero_temp_strain,
tc0m=superconducting_tf_coil_variables.temp_tf_superconductor_critical_zero_field_strain,
c0=1.0e10,
temp_tf_coolant_peak_field=tfcoil_variables.tftmp,
)

Copilot uses AI. Check for mistakes.
Comment on lines +3194 to +3218
!= SuperconductorMaterial.NB3SN
):
logger.warning(
"Calculating current density protection limit for Nb3Sn TF coil (LTS windings only)"
)
# 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,
)
)
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CROCOSuperconductingTFCoil.run() always performs the quench protection current-density calculation, but that routine is documented/implemented for CICC/LTS cases and depends on CICC geometry fractions (e.g. f_a_tf_turn_cable_space_cooling). For the CroCo/REBCO path this looks inconsistent and can yield incorrect results. Consider guarding this calculation to only run for the CICC-based conductors, matching the previous control flow.

Suggested change
!= SuperconductorMaterial.NB3SN
):
logger.warning(
"Calculating current density protection limit for Nb3Sn TF coil (LTS windings only)"
)
# 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,
)
)
== SuperconductorMaterial.NB3SN
):
# 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,
)
)
else:
logger.warning(
"Skipping current density protection limit calculation for non-Nb3Sn TF coil; this calculation is only valid for LTS windings"
)

Copilot uses AI. Check for mistakes.
Comment on lines +1464 to +1471
def run(self, output: bool = False):
"""Run the superconducting TF coil model for a CroCo conductor with REBCO tape.

po.ocmmnt(
self.outfile,
f"Superconductor used: {SuperconductorModel(tfcoil_variables.i_tf_sc_mat).full_name}",
)
Parameters
----------
output : bool
If True, print the results of the calculations.
"""
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstring for CICCSuperconductingTFCoil.run() says it runs the model "for a CroCo conductor with REBCO tape". This is misleading for the CICC implementation; please correct the description to match the class behavior.

Copilot uses AI. Check for mistakes.
Comment on lines 808 to 820
@@ -804,45 +816,47 @@ def test_superconducting_tf_wp_geometry(tfwpgeomparam, sctfcoil):
dx_tf_wp_insertion_gap=tfwpgeomparam.dx_tf_wp_insertion_gap,
)

assert dx_tf_wp_primary_toroidal == pytest.approx(
assert TFWPGeometry.dx_tf_wp_primary_toroidal == pytest.approx(
tfwpgeomparam.expected_dx_tf_wp_primary_toroidal
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The local variable name TFWPGeometry shadows the TFWPGeometry dataclass/type name and makes the assertions harder to read. Rename this to something like tfwp_geometry (or similar) so it’s clear this is an instance returned from superconducting_tf_wp_geometry().

Copilot uses AI. Check for mistakes.
data_structure.tfcoil_variables, int, range=(1, 100)
),
"i_tf_turn_type": InputVariable(
data_structure.superconducting_tf_coil_variables, int, choices=[0, 1, 2]
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i_tf_turn_type is registered with choices=[0, 1, 2], but SuperconductingTFTurnType only defines values 1 and 2. Allowing 0 will cause SuperconductingTFTurnType(i_tf_turn_type) to raise ValueError at runtime. Either remove 0 from choices or add/handle an explicit 0/UNKNOWN enum value consistently.

Suggested change
data_structure.superconducting_tf_coil_variables, int, choices=[0, 1, 2]
data_structure.superconducting_tf_coil_variables, int, choices=[1, 2]

Copilot uses AI. Check for mistakes.
Comment on lines +76 to +89
if (
SuperconductingTFTurnType(
data_structure.superconducting_tf_coil_variables.i_tf_turn_type
)
== SuperconductingTFTurnType.CABLE_IN_CONDUIT
):
models.cicc_sctfcoil.output()
elif (
SuperconductingTFTurnType(
data_structure.superconducting_tf_coil_variables.i_tf_turn_type
)
== SuperconductingTFTurnType.CROSS_CONDUCTOR
):
models.croco_sctfcoil.output()
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TF superconductor model selection has no fallback/else branch. If i_tf_turn_type is out of range (or not mapped), the model won’t run and the output will silently omit TF SC results. Consider adding an else that raises a ProcessValueError (or logs an error) with the invalid value.

Suggested change
if (
SuperconductingTFTurnType(
data_structure.superconducting_tf_coil_variables.i_tf_turn_type
)
== SuperconductingTFTurnType.CABLE_IN_CONDUIT
):
models.cicc_sctfcoil.output()
elif (
SuperconductingTFTurnType(
data_structure.superconducting_tf_coil_variables.i_tf_turn_type
)
== SuperconductingTFTurnType.CROSS_CONDUCTOR
):
models.croco_sctfcoil.output()
tf_turn_type = SuperconductingTFTurnType(
data_structure.superconducting_tf_coil_variables.i_tf_turn_type
)
if tf_turn_type == SuperconductingTFTurnType.CABLE_IN_CONDUIT:
models.cicc_sctfcoil.output()
elif tf_turn_type == SuperconductingTFTurnType.CROSS_CONDUCTOR:
models.croco_sctfcoil.output()
else:
raise ValueError(
"Unsupported superconducting TF turn type: "
f"{data_structure.superconducting_tf_coil_variables.i_tf_turn_type}"
)

Copilot uses AI. Check for mistakes.
Comment on lines +294 to +307
if (
SuperconductingTFTurnType(
data_structure.superconducting_tf_coil_variables.i_tf_turn_type
)
== SuperconductingTFTurnType.CABLE_IN_CONDUIT
):
self.models.cicc_sctfcoil.run()
elif (
SuperconductingTFTurnType(
data_structure.superconducting_tf_coil_variables.i_tf_turn_type
)
== SuperconductingTFTurnType.CROSS_CONDUCTOR
):
self.models.croco_sctfcoil.run()
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TF superconductor model selection has no fallback/else branch. If i_tf_turn_type is invalid/unhandled, no SC TF model will be run for that solver iteration, which can lead to inconsistent state. Consider adding an else that raises a ProcessValueError (or logs an error) with the invalid value.

Suggested change
if (
SuperconductingTFTurnType(
data_structure.superconducting_tf_coil_variables.i_tf_turn_type
)
== SuperconductingTFTurnType.CABLE_IN_CONDUIT
):
self.models.cicc_sctfcoil.run()
elif (
SuperconductingTFTurnType(
data_structure.superconducting_tf_coil_variables.i_tf_turn_type
)
== SuperconductingTFTurnType.CROSS_CONDUCTOR
):
self.models.croco_sctfcoil.run()
i_tf_turn_type = (
data_structure.superconducting_tf_coil_variables.i_tf_turn_type
)
turn_type = SuperconductingTFTurnType(i_tf_turn_type)
if turn_type == SuperconductingTFTurnType.CABLE_IN_CONDUIT:
self.models.cicc_sctfcoil.run()
elif turn_type == SuperconductingTFTurnType.CROSS_CONDUCTOR:
self.models.croco_sctfcoil.run()
else:
raise ValueError(
f"Unhandled superconducting TF turn type: {i_tf_turn_type!r}"
)

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Refactor TF Coil Toroidal field coil

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants