femagtools 1.8.1__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 (49) hide show
  1. femagtools/__init__.py +1 -1
  2. femagtools/dxfsl/area.py +110 -1
  3. femagtools/dxfsl/areabuilder.py +93 -45
  4. femagtools/dxfsl/conv.py +5 -0
  5. femagtools/dxfsl/converter.py +85 -27
  6. femagtools/dxfsl/fslrenderer.py +5 -4
  7. femagtools/dxfsl/functions.py +14 -6
  8. femagtools/dxfsl/geom.py +135 -149
  9. femagtools/dxfsl/journal.py +1 -1
  10. femagtools/dxfsl/machine.py +161 -9
  11. femagtools/dxfsl/shape.py +46 -1
  12. femagtools/dxfsl/svgparser.py +1 -1
  13. femagtools/dxfsl/symmetry.py +143 -38
  14. femagtools/femag.py +64 -61
  15. femagtools/fsl.py +15 -12
  16. femagtools/isa7.py +3 -2
  17. femagtools/machine/__init__.py +5 -4
  18. femagtools/machine/afpm.py +79 -33
  19. femagtools/machine/effloss.py +29 -18
  20. femagtools/machine/sizing.py +192 -13
  21. femagtools/machine/sm.py +34 -36
  22. femagtools/machine/utils.py +2 -2
  23. femagtools/mcv.py +58 -29
  24. femagtools/model.py +4 -3
  25. femagtools/multiproc.py +79 -80
  26. femagtools/parstudy.py +11 -5
  27. femagtools/plot/nc.py +2 -2
  28. femagtools/semi_fea.py +108 -0
  29. femagtools/templates/basic_modpar.mako +0 -3
  30. femagtools/templates/fe-contr.mako +18 -18
  31. femagtools/templates/ld_lq_fast.mako +3 -0
  32. femagtools/templates/mult_cal_fast.mako +3 -0
  33. femagtools/templates/pm_sym_f_cur.mako +4 -1
  34. femagtools/templates/pm_sym_fast.mako +3 -0
  35. femagtools/templates/pm_sym_loss.mako +3 -0
  36. femagtools/templates/psd_psq_fast.mako +3 -0
  37. femagtools/templates/torq_calc.mako +3 -0
  38. femagtools/tks.py +23 -20
  39. femagtools/zmq.py +213 -0
  40. {femagtools-1.8.1.dist-info → femagtools-1.8.3.dist-info}/METADATA +3 -3
  41. {femagtools-1.8.1.dist-info → femagtools-1.8.3.dist-info}/RECORD +49 -47
  42. {femagtools-1.8.1.dist-info → femagtools-1.8.3.dist-info}/WHEEL +1 -1
  43. tests/test_afpm.py +15 -6
  44. tests/test_femag.py +1 -1
  45. tests/test_fsl.py +4 -4
  46. tests/test_mcv.py +21 -15
  47. {femagtools-1.8.1.dist-info → femagtools-1.8.3.dist-info}/LICENSE +0 -0
  48. {femagtools-1.8.1.dist-info → femagtools-1.8.3.dist-info}/entry_points.txt +0 -0
  49. {femagtools-1.8.1.dist-info → femagtools-1.8.3.dist-info}/top_level.txt +0 -0
@@ -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])
@@ -1,6 +1,6 @@
1
1
  """general design of an AC electrical machine
2
2
 
3
- Types: spm, ipm, eesm, im
3
+ Types: spm, ipm, eesm, im, afpm
4
4
 
5
5
  """
6
6
  import numpy as np
@@ -10,6 +10,25 @@ from .utils import wdg_resistance
10
10
 
11
11
  logger = logging.getLogger("femagools.machine.sizing")
12
12
 
13
+ AFPM_DEFAULTS = dict(
14
+ airgap=2e-3,
15
+ kd=0.7, # ratio of inner vs outer diameter 0.6 .. 0.8
16
+ eta=0.92, # efficiency
17
+ cos_phi=0.95, # power factor
18
+ m=3, # number of phases
19
+ ui_u=0.8, # U ind / U
20
+ J=8.5e6, # current density A/mm²
21
+ sigmas=40e3, # shear force 30 .. 65 kN/m2
22
+ Ba=0.7, # flux density in airgap
23
+ Bth=1.5, # flux density in teeth 1.5 .. 2 T
24
+ By=1.2, # flux density in yoke 1.2 .. 1.5 T
25
+ kq=0.6, # stator winding fill factor 0.35 .. 0.75
26
+ mag_width=0.95, # rel magnet width 0.6 .. 1
27
+ Hc=700, # max. coercitive field strength, 500 .. 900 kA/m
28
+ brem=1.2, # remanence 0.3 .. 1.3 T
29
+ demag=6 # safety factor for demagnetisation (nom current)
30
+ )
31
+ """default sizing parameters for AFPM"""
13
32
 
14
33
  PM_DEFAULTS = dict(
15
34
  airgap=1.5e-3, # airgap width m
@@ -217,7 +236,7 @@ def _stator_slots(par, slots):
217
236
  # check sim factor, height/width ratio
218
237
  if qhbmin[1] == len(q)-1:
219
238
  # last has smallest sim factor
220
- return q[qhbmin[1]][0]
239
+ return q[qhbmin[1]][0]
221
240
  elif q[qhbmin[1]][2] < q[qhbmin[1]+1][2]:
222
241
  # select ideal height/width ratio (3.2)
223
242
  return q[qhbmin[1]][0]
@@ -270,7 +289,7 @@ def get_stator_dimensions(par, slots=[]):
270
289
  Ui_U = par['ui_u']
271
290
 
272
291
  # design parameters
273
- # AJ = papyr['AJ'] # 100*1e9 # thermal load, typically 100 .. 300 A²/mm³
292
+ # AJ = par['AJ'] # 100*1e9 # thermal load, typically 100 .. 300 A²/mm³
274
293
  J = par['J'] # current density 3 .. 6 A/mm²
275
294
  Ba = par['Ba'] # airgap flux density, typically 0.6 .. 1.5 T
276
295
  kq = par['kq'] # winding fill factor, typically 0.35 .. 0.75
@@ -431,15 +450,13 @@ def get_stator_dimensions(par, slots=[]):
431
450
 
432
451
  return r
433
452
 
434
-
435
453
  def _get_magnet_height(I1, N, kw, par):
436
454
  airgap = par['airgap']
437
455
  Ba = par['Ba']
438
456
  p = par['p']
439
457
  m = par['m']
440
458
 
441
- Hc = par['Hc']
442
- Hc = Hc*1e3 # unit kA/m
459
+ Hc = par['Hc']*1e3 # unit kA/m -> A/m
443
460
  # Safety Factor for demagnetization
444
461
  demag = par['demag']
445
462
  THETA1 = m/np.pi*np.sqrt(2)*I1*N*kw/p
@@ -611,8 +628,9 @@ def get_sm_rotor_dimensions(A, psi1, lfe, Da2, par):
611
628
  mue0 = 4*np.pi*1e-7
612
629
 
613
630
  wf = wp - wc
614
- anr = par['airgap']*par['Ba']/mue0/par['J']/par['kqr']
615
-
631
+ NI = par['airgap']*par['Ba']/mue0
632
+ anr = NI/par['J']/par['kqr']
633
+ r['NI'] = NI
616
634
  a = np.tan(alphap/2)/2
617
635
  b = (wp/2 - wc + np.tan(alphap/2)*(Da2/2 - hp))/2
618
636
  hc = min(max(4*anr/(wc+wp), 0.75*hfmax), hfmax)
@@ -648,6 +666,10 @@ def _set_defaults(par, defaults):
648
666
  par[k] = defaults[k]
649
667
 
650
668
 
669
+ def _set_afpm_defaults(par):
670
+ _set_defaults(par, AFPM_DEFAULTS)
671
+
672
+
651
673
  def _set_pm_defaults(par):
652
674
  _set_defaults(par, PM_DEFAULTS)
653
675
 
@@ -708,6 +730,163 @@ def spm(pnom: float, speed: float, p: int, **kwargs) -> dict:
708
730
  return r
709
731
 
710
732
 
733
+ def afpm(pnom: float, speed: float, p: int, afmtype: str, **kwargs) -> dict:
734
+ """returns dimension of a AFPM machine
735
+
736
+ Args:
737
+ pnom: power at rated speed (W)
738
+ speed: rotation speed (1/s)
739
+ p: number of pole pairs
740
+ afmtype: one of 'S1R1', 'S2R1', 'S1R2'
741
+
742
+ udc: (optional) DC link voltage (V)
743
+ u1: (optional) phase voltage (Vrms)
744
+ Q1: (optional) total number of stator slots
745
+ brem: (optional) remanence of magnet (T)
746
+ """
747
+ par = dict(
748
+ pnom=pnom, speed=speed, p=p)
749
+ par.update(kwargs)
750
+ _set_afpm_defaults(par)
751
+
752
+ kp = 1 if afmtype == 'S1R1' else 2
753
+ kps = 1 if afmtype in ('S1R1', 'S1R2') else 2
754
+ kpr = 2 if afmtype == 'S1R2' else 1
755
+ kd = par['kd'] # ratio of inner_diam/outer_diam
756
+ tnom = pnom/(2*np.pi*speed)
757
+ sigmas = par['sigmas'] # shear force
758
+ # outer diameter:
759
+ # https://web.mit.edu/kirtley/binlustuff/literature/electric%20machine/designOfAxialFluxPMM.pdf
760
+ Do = 2*np.power(tnom/(kp*sigmas*np.pi*kd*(1-kd**2)), 1/3)
761
+ Di = Do*kd
762
+ # pole width and iron length
763
+ Davg = (Do+Di)/2
764
+ taup = np.pi * Davg/(2*p)
765
+ lfe = (Do-Di)/2
766
+ # flux density in airgap
767
+ Bd1 = 4.0/np.pi*par['Ba']*np.sin(np.pi/2.0*par['mag_width'])
768
+
769
+ # rated phase voltage
770
+ if 'udc' in par:
771
+ u1nom = 0.9*par['udc']/np.sqrt(2)/np.sqrt(3)
772
+ else:
773
+ u1nom = par['u1']
774
+ f1 = speed*p
775
+ # flux linkage
776
+ psi1 = kpr*2.0/np.pi*taup*lfe*Bd1
777
+
778
+ # winding factor
779
+ Q1 = par['Q1']
780
+ m = par['m']
781
+ yd = par.get('coil_span', 0)
782
+ if yd:
783
+ wdg = Winding({'Q': par['Q1'], 'p': par['p'], 'm': 3,
784
+ 'yd': yd, 'l': 2})
785
+ else:
786
+ wdg = Winding({'Q': par['Q1'], 'p': par['p'], 'm': 3, 'l': 2})
787
+
788
+ kw = wdg.kw()
789
+
790
+ Ui = par['ui_u'] * u1nom
791
+ N = np.sqrt(2)*Ui/(2*np.pi*f1*kw*psi1)
792
+
793
+ # feasible number of turns per coil...
794
+ layers = wdg.l
795
+ # coils per phase (kps: number of stators)
796
+ ncoils = kps*Q1 // 2 // m * layers
797
+ ngroups = [1] + [g for g in range(2, layers*p + 1) if layers * p % g == 0]
798
+ ndiff = [abs(N - ncoils // a * a * round(N / ncoils))
799
+ for a in ngroups]
800
+ logger.debug("N %f ngroups %s ndiffs %s", N, ngroups, ndiff)
801
+ # parallel groups
802
+ a_calc = ngroups[np.argmin(ndiff)]
803
+ a = par.get("a", a_calc)
804
+ if a not in ngroups:
805
+ logger.warning("Check given number %s of parallel wdg groups. Valid ngroups are: %s",
806
+ a, ngroups)
807
+ # num wires per coil side (number of coil groups a)
808
+ num_wires = round(a * N / ncoils)
809
+
810
+ # correction of number of turns per phase
811
+ N_old = N
812
+ N = num_wires * ncoils / a
813
+
814
+ # correction of voltage
815
+ Ui = Ui/N_old*N
816
+ u1nom = Ui/par['ui_u']
817
+
818
+ # current loading
819
+ # A = np.sqrt(2)*sigmas/kw/Ba
820
+ I1 = pnom/(m*par['eta']*par['cos_phi']*u1nom)
821
+ A = 2*m*N*I1/np.pi/Di
822
+ # slot area
823
+ # J = AJ/A
824
+ hs1 = 1e-3 # slot opening height
825
+ taus = np.pi/Q1
826
+ ans = taus*Di*A/(par['kq']*par['J'])
827
+ bds = taus*(Di+2*hs1)*Bd1/par['Bth']
828
+ bns = taus*(Di+2*hs1) - bds
829
+
830
+ hns = (-bns + np.sqrt(bns**2 + 4*ans*np.tan(taus)))/2/np.tan(taus)/kps
831
+ hys = psi1/2/lfe/par['By']/kps
832
+
833
+ aw = ans * par['kq'] / layers / num_wires * kps
834
+
835
+ r = {'outer_diam': Do, 'inner_diam': Di, 'airgap': par['airgap']/kp,
836
+ 'afmtype': afmtype, 'lfe': lfe,
837
+ 'poles': 2*p,
838
+ 'ans': round(ans, 6),
839
+ 'hns': round(hns, 4),
840
+ 'bns': round(bns, 4),
841
+ 'A': round(A, 3),
842
+ 'AJ': round(par['J']*A, 0),
843
+ 'w1': int(N),
844
+ 'kw': round(kw, 4),
845
+ 'ess': round(1e-3*pnom/(60*speed)/(Do**2*lfe), 4),
846
+ 'q': wdg.q,
847
+ 'i1': round(I1, 3), # np.pi*Da1*A/2/m/N
848
+ 'psi1': round(psi1, 5),
849
+ 'u1': u1nom,
850
+ 'ui': Ui}
851
+ hs1 = 0
852
+ hs2 = 0
853
+ r['stator'] = dict(
854
+ u1nom=round(u1nom, 1), f1nom=round(f1, 1),
855
+ num_slots=Q1,
856
+ nodedist=1,
857
+ # num_slots_gen = req_poles*Q1/2/p,
858
+ afm_stator=dict(
859
+ slot_width=r['bns'],
860
+ slot_height=hs1+r['hns'],
861
+ slot_h1=hs1,
862
+ slot_h2=hs1,
863
+ slot_open_width=r['bns'],
864
+ slot_r1=0,
865
+ slot_r2=0, # bns2/2,
866
+ yoke_height=round(hys, 4) if kpr == 1 else 0))
867
+
868
+ relculen = 1.4
869
+ r['winding'] = dict(
870
+ #wire_diam=round(dwire, 5),
871
+ num_phases=m,
872
+ cufilfact=par['kq'],
873
+ culength=relculen,
874
+ num_par_wdgs=a,
875
+ num_layers=layers,
876
+ #resistance=round(r1, 4),
877
+ coil_span=wdg.yd,
878
+ num_wires=int(num_wires))
879
+
880
+ hm = _get_magnet_height(r['i1'], r['w1'], r['kw'], par)/kpr
881
+ r['magnet'] = dict(
882
+ afm_rotor=dict(
883
+ yoke_height=round(hys/kpr, 4) if kps == 1 else 0,
884
+ rel_magn_width=par['mag_width'],
885
+ magn_height=round(hm, 4))
886
+ )
887
+ return r
888
+
889
+
711
890
  def ipm(pnom: float, speed: float, p: int, **kwargs) -> dict:
712
891
  """returns dimension of a IPM machine
713
892
 
@@ -808,11 +987,11 @@ def eesm(pnom: float, speed: float, p: int, **kwargs) -> dict:
808
987
 
809
988
 
810
989
  if __name__ == "__main__":
811
-
812
- pnom = 10e3
813
- speed = 4400/60
814
- p = 4
815
- udc = 600
990
+ # sizing example with SPM
991
+ pnom = 10e3 # shaft power in W
992
+ speed = 4400/60 # speed in 1/s
993
+ p = 4 # number of pole pairs
994
+ udc = 600 # DC voltage in V
816
995
 
817
996
  r = spm(pnom, speed, p, udc=udc)
818
997
 
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()
@@ -449,8 +449,8 @@ def dqparident(workdir, engine, temp, machine,
449
449
  leakfile.unlink(missing_ok=True)
450
450
 
451
451
  period_frac = kwargs.get('period_frac', 6)
452
- if machine.get('external_rotor', False):
453
- period_frac = 1 # TODO: missing femag support
452
+ if machine.get('external_rotor', False) and period_frac > 1:
453
+ logger.warning("period frac for external rotor requires GT femag version >= 2024")
454
454
 
455
455
  if dqtype == 'ldq':
456
456
  simulation = dict(