femagtools 1.3.0__py3-none-any.whl → 1.3.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/airgap.py +11 -37
- femagtools/amela.py +148 -13
- femagtools/bch.py +19 -3
- femagtools/dxfsl/area.py +68 -15
- femagtools/dxfsl/converter.py +15 -6
- femagtools/dxfsl/fslrenderer.py +13 -8
- femagtools/dxfsl/functions.py +1 -1
- femagtools/dxfsl/geom.py +415 -62
- femagtools/dxfsl/machine.py +97 -5
- femagtools/dxfsl/shape.py +46 -2
- femagtools/ecloss.py +393 -0
- femagtools/femag.py +25 -1
- femagtools/fsl.py +3 -2
- femagtools/hxy.py +126 -0
- femagtools/isa7.py +37 -24
- femagtools/machine/__init__.py +14 -13
- femagtools/machine/effloss.py +153 -32
- femagtools/machine/im.py +137 -56
- femagtools/machine/pm.py +584 -202
- femagtools/machine/sm.py +218 -64
- femagtools/machine/utils.py +12 -8
- femagtools/mcv.py +6 -8
- femagtools/model.py +11 -1
- femagtools/parstudy.py +1 -1
- femagtools/plot.py +159 -35
- femagtools/templates/afm_rotor.mako +102 -0
- femagtools/templates/afm_stator.mako +141 -0
- femagtools/templates/airgapinduc.mako +3 -3
- femagtools/templates/basic_modpar.mako +23 -2
- femagtools/templates/cogg_calc.mako +28 -5
- femagtools/templates/cu_losses.mako +1 -1
- femagtools/templates/fieldcalc.mako +39 -0
- femagtools/templates/gen_winding.mako +52 -47
- femagtools/templates/mesh-airgap.mako +43 -0
- femagtools/templates/stator3Linear.mako +5 -4
- femagtools/templates/therm-dynamic.mako +12 -6
- femagtools/templates/therm-static.mako +12 -0
- femagtools/templates/torq_calc.mako +2 -4
- femagtools/utils.py +45 -0
- femagtools/windings.py +2 -1
- {femagtools-1.3.0.dist-info → femagtools-1.3.2.dist-info}/METADATA +1 -1
- {femagtools-1.3.0.dist-info → femagtools-1.3.2.dist-info}/RECORD +47 -41
- {femagtools-1.3.0.dist-info → femagtools-1.3.2.dist-info}/WHEEL +1 -1
- {femagtools-1.3.0.dist-info → femagtools-1.3.2.dist-info}/LICENSE +0 -0
- {femagtools-1.3.0.dist-info → femagtools-1.3.2.dist-info}/entry_points.txt +0 -0
- {femagtools-1.3.0.dist-info → femagtools-1.3.2.dist-info}/top_level.txt +0 -0
femagtools/machine/sm.py
CHANGED
@@ -10,21 +10,23 @@ import warnings
|
|
10
10
|
import numpy as np
|
11
11
|
import scipy.optimize as so
|
12
12
|
import scipy.interpolate as ip
|
13
|
-
from .utils import skin_resistance, wdg_resistance
|
13
|
+
from .utils import skin_resistance, wdg_resistance, betai1, iqd, KTH
|
14
14
|
from .. import parstudy, windings
|
15
15
|
import femagtools.bch
|
16
16
|
|
17
17
|
EPS = 1e-13
|
18
18
|
|
19
19
|
eecdefaults = dict(
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
20
|
+
zeta1=0.3,
|
21
|
+
zeta2=0,
|
22
|
+
gam=0.7,
|
23
|
+
kh=2,
|
24
|
+
tcu1=20,
|
25
|
+
tcu2=20,
|
26
|
+
rotor_mass=0,
|
27
|
+
kfric_b=1,
|
28
|
+
kpfe = 1 # iron loss factor
|
29
|
+
)
|
28
30
|
|
29
31
|
logger = logging.getLogger('sm')
|
30
32
|
logging.captureWarnings(True)
|
@@ -146,7 +148,7 @@ def parident(workdir, engine, machine,
|
|
146
148
|
r1 = wdg_resistance(wdg, N, g, aw, da1, hs, lfe)
|
147
149
|
|
148
150
|
if simulation['calculationMode'] == 'ld_lq_fast':
|
149
|
-
|
151
|
+
dqpars = dict(m=3, p=b['machine']['p'],
|
150
152
|
r1=r1,
|
151
153
|
r2=machine['rotor'].get('resistance', 1),
|
152
154
|
rotor_mass=rotor_mass, kfric_b=1,
|
@@ -162,7 +164,8 @@ def parident(workdir, engine, machine,
|
|
162
164
|
losses={k: b['ldq']['losses'][k]
|
163
165
|
for k in losskeys})
|
164
166
|
for b in results['f']])
|
165
|
-
|
167
|
+
else:
|
168
|
+
dqpars = dict(m=3, p=b['machine']['p'],
|
166
169
|
r1=r1,
|
167
170
|
r2=machine['rotor'].get('resistance', 1),
|
168
171
|
rotor_mass=rotor_mass, kfric_b=1,
|
@@ -176,6 +179,9 @@ def parident(workdir, engine, machine,
|
|
176
179
|
losses={k: b['psidq']['losses'][k]
|
177
180
|
for k in losskeys})
|
178
181
|
for b in results['f']])
|
182
|
+
if 'current_angles' in results['f'][0]:
|
183
|
+
dqpars['current_angles'] = results['f'][0]['current_angles']
|
184
|
+
return dqpars
|
179
185
|
|
180
186
|
def _linsampl(exc, excl, a):
|
181
187
|
"""auxiliary func for linear sampling of nonlinear sequence
|
@@ -229,7 +235,18 @@ def gradient_respecting_bounds(bounds, fun, eps=1e-8):
|
|
229
235
|
|
230
236
|
|
231
237
|
class SynchronousMachine(object):
|
232
|
-
def __init__(self, eecpars):
|
238
|
+
def __init__(self, eecpars, **kwargs):
|
239
|
+
self.kth1 = KTH
|
240
|
+
self.kth2 = KTH
|
241
|
+
self.skin_resistance = [None, None]
|
242
|
+
# here you can set user defined functions for calculating the skin-resistance,
|
243
|
+
# according to the current frequency w. First function in list is for stator, second for rotor.
|
244
|
+
# If None, the femagtools intern default implementation is used.
|
245
|
+
# User defined functions need to have the following arguments:
|
246
|
+
# - r0: (float) dc-resistance at 20°C
|
247
|
+
# - w: (float) current frequency in rad (2*pi*f)
|
248
|
+
# - tcu: (float) conductor temperature in deg Celsius
|
249
|
+
# - kth: (float) temperature coefficient (Default = 0.0039, Cu)
|
233
250
|
for k in eecdefaults.keys():
|
234
251
|
setattr(self, k, eecdefaults[k])
|
235
252
|
|
@@ -237,6 +254,14 @@ class SynchronousMachine(object):
|
|
237
254
|
if k not in ('ldq', 'psidq'):
|
238
255
|
setattr(self, k, eecpars[k])
|
239
256
|
|
257
|
+
for k in kwargs:
|
258
|
+
setattr(self, k, kwargs[k])
|
259
|
+
|
260
|
+
try:
|
261
|
+
self.tfric = self.kfric_b*self.rotor_mass*30e-3/np.pi
|
262
|
+
except AttributeError:
|
263
|
+
self.tfric = 0
|
264
|
+
|
240
265
|
self.fo = 50
|
241
266
|
self.plexp = {'styoke_hyst': 1.0,
|
242
267
|
'stteeth_hyst': 1.0,
|
@@ -254,21 +279,46 @@ class SynchronousMachine(object):
|
|
254
279
|
'rotor_hyst': hf,
|
255
280
|
'rotor_eddy': ef}
|
256
281
|
|
282
|
+
def pfric(self, n):
|
283
|
+
"""friction and windage losses"""
|
284
|
+
return 2*np.pi*n*self.tfric
|
285
|
+
|
257
286
|
def rstat(self, w):
|
258
287
|
"""stator resistance"""
|
259
|
-
|
260
|
-
|
288
|
+
sr = self.skin_resistance[0]
|
289
|
+
if sr is not None:
|
290
|
+
return sr(self.r1, w, self.tcu1, kth=self.kth1)
|
291
|
+
else:
|
292
|
+
return skin_resistance(self.r1, w, self.tcu1, self.zeta1,
|
293
|
+
self.gam, self.kh, kth=self.kth1)
|
261
294
|
|
262
295
|
def rrot(self, w):
|
263
296
|
"""rotor resistance"""
|
264
|
-
|
265
|
-
|
297
|
+
sr = self.skin_resistance[1]
|
298
|
+
if sr is not None:
|
299
|
+
return sr(self.r2, w, self.tcu2, kth=self.kth2)
|
300
|
+
else:
|
301
|
+
return skin_resistance(self.r2, w, self.tcu2, self.zeta2,
|
302
|
+
0.0, 1, kth=self.kth2)
|
266
303
|
|
267
304
|
def torque_iqd(self, iq, id, iex):
|
268
305
|
"torque at q-d-current"
|
269
306
|
psid, psiq = self.psi(iq, id, iex)
|
270
307
|
return self.m*self.p/2*(psid*iq - psiq*id)
|
271
308
|
|
309
|
+
def tloss_iqd(self, iq, id, iex, n):
|
310
|
+
"""return loss torque of d-q current, iron loss correction factor
|
311
|
+
and friction windage losses"""
|
312
|
+
if n > 1e-3:
|
313
|
+
f1 = self.p*n
|
314
|
+
plfe = self.kpfe * (self.iqd_plfe1(iq, id, iex, f1) + self.iqd_plfe2(iq, id, f1))
|
315
|
+
return (plfe + self.pfric(n))/(2*np.pi*n)
|
316
|
+
return 0
|
317
|
+
|
318
|
+
def tmech_iqd(self, iq, id, iex, n):
|
319
|
+
"""return shaft torque of d-q current and speed"""
|
320
|
+
return self.torque_iqd(iq, id, iex) - self.tloss_iqd(iq, id, iex, n)
|
321
|
+
|
272
322
|
def uqd(self, w1, iq, id, iex):
|
273
323
|
"""return uq, ud of frequency w1 and d-q current"""
|
274
324
|
psid, psiq = self.psi(iq, id, iex)
|
@@ -298,12 +348,41 @@ class SynchronousMachine(object):
|
|
298
348
|
def iqd_plmag(self, iq, id, f1):
|
299
349
|
return np.zeros(np.asarray(iq).shape)
|
300
350
|
|
351
|
+
def iqd_tmech(self, torque, n, disp=False, maxiter=500):
|
352
|
+
"""return currents for shaft torque with minimal losses"""
|
353
|
+
if torque > 0:
|
354
|
+
startvals = self.bounds[0][1], 0, sum(self.bounds[-1])
|
355
|
+
else:
|
356
|
+
startvals = -self.bounds[0][1], 0, sum(self.bounds[-1])
|
357
|
+
|
358
|
+
with warnings.catch_warnings():
|
359
|
+
warnings.simplefilter("ignore")
|
360
|
+
def sqrtculoss(iqde):
|
361
|
+
pcu = self.culoss(iqde)
|
362
|
+
return pcu
|
363
|
+
|
364
|
+
res = so.minimize(
|
365
|
+
self.culoss, startvals, method='SLSQP', # trust-constr
|
366
|
+
bounds=self.bounds,
|
367
|
+
# jac=gradient_respecting_bounds(self.bounds, self.culoss),
|
368
|
+
constraints=[
|
369
|
+
{'type': 'eq',
|
370
|
+
'fun': lambda iqd: self.tmech_iqd(*iqd, n) - torque}])
|
371
|
+
#options={'disp': disp, 'maxiter': maxiter})
|
372
|
+
if res['success']:
|
373
|
+
return res.x
|
374
|
+
|
375
|
+
logger.warning("%s: torque=%f %f, io=%s",
|
376
|
+
res['message'], torque, self.tmech_iqd(*startvals, n),
|
377
|
+
startvals)
|
378
|
+
raise ValueError(res['message'])
|
379
|
+
|
301
380
|
def iqd_torque(self, torque, disp=False, maxiter=500):
|
302
381
|
"""return currents for torque with minimal losses"""
|
303
382
|
if torque > 0:
|
304
|
-
startvals = self.bounds[0][1]
|
383
|
+
startvals = self.bounds[0][1], 0, sum(self.bounds[-1])
|
305
384
|
else:
|
306
|
-
startvals = -self.bounds[0][1]
|
385
|
+
startvals = -self.bounds[0][1], 0, sum(self.bounds[-1])
|
307
386
|
|
308
387
|
with warnings.catch_warnings():
|
309
388
|
warnings.simplefilter("ignore")
|
@@ -317,8 +396,8 @@ class SynchronousMachine(object):
|
|
317
396
|
# jac=gradient_respecting_bounds(self.bounds, self.culoss),
|
318
397
|
constraints=[
|
319
398
|
{'type': 'eq',
|
320
|
-
'fun': lambda iqd: self.torque_iqd(*iqd) - torque}]
|
321
|
-
options={'disp': disp, 'maxiter': maxiter})
|
399
|
+
'fun': lambda iqd: self.torque_iqd(*iqd) - torque}])
|
400
|
+
#options={'disp': disp, 'maxiter': maxiter})
|
322
401
|
if res['success']:
|
323
402
|
return res.x
|
324
403
|
logger.warning("%s: torque=%f %f, io=%s",
|
@@ -326,14 +405,73 @@ class SynchronousMachine(object):
|
|
326
405
|
startvals)
|
327
406
|
raise ValueError(res['message'])
|
328
407
|
|
408
|
+
def mtpa(self, i1max):
|
409
|
+
"""return iq, id, iex currents and maximum torque per current """
|
410
|
+
T0 = self.torque_iqd(np.sqrt(2)*i1max, 0, self.bounds[-1][1])
|
411
|
+
def i1tq(tq):
|
412
|
+
return abs(i1max) - np.linalg.norm(self.iqd_torque(tq)[:2])/np.sqrt(2)
|
413
|
+
with warnings.catch_warnings():
|
414
|
+
warnings.simplefilter("ignore")
|
415
|
+
tq = so.fsolve(i1tq, T0)[0]
|
416
|
+
iq, id, iex = self.iqd_torque(tq)
|
417
|
+
return iq, id, iex, tq
|
418
|
+
|
419
|
+
def mtpa_tmech(self, i1max, n):
|
420
|
+
"""return iq, id, iex currents and maximum torque per current """
|
421
|
+
T0 = self.torque_iqd(np.sqrt(2)*i1max, 0, self.bounds[-1][0])
|
422
|
+
def i1tq(tq):
|
423
|
+
return i1max - np.linalg.norm(self.iqd_tmech(tq, n)[:2])/np.sqrt(2)
|
424
|
+
tq = so.fsolve(i1tq, T0)[0]
|
425
|
+
iq, id, iex = self.iqd_tmech(tq, n)
|
426
|
+
return iq, id, iex, tq
|
427
|
+
|
428
|
+
def iqd_tmech_umax(self, torque, w1, u1max, log=0, **kwargs):
|
429
|
+
"""return currents and shaft torque at stator frequency and
|
430
|
+
with minimal losses at max voltage"""
|
431
|
+
iqde = self.iqd_tmech(torque, w1/2/np.pi/self.p)
|
432
|
+
if np.linalg.norm(
|
433
|
+
self.uqd(w1, *iqde)) <= u1max*np.sqrt(2):
|
434
|
+
if log:
|
435
|
+
log(iqde)
|
436
|
+
return (*iqde, torque)
|
437
|
+
beta, i1 = betai1(iqde[0], iqde[1])
|
438
|
+
iex = iqde[2]
|
439
|
+
|
440
|
+
def ubeta(b):
|
441
|
+
return np.sqrt(2)*u1max - np.linalg.norm(
|
442
|
+
self.uqd(w1, *iqd(b, i1), iex))
|
443
|
+
beta = -np.pi/4 if torque>0 else -3*np.pi/4
|
444
|
+
io = *iqd(beta, i1), iex
|
445
|
+
|
446
|
+
# logger.debug("--- torque %g io %s", torque, io)
|
447
|
+
with warnings.catch_warnings():
|
448
|
+
warnings.simplefilter("ignore")
|
449
|
+
n = w1/2/np.pi/self.p
|
450
|
+
def sqrtculoss(iqde):
|
451
|
+
pcu = self.culoss(iqde)
|
452
|
+
#logger.info("iqde %s pcu %g", iqde, pcu)
|
453
|
+
return pcu
|
454
|
+
|
455
|
+
res = so.minimize(
|
456
|
+
self.culoss, io, method='SLSQP', # trust-constr
|
457
|
+
bounds=self.bounds,
|
458
|
+
constraints=[
|
459
|
+
{'type': 'eq',
|
460
|
+
'fun': lambda iqd: self.tmech_iqd(*iqd, n) - torque},
|
461
|
+
{'type': 'eq',
|
462
|
+
'fun': lambda iqd: np.linalg.norm(
|
463
|
+
self.uqd(w1, *iqd)) - u1max*np.sqrt(2)}])
|
464
|
+
#if res['success']:
|
465
|
+
if log:
|
466
|
+
log(res.x)
|
467
|
+
return *res.x, self.tmech_iqd(*res.x, n)
|
468
|
+
#logger.warning("%s: w1=%f torque=%f, u1max=%f, io=%s",
|
469
|
+
# res['message'], w1, torque, u1max, io)
|
470
|
+
#raise ValueError(res['message'])
|
471
|
+
|
329
472
|
def iqd_torque_umax(self, torque, w1, u1max,
|
330
|
-
disp=False, maxiter=500, log=0):
|
473
|
+
disp=False, maxiter=500, log=0, **kwargs):
|
331
474
|
"""return currents for torque with minimal losses"""
|
332
|
-
#logger.info(">> torque %g w1 %g u1 %g io %s", torque, w1, u1max, io)
|
333
|
-
#if torque > 0:
|
334
|
-
# io = self.bounds[0][1]/2, 0, sum(self.bounds[-1])/2
|
335
|
-
#else:
|
336
|
-
# io = -self.bounds[0][1]/2, 0, sum(self.bounds[-1])/2
|
337
475
|
iqde = self.iqd_torque(torque, disp, maxiter)
|
338
476
|
if np.linalg.norm(
|
339
477
|
self.uqd(w1, *iqde)) <= u1max*np.sqrt(2):
|
@@ -342,8 +480,6 @@ class SynchronousMachine(object):
|
|
342
480
|
return (*iqde, torque)
|
343
481
|
io = iqde[0], 0, iqde[2]
|
344
482
|
# logger.debug("--- torque %g io %s", torque, io)
|
345
|
-
#logger.info(">> io %s", io)
|
346
|
-
|
347
483
|
with warnings.catch_warnings():
|
348
484
|
warnings.simplefilter("ignore")
|
349
485
|
def sqrtculoss(iqde):
|
@@ -354,7 +490,7 @@ class SynchronousMachine(object):
|
|
354
490
|
res = so.minimize(
|
355
491
|
self.culoss, io, method='SLSQP', # trust-constr
|
356
492
|
bounds=self.bounds,
|
357
|
-
options={'disp': disp, 'maxiter': maxiter},
|
493
|
+
#options={'disp': disp, 'maxiter': maxiter},
|
358
494
|
# jac=gradient_respecting_bounds(self.bounds, self.culoss),
|
359
495
|
constraints=[
|
360
496
|
{'type': 'eq',
|
@@ -363,6 +499,7 @@ class SynchronousMachine(object):
|
|
363
499
|
'fun': lambda iqd: np.linalg.norm(
|
364
500
|
self.uqd(w1, *iqd)) - u1max*np.sqrt(2)}])
|
365
501
|
if res['success']:
|
502
|
+
|
366
503
|
if log:
|
367
504
|
log(res.x)
|
368
505
|
return *res.x, self.torque_iqd(*res.x)
|
@@ -370,6 +507,17 @@ class SynchronousMachine(object):
|
|
370
507
|
res['message'], w1, torque, u1max, io)
|
371
508
|
raise ValueError(res['message'])
|
372
509
|
|
510
|
+
def w1_imax_umax(self, i1max, u1max):
|
511
|
+
"""return frequency w1 and shaft torque at voltage u1max and current i1max
|
512
|
+
|
513
|
+
Keyword arguments:
|
514
|
+
u1max -- the maximum voltage (Vrms)
|
515
|
+
i1max -- the maximum current (Arms)"""
|
516
|
+
iq, id, iex, T = self.mtpa(i1max)
|
517
|
+
n0 = np.sqrt(2)*u1max/np.linalg.norm(
|
518
|
+
self.psi(iq, id, iex))/2/np.pi/self.p
|
519
|
+
return self.w1_umax(u1max, iq, id, iex), T
|
520
|
+
|
373
521
|
def w1_umax(self, u, iq, id, iex):
|
374
522
|
"""return frequency w1 at given voltage u and id, iq current
|
375
523
|
|
@@ -381,7 +529,8 @@ class SynchronousMachine(object):
|
|
381
529
|
lambda w1: np.linalg.norm(self.uqd(w1, iq, id, iex))-u*np.sqrt(2),
|
382
530
|
w10)[0]
|
383
531
|
|
384
|
-
def characteristics(self, T, n, u1max, nsamples=50
|
532
|
+
def characteristics(self, T, n, u1max, nsamples=50,
|
533
|
+
with_tmech=True, **kwargs):
|
385
534
|
"""calculate torque speed characteristics.
|
386
535
|
return dict with list values of
|
387
536
|
n, T, u1, i1, beta, cosphi, pmech, n_type
|
@@ -391,15 +540,24 @@ class SynchronousMachine(object):
|
|
391
540
|
n -- (float) the maximum speed in 1/s
|
392
541
|
u1max -- (float) the maximum voltage in V rms
|
393
542
|
nsamples -- (optional) number of speed samples
|
543
|
+
with_tmech -- (optional) use friction and windage losses
|
394
544
|
"""
|
395
545
|
iq, id, iex = self.iqd_torque(T)
|
396
|
-
|
546
|
+
if with_tmech:
|
547
|
+
i1max = betai1(iq, id)[1]
|
548
|
+
if T < 0:
|
549
|
+
i1max = -i1max
|
550
|
+
w1type, Tf = self.w1_imax_umax(i1max, u1max)
|
551
|
+
else:
|
552
|
+
Tf = T
|
553
|
+
w1type = self.w1_umax(u1max, iq, id, iex)
|
554
|
+
logger.debug("w1type %f", w1type)
|
397
555
|
wmType = w1type/self.p
|
398
|
-
pmax =
|
556
|
+
pmax = Tf*wmType
|
399
557
|
|
400
558
|
def tload(wm):
|
401
|
-
if abs(wm*
|
402
|
-
return
|
559
|
+
if abs(wm*Tf) < abs(pmax):
|
560
|
+
return Tf
|
403
561
|
return pmax/wm
|
404
562
|
|
405
563
|
wmtab = []
|
@@ -417,27 +575,20 @@ class SynchronousMachine(object):
|
|
417
575
|
wmtab = (np.linspace(0, wmType, nx).tolist() +
|
418
576
|
np.linspace(wmType+dw, wmMax, nsamples-nx).tolist())
|
419
577
|
|
420
|
-
logger.info("Speed range %s", wmrange)
|
578
|
+
logger.info("Speed range T %f %s", Tf, wmrange)
|
421
579
|
wmtab[0] = 0
|
422
580
|
|
423
581
|
r = dict(u1=[], i1=[], id=[], iq=[], iex=[], T=[], cosphi=[], n=[],
|
424
582
|
beta=[], plfe1=[], plcu1=[], plcu2=[])
|
425
|
-
|
426
|
-
tfric = self.kfric_b*self.rotor_mass*30e-3/np.pi
|
427
|
-
w1tab = []
|
428
|
-
for wm, tq in zip(wmtab, T):
|
429
|
-
# try:
|
583
|
+
for wm, tq in zip(wmtab, [tload(wx) for wx in wmtab]):
|
430
584
|
w1 = wm*self.p
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
# min(self.bounds[-1][0], 0.9*iex)))[:-1]
|
439
|
-
#logger.info("w1 %g tq %g: iq %g iex %g tqx %g",
|
440
|
-
# w1, tq, iq, iex, tqx)
|
585
|
+
if with_tmech:
|
586
|
+
iq, id, iex, tqx = self.iqd_tmech_umax(
|
587
|
+
tq, w1, u1max)
|
588
|
+
else:
|
589
|
+
iq, id, iex, tqx = self.iqd_torque_umax(
|
590
|
+
tq, w1, u1max)
|
591
|
+
tqx -= self.tfric
|
441
592
|
uq, ud = self.uqd(w1, iq, id, iex)
|
442
593
|
u1 = np.linalg.norm((uq, ud))/np.sqrt(2)
|
443
594
|
f1 = w1/2/np.pi
|
@@ -456,18 +607,16 @@ class SynchronousMachine(object):
|
|
456
607
|
r['plfe1'].append(self.iqd_plfe1(iq, id, iex, f1))
|
457
608
|
r['plcu1'].append(self.m*i1**2*self.rstat(w1))
|
458
609
|
r['plcu2'].append(iex**2*self.rrot(0))
|
459
|
-
r['T'].append(
|
610
|
+
r['T'].append(tqx)
|
460
611
|
r['n'].append(wm/2/np.pi)
|
461
|
-
# except ValueError as ex:
|
462
|
-
# logger.warning("ex %s wm %f T %f", ex, wm, tq)
|
463
|
-
# break
|
464
612
|
|
465
613
|
r['plfe'] = r['plfe1']
|
466
614
|
r['plcu'] = (np.array(r['plcu1']) + np.array(r['plcu2'])).tolist()
|
467
|
-
r['
|
468
|
-
r['pmech'] = [2*np.pi*n*tq
|
615
|
+
r['plfw'] = [self.pfric(n) for n in r['n']]
|
616
|
+
r['pmech'] = [2*np.pi*n*tq
|
617
|
+
for n, tq in zip(r['n'], r['T'])]
|
469
618
|
pmech = np.array(r['pmech'])
|
470
|
-
pltotal = (np.array(r['plfe1']) + np.array(r['
|
619
|
+
pltotal = (np.array(r['plfe1']) + np.array(r['plfw']) +
|
471
620
|
np.array(r['plcu1']) + np.array(r['plcu2']))
|
472
621
|
r['losses'] = pltotal.tolist()
|
473
622
|
|
@@ -489,9 +638,9 @@ class SynchronousMachine(object):
|
|
489
638
|
|
490
639
|
class SynchronousMachinePsidq(SynchronousMachine):
|
491
640
|
|
492
|
-
def __init__(self, eecpars, lfe=1, wdg=1):
|
641
|
+
def __init__(self, eecpars, lfe=1, wdg=1, **kwargs):
|
493
642
|
super(self.__class__, self).__init__(
|
494
|
-
eecpars)
|
643
|
+
eecpars, **kwargs)
|
495
644
|
self.iqrange = (eecpars['psidq'][0]['iq'][0],
|
496
645
|
eecpars['psidq'][0]['iq'][-1])
|
497
646
|
self.idrange = (eecpars['psidq'][0]['id'][0],
|
@@ -578,8 +727,8 @@ class SynchronousMachinePsidq(SynchronousMachine):
|
|
578
727
|
|
579
728
|
|
580
729
|
class SynchronousMachineLdq(SynchronousMachine):
|
581
|
-
def __init__(self, eecpars, lfe=1, wdg=1):
|
582
|
-
super(self.__class__, self).__init__(eecpars)
|
730
|
+
def __init__(self, eecpars, lfe=1, wdg=1, **kwargs):
|
731
|
+
super(self.__class__, self).__init__(eecpars, **kwargs)
|
583
732
|
self.betarange = (eecpars['ldq'][0]['beta'][0]/180*np.pi,
|
584
733
|
eecpars['ldq'][0]['beta'][-1]/180*np.pi)
|
585
734
|
self.i1range = (0, eecpars['ldq'][0]['i1'][-1])
|
@@ -610,12 +759,15 @@ class SynchronousMachineLdq(SynchronousMachine):
|
|
610
759
|
[_splinterp(beta, i1, betax, i1x, l['psiq'])
|
611
760
|
for l in eecpars['ldq']]))
|
612
761
|
|
762
|
+
# extrapolate outside range
|
613
763
|
self.psidf = ip.RegularGridInterpolator(
|
614
764
|
(exc, betax, i1x), np.sqrt(2)*psid,
|
615
|
-
method='cubic',
|
765
|
+
method='cubic',
|
766
|
+
bounds_error=False, fill_value=None)
|
616
767
|
self.psiqf = ip.RegularGridInterpolator(
|
617
768
|
(exc, betax, i1x), np.sqrt(2)*psiq,
|
618
|
-
method='cubic'
|
769
|
+
method='cubic'
|
770
|
+
, bounds_error=False, fill_value=None)
|
619
771
|
i1max = np.sqrt(2)*(max(i1))
|
620
772
|
self.bounds = [(np.cos(min(beta))*i1max, i1max),
|
621
773
|
(-i1max, 0),
|
@@ -632,6 +784,7 @@ class SynchronousMachineLdq(SynchronousMachine):
|
|
632
784
|
np.array([l['losses'][k]
|
633
785
|
for l in eecpars[idname]]))
|
634
786
|
for k in keys}
|
787
|
+
# fill value with nan outside range
|
635
788
|
self._losses = {k: ip.RegularGridInterpolator(
|
636
789
|
(exc, beta, i1), lfe*np.array(pfe[k]),
|
637
790
|
method='cubic', bounds_error=False, fill_value=None)
|
@@ -655,7 +808,8 @@ class SynchronousMachineLdq(SynchronousMachine):
|
|
655
808
|
try:
|
656
809
|
return self.psidf((iex, beta, i1)), self.psiqf((iex, beta, i1))
|
657
810
|
except ValueError as ex:
|
658
|
-
logger.error(iex
|
811
|
+
logger.error("iex %s iq %f id %f beta %f i1 %f",
|
812
|
+
iex, iq, id, beta, i1)
|
659
813
|
raise ex
|
660
814
|
|
661
815
|
def plfe1(self, beta, i1, iex, f1):
|
femagtools/machine/utils.py
CHANGED
@@ -346,6 +346,7 @@ def dqparident(workdir, engine, temp, machine,
|
|
346
346
|
speed: rotor speed in 1/s (default 160/p)
|
347
347
|
i1_max: maximum current in A rms (default approx 3*i1nom)
|
348
348
|
period_frac: fraction of rotating angle (default 6)
|
349
|
+
cmd: femag executable
|
349
350
|
"""
|
350
351
|
import pathlib
|
351
352
|
|
@@ -368,13 +369,16 @@ def dqparident(workdir, engine, temp, machine,
|
|
368
369
|
period_frac = 1 # TODO: missing femag support
|
369
370
|
|
370
371
|
# winding resistance
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
372
|
+
wpar = {'Q': machine['stator']['num_slots'],
|
373
|
+
'm': machine['windings']['num_phases'],
|
374
|
+
'p': machine['poles']//2}
|
375
|
+
|
376
|
+
if 'coil_span' in machine['windings']:
|
377
|
+
wpar['yd'] = machine['windings']['coil_span']
|
378
|
+
if 'num_layers' in machine['windings']:
|
379
|
+
wpar['l'] = machine['windings']['num_layers']
|
380
|
+
|
381
|
+
wdg = windings.Winding(wpar)
|
378
382
|
|
379
383
|
lfe = machine['lfe']
|
380
384
|
g = machine['windings'].get('num_par_wdgs', 1)
|
@@ -401,7 +405,7 @@ def dqparident(workdir, engine, temp, machine,
|
|
401
405
|
parvar = parstudy.List(
|
402
406
|
workdir, condMat=condMat,
|
403
407
|
magnetizingCurves=magnetizingCurves,
|
404
|
-
magnets=magnetMat)
|
408
|
+
magnets=magnetMat, cmd=kwargs.get('cmd', None))
|
405
409
|
|
406
410
|
leakfile = pathlib.Path(workdir) / 'end_wind_leak.dat'
|
407
411
|
leakfile.unlink(missing_ok=True)
|
femagtools/mcv.py
CHANGED
@@ -496,8 +496,7 @@ class Writer(Mcv):
|
|
496
496
|
pfe = self.losses['pfe']
|
497
497
|
f = self.losses['f']
|
498
498
|
B = self.losses['B']
|
499
|
-
|
500
|
-
losses = [list(p) + [0]*(colsize-len(p)) for p in pfe]
|
499
|
+
losses = [list(p) + [None]*(len(B)-len(p)) for p in pfe]
|
501
500
|
fo = self.mc1_base_frequency
|
502
501
|
Bo = self.mc1_base_induction
|
503
502
|
fit_jordan = False
|
@@ -557,28 +556,27 @@ class Writer(Mcv):
|
|
557
556
|
self.losses['fo'] = fo
|
558
557
|
|
559
558
|
self.writeBlock([nfreq, nind])
|
560
|
-
self.writeBlock(B +
|
561
|
-
[0.0]*(M_LOSS_INDUCT - nind))
|
559
|
+
self.writeBlock([float(b) for b in B] + [0.0]*(M_LOSS_INDUCT - nind))
|
562
560
|
|
563
561
|
for f, p in zip(self.losses['f'], pfe):
|
564
562
|
if f > 0:
|
565
563
|
y = np.array(p)
|
566
|
-
losses = y[y != np.array(None)]
|
564
|
+
losses = [float(x) for x in y[y != np.array(None)]]
|
567
565
|
if len(losses) == nind:
|
568
566
|
pl = p
|
569
567
|
else:
|
570
568
|
n = len(losses)
|
571
569
|
cw, alfa, beta = lc.fitsteinmetz(
|
572
570
|
f, B[:n], losses, Bo, fo)
|
573
|
-
pl = [lc.pfe_steinmetz(
|
571
|
+
pl = losses + [lc.pfe_steinmetz(
|
574
572
|
f, b, cw, alfa, beta,
|
575
573
|
self.losses['fo'],
|
576
574
|
self.losses['Bo'])
|
577
|
-
|
575
|
+
for b in B[n:]]
|
578
576
|
logger.debug("%s", pl)
|
579
577
|
self.writeBlock(pl +
|
580
578
|
[0.0]*(M_LOSS_INDUCT - len(pl)))
|
581
|
-
self.writeBlock(f)
|
579
|
+
self.writeBlock(float(f))
|
582
580
|
for m in range(M_LOSS_FREQ - len(pfe)):
|
583
581
|
self.writeBlock([0.0]*M_LOSS_INDUCT)
|
584
582
|
self.writeBlock(0.0)
|
femagtools/model.py
CHANGED
@@ -11,6 +11,7 @@
|
|
11
11
|
import logging
|
12
12
|
import string
|
13
13
|
import numpy as np
|
14
|
+
from . import windings
|
14
15
|
|
15
16
|
logger = logging.getLogger(__name__)
|
16
17
|
#
|
@@ -156,8 +157,17 @@ class MachineModel(Model):
|
|
156
157
|
for mcv in ('mcvkey_yoke', 'mcvkey_shaft'):
|
157
158
|
if mcv not in self.magnet:
|
158
159
|
self.magnet[mcv] = 'dummy'
|
159
|
-
if 'coord_system' in parameters:
|
160
|
+
if 'coord_system' in parameters or 'afmtype' in parameters:
|
160
161
|
self.move_action = 1
|
162
|
+
wdg = windings.Winding({'Q': self.stator['num_slots'],
|
163
|
+
'p': self.poles//2,
|
164
|
+
'm': self.windings.get('num_phases', 3),
|
165
|
+
'l': self.windings.get('num_layers', 1)})
|
166
|
+
self.windings['wdgscheme'] = ''.join([
|
167
|
+
'{'] + [','.join([''.join(['{']+[','.join([''.join([
|
168
|
+
'{', ','.join(
|
169
|
+
[str(n) for n in z]), '}']) for z in l])] + ['}'])
|
170
|
+
for l in wdg.zoneplan()])] + ['}'])
|
161
171
|
else:
|
162
172
|
self.coord_system = 0
|
163
173
|
self.move_action = 0
|
femagtools/parstudy.py
CHANGED
@@ -111,7 +111,7 @@ class ParameterStudy(object):
|
|
111
111
|
raise ValueError("directory {} is not empty".format(dirname))
|
112
112
|
self.reportdir = dirname
|
113
113
|
|
114
|
-
def setup_model(self, builder, model, recsin):
|
114
|
+
def setup_model(self, builder, model, recsin=''):
|
115
115
|
"""builds model in current workdir and returns its filenames"""
|
116
116
|
# get and write mag curves
|
117
117
|
mc_files = self.femag.copy_magnetizing_curves(model, recsin=recsin)
|