femagtools 1.6.8__py3-none-any.whl → 1.7.0__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 +2 -2
- femagtools/bch.py +1 -1
- femagtools/dxfsl/area.py +334 -332
- femagtools/dxfsl/areabuilder.py +131 -10
- femagtools/dxfsl/conv.py +27 -9
- femagtools/dxfsl/converter.py +390 -125
- femagtools/dxfsl/corner.py +3 -0
- femagtools/dxfsl/femparser.py +1 -1
- femagtools/dxfsl/fslrenderer.py +290 -246
- femagtools/dxfsl/functions.py +4 -2
- femagtools/dxfsl/geom.py +1120 -886
- femagtools/dxfsl/journal.py +53 -22
- femagtools/dxfsl/machine.py +250 -74
- femagtools/dxfsl/plotrenderer.py +34 -3
- femagtools/dxfsl/shape.py +380 -103
- femagtools/dxfsl/symmetry.py +679 -110
- femagtools/femag.py +27 -2
- femagtools/forcedens.py +65 -40
- femagtools/fsl.py +71 -28
- femagtools/losscoeffs.py +46 -0
- femagtools/machine/effloss.py +8 -1
- femagtools/machine/im.py +3 -1
- femagtools/machine/pm.py +11 -7
- femagtools/machine/sizing.py +14 -11
- femagtools/machine/sm.py +114 -33
- femagtools/machine/utils.py +38 -34
- femagtools/model.py +12 -2
- femagtools/moo/population.py +1 -1
- femagtools/parstudy.py +17 -1
- femagtools/plot/__init__.py +1 -1
- femagtools/plot/char.py +24 -7
- femagtools/plot/forcedens.py +56 -29
- femagtools/plot/mcv.py +4 -1
- femagtools/plot/phasor.py +6 -1
- femagtools/poc.py +17 -10
- femagtools/templates/cogg_calc.mako +7 -1
- femagtools/templates/displ_stator_rotor.mako +33 -0
- femagtools/templates/fieldcalc.mako +10 -16
- femagtools/templates/pm_sym_f_cur.mako +1 -1
- femagtools/tks.py +3 -9
- {femagtools-1.6.8.dist-info → femagtools-1.7.0.dist-info}/LICENSE +1 -0
- {femagtools-1.6.8.dist-info → femagtools-1.7.0.dist-info}/METADATA +7 -4
- {femagtools-1.6.8.dist-info → femagtools-1.7.0.dist-info}/RECORD +50 -49
- tests/engines/__init__.py +0 -20
- tests/geom/__init__.py +0 -20
- tests/moo/__init__.py +0 -20
- tests/test_model.py +8 -1
- {femagtools-1.6.8.dist-info → femagtools-1.7.0.dist-info}/WHEEL +0 -0
- {femagtools-1.6.8.dist-info → femagtools-1.7.0.dist-info}/entry_points.txt +0 -0
- {femagtools-1.6.8.dist-info → femagtools-1.7.0.dist-info}/top_level.txt +0 -0
femagtools/machine/sizing.py
CHANGED
@@ -6,7 +6,7 @@
|
|
6
6
|
import numpy as np
|
7
7
|
import logging
|
8
8
|
|
9
|
-
logger = logging.getLogger("femagools.
|
9
|
+
logger = logging.getLogger("femagools.machine.sizing")
|
10
10
|
|
11
11
|
|
12
12
|
PM_DEFAULTS = dict(
|
@@ -94,13 +94,6 @@ def wdg_resistance(w1, l, d):
|
|
94
94
|
return 2*w1*l/S/a
|
95
95
|
|
96
96
|
|
97
|
-
def current_losses(w1, l, d, i1):
|
98
|
-
# w1: num turns/phase
|
99
|
-
# l: wire length/slot
|
100
|
-
# d: wire diameter
|
101
|
-
return wdg_resistance(w1, l, d) * i1**2
|
102
|
-
|
103
|
-
|
104
97
|
def check_symmetry_conditions(Q1, p, layers, m):
|
105
98
|
# if Q1 % 2*p == 0:
|
106
99
|
# raise ValidationError("Number of slots Q and poles 2*p are not prime to each other")
|
@@ -227,6 +220,10 @@ def _stator_slots(par, slots):
|
|
227
220
|
|
228
221
|
|
229
222
|
def get_stator_dimensions(par, slots=[]):
|
223
|
+
# Check symmetry
|
224
|
+
if 'Q1' in par:
|
225
|
+
from ..windings import Winding
|
226
|
+
wdg = Winding({'Q': par['Q1'], 'p': par['p'], 'm': 3})
|
230
227
|
# nominal (rated) operating parameters
|
231
228
|
pnom = par['pnom']
|
232
229
|
speednom = par['speed']
|
@@ -380,12 +377,16 @@ def get_stator_dimensions(par, slots=[]):
|
|
380
377
|
middle_line=0 if layers < 2 else middle_line))
|
381
378
|
# coils per phase and pole
|
382
379
|
# q = Q/2/p/m
|
383
|
-
# num wires per coil side
|
380
|
+
# num wires per coil side (number of coil groups a)
|
384
381
|
# n = a*N / 2 / p / q
|
385
|
-
|
382
|
+
ncoils = Q1//2//m*layers
|
383
|
+
ngroups = [1] + [g for g in range(2, p+1) if p%g == 0]
|
384
|
+
a = ngroups[np.argmin([abs(N-ncoils//a*round(a*N/ncoils))
|
385
|
+
for a in ngroups])]
|
386
|
+
num_wires = round(a*N/ncoils)
|
386
387
|
dwire = 2*np.sqrt(ans*kq/layers/np.pi/num_wires)
|
387
388
|
relculen = 1.4
|
388
|
-
r1 = wdg_resistance(N, relculen*lfe, dwire)
|
389
|
+
r1 = wdg_resistance(N, relculen*lfe, dwire)/a
|
389
390
|
r['pcu'] = round(m*r1*I1**2, 1)
|
390
391
|
|
391
392
|
r['winding'] = dict(
|
@@ -393,6 +394,7 @@ def get_stator_dimensions(par, slots=[]):
|
|
393
394
|
num_phases=m,
|
394
395
|
cufilfact=kq,
|
395
396
|
culength=1.4,
|
397
|
+
num_par_wdgs=a,
|
396
398
|
num_layers=layers,
|
397
399
|
resistance=round(r1, 4),
|
398
400
|
coil_span=int(coil_span),
|
@@ -662,6 +664,7 @@ def spm(pnom: float, speed: float, p: int, **kwargs) -> dict:
|
|
662
664
|
par.update(kwargs)
|
663
665
|
|
664
666
|
_set_pm_defaults(par)
|
667
|
+
|
665
668
|
# stator and magnet parameters of surface mounted magnet machine
|
666
669
|
r = get_stator_dimensions(par)
|
667
670
|
|
femagtools/machine/sm.py
CHANGED
@@ -45,6 +45,9 @@ def parident(workdir, engine, machine,
|
|
45
45
|
num_exc_steps: number of excitation current (default 7)
|
46
46
|
speed: rotor speed in 1/s (default 160/p)
|
47
47
|
i1_max: maximum current in A rms (default approx 3*i1nom)
|
48
|
+
beta_min: minimal current angle (default -180°)
|
49
|
+
beta_max: maximal current angle (default 0°)
|
50
|
+
num_move_steps: number of move steps
|
48
51
|
"""
|
49
52
|
cmd = kwargs.get('cmd', None)
|
50
53
|
da1 = machine['outer_diam']
|
@@ -60,7 +63,7 @@ def parident(workdir, engine, machine,
|
|
60
63
|
N = machine[wdgk]['num_wires']
|
61
64
|
i1_max = round(0.28*np.pi*hs*(da1+hs)/Q1/N*Jmax*1e5)*10 * \
|
62
65
|
machine[wdgk].get('num_par_wdgs', 1)
|
63
|
-
|
66
|
+
|
64
67
|
ifnom = machine['rotor']['ifnom']
|
65
68
|
exc_logspace = True
|
66
69
|
if exc_logspace:
|
@@ -86,22 +89,21 @@ def parident(workdir, engine, machine,
|
|
86
89
|
'ld_lq_fast'),
|
87
90
|
wind_temp=20.0,
|
88
91
|
i1_max=kwargs.get('i1_max', i1_max),
|
89
|
-
maxid=0,
|
90
|
-
minid
|
91
|
-
maxiq=i1_max,
|
92
|
-
miniq
|
93
|
-
delta_id=
|
94
|
-
delta_iq=
|
95
|
-
beta_min
|
96
|
-
beta_max=
|
97
|
-
calc_noload=0,
|
92
|
+
maxid=kwargs.get('maxid', 0),
|
93
|
+
minid=kwargs.get('minid', -i1_max),
|
94
|
+
maxiq=kwargs.get('maxiq', i1_max),
|
95
|
+
miniq=kwargs.get('miniq', -i1_max),
|
96
|
+
delta_id=kwargs.get('delta_id', i1_max/5),
|
97
|
+
delta_iq=kwargs.get('delta_iq', i1_max/5),
|
98
|
+
beta_min=kwargs.get('beta_min', -180),
|
99
|
+
beta_max=kwargs.get('beta_max', 0),
|
98
100
|
num_move_steps=kwargs.get('num_move_steps', 31),
|
99
101
|
load_ex_cur=0.5,
|
100
102
|
num_cur_steps=kwargs.get('num_cur_steps', 5),
|
101
103
|
num_beta_steps=kwargs.get('num_beta_steps', 13),
|
102
104
|
num_par_wdgs=machine[wdgk].get('num_par_wdgs', 1),
|
103
|
-
period_frac=6,
|
104
|
-
speed=50
|
105
|
+
period_frac=kwargs.get('period_frac', 6),
|
106
|
+
speed=kwargs.get('speed', 50))
|
105
107
|
|
106
108
|
###self.cleanup() # remove previously created files in workdir
|
107
109
|
results = parvar(parvardef, machine, simulation, engine)
|
@@ -119,9 +121,10 @@ def parident(workdir, engine, machine,
|
|
119
121
|
rotor_mass = 0 # need femag classic > rel-9.3.x-48-gca42bbd0
|
120
122
|
b = results['f'][-1]
|
121
123
|
|
122
|
-
losskeys = ('speed', 'ef', 'hf',
|
124
|
+
losskeys = ('speed', 'ef', 'hf', 'cf',
|
123
125
|
'styoke_hyst', 'stteeth_hyst', 'styoke_eddy',
|
124
|
-
'stteeth_eddy', 'rotor_hyst', 'rotor_eddy'
|
126
|
+
'stteeth_eddy', 'rotor_hyst', 'rotor_eddy',
|
127
|
+
'styoke_excess', 'stteeth_excess', 'rotor_excess')
|
125
128
|
|
126
129
|
# winding resistance
|
127
130
|
try:
|
@@ -159,7 +162,7 @@ def parident(workdir, engine, machine,
|
|
159
162
|
ld=b['ldq']['ld'],
|
160
163
|
lq=b['ldq']['lq'],
|
161
164
|
losses={k: b['ldq']['losses'][k]
|
162
|
-
for k in losskeys})
|
165
|
+
for k in losskeys if k in b['ldq']['losses']})
|
163
166
|
for b in results['f']])
|
164
167
|
else:
|
165
168
|
dqpars = dict(m=3, p=b['machine']['p'],
|
@@ -170,11 +173,12 @@ def parident(workdir, engine, machine,
|
|
170
173
|
ex_current=b['machine']['ex_current'],
|
171
174
|
iq=b['psidq']['iq'],
|
172
175
|
id=b['psidq']['id'],
|
176
|
+
psidq_ldq=b['psidq_ldq'],
|
173
177
|
psid=b['psidq']['psid'],
|
174
178
|
psiq=b['psidq']['psiq'],
|
175
179
|
torque=b['psidq']['torque'],
|
176
180
|
losses={k: b['psidq']['losses'][k]
|
177
|
-
for k in losskeys})
|
181
|
+
for k in losskeys if k in b['psidq']['losses']})
|
178
182
|
for b in results['f']])
|
179
183
|
if 'current_angles' in results['f'][0]:
|
180
184
|
dqpars['current_angles'] = results['f'][0]['current_angles']
|
@@ -242,6 +246,7 @@ class SynchronousMachine(object):
|
|
242
246
|
self.kth2 = KTH
|
243
247
|
self.skin_resistance = [None, None]
|
244
248
|
self.kpmag = 1
|
249
|
+
self.bertotti = False
|
245
250
|
# here you can set user defined functions for calculating the skin-resistance,
|
246
251
|
# according to the current frequency w. First function in list is for stator, second for rotor.
|
247
252
|
# If None, the femagtools intern default implementation is used.
|
@@ -282,6 +287,13 @@ class SynchronousMachine(object):
|
|
282
287
|
'rotor_hyst': hf,
|
283
288
|
'rotor_eddy': ef}
|
284
289
|
|
290
|
+
if self.bertotti:
|
291
|
+
self.plexp.update({
|
292
|
+
'styoke_excess': 1.5,
|
293
|
+
'stteeth_excess':1.5,
|
294
|
+
'rotor_excess': 1.5})
|
295
|
+
|
296
|
+
|
285
297
|
def pfric(self, n):
|
286
298
|
"""friction and windage losses"""
|
287
299
|
return 2*np.pi*n*self.tfric
|
@@ -315,7 +327,7 @@ class SynchronousMachine(object):
|
|
315
327
|
if n > 1e-3:
|
316
328
|
f1 = self.p*n
|
317
329
|
plfe = self.kpfe * (self.iqd_plfe1(iq, id, iex, f1)
|
318
|
-
+ self.iqd_plfe2(iq, id, f1))
|
330
|
+
+ self.iqd_plfe2(iq, id, iex, f1))
|
319
331
|
return (plfe + self.pfric(n))/(2*np.pi*n)
|
320
332
|
return 0
|
321
333
|
|
@@ -341,7 +353,7 @@ class SynchronousMachine(object):
|
|
341
353
|
"""return uq, ud of frequency w1 and d-q current"""
|
342
354
|
psid, psiq = self.psi(iq, id, iex)
|
343
355
|
r1 = self.rstat(w1)
|
344
|
-
return r1*
|
356
|
+
return r1*iq + w1*psid, r1*id - w1*psiq
|
345
357
|
|
346
358
|
def plcu1(self, iqde, w1):
|
347
359
|
r1 = self.rstat(w1)
|
@@ -360,7 +372,7 @@ class SynchronousMachine(object):
|
|
360
372
|
def iqd_plcu2(self, iq, id, iex):
|
361
373
|
return self.plcu2((iq, id, iex))
|
362
374
|
|
363
|
-
def
|
375
|
+
def iqd_plfe2_(self, iq, id, f1):
|
364
376
|
return np.zeros(np.asarray(iq).shape)
|
365
377
|
|
366
378
|
def iqd_plmag(self, iq, id, f1):
|
@@ -369,9 +381,9 @@ class SynchronousMachine(object):
|
|
369
381
|
def iqd_tmech(self, torque, n, disp=False, maxiter=500):
|
370
382
|
"""return currents for shaft torque with minimal losses"""
|
371
383
|
if torque > 0:
|
372
|
-
startvals = self.bounds[0][1], 0, sum(self.bounds[-1])
|
384
|
+
startvals = self.bounds[0][1]/2, 0, sum(self.bounds[-1])/2
|
373
385
|
else:
|
374
|
-
startvals = -self.bounds[0][1], 0, sum(self.bounds[-1])
|
386
|
+
startvals = -self.bounds[0][1]/2, 0, sum(self.bounds[-1])/2
|
375
387
|
|
376
388
|
with warnings.catch_warnings():
|
377
389
|
warnings.simplefilter("ignore")
|
@@ -398,9 +410,9 @@ class SynchronousMachine(object):
|
|
398
410
|
def iqd_torque(self, torque, disp=False, maxiter=500):
|
399
411
|
"""return currents for torque with minimal losses"""
|
400
412
|
if torque > 0:
|
401
|
-
startvals = self.bounds[0][1], 0, sum(self.bounds[-1])
|
413
|
+
startvals = self.bounds[0][1]/2, 0, sum(self.bounds[-1])/2
|
402
414
|
else:
|
403
|
-
startvals = -self.bounds[0][1], 0, sum(self.bounds[-1])
|
415
|
+
startvals = -self.bounds[0][1]/2, 0, sum(self.bounds[-1])/2
|
404
416
|
|
405
417
|
with warnings.catch_warnings():
|
406
418
|
warnings.simplefilter("ignore")
|
@@ -425,7 +437,7 @@ class SynchronousMachine(object):
|
|
425
437
|
|
426
438
|
def mtpa(self, i1max):
|
427
439
|
"""return iq, id, iex currents and maximum torque per current """
|
428
|
-
T0 = self.torque_iqd(np.sqrt(2)*i1max, 0, self.bounds[-1][1])
|
440
|
+
T0 = self.torque_iqd(np.sqrt(2)*i1max/2, 0, self.bounds[-1][1]/2)
|
429
441
|
def i1tq(tq):
|
430
442
|
return abs(i1max) - np.linalg.norm(self.iqd_torque(tq)[:2])/np.sqrt(2)
|
431
443
|
with warnings.catch_warnings():
|
@@ -436,7 +448,7 @@ class SynchronousMachine(object):
|
|
436
448
|
|
437
449
|
def mtpa_tmech(self, i1max, n):
|
438
450
|
"""return iq, id, iex currents and maximum torque per current """
|
439
|
-
T0 = self.torque_iqd(np.sqrt(2)*i1max, 0, self.bounds[-1][0])
|
451
|
+
T0 = self.torque_iqd(np.sqrt(2)*i1max/2, 0, self.bounds[-1][0])/2
|
440
452
|
def i1tq(tq):
|
441
453
|
return i1max - np.linalg.norm(self.iqd_tmech(tq, n)[:2])/np.sqrt(2)
|
442
454
|
tq = so.fsolve(i1tq, T0)[0]
|
@@ -560,7 +572,11 @@ class SynchronousMachine(object):
|
|
560
572
|
nsamples -- (optional) number of speed samples
|
561
573
|
with_tmech -- (optional) use friction and windage losses
|
562
574
|
with_torque_corr -- (optional) T is corrected if out of range
|
575
|
+
Optional arguments:
|
576
|
+
i1max: max. phase current (RMS)
|
563
577
|
"""
|
578
|
+
if kwargs.get('i1max', 0):
|
579
|
+
w1type, T = self.w1_imax_umax(kwargs['i1max'], u1max)
|
564
580
|
try:
|
565
581
|
iq, id, iex = self.iqd_torque(T)
|
566
582
|
except ValueError:
|
@@ -588,6 +604,7 @@ class SynchronousMachine(object):
|
|
588
604
|
if T < 0:
|
589
605
|
i1max = -i1max
|
590
606
|
w1type, Tf = self.w1_imax_umax(i1max, u1max)
|
607
|
+
|
591
608
|
else:
|
592
609
|
Tf = T
|
593
610
|
w1type = self.w1_umax(u1max, iq, id, iex)
|
@@ -619,7 +636,9 @@ class SynchronousMachine(object):
|
|
619
636
|
wmtab[0] = 0
|
620
637
|
|
621
638
|
r = dict(u1=[], i1=[], id=[], iq=[], iex=[], T=[], cosphi=[], n=[],
|
622
|
-
beta=[], plfe1=[], plcu1=[], plcu2=[])
|
639
|
+
beta=[], plfe1=[], plfe2=[], plcu1=[], plcu2=[])
|
640
|
+
# add type speed to result dict
|
641
|
+
r['n_type'] = wmType/2/np.pi
|
623
642
|
for wm, tq in zip(wmtab, [tload(wx) for wx in wmtab]):
|
624
643
|
w1 = wm*self.p
|
625
644
|
if with_tmech:
|
@@ -645,12 +664,13 @@ class SynchronousMachine(object):
|
|
645
664
|
gamma = np.arctan2(ud, uq)
|
646
665
|
r['cosphi'].append(np.cos(gamma - beta))
|
647
666
|
r['plfe1'].append(self.iqd_plfe1(iq, id, iex, f1))
|
667
|
+
r['plfe2'].append(self.iqd_plfe2(iq, id, iex, f1))
|
648
668
|
r['plcu1'].append(self.m*i1**2*self.rstat(w1))
|
649
669
|
r['plcu2'].append(iex**2*self.rrot(0))
|
650
670
|
r['T'].append(tqx)
|
651
671
|
r['n'].append(wm/2/np.pi)
|
652
672
|
|
653
|
-
r['plfe'] = r['plfe1']
|
673
|
+
r['plfe'] = (np.array(r['plfe1']) + np.array(r['plfe2'])).tolist()
|
654
674
|
r['plcu'] = (np.array(r['plcu1']) + np.array(r['plcu2'])).tolist()
|
655
675
|
r['plfw'] = [self.pfric(n) for n in r['n']]
|
656
676
|
r['pmech'] = [2*np.pi*n*tq
|
@@ -685,6 +705,10 @@ class SynchronousMachinePsidq(SynchronousMachine):
|
|
685
705
|
eecpars['psidq'][0]['iq'][-1])
|
686
706
|
self.idrange = (eecpars['psidq'][0]['id'][0],
|
687
707
|
eecpars['psidq'][0]['id'][-1])
|
708
|
+
|
709
|
+
self.betarange = (-np.pi if min(self.iqrange) < 0 else -np.pi/2,
|
710
|
+
0 if max(self.iqrange) > 0 else -np.pi/2)
|
711
|
+
self.i1range = (0, betai1(np.max(self.iqrange), 0)[1])
|
688
712
|
islinear = True
|
689
713
|
iexc = [l['ex_current'] for l in eecpars['psidq']]
|
690
714
|
self.exc_max = iexc[-1]
|
@@ -722,9 +746,18 @@ class SynchronousMachinePsidq(SynchronousMachine):
|
|
722
746
|
self.bounds = [(min(iq), max(iq)),
|
723
747
|
(min(id), 0),
|
724
748
|
(iexc[0], iexc[-1])]
|
749
|
+
|
750
|
+
keys = self.plexp.keys()
|
725
751
|
try:
|
726
752
|
idname = 'psidq'
|
727
|
-
|
753
|
+
# check if bertotti
|
754
|
+
if 'styoke_excess' in eecpars[idname][0]['losses'] and \
|
755
|
+
np.any(np.array(eecpars[idname][0]['losses']['styoke_excess'])):
|
756
|
+
self.bertotti = True
|
757
|
+
keys.update({{
|
758
|
+
'styoke_excess': 1.5,
|
759
|
+
'stteeth_excess':1.5,
|
760
|
+
'rotor_excess': 1.5}})
|
728
761
|
if islinear:
|
729
762
|
pfe = {k: np.array([l['losses'][k]
|
730
763
|
for l in eecpars[idname]])
|
@@ -758,14 +791,27 @@ class SynchronousMachinePsidq(SynchronousMachine):
|
|
758
791
|
raise ex
|
759
792
|
|
760
793
|
def plfe1(self, iq, id, iex, f1):
|
794
|
+
losskeys = ['styoke_eddy', 'styoke_hyst',
|
795
|
+
'stteeth_eddy', 'stteeth_hyst']
|
796
|
+
if self.bertotti:
|
797
|
+
losskeys += ['styoke_excess', 'stteeth_excess']
|
798
|
+
return np.sum([
|
799
|
+
self._losses[k]((iex, iq, id))*(f1/self.fo)**self.plexp[k][0]
|
800
|
+
for k in losskeys], axis=0)
|
801
|
+
|
802
|
+
def plfe2(self, iq, id, iex, f1):
|
803
|
+
losskeys = ['rotor_hyst', 'rotor_eddy']
|
804
|
+
if self.bertotti:
|
805
|
+
losskeys += ['rotor_excess']
|
761
806
|
return np.sum([
|
762
807
|
self._losses[k]((iex, iq, id))*(f1/self.fo)**self.plexp[k][0]
|
763
|
-
for k in
|
764
|
-
'stteeth_eddy', 'stteeth_hyst')], axis=0)
|
808
|
+
for k in losskeys], axis=0)
|
765
809
|
|
766
810
|
def iqd_plfe1(self, iq, id, iex, f1):
|
767
811
|
return self.plfe1(iq, id, iex, f1)
|
768
812
|
|
813
|
+
def iqd_plfe2(self, iq, id, iex, f1):
|
814
|
+
return self.plfe2(iq, id, iex, f1)
|
769
815
|
|
770
816
|
class SynchronousMachineLdq(SynchronousMachine):
|
771
817
|
def __init__(self, eecpars, lfe=1, wdg=1, **kwargs):
|
@@ -814,9 +860,20 @@ class SynchronousMachineLdq(SynchronousMachine):
|
|
814
860
|
self.bounds = [(np.cos(min(beta))*i1max, i1max),
|
815
861
|
(-i1max, 0),
|
816
862
|
(iexc[0], iexc[-1])]
|
863
|
+
|
864
|
+
# iron losses
|
817
865
|
keys = self.plexp.keys()
|
818
866
|
try:
|
819
867
|
idname = 'ldq'
|
868
|
+
# check bertotti losses
|
869
|
+
if 'styoke_excess' in eecpars[idname][0]['losses'] and \
|
870
|
+
np.any(np.array(eecpars[idname][0]['losses']['styoke_excess'])):
|
871
|
+
self.bertotti = True
|
872
|
+
keys.update({{
|
873
|
+
'styoke_excess': 1.5,
|
874
|
+
'stteeth_excess':1.5,
|
875
|
+
'rotor_excess': 1.5}})
|
876
|
+
|
820
877
|
if islinear:
|
821
878
|
pfe = {k: np.array([l['losses'][k]
|
822
879
|
for l in eecpars[idname]])
|
@@ -826,6 +883,7 @@ class SynchronousMachineLdq(SynchronousMachine):
|
|
826
883
|
np.array([l['losses'][k]
|
827
884
|
for l in eecpars[idname]]))
|
828
885
|
for k in keys}
|
886
|
+
|
829
887
|
# fill value with nan outside range
|
830
888
|
self._losses = {k: ip.RegularGridInterpolator(
|
831
889
|
(exc, beta, i1), lfe*np.array(pfe[k]),
|
@@ -834,12 +892,13 @@ class SynchronousMachineLdq(SynchronousMachine):
|
|
834
892
|
self._set_losspar(eecpars[idname][0]['losses']['speed'],
|
835
893
|
eecpars[idname][0]['losses']['ef'],
|
836
894
|
eecpars[idname][0]['losses']['hf'])
|
837
|
-
except
|
895
|
+
except KeyError:
|
838
896
|
logger.warning("loss map missing")
|
839
897
|
self._losses = {k: lambda x: 0 for k in (
|
840
898
|
'styoke_hyst', 'stteeth_hyst',
|
841
899
|
'styoke_eddy', 'stteeth_eddy',
|
842
900
|
'rotor_hyst', 'rotor_eddy')}
|
901
|
+
pass
|
843
902
|
|
844
903
|
def psi(self, iq, id, iex):
|
845
904
|
"""return psid, psiq of currents iq, id"""
|
@@ -855,10 +914,22 @@ class SynchronousMachineLdq(SynchronousMachine):
|
|
855
914
|
raise ex
|
856
915
|
|
857
916
|
def plfe1(self, beta, i1, iex, f1):
|
917
|
+
losskeys = ['styoke_eddy', 'styoke_hyst',
|
918
|
+
'stteeth_eddy', 'stteeth_hyst']
|
919
|
+
if self.bertotti:
|
920
|
+
losskeys += ['styoke_excess', 'stteeth_excess']
|
921
|
+
return np.sum([
|
922
|
+
self._losses[k]((iex, beta, i1))*(f1/self.fo)**self.plexp[k][0]
|
923
|
+
for k in losskeys], axis=0)
|
924
|
+
|
925
|
+
def plfe2(self, beta, i1, iex, f1):
|
926
|
+
losskeys = ['rotor_hyst', 'rotor_eddy']
|
927
|
+
if self.bertotti:
|
928
|
+
losskeys += ['rotor_excess']
|
858
929
|
return np.sum([
|
859
930
|
self._losses[k]((iex, beta, i1))*(f1/self.fo)**self.plexp[k][0]
|
860
|
-
for k in
|
861
|
-
|
931
|
+
for k in losskeys], axis=0)
|
932
|
+
|
862
933
|
def iqd_plfe1(self, iq, id, iex, f1):
|
863
934
|
beta = np.arctan2(id, iq)
|
864
935
|
if np.isscalar(beta):
|
@@ -869,6 +940,16 @@ class SynchronousMachineLdq(SynchronousMachine):
|
|
869
940
|
i1 = np.linalg.norm((id, iq), axis=0)/np.sqrt(2.0)
|
870
941
|
return self.plfe1(beta, i1, iex, f1)
|
871
942
|
|
943
|
+
def iqd_plfe2(self, iq, id, iex, f1):
|
944
|
+
beta = np.arctan2(id, iq)
|
945
|
+
if np.isscalar(beta):
|
946
|
+
if beta > 0:
|
947
|
+
beta -= 2*np.pi
|
948
|
+
else:
|
949
|
+
beta[beta > 0] -= 2*np.pi
|
950
|
+
i1 = np.linalg.norm((id, iq), axis=0)/np.sqrt(2.0)
|
951
|
+
return self.plfe2(beta, i1, iex, f1)
|
952
|
+
|
872
953
|
|
873
954
|
if __name__ == '__main__':
|
874
955
|
import sys
|
femagtools/machine/utils.py
CHANGED
@@ -166,15 +166,15 @@ def wdg_leakage_inductances(machine):
|
|
166
166
|
Juha Pyrhönen, Tapani Jokinen, Valeria Hrabovcova
|
167
167
|
(Ed. 2008) page 236ff
|
168
168
|
"""
|
169
|
-
|
170
|
-
wdg = Winding(
|
169
|
+
wdgk = 'windings' if 'windings' in machine else 'winding'
|
170
|
+
wdg = windings.Winding(
|
171
171
|
{'Q': machine['stator']['num_slots'],
|
172
|
-
'm': machine[
|
172
|
+
'm': machine[wdgk]['num_phases'],
|
173
173
|
'p': machine['poles']//2,
|
174
|
-
'l': machine[
|
175
|
-
'yd': machine[
|
176
|
-
n1 = wdg.turns_per_phase(machine[
|
177
|
-
machine[
|
174
|
+
'l': machine[wdgk]['num_layers'],
|
175
|
+
'yd': machine[wdgk]['coil_span']})
|
176
|
+
n1 = wdg.turns_per_phase(machine[wdgk]['num_wires'],
|
177
|
+
machine[wdgk]['num_par_wdgs'])
|
178
178
|
m = wdg.m
|
179
179
|
p = wdg.p
|
180
180
|
Q = wdg.Q
|
@@ -218,6 +218,20 @@ def wdg_leakage_inductances(machine):
|
|
218
218
|
return Lu, Lew
|
219
219
|
|
220
220
|
|
221
|
+
def create_wdg(machine):
|
222
|
+
"""create winding from machine parameters"""
|
223
|
+
wdgk = 'windings' if 'windings' in machine else 'winding'
|
224
|
+
wpar = {'Q': machine['stator']['num_slots'],
|
225
|
+
'm': machine[wdgk]['num_phases'],
|
226
|
+
'p': machine['poles']//2}
|
227
|
+
|
228
|
+
if 'coil_span' in machine[wdgk]:
|
229
|
+
wpar['yd'] = machine[wdgk]['coil_span']
|
230
|
+
if 'num_layers' in machine[wdgk]:
|
231
|
+
wpar['l'] = machine[wdgk]['num_layers']
|
232
|
+
return windings.Winding(wpar)
|
233
|
+
|
234
|
+
|
221
235
|
def betai1(iq, id):
|
222
236
|
"""return beta and amplitude of dq currents"""
|
223
237
|
return (np.arctan2(id, iq),
|
@@ -352,18 +366,6 @@ def dqparident(workdir, engine, temp, machine,
|
|
352
366
|
"""
|
353
367
|
import pathlib
|
354
368
|
|
355
|
-
wdgk = 'windings' if 'windings' in machine else 'winding'
|
356
|
-
def create_wdg(machine):
|
357
|
-
wpar = {'Q': machine['stator']['num_slots'],
|
358
|
-
'm': machine[wdgk]['num_phases'],
|
359
|
-
'p': machine['poles']//2}
|
360
|
-
|
361
|
-
if 'coil_span' in machine[wdgk]:
|
362
|
-
wpar['yd'] = machine[wdgk]['coil_span']
|
363
|
-
if 'num_layers' in machine[wdgk]:
|
364
|
-
wpar['l'] = machine[wdgk]['num_layers']
|
365
|
-
|
366
|
-
return windings.Winding(wpar)
|
367
369
|
try:
|
368
370
|
defspeed = 160/machine['poles']
|
369
371
|
except KeyError:
|
@@ -372,6 +374,7 @@ def dqparident(workdir, engine, temp, machine,
|
|
372
374
|
defspeed = kwargs['speed']
|
373
375
|
|
374
376
|
lfe = machine['lfe']
|
377
|
+
wdgk = 'windings' if 'windings' in machine else 'winding'
|
375
378
|
g = machine[wdgk].get('num_par_wdgs', 1)
|
376
379
|
N = machine[wdgk]['num_wires']
|
377
380
|
if 'cufilfact' in machine[wdgk]:
|
@@ -382,21 +385,22 @@ def dqparident(workdir, engine, temp, machine,
|
|
382
385
|
fcu = 0.42
|
383
386
|
|
384
387
|
try: # calc basic dimensions if not fsl or dxf model
|
385
|
-
|
386
|
-
|
388
|
+
wdg = create_wdg(machine)
|
389
|
+
Q1 = wdg.Q
|
387
390
|
slotmodel = [k for k in machine['stator'] if isinstance(
|
388
391
|
machine['stator'][k], dict)][-1]
|
389
392
|
if slotmodel == 'stator1':
|
390
393
|
hs = machine['stator']['stator1']['slot_rf1'] - \
|
391
394
|
machine['stator']['stator1']['tip_rh1']
|
392
395
|
else:
|
396
|
+
da1 = machine['bore_diam']
|
393
397
|
dy1 = machine['outer_diam']
|
394
398
|
hs = machine['stator'][slotmodel].get(
|
395
399
|
'slot_height', 0.6*(dy1-da1)/2)
|
396
400
|
|
397
401
|
Jmax = 30e6 # max current density in A/m2
|
398
|
-
Acu = fcu*
|
399
|
-
i1_max = round(Acu/
|
402
|
+
Acu = fcu*np.pi*(da1+hs)*hs/Q1/2 # approx. copper area of one slot
|
403
|
+
i1_max = round(g*Acu/wdg.l/N*Jmax/10)*10
|
400
404
|
except KeyError:
|
401
405
|
if kwargs.get('i1_max', 0) == 0:
|
402
406
|
raise ValueError('i1_max missing')
|
@@ -404,14 +408,12 @@ def dqparident(workdir, engine, temp, machine,
|
|
404
408
|
|
405
409
|
# winding resistance
|
406
410
|
try:
|
407
|
-
wdg = create_wdg(machine)
|
408
|
-
|
409
411
|
if 'wire_gauge' in machine[wdgk]:
|
410
412
|
aw = machine[wdgk]['wire_gauge']
|
411
413
|
elif 'dia_wire' in machine[wdgk]:
|
412
414
|
aw = np.pi*machine[wdgk].get('dia_wire', 1e-3)**2/4
|
413
415
|
elif ('wire_width' in machine[wdgk]) and ('wire_height' in machine[wdgk]):
|
414
|
-
aw = machine[
|
416
|
+
aw = machine[wdgk]['wire_width']*machine[wdgk]['wire_height']
|
415
417
|
else: # wire diameter from slot area
|
416
418
|
aw = 0.75 * fcu * np.pi*da1*hs/Q1/wdg.l/N
|
417
419
|
r1 = wdg_resistance(wdg, N, g, aw, da1, hs, lfe)
|
@@ -456,12 +458,10 @@ def dqparident(workdir, engine, temp, machine,
|
|
456
458
|
# TODO: cleanup() # remove previously created files in workdir
|
457
459
|
# start calculation
|
458
460
|
results = parvar(parvardef, machine, simulation, engine)
|
459
|
-
if 'poles' not in machine:
|
461
|
+
if 'poles' not in machine: # dxf model?
|
460
462
|
machine['poles'] = 2*results['f'][0]['machine']['p']
|
461
463
|
da1 = 2*results['f'][0]['machine']['fc_radius']
|
462
|
-
|
463
|
-
#with open('results.json', 'w') as fp:
|
464
|
-
# json.dump(results, fp)
|
464
|
+
wdg = create_wdg(machine)
|
465
465
|
ls1 = 0
|
466
466
|
try:
|
467
467
|
leakages = [float(x)
|
@@ -522,13 +522,17 @@ def dqparident(workdir, engine, temp, machine,
|
|
522
522
|
else:
|
523
523
|
from .. import nc
|
524
524
|
model = nc.read(str(pathlib.Path(workdir) / machine['name']))
|
525
|
-
|
526
|
-
|
527
|
-
|
525
|
+
try:
|
526
|
+
nlayers = wdg.l
|
527
|
+
except UnboundLocalError:
|
528
|
+
wdg = create_wdg(machine)
|
529
|
+
nlayers = wdg.l
|
530
|
+
da1 = machine['outer_diam']
|
531
|
+
Q1 = wdg.Q
|
528
532
|
istat = 0 if model.get_areas()[0]['slots'] else 1
|
529
533
|
asl = model.get_areas()[istat]['slots']
|
530
534
|
# diameter of wires
|
531
|
-
aw = fcu*asl/Q1/
|
535
|
+
aw = fcu*asl/Q1/nlayers/N
|
532
536
|
hs = asl/(np.pi*da1/3)
|
533
537
|
dqpars['r1'] = wdg_resistance(wdg, N, g, aw, da1, hs, lfe)
|
534
538
|
|
femagtools/model.py
CHANGED
@@ -65,7 +65,7 @@ class Model(object):
|
|
65
65
|
"""return value of key name
|
66
66
|
|
67
67
|
Args:
|
68
|
-
name: key of parameter value
|
68
|
+
name (str or list of str): key of parameter value
|
69
69
|
|
70
70
|
Return:
|
71
71
|
value of parameter identified by key
|
@@ -214,7 +214,7 @@ class MachineModel(Model):
|
|
214
214
|
logging.debug(slotgen)
|
215
215
|
g = np.gcd.reduce(slotgen)
|
216
216
|
if hasattr(self, 'magnet') and g > 1:
|
217
|
-
g
|
217
|
+
g //= m
|
218
218
|
if 'num_slots' in self.stator:
|
219
219
|
Q1 = self.stator['num_slots']
|
220
220
|
self.stator['num_slots_gen'] = Q1 // g
|
@@ -415,6 +415,16 @@ class MachineModel(Model):
|
|
415
415
|
def has_magnet(self): # either rotor or magnet
|
416
416
|
return hasattr(self, 'magnet')
|
417
417
|
|
418
|
+
def props(self):
|
419
|
+
"""returns dict of this model"""
|
420
|
+
keys = ('name', 'poles', 'outer_diam', 'airgap',
|
421
|
+
'bore_diam', 'external_rotor', 'agndst')
|
422
|
+
model = {k: getattr(self, k) for k in keys if hasattr(self, k)}
|
423
|
+
if hasattr(self, 'stator'):
|
424
|
+
model['stator'] = {k: self.stator[k]
|
425
|
+
for k in ('num_slots', 'num_slots_gen')
|
426
|
+
if k in self.stator}
|
427
|
+
return model
|
418
428
|
|
419
429
|
class FeaModel(Model):
|
420
430
|
"""represents a simulation model for a FE analysis"""
|
femagtools/moo/population.py
CHANGED
femagtools/parstudy.py
CHANGED
@@ -188,6 +188,9 @@ class ParameterStudy(object):
|
|
188
188
|
simulation.update(model.winding)
|
189
189
|
if 'pocfilename' not in simulation:
|
190
190
|
simulation['pocfilename'] = f"{model.name}.poc"
|
191
|
+
if simulation['calculationMode'] == 'psd_psq_fast':
|
192
|
+
simulation['pocfilename'] = f"{model.name}_{model.get('poles')}p.poc"
|
193
|
+
|
191
194
|
fea = femagtools.model.FeaModel(simulation)
|
192
195
|
|
193
196
|
prob = femagtools.moproblem.FemagMoProblem(decision_vars,
|
@@ -196,7 +199,20 @@ class ParameterStudy(object):
|
|
196
199
|
if immutable_model:
|
197
200
|
modelfiles = self.setup_model(builder, model, recsin=fea.recsin)
|
198
201
|
logger.info("Files %s", modelfiles+extra_files)
|
199
|
-
|
202
|
+
logger.info("model %s", model.props())
|
203
|
+
for k in ('name', 'poles', 'outer_diam', 'airgap', 'bore_diam',
|
204
|
+
'external_rotor'):
|
205
|
+
if k not in machine:
|
206
|
+
try:
|
207
|
+
machine[k] = model.get(k)
|
208
|
+
except:
|
209
|
+
pass
|
210
|
+
for k in ('num_slots', 'num_slots_gen'):
|
211
|
+
if k not in machine['stator']:
|
212
|
+
try:
|
213
|
+
machine['stator'][k] = model.stator[k]
|
214
|
+
except:
|
215
|
+
pass
|
200
216
|
job = engine.create_job(self.femag.workdir)
|
201
217
|
# for progress logger
|
202
218
|
job.num_cur_steps = fea.get_num_cur_steps()
|
femagtools/plot/__init__.py
CHANGED
@@ -10,7 +10,7 @@ from .bch import torque, torque_fft, force, force_fft, \
|
|
10
10
|
i1beta_psiq, i1beta_psim, i1beta_up, \
|
11
11
|
idq_ld, idq_lq, idq_psid, idq_psim, idq_psiq, idq_torque
|
12
12
|
from .char import mtpa, mtpv, characteristics, efficiency_map, losses_map
|
13
|
-
from .forcedens import forcedens, forcedens_surface, forcedens_fft
|
13
|
+
from .forcedens import forcedens, forcedens_surface, forcedens_contour, forcedens_fft
|
14
14
|
from .nc import spel, mesh, demag, demag_pos, \
|
15
15
|
flux_density, flux_density_eccentricity, \
|
16
16
|
airgap_flux_density_pos, loss_density
|