femagtools 1.8.2__py3-none-any.whl → 1.8.3__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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):