Skip to content

Commit 8a14771

Browse files
Add current loop (#10)
* add Circle target * changelog * pylint * version bump
1 parent 030de15 commit 8a14771

File tree

7 files changed

+121
-10
lines changed

7 files changed

+121
-10
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Unpublished
22

3+
# v0.3.0 (2024/10/29)
4+
- Added the current.Cicle class to the force computation
5+
- Improved warning msg when meshing parameter not given
6+
- Added more physics tests
7+
38
# v0.2.0 (2024/10/11)
49
- Added a warning when no anchor is given to getFT to avoid misuse.
510
- Added Cylinder computation

magpylib_force/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
from magpylib_force.force import getFT
88

99
# module level dunders
10-
__version__ = "0.2.0"
11-
__author__ = "SAL"
10+
__version__ = "0.3.0"
11+
__author__ = "The Magpylib Team"
1212
__all__ = [
1313
"getFT",
1414
]

magpylib_force/force.py

+43-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
22
Force computation codes
33
"""
4+
import warnings
45

56
import magpylib as magpy
67
import numpy as np
@@ -10,6 +11,7 @@
1011
from magpylib._src.obj_classes.class_magnet_Sphere import Sphere
1112
from magpylib._src.obj_classes.class_magnet_Cylinder import Cylinder
1213
from magpylib._src.obj_classes.class_magnet_CylinderSegment import CylinderSegment
14+
from magpylib._src.obj_classes.class_current_Circle import Circle
1315

1416
from magpylib_force.meshing import mesh_target
1517
from magpylib_force.utility import check_input_anchor
@@ -63,8 +65,12 @@ def getFT(sources, targets, anchor=None, eps=1e-5, squeeze=True):
6365
n = len(targets)
6466

6567
# split targets into lists of similar types
66-
TARGET_TYPES = [Cuboid, Polyline, Sphere, Cylinder, CylinderSegment]
67-
getFT_FUNCS = [getFTmagnet, getFTcurrent, getFTmagnet, getFTmagnet, getFTmagnet]
68+
TARGET_TYPES = [
69+
Cuboid, Polyline, Sphere, Cylinder, CylinderSegment, Circle
70+
]
71+
getFT_FUNCS = [
72+
getFTmagnet, getFTcurrent, getFTmagnet, getFTmagnet, getFTmagnet, getFTcurrent_circ
73+
]
6874
objects = [[] for _ in TARGET_TYPES]
6975
orders = [[] for _ in TARGET_TYPES]
7076

@@ -192,6 +198,41 @@ def getFTmagnet(sources, targets, eps=1e-5, anchor=None):
192198

193199
return np.array((F, -T))
194200

201+
202+
def getFTcurrent_circ(sources, targets, anchor=None, eps=None):
203+
"""
204+
The current force computation is designed for Polylines.
205+
To make use of this, Circle objects are transformed into Polylines.
206+
Its a dirty solution that should be fixed at some point
207+
"""
208+
new_targets = []
209+
for tgt in targets:
210+
n = tgt.meshing
211+
if n<20:
212+
warnings.warn(
213+
"Circle meshing parameter with low value detected. "
214+
"This will give bad results. Please increase meshing. "
215+
"Circle meshing defines the number of points on the circle."
216+
)
217+
r = tgt.diameter/2
218+
verts = np.zeros((3,n))
219+
verts[2] = np.linspace(0, 2*np.pi, n)
220+
verts[0] = r*np.cos(verts[2])
221+
verts[1] = r*np.sin(verts[2])
222+
verts[2] = 0
223+
224+
poly = magpy.current.Polyline(
225+
vertices=verts.T,
226+
current=tgt.current,
227+
position=tgt.position,
228+
orientation=tgt.orientation,
229+
)
230+
poly.meshing=1
231+
new_targets.append(poly)
232+
233+
return getFTcurrent(sources, new_targets, anchor, eps)
234+
235+
195236
#pylint: disable=unused-argument
196237
def getFTcurrent(sources, targets, anchor=None, eps=None):
197238
"""
@@ -203,7 +244,6 @@ def getFTcurrent(sources, targets, anchor=None, eps=None):
203244
segements = linear segments within Polyline objects
204245
instances = computation instances, each segment is split into `meshing` points
205246
"""
206-
207247
# number of Polylines
208248
tgt_number = len(targets)
209249

magpylib_force/meshing.py

-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
#pylint: disable=too-many-locals
1515

16-
1716
def mesh_target(obj):
1817
"""
1918
create mesh for target objects

magpylib_force/utility.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import numpy as np
77
from magpylib._src.obj_classes.class_magnet_Cuboid import Cuboid
88
from magpylib._src.obj_classes.class_current_Polyline import Polyline
9+
from magpylib._src.obj_classes.class_current_Circle import Circle
910
from magpylib._src.obj_classes.class_magnet_Sphere import Sphere
1011
from magpylib._src.obj_classes.class_magnet_Cylinder import Cylinder
1112
from magpylib._src.obj_classes.class_magnet_CylinderSegment import CylinderSegment
@@ -34,16 +35,16 @@ def check_input_targets(targets):
3435
if not isinstance(targets, list):
3536
targets = [targets]
3637
for t in targets:
37-
if not isinstance(t, (Cuboid, Polyline, Sphere, Cylinder, CylinderSegment)):
38+
if not isinstance(t, (Cuboid, Polyline, Sphere, Cylinder, CylinderSegment, Circle)):
3839
raise ValueError(
3940
"Bad `targets` input for getFT."
40-
" `targets` can only be Cuboids, Polylines, Spheres, Cylinders, and"
41-
" CylinderSegments."
41+
" `targets` can only be Cuboids, Polylines, Spheres, Cylinders, "
42+
" CylinderSegments, and Circles."
4243
f" Instead receivd type {type(t)} target."
4344
)
4445
if not hasattr(t, "meshing"):
4546
raise ValueError(
46-
"Bad `targets` input for getFT."
47+
"Missing input for getFT `targets`."
4748
" `targets` must have the `meshing` parameter set."
4849
)
4950
return targets

tests/test_physics.py

+28
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import numpy as np
33
import magpylib as magpy
44
from magpylib_force.force import getFT
5+
from scipy.special import ellipe
6+
from scipy.special import ellipk
57

68

79
# def test_physics_loop_torque():
@@ -304,3 +306,29 @@ def test_torque_sign():
304306
_,T = getFT(mag, loop, anchor=(0,0,0))
305307

306308
assert T[1] < 0
309+
310+
311+
def test_force_between_cocentric_loops():
312+
"""
313+
compare the numerical solution against the analytical solution of the force between two
314+
cocentric current loops.
315+
See e.g. IEEE TRANSACTIONS ON MAGNETICS, VOL. 49, NO. 8, AUGUST 2013
316+
"""
317+
z1, z2 = 0.123, 1.321
318+
i1, i2 = 3.2, 5.1
319+
r1, r2 = 1.2, 2.3
320+
321+
# numerical solution
322+
loop1 = magpy.current.Circle(diameter=2*r1, current=i1, position=(0,0,z1))
323+
loop2 = magpy.current.Circle(diameter=2*r2, current=i2, position=(0,0,z2))
324+
loop2.meshing=1000
325+
F_num = getFT(loop1, loop2, anchor=(0,0,0))[0,2]
326+
327+
# analytical solution
328+
k2 = 4*r1*r2 / ((r1+r2)**2+(z1-z2)**2)
329+
k = np.sqrt(k2)
330+
pf = magpy.mu_0*i1*i2*(z1-z2)*k / 4 / np.sqrt(r1*r2)
331+
F_ana = pf*( (2-k2)/(1-k2)*ellipe(k**2) - 2*ellipk(k**2) )
332+
333+
assert abs((F_num - F_ana)/(F_num + F_ana)) < 1e-5
334+

tests/test_self_consistency.py

+38
Original file line numberDiff line numberDiff line change
@@ -229,3 +229,41 @@ def test_consistency_cylinder_segment_cuboid():
229229
ft2 = getFT(cube, cyls, anchor=(0,0,0))
230230

231231
assert np.amax(abs((ft1+ft2)/(ft1-ft2))) < 0.09
232+
233+
234+
def test_consistency_polyline_circle():
235+
"""
236+
compare Polyline solution to circle solution
237+
"""
238+
239+
src = magpy.magnet.Sphere(diameter=1, polarization=(1,2,3), position=(0,0,-1))
240+
241+
# circle
242+
loop1 = magpy.current.Circle(diameter=3, current=123)
243+
loop1.meshing=500
244+
# polyline
245+
rr = loop1.diameter/2
246+
ii = loop1.current
247+
phis = np.linspace(0,2*np.pi,500)
248+
verts = [(rr*np.cos(p), rr*np.sin(p), 0) for p in phis]
249+
loop2 = magpy.current.Polyline(current=ii, vertices=verts)
250+
loop2.meshing=1
251+
252+
F1,T1 = getFT(src, loop1, anchor=(0,0,0))
253+
F2,T2 = getFT(src, loop2, anchor=(0,0,0))
254+
assert abs(np.linalg.norm(F1-F2)/np.linalg.norm(F1+F2)) < 1e-7
255+
assert abs(np.linalg.norm(T1-T2)/np.linalg.norm(T1+T2)) < 1e-7
256+
257+
loop1.move((1.123,2.321,.123))
258+
loop2.move((1.123,2.321,.123))
259+
F1,T1 = getFT(src, loop1, anchor=(0,0,0))
260+
F2,T2 = getFT(src, loop2, anchor=(0,0,0))
261+
assert abs(np.linalg.norm(F1-F2)/np.linalg.norm(F1+F2)) < 1e-7
262+
assert abs(np.linalg.norm(T1-T2)/np.linalg.norm(T1+T2)) < 1e-7
263+
264+
loop1.rotate_from_angax(20, 'x')
265+
loop2.rotate_from_angax(20, 'x')
266+
F1,T1 = getFT(src, loop1, anchor=(0,0,0))
267+
F2,T2 = getFT(src, loop2, anchor=(0,0,0))
268+
assert abs(np.linalg.norm(F1-F2)/np.linalg.norm(F1+F2)) < 1e-7
269+
assert abs(np.linalg.norm(T1-T2)/np.linalg.norm(T1+T2)) < 1e-7

0 commit comments

Comments
 (0)