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/sm.py CHANGED
@@ -10,21 +10,23 @@ import warnings
10
10
  import numpy as np
11
11
  import scipy.optimize as so
12
12
  import scipy.interpolate as ip
13
- from .utils import skin_resistance, wdg_resistance
13
+ from .utils import skin_resistance, wdg_resistance, betai1, iqd, KTH
14
14
  from .. import parstudy, windings
15
15
  import femagtools.bch
16
16
 
17
17
  EPS = 1e-13
18
18
 
19
19
  eecdefaults = dict(
20
- zeta1=0.3,
21
- zeta2=0,
22
- gam=0.7,
23
- kh=2,
24
- tcu1=20,
25
- tcu2=20,
26
- rotor_mass=0,
27
- kfric_b=1)
20
+ zeta1=0.3,
21
+ zeta2=0,
22
+ gam=0.7,
23
+ kh=2,
24
+ tcu1=20,
25
+ tcu2=20,
26
+ rotor_mass=0,
27
+ kfric_b=1,
28
+ kpfe = 1 # iron loss factor
29
+ )
28
30
 
29
31
  logger = logging.getLogger('sm')
30
32
  logging.captureWarnings(True)
@@ -146,7 +148,7 @@ def parident(workdir, engine, machine,
146
148
  r1 = wdg_resistance(wdg, N, g, aw, da1, hs, lfe)
147
149
 
148
150
  if simulation['calculationMode'] == 'ld_lq_fast':
149
- return dict(m=3, p=b['machine']['p'],
151
+ dqpars = dict(m=3, p=b['machine']['p'],
150
152
  r1=r1,
151
153
  r2=machine['rotor'].get('resistance', 1),
152
154
  rotor_mass=rotor_mass, kfric_b=1,
@@ -162,7 +164,8 @@ def parident(workdir, engine, machine,
162
164
  losses={k: b['ldq']['losses'][k]
163
165
  for k in losskeys})
164
166
  for b in results['f']])
165
- return dict(m=3, p=b['machine']['p'],
167
+ else:
168
+ dqpars = dict(m=3, p=b['machine']['p'],
166
169
  r1=r1,
167
170
  r2=machine['rotor'].get('resistance', 1),
168
171
  rotor_mass=rotor_mass, kfric_b=1,
@@ -176,6 +179,9 @@ def parident(workdir, engine, machine,
176
179
  losses={k: b['psidq']['losses'][k]
177
180
  for k in losskeys})
178
181
  for b in results['f']])
182
+ if 'current_angles' in results['f'][0]:
183
+ dqpars['current_angles'] = results['f'][0]['current_angles']
184
+ return dqpars
179
185
 
180
186
  def _linsampl(exc, excl, a):
181
187
  """auxiliary func for linear sampling of nonlinear sequence
@@ -229,7 +235,18 @@ def gradient_respecting_bounds(bounds, fun, eps=1e-8):
229
235
 
230
236
 
231
237
  class SynchronousMachine(object):
232
- def __init__(self, eecpars):
238
+ def __init__(self, eecpars, **kwargs):
239
+ self.kth1 = KTH
240
+ self.kth2 = KTH
241
+ self.skin_resistance = [None, None]
242
+ # here you can set user defined functions for calculating the skin-resistance,
243
+ # according to the current frequency w. First function in list is for stator, second for rotor.
244
+ # If None, the femagtools intern default implementation is used.
245
+ # User defined functions need to have the following arguments:
246
+ # - r0: (float) dc-resistance at 20°C
247
+ # - w: (float) current frequency in rad (2*pi*f)
248
+ # - tcu: (float) conductor temperature in deg Celsius
249
+ # - kth: (float) temperature coefficient (Default = 0.0039, Cu)
233
250
  for k in eecdefaults.keys():
234
251
  setattr(self, k, eecdefaults[k])
235
252
 
@@ -237,6 +254,14 @@ class SynchronousMachine(object):
237
254
  if k not in ('ldq', 'psidq'):
238
255
  setattr(self, k, eecpars[k])
239
256
 
257
+ for k in kwargs:
258
+ setattr(self, k, kwargs[k])
259
+
260
+ try:
261
+ self.tfric = self.kfric_b*self.rotor_mass*30e-3/np.pi
262
+ except AttributeError:
263
+ self.tfric = 0
264
+
240
265
  self.fo = 50
241
266
  self.plexp = {'styoke_hyst': 1.0,
242
267
  'stteeth_hyst': 1.0,
@@ -254,21 +279,46 @@ class SynchronousMachine(object):
254
279
  'rotor_hyst': hf,
255
280
  'rotor_eddy': ef}
256
281
 
282
+ def pfric(self, n):
283
+ """friction and windage losses"""
284
+ return 2*np.pi*n*self.tfric
285
+
257
286
  def rstat(self, w):
258
287
  """stator resistance"""
259
- return skin_resistance(self.r1, w, self.tcu1, self.zeta1,
260
- self.gam, self.kh)
288
+ sr = self.skin_resistance[0]
289
+ if sr is not None:
290
+ return sr(self.r1, w, self.tcu1, kth=self.kth1)
291
+ else:
292
+ return skin_resistance(self.r1, w, self.tcu1, self.zeta1,
293
+ self.gam, self.kh, kth=self.kth1)
261
294
 
262
295
  def rrot(self, w):
263
296
  """rotor resistance"""
264
- return skin_resistance(self.r2, w, self.tcu2, self.zeta2,
265
- 0.0, 1)
297
+ sr = self.skin_resistance[1]
298
+ if sr is not None:
299
+ return sr(self.r2, w, self.tcu2, kth=self.kth2)
300
+ else:
301
+ return skin_resistance(self.r2, w, self.tcu2, self.zeta2,
302
+ 0.0, 1, kth=self.kth2)
266
303
 
267
304
  def torque_iqd(self, iq, id, iex):
268
305
  "torque at q-d-current"
269
306
  psid, psiq = self.psi(iq, id, iex)
270
307
  return self.m*self.p/2*(psid*iq - psiq*id)
271
308
 
309
+ def tloss_iqd(self, iq, id, iex, n):
310
+ """return loss torque of d-q current, iron loss correction factor
311
+ and friction windage losses"""
312
+ if n > 1e-3:
313
+ f1 = self.p*n
314
+ plfe = self.kpfe * (self.iqd_plfe1(iq, id, iex, f1) + self.iqd_plfe2(iq, id, f1))
315
+ return (plfe + self.pfric(n))/(2*np.pi*n)
316
+ return 0
317
+
318
+ def tmech_iqd(self, iq, id, iex, n):
319
+ """return shaft torque of d-q current and speed"""
320
+ return self.torque_iqd(iq, id, iex) - self.tloss_iqd(iq, id, iex, n)
321
+
272
322
  def uqd(self, w1, iq, id, iex):
273
323
  """return uq, ud of frequency w1 and d-q current"""
274
324
  psid, psiq = self.psi(iq, id, iex)
@@ -298,12 +348,41 @@ class SynchronousMachine(object):
298
348
  def iqd_plmag(self, iq, id, f1):
299
349
  return np.zeros(np.asarray(iq).shape)
300
350
 
351
+ def iqd_tmech(self, torque, n, disp=False, maxiter=500):
352
+ """return currents for shaft torque with minimal losses"""
353
+ if torque > 0:
354
+ startvals = self.bounds[0][1], 0, sum(self.bounds[-1])
355
+ else:
356
+ startvals = -self.bounds[0][1], 0, sum(self.bounds[-1])
357
+
358
+ with warnings.catch_warnings():
359
+ warnings.simplefilter("ignore")
360
+ def sqrtculoss(iqde):
361
+ pcu = self.culoss(iqde)
362
+ return pcu
363
+
364
+ res = so.minimize(
365
+ self.culoss, startvals, method='SLSQP', # trust-constr
366
+ bounds=self.bounds,
367
+ # jac=gradient_respecting_bounds(self.bounds, self.culoss),
368
+ constraints=[
369
+ {'type': 'eq',
370
+ 'fun': lambda iqd: self.tmech_iqd(*iqd, n) - torque}])
371
+ #options={'disp': disp, 'maxiter': maxiter})
372
+ if res['success']:
373
+ return res.x
374
+
375
+ logger.warning("%s: torque=%f %f, io=%s",
376
+ res['message'], torque, self.tmech_iqd(*startvals, n),
377
+ startvals)
378
+ raise ValueError(res['message'])
379
+
301
380
  def iqd_torque(self, torque, disp=False, maxiter=500):
302
381
  """return currents for torque with minimal losses"""
303
382
  if torque > 0:
304
- startvals = self.bounds[0][1]/2, 0, sum(self.bounds[-1])/2
383
+ startvals = self.bounds[0][1], 0, sum(self.bounds[-1])
305
384
  else:
306
- startvals = -self.bounds[0][1]/2, 0, sum(self.bounds[-1])/2
385
+ startvals = -self.bounds[0][1], 0, sum(self.bounds[-1])
307
386
 
308
387
  with warnings.catch_warnings():
309
388
  warnings.simplefilter("ignore")
@@ -317,8 +396,8 @@ class SynchronousMachine(object):
317
396
  # jac=gradient_respecting_bounds(self.bounds, self.culoss),
318
397
  constraints=[
319
398
  {'type': 'eq',
320
- 'fun': lambda iqd: self.torque_iqd(*iqd) - torque}],
321
- options={'disp': disp, 'maxiter': maxiter})
399
+ 'fun': lambda iqd: self.torque_iqd(*iqd) - torque}])
400
+ #options={'disp': disp, 'maxiter': maxiter})
322
401
  if res['success']:
323
402
  return res.x
324
403
  logger.warning("%s: torque=%f %f, io=%s",
@@ -326,14 +405,73 @@ class SynchronousMachine(object):
326
405
  startvals)
327
406
  raise ValueError(res['message'])
328
407
 
408
+ def mtpa(self, i1max):
409
+ """return iq, id, iex currents and maximum torque per current """
410
+ T0 = self.torque_iqd(np.sqrt(2)*i1max, 0, self.bounds[-1][1])
411
+ def i1tq(tq):
412
+ return abs(i1max) - np.linalg.norm(self.iqd_torque(tq)[:2])/np.sqrt(2)
413
+ with warnings.catch_warnings():
414
+ warnings.simplefilter("ignore")
415
+ tq = so.fsolve(i1tq, T0)[0]
416
+ iq, id, iex = self.iqd_torque(tq)
417
+ return iq, id, iex, tq
418
+
419
+ def mtpa_tmech(self, i1max, n):
420
+ """return iq, id, iex currents and maximum torque per current """
421
+ T0 = self.torque_iqd(np.sqrt(2)*i1max, 0, self.bounds[-1][0])
422
+ def i1tq(tq):
423
+ return i1max - np.linalg.norm(self.iqd_tmech(tq, n)[:2])/np.sqrt(2)
424
+ tq = so.fsolve(i1tq, T0)[0]
425
+ iq, id, iex = self.iqd_tmech(tq, n)
426
+ return iq, id, iex, tq
427
+
428
+ def iqd_tmech_umax(self, torque, w1, u1max, log=0, **kwargs):
429
+ """return currents and shaft torque at stator frequency and
430
+ with minimal losses at max voltage"""
431
+ iqde = self.iqd_tmech(torque, w1/2/np.pi/self.p)
432
+ if np.linalg.norm(
433
+ self.uqd(w1, *iqde)) <= u1max*np.sqrt(2):
434
+ if log:
435
+ log(iqde)
436
+ return (*iqde, torque)
437
+ beta, i1 = betai1(iqde[0], iqde[1])
438
+ iex = iqde[2]
439
+
440
+ def ubeta(b):
441
+ return np.sqrt(2)*u1max - np.linalg.norm(
442
+ self.uqd(w1, *iqd(b, i1), iex))
443
+ beta = -np.pi/4 if torque>0 else -3*np.pi/4
444
+ io = *iqd(beta, i1), iex
445
+
446
+ # logger.debug("--- torque %g io %s", torque, io)
447
+ with warnings.catch_warnings():
448
+ warnings.simplefilter("ignore")
449
+ n = w1/2/np.pi/self.p
450
+ def sqrtculoss(iqde):
451
+ pcu = self.culoss(iqde)
452
+ #logger.info("iqde %s pcu %g", iqde, pcu)
453
+ return pcu
454
+
455
+ res = so.minimize(
456
+ self.culoss, io, method='SLSQP', # trust-constr
457
+ bounds=self.bounds,
458
+ constraints=[
459
+ {'type': 'eq',
460
+ 'fun': lambda iqd: self.tmech_iqd(*iqd, n) - torque},
461
+ {'type': 'eq',
462
+ 'fun': lambda iqd: np.linalg.norm(
463
+ self.uqd(w1, *iqd)) - u1max*np.sqrt(2)}])
464
+ #if res['success']:
465
+ if log:
466
+ log(res.x)
467
+ return *res.x, self.tmech_iqd(*res.x, n)
468
+ #logger.warning("%s: w1=%f torque=%f, u1max=%f, io=%s",
469
+ # res['message'], w1, torque, u1max, io)
470
+ #raise ValueError(res['message'])
471
+
329
472
  def iqd_torque_umax(self, torque, w1, u1max,
330
- disp=False, maxiter=500, log=0):
473
+ disp=False, maxiter=500, log=0, **kwargs):
331
474
  """return currents for torque with minimal losses"""
332
- #logger.info(">> torque %g w1 %g u1 %g io %s", torque, w1, u1max, io)
333
- #if torque > 0:
334
- # io = self.bounds[0][1]/2, 0, sum(self.bounds[-1])/2
335
- #else:
336
- # io = -self.bounds[0][1]/2, 0, sum(self.bounds[-1])/2
337
475
  iqde = self.iqd_torque(torque, disp, maxiter)
338
476
  if np.linalg.norm(
339
477
  self.uqd(w1, *iqde)) <= u1max*np.sqrt(2):
@@ -342,8 +480,6 @@ class SynchronousMachine(object):
342
480
  return (*iqde, torque)
343
481
  io = iqde[0], 0, iqde[2]
344
482
  # logger.debug("--- torque %g io %s", torque, io)
345
- #logger.info(">> io %s", io)
346
-
347
483
  with warnings.catch_warnings():
348
484
  warnings.simplefilter("ignore")
349
485
  def sqrtculoss(iqde):
@@ -354,7 +490,7 @@ class SynchronousMachine(object):
354
490
  res = so.minimize(
355
491
  self.culoss, io, method='SLSQP', # trust-constr
356
492
  bounds=self.bounds,
357
- options={'disp': disp, 'maxiter': maxiter},
493
+ #options={'disp': disp, 'maxiter': maxiter},
358
494
  # jac=gradient_respecting_bounds(self.bounds, self.culoss),
359
495
  constraints=[
360
496
  {'type': 'eq',
@@ -363,6 +499,7 @@ class SynchronousMachine(object):
363
499
  'fun': lambda iqd: np.linalg.norm(
364
500
  self.uqd(w1, *iqd)) - u1max*np.sqrt(2)}])
365
501
  if res['success']:
502
+
366
503
  if log:
367
504
  log(res.x)
368
505
  return *res.x, self.torque_iqd(*res.x)
@@ -370,6 +507,17 @@ class SynchronousMachine(object):
370
507
  res['message'], w1, torque, u1max, io)
371
508
  raise ValueError(res['message'])
372
509
 
510
+ def w1_imax_umax(self, i1max, u1max):
511
+ """return frequency w1 and shaft torque at voltage u1max and current i1max
512
+
513
+ Keyword arguments:
514
+ u1max -- the maximum voltage (Vrms)
515
+ i1max -- the maximum current (Arms)"""
516
+ iq, id, iex, T = self.mtpa(i1max)
517
+ n0 = np.sqrt(2)*u1max/np.linalg.norm(
518
+ self.psi(iq, id, iex))/2/np.pi/self.p
519
+ return self.w1_umax(u1max, iq, id, iex), T
520
+
373
521
  def w1_umax(self, u, iq, id, iex):
374
522
  """return frequency w1 at given voltage u and id, iq current
375
523
 
@@ -381,7 +529,8 @@ class SynchronousMachine(object):
381
529
  lambda w1: np.linalg.norm(self.uqd(w1, iq, id, iex))-u*np.sqrt(2),
382
530
  w10)[0]
383
531
 
384
- def characteristics(self, T, n, u1max, nsamples=50):
532
+ def characteristics(self, T, n, u1max, nsamples=50,
533
+ with_tmech=True, **kwargs):
385
534
  """calculate torque speed characteristics.
386
535
  return dict with list values of
387
536
  n, T, u1, i1, beta, cosphi, pmech, n_type
@@ -391,15 +540,24 @@ class SynchronousMachine(object):
391
540
  n -- (float) the maximum speed in 1/s
392
541
  u1max -- (float) the maximum voltage in V rms
393
542
  nsamples -- (optional) number of speed samples
543
+ with_tmech -- (optional) use friction and windage losses
394
544
  """
395
545
  iq, id, iex = self.iqd_torque(T)
396
- w1type = self.w1_umax(u1max, iq, id, iex)
546
+ if with_tmech:
547
+ i1max = betai1(iq, id)[1]
548
+ if T < 0:
549
+ i1max = -i1max
550
+ w1type, Tf = self.w1_imax_umax(i1max, u1max)
551
+ else:
552
+ Tf = T
553
+ w1type = self.w1_umax(u1max, iq, id, iex)
554
+ logger.debug("w1type %f", w1type)
397
555
  wmType = w1type/self.p
398
- pmax = T*wmType
556
+ pmax = Tf*wmType
399
557
 
400
558
  def tload(wm):
401
- if abs(wm*T) < abs(pmax):
402
- return T
559
+ if abs(wm*Tf) < abs(pmax):
560
+ return Tf
403
561
  return pmax/wm
404
562
 
405
563
  wmtab = []
@@ -417,27 +575,20 @@ class SynchronousMachine(object):
417
575
  wmtab = (np.linspace(0, wmType, nx).tolist() +
418
576
  np.linspace(wmType+dw, wmMax, nsamples-nx).tolist())
419
577
 
420
- logger.info("Speed range %s", wmrange)
578
+ logger.info("Speed range T %f %s", Tf, wmrange)
421
579
  wmtab[0] = 0
422
580
 
423
581
  r = dict(u1=[], i1=[], id=[], iq=[], iex=[], T=[], cosphi=[], n=[],
424
582
  beta=[], plfe1=[], plcu1=[], plcu2=[])
425
- T = [tload(wx) for wx in wmtab]
426
- tfric = self.kfric_b*self.rotor_mass*30e-3/np.pi
427
- w1tab = []
428
- for wm, tq in zip(wmtab, T):
429
- # try:
583
+ for wm, tq in zip(wmtab, [tload(wx) for wx in wmtab]):
430
584
  w1 = wm*self.p
431
- tqx = tq
432
- # if w1 <= w1type:
433
- # iq, id, iex = self.iqd_torque(tq)
434
- # else:
435
- iq, id, iex, tqx = self.iqd_torque_umax(
436
- tq, w1, u1max)
437
- # (0.9*iq, 0.9*id,
438
- # min(self.bounds[-1][0], 0.9*iex)))[:-1]
439
- #logger.info("w1 %g tq %g: iq %g iex %g tqx %g",
440
- # w1, tq, iq, iex, tqx)
585
+ if with_tmech:
586
+ iq, id, iex, tqx = self.iqd_tmech_umax(
587
+ tq, w1, u1max)
588
+ else:
589
+ iq, id, iex, tqx = self.iqd_torque_umax(
590
+ tq, w1, u1max)
591
+ tqx -= self.tfric
441
592
  uq, ud = self.uqd(w1, iq, id, iex)
442
593
  u1 = np.linalg.norm((uq, ud))/np.sqrt(2)
443
594
  f1 = w1/2/np.pi
@@ -456,18 +607,16 @@ class SynchronousMachine(object):
456
607
  r['plfe1'].append(self.iqd_plfe1(iq, id, iex, f1))
457
608
  r['plcu1'].append(self.m*i1**2*self.rstat(w1))
458
609
  r['plcu2'].append(iex**2*self.rrot(0))
459
- r['T'].append(tq-tfric)
610
+ r['T'].append(tqx)
460
611
  r['n'].append(wm/2/np.pi)
461
- # except ValueError as ex:
462
- # logger.warning("ex %s wm %f T %f", ex, wm, tq)
463
- # break
464
612
 
465
613
  r['plfe'] = r['plfe1']
466
614
  r['plcu'] = (np.array(r['plcu1']) + np.array(r['plcu2'])).tolist()
467
- r['plfric'] = [2*np.pi*n*tfric for n in r['n']]
468
- r['pmech'] = [2*np.pi*n*tq for n, tq in zip(r['n'], r['T'])]
615
+ r['plfw'] = [self.pfric(n) for n in r['n']]
616
+ r['pmech'] = [2*np.pi*n*tq
617
+ for n, tq in zip(r['n'], r['T'])]
469
618
  pmech = np.array(r['pmech'])
470
- pltotal = (np.array(r['plfe1']) + np.array(r['plfric']) +
619
+ pltotal = (np.array(r['plfe1']) + np.array(r['plfw']) +
471
620
  np.array(r['plcu1']) + np.array(r['plcu2']))
472
621
  r['losses'] = pltotal.tolist()
473
622
 
@@ -489,9 +638,9 @@ class SynchronousMachine(object):
489
638
 
490
639
  class SynchronousMachinePsidq(SynchronousMachine):
491
640
 
492
- def __init__(self, eecpars, lfe=1, wdg=1):
641
+ def __init__(self, eecpars, lfe=1, wdg=1, **kwargs):
493
642
  super(self.__class__, self).__init__(
494
- eecpars)
643
+ eecpars, **kwargs)
495
644
  self.iqrange = (eecpars['psidq'][0]['iq'][0],
496
645
  eecpars['psidq'][0]['iq'][-1])
497
646
  self.idrange = (eecpars['psidq'][0]['id'][0],
@@ -578,8 +727,8 @@ class SynchronousMachinePsidq(SynchronousMachine):
578
727
 
579
728
 
580
729
  class SynchronousMachineLdq(SynchronousMachine):
581
- def __init__(self, eecpars, lfe=1, wdg=1):
582
- super(self.__class__, self).__init__(eecpars)
730
+ def __init__(self, eecpars, lfe=1, wdg=1, **kwargs):
731
+ super(self.__class__, self).__init__(eecpars, **kwargs)
583
732
  self.betarange = (eecpars['ldq'][0]['beta'][0]/180*np.pi,
584
733
  eecpars['ldq'][0]['beta'][-1]/180*np.pi)
585
734
  self.i1range = (0, eecpars['ldq'][0]['i1'][-1])
@@ -610,12 +759,15 @@ class SynchronousMachineLdq(SynchronousMachine):
610
759
  [_splinterp(beta, i1, betax, i1x, l['psiq'])
611
760
  for l in eecpars['ldq']]))
612
761
 
762
+ # extrapolate outside range
613
763
  self.psidf = ip.RegularGridInterpolator(
614
764
  (exc, betax, i1x), np.sqrt(2)*psid,
615
- method='cubic', bounds_error=False, fill_value=None)
765
+ method='cubic',
766
+ bounds_error=False, fill_value=None)
616
767
  self.psiqf = ip.RegularGridInterpolator(
617
768
  (exc, betax, i1x), np.sqrt(2)*psiq,
618
- method='cubic', bounds_error=False, fill_value=None)
769
+ method='cubic'
770
+ , bounds_error=False, fill_value=None)
619
771
  i1max = np.sqrt(2)*(max(i1))
620
772
  self.bounds = [(np.cos(min(beta))*i1max, i1max),
621
773
  (-i1max, 0),
@@ -632,6 +784,7 @@ class SynchronousMachineLdq(SynchronousMachine):
632
784
  np.array([l['losses'][k]
633
785
  for l in eecpars[idname]]))
634
786
  for k in keys}
787
+ # fill value with nan outside range
635
788
  self._losses = {k: ip.RegularGridInterpolator(
636
789
  (exc, beta, i1), lfe*np.array(pfe[k]),
637
790
  method='cubic', bounds_error=False, fill_value=None)
@@ -655,7 +808,8 @@ class SynchronousMachineLdq(SynchronousMachine):
655
808
  try:
656
809
  return self.psidf((iex, beta, i1)), self.psiqf((iex, beta, i1))
657
810
  except ValueError as ex:
658
- logger.error(iex, iq, id, beta, i1)
811
+ logger.error("iex %s iq %f id %f beta %f i1 %f",
812
+ iex, iq, id, beta, i1)
659
813
  raise ex
660
814
 
661
815
  def plfe1(self, beta, i1, iex, f1):
@@ -346,6 +346,7 @@ def dqparident(workdir, engine, temp, machine,
346
346
  speed: rotor speed in 1/s (default 160/p)
347
347
  i1_max: maximum current in A rms (default approx 3*i1nom)
348
348
  period_frac: fraction of rotating angle (default 6)
349
+ cmd: femag executable
349
350
  """
350
351
  import pathlib
351
352
 
@@ -368,13 +369,16 @@ def dqparident(workdir, engine, temp, machine,
368
369
  period_frac = 1 # TODO: missing femag support
369
370
 
370
371
  # winding resistance
371
- yd = machine['windings'].get('coil_span', Q1/machine['poles'])
372
- wdg = windings.Winding(
373
- {'Q': machine['stator']['num_slots'],
374
- 'm': machine['windings']['num_phases'],
375
- 'p': machine['poles']//2,
376
- 'l': machine['windings']['num_layers'],
377
- 'yd': yd})
372
+ wpar = {'Q': machine['stator']['num_slots'],
373
+ 'm': machine['windings']['num_phases'],
374
+ 'p': machine['poles']//2}
375
+
376
+ if 'coil_span' in machine['windings']:
377
+ wpar['yd'] = machine['windings']['coil_span']
378
+ if 'num_layers' in machine['windings']:
379
+ wpar['l'] = machine['windings']['num_layers']
380
+
381
+ wdg = windings.Winding(wpar)
378
382
 
379
383
  lfe = machine['lfe']
380
384
  g = machine['windings'].get('num_par_wdgs', 1)
@@ -401,7 +405,7 @@ def dqparident(workdir, engine, temp, machine,
401
405
  parvar = parstudy.List(
402
406
  workdir, condMat=condMat,
403
407
  magnetizingCurves=magnetizingCurves,
404
- magnets=magnetMat)
408
+ magnets=magnetMat, cmd=kwargs.get('cmd', None))
405
409
 
406
410
  leakfile = pathlib.Path(workdir) / 'end_wind_leak.dat'
407
411
  leakfile.unlink(missing_ok=True)
femagtools/mcv.py CHANGED
@@ -496,8 +496,7 @@ class Writer(Mcv):
496
496
  pfe = self.losses['pfe']
497
497
  f = self.losses['f']
498
498
  B = self.losses['B']
499
- colsize = len(f)
500
- losses = [list(p) + [0]*(colsize-len(p)) for p in pfe]
499
+ losses = [list(p) + [None]*(len(B)-len(p)) for p in pfe]
501
500
  fo = self.mc1_base_frequency
502
501
  Bo = self.mc1_base_induction
503
502
  fit_jordan = False
@@ -557,28 +556,27 @@ class Writer(Mcv):
557
556
  self.losses['fo'] = fo
558
557
 
559
558
  self.writeBlock([nfreq, nind])
560
- self.writeBlock(B +
561
- [0.0]*(M_LOSS_INDUCT - nind))
559
+ self.writeBlock([float(b) for b in B] + [0.0]*(M_LOSS_INDUCT - nind))
562
560
 
563
561
  for f, p in zip(self.losses['f'], pfe):
564
562
  if f > 0:
565
563
  y = np.array(p)
566
- losses = y[y != np.array(None)].tolist()
564
+ losses = [float(x) for x in y[y != np.array(None)]]
567
565
  if len(losses) == nind:
568
566
  pl = p
569
567
  else:
570
568
  n = len(losses)
571
569
  cw, alfa, beta = lc.fitsteinmetz(
572
570
  f, B[:n], losses, Bo, fo)
573
- pl = [lc.pfe_steinmetz(
571
+ pl = losses + [lc.pfe_steinmetz(
574
572
  f, b, cw, alfa, beta,
575
573
  self.losses['fo'],
576
574
  self.losses['Bo'])
577
- for b in B]
575
+ for b in B[n:]]
578
576
  logger.debug("%s", pl)
579
577
  self.writeBlock(pl +
580
578
  [0.0]*(M_LOSS_INDUCT - len(pl)))
581
- self.writeBlock(f)
579
+ self.writeBlock(float(f))
582
580
  for m in range(M_LOSS_FREQ - len(pfe)):
583
581
  self.writeBlock([0.0]*M_LOSS_INDUCT)
584
582
  self.writeBlock(0.0)
femagtools/model.py CHANGED
@@ -11,6 +11,7 @@
11
11
  import logging
12
12
  import string
13
13
  import numpy as np
14
+ from . import windings
14
15
 
15
16
  logger = logging.getLogger(__name__)
16
17
  #
@@ -156,8 +157,17 @@ class MachineModel(Model):
156
157
  for mcv in ('mcvkey_yoke', 'mcvkey_shaft'):
157
158
  if mcv not in self.magnet:
158
159
  self.magnet[mcv] = 'dummy'
159
- if 'coord_system' in parameters:
160
+ if 'coord_system' in parameters or 'afmtype' in parameters:
160
161
  self.move_action = 1
162
+ wdg = windings.Winding({'Q': self.stator['num_slots'],
163
+ 'p': self.poles//2,
164
+ 'm': self.windings.get('num_phases', 3),
165
+ 'l': self.windings.get('num_layers', 1)})
166
+ self.windings['wdgscheme'] = ''.join([
167
+ '{'] + [','.join([''.join(['{']+[','.join([''.join([
168
+ '{', ','.join(
169
+ [str(n) for n in z]), '}']) for z in l])] + ['}'])
170
+ for l in wdg.zoneplan()])] + ['}'])
161
171
  else:
162
172
  self.coord_system = 0
163
173
  self.move_action = 0
femagtools/parstudy.py CHANGED
@@ -111,7 +111,7 @@ class ParameterStudy(object):
111
111
  raise ValueError("directory {} is not empty".format(dirname))
112
112
  self.reportdir = dirname
113
113
 
114
- def setup_model(self, builder, model, recsin):
114
+ def setup_model(self, builder, model, recsin=''):
115
115
  """builds model in current workdir and returns its filenames"""
116
116
  # get and write mag curves
117
117
  mc_files = self.femag.copy_magnetizing_curves(model, recsin=recsin)