11import numpy as np
22import magpylib as magpy
3+ from magpylib_force .meshing import mesh_target
34from magpylib_force .meshing import mesh_cuboid
45from magpylib_force .utility import check_input_anchor
56from magpylib_force .utility import check_input_targets
67from magpylib ._src .obj_classes .class_current_Polyline import Polyline
78from magpylib ._src .obj_classes .class_magnet_Cuboid import Cuboid
9+ from magpylib ._src .obj_classes .class_magnet_Sphere import Sphere
810
911
1012def getFT (sources , targets , anchor = None , eps = 1e-5 , squeeze = True ):
@@ -21,62 +23,90 @@ def getFT(sources, targets, anchor=None, eps=1e-5, squeeze=True):
2123 targets: single object or 1D list of t objects that are in [Cuboid, Polyline]
2224 Force and Torque acting on targets in the magnetic field generated by the sources
2325 will be computed. A target must have a valid `meshing` parameter.
24-
25- eps: float, default=1e-5
26- For magnet-targets the magnetic field gradient is computed using finite
27- differences (FD). `eps` is the FD step size. A good value
28- is 1e-5 * characteristic system size (magnet size, distance between sources
29- and targets, ...).
3026
3127 anchor: array_like, default=None
3228 The Force adds to the Torque via the anchor point. For a freely floating magnet
3329 this would be the barycenter. If `anchor=None`, this part of the Torque computation
3430 is ommitted.
3531
32+ eps: float, default=1e-5
33+ This is only used for magnet targets for computing the magnetic field gradient
34+ using finite differences (FD). `eps` is the FD step size. A good value
35+ is 1e-5 * characteristic_system_size (magnet size, distance between sources
36+ and targets, ...).
37+
3638 squeeze: bool, default=True
3739 The output of the computation has the shape (n,3) where n corresponds to the number
3840 of targets. By default this is reduced to (3,) when there is only one target.
3941
4042 Returns
4143 -------
42- Force-Torque as ndarray of shape (2,3), of (t,2,3) when t targets are given
44+ Force-Torque as ndarray of shape (2,3), or (t,2,3) when t targets are given
4345 """
4446 anchor = check_input_anchor (anchor )
4547 targets = check_input_targets (targets )
46- # MISSING: allow Collections
48+ # MISSING: allow Collections as targets
4749
4850 n = len (targets )
4951
50- cuboids , order_cub = [], []
51- polylines , order_poly = [], []
52- for i ,t in enumerate (targets ):
53- if isinstance (t , Cuboid ):
54- cuboids .append (t )
55- order_cub .append (i )
56- elif isinstance (t , Polyline ):
57- polylines .append (t )
58- order_poly .append (i )
52+ # split targets into lists of similar types
53+ TARGET_TYPES = [Cuboid , Polyline , Sphere ]
54+ getFT_FUNCS = [getFTmagnet , getFTcurrent , getFTmagnet ]
55+ objects = [[] for _ in TARGET_TYPES ]
56+ orders = [[] for _ in TARGET_TYPES ]
5957
58+ for i ,tgt in enumerate (targets ):
59+ for j ,ttyp in enumerate (TARGET_TYPES ):
60+ if isinstance (tgt , ttyp ):
61+ objects [j ].append (tgt )
62+ orders [j ].append (i )
63+
64+ # allocate FT
6065 FT = np .zeros ((n , 2 , 3 ))
61- if cuboids :
62- FT_cube = getFTcube (sources , cuboids , eps = eps , anchor = anchor , squeeze = False )
63- FT_cube = np .swapaxes (FT_cube , 0 , 1 )
64- for ft ,o in zip (FT_cube , order_cub ):
65- FT [o ] = ft
66- if polylines :
67- FT_poly = getFTwire (sources , polylines , anchor = anchor , squeeze = False )
68- FT_poly = np .swapaxes (FT_poly , 0 , 1 )
69- for ft ,o in zip (FT_poly , order_poly ):
70- FT [o ] = ft
71- #FT = np.swapaxes(FT, 0, 1)
66+
67+ # FT-computation and broadcasting
68+ for i in range (len (TARGET_TYPES )):
69+ if objects [i ]:
70+ ft_part = getFT_FUNCS [i ](sources , objects [i ], eps = eps , anchor = anchor )
71+ ft_part = np .swapaxes (ft_part , 0 , 1 )
72+ for ft ,j in zip (ft_part , orders [i ]):
73+ FT [j ] = ft
74+
7275 if squeeze :
7376 return np .squeeze (FT )
7477 return FT
7578
7679
77- def getFTcube ( sources , targets , eps = 1e-5 , anchor = None , squeeze = True ):
80+ def mesh_instances ( targets ):
7881 """
79- Compute force and torque acting on a Cuboid magnet.
82+ utility for getFTmagnet: compute number of mesh instances for each target magnet
83+ targets: list of target magnet objects
84+ returns: int
85+ """
86+ if isinstance (targets [0 ], Cuboid ):
87+ return np .array ([np .prod (tgt .meshing ) for tgt in targets ])
88+ elif isinstance (targets [0 ], Sphere ):
89+ SPH_MESH = (0 ,1 ,8 ,19 ,32 ,81 ,136 ,179 ,280 ,389 ,552 ,739 ,912 ,1189 ,1472 ,1791 ,2176 ,2553 ,3112 ,3695 ,4224 )
90+ return np .array ([SPH_MESH [tgt .meshing ] for tgt in targets ])
91+ raise RuntimeError ("fktn `mesh_instances` - I shouldt be here." )
92+
93+
94+ def volume (target ):
95+ """
96+ utility for getFTmagnet: compute physical volume of target magnets
97+ target: target magnet object
98+ return: volume, float
99+ """
100+ if isinstance (target , Cuboid ):
101+ return np .prod (target .dimension )
102+ elif isinstance (target , Sphere ):
103+ return target .diameter ** 3 * np .pi / 6
104+ raise RuntimeError ("fktn `volume` - I shouldt be here." )
105+
106+
107+ def getFTmagnet (sources , targets , eps = 1e-5 , anchor = None ):
108+ """
109+ Compute force and torque acting on magnets of same type
80110
81111 Parameters
82112 ----------
@@ -97,17 +127,12 @@ def getFTcube(sources, targets, eps=1e-5, anchor=None, squeeze=True):
97127 The Force adds to the Torque via the anchor point. For a freely floating magnet
98128 this would be the barycenter. If `anchor=None`, this part of the Torque computation
99129 is ommitted.
100-
101- squeeze: bool, default=True
102- The output of the computation has the shape (n,3) where n corresponds to the number
103- of targets. By default this is reduced to (3,) when there is only one target.
104130 """
105-
106- # number of Cuboids
131+ # number of magnets
107132 tgt_number = len (targets )
108133
109- # number of instances of each Cuboid
110- inst_numbers = np . array ([ np . prod ( tgt . meshing ) for tgt in targets ] )
134+ # number of instances of each magnet
135+ inst_numbers = mesh_instances ( targets )
111136
112137 # total number of instances
113138 no_inst = np .sum (inst_numbers )
@@ -121,15 +146,15 @@ def getFTcube(sources, targets, eps=1e-5, anchor=None, squeeze=True):
121146 # moment of each instance
122147 MOM = np .zeros ((no_inst ,3 ))
123148
124- # MISSING: eps should be defined relative to the sizes of the system
149+ # MISSING: eps should be defined relative to the sizes of the objects
125150 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 )])
126151
127152 for i ,tgt in enumerate (targets ):
128- tgt_vol = np . prod (tgt . dimension )
153+ tgt_vol = volume (tgt )
129154 inst_mom = tgt .magnetization * tgt_vol / inst_numbers [i ]
130155 MOM [insti [i ]:insti [i + 1 ]] = inst_mom
131156
132- mesh = mesh_cuboid (tgt )
157+ mesh = mesh_target (tgt )
133158 for j ,ev in enumerate (eps_vec ):
134159 POSS [insti [i ]:insti [i + 1 ],j ] = mesh + ev + tgt .position
135160
@@ -146,17 +171,94 @@ def getFTcube(sources, targets, eps=1e-5, anchor=None, squeeze=True):
146171 T = np .array ([np .sum (Ts [insti [i ]:insti [i + 1 ]],axis = 0 ) for i in range (tgt_number )])
147172 F = np .array ([np .sum (Fs [insti [i ]:insti [i + 1 ]],axis = 0 ) for i in range (tgt_number )])
148173
149- if squeeze :
150- F = np .squeeze (F )
151- T = np .squeeze (T )
152-
153174 return np .array ((F , T ))
154175
155176
156177
157- def getFTwire (sources , targets , anchor = None , squeeze = True ):
178+ # def getFTcube(sources, targets, eps=1e-5, anchor=None, squeeze=True):
179+ # """
180+ # Compute force and torque acting on a Cuboid magnet.
181+
182+ # Parameters
183+ # ----------
184+ # sources: source and collection objects or 1D list thereof
185+ # Sources that generate the magnetic field. Can be a single source (or collection)
186+ # or a 1D list of l sources and/or collection objects.
187+
188+ # targets: Cuboid object or 1D list of Cuboid objects
189+ # Force and Torque acting on targets in the magnetic field generated by the sources
190+ # will be computed. A target must have a valid `meshing` parameter.
191+
192+ # eps: float, default=1e-5
193+ # The magnetic field gradient is computed using finite differences (FD). eps is
194+ # the FD step size. A good value is 1e-5 * characteristic system size (magnet size,
195+ # distance between sources and targets, ...).
196+
197+ # anchor: array_like, default=None
198+ # The Force adds to the Torque via the anchor point. For a freely floating magnet
199+ # this would be the barycenter. If `anchor=None`, this part of the Torque computation
200+ # is ommitted.
201+
202+ # squeeze: bool, default=True
203+ # The output of the computation has the shape (n,3) where n corresponds to the number
204+ # of targets. By default this is reduced to (3,) when there is only one target.
205+ # """
206+
207+ # # number of Cuboids
208+ # tgt_number = len(targets)
209+
210+ # # number of instances of each Cuboid
211+ # inst_numbers = np.array([np.prod(tgt.meshing) for tgt in targets])
212+
213+ # # total number of instances
214+ # no_inst = np.sum(inst_numbers)
215+
216+ # # cumsum of number of instances (used for indexing)
217+ # insti = np.r_[0, np.cumsum(inst_numbers)]
218+
219+ # # field computation positions (1xfor B, 6x for gradB)
220+ # POSS = np.zeros((no_inst,7,3))
221+
222+ # # moment of each instance
223+ # MOM = np.zeros((no_inst,3))
224+
225+ # # MISSING: eps should be defined relative to the sizes of the system
226+ # 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)])
227+
228+ # for i,tgt in enumerate(targets):
229+ # tgt_vol = np.prod(tgt.dimension)
230+ # inst_mom = tgt.magnetization * tgt_vol / inst_numbers[i]
231+ # MOM[insti[i]:insti[i+1]] = inst_mom
232+
233+ # mesh = mesh_cuboid(tgt)
234+ # for j,ev in enumerate(eps_vec):
235+ # POSS[insti[i]:insti[i+1],j] = mesh + ev + tgt.position
236+
237+ # BB = magpy.getB(sources, POSS, sumup=True)
238+ # gradB = (BB[:,1::2]-BB[:,2::2]) / (2*eps)
239+ # gradB = np.swapaxes(gradB,0,1)
240+
241+ # Fs = np.sum((gradB*MOM),axis=2).T
242+ # #Ts = np.zeros((no_inst,3))
243+ # Ts = np.cross(BB[:,0], MOM)
244+ # if anchor is not None:
245+ # Ts -= np.cross(POSS[:,0]-anchor, Fs)
246+
247+ # T = np.array([np.sum(Ts[insti[i]:insti[i+1]],axis=0) for i in range(tgt_number)])
248+ # F = np.array([np.sum(Fs[insti[i]:insti[i+1]],axis=0) for i in range(tgt_number)])
249+
250+ # if squeeze:
251+ # F = np.squeeze(F)
252+ # T = np.squeeze(T)
253+
254+ # return np.array((F, T))
255+
256+
257+
258+ def getFTcurrent (sources , targets , anchor = None , eps = None ):
158259 """
159260 compute force acting on tgt Polyline
261+ eps is a dummy variable that is not used
160262
161263 info:
162264 targets = Polyline objects
@@ -218,9 +320,4 @@ def getFTwire(sources, targets, anchor=None, squeeze=True):
218320 # sumup force for every target
219321 F = np .array ([np .sum (F [insti [i ]:insti [i + 1 ]],axis = 0 ) for i in range (tgt_number )])
220322
221- # squeeze output
222- if squeeze :
223- F = np .squeeze (F )
224- T = np .squeeze (T )
225-
226323 return np .array ((F , T ))
0 commit comments