femagtools 1.6.8__py3-none-any.whl → 1.7.0__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 (50) hide show
  1. femagtools/__init__.py +2 -2
  2. femagtools/bch.py +1 -1
  3. femagtools/dxfsl/area.py +334 -332
  4. femagtools/dxfsl/areabuilder.py +131 -10
  5. femagtools/dxfsl/conv.py +27 -9
  6. femagtools/dxfsl/converter.py +390 -125
  7. femagtools/dxfsl/corner.py +3 -0
  8. femagtools/dxfsl/femparser.py +1 -1
  9. femagtools/dxfsl/fslrenderer.py +290 -246
  10. femagtools/dxfsl/functions.py +4 -2
  11. femagtools/dxfsl/geom.py +1120 -886
  12. femagtools/dxfsl/journal.py +53 -22
  13. femagtools/dxfsl/machine.py +250 -74
  14. femagtools/dxfsl/plotrenderer.py +34 -3
  15. femagtools/dxfsl/shape.py +380 -103
  16. femagtools/dxfsl/symmetry.py +679 -110
  17. femagtools/femag.py +27 -2
  18. femagtools/forcedens.py +65 -40
  19. femagtools/fsl.py +71 -28
  20. femagtools/losscoeffs.py +46 -0
  21. femagtools/machine/effloss.py +8 -1
  22. femagtools/machine/im.py +3 -1
  23. femagtools/machine/pm.py +11 -7
  24. femagtools/machine/sizing.py +14 -11
  25. femagtools/machine/sm.py +114 -33
  26. femagtools/machine/utils.py +38 -34
  27. femagtools/model.py +12 -2
  28. femagtools/moo/population.py +1 -1
  29. femagtools/parstudy.py +17 -1
  30. femagtools/plot/__init__.py +1 -1
  31. femagtools/plot/char.py +24 -7
  32. femagtools/plot/forcedens.py +56 -29
  33. femagtools/plot/mcv.py +4 -1
  34. femagtools/plot/phasor.py +6 -1
  35. femagtools/poc.py +17 -10
  36. femagtools/templates/cogg_calc.mako +7 -1
  37. femagtools/templates/displ_stator_rotor.mako +33 -0
  38. femagtools/templates/fieldcalc.mako +10 -16
  39. femagtools/templates/pm_sym_f_cur.mako +1 -1
  40. femagtools/tks.py +3 -9
  41. {femagtools-1.6.8.dist-info → femagtools-1.7.0.dist-info}/LICENSE +1 -0
  42. {femagtools-1.6.8.dist-info → femagtools-1.7.0.dist-info}/METADATA +7 -4
  43. {femagtools-1.6.8.dist-info → femagtools-1.7.0.dist-info}/RECORD +50 -49
  44. tests/engines/__init__.py +0 -20
  45. tests/geom/__init__.py +0 -20
  46. tests/moo/__init__.py +0 -20
  47. tests/test_model.py +8 -1
  48. {femagtools-1.6.8.dist-info → femagtools-1.7.0.dist-info}/WHEEL +0 -0
  49. {femagtools-1.6.8.dist-info → femagtools-1.7.0.dist-info}/entry_points.txt +0 -0
  50. {femagtools-1.6.8.dist-info → femagtools-1.7.0.dist-info}/top_level.txt +0 -0
@@ -6,7 +6,7 @@
6
6
  import numpy as np
7
7
  import logging
8
8
 
9
- logger = logging.getLogger("femagools.machineo.sizing")
9
+ logger = logging.getLogger("femagools.machine.sizing")
10
10
 
11
11
 
12
12
  PM_DEFAULTS = dict(
@@ -94,13 +94,6 @@ def wdg_resistance(w1, l, d):
94
94
  return 2*w1*l/S/a
95
95
 
96
96
 
97
- def current_losses(w1, l, d, i1):
98
- # w1: num turns/phase
99
- # l: wire length/slot
100
- # d: wire diameter
101
- return wdg_resistance(w1, l, d) * i1**2
102
-
103
-
104
97
  def check_symmetry_conditions(Q1, p, layers, m):
105
98
  # if Q1 % 2*p == 0:
106
99
  # raise ValidationError("Number of slots Q and poles 2*p are not prime to each other")
@@ -227,6 +220,10 @@ def _stator_slots(par, slots):
227
220
 
228
221
 
229
222
  def get_stator_dimensions(par, slots=[]):
223
+ # Check symmetry
224
+ if 'Q1' in par:
225
+ from ..windings import Winding
226
+ wdg = Winding({'Q': par['Q1'], 'p': par['p'], 'm': 3})
230
227
  # nominal (rated) operating parameters
231
228
  pnom = par['pnom']
232
229
  speednom = par['speed']
@@ -380,12 +377,16 @@ def get_stator_dimensions(par, slots=[]):
380
377
  middle_line=0 if layers < 2 else middle_line))
381
378
  # coils per phase and pole
382
379
  # q = Q/2/p/m
383
- # num wires per coil side
380
+ # num wires per coil side (number of coil groups a)
384
381
  # n = a*N / 2 / p / q
385
- num_wires = round(2*m*N/layers/Q1)
382
+ ncoils = Q1//2//m*layers
383
+ ngroups = [1] + [g for g in range(2, p+1) if p%g == 0]
384
+ a = ngroups[np.argmin([abs(N-ncoils//a*round(a*N/ncoils))
385
+ for a in ngroups])]
386
+ num_wires = round(a*N/ncoils)
386
387
  dwire = 2*np.sqrt(ans*kq/layers/np.pi/num_wires)
387
388
  relculen = 1.4
388
- r1 = wdg_resistance(N, relculen*lfe, dwire)
389
+ r1 = wdg_resistance(N, relculen*lfe, dwire)/a
389
390
  r['pcu'] = round(m*r1*I1**2, 1)
390
391
 
391
392
  r['winding'] = dict(
@@ -393,6 +394,7 @@ def get_stator_dimensions(par, slots=[]):
393
394
  num_phases=m,
394
395
  cufilfact=kq,
395
396
  culength=1.4,
397
+ num_par_wdgs=a,
396
398
  num_layers=layers,
397
399
  resistance=round(r1, 4),
398
400
  coil_span=int(coil_span),
@@ -662,6 +664,7 @@ def spm(pnom: float, speed: float, p: int, **kwargs) -> dict:
662
664
  par.update(kwargs)
663
665
 
664
666
  _set_pm_defaults(par)
667
+
665
668
  # stator and magnet parameters of surface mounted magnet machine
666
669
  r = get_stator_dimensions(par)
667
670
 
femagtools/machine/sm.py CHANGED
@@ -45,6 +45,9 @@ def parident(workdir, engine, machine,
45
45
  num_exc_steps: number of excitation current (default 7)
46
46
  speed: rotor speed in 1/s (default 160/p)
47
47
  i1_max: maximum current in A rms (default approx 3*i1nom)
48
+ beta_min: minimal current angle (default -180°)
49
+ beta_max: maximal current angle (default 0°)
50
+ num_move_steps: number of move steps
48
51
  """
49
52
  cmd = kwargs.get('cmd', None)
50
53
  da1 = machine['outer_diam']
@@ -60,7 +63,7 @@ def parident(workdir, engine, machine,
60
63
  N = machine[wdgk]['num_wires']
61
64
  i1_max = round(0.28*np.pi*hs*(da1+hs)/Q1/N*Jmax*1e5)*10 * \
62
65
  machine[wdgk].get('num_par_wdgs', 1)
63
-
66
+
64
67
  ifnom = machine['rotor']['ifnom']
65
68
  exc_logspace = True
66
69
  if exc_logspace:
@@ -86,22 +89,21 @@ def parident(workdir, engine, machine,
86
89
  'ld_lq_fast'),
87
90
  wind_temp=20.0,
88
91
  i1_max=kwargs.get('i1_max', i1_max),
89
- maxid=0,
90
- minid=-i1_max,
91
- maxiq=i1_max,
92
- miniq=-i1_max,
93
- delta_id=i1_max/kwargs.get('num_cur_steps', 5),
94
- delta_iq=i1_max/kwargs.get('num_cur_steps', 5),
95
- beta_min=-180.0,
96
- beta_max=0.0,
97
- calc_noload=0,
92
+ maxid=kwargs.get('maxid', 0),
93
+ minid=kwargs.get('minid', -i1_max),
94
+ maxiq=kwargs.get('maxiq', i1_max),
95
+ miniq=kwargs.get('miniq', -i1_max),
96
+ delta_id=kwargs.get('delta_id', i1_max/5),
97
+ delta_iq=kwargs.get('delta_iq', i1_max/5),
98
+ beta_min=kwargs.get('beta_min', -180),
99
+ beta_max=kwargs.get('beta_max', 0),
98
100
  num_move_steps=kwargs.get('num_move_steps', 31),
99
101
  load_ex_cur=0.5,
100
102
  num_cur_steps=kwargs.get('num_cur_steps', 5),
101
103
  num_beta_steps=kwargs.get('num_beta_steps', 13),
102
104
  num_par_wdgs=machine[wdgk].get('num_par_wdgs', 1),
103
- period_frac=6,
104
- speed=50.0)
105
+ period_frac=kwargs.get('period_frac', 6),
106
+ speed=kwargs.get('speed', 50))
105
107
 
106
108
  ###self.cleanup() # remove previously created files in workdir
107
109
  results = parvar(parvardef, machine, simulation, engine)
@@ -119,9 +121,10 @@ def parident(workdir, engine, machine,
119
121
  rotor_mass = 0 # need femag classic > rel-9.3.x-48-gca42bbd0
120
122
  b = results['f'][-1]
121
123
 
122
- losskeys = ('speed', 'ef', 'hf',
124
+ losskeys = ('speed', 'ef', 'hf', 'cf',
123
125
  'styoke_hyst', 'stteeth_hyst', 'styoke_eddy',
124
- 'stteeth_eddy', 'rotor_hyst', 'rotor_eddy')
126
+ 'stteeth_eddy', 'rotor_hyst', 'rotor_eddy',
127
+ 'styoke_excess', 'stteeth_excess', 'rotor_excess')
125
128
 
126
129
  # winding resistance
127
130
  try:
@@ -159,7 +162,7 @@ def parident(workdir, engine, machine,
159
162
  ld=b['ldq']['ld'],
160
163
  lq=b['ldq']['lq'],
161
164
  losses={k: b['ldq']['losses'][k]
162
- for k in losskeys})
165
+ for k in losskeys if k in b['ldq']['losses']})
163
166
  for b in results['f']])
164
167
  else:
165
168
  dqpars = dict(m=3, p=b['machine']['p'],
@@ -170,11 +173,12 @@ def parident(workdir, engine, machine,
170
173
  ex_current=b['machine']['ex_current'],
171
174
  iq=b['psidq']['iq'],
172
175
  id=b['psidq']['id'],
176
+ psidq_ldq=b['psidq_ldq'],
173
177
  psid=b['psidq']['psid'],
174
178
  psiq=b['psidq']['psiq'],
175
179
  torque=b['psidq']['torque'],
176
180
  losses={k: b['psidq']['losses'][k]
177
- for k in losskeys})
181
+ for k in losskeys if k in b['psidq']['losses']})
178
182
  for b in results['f']])
179
183
  if 'current_angles' in results['f'][0]:
180
184
  dqpars['current_angles'] = results['f'][0]['current_angles']
@@ -242,6 +246,7 @@ class SynchronousMachine(object):
242
246
  self.kth2 = KTH
243
247
  self.skin_resistance = [None, None]
244
248
  self.kpmag = 1
249
+ self.bertotti = False
245
250
  # here you can set user defined functions for calculating the skin-resistance,
246
251
  # according to the current frequency w. First function in list is for stator, second for rotor.
247
252
  # If None, the femagtools intern default implementation is used.
@@ -282,6 +287,13 @@ class SynchronousMachine(object):
282
287
  'rotor_hyst': hf,
283
288
  'rotor_eddy': ef}
284
289
 
290
+ if self.bertotti:
291
+ self.plexp.update({
292
+ 'styoke_excess': 1.5,
293
+ 'stteeth_excess':1.5,
294
+ 'rotor_excess': 1.5})
295
+
296
+
285
297
  def pfric(self, n):
286
298
  """friction and windage losses"""
287
299
  return 2*np.pi*n*self.tfric
@@ -315,7 +327,7 @@ class SynchronousMachine(object):
315
327
  if n > 1e-3:
316
328
  f1 = self.p*n
317
329
  plfe = self.kpfe * (self.iqd_plfe1(iq, id, iex, f1)
318
- + self.iqd_plfe2(iq, id, f1))
330
+ + self.iqd_plfe2(iq, id, iex, f1))
319
331
  return (plfe + self.pfric(n))/(2*np.pi*n)
320
332
  return 0
321
333
 
@@ -341,7 +353,7 @@ class SynchronousMachine(object):
341
353
  """return uq, ud of frequency w1 and d-q current"""
342
354
  psid, psiq = self.psi(iq, id, iex)
343
355
  r1 = self.rstat(w1)
344
- return r1*id + w1*psid, r1*iq - w1*psiq
356
+ return r1*iq + w1*psid, r1*id - w1*psiq
345
357
 
346
358
  def plcu1(self, iqde, w1):
347
359
  r1 = self.rstat(w1)
@@ -360,7 +372,7 @@ class SynchronousMachine(object):
360
372
  def iqd_plcu2(self, iq, id, iex):
361
373
  return self.plcu2((iq, id, iex))
362
374
 
363
- def iqd_plfe2(self, iq, id, f1):
375
+ def iqd_plfe2_(self, iq, id, f1):
364
376
  return np.zeros(np.asarray(iq).shape)
365
377
 
366
378
  def iqd_plmag(self, iq, id, f1):
@@ -369,9 +381,9 @@ class SynchronousMachine(object):
369
381
  def iqd_tmech(self, torque, n, disp=False, maxiter=500):
370
382
  """return currents for shaft torque with minimal losses"""
371
383
  if torque > 0:
372
- startvals = self.bounds[0][1], 0, sum(self.bounds[-1])
384
+ startvals = self.bounds[0][1]/2, 0, sum(self.bounds[-1])/2
373
385
  else:
374
- startvals = -self.bounds[0][1], 0, sum(self.bounds[-1])
386
+ startvals = -self.bounds[0][1]/2, 0, sum(self.bounds[-1])/2
375
387
 
376
388
  with warnings.catch_warnings():
377
389
  warnings.simplefilter("ignore")
@@ -398,9 +410,9 @@ class SynchronousMachine(object):
398
410
  def iqd_torque(self, torque, disp=False, maxiter=500):
399
411
  """return currents for torque with minimal losses"""
400
412
  if torque > 0:
401
- startvals = self.bounds[0][1], 0, sum(self.bounds[-1])
413
+ startvals = self.bounds[0][1]/2, 0, sum(self.bounds[-1])/2
402
414
  else:
403
- startvals = -self.bounds[0][1], 0, sum(self.bounds[-1])
415
+ startvals = -self.bounds[0][1]/2, 0, sum(self.bounds[-1])/2
404
416
 
405
417
  with warnings.catch_warnings():
406
418
  warnings.simplefilter("ignore")
@@ -425,7 +437,7 @@ class SynchronousMachine(object):
425
437
 
426
438
  def mtpa(self, i1max):
427
439
  """return iq, id, iex currents and maximum torque per current """
428
- T0 = self.torque_iqd(np.sqrt(2)*i1max, 0, self.bounds[-1][1])
440
+ T0 = self.torque_iqd(np.sqrt(2)*i1max/2, 0, self.bounds[-1][1]/2)
429
441
  def i1tq(tq):
430
442
  return abs(i1max) - np.linalg.norm(self.iqd_torque(tq)[:2])/np.sqrt(2)
431
443
  with warnings.catch_warnings():
@@ -436,7 +448,7 @@ class SynchronousMachine(object):
436
448
 
437
449
  def mtpa_tmech(self, i1max, n):
438
450
  """return iq, id, iex currents and maximum torque per current """
439
- T0 = self.torque_iqd(np.sqrt(2)*i1max, 0, self.bounds[-1][0])
451
+ T0 = self.torque_iqd(np.sqrt(2)*i1max/2, 0, self.bounds[-1][0])/2
440
452
  def i1tq(tq):
441
453
  return i1max - np.linalg.norm(self.iqd_tmech(tq, n)[:2])/np.sqrt(2)
442
454
  tq = so.fsolve(i1tq, T0)[0]
@@ -560,7 +572,11 @@ class SynchronousMachine(object):
560
572
  nsamples -- (optional) number of speed samples
561
573
  with_tmech -- (optional) use friction and windage losses
562
574
  with_torque_corr -- (optional) T is corrected if out of range
575
+ Optional arguments:
576
+ i1max: max. phase current (RMS)
563
577
  """
578
+ if kwargs.get('i1max', 0):
579
+ w1type, T = self.w1_imax_umax(kwargs['i1max'], u1max)
564
580
  try:
565
581
  iq, id, iex = self.iqd_torque(T)
566
582
  except ValueError:
@@ -588,6 +604,7 @@ class SynchronousMachine(object):
588
604
  if T < 0:
589
605
  i1max = -i1max
590
606
  w1type, Tf = self.w1_imax_umax(i1max, u1max)
607
+
591
608
  else:
592
609
  Tf = T
593
610
  w1type = self.w1_umax(u1max, iq, id, iex)
@@ -619,7 +636,9 @@ class SynchronousMachine(object):
619
636
  wmtab[0] = 0
620
637
 
621
638
  r = dict(u1=[], i1=[], id=[], iq=[], iex=[], T=[], cosphi=[], n=[],
622
- beta=[], plfe1=[], plcu1=[], plcu2=[])
639
+ beta=[], plfe1=[], plfe2=[], plcu1=[], plcu2=[])
640
+ # add type speed to result dict
641
+ r['n_type'] = wmType/2/np.pi
623
642
  for wm, tq in zip(wmtab, [tload(wx) for wx in wmtab]):
624
643
  w1 = wm*self.p
625
644
  if with_tmech:
@@ -645,12 +664,13 @@ class SynchronousMachine(object):
645
664
  gamma = np.arctan2(ud, uq)
646
665
  r['cosphi'].append(np.cos(gamma - beta))
647
666
  r['plfe1'].append(self.iqd_plfe1(iq, id, iex, f1))
667
+ r['plfe2'].append(self.iqd_plfe2(iq, id, iex, f1))
648
668
  r['plcu1'].append(self.m*i1**2*self.rstat(w1))
649
669
  r['plcu2'].append(iex**2*self.rrot(0))
650
670
  r['T'].append(tqx)
651
671
  r['n'].append(wm/2/np.pi)
652
672
 
653
- r['plfe'] = r['plfe1']
673
+ r['plfe'] = (np.array(r['plfe1']) + np.array(r['plfe2'])).tolist()
654
674
  r['plcu'] = (np.array(r['plcu1']) + np.array(r['plcu2'])).tolist()
655
675
  r['plfw'] = [self.pfric(n) for n in r['n']]
656
676
  r['pmech'] = [2*np.pi*n*tq
@@ -685,6 +705,10 @@ class SynchronousMachinePsidq(SynchronousMachine):
685
705
  eecpars['psidq'][0]['iq'][-1])
686
706
  self.idrange = (eecpars['psidq'][0]['id'][0],
687
707
  eecpars['psidq'][0]['id'][-1])
708
+
709
+ self.betarange = (-np.pi if min(self.iqrange) < 0 else -np.pi/2,
710
+ 0 if max(self.iqrange) > 0 else -np.pi/2)
711
+ self.i1range = (0, betai1(np.max(self.iqrange), 0)[1])
688
712
  islinear = True
689
713
  iexc = [l['ex_current'] for l in eecpars['psidq']]
690
714
  self.exc_max = iexc[-1]
@@ -722,9 +746,18 @@ class SynchronousMachinePsidq(SynchronousMachine):
722
746
  self.bounds = [(min(iq), max(iq)),
723
747
  (min(id), 0),
724
748
  (iexc[0], iexc[-1])]
749
+
750
+ keys = self.plexp.keys()
725
751
  try:
726
752
  idname = 'psidq'
727
- keys = self.plexp.keys()
753
+ # check if bertotti
754
+ if 'styoke_excess' in eecpars[idname][0]['losses'] and \
755
+ np.any(np.array(eecpars[idname][0]['losses']['styoke_excess'])):
756
+ self.bertotti = True
757
+ keys.update({{
758
+ 'styoke_excess': 1.5,
759
+ 'stteeth_excess':1.5,
760
+ 'rotor_excess': 1.5}})
728
761
  if islinear:
729
762
  pfe = {k: np.array([l['losses'][k]
730
763
  for l in eecpars[idname]])
@@ -758,14 +791,27 @@ class SynchronousMachinePsidq(SynchronousMachine):
758
791
  raise ex
759
792
 
760
793
  def plfe1(self, iq, id, iex, f1):
794
+ losskeys = ['styoke_eddy', 'styoke_hyst',
795
+ 'stteeth_eddy', 'stteeth_hyst']
796
+ if self.bertotti:
797
+ losskeys += ['styoke_excess', 'stteeth_excess']
798
+ return np.sum([
799
+ self._losses[k]((iex, iq, id))*(f1/self.fo)**self.plexp[k][0]
800
+ for k in losskeys], axis=0)
801
+
802
+ def plfe2(self, iq, id, iex, f1):
803
+ losskeys = ['rotor_hyst', 'rotor_eddy']
804
+ if self.bertotti:
805
+ losskeys += ['rotor_excess']
761
806
  return np.sum([
762
807
  self._losses[k]((iex, iq, id))*(f1/self.fo)**self.plexp[k][0]
763
- for k in ('styoke_eddy', 'styoke_hyst',
764
- 'stteeth_eddy', 'stteeth_hyst')], axis=0)
808
+ for k in losskeys], axis=0)
765
809
 
766
810
  def iqd_plfe1(self, iq, id, iex, f1):
767
811
  return self.plfe1(iq, id, iex, f1)
768
812
 
813
+ def iqd_plfe2(self, iq, id, iex, f1):
814
+ return self.plfe2(iq, id, iex, f1)
769
815
 
770
816
  class SynchronousMachineLdq(SynchronousMachine):
771
817
  def __init__(self, eecpars, lfe=1, wdg=1, **kwargs):
@@ -814,9 +860,20 @@ class SynchronousMachineLdq(SynchronousMachine):
814
860
  self.bounds = [(np.cos(min(beta))*i1max, i1max),
815
861
  (-i1max, 0),
816
862
  (iexc[0], iexc[-1])]
863
+
864
+ # iron losses
817
865
  keys = self.plexp.keys()
818
866
  try:
819
867
  idname = 'ldq'
868
+ # check bertotti losses
869
+ if 'styoke_excess' in eecpars[idname][0]['losses'] and \
870
+ np.any(np.array(eecpars[idname][0]['losses']['styoke_excess'])):
871
+ self.bertotti = True
872
+ keys.update({{
873
+ 'styoke_excess': 1.5,
874
+ 'stteeth_excess':1.5,
875
+ 'rotor_excess': 1.5}})
876
+
820
877
  if islinear:
821
878
  pfe = {k: np.array([l['losses'][k]
822
879
  for l in eecpars[idname]])
@@ -826,6 +883,7 @@ class SynchronousMachineLdq(SynchronousMachine):
826
883
  np.array([l['losses'][k]
827
884
  for l in eecpars[idname]]))
828
885
  for k in keys}
886
+
829
887
  # fill value with nan outside range
830
888
  self._losses = {k: ip.RegularGridInterpolator(
831
889
  (exc, beta, i1), lfe*np.array(pfe[k]),
@@ -834,12 +892,13 @@ class SynchronousMachineLdq(SynchronousMachine):
834
892
  self._set_losspar(eecpars[idname][0]['losses']['speed'],
835
893
  eecpars[idname][0]['losses']['ef'],
836
894
  eecpars[idname][0]['losses']['hf'])
837
- except ValueError:
895
+ except KeyError:
838
896
  logger.warning("loss map missing")
839
897
  self._losses = {k: lambda x: 0 for k in (
840
898
  'styoke_hyst', 'stteeth_hyst',
841
899
  'styoke_eddy', 'stteeth_eddy',
842
900
  'rotor_hyst', 'rotor_eddy')}
901
+ pass
843
902
 
844
903
  def psi(self, iq, id, iex):
845
904
  """return psid, psiq of currents iq, id"""
@@ -855,10 +914,22 @@ class SynchronousMachineLdq(SynchronousMachine):
855
914
  raise ex
856
915
 
857
916
  def plfe1(self, beta, i1, iex, f1):
917
+ losskeys = ['styoke_eddy', 'styoke_hyst',
918
+ 'stteeth_eddy', 'stteeth_hyst']
919
+ if self.bertotti:
920
+ losskeys += ['styoke_excess', 'stteeth_excess']
921
+ return np.sum([
922
+ self._losses[k]((iex, beta, i1))*(f1/self.fo)**self.plexp[k][0]
923
+ for k in losskeys], axis=0)
924
+
925
+ def plfe2(self, beta, i1, iex, f1):
926
+ losskeys = ['rotor_hyst', 'rotor_eddy']
927
+ if self.bertotti:
928
+ losskeys += ['rotor_excess']
858
929
  return np.sum([
859
930
  self._losses[k]((iex, beta, i1))*(f1/self.fo)**self.plexp[k][0]
860
- for k in ('styoke_eddy', 'styoke_hyst',
861
- 'stteeth_eddy', 'stteeth_hyst')], axis=0)
931
+ for k in losskeys], axis=0)
932
+
862
933
  def iqd_plfe1(self, iq, id, iex, f1):
863
934
  beta = np.arctan2(id, iq)
864
935
  if np.isscalar(beta):
@@ -869,6 +940,16 @@ class SynchronousMachineLdq(SynchronousMachine):
869
940
  i1 = np.linalg.norm((id, iq), axis=0)/np.sqrt(2.0)
870
941
  return self.plfe1(beta, i1, iex, f1)
871
942
 
943
+ def iqd_plfe2(self, iq, id, iex, f1):
944
+ beta = np.arctan2(id, iq)
945
+ if np.isscalar(beta):
946
+ if beta > 0:
947
+ beta -= 2*np.pi
948
+ else:
949
+ beta[beta > 0] -= 2*np.pi
950
+ i1 = np.linalg.norm((id, iq), axis=0)/np.sqrt(2.0)
951
+ return self.plfe2(beta, i1, iex, f1)
952
+
872
953
 
873
954
  if __name__ == '__main__':
874
955
  import sys
@@ -166,15 +166,15 @@ def wdg_leakage_inductances(machine):
166
166
  Juha Pyrhönen, Tapani Jokinen, Valeria Hrabovcova
167
167
  (Ed. 2008) page 236ff
168
168
  """
169
- from ..windings import Winding
170
- wdg = Winding(
169
+ wdgk = 'windings' if 'windings' in machine else 'winding'
170
+ wdg = windings.Winding(
171
171
  {'Q': machine['stator']['num_slots'],
172
- 'm': machine['windings']['num_phases'],
172
+ 'm': machine[wdgk]['num_phases'],
173
173
  'p': machine['poles']//2,
174
- 'l': machine['windings']['num_layers'],
175
- 'yd': machine['windings']['coil_span']})
176
- n1 = wdg.turns_per_phase(machine['windings']['num_wires'],
177
- machine['windings']['num_par_wdgs'])
174
+ 'l': machine[wdgk]['num_layers'],
175
+ 'yd': machine[wdgk]['coil_span']})
176
+ n1 = wdg.turns_per_phase(machine[wdgk]['num_wires'],
177
+ machine[wdgk]['num_par_wdgs'])
178
178
  m = wdg.m
179
179
  p = wdg.p
180
180
  Q = wdg.Q
@@ -218,6 +218,20 @@ def wdg_leakage_inductances(machine):
218
218
  return Lu, Lew
219
219
 
220
220
 
221
+ def create_wdg(machine):
222
+ """create winding from machine parameters"""
223
+ wdgk = 'windings' if 'windings' in machine else 'winding'
224
+ wpar = {'Q': machine['stator']['num_slots'],
225
+ 'm': machine[wdgk]['num_phases'],
226
+ 'p': machine['poles']//2}
227
+
228
+ if 'coil_span' in machine[wdgk]:
229
+ wpar['yd'] = machine[wdgk]['coil_span']
230
+ if 'num_layers' in machine[wdgk]:
231
+ wpar['l'] = machine[wdgk]['num_layers']
232
+ return windings.Winding(wpar)
233
+
234
+
221
235
  def betai1(iq, id):
222
236
  """return beta and amplitude of dq currents"""
223
237
  return (np.arctan2(id, iq),
@@ -352,18 +366,6 @@ def dqparident(workdir, engine, temp, machine,
352
366
  """
353
367
  import pathlib
354
368
 
355
- wdgk = 'windings' if 'windings' in machine else 'winding'
356
- def create_wdg(machine):
357
- wpar = {'Q': machine['stator']['num_slots'],
358
- 'm': machine[wdgk]['num_phases'],
359
- 'p': machine['poles']//2}
360
-
361
- if 'coil_span' in machine[wdgk]:
362
- wpar['yd'] = machine[wdgk]['coil_span']
363
- if 'num_layers' in machine[wdgk]:
364
- wpar['l'] = machine[wdgk]['num_layers']
365
-
366
- return windings.Winding(wpar)
367
369
  try:
368
370
  defspeed = 160/machine['poles']
369
371
  except KeyError:
@@ -372,6 +374,7 @@ def dqparident(workdir, engine, temp, machine,
372
374
  defspeed = kwargs['speed']
373
375
 
374
376
  lfe = machine['lfe']
377
+ wdgk = 'windings' if 'windings' in machine else 'winding'
375
378
  g = machine[wdgk].get('num_par_wdgs', 1)
376
379
  N = machine[wdgk]['num_wires']
377
380
  if 'cufilfact' in machine[wdgk]:
@@ -382,21 +385,22 @@ def dqparident(workdir, engine, temp, machine,
382
385
  fcu = 0.42
383
386
 
384
387
  try: # calc basic dimensions if not fsl or dxf model
385
- Q1 = machine['stator']['num_slots']
386
- da1 = machine['bore_diam']
388
+ wdg = create_wdg(machine)
389
+ Q1 = wdg.Q
387
390
  slotmodel = [k for k in machine['stator'] if isinstance(
388
391
  machine['stator'][k], dict)][-1]
389
392
  if slotmodel == 'stator1':
390
393
  hs = machine['stator']['stator1']['slot_rf1'] - \
391
394
  machine['stator']['stator1']['tip_rh1']
392
395
  else:
396
+ da1 = machine['bore_diam']
393
397
  dy1 = machine['outer_diam']
394
398
  hs = machine['stator'][slotmodel].get(
395
399
  'slot_height', 0.6*(dy1-da1)/2)
396
400
 
397
401
  Jmax = 30e6 # max current density in A/m2
398
- Acu = fcu*0.5*np.pi*(da1+hs)*hs
399
- i1_max = round(Acu/Q1/N*Jmax/10)*10
402
+ Acu = fcu*np.pi*(da1+hs)*hs/Q1/2 # approx. copper area of one slot
403
+ i1_max = round(g*Acu/wdg.l/N*Jmax/10)*10
400
404
  except KeyError:
401
405
  if kwargs.get('i1_max', 0) == 0:
402
406
  raise ValueError('i1_max missing')
@@ -404,14 +408,12 @@ def dqparident(workdir, engine, temp, machine,
404
408
 
405
409
  # winding resistance
406
410
  try:
407
- wdg = create_wdg(machine)
408
-
409
411
  if 'wire_gauge' in machine[wdgk]:
410
412
  aw = machine[wdgk]['wire_gauge']
411
413
  elif 'dia_wire' in machine[wdgk]:
412
414
  aw = np.pi*machine[wdgk].get('dia_wire', 1e-3)**2/4
413
415
  elif ('wire_width' in machine[wdgk]) and ('wire_height' in machine[wdgk]):
414
- aw = machine['windings']['wire_width']*machine[wdgk]['wire_height']
416
+ aw = machine[wdgk]['wire_width']*machine[wdgk]['wire_height']
415
417
  else: # wire diameter from slot area
416
418
  aw = 0.75 * fcu * np.pi*da1*hs/Q1/wdg.l/N
417
419
  r1 = wdg_resistance(wdg, N, g, aw, da1, hs, lfe)
@@ -456,12 +458,10 @@ def dqparident(workdir, engine, temp, machine,
456
458
  # TODO: cleanup() # remove previously created files in workdir
457
459
  # start calculation
458
460
  results = parvar(parvardef, machine, simulation, engine)
459
- if 'poles' not in machine:
461
+ if 'poles' not in machine: # dxf model?
460
462
  machine['poles'] = 2*results['f'][0]['machine']['p']
461
463
  da1 = 2*results['f'][0]['machine']['fc_radius']
462
- #import json
463
- #with open('results.json', 'w') as fp:
464
- # json.dump(results, fp)
464
+ wdg = create_wdg(machine)
465
465
  ls1 = 0
466
466
  try:
467
467
  leakages = [float(x)
@@ -522,13 +522,17 @@ def dqparident(workdir, engine, temp, machine,
522
522
  else:
523
523
  from .. import nc
524
524
  model = nc.read(str(pathlib.Path(workdir) / machine['name']))
525
- Q1 = model.num_slots
526
- #machine['stator']['num_slots'] = Q1
527
- wdg = create_wdg(machine)
525
+ try:
526
+ nlayers = wdg.l
527
+ except UnboundLocalError:
528
+ wdg = create_wdg(machine)
529
+ nlayers = wdg.l
530
+ da1 = machine['outer_diam']
531
+ Q1 = wdg.Q
528
532
  istat = 0 if model.get_areas()[0]['slots'] else 1
529
533
  asl = model.get_areas()[istat]['slots']
530
534
  # diameter of wires
531
- aw = fcu*asl/Q1/wdg.l/N
535
+ aw = fcu*asl/Q1/nlayers/N
532
536
  hs = asl/(np.pi*da1/3)
533
537
  dqpars['r1'] = wdg_resistance(wdg, N, g, aw, da1, hs, lfe)
534
538
 
femagtools/model.py CHANGED
@@ -65,7 +65,7 @@ class Model(object):
65
65
  """return value of key name
66
66
 
67
67
  Args:
68
- name: key of parameter value
68
+ name (str or list of str): key of parameter value
69
69
 
70
70
  Return:
71
71
  value of parameter identified by key
@@ -214,7 +214,7 @@ class MachineModel(Model):
214
214
  logging.debug(slotgen)
215
215
  g = np.gcd.reduce(slotgen)
216
216
  if hasattr(self, 'magnet') and g > 1:
217
- g /= m
217
+ g //= m
218
218
  if 'num_slots' in self.stator:
219
219
  Q1 = self.stator['num_slots']
220
220
  self.stator['num_slots_gen'] = Q1 // g
@@ -415,6 +415,16 @@ class MachineModel(Model):
415
415
  def has_magnet(self): # either rotor or magnet
416
416
  return hasattr(self, 'magnet')
417
417
 
418
+ def props(self):
419
+ """returns dict of this model"""
420
+ keys = ('name', 'poles', 'outer_diam', 'airgap',
421
+ 'bore_diam', 'external_rotor', 'agndst')
422
+ model = {k: getattr(self, k) for k in keys if hasattr(self, k)}
423
+ if hasattr(self, 'stator'):
424
+ model['stator'] = {k: self.stator[k]
425
+ for k in ('num_slots', 'num_slots_gen')
426
+ if k in self.stator}
427
+ return model
418
428
 
419
429
  class FeaModel(Model):
420
430
  """represents a simulation model for a FE analysis"""
@@ -1,5 +1,5 @@
1
1
  """
2
- Copyright (c) 2015 Semafor Informatik & Energie AG
2
+ Population
3
3
 
4
4
  """
5
5
  import sys
femagtools/parstudy.py CHANGED
@@ -188,6 +188,9 @@ class ParameterStudy(object):
188
188
  simulation.update(model.winding)
189
189
  if 'pocfilename' not in simulation:
190
190
  simulation['pocfilename'] = f"{model.name}.poc"
191
+ if simulation['calculationMode'] == 'psd_psq_fast':
192
+ simulation['pocfilename'] = f"{model.name}_{model.get('poles')}p.poc"
193
+
191
194
  fea = femagtools.model.FeaModel(simulation)
192
195
 
193
196
  prob = femagtools.moproblem.FemagMoProblem(decision_vars,
@@ -196,7 +199,20 @@ class ParameterStudy(object):
196
199
  if immutable_model:
197
200
  modelfiles = self.setup_model(builder, model, recsin=fea.recsin)
198
201
  logger.info("Files %s", modelfiles+extra_files)
199
-
202
+ logger.info("model %s", model.props())
203
+ for k in ('name', 'poles', 'outer_diam', 'airgap', 'bore_diam',
204
+ 'external_rotor'):
205
+ if k not in machine:
206
+ try:
207
+ machine[k] = model.get(k)
208
+ except:
209
+ pass
210
+ for k in ('num_slots', 'num_slots_gen'):
211
+ if k not in machine['stator']:
212
+ try:
213
+ machine['stator'][k] = model.stator[k]
214
+ except:
215
+ pass
200
216
  job = engine.create_job(self.femag.workdir)
201
217
  # for progress logger
202
218
  job.num_cur_steps = fea.get_num_cur_steps()
@@ -10,7 +10,7 @@ from .bch import torque, torque_fft, force, force_fft, \
10
10
  i1beta_psiq, i1beta_psim, i1beta_up, \
11
11
  idq_ld, idq_lq, idq_psid, idq_psim, idq_psiq, idq_torque
12
12
  from .char import mtpa, mtpv, characteristics, efficiency_map, losses_map
13
- from .forcedens import forcedens, forcedens_surface, forcedens_fft
13
+ from .forcedens import forcedens, forcedens_surface, forcedens_contour, forcedens_fft
14
14
  from .nc import spel, mesh, demag, demag_pos, \
15
15
  flux_density, flux_density_eccentricity, \
16
16
  airgap_flux_density_pos, loss_density