Skip to content

Commit 1c3e8c0

Browse files
committed
force computation for dipole
1 parent 9f8cc41 commit 1c3e8c0

File tree

3 files changed

+108
-17
lines changed

3 files changed

+108
-17
lines changed

magpylib_force/force.py

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from magpylib._src.obj_classes.class_magnet_Cylinder import Cylinder
1313
from magpylib._src.obj_classes.class_magnet_CylinderSegment import CylinderSegment
1414
from magpylib._src.obj_classes.class_current_Circle import Circle
15+
from magpylib._src.obj_classes.class_misc_Dipole import Dipole
1516

1617
from magpylib_force.meshing import mesh_target
1718
from magpylib_force.utility import check_input_anchor
@@ -35,8 +36,8 @@ def getFT(sources, targets, anchor=None, eps=1e-5, squeeze=True):
3536
or a 1D list of l sources and/or collection objects.
3637
3738
targets: single object or 1D list of t objects that are Sphere, Cuboid, Polyline,
38-
Cylinder, or CylinderSegment. Force and Torque acting on targets in the magnetic
39-
field generated by the sources will be computed. A target must have a valid
39+
Cylinder, CylinderSegment, or Dipole. Force and Torque acting on targets in the magnetic
40+
field generated by the sources will be computed. A target (except of Dipole) must have a valid
4041
`meshing` parameter.
4142
4243
anchor: array_like, default=None
@@ -66,10 +67,10 @@ def getFT(sources, targets, anchor=None, eps=1e-5, squeeze=True):
6667

6768
# split targets into lists of similar types
6869
TARGET_TYPES = [
69-
Cuboid, Polyline, Sphere, Cylinder, CylinderSegment, Circle
70+
Cuboid, Polyline, Sphere, Cylinder, CylinderSegment, Circle, Dipole
7071
]
7172
getFT_FUNCS = [
72-
getFTmagnet, getFTcurrent, getFTmagnet, getFTmagnet, getFTmagnet, getFTcurrent_circ
73+
getFTmagnet, getFTcurrent, getFTmagnet, getFTmagnet, getFTmagnet, getFTcurrent_circ, getFTdipole
7374
]
7475
objects = [[] for _ in TARGET_TYPES]
7576
orders = [[] for _ in TARGET_TYPES]
@@ -184,6 +185,8 @@ def getFTmagnet(sources, targets, eps=1e-5, anchor=None):
184185
POSS[insti[i]:insti[i+1],j] = mesh + ev + tgt.position
185186

186187
BB = magpy.getB(sources, POSS, sumup=True)
188+
if BB.ndim == 2:
189+
BB = np.expand_dims(BB, axis=0)
187190
gradB = (BB[:,1::2]-BB[:,2::2]) / (2*eps)
188191
gradB = np.swapaxes(gradB,0,1)
189192

@@ -300,3 +303,61 @@ def getFTcurrent(sources, targets, anchor=None, eps=None):
300303
F = np.array([np.sum(F[insti[i]:insti[i+1]],axis=0) for i in range(tgt_number)])
301304

302305
return np.array((F, -T))
306+
307+
308+
def getFTdipole(sources, targets, anchor=None, eps=None):
309+
"""
310+
Compute force and torque acting on magnetic dipoles
311+
312+
Parameters
313+
----------
314+
sources: source and collection objects or 1D list thereof
315+
Sources that generate the magnetic field. Can be a single source (or collection)
316+
or a 1D list of l sources and/or collection objects.
317+
318+
targets: Dipole object or 1D list of Dipole objects
319+
Force and Torque acting on targets in the magnetic field generated by the sources
320+
will be computed.
321+
322+
eps: float, default=1e-5
323+
The magnetic field gradient is computed using finite differences (FD). eps is
324+
the FD step size. A good value is 1e-5 * characteristic system size (magnet size,
325+
distance between sources and targets, ...).
326+
327+
anchor: array_like, default=None
328+
The Force adds to the Torque via the anchor point. For a freely floating magnet
329+
this would be the barycenter. If `anchor=None`, this part of the Torque computation
330+
is ommitted.
331+
"""
332+
# number of magnets
333+
tgt_number = len(targets)
334+
335+
# field computation positions (1xfor B, 6x for gradB)
336+
POSS = np.zeros((tgt_number,7,3))
337+
338+
# moment of each instance
339+
MOM = np.zeros((tgt_number,3))
340+
341+
# MISSING: eps should be defined relative to the sizes of the objects
342+
eps_vec = np.array([(0,0,0),(eps,0,0),(-eps,0,0),(0,eps,0),(0,-eps,0),(0,0,eps),(0,0,-eps)])
343+
344+
for i,tgt in enumerate(targets):
345+
dipole_mom = tgt.orientation.apply(tgt.moment)
346+
MOM[i] = dipole_mom
347+
348+
for j,ev in enumerate(eps_vec):
349+
POSS[i,j] = ev + tgt.position
350+
351+
BB = magpy.getB(sources, POSS, sumup=True)
352+
if BB.ndim == 2:
353+
BB = np.expand_dims(BB, axis=0)
354+
gradB = (BB[:,1::2]-BB[:,2::2]) / (2*eps)
355+
gradB = np.swapaxes(gradB,0,1)
356+
357+
F = np.sum((gradB*MOM),axis=2).T
358+
#Ts = np.zeros((no_inst,3))
359+
T = np.cross(BB[:,0], MOM)
360+
if anchor is not None:
361+
T -= np.cross(POSS[:,0]-anchor, F)
362+
363+
return np.array((F, -T))

magpylib_force/utility.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from magpylib._src.obj_classes.class_magnet_Sphere import Sphere
1111
from magpylib._src.obj_classes.class_magnet_Cylinder import Cylinder
1212
from magpylib._src.obj_classes.class_magnet_CylinderSegment import CylinderSegment
13+
from magpylib._src.obj_classes.class_misc_Dipole import Dipole
1314

1415
def check_input_anchor(anchor):
1516
"""
@@ -35,23 +36,24 @@ def check_input_targets(targets):
3536
if not isinstance(targets, list):
3637
targets = [targets]
3738
for t in targets:
38-
if not isinstance(t, (Cuboid, Polyline, Sphere, Cylinder, CylinderSegment, Circle)):
39+
if not isinstance(t, (Cuboid, Polyline, Sphere, Cylinder, CylinderSegment, Circle, Dipole)):
3940
raise ValueError(
4041
"Bad `targets` input for getFT."
4142
" `targets` can only be Cuboids, Polylines, Spheres, Cylinders, "
4243
" CylinderSegments, and Circles."
4344
f" Instead receivd type {type(t)} target."
4445
)
45-
if not hasattr(t, "meshing"):
46-
raise ValueError(
47-
"Missing input for getFT `targets`."
48-
" `targets` must have the `meshing` parameter set."
49-
)
50-
if not isinstance(t, (Polyline, )):
51-
if np.isscalar(t.meshing):
52-
if t.meshing<20:
53-
warnings.warn(
54-
"Input parameter `meshing` is set to a low value which will result in "
55-
"inaccurate computation of force and torque."
56-
)
46+
if not isinstance(t, (Dipole, )):
47+
if not hasattr(t, "meshing"):
48+
raise ValueError(
49+
"Missing input for getFT `targets`."
50+
" `targets` must have the `meshing` parameter set."
51+
)
52+
if not isinstance(t, (Polyline, )):
53+
if np.isscalar(t.meshing):
54+
if t.meshing<20:
55+
warnings.warn(
56+
"Input parameter `meshing` is set to a low value which will result in "
57+
"inaccurate computation of force and torque."
58+
)
5759
return targets

tests/test_physics.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,3 +239,31 @@ def test_physics_force_between_cocentric_loops():
239239
F_ana = pf*( (2-k2)/(1-k2)*ellipe(k**2) - 2*ellipk(k**2) )
240240

241241
assert abs((F_num - F_ana)/(F_num + F_ana)) < 1e-5
242+
243+
244+
def test_physics_force_between_two_dipoles():
245+
"""
246+
Compare force between two magnetic Dipoles with well-known formula
247+
(e.g. https://en.wikipedia.org/wiki/Magnetic_dipole%E2%80%93dipole_interaction)
248+
"""
249+
m1, m2 = np.array((0.976, 4.304, 2.055)), np.array((0.878, -1.527, 2.918))
250+
p1, p2 = np.array((-1.248, 7.835, 9.273)), np.array((-2.331, 5.835, 0.578))
251+
252+
# numerical solution
253+
dipole1 = magpy.misc.Dipole(position=p1, moment=m1)
254+
dipole2 = magpy.misc.Dipole(position=p2, moment=m2)
255+
F_num = getFT(dipole1, dipole2, anchor=(0,0,0))[0,:]
256+
#F_num = getFT([dipole1], [dipole2, dipole2])
257+
258+
# analytical solution
259+
r = p2 - p1
260+
r_abs = np.linalg.norm(r)
261+
r_unit = r / r_abs
262+
F_ana = 3*magpy.mu_0 / (4*np.pi*r_abs**4) * (
263+
np.cross(np.cross(r_unit, m1), m2) +
264+
np.cross(np.cross(r_unit, m2), m1) -
265+
2*r_unit*np.dot(m1, m2) +
266+
5*r_unit*(np.dot(np.cross(r_unit, m1), np.cross(r_unit, m2)))
267+
)
268+
269+
np.testing.assert_allclose(F_num, F_ana)

0 commit comments

Comments
 (0)