femagtools 1.8.6__py3-none-any.whl → 1.8.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- femagtools/__init__.py +2 -2
- femagtools/amela.py +18 -286
- femagtools/bch.py +11 -7
- femagtools/ecloss.py +121 -105
- femagtools/femag.py +13 -73
- femagtools/fsl.py +11 -8
- femagtools/isa7.py +174 -10
- femagtools/leakinduc.py +63 -0
- femagtools/losscoeffs.py +29 -3
- femagtools/machine/afpm.py +138 -60
- femagtools/machine/effloss.py +13 -1
- femagtools/mcv.py +173 -9
- femagtools/nc.py +16 -14
- femagtools/parstudy.py +3 -1
- femagtools/plot/bch.py +126 -44
- femagtools/plot/nc.py +13 -0
- femagtools/shortcircuit.py +378 -0
- femagtools/templates/psi-torq-rem-rot.mako +127 -0
- femagtools/templates/psi-torq-rot.mako +98 -0
- femagtools/tks.py +1 -1
- femagtools/windings.py +65 -0
- {femagtools-1.8.6.dist-info → femagtools-1.8.8.dist-info}/METADATA +1 -1
- {femagtools-1.8.6.dist-info → femagtools-1.8.8.dist-info}/RECORD +31 -27
- {femagtools-1.8.6.dist-info → femagtools-1.8.8.dist-info}/WHEEL +1 -1
- tests/test_afpm.py +2 -2
- tests/test_amela.py +1 -3
- tests/test_fsl.py +4 -4
- tests/test_nc.py +10 -0
- {femagtools-1.8.6.dist-info → femagtools-1.8.8.dist-info}/LICENSE +0 -0
- {femagtools-1.8.6.dist-info → femagtools-1.8.8.dist-info}/entry_points.txt +0 -0
- {femagtools-1.8.6.dist-info → femagtools-1.8.8.dist-info}/top_level.txt +0 -0
femagtools/__init__.py
CHANGED
@@ -2,10 +2,10 @@
|
|
2
2
|
|
3
3
|
"""
|
4
4
|
__title__ = 'femagtools'
|
5
|
-
__version__ = '1.8.
|
5
|
+
__version__ = '1.8.8'
|
6
6
|
__author__ = 'Ronald Tanner'
|
7
7
|
__license__ = 'BSD'
|
8
|
-
__copyright__ = 'Copyright 2023-
|
8
|
+
__copyright__ = 'Copyright 2023-2025 Gamma Technology'
|
9
9
|
|
10
10
|
from .asm import read
|
11
11
|
from .bch import Reader
|
femagtools/amela.py
CHANGED
@@ -14,104 +14,6 @@ import subprocess
|
|
14
14
|
# set logging
|
15
15
|
logger = logging.getLogger(__name__)
|
16
16
|
|
17
|
-
def geometry_id(bndx, bndy):
|
18
|
-
'''identify the magnet geometry'''
|
19
|
-
# caculate magnet area
|
20
|
-
xy = 0
|
21
|
-
yx = 0
|
22
|
-
for i in range(len(bndy)):
|
23
|
-
if i < (len(bndy)-1):
|
24
|
-
xy += bndx[i]*bndy[i+1]
|
25
|
-
yx += bndy[i]*bndx[i+1]
|
26
|
-
else:
|
27
|
-
xy += bndx[i]*bndy[0]
|
28
|
-
yx += bndy[i]*bndx[0]
|
29
|
-
area = np.abs(yx-xy)*0.5
|
30
|
-
|
31
|
-
x0, y0 = np.mean(bndx), np.mean(bndy)
|
32
|
-
distances = []
|
33
|
-
for i in range(len(bndx)):
|
34
|
-
for j in range(len(bndy)):
|
35
|
-
dist = np.sqrt((bndx[i] - bndx[j])**2 + (bndy[i] - bndy[j])**2)
|
36
|
-
distances.append([dist, bndx[i], bndx[j], bndy[i], bndy[j]])
|
37
|
-
|
38
|
-
distances.sort(reverse=True)
|
39
|
-
xe = [distances[0][2], distances[2][1], distances[0][1], distances[2][2]]
|
40
|
-
ye = [distances[0][4], distances[2][3], distances[0][3], distances[2][4]]
|
41
|
-
|
42
|
-
# dimension
|
43
|
-
dim = np.zeros((3, 5))
|
44
|
-
x1, y1 = xe[0], ye[0]
|
45
|
-
|
46
|
-
for i in range(3):
|
47
|
-
x2, y2 = xe[i+1], ye[i+1]
|
48
|
-
dim[i, 0] = np.sqrt((x2-x1)**2 + (y2-y1)**2)
|
49
|
-
dim[i, 1:3] = [x1, x2]
|
50
|
-
dim[i, 3:5] = [y1, y2]
|
51
|
-
|
52
|
-
dim = dim[np.lexsort((dim[:, 2], dim[:, 1], dim[:, 0]))]
|
53
|
-
dx12, dy12 = dim[1, 1] - dim[1, 2], dim[1, 3] - dim[1, 4]
|
54
|
-
dx14, dy14 = dim[0, 1] - dim[0, 2], dim[0, 3] - dim[0, 4]
|
55
|
-
|
56
|
-
alp1 = np.arctan2(dy12, dx12)
|
57
|
-
alp2 = np.arctan2(dy14, dx14)
|
58
|
-
|
59
|
-
if alp1 < 0: alp1 += 2*np.pi
|
60
|
-
if alp2 < 0: alp2 += 2*np.pi
|
61
|
-
if alp2 < alp1:
|
62
|
-
alp2 += np.pi
|
63
|
-
alpha = (alp1 + (alp2 - np.pi/2))/2
|
64
|
-
else:
|
65
|
-
alpha = (alp1 + (alp2 - np.pi/2))/2 + np.pi
|
66
|
-
|
67
|
-
wm = dim[1, 0]
|
68
|
-
hm = area/wm
|
69
|
-
|
70
|
-
return dict(wm=wm,
|
71
|
-
hm=hm,
|
72
|
-
x0=x0,
|
73
|
-
y0=y0,
|
74
|
-
area=area,
|
75
|
-
alpha=alpha)
|
76
|
-
|
77
|
-
def tf(b1, b2, alpha):
|
78
|
-
'''Tranformation Matrix'''
|
79
|
-
T = np.array([[cos(alpha), sin(alpha)],
|
80
|
-
[-sin(alpha), cos(alpha)]])
|
81
|
-
if b1.ndim > 1:
|
82
|
-
r = T.dot(((b1.ravel()), (b2.ravel())))
|
83
|
-
return [r[0, :].reshape(*b1.shape),
|
84
|
-
r[1, :].reshape(*b1.shape)]
|
85
|
-
else:
|
86
|
-
return T.dot(((b1), (b2)))
|
87
|
-
|
88
|
-
def transform_coord(geometry, xcp, ycp):
|
89
|
-
'''transform from global coord to local coord'''
|
90
|
-
# transformation
|
91
|
-
elcp = tf(b1=np.array(xcp)-geometry['x0'],
|
92
|
-
b2=np.array(ycp)-geometry['y0'],
|
93
|
-
alpha=geometry['alpha'])
|
94
|
-
return dict(excpl=elcp[0, :]+geometry['wm']/2,
|
95
|
-
eycpl=elcp[1, :]+geometry['hm']/2,
|
96
|
-
excp=np.array(xcp),
|
97
|
-
eycp=np.array(ycp))
|
98
|
-
|
99
|
-
def transform_flux_denstiy(geometry, bx, by):
|
100
|
-
'''transform the magnet flux density to local coordinate system'''
|
101
|
-
# transformation
|
102
|
-
bxy = tf(b1=bx,
|
103
|
-
b2=by,
|
104
|
-
alpha=geometry['alpha'])
|
105
|
-
|
106
|
-
# remove DC component
|
107
|
-
bxf = np.mean(bxy[0].T - np.mean(bxy[0],axis=1).T,axis=1)
|
108
|
-
byf = np.mean(bxy[1].T - np.mean(bxy[1],axis=1).T,axis=1)
|
109
|
-
|
110
|
-
return dict(bxl=bxy[0],
|
111
|
-
byl=bxy[1],
|
112
|
-
bxf=bxf,
|
113
|
-
byf=byf
|
114
|
-
)
|
115
17
|
|
116
18
|
class Amela():
|
117
19
|
'''Run Amela Calculation
|
@@ -133,7 +35,8 @@ class Amela():
|
|
133
35
|
nseglen=0)
|
134
36
|
'''
|
135
37
|
|
136
|
-
def __init__(self, workdir: str, magnet_data: dict,
|
38
|
+
def __init__(self, workdir: str, magnet_data: dict,
|
39
|
+
amela_dir=None):
|
137
40
|
|
138
41
|
self.magn = magnet_data
|
139
42
|
self.workdir = pathlib.Path(workdir)
|
@@ -157,193 +60,11 @@ class Amela():
|
|
157
60
|
if 'nseglen' in self.magn:
|
158
61
|
self.cmd.append(f"--nseglen {self.magn['nseglen']}")
|
159
62
|
|
160
|
-
def get_magnet_data(self, ibeta=None) -> list:
|
161
|
-
'''Extract magnet data from nc file
|
162
|
-
|
163
|
-
Args:
|
164
|
-
ibeta: load case
|
165
|
-
|
166
|
-
Returns:
|
167
|
-
pm_data: list of magnet data
|
168
|
-
|
169
|
-
'''
|
170
|
-
nc_name = self.workdir / self.magn['name']
|
171
|
-
r = femagtools.nc.read(nc_name)
|
172
|
-
|
173
|
-
mag_spels = r.magnet_super_elements()
|
174
|
-
spel_key = [i.key for i in mag_spels]
|
175
|
-
spel_area = [float(i.area())*1e6 for i in mag_spels]
|
176
|
-
|
177
|
-
pm_elem_key = []
|
178
|
-
elem = []
|
179
|
-
pm_node_key = [[] for i in range(len(spel_key))]
|
180
|
-
bndkey = [[] for i in range(len(spel_key))]
|
181
|
-
bx = [[] for i in range(len(spel_key))]
|
182
|
-
by = [[] for i in range(len(spel_key))]
|
183
|
-
xcp = [[] for i in range(len(spel_key))]
|
184
|
-
ycp = [[] for i in range(len(spel_key))]
|
185
|
-
bndx = [[] for i in range(len(spel_key))]
|
186
|
-
bndy = [[] for i in range(len(spel_key))]
|
187
|
-
# prepare data for ialh method
|
188
|
-
wm = []
|
189
|
-
hm = []
|
190
|
-
alpha = []
|
191
|
-
x0 = []
|
192
|
-
y0 = []
|
193
|
-
geometry = []
|
194
|
-
elcp = []
|
195
|
-
bl = []
|
196
|
-
# conductivity and permeability of the magnets
|
197
|
-
cond = 0
|
198
|
-
mur = 0
|
199
|
-
# read boundary nodes
|
200
|
-
for k, i in enumerate(mag_spels):
|
201
|
-
|
202
|
-
cond = i.conduc
|
203
|
-
if cond == 0:
|
204
|
-
cond = 625000
|
205
|
-
logger.info('Magnet conductivity equals 0, using 625000 S/m')
|
206
|
-
|
207
|
-
mur = np.abs(1/i.elements[0].reluc[0])
|
208
|
-
logger.debug('Magnet: mur=%s, conductivity=%s', mur, cond)
|
209
|
-
|
210
|
-
pm_elem_key.append([j.key for j in i.elements])
|
211
|
-
elem.append(len(i.elements))
|
212
|
-
for j in i.elements:
|
213
|
-
for kk in j.vertices:
|
214
|
-
pm_node_key[k].append(kk.key - 1)
|
215
|
-
|
216
|
-
for bnd in i.nodechains:
|
217
|
-
for kk in bnd.nodes:
|
218
|
-
if len(bndkey[k]) > 0:
|
219
|
-
if kk.key != bndkey[k][-1]:
|
220
|
-
bndkey[k].append(kk.key)
|
221
|
-
bndx[k].append(kk.x*1e3)
|
222
|
-
bndy[k].append(kk.y*1e3)
|
223
|
-
else:
|
224
|
-
bndkey[k].append(kk.key)
|
225
|
-
bndx[k].append(kk.x*1e3)
|
226
|
-
bndy[k].append(kk.y*1e3)
|
227
|
-
|
228
|
-
bndkey[k].pop(-1)
|
229
|
-
bndx[k].pop(-1)
|
230
|
-
bndy[k].pop(-1)
|
231
|
-
geo = geometry_id(bndx=bndx[k], bndy=bndy[k])
|
232
|
-
geometry.append(geo)
|
233
|
-
# necessary?
|
234
|
-
hm.append(geo['hm'])
|
235
|
-
wm.append(geo['wm'])
|
236
|
-
x0.append(geo['x0'])
|
237
|
-
y0.append(geo['y0'])
|
238
|
-
alpha.append(geo['alpha'])
|
239
|
-
|
240
|
-
# default load angle (input beta I vs Up)
|
241
|
-
if ibeta is not None:
|
242
|
-
indx = ibeta
|
243
|
-
else:
|
244
|
-
num_cases = r.el_fe_induction_1.shape[3] - 1
|
245
|
-
if 'loadcase' in self.magn:
|
246
|
-
indx = self.magn['loadcase'] - 1
|
247
|
-
else:
|
248
|
-
indx = num_cases
|
249
|
-
if indx == 3:
|
250
|
-
indx = num_cases # avoid error
|
251
|
-
|
252
|
-
# stationary case, no rotation
|
253
|
-
poles = 0
|
254
|
-
try:
|
255
|
-
poles = r.num_poles
|
256
|
-
except AttributeError:
|
257
|
-
pass
|
258
|
-
|
259
|
-
# read mesh and flux density
|
260
|
-
for i in range(len(spel_key)):
|
261
|
-
bx[i].append(np.zeros((elem[i], len(r.pos_el_fe_induction))))
|
262
|
-
by[i].append(np.zeros((elem[i], len(r.pos_el_fe_induction))))
|
263
|
-
for index, j in enumerate(mag_spels[i].elements):
|
264
|
-
xcp[i].append(float(j.center[0]*1e3))
|
265
|
-
ycp[i].append(float(j.center[1]*1e3))
|
266
|
-
theta = np.arctan2(float(j.center[1]), float(j.center[0]))
|
267
|
-
fd = r.flux_density(j, 0, indx)
|
268
|
-
if poles == 0:
|
269
|
-
bx[i][0][index, :] = fd['bx']
|
270
|
-
by[i][0][index, :] = fd['by']
|
271
|
-
else:
|
272
|
-
bx[i][0][index, :] = fd['bx']*np.cos(theta) - \
|
273
|
-
fd['by']*np.sin(theta)
|
274
|
-
by[i][0][index, :] = fd['bx']*np.sin(theta) + \
|
275
|
-
fd['by']*np.cos(theta)
|
276
|
-
|
277
|
-
elcp.append(transform_coord(geometry[i], xcp[i], ycp[i]))
|
278
|
-
bl.append(transform_flux_denstiy(geometry[i], bx[i][0], by[i][0]))
|
279
|
-
|
280
|
-
if poles == 0:
|
281
|
-
freq = self.magn.get('f', r.speed)
|
282
|
-
time_vec = np.linspace(0, 1/freq, len(r.pos_el_fe_induction))
|
283
|
-
pos = dict(time=time_vec.tolist(),
|
284
|
-
freq=freq,
|
285
|
-
t=float(1/freq))
|
286
|
-
# reset num.poles
|
287
|
-
poles = 1
|
288
|
-
else:
|
289
|
-
rpm = self.magn.get('speed', r.speed)
|
290
|
-
ag_sim = r.pos_el_fe_induction[-1] - r.pos_el_fe_induction[0]
|
291
|
-
pos = dict(phi=(r.pos_el_fe_induction*180/pi).tolist(),
|
292
|
-
speed=rpm,
|
293
|
-
t=float(60/rpm*ag_sim/360)) # TODO
|
294
|
-
# prep dictionary for the loss calculation
|
295
|
-
pm_data = []
|
296
|
-
for i in range(len(spel_key)):
|
297
|
-
pm_data.append(dict(name='pm_data_se' + str(spel_key[i]),
|
298
|
-
hm=self.magn.get('hm', hm[i]),
|
299
|
-
wm=self.magn.get('wm', wm[i]),
|
300
|
-
lm=self.magn.get('lm', r.arm_length*1e3),
|
301
|
-
alpha=alpha[i],
|
302
|
-
ls=r.arm_length*1e3,
|
303
|
-
sigma=float(self.magn.get('sigma', cond)),
|
304
|
-
mur=float(self.magn.get('mur', mur)),
|
305
|
-
loadcase=self.magn.get('loadcase', indx),
|
306
|
-
numpoles=poles,
|
307
|
-
nodes=dict(),
|
308
|
-
elements=dict(),
|
309
|
-
bndkeys=bndkey[i],
|
310
|
-
bndx=[float(c) for c in bndx[i]],
|
311
|
-
bndy=[float(c) for c in bndy[i]],
|
312
|
-
bl=bl[i],
|
313
|
-
elcp=elcp[i],
|
314
|
-
area=spel_area[i],
|
315
|
-
spel_key=spel_key[i]))
|
316
|
-
pm_data[i].update(pos)
|
317
|
-
|
318
|
-
for k in range(len(pm_node_key)):
|
319
|
-
for i, j in enumerate(pm_node_key[k]):
|
320
|
-
pm_data[k]['nodes'][str(j + 1)] = dict(
|
321
|
-
key=int(j + 1),
|
322
|
-
x=float(r.nodes[j].x*1e3),
|
323
|
-
y=float(r.nodes[j].y*1e3),
|
324
|
-
Az=float(r.nodes[j].vpot[0])
|
325
|
-
)
|
326
|
-
for i, j in enumerate(pm_elem_key[k]):
|
327
|
-
pm_data[k]['elements'][str(j)] = dict(
|
328
|
-
key=int(j + 1),
|
329
|
-
xcp=xcp[k][i],
|
330
|
-
ycp=ycp[k][i],
|
331
|
-
Bx=bx[k][0][i, :].tolist(),
|
332
|
-
By=by[k][0][i, :].tolist()
|
333
|
-
)
|
334
|
-
|
335
|
-
if len(mag_spels) / r.poles_sim > 1:
|
336
|
-
return pm_data
|
337
|
-
else:
|
338
|
-
return [pm_data[0]]
|
339
|
-
|
340
63
|
def get_magnet_data_all(self, num_op):
|
341
64
|
'''get all magnet data for all loadcases'''
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
pm_data.append(pmd)
|
346
|
-
return pm_data
|
65
|
+
nc_name = self.workdir / self.magn['name']
|
66
|
+
r = femagtools.nc.read(nc_name)
|
67
|
+
return [r.get_magnet_data(ibeta=i) for i in num_op]
|
347
68
|
|
348
69
|
def export_json(self, pm_data: list):
|
349
70
|
'''Export magnet data to json files
|
@@ -352,6 +73,7 @@ class Amela():
|
|
352
73
|
pm_data: list of magnet data
|
353
74
|
|
354
75
|
'''
|
76
|
+
return
|
355
77
|
pm_dir = self.amela_dir / self.magn['name']
|
356
78
|
pm_dir.mkdir(exist_ok=True)
|
357
79
|
for i in pm_data:
|
@@ -396,9 +118,19 @@ class Amela():
|
|
396
118
|
losses
|
397
119
|
'''
|
398
120
|
# get magnet data
|
399
|
-
|
121
|
+
nc_name = self.workdir / self.magn['name']
|
122
|
+
nc = femagtools.nc.read(nc_name)
|
123
|
+
num_cases = nc.el_fe_induction_1.shape[3] - 1
|
124
|
+
if 'loadcase' in self.magn:
|
125
|
+
indx = self.magn['loadcase'] - 1
|
126
|
+
else:
|
127
|
+
indx = num_cases
|
128
|
+
if indx == 3:
|
129
|
+
indx = num_cases # avoid error
|
130
|
+
|
131
|
+
r = nc.get_magnet_data(indx)
|
400
132
|
# export to json
|
401
|
-
self.export_json(r)
|
133
|
+
#self.export_json(r)
|
402
134
|
# run amela
|
403
135
|
calc_method = 'IALH' if ialh else '3DI'
|
404
136
|
cmd = self.cmd + ['--calc', calc_method, self.magn['name'] + '/']
|
femagtools/bch.py
CHANGED
@@ -1057,10 +1057,11 @@ class Reader:
|
|
1057
1057
|
e.g. : idList[-450, -350, -250, -150, -50, 0]
|
1058
1058
|
idList[-500, -400, -300, -200, -100, 0, 0]
|
1059
1059
|
'''
|
1060
|
-
|
1061
|
-
if idList[-1] == 0 and len(idList) > 2 and \
|
1062
|
-
diff[-1] == 0:
|
1060
|
+
if list(idList).count(0) > 1: # femag writes duplicate id==0 values
|
1063
1061
|
idList = idList[:-1]
|
1062
|
+
diff = np.floor(np.abs(np.diff(idList)))
|
1063
|
+
if n := np.trim_zeros(np.diff(diff)).size:
|
1064
|
+
idList = idList[:-n]
|
1064
1065
|
return idList
|
1065
1066
|
|
1066
1067
|
def __read_psidq(self, content):
|
@@ -1085,8 +1086,7 @@ class Reader:
|
|
1085
1086
|
ncols = ncols-1
|
1086
1087
|
|
1087
1088
|
id = np.reshape(m[0], (-1, ncols)).T[0]
|
1088
|
-
|
1089
|
-
id = self.__removeTrailingZero(id)
|
1089
|
+
id = self.__removeTrailingZero(id)
|
1090
1090
|
nrows = len(id)
|
1091
1091
|
if nrows > 1 and id[nrows-1] < id[nrows-2]:
|
1092
1092
|
nrows = nrows-1
|
@@ -1121,8 +1121,7 @@ class Reader:
|
|
1121
1121
|
ncols = ncols-1
|
1122
1122
|
|
1123
1123
|
id = np.reshape(m[0], (-1, ncols)).T[0]
|
1124
|
-
|
1125
|
-
id = self.__removeTrailingZero(id)
|
1124
|
+
id = self.__removeTrailingZero(id)
|
1126
1125
|
nrows = len(id)
|
1127
1126
|
if nrows > 1 and id[nrows-1] < id[nrows-2]:
|
1128
1127
|
nrows = nrows-1
|
@@ -1610,6 +1609,11 @@ class Reader:
|
|
1610
1609
|
losses['staza'] = floatnan(rec[0])
|
1611
1610
|
losses['stajo'] = floatnan(rec[1])
|
1612
1611
|
losses['total'] += losses['staza']+losses['stajo']
|
1612
|
+
|
1613
|
+
elif content[i+1].split() == ['Iron', '----']:
|
1614
|
+
losses['rotfe'] = sum([floatnan(x) for x in rec])
|
1615
|
+
losses['total'] += losses['rotfe']
|
1616
|
+
|
1613
1617
|
else:
|
1614
1618
|
losses['rotfe'] = floatnan(rec[1])
|
1615
1619
|
losses['total'] += losses['rotfe']
|