femagtools 1.8.1__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 (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(