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/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):
|
@@ -1418,6 +1537,13 @@ class Element(BaseEntity):
|
|
1418
1537
|
"""return True if the element has lamination properties"""
|
1419
1538
|
return self.reluc != (1.0, 1.0) and self.mag == (0.0, 0.0)
|
1420
1539
|
|
1540
|
+
def remanence(self, temperature=20):
|
1541
|
+
"""return remanence Brx, Bry at element
|
1542
|
+
Arguments:
|
1543
|
+
temperature: (float) temperature in °C"""
|
1544
|
+
br_temp_corr = 1. + self.br_temp_coef*(temperature - 20.)
|
1545
|
+
return self.mag[0]*br_temp_corr, self.mag[1]*br_temp_corr
|
1546
|
+
|
1421
1547
|
def demagnetization(self, temperature=20):
|
1422
1548
|
"""return demagnetization Hx, Hy of this element"""
|
1423
1549
|
return self.demag_b(self.flux_density(cosys='polar'), temperature)
|
@@ -1429,9 +1555,9 @@ class Element(BaseEntity):
|
|
1429
1555
|
# assume polar coordinates of b
|
1430
1556
|
pos = np.arctan2(self.center[1], self.center[0])
|
1431
1557
|
#pos = 0 # cartesian
|
1432
|
-
|
1433
|
-
magn = np.sqrt(
|
1434
|
-
alfa = np.arctan2(
|
1558
|
+
mag = self.remanence(temperature)
|
1559
|
+
magn = np.sqrt(mag[0]**2 + mag[1]**2)
|
1560
|
+
alfa = np.arctan2(mag[1], mag[0]) - pos
|
1435
1561
|
b1, b2 = b
|
1436
1562
|
bpol = b1 * np.cos(alfa) + b2 * np.sin(alfa)
|
1437
1563
|
reluc = abs(self.reluc[0]) / (4*np.pi*1e-7 * 1000)
|
@@ -1505,6 +1631,44 @@ class SuperElement(BaseEntity):
|
|
1505
1631
|
"""return area of this superelement"""
|
1506
1632
|
return sum([e.area for e in self.elements])
|
1507
1633
|
|
1634
|
+
def get_rect_geom(self):
|
1635
|
+
"""return rectangle parameters of superelement:
|
1636
|
+
cxy: center coordinates
|
1637
|
+
w, h: width and height
|
1638
|
+
alpha: angle of main axis"""
|
1639
|
+
bxy = np.array([n.xy for b in self.nodechains
|
1640
|
+
for n in b.nodes[:-1]])
|
1641
|
+
# center
|
1642
|
+
cxy = np.mean(bxy, axis=0)
|
1643
|
+
# corner points: calculate angles
|
1644
|
+
b = np.vstack((bxy[-1], bxy, bxy[0]))
|
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]
|
1649
|
+
c = bxy[peaks]
|
1650
|
+
# width and height (distances between corners)
|
1651
|
+
dxy = np.linalg.norm(np.vstack(
|
1652
|
+
(np.diff(bxy, axis=0),
|
1653
|
+
bxy[-1, :] - bxy[0, :])), axis=1)
|
1654
|
+
dc = (np.sum(dxy[peaks[0]:peaks[1]]),
|
1655
|
+
np.sum(dxy[peaks[1]:peaks[2]]),
|
1656
|
+
np.sum(dxy[peaks[2]:peaks[3]]),
|
1657
|
+
np.sum(np.hstack(
|
1658
|
+
(dxy[peaks[3]:], dxy[:peaks[0]]))))
|
1659
|
+
w = np.mean(np.sort(dc)[-2:])
|
1660
|
+
area = self.area()
|
1661
|
+
h = area/w
|
1662
|
+
# angle of main axis
|
1663
|
+
i = np.argmax(dc)
|
1664
|
+
c = np.vstack((c, c[0]))
|
1665
|
+
alpha = np.arctan2(c[i+1, 1]-c[i, 1], c[i+1, 0]-c[i, 0])
|
1666
|
+
if alpha < 0:
|
1667
|
+
alpha += np.pi
|
1668
|
+
return {'w': w, 'h': h, 'cxy': cxy,
|
1669
|
+
'area': area, 'alpha': alpha}
|
1670
|
+
|
1671
|
+
|
1508
1672
|
class SubRegion(BaseEntity):
|
1509
1673
|
def __init__(self, key, sr_type, color, name, nturns, curdir, wb_key,
|
1510
1674
|
superelements, nodechains):
|
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
|
+
|
femagtools/losscoeffs.py
CHANGED
@@ -20,6 +20,11 @@ def pfe_jordan(f, B, ch, fh, cw, fw, fb, fo, Bo):
|
|
20
20
|
def pfe_steinmetz(f, B, cw, fw, fb, fo, Bo):
|
21
21
|
return cw*(f/fo)**fw * (B/Bo)**fb
|
22
22
|
|
23
|
+
def pfe_modified_steinmetz(f, B, ch, cw, alpha, beta, fo, Bo):
|
24
|
+
return ch*(f/fo)*(B/Bo)**(alpha + B*beta) + cw*(f/fo)**2*(B/Bo)**2
|
25
|
+
|
26
|
+
def wbert(f, b, ch, cw, cx):
|
27
|
+
return (ch + cw*f)*b**2 + cx*f**0.5*b**1.5
|
23
28
|
|
24
29
|
def fitsteinmetz(f, B, losses, Bo, fo, alpha0=1.0):
|
25
30
|
"""fit coeffs of
|
@@ -56,6 +61,30 @@ def fitsteinmetz(f, B, losses, Bo, fo, alpha0=1.0):
|
|
56
61
|
fbx, y, (1.0, 1.0, 2.0))
|
57
62
|
return fitp
|
58
63
|
|
64
|
+
def fit_modified_steinmetz(f, B, losses, Bo, fo):
|
65
|
+
"""fit coeffs of modifeld steinmetz
|
66
|
+
losses(f,B)=ch*(f/fo)*(B/Bo)**(alpha + B*beta) + cw*(f/fo)**2*(B/Bo)**2
|
67
|
+
returns (ch, cw, alpha, beta)
|
68
|
+
"""
|
69
|
+
pfe = losses
|
70
|
+
z = []
|
71
|
+
for i, fx in enumerate(f):
|
72
|
+
if fx:
|
73
|
+
if isinstance(B[0], float):
|
74
|
+
z += [(fx, bx, y)
|
75
|
+
for bx, y in zip(B, pfe[i])
|
76
|
+
if y]
|
77
|
+
else:
|
78
|
+
z += [(fx, bx, y)
|
79
|
+
for bx, y in zip(B[i], pfe[i])
|
80
|
+
if y]
|
81
|
+
|
82
|
+
fbx = np.array(z).T[0:2]
|
83
|
+
y = np.array(z).T[2]
|
84
|
+
fitp, cov = so.curve_fit(lambda x, ch, cw, alpha, beta: pfe_modified_steinmetz(
|
85
|
+
x[0], x[1], ch, cw, alpha, beta, fo, Bo),
|
86
|
+
fbx, y, (1.0, 1.0, 1.0, 1.0))
|
87
|
+
return fitp
|
59
88
|
|
60
89
|
def fitjordan(f, B, losses, Bo, fo):
|
61
90
|
"""fit coeffs of
|
@@ -115,9 +144,6 @@ def fit_bertotti(f, B, losses):
|
|
115
144
|
if j > 2:
|
116
145
|
v.append(np.array((f[i0:j+i0], bb, y)).T.tolist())
|
117
146
|
|
118
|
-
def wbert(f, b, ch, cw, cx):
|
119
|
-
return (ch + cw*f)*b**2 + cx*f**0.5*b**1.5
|
120
|
-
|
121
147
|
z = np.array([b for a in v for b in a]).T
|
122
148
|
fbx = z[0:2]
|
123
149
|
y = z[2]
|