Skip to content

Commit 2b50a57

Browse files
authored
Tidying up toroidal harmonics notebooks on develop for release (#4039)
* Tidying up toroidal harmonics notebooks on develop for release * fix test failures * Changes based on review comments
1 parent 52fc1f5 commit 2b50a57

File tree

6 files changed

+192
-518
lines changed

6 files changed

+192
-518
lines changed

CITATION.cff

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ authors:
8484
given-names: S.
8585
affiliation: United Kingdom Atomic Energy Authority, Culham Science Centre, Abingdon, Oxfordshire OX14 3DB, United Kingdom
8686

87+
- family-names: Mould
88+
given-names: C. L.
89+
affiliation: United Kingdom Atomic Energy Authority, Culham Science Centre, Abingdon, Oxfordshire OX14 3DB, United Kingdom
90+
8791
- family-names: Saunders
8892
given-names: H.
8993
affiliation: United Kingdom Atomic Energy Authority, Culham Science Centre, Abingdon, Oxfordshire OX14 3DB, United Kingdom

bluemira/equilibria/optimisation/harmonics/toroidal_harmonics_approx_functions.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,9 @@ def coil_toroidal_harmonic_amplitude_matrix(
324324
Legendre functions of the first kind of degree lambda and order minus mu, and :math:
325325
\\Delta_c = \\cosh{\\tau_c} - \\cos{\\sigma_c}.
326326
327+
Note: the factorial term \\frac{(2m+1)!!}{2^m m!} is equivalent to 1 if m = 0,
328+
otherwise \\prod_{i=0}^{m-1} \\left( 1 + \\frac{1}{2(m-i)}\\right)
329+
327330
Parameters
328331
----------
329332
input_coils:
@@ -368,7 +371,7 @@ def coil_toroidal_harmonic_amplitude_matrix(
368371
currents2harmonics = np.zeros([max_degree, np.size(tau_c)])
369372

370373
# TH coefficients from function of the current distribution
371-
# outside of the region containing the core plamsa
374+
# outside of the region containing the core plasma
372375
# TH coefficients = currents2harmonics @ coil currents
373376
degrees = np.arange(0, max_degree)[:, None]
374377
factorial_term = np.array([

documentation/source/examples.rst

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,8 @@ Equilibria Examples
2727
examples/equilibria/coilset_opt_problem_tutorial
2828
examples/equilibria/anaylsis_toolbox_examples
2929
examples/equilibria/quick_look_at_example_coilsets
30-
examples/equilibria/toroidal_harmonics_approximation_basic_use
31-
examples/equilibria/toroidal_harmonics_component_function_walkthrough_and_verification
32-
examples/equilibria/toroidal_harmonics_full_coilset_approximation_bluemira_comparison
30+
examples/equilibria/Toroidal_Approximation_Explained
31+
examples/equilibria/Toroidal_Harmonics_Optimisation_Set_Up
3332

3433

3534
Geometry Examples

examples/equilibria/toroidal_harmonics_full_coilset_approximation_bluemira_comparison.ex.py renamed to examples/equilibria/Toroidal_Approximation_Explained.ex.py

Lines changed: 114 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@
3030
# This example illustrates the inner workings of the bluemira toroidal harmonics
3131
# approximation function (toroidal_harmonic_approximation) which can be used in
3232
# coilset current and position optimisation for conventional aspect ratio tokamaks.
33-
# For full details and all equations, look in the
34-
# notebook toroidal_harmonics_component_function_walkthrough_and_verification.ex.py.
33+
# For an example of how toroidal_harmonic_approximation is used
34+
# to create toroidal harmonics constraints for use in a coilset optimisation problem,
35+
# please see
36+
# Toroidal_Harmonics_Optimisation_Set_Up.ex.py
3537
#
3638
# ## Premise:
3739
#
@@ -49,21 +51,21 @@
4951
# There are benefits to using TH as a minimal set of constraints:
5052
# - We can choose not to re-solve for the plasma equilibrium at each step, since the
5153
# coilset contribution to the core plasma (within the LCFS) is constrained.
52-
# - We have a minimal set of constraints (a set of harmonic amplitudes) for the core
53-
# plasma contribution, which can reduce the dimensionality of the problem we are
54+
# - We have a small, viable set of constraints (a set of harmonic amplitudes) for the
55+
# core plasma contribution, which can reduce the dimensionality of the problem we are
5456
# considering.
5557
#
58+
# %% [markdown]
5659
# We get the TH amplitudes/coefficients, $A(\tau, \sigma)$, from the following equations:
5760
#
5861
# $$ A(\tau, \sigma) = \sum_{m=0}^{\infty} A_m^{\cos} \epsilon_m m! \sqrt{\frac{2}{\pi}}
59-
# \Delta^{\frac{1}{2}} \textbf{Q}_{m-\frac{1}{2}}^{1}(\cosh \tau) \cos(m \sigma) + A_m^
60-
# {\sin}
61-
# \epsilon_m m! \sqrt{\frac{2}{\pi}} \Delta^{\frac{1}{2}}
62+
# \Delta^{\frac{1}{2}} \textbf{Q}_{m-\frac{1}{2}}^{1}(\cosh \tau) \cos(m \sigma)
63+
# \\+ A_m^{\sin} \epsilon_m m! \sqrt{\frac{2}{\pi}} \Delta^{\frac{1}{2}}
6264
# \textbf{Q}_{m-\frac{1}{2}}^{1}(\cosh \tau) \sin(m \sigma) $$
6365
#
6466
# where
6567
#
66-
# $$ A_m^{\cos, \sin} = \frac{\mu_0 I_c}{2^{\frac{5}{2}}} factorial\_term \frac{\sinh(
68+
# $$ A_m^{\cos, \sin} = \frac{\mu_0 I_c}{2^{\frac{5}{2}}} \upsilon_{fact} \frac{\sinh(
6769
# \tau_c)}
6870
# {\Delta_c^{\frac{1}{2}}} P_{m - \frac{1}{2}}^{-1}(\cosh(\tau_c)) ^{\cos}_{\sin}(m
6971
# \sigma_c) $$
@@ -82,15 +84,16 @@
8284
# - $\varepsilon_m = 1 $ for $m = 0$ and $\varepsilon_m = 2$ for $m \ge 1$
8385
# - $ \Delta = \cosh(\tau) - \cos(\sigma) $
8486
# - $ \Delta_c = \cosh(\tau_c) - \cos(\sigma_c) $
85-
# - $ factorial\_term = \prod_{i=0}^{m-1} \left( 1 + \frac{1}{2(m-i)}\right) $
87+
# - The factorial term, $\upsilon_{fact}$ = 1 if $m = 0$, else $= \prod_{i=0}^{m-1}
88+
# \left( 1 + \frac{1}{2(m-i)}\right) $
8689
#
87-
# %% [markdown]
88-
# ## Imports
90+
# We can then obtain the flux, $\psi$ by using $\psi = R A$.
8991

9092
# %%
9193
from copy import deepcopy
9294
from pathlib import Path
9395

96+
import matplotlib.patches as patch
9497
import matplotlib.pyplot as plt
9598
import numpy as np
9699

@@ -109,79 +112,98 @@
109112
from bluemira.geometry.coordinates import Coordinates
110113

111114
# %% [markdown]
112-
# Get equilibrium from EQDSK file and plot
115+
# Get equilibrium from EQDSK file
113116

114117
# %%
115118
# Get equilibrium
116119
EQDATA = get_bluemira_path("equilibria/test_data", subfolder="tests")
117120

118-
# eq_name = "eqref_OOB.json"
119-
# eq_name = Path(EQDATA, eq_name)
120-
# eq = Equilibrium.from_eqdsk(eq_name, from_cocos=7)
121-
122121
eq_name = "DN-DEMO_eqref.json"
123122
eq_name = Path(EQDATA, eq_name)
124123
eq = Equilibrium.from_eqdsk(eq_name, from_cocos=3, qpsi_positive=False)
125124

126-
# Plot the equilibrium
127-
f, ax = plt.subplots()
128-
eq.plot(ax=ax)
129-
eq.coilset.plot(ax=ax)
130-
plt.show()
131125

132126
# %% [markdown]
133127
# Set the focus point
134128
#
135129
# Note: there is the possibility to experiment with the position of the focus, but
136-
# the default is to use the plasma o point.
130+
# the default is to use the effective centre of the plasma.
137131
# %%
138132
# Set the focus point
139133
R_0, Z_0 = eq.effective_centre()
140134

141135
# %% [markdown]
142136
# Use the `toroidal_harmonic_grid_and_coil_setup` to obtain the necessary
143137
# parameters required for the TH approximation, such as the R and Z coordinates of the
144-
# TH grid.
145-
#
146-
# We then use the `toroidal_harmonic_approximate_psi` function to approximate the coilset
147-
# contribution to the flux using toroidal harmonics. We need to provide this function
148-
# with the equilibrium, eq, and the ToroidalHarmonicsParams dataclass, which contains
149-
# necessary parameters for the TH approximation, such as the relevant coordinates
150-
# and coil names for use in the approximation. The
151-
# function returns the approx_coilset_psi array and the TH coefficient matrix A_m.
152-
# The default focus point is the plasma o point.
153-
# The white dot in the plot shows the focus point.
138+
# TH grid. We then plot the equilibrium and in orange we show the region over which
139+
# we will use our toroidal harmonic approximation.
140+
154141
# %%
155142
# Approximate psi and plot
156143
th_params = toroidal_harmonic_grid_and_coil_setup(eq=eq, R_0=R_0, Z_0=Z_0)
157144

158145
R_approx = th_params.R
159146
Z_approx = th_params.Z
160147

148+
# Plot the equilibrium and the region over which we approximate the flux using toroidal
149+
# harmonics.
150+
f, ax = plt.subplots()
151+
eq.plot(ax=ax)
152+
eq.coilset.plot(ax=ax)
153+
max_R = np.max(th_params.R) # noqa: N816
154+
min_R = np.min(th_params.R) # noqa: N816
155+
max_Z = np.max(th_params.Z) # noqa: N816
156+
min_Z = np.min(th_params.Z) # noqa: N816
157+
centre_R = (max_R - min_R) / 2 + min_R # noqa: N816
158+
centre_Z = (max_Z - np.abs(min_Z)) / 2 # noqa: N816
159+
radius = (max_R - min_R) / 2
160+
161+
ax.add_patch(
162+
patch.Circle((centre_R, centre_Z), radius, ec="orange", fill=True, fc="orange")
163+
)
164+
plt.show()
165+
# %% [markdown]
166+
#
167+
# We then use the `toroidal_harmonic_approximate_psi` function to approximate the coilset
168+
# contribution to the flux using toroidal harmonics. This makes use of the equation for
169+
# $A_m^{\cos, \sin}$ as displayed at the start of this notebook.
170+
# We need to provide this function
171+
# with the equilibrium, eq, and the ToroidalHarmonicsParams dataclass, which contains
172+
# necessary parameters for the TH approximation, such as the relevant coordinates
173+
# and coil names for use in the approximation. The
174+
# function returns the approx_coilset_psi array and the TH coefficient matrices Am_cos
175+
# and Am_sin.
176+
177+
# %%
161178
approx_coilset_psi, _, _ = toroidal_harmonic_approximate_psi(
162179
eq=eq, th_params=th_params, max_degree=5
163180
)
164181

165-
nlevels = PLOT_DEFAULTS["psi"]["nlevels"]
166-
cmap = PLOT_DEFAULTS["psi"]["cmap"]
167-
plt.contourf(R_approx, Z_approx, approx_coilset_psi, nlevels, cmap=cmap)
168-
plt.xlabel("R")
169-
plt.ylabel("Z")
170-
plt.title("TH Approximation for Coilset Psi")
171-
plt.show()
172-
173182

174183
# %% [markdown]
175-
# Now we want to compare this approximation to the solution from bluemira.
184+
# Now we want to compare this approximation for the coilset psi to the solution from
185+
# bluemira.
186+
# Since we assume the flux in the plasma region is kept fixed, we can calculate
187+
# approximate total psi = bluemira plasma psi + toroidal harmonic coilset psi
188+
# approximation.
189+
#
190+
# We also create a difference plot to compare our TH coilset psi approximation to the
191+
# bluemira coilset psi.
192+
# We can see zero difference in the core region, which we expect as we are constraining
193+
# the flux in this region, and we see differences outside of the approximation
194+
# region, but this is expected as we are only requiring the core region to be kept
195+
# fixed.
196+
# Since the plasma psi is kept fixed, the total psi difference plot would look
197+
# the same as the coilset psi difference plot shown below.
176198

177199
# %%
178-
# Plot total psi using approximate coilset psi from TH, and plasma psi from bluemira
200+
# Approximate total psi = approximate coilset psi from TH + plasma psi from bluemira
179201

180202
plasma_psi = eq.plasma.psi(R_approx, Z_approx)
181203

182204
total_psi = approx_coilset_psi + plasma_psi
183205

184-
# Find LCFS from TH approx
206+
# Find LCFS from TH approx to add to plot
185207
approx_eq = deepcopy(eq)
186208
o_points, x_points = approx_eq.get_OX_points(total_psi)
187209

@@ -197,84 +219,63 @@
197219
eq.get_LCFS() if np.isclose(psi_norm, 1.0) else eq.get_flux_surface(psi_norm)
198220
)
199221

222+
# Difference in coilset psi between TH approximation and bluemira
223+
bluemira_coilset_psi = eq.coilset.psi(R_approx, Z_approx)
224+
225+
coilset_psi_diff = np.abs(approx_coilset_psi - bluemira_coilset_psi) / np.max(
226+
np.abs(bluemira_coilset_psi)
227+
)
228+
coilset_psi_diff_plot = coilset_psi_diff
229+
f, axs = plt.subplots(1, 2)
230+
200231
# Plot
201-
plt.contourf(R_approx, Z_approx, total_psi, nlevels, cmap=cmap)
202-
plt.xlabel("R")
203-
plt.ylabel("Z")
204-
plt.plot(
232+
nlevels = PLOT_DEFAULTS["psi"]["nlevels"]
233+
cmap = PLOT_DEFAULTS["psi"]["cmap"]
234+
axs0_plot = axs[0].contourf(R_approx, Z_approx, total_psi, nlevels, cmap=cmap)
235+
axs[0].set_xlabel("R")
236+
axs[0].set_ylabel("Z")
237+
axs[0].plot(
205238
approx_fs.x,
206239
approx_fs.z,
207240
color="red",
208-
label=f"Closed Flux Surface (psi_n={psi_norm}) from TH approximation",
241+
label="Approx. FS from TH",
209242
)
210-
plt.title("Total Psi using TH approximation for coilset psi")
211-
plt.legend(loc="upper right")
212-
plt.show()
213-
214-
# %%
215-
# Plot bluemira total psi
216-
# Obtain psi from Bluemira coilset
217-
bm_coil_psi = np.zeros(np.shape(eq.grid.x))
218-
for n in eq.coilset.name:
219-
bm_coil_psi = np.sum([bm_coil_psi, eq.coilset[n].psi(eq.grid.x, eq.grid.z)], axis=0)
220-
221-
222-
# Obtain total psi from Bluemira
223-
bluemira_total_psi = eq.psi(R_approx, Z_approx)
224-
225-
# Plotting
226-
plt.contourf(R_approx, Z_approx, bluemira_total_psi, levels=nlevels, cmap=cmap)
227-
plt.xlabel("R")
228-
plt.ylabel("Z")
229-
plt.title("Total Bluemira Psi")
230-
plt.show()
231-
# %%
232-
# Plot bluemira coilset psi
233-
coilset_psi = eq.coilset.psi(R_approx, Z_approx)
234-
235-
# Plotting
236-
plt.contourf(R_approx, Z_approx, coilset_psi, nlevels, cmap=cmap)
237-
plt.xlabel("R")
238-
plt.ylabel("Z")
239-
plt.title("Bluemira Coilset Psi")
240-
plt.show()
241-
# %%
242-
# Difference plot to compare TH approximation to Bluemira coilset psi
243-
# We see zero difference in the core region, which we expect as we are constraining
244-
# the flux in this region, and we see larger differences outside of the approximation
245-
# region.
246-
coilset_psi_diff = np.abs(approx_coilset_psi - coilset_psi) / np.max(np.abs(coilset_psi))
247-
coilset_psi_diff_plot = coilset_psi_diff
248-
f, ax = plt.subplots()
249-
ax.plot(approx_fs.x, approx_fs.z, color="red", label="Approximate LCFS from TH")
250-
ax.plot(original_fs.x, original_fs.z, color="blue", label="LCFS from Bluemira")
251-
im = ax.contourf(R_approx, Z_approx, coilset_psi_diff_plot, levels=nlevels, cmap=cmap)
252-
f.colorbar(mappable=im)
253-
ax.set_title("Absolute relative difference between coilset psi and TH approximation psi")
254-
ax.legend(loc="upper right", bbox_to_anchor=(1.1, 1.0))
255-
eq.coilset.plot(ax=ax)
256-
plt.show()
257-
258-
259-
# %%
260-
# Difference plot to compare TH approximation to Bluemira total psi
261-
total_psi_diff = np.abs(total_psi - bluemira_total_psi) / np.max(
262-
np.abs(bluemira_total_psi)
243+
axs[0].set_title("Total Psi using TH approximation for coilset psi")
244+
axs[0].legend(loc="upper right")
245+
246+
axs[1].plot(approx_fs.x, approx_fs.z, color="red", label="Approx. FS from TH")
247+
axs[1].plot(
248+
original_fs.x,
249+
original_fs.z,
250+
color="c",
251+
linestyle="dashed",
252+
label="LCFS from Bluemira",
263253
)
264-
total_psi_diff_plot = total_psi_diff
265-
f, ax = plt.subplots()
266-
ax.plot(approx_fs.x, approx_fs.z, color="red", label="Approx FS from TH")
267-
ax.plot(original_fs.x, original_fs.z, color="blue", label="FS from Bluemira")
268-
im = ax.contourf(R_approx, Z_approx, total_psi_diff_plot, levels=nlevels, cmap=cmap)
269-
f.colorbar(mappable=im)
270-
ax.set_title("Absolute relative difference between total psi and TH approximation psi")
271-
ax.legend(loc="upper right")
272-
eq.coilset.plot(ax=ax)
254+
axs1_plot = axs[1].contourf(
255+
R_approx, Z_approx, coilset_psi_diff_plot, levels=nlevels, cmap=cmap
256+
)
257+
f.colorbar(axs1_plot, ax=axs[1], fraction=0.05)
258+
axs[1].set_title(
259+
"Absolute relative difference between coilset psi \nand TH approximation psi"
260+
)
261+
axs[1].legend(loc="upper right", bbox_to_anchor=(1.1, 1.0))
262+
eq.coilset.plot(ax=axs[1])
263+
axs[0].set_aspect("equal")
264+
axs[1].set_aspect("equal")
273265
plt.show()
274266

267+
275268
# %% [markdown]
276269
# We use a fit metric to evaluate the TH approximation.
277-
270+
# This involves
271+
# finding a flux surface (here corresponding to a psi_norm=0.95) for our
272+
# approximate total psi, and comparing to the equivalent flux surface from
273+
# the bluemira total psi. The fit metric we use for this flux surface
274+
# comparison is as follows: fit metric value = total area within one but not
275+
# both flux surfaces / (bluemira flux surface area + approximation flux surface area).
276+
# A fit metric of 0 would correspond to a perfect match between our approximation
277+
# and bluemira, and a fit metric of 1 would correspond to no overlap between the
278+
# approximation and bluemira flux surfaces.
278279
# %%
279280
# Fit metric to evaluate TH approximation
280281
fit_metric_value = fs_fit_metric(original_fs, approx_fs)
@@ -287,7 +288,8 @@
287288
# number of degrees to use for the approximation.
288289
#
289290
# Here is an example of using the function, setting plot to True outputs a graph of the
290-
# difference in total psi between the TH approximation and bluemira.
291+
# difference in total psi between the TH approximation and bluemira. This graph
292+
# is the same as the one we produced in this notebook above.
291293
# %%
292294
(
293295
toroidal_harmonics_params,

0 commit comments

Comments
 (0)