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/__init__.py +1 -1
- femagtools/dxfsl/area.py +47 -3
- femagtools/dxfsl/areabuilder.py +93 -45
- femagtools/dxfsl/conv.py +0 -5
- femagtools/dxfsl/converter.py +92 -33
- femagtools/dxfsl/fslrenderer.py +14 -5
- femagtools/dxfsl/geom.py +245 -210
- femagtools/dxfsl/machine.py +166 -11
- femagtools/dxfsl/shape.py +46 -1
- femagtools/dxfsl/svgparser.py +1 -1
- femagtools/dxfsl/symmetry.py +115 -30
- femagtools/ecloss.py +13 -8
- femagtools/femag.py +61 -29
- femagtools/fsl.py +21 -6
- femagtools/machine/__init__.py +5 -4
- femagtools/machine/afpm.py +41 -15
- femagtools/machine/sizing.py +189 -11
- femagtools/machine/sm.py +5 -11
- femagtools/machine/utils.py +2 -2
- femagtools/mcv.py +2 -3
- femagtools/model.py +4 -3
- femagtools/parstudy.py +1 -1
- femagtools/plot/nc.py +2 -2
- femagtools/templates/afm_rotor.mako +4 -0
- femagtools/templates/prepare_thermal.mako +57 -54
- {femagtools-1.8.0.dist-info → femagtools-1.8.2.dist-info}/METADATA +1 -1
- {femagtools-1.8.0.dist-info → femagtools-1.8.2.dist-info}/RECORD +32 -32
- tests/test_mcv.py +1 -1
- {femagtools-1.8.0.dist-info → femagtools-1.8.2.dist-info}/LICENSE +0 -0
- {femagtools-1.8.0.dist-info → femagtools-1.8.2.dist-info}/WHEEL +0 -0
- {femagtools-1.8.0.dist-info → femagtools-1.8.2.dist-info}/entry_points.txt +0 -0
- {femagtools-1.8.0.dist-info → femagtools-1.8.2.dist-info}/top_level.txt +0 -0
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
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
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
|
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
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
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
|
-
|
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.
|
353
|
-
model.
|
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:
|
femagtools/machine/__init__.py
CHANGED
@@ -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']
|
72
|
-
losses['
|
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['
|
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['
|
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:
|
femagtools/machine/afpm.py
CHANGED
@@ -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'] =
|
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(
|
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'] =
|
608
|
-
|
609
|
-
|
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,
|
femagtools/machine/sizing.py
CHANGED
@@ -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
|
-
|
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 =
|
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
|
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
|
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]
|
femagtools/machine/utils.py
CHANGED
@@ -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
|
-
|
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
|
339
|
-
self.
|
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
|