Skip to content

Commit

Permalink
✨ Rzip
Browse files Browse the repository at this point in the history
  • Loading branch information
je-cook committed Oct 21, 2024
1 parent 7f8cd51 commit df2e526
Showing 1 changed file with 302 additions and 0 deletions.
302 changes: 302 additions & 0 deletions bluemira/equilibria/vertical_stability.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,302 @@
# SPDX-FileCopyrightText: 2021-present M. Coleman, J. Cook, F. Franza
# SPDX-FileCopyrightText: 2021-present I.A. Maione, S. McIntosh
# SPDX-FileCopyrightText: 2021-present J. Morris, D. Short
#
# SPDX-License-Identifier: LGPL-2.1-or-later

"""
Vertical stability calculations
"""

from __future__ import annotations

Check warning on line 11 in bluemira/equilibria/vertical_stability.py

View check run for this annotation

Codecov / codecov/patch

bluemira/equilibria/vertical_stability.py#L11

Added line #L11 was not covered by tests

from typing import TYPE_CHECKING

Check warning on line 13 in bluemira/equilibria/vertical_stability.py

View check run for this annotation

Codecov / codecov/patch

bluemira/equilibria/vertical_stability.py#L13

Added line #L13 was not covered by tests

import numpy as np

Check warning on line 15 in bluemira/equilibria/vertical_stability.py

View check run for this annotation

Codecov / codecov/patch

bluemira/equilibria/vertical_stability.py#L15

Added line #L15 was not covered by tests

from bluemira.base.look_and_feel import bluemira_debug, bluemira_warn
from bluemira.equilibria.coils._tools import make_mutual_inductance_matrix

Check warning on line 18 in bluemira/equilibria/vertical_stability.py

View check run for this annotation

Codecov / codecov/patch

bluemira/equilibria/vertical_stability.py#L17-L18

Added lines #L17 - L18 were not covered by tests

if TYPE_CHECKING:
import numpy.typing as npt

from bluemira.equilibria.coils._grouping import CoilSet
from bluemira.equilibria.equilibrium import Equilibrium


def calculate_rzip_stability_criterion(eq: Equilibrium) -> float:

Check warning on line 27 in bluemira/equilibria/vertical_stability.py

View check run for this annotation

Codecov / codecov/patch

bluemira/equilibria/vertical_stability.py#L27

Added line #L27 was not covered by tests
"""
Calculate the rzip stability criterion for a given equilibrium and coilset
Returns
-------
:
Stability criterion
Notes
-----
See ~:class:`~bluemira.equilibria.vertical_stability.RZIp` for details
"""
return RZIp(eq.coilset)(eq)

Check warning on line 40 in bluemira/equilibria/vertical_stability.py

View check run for this annotation

Codecov / codecov/patch

bluemira/equilibria/vertical_stability.py#L40

Added line #L40 was not covered by tests


class RZIp:

Check warning on line 43 in bluemira/equilibria/vertical_stability.py

View check run for this annotation

Codecov / codecov/patch

bluemira/equilibria/vertical_stability.py#L43

Added line #L43 was not covered by tests
"""
RZIp model
Parameters
----------
coilset:
The full coilset
Notes
-----
A value ~ 1.5 considered optimal and controllable.
See https://doi.org/10.13182/FST89-A39747 for further explanation
< 1 plasma mass becomes a factor
= 1 massless plasma solution not valid (said to represent MHD effects)
> 1 displacement growth dominated by L/R of passive system
.. math::
f = -\\frac{F_s}{F_d}
= \\frac{I_p^T M^{\\prime}_p|_s [M_s|_s]^{-1} M^{\\prime}_s|_p I_p}
{I_p^T M^{\\prime\\prime}_p|_c I_c}
"""

def __init__(self, coilset):
self.coilset = coilset

Check warning on line 69 in bluemira/equilibria/vertical_stability.py

View check run for this annotation

Codecov / codecov/patch

bluemira/equilibria/vertical_stability.py#L68-L69

Added lines #L68 - L69 were not covered by tests

@property
def coilset(self):
return self._coilset

Check warning on line 73 in bluemira/equilibria/vertical_stability.py

View check run for this annotation

Codecov / codecov/patch

bluemira/equilibria/vertical_stability.py#L71-L73

Added lines #L71 - L73 were not covered by tests

@coilset.setter
def coilset(self, cs: CoilSet):

Check warning on line 76 in bluemira/equilibria/vertical_stability.py

View check run for this annotation

Codecov / codecov/patch

bluemira/equilibria/vertical_stability.py#L75-L76

Added lines #L75 - L76 were not covered by tests
# TODO @je-cook Possibly implement quad indexing?
self._coilset = cs
self.ind_mat = make_mutual_inductance_matrix(cs, square_coil=True)

Check warning on line 79 in bluemira/equilibria/vertical_stability.py

View check run for this annotation

Codecov / codecov/patch

bluemira/equilibria/vertical_stability.py#L78-L79

Added lines #L78 - L79 were not covered by tests

def __call__(

Check warning on line 81 in bluemira/equilibria/vertical_stability.py

View check run for this annotation

Codecov / codecov/patch

bluemira/equilibria/vertical_stability.py#L81

Added line #L81 was not covered by tests
self,
eq: Equilibrium,
cc_current_vector: None | npt.NDArray = None,
) -> float:
if not hasattr(eq, "profiles"):
bluemira_warn("No profiles found on equilibrium")

Check warning on line 87 in bluemira/equilibria/vertical_stability.py

View check run for this annotation

Codecov / codecov/patch

bluemira/equilibria/vertical_stability.py#L86-L87

Added lines #L86 - L87 were not covered by tests
# this constraint is pretty irrelevant for breakdown
return 0

Check warning on line 89 in bluemira/equilibria/vertical_stability.py

View check run for this annotation

Codecov / codecov/patch

bluemira/equilibria/vertical_stability.py#L89

Added line #L89 was not covered by tests

cc = self.coilset.get_control_coils()

Check warning on line 91 in bluemira/equilibria/vertical_stability.py

View check run for this annotation

Codecov / codecov/patch

bluemira/equilibria/vertical_stability.py#L91

Added line #L91 was not covered by tests

if cc_current_vector is None:
cc_current_vector = cc.current

Check warning on line 94 in bluemira/equilibria/vertical_stability.py

View check run for this annotation

Codecov / codecov/patch

bluemira/equilibria/vertical_stability.py#L93-L94

Added lines #L93 - L94 were not covered by tests
else:
cc.current = cc_current_vector

Check warning on line 96 in bluemira/equilibria/vertical_stability.py

View check run for this annotation

Codecov / codecov/patch

bluemira/equilibria/vertical_stability.py#L96

Added line #L96 was not covered by tests

eq._remap_greens()

Check warning on line 98 in bluemira/equilibria/vertical_stability.py

View check run for this annotation

Codecov / codecov/patch

bluemira/equilibria/vertical_stability.py#L98

Added line #L98 was not covered by tests

control_ind = self.coilset._control_ind
uncontrolled_ind = list(

Check warning on line 101 in bluemira/equilibria/vertical_stability.py

View check run for this annotation

Codecov / codecov/patch

bluemira/equilibria/vertical_stability.py#L100-L101

Added lines #L100 - L101 were not covered by tests
set(self.coilset._get_type_index()) - set(self.coilset._control_ind)
)

return stab_destab(

Check warning on line 105 in bluemira/equilibria/vertical_stability.py

View check run for this annotation

Codecov / codecov/patch

bluemira/equilibria/vertical_stability.py#L105

Added line #L105 was not covered by tests
cc_current=cc_current_vector,
# res_ps=self.coilset.get_uncontrolled_coils().resistance,
l_ps_ps=self.ind_mat[uncontrolled_ind][:, uncontrolled_ind],
l_ac_ps=self.ind_mat[control_ind][:, uncontrolled_ind],
l_ac_ac=self.ind_mat[control_ind][:, control_ind],
# ind=M,
r_struct=self.coilset.x,
x_ac=cc.x,
i_plasma=eq._jtor * eq.grid.step,
br_struct_grid=eq._bx_green,
dbrdz_struct_grid=eq._db_green[..., control_ind],
# max_eigens=self.max_eigens,
)


def stab_destab(

Check warning on line 121 in bluemira/equilibria/vertical_stability.py

View check run for this annotation

Codecov / codecov/patch

bluemira/equilibria/vertical_stability.py#L121

Added line #L121 was not covered by tests
cc_current,
# res_ps,
l_ps_ps,
l_ac_ps,
l_ac_ac,
r_struct,
x_ac,
i_plasma,
br_struct_grid,
dbrdz_struct_grid,
# max_eigens,
) -> float:
"""
Calculate the stabilising / destabilising effect of the equilibria and structures
"""
# # single filament approx l_acps_eigen_vec
# eigen_values, eigen_vectors = eigenv(res_ps, l_ps_ps, max_eigens=max_eigens)
# l_acps_eigen_vec = l_ac_ps @ eigen_vectors

# # l_struct in rzip
# mss = np.hstack([
# np.vstack([l_ac_ac, l_acps_eigen_vec.T]),
# np.vstack([l_acps_eigen_vec, eigen_values]),
# ])

# l_struct in rzip
mss = np.vstack([

Check warning on line 148 in bluemira/equilibria/vertical_stability.py

View check run for this annotation

Codecov / codecov/patch

bluemira/equilibria/vertical_stability.py#L148

Added line #L148 was not covered by tests
np.hstack([l_ac_ac, l_ac_ps]),
np.hstack([l_ac_ps.T, l_ps_ps]),
])

msp_prime = (2 * np.pi * r_struct * br_struct_grid).reshape(-1, r_struct.size)
i_plasma = i_plasma.reshape(-1)

Check warning on line 154 in bluemira/equilibria/vertical_stability.py

View check run for this annotation

Codecov / codecov/patch

bluemira/equilibria/vertical_stability.py#L153-L154

Added lines #L153 - L154 were not covered by tests

# stabilising force/ destabilising force differentiated wrt to z coord
# f = -d_fs / d_fd
destabilising = np.einsum(

Check warning on line 158 in bluemira/equilibria/vertical_stability.py

View check run for this annotation

Codecov / codecov/patch

bluemira/equilibria/vertical_stability.py#L158

Added line #L158 was not covered by tests
"b, bd, d",
i_plasma,
(2 * np.pi * x_ac * dbrdz_struct_grid).reshape(-1, cc_current.size),
cc_current,
optimize=["einsum_path", (0, 1), (0, 1)],
)

stabilising = np.einsum(

Check warning on line 166 in bluemira/equilibria/vertical_stability.py

View check run for this annotation

Codecov / codecov/patch

bluemira/equilibria/vertical_stability.py#L166

Added line #L166 was not covered by tests
"d, db, bc, ac, a",
i_plasma,
msp_prime,
np.linalg.inv(mss),
msp_prime,
i_plasma,
optimize=["einsum_path", (0, 1), (1, 2), (0, 1), (0, 1)],
)

# from scipy.io import loadmat

# ml = loadmat("leuer_test.mat")
# import ipdb

# ipdb.set_trace()
# not infinite if destabilising is 0 because therefore it is stable
criterion = (-stabilising / destabilising) if destabilising != 0 else 0
bluemira_debug(f"{stabilising=}, {destabilising=}, {criterion=}")
return criterion

Check warning on line 185 in bluemira/equilibria/vertical_stability.py

View check run for this annotation

Codecov / codecov/patch

bluemira/equilibria/vertical_stability.py#L183-L185

Added lines #L183 - L185 were not covered by tests


# def mutual_ind(xc, zc, x, z):
# r"""File to compute the mutual inductance between two coaxial circular filaments,
# i.e. the planes of the two coils are parallel and they share the same central axis.
# (William R. Smythe: Static and Dynaimc Electricity Third Edition, p.335 equation 1)

# .. math::

# M_{12} &= 2\mu_0 k^{-1} (ab)^{1/2} \left[(1-\frac{1}{2}k^2)K-E\right]

# &= \frac{2 \mu_0 }{k} \sqrt{ab}\left[\left(1-\frac{k^2}{2}\right)K-E\right]

# where

# .. math::

# k^2 = \frac{4ab}{(a+b)^2 + z^2}

# Note the symmetry between :math:`a` and :math:`b` in the equations:
# :math:`a` and :math:`b` are commutable.

# Parameters
# ----------
# a, b : radii of coil 1 and coil 2 respectively [m]
# (denoted with :math:`a` and :math:`b` in the equation)

# z: separation between the planes of coils [m]

# Returns
# -------
# Mutual Inductance: unit: [N/m/A^2]
# """
# _, k2 = calc_a_k2(xc, zc, x, z)
# e, k = calc_e_k(k2)
# i_ch = np.nonzero(k2 == 1)
# calc_a_k2(xc, zc, x, z)
# # TODO: review this hack!
# k2[i_ch] = 0.1
# mut_ind = 2 * MU_0 / np.sqrt(k2) * np.sqrt(xc * zc) * ((1 - 0.5 * k2) * k - e)
# mut_ind[i_ch] = 0
# return mut_ind


def eigenv(res_ps, l_ps_ps, max_eigens=40):
inv_r = np.linalg.inv(res_ps[..., None] * np.eye(res_ps.shape[-1]))
inv_r_l = np.einsum("...ab, ...bc -> ...ac", inv_r, l_ps_ps)
eig_val, eig_vec = np.linalg.eig(np.asarray(inv_r_l, dtype=np.complex128))

Check warning on line 233 in bluemira/equilibria/vertical_stability.py

View check run for this annotation

Codecov / codecov/patch

bluemira/equilibria/vertical_stability.py#L230-L233

Added lines #L230 - L233 were not covered by tests

# sort (eig_vec_sort) into ascending importance of values (vectors Es and values Vs)
sort_ind = np.argsort(eig_val, axis=-1)[..., ::-1]
eig_val = eig_val[..., sort_ind].real
eig_vec = eig_vec[..., sort_ind].real
norm_res = 1 / np.sum( # TODO compare with rzip with multiple structures

Check warning on line 239 in bluemira/equilibria/vertical_stability.py

View check run for this annotation

Codecov / codecov/patch

bluemira/equilibria/vertical_stability.py#L236-L239

Added lines #L236 - L239 were not covered by tests
1 / np.atleast_2d(res_ps), axis=-1
)
norm_mat = np.einsum( # diagonalise

Check warning on line 242 in bluemira/equilibria/vertical_stability.py

View check run for this annotation

Codecov / codecov/patch

bluemira/equilibria/vertical_stability.py#L242

Added line #L242 was not covered by tests
"...aa -> ...a",
np.einsum( # arr1 @ arr2 @ inv(arr3.T)
"...ab, ...bc, ...cd -> ...ad",
norm_res * np.linalg.inv(eig_vec),
inv_r,
np.linalg.inv(np.einsum("...ij -> ...ji", eig_vec)),
optimize=["einsum_path", (0, 1), (0, 1)],
),
)
idx = np.nonzero(norm_mat >= 0)
norm_mat[idx] = np.sqrt(norm_mat[idx])
idx = np.nonzero(norm_mat < 0)
norm_mat[idx] = -np.sqrt(-norm_mat[idx])

Check warning on line 255 in bluemira/equilibria/vertical_stability.py

View check run for this annotation

Codecov / codecov/patch

bluemira/equilibria/vertical_stability.py#L252-L255

Added lines #L252 - L255 were not covered by tests

eig_val *= norm_res
eig_vec @= norm_mat[..., None] * np.eye(norm_mat.shape[-1])

Check warning on line 258 in bluemira/equilibria/vertical_stability.py

View check run for this annotation

Codecov / codecov/patch

bluemira/equilibria/vertical_stability.py#L257-L258

Added lines #L257 - L258 were not covered by tests

# eig_val = eig_val[..., :max_eigens]
# eig_vec = eig_vec[..., :max_eigens]
return eig_val * np.eye(eig_val.shape[0]), eig_vec

Check warning on line 262 in bluemira/equilibria/vertical_stability.py

View check run for this annotation

Codecov / codecov/patch

bluemira/equilibria/vertical_stability.py#L262

Added line #L262 was not covered by tests

# TODO inter structure interactions
eigen_vectors_2 = np.zeros((n_passive_fils, n_eigen_modes_max))
eigen_values_2 = np.zeros((1, n_eigen_modes_max))

for k in range(n_passive_struct):
passive_range = slice(passive_coil_ranges[k, 0], passive_coil_ranges[k, 1])
eigen_range_start = n_eigen_modes_array_max[k] if k > 0 else 0
eigen_range = slice(
eigen_range_start, n_eigen_modes_array_max[k] + eigen_range_start
)

# structure to structure terms
# TODO untested
for k2 in range(k + 1, n_passive_struct):
raise NotImplementedError(">1 passive structure not implemented")
passive_range2 = slice(
passive_coil_ranges[k2, 0], passive_coil_ranges[k2, 1]
)

eigen_range_start2 = sum(n_eigen_modes_array_max[:k2])
eigen_range2 = slice(
eigen_range_start2, n_eigen_modes_array_max[k2] + eigen_range_start2
)

et_me = (
eigen_vectors[k2, :, : n_eigen_modes_array_max[k]]
@ l_pf_pf[passive_range, passive_range2]
) @ eigen_vectors[k2, :, : n_eigen_modes_array_max[k2]]
eigen_values_2[k, eigen_range, eigen_range2] = et_me
eigen_values_2[k, eigen_range2, eigen_range] = et_me

# TODO if there is more than 1 passive structure it will break
eigen_values_2[eigen_range] = eigen_values[k]
eigen_vectors_2[passive_range, eigen_range] = eigen_vectors[k]

eigen_vectors = eigen_vectors_2
eigen_values = eigen_values_2

return eigen_values, eigen_vectors

0 comments on commit df2e526

Please sign in to comment.