Skip to content

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, vertices must not be None.

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