femagtools 1.8.7__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/ecloss.py +121 -105
- femagtools/femag.py +5 -3
- femagtools/fsl.py +5 -5
- femagtools/isa7.py +134 -14
- femagtools/leakinduc.py +63 -0
- femagtools/machine/afpm.py +125 -56
- femagtools/machine/effloss.py +13 -1
- femagtools/mcv.py +26 -17
- femagtools/nc.py +16 -14
- femagtools/templates/psi-torq-rot.mako +98 -0
- femagtools/tks.py +1 -1
- femagtools/windings.py +65 -0
- {femagtools-1.8.7.dist-info → femagtools-1.8.8.dist-info}/METADATA +1 -1
- {femagtools-1.8.7.dist-info → femagtools-1.8.8.dist-info}/RECORD +24 -22
- {femagtools-1.8.7.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 +1 -2
- {femagtools-1.8.7.dist-info → femagtools-1.8.8.dist-info}/LICENSE +0 -0
- {femagtools-1.8.7.dist-info → femagtools-1.8.8.dist-info}/entry_points.txt +0 -0
- {femagtools-1.8.7.dist-info → femagtools-1.8.8.dist-info}/top_level.txt +0 -0
femagtools/isa7.py
CHANGED
@@ -13,6 +13,37 @@ import numpy as np
|
|
13
13
|
logger = logging.getLogger('femagtools.isa7')
|
14
14
|
|
15
15
|
|
16
|
+
def Trot(alpha):
|
17
|
+
return np.array([[np.cos(alpha), np.sin(alpha)],
|
18
|
+
[-np.sin(alpha), np.cos(alpha)]])
|
19
|
+
|
20
|
+
def transform_coord(geometry, xy):
|
21
|
+
'''transform from global coord to local coord'''
|
22
|
+
ecpl = Trot(geometry['alpha']).dot((xy-geometry['cxy']).T).T
|
23
|
+
return dict(ecpl=(ecpl + (geometry['w']/2,
|
24
|
+
geometry['h']/2)).T,
|
25
|
+
ecp=np.asarray(xy).T)
|
26
|
+
|
27
|
+
|
28
|
+
def transform_flux_density(alpha, bxy):
|
29
|
+
'''transform the magnet flux density to local coordinate system'''
|
30
|
+
def tf(b1, b2, alpha):
|
31
|
+
if b1.ndim > 1:
|
32
|
+
r = Trot(alpha).dot(((b1.ravel()), (b2.ravel())))
|
33
|
+
return [r[0, :].reshape(*b1.shape),
|
34
|
+
r[1, :].reshape(*b1.shape)]
|
35
|
+
else:
|
36
|
+
return Trot(alpha).dot(((b1), (b2)))
|
37
|
+
|
38
|
+
b = tf(b1=bxy[:, 0, :], b2=bxy[:, 1, :], alpha=alpha)
|
39
|
+
|
40
|
+
# remove DC component
|
41
|
+
bxf = np.mean(b[0].T - np.mean(b[0], axis=1).T, axis=1)
|
42
|
+
byf = np.mean(b[1].T - np.mean(b[1], axis=1).T, axis=1)
|
43
|
+
return {'bxyl': np.asarray(b),
|
44
|
+
'bxyf': np.array([bxf, byf])}
|
45
|
+
|
46
|
+
|
16
47
|
def jordanpfe(Bxnu, Bynu, fnu, losscoeffs, axr):
|
17
48
|
"""example of custom core loss calculation
|
18
49
|
Args:
|
@@ -72,6 +103,11 @@ class ElType(Enum):
|
|
72
103
|
SquareRectangle = 4
|
73
104
|
"""Types of elements"""
|
74
105
|
|
106
|
+
class MoveType(Enum):
|
107
|
+
Rotate = 0
|
108
|
+
Linear = 1
|
109
|
+
"""Types of rotor movement"""
|
110
|
+
|
75
111
|
class Reader(object):
|
76
112
|
"""
|
77
113
|
Open and Read I7/ISA7 file
|
@@ -328,6 +364,7 @@ class Reader(object):
|
|
328
364
|
HI, num_move_ar, self.ANGL_I_UP,
|
329
365
|
num_par_wdgs, cur_control) = self.next_block("f")[:8]
|
330
366
|
self.NUM_PAR_WDGS = int(num_par_wdgs)
|
367
|
+
self.move_action = move_action # rotate 0, linear 1
|
331
368
|
self.arm_length = arm_length # unit is m
|
332
369
|
self.skip_block(2)
|
333
370
|
self.skip_block(30 * 30)
|
@@ -752,13 +789,14 @@ class Isa7(object):
|
|
752
789
|
self.element_pos = np.array([e.center
|
753
790
|
for e in self.elements])
|
754
791
|
|
755
|
-
for a in ('FC_RADIUS', 'pole_pairs', 'poles_sim',
|
792
|
+
for a in ('FC_RADIUS', 'pole_pairs', 'poles_sim', 'move_action',
|
756
793
|
'layers', 'coil_span', 'delta_node_angle', 'speed',
|
757
794
|
'MAGN_TEMPERATURE', 'BR_TEMP_COEF',
|
758
795
|
'MA_SPEZ_WEIGHT', 'CU_SPEZ_WEIGHT'):
|
759
|
-
|
760
|
-
|
761
|
-
|
796
|
+
try:
|
797
|
+
setattr(self, a, getattr(reader, a))
|
798
|
+
except AttributeError:
|
799
|
+
pass
|
762
800
|
if getattr(reader, 'pole_pairs', 0):
|
763
801
|
self.num_poles = 2*self.pole_pairs
|
764
802
|
if getattr(reader, 'slots', 0):
|
@@ -771,7 +809,7 @@ class Isa7(object):
|
|
771
809
|
|
772
810
|
self.airgap_inner_elements = [] # used for rotate
|
773
811
|
self.airgap_outer_elements = []
|
774
|
-
if
|
812
|
+
if getattr(self, 'FC_RADIUS', 0) > 0: # Note: cosys r/phi only
|
775
813
|
# TODO: handle multiple airgaps
|
776
814
|
airgap_center_elements = []
|
777
815
|
for n in self.nodes:
|
@@ -1251,13 +1289,94 @@ class Isa7(object):
|
|
1251
1289
|
try:
|
1252
1290
|
poles_sim = self.poles_sim
|
1253
1291
|
except:
|
1254
|
-
|
1255
1292
|
poles_sim = poles
|
1256
1293
|
|
1257
1294
|
scale_factor = poles/poles_sim
|
1258
|
-
|
1259
1295
|
return scale_factor
|
1260
1296
|
|
1297
|
+
def get_magnet_data(self, ibeta=0, icur=0) -> list:
|
1298
|
+
'''Extract magnet data from nc file
|
1299
|
+
|
1300
|
+
Args:
|
1301
|
+
nc: nc object
|
1302
|
+
icur, ibeta: load case
|
1303
|
+
|
1304
|
+
Returns:
|
1305
|
+
pm_data: list of magnet data
|
1306
|
+
'''
|
1307
|
+
mag_spels = self.magnet_super_elements()
|
1308
|
+
if len(mag_spels) / self.poles_sim == 1:
|
1309
|
+
mag_spels = [mag_spels[0]]
|
1310
|
+
|
1311
|
+
# prepare data for ialh method
|
1312
|
+
# conductivity and permeability of the magnets
|
1313
|
+
cond = 0
|
1314
|
+
mur = 0
|
1315
|
+
# read boundary nodes
|
1316
|
+
for se in mag_spels:
|
1317
|
+
cond = se.conduc
|
1318
|
+
if cond <= 0:
|
1319
|
+
cond = 625000
|
1320
|
+
logging.warning('Magnet conductivity <= 0, using 625000 S/m')
|
1321
|
+
mur = np.abs(1/se.elements[0].reluc[0])
|
1322
|
+
logging.debug('Magnet: mur=%s, conductivity=%s', mur, cond)
|
1323
|
+
|
1324
|
+
# stationary case, no rotation
|
1325
|
+
poles = 0
|
1326
|
+
try:
|
1327
|
+
poles = self.num_poles
|
1328
|
+
except AttributeError:
|
1329
|
+
pass
|
1330
|
+
|
1331
|
+
if poles == 0: # no rotation
|
1332
|
+
freq = self.speed
|
1333
|
+
time_vec = np.linspace(0, 1/freq, len(self.pos_el_fe_induction))
|
1334
|
+
pos = dict(time=time_vec.tolist(),
|
1335
|
+
freq=freq,
|
1336
|
+
t=float(1/freq))
|
1337
|
+
# reset num.poles
|
1338
|
+
poles = 1
|
1339
|
+
else:
|
1340
|
+
if self.move_action == 0:
|
1341
|
+
phi = self.pos_el_fe_induction*180/np.pi
|
1342
|
+
pos = dict(phi=phi,
|
1343
|
+
speed=self.speed)
|
1344
|
+
else:
|
1345
|
+
pos = dict(displ=self.pos_el_fe_induction,
|
1346
|
+
speed=self.speed)
|
1347
|
+
# prep dictionary for the loss calculation
|
1348
|
+
pm_data = []
|
1349
|
+
for i, se in enumerate(mag_spels):
|
1350
|
+
ecp = [e.center for e in se.elements]
|
1351
|
+
geometry = se.get_rect_geom()
|
1352
|
+
|
1353
|
+
bxy = []
|
1354
|
+
for e in se.elements:
|
1355
|
+
theta = np.arctan2(float(e.center[1]),
|
1356
|
+
float(e.center[0]))
|
1357
|
+
fd = self.flux_density(e, icur, ibeta)
|
1358
|
+
bxy.append(Trot(-theta).dot((fd['bx'], fd['by'])))
|
1359
|
+
#= np.moveaxis(bxy, 1, 0)
|
1360
|
+
pd = dict(name='pm_data_se' + str(se.key),
|
1361
|
+
hm=geometry['h'],
|
1362
|
+
wm=geometry['w'],
|
1363
|
+
lm=self.arm_length,
|
1364
|
+
alpha=geometry['alpha'],
|
1365
|
+
ls=self.arm_length,
|
1366
|
+
sigma=cond,
|
1367
|
+
mur=mur,
|
1368
|
+
loadcase=ibeta,
|
1369
|
+
numpoles=poles,
|
1370
|
+
bl=transform_flux_density(geometry['alpha'],
|
1371
|
+
np.array(bxy)),
|
1372
|
+
elcp=transform_coord(geometry, ecp),
|
1373
|
+
area=se.area(),
|
1374
|
+
spel_key=se.key)
|
1375
|
+
pd.update(pos)
|
1376
|
+
|
1377
|
+
pm_data.append(pd)
|
1378
|
+
return pm_data
|
1379
|
+
|
1261
1380
|
|
1262
1381
|
class Point(object):
|
1263
1382
|
def __init__(self, x, y):
|
@@ -1514,7 +1633,7 @@ class SuperElement(BaseEntity):
|
|
1514
1633
|
|
1515
1634
|
def get_rect_geom(self):
|
1516
1635
|
"""return rectangle parameters of superelement:
|
1517
|
-
|
1636
|
+
cxy: center coordinates
|
1518
1637
|
w, h: width and height
|
1519
1638
|
alpha: angle of main axis"""
|
1520
1639
|
bxy = np.array([n.xy for b in self.nodechains
|
@@ -1523,13 +1642,14 @@ class SuperElement(BaseEntity):
|
|
1523
1642
|
cxy = np.mean(bxy, axis=0)
|
1524
1643
|
# corner points: calculate angles
|
1525
1644
|
b = np.vstack((bxy[-1], bxy, bxy[0]))
|
1526
|
-
|
1527
|
-
|
1528
|
-
|
1645
|
+
db = np.diff(b, axis=0)
|
1646
|
+
a = np.arctan2(db[:, 1], db[:, 0])
|
1647
|
+
da = np.abs(np.diff(a))
|
1648
|
+
peaks = np.where((da < 6) & (da > 1))[0]
|
1529
1649
|
c = bxy[peaks]
|
1530
|
-
# width and height
|
1650
|
+
# width and height (distances between corners)
|
1531
1651
|
dxy = np.linalg.norm(np.vstack(
|
1532
|
-
(bxy
|
1652
|
+
(np.diff(bxy, axis=0),
|
1533
1653
|
bxy[-1, :] - bxy[0, :])), axis=1)
|
1534
1654
|
dc = (np.sum(dxy[peaks[0]:peaks[1]]),
|
1535
1655
|
np.sum(dxy[peaks[1]:peaks[2]]),
|
@@ -1545,7 +1665,7 @@ class SuperElement(BaseEntity):
|
|
1545
1665
|
alpha = np.arctan2(c[i+1, 1]-c[i, 1], c[i+1, 0]-c[i, 0])
|
1546
1666
|
if alpha < 0:
|
1547
1667
|
alpha += np.pi
|
1548
|
-
return {'w': w, 'h': h, '
|
1668
|
+
return {'w': w, 'h': h, 'cxy': cxy,
|
1549
1669
|
'area': area, 'alpha': alpha}
|
1550
1670
|
|
1551
1671
|
|
femagtools/leakinduc.py
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
'''Calculate leakage inductances'''
|
3
|
+
import numpy as np
|
4
|
+
import logging
|
5
|
+
|
6
|
+
logger = logging.getLogger(__name__)
|
7
|
+
|
8
|
+
|
9
|
+
def end_wdg_leak_ind_round_wires(l_ew, p, q, m, y, taup, w1, layers, num_par_wdgs, slot_height):
|
10
|
+
'''returns end winding leakage inductance per phase per end winding side (DE or NDE)'''
|
11
|
+
mue0 = 4*np.pi*1e-7
|
12
|
+
if layers == 2:
|
13
|
+
lambda_ew = 0.34*q*(1 - 2*y*taup/(np.pi*l_ew*m*q))
|
14
|
+
if layers == 1:
|
15
|
+
lambda_ew = q*(0.67 - 0.43*(taup + slot_height*np.pi/(2*p))/l_ew)
|
16
|
+
L_ew = mue0*2/(p*q)*(w1/num_par_wdgs)**2*l_ew*lambda_ew
|
17
|
+
return L_ew
|
18
|
+
|
19
|
+
|
20
|
+
def end_wdg_leak_ind_hairpin_wires(): #TODO
|
21
|
+
L_ew = 0
|
22
|
+
return L_ew
|
23
|
+
|
24
|
+
|
25
|
+
#def slot_leakage_inductance_round_wires(p, q, w1, num_par_wdgs, layers):
|
26
|
+
# '''returns slot leakage inductance per phase'''
|
27
|
+
# mue0 = 4*np.pi*1e-7
|
28
|
+
# if layers == 1:
|
29
|
+
# lambda_slot = 0 # tbc
|
30
|
+
# if layers == 2:
|
31
|
+
# t1 = b2/bs
|
32
|
+
# t2 = b1/b2
|
33
|
+
# t12 = b1/b2
|
34
|
+
# kt1 = (4*t1**2 - t1**4 - 4*np.log(t1) -3)/(4*(1 - t1)*(1 - t1**2)**2) if t1 != 1 else 0
|
35
|
+
# kt2 = (4*t2**2 - t2**4 - 4*np.log(t2) - 3)/(4*(1 - t2)*(1 - t2**2)**2) if t2 != 1 else 0
|
36
|
+
# kt12 = (t12**2 - 2*np.log(t12) - 1)/(2*(1 - t12)*(1 - t12**2)) if t12 != 1 else 0
|
37
|
+
# const = 0.1424 + 0.5*np.arcsin(np.sqrt(1 - (bo/b1)**2)) + ho/bo
|
38
|
+
# lambda_t = h2/b2*kt2 + const
|
39
|
+
# lambda_b = h3/bs*kt1 + h2/(b2-b1)*np.log(b2/b1) + const if b2 != b1 else h3/bs*kt1 + const
|
40
|
+
# lambda_tb = h2/b2*kt12 + const
|
41
|
+
# lambda_slot = lambda_t + lambda_b + lambda_tb
|
42
|
+
# L_slot = mue0*2/(p*q)*(w1/num_par_wdgs)**2*lambda_slot
|
43
|
+
# return L_slot
|
44
|
+
|
45
|
+
|
46
|
+
def slot_leak_ind_fea(): #TODO
|
47
|
+
'''returns slot and tooth tip leakage inductance'''
|
48
|
+
# make a single slot model with detailed windings
|
49
|
+
# run current through windings
|
50
|
+
# L_slot = flux / current
|
51
|
+
# scale to get values per phase
|
52
|
+
L_slot = 0
|
53
|
+
return L_slot
|
54
|
+
|
55
|
+
|
56
|
+
def harm_leak_ind(E_fft, order_fft, freq, Ia): # needs to be validated
|
57
|
+
'''returns harmonic leakage inductance per phase'''
|
58
|
+
L_harm = []
|
59
|
+
for ii in range(1, len(order_fft)): # skip 1st order
|
60
|
+
L_harm.append(E_fft[ii] / Ia / (2*np.pi*freq*order_fft[ii]))
|
61
|
+
|
62
|
+
return sum(L_harm)
|
63
|
+
|