femagtools 1.8.2__py3-none-any.whl → 1.8.4__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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