Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
d036308
Refactor PF coil placement logic for improved readability and consist…
chris-ashe Sep 10, 2025
5a7033d
Refactor PF coil placement logic by introducing a helper function for…
chris-ashe Sep 10, 2025
0bdd91a
🔄 - Rename rcls to r_pf_coil_middle_group_array for clarity and updat…
chris-ashe Sep 10, 2025
a823b17
🔄 - Rename zcls to z_pf_coil_middle_group_array for clarity and updat…
chris-ashe Sep 10, 2025
3155891
🔄 - Rename rpf1 to dr_pf_cs_middle_offset for clarity and update refe…
chris-ashe Sep 10, 2025
040b646
🔄 - Rename 'routr' to 'dr_pf_tf_outboard_out_offset' for clarity and …
chris-ashe Sep 10, 2025
ff81bce
🔄 - Rename 'rclsnorm' to 'r_pf_outside_tf_midplane' for clarity and u…
chris-ashe Sep 10, 2025
a4d0cb3
🔄 - Refactor PF coil placement logic and add 'place_pf_above_tf' meth…
chris-ashe Sep 10, 2025
23ccfef
🔄 - Rename 'hpfdif' to 'dz_tf_upper_lower_midplane' for clarity and u…
chris-ashe Sep 10, 2025
6312949
:bug: Fix calculated value of dz_tf_upper_lower_midplan
chris-ashe Sep 10, 2025
bd7a3e3
:art: Add 'dz_tf_upper_lower_midplane' to plot and output for improve…
chris-ashe Sep 10, 2025
092551b
🔄 - Rename 'i_sup_pf_shape' to 'i_r_pf_outside_tf_placement' for clar…
chris-ashe Sep 10, 2025
1581920
:bug: Fix false association between TF coil shape and conductor type
chris-ashe Sep 10, 2025
77d23be
:recycle: Add new function for placing PF coils outside the TF
chris-ashe Sep 10, 2025
8c9cfa9
:recycle: Refactor PF coil placement logic into a new method for impr…
chris-ashe Sep 11, 2025
33c892d
:art: Update PF coil positioning documentation for clarity and accura…
chris-ashe Sep 11, 2025
b5d52d1
:recycle: Add parameterized test for placing PF coils above the CS wi…
chris-ashe Sep 11, 2025
3e4e4b4
:recycle: Add unit tests for placing PF coils above TF with various c…
chris-ashe Sep 11, 2025
1107d76
Update r/z_pf_coil_middle_group_array in the general case
timothy-nunn Sep 23, 2025
2db8c96
:recycle: Update obsolete variable mappings for PF coil placement off…
chris-ashe Sep 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 85 additions & 21 deletions documentation/eng-models/pf-coil.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,37 +9,101 @@ position and shape during the flat-top period.
The positions and sizes of te PF coils are partly input, and partly calculated after consideration
of the required currents and allowable current density.

The PF coil locations are controlled using a set of switched stored in array `i_pf_location` (see
Figure 1), and are calculated in routine `PFCOIL`. The coils are (usually) organised into groups
The PF coil locations are controlled using a set of switches stored in array `i_pf_location[]`. The coils are (usually) organised into groups
containing two PF coils placed symmetrically above and below the midplane, and each group `j` has
an element `i_pf_location(j)` assigned to it. Input parameter `n_pf_coil_groups` should be set to the number of groups,
and `n_pf_coils_in_group(j)` should be assigned the number of coils in each group - which should be 2 in each case.

<figure markdown>
![Machine build](../images/vertical-build.png){ width="100%"}
<figcaption>Figure 1: Machine build for D-shaped major components</figcaption>
</figure>

In the following, all variables are defined in the variable descriptor file `vardes.html`. The
values for `rpf1`, `rpf2`, `zref(j)` and `routr` should be adjusted by the user to locate the PF
The values for `dr_pf_cs_middle_offset`, `rpf2`, `zref(j)` and `dr_pf_tf_outboard_out_offset` should be adjusted by the user to locate the PF
coils accurately.

The three possible values of `i_pf_location(j)` correspond to the following PF coil positions: (Redo taking
into account `i_single_null` and other recent changes e.g. rclsnorm)
For PF coils going to be placed outside the TF coils the key radius is defined as:

$$
\overbrace{R_{\text{PF, outside-TF}}}^{\texttt{r_pf_outside_tf_midplane}} = \overbrace{R_{\text{TF,outboard-out}}}^{\texttt{r_tf_outboard_out}} + \overbrace{dR_{\text{PF,TF-offset}}}^{\texttt{dr_tf_outboard_out_offset}}
$$

The four possible values of `i_pf_location(j)` correspond to the following PF coil positions:

### Above the central solenoid (one group only) | `place_pf_above_cs()`

- `i_pf_location(j) = 1`;

$$
R = \overbrace{R_{\text{CS,middle}}}^{\texttt{r_cs_middle}} + \overbrace{dR_{\text{offset}}}^{\texttt{dr_pf_cs_middle_offset}}
$$

$$
Z = \pm \ \overbrace{Z_{\text{CS,top}}}^{\texttt{z_cs_coil_upper}} + 0.1
\\ + \frac{1}{2}\left(\left(\underbrace{Z_{\text{TF,inside}}}_{\texttt{z_tf_inside_half}}-Z_{\text{CS,top}}\right)+\underbrace{dR_{\text{TF,inboard}}}_{\texttt{dr_tf_inboard}}+0.1\right)
$$

-----------------------------

### Above the TF coils (one group only) | `place_pf_above_tf()`

- `i_pf_location(j) = 2`;

$$
R = R_0 + \texttt{rpf2} \times \delta \times a
$$

Due to the nautre of the TF coils not being top-down symmetric for single null cases the positioning slightly differs if the coil is above or below the midplane.

For above the midplane:

$$
Z = Z_{\text{TF,top}} + 0.86
$$

For below the midplane:

$$
Z = - \left(Z_{\text{TF,top}} - \overbrace{dZ_{\text{TF,upper-lower-midplane}}}^{\texttt{dz_tf_upper_lower_midplane}} + 0.86 \right)
$$

-------------------


### Outside the TF coils | `place_pf_outside_tf()`

- `i_pf_location(j) = 3`;

The PF coils can either be stacked vertically outside the TF (ideal for a picture frame coil) or follow the TF coil curve (D-shaped):

If the chosen value for `i_tf_shape` is that of a picture frame or `i_r_pf_outside_tf_placement == 1` then:

$$
R = \overbrace{R_{\text{PF, outside-TF}}}^{\texttt{r_pf_outside_tf_midplane}}
$$

Else, the coils follow a D-shape curve

$$
R = \sqrt{\left(\overbrace{R_{\text{PF, outside-TF}}}^{\texttt{r_pf_outside_tf_midplane}}\right)^2 - Z^2}
$$

$$
Z = \pm \ a \times \texttt{zref}
$$

---------------------

### General placement | `place_pf_generally()`

- `i_pf_location(j) = 4`;

`i_pf_location(j)` = 1: PF coils are placed above the central solenoid (one group only);
*R* = `r_cs_middle` + `rpf1`<br>
*Z* = $\pm$(`z_tf_inside_half` * `f_z_cs_tf_internal` + 0.1 + 0.5 * (`z_tf_inside_half` * (1 - `f_z_cs_tf_internal`) + `dr_tf_inboard` + 0.1))
The PF coils are placed generally in units of minor radius ($a$) relative to the mid-plane and plasma major radius ($R_0$);

`i_pf_location(j)` = 2: PF coils are placed above the TF coils (one group only);<br>
*R* = `rmajor` + `rpf2`<br>
*Z* = $\pm$(`z_tf_inside_half` * `dr_tf_inboard` + 0.86)
$$
R = \pm \ a \times \texttt{rref[]} + R_0
$$

`i_pf_location(j)` = 3: PF coils are placed radially outside the TF coils (any number of groups);<br>
*R* = `rtot` + `dr_tf_outboard`/2 + `routr`<br>
*Z* = $\pm$(`rminor` * `zref(j)`
$$
Z = \pm \ a \times \texttt{zref[]}
$$

The void fraction (for coolant) in each coil `i`'s winding pack is given by `f_a_pf_coil_void(i)`.
-------------------------

## Coil currents

Expand Down
9 changes: 4 additions & 5 deletions process/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -826,7 +826,7 @@ def calculate_vertical_build(self, output: bool) -> None:
build_variables.z_tf_top = (
build_variables.z_tf_inside_half + build_variables.dr_tf_inboard
)
build_variables.hpfdif = 0.0e0
build_variables.dz_tf_upper_lower_midplane = 0.0e0
else:
build_variables.z_tf_top = (
build_variables.dr_tf_inboard
Expand All @@ -842,10 +842,9 @@ def calculate_vertical_build(self, output: bool) -> None:
+ build_variables.dz_fw_plasma_gap
+ build_variables.z_plasma_xpoint_upper
)
build_variables.hpfdif = (
build_variables.z_tf_top
- (build_variables.z_tf_inside_half + build_variables.dr_tf_inboard)
) / 2.0e0
Comment thread
timothy-nunn marked this conversation as resolved.
build_variables.dz_tf_upper_lower_midplane = build_variables.z_tf_top - (
build_variables.z_tf_inside_half + build_variables.dr_tf_inboard
)

def divgeom(self, output: bool):
"""
Expand Down
6 changes: 3 additions & 3 deletions process/data_structure/build_variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@
"""maximum (half-)height of TF coil (inside edge) (m)"""


hpfdif: float = None
dz_tf_upper_lower_midplane: float = None
"""difference in distance from midplane of upper and lower portions of TF
legs (non-zero for single-null devices) (m)
"""
Expand Down Expand Up @@ -443,7 +443,7 @@ def init_build_variables():
global gapomin
global dr_shld_vv_gap_outboard
global z_tf_inside_half
global hpfdif
global dz_tf_upper_lower_midplane
global z_tf_top
global hr1
global iohcl
Expand Down Expand Up @@ -538,7 +538,7 @@ def init_build_variables():
gapomin = 0.234
dr_shld_vv_gap_outboard = 0.0
z_tf_inside_half = 0.0
hpfdif = 0.0
dz_tf_upper_lower_midplane = 0.0
z_tf_top = 0.0
hr1 = 0.0
iohcl = 1
Expand Down
33 changes: 17 additions & 16 deletions process/data_structure/pfcoil_variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,11 @@

xind: list[float] = None

rcls: list[float] = None
r_pf_coil_middle_group_array: list[float] = None
"""2D array of PF coil middle radii, indexed by group and coil in group"""

zcls: list[float] = None
z_pf_coil_middle_group_array: list[float] = None
"""2D array of PF coil middle heights, indexed by group and coil in group"""

ccls: list[float] = None

Expand Down Expand Up @@ -245,9 +247,8 @@
"""


i_sup_pf_shape: int = None
i_r_pf_outside_tf_placement: int = None
"""Switch for the placement of Location 3 (outboard) PF coils
when the TF coils are superconducting (i_tf_sup = 1)
- =0 (Default) Outboard PF coils follow TF shape
in an ellipsoidal winding surface
- =1 Outboard PF coils all have same radius, cylindrical
Expand Down Expand Up @@ -379,15 +380,15 @@
"""Full height of the central solenoid (m)"""


routr: float = None
dr_pf_tf_outboard_out_offset: float = None
"""radial distance (m) from outboard TF coil leg to centre of `i_pf_location=3` PF coils"""


r_pf_coil_middle: list[float] = None
"""radius of PF coil i (m)"""


rpf1: float = None
dr_pf_cs_middle_offset: float = None
"""offset (m) of radial position of `i_pf_location=1` PF coils from being directly above
the central solenoid
"""
Expand Down Expand Up @@ -571,8 +572,8 @@ def init_pfcoil_module():
global zfxf
global cfxf
global xind
global rcls
global zcls
global r_pf_coil_middle_group_array
global z_pf_coil_middle_group_array
global ccls
global ccl0
global bpf2
Expand All @@ -592,8 +593,8 @@ def init_pfcoil_module():
zfxf = np.zeros(NFIXMX)
cfxf = np.zeros(NFIXMX)
xind = np.zeros(NFIXMX)
rcls = np.zeros((N_PF_GROUPS_MAX, N_PF_COILS_IN_GROUP_MAX))
zcls = np.zeros((N_PF_GROUPS_MAX, N_PF_COILS_IN_GROUP_MAX))
r_pf_coil_middle_group_array = np.zeros((N_PF_GROUPS_MAX, N_PF_COILS_IN_GROUP_MAX))
z_pf_coil_middle_group_array = np.zeros((N_PF_GROUPS_MAX, N_PF_COILS_IN_GROUP_MAX))
ccls = np.zeros(N_PF_GROUPS_MAX)
ccl0 = np.zeros(N_PF_GROUPS_MAX)
bpf2 = np.zeros(NGC2)
Expand Down Expand Up @@ -634,7 +635,7 @@ def init_pfcoil_variables():
global j_crit_str_cs
global j_crit_str_pf
global i_pf_current
global i_sup_pf_shape
global i_r_pf_outside_tf_placement
global j_cs_conductor_critical_pulse_start
global j_cs_conductor_critical_flat_top_end
global jcableoh_bop
Expand Down Expand Up @@ -664,9 +665,9 @@ def init_pfcoil_variables():
global j_pf_wp_critical
global r_cs_middle
global dz_cs_full
global routr
global dr_pf_tf_outboard_out_offset
global r_pf_coil_middle
global rpf1
global dr_pf_cs_middle_offset
global rpf2
global rref
global s_shear_cs_peak
Expand Down Expand Up @@ -736,7 +737,7 @@ def init_pfcoil_variables():
j_crit_str_cs = 0.0
j_crit_str_pf = 0.0
i_pf_current = 1
i_sup_pf_shape = 0
i_r_pf_outside_tf_placement = 0
j_cs_conductor_critical_pulse_start = 0.0
j_cs_conductor_critical_flat_top_end = 0.0
jcableoh_bop = 0.0
Expand Down Expand Up @@ -766,9 +767,9 @@ def init_pfcoil_variables():
j_pf_wp_critical = np.zeros(NGC2)
r_cs_middle = 0.0
dz_cs_full = 0.0
routr = 1.5
dr_pf_tf_outboard_out_offset = 1.5
r_pf_coil_middle = np.zeros(NGC2)
rpf1 = 0.0
dr_pf_cs_middle_offset = 0.0
rpf2 = -1.63
rref = np.full(N_PF_GROUPS_MAX, 7.0)
s_shear_cs_peak = 0.0
Expand Down
10 changes: 7 additions & 3 deletions process/input.py
Comment thread
chris-ashe marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -1490,9 +1490,13 @@ def __post_init__(self):
"roughness_fw_channel": InputVariable(
data_structure.fwbs_variables, float, range=(0.0, 0.01)
),
"routr": InputVariable(data_structure.pfcoil_variables, float, range=(-3.0, 3.0)),
"dr_pf_tf_outboard_out_offset": InputVariable(
data_structure.pfcoil_variables, float, range=(-3.0, 3.0)
),
"row": InputVariable(data_structure.buildings_variables, float, range=(0.0, 10.0)),
"rpf1": InputVariable(data_structure.pfcoil_variables, float, range=(0.0, 3.0)),
"dr_pf_cs_middle_offset": InputVariable(
data_structure.pfcoil_variables, float, range=(0.0, 3.0)
),
"rpf2": InputVariable(data_structure.pfcoil_variables, float, range=(-3.0, 3.0)),
"rrin": InputVariable(data_structure.ife_variables, float, range=(0.1, 50.0)),
"rrmax": InputVariable(data_structure.ife_variables, float, range=(1.0, 50.0)),
Expand Down Expand Up @@ -2057,7 +2061,7 @@ def __post_init__(self):
data_structure.physics_variables, int, choices=[0, 1]
),
"i_str_wp": InputVariable(data_structure.tfcoil_variables, int, choices=[0, 1]),
"i_sup_pf_shape": InputVariable(
"i_r_pf_outside_tf_placement": InputVariable(
data_structure.pfcoil_variables, int, choices=[0, 1]
),
"i_tf_bucking": InputVariable(data_structure.tfcoil_variables, int, range=(0, 3)),
Expand Down
3 changes: 3 additions & 0 deletions process/io/obsolete_vars.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,9 @@
"tdmptf": "t_tf_superconductor_quench",
"tmaxpro": "temp_tf_conductor_quench_max",
"coreradiationfraction": "f_p_plasma_core_rad_reduction",
"routr": "dr_pf_tf_outboard_out_offset",
"rpf1": "dr_pf_cs_middle_offset",
"i_sup_pf_shape": "i_r_pf_outside_tf_placement",
}

OBS_VARS_HELP = {
Expand Down
18 changes: 17 additions & 1 deletion process/io/plot_proc.py
Original file line number Diff line number Diff line change
Expand Up @@ -8764,6 +8764,9 @@ def plot_tf_coil_structure(axis, mfile_data, scan, colour_scheme=1):
r_tf_inboard_in = mfile_data.data["r_tf_inboard_in"].get_scan(scan)
dr_tf_outboard = mfile_data.data["dr_tf_outboard"].get_scan(scan)
len_tf_coil = mfile_data.data["len_tf_coil"].get_scan(scan)
dz_tf_upper_lower_midplane = mfile_data.data["dz_tf_upper_lower_midplane"].get_scan(
scan
)

# Plot the points as black dots, number them, and connect them with lines
xs = [x1, x2, x3, x4, x5]
Expand Down Expand Up @@ -9036,7 +9039,7 @@ def plot_tf_coil_structure(axis, mfile_data, scan, colour_scheme=1):

# ==============================================================

# Add a label for the inboard thickness
# Add a label for the length of the coil
axis.text(
(r_tf_outboard_in + 2 * dr_tf_outboard),
0.0,
Expand All @@ -9049,6 +9052,19 @@ def plot_tf_coil_structure(axis, mfile_data, scan, colour_scheme=1):

# ==============================================================

# Add a label for the length of the coil
axis.text(
(r_tf_outboard_in + 2 * dr_tf_outboard),
-1.0,
f"$\\Delta Z$ upper and lower to midplane = {dz_tf_upper_lower_midplane:.3f} m",
fontsize=7,
color="black",
verticalalignment="center",
bbox={"boxstyle": "round", "facecolor": "pink", "alpha": 1.0},
)

# ==============================================================

# Add arow for inboard coil radius
axis.annotate(
"",
Expand Down
Loading
Loading