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.
- 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)
|