femagtools 1.8.0__py3-none-any.whl → 1.8.2__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/femag.py CHANGED
@@ -359,42 +359,71 @@ class BaseFemag(object):
359
359
 
360
360
  return dict()
361
361
 
362
- def read_hsn(self, modelname=None):
362
+ def read_hsn(self, modelname=None):
363
+ import numpy as np
363
364
  "read heat network result"
364
365
  _map = {
365
- "StZa": "plfe1",
366
+ "StZa": "plfe1",
366
367
  "outs": "-",
367
- "StJo": "plfe1",
368
+ "StJo": "plfe1",
368
369
  "Slot": "-",
369
- "Shaf": "-",
370
- "Iron": "plfe2",
370
+ "Shaf": "-",
371
+ "Iron": "plfe2",
371
372
  "PMag": "plmag",
372
373
  "PMag_1": "plmag",
373
374
  "PMag_2": "plmag",
374
375
  "PMag_3": "plmag",
375
376
  "PMag_4": "plmag",
376
- "W1 ": "plcu1",
377
- "W2 ": "plcu1",
377
+ "W1 ": "plcu1",
378
+ "W2 ": "plcu1",
378
379
  "W3 ": "plcu1"
379
380
  }
380
381
  if not modelname:
381
382
  modelname = self._get_modelname_from_log()
382
383
  hsn_list = sorted(glob.glob(os.path.join(
383
384
  self.workdir, modelname+'.hsn')))
384
- with open(hsn_list[-1], 'r') as f:
385
+ with open(hsn_list[-1], 'r') as f:
385
386
  hsn_data = json.load(f)
387
+
388
+ # area calculation
389
+ nc_file = self.read_nc(modelname)
390
+ slot_area = 0.0
391
+ wdg_area = []
392
+ for i in nc_file.windings:
393
+ for j in i.subregions:
394
+ wdg_area.append(j.area())
395
+
396
+ slot_area = np.sum(wdg_area).item()/3
397
+ magnet_area = 0.0
398
+ num_sreg_mag = 0
399
+ area = dict()
400
+ for i in nc_file.subregions:
401
+ if i.name in ("StZa", "StJo", "Iron"):
402
+ area[i.name] = i.area().item()
403
+ elif i.name == "PMag":
404
+ magnet_area += i.area().item()
405
+ num_sreg_mag += 1
406
+ else:
407
+ pass
408
+
409
+ area['PMag'] = magnet_area/num_sreg_mag
410
+ for i in ('W1', 'W2', 'W3'):
411
+ area[i] = slot_area
412
+
386
413
  pmag_index = []
387
- if "Nodes" in hsn_data:
388
- for k ,i in enumerate(hsn_data['Nodes']):
414
+ if "Nodes" in hsn_data:
415
+ for k ,i in enumerate(hsn_data['Nodes']):
389
416
  i.update({"mass": i['weight'], "losses": _map[i['Name']]})
390
- if "PMag" in i['Name']:
417
+ if "PMag" in i['Name']:
391
418
  pmag_index.append(k)
392
- if pmag_index:
393
- for i in range(len(pmag_index)):
394
- hsn_data["Nodes"][pmag_index[i]]['Name'] = f"PMag_{i+1}"
395
- with open(hsn_list[-1], 'w') as f:
419
+ if i['Name'].strip() in area.keys():
420
+ i.update({"area": area[i['Name'].strip()]})
421
+ if pmag_index:
422
+ for i in range(len(pmag_index)):
423
+ hsn_data["Nodes"][pmag_index[i]]['Name'] = f"PMag_{i+1}"
424
+ with open(hsn_list[-1], 'w') as f:
396
425
  json.dump(hsn_data, f)
397
- return hsn_data
426
+ return nc_file
398
427
 
399
428
  def _get_modelname_from_log(self):
400
429
  """
@@ -439,11 +468,13 @@ class BaseFemag(object):
439
468
  return {'t': ttemp[0], 'temperature': ttemp[1]}
440
469
 
441
470
  if simulation['calculationMode'] == 'hsn':
442
- try:
443
- hsn_result = self.read_hsn()
444
- except:
445
- pass
446
- model = self.read_nc()
471
+ model = None
472
+ try:
473
+ model = self.read_hsn()
474
+ except:
475
+ pass
476
+ if model is None:
477
+ model = self.read_nc()
447
478
  return model.get_minmax_temp()
448
479
 
449
480
  if not bch:
@@ -497,7 +528,8 @@ class BaseFemag(object):
497
528
  ops = [k for k in range(len(bch.torque))]
498
529
  m = femagtools.ecloss.MagnLoss(self.workdir, self.modelname, ibeta=ops)
499
530
  try:
500
- magn_losses = m.calc_losses()
531
+ # change from ialh to ialh2: since v1.8.1
532
+ magn_losses = m.calc_losses_ialh2()
501
533
  except:
502
534
  magn_losses = [0 for i in range(len(ops))]
503
535
 
@@ -513,11 +545,11 @@ class BaseFemag(object):
513
545
  bch.magnet_loss_th = m.th_loss
514
546
  except:
515
547
  pass
516
- try:
517
- if hasattr(self, 'dy2'):
548
+ try:
549
+ if hasattr(self, 'dy2'):
518
550
  setattr(bch, 'dy2', self.dy2)
519
- except:
520
- pass
551
+ except:
552
+ pass
521
553
  return bch
522
554
 
523
555
 
@@ -649,10 +681,10 @@ class Femag(BaseFemag):
649
681
  stateofproblem = 'mag_static'
650
682
 
651
683
  self.run(fslfile, options, fsl_args, stateofproblem=stateofproblem)
652
-
653
- try:
684
+
685
+ try:
654
686
  setattr(self, "dy2", machine['stator']['dy2'])
655
- except:
687
+ except:
656
688
  pass
657
689
  if simulation:
658
690
  return self.readResult(simulation)
femagtools/fsl.py CHANGED
@@ -157,10 +157,18 @@ class Builder:
157
157
  + model.stator['dxf']['fsl'])
158
158
  if templ == 'statorFsl':
159
159
  # obsolete
160
+ th_props = [' ']
161
+ try:
162
+ th_props = [f'stator_density = {model.stator["density"]}',
163
+ f'stator_thcond = {model.stator["thcond"]}',
164
+ f'stator_thcap = {model.stator["thcap"]}',
165
+ ]
166
+ except:
167
+ pass
160
168
  if 'parameter' in model.stator['statorFsl']:
161
169
  return self.render_template(
162
170
  model.stator['statorFsl']['content_template'],
163
- model.stator['statorFsl']['parameter'])
171
+ model.stator['statorFsl']['parameter']) + th_props
164
172
  elif model.stator['statorFsl'].get('content'):
165
173
  return (['agndst = {}'.format(model.get('agndst', 1e-3)*1e3),
166
174
  'ndt(agndst)'] +
@@ -214,14 +222,22 @@ class Builder:
214
222
  .format(model.magnet.get('mcvkey_yoke', 'dummy')),
215
223
  "mcvkey_shaft = '{}'"
216
224
  .format(model.magnet.get('mcvkey_shaft', 'dummy'))]
217
-
218
225
  if 'magnetFsl' in model.magnet:
219
226
  self.fsl_rotor = True
220
227
  # obsolete
228
+ th_props = [' ']
229
+ try:
230
+ logger.info(model.magnet)
231
+ th_props = [f'rotor_density = {model["magnet"]["density"]}',
232
+ f'rotor_thcond = {model["magnet"]["thcond"]}',
233
+ f'rotor_thcap = {model["magnet"]["thcap"]}'
234
+ ]
235
+ except:
236
+ pass
221
237
  if 'parameter' in model.magnet['magnetFsl']:
222
238
  return mcv + self.render_template(
223
239
  model.magnet['magnetFsl']['content_template'],
224
- model.magnet['magnetFsl']['parameter'])
240
+ model.magnet['magnetFsl']['parameter']) + th_props
225
241
  elif model.magnet['magnetFsl'].get('content'):
226
242
  return mcv + model.magnet['magnetFsl']['content'].split('\n')
227
243
  if isinstance(model.magnet['magnetFsl']
@@ -231,7 +247,6 @@ class Builder:
231
247
  templ = [l.strip() for l in f.readlines()]
232
248
  else:
233
249
  templ = model.magnet['magnetFsl']['content_template']
234
-
235
250
  return mcv + self.render_template(
236
251
  '\n'.join(templ),
237
252
  model.magnet['magnetFsl'])
@@ -349,8 +364,8 @@ class Builder:
349
364
  """return connect_model if rotating machine and incomplete model
350
365
  (Note: femag bug with connect model)"
351
366
  """
352
- if (model.get('move_action') == 0 and (
353
- model.connect_full or
367
+ if (model.connect_full or (
368
+ model.get('move_action', 0) == 0 and
354
369
  model.stator['num_slots'] > model.stator['num_slots_gen'])):
355
370
  fslcmds = ['pre_models("connect_models")\n']
356
371
  if 'thcond' in model.stator:
@@ -68,8 +68,9 @@ def create_from_eecpars(temp, eecpars, lfe=1, wdg=1):
68
68
  psid = rwdg*rlfe*dqp['psid']
69
69
  psiq = rwdg*rlfe*dqp['psiq']
70
70
  losses = __scale_losses(dqp['losses'], rlfe)
71
- losses['ef'] = dqpars[-1]['losses']['ef']
72
- losses['eh'] = dqpars[-1]['losses']['ef']
71
+ losses['ef'] = dqpars[-1]['losses'].get('ef', [2.0, 2.0])
72
+ losses['hf'] = dqpars[-1]['losses'].get('hf', [1.0, 1.0])
73
+ # TODO handle bertotti excess loss factor
73
74
 
74
75
  if 'psidq' in eecpars:
75
76
  machine = PmRelMachinePsidq(
@@ -140,7 +141,7 @@ def create(bch, r1, ls, lfe=1, wdg=1):
140
141
  try:
141
142
  losses = __scale_losses(bch.psidq['losses'], rlfe)
142
143
  losses['ef'] = bch.lossPar.get('ef', [2.0, 2.0])
143
- losses['eh'] = bch.lossPar.get('eh', [1.0, 1.0])
144
+ losses['hf'] = bch.lossPar.get('hf', [1.0, 1.0])
144
145
  except KeyError:
145
146
  losses = {}
146
147
  if 'ex_current' in bch.machine:
@@ -162,7 +163,7 @@ def create(bch, r1, ls, lfe=1, wdg=1):
162
163
  try:
163
164
  losses = __scale_losses(bch.ldq['losses'], rlfe)
164
165
  losses['ef'] = bch.lossPar.get('ef', [2.0, 2.0])
165
- losses['eh'] = bch.lossPar.get('eh', [1.0, 1.0])
166
+ losses['hf'] = bch.lossPar.get('hf', [1.0, 1.0])
166
167
  except KeyError:
167
168
  losses = {}
168
169
  if 'ex_current' in bch.machine:
@@ -25,6 +25,25 @@ AFM_TYPES = (
25
25
  "S2R1_all" # 2 stator, 1 rotor, all simulated
26
26
  )
27
27
 
28
+ def num_agnodes(Q, p, pw, ag):
29
+ """return total number of nodes in airgap per pole
30
+ Args:
31
+ Q: (int) number of slots
32
+ p: (int) number of pole pairs
33
+ pw: (float) pole width
34
+ ag: (float) airgap height
35
+ """
36
+ num_nodes = np.arange(12, 120, 6)
37
+ i = np.argmin(np.abs(pw/num_nodes - ag/2))
38
+ if p*num_nodes[i-1] % Q:
39
+ lcm = np.lcm(Q, 2*p)//p
40
+ nmin, nmax = num_nodes[0]//lcm, num_nodes[-1]//lcm
41
+ num_nodes = np.array(
42
+ [i*lcm for i in range(nmin, nmax) if i*lcm % 6 == 0])
43
+ i = np.argmin(np.abs(pw/num_nodes - ag/2))
44
+ # nodedist 0.5, 2, 4, 6
45
+ return num_nodes[i-1]
46
+
28
47
  def _integrate(radius, pos, val):
29
48
  interp = RegularGridInterpolator((radius, pos), val)
30
49
  def func(x, y):
@@ -141,10 +160,8 @@ def parident(workdir, engine, temp, machine,
141
160
 
142
161
  if "num_agnodes" not in machine:
143
162
  for pw in pole_width:
144
- machine['num_agnodes'] = 6*round(pw/machine['airgap']/4)
145
-
146
- #if machine['num_agnodes'] < nper:
147
- # machine['num_agnodes'] = 8*round(pw/machine['airgap']/4)
163
+ machine['num_agnodes'] = num_agnodes(Q1, p//2, pw,
164
+ machine['airgap'])
148
165
 
149
166
  nlparvardef = {
150
167
  "decision_vars": [
@@ -189,7 +206,8 @@ def parident(workdir, engine, temp, machine,
189
206
 
190
207
  nlresults = pstudy(nlparvardef, machine, nlcalc, engine)
191
208
  if nlresults['status'].count('C') != len(nlresults['status']):
192
- raise ValueError('Noload simulation failed %s', nlresults['status'])
209
+ raise ValueError(
210
+ f"Noload simulation failed {nlresults['status']}")
193
211
  else:
194
212
  nlresults = {"x": [], "f": []}
195
213
  i = 0
@@ -210,7 +228,7 @@ def parident(workdir, engine, temp, machine,
210
228
  nlresults['f'].append({k: v for k, v in r.items()})
211
229
  i = i + 1
212
230
  nlresults.update(process(lfe, pole_width, machine, nlresults['f']))
213
-
231
+ weights = nlresults['weights']
214
232
  current_angles = nlresults['f'][0]['current_angles']
215
233
  results = []
216
234
  i = 0
@@ -325,7 +343,7 @@ def parident(workdir, engine, temp, machine,
325
343
  (-1, num_beta_steps)),
326
344
  axis=1).T.tolist()})
327
345
  ldq.append({'temperature': magtemp,
328
- 'i1':i1, 'beta':beta,
346
+ 'i1': i1, 'beta': beta,
329
347
  'psid': psid.tolist(), 'psiq': psiq.tolist(),
330
348
  'ld': ld, 'lq': lq,
331
349
  'torque': torque.tolist(),
@@ -334,7 +352,8 @@ def parident(workdir, engine, temp, machine,
334
352
  #iq, id = femagtools.machine.utils.iqd(*np.meshgrid(beta, i1))
335
353
 
336
354
  return {'m': machine[wdgk]['num_phases'],
337
- 'p': machine['poles']//2,
355
+ 'p': machine['poles']//2, 'weights': weights,
356
+ 'rotor_mass': sum(weights[1]), "kfric_b": 1,
338
357
  'ls1': 0, 'r1': r1, 'ldq': ldq}
339
358
 
340
359
 
@@ -442,8 +461,17 @@ def process(lfe, pole_width, machine, bch):
442
461
  mmod.outer_diam, mmod.inner_diam)
443
462
  i1 = np.mean([np.max(c) for c in currents])/np.sqrt(2)
444
463
  plcu = mmod.winding['num_phases']*i1**2*r1
464
+ weights = np.array([[0, 0, 0], [0, 0, 0]])
465
+ try:
466
+ for b in bch:
467
+ weights = weights + b['weights']
468
+ weights *= scale_factor
469
+ except KeyError as exc:
470
+ #logger.warning("missing key %s", exc)
471
+ pass
445
472
 
446
473
  return {
474
+ 'weights': weights.tolist(),
447
475
  'pos': pos.tolist(), 'r1': r1,
448
476
  'torque': torque,
449
477
  'emf': emf,
@@ -603,14 +631,12 @@ class AFPM:
603
631
  for pw in pole_width]
604
632
 
605
633
  if "num_agnodes" not in machine:
634
+ Q1 = machine['stator']['num_slots']
635
+ p = machine['poles']
606
636
  for pw in pole_width:
607
- machine['num_agnodes'] = 6*round(pw/machine['airgap']/4)
608
- #Q = machine['stator']['num_slots']
609
- #p = machine['poles']
610
- #nper = np.lcm(Q, p)
611
- #if machine['num_agnodes'] < nper:
612
- # machine['num_agnodes'] = 8*round(pw/machine['airgap']/4)
613
-
637
+ machine['num_agnodes'] = num_agnodes(Q1, p//2, pw,
638
+ machine['airgap'])
639
+ logger.info("Num agnodes/pole %d", machine['num_agnodes'])
614
640
  parvardef = {
615
641
  "decision_vars": [
616
642
  {"values": pole_width,
@@ -1,6 +1,6 @@
1
1
  """general design of an AC electrical machine
2
2
 
3
- Types: spm, ipm, eesm, im
3
+ Types: spm, ipm, eesm, im, afpm
4
4
 
5
5
  """
6
6
  import numpy as np
@@ -10,6 +10,25 @@ from .utils import wdg_resistance
10
10
 
11
11
  logger = logging.getLogger("femagools.machine.sizing")
12
12
 
13
+ AFPM_DEFAULTS = dict(
14
+ airgap=2e-3,
15
+ kd=0.7, # ratio of inner vs outer diameter 0.6 .. 0.8
16
+ eta=0.92, # efficiency
17
+ cos_phi=0.95, # power factor
18
+ m=3, # number of phases
19
+ ui_u=0.8, # U ind / U
20
+ J=8.5e6, # current density A/mm²
21
+ sigmas=40e3, # shear force 30 .. 65 kN/m2
22
+ Ba=0.7, # flux density in airgap
23
+ Bth=1.5, # flux density in teeth 1.5 .. 2 T
24
+ By=1.2, # flux density in yoke 1.2 .. 1.5 T
25
+ kq=0.6, # stator winding fill factor 0.35 .. 0.75
26
+ mag_width=0.95, # rel magnet width 0.6 .. 1
27
+ Hc=700, # max. coercitive field strength, 500 .. 900 kA/m
28
+ brem=1.2, # remanence 0.3 .. 1.3 T
29
+ demag=6 # safety factor for demagnetisation (nom current)
30
+ )
31
+ """default sizing parameters for AFPM"""
13
32
 
14
33
  PM_DEFAULTS = dict(
15
34
  airgap=1.5e-3, # airgap width m
@@ -217,7 +236,7 @@ def _stator_slots(par, slots):
217
236
  # check sim factor, height/width ratio
218
237
  if qhbmin[1] == len(q)-1:
219
238
  # last has smallest sim factor
220
- return q[qhbmin[1]][0]
239
+ return q[qhbmin[1]][0]
221
240
  elif q[qhbmin[1]][2] < q[qhbmin[1]+1][2]:
222
241
  # select ideal height/width ratio (3.2)
223
242
  return q[qhbmin[1]][0]
@@ -270,7 +289,7 @@ def get_stator_dimensions(par, slots=[]):
270
289
  Ui_U = par['ui_u']
271
290
 
272
291
  # design parameters
273
- # AJ = papyr['AJ'] # 100*1e9 # thermal load, typically 100 .. 300 A²/mm³
292
+ # AJ = par['AJ'] # 100*1e9 # thermal load, typically 100 .. 300 A²/mm³
274
293
  J = par['J'] # current density 3 .. 6 A/mm²
275
294
  Ba = par['Ba'] # airgap flux density, typically 0.6 .. 1.5 T
276
295
  kq = par['kq'] # winding fill factor, typically 0.35 .. 0.75
@@ -431,15 +450,13 @@ def get_stator_dimensions(par, slots=[]):
431
450
 
432
451
  return r
433
452
 
434
-
435
453
  def _get_magnet_height(I1, N, kw, par):
436
454
  airgap = par['airgap']
437
455
  Ba = par['Ba']
438
456
  p = par['p']
439
457
  m = par['m']
440
458
 
441
- Hc = par['Hc']
442
- Hc = Hc*1e3 # unit kA/m
459
+ Hc = par['Hc']*1e3 # unit kA/m -> A/m
443
460
  # Safety Factor for demagnetization
444
461
  demag = par['demag']
445
462
  THETA1 = m/np.pi*np.sqrt(2)*I1*N*kw/p
@@ -648,6 +665,10 @@ def _set_defaults(par, defaults):
648
665
  par[k] = defaults[k]
649
666
 
650
667
 
668
+ def _set_afpm_defaults(par):
669
+ _set_defaults(par, AFPM_DEFAULTS)
670
+
671
+
651
672
  def _set_pm_defaults(par):
652
673
  _set_defaults(par, PM_DEFAULTS)
653
674
 
@@ -708,6 +729,163 @@ def spm(pnom: float, speed: float, p: int, **kwargs) -> dict:
708
729
  return r
709
730
 
710
731
 
732
+ def afpm(pnom: float, speed: float, p: int, afmtype: str, **kwargs) -> dict:
733
+ """returns dimension of a AFPM machine
734
+
735
+ Args:
736
+ pnom: power at rated speed (W)
737
+ speed: rotation speed (1/s)
738
+ p: number of pole pairs
739
+ afmtype: one of 'S1R1', 'S2R1', 'S1R2'
740
+
741
+ udc: (optional) DC link voltage (V)
742
+ u1: (optional) phase voltage (Vrms)
743
+ Q1: (optional) total number of stator slots
744
+ brem: (optional) remanence of magnet (T)
745
+ """
746
+ par = dict(
747
+ pnom=pnom, speed=speed, p=p)
748
+ par.update(kwargs)
749
+ _set_afpm_defaults(par)
750
+
751
+ kp = 1 if afmtype == 'S1R1' else 2
752
+ kps = 1 if afmtype in ('S1R1', 'S1R2') else 2
753
+ kpr = 2 if afmtype == 'S1R2' else 1
754
+ kd = par['kd'] # ratio of inner_diam/outer_diam
755
+ tnom = pnom/(2*np.pi*speed)
756
+ sigmas = par['sigmas'] # shear force
757
+ # outer diameter:
758
+ # https://web.mit.edu/kirtley/binlustuff/literature/electric%20machine/designOfAxialFluxPMM.pdf
759
+ Do = 2*np.power(tnom/(kp*sigmas*np.pi*kd*(1-kd**2)), 1/3)
760
+ Di = Do*kd
761
+ # pole width and iron length
762
+ Davg = (Do+Di)/2
763
+ taup = np.pi * Davg/(2*p)
764
+ lfe = (Do-Di)/2
765
+ # flux density in airgap
766
+ Bd1 = 4.0/np.pi*par['Ba']*np.sin(np.pi/2.0*par['mag_width'])
767
+
768
+ # rated phase voltage
769
+ if 'udc' in par:
770
+ u1nom = 0.9*par['udc']/np.sqrt(2)/np.sqrt(3)
771
+ else:
772
+ u1nom = par['u1']
773
+ f1 = speed*p
774
+ # flux linkage
775
+ psi1 = kpr*2.0/np.pi*taup*lfe*Bd1
776
+
777
+ # winding factor
778
+ Q1 = par['Q1']
779
+ m = par['m']
780
+ yd = par.get('coil_span', 0)
781
+ if yd:
782
+ wdg = Winding({'Q': par['Q1'], 'p': par['p'], 'm': 3,
783
+ 'yd': yd, 'l': 2})
784
+ else:
785
+ wdg = Winding({'Q': par['Q1'], 'p': par['p'], 'm': 3, 'l': 2})
786
+
787
+ kw = wdg.kw()
788
+
789
+ Ui = par['ui_u'] * u1nom
790
+ N = np.sqrt(2)*Ui/(2*np.pi*f1*kw*psi1)
791
+
792
+ # feasible number of turns per coil...
793
+ layers = wdg.l
794
+ # coils per phase (kps: number of stators)
795
+ ncoils = kps*Q1 // 2 // m * layers
796
+ ngroups = [1] + [g for g in range(2, layers*p + 1) if layers * p % g == 0]
797
+ ndiff = [abs(N - ncoils // a * a * round(N / ncoils))
798
+ for a in ngroups]
799
+ logger.debug("N %f ngroups %s ndiffs %s", N, ngroups, ndiff)
800
+ # parallel groups
801
+ a_calc = ngroups[np.argmin(ndiff)]
802
+ a = par.get("a", a_calc)
803
+ if a not in ngroups:
804
+ logger.warning("Check given number %s of parallel wdg groups. Valid ngroups are: %s",
805
+ a, ngroups)
806
+ # num wires per coil side (number of coil groups a)
807
+ num_wires = round(a * N / ncoils)
808
+
809
+ # correction of number of turns per phase
810
+ N_old = N
811
+ N = num_wires * ncoils / a
812
+
813
+ # correction of voltage
814
+ Ui = Ui/N_old*N
815
+ u1nom = Ui/par['ui_u']
816
+
817
+ # current loading
818
+ # A = np.sqrt(2)*sigmas/kw/Ba
819
+ I1 = pnom/(m*par['eta']*par['cos_phi']*u1nom)
820
+ A = 2*m*N*I1/np.pi/Di
821
+ # slot area
822
+ # J = AJ/A
823
+ hs1 = 1e-3 # slot opening height
824
+ taus = np.pi/Q1
825
+ ans = taus*Di*A/(par['kq']*par['J'])
826
+ bds = taus*(Di+2*hs1)*Bd1/par['Bth']
827
+ bns = taus*(Di+2*hs1) - bds
828
+
829
+ hns = (-bns + np.sqrt(bns**2 + 4*ans*np.tan(taus)))/2/np.tan(taus)/kps
830
+ hys = psi1/lfe/par['By']/kps
831
+
832
+ aw = ans * par['kq'] / layers / num_wires * kps
833
+
834
+ r = {'outer_diam': Do, 'inner_diam': Di, 'airgap': par['airgap']/kp,
835
+ 'afmtype': afmtype, 'lfe': lfe,
836
+ 'poles': 2*p,
837
+ 'ans': round(ans, 6),
838
+ 'hns': round(hns, 4),
839
+ 'bns': round(bns, 4),
840
+ 'A': round(A, 3),
841
+ 'AJ': round(par['J']*A, 0),
842
+ 'w1': int(N),
843
+ 'kw': round(kw, 4),
844
+ 'ess': round(1e-3*pnom/(60*speed)/(Do**2*lfe), 4),
845
+ 'q': wdg.q,
846
+ 'i1': round(I1, 3), # np.pi*Da1*A/2/m/N
847
+ 'psi1': round(psi1, 5),
848
+ 'u1': u1nom,
849
+ 'ui': Ui}
850
+ hs1 = 0
851
+ hs2 = 0
852
+ r['stator'] = dict(
853
+ u1nom=round(u1nom, 1), f1nom=round(f1, 1),
854
+ num_slots=Q1,
855
+ nodedist=1,
856
+ # num_slots_gen = req_poles*Q1/2/p,
857
+ afm_stator=dict(
858
+ slot_width=r['bns'],
859
+ slot_height=hs1+r['hns'],
860
+ slot_h1=hs1,
861
+ slot_h2=hs1,
862
+ slot_open_width=r['bns'],
863
+ slot_r1=0,
864
+ slot_r2=0, # bns2/2,
865
+ yoke_height=round(hys, 4) if kpr == 1 else 0))
866
+
867
+ relculen = 1.4
868
+ r['winding'] = dict(
869
+ #wire_diam=round(dwire, 5),
870
+ num_phases=m,
871
+ cufilfact=par['kq'],
872
+ culength=relculen,
873
+ num_par_wdgs=a,
874
+ num_layers=layers,
875
+ #resistance=round(r1, 4),
876
+ coil_span=wdg.yd,
877
+ num_wires=int(num_wires))
878
+
879
+ hm = _get_magnet_height(r['i1'], r['w1'], r['kw'], par)/kpr
880
+ r['magnet'] = dict(
881
+ afm_rotor=dict(
882
+ yoke_height=round(hys/kpr, 4) if kps == 1 else 0,
883
+ rel_magn_width=par['mag_width'],
884
+ magn_height=round(hm, 4))
885
+ )
886
+ return r
887
+
888
+
711
889
  def ipm(pnom: float, speed: float, p: int, **kwargs) -> dict:
712
890
  """returns dimension of a IPM machine
713
891
 
@@ -808,11 +986,11 @@ def eesm(pnom: float, speed: float, p: int, **kwargs) -> dict:
808
986
 
809
987
 
810
988
  if __name__ == "__main__":
811
-
812
- pnom = 10e3
813
- speed = 4400/60
814
- p = 4
815
- udc = 600
989
+ # sizing example with SPM
990
+ pnom = 10e3 # shaft power in W
991
+ speed = 4400/60 # speed in 1/s
992
+ p = 4 # number of pole pairs
993
+ udc = 600 # DC voltage in V
816
994
 
817
995
  r = spm(pnom, speed, p, udc=udc)
818
996
 
femagtools/machine/sm.py CHANGED
@@ -344,13 +344,13 @@ class SynchronousMachine(object):
344
344
  freq = w/2/np.pi
345
345
  kr = self.zeta1[0]*freq**3 + self.zeta1[1]*freq**2 + \
346
346
  self.zeta1[2]*freq + self.zeta1[3]
347
- if isinstance(kr, list):
347
+ if isinstance(kr, list):
348
348
  kr = np.array(kr)
349
349
  kr[kr<1.0] = 1.0
350
350
  elif isinstance(kr, np.ndarray):
351
351
  kr[kr<1.0] = 1.0
352
- else:
353
- if kr < 1.0:
352
+ else:
353
+ if kr < 1.0:
354
354
  kr = 1.0
355
355
  return self.r1*(1 + self.kth1*(self.tcu1 - 20))*kr # ref 20°C
356
356
  sr = self.skin_resistance[0]
@@ -806,10 +806,7 @@ class SynchronousMachinePsidq(SynchronousMachine):
806
806
  if 'styoke_excess' in eecpars[idname][0]['losses'] and \
807
807
  np.any(np.array(eecpars[idname][0]['losses']['styoke_excess'])):
808
808
  self.bertotti = True
809
- keys.update({{
810
- 'styoke_excess': 1.5,
811
- 'stteeth_excess':1.5,
812
- 'rotor_excess': 1.5}})
809
+ keys += ['styoke_excess', 'stteeth_excess','rotor_excess']
813
810
  if islinear:
814
811
  pfe = {k: np.array([l['losses'][k]
815
812
  for l in eecpars[idname]])
@@ -926,10 +923,7 @@ class SynchronousMachineLdq(SynchronousMachine):
926
923
  if 'styoke_excess' in eecpars[idname][0]['losses'] and \
927
924
  np.any(np.array(eecpars[idname][0]['losses']['styoke_excess'])):
928
925
  self.bertotti = True
929
- keys.update({{
930
- 'styoke_excess': 1.5,
931
- 'stteeth_excess':1.5,
932
- 'rotor_excess': 1.5}})
926
+ keys += ['styoke_excess', 'stteeth_excess','rotor_excess']
933
927
 
934
928
  if islinear:
935
929
  pfe = {k: np.array([l['losses'][k]
@@ -449,8 +449,8 @@ def dqparident(workdir, engine, temp, machine,
449
449
  leakfile.unlink(missing_ok=True)
450
450
 
451
451
  period_frac = kwargs.get('period_frac', 6)
452
- if machine.get('external_rotor', False):
453
- period_frac = 1 # TODO: missing femag support
452
+ if machine.get('external_rotor', False) and period_frac > 1:
453
+ logger.warning("period frac for external rotor requires GT femag version >= 2024")
454
454
 
455
455
  if dqtype == 'ldq':
456
456
  simulation = dict(
femagtools/mcv.py CHANGED
@@ -335,9 +335,8 @@ class Mcv(object):
335
335
  self.setData(data)
336
336
 
337
337
  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):
338
+ if (self.mc1_type in (ORIENT_CRV, ORIENT_PM_CRV)
339
+ or self.mc1_curves > 1):
341
340
  self.version_mc_curve = self.ORIENTED_VERSION_MC_CURVE
342
341
  elif self.mc1_type == DEMCRV_BR:
343
342
  self.version_mc_curve = self.PARAMETER_PM_CURVE