The idea behind magpylib is to provide a simple and easy-to-use interface for computing the magnetic field of magnets, currents and moments. The computations are based on (semi-)analytical solutions found in the literature, discussed in the [WIP] Physics & Computation section.
- The source module includes a set of classes that represent physical sources of the magnetic field (e.g. permanent magnets).
- The vector module includes functions for performance computation.
- The math module contains practical functions for working with angle-axis rotations and transformation to and from Euler angle representation.
- The Collection class is used to group sources and for common manipulation.
- The Sensor class represents a 3D magnetic sensor.
- The displaySystem function is used to create a graphical output of the system geometry.
Units and IO types¶
In magpylib all inputs and outputs are made in the physical units of
- Millimeter for lengths
- Degree for angles
- Millitesla for magnetization/remanence, magnetic moment and magnetic field,
- Ampere for currents.
Unless specifically state otherwise in the docstring (see vector package), scalar input can be of
float type and vector/matrix input can be given either in the form of a
list, as a
tuple or as a
The library output and all object attributes are either of
The Source Class¶
This is the core class of the library. The idea is that source objects represent physical magnetic field sources in Cartesian three-dimensional space. The following source types are currently implemented,
All source objects share various attributes and methods. The attributes characterize the source (e.g. position, orientation, dimension) while the methods can be used for geometric manipulation and for calculating the magnetic field. The figure below gives a graphical overview.
Position and Orientation¶
The most fundamental properties of a source object
s are position and orientation which are represented through the attributes
s.angle (float) and
s.axis (arr3). At source initialization, if no values are specified, the source object is initialized by default with
position=(0,0,0), and init orientation defined to be
Due to their different nature each source type is characterized by different attributes. However, in general the
position attribute refers to the position of the geometric center of the source. The init orientation generally defines sources standing upright oriented along the Cartesian coordinates axes, see e.g. the following image below.
An orientation of a source
s given by (
axis ) refers to a rotation of
s RELATIVE TO the init orientation about an axis specified by the
s.axis vector which is anchored at
s.position. The angle of this rotation is given by the
s.angle attribute. Mathematically, every possible orientation can be expressed by such a single angle-axis rotation. For easier use of the angle-axis rotation and transformation to Euler angles the Math Package provides some useful methods.
Dimension & Excitation¶
While position and orientation have default values, a source is defined through its geometry (e.g. cylinder) and excitation (e.g. magnetization vector) which must be initialized to provide meaning.
dimension input specifies the size of the source. However, as each source type requires different input parameters the format is always different:
Box.dimensionis a 3D array of the cuboid sides, [a,b,c]
Cylinder.dimensionis a 2D array of diameter and height, [d,h]
Sphere.dimensionis a float describing the diameter d
Facet.dimensionis a 3x3 array of the three corner vertices, [A,B,C]
Line.dimensionis a Nx3 array of N subsequent vertices, [V1,V2,V3,…]
Circular.dimensionis a float describing the diameter d
The excitation of a source is either the
current or the magnetic
- Magnet sources represent homogeneously magnetized permanent magnets (other types with radial or multipole magnetization are not implemented at this point). Such excitations are given by the
magnetization(vec3) input which is always given with respect to the init orientation of the magnet.
- Current sources represent electrical line currents. Their excitation is simply the electrical current in units of Ampere defined by the
- The moment class represents a magnetic dipole moment described by the
Detailed information about the attributes of each specific source type and how to initialize them can also be found in the respective class docstrings:
For convenience the attributes
position are initialized through the keywords
The following code shows how to initialize a source object, a D4H5 permanent magnet cylinder with diagonal magnetization, positioned with the center in the origin, standing upright with axis in z-direction.
from magpylib.source.magnet import Cylinder s = Cylinder( mag = [500,0,500], # The magnetization vector in mT. dim = [4,5]) # dimension (diameter,height) in mm. # no pos, angle, axis specified so default values are used print(s.magnetization) # Output: [500. 0. 500.] print(s.dimension) # Output: [4. 5.] print(s.position) # Output: [0. 0. 0.] print(s.angle) # Output: 0.0 print(s.axis) # Output: [0. 0. 1.]
Methods for Geometric Manipulation¶
In most cases we want to move the source to a designated position, orient it in a desired way or change its dimension dynamically. There are several ways to achieve this:
When initializing the source we can set all attributes as desired. So instead of moving one source around one could create a new source for each parameter set of interest.
Manipulation after initialization:
We initialize the source and manipulate it afterwards as desired by
- directly setting the source attributes (e.g.
- or by using provided methods of manipulation.
The latter is often the most practical and intuitive way. To this end the source class provides a set of methods for convenient geometric manipulation. The methods include
move for translation of the objects as well as
rotate for rotation operations. Upon application they will simply modify the source object attributes accordingly.
s.setPosition(newPos): Moves the source to the position given by the argument vector (
s.move(displacement): Moves the source by the argument vector. (
s.position + displacement)
s.setOrientation(angle,axis): Sets a new source orientation given by the arguments. (
s.rotate(ang,ax,anchor=anch): Rotates the source object by the angle
angabout the axis
axwhich passes through a position given by
anch. As a result, source position and orientation attributes are modified. If no value for anchor is specified, the anchor is set to the object position, which means that the object rotates about itself.
The following videos show the application of the four methods for geometric manipulation.
The following example code shows how geometric operations are applied to source objects.
from magpylib.source.magnet import Cylinder s = Cylinder( mag = [500,0,500], dim = [4,5]) print(s.position) # Output: [0. 0. 0.] s.move([1,2,3]) print(s.position) # Output: [1. 2. 3.] s.move([1,2,3]) print(s.position) # Output: [2. 4. 6.]
Calculating the Magnetic Field¶
To calculate the field, magpylib uses mostly analytical expressions that can be found in the literature. References, validity and discussion of these solutions can be found in the [WIP] Physics & Computation section. In a nutshell, the fields of the dipole and the currents are exact. The analytical magnet solutions deal with homogeneous, fixed magnetizations. For hard ferromagnets with large coercive fields like Ferrite, Neodyme and SmCo the error is typically below 2%.
There are two possibilities to calculate the magnetic field of a source object
- Using the
- Using the
The first method: Each source object (or collection)
s has a method
s.getB(pos) which returns the magnetic field generated by
s at the position
from magpylib.source.magnet import Cylinder s = Cylinder( mag = [500,0,500], dim = [4,5]) print(s.getB([4,4,4])) # Output: [ 7.69869084 15.407166 6.40155549]
The second method: In most cases one will be interested to determine the field for a set of positions, or for different magnet positions and orientations. While this can manually be achieved by looping
s.getB this is computationally inefficient. For performance computation the
magpylib.vector subpackage contains the
getBv functions that offer quick access to VECTORIZED CODE. A discussion of vectorization, SIMD and performance is presented in the [WIP] Physics & Computation section.
magpylib.vector.getBv functions evaluate the field for N different sets of input parameters. These N parameter sets are provided to the
getBv functions as arrays of size N for each input (e.g. an Nx3 array for the N different positions):
getBv_magnet(type, MAG, DIM, POSo, POSm, [angs1,angs2,...], [AXIS1,AXIS2,...], [ANCH1,ANCH2,...])
typeis a string that specifies the magnet geometry (e.g. ‘box’ or ‘sphere’).
MAGis an Nx3 array of magnetization vectors.
DIMis an Nx3 array of magnet dimensions.
POSois an Nx3 array of observer positions.
POSmis an Nx3 array of initial (before rotation) magnet positions.
- The inputs
[angs1, angs2, ...],
[AXIS1, AXIS2, ...],
[ANCH1, ANCH2, ...]are a lists of N/Nx3 arrays that correspond to angles, axes and anchors of rotation operations. By providing multiple list entries one can apply subsequent rotation operations. By ommitting these inputs it is assumed that no rotations are applied.
As a rule of thumb,
s.getB() will be faster than
getBv for ~5 or less field evaluations while the vectorized code will be up to ~100 times faster for 10 or more field evaluations. To achieve this performance it is critical that one follows the vectorized code paradigm (use only numpy native) when creating the
getBv inputs. This is demonstrated in the following example where the magnetic field at a fixed observer position is calculated for a magnet that moves linearly in x-direction above the observer.
import magpylib as magpy import numpy as np # vector size: we calculate the field N times with different inputs N = 1000 # Constant vectors, specify dtype mag = np.array([0,0,1000.]) # magnet magnetization dim = np.array([2,2,2.]) # magnet dimension poso = np.array([0,0,-4.]) # position of observer # magnet x-positions xMag = np.linspace(-10,10,N) # magpylib classic --------------------------- Bc = np.zeros((N,3)) for i,x in enumerate(xMag): s = magpy.source.magnet.Box(mag,dim,[x,0,0]) Bc[i] = s.getB(poso) # magpylib vector --------------------------- # Vectorizing input using numpy native instead of python loops MAG = np.tile(mag,(N,1)) DIM = np.tile(dim,(N,1)) POSo = np.tile(poso,(N,1)) POSm = np.c_[xMag,np.zeros((N,2))] # Evaluation of the N fields using vectorized code Bv = magpy.vector.getBv_magnet('box',MAG,DIM,POSo,POSm) # result ----------------------------------- # Bc == Bv ... up to some 1e-16 # Copare classic and vector computation times using e.g. time.perf_counter()
More examples of vectorized code can be found in the Vectorized Code Example section.
The functions included in the
magpylib.vector package do not check the input format. All input must be in the form of numpy arrays.
The idea behind the top level
magpylib.Collection class is to group multiple source objects for common manipulation and evaluation of the fields.
In principle a collection
c is simply a list of source objects that are collected in the attribute
c.sources (list). Operations applied individually to the collection will be applied to all sources that are part of the collection.
Collections can be constructed at initialization by simply giving the sources objects as arguments. It is possible to add single sources, lists of multiple sources and even other collection objects. All sources are simply added to the
sources attribute of the target collection.
With the collection kwarg
dupWarning=True, adding multiples of the same source will be prevented, and a warning will be displayed informing the user that a source object is already in the collection’s
source attribute. Adding the same object multiple times can be done by setting
In addition, the collection class features methods to add and remove sources for command line like manipulation. The method
c.addSources(*sources) will add all sources given to it to the collection
c. The method
c.removeSource(ref) will remove the referenced source from the collection. Here the
ref argument can be either a source or an integer indicating the reference position in the collection, and it defaults to the latest added source in the collection.
import magpylib as magpy #define some magnet objects mag1 = magpy.source.magnet.Box(mag=[1,2,3],dim=[1,2,3]) mag2 = magpy.source.magnet.Box(mag=[1,2,3],dim=[1,2,3],pos=[5,5,5]) mag3 = magpy.source.magnet.Box(mag=[1,2,3],dim=[1,2,3],pos=[-5,-5,-5]) #create/manipulate collection and print source positions c = magpy.Collection(mag1,mag2,mag3) print([s.position for s in c.sources]) #OUTPUT: [array([0., 0., 0.]), array([5., 5., 5.]), array([-5., -5., -5.])] c.removeSource(1) print([s.position for s in c.sources]) #OUTPUT: [array([0., 0., 0.]), array([-5., -5., -5.])] c.addSources(mag2) print([s.position for s in c.sources]) #OUTPUT: [array([0., 0., 0.]), array([-5., -5., -5.]), array([5., 5., 5.])]
All methods of geometric operation,
rotate are also methods of the collection class. A geometric operation applied to a collection is directly applied to each object within that collection individually. In practice this means that a whole group of magnets can be rotated about a common pivot point with a single command.
For calculating the magnetic field that is generated by a whole collection the method
getB is also available. The total magnetic field is simply given by the superposition of the fields of all sources.
Figure: Collection Example. Circular current sources are grouped into a collection to form a coil. The whole coil is then geometrically manipulated and the total magnetic field is calculated and shown in the xz-plane.
The Sensor Class¶
getB method will always calculate the field in the underlying canonical basis. But often one is dealing with moving and tilting sensors. For this magpylib also offers a
Geometrically, a sensor object
sens behaves just like a source object, having position and orientation attributes that can be set using the convenient methods
To return the field of the source
s as seen by the sensor one can use the method
s can be a source object or a collection of sources.
import magpylib as magpy # define sensor sens = magpy.Sensor(pos=[5,0,0]) # define source s = magpy.source.magnet.Sphere(mag=[123,0,0],dim=5) # determine sensor-field B1 = sens.getB(s) # rotate sensor about itself (no anchor specified) sens.rotate(90,[0,0,1]) # determine sensor-field B2 = sens.getB(s) # print fields print(B1) # output: [10.25 0. 0.] print(B2) # output: [0. -10.25 0.]
Display System Graphically¶
Then top level function
displaySystem(c) can be used to quickly check the geometry of a source-sensor-marker assembly graphically in a 3D plot. Here
c can be a source, a list of sources or a collection.
displaySystem uses the matplotlib package and its limited capabilities of 3D plotting which often results in bad object overlapping.
displaySystem(c) comes with several keyword arguments:
markers=listOfPosfor displaying designated reference positions. By default a marker is set at the origin. By providing
[a,b,c,'text']instead of just a simple position vector
'text'is displayed with the marker.
suppress=Truefor suppressing the immediate figure output when the function is called. To do so it is necessary to deactivate the interactive mode by calling
pyplot.ioff(). With Spyder’s IPython Inline plotting, graphs made with
displaySystem()can be blank if the
suppress=Trueoption is not used. Set IPython Graphics backend to Automatic or Qt5 instead of Inline in settings/IPython console/Graphics method to address this.
direc=Truefor displaying current and magnetization directions in the figure.
subplotAx=Nonefor displaying the plot on a designated figure subplot instance.
figsize=(8,8)for setting the size of the output graphic.
The following example code shows how to use
import magpylib as magpy # create sources s1 = magpy.source.magnet.Cylinder( mag = [1,1,0],dim = [4,5], pos = [0,0,5]) s2 = magpy.source.magnet.Box( mag = [0,0,-1],dim = [1,2,3],pos=[0,0,-5]) s3 = magpy.source.current.Circular( curr = 1, dim =10) #create collection c = magpy.Collection(s1,s2,s3) # create sensors se1 = magpy.Sensor(pos=[10,0,0]) se2 = magpy.Sensor(pos=[10,0,0]) se3 = magpy.Sensor(pos=[10,0,0]) se2.rotate(70,[0,0,1],anchor=[0,0,0]) se3.rotate(140,[0,0,1],anchor=[0,0,0]) #display system markerPos = [(0,0,0,'origin'),(10,10,10),(-10,-10,-10)] magpy.displaySystem(c,sensors=[se1,se2,se3],markers=markerPos)
Complex Magnet Geometries¶
As a result of the superposition principle complex magnet shapes and inhomogeneous magnetizations can be generated by combining multiple sources. Specifically, when two magnets overlap in this region a vector union applies. This means that in the overlap the magnetization vector is given by the sum of the two vectors of each object.
Geometric addition is simply achieved by placing magnets with similar magnetization next to each other. Subtraction is realized by placing a small magnet with opposite magnetization inside a large magnet. The magnetization vectors cancel in the overlap, meaning that a small volume is cut out from a larger one. An example of a hollow cylinder is given in the examples section: Complex Magnet Shapes: Hollow Cylinder.
The math package provides several practical functions that relate angle-axis (quaternion) rotations with the Euler angle rotations. All functions are also available in their vectorized versions for performance computation.
anglesFromAxis(axis): This function takes an arbitrary
axisargument (vec3) and returns its orientation given by the angles
(phi, theta)that are defined as in spherical coordinates.
phiis the azimuth angle and
thetais the polar angle.
import magpylib as magpy angles = magpy.math.anglesFromAxis([1,1,0]) print(angles) # Output = [45. 90.]
anglesFromAxisV(AXIS): This is the vectorized version of
anglesFromAxis. It takes an Nx3 array of axis-vectors and returns an Nx2 array of angle pairs. Each angle pair is
(phi,theta)which are azimuth and polar angle in a spherical coordinate system respectively.
import numpy as np import magpylib as magpy AX = np.array([[0,0,1],[0,0,1],[1,0,0]]) ANGS = magpy.math.anglesFromAxisV(AX) print(ANGS) # Output: [[0. 0.] [90. 90.] [0. 90.]])
axisFromAngles(angles): This function generates an axis (vec3) from the angle pair
phiis the azimuth angle and
thetais the polar angle of a spherical coordinate system.
import magpylib as magpy ax = magpy.math.axisFromAngles([90,90]) print(ax) # Output: [0.0 1.0 0.0]
axisFromAnglesV(ANGLES): This is the vectorized version of
axisFromAngles. It generates an Nx3 array of axis vectors from the Nx2 array of input angle pairs
angles. Each angle pair is
(phi,theta)which are azimuth and polar angle in a spherical coordinate system respectively.
import magpylib as magpy import numpy as np ANGS = np.array([[0,90],[90,180],[90,0]]) AX = magpy.math.axisFromAnglesV(ANGS) print(np.around(AX,4)) # Output: [[1. 0. 0.] [0. 0. -1.] [0. 0. 1.]]
randomAxis(): Designed for Monte Carlo simulations, this function returns a random axis (arr3) of length 1 with equal angular distribution.
import magpylib as magpy ax = magpy.math.randomAxis() print(ax) # Output: [-0.24834468 0.96858637 0.01285925]
randomAxisV(N): This is the vectorized version of
randomAxis. It simply returns an Nx3 array of random vectors.
import magpylib as magpy AXS = magpy.math.randomAxisV(3) print(AXS) #Output: [[ 0.39480364 -0.53600779 -0.74620757] # [ 0.02974442 0.10916333 0.9935787 ] # [-0.54639126 0.76659756 -0.33731997]]
angleAxisRotation(pos, angle, axis, anchor=[0,0,0]): This function applies an angle-axis rotation. The position vector
pos(vec3) is rotated by the angle
angle(float) about an axis defined by the
axisvector (vec3) which passes through the
anchorposition (vec3). The anchor argument is optional and is set to
import magpylib as magpy pos = [1,1,0] angle = -90 axis = [0,0,1] anchor = [1,0,0] posNew = magpy.math.angleAxisRotation(pos,angle,axis,anchor) print(posNew) # Output = [2. 0. 0.]
angleAxisRotationV(POS, ANG, AXS, ANCH): This is the vectorized version of
angleAxisRotation. Each entry of
POS(arrNx3) is rotated according to the angles
ANG(arrN), about the axis vectors
AXS(arrNx3) which pass throught the anchors
ANCH(arrNx3) where N refers to the length of the input vectors.
import magpylib as magpy import numpy as np POS = np.array([[1,0,0]]*5) # avoid this slow Python loop for performance conputation ANG = np.linspace(0,180,5) AXS = np.array([[0,0,1]]*5) # avoid this slow Python loop for performance conputation ANCH = np.zeros((5,3)) POSnew = magpy.math.angleAxisRotationV(POS,ANG,AXS,ANCH) print(np.around(POSnew,4)) # Output: [[ 1. 0. 0. ] # [ 0.7071 0.7071 0. ] # [ 0. 1. 0. ] # [-0.7071 0.7071 0. ] # [-1. 0. 0. ]]