Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit e4802a0

Browse files
committedDec 29, 2019
vectorized version of Circular current
1 parent b457924 commit e4802a0

File tree

7 files changed

+237
-31
lines changed

7 files changed

+237
-31
lines changed
 

‎CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ All notable changes to magpylib are documented here.
1010

1111
### Changed
1212
- Improved performance of getB for diametral magnetized Cylinders by 20%
13+
- IMPORTANT: position arguments of `getBv` functions have been flipped! First comes the source position POSm THEN the observer position POSo!
1314

1415
### Added
15-
- getBv_Cylinder
16-
- added private vectorized implementation ellipticV to mathLib_vector
16+
- completed the library vector functionality adding magnet Cylinder, moment Dipole, current Circular and Line. This includes adding several private vectorized functions (e.g. ellipticV) to mathLib_vector, adding respective tests and docu examples.
1717

1818
## [2.1.0b] - 2019-12-06
1919

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# -------------------------------------------------------------------------------
2+
# magpylib -- A Python 3 toolbox for working with magnetic fields.
3+
# Copyright (C) Silicon Austria Labs, https://silicon-austria-labs.com/,
4+
# Michael Ortner <magpylib@gmail.com>
5+
#
6+
# This program is free software: you can redistribute it and/or modify it under
7+
# the terms of the GNU Affero General Public License as published by the Free
8+
# Software Foundation, either version 3 of the License, or (at your option) any
9+
# later version.
10+
#
11+
# This program is distributed in the hope that it will be useful, but WITHOUT ANY
12+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
13+
# PARTICULAR PURPOSE. See the GNU Affero General Public License for more
14+
# details.
15+
#
16+
# You should have received a copy of the GNU Affero General Public License along
17+
# with this program. If not, see <https://www.gnu.org/licenses/>.
18+
# The acceptance of the conditions of the GNU Affero General Public License are
19+
# compulsory for the usage of the software.
20+
#
21+
# For contact information, reach out over at <magpylib@gmail.com> or our issues
22+
# page at https://www.github.com/magpylib/magpylib/issues.
23+
# -------------------------------------------------------------------------------
24+
25+
from numpy import sqrt, array, cos, sin, NaN, arctan2, empty, pi
26+
from magpylib._lib.mathLib_vector import ellipticKV, ellipticEV, angleAxisRotationV_priv
27+
from warnings import warn
28+
import numpy as np
29+
30+
31+
# %% CIRCULAR CURRENT LOOP
32+
# Describes the magnetic field of a circular current loop that lies parallel to
33+
# the x-y plane. The loop has the radius r0 and carries the current i0, the
34+
# center of the current loop lies at posCL
35+
36+
# i0 : float [A] current in the loop
37+
# d0 : float [mm] diameter of the current loop
38+
# posCL: arr3 [mm] Position of the center of the current loop
39+
40+
# VECTORIZATION
41+
42+
def Bfield_CircularCurrentLoopV(I0, D, POS):
43+
44+
R = D/2 #radius
45+
46+
N = len(D) # vector size
47+
48+
X,Y,Z = POS[:,0],POS[:,1],POS[:,2]
49+
50+
RR, PHI = sqrt(X**2+Y**2), arctan2(Y, X) # cylindrical coordinates
51+
52+
53+
deltaP = sqrt((RR+R)**2+Z**2)
54+
deltaM = sqrt((RR-R)**2+Z**2)
55+
kappa = deltaP**2/deltaM**2
56+
kappaBar = 1-kappa
57+
58+
# allocate solution vector
59+
field_R = empty([N])
60+
61+
# avoid discontinuity of Br on z-axis
62+
maskRR0 = RR == np.zeros([N])
63+
field_R[maskRR0] = 0
64+
# R-component computation
65+
notM = np.invert(maskRR0)
66+
field_R[notM] = -2*1e-4*(Z[notM]/RR[notM]/deltaM[notM])*(ellipticKV(kappaBar[notM]) -
67+
(2-kappaBar[notM])/(2-2*kappaBar[notM])*ellipticEV(kappaBar[notM]))
68+
69+
# Z-component computation
70+
field_Z = -2*1e-4*(1/deltaM)*(-ellipticKV(kappaBar)+(2-kappaBar -
71+
4*(R/deltaM)**2)/(2-2*kappaBar)*ellipticEV(kappaBar))
72+
73+
# transformation to cartesian coordinates
74+
Bcy = np.array([field_R,np.zeros(N),field_Z]).T
75+
AX = np.zeros([N,3])
76+
AX[:,2] = 1
77+
Bkart = angleAxisRotationV_priv(PHI/pi*180,AX,Bcy)
78+
79+
return (Bkart.T * I0).T * 1000 # to mT
80+

‎magpylib/_lib/fields/PM_Cylinder_vector.py

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -169,23 +169,8 @@ def Bfield_CylinderV(MAG, POS, DIM, Nphi0): # returns arr3
169169
mask0Inside = mask0 * (RR<R) * (abs(Z)<H/2)
170170
Bfield[mask0Inside,:2] += MAG[mask0Inside,:2]
171171

172-
print(mask0Inside)
173172
### END TRANS MAG CONTRIBUTION ###########################
174173

175174
return(Bfield)
176175

177176

178-
MAG = np.array([[0,0,-44],[0,0,55],[11,22,33],[-14,25,36],[17,-28,39],[-10,-21,32],[0,12,23],[0,-14,25],[16,0,27],[-18,0,29]])
179-
POS=MAG*0.1*np.array([.8,-1,-1.3])
180-
DIM = np.array([[1,1]]*10)
181-
182-
Bv = Bfield_CylinderV(MAG,POS,DIM,50)
183-
print(Bv)
184-
185-
MAG = np.array([[0,0,1],[0,1,0],[1,0,0],[0,1,1],[1,0,1],[1,1,0],[1,1,1]])
186-
POS = np.zeros([7,3])-.1
187-
DIM = np.ones([7,2])
188-
189-
Bv = Bfield_CylinderV(MAG,POS,DIM,50)
190-
191-

‎magpylib/_lib/getBvector.py

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,13 @@
2525
from magpylib._lib.fields.PM_Cylinder_vector import Bfield_CylinderV
2626
from magpylib._lib.fields.PM_Sphere_vector import Bfield_SphereV
2727
from magpylib._lib.fields.Moment_Dipole_vector import Bfield_DipoleV
28+
from magpylib._lib.fields.Current_CircularLoop_vector import Bfield_CircularCurrentLoopV
2829
from magpylib._lib.mathLib_vector import QconjV, QrotationV, QmultV, getRotQuatV, angleAxisRotationV_priv
2930
import numpy as np
3031

31-
def getBv_magnet(type,MAG,DIM,POSo,POSm,ANG=[],AX=[],ANCH=[],Nphi0=50):
32+
def getBv_magnet(type,MAG,DIM,POSm,POSo,ANG=[],AX=[],ANCH=[],Nphi0=50):
3233
"""
3334
Calculate the field of magnets using vectorized performance code.
34-
This function is faster than getB when more than ~5 fields are evaluated.
3535
3636
Parameters
3737
----------
@@ -106,16 +106,77 @@ def getBv_magnet(type,MAG,DIM,POSo,POSm,ANG=[],AX=[],ANCH=[],Nphi0=50):
106106
# -------------------------------------------------------------------------------
107107

108108

109-
def getBv_current():
110-
return 0
109+
def getBv_current(type,CURR,DIM,POSm,POSo,ANG=[],AX=[],ANCH=[]):
110+
"""
111+
Calculate the field of currents using vectorized performance code.
112+
113+
Parameters
114+
----------
115+
116+
type : string
117+
source type either 'circular' or 'line'
118+
119+
MAG : Nx3 numpy array float [mT]
120+
vector of N magnetizations.
121+
122+
DIM : NxY numpy array float [mm]
123+
vector of N dimensions for each evaluation. The form of this vector depends
124+
on the source type. Y=3/2/1 for box/cylinder/sphere
125+
126+
POSo : Nx3 numpy array float [mm]
127+
vector of N positions of the observer.
128+
129+
POSm : Nx3 numpy array float [mm]
130+
vector of N initial source positions. These positions will be adjusted by
131+
the given rotation parameters.
132+
133+
ANG=[] : length M list of size N numpy arrays float [deg]
134+
Angles of M subsequent rotation operations applied to the N-sized POSm and
135+
the implicit source orientation.
136+
137+
AX=[] : length M list of Nx3 numpy arrays float []
138+
Axis vectors of M subsequent rotation operations applied to the N-sized
139+
POSm and the implicit source orientation.
140+
141+
ANCH=[] : length M list of Nx3 numpy arrays float [mm]
142+
Anchor positions of M subsequent rotations applied ot the N-sized POSm and
143+
the implicit source orientation.
144+
"""
145+
N = len(POSo)
146+
147+
Q = np.zeros([N,4])
148+
Q[:,0] = 1 # init orientation
149+
150+
Pm = POSm #initial position
151+
152+
#apply rotation operations
153+
for ANGLE,AXIS,ANCHOR in zip(ANG,AX,ANCH):
154+
Q = QmultV(getRotQuatV(ANGLE,AXIS),Q)
155+
Pm = angleAxisRotationV_priv(ANGLE,AXIS,Pm-ANCHOR)+ANCHOR
156+
157+
# transform into CS of source
158+
POSrel = POSo-Pm #relative position
159+
Qc = QconjV(Q) #orientierung
160+
POSrot = QrotationV(Qc,POSrel) #rotation der pos in das CS der Quelle
161+
162+
# calculate field
163+
if type == 'circular':
164+
Brot = Bfield_CircularCurrentLoopV(CURR, DIM, POSrot)
165+
else:
166+
print('Bad type')
167+
return 0
168+
169+
# transform back
170+
B = QrotationV(Q,Brot) #rückrotation des feldes
171+
172+
return B
111173

112174
# -------------------------------------------------------------------------------
113175
# -------------------------------------------------------------------------------
114176

115-
def getBv_moment(type,MOM,POSo,POSm,ANG=[],AX=[],ANCH=[]):
177+
def getBv_moment(type,MOM,POSm,POSo,ANG=[],AX=[],ANCH=[]):
116178
"""
117179
Calculate the field of magnetic moments using vectorized performance code.
118-
This function is faster than getB when more than ~5 fields are evaluated.
119180
120181
Parameters
121182
----------

‎magpylib/_lib/mathLib_vector.py

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ def anglesFromAxisV(AXIS):
210210
return np.array([PHI, TH]).transpose()
211211

212212

213+
213214
def angleAxisRotationV(POS,ANG,AXIS,ANCHOR):
214215
"""
215216
This is the vectorized version of angleAxisRotation(). Each entry
@@ -258,8 +259,12 @@ def angleAxisRotationV(POS,ANG,AXIS,ANCHOR):
258259
return POSnew
259260

260261

261-
# vectorized version of elliptic integral
262+
262263
def ellipticV(kc,p,c,s):
264+
'''
265+
vectorized version of the elliptic integral
266+
original implementation from paper Kirby
267+
'''
263268

264269
#if kc == 0:
265270
# return NaN
@@ -319,4 +324,39 @@ def ellipticV(kc,p,c,s):
319324
# entries are reiterated
320325
mask = (np.abs(g-k) > g*errtol)
321326

322-
return(np.pi/2)*(ss+cc*em)/(em*(em+pp))
327+
return(np.pi/2)*(ss+cc*em)/(em*(em+pp))
328+
329+
330+
331+
def ellipticKV(x):
332+
'''
333+
special case complete elliptic integral of first kind ellipticK
334+
0 <= x <1
335+
'''
336+
N = len(x)
337+
onez = np.ones([N])
338+
return ellipticV((1-x)**(1/2.), onez, onez, onez)
339+
340+
341+
342+
def ellipticEV(x):
343+
'''
344+
special case complete elliptic integral of second kind ellipticE
345+
E(x) = int_0^pi/2 (1-x sin(phi)^2)^(1/2) dphi
346+
requires x < 1 !
347+
'''
348+
N = len(x)
349+
onez = np.ones([N])
350+
return ellipticV((1-x)**(1/2.), onez, onez, 1-x)
351+
352+
353+
354+
def ellipticPiV(x, y):
355+
'''
356+
special case complete elliptic integral of third kind ellipticPi
357+
E(x) = int_0^pi/2 (1-x sin(phi)^2)^(1/2) dphi
358+
requires x < 1 !
359+
'''
360+
N = len(x)
361+
onez = np.ones([N])
362+
return ellipticV((1-y)**(1/2.), 1-x, onez, onez)

‎tests/test_MathVector.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
from magpylib._lib.mathLib import getRotQuat, axisFromAngles, anglesFromAxis, angleAxisRotation, elliptic
55
from magpylib.math import randomAxisV
66
import numpy as np
7+
from magpylib._lib.mathLib import ellipticK, ellipticE, ellipticPi
8+
from magpylib._lib.mathLib_vector import ellipticKV, ellipticEV, ellipticPiV
79

810

911
def test_QV():
@@ -105,4 +107,22 @@ def test_ellipticV():
105107
#vector solution
106108
solV = ellipticV(INP[:,0],INP[:,1],INP[:,2],INP[:,3])
107109

108-
assert np.amax(abs(solC-solV)) < 1e-10
110+
assert np.amax(abs(solC-solV)) < 1e-10
111+
112+
113+
114+
def test_ellipticSpecialCases():
115+
X = np.linspace(0,.9999,33)
116+
Y = np.linspace(0,.9999,33)[::-1]
117+
118+
solV = ellipticKV(X)
119+
solC = np.array([ellipticK(x) for x in X])
120+
assert np.amax(abs(solV-solC)) < 1e-15
121+
122+
solV = ellipticEV(X)
123+
solC = np.array([ellipticE(x) for x in X])
124+
assert np.amax(abs(solV-solC)) < 1e-15
125+
126+
solV = ellipticPiV(X,Y)
127+
solC = np.array([ellipticPi(x,y) for x,y in zip(X,Y)])
128+
assert np.amax(abs(solV-solC)) < 1e-15

‎tests/test_magpyVector.py

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import numpy as np
44
from magpylib.source.magnet import Box, Cylinder, Sphere
55
from magpylib.source.moment import Dipole
6+
from magpylib.source.current import Circular
67
from magpylib.vector import getBv_magnet, getBv_current, getBv_moment
78
from magpylib.math import axisFromAngles
89
from magpylib.math import angleAxisRotationV
@@ -59,7 +60,7 @@ def getB(phi,th,psi):
5960

6061
# vector
6162
DIM = np.array([dim]*NN)
62-
Bv = getBv_magnet('box',MAG,DIM,POSo,POSm,[ANG1,ANG2],[AX1,AX2],[ANCH1,ANCH2])
63+
Bv = getBv_magnet('box',MAG,DIM,POSm,POSo,[ANG1,ANG2],[AX1,AX2],[ANCH1,ANCH2])
6364
Bv = Bv.reshape([Nth,Npsi,Nphi,3])
6465

6566
# assert
@@ -78,7 +79,7 @@ def getB2(phi,th,psi):
7879

7980
# vector
8081
DIM2 = np.array([dim2]*NN)
81-
Bv = getBv_magnet('sphere',MAG,DIM2,POSo,POSm,[ANG1,ANG2],[AX1,AX2],[ANCH1,ANCH2])
82+
Bv = getBv_magnet('sphere',MAG,DIM2,POSm,POSo,[ANG1,ANG2],[AX1,AX2],[ANCH1,ANCH2])
8283
Bv = Bv.reshape([Nth,Npsi,Nphi,3])
8384

8485
#assert
@@ -92,7 +93,7 @@ def test_vectorMagnetCylinder():
9293
POSO = MAG*0.1*np.array([.8,-1,-1.3])+POSM
9394
DIM = np.ones([10,2])
9495

95-
Bv = getBv_magnet('cylinder',MAG,DIM,POSO,POSM)
96+
Bv = getBv_magnet('cylinder',MAG,DIM,POSM,POSO)
9697

9798
Bc = []
9899
for mag,posM,posO,dim in zip(MAG,POSM,POSO,DIM):
@@ -109,7 +110,7 @@ def test_vectorMagnetCylinder():
109110
DIM = np.ones([7,2])
110111
POSM = np.zeros([7,3])
111112

112-
Bv = getBv_magnet('cylinder',MAG,DIM,POSO,POSM,Nphi0=11)
113+
Bv = getBv_magnet('cylinder',MAG,DIM,POSM,POSO,Nphi0=11)
113114

114115
Bc = []
115116
for mag,posM,posO,dim in zip(MAG,POSM,POSO,DIM):
@@ -127,7 +128,7 @@ def test_vectorMomentDipole():
127128
POSM = np.ones([10,3])
128129
POSO = MOM*0.1*np.array([.8,-1,-1.3])+POSM
129130

130-
Bv = getBv_moment('dipole',MOM,POSO,POSM)
131+
Bv = getBv_moment('dipole',MOM,POSM,POSO)
131132

132133
Bc = []
133134
for mom,posM,posO in zip(MOM,POSM,POSO):
@@ -136,3 +137,22 @@ def test_vectorMomentDipole():
136137
Bc = np.array(Bc)
137138

138139
assert np.amax(abs(Bv-Bc)) < 1e-15
140+
141+
142+
143+
def test_vectorCurrentCircular():
144+
145+
I = np.ones([10])
146+
D = np.ones([10])*4
147+
Pm = np.zeros([10,3])
148+
Po = np.array([[0,0,1],[0,0,-1],[1,1,0],[1,-1,0],[-1,-1,0],[-1,1,0],[5,5,0],[5,-5,0],[-5,-5,0],[-5,5,0]])
149+
150+
Bc = []
151+
for i,d,pm,po in zip(I,D,Pm,Po):
152+
s = Circular(curr=i,dim=d,pos=pm)
153+
Bc += [s.getB(po)]
154+
Bc = np.array(Bc)
155+
156+
Bv = getBv_current('circular',I,D,Pm,Po)
157+
158+
assert np.amax(abs(Bc-Bv))<1e-10

0 commit comments

Comments
 (0)