femagtools 1.8.1__py3-none-any.whl → 1.8.3__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/dxfsl/area.py +110 -1
- femagtools/dxfsl/areabuilder.py +93 -45
- femagtools/dxfsl/conv.py +5 -0
- femagtools/dxfsl/converter.py +85 -27
- femagtools/dxfsl/fslrenderer.py +5 -4
- femagtools/dxfsl/functions.py +14 -6
- femagtools/dxfsl/geom.py +135 -149
- femagtools/dxfsl/journal.py +1 -1
- femagtools/dxfsl/machine.py +161 -9
- femagtools/dxfsl/shape.py +46 -1
- femagtools/dxfsl/svgparser.py +1 -1
- femagtools/dxfsl/symmetry.py +143 -38
- femagtools/femag.py +64 -61
- femagtools/fsl.py +15 -12
- femagtools/isa7.py +3 -2
- femagtools/machine/__init__.py +5 -4
- femagtools/machine/afpm.py +79 -33
- femagtools/machine/effloss.py +29 -18
- femagtools/machine/sizing.py +192 -13
- femagtools/machine/sm.py +34 -36
- femagtools/machine/utils.py +2 -2
- femagtools/mcv.py +58 -29
- femagtools/model.py +4 -3
- femagtools/multiproc.py +79 -80
- femagtools/parstudy.py +11 -5
- femagtools/plot/nc.py +2 -2
- 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/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/zmq.py +213 -0
- {femagtools-1.8.1.dist-info → femagtools-1.8.3.dist-info}/METADATA +3 -3
- {femagtools-1.8.1.dist-info → femagtools-1.8.3.dist-info}/RECORD +49 -47
- {femagtools-1.8.1.dist-info → femagtools-1.8.3.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 +21 -15
- {femagtools-1.8.1.dist-info → femagtools-1.8.3.dist-info}/LICENSE +0 -0
- {femagtools-1.8.1.dist-info → femagtools-1.8.3.dist-info}/entry_points.txt +0 -0
- {femagtools-1.8.1.dist-info → femagtools-1.8.3.dist-info}/top_level.txt +0 -0
femagtools/mcv.py
CHANGED
@@ -8,6 +8,7 @@ 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
|
@@ -292,7 +293,12 @@ class Mcv(object):
|
|
292
293
|
self.MC1_CW_FREQ_FACTOR = 0.0
|
293
294
|
self.MC1_INDUCTION_FACTOR = 0.0
|
294
295
|
self.MC1_INDUCTION_BETA_FACTOR = 0.0
|
295
|
-
|
296
|
+
self.jordan = {}
|
297
|
+
# {'ch': 0, 'cw': 0, 'ch_freq':0, 'cw_freq':0}
|
298
|
+
self.steinmetz = {}
|
299
|
+
# {'ch': 0, 'cw': 0, 'ch_freq':0, 'cw_freq':0}
|
300
|
+
self.bertotti = {}
|
301
|
+
# {'ch': 0, 'cw': 0, 'ce':0, 'ch_freq':0, 'cw_freq':0}
|
296
302
|
self.MC1_FE_SPEZ_WEIGTH = 7.65
|
297
303
|
self.MC1_FE_SAT_MAGNETIZATION = 2.15
|
298
304
|
|
@@ -335,9 +341,8 @@ class Mcv(object):
|
|
335
341
|
self.setData(data)
|
336
342
|
|
337
343
|
self.mc1_curves = len(self.curve)
|
338
|
-
if self.mc1_type
|
339
|
-
self.
|
340
|
-
if self.mc1_type in (ORIENT_CRV, ORIENT_PM_CRV):
|
344
|
+
if (self.mc1_type in (ORIENT_CRV, ORIENT_PM_CRV)
|
345
|
+
or self.mc1_curves > 1):
|
341
346
|
self.version_mc_curve = self.ORIENTED_VERSION_MC_CURVE
|
342
347
|
elif self.mc1_type == DEMCRV_BR:
|
343
348
|
self.version_mc_curve = self.PARAMETER_PM_CURVE
|
@@ -355,6 +360,9 @@ class Mcv(object):
|
|
355
360
|
for k in wtrans:
|
356
361
|
if wtrans[k] in data.keys():
|
357
362
|
self.__setattr__(k, data[wtrans[k]])
|
363
|
+
for k in ('bertotti', 'jordan', 'steinmetz'):
|
364
|
+
if k in data:
|
365
|
+
self.__setattr__(k, data[k])
|
358
366
|
self.curve = data['curve']
|
359
367
|
try:
|
360
368
|
self.mc1_angle = [c['angle'] for c in data['curve']]
|
@@ -364,6 +372,9 @@ class Mcv(object):
|
|
364
372
|
self.losses = data['losses']
|
365
373
|
except Exception:
|
366
374
|
pass
|
375
|
+
# assume jordan iron loss parameters
|
376
|
+
for k in self.jordan:
|
377
|
+
self.jordan[k] = getattr(self, transl[k])
|
367
378
|
return
|
368
379
|
|
369
380
|
def rtrimValueList(self, vlist):
|
@@ -471,13 +482,25 @@ class Writer(Mcv):
|
|
471
482
|
for c in curve]
|
472
483
|
return curve
|
473
484
|
|
474
|
-
def writeBinaryFile(self, fillfac=None, recsin=''):
|
485
|
+
def writeBinaryFile(self, fillfac=None, recsin='', feloss=''):
|
475
486
|
"""write binary file after conversion if requested.
|
476
487
|
arguments:
|
477
488
|
fillfac: (float) fill actor
|
478
489
|
recsin: (str) either 'flux' or 'cur'
|
490
|
+
feloss: (str) iron loss method (bertotti, jordan)
|
479
491
|
"""
|
480
492
|
curve = self._prepare(fillfac, recsin)
|
493
|
+
try:
|
494
|
+
if feloss.lower() == 'bertotti':
|
495
|
+
for k in self.bertotti:
|
496
|
+
setattr(self, transl[k], self.bertotti[k])
|
497
|
+
del self.losses
|
498
|
+
else:
|
499
|
+
for k in self.jordan:
|
500
|
+
setattr(self, transl[k], self.jordan[k])
|
501
|
+
except AttributeError as e:
|
502
|
+
logger.warning("%s", e)
|
503
|
+
pass
|
481
504
|
mc1_type = self.mc1_type
|
482
505
|
mc1_recalc = self.mc1_recalc
|
483
506
|
mc1_fillfac = self.mc1_fillfac
|
@@ -557,6 +580,7 @@ class Writer(Mcv):
|
|
557
580
|
|
558
581
|
try:
|
559
582
|
if not (self.mc1_ch_factor or self.mc1_cw_factor) and self.losses:
|
583
|
+
# fit loss parameters
|
560
584
|
pfe = self.losses['pfe']
|
561
585
|
f = self.losses['f']
|
562
586
|
B = self.losses['B']
|
@@ -599,7 +623,8 @@ class Writer(Mcv):
|
|
599
623
|
return
|
600
624
|
|
601
625
|
try:
|
602
|
-
|
626
|
+
freq = [x for x in self.losses['f'] if x > 0]
|
627
|
+
nfreq = len(freq)
|
603
628
|
nind = len(self.losses['B'])
|
604
629
|
if nind < 1 or nfreq < 1:
|
605
630
|
return
|
@@ -632,26 +657,28 @@ class Writer(Mcv):
|
|
632
657
|
self.writeBlock([nfreq, nind])
|
633
658
|
self.writeBlock([float(b) for b in B] + [0.0]*(M_LOSS_INDUCT - nind))
|
634
659
|
|
660
|
+
nrec = 0
|
635
661
|
for f, p in zip(self.losses['f'], pfe):
|
636
662
|
if f > 0:
|
637
663
|
y = np.array(p)
|
638
664
|
losses = [float(x) for x in y[y != np.array(None)]]
|
639
|
-
|
640
|
-
|
665
|
+
nloss = len(losses)
|
666
|
+
if nloss == nind:
|
667
|
+
pl = list(p)
|
641
668
|
else:
|
642
|
-
n = len(losses)
|
643
669
|
cw, alfa, beta = lc.fitsteinmetz(
|
644
|
-
f, B[:
|
670
|
+
f, B[:nloss], losses, Bo, fo)
|
645
671
|
pl = losses + [lc.pfe_steinmetz(
|
646
672
|
f, b, cw, alfa, beta,
|
647
673
|
self.losses['fo'],
|
648
674
|
self.losses['Bo'])
|
649
|
-
for b in B[
|
675
|
+
for b in B[nloss:]]
|
650
676
|
logger.debug("%s", pl)
|
651
677
|
self.writeBlock(pl +
|
652
678
|
[0.0]*(M_LOSS_INDUCT - len(pl)))
|
653
679
|
self.writeBlock(float(f))
|
654
|
-
|
680
|
+
nrec += 1
|
681
|
+
for m in range(M_LOSS_FREQ - nrec):
|
655
682
|
self.writeBlock([0.0]*M_LOSS_INDUCT)
|
656
683
|
self.writeBlock(0.0)
|
657
684
|
|
@@ -663,21 +690,21 @@ class Writer(Mcv):
|
|
663
690
|
except Exception as e:
|
664
691
|
logger.error("Exception %s", e, exc_info=True)
|
665
692
|
|
666
|
-
def writeMcv(self, filename, fillfac=None, recsin=''):
|
693
|
+
def writeMcv(self, filename, fillfac=None, recsin='', feloss='Jordan'):
|
667
694
|
# windows needs this strip to remove '\r'
|
668
|
-
filename =
|
669
|
-
self.name =
|
695
|
+
filename = pathlib.Path(filename)
|
696
|
+
self.name = filename.stem
|
670
697
|
|
671
|
-
if filename.upper()
|
672
|
-
filename.upper().endswith('.MC'):
|
698
|
+
if filename.suffix.upper() in ('.MCV', '.MC'):
|
673
699
|
binary = True
|
674
|
-
self.fp = open(
|
700
|
+
self.fp = filename.open(mode="wb")
|
675
701
|
else:
|
676
702
|
binary = False
|
677
|
-
self.fp = open(
|
678
|
-
logger.info("Write File %s, binary format",
|
703
|
+
self.fp = filename.open(mode="w")
|
704
|
+
logger.info("Write File %s, binary format (feloss '%s')",
|
705
|
+
filename, feloss)
|
679
706
|
|
680
|
-
self.writeBinaryFile(fillfac, recsin)
|
707
|
+
self.writeBinaryFile(fillfac, recsin, feloss)
|
681
708
|
self.fp.close()
|
682
709
|
|
683
710
|
|
@@ -738,17 +765,16 @@ class Reader(Mcv):
|
|
738
765
|
|
739
766
|
def readMcv(self, filename):
|
740
767
|
# intens bug : windows needs this strip to remove '\r'
|
741
|
-
filename =
|
768
|
+
filename = pathlib.Path(filename)
|
742
769
|
|
743
|
-
if filename.
|
744
|
-
filename.upper().endswith('.MC'):
|
770
|
+
if filename.suffix in ('.MCV', '.MC'):
|
745
771
|
binary = True
|
746
|
-
self.fp = open(
|
772
|
+
self.fp = filename.open(mode="rb")
|
747
773
|
else:
|
748
774
|
binary = False
|
749
|
-
self.fp = open(
|
775
|
+
self.fp = filename.open(mode="r")
|
750
776
|
|
751
|
-
self.name =
|
777
|
+
self.name = filename.stem
|
752
778
|
# read curve version (INTEGER)
|
753
779
|
if binary:
|
754
780
|
self.version_mc_curve = self.readBlock(int)
|
@@ -904,6 +930,8 @@ class Reader(Mcv):
|
|
904
930
|
if self.MC1_INDUCTION_FACTOR > 2.0:
|
905
931
|
self.MC1_INDUCTION_FACTOR = 2.0
|
906
932
|
|
933
|
+
# TODO: handle self.mc1_ce_factor, self.mc1_induction_beta_factor
|
934
|
+
|
907
935
|
self.losses = {}
|
908
936
|
try:
|
909
937
|
(nfreq, njind) = self.readBlock([int, int])
|
@@ -1086,13 +1114,14 @@ class MagnetizingCurve(object):
|
|
1086
1114
|
repls.items(), name)
|
1087
1115
|
|
1088
1116
|
def writefile(self, name, directory='.',
|
1089
|
-
fillfac=None, recsin=''):
|
1117
|
+
fillfac=None, recsin='', feloss='jordan'):
|
1090
1118
|
"""find magnetic curve by name or id and write binary file
|
1091
1119
|
Arguments:
|
1092
1120
|
name: key of mcv dict (name or id)
|
1093
1121
|
directory: destination directory (must be writable)
|
1094
1122
|
fillfac: (float) new fill factor (curves will be recalulated if not None or 0)
|
1095
1123
|
recsin: (str) either 'flux' or 'cur' recalculates for eddy current calculation (dynamic simulation)
|
1124
|
+
feloss: (str) iron loss calc method ('jordan', 'bertotti', 'steinmetz')
|
1096
1125
|
|
1097
1126
|
returns filename if found else None
|
1098
1127
|
"""
|
@@ -1126,7 +1155,7 @@ class MagnetizingCurve(object):
|
|
1126
1155
|
filename = ''.join((bname, ext))
|
1127
1156
|
writer = Writer(mcv)
|
1128
1157
|
writer.writeMcv(os.path.join(directory, filename),
|
1129
|
-
fillfac=fillfac, recsin=recsin)
|
1158
|
+
fillfac=fillfac, recsin=recsin, feloss=feloss)
|
1130
1159
|
return filename
|
1131
1160
|
|
1132
1161
|
def fitLossCoeffs(self):
|
femagtools/model.py
CHANGED
@@ -136,14 +136,15 @@ class MachineModel(Model):
|
|
136
136
|
name = 'DRAFT'
|
137
137
|
if isinstance(parameters, str):
|
138
138
|
name = parameters
|
139
|
+
self.connect_full = False # no matter
|
139
140
|
else:
|
140
141
|
if 'name' in parameters:
|
141
142
|
name = parameters['name']
|
142
143
|
if 'windings' in parameters:
|
143
144
|
self.winding = self.windings
|
144
145
|
|
145
|
-
|
146
|
-
|
146
|
+
# connect model even for complete model (see fsl connect_models)
|
147
|
+
self.connect_full = parameters.get('afmtype', '') == ''
|
147
148
|
# must sanitize name to prevent femag complaints
|
148
149
|
self.name = ''.join([n
|
149
150
|
for n in name.strip()
|
@@ -410,7 +411,7 @@ class MachineModel(Model):
|
|
410
411
|
missing += m
|
411
412
|
names += n
|
412
413
|
if missing:
|
413
|
-
raise MCerror("
|
414
|
+
raise MCerror("Material properties missing: {}".format(
|
414
415
|
', '.join(set(missing))))
|
415
416
|
return set(names)
|
416
417
|
|
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)
|
femagtools/parstudy.py
CHANGED
@@ -105,10 +105,10 @@ class ParameterStudy(object):
|
|
105
105
|
raise ValueError("directory {} is not empty".format(dirname))
|
106
106
|
self.reportdir = dirname
|
107
107
|
|
108
|
-
def setup_model(self, builder, model, recsin=''):
|
108
|
+
def setup_model(self, builder, model, recsin='', feloss=''):
|
109
109
|
"""builds model in current workdir and returns its filenames"""
|
110
110
|
# get and write mag curves
|
111
|
-
mc_files = self.femag.copy_magnetizing_curves(model, recsin=recsin)
|
111
|
+
mc_files = self.femag.copy_magnetizing_curves(model, recsin=recsin, feloss=feloss)
|
112
112
|
|
113
113
|
if model.is_complete():
|
114
114
|
logger.info("setup model in %s", self.femag.workdir)
|
@@ -192,7 +192,8 @@ class ParameterStudy(object):
|
|
192
192
|
objective_vars)
|
193
193
|
|
194
194
|
if immutable_model:
|
195
|
-
modelfiles = self.setup_model(builder, model, recsin=fea.recsin
|
195
|
+
modelfiles = self.setup_model(builder, model, recsin=fea.recsin,
|
196
|
+
feloss=simulation.get('feloss', ''))
|
196
197
|
logger.info("Files %s", modelfiles+extra_files)
|
197
198
|
logger.info("model %s", model.props())
|
198
199
|
for k in ('name', 'poles', 'outer_diam', 'airgap', 'bore_diam',
|
@@ -262,6 +263,10 @@ class ParameterStudy(object):
|
|
262
263
|
p, int(np.ceil(len(par_range)/popsize)),
|
263
264
|
np.shape(f))
|
264
265
|
job.cleanup()
|
266
|
+
try:
|
267
|
+
feloss = fea.calc_fe_loss
|
268
|
+
except AttributeError:
|
269
|
+
feloss = ''
|
265
270
|
for k, x in enumerate(population):
|
266
271
|
task = job.add_task(self.result_func)
|
267
272
|
for fn in extra_files:
|
@@ -284,7 +289,8 @@ class ParameterStudy(object):
|
|
284
289
|
for mc in self.femag.copy_magnetizing_curves(
|
285
290
|
model,
|
286
291
|
dir=task.directory,
|
287
|
-
recsin=fea.recsin
|
292
|
+
recsin=fea.recsin,
|
293
|
+
feloss=feloss):
|
288
294
|
task.add_file(mc)
|
289
295
|
set_magnet_properties(model, fea, self.femag.magnets)
|
290
296
|
task.add_file(
|
@@ -325,7 +331,7 @@ class ParameterStudy(object):
|
|
325
331
|
shutil.copy(ff, repdir)
|
326
332
|
calcid += 1
|
327
333
|
if isinstance(r, dict) and 'error' in r:
|
328
|
-
logger.
|
334
|
+
logger.warning("job %d failed: %s", k, r['error'])
|
329
335
|
if objective_vars:
|
330
336
|
f.append([float('nan')]*len(objective_vars))
|
331
337
|
else:
|
femagtools/plot/nc.py
CHANGED
@@ -14,7 +14,7 @@ DEFAULT_CMAP='viridis'
|
|
14
14
|
"""default colormap (see https://matplotlib.org/stable/users/explain/colors/colormaps.html)"""
|
15
15
|
|
16
16
|
|
17
|
-
def spel(isa, superelements=[], with_axis=False, ax=0):
|
17
|
+
def spel(isa, superelements=[], with_axis=False, with_wiredir=False, ax=0):
|
18
18
|
"""plot super elements of I7/ISA7 model
|
19
19
|
Args:
|
20
20
|
isa: Isa7 object
|
@@ -35,7 +35,7 @@ def spel(isa, superelements=[], with_axis=False, ax=0):
|
|
35
35
|
color=isa.color[se.color], lw=0))
|
36
36
|
try:
|
37
37
|
# draw wire direction
|
38
|
-
if se.subregion:
|
38
|
+
if se.subregion and with_wiredir:
|
39
39
|
if se.subregion.curdir != 0:
|
40
40
|
wkey = se.subregion.winding.key
|
41
41
|
if se.subregion.curdir < 0:
|
femagtools/semi_fea.py
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
import numpy as np
|
2
|
+
from .utils import fft
|
3
|
+
import logging
|
4
|
+
|
5
|
+
logger = logging.getLogger('femagtools.semi_fea')
|
6
|
+
|
7
|
+
def shift_array(v, idx):
|
8
|
+
'''shift array by index'''
|
9
|
+
return v[idx::] + v[0:idx]
|
10
|
+
|
11
|
+
def fft_filter(result_fft, perc=0.01):
|
12
|
+
'''filter FFT result with amplitude'''
|
13
|
+
result = {"order": [], "y":[]}
|
14
|
+
base_amp = result_fft['a']
|
15
|
+
for i, j in enumerate(result_fft['nue']):
|
16
|
+
if j >= perc*base_amp:
|
17
|
+
result['order'].append(i)
|
18
|
+
result['y'].append(j)
|
19
|
+
return result
|
20
|
+
|
21
|
+
def fast_skew_cogg(result, skew_setup):
|
22
|
+
'''Calculate cogging torque/Back-EMF with step skewing based on unskewed result
|
23
|
+
Arguments:
|
24
|
+
result: BCH objects
|
25
|
+
skew_setup(dict): {"skew_angle": 10, "nu_skew_steps": 2}
|
26
|
+
'''
|
27
|
+
skew_angle = skew_setup['skew_angle']
|
28
|
+
num_skew_steps =skew_setup['num_skew_steps']
|
29
|
+
skew_angle_intern = 0.0
|
30
|
+
skew_angle_array = []
|
31
|
+
T_slice = []
|
32
|
+
bemf = {"1": [], "2": [], "3": []}
|
33
|
+
bemf_slice = {"1": [], "2": [], "3": []}
|
34
|
+
bemf_skew = {"1": [], "2": [], "3": []}
|
35
|
+
bemf_skew_fft = {"1": [], "2": [], "3": []}
|
36
|
+
|
37
|
+
keyset = ('1', '2', '3')
|
38
|
+
|
39
|
+
# check if skew steps equals 2
|
40
|
+
if num_skew_steps == 2:
|
41
|
+
skew_angle_intern = skew_angle
|
42
|
+
skew_angle_array = [-skew_angle_intern/2, skew_angle_intern/2]
|
43
|
+
else:
|
44
|
+
skew_angle_intern = skew_angle/num_skew_steps*(num_skew_steps-1)/2
|
45
|
+
skew_angle_array = np.linspace(-skew_angle_intern, skew_angle_intern,
|
46
|
+
num_skew_steps, endpoint=True).tolist()
|
47
|
+
|
48
|
+
angle = result.torque[-1]['angle']
|
49
|
+
T = result.torque[-1]['torque'][0:-1]
|
50
|
+
# get back-emf from BCH
|
51
|
+
for i in keyset:
|
52
|
+
bemf[i] = result.flux[i][-1]['voltage_dpsi'][0:-1]
|
53
|
+
|
54
|
+
angl_resl = angle[1]
|
55
|
+
tmp = np.unique(np.abs(skew_angle_array))
|
56
|
+
skew_angl_resl = 0.0
|
57
|
+
if np.amin(tmp) == 0.0:
|
58
|
+
skew_angl_resl = tmp[1]
|
59
|
+
else:
|
60
|
+
skew_angl_resl = tmp[0]
|
61
|
+
|
62
|
+
divider = skew_angl_resl/angl_resl
|
63
|
+
if divider - np.floor(divider) > 1e-15:
|
64
|
+
# TODO: Interpolation if angle resolution doesn't match
|
65
|
+
logger.warning("Wrong Mesh Size in the airgap mesh")
|
66
|
+
else:
|
67
|
+
logger.info(f"number of element shifted {divider}")
|
68
|
+
|
69
|
+
for i in skew_angle_array:
|
70
|
+
idx = int(i/angl_resl)
|
71
|
+
if i != 0:
|
72
|
+
T_slice.append(shift_array(T, idx))
|
73
|
+
for j in keyset:
|
74
|
+
bemf_slice[j].append(shift_array(bemf[j], idx))
|
75
|
+
else:
|
76
|
+
# do nothing
|
77
|
+
T_slice.append(T)
|
78
|
+
for j in keyset:
|
79
|
+
bemf_slice[j].append(bemf[j])
|
80
|
+
|
81
|
+
# average torque
|
82
|
+
T_sum = 0
|
83
|
+
for i in T_slice:
|
84
|
+
T_sum += np.array(i)
|
85
|
+
T_skew = (T_sum/num_skew_steps).tolist()
|
86
|
+
T_skew += [T_skew[0]]
|
87
|
+
T_fft = fft_filter(fft(angle, T_skew, pmod=2))
|
88
|
+
|
89
|
+
# average back-emf
|
90
|
+
for j in keyset:
|
91
|
+
flx_skew = 0
|
92
|
+
for k in bemf_slice[j]:
|
93
|
+
flx_skew+=np.array(k)
|
94
|
+
bemf_skew[j] = (flx_skew/num_skew_steps).tolist()
|
95
|
+
bemf_skew[j] += [bemf_skew[j][0]]
|
96
|
+
bemf_skew_fft[j] = fft_filter(fft(angle, bemf_skew[j], pmod=2))
|
97
|
+
|
98
|
+
for i in range(len(T_slice)):
|
99
|
+
T_slice[i] = (np.array(T_slice[i])/num_skew_steps).tolist()
|
100
|
+
T_slice[i]+=[T_slice[i][0]]
|
101
|
+
|
102
|
+
return {"angle": angle,
|
103
|
+
"cogging_torque": T_skew,
|
104
|
+
"cogging_torque_fft": T_fft,
|
105
|
+
"BEMF": bemf_skew,
|
106
|
+
"BEMF_fft": bemf_skew_fft,
|
107
|
+
"cogging_torque_slice": T_slice}
|
108
|
+
|
@@ -88,9 +88,6 @@ m.pole_width = ${model['pole_width']*1e3}
|
|
88
88
|
% if hasattr(model, 'lfe'):
|
89
89
|
m.arm_length = ${model.get(['lfe'])*1e3}
|
90
90
|
% endif
|
91
|
-
% if hasattr(model, 'lfe'):
|
92
|
-
m.arm_length = ${model.get(['lfe'])*1e3}
|
93
|
-
% endif
|
94
91
|
% if hasattr(model, 'winding'):
|
95
92
|
% if 'num_par_wdgs' in model.winding:
|
96
93
|
m.num_par_wdgs = ${model.winding['num_par_wdgs']}
|