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.
Files changed (49) hide show
  1. femagtools/__init__.py +1 -1
  2. femagtools/bch.py +10 -6
  3. femagtools/dxfsl/area.py +69 -1
  4. femagtools/dxfsl/conv.py +53 -16
  5. femagtools/dxfsl/converter.py +273 -76
  6. femagtools/dxfsl/fslrenderer.py +18 -22
  7. femagtools/dxfsl/functions.py +38 -8
  8. femagtools/dxfsl/geom.py +112 -35
  9. femagtools/dxfsl/journal.py +1 -1
  10. femagtools/dxfsl/machine.py +44 -7
  11. femagtools/dxfsl/shape.py +4 -0
  12. femagtools/dxfsl/symmetry.py +105 -32
  13. femagtools/femag.py +64 -61
  14. femagtools/fsl.py +4 -2
  15. femagtools/isa7.py +3 -2
  16. femagtools/machine/afpm.py +45 -25
  17. femagtools/machine/effloss.py +31 -20
  18. femagtools/machine/im.py +6 -8
  19. femagtools/machine/sizing.py +4 -3
  20. femagtools/machine/sm.py +35 -37
  21. femagtools/mcv.py +66 -37
  22. femagtools/multiproc.py +79 -80
  23. femagtools/parstudy.py +10 -4
  24. femagtools/semi_fea.py +108 -0
  25. femagtools/templates/basic_modpar.mako +0 -3
  26. femagtools/templates/fe-contr.mako +18 -18
  27. femagtools/templates/ld_lq_fast.mako +3 -0
  28. femagtools/templates/mesh-airgap.mako +6 -0
  29. femagtools/templates/mult_cal_fast.mako +3 -0
  30. femagtools/templates/pm_sym_f_cur.mako +4 -1
  31. femagtools/templates/pm_sym_fast.mako +3 -0
  32. femagtools/templates/pm_sym_loss.mako +3 -0
  33. femagtools/templates/psd_psq_fast.mako +3 -0
  34. femagtools/templates/torq_calc.mako +3 -0
  35. femagtools/tks.py +23 -20
  36. femagtools/utils.py +1 -1
  37. femagtools/windings.py +11 -4
  38. femagtools/zmq.py +213 -0
  39. {femagtools-1.8.2.dist-info → femagtools-1.8.4.dist-info}/METADATA +3 -3
  40. {femagtools-1.8.2.dist-info → femagtools-1.8.4.dist-info}/RECORD +49 -47
  41. {femagtools-1.8.2.dist-info → femagtools-1.8.4.dist-info}/WHEEL +1 -1
  42. tests/test_afpm.py +15 -6
  43. tests/test_femag.py +1 -1
  44. tests/test_fsl.py +4 -4
  45. tests/test_mcv.py +20 -14
  46. tests/test_parident.py +2 -1
  47. {femagtools-1.8.2.dist-info → femagtools-1.8.4.dist-info}/LICENSE +0 -0
  48. {femagtools-1.8.2.dist-info → femagtools-1.8.4.dist-info}/entry_points.txt +0 -0
  49. {femagtools-1.8.2.dist-info → femagtools-1.8.4.dist-info}/top_level.txt +0 -0
femagtools/femag.py CHANGED
@@ -27,6 +27,7 @@ import femagtools.fsl
27
27
  import femagtools.config
28
28
  import femagtools.ecloss
29
29
  import femagtools.forcedens
30
+ import femagtools.zmq
30
31
 
31
32
  from femagtools import ntib
32
33
 
@@ -180,15 +181,22 @@ class BaseFemag(object):
180
181
  def copy_winding_file(self, name, wdg):
181
182
  wdg.write(name, self.workdir)
182
183
 
183
- def copy_magnetizing_curves(self, model, dir=None, recsin=''):
184
+ def copy_magnetizing_curves(self, model, dir=None, recsin='', feloss=''):
184
185
  """extract mc names from model and write files into workdir or dir if given
185
186
 
186
187
  Return:
187
188
  list of extracted mc names (:obj:`list`)
188
189
  """
189
190
  dest = dir if dir else self.workdir
191
+ if isinstance(feloss, (int, float)):
192
+ try:
193
+ feloss = {1: 'jordan', 11: 'bertotti'}[int(feloss)]
194
+ except KeyError:
195
+ feloss = ''
190
196
  return [self.magnetizingCurves.writefile(m[0], dest,
191
- fillfac=m[1], recsin=recsin)
197
+ fillfac=m[1],
198
+ recsin=recsin,
199
+ feloss=feloss)
192
200
  for m in model.set_magcurves(
193
201
  self.magnetizingCurves, self.magnets)]
194
202
 
@@ -212,9 +220,11 @@ class BaseFemag(object):
212
220
  self.model = femagtools.model.MachineModel(machine)
213
221
  self.modelname = self.model.name
214
222
  recsin = ''
223
+ feloss = ''
215
224
  if simulation:
216
225
  recsin = simulation.get('recsin', '')
217
- self.copy_magnetizing_curves(self.model, recsin=recsin)
226
+ feloss = simulation.get('calc_fe_loss', '')
227
+ self.copy_magnetizing_curves(self.model, recsin=recsin, feloss=feloss)
218
228
  try:
219
229
  if 'wdgdef' in self.model.winding:
220
230
  self.model.winding['wdgfile'] = self.create_wdg_def(
@@ -429,12 +439,15 @@ class BaseFemag(object):
429
439
  """
430
440
  Read the modelname from the Femag Log file
431
441
  """
432
- with open(os.path.join(self.workdir, 'FEMAG-FSL.log')) as f:
433
- for l in f:
434
- if l.startswith('New model') or l.startswith('Load model'):
435
- return l.split('"')[1]
442
+ try:
443
+ with open(os.path.join(self.workdir, 'FEMAG-FSL.log')) as f:
444
+ for l in f:
445
+ if l.startswith('New model') or l.startswith('Load model'):
446
+ return l.split('"')[1]
447
+ except FileNotFoundError:
448
+ pass
436
449
 
437
- raise ValueError(f"Model not found in {self.workdir}/'FEMAG-FSL.log'")
450
+ return list(pathlib.Path(self.workdir).glob('*.PROT'))[0].stem
438
451
 
439
452
  def readResult(self, simulation, bch=None):
440
453
  if simulation:
@@ -715,55 +728,6 @@ class FemagTask(threading.Thread):
715
728
  self.returncode = self.proc.wait()
716
729
 
717
730
 
718
- class SubscriberTask(threading.Thread):
719
- def __init__(self, port, host, notify):
720
- threading.Thread.__init__(self)
721
- context = zmq.Context.instance()
722
- self.subscriber = context.socket(zmq.SUB)
723
- if not host:
724
- host = 'localhost'
725
- self.subscriber.connect(f'tcp://{host}:{port}')
726
- self.subscriber.setsockopt(zmq.SUBSCRIBE, b'')
727
- self.controller = zmq.Context.instance().socket(zmq.PULL)
728
- self.controller_url = 'inproc://publisher'
729
- self.controller.bind(self.controller_url)
730
- self.poller = zmq.Poller()
731
- self.poller.register(self.subscriber, zmq.POLLIN)
732
- self.poller.register(self.controller, zmq.POLLIN)
733
- self.logger = logger
734
- self.notify = notify
735
-
736
- def stop(self):
737
- socket = zmq.Context.instance().socket(zmq.PUSH)
738
- socket.connect(self.controller_url)
739
- socket.send(b"quit")
740
- socket.close()
741
-
742
- def run(self):
743
- self.logger.info("subscriber is ready")
744
- while True:
745
- socks = dict(self.poller.poll())
746
- if socks.get(self.subscriber) == zmq.POLLIN:
747
- try:
748
- response = self.subscriber.recv_multipart()
749
- # Sometimes femag send messages with only len = 1. These messages must be ignored
750
- if len(response) < 2:
751
- continue
752
- self.notify([s.decode('latin1') for s in response])
753
-
754
- except Exception:
755
- self.logger.error(
756
- "error in subscription message processing", exc_info=True)
757
-
758
- if socks.get(self.controller) == zmq.POLLIN:
759
- req = self.controller.recv()
760
- self.logger.info("subscriber %s", req)
761
- break
762
- self.subscriber.close()
763
- self.controller.close()
764
- self.logger.debug("subscriber stopped")
765
-
766
-
767
731
  class ZmqFemag(BaseFemag):
768
732
  """Invoke and control execution of FEMAG with ZeroMQ
769
733
 
@@ -838,8 +802,8 @@ class ZmqFemag(BaseFemag):
838
802
  """attaches a notify function"""
839
803
  logger.info("Subscribe on '%s' port %d", self.femaghost, self.port+1)
840
804
  if self.subscriber is None:
841
- self.subscriber = SubscriberTask(
842
- self.port+1, self.femaghost, notify)
805
+ self.subscriber = femagtools.zmq.SubscriberTask(
806
+ port=self.port+1, host=self.femaghost, notify=notify)
843
807
  self.subscriber.start()
844
808
  else:
845
809
  # reattach?
@@ -1149,6 +1113,38 @@ class ZmqFemag(BaseFemag):
1149
1113
  logger.warning(response[0])
1150
1114
  return [s.decode('latin1') for s in response]
1151
1115
 
1116
+ def airgap_flux_density(self, pmod):
1117
+ # try to read bag.dat
1118
+ agr = self.getfile("bag.dat")
1119
+ status = json.loads(agr[0])['status']
1120
+ if status == 'ok':
1121
+ datfile = os.path.join(self.workdir, 'bag.dat')
1122
+ with open(datfile, 'wb') as bagfile:
1123
+ bagfile.write(agr[1])
1124
+ agi = ag.read(datfile, pmod)
1125
+ else:
1126
+ import numpy as np
1127
+ # try to read model file (TODO download with getfile)
1128
+ nc = self.read_nc()
1129
+ ag_elmnts = nc.airgap_center_elements
1130
+ logger.info("Airgap elements %d scale_factor %f",
1131
+ len(ag_elmnts), nc.scale_factor())
1132
+ if len(ag_elmnts) < 1:
1133
+ raise ValueError("Missing airgap elements")
1134
+ scf = 360/nc.scale_factor()/ag_elmnts[-1].center[0]
1135
+ pos = np.array([e.center[0]*scf for e in ag_elmnts])
1136
+ bxy = np.array([e.flux_density() for e in ag_elmnts]).T
1137
+ if np.max(bxy[0]) > np.max(bxy[1]):
1138
+ agi = ag.fft(pos, bxy[0], pmod)
1139
+ else:
1140
+ agi = ag.fft(pos, bxy[1], pmod)
1141
+ return dict(Bamp=agi['Bamp'],
1142
+ phi0=agi['phi0'],
1143
+ angle=agi['pos'],
1144
+ angle_fft=agi['pos'],
1145
+ B=agi['B'],
1146
+ B_fft=agi['B_fft'])
1147
+
1152
1148
  def interrupt(self):
1153
1149
  """send push message to control port to stop current calculation"""
1154
1150
  context = zmq.Context.instance()
@@ -1165,7 +1161,7 @@ class ZmqFemag(BaseFemag):
1165
1161
  wdg.write(name, self.workdir)
1166
1162
  self.upload(os.path.join(self.workdir, name+'.WID'))
1167
1163
 
1168
- def copy_magnetizing_curves(self, model, dir=None, recsin=''):
1164
+ def copy_magnetizing_curves(self, model, dir=None, recsin='', feloss=''):
1169
1165
  """extract mc names from model and write files into workdir or dir if given
1170
1166
  and upload to Femag
1171
1167
 
@@ -1175,9 +1171,16 @@ class ZmqFemag(BaseFemag):
1175
1171
  dest = dir if dir else self.workdir
1176
1172
  mc_names = [m for m in model.set_magcurves(
1177
1173
  self.magnetizingCurves, self.magnets)]
1174
+ if isinstance(feloss, (int, float)):
1175
+ try:
1176
+ feloss = {1: 'jordan', 11: 'bertotti'}[int(feloss)]
1177
+ except KeyError:
1178
+ feloss = ''
1178
1179
  for m in mc_names:
1179
1180
  f = self.magnetizingCurves.writefile(m[0], dest,
1180
- fillfac=m[1], recsin=recsin)
1181
+ fillfac=m[1],
1182
+ recsin=recsin,
1183
+ feloss=feloss)
1181
1184
  self.upload(os.path.join(dest, f))
1182
1185
  return mc_names
1183
1186
 
femagtools/fsl.py CHANGED
@@ -556,7 +556,6 @@ class Builder:
556
556
  if 'fsl_rotor' in conv:
557
557
  self.fsl_rotor = True
558
558
  th_props = ['']
559
- logger.info(model['magnet'])
560
559
  if hasattr(model, 'magnet'):
561
560
  if model['magnet'].get('thcond', 0):
562
561
  th_props = [f'rotor_density = {model["magnet"]["density"]}',
@@ -730,7 +729,7 @@ class Builder:
730
729
  custom_fefunc = ['']
731
730
  if pfefunc:
732
731
  sim['loss_funct'] = 1 # 3?
733
- if pfefunc == 'bertotti' or 'modified_steinmetz':
732
+ if pfefunc in ('bertotti', 'modified_steinmetz'):
734
733
  custom_fefunc = self.__render(sim['PVFE_FSL'], pfefunc)
735
734
  else:
736
735
  custom_fefunc = pfefunc.split('\n')
@@ -750,6 +749,9 @@ class Builder:
750
749
  revert_displ = self.create_displ_stator_rotor(
751
750
  sim['eccentricity'])
752
751
 
752
+ if sim.get('calculationMode') == 'pm_sym_f_cur':
753
+ if 'nload_ex_cur' in sim.keys(): # convert obsolete key
754
+ sim['noload_ex_cur'] = sim.pop('nload_ex_cur')
753
755
  felosses = custom_fefunc + self.create_fe_losses(sim)
754
756
  fslcalc = (displ_stator_rotor
755
757
  + self.__render(sim, sim.get('calculationMode'))
femagtools/isa7.py CHANGED
@@ -815,8 +815,9 @@ class Isa7(object):
815
815
  airgap_positions.append(axy[0][0])
816
816
  self.airgap_center_elements.append(se.elements)
817
817
 
818
- if len(self.airgap_center_elements) == 1:
819
- self.airgap_center_elements = self.airgap_center_elements[0]
818
+ if self.airgap_center_elements:
819
+ # TODO check airgap center
820
+ self.airgap_center_elements = self.airgap_center_elements[-1]
820
821
  if horiz:
821
822
  airgap_positions.append(np.min(nxy[:, 1]))
822
823
  else:
@@ -11,7 +11,7 @@ from .. import model
11
11
  from .. import utils
12
12
  from .. import windings
13
13
  from .. import femag
14
- from scipy.interpolate import RegularGridInterpolator, interp1d, RectBivariateSpline
14
+ from scipy.interpolate import make_interp_spline, RegularGridInterpolator, RectBivariateSpline
15
15
  from scipy.integrate import quad
16
16
  import copy
17
17
 
@@ -33,16 +33,21 @@ def num_agnodes(Q, p, pw, ag):
33
33
  pw: (float) pole width
34
34
  ag: (float) airgap height
35
35
  """
36
- num_nodes = np.arange(12, 120, 6)
36
+ num_nodes = np.arange(6, 120, 6)
37
37
  i = np.argmin(np.abs(pw/num_nodes - ag/2))
38
- if p*num_nodes[i-1] % Q:
38
+ nag = num_nodes[i-1]
39
+ if p*nag % Q:
39
40
  lcm = np.lcm(Q, 2*p)//p
40
- nmin, nmax = num_nodes[0]//lcm, num_nodes[-1]//lcm
41
+ nmin, nmax = 1, num_nodes[-1]//lcm
41
42
  num_nodes = np.array(
42
43
  [i*lcm for i in range(nmin, nmax) if i*lcm % 6 == 0])
43
44
  i = np.argmin(np.abs(pw/num_nodes - ag/2))
44
- # nodedist 0.5, 2, 4, 6
45
- return num_nodes[i-1]
45
+ if i > 0:
46
+ nag = num_nodes[i-1]
47
+ else:
48
+ nag = num_nodes[i]
49
+ # TODO nodedist 0.5, 2, 4, 6
50
+ return nag
46
51
 
47
52
  def _integrate(radius, pos, val):
48
53
  interp = RegularGridInterpolator((radius, pos), val)
@@ -53,7 +58,7 @@ def _integrate(radius, pos, val):
53
58
 
54
59
 
55
60
  def _integrate1d(radius, val):
56
- interp = interp1d(radius, val)
61
+ interp = make_interp_spline(radius, val, k=1)
57
62
  def func(x):
58
63
  return interp((x))
59
64
  return quad(func, radius[0], radius[-1])[0]
@@ -162,7 +167,6 @@ def parident(workdir, engine, temp, machine,
162
167
  for pw in pole_width:
163
168
  machine['num_agnodes'] = num_agnodes(Q1, p//2, pw,
164
169
  machine['airgap'])
165
-
166
170
  nlparvardef = {
167
171
  "decision_vars": [
168
172
  {"values": pole_width,
@@ -182,13 +186,13 @@ def parident(workdir, engine, temp, machine,
182
186
  parvardef = {
183
187
  "decision_vars": [
184
188
  # {"values": sorted(2*temp), "name": "magn_temp"},
185
- {"steps": num_beta_steps, "bounds": [beta_min, 0], "name": "angl_i_up"},
189
+ {"steps": num_beta_steps, "bounds": [beta_min, 0],
190
+ "name": "angl_i_up"},
186
191
  {"steps": num_cur_steps,
187
192
  "bounds": [i1_max/num_cur_steps, i1_max], "name": "current"}
188
193
  ]
189
194
  }
190
195
 
191
-
192
196
  ldq = []
193
197
  for magtemp in temp:
194
198
  nlcalc = dict(
@@ -381,7 +385,7 @@ def process(lfe, pole_width, machine, bch):
381
385
  num_slots = machine['stator']['num_slots']
382
386
  mmod = model.MachineModel(machine)
383
387
  slots_gen = mmod.stator['num_slots_gen']
384
- scale_factor =_get_scale_factor(model_type, num_slots, slots_gen)
388
+ scale_factor = _get_scale_factor(model_type, num_slots, slots_gen)
385
389
  endpos = [2*pw*1e3 for pw in pole_width]
386
390
  displ = [[d for d in r['linearForce'][0]['displ']
387
391
  if d < e*(1+1/len(r['linearForce'][0]['displ']))]
@@ -392,11 +396,14 @@ def process(lfe, pole_width, machine, bch):
392
396
  currents = [bch[0]['flux'][k][0]['current_k'][:n]
393
397
  for k in bch[0]['flux']]
394
398
  if len(pole_width) > 1:
399
+ # check homogenity:
400
+ if np.diff([len(d) for d in displ]).any():
401
+ raise ValueError(
402
+ f"inhomogenous number of steps: {[len(d) for d in displ]}")
403
+ lfx = [r['linearForce'][0]['force_x'] for r in bch]
395
404
  torque = _integrate(radius, rotpos[0], np.array(
396
405
  [r*scale_factor*np.array(fx[:-1])/l
397
- for l, r, fx in zip(lfe, radius,
398
- [r['linearForce'][0]['force_x']
399
- for r in bch])]))
406
+ for l, r, fx in zip(lfe, radius, lfx)]))
400
407
 
401
408
  voltage = {k: [scale_factor * np.array(ux[:-1])/l
402
409
  for l, ux in zip(lfe, [r['flux'][k][0]['voltage_dpsi']
@@ -404,6 +411,13 @@ def process(lfe, pole_width, machine, bch):
404
411
  for k in bch[0]['flux']}
405
412
  emf = [_integrate(radius, rotpos[0], np.array(voltage[k]))
406
413
  for k in voltage]
414
+
415
+ fluxxy = {k: [scale_factor * np.array(flx[:-1])/l
416
+ for l, flx in zip(lfe, [r['flux'][k][0]['flux_k']
417
+ for r in bch])]
418
+ for k in bch[0]['flux']}
419
+ flux = [_integrate(radius, rotpos[0], np.array(fluxxy[k]))
420
+ for k in fluxxy]
407
421
  else:
408
422
  r = radius[0]
409
423
  torque = [r*scale_factor*fx
@@ -412,6 +426,7 @@ def process(lfe, pole_width, machine, bch):
412
426
  for ux in bch[0]['flux'][k][0]['voltage_dpsi'][:-1]]
413
427
  for k in bch[0]['flux']}
414
428
  emf = [voltage[k][:n] for k in voltage]
429
+ flux = [fluxxy[k][:n] for k in fluxxy]
415
430
 
416
431
  pos = (rotpos[0]/np.pi*180)
417
432
  emffft = utils.fft(pos, emf[0])
@@ -474,6 +489,7 @@ def process(lfe, pole_width, machine, bch):
474
489
  'weights': weights.tolist(),
475
490
  'pos': pos.tolist(), 'r1': r1,
476
491
  'torque': torque,
492
+ 'flux_k': flux,
477
493
  'emf': emf,
478
494
  'emf_amp': emffft['a'], 'emf_angle': emffft['alfa0'],
479
495
  'freq': freq,
@@ -591,10 +607,10 @@ class AFPM:
591
607
  condMat: conductor material
592
608
  """
593
609
  def __init__(self, workdir, magnetizingCurves='.', magnetMat='',
594
- condMat=''):
610
+ condMat='', cmd=None):
595
611
  self.parstudy = parstudy.List(
596
612
  workdir, condMat=condMat, magnets=magnetMat,
597
- magnetizingCurves=magnetizingCurves)
613
+ magnetizingCurves=magnetizingCurves, cmd=cmd)
598
614
 
599
615
  def __call__(self, engine, machine, simulation, num_slices):
600
616
  """ run FE simulation
@@ -648,21 +664,24 @@ class AFPM:
648
664
  }
649
665
  machine['pole_width'] = np.pi * machine['inner_diam']/machine['poles']
650
666
  machine['lfe'] = machine['outer_diam'] - machine['inner_diam']
651
-
667
+ simulation['skew_displ'] = (simulation.get('skew_angle', 0)/180 * np.pi
668
+ * machine['inner_diam'])
652
669
  nlresults = {}
653
- if (simulation['calculationMode'] != 'cogg_calc' and
654
- 'poc' not in simulation):
670
+ if (simulation['calculationMode'] != 'cogg_calc'
671
+ and 'poc' not in simulation):
655
672
  nlcalc = dict(
656
673
  calculationMode="cogg_calc",
657
674
  magn_temp=simulation.get('magn_temp', 20),
658
675
  num_move_steps=60,
676
+ skew_linear=0,
677
+ skew_steps=0,
659
678
  poc=poc.Poc(machine['pole_width']),
660
679
  speed=0)
661
680
  logging.info("Noload simulation")
662
681
  nlresults = self.parstudy(parvardef,
663
682
  machine, nlcalc, engine)
664
683
  if nlresults['status'].count('C') != len(nlresults['status']):
665
- raise ValueError('Noload simulation failed %s', nlresults['status'])
684
+ raise ValueError(f'Noload simulation failed {nlresults["status"]}')
666
685
  nlresults.update(process(lfe, pole_width, machine, nlresults['f']))
667
686
 
668
687
  current_angles = nlresults['f'][0]['current_angles']
@@ -671,14 +690,15 @@ class AFPM:
671
690
  parameters={
672
691
  'phi_voltage_winding': current_angles})
673
692
  logger.info("Current angles: %s", current_angles)
674
- elif (simulation['calculationMode'] == 'cogg_calc' and
675
- 'poc' not in simulation):
693
+ elif (simulation['calculationMode'] == 'cogg_calc'
694
+ and 'poc' not in simulation):
676
695
  simulation['poc'] = poc.Poc(machine['pole_width'])
677
696
 
678
697
  lresults = self.parstudy(
679
- parvardef,
680
- {k: machine[k] for k in machine if k!= 'afm_rotor'},
681
- simulation, engine) # Note: imcomplete machine prevents rebuild
698
+ parvardef, machine, simulation, engine)
699
+ if lresults['status'].count('C') != len(lresults['status']):
700
+ raise ValueError(
701
+ f'{simulation["calculationMode"]} failed {lresults["status"]}')
682
702
 
683
703
  results = process(lfe, pole_width, machine, lresults['f'])
684
704
  if nlresults:
@@ -39,7 +39,7 @@ def iqd_tmech_umax(m, u1, with_mtpa, progress, speed_torque, iq, id, iex):
39
39
  finally:
40
40
  progress.close()
41
41
 
42
- def iqd_tmech_umax_multi(num_proc, ntmesh, m, u1, with_mtpa):
42
+ def iqd_tmech_umax_multi(num_proc, ntmesh, m, u1, with_mtpa, publish=0):
43
43
  """calculate iqd for sm and pm using multiproc
44
44
  """
45
45
  progress_readers = []
@@ -79,6 +79,16 @@ def iqd_tmech_umax_multi(num_proc, ntmesh, m, u1, with_mtpa):
79
79
  progress_readers.remove(r)
80
80
  else:
81
81
  if i % len(progress_readers) == 0:
82
+ if publish:
83
+ numTot = len(collected_msg)
84
+ numOf = f"{num_proc-numTot} of {num_proc}"
85
+ workdone = sum([float(i[:-1])
86
+ for i in collected_msg]) / numTot
87
+ logger.debug("numTot %d numOf %s workdone %s, %s",
88
+ numTot, numOf, workdone, collected_msg)
89
+ publish(('progress_logger',
90
+ f"{numTot}:{numOf}:{workdone}:"))
91
+ # {' '.join(collected_msg)}"))
82
92
  logger.info("Losses/Eff Map: %s",
83
93
  ', '.join(collected_msg))
84
94
  collected_msg = []
@@ -156,9 +166,9 @@ def _generate_mesh(n, T, nb, Tb, npoints):
156
166
  nmax = max(n)
157
167
  tmin, tmax = 0, max(T)
158
168
  tnum = npoints[1]
159
- tip = ip.interp1d(n, T)
169
+ tip = ip.make_interp_spline(n, T, k=1)
160
170
  if nb and Tb:
161
- tbip = ip.interp1d(nb, Tb)
171
+ tbip = ip.make_interp_spline(nb, Tb, k=1)
162
172
  else:
163
173
  def tbip(x): return 0
164
174
 
@@ -197,7 +207,7 @@ def efficiency_losses_map(eecpars, u1, T, temp, n, npoints=(60, 40),
197
207
  with_mtpa -- (optional) use mtpa if True (default), disables mtpv if False
198
208
  with_tmech -- (optional) use friction and windage losses (default)
199
209
  num_proc -- (optional) number of parallel processes (default 0)
200
- progress -- (optional) custom function for progress logging
210
+ progress -- (optional) custom function for progress logging (publishing)
201
211
  with_torque_corr -- (optional) T is corrected if out of range (default False)
202
212
 
203
213
  Returns:
@@ -250,41 +260,42 @@ def efficiency_losses_map(eecpars, u1, T, temp, n, npoints=(60, 40),
250
260
  logger.info("total speed,torque samples %d", ntmesh.shape[1])
251
261
  if isinstance(m, (PmRelMachine, SynchronousMachine)):
252
262
  if num_proc > 1:
253
- iqd = iqd_tmech_umax_multi(num_proc, ntmesh, m, u1, with_mtpa)
263
+ iqd = iqd_tmech_umax_multi(num_proc, ntmesh, m, u1, with_mtpa,
264
+ publish=progress)
254
265
  else:
255
266
  class ProgressLogger:
256
- def __init__(self, nsamples):
267
+ def __init__(self, nsamples, publish):
257
268
  self.n = 0
269
+ self.publish = publish
258
270
  self.nsamples = nsamples
259
271
  self.num_iv = round(nsamples/15)
260
272
  def __call__(self, iqd):
261
273
  self.n += 1
262
274
  if self.n % self.num_iv == 0:
263
- logger.info("Losses/Eff Map: %d%%",
264
- round(100*self.n/self.nsamples))
265
- if progress is None:
266
- progress = ProgressLogger(ntmesh.shape[1])
267
- else:
268
- try:
269
- progress.nsamples=ntmesh.shape[1]
270
- progress(0) # To check conformity
271
- progress.n = 0
272
- except:
273
- logger.warning("Invalid ProgressLogger given to efficiency_losses_map, using default one!")
274
- progress = ProgressLogger(ntmesh.shape[1])
275
+ workdone=round(100*self.n/self.nsamples)
276
+ if self.publish:
277
+ self.publish(
278
+ ('progress_logger',
279
+ f"{self.n}:{self.n} of {self.nsamples}:{workdone}"))
280
+ logger.info("Losses/Eff Map: %d%%", workdone)
281
+
282
+ progress_logger = ProgressLogger(ntmesh.shape[1], progress)
283
+ progress_logger.nsamples = ntmesh.shape[1]
284
+ progress_logger(0) # To check conformity
285
+ progress_logger.n = 0
275
286
  if with_tmech:
276
287
  iqd = np.array([
277
288
  m.iqd_tmech_umax(
278
289
  nt[1],
279
290
  2*np.pi*nt[0]*m.p,
280
- u1, log=progress, with_mtpa=with_mtpa)[:-1]
291
+ u1, log=progress_logger, with_mtpa=with_mtpa)[:-1]
281
292
  for nt in ntmesh.T]).T
282
293
  else:
283
294
  iqd = np.array([
284
295
  m.iqd_torque_umax(
285
296
  nt[1],
286
297
  2*np.pi*nt[0]*m.p,
287
- u1, log=progress, with_mtpa=with_mtpa)[:-1]
298
+ u1, log=progress_logger, with_mtpa=with_mtpa)[:-1]
288
299
  for nt in ntmesh.T]).T
289
300
 
290
301
  beta, i1 = betai1(iqd[0], iqd[1])
femagtools/machine/im.py CHANGED
@@ -35,6 +35,9 @@ def log_interp1d(x, y, kind='cubic'):
35
35
  logy = np.log10(yy)
36
36
  lin_interp = ip.interp1d(logx, logy, kind=kind, fill_value="extrapolate")
37
37
  def log_interp(zz): return np.power(10.0, lin_interp(np.log10(zz)))
38
+ # TODO check replace interp1d
39
+ #lin_interp = ip.make_interp_spline(logx, logy, bc_type='not-a-knot')
40
+ #def log_interp(zz): return np.power(10.0, lin_interp(np.log10(zz), nu=1))
38
41
  return log_interp
39
42
 
40
43
 
@@ -792,15 +795,10 @@ def parident(workdir, engine, f1, u1, wdgcon,
792
795
  tend = time.time()
793
796
  logger.info("Elapsed time %d s Status %s",
794
797
  (tend-tstart), status)
798
+ if any([x != 'C' for x in status]):
799
+ raise ValueError("AC simulation failed")
795
800
  # collect results
796
- results = []
797
- errors = []
798
- for t in job.tasks:
799
- if t.status == 'C':
800
- results.append(t.get_results())
801
- else:
802
- logger.warning("Status %s", t.status)
803
- results.append({})
801
+ results = [t.get_results() for t in job.tasks]
804
802
 
805
803
  i1_0 = results[0]['i1_0'].tolist()
806
804
  psi1_0 = results[0]['psi1_0'].tolist()
@@ -628,8 +628,9 @@ def get_sm_rotor_dimensions(A, psi1, lfe, Da2, par):
628
628
  mue0 = 4*np.pi*1e-7
629
629
 
630
630
  wf = wp - wc
631
- anr = par['airgap']*par['Ba']/mue0/par['J']/par['kqr']
632
-
631
+ NI = par['airgap']*par['Ba']/mue0
632
+ anr = NI/par['J']/par['kqr']
633
+ r['NI'] = NI
633
634
  a = np.tan(alphap/2)/2
634
635
  b = (wp/2 - wc + np.tan(alphap/2)*(Da2/2 - hp))/2
635
636
  hc = min(max(4*anr/(wc+wp), 0.75*hfmax), hfmax)
@@ -827,7 +828,7 @@ def afpm(pnom: float, speed: float, p: int, afmtype: str, **kwargs) -> dict:
827
828
  bns = taus*(Di+2*hs1) - bds
828
829
 
829
830
  hns = (-bns + np.sqrt(bns**2 + 4*ans*np.tan(taus)))/2/np.tan(taus)/kps
830
- hys = psi1/lfe/par['By']/kps
831
+ hys = psi1/2/lfe/par['By']/kps
831
832
 
832
833
  aw = ans * par['kq'] / layers / num_wires * kps
833
834