femagtools 1.8.2__py3-none-any.whl → 1.8.4__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/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)