Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: polyhedral cross-section current sources and a general VIM formulation of 3-D magnetostatics #2703

Merged
merged 58 commits into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
e65ee13
baseclasses and housekeeping
CoronelBuendia Oct 11, 2023
5fdf8d0
pause because PROCESS
CoronelBuendia Oct 11, 2023
c9be43a
6 hours
CoronelBuendia Oct 11, 2023
617acbf
write asserts
CoronelBuendia Oct 11, 2023
d9f47cb
vector potential
CoronelBuendia Oct 11, 2023
b1a0550
add failing babic test
CoronelBuendia Oct 12, 2023
8c2275f
add singularity protection
CoronelBuendia Oct 12, 2023
2453a37
add singularity protection
CoronelBuendia Oct 12, 2023
1abeca7
typing
CoronelBuendia Oct 12, 2023
44b3976
add plotting to Babic Aykel test
CoronelBuendia Oct 12, 2023
912b8c0
baseclass shenanigans
CoronelBuendia Oct 12, 2023
79a5d4f
DRY mixin
CoronelBuendia Oct 12, 2023
7df8ef5
working arbirtrary geometry
CoronelBuendia Oct 12, 2023
9bd9c0e
clean up
CoronelBuendia Oct 12, 2023
d142dec
clean up
CoronelBuendia Oct 12, 2023
d272fe7
kill some list reflection but still not fully JIT
CoronelBuendia Oct 12, 2023
c665876
fix signatures
CoronelBuendia Oct 12, 2023
2d44aaf
add triangle test
CoronelBuendia Oct 12, 2023
1b5a8c4
rotate midpoints too
CoronelBuendia Oct 12, 2023
77c94e4
comments and clearer code
CoronelBuendia Oct 12, 2023
35f3066
add some docstring equations
CoronelBuendia Oct 12, 2023
ade2452
more docstring equations
CoronelBuendia Oct 12, 2023
ed405a0
parametrise babic aykel test
CoronelBuendia Oct 13, 2023
16f1edf
more source doucmentation
CoronelBuendia Oct 13, 2023
aa523dd
parametrise maths test
CoronelBuendia Oct 13, 2023
1598cbd
remove useless baseclass and make everything private
CoronelBuendia Oct 13, 2023
c2f688c
make more stuff private
CoronelBuendia Oct 13, 2023
2742e37
make stuff private everywhere
CoronelBuendia Oct 13, 2023
87fefcd
better file docstring
CoronelBuendia Oct 13, 2023
57f8475
biot savart private points
CoronelBuendia Oct 16, 2023
fe0bd2e
fix some polyhedral tests
CoronelBuendia Oct 16, 2023
247a9ed
add trapezoidal test
CoronelBuendia Oct 16, 2023
a5bddca
add polyhedral test... that fails too
CoronelBuendia Oct 16, 2023
e980ea2
add polyhedral test... that fails too
CoronelBuendia Oct 16, 2023
1479ad6
add polyhedral test... that fails too
CoronelBuendia Oct 16, 2023
137b242
add temporary sanity check
CoronelBuendia Oct 16, 2023
c01075f
add geometry test
CoronelBuendia Oct 16, 2023
ce863a6
added test to check whether combined shape matching single shape (#2718)
kj5248 Oct 18, 2023
44593f0
Add an alternative polyhedral VIM formulation (#2731)
CoronelBuendia Oct 20, 2023
47c27e6
Add `ArbitraryPlanarPolyhedralXSCircuit` (#2704)
CoronelBuendia Oct 20, 2023
a7300ad
fix tests and add error for angles
CoronelBuendia Dec 12, 2023
13a0db4
better error
CoronelBuendia Dec 12, 2023
6b2c1b2
add vector potential flux test
CoronelBuendia Dec 12, 2023
ef30299
remove sanity check
CoronelBuendia Dec 12, 2023
7d89bc1
docstring change
CoronelBuendia Dec 12, 2023
69a0fef
copyright and remove tools
CoronelBuendia Dec 12, 2023
6d9b140
sonarcloud bug
CoronelBuendia Dec 12, 2023
f52b4c1
Add test using ArbitraryPlanarPolyhedralXSCircuit for a 2D ring coil …
kj5248 Dec 13, 2023
542671c
`PolyhedralPrismCurrentSource` documentation (#2705)
CoronelBuendia Dec 13, 2023
8641472
code smells
CoronelBuendia Dec 13, 2023
cbe4ea0
JIT `PolyhedralPrism` even more, and remove reflected lists (#2874)
CoronelBuendia Dec 14, 2023
ebe2a3d
plt show/close is automatic now
je-cook Jan 17, 2024
a9a1c74
🎨 Ruff
je-cook Jan 18, 2024
45ebcf9
make tests check symmetry of field
CoronelBuendia Jan 24, 2024
464c742
resolve numba warning
CoronelBuendia Jan 24, 2024
81e35f1
trigger ruff
CoronelBuendia Jan 24, 2024
4d48ae9
run precommit
CoronelBuendia Jan 24, 2024
97f6e28
fix sonarcloud issue
CoronelBuendia Jan 24, 2024
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
116 changes: 91 additions & 25 deletions bluemira/magnetostatics/baseclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@

from bluemira.geometry.bound_box import BoundingBox
from bluemira.geometry.coordinates import rotation_matrix
from bluemira.magnetostatics.error import MagnetostaticsError
from bluemira.utilities.plot_tools import Plot3D

__all__ = ["CurrentSource", "RectangularCrossSectionCurrentSource", "SourceGroup"]
__all__ = ["CurrentSource", "CrossSectionCurrentSource", "SourceGroup"]


class CurrentSource(ABC):
Expand Down Expand Up @@ -99,17 +100,16 @@
return deepcopy(self)


class RectangularCrossSectionCurrentSource(CurrentSource):
class CrossSectionCurrentSource(CurrentSource):
"""
Abstract base class for a current source with a rectangular cross-section.
Abstract class for a current source with a cross-section
"""

origin: np.array
dcm: np.array
points: np.array
breadth: float
depth: float
length: float
_origin: np.array
_dcm: np.array
_points: np.array
_rho: float
_area: float

def set_current(self, current: float):
"""
Expand All @@ -121,7 +121,7 @@
The current of the source [A]
"""
super().set_current(current)
self.rho = current / (4 * self.breadth * self.depth)
self._rho = current / self._area

def rotate(self, angle: float, axis: Union[np.ndarray, str]):
"""
Expand All @@ -135,21 +135,21 @@
The axis of rotation
"""
r = rotation_matrix(np.deg2rad(angle), axis).T
self.origin = self.origin @ r
self.points = np.array([p @ r for p in self.points], dtype=object)
self.dcm = self.dcm @ r
self._origin = self._origin @ r
self._points = np.array([p @ r for p in self._points], dtype=object)
self._dcm = self._dcm @ r

def _local_to_global(self, points: np.ndarray) -> np.ndarray:
"""
Convert local x', y', z' point coordinates to global x, y, z point coordinates.
"""
return np.array([self.origin + self.dcm.T @ p for p in points])
return np.array([self._origin + self._dcm.T @ p for p in points])

def _global_to_local(self, points: np.ndarray) -> np.ndarray:
"""
Convert global x, y, z point coordinates to local x', y', z' point coordinates.
"""
return np.array([(self.dcm @ (p - self.origin)) for p in points])
return np.array([(self._dcm @ (p - self._origin)) for p in points])

def plot(self, ax: Optional[Axes] = None, show_coord_sys: bool = False):
"""
Expand All @@ -166,21 +166,87 @@
ax = Plot3D()
# If no ax provided, we assume that we want to plot only this source,
# and thus set aspect ratio equality on this term only
edge_points = np.concatenate(self.points)
edge_points = np.concatenate(self._points)

# Invisible bounding box to set equal aspect ratio plot
xbox, ybox, zbox = BoundingBox.from_xyz(*edge_points.T).get_box_arrays()
ax.plot(1.1 * xbox, 1.1 * ybox, 1.1 * zbox, "s", alpha=0)

for points in self.points:
for points in self._points:
ax.plot(*points.T, color="b", linewidth=1)

# Plot local coordinate system
if show_coord_sys:
ax.scatter(*self.origin, color="k")
ax.quiver(*self.origin, *self.dcm[0], length=self.breadth, color="r")
ax.quiver(*self.origin, *self.dcm[1], length=self.length, color="r")
ax.quiver(*self.origin, *self.dcm[2], length=self.depth, color="r")
ax.scatter(*self._origin, color="k")
ax.quiver(*self._origin, *self._dcm[0], length=1, color="r")
ax.quiver(*self._origin, *self._dcm[1], length=1, color="r")
ax.quiver(*self._origin, *self._dcm[2], length=1, color="r")

Check warning on line 183 in bluemira/magnetostatics/baseclass.py

View check run for this annotation

Codecov / codecov/patch

bluemira/magnetostatics/baseclass.py#L180-L183

Added lines #L180 - L183 were not covered by tests


class PolyhedralCrossSectionCurrentSource(CrossSectionCurrentSource):
"""
Abstract base class for a current source with a polyhedral cross-section.
"""

_face_points: np.ndarray
_face_normals: np.ndarray
_mid_points: np.ndarray

def rotate(self, angle: float, axis: Union[np.ndarray, str]):
"""
Rotate the CurrentSource about an axis.

Parameters
----------
angle:
The rotation degree [degree]
axis:
The axis of rotation
"""
super().rotate(angle, axis)
r = rotation_matrix(np.deg2rad(angle), axis).T
self._face_normals = np.array([n @ r for n in self._face_normals])
self._face_points = np.array([p @ r for p in self._face_points])
self._mid_points = np.array([p @ r for p in self._mid_points])

Check warning on line 210 in bluemira/magnetostatics/baseclass.py

View check run for this annotation

Codecov / codecov/patch

bluemira/magnetostatics/baseclass.py#L206-L210

Added lines #L206 - L210 were not covered by tests


class PrismEndCapMixin:
def _check_angle_values(self, alpha, beta):
"""
Check that end-cap angles are acceptable.
"""
sign_alpha = np.sign(alpha)
sign_beta = np.sign(beta)
one_zero = np.any(np.array([sign_alpha, sign_beta]) == 0)
if not one_zero and sign_alpha != sign_beta:
raise MagnetostaticsError(
f"{self.__class__.__name__} instantiation error: end-cap angles "
f"must have the same sign {alpha=:.3f}, {beta=:.3f}."
)
if not (0 <= abs(alpha) < 0.5 * np.pi):
raise MagnetostaticsError(
f"{self.__class__.__name__} instantiation error: {alpha=:.3f} is outside"
" bounds of [0, 180°)."
)
if not (0 <= abs(beta) < 0.5 * np.pi):
raise MagnetostaticsError(

Check warning on line 232 in bluemira/magnetostatics/baseclass.py

View check run for this annotation

Codecov / codecov/patch

bluemira/magnetostatics/baseclass.py#L232

Added line #L232 was not covered by tests
f"{self.__class__.__name__} instantiation error: {beta=:.3f} is outside "
"bounds of [0, 180°)."
)

def _check_raise_self_intersection(
self, length: float, breadth: float, alpha: float, beta: float
):
"""
Check for bad combinations of source length and end-cap angles.
"""
a = np.tan(alpha) * breadth
b = np.tan(beta) * breadth
if (a + b) > length:
raise MagnetostaticsError(
f"{self.__class__.__name__} instantiation error: source length and "
"angles imply a self-intersecting trapezoidal prism."
)


class SourceGroup(ABC):
Expand All @@ -189,11 +255,11 @@
"""

sources: List[CurrentSource]
points: np.array
_points: np.array

def __init__(self, sources: List[CurrentSource]):
self.sources = sources
self.points = np.vstack([np.vstack(s.points) for s in self.sources])
self._points = np.vstack([np.vstack(s._points) for s in self.sources])

def set_current(self, current: float):
"""
Expand Down Expand Up @@ -244,7 +310,7 @@
"""
for source in self.sources:
source.rotate(angle, axis)
self.points = self.points @ rotation_matrix(angle, axis)
self._points = self._points @ rotation_matrix(angle, axis)

def plot(self, ax: Optional[Axes] = None, show_coord_sys: bool = False):
"""
Expand All @@ -261,7 +327,7 @@
ax = Plot3D()

# Invisible bounding box to set equal aspect ratio plot
xbox, ybox, zbox = BoundingBox.from_xyz(*self.points.T).get_box_arrays()
xbox, ybox, zbox = BoundingBox.from_xyz(*self._points.T).get_box_arrays()
ax.plot(1.1 * xbox, 1.1 * ybox, 1.1 * zbox, "s", alpha=0)

for source in self.sources:
Expand Down
42 changes: 21 additions & 21 deletions bluemira/magnetostatics/biot_savart.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,12 @@
self.length_scale = np.min(lengths)

# Assemble arrays and vector
self.d_l = np.vstack(d_ls)
self.d_l_hat = np.linalg.norm(self.d_l, axis=1)
self.mid_points = np.vstack(mids_points)
self.points = np.vstack(points)
self._d_l = np.vstack(d_ls)
self._d_l_hat = np.linalg.norm(self._d_l, axis=1)
self._mid_points = np.vstack(mids_points)
self._points = np.vstack(points)
self._arrays = arrays
self.radius = radius
self._radius = radius
self.current = current

@staticmethod
Expand Down Expand Up @@ -124,16 +124,16 @@
The vector potential at the point due to the arbitrarily shaped Coordinates
"""
point = np.array([x, y, z])
r = point - self.points
r = point - self._points

Check warning on line 127 in bluemira/magnetostatics/biot_savart.py

View check run for this annotation

Codecov / codecov/patch

bluemira/magnetostatics/biot_savart.py#L127

Added line #L127 was not covered by tests
r_mag = tools.norm(r, axis=1)
r_mag[r_mag < EPS] = EPS
core = r_mag / self.radius
core[r_mag > self.radius] = 1
core = r_mag / self._radius
core[r_mag > self._radius] = 1

Check warning on line 131 in bluemira/magnetostatics/biot_savart.py

View check run for this annotation

Codecov / codecov/patch

bluemira/magnetostatics/biot_savart.py#L130-L131

Added lines #L130 - L131 were not covered by tests

# The below einsum operation is equivalent to:
# self.current * np.sum(core * self.d_l.T / r_mag, axis=0) / (4 * np.pi)
return np.einsum(
"i, ji, ... -> j", core, self.d_l / r_mag[None], ONE_4PI * self.current
"i, ji, ... -> j", core, self._d_l / r_mag[None], ONE_4PI * self.current
)

@process_xyz_array
Expand Down Expand Up @@ -167,18 +167,18 @@
smoothing. Do not use for values near the coil current centreline.
""" # noqa: W505, E501
point = np.array([x, y, z])
r = point - self.mid_points
r = point - self._mid_points
r3 = np.linalg.norm(r, axis=1) ** 3

ds = np.cross(self.d_l, r)
ds = np.cross(self._d_l, r)

# Coil core correction
d_l_hat = self.d_l_hat[:, None]
d_l_hat = self._d_l_hat[:, None]
ds_mag = np.linalg.norm(ds / d_l_hat, axis=1)
ds_mag = np.tile(ds_mag, (3, 1)).T
ds_mag[ds_mag < EPS] = EPS
core = ds_mag**2 / self.radius**2
core[ds_mag > self.radius] = 1
core = ds_mag**2 / self._radius**2
core[ds_mag > self._radius] = 1
return MU_0_4PI * self.current * np.sum(core * ds / r3[:, np.newaxis], axis=0)

def inductance(self) -> float:
Expand All @@ -203,16 +203,16 @@
inductance = 0
for _i, (x1, dx1) in enumerate(zip(self.ref_mid_points, self.ref_d_l)):
# We create a mask to drop the point where x1 == x2
r = x1 - self.mid_points
r = x1 - self._mid_points
mask = np.sum(r**2, axis=1) > 0.5 * self.length_scale
inductance += np.sum(
np.dot(dx1, self.d_l[mask].T) / np.linalg.norm(r[mask], axis=1)
np.dot(dx1, self._d_l[mask].T) / np.linalg.norm(r[mask], axis=1)
)

# Self-inductance correction (Y = 0.5 for homogenous current distribution)
# Equation 6 of https://arxiv.org/pdf/1204.1486.pdf
error_tail = 0
a, b = self.radius, 0.5 * self.length_scale
a, b = self._radius, 0.5 * self.length_scale
if b > 10 * a:
# Equation A.4 of https://arxiv.org/pdf/1204.1486.pdf
error_tail = a**2 / b**2 - 3 / (8 * b**4) * (a**4 - 2 * a**2)
Expand All @@ -232,9 +232,9 @@
The axis of rotation
"""
r = rotation_matrix(np.deg2rad(angle), axis).T
self.points = self.points @ r
self.d_l = self.d_l @ r
self.mid_points = self.mid_points @ r
self._points = self._points @ r
self._d_l = self._d_l @ r
self._mid_points = self._mid_points @ r
self.ref_d_l = self.ref_d_l @ r
self.ref_mid_points = self.ref_mid_points @ r
self._arrays = [array @ r for array in self._arrays]
Expand All @@ -255,7 +255,7 @@
# If no ax provided, we assume that we want to plot only this source,
# and thus set aspect ratio equality on this term only
# Invisible bounding box to set equal aspect ratio plot
xbox, ybox, zbox = BoundingBox.from_xyz(*self.points.T).get_box_arrays()
xbox, ybox, zbox = BoundingBox.from_xyz(*self._points.T).get_box_arrays()
ax.plot(1.1 * xbox, 1.1 * ybox, 1.1 * zbox, "s", alpha=0)

for array in self._arrays:
Expand Down
Loading