femagtools 1.8.2__py3-none-any.whl → 1.8.4__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/bch.py +10 -6
  3. femagtools/dxfsl/area.py +69 -1
  4. femagtools/dxfsl/conv.py +53 -16
  5. femagtools/dxfsl/converter.py +273 -76
  6. femagtools/dxfsl/fslrenderer.py +18 -22
  7. femagtools/dxfsl/functions.py +38 -8
  8. femagtools/dxfsl/geom.py +112 -35
  9. femagtools/dxfsl/journal.py +1 -1
  10. femagtools/dxfsl/machine.py +44 -7
  11. femagtools/dxfsl/shape.py +4 -0
  12. femagtools/dxfsl/symmetry.py +105 -32
  13. femagtools/femag.py +64 -61
  14. femagtools/fsl.py +4 -2
  15. femagtools/isa7.py +3 -2
  16. femagtools/machine/afpm.py +45 -25
  17. femagtools/machine/effloss.py +31 -20
  18. femagtools/machine/im.py +6 -8
  19. femagtools/machine/sizing.py +4 -3
  20. femagtools/machine/sm.py +35 -37
  21. femagtools/mcv.py +66 -37
  22. femagtools/multiproc.py +79 -80
  23. femagtools/parstudy.py +10 -4
  24. femagtools/semi_fea.py +108 -0
  25. femagtools/templates/basic_modpar.mako +0 -3
  26. femagtools/templates/fe-contr.mako +18 -18
  27. femagtools/templates/ld_lq_fast.mako +3 -0
  28. femagtools/templates/mesh-airgap.mako +6 -0
  29. femagtools/templates/mult_cal_fast.mako +3 -0
  30. femagtools/templates/pm_sym_f_cur.mako +4 -1
  31. femagtools/templates/pm_sym_fast.mako +3 -0
  32. femagtools/templates/pm_sym_loss.mako +3 -0
  33. femagtools/templates/psd_psq_fast.mako +3 -0
  34. femagtools/templates/torq_calc.mako +3 -0
  35. femagtools/tks.py +23 -20
  36. femagtools/utils.py +1 -1
  37. femagtools/windings.py +11 -4
  38. femagtools/zmq.py +213 -0
  39. {femagtools-1.8.2.dist-info → femagtools-1.8.4.dist-info}/METADATA +3 -3
  40. {femagtools-1.8.2.dist-info → femagtools-1.8.4.dist-info}/RECORD +49 -47
  41. {femagtools-1.8.2.dist-info → femagtools-1.8.4.dist-info}/WHEEL +1 -1
  42. tests/test_afpm.py +15 -6
  43. tests/test_femag.py +1 -1
  44. tests/test_fsl.py +4 -4
  45. tests/test_mcv.py +20 -14
  46. tests/test_parident.py +2 -1
  47. {femagtools-1.8.2.dist-info → femagtools-1.8.4.dist-info}/LICENSE +0 -0
  48. {femagtools-1.8.2.dist-info → femagtools-1.8.4.dist-info}/entry_points.txt +0 -0
  49. {femagtools-1.8.2.dist-info → femagtools-1.8.4.dist-info}/top_level.txt +0 -0
femagtools/machine/sm.py CHANGED
@@ -187,7 +187,7 @@ def parident(workdir, engine, machine,
187
187
 
188
188
  if simulation['calculationMode'] == 'ld_lq_fast':
189
189
  dqpars = dict(m=3, p=b['machine']['p'],
190
- r1=r1,
190
+ r1=float(r1),
191
191
  r2=machine['rotor'].get('resistance', 1),
192
192
  rotor_mass=rotor_mass, kfric_b=1,
193
193
  ldq=[dict(
@@ -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]/2, 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,10 +8,11 @@ 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
14
- import scipy.interpolate as ip
15
+ from scipy.interpolate import make_interp_spline
15
16
  from six import string_types
16
17
  import femagtools.losscoeffs as lc
17
18
 
@@ -98,7 +99,7 @@ def norm_pfe(B, pfe):
98
99
  b = list(b)
99
100
  b[-1] = Bv[n]
100
101
  n += 1
101
- pfunc = ip.interp1d(b, pfe[i], kind='cubic')
102
+ pfunc = make_interp_spline(b, pfe[i])
102
103
  m.append([float(pfunc(x))
103
104
  for x in Bv[:n]] + [None]*(len(Bv)-n))
104
105
  return Bv.tolist(), m
@@ -167,8 +168,7 @@ def recalc_bsin(curve):
167
168
  if bi[0] > 0:
168
169
  bi.insert(0, 0)
169
170
  hi.insert(0, 0)
170
- bh = ip.interp1d(bi, hi,
171
- kind='cubic', assume_sorted=True)
171
+ bh = make_interp_spline(bi, hi)
172
172
  for bx in c['bi'][2:]:
173
173
  bt = bx*np.sin(2*np.pi/4/ndel*x)
174
174
  nue = np.sum(bh(bt)/bt)/ndel
@@ -195,8 +195,7 @@ def recalc_hsin(curve):
195
195
  if hi[0] > 0:
196
196
  hi.insert(0, 0)
197
197
  bi.insert(0, 0)
198
- hb = ip.interp1d(hi, bi,
199
- kind='cubic', assume_sorted=True)
198
+ hb = make_interp_spline(hi, bi)
200
199
  for hx in c['hi'][2:]:
201
200
  ht = hx*np.sin(2*np.pi/4/ndel*x)
202
201
  bt = hb(ht)*np.sin(2*np.pi/4/ndel*x)
@@ -242,11 +241,12 @@ def approx(db2, curve, ctype):
242
241
  if ctype in (DEMCRV, MAG_AC_CRV):
243
242
  dhdbn = 0
244
243
  k = len(bi2)-1
245
- if curve['bi'][k] - curve['bi'][k-1] > 0:
246
- dhdbn = ((curve['hi'][k] - curve['h'][k-1],KK)
247
- /(curve['bi'][k] - curve['bi'][k-1]))
248
- a.append(MUE0*dhdbn)
249
- b.append(MUE0*curve['hi'][k] - dhdbn*curve['bi'][k])
244
+ if k < len(curve['bi']):
245
+ if curve['bi'][k] - curve['bi'][k-1] > 0:
246
+ dhdbn = ((curve['hi'][k] - curve['h'][k-1])
247
+ /(curve['bi'][k] - curve['bi'][k-1]))
248
+ a.append(MUE0*dhdbn)
249
+ b.append(MUE0*curve['hi'][k] - dhdbn*curve['bi'][k])
250
250
  else:
251
251
  a.append(1.0)
252
252
  b.append(MUE0*curve['hi'][-1]-curve['bi'][-1])
@@ -292,7 +292,12 @@ class Mcv(object):
292
292
  self.MC1_CW_FREQ_FACTOR = 0.0
293
293
  self.MC1_INDUCTION_FACTOR = 0.0
294
294
  self.MC1_INDUCTION_BETA_FACTOR = 0.0
295
-
295
+ self.jordan = {}
296
+ # {'ch': 0, 'cw': 0, 'ch_freq':0, 'cw_freq':0}
297
+ self.steinmetz = {}
298
+ # {'ch': 0, 'cw': 0, 'ch_freq':0, 'cw_freq':0}
299
+ self.bertotti = {}
300
+ # {'ch': 0, 'cw': 0, 'ce':0, 'ch_freq':0, 'cw_freq':0}
296
301
  self.MC1_FE_SPEZ_WEIGTH = 7.65
297
302
  self.MC1_FE_SAT_MAGNETIZATION = 2.15
298
303
 
@@ -354,6 +359,9 @@ class Mcv(object):
354
359
  for k in wtrans:
355
360
  if wtrans[k] in data.keys():
356
361
  self.__setattr__(k, data[wtrans[k]])
362
+ for k in ('bertotti', 'jordan', 'steinmetz'):
363
+ if k in data:
364
+ self.__setattr__(k, data[k])
357
365
  self.curve = data['curve']
358
366
  try:
359
367
  self.mc1_angle = [c['angle'] for c in data['curve']]
@@ -363,6 +371,9 @@ class Mcv(object):
363
371
  self.losses = data['losses']
364
372
  except Exception:
365
373
  pass
374
+ # assume jordan iron loss parameters
375
+ for k in self.jordan:
376
+ self.jordan[k] = getattr(self, transl[k])
366
377
  return
367
378
 
368
379
  def rtrimValueList(self, vlist):
@@ -470,13 +481,25 @@ class Writer(Mcv):
470
481
  for c in curve]
471
482
  return curve
472
483
 
473
- def writeBinaryFile(self, fillfac=None, recsin=''):
484
+ def writeBinaryFile(self, fillfac=None, recsin='', feloss=''):
474
485
  """write binary file after conversion if requested.
475
486
  arguments:
476
487
  fillfac: (float) fill actor
477
488
  recsin: (str) either 'flux' or 'cur'
489
+ feloss: (str) iron loss method (bertotti, jordan)
478
490
  """
479
491
  curve = self._prepare(fillfac, recsin)
492
+ try:
493
+ if feloss.lower() == 'bertotti':
494
+ for k in self.bertotti:
495
+ setattr(self, transl[k], self.bertotti[k])
496
+ del self.losses
497
+ else:
498
+ for k in self.jordan:
499
+ setattr(self, transl[k], self.jordan[k])
500
+ except AttributeError as e:
501
+ logger.warning("%s", e)
502
+ pass
480
503
  mc1_type = self.mc1_type
481
504
  mc1_recalc = self.mc1_recalc
482
505
  mc1_fillfac = self.mc1_fillfac
@@ -556,6 +579,7 @@ class Writer(Mcv):
556
579
 
557
580
  try:
558
581
  if not (self.mc1_ch_factor or self.mc1_cw_factor) and self.losses:
582
+ # fit loss parameters
559
583
  pfe = self.losses['pfe']
560
584
  f = self.losses['f']
561
585
  B = self.losses['B']
@@ -598,7 +622,8 @@ class Writer(Mcv):
598
622
  return
599
623
 
600
624
  try:
601
- nfreq = len([1 for x in self.losses['f'] if x > 0])
625
+ freq = [x for x in self.losses['f'] if x > 0]
626
+ nfreq = len(freq)
602
627
  nind = len(self.losses['B'])
603
628
  if nind < 1 or nfreq < 1:
604
629
  return
@@ -631,26 +656,28 @@ class Writer(Mcv):
631
656
  self.writeBlock([nfreq, nind])
632
657
  self.writeBlock([float(b) for b in B] + [0.0]*(M_LOSS_INDUCT - nind))
633
658
 
659
+ nrec = 0
634
660
  for f, p in zip(self.losses['f'], pfe):
635
661
  if f > 0:
636
662
  y = np.array(p)
637
663
  losses = [float(x) for x in y[y != np.array(None)]]
638
- if len(losses) == nind:
639
- pl = p
664
+ nloss = len(losses)
665
+ if nloss == nind:
666
+ pl = list(p)
640
667
  else:
641
- n = len(losses)
642
668
  cw, alfa, beta = lc.fitsteinmetz(
643
- f, B[:n], losses, Bo, fo)
669
+ f, B[:nloss], losses, Bo, fo)
644
670
  pl = losses + [lc.pfe_steinmetz(
645
671
  f, b, cw, alfa, beta,
646
672
  self.losses['fo'],
647
673
  self.losses['Bo'])
648
- for b in B[n:]]
674
+ for b in B[nloss:]]
649
675
  logger.debug("%s", pl)
650
676
  self.writeBlock(pl +
651
677
  [0.0]*(M_LOSS_INDUCT - len(pl)))
652
678
  self.writeBlock(float(f))
653
- for m in range(M_LOSS_FREQ - len(pfe)):
679
+ nrec += 1
680
+ for m in range(M_LOSS_FREQ - nrec):
654
681
  self.writeBlock([0.0]*M_LOSS_INDUCT)
655
682
  self.writeBlock(0.0)
656
683
 
@@ -662,21 +689,21 @@ class Writer(Mcv):
662
689
  except Exception as e:
663
690
  logger.error("Exception %s", e, exc_info=True)
664
691
 
665
- def writeMcv(self, filename, fillfac=None, recsin=''):
692
+ def writeMcv(self, filename, fillfac=None, recsin='', feloss='Jordan'):
666
693
  # windows needs this strip to remove '\r'
667
- filename = filename.strip()
668
- self.name = os.path.splitext(filename)[0]
694
+ filename = pathlib.Path(filename)
695
+ self.name = filename.stem
669
696
 
670
- if filename.upper().endswith('.MCV') or \
671
- filename.upper().endswith('.MC'):
697
+ if filename.suffix.upper() in ('.MCV', '.MC'):
672
698
  binary = True
673
- self.fp = open(filename, "wb")
699
+ self.fp = filename.open(mode="wb")
674
700
  else:
675
701
  binary = False
676
- self.fp = open(filename, "wb")
677
- logger.info("Write File %s, binary format", filename)
702
+ self.fp = filename.open(mode="w")
703
+ logger.info("Write File %s, binary format (feloss '%s')",
704
+ filename, feloss)
678
705
 
679
- self.writeBinaryFile(fillfac, recsin)
706
+ self.writeBinaryFile(fillfac, recsin, feloss)
680
707
  self.fp.close()
681
708
 
682
709
 
@@ -737,17 +764,16 @@ class Reader(Mcv):
737
764
 
738
765
  def readMcv(self, filename):
739
766
  # intens bug : windows needs this strip to remove '\r'
740
- filename = filename.strip()
767
+ filename = pathlib.Path(filename)
741
768
 
742
- if filename.upper().endswith('.MCV') or \
743
- filename.upper().endswith('.MC'):
769
+ if filename.suffix in ('.MCV', '.MC'):
744
770
  binary = True
745
- self.fp = open(filename, "rb")
771
+ self.fp = filename.open(mode="rb")
746
772
  else:
747
773
  binary = False
748
- self.fp = open(filename, "r")
774
+ self.fp = filename.open(mode="r")
749
775
 
750
- self.name = os.path.splitext(os.path.basename(filename))[0]
776
+ self.name = filename.stem
751
777
  # read curve version (INTEGER)
752
778
  if binary:
753
779
  self.version_mc_curve = self.readBlock(int)
@@ -903,6 +929,8 @@ class Reader(Mcv):
903
929
  if self.MC1_INDUCTION_FACTOR > 2.0:
904
930
  self.MC1_INDUCTION_FACTOR = 2.0
905
931
 
932
+ # TODO: handle self.mc1_ce_factor, self.mc1_induction_beta_factor
933
+
906
934
  self.losses = {}
907
935
  try:
908
936
  (nfreq, njind) = self.readBlock([int, int])
@@ -1085,13 +1113,14 @@ class MagnetizingCurve(object):
1085
1113
  repls.items(), name)
1086
1114
 
1087
1115
  def writefile(self, name, directory='.',
1088
- fillfac=None, recsin=''):
1116
+ fillfac=None, recsin='', feloss='jordan'):
1089
1117
  """find magnetic curve by name or id and write binary file
1090
1118
  Arguments:
1091
1119
  name: key of mcv dict (name or id)
1092
1120
  directory: destination directory (must be writable)
1093
1121
  fillfac: (float) new fill factor (curves will be recalulated if not None or 0)
1094
1122
  recsin: (str) either 'flux' or 'cur' recalculates for eddy current calculation (dynamic simulation)
1123
+ feloss: (str) iron loss calc method ('jordan', 'bertotti', 'steinmetz')
1095
1124
 
1096
1125
  returns filename if found else None
1097
1126
  """
@@ -1125,7 +1154,7 @@ class MagnetizingCurve(object):
1125
1154
  filename = ''.join((bname, ext))
1126
1155
  writer = Writer(mcv)
1127
1156
  writer.writeMcv(os.path.join(directory, filename),
1128
- fillfac=fillfac, recsin=recsin)
1157
+ fillfac=fillfac, recsin=recsin, feloss=feloss)
1129
1158
  return filename
1130
1159
 
1131
1160
  def fitLossCoeffs(self):
femagtools/multiproc.py CHANGED
@@ -12,6 +12,8 @@ import pathlib
12
12
  import logging
13
13
  from .job import Job
14
14
  import femagtools.config as cfg
15
+ import femagtools.zmq
16
+ from femagtools.zmq import SubscriberTask
15
17
  try:
16
18
  from subprocess import DEVNULL
17
19
  except ImportError:
@@ -19,86 +21,40 @@ except ImportError:
19
21
 
20
22
  logger = logging.getLogger(__name__)
21
23
 
22
- numpat = re.compile(r'([+-]?\d+(?:\.\d+)?(?:[eE][+-]\d+)?)\s*')
23
-
24
24
  class LicenseError(Exception):
25
25
  pass
26
26
 
27
- class ProtFile:
28
- def __init__(self, dirname, num_cur_steps):
29
- self.size = 0
30
- self.looplen = 0
31
- self.cur_steps = [1, num_cur_steps]
32
- self.n = 0
33
- self.num_loops = 0
34
- self.dirname = dirname
35
- self.name = 'samples'
36
-
37
- def percent(self):
38
- if self.looplen > 0:
39
- return min(100 * self.n / self.looplen, 100)
40
- return 0
41
-
42
- def update(self):
43
- p = list(pathlib.Path(self.dirname).glob('*.PROT'))
44
- if p:
45
- buf = ''
46
- if self.size < p[0].stat().st_size:
47
- with p[0].open() as fp:
48
- fp.seek(self.size)
49
- buf = fp.read()
50
- return self.append(buf)
51
- return ''
52
-
53
- def append(self, buf):
54
- self.size += len(buf)
55
- for line in [l.strip() for l in buf.split('\n') if l]:
56
- if line.startswith('Loop'):
57
- self.n = 0
58
- try:
59
- cur_steps = self.cur_steps[self.num_loops]
60
- except IndexError:
61
- cur_steps = 1
62
- x0, x1, dx, nbeta = [float(f)
63
- for f in re.findall(numpat, line)][:4]
64
- move_steps = round((x1-x0)/dx+1)
65
- beta_steps = int(nbeta)
66
- self.looplen = cur_steps*beta_steps*move_steps
67
- self.num_loops += 1
68
- elif (line.startswith('Cur') or
69
- line.startswith('Id')):
70
- self.n += 1
71
- elif line.startswith('Number movesteps Fe-Losses'):
72
- return ''
73
- elif line.startswith('begin'):
74
- self.name = line.split()[1].strip()
75
-
76
- return f'{self.percent():3.1f}%' # {self.n}/{self.looplen}'
77
-
78
-
79
27
  class ProgressLogger(threading.Thread):
80
- def __init__(self, dirs, num_cur_steps, timestep):
28
+ def __init__(self, dirs, num_cur_steps, timestep, notify):
81
29
  threading.Thread.__init__(self)
82
- self.dirs = dirs
83
- self.num_cur_steps = num_cur_steps
30
+ self.protfiles = [femagtools.zmq.ProtFile(d, num_cur_steps)
31
+ for d in dirs]
32
+ self.numTot = len(dirs)
84
33
  self.running = False
85
34
  self.timestep = timestep
35
+ self.notify = notify
86
36
 
87
37
  def run(self):
88
38
  self.running = True
89
- protfiles = [ProtFile(d, self.num_cur_steps)
90
- for d in self.dirs]
91
39
  while self.running:
92
- time.sleep(self.timestep)
93
- logmsg = [p.update() for p in protfiles]
40
+ if self.timestep > 0:
41
+ time.sleep(self.timestep)
42
+ logmsg = [p.update() for p in self.protfiles]
94
43
  summary = [l # f'<{i}> {l}'
95
44
  for i, l in enumerate(logmsg)
96
45
  if l]
97
46
  if summary:
98
- labels = set([p.name for p in protfiles])
47
+ labels = set([p.name for p in self.protfiles])
99
48
  logger.info('%s: %s',
100
49
  ', '.join(labels),
101
50
  ', '.join(summary))
51
+ if self.notify:
52
+ numOf = f"{summary.count('100.0%')} of {self.numTot}"
53
+ percent = sum([float(i[:-1])
54
+ for i in summary]) / self.numTot
55
+ self.notify(
56
+ ["progress_logger",
57
+ f"{self.numTot}:{numOf}:{percent}:{' '.join(summary)}"])
102
58
  else:
103
59
  logger.info('collecting FE losses ...')
104
60
  return
@@ -107,7 +63,7 @@ class ProgressLogger(threading.Thread):
107
63
  self.running = False
108
64
 
109
65
 
110
- def run_femag(cmd, workdir, fslfile):
66
+ def run_femag(cmd, workdir, fslfile, port):
111
67
  """Start the femag command as subprocess.
112
68
 
113
69
  :internal:
@@ -120,7 +76,8 @@ def run_femag(cmd, workdir, fslfile):
120
76
  with open(os.path.join(workdir, "femag.out"), "wb") as out, \
121
77
  open(os.path.join(workdir, "femag.err"), "wb") as err:
122
78
  try:
123
- proc = subprocess.Popen(cmd + ['-b', fslfile],
79
+ args = ['-b', str(port), fslfile] if port else ['-b', fslfile]
80
+ proc = subprocess.Popen(cmd + args,
124
81
  shell=False,
125
82
  stdin=DEVNULL,
126
83
  stdout=out,
@@ -163,11 +120,16 @@ class Engine:
163
120
  cmd: the program (executable image) to be run
164
121
  (femag dc is used if None)
165
122
  process_count: number of processes (cpu_count() if None)
166
- progress_timestep: time step in seconds for progress log messages if > 0)
123
+ timestep: time step in seconds for progress log messages if > 0)
167
124
  """
168
125
 
169
126
  def __init__(self, **kwargs):
170
127
  self.process_count = kwargs.get('process_count', None)
128
+ self.notify = kwargs.get('notify', None)
129
+ # cogg_calc mode, subscribe xyplot
130
+ self.calc_mode = kwargs.get('calc_mode')
131
+ self.port = kwargs.get('port', 0)
132
+ self.curve_label = kwargs.get('curve_label')
171
133
  cmd = kwargs.get('cmd', '')
172
134
  if cmd:
173
135
  self.cmd = [cmd]
@@ -175,7 +137,10 @@ class Engine:
175
137
  self.cmd.append('-m')
176
138
 
177
139
  self.progressLogger = 0
178
- self.progress_timestep = kwargs.get('timestep', 5)
140
+ self.progress_timestep = kwargs.get('timestep', -1)
141
+ self.subscriber = None
142
+ self.job = None
143
+ self.tasks = []
179
144
 
180
145
  def create_job(self, workdir):
181
146
  """Create a FEMAG :py:class:`Job`
@@ -209,20 +174,41 @@ class Engine:
209
174
  t.cmd = [cfg.get_executable(
210
175
  t.stateofproblem)] + args
211
176
 
212
- self.pool = multiprocessing.Pool(self.process_count)
213
- self.tasks = [self.pool.apply_async(run_femag,
214
- args=(t.cmd,
215
- t.directory,
216
- t.fsl_file))
217
- for t in self.job.tasks]
177
+ num_proc = self.process_count
178
+ if not num_proc and multiprocessing.cpu_count() > 1:
179
+ num_proc = min(multiprocessing.cpu_count()-1, len(self.job.tasks))
180
+ self.pool = multiprocessing.Pool(num_proc)
181
+ if self.port:
182
+ header = [b'progress']
183
+ if self.calc_mode == 'cogg_calc':
184
+ header +=[b'xyplot']
185
+ self.subscriber = [SubscriberTask(port=self.port + i * 5,
186
+ host='127.0.0.1',
187
+ notify=self.notify,
188
+ header=header,
189
+ curve_label=self.curve_label,
190
+ num_cur_steps=self.job.num_cur_steps,
191
+ timestep=self.progress_timestep
192
+ )
193
+ for i, t in enumerate(self.job.tasks)]
194
+ [s.start() for s in self.subscriber]
195
+ self.tasks = [self.pool.apply_async(
196
+ run_femag, args=(t.cmd, t.directory, t.fsl_file, self.port + i * 5))
197
+ for i, t in enumerate(self.job.tasks)]
198
+ else:
199
+ self.tasks = [self.pool.apply_async(
200
+ run_femag, args=(t.cmd, t.directory, t.fsl_file, 0))
201
+ for t in self.job.tasks]
218
202
  self.pool.close()
219
203
 
220
- if (self.progress_timestep and
221
- self.job.num_cur_steps):
204
+ # only works on linux
205
+ if (self.progress_timestep and not self.port and
206
+ self.job.num_cur_steps):
222
207
  self.progressLogger = ProgressLogger(
223
208
  [t.directory for t in self.job.tasks],
224
209
  num_cur_steps=self.job.num_cur_steps,
225
- timestep=self.progress_timestep)
210
+ timestep=self.progress_timestep,
211
+ notify=self.notify)
226
212
  self.progressLogger.start()
227
213
  return len(self.tasks)
228
214
 
@@ -244,17 +230,30 @@ class Engine:
244
230
  if t.errmsg:
245
231
  logger.error(t.errmsg)
246
232
  status.append(t.status)
233
+ self.stopThreads()
234
+ self.pool = None # garbage collector deletes threads
235
+ return status
236
+
237
+ def stopThreads(self):
238
+ """ stop all running treads
239
+ """
247
240
  if self.progressLogger:
248
241
  self.progressLogger.stop()
249
- return status
242
+ if self.port and self.subscriber:
243
+ [s.stop() for s in self.subscriber]
244
+ SubscriberTask.clear()
245
+ self.subscriber = None
250
246
 
251
247
  def terminate(self):
248
+ """ terminate all
249
+ """
252
250
  logger.info("terminate Engine")
253
- if self.progressLogger:
254
- self.progressLogger.stop()
251
+ self.stopThreads()
252
+
255
253
  # terminate pool
256
254
  try:
257
- self.pool.terminate()
258
- self.pool.close()
255
+ if self.pool:
256
+ self.pool.terminate()
257
+ self.pool = None # garbage collector deletes threads
259
258
  except AttributeError as e:
260
259
  logger.warn("%s", e)