femagtools 1.8.1__py3-none-any.whl → 1.8.3__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/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']}
|