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.
- femagtools/__init__.py +1 -1
- femagtools/bch.py +10 -6
- femagtools/dxfsl/area.py +69 -1
- femagtools/dxfsl/conv.py +53 -16
- femagtools/dxfsl/converter.py +273 -76
- femagtools/dxfsl/fslrenderer.py +18 -22
- femagtools/dxfsl/functions.py +38 -8
- femagtools/dxfsl/geom.py +112 -35
- femagtools/dxfsl/journal.py +1 -1
- femagtools/dxfsl/machine.py +44 -7
- femagtools/dxfsl/shape.py +4 -0
- femagtools/dxfsl/symmetry.py +105 -32
- femagtools/femag.py +64 -61
- femagtools/fsl.py +4 -2
- femagtools/isa7.py +3 -2
- femagtools/machine/afpm.py +45 -25
- femagtools/machine/effloss.py +31 -20
- femagtools/machine/im.py +6 -8
- femagtools/machine/sizing.py +4 -3
- femagtools/machine/sm.py +35 -37
- femagtools/mcv.py +66 -37
- femagtools/multiproc.py +79 -80
- femagtools/parstudy.py +10 -4
- femagtools/semi_fea.py +108 -0
- femagtools/templates/basic_modpar.mako +0 -3
- femagtools/templates/fe-contr.mako +18 -18
- femagtools/templates/ld_lq_fast.mako +3 -0
- femagtools/templates/mesh-airgap.mako +6 -0
- femagtools/templates/mult_cal_fast.mako +3 -0
- femagtools/templates/pm_sym_f_cur.mako +4 -1
- femagtools/templates/pm_sym_fast.mako +3 -0
- femagtools/templates/pm_sym_loss.mako +3 -0
- femagtools/templates/psd_psq_fast.mako +3 -0
- femagtools/templates/torq_calc.mako +3 -0
- femagtools/tks.py +23 -20
- femagtools/utils.py +1 -1
- femagtools/windings.py +11 -4
- femagtools/zmq.py +213 -0
- {femagtools-1.8.2.dist-info → femagtools-1.8.4.dist-info}/METADATA +3 -3
- {femagtools-1.8.2.dist-info → femagtools-1.8.4.dist-info}/RECORD +49 -47
- {femagtools-1.8.2.dist-info → femagtools-1.8.4.dist-info}/WHEEL +1 -1
- tests/test_afpm.py +15 -6
- tests/test_femag.py +1 -1
- tests/test_fsl.py +4 -4
- tests/test_mcv.py +20 -14
- tests/test_parident.py +2 -1
- {femagtools-1.8.2.dist-info → femagtools-1.8.4.dist-info}/LICENSE +0 -0
- {femagtools-1.8.2.dist-info → femagtools-1.8.4.dist-info}/entry_points.txt +0 -0
- {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,
|
437
|
+
startvals = self.bounds[0][1]/2, 0, self.bounds[-1][1]
|
438
438
|
else:
|
439
|
-
startvals = -self.bounds[0][1]/2, 0,
|
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,
|
466
|
+
startvals = self.bounds[0][1]/2, 0, self.bounds[-1][1]
|
467
467
|
else:
|
468
|
-
startvals = -self.bounds[0][1]/2, 0,
|
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
|
-
|
487
|
-
|
488
|
-
|
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
|
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
|
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
|
-
|
524
|
-
|
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)
|
540
|
+
'fun': lambda iqd: torque - self.tmech_iqd(*iqd, n)},
|
544
541
|
{'type': 'eq',
|
545
|
-
'fun': lambda iqd: np.
|
546
|
-
|
542
|
+
'fun': lambda iqd: u1max*np.sqrt(2)
|
543
|
+
- np.linalg.norm(self.uqd(w1, *iqd))}])
|
547
544
|
#if res['success']:
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
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))
|
584
|
-
if res['success']:
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
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 =
|
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
|
-
|
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 =
|
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 =
|
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 =
|
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
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
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
|
-
|
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
|
-
|
639
|
-
|
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[:
|
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[
|
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
|
-
|
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 =
|
668
|
-
self.name =
|
694
|
+
filename = pathlib.Path(filename)
|
695
|
+
self.name = filename.stem
|
669
696
|
|
670
|
-
if filename.upper()
|
671
|
-
filename.upper().endswith('.MC'):
|
697
|
+
if filename.suffix.upper() in ('.MCV', '.MC'):
|
672
698
|
binary = True
|
673
|
-
self.fp = open(
|
699
|
+
self.fp = filename.open(mode="wb")
|
674
700
|
else:
|
675
701
|
binary = False
|
676
|
-
self.fp = open(
|
677
|
-
logger.info("Write File %s, binary format",
|
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 =
|
767
|
+
filename = pathlib.Path(filename)
|
741
768
|
|
742
|
-
if filename.
|
743
|
-
filename.upper().endswith('.MC'):
|
769
|
+
if filename.suffix in ('.MCV', '.MC'):
|
744
770
|
binary = True
|
745
|
-
self.fp = open(
|
771
|
+
self.fp = filename.open(mode="rb")
|
746
772
|
else:
|
747
773
|
binary = False
|
748
|
-
self.fp = open(
|
774
|
+
self.fp = filename.open(mode="r")
|
749
775
|
|
750
|
-
self.name =
|
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.
|
83
|
-
|
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
|
-
|
93
|
-
|
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
|
-
|
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
|
-
|
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',
|
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
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
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
|
-
|
221
|
-
|
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
|
-
|
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
|
-
|
254
|
-
|
251
|
+
self.stopThreads()
|
252
|
+
|
255
253
|
# terminate pool
|
256
254
|
try:
|
257
|
-
self.pool
|
258
|
-
|
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)
|