femagtools 1.8.2__py3-none-any.whl → 1.8.3__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 (40) hide show
  1. femagtools/__init__.py +1 -1
  2. femagtools/dxfsl/area.py +65 -0
  3. femagtools/dxfsl/conv.py +5 -0
  4. femagtools/dxfsl/converter.py +34 -1
  5. femagtools/dxfsl/functions.py +14 -6
  6. femagtools/dxfsl/geom.py +12 -12
  7. femagtools/dxfsl/journal.py +1 -1
  8. femagtools/dxfsl/symmetry.py +28 -8
  9. femagtools/femag.py +64 -61
  10. femagtools/fsl.py +5 -2
  11. femagtools/isa7.py +3 -2
  12. femagtools/machine/afpm.py +43 -23
  13. femagtools/machine/effloss.py +29 -18
  14. femagtools/machine/sizing.py +4 -3
  15. femagtools/machine/sm.py +34 -36
  16. femagtools/mcv.py +56 -26
  17. femagtools/multiproc.py +79 -80
  18. femagtools/parstudy.py +10 -4
  19. femagtools/semi_fea.py +108 -0
  20. femagtools/templates/basic_modpar.mako +0 -3
  21. femagtools/templates/fe-contr.mako +18 -18
  22. femagtools/templates/ld_lq_fast.mako +3 -0
  23. femagtools/templates/mult_cal_fast.mako +3 -0
  24. femagtools/templates/pm_sym_f_cur.mako +4 -1
  25. femagtools/templates/pm_sym_fast.mako +3 -0
  26. femagtools/templates/pm_sym_loss.mako +3 -0
  27. femagtools/templates/psd_psq_fast.mako +3 -0
  28. femagtools/templates/torq_calc.mako +3 -0
  29. femagtools/tks.py +23 -20
  30. femagtools/zmq.py +213 -0
  31. {femagtools-1.8.2.dist-info → femagtools-1.8.3.dist-info}/METADATA +3 -3
  32. {femagtools-1.8.2.dist-info → femagtools-1.8.3.dist-info}/RECORD +40 -38
  33. {femagtools-1.8.2.dist-info → femagtools-1.8.3.dist-info}/WHEEL +1 -1
  34. tests/test_afpm.py +15 -6
  35. tests/test_femag.py +1 -1
  36. tests/test_fsl.py +4 -4
  37. tests/test_mcv.py +20 -14
  38. {femagtools-1.8.2.dist-info → femagtools-1.8.3.dist-info}/LICENSE +0 -0
  39. {femagtools-1.8.2.dist-info → femagtools-1.8.3.dist-info}/entry_points.txt +0 -0
  40. {femagtools-1.8.2.dist-info → femagtools-1.8.3.dist-info}/top_level.txt +0 -0
@@ -33,16 +33,21 @@ def num_agnodes(Q, p, pw, ag):
33
33
  pw: (float) pole width
34
34
  ag: (float) airgap height
35
35
  """
36
- num_nodes = np.arange(12, 120, 6)
36
+ num_nodes = np.arange(6, 120, 6)
37
37
  i = np.argmin(np.abs(pw/num_nodes - ag/2))
38
- if p*num_nodes[i-1] % Q:
38
+ nag = num_nodes[i-1]
39
+ if p*nag % Q:
39
40
  lcm = np.lcm(Q, 2*p)//p
40
- nmin, nmax = num_nodes[0]//lcm, num_nodes[-1]//lcm
41
+ nmin, nmax = 1, num_nodes[-1]//lcm
41
42
  num_nodes = np.array(
42
43
  [i*lcm for i in range(nmin, nmax) if i*lcm % 6 == 0])
43
44
  i = np.argmin(np.abs(pw/num_nodes - ag/2))
44
- # nodedist 0.5, 2, 4, 6
45
- return num_nodes[i-1]
45
+ if i > 0:
46
+ nag = num_nodes[i-1]
47
+ else:
48
+ nag = num_nodes[i]
49
+ # TODO nodedist 0.5, 2, 4, 6
50
+ return nag
46
51
 
47
52
  def _integrate(radius, pos, val):
48
53
  interp = RegularGridInterpolator((radius, pos), val)
@@ -162,7 +167,6 @@ def parident(workdir, engine, temp, machine,
162
167
  for pw in pole_width:
163
168
  machine['num_agnodes'] = num_agnodes(Q1, p//2, pw,
164
169
  machine['airgap'])
165
-
166
170
  nlparvardef = {
167
171
  "decision_vars": [
168
172
  {"values": pole_width,
@@ -182,13 +186,13 @@ def parident(workdir, engine, temp, machine,
182
186
  parvardef = {
183
187
  "decision_vars": [
184
188
  # {"values": sorted(2*temp), "name": "magn_temp"},
185
- {"steps": num_beta_steps, "bounds": [beta_min, 0], "name": "angl_i_up"},
189
+ {"steps": num_beta_steps, "bounds": [beta_min, 0],
190
+ "name": "angl_i_up"},
186
191
  {"steps": num_cur_steps,
187
192
  "bounds": [i1_max/num_cur_steps, i1_max], "name": "current"}
188
193
  ]
189
194
  }
190
195
 
191
-
192
196
  ldq = []
193
197
  for magtemp in temp:
194
198
  nlcalc = dict(
@@ -381,7 +385,7 @@ def process(lfe, pole_width, machine, bch):
381
385
  num_slots = machine['stator']['num_slots']
382
386
  mmod = model.MachineModel(machine)
383
387
  slots_gen = mmod.stator['num_slots_gen']
384
- scale_factor =_get_scale_factor(model_type, num_slots, slots_gen)
388
+ scale_factor = _get_scale_factor(model_type, num_slots, slots_gen)
385
389
  endpos = [2*pw*1e3 for pw in pole_width]
386
390
  displ = [[d for d in r['linearForce'][0]['displ']
387
391
  if d < e*(1+1/len(r['linearForce'][0]['displ']))]
@@ -392,11 +396,14 @@ def process(lfe, pole_width, machine, bch):
392
396
  currents = [bch[0]['flux'][k][0]['current_k'][:n]
393
397
  for k in bch[0]['flux']]
394
398
  if len(pole_width) > 1:
399
+ # check homogenity:
400
+ if np.diff([len(d) for d in displ]).any():
401
+ raise ValueError(
402
+ f"inhomogenous number of steps: {[len(d) for d in displ]}")
403
+ lfx = [r['linearForce'][0]['force_x'] for r in bch]
395
404
  torque = _integrate(radius, rotpos[0], np.array(
396
405
  [r*scale_factor*np.array(fx[:-1])/l
397
- for l, r, fx in zip(lfe, radius,
398
- [r['linearForce'][0]['force_x']
399
- for r in bch])]))
406
+ for l, r, fx in zip(lfe, radius, lfx)]))
400
407
 
401
408
  voltage = {k: [scale_factor * np.array(ux[:-1])/l
402
409
  for l, ux in zip(lfe, [r['flux'][k][0]['voltage_dpsi']
@@ -404,6 +411,13 @@ def process(lfe, pole_width, machine, bch):
404
411
  for k in bch[0]['flux']}
405
412
  emf = [_integrate(radius, rotpos[0], np.array(voltage[k]))
406
413
  for k in voltage]
414
+
415
+ fluxxy = {k: [scale_factor * np.array(flx[:-1])/l
416
+ for l, flx in zip(lfe, [r['flux'][k][0]['flux_k']
417
+ for r in bch])]
418
+ for k in bch[0]['flux']}
419
+ flux = [_integrate(radius, rotpos[0], np.array(fluxxy[k]))
420
+ for k in fluxxy]
407
421
  else:
408
422
  r = radius[0]
409
423
  torque = [r*scale_factor*fx
@@ -412,6 +426,7 @@ def process(lfe, pole_width, machine, bch):
412
426
  for ux in bch[0]['flux'][k][0]['voltage_dpsi'][:-1]]
413
427
  for k in bch[0]['flux']}
414
428
  emf = [voltage[k][:n] for k in voltage]
429
+ flux = [fluxxy[k][:n] for k in fluxxy]
415
430
 
416
431
  pos = (rotpos[0]/np.pi*180)
417
432
  emffft = utils.fft(pos, emf[0])
@@ -474,6 +489,7 @@ def process(lfe, pole_width, machine, bch):
474
489
  'weights': weights.tolist(),
475
490
  'pos': pos.tolist(), 'r1': r1,
476
491
  'torque': torque,
492
+ 'flux_k': flux,
477
493
  'emf': emf,
478
494
  'emf_amp': emffft['a'], 'emf_angle': emffft['alfa0'],
479
495
  'freq': freq,
@@ -591,10 +607,10 @@ class AFPM:
591
607
  condMat: conductor material
592
608
  """
593
609
  def __init__(self, workdir, magnetizingCurves='.', magnetMat='',
594
- condMat=''):
610
+ condMat='', cmd=None):
595
611
  self.parstudy = parstudy.List(
596
612
  workdir, condMat=condMat, magnets=magnetMat,
597
- magnetizingCurves=magnetizingCurves)
613
+ magnetizingCurves=magnetizingCurves, cmd=cmd)
598
614
 
599
615
  def __call__(self, engine, machine, simulation, num_slices):
600
616
  """ run FE simulation
@@ -648,21 +664,24 @@ class AFPM:
648
664
  }
649
665
  machine['pole_width'] = np.pi * machine['inner_diam']/machine['poles']
650
666
  machine['lfe'] = machine['outer_diam'] - machine['inner_diam']
651
-
667
+ simulation['skew_displ'] = (simulation.get('skew_angle', 0)/180 * np.pi
668
+ * machine['inner_diam'])
652
669
  nlresults = {}
653
- if (simulation['calculationMode'] != 'cogg_calc' and
654
- 'poc' not in simulation):
670
+ if (simulation['calculationMode'] != 'cogg_calc'
671
+ and 'poc' not in simulation):
655
672
  nlcalc = dict(
656
673
  calculationMode="cogg_calc",
657
674
  magn_temp=simulation.get('magn_temp', 20),
658
675
  num_move_steps=60,
676
+ skew_linear=0,
677
+ skew_steps=0,
659
678
  poc=poc.Poc(machine['pole_width']),
660
679
  speed=0)
661
680
  logging.info("Noload simulation")
662
681
  nlresults = self.parstudy(parvardef,
663
682
  machine, nlcalc, engine)
664
683
  if nlresults['status'].count('C') != len(nlresults['status']):
665
- raise ValueError('Noload simulation failed %s', nlresults['status'])
684
+ raise ValueError(f'Noload simulation failed {nlresults["status"]}')
666
685
  nlresults.update(process(lfe, pole_width, machine, nlresults['f']))
667
686
 
668
687
  current_angles = nlresults['f'][0]['current_angles']
@@ -671,14 +690,15 @@ class AFPM:
671
690
  parameters={
672
691
  'phi_voltage_winding': current_angles})
673
692
  logger.info("Current angles: %s", current_angles)
674
- elif (simulation['calculationMode'] == 'cogg_calc' and
675
- 'poc' not in simulation):
693
+ elif (simulation['calculationMode'] == 'cogg_calc'
694
+ and 'poc' not in simulation):
676
695
  simulation['poc'] = poc.Poc(machine['pole_width'])
677
696
 
678
697
  lresults = self.parstudy(
679
- parvardef,
680
- {k: machine[k] for k in machine if k!= 'afm_rotor'},
681
- simulation, engine) # Note: imcomplete machine prevents rebuild
698
+ parvardef, machine, simulation, engine)
699
+ if lresults['status'].count('C') != len(lresults['status']):
700
+ raise ValueError(
701
+ f'{simulation["calculationMode"]} failed {lresults["status"]}')
682
702
 
683
703
  results = process(lfe, pole_width, machine, lresults['f'])
684
704
  if nlresults:
@@ -39,7 +39,7 @@ def iqd_tmech_umax(m, u1, with_mtpa, progress, speed_torque, iq, id, iex):
39
39
  finally:
40
40
  progress.close()
41
41
 
42
- def iqd_tmech_umax_multi(num_proc, ntmesh, m, u1, with_mtpa):
42
+ def iqd_tmech_umax_multi(num_proc, ntmesh, m, u1, with_mtpa, publish=0):
43
43
  """calculate iqd for sm and pm using multiproc
44
44
  """
45
45
  progress_readers = []
@@ -79,6 +79,16 @@ def iqd_tmech_umax_multi(num_proc, ntmesh, m, u1, with_mtpa):
79
79
  progress_readers.remove(r)
80
80
  else:
81
81
  if i % len(progress_readers) == 0:
82
+ if publish:
83
+ numTot = len(collected_msg)
84
+ numOf = f"{num_proc-numTot} of {num_proc}"
85
+ workdone = sum([float(i[:-1])
86
+ for i in collected_msg]) / numTot
87
+ logger.debug("numTot %d numOf %s workdone %s, %s",
88
+ numTot, numOf, workdone, collected_msg)
89
+ publish(('progress_logger',
90
+ f"{numTot}:{numOf}:{workdone}:"))
91
+ # {' '.join(collected_msg)}"))
82
92
  logger.info("Losses/Eff Map: %s",
83
93
  ', '.join(collected_msg))
84
94
  collected_msg = []
@@ -197,7 +207,7 @@ def efficiency_losses_map(eecpars, u1, T, temp, n, npoints=(60, 40),
197
207
  with_mtpa -- (optional) use mtpa if True (default), disables mtpv if False
198
208
  with_tmech -- (optional) use friction and windage losses (default)
199
209
  num_proc -- (optional) number of parallel processes (default 0)
200
- progress -- (optional) custom function for progress logging
210
+ progress -- (optional) custom function for progress logging (publishing)
201
211
  with_torque_corr -- (optional) T is corrected if out of range (default False)
202
212
 
203
213
  Returns:
@@ -250,41 +260,42 @@ def efficiency_losses_map(eecpars, u1, T, temp, n, npoints=(60, 40),
250
260
  logger.info("total speed,torque samples %d", ntmesh.shape[1])
251
261
  if isinstance(m, (PmRelMachine, SynchronousMachine)):
252
262
  if num_proc > 1:
253
- iqd = iqd_tmech_umax_multi(num_proc, ntmesh, m, u1, with_mtpa)
263
+ iqd = iqd_tmech_umax_multi(num_proc, ntmesh, m, u1, with_mtpa,
264
+ publish=progress)
254
265
  else:
255
266
  class ProgressLogger:
256
- def __init__(self, nsamples):
267
+ def __init__(self, nsamples, publish):
257
268
  self.n = 0
269
+ self.publish = publish
258
270
  self.nsamples = nsamples
259
271
  self.num_iv = round(nsamples/15)
260
272
  def __call__(self, iqd):
261
273
  self.n += 1
262
274
  if self.n % self.num_iv == 0:
263
- logger.info("Losses/Eff Map: %d%%",
264
- round(100*self.n/self.nsamples))
265
- if progress is None:
266
- progress = ProgressLogger(ntmesh.shape[1])
267
- else:
268
- try:
269
- progress.nsamples=ntmesh.shape[1]
270
- progress(0) # To check conformity
271
- progress.n = 0
272
- except:
273
- logger.warning("Invalid ProgressLogger given to efficiency_losses_map, using default one!")
274
- progress = ProgressLogger(ntmesh.shape[1])
275
+ workdone=round(100*self.n/self.nsamples)
276
+ if self.publish:
277
+ self.publish(
278
+ ('progress_logger',
279
+ f"{self.n}:{self.n} of {self.nsamples}:{workdone}"))
280
+ logger.info("Losses/Eff Map: %d%%", workdone)
281
+
282
+ progress_logger = ProgressLogger(ntmesh.shape[1], progress)
283
+ progress_logger.nsamples = ntmesh.shape[1]
284
+ progress_logger(0) # To check conformity
285
+ progress_logger.n = 0
275
286
  if with_tmech:
276
287
  iqd = np.array([
277
288
  m.iqd_tmech_umax(
278
289
  nt[1],
279
290
  2*np.pi*nt[0]*m.p,
280
- u1, log=progress, with_mtpa=with_mtpa)[:-1]
291
+ u1, log=progress_logger, with_mtpa=with_mtpa)[:-1]
281
292
  for nt in ntmesh.T]).T
282
293
  else:
283
294
  iqd = np.array([
284
295
  m.iqd_torque_umax(
285
296
  nt[1],
286
297
  2*np.pi*nt[0]*m.p,
287
- u1, log=progress, with_mtpa=with_mtpa)[:-1]
298
+ u1, log=progress_logger, with_mtpa=with_mtpa)[:-1]
288
299
  for nt in ntmesh.T]).T
289
300
 
290
301
  beta, i1 = betai1(iqd[0], iqd[1])
@@ -628,8 +628,9 @@ def get_sm_rotor_dimensions(A, psi1, lfe, Da2, par):
628
628
  mue0 = 4*np.pi*1e-7
629
629
 
630
630
  wf = wp - wc
631
- anr = par['airgap']*par['Ba']/mue0/par['J']/par['kqr']
632
-
631
+ NI = par['airgap']*par['Ba']/mue0
632
+ anr = NI/par['J']/par['kqr']
633
+ r['NI'] = NI
633
634
  a = np.tan(alphap/2)/2
634
635
  b = (wp/2 - wc + np.tan(alphap/2)*(Da2/2 - hp))/2
635
636
  hc = min(max(4*anr/(wc+wp), 0.75*hfmax), hfmax)
@@ -827,7 +828,7 @@ def afpm(pnom: float, speed: float, p: int, afmtype: str, **kwargs) -> dict:
827
828
  bns = taus*(Di+2*hs1) - bds
828
829
 
829
830
  hns = (-bns + np.sqrt(bns**2 + 4*ans*np.tan(taus)))/2/np.tan(taus)/kps
830
- hys = psi1/lfe/par['By']/kps
831
+ hys = psi1/2/lfe/par['By']/kps
831
832
 
832
833
  aw = ans * par['kq'] / layers / num_wires * kps
833
834
 
femagtools/machine/sm.py CHANGED
@@ -434,9 +434,9 @@ class SynchronousMachine(object):
434
434
  def iqd_tmech(self, torque, n, disp=False, maxiter=500):
435
435
  """return currents for shaft torque with minimal losses"""
436
436
  if torque > 0:
437
- startvals = self.bounds[0][1]/2, 0, sum(self.bounds[-1])/2
437
+ startvals = self.bounds[0][1], 0, self.bounds[-1][1]
438
438
  else:
439
- startvals = -self.bounds[0][1]/2, 0, sum(self.bounds[-1])/2
439
+ startvals = -self.bounds[0][1]/2, 0, self.bounds[-1][1]
440
440
 
441
441
  with warnings.catch_warnings():
442
442
  warnings.simplefilter("ignore")
@@ -463,9 +463,9 @@ class SynchronousMachine(object):
463
463
  def iqd_torque(self, torque, disp=False, maxiter=500):
464
464
  """return currents for torque with minimal losses"""
465
465
  if torque > 0:
466
- startvals = self.bounds[0][1]/2, 0, sum(self.bounds[-1])/2
466
+ startvals = self.bounds[0][1]/2, 0, self.bounds[-1][1]
467
467
  else:
468
- startvals = -self.bounds[0][1]/2, 0, sum(self.bounds[-1])/2
468
+ startvals = -self.bounds[0][1]/2, 0, self.bounds[-1][1]
469
469
 
470
470
  with warnings.catch_warnings():
471
471
  warnings.simplefilter("ignore")
@@ -483,14 +483,14 @@ class SynchronousMachine(object):
483
483
  #options={'disp': disp, 'maxiter': maxiter})
484
484
  if res['success']:
485
485
  return res.x
486
- logger.warning("%s: torque=%f %f, io=%s",
487
- res['message'], torque, self.torque_iqd(*startvals),
488
- startvals)
486
+ logger.warning("%s: torque=%f %f, io=%s",
487
+ res['message'], torque, self.torque_iqd(*startvals),
488
+ startvals)
489
489
  raise ValueError(res['message'])
490
490
 
491
491
  def mtpa(self, i1max):
492
492
  """return iq, id, iex currents and maximum torque per current """
493
- T0 = self.torque_iqd(np.sqrt(2)*i1max/2, 0, self.bounds[-1][1]/2)
493
+ T0 = self.torque_iqd(np.sqrt(2)*i1max, 0, self.bounds[-1][1])
494
494
  def i1tq(tq):
495
495
  return abs(i1max) - np.linalg.norm(self.iqd_torque(tq)[:2])/np.sqrt(2)
496
496
  with warnings.catch_warnings():
@@ -501,7 +501,7 @@ class SynchronousMachine(object):
501
501
 
502
502
  def mtpa_tmech(self, i1max, n):
503
503
  """return iq, id, iex currents and maximum torque per current """
504
- T0 = self.torque_iqd(np.sqrt(2)*i1max/2, 0, self.bounds[-1][0])/2
504
+ T0 = self.torque_iqd(np.sqrt(2)*i1max, 0, self.bounds[-1][1])
505
505
  def i1tq(tq):
506
506
  return i1max - np.linalg.norm(self.iqd_tmech(tq, n)[:2])/np.sqrt(2)
507
507
  tq = so.fsolve(i1tq, T0)[0]
@@ -517,14 +517,11 @@ class SynchronousMachine(object):
517
517
  if log:
518
518
  log(iqde)
519
519
  return (*iqde, torque)
520
- beta, i1 = betai1(iqde[0], iqde[1])
521
- iex = iqde[2]
520
+ #beta, i1 = betai1(iqde[0], iqde[1])
521
+ #iex = iqde[2]
522
522
 
523
- def ubeta(b):
524
- return np.sqrt(2)*u1max - np.linalg.norm(
525
- self.uqd(w1, *iqd(b, i1), iex))
526
- beta = -np.pi/4 if torque>0 else -3*np.pi/4
527
- io = *iqd(beta, i1), iex
523
+ #beta = 0 if torque>0 else np.pi
524
+ io = iqde[0], 0, iqde[2] #*iqd(beta, i1), iex
528
525
 
529
526
  # logger.debug("--- torque %g io %s", torque, io)
530
527
  with warnings.catch_warnings():
@@ -540,17 +537,18 @@ class SynchronousMachine(object):
540
537
  bounds=self.bounds,
541
538
  constraints=[
542
539
  {'type': 'eq',
543
- 'fun': lambda iqd: self.tmech_iqd(*iqd, n) - torque},
540
+ 'fun': lambda iqd: torque - self.tmech_iqd(*iqd, n)},
544
541
  {'type': 'eq',
545
- 'fun': lambda iqd: np.linalg.norm(
546
- self.uqd(w1, *iqd)) - u1max*np.sqrt(2)}])
542
+ 'fun': lambda iqd: u1max*np.sqrt(2)
543
+ - np.linalg.norm(self.uqd(w1, *iqd))}])
547
544
  #if res['success']:
548
- if log:
549
- log(res.x)
550
- return *res.x, self.tmech_iqd(*res.x, n)
551
- #logger.warning("%s: w1=%f torque=%f, u1max=%f, io=%s",
552
- # res['message'], w1, torque, u1max, io)
553
- #raise ValueError(res['message'])
545
+ if log:
546
+ log(res.x)
547
+ return *res.x, self.tmech_iqd(*res.x, n)
548
+ #logger.warning("%s: w1=%f torque=%f, u1max=%f, io=%s",
549
+ # res['message'], w1, torque, u1max, io)
550
+ #raise ValueError(res['message'])
551
+ #return [float('nan')]*4
554
552
 
555
553
  def iqd_torque_umax(self, torque, w1, u1max,
556
554
  disp=False, maxiter=500, log=0, **kwargs):
@@ -579,16 +577,15 @@ class SynchronousMachine(object):
579
577
  {'type': 'eq',
580
578
  'fun': lambda iqd: self.torque_iqd(*iqd) - torque},
581
579
  {'type': 'eq',
582
- 'fun': lambda iqd: np.linalg.norm(
583
- self.uqd(w1, *iqd)) - u1max*np.sqrt(2)}])
584
- if res['success']:
585
-
586
- if log:
587
- log(res.x)
588
- return *res.x, self.torque_iqd(*res.x)
589
- logger.warning("%s: w1=%f torque=%f, u1max=%f, io=%s",
590
- res['message'], w1, torque, u1max, io)
591
- raise ValueError(res['message'])
580
+ 'fun': lambda iqd: u1max*np.sqrt(2) - np.linalg.norm(
581
+ self.uqd(w1, *iqd))}])
582
+ #if res['success']:
583
+ if log:
584
+ log(res.x)
585
+ return *res.x, self.torque_iqd(*res.x)
586
+ #logger.warning("%s: w1=%f torque=%f, u1max=%f, io=%s",
587
+ # res['message'], w1, torque, u1max, io)
588
+ #raise ValueError(res['message'])
592
589
 
593
590
  def w1_imax_umax(self, i1max, u1max):
594
591
  """return frequency w1 and shaft torque at voltage u1max and current i1max
@@ -672,9 +669,10 @@ class SynchronousMachine(object):
672
669
 
673
670
  wmtab = []
674
671
  dw = 0
675
- wmMax = 3.5*wmType
672
+ wmMax = 5*wmType
676
673
  if n > 0:
677
674
  wmMax = min(wmMax, 2*np.pi*n)
675
+
678
676
  if wmType > wmMax:
679
677
  wmrange = sorted([0, wmMax])
680
678
  wmtab = np.linspace(0, wmMax, nsamples).tolist()
femagtools/mcv.py CHANGED
@@ -8,6 +8,7 @@ import sys
8
8
  import copy
9
9
  import logging
10
10
  import os.path
11
+ import pathlib
11
12
  import struct
12
13
  import math
13
14
  import numpy as np
@@ -292,7 +293,12 @@ class Mcv(object):
292
293
  self.MC1_CW_FREQ_FACTOR = 0.0
293
294
  self.MC1_INDUCTION_FACTOR = 0.0
294
295
  self.MC1_INDUCTION_BETA_FACTOR = 0.0
295
-
296
+ self.jordan = {}
297
+ # {'ch': 0, 'cw': 0, 'ch_freq':0, 'cw_freq':0}
298
+ self.steinmetz = {}
299
+ # {'ch': 0, 'cw': 0, 'ch_freq':0, 'cw_freq':0}
300
+ self.bertotti = {}
301
+ # {'ch': 0, 'cw': 0, 'ce':0, 'ch_freq':0, 'cw_freq':0}
296
302
  self.MC1_FE_SPEZ_WEIGTH = 7.65
297
303
  self.MC1_FE_SAT_MAGNETIZATION = 2.15
298
304
 
@@ -354,6 +360,9 @@ class Mcv(object):
354
360
  for k in wtrans:
355
361
  if wtrans[k] in data.keys():
356
362
  self.__setattr__(k, data[wtrans[k]])
363
+ for k in ('bertotti', 'jordan', 'steinmetz'):
364
+ if k in data:
365
+ self.__setattr__(k, data[k])
357
366
  self.curve = data['curve']
358
367
  try:
359
368
  self.mc1_angle = [c['angle'] for c in data['curve']]
@@ -363,6 +372,9 @@ class Mcv(object):
363
372
  self.losses = data['losses']
364
373
  except Exception:
365
374
  pass
375
+ # assume jordan iron loss parameters
376
+ for k in self.jordan:
377
+ self.jordan[k] = getattr(self, transl[k])
366
378
  return
367
379
 
368
380
  def rtrimValueList(self, vlist):
@@ -470,13 +482,25 @@ class Writer(Mcv):
470
482
  for c in curve]
471
483
  return curve
472
484
 
473
- def writeBinaryFile(self, fillfac=None, recsin=''):
485
+ def writeBinaryFile(self, fillfac=None, recsin='', feloss=''):
474
486
  """write binary file after conversion if requested.
475
487
  arguments:
476
488
  fillfac: (float) fill actor
477
489
  recsin: (str) either 'flux' or 'cur'
490
+ feloss: (str) iron loss method (bertotti, jordan)
478
491
  """
479
492
  curve = self._prepare(fillfac, recsin)
493
+ try:
494
+ if feloss.lower() == 'bertotti':
495
+ for k in self.bertotti:
496
+ setattr(self, transl[k], self.bertotti[k])
497
+ del self.losses
498
+ else:
499
+ for k in self.jordan:
500
+ setattr(self, transl[k], self.jordan[k])
501
+ except AttributeError as e:
502
+ logger.warning("%s", e)
503
+ pass
480
504
  mc1_type = self.mc1_type
481
505
  mc1_recalc = self.mc1_recalc
482
506
  mc1_fillfac = self.mc1_fillfac
@@ -556,6 +580,7 @@ class Writer(Mcv):
556
580
 
557
581
  try:
558
582
  if not (self.mc1_ch_factor or self.mc1_cw_factor) and self.losses:
583
+ # fit loss parameters
559
584
  pfe = self.losses['pfe']
560
585
  f = self.losses['f']
561
586
  B = self.losses['B']
@@ -598,7 +623,8 @@ class Writer(Mcv):
598
623
  return
599
624
 
600
625
  try:
601
- nfreq = len([1 for x in self.losses['f'] if x > 0])
626
+ freq = [x for x in self.losses['f'] if x > 0]
627
+ nfreq = len(freq)
602
628
  nind = len(self.losses['B'])
603
629
  if nind < 1 or nfreq < 1:
604
630
  return
@@ -631,26 +657,28 @@ class Writer(Mcv):
631
657
  self.writeBlock([nfreq, nind])
632
658
  self.writeBlock([float(b) for b in B] + [0.0]*(M_LOSS_INDUCT - nind))
633
659
 
660
+ nrec = 0
634
661
  for f, p in zip(self.losses['f'], pfe):
635
662
  if f > 0:
636
663
  y = np.array(p)
637
664
  losses = [float(x) for x in y[y != np.array(None)]]
638
- if len(losses) == nind:
639
- pl = p
665
+ nloss = len(losses)
666
+ if nloss == nind:
667
+ pl = list(p)
640
668
  else:
641
- n = len(losses)
642
669
  cw, alfa, beta = lc.fitsteinmetz(
643
- f, B[:n], losses, Bo, fo)
670
+ f, B[:nloss], losses, Bo, fo)
644
671
  pl = losses + [lc.pfe_steinmetz(
645
672
  f, b, cw, alfa, beta,
646
673
  self.losses['fo'],
647
674
  self.losses['Bo'])
648
- for b in B[n:]]
675
+ for b in B[nloss:]]
649
676
  logger.debug("%s", pl)
650
677
  self.writeBlock(pl +
651
678
  [0.0]*(M_LOSS_INDUCT - len(pl)))
652
679
  self.writeBlock(float(f))
653
- for m in range(M_LOSS_FREQ - len(pfe)):
680
+ nrec += 1
681
+ for m in range(M_LOSS_FREQ - nrec):
654
682
  self.writeBlock([0.0]*M_LOSS_INDUCT)
655
683
  self.writeBlock(0.0)
656
684
 
@@ -662,21 +690,21 @@ class Writer(Mcv):
662
690
  except Exception as e:
663
691
  logger.error("Exception %s", e, exc_info=True)
664
692
 
665
- def writeMcv(self, filename, fillfac=None, recsin=''):
693
+ def writeMcv(self, filename, fillfac=None, recsin='', feloss='Jordan'):
666
694
  # windows needs this strip to remove '\r'
667
- filename = filename.strip()
668
- self.name = os.path.splitext(filename)[0]
695
+ filename = pathlib.Path(filename)
696
+ self.name = filename.stem
669
697
 
670
- if filename.upper().endswith('.MCV') or \
671
- filename.upper().endswith('.MC'):
698
+ if filename.suffix.upper() in ('.MCV', '.MC'):
672
699
  binary = True
673
- self.fp = open(filename, "wb")
700
+ self.fp = filename.open(mode="wb")
674
701
  else:
675
702
  binary = False
676
- self.fp = open(filename, "wb")
677
- logger.info("Write File %s, binary format", filename)
703
+ self.fp = filename.open(mode="w")
704
+ logger.info("Write File %s, binary format (feloss '%s')",
705
+ filename, feloss)
678
706
 
679
- self.writeBinaryFile(fillfac, recsin)
707
+ self.writeBinaryFile(fillfac, recsin, feloss)
680
708
  self.fp.close()
681
709
 
682
710
 
@@ -737,17 +765,16 @@ class Reader(Mcv):
737
765
 
738
766
  def readMcv(self, filename):
739
767
  # intens bug : windows needs this strip to remove '\r'
740
- filename = filename.strip()
768
+ filename = pathlib.Path(filename)
741
769
 
742
- if filename.upper().endswith('.MCV') or \
743
- filename.upper().endswith('.MC'):
770
+ if filename.suffix in ('.MCV', '.MC'):
744
771
  binary = True
745
- self.fp = open(filename, "rb")
772
+ self.fp = filename.open(mode="rb")
746
773
  else:
747
774
  binary = False
748
- self.fp = open(filename, "r")
775
+ self.fp = filename.open(mode="r")
749
776
 
750
- self.name = os.path.splitext(os.path.basename(filename))[0]
777
+ self.name = filename.stem
751
778
  # read curve version (INTEGER)
752
779
  if binary:
753
780
  self.version_mc_curve = self.readBlock(int)
@@ -903,6 +930,8 @@ class Reader(Mcv):
903
930
  if self.MC1_INDUCTION_FACTOR > 2.0:
904
931
  self.MC1_INDUCTION_FACTOR = 2.0
905
932
 
933
+ # TODO: handle self.mc1_ce_factor, self.mc1_induction_beta_factor
934
+
906
935
  self.losses = {}
907
936
  try:
908
937
  (nfreq, njind) = self.readBlock([int, int])
@@ -1085,13 +1114,14 @@ class MagnetizingCurve(object):
1085
1114
  repls.items(), name)
1086
1115
 
1087
1116
  def writefile(self, name, directory='.',
1088
- fillfac=None, recsin=''):
1117
+ fillfac=None, recsin='', feloss='jordan'):
1089
1118
  """find magnetic curve by name or id and write binary file
1090
1119
  Arguments:
1091
1120
  name: key of mcv dict (name or id)
1092
1121
  directory: destination directory (must be writable)
1093
1122
  fillfac: (float) new fill factor (curves will be recalulated if not None or 0)
1094
1123
  recsin: (str) either 'flux' or 'cur' recalculates for eddy current calculation (dynamic simulation)
1124
+ feloss: (str) iron loss calc method ('jordan', 'bertotti', 'steinmetz')
1095
1125
 
1096
1126
  returns filename if found else None
1097
1127
  """
@@ -1125,7 +1155,7 @@ class MagnetizingCurve(object):
1125
1155
  filename = ''.join((bname, ext))
1126
1156
  writer = Writer(mcv)
1127
1157
  writer.writeMcv(os.path.join(directory, filename),
1128
- fillfac=fillfac, recsin=recsin)
1158
+ fillfac=fillfac, recsin=recsin, feloss=feloss)
1129
1159
  return filename
1130
1160
 
1131
1161
  def fitLossCoeffs(self):