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.
Files changed (49) hide show
  1. femagtools/__init__.py +1 -1
  2. femagtools/dxfsl/area.py +110 -1
  3. femagtools/dxfsl/areabuilder.py +93 -45
  4. femagtools/dxfsl/conv.py +5 -0
  5. femagtools/dxfsl/converter.py +85 -27
  6. femagtools/dxfsl/fslrenderer.py +5 -4
  7. femagtools/dxfsl/functions.py +14 -6
  8. femagtools/dxfsl/geom.py +135 -149
  9. femagtools/dxfsl/journal.py +1 -1
  10. femagtools/dxfsl/machine.py +161 -9
  11. femagtools/dxfsl/shape.py +46 -1
  12. femagtools/dxfsl/svgparser.py +1 -1
  13. femagtools/dxfsl/symmetry.py +143 -38
  14. femagtools/femag.py +64 -61
  15. femagtools/fsl.py +15 -12
  16. femagtools/isa7.py +3 -2
  17. femagtools/machine/__init__.py +5 -4
  18. femagtools/machine/afpm.py +79 -33
  19. femagtools/machine/effloss.py +29 -18
  20. femagtools/machine/sizing.py +192 -13
  21. femagtools/machine/sm.py +34 -36
  22. femagtools/machine/utils.py +2 -2
  23. femagtools/mcv.py +58 -29
  24. femagtools/model.py +4 -3
  25. femagtools/multiproc.py +79 -80
  26. femagtools/parstudy.py +11 -5
  27. femagtools/plot/nc.py +2 -2
  28. femagtools/semi_fea.py +108 -0
  29. femagtools/templates/basic_modpar.mako +0 -3
  30. femagtools/templates/fe-contr.mako +18 -18
  31. femagtools/templates/ld_lq_fast.mako +3 -0
  32. femagtools/templates/mult_cal_fast.mako +3 -0
  33. femagtools/templates/pm_sym_f_cur.mako +4 -1
  34. femagtools/templates/pm_sym_fast.mako +3 -0
  35. femagtools/templates/pm_sym_loss.mako +3 -0
  36. femagtools/templates/psd_psq_fast.mako +3 -0
  37. femagtools/templates/torq_calc.mako +3 -0
  38. femagtools/tks.py +23 -20
  39. femagtools/zmq.py +213 -0
  40. {femagtools-1.8.1.dist-info → femagtools-1.8.3.dist-info}/METADATA +3 -3
  41. {femagtools-1.8.1.dist-info → femagtools-1.8.3.dist-info}/RECORD +49 -47
  42. {femagtools-1.8.1.dist-info → femagtools-1.8.3.dist-info}/WHEEL +1 -1
  43. tests/test_afpm.py +15 -6
  44. tests/test_femag.py +1 -1
  45. tests/test_fsl.py +4 -4
  46. tests/test_mcv.py +21 -15
  47. {femagtools-1.8.1.dist-info → femagtools-1.8.3.dist-info}/LICENSE +0 -0
  48. {femagtools-1.8.1.dist-info → femagtools-1.8.3.dist-info}/entry_points.txt +0 -0
  49. {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 == MAGCRV and self.mc1_curves > 1:
339
- self.mc1_type = ORIENT_CRV
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
- nfreq = len([1 for x in self.losses['f'] if x > 0])
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
- if len(losses) == nind:
640
- pl = p
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[:n], losses, Bo, fo)
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[n:]]
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
- for m in range(M_LOSS_FREQ - len(pfe)):
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 = filename.strip()
669
- self.name = os.path.splitext(filename)[0]
695
+ filename = pathlib.Path(filename)
696
+ self.name = filename.stem
670
697
 
671
- if filename.upper().endswith('.MCV') or \
672
- filename.upper().endswith('.MC'):
698
+ if filename.suffix.upper() in ('.MCV', '.MC'):
673
699
  binary = True
674
- self.fp = open(filename, "wb")
700
+ self.fp = filename.open(mode="wb")
675
701
  else:
676
702
  binary = False
677
- self.fp = open(filename, "wb")
678
- logger.info("Write File %s, binary format", filename)
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 = filename.strip()
768
+ filename = pathlib.Path(filename)
742
769
 
743
- if filename.upper().endswith('.MCV') or \
744
- filename.upper().endswith('.MC'):
770
+ if filename.suffix in ('.MCV', '.MC'):
745
771
  binary = True
746
- self.fp = open(filename, "rb")
772
+ self.fp = filename.open(mode="rb")
747
773
  else:
748
774
  binary = False
749
- self.fp = open(filename, "r")
775
+ self.fp = filename.open(mode="r")
750
776
 
751
- self.name = os.path.splitext(os.path.basename(filename))[0]
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
- # connect model even for complete model (see fsl connect_models)
146
- self.connect_full = True
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("MC pars missing: {}".format(
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.dirs = dirs
83
- self.num_cur_steps = num_cur_steps
30
+ self.protfiles = [femagtools.zmq.ProtFile(d, num_cur_steps)
31
+ for d in dirs]
32
+ self.numTot = len(dirs)
84
33
  self.running = False
85
34
  self.timestep = timestep
35
+ self.notify = notify
86
36
 
87
37
  def run(self):
88
38
  self.running = True
89
- protfiles = [ProtFile(d, self.num_cur_steps)
90
- for d in self.dirs]
91
39
  while self.running:
92
- time.sleep(self.timestep)
93
- logmsg = [p.update() for p in protfiles]
40
+ if self.timestep > 0:
41
+ time.sleep(self.timestep)
42
+ logmsg = [p.update() for p in self.protfiles]
94
43
  summary = [l # f'<{i}> {l}'
95
44
  for i, l in enumerate(logmsg)
96
45
  if l]
97
46
  if summary:
98
- labels = set([p.name for p in protfiles])
47
+ labels = set([p.name for p in self.protfiles])
99
48
  logger.info('%s: %s',
100
49
  ', '.join(labels),
101
50
  ', '.join(summary))
51
+ if self.notify:
52
+ numOf = f"{summary.count('100.0%')} of {self.numTot}"
53
+ percent = sum([float(i[:-1])
54
+ for i in summary]) / self.numTot
55
+ self.notify(
56
+ ["progress_logger",
57
+ f"{self.numTot}:{numOf}:{percent}:{' '.join(summary)}"])
102
58
  else:
103
59
  logger.info('collecting FE losses ...')
104
60
  return
@@ -107,7 +63,7 @@ class ProgressLogger(threading.Thread):
107
63
  self.running = False
108
64
 
109
65
 
110
- def run_femag(cmd, workdir, fslfile):
66
+ def run_femag(cmd, workdir, fslfile, port):
111
67
  """Start the femag command as subprocess.
112
68
 
113
69
  :internal:
@@ -120,7 +76,8 @@ def run_femag(cmd, workdir, fslfile):
120
76
  with open(os.path.join(workdir, "femag.out"), "wb") as out, \
121
77
  open(os.path.join(workdir, "femag.err"), "wb") as err:
122
78
  try:
123
- proc = subprocess.Popen(cmd + ['-b', fslfile],
79
+ args = ['-b', str(port), fslfile] if port else ['-b', fslfile]
80
+ proc = subprocess.Popen(cmd + args,
124
81
  shell=False,
125
82
  stdin=DEVNULL,
126
83
  stdout=out,
@@ -163,11 +120,16 @@ class Engine:
163
120
  cmd: the program (executable image) to be run
164
121
  (femag dc is used if None)
165
122
  process_count: number of processes (cpu_count() if None)
166
- progress_timestep: time step in seconds for progress log messages if > 0)
123
+ timestep: time step in seconds for progress log messages if > 0)
167
124
  """
168
125
 
169
126
  def __init__(self, **kwargs):
170
127
  self.process_count = kwargs.get('process_count', None)
128
+ self.notify = kwargs.get('notify', None)
129
+ # cogg_calc mode, subscribe xyplot
130
+ self.calc_mode = kwargs.get('calc_mode')
131
+ self.port = kwargs.get('port', 0)
132
+ self.curve_label = kwargs.get('curve_label')
171
133
  cmd = kwargs.get('cmd', '')
172
134
  if cmd:
173
135
  self.cmd = [cmd]
@@ -175,7 +137,10 @@ class Engine:
175
137
  self.cmd.append('-m')
176
138
 
177
139
  self.progressLogger = 0
178
- self.progress_timestep = kwargs.get('timestep', 5)
140
+ self.progress_timestep = kwargs.get('timestep', -1)
141
+ self.subscriber = None
142
+ self.job = None
143
+ self.tasks = []
179
144
 
180
145
  def create_job(self, workdir):
181
146
  """Create a FEMAG :py:class:`Job`
@@ -209,20 +174,41 @@ class Engine:
209
174
  t.cmd = [cfg.get_executable(
210
175
  t.stateofproblem)] + args
211
176
 
212
- self.pool = multiprocessing.Pool(self.process_count)
213
- self.tasks = [self.pool.apply_async(run_femag,
214
- args=(t.cmd,
215
- t.directory,
216
- t.fsl_file))
217
- for t in self.job.tasks]
177
+ num_proc = self.process_count
178
+ if not num_proc and multiprocessing.cpu_count() > 1:
179
+ num_proc = min(multiprocessing.cpu_count()-1, len(self.job.tasks))
180
+ self.pool = multiprocessing.Pool(num_proc)
181
+ if self.port:
182
+ header = [b'progress']
183
+ if self.calc_mode == 'cogg_calc':
184
+ header +=[b'xyplot']
185
+ self.subscriber = [SubscriberTask(port=self.port + i * 5,
186
+ host='127.0.0.1',
187
+ notify=self.notify,
188
+ header=header,
189
+ curve_label=self.curve_label,
190
+ num_cur_steps=self.job.num_cur_steps,
191
+ timestep=self.progress_timestep
192
+ )
193
+ for i, t in enumerate(self.job.tasks)]
194
+ [s.start() for s in self.subscriber]
195
+ self.tasks = [self.pool.apply_async(
196
+ run_femag, args=(t.cmd, t.directory, t.fsl_file, self.port + i * 5))
197
+ for i, t in enumerate(self.job.tasks)]
198
+ else:
199
+ self.tasks = [self.pool.apply_async(
200
+ run_femag, args=(t.cmd, t.directory, t.fsl_file, 0))
201
+ for t in self.job.tasks]
218
202
  self.pool.close()
219
203
 
220
- if (self.progress_timestep and
221
- self.job.num_cur_steps):
204
+ # only works on linux
205
+ if (self.progress_timestep and not self.port and
206
+ self.job.num_cur_steps):
222
207
  self.progressLogger = ProgressLogger(
223
208
  [t.directory for t in self.job.tasks],
224
209
  num_cur_steps=self.job.num_cur_steps,
225
- timestep=self.progress_timestep)
210
+ timestep=self.progress_timestep,
211
+ notify=self.notify)
226
212
  self.progressLogger.start()
227
213
  return len(self.tasks)
228
214
 
@@ -244,17 +230,30 @@ class Engine:
244
230
  if t.errmsg:
245
231
  logger.error(t.errmsg)
246
232
  status.append(t.status)
233
+ self.stopThreads()
234
+ self.pool = None # garbage collector deletes threads
235
+ return status
236
+
237
+ def stopThreads(self):
238
+ """ stop all running treads
239
+ """
247
240
  if self.progressLogger:
248
241
  self.progressLogger.stop()
249
- return status
242
+ if self.port and self.subscriber:
243
+ [s.stop() for s in self.subscriber]
244
+ SubscriberTask.clear()
245
+ self.subscriber = None
250
246
 
251
247
  def terminate(self):
248
+ """ terminate all
249
+ """
252
250
  logger.info("terminate Engine")
253
- if self.progressLogger:
254
- self.progressLogger.stop()
251
+ self.stopThreads()
252
+
255
253
  # terminate pool
256
254
  try:
257
- self.pool.terminate()
258
- self.pool.close()
255
+ if self.pool:
256
+ self.pool.terminate()
257
+ self.pool = None # garbage collector deletes threads
259
258
  except AttributeError as e:
260
259
  logger.warn("%s", e)
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.warn("job %d failed: %s", k, r['error'])
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']}