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/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
- v = getattr(reader, a, '')
760
- if v:
761
- setattr(self, a, v)
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 hasattr(self, 'FC_RADIUS'): # Note: cosys r/phi only
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
- br_temp_corr = 1. + self.br_temp_coef*(temperature - 20.)
1433
- magn = np.sqrt(self.mag[0]**2 + self.mag[1]**2)*br_temp_corr
1434
- alfa = np.arctan2(self.mag[1], self.mag[0]) - pos
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):
@@ -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]