Magnets module
pymagnet.magnets
This module imports the classes and functions in the private modules to create a public API.
Magnet Base class
This private module implements the registry and base magnet classes
Magnet (Registry)
Magnet base class
Returns:
Type | Description |
---|---|
Magnet |
magnet base class |
Source code in pymagnet/magnets/_magnet_base.py
class Magnet(Registry):
"""Magnet base class
Returns:
Magnet: magnet base class
"""
tol = MAG_TOL # tolerance for rotations, sufficient for 0.01 degree accuracy
mag_type = "Magnet"
def __init__(self, *args, **kwargs):
super().__init__()
self.center = _np.array([0.0, 0.0])
Registry
Registry class for tracking instances
Instances are tracked in class.instances
using weak references.
This also includes any instances that are deleted manually or go out of
scope.
Class methods:
`print_instances()`
`get_instances()`
`get_num_instances()`
`reset()
Source code in pymagnet/magnets/_magnet_base.py
class Registry:
"""Registry class for tracking instances
Instances are tracked in `class.instances` using weak references.
This also includes any instances that are deleted manually or go out of
scope.
Class methods:
`print_instances()`
`get_instances()`
`get_num_instances()`
`reset()
"""
instances = WeakSet()
_class_instances = []
def __new__(cls, *args, **kwargs):
o = object.__new__(cls)
cls._register_instance(o)
return o
def __init__(self, *args, **kwargs) -> None:
super().__init__()
self.__class__._class_instances.append(self)
def __del__(self) -> None:
if self in self.__class__._class_instances:
self.__class__._class_instances.remove(self)
@classmethod
def print_instances(cls):
"""Prints class instantiations"""
if len(cls.instances) < 1:
print("No Instances")
else:
for instance in cls.instances:
print(instance)
@classmethod
def get_instances(cls):
"""Gets lists of instances
Returns:
set: all class instances
"""
return cls.instances
@classmethod
def get_num_instances(cls, Print_Val=False):
"""Return number of instances of class
Args:
Print_Val (bool, optional): [Print to screen]. Defaults to False.
Returns:
num_instances [int]:
"""
if Print_Val:
print(len(cls.instances))
return len(cls.instances)
@classmethod
def _register_instance(cls, instance):
"""Adds class instance to registry
Args:
instance (instance): class instance
"""
cls.instances.add(instance)
for b in cls.__bases__:
if issubclass(b, Registry):
b._register_instance(instance)
@classmethod
def reset(cls):
"""Removes all instances from registry."""
for magnet in cls._class_instances:
del magnet
cls.instances = WeakSet()
cls._class_instances = []
def __init_subclass__(cls):
cls.instances = WeakSet()
cls._class_instances = []
__init_subclass__()
classmethod
special
This method is called when a class is subclassed.
The default implementation does nothing. It may be overridden to extend subclasses.
Source code in pymagnet/magnets/_magnet_base.py
def __init_subclass__(cls):
cls.instances = WeakSet()
cls._class_instances = []
__new__(cls, *args, **kwargs)
special
staticmethod
Create and return a new object. See help(type) for accurate signature.
Source code in pymagnet/magnets/_magnet_base.py
def __new__(cls, *args, **kwargs):
o = object.__new__(cls)
cls._register_instance(o)
return o
get_instances()
classmethod
Gets lists of instances
Returns:
Type | Description |
---|---|
set |
all class instances |
Source code in pymagnet/magnets/_magnet_base.py
@classmethod
def get_instances(cls):
"""Gets lists of instances
Returns:
set: all class instances
"""
return cls.instances
get_num_instances(Print_Val=False)
classmethod
Return number of instances of class
Parameters:
Name | Type | Description | Default |
---|---|---|---|
Print_Val |
bool |
[Print to screen]. Defaults to False. |
False |
Returns:
Type | Description |
---|---|
num_instances [int] |
Source code in pymagnet/magnets/_magnet_base.py
@classmethod
def get_num_instances(cls, Print_Val=False):
"""Return number of instances of class
Args:
Print_Val (bool, optional): [Print to screen]. Defaults to False.
Returns:
num_instances [int]:
"""
if Print_Val:
print(len(cls.instances))
return len(cls.instances)
print_instances()
classmethod
Prints class instantiations
Source code in pymagnet/magnets/_magnet_base.py
@classmethod
def print_instances(cls):
"""Prints class instantiations"""
if len(cls.instances) < 1:
print("No Instances")
else:
for instance in cls.instances:
print(instance)
reset()
classmethod
Removes all instances from registry.
Source code in pymagnet/magnets/_magnet_base.py
@classmethod
def reset(cls):
"""Removes all instances from registry."""
for magnet in cls._class_instances:
del magnet
cls.instances = WeakSet()
cls._class_instances = []
list()
Returns a list of all instantiated magnets.
Assumes that the child class registries have not been modified outside of
using pymagnet.reset_magnets()
.
Source code in pymagnet/magnets/_magnet_base.py
def list():
"""Returns a list of all instantiated magnets.
Assumes that the child class registries have not been modified outside of
using `pymagnet.reset_magnets()`.
"""
return Magnet.print_instances()
reset()
Returns a list of all instantiated magnets.
Source code in pymagnet/magnets/_magnet_base.py
def reset():
"""Returns a list of all instantiated magnets."""
from ._magnet2D import Circle, Magnet2D, Rectangle, Square
from ._magnet3D import Cube, Cylinder, Magnet3D, Prism, Sphere
from ._polygon2D import PolyMagnet
from ._polygon3D import Mesh
magnet_classes = [
# Registry,
Magnet,
Magnet2D,
Rectangle,
Square,
Circle,
PolyMagnet,
Magnet3D,
Prism,
Cube,
Cylinder,
Sphere,
Mesh,
]
for cls in magnet_classes:
cls.reset()
1D High symmetry methods
This private module implements magnetic field calculations in z along the symmetry centre of a cylindrical or cuboidal magnet.
magnetic_field_cylinder_1D(magnet, z)
Calculates the magnetic field z-component due to a cuboid along its axial symmetry center.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
magnet |
Magnet3D |
magnet object, Cylinder |
required |
z |
ndarray |
Array of points along the symmetry axis |
required |
Returns:
Type | Description |
---|---|
Field1 |
z-component of the magnetic field and associated unit ('T') |
Source code in pymagnet/magnets/_magnet1D.py
def magnetic_field_cylinder_1D(magnet, z):
"""Calculates the magnetic field z-component due to a cuboid along its
axial symmetry center.
Args:
magnet (Magnet3D): magnet object, Cylinder
z (ndarray): Array of points along the symmetry axis
Returns:
Field1: z-component of the magnetic field and associated unit ('T')
"""
from ._magnet3D import Cylinder
if issubclass(magnet.__class__, Cylinder):
L = magnet.length
R = magnet.radius
Jr = magnet.Jr
z_local = _np.asarray(z) - magnet.length / 2 - magnet.center[2]
zL = z_local + L
R_sq = _np.power(R, 2)
z_sq = _np.power(z_local, 2)
zL_sq = _np.power(zL, 2)
Bz = (zL / _np.sqrt(zL_sq + R_sq)) - (z_local / _np.sqrt(z_sq + R_sq))
Bz *= Jr / 2
data = Field1(Bz)
return data
else:
print(f"Error, the magnet should be a 3D magnet not {magnet.__class__}")
return None
magnetic_field_prism_1D(magnet, z)
Calculates the magnetic field z-component due to a cuboid along its axial symmetry center.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
magnet |
Magnet3D |
magnet object, one of Prism, or Cube |
required |
z |
ndarray |
Array of points along the symmetry axis |
required |
Returns:
Type | Description |
---|---|
Field1 |
z-component of the magnetic field and associated unit ('T') |
Source code in pymagnet/magnets/_magnet1D.py
def magnetic_field_prism_1D(magnet, z):
"""Calculates the magnetic field z-component due to a cuboid along its
axial symmetry center.
Args:
magnet (Magnet3D): magnet object, one of Prism, or Cube
z (ndarray): Array of points along the symmetry axis
Returns:
Field1: z-component of the magnetic field and associated unit ('T')
"""
from ._magnet3D import Prism
if issubclass(magnet.__class__, Prism):
a = magnet.a
b = magnet.b
c = magnet.c
Jr = magnet.Jr
z_local = _np.asarray(z) - c - magnet.center[2]
ab = a * b
a_sq = _np.power(a, 2)
b_sq = _np.power(b, 2)
z_sq = _np.power(z_local, 2)
zc = z_local + 2 * c
zc_sq = _np.power(zc, 2)
Bz = _np.arctan2(zc * _np.sqrt(a_sq + b_sq + zc_sq), ab) - _np.arctan2(
z_local * _np.sqrt(a_sq + b_sq + z_sq), ab
)
Bz *= Jr / PI
field = Field1(Bz)
return field
else:
print(f"Error, the magnet should be a 3D magnet not {magnet.__class__}")
return None
2D Magnet Classes
This private module implements the Rectangle
and Square
and Circle
2D magnet classes. The parent class Magnet2D
implements the location and orientation
methods, i.e. magnet center and quaternion methods for rotating the magnet with respect
to each principal axis.
Circle (Magnet2D)
Circle 2D Magnet Class
Source code in pymagnet/magnets/_magnet2D.py
class Circle(Magnet2D):
"""Circle 2D Magnet Class"""
mag_type = "Circle"
def __init__(
self,
radius=10,
Jr=1.0, # local magnetisation
**kwargs,
):
"""Init Method
Args:
radius (float, optional): Radius. Defaults to 10.0.
Jr (float, optional): Remnant magnetisation. Defaults to 1.0.
Kwargs:
alpha (float): Unused. For rotations use phi instead
center (tuple or ndarray): magnet center (x, y). Defaults to (0,0)
phi (float): Rotation Angle (in degrees) of magnet w.r.t x-axis. Defaults to 90.
"""
super().__init__(Jr, **kwargs)
self.radius = radius
self.phi = kwargs.pop("phi", 0)
self.phi_rad = _np.deg2rad(self.phi)
self.Jx = _np.around(Jr * _np.cos(self.phi_rad), decimals=6)
self.Jy = _np.around(Jr * _np.sin(self.phi_rad), decimals=6)
self.tol = MAG_TOL # sufficient for 0.01 degree accuracy
self.center = kwargs.pop("center", _np.array([0.0, 0.0]))
self.center = _np.asarray(self.center)
def __str__(self):
str = (
f"{self.__class__.mag_type}\n"
+ f"J: {self.get_Jr()} (T)\n"
+ f"Size: {self.get_size()}\n"
+ f"Center {self.get_center()}\n"
+ f"Orientation: alpha {self.get_orientation()}\n"
)
return str
def __repr__(self):
str = (
f"{self.__class__.mag_type}\n"
+ f"J: {self.get_Jr()} (T)\n"
+ f"Size: {self.get_size()}\n"
+ f"Center {self.get_center()}\n"
+ f"Orientation: alpha {self.get_orientation()}\n"
)
return str
def get_size(self):
"""Returns radius
Returns:
ndarray: radius
"""
return _np.array([self.radius])
def get_Jr(self):
"""Returns signed remnant magnetisation
Returns:
ndarray: remnant magnetisation
"""
return _np.array([self.Jx, self.Jy])
def get_field(self, x, y):
"""Calculates the magnetic field due to long bipolar cylinder
Args:
x (ndarray): x coordinates
y (ndarray): y coordinates
Returns:
tuple: Bx, By magnetic field in cartesian coordinates
"""
from ..utils._conversions import cart2pol, vector_pol2cart
from ..utils._routines2D import rotate_points_2D
if _np.fabs(self.alpha_radians) > Magnet2D.tol:
xi, yi = rotate_points_2D(
x - self.center[0], y - self.center[1], self.alpha_radians
)
rho, phi = cart2pol(xi, yi)
Brho, Bphi = self._calcB_polar(rho, phi - self.phi_rad)
# Convert magnetic fields from cylindrical to cartesian
Bx, By = vector_pol2cart(Brho, Bphi, phi)
Bx, By = rotate_points_2D(Bx, By, 2 * PI - self.alpha_radians)
return Bx, By
rho, phi = cart2pol(x - self.center[0], y - self.center[1])
Brho, Bphi = self._calcB_polar(rho, phi - self.phi_rad)
# Convert magnetic fields from cylindrical to cartesian
Bx, By = vector_pol2cart(Brho, Bphi, phi)
return Bx, By
def _calcB_polar(self, rho, phi):
"""Calculates the magnetic field due to long bipolar cylinder in polar
coordinates
Args:
rho (ndarray): radial values
phi (ndarray): azimuthal values
Returns:
tuple: Br, Bphi magnetic field in polar coordinates
"""
prefac = self.Jr * (self.radius ** 2 / rho ** 2) / 2
Brho = prefac * _np.cos(phi)
Bphi = prefac * _np.sin(phi)
return Brho, Bphi
__init__(self, radius=10, Jr=1.0, **kwargs)
special
Init Method
Parameters:
Name | Type | Description | Default |
---|---|---|---|
radius |
float |
Radius. Defaults to 10.0. |
10 |
Jr |
float |
Remnant magnetisation. Defaults to 1.0. |
1.0 |
Kwargs
alpha (float): Unused. For rotations use phi instead center (tuple or ndarray): magnet center (x, y). Defaults to (0,0) phi (float): Rotation Angle (in degrees) of magnet w.r.t x-axis. Defaults to 90.
Source code in pymagnet/magnets/_magnet2D.py
def __init__(
self,
radius=10,
Jr=1.0, # local magnetisation
**kwargs,
):
"""Init Method
Args:
radius (float, optional): Radius. Defaults to 10.0.
Jr (float, optional): Remnant magnetisation. Defaults to 1.0.
Kwargs:
alpha (float): Unused. For rotations use phi instead
center (tuple or ndarray): magnet center (x, y). Defaults to (0,0)
phi (float): Rotation Angle (in degrees) of magnet w.r.t x-axis. Defaults to 90.
"""
super().__init__(Jr, **kwargs)
self.radius = radius
self.phi = kwargs.pop("phi", 0)
self.phi_rad = _np.deg2rad(self.phi)
self.Jx = _np.around(Jr * _np.cos(self.phi_rad), decimals=6)
self.Jy = _np.around(Jr * _np.sin(self.phi_rad), decimals=6)
self.tol = MAG_TOL # sufficient for 0.01 degree accuracy
self.center = kwargs.pop("center", _np.array([0.0, 0.0]))
self.center = _np.asarray(self.center)
get_Jr(self)
Returns signed remnant magnetisation
Returns:
Type | Description |
---|---|
ndarray |
remnant magnetisation |
Source code in pymagnet/magnets/_magnet2D.py
def get_Jr(self):
"""Returns signed remnant magnetisation
Returns:
ndarray: remnant magnetisation
"""
return _np.array([self.Jx, self.Jy])
get_field(self, x, y)
Calculates the magnetic field due to long bipolar cylinder
Parameters:
Name | Type | Description | Default |
---|---|---|---|
x |
ndarray |
x coordinates |
required |
y |
ndarray |
y coordinates |
required |
Returns:
Type | Description |
---|---|
tuple |
Bx, By magnetic field in cartesian coordinates |
Source code in pymagnet/magnets/_magnet2D.py
def get_field(self, x, y):
"""Calculates the magnetic field due to long bipolar cylinder
Args:
x (ndarray): x coordinates
y (ndarray): y coordinates
Returns:
tuple: Bx, By magnetic field in cartesian coordinates
"""
from ..utils._conversions import cart2pol, vector_pol2cart
from ..utils._routines2D import rotate_points_2D
if _np.fabs(self.alpha_radians) > Magnet2D.tol:
xi, yi = rotate_points_2D(
x - self.center[0], y - self.center[1], self.alpha_radians
)
rho, phi = cart2pol(xi, yi)
Brho, Bphi = self._calcB_polar(rho, phi - self.phi_rad)
# Convert magnetic fields from cylindrical to cartesian
Bx, By = vector_pol2cart(Brho, Bphi, phi)
Bx, By = rotate_points_2D(Bx, By, 2 * PI - self.alpha_radians)
return Bx, By
rho, phi = cart2pol(x - self.center[0], y - self.center[1])
Brho, Bphi = self._calcB_polar(rho, phi - self.phi_rad)
# Convert magnetic fields from cylindrical to cartesian
Bx, By = vector_pol2cart(Brho, Bphi, phi)
return Bx, By
get_size(self)
Returns radius
Returns:
Type | Description |
---|---|
ndarray |
radius |
Source code in pymagnet/magnets/_magnet2D.py
def get_size(self):
"""Returns radius
Returns:
ndarray: radius
"""
return _np.array([self.radius])
Magnet2D (Magnet)
2D Magnet Base Class
Source code in pymagnet/magnets/_magnet2D.py
class Magnet2D(Magnet):
"""2D Magnet Base Class"""
mag_type = "Magnet2D"
def __init__(self, Jr, **kwargs) -> None:
"""Init Method
Args:
Jr (float): signed magnetised of remnant magnetisation
Kwargs:
alpha (float): Magnetisation orientation angle (in degrees). Defaults to 0.
center (tuple or ndarray): magnet center (x, y). Defaults to (0,0).
"""
super().__init__()
self.Jr = Jr
# Magnet rotation w.r.t. x-axis
self.alpha = kwargs.pop("alpha", 0.0)
self.alpha_radians = _np.deg2rad(self.alpha)
self.center = kwargs.pop("center", _np.array([0.0, 0.0]))
self.center = _np.asarray(self.center)
def get_center(self):
"""Returns magnet centre
Returns:
center (ndarray): numpy array [xc, yc]
"""
return self.center
def get_orientation(self):
"""Returns magnet orientation, `alpha` in degrees
Returns:
float: alpha, rotation angle w.r.t x-axis.
"""
return self.alpha
def get_field(self):
"""Calculates the magnetic field.
This is a template that needs to be implemented for each magnet
"""
pass
def get_force_torque(self):
"""Calculates the force and torque on a magnet due to all other magnets.
This is a template that needs to be implemented for each magnet.
"""
pass
__init__(self, Jr, **kwargs)
special
Init Method
Parameters:
Name | Type | Description | Default |
---|---|---|---|
Jr |
float |
signed magnetised of remnant magnetisation |
required |
Kwargs
alpha (float): Magnetisation orientation angle (in degrees). Defaults to 0. center (tuple or ndarray): magnet center (x, y). Defaults to (0,0).
Source code in pymagnet/magnets/_magnet2D.py
def __init__(self, Jr, **kwargs) -> None:
"""Init Method
Args:
Jr (float): signed magnetised of remnant magnetisation
Kwargs:
alpha (float): Magnetisation orientation angle (in degrees). Defaults to 0.
center (tuple or ndarray): magnet center (x, y). Defaults to (0,0).
"""
super().__init__()
self.Jr = Jr
# Magnet rotation w.r.t. x-axis
self.alpha = kwargs.pop("alpha", 0.0)
self.alpha_radians = _np.deg2rad(self.alpha)
self.center = kwargs.pop("center", _np.array([0.0, 0.0]))
self.center = _np.asarray(self.center)
get_center(self)
Returns magnet centre
Returns:
Type | Description |
---|---|
center (ndarray) |
numpy array [xc, yc] |
Source code in pymagnet/magnets/_magnet2D.py
def get_center(self):
"""Returns magnet centre
Returns:
center (ndarray): numpy array [xc, yc]
"""
return self.center
get_field(self)
Calculates the magnetic field.
This is a template that needs to be implemented for each magnet
Source code in pymagnet/magnets/_magnet2D.py
def get_field(self):
"""Calculates the magnetic field.
This is a template that needs to be implemented for each magnet
"""
pass
get_force_torque(self)
Calculates the force and torque on a magnet due to all other magnets.
This is a template that needs to be implemented for each magnet.
Source code in pymagnet/magnets/_magnet2D.py
def get_force_torque(self):
"""Calculates the force and torque on a magnet due to all other magnets.
This is a template that needs to be implemented for each magnet.
"""
pass
get_orientation(self)
Returns magnet orientation, alpha
in degrees
Returns:
Type | Description |
---|---|
float |
alpha, rotation angle w.r.t x-axis. |
Source code in pymagnet/magnets/_magnet2D.py
def get_orientation(self):
"""Returns magnet orientation, `alpha` in degrees
Returns:
float: alpha, rotation angle w.r.t x-axis.
"""
return self.alpha
Rectangle (Magnet2D)
Rectangular 2D Magnet Class
Source code in pymagnet/magnets/_magnet2D.py
class Rectangle(Magnet2D):
"""Rectangular 2D Magnet Class"""
mag_type = "Rectangle"
def __init__(self, width=20.0, height=40.0, Jr=1.0, **kwargs):
"""Init Method
Args:
width (float, optional): Magnet Width. Defaults to 20.0.
height (float, optional): Magnet Height. Defaults to 40.0.
Jr (float, optional): Remnant Magnetisation. Defaults to 1.0.
Kwargs:
alpha (float): Magnetisation orientation angle (in degrees). Defaults to 0.
center (tuple or ndarray): magnet center (x, y). Defaults to (0,0).
phi (float): Rotation Angle (in degrees) of magnet w.r.t x-axis. Defaults to 90.
"""
super().__init__(Jr, **kwargs)
self.width = width
self.height = height
self.a = width / 2
self.b = height / 2
self.phi = kwargs.pop("phi", 90)
self.phi_rad = _np.deg2rad(self.phi)
self.Jx = _np.around(Jr * _np.cos(self.phi_rad), decimals=6)
self.Jy = _np.around(Jr * _np.sin(self.phi_rad), decimals=6)
self.tol = MAG_TOL # sufficient for 0.01 degree accuracy
def get_size(self):
"""Returns magnet dimesions
Returns:
ndarray: numpy array [width, height]
"""
return _np.array([self.width, self.height])
def __str__(self):
str = (
f"{self.__class__.mag_type}\n"
+ f"J: {self.get_Jr()} (T)\n"
+ f"Size: {self.get_size()}\n"
+ f"Center {self.get_center()}\n"
+ f"Orientation: alpha {self.get_orientation()}\n"
)
return str
def __repr__(self):
str = (
f"{self.__class__.mag_type}\n"
+ f"J: {self.get_Jr()} (T)\n"
+ f"Size: {self.get_size()}\n"
+ f"Center {self.get_center()}\n"
+ f"Orientation: alpha {self.get_orientation()}\n"
)
return str
def get_Jr(self):
"""Returns Magnetisation vector
Returns:
ndarray: [Jx, Jy]
"""
return _np.array([self.Jx, self.Jy])
def get_field(self, x, y):
"""Calculates the magnetic field at point(s) x,y due to a rectangular magnet
Args:
x (ndarray): x co-ordinates
y (ndarray): y co-ordinates
Returns:
tuple: magnetic field vector Bx (ndarray), By (ndarray)
"""
from ..utils._routines2D import _get_field_array_shape2, rotate_points_2D
array_shape = _get_field_array_shape2(x, y)
Bx, By = _np.zeros(array_shape), _np.zeros(array_shape)
if _np.fabs(self.alpha_radians) > Magnet2D.tol:
xi, yi = rotate_points_2D(
x - self.center[0], y - self.center[1], self.alpha_radians
)
# Calculate field due to x-component of magnetisation
if _np.fabs(self.Jx / self.Jr) > Magnet2D.tol:
if _np.fabs(self.alpha_radians) > Magnet2D.tol:
# Calculate fields in local frame
Btx = self._calcBx_mag_x(xi, yi)
Bty = self._calcBy_mag_x(xi, yi)
# Rotate fields to global frame
Bx, By = rotate_points_2D(Btx, Bty, 2 * PI - self.alpha_radians)
else:
Bx = self._calcBx_mag_x(x - self.center[0], y - self.center[1])
By = self._calcBy_mag_x(x - self.center[0], y - self.center[1])
# Calculate field due to y-component of magnetisation
if _np.fabs(self.Jy / self.Jr) > Magnet2D.tol:
if _np.fabs(self.alpha_radians) > Magnet2D.tol:
Btx = self._calcBx_mag_y(xi, yi)
Bty = self._calcBy_mag_y(xi, yi)
Bxt, Byt = rotate_points_2D(Btx, Bty, 2 * PI - self.alpha_radians)
Bx += Bxt
By += Byt
else:
Bx += self._calcBx_mag_y(x - self.center[0], y - self.center[1])
By += self._calcBy_mag_y(x - self.center[0], y - self.center[1])
return Bx, By
def _calcBx_mag_x(self, x, y):
"""Bx using 2D Model for rectangular sheets magnetised in x-plane
Args:
x (ndarray): x-coordinates
y (ndarray): y-coordinates
Returns:
ndarray: Bx, x component of magnetic field
"""
a = self.a
b = self.b
J = self.Jx
# Hide the warning for situtations where there is a divide by zero.
# This returns a NaN in the array, which is ignored for plotting.
with _np.errstate(divide="ignore", invalid="ignore"):
return (J / (2 * PI)) * (
_np.arctan2((2 * a * (b + y)), (x ** 2 - a ** 2 + (y + b) ** 2))
+ _np.arctan2((2 * a * (b - y)), (x ** 2 - a ** 2 + (y - b) ** 2))
)
def _calcBy_mag_x(self, x, y):
"""By using 2D Model for rectangular sheets magnetised in x-plane
Args:
x (ndarray): x-coordinates
y (ndarray): y-coordinates
Returns:
ndarray: By, x component of magnetic field
"""
a = self.a
b = self.b
J = self.Jx
# Hide the warning for situtations where there is a divide by zero.
# This returns a NaN in the array, which is ignored for plotting.
with _np.errstate(divide="ignore", invalid="ignore"):
return (-J / (4 * PI)) * (
_np.log(((x - a) ** 2 + (y - b) ** 2) / ((x + a) ** 2 + (y - b) ** 2))
- _np.log(((x - a) ** 2 + (y + b) ** 2) / ((x + a) ** 2 + (y + b) ** 2))
)
def _calcBx_mag_y(self, x, y):
"""Bx using 2D Model for rectangular sheets magnetised in y-plane
Args:
x (ndarray): x-coordinates
y (ndarray): y-coordinates
Returns:
ndarray: Bx, x component of magnetic field
"""
a = self.a
b = self.b
J = self.Jy
# Hide the warning for situtations where there is a divide by zero.
# This returns a NaN in the array, which is ignored for plotting.
with _np.errstate(divide="ignore", invalid="ignore"):
return (J / (4 * PI)) * (
_np.log(((x + a) ** 2 + (y - b) ** 2) / ((x + a) ** 2 + (y + b) ** 2))
- _np.log(((x - a) ** 2 + (y - b) ** 2) / ((x - a) ** 2 + (y + b) ** 2))
)
def _calcBy_mag_y(self, x: float, y: float) -> float:
"""Bx using 2D Model for rectangular sheets magnetised in y-plane
Args:
x (ndarray): x-coordinates
y (ndarray): y-coordinates
Returns:
ndarray: By, x component of magnetic field
"""
a = self.a
b = self.b
J = self.Jy
return (J / (2 * PI)) * (
_np.arctan2((2 * b * (x + a)), ((x + a) ** 2 + y ** 2 - b ** 2))
- _np.arctan2((2 * b * (x - a)), ((x - a) ** 2 + y ** 2 - b ** 2))
)
__init__(self, width=20.0, height=40.0, Jr=1.0, **kwargs)
special
Init Method
Parameters:
Name | Type | Description | Default |
---|---|---|---|
width |
float |
Magnet Width. Defaults to 20.0. |
20.0 |
height |
float |
Magnet Height. Defaults to 40.0. |
40.0 |
Jr |
float |
Remnant Magnetisation. Defaults to 1.0. |
1.0 |
Kwargs
alpha (float): Magnetisation orientation angle (in degrees). Defaults to 0. center (tuple or ndarray): magnet center (x, y). Defaults to (0,0). phi (float): Rotation Angle (in degrees) of magnet w.r.t x-axis. Defaults to 90.
Source code in pymagnet/magnets/_magnet2D.py
def __init__(self, width=20.0, height=40.0, Jr=1.0, **kwargs):
"""Init Method
Args:
width (float, optional): Magnet Width. Defaults to 20.0.
height (float, optional): Magnet Height. Defaults to 40.0.
Jr (float, optional): Remnant Magnetisation. Defaults to 1.0.
Kwargs:
alpha (float): Magnetisation orientation angle (in degrees). Defaults to 0.
center (tuple or ndarray): magnet center (x, y). Defaults to (0,0).
phi (float): Rotation Angle (in degrees) of magnet w.r.t x-axis. Defaults to 90.
"""
super().__init__(Jr, **kwargs)
self.width = width
self.height = height
self.a = width / 2
self.b = height / 2
self.phi = kwargs.pop("phi", 90)
self.phi_rad = _np.deg2rad(self.phi)
self.Jx = _np.around(Jr * _np.cos(self.phi_rad), decimals=6)
self.Jy = _np.around(Jr * _np.sin(self.phi_rad), decimals=6)
self.tol = MAG_TOL # sufficient for 0.01 degree accuracy
get_Jr(self)
Returns Magnetisation vector
Returns:
Type | Description |
---|---|
ndarray |
[Jx, Jy] |
Source code in pymagnet/magnets/_magnet2D.py
def get_Jr(self):
"""Returns Magnetisation vector
Returns:
ndarray: [Jx, Jy]
"""
return _np.array([self.Jx, self.Jy])
get_field(self, x, y)
Calculates the magnetic field at point(s) x,y due to a rectangular magnet
Parameters:
Name | Type | Description | Default |
---|---|---|---|
x |
ndarray |
x co-ordinates |
required |
y |
ndarray |
y co-ordinates |
required |
Returns:
Type | Description |
---|---|
tuple |
magnetic field vector Bx (ndarray), By (ndarray) |
Source code in pymagnet/magnets/_magnet2D.py
def get_field(self, x, y):
"""Calculates the magnetic field at point(s) x,y due to a rectangular magnet
Args:
x (ndarray): x co-ordinates
y (ndarray): y co-ordinates
Returns:
tuple: magnetic field vector Bx (ndarray), By (ndarray)
"""
from ..utils._routines2D import _get_field_array_shape2, rotate_points_2D
array_shape = _get_field_array_shape2(x, y)
Bx, By = _np.zeros(array_shape), _np.zeros(array_shape)
if _np.fabs(self.alpha_radians) > Magnet2D.tol:
xi, yi = rotate_points_2D(
x - self.center[0], y - self.center[1], self.alpha_radians
)
# Calculate field due to x-component of magnetisation
if _np.fabs(self.Jx / self.Jr) > Magnet2D.tol:
if _np.fabs(self.alpha_radians) > Magnet2D.tol:
# Calculate fields in local frame
Btx = self._calcBx_mag_x(xi, yi)
Bty = self._calcBy_mag_x(xi, yi)
# Rotate fields to global frame
Bx, By = rotate_points_2D(Btx, Bty, 2 * PI - self.alpha_radians)
else:
Bx = self._calcBx_mag_x(x - self.center[0], y - self.center[1])
By = self._calcBy_mag_x(x - self.center[0], y - self.center[1])
# Calculate field due to y-component of magnetisation
if _np.fabs(self.Jy / self.Jr) > Magnet2D.tol:
if _np.fabs(self.alpha_radians) > Magnet2D.tol:
Btx = self._calcBx_mag_y(xi, yi)
Bty = self._calcBy_mag_y(xi, yi)
Bxt, Byt = rotate_points_2D(Btx, Bty, 2 * PI - self.alpha_radians)
Bx += Bxt
By += Byt
else:
Bx += self._calcBx_mag_y(x - self.center[0], y - self.center[1])
By += self._calcBy_mag_y(x - self.center[0], y - self.center[1])
return Bx, By
get_size(self)
Returns magnet dimesions
Returns:
Type | Description |
---|---|
ndarray |
numpy array [width, height] |
Source code in pymagnet/magnets/_magnet2D.py
def get_size(self):
"""Returns magnet dimesions
Returns:
ndarray: numpy array [width, height]
"""
return _np.array([self.width, self.height])
Square (Rectangle)
Square 2D Magnet Class
Source code in pymagnet/magnets/_magnet2D.py
class Square(Rectangle):
"""Square 2D Magnet Class"""
mag_type = "Square"
def __init__(self, width=20, Jr=1.0, **kwargs):
"""Init Method
Args:
width (float, optional): Square side length. Defaults to 20.0.
Jr (float, optional): Remnant Magnetisation. Defaults to 1.0.
Kwargs:
alpha (float): Magnetisation orientation angle (in degrees). Defaults to 0.
center (tuple or ndarray): magnet center (x, y). Defaults to (0,0).
phi (float): Rotation Angle (in degrees) of magnet w.r.t x-axis. Defaults to 90.
"""
super().__init__(width=width, height=width, Jr=Jr, **kwargs)
__init__(self, width=20, Jr=1.0, **kwargs)
special
Init Method
Parameters:
Name | Type | Description | Default |
---|---|---|---|
width |
float |
Square side length. Defaults to 20.0. |
20 |
Jr |
float |
Remnant Magnetisation. Defaults to 1.0. |
1.0 |
Kwargs
alpha (float): Magnetisation orientation angle (in degrees). Defaults to 0. center (tuple or ndarray): magnet center (x, y). Defaults to (0,0). phi (float): Rotation Angle (in degrees) of magnet w.r.t x-axis. Defaults to 90.
Source code in pymagnet/magnets/_magnet2D.py
def __init__(self, width=20, Jr=1.0, **kwargs):
"""Init Method
Args:
width (float, optional): Square side length. Defaults to 20.0.
Jr (float, optional): Remnant Magnetisation. Defaults to 1.0.
Kwargs:
alpha (float): Magnetisation orientation angle (in degrees). Defaults to 0.
center (tuple or ndarray): magnet center (x, y). Defaults to (0,0).
phi (float): Rotation Angle (in degrees) of magnet w.r.t x-axis. Defaults to 90.
"""
super().__init__(width=width, height=width, Jr=Jr, **kwargs)
2D Polygon Magnet class
Line
Line Class for storing properties of a sheet manget
Source code in pymagnet/magnets/_polygon2D.py
class Line(object):
"""Line Class for storing properties of a sheet manget"""
def __init__(self, length, center, beta, K):
"""Init Method
Args:
length (float): side length
center (ndarray): magnet center (x, y)
beta (float): Orientation w.r.t. z-axis in degrees
K (float): Sheet current density in tesla
"""
self.length = length
self.center = center
self.beta = beta
self.beta_rad = _np.deg2rad(beta)
self.xc = center[0]
self.yc = center[1]
self.K = K
self.tol = MAG_TOL
def __str__(self):
str = (
f"K: {self.K} (T)\n"
+ f"Length: {self.length} (m)\n"
+ f"Center {self.center} (m)\n"
+ f"Orientation: {self.beta}\n"
)
return str
def __repr__(self):
str = (
f"K: {self.K} (T)\n"
+ f"Length: {self.length}\n"
+ f"Center {self.center}\n"
+ f"Orientation: {self.beta}\n"
)
return str
def get_center(self):
"""Returns line center
Returns:
ndarray: center (x,y)
"""
return self.center
def get_field(self, x, y):
"""Calculates the magnetic field due to a sheet magnet
First a transformation into the local coordinates is made, the field calculated
and then the magnetic field it rotated out to the global coordinates
Args:
x (ndarray): x-coordinates
y (ndarray): y-coordinates
Returns:
tuple: Bx (ndarray), By (ndarray) magnetic field vector
"""
from ..utils._routines2D import _get_field_array_shape2, rotate_points_2D
array_shape = _get_field_array_shape2(x, y)
Bx, By = _np.zeros(array_shape), _np.zeros(array_shape)
if _np.fabs(self.beta_rad) > self.tol:
xt, yt = rotate_points_2D(x - self.xc, y - self.yc, 2 * PI - self.beta_rad)
Btx, Bty = _sheet_field(xt, yt, self.length / 2, self.K)
Bx, By = rotate_points_2D(Btx, Bty, self.beta_rad)
else:
Bx, By = _sheet_field(x - self.xc, y - self.yc, self.length / 2, self.K)
return Bx, By
__init__(self, length, center, beta, K)
special
Init Method
Parameters:
Name | Type | Description | Default |
---|---|---|---|
length |
float |
side length |
required |
center |
ndarray |
magnet center (x, y) |
required |
beta |
float |
Orientation w.r.t. z-axis in degrees |
required |
K |
float |
Sheet current density in tesla |
required |
Source code in pymagnet/magnets/_polygon2D.py
def __init__(self, length, center, beta, K):
"""Init Method
Args:
length (float): side length
center (ndarray): magnet center (x, y)
beta (float): Orientation w.r.t. z-axis in degrees
K (float): Sheet current density in tesla
"""
self.length = length
self.center = center
self.beta = beta
self.beta_rad = _np.deg2rad(beta)
self.xc = center[0]
self.yc = center[1]
self.K = K
self.tol = MAG_TOL
get_center(self)
Returns line center
Returns:
Type | Description |
---|---|
ndarray |
center (x,y) |
Source code in pymagnet/magnets/_polygon2D.py
def get_center(self):
"""Returns line center
Returns:
ndarray: center (x,y)
"""
return self.center
get_field(self, x, y)
Calculates the magnetic field due to a sheet magnet First a transformation into the local coordinates is made, the field calculated and then the magnetic field it rotated out to the global coordinates
Parameters:
Name | Type | Description | Default |
---|---|---|---|
x |
ndarray |
x-coordinates |
required |
y |
ndarray |
y-coordinates |
required |
Returns:
Type | Description |
---|---|
tuple |
Bx (ndarray), By (ndarray) magnetic field vector |
Source code in pymagnet/magnets/_polygon2D.py
def get_field(self, x, y):
"""Calculates the magnetic field due to a sheet magnet
First a transformation into the local coordinates is made, the field calculated
and then the magnetic field it rotated out to the global coordinates
Args:
x (ndarray): x-coordinates
y (ndarray): y-coordinates
Returns:
tuple: Bx (ndarray), By (ndarray) magnetic field vector
"""
from ..utils._routines2D import _get_field_array_shape2, rotate_points_2D
array_shape = _get_field_array_shape2(x, y)
Bx, By = _np.zeros(array_shape), _np.zeros(array_shape)
if _np.fabs(self.beta_rad) > self.tol:
xt, yt = rotate_points_2D(x - self.xc, y - self.yc, 2 * PI - self.beta_rad)
Btx, Bty = _sheet_field(xt, yt, self.length / 2, self.K)
Bx, By = rotate_points_2D(Btx, Bty, self.beta_rad)
else:
Bx, By = _sheet_field(x - self.xc, y - self.yc, self.length / 2, self.K)
return Bx, By
LineUtils
Utility class consisting of rountines for 2D line elements
Source code in pymagnet/magnets/_polygon2D.py
class LineUtils(object):
"""Utility class consisting of rountines for 2D line elements"""
@staticmethod
def unit_norm(vertex_1, vertex_2, clockwise=True):
"""Get unit normal to vertex
Args:
vertex_1 (ndarray): vertex 1
vertex_2 (ndarray): vertex 2
clockwise (bool, optional): Clockwise orientation of points. Defaults to True.
Returns:
tuple: normal vector (ndarray), length i.e. distance between vertices (float)
"""
dx = vertex_1[0] - vertex_2[0]
dy = vertex_1[1] - vertex_2[1]
# Clockwise winding of points:
if clockwise:
norm = _np.array([dy, -dx])
else:
norm = _np.array([-dy, dx])
length = _np.linalg.norm(norm)
norm = norm / length
return norm, length
@staticmethod
def line_center(vertex_1, vertex_2):
"""Gets midpoint of two vertices
Args:
vertex_1 (ndarray): vertex 1
vertex_2 (ndarray): vertex 2
Returns:
ndarray: midpoint
"""
xc = (vertex_1[0] + vertex_2[0]) / 2
yc = (vertex_1[1] + vertex_2[1]) / 2
return _np.array([xc, yc])
@staticmethod
def signed_area2D(polygon):
"""Calculates signed area of a polygon
Args:
polygon (Polygon): Polygon instance
Returns:
float: signed area
"""
j = 1
NP = polygon.num_vertices()
area = 0
norm = _np.empty([NP, 2])
center = _np.empty([NP, 2])
beta = _np.empty(NP) # angle w.r.t. y axis
length = _np.empty(NP)
for i in range(NP):
j = j % NP
area += (polygon.vertices[j][0] - polygon.vertices[i][0]) * (
polygon.vertices[j][1] + polygon.vertices[i][1]
)
norm[i, :], length[i] = LineUtils.unit_norm(
polygon.vertices[i], polygon.vertices[j]
)
center[i, :] = LineUtils.line_center(
polygon.vertices[i], polygon.vertices[j]
)
j += 1
# check winding order of polygon, area < 0 for clockwise ordering of points
if area < 0:
norm *= -1
beta[:] = _np.rad2deg(_np.arctan2(norm[:, 1], norm[:, 0]))
return area / 2.0, norm, beta, length, center
line_center(vertex_1, vertex_2)
staticmethod
Gets midpoint of two vertices
Parameters:
Name | Type | Description | Default |
---|---|---|---|
vertex_1 |
ndarray |
vertex 1 |
required |
vertex_2 |
ndarray |
vertex 2 |
required |
Returns:
Type | Description |
---|---|
ndarray |
midpoint |
Source code in pymagnet/magnets/_polygon2D.py
@staticmethod
def line_center(vertex_1, vertex_2):
"""Gets midpoint of two vertices
Args:
vertex_1 (ndarray): vertex 1
vertex_2 (ndarray): vertex 2
Returns:
ndarray: midpoint
"""
xc = (vertex_1[0] + vertex_2[0]) / 2
yc = (vertex_1[1] + vertex_2[1]) / 2
return _np.array([xc, yc])
signed_area2D(polygon)
staticmethod
Calculates signed area of a polygon
Parameters:
Name | Type | Description | Default |
---|---|---|---|
polygon |
Polygon |
Polygon instance |
required |
Returns:
Type | Description |
---|---|
float |
signed area |
Source code in pymagnet/magnets/_polygon2D.py
@staticmethod
def signed_area2D(polygon):
"""Calculates signed area of a polygon
Args:
polygon (Polygon): Polygon instance
Returns:
float: signed area
"""
j = 1
NP = polygon.num_vertices()
area = 0
norm = _np.empty([NP, 2])
center = _np.empty([NP, 2])
beta = _np.empty(NP) # angle w.r.t. y axis
length = _np.empty(NP)
for i in range(NP):
j = j % NP
area += (polygon.vertices[j][0] - polygon.vertices[i][0]) * (
polygon.vertices[j][1] + polygon.vertices[i][1]
)
norm[i, :], length[i] = LineUtils.unit_norm(
polygon.vertices[i], polygon.vertices[j]
)
center[i, :] = LineUtils.line_center(
polygon.vertices[i], polygon.vertices[j]
)
j += 1
# check winding order of polygon, area < 0 for clockwise ordering of points
if area < 0:
norm *= -1
beta[:] = _np.rad2deg(_np.arctan2(norm[:, 1], norm[:, 0]))
return area / 2.0, norm, beta, length, center
unit_norm(vertex_1, vertex_2, clockwise=True)
staticmethod
Get unit normal to vertex
Parameters:
Name | Type | Description | Default |
---|---|---|---|
vertex_1 |
ndarray |
vertex 1 |
required |
vertex_2 |
ndarray |
vertex 2 |
required |
clockwise |
bool |
Clockwise orientation of points. Defaults to True. |
True |
Returns:
Type | Description |
---|---|
tuple |
normal vector (ndarray), length i.e. distance between vertices (float) |
Source code in pymagnet/magnets/_polygon2D.py
@staticmethod
def unit_norm(vertex_1, vertex_2, clockwise=True):
"""Get unit normal to vertex
Args:
vertex_1 (ndarray): vertex 1
vertex_2 (ndarray): vertex 2
clockwise (bool, optional): Clockwise orientation of points. Defaults to True.
Returns:
tuple: normal vector (ndarray), length i.e. distance between vertices (float)
"""
dx = vertex_1[0] - vertex_2[0]
dy = vertex_1[1] - vertex_2[1]
# Clockwise winding of points:
if clockwise:
norm = _np.array([dy, -dx])
else:
norm = _np.array([-dy, dx])
length = _np.linalg.norm(norm)
norm = norm / length
return norm, length
PolyMagnet (Magnet2D)
2D Magnet Polygon class.
Source code in pymagnet/magnets/_polygon2D.py
class PolyMagnet(Magnet2D):
"""2D Magnet Polygon class."""
mag_type = "PolyMagnet"
def __init__(self, Jr, **kwargs) -> None:
"""Init method
NOTE:
* When creating a regular polygon, one of apothem, radius, or length must be defined as a kwarg or an exception will be raised.
* When creating a regular polygon, the number of sides `num_sides` must be at least 3 or an exception will be raised.
* When creating a custom polygon at least one vertex pair must be defined with `vertices` or an exception will be raised.
Args:
Jr (float): signed magnitude of remnant magnetisation
Kwargs:
alpha (float): Not used
theta (float): Orientation of magnet w.r.t x-axis of magnet
phi (float): Orientation of magnetisation w.r.t x-axis of magnet in degrees. Defaults to 90.0.
center (ndarray): magnet center (x, y). Defaults to (0.0, 0.0)
length (float): side length if creating a regular polygon
apothem (float): apothem (incircle radius) if creating a regular polygon
radius (float): radius (circumcircle radius) if creating a regular polygon
num_sides (int): number of sides of a regular polygon. Defaults to 6.
custom_polygon (bool): Flag to define a custom polygon. Defaults to False.
vertices (ndarray, list): List of custom vertices. Defaults to None.
Raises:
Exception: If creating a custom polygon, `vertices` must not be None.
"""
from ..utils._routines2D import rotate_points_2D
super().__init__(Jr, **kwargs)
# Magnet rotation w.r.t. x-axis
self.alpha = kwargs.pop("alpha", 0.0)
self.alpha_radians = _np.deg2rad(self.alpha)
self.theta = kwargs.pop("theta", 0.0)
self.theta_radians = _np.deg2rad(self.theta)
self.phi = kwargs.pop("phi", 90.0)
self.phi_rad = _np.deg2rad(self.phi)
self.Jx = _np.around(Jr * _np.cos(self.phi_rad), decimals=6)
self.Jy = _np.around(Jr * _np.sin(self.phi_rad), decimals=6)
self.tol = MAG_TOL
self.area = None
self.custom_polygon = kwargs.pop("custom_polygon", False)
self.center = kwargs.pop("center", _np.array([0.0, 0.0, 0.0]))
self.center = _np.asarray(self.center)
if self.custom_polygon:
vertices = kwargs.pop("vertices", None)
if vertices is None:
raise Exception("Error, no vertices were defined.")
vertices = _np.atleast_2d(vertices)
x_rot, y_rot = rotate_points_2D(
vertices[:, 0],
vertices[:, 1],
self.theta_radians, # + self.alpha_radians,
)
vertices = _np.stack([x_rot, y_rot]).T + self.center
self.polygon = Polygon(vertices=vertices.tolist())
else:
self.length = kwargs.pop("length", None)
self.apothem = kwargs.pop("apothem", None)
self.radius = kwargs.pop("radius", None)
self.num_sides = kwargs.pop("num_sides", 6)
self.radius = Polygon.check_radius(
self.num_sides,
self.apothem,
self.length,
self.radius,
)
# Generate Polygon
self.polygon = Polygon(
vertices=Polygon.gen_polygon(
self.num_sides,
self.center,
self.theta, # + self.alpha,
length=self.length,
apothem=self.apothem,
radius=self.radius,
),
center=self.center,
)
def get_center(self):
"""Returns magnet centre
Returns:
center (ndarray): numpy array [xc, yc]
"""
return self.center
def get_orientation(self):
"""Returns magnet orientation, `alpha` in degrees
Returns:
float: alpha, rotation angle w.r.t x-axis.
"""
return self.alpha
def _gen_sheet_magnets(self):
"""Generates orientation, size, and centre of sheet magnets for a given
polygon
Returns:
tuple: beta (ndarray), length (ndarray), centre (ndarray), K (ndarray) - sheet current density in tesla.
"""
area, norms, beta, length, center = LineUtils.signed_area2D(self.polygon)
K = self.Jx * norms[:, 1] - self.Jy * norms[:, 0]
self.area = area
return beta, length, center, K
def get_field(self, x, y):
"""Calculates the magnetic field of a polygon due to each face
Args:
x (ndarray): x-coordinates
y (ndarray): y-coordinates
Returns:
tuple: Bx (ndarray), By (ndarray) magnetic field vector
"""
from ..utils._routines2D import _get_field_array_shape2
array_shape = _get_field_array_shape2(x, y)
Bx, By = _np.zeros(array_shape), _np.zeros(array_shape)
beta, length, center, K = self._gen_sheet_magnets()
if _np.fabs(self.alpha_radians) > self.tol:
pass
print("Arbitrary rotation with alpha not yet implemented!!")
# FIXME: rotate centres
# xt, yt = rotate_points_2D(x - self.xc, y - self.yc, self.alpha_radians)
# beta += self.alpha
# xc_rot, yc_rot = rotate_points_2D(
# center[:, 0] - self.xc,
# center[:, 1] - self.yc,
# self.alpha_radians,
# )
# center[:, 0] = xc_rot
# center[:, 1] = yc_rot
#
#
# for i in range(len(K)):
# sheet = Line(length[i], center[i], beta[i], K[i])
# Btx, Bty = sheet.get_field(xt, yt)
# Btx, Bty = rotate_points_2D(Btx, Bty, 2 * PI - self.alpha_radians)
# Bx += Btx
# By += Bty
for i in range(len(K)):
sheet = Line(length[i], center[i], beta[i], K[i])
Btx, Bty = sheet.get_field(x, y)
Bx += Btx
By += Bty
return Bx, By
__init__(self, Jr, **kwargs)
special
Init method
Note
- When creating a regular polygon, one of apothem, radius, or length must be defined as a kwarg or an exception will be raised.
- When creating a regular polygon, the number of sides
num_sides
must be at least 3 or an exception will be raised. - When creating a custom polygon at least one vertex pair must be defined with
vertices
or an exception will be raised.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
Jr |
float |
signed magnitude of remnant magnetisation |
required |
Kwargs
alpha (float): Not used theta (float): Orientation of magnet w.r.t x-axis of magnet phi (float): Orientation of magnetisation w.r.t x-axis of magnet in degrees. Defaults to 90.0. center (ndarray): magnet center (x, y). Defaults to (0.0, 0.0) length (float): side length if creating a regular polygon apothem (float): apothem (incircle radius) if creating a regular polygon radius (float): radius (circumcircle radius) if creating a regular polygon num_sides (int): number of sides of a regular polygon. Defaults to 6. custom_polygon (bool): Flag to define a custom polygon. Defaults to False. vertices (ndarray, list): List of custom vertices. Defaults to None.
Exceptions:
Type | Description |
---|---|
Exception |
If creating a custom polygon, |
Source code in pymagnet/magnets/_polygon2D.py
def __init__(self, Jr, **kwargs) -> None:
"""Init method
NOTE:
* When creating a regular polygon, one of apothem, radius, or length must be defined as a kwarg or an exception will be raised.
* When creating a regular polygon, the number of sides `num_sides` must be at least 3 or an exception will be raised.
* When creating a custom polygon at least one vertex pair must be defined with `vertices` or an exception will be raised.
Args:
Jr (float): signed magnitude of remnant magnetisation
Kwargs:
alpha (float): Not used
theta (float): Orientation of magnet w.r.t x-axis of magnet
phi (float): Orientation of magnetisation w.r.t x-axis of magnet in degrees. Defaults to 90.0.
center (ndarray): magnet center (x, y). Defaults to (0.0, 0.0)
length (float): side length if creating a regular polygon
apothem (float): apothem (incircle radius) if creating a regular polygon
radius (float): radius (circumcircle radius) if creating a regular polygon
num_sides (int): number of sides of a regular polygon. Defaults to 6.
custom_polygon (bool): Flag to define a custom polygon. Defaults to False.
vertices (ndarray, list): List of custom vertices. Defaults to None.
Raises:
Exception: If creating a custom polygon, `vertices` must not be None.
"""
from ..utils._routines2D import rotate_points_2D
super().__init__(Jr, **kwargs)
# Magnet rotation w.r.t. x-axis
self.alpha = kwargs.pop("alpha", 0.0)
self.alpha_radians = _np.deg2rad(self.alpha)
self.theta = kwargs.pop("theta", 0.0)
self.theta_radians = _np.deg2rad(self.theta)
self.phi = kwargs.pop("phi", 90.0)
self.phi_rad = _np.deg2rad(self.phi)
self.Jx = _np.around(Jr * _np.cos(self.phi_rad), decimals=6)
self.Jy = _np.around(Jr * _np.sin(self.phi_rad), decimals=6)
self.tol = MAG_TOL
self.area = None
self.custom_polygon = kwargs.pop("custom_polygon", False)
self.center = kwargs.pop("center", _np.array([0.0, 0.0, 0.0]))
self.center = _np.asarray(self.center)
if self.custom_polygon:
vertices = kwargs.pop("vertices", None)
if vertices is None:
raise Exception("Error, no vertices were defined.")
vertices = _np.atleast_2d(vertices)
x_rot, y_rot = rotate_points_2D(
vertices[:, 0],
vertices[:, 1],
self.theta_radians, # + self.alpha_radians,
)
vertices = _np.stack([x_rot, y_rot]).T + self.center
self.polygon = Polygon(vertices=vertices.tolist())
else:
self.length = kwargs.pop("length", None)
self.apothem = kwargs.pop("apothem", None)
self.radius = kwargs.pop("radius", None)
self.num_sides = kwargs.pop("num_sides", 6)
self.radius = Polygon.check_radius(
self.num_sides,
self.apothem,
self.length,
self.radius,
)
# Generate Polygon
self.polygon = Polygon(
vertices=Polygon.gen_polygon(
self.num_sides,
self.center,
self.theta, # + self.alpha,
length=self.length,
apothem=self.apothem,
radius=self.radius,
),
center=self.center,
)
get_center(self)
Returns magnet centre
Returns:
Type | Description |
---|---|
center (ndarray) |
numpy array [xc, yc] |
Source code in pymagnet/magnets/_polygon2D.py
def get_center(self):
"""Returns magnet centre
Returns:
center (ndarray): numpy array [xc, yc]
"""
return self.center
get_field(self, x, y)
Calculates the magnetic field of a polygon due to each face
Parameters:
Name | Type | Description | Default |
---|---|---|---|
x |
ndarray |
x-coordinates |
required |
y |
ndarray |
y-coordinates |
required |
Returns:
Type | Description |
---|---|
tuple |
Bx (ndarray), By (ndarray) magnetic field vector |
Source code in pymagnet/magnets/_polygon2D.py
def get_field(self, x, y):
"""Calculates the magnetic field of a polygon due to each face
Args:
x (ndarray): x-coordinates
y (ndarray): y-coordinates
Returns:
tuple: Bx (ndarray), By (ndarray) magnetic field vector
"""
from ..utils._routines2D import _get_field_array_shape2
array_shape = _get_field_array_shape2(x, y)
Bx, By = _np.zeros(array_shape), _np.zeros(array_shape)
beta, length, center, K = self._gen_sheet_magnets()
if _np.fabs(self.alpha_radians) > self.tol:
pass
print("Arbitrary rotation with alpha not yet implemented!!")
# FIXME: rotate centres
# xt, yt = rotate_points_2D(x - self.xc, y - self.yc, self.alpha_radians)
# beta += self.alpha
# xc_rot, yc_rot = rotate_points_2D(
# center[:, 0] - self.xc,
# center[:, 1] - self.yc,
# self.alpha_radians,
# )
# center[:, 0] = xc_rot
# center[:, 1] = yc_rot
#
#
# for i in range(len(K)):
# sheet = Line(length[i], center[i], beta[i], K[i])
# Btx, Bty = sheet.get_field(xt, yt)
# Btx, Bty = rotate_points_2D(Btx, Bty, 2 * PI - self.alpha_radians)
# Bx += Btx
# By += Bty
for i in range(len(K)):
sheet = Line(length[i], center[i], beta[i], K[i])
Btx, Bty = sheet.get_field(x, y)
Bx += Btx
By += Bty
return Bx, By
get_orientation(self)
Returns magnet orientation, alpha
in degrees
Returns:
Type | Description |
---|---|
float |
alpha, rotation angle w.r.t x-axis. |
Source code in pymagnet/magnets/_polygon2D.py
def get_orientation(self):
"""Returns magnet orientation, `alpha` in degrees
Returns:
float: alpha, rotation angle w.r.t x-axis.
"""
return self.alpha
Polygon
Polygon class for generating list of vertices
Source code in pymagnet/magnets/_polygon2D.py
class Polygon(object):
"""Polygon class for generating list of vertices"""
def __init__(self, **kwargs):
vertices = kwargs.pop("vertices", None)
center = kwargs.pop("center", None)
if vertices is not None:
if type(vertices) is _np.ndarray:
if center is not None:
vertices += center
self.vertices = vertices.tolist()
else:
self.vertices = vertices
if center is not None:
self.center = center
else:
self.set_center()
else:
self.vertices = []
self.center = _np.NaN
def append(self, vertex):
"""Appends vertex to list of vertices
Args:
vertex (list): list of vertices
"""
if len(vertex) != 2:
print("Error")
if type(vertex) == tuple:
self.vertices.append(vertex)
elif len(vertex) == 2:
self.vertices.append(tuple(vertex))
self.set_center()
def num_vertices(self):
"""Gets number of vertices
Returns:
int: number of vertices
"""
return len(self.vertices)
def set_center(self):
"""Sets center of polygon to be centroid"""
# FIXME: This is not the correct method!!! It should be the weighted mean
self.center = _np.mean(_np.asarray(self.vertices), axis=0)
def get_centroid_area(vertex_array):
sumCx = 0
sumCy = 0
sumAc = 0
for i in range(len(vertex_array) - 1):
cX = (vertex_array[i][0] + vertex_array[i + 1][0]) * (
vertex_array[i][0] * vertex_array[i + 1][1]
- vertex_array[i + 1][0] * vertex_array[i][1]
)
cY = (vertex_array[i][1] + vertex_array[i + 1][1]) * (
vertex_array[i][0] * vertex_array[i + 1][1]
- vertex_array[i + 1][0] * vertex_array[i][1]
)
pA = (vertex_array[i][0] * vertex_array[i + 1][1]) - (
vertex_array[i + 1][0] * vertex_array[i][1]
)
sumCx += cX
sumCy += cY
sumAc += pA
area = sumAc / 2.0
center = ((1.0 / (6.0 * area)) * sumCx, (1.0 / (6.0 * area)) * sumCy)
return center, area
@staticmethod
def gen_polygon(N=6, center=(0.0, 0.0), alpha=0.0, **kwargs):
"""Generates regular polygon. One of apothem, side length or radius must be defined.
Args:
N (int, optional): Number of sides. Defaults to 6.
center (tuple, optional): Polygon center. Defaults to (0.0, 0.0).
alpha (float, optional): Orientration with respect to x-axis. Defaults to 0.0.
Raises:
Exception: N must be > 2
Returns:
ndarray: polygon vertices
"""
N = int(N)
if N < 3:
raise Exception("Error, N must be > 2.")
apothem = kwargs.pop("apothem", None)
length = kwargs.pop("length", None)
radius = kwargs.pop("radius", None)
radius = Polygon.check_radius(N, apothem, length, radius)
k = _np.arange(0, N, 1)
xc = center[0]
yc = center[1]
def f(N):
if N % 2 == 0:
return PI / N + _np.deg2rad(alpha)
else:
return PI / N + PI + _np.deg2rad(alpha)
xv = xc + radius * _np.sin(2 * PI * k / N + f(N))
yv = yc + radius * _np.cos(2 * PI * k / N + f(N))
poly_verts = _np.vstack((xv, yv)).T.tolist()
return poly_verts
@staticmethod
def check_radius(N, apothem, length, radius):
"""Checks which of apothem, side length, or radius has been passed as kwargs
to `gen_polygon()`. Order of precendence is apothem, length, radius.
Args:
N (int): Number of sides
apothem (float): polygon apothem
length (float): side length
radius (float): outcircle radius
Raises:
Exception: One of apothem, length, or raduis must be defined
Returns:
float: returns radius
"""
if apothem is not None:
return apothem / _np.around(_np.cos(PI / N), 4)
elif length is not None:
return length / _np.around(2 * _np.sin(PI / N), 4)
elif radius is not None:
return radius
else:
raise Exception("Error, one of apothem, length, or radius must be defined.")
append(self, vertex)
Appends vertex to list of vertices
Parameters:
Name | Type | Description | Default |
---|---|---|---|
vertex |
list |
list of vertices |
required |
Source code in pymagnet/magnets/_polygon2D.py
def append(self, vertex):
"""Appends vertex to list of vertices
Args:
vertex (list): list of vertices
"""
if len(vertex) != 2:
print("Error")
if type(vertex) == tuple:
self.vertices.append(vertex)
elif len(vertex) == 2:
self.vertices.append(tuple(vertex))
self.set_center()
check_radius(N, apothem, length, radius)
staticmethod
Checks which of apothem, side length, or radius has been passed as kwargs
to gen_polygon()
. Order of precendence is apothem, length, radius.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
N |
int |
Number of sides |
required |
apothem |
float |
polygon apothem |
required |
length |
float |
side length |
required |
radius |
float |
outcircle radius |
required |
Exceptions:
Type | Description |
---|---|
Exception |
One of apothem, length, or raduis must be defined |
Returns:
Type | Description |
---|---|
float |
returns radius |
Source code in pymagnet/magnets/_polygon2D.py
@staticmethod
def check_radius(N, apothem, length, radius):
"""Checks which of apothem, side length, or radius has been passed as kwargs
to `gen_polygon()`. Order of precendence is apothem, length, radius.
Args:
N (int): Number of sides
apothem (float): polygon apothem
length (float): side length
radius (float): outcircle radius
Raises:
Exception: One of apothem, length, or raduis must be defined
Returns:
float: returns radius
"""
if apothem is not None:
return apothem / _np.around(_np.cos(PI / N), 4)
elif length is not None:
return length / _np.around(2 * _np.sin(PI / N), 4)
elif radius is not None:
return radius
else:
raise Exception("Error, one of apothem, length, or radius must be defined.")
gen_polygon(N=6, center=(0.0, 0.0), alpha=0.0, **kwargs)
staticmethod
Generates regular polygon. One of apothem, side length or radius must be defined.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
N |
int |
Number of sides. Defaults to 6. |
6 |
center |
tuple |
Polygon center. Defaults to (0.0, 0.0). |
(0.0, 0.0) |
alpha |
float |
Orientration with respect to x-axis. Defaults to 0.0. |
0.0 |
Exceptions:
Type | Description |
---|---|
Exception |
N must be > 2 |
Returns:
Type | Description |
---|---|
ndarray |
polygon vertices |
Source code in pymagnet/magnets/_polygon2D.py
@staticmethod
def gen_polygon(N=6, center=(0.0, 0.0), alpha=0.0, **kwargs):
"""Generates regular polygon. One of apothem, side length or radius must be defined.
Args:
N (int, optional): Number of sides. Defaults to 6.
center (tuple, optional): Polygon center. Defaults to (0.0, 0.0).
alpha (float, optional): Orientration with respect to x-axis. Defaults to 0.0.
Raises:
Exception: N must be > 2
Returns:
ndarray: polygon vertices
"""
N = int(N)
if N < 3:
raise Exception("Error, N must be > 2.")
apothem = kwargs.pop("apothem", None)
length = kwargs.pop("length", None)
radius = kwargs.pop("radius", None)
radius = Polygon.check_radius(N, apothem, length, radius)
k = _np.arange(0, N, 1)
xc = center[0]
yc = center[1]
def f(N):
if N % 2 == 0:
return PI / N + _np.deg2rad(alpha)
else:
return PI / N + PI + _np.deg2rad(alpha)
xv = xc + radius * _np.sin(2 * PI * k / N + f(N))
yv = yc + radius * _np.cos(2 * PI * k / N + f(N))
poly_verts = _np.vstack((xv, yv)).T.tolist()
return poly_verts
num_vertices(self)
Gets number of vertices
Returns:
Type | Description |
---|---|
int |
number of vertices |
Source code in pymagnet/magnets/_polygon2D.py
def num_vertices(self):
"""Gets number of vertices
Returns:
int: number of vertices
"""
return len(self.vertices)
set_center(self)
Sets center of polygon to be centroid
Source code in pymagnet/magnets/_polygon2D.py
def set_center(self):
"""Sets center of polygon to be centroid"""
# FIXME: This is not the correct method!!! It should be the weighted mean
self.center = _np.mean(_np.asarray(self.vertices), axis=0)
Mesh (Magnet3D)
Mesh Magnet Class.
Source code in pymagnet/magnets/_polygon3D.py
class Mesh(Magnet3D):
"""Mesh Magnet Class."""
mag_type = "Mesh"
def __init__(
self,
filename,
Jr=1.0, # local magnetisation
**kwargs,
):
"""Init Method
Args:
filename (string): path to stl file to be imported
Jr (float, optional): Signed remnant magnetisation. Defaults to 1.0.
Kwargs:
phi (float):
theta (float):
mesh_scale (float): scaling factor if mesh needs to be resized. Defaults to 1.0
"""
super().__init__(Jr, **kwargs)
self.phi = kwargs.pop("theta", 90.0)
self.phi_rad = _np.deg2rad(self.phi)
self.theta = kwargs.pop("phi", 0.0)
self.theta_rad = _np.deg2rad(self.theta)
self.mesh_scale = kwargs.pop("mesh_scale", 1.0)
self._filename = filename
(
self.mesh_vectors,
self.mesh_normals,
self.volume,
self.centroid,
) = self._import_mesh()
self.Jx = _np.around(
Jr * _np.cos(self.phi_rad) * _np.sin(self.theta_rad), decimals=6
)
self.Jy = _np.around(
Jr * _np.sin(self.phi_rad) * _np.sin(self.theta_rad), decimals=6
)
self.Jz = _np.around(Jr * _np.cos(self.theta_rad), decimals=6)
self.tol = MAG_TOL # sufficient for 0.01 degree accuracy
self.J = _np.array([self.Jx, self.Jy, self.Jz])
# FIXME: Sort out rotation of magnetisation with rotation of mesh
# if _np.any(
# _np.fabs([self.alpha_rad, self.beta_rad, self.gamma_rad]) > ALIGN_CUTOFF
# ):
# mag_rotation = Quaternion.gen_rotation_quaternion(
# self.alpha_rad, self.beta_rad, self.gamma_rad
# )
# Jrot = mag_rotation * self.J
# self.Jx = Jrot[0]
# self.Jy = Jrot[1]
# self.Jz = Jrot[2]
# self.J = _np.array([self.Jx, self.Jy, self.Jz])
self.Jnorm = _np.dot(self.J, self.mesh_normals.T)
def __str__(self):
str = (
f"{self.__class__.mag_type}\n"
+ f"J: {self.get_Jr()} (T)\n"
+ f"Center {self.get_center()}\n"
+ f"Orientation alpha,beta,gamma: {self.get_orientation()}\n"
)
return str
def __repr__(self):
str = (
f"{self.__class__.mag_type}\n"
+ f"J: {self.get_Jr()} (T)\n"
+ f"Center {self.get_center()}\n"
+ f"Orientation alpha,beta,gamma: {self.get_orientation()}\n"
)
return str
def get_Jr(self):
"""Returns Magnetisation vector
Returns:
ndarray: [Jx, Jy, Jz]
"""
return self.J
def size(self):
"""Returns magnet dimesions
Returns:
size (ndarray): numpy array [width, depth, height]
"""
pass
def get_center(self):
"""Returns magnet center
Returns:
ndarray: magnet center
"""
return self.center
def get_field(self, x, y, z):
"""Calculates the magnetic field at point(s) x,y,z due to a 3D magnet
The calculations are always performed in local coordinates with the centre of the magnet at origin and z magnetisation pointing along the local z' axis.
The rotations and translations are performed first, and the internal field calculation functions are called.
Args:
x (float/array): x co-ordinates
y (float/array): y co-ordinates
z (float/array): z co-ordinates
Returns:
tuple: Bx(ndarray), By(ndarray), Bz(ndarray) field vector
"""
B = self._get_field_internal(x, y, z)
return B.x, B.y, B.z
def get_force_torque(self, depth=4, unit="mm"):
"""Calculates the force and torque on a prism magnet due to all other magnets.
Args:
depth (int, optional): Number of recursions of division by 4 per simplex
unit (str, optional): Length scale. Defaults to 'mm'.
Returns:
tuple: force (ndarray (3,) ) and torque (ndarray (3,) )
"""
from ..forces._mesh_force import calc_force_mesh
force, torque = calc_force_mesh(self, depth, unit)
return force, torque
def _get_field_internal(self, x, y, z):
"""Internal magnetic field calculation methods.
Iterates over each triangle that makes up the mesh magnet and calculates the magnetic field
Args:
x (float/array): x co-ordinates
y (float/array): y co-ordinates
z (float/array): z co-ordinates
Returns:
Field3: Magnetic field array
"""
from ..utils._routines3D import _allocate_field_array3
B = _allocate_field_array3(x, y, z)
vec_shape = B.x.shape
B.x = B.x.ravel()
B.y = B.y.ravel()
B.z = B.z.ravel()
# debug for loop, used when needing to check certain triangles, or groups of triangles
# for i in range(self.start, self.stop):
for i in range(len(self.mesh_vectors)):
if _np.fabs(self.Jnorm[i] / self.Jr) > 1e-4:
Btx, Bty, Btz, _, _, _ = self.calcB_triangle(
self.mesh_vectors[i],
self.Jnorm[i],
x,
y,
z,
i,
)
B.x += Btx
B.y += Bty
B.z += Btz
B.x = _np.reshape(B.x, vec_shape)
B.y = _np.reshape(B.y, vec_shape)
B.z = _np.reshape(B.z, vec_shape)
B.n = _np.linalg.norm([B.x, B.y, B.z], axis=0)
return B
def _import_mesh(self):
"""Imports mesh from STL file
Returns:
tuple: mesh_vectors (ndarray of mesh triangles), mesh_normals (ndarray of normals to each triangle)
"""
stl_mesh = mesh.Mesh.from_file(self._filename)
if _np.any(
_np.fabs([self.alpha_rad, self.beta_rad, self.gamma_rad]) > ALIGN_CUTOFF
):
mesh_rotation = Quaternion.gen_rotation_quaternion(
self.alpha_rad, self.beta_rad, self.gamma_rad
)
angle, axis = mesh_rotation.get_axisangle()
stl_mesh.rotate(axis, angle)
# to ensure that the initial center is set to the centroid
_, centroid, _ = stl_mesh.get_mass_properties()
stl_mesh.translate(-centroid)
offset = self.get_center()
stl_mesh.translate(offset / self.mesh_scale)
# get values after translation
volume, centroid, _ = stl_mesh.get_mass_properties()
mesh_vectors = stl_mesh.vectors.astype(_np.float64)
mesh_normals = stl_mesh.normals.astype(_np.float64)
# scale values
volume *= self.mesh_scale**3
centroid *= self.mesh_scale
mesh_vectors *= self.mesh_scale
mesh_normals = mesh_normals / _np.linalg.norm(
mesh_normals, axis=1, keepdims=True
)
return mesh_vectors, mesh_normals, volume, centroid
def _generate_mask(self, x, y, z):
"""Generates mask of points inside a magnet
NOTE: not implemented for Mesh magnets.
Args:
x (ndarray/float): x-coordinates
y (ndarray/float): y-coordinates
z (ndarray/float): z-coordinates
"""
pass
def calcB_triangle(self, triangle, Jr, x, y, z, i):
"""Calculates the magnetic field due to a triangle
Args:
triangle (ndarray): Vertices of a triangle
Jr (float): Remnant magnetisation component normal to triangle
x (ndarray): x coordinates
y (ndarray): y coordinates
z (ndarray): z coordinates
Returns:
tuple: Bx, By, Bz magnetic field components
"""
(
total_rotation,
rotated_triangle,
offset,
RA_triangle1,
RA_triangle2,
) = _rotate_triangle(triangle, Jr)
# Prepare points and quaternion
pos_vec = Quaternion._prepare_vector(x, y, z)
# Rotate points
x_rot, y_rot, z_rot = total_rotation * pos_vec
norm1 = norm_plane(triangle)
if _np.allclose(norm1, [0, -1, 0], atol=ALIGN_CUTOFF) and Jr < 0:
RA_triangle1, RA_triangle2 = RA_triangle2, RA_triangle1
Btx, Bty, Btz = self._calcB_2_triangles(
RA_triangle1,
RA_triangle2,
Jr,
x_rot - offset[0],
y_rot - offset[1],
z_rot - offset[2],
)
Bvec = Quaternion._prepare_vector(Btx, Bty, Btz)
Bx, By, Bz = total_rotation.get_conjugate() * Bvec
return Bx, By, Bz, rotated_triangle, offset, total_rotation
def _calcB_2_triangles(self, triangle1, triangle2, Jr, x, y, z):
"""Calculates the magnetic field due to two split right angled triangles
in their local frame.
Args:
triangle1 (ndarray): Vertices of triangle 1
triangle2 (ndarray): Vertices of triangle 2
Jr (float): normal remnant magnetisation
x (ndarray): x coordinates
y (ndarray): y coordinates
z (ndarray): z coordinates
Returns:
tuple: Bx, By, Bz magnetic field components
"""
# Calc RA1 Field
Btx, Bty, Btz = self._charge_sheet(triangle1[0], triangle1[1], Jr, x, y, z)
# Rotate into local of RA2
rotate_about_z = Quaternion.q_angle_from_axis(PI, (0, 0, 1))
pos_vec_RA2 = Quaternion._prepare_vector(x - triangle1[0], y, z)
x_local, y_local, z_local = rotate_about_z * pos_vec_RA2
# Calc RA2 Field
Btx2, Bty2, Btz2 = self._charge_sheet(
triangle2[0], triangle2[1], Jr, x_local + triangle2[0], y_local, z_local
)
# Inverse Rot of RA2 Field
Bvec = Quaternion._prepare_vector(Btx2, Bty2, Btz2)
Btx2, Bty2, Btz2 = rotate_about_z.get_conjugate() * Bvec
Btx += Btx2
Bty += Bty2
Btz += Btz2
return Btx, Bty, Btz
@staticmethod
def _charge_sheet(a, b, Jr, x, y, z):
sigma = Jr
with _np.errstate(all="ignore"):
Bx = _charge_sheet_x(a, b, sigma, x, y, z)
By = _charge_sheet_y(a, b, sigma, x, y, z)
Bz = _charge_sheet_z(a, b, sigma, x, y, z)
return Bx, By, Bz
__init__(self, filename, Jr=1.0, **kwargs)
special
Init Method
Parameters:
Name | Type | Description | Default |
---|---|---|---|
filename |
string |
path to stl file to be imported |
required |
Jr |
float |
Signed remnant magnetisation. Defaults to 1.0. |
1.0 |
Kwargs
phi (float): theta (float): mesh_scale (float): scaling factor if mesh needs to be resized. Defaults to 1.0
Source code in pymagnet/magnets/_polygon3D.py
def __init__(
self,
filename,
Jr=1.0, # local magnetisation
**kwargs,
):
"""Init Method
Args:
filename (string): path to stl file to be imported
Jr (float, optional): Signed remnant magnetisation. Defaults to 1.0.
Kwargs:
phi (float):
theta (float):
mesh_scale (float): scaling factor if mesh needs to be resized. Defaults to 1.0
"""
super().__init__(Jr, **kwargs)
self.phi = kwargs.pop("theta", 90.0)
self.phi_rad = _np.deg2rad(self.phi)
self.theta = kwargs.pop("phi", 0.0)
self.theta_rad = _np.deg2rad(self.theta)
self.mesh_scale = kwargs.pop("mesh_scale", 1.0)
self._filename = filename
(
self.mesh_vectors,
self.mesh_normals,
self.volume,
self.centroid,
) = self._import_mesh()
self.Jx = _np.around(
Jr * _np.cos(self.phi_rad) * _np.sin(self.theta_rad), decimals=6
)
self.Jy = _np.around(
Jr * _np.sin(self.phi_rad) * _np.sin(self.theta_rad), decimals=6
)
self.Jz = _np.around(Jr * _np.cos(self.theta_rad), decimals=6)
self.tol = MAG_TOL # sufficient for 0.01 degree accuracy
self.J = _np.array([self.Jx, self.Jy, self.Jz])
# FIXME: Sort out rotation of magnetisation with rotation of mesh
# if _np.any(
# _np.fabs([self.alpha_rad, self.beta_rad, self.gamma_rad]) > ALIGN_CUTOFF
# ):
# mag_rotation = Quaternion.gen_rotation_quaternion(
# self.alpha_rad, self.beta_rad, self.gamma_rad
# )
# Jrot = mag_rotation * self.J
# self.Jx = Jrot[0]
# self.Jy = Jrot[1]
# self.Jz = Jrot[2]
# self.J = _np.array([self.Jx, self.Jy, self.Jz])
self.Jnorm = _np.dot(self.J, self.mesh_normals.T)
calcB_triangle(self, triangle, Jr, x, y, z, i)
Calculates the magnetic field due to a triangle
Parameters:
Name | Type | Description | Default |
---|---|---|---|
triangle |
ndarray |
Vertices of a triangle |
required |
Jr |
float |
Remnant magnetisation component normal to triangle |
required |
x |
ndarray |
x coordinates |
required |
y |
ndarray |
y coordinates |
required |
z |
ndarray |
z coordinates |
required |
Returns:
Type | Description |
---|---|
tuple |
Bx, By, Bz magnetic field components |
Source code in pymagnet/magnets/_polygon3D.py
def calcB_triangle(self, triangle, Jr, x, y, z, i):
"""Calculates the magnetic field due to a triangle
Args:
triangle (ndarray): Vertices of a triangle
Jr (float): Remnant magnetisation component normal to triangle
x (ndarray): x coordinates
y (ndarray): y coordinates
z (ndarray): z coordinates
Returns:
tuple: Bx, By, Bz magnetic field components
"""
(
total_rotation,
rotated_triangle,
offset,
RA_triangle1,
RA_triangle2,
) = _rotate_triangle(triangle, Jr)
# Prepare points and quaternion
pos_vec = Quaternion._prepare_vector(x, y, z)
# Rotate points
x_rot, y_rot, z_rot = total_rotation * pos_vec
norm1 = norm_plane(triangle)
if _np.allclose(norm1, [0, -1, 0], atol=ALIGN_CUTOFF) and Jr < 0:
RA_triangle1, RA_triangle2 = RA_triangle2, RA_triangle1
Btx, Bty, Btz = self._calcB_2_triangles(
RA_triangle1,
RA_triangle2,
Jr,
x_rot - offset[0],
y_rot - offset[1],
z_rot - offset[2],
)
Bvec = Quaternion._prepare_vector(Btx, Bty, Btz)
Bx, By, Bz = total_rotation.get_conjugate() * Bvec
return Bx, By, Bz, rotated_triangle, offset, total_rotation
get_Jr(self)
Returns Magnetisation vector
Returns:
Type | Description |
---|---|
ndarray |
[Jx, Jy, Jz] |
Source code in pymagnet/magnets/_polygon3D.py
def get_Jr(self):
"""Returns Magnetisation vector
Returns:
ndarray: [Jx, Jy, Jz]
"""
return self.J
get_center(self)
Returns magnet center
Returns:
Type | Description |
---|---|
ndarray |
magnet center |
Source code in pymagnet/magnets/_polygon3D.py
def get_center(self):
"""Returns magnet center
Returns:
ndarray: magnet center
"""
return self.center
get_field(self, x, y, z)
Calculates the magnetic field at point(s) x,y,z due to a 3D magnet The calculations are always performed in local coordinates with the centre of the magnet at origin and z magnetisation pointing along the local z' axis.
The rotations and translations are performed first, and the internal field calculation functions are called.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
x |
float/array |
x co-ordinates |
required |
y |
float/array |
y co-ordinates |
required |
z |
float/array |
z co-ordinates |
required |
Returns:
Type | Description |
---|---|
tuple |
Bx(ndarray), By(ndarray), Bz(ndarray) field vector |
Source code in pymagnet/magnets/_polygon3D.py
def get_field(self, x, y, z):
"""Calculates the magnetic field at point(s) x,y,z due to a 3D magnet
The calculations are always performed in local coordinates with the centre of the magnet at origin and z magnetisation pointing along the local z' axis.
The rotations and translations are performed first, and the internal field calculation functions are called.
Args:
x (float/array): x co-ordinates
y (float/array): y co-ordinates
z (float/array): z co-ordinates
Returns:
tuple: Bx(ndarray), By(ndarray), Bz(ndarray) field vector
"""
B = self._get_field_internal(x, y, z)
return B.x, B.y, B.z
get_force_torque(self, depth=4, unit='mm')
Calculates the force and torque on a prism magnet due to all other magnets.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
depth |
int |
Number of recursions of division by 4 per simplex |
4 |
unit |
str |
Length scale. Defaults to 'mm'. |
'mm' |
Returns:
Type | Description |
---|---|
tuple |
force (ndarray (3,) ) and torque (ndarray (3,) ) |
Source code in pymagnet/magnets/_polygon3D.py
def get_force_torque(self, depth=4, unit="mm"):
"""Calculates the force and torque on a prism magnet due to all other magnets.
Args:
depth (int, optional): Number of recursions of division by 4 per simplex
unit (str, optional): Length scale. Defaults to 'mm'.
Returns:
tuple: force (ndarray (3,) ) and torque (ndarray (3,) )
"""
from ..forces._mesh_force import calc_force_mesh
force, torque = calc_force_mesh(self, depth, unit)
return force, torque
size(self)
Returns magnet dimesions
Returns:
Type | Description |
---|---|
size (ndarray) |
numpy array [width, depth, height] |
Source code in pymagnet/magnets/_polygon3D.py
def size(self):
"""Returns magnet dimesions
Returns:
size (ndarray): numpy array [width, depth, height]
"""
pass