femagtools 1.3.0__py3-none-any.whl → 1.3.2__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.
Files changed (47) hide show
  1. femagtools/__init__.py +1 -1
  2. femagtools/airgap.py +11 -37
  3. femagtools/amela.py +148 -13
  4. femagtools/bch.py +19 -3
  5. femagtools/dxfsl/area.py +68 -15
  6. femagtools/dxfsl/converter.py +15 -6
  7. femagtools/dxfsl/fslrenderer.py +13 -8
  8. femagtools/dxfsl/functions.py +1 -1
  9. femagtools/dxfsl/geom.py +415 -62
  10. femagtools/dxfsl/machine.py +97 -5
  11. femagtools/dxfsl/shape.py +46 -2
  12. femagtools/ecloss.py +393 -0
  13. femagtools/femag.py +25 -1
  14. femagtools/fsl.py +3 -2
  15. femagtools/hxy.py +126 -0
  16. femagtools/isa7.py +37 -24
  17. femagtools/machine/__init__.py +14 -13
  18. femagtools/machine/effloss.py +153 -32
  19. femagtools/machine/im.py +137 -56
  20. femagtools/machine/pm.py +584 -202
  21. femagtools/machine/sm.py +218 -64
  22. femagtools/machine/utils.py +12 -8
  23. femagtools/mcv.py +6 -8
  24. femagtools/model.py +11 -1
  25. femagtools/parstudy.py +1 -1
  26. femagtools/plot.py +159 -35
  27. femagtools/templates/afm_rotor.mako +102 -0
  28. femagtools/templates/afm_stator.mako +141 -0
  29. femagtools/templates/airgapinduc.mako +3 -3
  30. femagtools/templates/basic_modpar.mako +23 -2
  31. femagtools/templates/cogg_calc.mako +28 -5
  32. femagtools/templates/cu_losses.mako +1 -1
  33. femagtools/templates/fieldcalc.mako +39 -0
  34. femagtools/templates/gen_winding.mako +52 -47
  35. femagtools/templates/mesh-airgap.mako +43 -0
  36. femagtools/templates/stator3Linear.mako +5 -4
  37. femagtools/templates/therm-dynamic.mako +12 -6
  38. femagtools/templates/therm-static.mako +12 -0
  39. femagtools/templates/torq_calc.mako +2 -4
  40. femagtools/utils.py +45 -0
  41. femagtools/windings.py +2 -1
  42. {femagtools-1.3.0.dist-info → femagtools-1.3.2.dist-info}/METADATA +1 -1
  43. {femagtools-1.3.0.dist-info → femagtools-1.3.2.dist-info}/RECORD +47 -41
  44. {femagtools-1.3.0.dist-info → femagtools-1.3.2.dist-info}/WHEEL +1 -1
  45. {femagtools-1.3.0.dist-info → femagtools-1.3.2.dist-info}/LICENSE +0 -0
  46. {femagtools-1.3.0.dist-info → femagtools-1.3.2.dist-info}/entry_points.txt +0 -0
  47. {femagtools-1.3.0.dist-info → femagtools-1.3.2.dist-info}/top_level.txt +0 -0
femagtools/machine/im.py CHANGED
@@ -48,6 +48,7 @@ eecdefaults = dict(
48
48
  gam=0, # gam=0.7, 0..1
49
49
  kh=1, # kh=2, number of vertical conductors in slot
50
50
  tcu1=20,
51
+ kpfe=1,
51
52
  rotor_mass=0,
52
53
  kfric_b=1,
53
54
  pl2v=0.5,
@@ -140,6 +141,33 @@ class InductionMachine(Component):
140
141
  for k in eecdefaults.keys():
141
142
  if not hasattr(self, k):
142
143
  setattr(self, k, eecdefaults[k])
144
+ try:
145
+ self.tfric = self.kfric_b*self.rotor_mass*30e-3/np.pi
146
+ # formula from
147
+ # PERMANENT MAGNET MOTOR TECHNOLOGY: DESIGN AND APPLICATIONS
148
+ # Jacek Gieras
149
+ #
150
+ # plfric = kfric_b m_r n 1e-3 W
151
+ # -- kfric_b : 1..3 W/kg/rpm
152
+ # -- m_r: rotor mass in kg
153
+ # -- n: rotor speed in rpm
154
+ except AttributeError:
155
+ self.tfric = 0
156
+
157
+ self.skin_resistance = [None, None]
158
+ # here you can set user defined functions for calculating the skin-resistance,
159
+ # according to the current frequency w. First function in list is for stator, second for rotor.
160
+ # If None, the femagtools intern default implementation is used.
161
+ # User defined functions need to have the following arguments:
162
+ # - r0: (float) dc-resistance at 20°C
163
+ # - w: (float) current frequency in rad (2*pi*f)
164
+ # - tcu: (float) conductor temperature in deg Celsius
165
+ # - kth: (float) temperature coefficient (Default = 0.0039, Cu)
166
+
167
+ def pfric(self, n):
168
+ """friction and windage losses"""
169
+ return 2*np.pi*n*self.tfric
170
+
143
171
 
144
172
  def imag(self, psi):
145
173
  """magnetizing current"""
@@ -153,16 +181,17 @@ class InductionMachine(Component):
153
181
  if np.isclose(w, 0):
154
182
  return 0
155
183
  else:
156
- return self.m*((w*w)*(psi * psi)) / (
157
- (self.fec + self.fee *
158
- np.power(np.abs(psi)/self.psiref, self.fexp)) *
159
- (0.75 + 0.25*(np.abs(w)/self.wref)) *
160
- (np.abs(w)/self.wref) *
161
- np.power((np.abs(psi)/self.psiref), 2.0))
184
+ return self.m*(w*psi)**2 / self.plfe1(w, psi)
162
185
  except AttributeError:
163
186
  pass
164
187
  return self.rh
165
188
 
189
+ def plfe1(self, w1, psi):
190
+ return ((self.fec + self.fee *
191
+ np.power(np.abs(psi)/self.psiref, self.fexp)) *
192
+ (0.75 + 0.25*(np.abs(w1)/self.wref)) *
193
+ (np.abs(w1)/self.wref) * np.power((np.abs(psi)/self.psiref), 2.0))
194
+
166
195
  def lrot(self, w):
167
196
  """rotor leakage inductance"""
168
197
  return skin_leakage_inductance(self.lsigma2, w, self.tcu2,
@@ -174,13 +203,21 @@ class InductionMachine(Component):
174
203
 
175
204
  def rrot(self, w):
176
205
  """rotor resistance"""
177
- return skin_resistance(self.r2, w, self.tcu2, self.zeta2,
178
- 0.0, 1, kth=self.kth2)
206
+ sr = self.skin_resistance[1]
207
+ if sr is not None:
208
+ return sr(self.r2, w, self.tcu2, kth=self.kth2)
209
+ else:
210
+ return skin_resistance(self.r2, w, self.tcu2, self.zeta2,
211
+ 0.0, 1, kth=self.kth2)
179
212
 
180
213
  def rstat(self, w):
181
214
  """stator resistance"""
182
- return skin_resistance(self.r1, w, self.tcu1, self.zeta1,
183
- self.gam, self.kh, kth=self.kth1)
215
+ sr = self.skin_resistance[0]
216
+ if sr is not None:
217
+ return sr(self.r1, w, self.tcu1, kth=self.kth1)
218
+ else:
219
+ return skin_resistance(self.r1, w, self.tcu1, self.zeta1,
220
+ self.gam, self.kh, kth=self.kth1)
184
221
  # return self.r1
185
222
 
186
223
  def sigma(self, w, psi):
@@ -221,6 +258,17 @@ class InductionMachine(Component):
221
258
  psi)[0]
222
259
  return self.torque(w1, self.psi, wm)
223
260
 
261
+ def w1tmech(self, w1, u1max, psi, wm):
262
+ """calculate motor shaft torque"""
263
+ # check stator voltage
264
+ u1 = self.u1(w1, psi, wm)
265
+ self.psi = psi
266
+ if abs(u1) > u1max: # must adjust flux
267
+ self.psi = so.fsolve(
268
+ lambda psix: u1max - abs(self.u1(w1, psix, wm)),
269
+ psi)[0]
270
+ return self.tmech(w1, self.psi, wm)
271
+
224
272
  def pullouttorque(self, w1, u1):
225
273
  """pull out torque"""
226
274
  sk = self.sk(w1, u1/w1)
@@ -255,6 +303,11 @@ class InductionMachine(Component):
255
303
  i2 = self.i2(w1, psi, wm)
256
304
  return self.m*self.p/w2*r2*(i2*i2.conjugate()).real
257
305
 
306
+ def tmech(self, w1, psi, wm):
307
+ """shaft torque"""
308
+ u1 = self.u1(w1, psi, wm)
309
+ return self.torque(w1, psi, wm) - self.tfric
310
+
258
311
  def torqueu(self, w1, u1max, wm):
259
312
  """electric torque (in airgap)"""
260
313
  if np.isclose(w1, self.p*wm):
@@ -268,7 +321,20 @@ class InductionMachine(Component):
268
321
  psi)[0]
269
322
  return self.torque(w1, self.psi, wm)
270
323
 
271
- def w1(self, u1max, psi, tload, wm):
324
+ def tmechu(self, w1, u1max, wm):
325
+ """shaft torque"""
326
+ if np.isclose(w1, self.p*wm):
327
+ return 0.
328
+ # find psi
329
+ psi = u1max/w1
330
+ with warnings.catch_warnings():
331
+ warnings.simplefilter("ignore")
332
+ self.psi = so.fsolve(
333
+ lambda psi: u1max - abs(self.u1(w1, psi, wm)),
334
+ psi)[0]
335
+ return self.torque(w1, self.psi, wm)
336
+
337
+ def w1(self, u1max, psi, tload, wm, with_tmech=True):
272
338
  """calculate stator frequency with given torque and speed"""
273
339
  wsync = max(wm*self.p, 0.001)
274
340
  # sk = self.rrot(0.)/(wsync*(self.lstat(wsync) + self.lrot(0.0)))
@@ -276,27 +342,36 @@ class InductionMachine(Component):
276
342
  b = 0.999*wsync
277
343
  else:
278
344
  b = 1.001*wsync
345
+ if with_tmech:
346
+ logger.debug("wm %s tload %s w1tmech %s",
347
+ wm, tload, self.w1tmech(b, u1max, psi, wm))
348
+ with warnings.catch_warnings():
349
+ warnings.simplefilter("ignore")
350
+ return so.fsolve(
351
+ lambda w1: self.w1tmech(w1, u1max, psi, wm) - tload, b)[0]
352
+
279
353
  logger.debug("wm %s tload %s w1torque %s",
280
- wm, tload, self.w1torque(b, u1max, psi, wm))
354
+ wm, tload, self.w1tmech(b, u1max, psi, wm))
281
355
  with warnings.catch_warnings():
282
356
  warnings.simplefilter("ignore")
283
357
  return so.fsolve(
284
358
  lambda w1: self.w1torque(w1, u1max, psi, wm) - tload, b)[0]
285
359
 
286
- def wmfweak(self, u1max, psi, torque):
360
+ def wmfweak(self, u1max, psi, torque, with_tmech=True):
287
361
  """return lower speed limit of field weakening range"""
288
362
  wm0 = u1max/psi/self.p
289
363
  return so.fsolve(
290
364
  lambda wm: u1max - np.abs(self.u1(
291
- self.w1(u1max, psi, torque, wm), psi, wm)),
365
+ self.w1(u1max, psi, torque, wm, with_tmech), psi, wm)),
292
366
  wm0)[0]
293
367
 
294
- def torque_chart(self, smin=-0.1, smax=0.1, nsamples=100):
368
+ def torque_chart(self, smin=-0.1, smax=0.1, nsamples=100, with_tmech=True):
295
369
  """
296
370
  calculate torque(s) curve
297
371
 
298
372
  :param smin: min slip
299
373
  :param smax: max slip
374
+ :param with_tmech: use friction and windage losses (shaft torque)
300
375
  :return: dict with slip and torque lists
301
376
  """
302
377
 
@@ -307,10 +382,13 @@ class InductionMachine(Component):
307
382
  wm = (1-s) * w1 / self.p
308
383
  r = {}
309
384
  r['s'] = list(s)
310
- r['T'] = [self.torqueu(w1, u1, wx) for wx in wm]
385
+ if with_tmech:
386
+ r['T'] = [self.tmechu(w1, u1, wx) for wx in wm]
387
+ else:
388
+ r['T'] = [self.torqueu(w1, u1, wx) for wx in wm]
311
389
  return r
312
390
 
313
- def operating_point(self, T, n, u1max, Tfric=None):
391
+ def operating_point(self, T, n, u1max, Tfric=None, **kwargs):
314
392
  """
315
393
  calculate single operating point.
316
394
 
@@ -340,6 +418,7 @@ class InductionMachine(Component):
340
418
  Tfric -- (float, optional) the friction torque to consider in Nm.
341
419
  If Tfric is None, the friction torque is calculated according to the rotor-mass and the
342
420
  frition factor of the machine (kfric_b). Friction is written to the result dict!
421
+ with_tmech -- (optional) use friction and windage losses
343
422
  """
344
423
 
345
424
  r = {} # result dit
@@ -347,20 +426,16 @@ class InductionMachine(Component):
347
426
  if Tfric:
348
427
  tfric = Tfric
349
428
  else:
350
- # formula from
351
- # PERMANENT MAGNET MOTOR TECHNOLOGY: DESIGN AND APPLICATIONS
352
- # Jacek Gieras
353
- #
354
- # plfric = kfric_b m_r n 1e-3 W
355
- # -- kfric_b : 1..3 W/kg/rpm
356
- # -- m_r: rotor mass in kg
357
- # -- n: rotor speed in rpm
358
- tfric = self.kfric_b * self.rotor_mass * 30e-3 / np.pi
429
+ tfric = self.tfric
359
430
  # TODO: make frictiontorque speed depended?
360
431
 
361
- tq = T + tfric
432
+ with_tmech = kwargs.get('with_tmech', True)
433
+ if with_tmech:
434
+ tq = T + tfric
435
+ else:
436
+ tq = T
362
437
  wm = 2*np.pi*n
363
- w1 = self.w1(u1max, self.psiref, tq, wm)
438
+ w1 = self.w1(u1max, self.psiref, tq, wm, with_tmech)
364
439
  s = (w1 - self.p*wm) / w1
365
440
  r['s'] = float(s)
366
441
  r['f1'] = float(w1/2/np.pi)
@@ -369,16 +444,16 @@ class InductionMachine(Component):
369
444
  i1 = self.i1(w1, self.psi, wm)
370
445
  r['i1'] = float(np.abs(i1))
371
446
  r['cosphi'] = float(np.cos(np.angle(u1) - np.angle(i1)))
372
- r['plfe1'] = float(self.m * np.abs(u1) ** 2 / self.rfe(w1, self.psi))
447
+ r['plfe1'] = self.plfe1(w1, self.psi)
373
448
  i2 = self.i2(w1, self.psi, wm)
374
449
  r['i2'] = float(np.abs(i2))
375
450
  r['plcu1'] = float(self.m * np.abs(i1) ** 2 * self.rstat(w1))
376
451
  r['plcu2'] = float(self.m * np.abs(i2) ** 2 *
377
452
  self.rrot(w1 - self.p * wm))
378
- r['plfric'] = float(2 * np.pi * n * tfric)
453
+ r['plfw'] = float(self.pfric(n))
379
454
  pmech = 2 * np.pi * n * tq
380
455
  r['pmech'] = float(pmech)
381
- pltotal = r['plfe1'] + r['plfric'] + r['plcu1'] + r['plcu2']
456
+ pltotal = r['plfe1'] + r['plfw'] + r['plcu1'] + r['plcu2']
382
457
  r['losses'] = float(pltotal)
383
458
  p1 = pmech + pltotal
384
459
  r['p1'] = float(p1)
@@ -397,10 +472,10 @@ class InductionMachine(Component):
397
472
 
398
473
  return r
399
474
 
400
- def characteristics(self, T, n, u1max, nsamples=50, kpo=0.9):
475
+ def characteristics(self, T, n, u1max, nsamples=70, kpo=0.9, **kwargs):
401
476
  """calculate torque speed characteristics.
402
477
  return dict with list values of
403
- id, iq, n, T, ud, uq, u1, i1,
478
+ id, iq, n, T, ud, uq, u1, i1, s, pullout slip
404
479
  beta, gamma, phi, cosphi, pmech, n_type
405
480
 
406
481
  Keyword arguments:
@@ -408,9 +483,12 @@ class InductionMachine(Component):
408
483
  n -- (float) the maximum speed in 1/s
409
484
  u1max -- (float) the maximum phase voltage in V rms
410
485
  nsamples -- (optional) number of speed samples
486
+ with_tmech -- (optional) use friction and windage losses
487
+
411
488
  """
489
+ with_tmech = kwargs.get('with_tmech', True)
412
490
 
413
- wmType = self.wmfweak(u1max, self.psiref, T)
491
+ wmType = self.wmfweak(u1max, self.psiref, T, with_tmech)
414
492
  pmmax = wmType*T
415
493
  wmPullout = so.fsolve(
416
494
  lambda wx: (kpo*self.pullouttorque(self.p *
@@ -421,7 +499,7 @@ class InductionMachine(Component):
421
499
  logger.debug("u1 %g psi %g tq %g wm %g",
422
500
  u1max, self.psiref, tq, wm)
423
501
  try:
424
- w1 = self.w1(u1max, self.psiref, tq, wm)
502
+ w1 = self.w1(u1max, self.psiref, tq, wm, with_tmech)
425
503
  except ValueError:
426
504
  wmPullout = wm
427
505
  break
@@ -435,7 +513,7 @@ class InductionMachine(Component):
435
513
  logger.debug("u1 %g psi %g tq %g wm %g",
436
514
  u1max, self.psiref, tq, wm)
437
515
  try:
438
- w1 = self.w1(u1max, self.psiref, tq, wm)
516
+ w1 = self.w1(u1max, self.psiref, tq, wm, with_tmech)
439
517
  except ValueError:
440
518
  wmMax = wm
441
519
  break
@@ -464,7 +542,7 @@ class InductionMachine(Component):
464
542
  if len(wmlin) > 1:
465
543
  wmtab = np.concatenate(wmlin)
466
544
  else:
467
- wmtab = wmlin[0]
545
+ wmtab = wmlin[1:]
468
546
 
469
547
  def tload2(wm):
470
548
  if wm < wmType and wm < wmPullout:
@@ -478,37 +556,39 @@ class InductionMachine(Component):
478
556
  return min(wmPullout*pmmax/wm**2, T)
479
557
 
480
558
  r = dict(u1=[], i1=[], T=[], cosphi=[], n=[], s=[], sk=[],
481
- plfe1=[], plcu1=[], plcu2=[])
559
+ plfe1=[], plcu1=[], plcu2=[], f1=[])
482
560
  T = [tload2(wx) for wx in wmtab]
483
- tfric = self.kfric_b*self.rotor_mass*30e-3/np.pi
484
- w1tab = []
561
+ tfric = self.tfric
485
562
  with warnings.catch_warnings():
486
563
  warnings.simplefilter("ignore")
487
564
  for wm, tq in zip(wmtab, T):
488
565
  logger.debug("u1 %g psi %g tq %g wm %g",
489
566
  u1max, self.psiref, tq, wm)
490
567
  # try:
491
- w1 = self.w1(u1max, self.psiref, tq, wm)
492
- w1tab.append(w1)
568
+ w1 = self.w1(u1max, self.psiref, tq, wm, with_tmech)
569
+ r['f1'].append(w1/np.pi/2)
493
570
  u1 = self.u1(w1, self.psi, wm)
494
571
  r['u1'].append(np.abs(u1))
495
572
  i1 = self.i1(w1, self.psi, wm)
496
573
  r['i1'].append(np.abs(i1))
497
574
  r['cosphi'].append(np.cos(np.angle(u1) - np.angle(i1)))
498
- r['plfe1'].append(self.m*np.abs(u1)**2/self.rfe(w1, self.psi))
575
+ r['plfe1'].append(self.plfe1(w1, self.psi))
499
576
  i2 = self.i2(w1, self.psi, wm)
500
577
  r['plcu1'].append(self.m*np.abs(i1)**2*self.rstat(w1))
501
578
  r['plcu2'].append(self.m*np.abs(i2)**2*self.rrot(w1-self.p*wm))
502
- r['T'].append(tq - tfric)
579
+ if with_tmech:
580
+ r['T'].append(tq)
581
+ else:
582
+ r['T'].append(tq - tfric)
503
583
  r['n'].append(wm/2/np.pi)
504
584
  r['s'].append(float((w1 - self.p * wm) / w1))
505
585
  r['sk'].append(self.sk(w1, u1/w1))
506
- # except ValueError as ex:
507
- # break
508
- r['plfric'] = [2*np.pi*n*tfric for n in r['n']]
586
+ # except ValueError as ex:
587
+ # break
588
+ r['plfw'] = [self.pfric(n) for n in r['n']]
509
589
  r['pmech'] = [2*np.pi*n*tq for n, tq in zip(r['n'], r['T'])]
510
590
  pmech = np.array(r['pmech'])
511
- pltotal = (np.array(r['plfe1']) + np.array(r['plfric']) +
591
+ pltotal = (np.array(r['plfe1']) + np.array(r['plfw']) +
512
592
  np.array(r['plcu1']) + np.array(r['plcu2']))
513
593
  r['losses'] = pltotal.tolist()
514
594
 
@@ -657,7 +737,8 @@ def parident(workdir, engine, f1, u1, wdgcon,
657
737
 
658
738
  # prepare calculation
659
739
  job = engine.create_job(workdir)
660
- task = job.add_task(_eval_noloadrot(), extra_result_files)
740
+ pmod = model.poles * model.stator['num_slots_gen'] / model.stator['num_slots']
741
+ task = job.add_task(_eval_noloadrot(pmod), extra_result_files)
661
742
  logger.debug("Task %s noload workdir %s result files %s",
662
743
  task.id, task.directory, task.extra_result_files)
663
744
  # create model
@@ -862,8 +943,8 @@ def parident(workdir, engine, f1, u1, wdgcon,
862
943
  class _eval_noloaddc():
863
944
  """ Result Functor for noloadflux dc calc"""
864
945
 
865
- def __init__(self):
866
- pass
946
+ def __init__(self, pmod):
947
+ self.pmod = pmod
867
948
 
868
949
  def __call__(self, task):
869
950
  basedir = pathlib.Path(task.directory)
@@ -877,7 +958,7 @@ class _eval_noloaddc():
877
958
 
878
959
  bag = np.loadtxt(basedir / 'noloadbag.dat').T
879
960
  Bamp = [b['Bamp'] for b in [
880
- femagtools.airgap.fft(bag[0], b)
961
+ femagtools.airgap.fft(bag[0], b, self.pmod)
881
962
  for b in bag[1:]]]
882
963
  return {'i1_0': i0, 'psi1_0': psi0, 'Bamp': Bamp}
883
964
 
@@ -885,8 +966,8 @@ class _eval_noloaddc():
885
966
  class _eval_noloadrot():
886
967
  """ Result Functor for noloadflux rot calc"""
887
968
 
888
- def __init__(self):
889
- pass
969
+ def __init__(self, pmod):
970
+ self.pmod = pmod
890
971
 
891
972
  def __call__(self, task):
892
973
  basedir = pathlib.Path(task.directory)
@@ -906,7 +987,7 @@ class _eval_noloadrot():
906
987
  for k in range(ncurs)])
907
988
 
908
989
  # matrix (i x j x k) of curr, rotor pos, angle
909
- Bamp = [[femagtools.airgap.fft(bags[:, 0], b)['Bamp']
990
+ Bamp = [[femagtools.airgap.fft(bags[:, 0], b, self.pmod)['Bamp']
910
991
  for b in bags.T[1:]]
911
992
  for bags in [np.loadtxt(p) for p in sorted(
912
993
  basedir.glob(f"noloadbag-*.dat"),
@@ -983,7 +1064,7 @@ if __name__ == '__main__':
983
1064
  fec=9100,
984
1065
  fee=0,
985
1066
  fexp=7.0,
986
- pfric=2544.6,
1067
+ #pfric=2544.6,
987
1068
  rexp=2.4,
988
1069
  iml=79.8286,
989
1070
  ims=27.5346,