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/pm.py
CHANGED
@@ -7,11 +7,13 @@
|
|
7
7
|
Copyright 2022: Semafor Informatik & Energie AG, Switzerland
|
8
8
|
"""
|
9
9
|
import logging
|
10
|
+
import warnings
|
10
11
|
import numpy as np
|
11
12
|
import numpy.linalg as la
|
12
|
-
from .utils import iqd, betai1, skin_resistance, dqparident
|
13
|
+
from .utils import iqd, betai1, skin_resistance, dqparident, KTH
|
13
14
|
import scipy.optimize as so
|
14
15
|
import scipy.interpolate as ip
|
16
|
+
from functools import partial
|
15
17
|
|
16
18
|
logger = logging.getLogger(__name__)
|
17
19
|
|
@@ -42,14 +44,31 @@ class PmRelMachine(object):
|
|
42
44
|
self.io = (1, -1)
|
43
45
|
self.fo = 50.0
|
44
46
|
self.tcu1 = 20
|
45
|
-
self.
|
47
|
+
self.tmag = 20
|
48
|
+
self.zeta1 = 0.2
|
46
49
|
self.gam = 0.7
|
47
50
|
self.kh = 2
|
51
|
+
self.kpfe = 1 # iron loss factor
|
48
52
|
self.kfric_b = 1
|
53
|
+
self.rotor_mass = 0
|
54
|
+
self.kth1 = KTH
|
55
|
+
|
56
|
+
# overwritable function: skin_resistance(r0, w, tcu, kth)
|
57
|
+
# Arguments:
|
58
|
+
# r0: (float) dc resistance in Ohm at 20°C
|
59
|
+
# w: (float) frequency in rad
|
60
|
+
# tcu: (float) conductor temperature in °C
|
61
|
+
# kth: (float) temperature coefficient (default 3.9 e-3)
|
62
|
+
self.skin_resistance = None
|
63
|
+
|
49
64
|
# TODO: need this for speedranges and idq_imax_umax mtpv only
|
50
65
|
self.check_extrapolation = True
|
51
66
|
for k in kwargs.keys():
|
52
67
|
setattr(self, k, kwargs[k])
|
68
|
+
try:
|
69
|
+
self.tfric = self.kfric_b*self.rotor_mass*30e-3/np.pi
|
70
|
+
except AttributeError:
|
71
|
+
self.tfric = 0
|
53
72
|
|
54
73
|
self.plexp = {'styoke_hyst': 1.0,
|
55
74
|
'stteeth_hyst': 1.0,
|
@@ -57,14 +76,24 @@ class PmRelMachine(object):
|
|
57
76
|
'stteeth_eddy': 2.0,
|
58
77
|
'rotor_hyst': 1.0,
|
59
78
|
'rotor_eddy': 2.0}
|
60
|
-
|
79
|
+
def nolosses(x, y):
|
80
|
+
return 0
|
81
|
+
self._losses = {k: nolosses for k in (
|
61
82
|
'styoke_hyst', 'stteeth_hyst',
|
62
83
|
'styoke_eddy', 'stteeth_eddy',
|
63
84
|
'rotor_hyst', 'rotor_eddy',
|
64
85
|
'magnet')}
|
65
86
|
|
87
|
+
def pfric(self, n):
|
88
|
+
"""friction and windage losses"""
|
89
|
+
return 2*np.pi*n*self.tfric
|
90
|
+
|
66
91
|
def rstat(self, w):
|
67
|
-
"""stator resistance
|
92
|
+
"""stator resistance
|
93
|
+
"""
|
94
|
+
if self.skin_resistance is not None:
|
95
|
+
return self.skin_resistance(self.r1, w, self.tcu1, kth=self.kth1)
|
96
|
+
|
68
97
|
return skin_resistance(self.r1, w, self.tcu1, self.zeta1,
|
69
98
|
self.gam, self.kh)
|
70
99
|
|
@@ -74,32 +103,81 @@ class PmRelMachine(object):
|
|
74
103
|
tq = self.m*self.p/2*(psid*iq - psiq*id)
|
75
104
|
return tq
|
76
105
|
|
77
|
-
def iqd_torque(self, torque):
|
106
|
+
def iqd_torque(self, torque, iqd0=0, with_mtpa=True):
|
78
107
|
"""return minimum d-q-current for torque"""
|
79
108
|
if np.abs(torque) < 1e-2:
|
80
109
|
return (0, 0)
|
81
|
-
if
|
82
|
-
|
110
|
+
if np.isscalar(iqd0):
|
111
|
+
i0 = self.io
|
83
112
|
else:
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
113
|
+
i0 = iqd0
|
114
|
+
if with_mtpa:
|
115
|
+
res = so.minimize(
|
116
|
+
lambda iqd: la.norm(iqd), i0, method='SLSQP',
|
117
|
+
constraints=({'type': 'eq',
|
118
|
+
'fun': lambda iqd:
|
119
|
+
self.torque_iqd(*iqd) - torque}))
|
120
|
+
if res.success:
|
121
|
+
#raise ValueError(f'Torque {torque}, io {i0}: {res.message}')
|
122
|
+
return res.x
|
123
|
+
def func(i1):
|
124
|
+
return torque - self.mtpa(i1)[2]
|
125
|
+
i1 = so.fsolve(func, res.x[0])[0]
|
126
|
+
return self.mtpa(i1)[:2]
|
127
|
+
|
128
|
+
|
129
|
+
def tqiq(iq):
|
130
|
+
return torque - self.torque_iqd(float(iq), 0)
|
131
|
+
iq = so.fsolve(tqiq, (i0[0],))[0]
|
132
|
+
return iq, 0, self.torque_iqd(iq, 0)
|
133
|
+
|
134
|
+
def iqd_tmech(self, torque, n, iqd0=0, with_mtpa=True):
|
135
|
+
"""return minimum d-q-current for shaft torque"""
|
136
|
+
if np.abs(torque) < 1e-2:
|
137
|
+
return (0, 0)
|
138
|
+
if np.isscalar(iqd0):
|
139
|
+
i0 = self.io
|
140
|
+
else:
|
141
|
+
i0 = iqd0
|
142
|
+
|
143
|
+
if with_mtpa:
|
144
|
+
res = so.minimize(
|
145
|
+
lambda iqd: la.norm(iqd), i0, method='SLSQP',
|
146
|
+
constraints=({'type': 'eq',
|
147
|
+
'fun': lambda iqd:
|
148
|
+
self.tmech_iqd(*iqd, n) - torque}))
|
149
|
+
if res.success:
|
150
|
+
return res.x
|
151
|
+
|
152
|
+
#logger.warning("n: %s, torque %s: %s %s",
|
153
|
+
# 60*n, torque, res.message, i0)
|
154
|
+
# try a different approach:
|
155
|
+
#raise ValueError(
|
156
|
+
# f'Torque {torque:.1f} speed {60*n:.1f} {res.message}')
|
157
|
+
def func(i1):
|
158
|
+
return torque - self.mtpa_tmech(i1, n)[2]
|
159
|
+
i1 = so.fsolve(func, res.x[0])[0]
|
160
|
+
return self.mtpa_tmech(i1, n)[:2]
|
161
|
+
|
162
|
+
def tqiq(iq):
|
163
|
+
return torque - self.tmech_iqd(float(iq), 0, n)
|
164
|
+
iq = so.fsolve(tqiq, (i0[0],))[0]
|
165
|
+
return iq, 0, self.tmech_iqd(iq, 0, n)
|
166
|
+
|
167
|
+
def tloss_iqd(self, iq, id, n):
|
168
|
+
"""return loss torque of d-q current, iron loss correction factor
|
169
|
+
and friction windage losses"""
|
170
|
+
if n > 1e-3:
|
171
|
+
f1 = self.p*n
|
172
|
+
plfe = self.kpfe * (self.iqd_plfe1(iq, id, f1) + self.iqd_plfe2(iq, id, f1))
|
173
|
+
pmag = self.iqd_plmag(iq, id, f1)
|
174
|
+
return (plfe + pmag + self.pfric(n))/(2*np.pi*n)
|
175
|
+
return 0
|
176
|
+
|
177
|
+
def tmech_iqd(self, iq, id, n):
|
178
|
+
"""return shaft torque of d-q current and speed"""
|
179
|
+
tq = self.torque_iqd(iq, id)
|
180
|
+
return tq - self.tloss_iqd(iq, id, n)
|
103
181
|
|
104
182
|
def uqd(self, w1, iq, id):
|
105
183
|
"""return uq, ud of frequency w1 and d-q current"""
|
@@ -120,6 +198,26 @@ class PmRelMachine(object):
|
|
120
198
|
lambda w1: la.norm(self.uqd(w1, iq, id))-u*np.sqrt(2),
|
121
199
|
w10)[0]
|
122
200
|
|
201
|
+
def w1_imax_umax(self, i1max, u1max):
|
202
|
+
"""return frequency w1 and torque at voltage u1max and current i1max
|
203
|
+
|
204
|
+
Keyword arguments:
|
205
|
+
u1max -- the maximum voltage (Vrms)
|
206
|
+
i1max -- the maximum current (Arms)"""
|
207
|
+
iq, id, T = self.mtpa(i1max)
|
208
|
+
n0 = u1max/np.linalg.norm(self.psi(iq, id))/2/2/np.pi/self.p
|
209
|
+
sign = -1 if i1max > 0 else 1
|
210
|
+
res = so.minimize(
|
211
|
+
lambda n: sign*self.mtpa_tmech(i1max, n)[2],
|
212
|
+
n0,
|
213
|
+
constraints={
|
214
|
+
'type': 'eq',
|
215
|
+
'fun': lambda n:
|
216
|
+
np.sqrt(2)*u1max - la.norm(
|
217
|
+
self.uqd(2*np.pi*n*self.p,
|
218
|
+
*self.mtpa_tmech(i1max, n)[:2]))})
|
219
|
+
return 2*np.pi*res.x[0]*self.p, self.mtpa_tmech(i1max, res.x[0])[2]
|
220
|
+
|
123
221
|
def w1_u(self, u, iq, id):
|
124
222
|
"""return frequency w1 at given voltage u and id, iq current
|
125
223
|
(obsolete, use w1_umax)"""
|
@@ -162,16 +260,27 @@ class PmRelMachine(object):
|
|
162
260
|
np.array((uq, ud)) - self.uqd(w1, *iqd),
|
163
261
|
(0, self.io[1]))
|
164
262
|
|
263
|
+
def i1_tmech(self, torque, beta, n):
|
264
|
+
"return i1 current with given torque and beta"
|
265
|
+
i1, info, ier, mesg = so.fsolve(
|
266
|
+
lambda i1: self.tmech_iqd(*iqd(beta, i1), n)-torque,
|
267
|
+
self.i1range[1]/2,
|
268
|
+
full_output=True)
|
269
|
+
if ier == 1:
|
270
|
+
return i1
|
271
|
+
raise ValueError("no solution found for torque {}, beta {} io {}".format(
|
272
|
+
torque, beta, self.io))
|
273
|
+
|
165
274
|
def i1_torque(self, torque, beta):
|
166
275
|
"return i1 current with given torque and beta"
|
167
276
|
i1, info, ier, mesg = so.fsolve(
|
168
277
|
lambda i1: self.torque_iqd(*iqd(beta, i1))-torque,
|
169
|
-
self.
|
278
|
+
self.i1range[1]/2,
|
170
279
|
full_output=True)
|
171
280
|
if ier == 1:
|
172
281
|
return i1
|
173
|
-
raise ValueError("no solution found for torque {}, beta {}".format(
|
174
|
-
torque, beta))
|
282
|
+
raise ValueError("no solution found for torque {}, beta {} io {}".format(
|
283
|
+
torque, beta, self.io))
|
175
284
|
|
176
285
|
def i1_voltage(self, w1, u1, beta):
|
177
286
|
"return i1 current with given w1, u1 and beta"
|
@@ -190,10 +299,64 @@ class PmRelMachine(object):
|
|
190
299
|
return so.fsolve(
|
191
300
|
lambda id: self.torque_iqd(np.array([iq]), id)-torque, id0)[0]
|
192
301
|
|
193
|
-
def
|
302
|
+
def iqd_tmech_umax(self, torque, w1, u1max, log=0, with_mtpa=True):
|
303
|
+
"""return d-q current and shaft torque at stator frequency and max voltage
|
304
|
+
with minimal current"""
|
305
|
+
n = w1/2/np.pi/self.p
|
306
|
+
if with_mtpa:
|
307
|
+
i0 = self.iqd_tmech(torque, n)
|
308
|
+
else:
|
309
|
+
i1 = self.i1_tmech(torque, 0, n)
|
310
|
+
i0 = iqd(0, i1)
|
311
|
+
|
312
|
+
if np.linalg.norm(self.uqd(w1, *i0))/np.sqrt(2) > u1max:
|
313
|
+
beta, i1 = betai1(*i0)
|
314
|
+
with warnings.catch_warnings():
|
315
|
+
warnings.simplefilter("ignore")
|
316
|
+
i0 = iqd(so.fsolve(lambda b: u1max - np.linalg.norm(
|
317
|
+
self.uqd(w1, *iqd(b, i1)))/np.sqrt(2),
|
318
|
+
beta)[0], i1)
|
319
|
+
|
320
|
+
res = so.minimize(lambda iqd: np.linalg.norm(iqd), i0, method='SLSQP',
|
321
|
+
constraints=(
|
322
|
+
{'type': 'eq',
|
323
|
+
'fun': lambda iqd:
|
324
|
+
self.tmech_iqd(*iqd, n) - torque},
|
325
|
+
{'type': 'ineq',
|
326
|
+
'fun': lambda iqd:
|
327
|
+
np.sqrt(2)*u1max - la.norm(self.uqd(w1, *iqd))}))
|
328
|
+
iq, id = res.x
|
329
|
+
else:
|
330
|
+
iq, id = i0
|
331
|
+
if log:
|
332
|
+
try:
|
333
|
+
log((iq,id))
|
334
|
+
except:
|
335
|
+
pass # logger is not correct
|
336
|
+
logger.debug("iqd_tmech_umax w1=%f torque=%f %f iq=%f id=%f u1 u1 %f %f",
|
337
|
+
w1, torque, self.torque_iqd(iq, id), iq, id,
|
338
|
+
u1max, np.linalg.norm(
|
339
|
+
self.uqd(w1, iq, id))/np.sqrt(2))
|
340
|
+
return iq, id, self.tmech_iqd(iq, id, n)
|
341
|
+
|
342
|
+
def iqd_torque_umax(self, torque, w1, u1max, log=0, with_mtpa=True):
|
194
343
|
"""return d-q current and torque at stator frequency and max voltage
|
195
|
-
with
|
196
|
-
|
344
|
+
with minimal current"""
|
345
|
+
if with_mtpa:
|
346
|
+
i0 = self.iqd_torque(torque)
|
347
|
+
else:
|
348
|
+
i1 = self.i1_torque(torque, 0)
|
349
|
+
i0 = iqd(0, i1)
|
350
|
+
|
351
|
+
if np.linalg.norm(self.uqd(w1, *i0))/np.sqrt(2) > u1max:
|
352
|
+
beta, i1 = betai1(*i0)
|
353
|
+
with warnings.catch_warnings():
|
354
|
+
warnings.simplefilter("ignore")
|
355
|
+
i0 = iqd(so.fsolve(lambda b: u1max - np.linalg.norm(
|
356
|
+
self.uqd(w1, *iqd(b, i1)))/np.sqrt(2),
|
357
|
+
beta)[0], i1)
|
358
|
+
|
359
|
+
res = so.minimize(lambda iqd: la.norm(iqd), i0, method='SLSQP',
|
197
360
|
constraints=(
|
198
361
|
{'type': 'eq',
|
199
362
|
'fun': lambda iqd:
|
@@ -201,18 +364,70 @@ class PmRelMachine(object):
|
|
201
364
|
{'type': 'ineq',
|
202
365
|
'fun': lambda iqd:
|
203
366
|
np.sqrt(2)*u1max - la.norm(self.uqd(w1, *iqd))}))
|
367
|
+
|
204
368
|
if log:
|
205
369
|
log(res.x)
|
206
370
|
logger.debug("iqd_torque_umax w1=%f torque=%f %f iq=%f id=%f u1 u1 %f %f",
|
207
|
-
|
208
|
-
|
209
|
-
|
371
|
+
w1, torque, self.torque_iqd(*res.x), res.x[0], res.x[1],
|
372
|
+
u1max, np.linalg.norm(
|
373
|
+
self.uqd(w1, *res.x))/np.sqrt(2))
|
210
374
|
return res.x[0], res.x[1], self.torque_iqd(*res.x)
|
211
375
|
|
212
|
-
def
|
376
|
+
def iqd_pmech_imax_umax(self, n, P, i1max, u1max, with_mtpa, with_tmech, log=0):
|
377
|
+
"""return d-q current and shaft torque at speed n, P const and max voltage"""
|
378
|
+
T = P / n / 2 / np.pi
|
379
|
+
w1 = 2*np.pi *n * self.p
|
380
|
+
logger.debug("field weakening mode %.2f kW @ %.0f rpm %.1f Nm; "
|
381
|
+
"u1=%.0f V; plfric=%.2f W",
|
382
|
+
P/1000, n*60, T, u1max, self.pfric(n))
|
383
|
+
iq, id = self.iqd_torque_imax_umax(T, n, u1max, with_tmech=with_tmech)[:2]
|
384
|
+
|
385
|
+
if with_tmech:
|
386
|
+
tcon = {'type': 'eq',
|
387
|
+
'fun': lambda iqd:
|
388
|
+
self.tmech_iqd(*iqd, n) - T}
|
389
|
+
else:
|
390
|
+
tcon = {'type': 'eq',
|
391
|
+
'fun': lambda iqd:
|
392
|
+
self.torque_iqd(*iqd) - T}
|
393
|
+
|
394
|
+
res = so.minimize(lambda iqd: np.linalg.norm(iqd), (iq, id),
|
395
|
+
method='SLSQP',
|
396
|
+
constraints=[tcon,
|
397
|
+
{'type': 'ineq',
|
398
|
+
'fun': lambda iqd:
|
399
|
+
(u1max * np.sqrt(2)) - np.linalg.norm(
|
400
|
+
self.uqd(w1, *iqd))}])
|
401
|
+
if res.success:
|
402
|
+
beta, i1 = betai1(*res.x)
|
403
|
+
logger.debug("pconst %s i1 %.2f", res.x, betai1(*res.x)[1])
|
404
|
+
if log:
|
405
|
+
log(res.x)
|
406
|
+
if i1 > abs(i1max):
|
407
|
+
return self.iqd_imax_umax(i1max, w1, u1max, T,
|
408
|
+
with_mtpv=False,
|
409
|
+
with_tmech=with_tmech)
|
410
|
+
if with_tmech:
|
411
|
+
return *res.x, self.tmech_iqd(*res.x, n)
|
412
|
+
else:
|
413
|
+
return *res.x, self.torque_iqd(*res.x)
|
414
|
+
|
415
|
+
if with_tmech:
|
416
|
+
iq, id, tq = self.mtpv_tmech(w1, u1max, iqd0=res.x, maxtorque=T>0)
|
417
|
+
else:
|
418
|
+
iq, id, tq = self.mtpv(w1, u1max, iqd0=res.x, maxtorque=T>0)
|
419
|
+
logger.debug("mtpa %s i1 %.2f", res.x, betai1(*res.x)[1])
|
420
|
+
if log:
|
421
|
+
log((iq, id, tq))
|
422
|
+
return iq, id, tq
|
423
|
+
|
424
|
+
def iqd_torque_imax_umax(self, torque, n, u1max, with_tmech=False, log=0):
|
213
425
|
"""return d-q current and torque at stator frequency w1,
|
214
426
|
max voltage and current"""
|
215
|
-
|
427
|
+
if with_tmech:
|
428
|
+
iq, id = self.iqd_tmech(torque, n)
|
429
|
+
else:
|
430
|
+
iq, id = self.iqd_torque(torque)
|
216
431
|
w1 = 2*np.pi*n*self.p
|
217
432
|
# Constant torque range
|
218
433
|
if np.linalg.norm(self.uqd(w1, iq, id)) <= u1max*np.sqrt(2):
|
@@ -221,30 +436,14 @@ class PmRelMachine(object):
|
|
221
436
|
return (iq, id, torque)
|
222
437
|
# Field weaking range
|
223
438
|
imax = betai1(iq, id)[1]
|
224
|
-
iq, id, tq = self.iqd_imax_umax(imax, w1, u1max, torque,
|
439
|
+
iq, id, tq = self.iqd_imax_umax(imax, w1, u1max, torque,
|
440
|
+
with_mtpv=False, with_tmech=with_tmech)
|
225
441
|
if log:
|
226
442
|
log((iq, id, tq))
|
227
443
|
return iq, id, tq
|
228
444
|
|
229
|
-
def
|
230
|
-
"""return d-q current and
|
231
|
-
max voltage and current"""
|
232
|
-
sign = -1 if i1max > 0 else 1
|
233
|
-
res = so.minimize(lambda iqd: sign*self.torque_iqd(*iqd),
|
234
|
-
self.mtpa(i1max)[:2],
|
235
|
-
method='SLSQP',
|
236
|
-
constraints=(
|
237
|
-
{'type': 'ineq',
|
238
|
-
'fun': lambda iqd:
|
239
|
-
np.sqrt(2)*abs(i1max) - betai1(*iqd)[1]},
|
240
|
-
{'type': 'eq',
|
241
|
-
'fun': lambda iqd:
|
242
|
-
np.sqrt(2)*u1max - la.norm(self.uqd(w1, *iqd))}
|
243
|
-
))
|
244
|
-
return res.x[0], res.x[1], self.torque_iqd(*res.x)
|
245
|
-
|
246
|
-
def iqd_imax_umax(self, i1max, w1, u1max, torque, with_mtpv=True):
|
247
|
-
"""return d-q current and torque at stator frequency and max voltage
|
445
|
+
def iqd_imax_umax(self, i1max, w1, u1max, torque, with_mtpv=True, with_tmech=True):
|
446
|
+
"""return d-q current and shaft torque at stator frequency and max voltage
|
248
447
|
and max current (for motor operation if maxtorque else generator operation)"""
|
249
448
|
|
250
449
|
if torque > 0:
|
@@ -268,7 +467,11 @@ class PmRelMachine(object):
|
|
268
467
|
self.uqd(w1, *iqd(b, abs(i1max))))/np.sqrt(2)
|
269
468
|
if u1norm(b1) < u1max:
|
270
469
|
# must reduce current (torque)
|
271
|
-
|
470
|
+
logger.debug('iqd_imax_umax must reduce torque')
|
471
|
+
if with_tmech:
|
472
|
+
iq, id, tq = self.iqd_tmech_umax(torque, w1, u1max)
|
473
|
+
else:
|
474
|
+
iq, id, tq = self.iqd_torque_umax(torque, w1, u1max)
|
272
475
|
if not with_mtpv:
|
273
476
|
return iq, id, tq
|
274
477
|
beta, i1 = betai1(iq, id)
|
@@ -276,7 +479,6 @@ class PmRelMachine(object):
|
|
276
479
|
for k in range(kmax):
|
277
480
|
bx = b0 + (b1-b0)/2
|
278
481
|
ux = u1norm(bx)
|
279
|
-
#logger.info("%d: bx %f ux %f", k, bx, ux)
|
280
482
|
if ux > u1max:
|
281
483
|
b1 = bx
|
282
484
|
else:
|
@@ -290,29 +492,28 @@ class PmRelMachine(object):
|
|
290
492
|
if abs(du) < 0.1:
|
291
493
|
iq, id = iqd(beta, abs(i1max))
|
292
494
|
else:
|
293
|
-
|
495
|
+
logger.debug('oops? iqd_imax_umax one more torque reduction')
|
496
|
+
if with_tmech:
|
497
|
+
iq, id = self.iqd_tmech(torque, w1/2/np.pi/self.p,
|
498
|
+
iqd(beta, i1))[:2]
|
499
|
+
else:
|
500
|
+
iq, id = self.iqd_torque(torque, w1/2/np.pi/self.p,
|
501
|
+
iqd(beta, i1))[:2]
|
294
502
|
if with_mtpv:
|
295
|
-
# must check mtpv
|
296
|
-
self.check_extrapolation = False
|
297
503
|
try:
|
298
|
-
|
299
|
-
return
|
300
|
-
|
301
|
-
|
302
|
-
w, _, ier, _ = so.fsolve(voltw1, w1, full_output=True)
|
303
|
-
logger.info("3: ier %d w %f w1 %f", ier, w, w1)
|
304
|
-
if ier in (1,5) and abs(w[0]) <= w1:
|
305
|
-
self.check_extrapolation = True
|
306
|
-
return self.mtpv(w1, u1max)
|
504
|
+
if with_tmech:
|
505
|
+
return self.mtpv_tmech(w1, u1max, iqd0=(iq, id))
|
506
|
+
else:
|
507
|
+
return self.mtpv(w1, u1max, iqd0=(iq, id))
|
307
508
|
except ValueError as e:
|
308
|
-
logger.warning(
|
309
|
-
w1, i1, u1max, e)
|
310
|
-
self.check_extrapolation = True
|
311
|
-
|
509
|
+
logger.warning(e)
|
312
510
|
iq, id = iqd(beta, abs(i1))
|
313
|
-
|
511
|
+
if with_tmech:
|
512
|
+
tq = self.tmech_iqd(iq, id, w1/2/np.pi/self.p)
|
513
|
+
else:
|
514
|
+
tq = self.torque_iqd(iq, id)
|
314
515
|
logger.debug("iqd_imax_umax w1=%f torque=%f %f iq=%f id=%f u1 %f %f",
|
315
|
-
|
516
|
+
w1, torque, tq, iq, id, u1max, np.linalg.norm(
|
316
517
|
self.uqd(w1, iq, id))/np.sqrt(2))
|
317
518
|
return iq, id, tq
|
318
519
|
|
@@ -327,6 +528,17 @@ class PmRelMachine(object):
|
|
327
528
|
iq, id = iqd(bopt[0], abs(i1))
|
328
529
|
return [iq, id, sign*fopt]
|
329
530
|
|
531
|
+
def mtpa_tmech(self, i1, n):
|
532
|
+
"""return iq, id, shaft torque at maximum torque of current i1"""
|
533
|
+
sign = -1 if i1 > 0 else 1
|
534
|
+
b0 = 0 if i1 > 0 else -np.pi
|
535
|
+
bopt, fopt, iter, funcalls, warnflag = so.fmin(
|
536
|
+
lambda x: sign*self.tmech_iqd(*iqd(x, abs(i1)), n), b0,
|
537
|
+
full_output=True,
|
538
|
+
disp=0)
|
539
|
+
iq, id = iqd(bopt[0], abs(i1))
|
540
|
+
return [iq, id, sign*fopt]
|
541
|
+
|
330
542
|
def mtpv(self, w1, u1, iqd0=0, maxtorque=True):
|
331
543
|
"""return d-q-current, torque for voltage and frequency
|
332
544
|
with maximum (maxtorque=True) or minimum torque """
|
@@ -335,19 +547,38 @@ class PmRelMachine(object):
|
|
335
547
|
i0 = (-sign*self.i1range[1]/20, -self.i1range[1]/np.sqrt(2))
|
336
548
|
else:
|
337
549
|
i0 = iqd0
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
'fun': lambda iqd:
|
346
|
-
np.sqrt(2)*u1 - la.norm(self.uqd(w1, *iqd))},))
|
550
|
+
n = w1/2/np.pi/self.p
|
551
|
+
res = so.minimize(lambda iqd: sign*self.torque_iqd(*iqd), i0,
|
552
|
+
method='SLSQP',
|
553
|
+
constraints={
|
554
|
+
'type': 'eq',
|
555
|
+
'fun': lambda iqd:
|
556
|
+
np.sqrt(2)*u1 - la.norm(self.uqd(w1, *iqd))})
|
347
557
|
if res['success']:
|
348
558
|
return res.x[0], res.x[1], sign*res.fun
|
349
559
|
raise ValueError(f"mtpv w1={w1} u1={u1} maxtorque={maxtorque} res: {res['message']}")
|
350
560
|
|
561
|
+
def mtpv_tmech(self, w1, u1, iqd0=0, maxtorque=True):
|
562
|
+
"""return d-q-current, shaft torque for voltage and frequency
|
563
|
+
with maximum (maxtorque=True) or minimum torque """
|
564
|
+
sign = -1 if maxtorque else 1
|
565
|
+
if np.isscalar(iqd0):
|
566
|
+
i0 = (-sign*self.i1range[1]/20, -self.i1range[1]/np.sqrt(2))
|
567
|
+
else:
|
568
|
+
i0 = iqd0
|
569
|
+
n = w1/2/np.pi/self.p
|
570
|
+
res = so.minimize(lambda iqd: sign*self.tmech_iqd(
|
571
|
+
*iqd, n), i0,
|
572
|
+
method='SLSQP',
|
573
|
+
constraints={'type': 'eq',
|
574
|
+
'fun': lambda iqd:
|
575
|
+
np.sqrt(2)*u1 - la.norm(
|
576
|
+
self.uqd(w1, *iqd))})
|
577
|
+
if res['success']:
|
578
|
+
return res.x[0], res.x[1], sign*res.fun
|
579
|
+
#logger.warning("w1=%.1f u1=%.1f maxtorque=%s %s: %s", w1, u1, maxtorque, res.x, res.message)
|
580
|
+
raise ValueError(f"mtpv_tmech w1={w1} u1={u1} maxtorque={maxtorque} res: {res['message']}")
|
581
|
+
|
351
582
|
def _set_losspar(self, pfe):
|
352
583
|
self.fo = pfe['speed']*self.p
|
353
584
|
ef = pfe.get('ef', [2.0, 2.0])
|
@@ -376,7 +607,7 @@ class PmRelMachine(object):
|
|
376
607
|
return np.sum([self.betai1_plfe1(beta, i1, f),
|
377
608
|
self.betai1_plfe2(beta, i1, f),
|
378
609
|
self.betai1_plmag(beta, i1, f),
|
379
|
-
self.betai1_plcu(i1
|
610
|
+
self.betai1_plcu(i1, 2*np.pi*f)], axis=0)
|
380
611
|
|
381
612
|
def iqd_losses(self, iq, id, f):
|
382
613
|
return np.sum([self.iqd_plfe1(iq, id, f),
|
@@ -384,51 +615,72 @@ class PmRelMachine(object):
|
|
384
615
|
self.iqd_plmag(iq, id, f),
|
385
616
|
self.iqd_plcu(iq, id, 2*np.pi*f)], axis=0)
|
386
617
|
|
387
|
-
def speedranges(self, i1max, u1max, speedmax,
|
618
|
+
def speedranges(self, i1max, u1max, speedmax,
|
619
|
+
with_pmconst, with_mtpa, with_mtpv, with_tmech):
|
388
620
|
"""calculate speed range intervals:
|
389
621
|
1. const current MTPA (u < u1max)
|
390
622
|
2. const voltage: flux reduction / MTPA and MTPV (if enabled)
|
391
623
|
returns list of speed limit for each interval
|
624
|
+
calculates with friction and windage losses if with_tmech=True
|
392
625
|
"""
|
393
|
-
|
394
|
-
|
626
|
+
if with_tmech:
|
627
|
+
w1type, T = self.w1_imax_umax(i1max, u1max)
|
628
|
+
else:
|
629
|
+
iq, id, T = self.mtpa(i1max)
|
630
|
+
w1type = self.w1_umax(u1max, iq, id)
|
631
|
+
Pmax = w1type/self.p*T
|
395
632
|
w1max = 2*np.pi*speedmax*self.p
|
396
|
-
|
397
|
-
wl, wu = [w1type, w1max]
|
633
|
+
wl, wu = [w1type, min(4*w1type, w1max)]
|
398
634
|
if with_mtpv:
|
399
635
|
kmax = 6
|
400
636
|
self.check_extrapolation = False
|
401
637
|
else:
|
402
638
|
kmax = 0
|
403
639
|
k = kmax
|
640
|
+
logger.debug("speedranges w1type %f wu %f", wl, wu)
|
404
641
|
for k in range(kmax):
|
405
642
|
wx = wl + (wu-wl)/2
|
406
643
|
try:
|
407
|
-
|
408
|
-
|
644
|
+
if with_pmconst:
|
645
|
+
iq, id = self.iqd_pmech_imax_umax(
|
646
|
+
wx/np.pi/2/self.p, Pmax, i1max, u1max,
|
647
|
+
with_mtpa, with_tmech)[:2]
|
648
|
+
else:
|
649
|
+
iq, id = self.iqd_imax_umax(i1max, wx, u1max,
|
650
|
+
T, with_mtpv=False)[:2]
|
409
651
|
i1 = betai1(iq, id)[1]
|
410
652
|
try:
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
653
|
+
if with_tmech:
|
654
|
+
def voltw1(wx):
|
655
|
+
return np.sqrt(2)*i1 - np.linalg.norm(
|
656
|
+
self.mtpv_tmech(wx, u1max, iqd0=(iq, id),
|
657
|
+
maxtorque=T > 0)[:2])
|
658
|
+
else:
|
659
|
+
def voltw1(wx):
|
660
|
+
return np.sqrt(2)*i1 - np.linalg.norm(
|
661
|
+
self.mtpv(wx, u1max, iqd0=(iq, id),
|
662
|
+
maxtorque=T > 0)[:2])
|
415
663
|
w, _, ier, _ = so.fsolve(voltw1, wx, full_output=True)
|
416
|
-
logger.debug("
|
664
|
+
logger.debug("fsolve ier %d T %f w %f w1 %f", ier, T, w, wx)
|
417
665
|
if ier in (1, 4, 5):
|
418
|
-
self.check_extrapolation = True
|
419
666
|
if abs(w[0]) <= wx:
|
667
|
+
self.check_extrapolation = True
|
420
668
|
return [w/2/np.pi/self.p
|
421
669
|
for w in (w1type, w[0], w1max)] # ['MTPA', 'MTPV']
|
422
|
-
|
670
|
+
if w[0] > wu:
|
671
|
+
wl += (wu-wl)/4
|
672
|
+
else:
|
673
|
+
wl = w[0]
|
423
674
|
except ValueError as e:
|
424
|
-
logger.debug(e)
|
675
|
+
logger.debug("%s: wx %f wl %f wu %f", e, wx, wl, wu)
|
425
676
|
wl = wx
|
426
677
|
pass
|
427
678
|
except ValueError as e:
|
428
679
|
logger.warning(e)
|
429
680
|
wu = wx
|
681
|
+
|
430
682
|
logger.debug("%d: wx %f wl %f wu %f --> %f",
|
431
|
-
|
683
|
+
k, wx, wl, wu, 100*(wu-wl)/wl)
|
432
684
|
|
433
685
|
self.check_extrapolation = True
|
434
686
|
w1max = min(w1max, self.w1_umax(
|
@@ -436,7 +688,94 @@ class PmRelMachine(object):
|
|
436
688
|
return [w/2/np.pi/self.p for w in (w1type, w1max)] # ['MTPA']
|
437
689
|
|
438
690
|
|
439
|
-
def
|
691
|
+
def operating_point(self, T, n, u1max, with_mtpa=True):
|
692
|
+
"""
|
693
|
+
calculate single operating point.
|
694
|
+
|
695
|
+
return dict with values for
|
696
|
+
f1 -- (float) stator frequency in Hz
|
697
|
+
u1 -- (float) stator phase voltage in V rms
|
698
|
+
i1 -- (float) stator phase current in A rms
|
699
|
+
beta -- (float) current angle in rad
|
700
|
+
id -- (float) D-current in A peak
|
701
|
+
iq -- (float) Q-current in A peak
|
702
|
+
ud -- (float) D-voltage in A peak
|
703
|
+
uq -- (float) Q-voltgae in A peak
|
704
|
+
p1 -- (float) electrical power in W
|
705
|
+
pmech -- (float) mechanical power in W
|
706
|
+
plcu1 -- (float) stator copper losses in W
|
707
|
+
plfe1 -- (float) stator iron losses in W
|
708
|
+
plfe2 -- (float) rotor iron losses in W
|
709
|
+
plmag -- (float) magnet losses in W
|
710
|
+
plfric -- (float) friction losses in W, according to Tfric
|
711
|
+
losses -- (float) total losses in W
|
712
|
+
cosphi -- (float) power factor for this op
|
713
|
+
eta -- (float) efficiency for this op
|
714
|
+
T -- (float) torque for this op in Nm, copy of argument T
|
715
|
+
Tfric -- (float) friction torque in Nm
|
716
|
+
n -- (float) speed for this op in 1/s, copy of argument n
|
717
|
+
tcu1/2 -- (float) temperature of statur/rotor in deg. C
|
718
|
+
|
719
|
+
Keyword arguments:
|
720
|
+
T -- (float) the output torque at the shaft in Nm
|
721
|
+
n -- (float) the speed of the machine in 1/s
|
722
|
+
u1max -- (float) the maximum phase voltage in V rms
|
723
|
+
"""
|
724
|
+
|
725
|
+
r = {} # result dit
|
726
|
+
|
727
|
+
f1 = n * self.p
|
728
|
+
w1 = f1 * 2 * np.pi
|
729
|
+
iq, id, tq = self.iqd_tmech_umax(T, w1, u1max, with_mtpa=with_mtpa)
|
730
|
+
uq, ud = self.uqd(w1, iq, id)
|
731
|
+
u1 = la.norm((ud, uq)) / np.sqrt(2.0)
|
732
|
+
i1 = la.norm((id, iq)) / np.sqrt(2.0)
|
733
|
+
beta = np.arctan2(id, iq)
|
734
|
+
gamma = np.arctan2(ud, uq)
|
735
|
+
cosphi = np.cos(beta - gamma)
|
736
|
+
pmech = tq * n * 2 * np.pi
|
737
|
+
plfe1 = self.iqd_plfe1(iq, id, f1)
|
738
|
+
plfe2 = self.iqd_plfe2(iq, id, f1)
|
739
|
+
plmag = self.iqd_plmag(iq, id, f1)
|
740
|
+
plfe = plfe1 + plfe2 + plmag
|
741
|
+
plfric = self.pfric(n)
|
742
|
+
plcu = self.betai1_plcu(i1, 2 * np.pi * f1)
|
743
|
+
pltotal = plfe + plcu + plfric
|
744
|
+
p1 = pmech + pltotal
|
745
|
+
if np.abs(pmech) < 1e-12:
|
746
|
+
eta = 0 # power to low for eta calculation
|
747
|
+
elif p1 > pmech:
|
748
|
+
eta = pmech/p1 # motor
|
749
|
+
else:
|
750
|
+
eta = p1/pmech # generator
|
751
|
+
r['i1'] = float(i1)
|
752
|
+
r['u1'] = float(u1)
|
753
|
+
r['iq'] = float(iq)
|
754
|
+
r['id'] = float(id)
|
755
|
+
r['beta'] = float(beta/ np.pi * 180)
|
756
|
+
r['uq'] = float(uq)
|
757
|
+
r['ud'] = float(ud)
|
758
|
+
r['pmech'] = float(pmech)
|
759
|
+
r['p1'] = float(p1)
|
760
|
+
r['plfe1']= float(plfe1)
|
761
|
+
r['plfe2'] = float(plfe2)
|
762
|
+
r['plmag'] = float(plmag)
|
763
|
+
r['plfric'] = float(plfric)
|
764
|
+
r['losses'] = float(pltotal)
|
765
|
+
r['T'] = float(tq)
|
766
|
+
r['Tfric'] = float(self.tfric)
|
767
|
+
r['n'] = float(n)
|
768
|
+
r['f1'] = float(f1)
|
769
|
+
r['eta'] = eta
|
770
|
+
r['cosphi'] = cosphi
|
771
|
+
r['t_cu1'] = self.tcu1
|
772
|
+
r['t_mag'] = self.tmag
|
773
|
+
|
774
|
+
return r
|
775
|
+
|
776
|
+
def characteristics(self, T, n, u1max, nsamples=60,
|
777
|
+
with_mtpv=True, with_mtpa=True,
|
778
|
+
with_pmconst=True, with_tmech=True):
|
440
779
|
"""calculate torque speed characteristics.
|
441
780
|
return dict with list values of
|
442
781
|
id, iq, n, T, ud, uq, u1, i1,
|
@@ -447,75 +786,112 @@ class PmRelMachine(object):
|
|
447
786
|
n -- the maximum speed or the list of speed values in 1/s
|
448
787
|
u1max -- the maximum voltage in V rms
|
449
788
|
nsamples -- (optional) number of speed samples
|
450
|
-
with_mtpv -- (optional) use mtpv if
|
789
|
+
with_mtpv -- (optional) use mtpv if True (default)
|
790
|
+
with_pmconst -- (optional) keep pmech const if True (default)
|
791
|
+
with_mtpa -- (optional) use mtpa if True (default) in const speed range, set id=0 if false
|
792
|
+
with_tmech -- (optional) use friction and windage losses if True (default)
|
451
793
|
"""
|
452
794
|
r = dict(id=[], iq=[], uq=[], ud=[], u1=[], i1=[], T=[],
|
453
795
|
beta=[], gamma=[], phi=[], cosphi=[], pmech=[], n=[])
|
454
796
|
if np.isscalar(T):
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
797
|
+
if with_mtpa:
|
798
|
+
iq, id = self.iqd_torque(T)
|
799
|
+
i1max = betai1(iq, id)[1]
|
800
|
+
if T < 0:
|
801
|
+
i1max = -i1max
|
802
|
+
else:
|
803
|
+
i1max = self.i1_torque(T, 0)
|
804
|
+
iq, id = iqd(0, i1max)
|
805
|
+
if with_tmech:
|
806
|
+
w1, Tf = self.w1_imax_umax(i1max, u1max)
|
807
|
+
else:
|
808
|
+
iq, id = self.iqd_torque(T)
|
809
|
+
Tf = T
|
810
|
+
w1 = self.w1_umax(u1max, iq, id)
|
460
811
|
n1 = w1/2/np.pi/self.p
|
461
812
|
r['n_type'] = n1
|
462
813
|
nmax = n
|
463
|
-
logger.info("Type speed
|
464
|
-
60*n1, 60*n, 60*nmax)
|
814
|
+
logger.info("Type speed %.4f n: %.4f nmax %.1f T %.1f i1max %.1f",
|
815
|
+
60*n1, 60*n, 60*nmax, Tf, i1max)
|
465
816
|
|
466
817
|
n1 = min(n1, nmax)
|
467
818
|
if n1 < nmax:
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
819
|
+
interv = 'MTPA', # fieldweakening range is always MTPA
|
820
|
+
if with_mtpa:
|
821
|
+
speedrange = [0] + self.speedranges(
|
822
|
+
i1max, u1max, nmax,
|
823
|
+
with_pmconst, with_mtpa, with_mtpv, with_tmech)
|
824
|
+
if len(speedrange) > 3:
|
825
|
+
interv = 'MTPA', 'MTPV'
|
472
826
|
else:
|
473
|
-
|
827
|
+
speedrange = [0, n1, nmax]
|
474
828
|
else:
|
475
|
-
speedrange = [0, n1]
|
476
829
|
interv = []
|
830
|
+
nmax = min(nmax, self.w1_umax(
|
831
|
+
u1max, *iqd(-np.pi/2, abs(i1max))))/2/np.pi/self.p
|
832
|
+
speedrange = [0, n1, nmax]
|
477
833
|
|
478
|
-
|
834
|
+
if speedrange[-1] < speedrange[-2]:
|
835
|
+
speedrange = speedrange[:-1]
|
836
|
+
logger.info("Speedrange T=%g %s", Tf, speedrange)
|
479
837
|
n3 = speedrange[-1]
|
480
838
|
nstab = [int(nsamples*(x1-x2)/n3)
|
481
839
|
for x1, x2 in zip(speedrange[1:],
|
482
840
|
speedrange)]
|
483
841
|
logger.info("sample intervals %s", nstab)
|
484
842
|
for nx in np.linspace(0, n1, nstab[0]):
|
843
|
+
if with_tmech:
|
844
|
+
iq, id = self.iqd_tmech(Tf, nx, (iq, id),
|
845
|
+
with_mtpa or nx == n1)[:2]
|
846
|
+
else:
|
847
|
+
iq, id = self.iqd_torque(Tf, (iq, id),
|
848
|
+
with_mtpa or nx == n1)[:2]
|
485
849
|
r['id'].append(id)
|
486
850
|
r['iq'].append(iq)
|
487
851
|
r['n'].append(nx)
|
488
|
-
r['T'].append(
|
852
|
+
r['T'].append(Tf)
|
489
853
|
|
854
|
+
Pmax = 2*np.pi*n1*Tf
|
490
855
|
for ns, nu, iv in zip(nstab[1:], speedrange[2:], interv):
|
491
856
|
# find id, iq, torque in fieldweakening range
|
492
|
-
if ns
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
857
|
+
if ns > 0:
|
858
|
+
dn = (nu - r['n'][-1])/ns
|
859
|
+
logger.info("RANGE %s %d: %f -- %f",
|
860
|
+
iv, ns, r['n'][-1] + dn, nu)
|
861
|
+
try:
|
862
|
+
for nn in np.linspace(r['n'][-1]+dn, nu, ns):
|
863
|
+
w1 = 2*np.pi*nn*self.p
|
864
|
+
logger.debug("fieldweakening: n %g T %g i1max %g w1 %g u1 %g",
|
865
|
+
nn*60, Tf, i1max, w1, u1max)
|
866
|
+
if iv == 'MTPA':
|
867
|
+
if with_pmconst:
|
868
|
+
iq, id, tq = self.iqd_pmech_imax_umax(
|
869
|
+
nn, Pmax, i1max, u1max,
|
870
|
+
with_mtpa=with_mtpa,
|
871
|
+
with_tmech=with_tmech)
|
872
|
+
else:
|
873
|
+
iq, id, tq = self.iqd_imax_umax(
|
874
|
+
i1max, w1, u1max,
|
875
|
+
Tf, with_tmech=with_tmech,
|
876
|
+
with_mtpv=(ns == 1))
|
877
|
+
else:
|
878
|
+
if with_tmech:
|
879
|
+
iq, id, tq = self.mtpv_tmech(w1, u1max,
|
880
|
+
maxtorque=T > 0)
|
881
|
+
else:
|
882
|
+
iq, id, tq = self.mtpv(w1, u1max,
|
883
|
+
maxtorque=T > 0)
|
884
|
+
if (T > 0 and tq > 0) or (T < 0 and tq < 0):
|
885
|
+
r['id'].append(id)
|
886
|
+
r['iq'].append(iq)
|
887
|
+
r['n'].append(nn)
|
888
|
+
r['T'].append(tq)
|
889
|
+
else:
|
890
|
+
logger.warning("fieldweakening: n %g T %g tq %g i1max %g w1 %g u1 %g",
|
891
|
+
nn*60, T, tq, i1max, w1, u1max)
|
892
|
+
except ValueError as e:
|
893
|
+
nmax = r['n'][-1]
|
894
|
+
logger.warning("%s: adjusted nmax %f T %f", e, nmax, r['T'][-1])
|
519
895
|
else:
|
520
896
|
for t, nx in zip(T, n):
|
521
897
|
w1 = 2*np.pi*nx*self.p
|
@@ -538,17 +914,22 @@ class PmRelMachine(object):
|
|
538
914
|
r['phi'].append(r['beta'][-1] - r['gamma'][-1])
|
539
915
|
r['cosphi'].append(np.cos(r['phi'][-1]/180*np.pi))
|
540
916
|
|
541
|
-
|
917
|
+
if with_tmech:
|
918
|
+
pmech = np.array([2*np.pi*nx*tq for nx, tq in zip(r['n'], r['T'])])
|
919
|
+
else:
|
920
|
+
pmech = np.array([2*np.pi*nx*(tq-self.tfric) for nx, tq in zip(r['n'], r['T'])])
|
542
921
|
f1 = np.array(r['n'])*self.p
|
543
922
|
plfe1 = self.iqd_plfe1(np.array(r['iq']), np.array(r['id']), f1)
|
544
923
|
plfe2 = self.iqd_plfe2(np.array(r['iq']), np.array(r['id']), f1)
|
545
924
|
plmag = self.iqd_plmag(np.array(r['iq']), np.array(r['id']), f1)
|
546
925
|
plfe = plfe1 + plfe2 + plmag
|
547
926
|
plcu = self.betai1_plcu(np.array(r['i1']), 2*np.pi*f1)
|
548
|
-
|
927
|
+
plfw = self.pfric(2*np.pi*f1)
|
928
|
+
pltotal = plfe + plcu + plfw
|
549
929
|
r['pmech'] = pmech.tolist()
|
550
930
|
r['plfe'] = plfe.tolist()
|
551
931
|
r['plcu'] = plcu.tolist()
|
932
|
+
r['plfw'] = plfw.tolist()
|
552
933
|
r['losses'] = pltotal.tolist()
|
553
934
|
if pmech.any():
|
554
935
|
p1 = pmech + pltotal
|
@@ -584,7 +965,7 @@ class PmRelMachine(object):
|
|
584
965
|
u1 = la.norm((ud, uq))/np.sqrt(2)
|
585
966
|
logger.debug("ud %s uq %s --> u1 %s", ud, uq, u1)
|
586
967
|
|
587
|
-
tq = self.
|
968
|
+
tq = self.tmech_iqd(iq, id)
|
588
969
|
|
589
970
|
r['id'].append(id)
|
590
971
|
r['iq'].append(iq)
|
@@ -635,14 +1016,16 @@ class PmRelMachineLdq(PmRelMachine):
|
|
635
1016
|
def __init__(self, m, p, psim=[], ld=[], lq=[],
|
636
1017
|
r1=0, beta=[], i1=[], ls=0, **kwargs):
|
637
1018
|
|
638
|
-
super(self.__class__, self).__init__(m, p, r1, ls)
|
1019
|
+
super(self.__class__, self).__init__(m, p, r1, ls, **kwargs)
|
639
1020
|
self.psid = None
|
640
1021
|
self.betarange = (-np.pi, np.pi)
|
641
1022
|
self.i1range = (0, np.inf)
|
642
1023
|
if np.isscalar(ld):
|
643
|
-
|
644
|
-
|
645
|
-
self.
|
1024
|
+
def constval(x, b, i):
|
1025
|
+
return x
|
1026
|
+
self.ld = partial(constval, ld)
|
1027
|
+
self.psim = partial(constval, psim)
|
1028
|
+
self.lq = partial(constval, lq)
|
646
1029
|
logger.debug("ld %s lq %s psim %s", ld, lq, psim)
|
647
1030
|
return
|
648
1031
|
|
@@ -651,15 +1034,18 @@ class PmRelMachineLdq(PmRelMachine):
|
|
651
1034
|
self.io = iqd(min(beta)*np.pi/360, max(i1)/2)
|
652
1035
|
except:
|
653
1036
|
self.io = (1, -1)
|
654
|
-
|
655
|
-
|
656
|
-
self.
|
1037
|
+
def constval(x, b, i):
|
1038
|
+
return x
|
1039
|
+
self.ld = partial(constval, ld[0])
|
1040
|
+
self.psim = partial(constval, psim[0])
|
1041
|
+
self.lq = partial(constval, lq[0])
|
657
1042
|
logger.debug("ld %s lq %s psim %s", ld, lq, psim)
|
658
1043
|
return
|
659
1044
|
|
660
1045
|
beta = np.asarray(beta)/180.0*np.pi
|
661
1046
|
if np.any(beta[beta > np.pi]):
|
662
1047
|
beta[beta > np.pi] = beta - 2*np.pi
|
1048
|
+
|
663
1049
|
self.io = iqd((np.min(beta)+max(beta))/2, np.max(i1)/2)
|
664
1050
|
kx = ky = 3
|
665
1051
|
if len(i1) < 4:
|
@@ -682,63 +1068,61 @@ class PmRelMachineLdq(PmRelMachine):
|
|
682
1068
|
if 'psid' in kwargs:
|
683
1069
|
self.betarange = min(beta), max(beta)
|
684
1070
|
self.i1range = (0, np.max(i1))
|
685
|
-
self.psid =
|
1071
|
+
self.psid = ip.RectBivariateSpline(
|
686
1072
|
beta, i1, np.sqrt(2)*np.asarray(kwargs['psid']),
|
687
|
-
kx=kx, ky=ky).ev
|
688
|
-
self.psiq =
|
1073
|
+
kx=kx, ky=ky).ev
|
1074
|
+
self.psiq = ip.RectBivariateSpline(
|
689
1075
|
beta, i1, np.sqrt(2)*np.asarray(kwargs['psiq']),
|
690
|
-
kx=kx, ky=ky).ev
|
691
|
-
|
1076
|
+
kx=kx, ky=ky).ev
|
692
1077
|
return
|
693
1078
|
|
694
1079
|
if len(i1) < 4 or len(beta) < 4:
|
695
1080
|
if len(i1) == len(beta):
|
696
|
-
self.ld =
|
697
|
-
self.psim =
|
698
|
-
self.lq =
|
1081
|
+
self.ld = ip.interp2d(beta, i1, ld.T)
|
1082
|
+
self.psim = ip.interp2d(beta, i1, psim.T)
|
1083
|
+
self.lq = ip.interp2d(beta, i1, lq.T)
|
699
1084
|
logger.debug("interp2d beta %s i1 %s", beta, i1)
|
700
1085
|
return
|
701
1086
|
elif len(i1) == 1:
|
702
|
-
|
703
|
-
beta,
|
704
|
-
self.
|
705
|
-
|
706
|
-
self.lq =
|
707
|
-
beta, lq, k=1)(x)
|
1087
|
+
def interp(x, b, i):
|
1088
|
+
return ip.InterpolatedUnivariateSpline(beta, x, k=1)(b)
|
1089
|
+
self.ld = partial(interp, ld)
|
1090
|
+
self.psim = partial(interp, psim)
|
1091
|
+
self.lq = partial(interp, lq)
|
708
1092
|
logger.debug("interpolatedunivariatespline beta %s", beta)
|
709
1093
|
return
|
710
1094
|
if len(beta) == 1:
|
711
|
-
|
712
|
-
i1,
|
713
|
-
self.
|
714
|
-
|
715
|
-
self.lq =
|
716
|
-
i1, lq, k=1)(y)
|
1095
|
+
def interp(x, b, i):
|
1096
|
+
return ip.InterpolatedUnivariateSpline(i1, x, k=1)(i)
|
1097
|
+
self.ld = partial(interp, ld)
|
1098
|
+
self.psim = partial(interp, psim)
|
1099
|
+
self.lq = partial(interp, lq)
|
717
1100
|
logger.debug("interpolatedunivariatespline i1 %s", i1)
|
718
1101
|
return
|
719
1102
|
|
720
|
-
raise ValueError("unsupported array size {}x{}".format(
|
1103
|
+
raise ValueError("unsupported array size {0}x{1}".format(
|
721
1104
|
len(beta), len(i1)))
|
722
1105
|
|
723
1106
|
self.betarange = min(beta), max(beta)
|
724
1107
|
self.i1range = (0, np.max(i1))
|
725
|
-
|
726
|
-
beta, i1, np.asarray(
|
727
|
-
self.
|
728
|
-
|
729
|
-
self.lq =
|
730
|
-
beta, i1, np.asarray(lq)).ev(x, y)
|
1108
|
+
def interp(x, b, i):
|
1109
|
+
return ip.RectBivariateSpline(beta, i1, np.asarray(x)).ev(b, i)
|
1110
|
+
self.ld = partial(interp, ld)
|
1111
|
+
self.psim = partial(interp, psim)
|
1112
|
+
self.lq = partial(interp, lq)
|
731
1113
|
logger.debug("rectbivariatespline beta %s i1 %s", beta, i1)
|
732
1114
|
|
733
|
-
def psi(self, iq, id):
|
1115
|
+
def psi(self, iq, id, tol=1e-4):
|
734
1116
|
"""return psid, psiq of currents iq, id"""
|
735
1117
|
beta, i1 = betai1(np.asarray(iq), np.asarray(id))
|
1118
|
+
if np.isclose(beta, np.pi, atol=1e-4):
|
1119
|
+
beta = -np.pi
|
736
1120
|
#logger.debug('beta %f (%f, %f) i1 %f %f',
|
737
1121
|
# beta, self.betarange[0], self.betarange[1],
|
738
1122
|
# i1, self.i1range[1])
|
739
1123
|
if self.check_extrapolation:
|
740
|
-
if (self.betarange[0] > beta or
|
741
|
-
self.betarange[1] < beta or
|
1124
|
+
if (self.betarange[0]-tol > beta or
|
1125
|
+
self.betarange[1]+tol < beta or
|
742
1126
|
i1 > 1.01*self.i1range[1]):
|
743
1127
|
return (np.nan, np.nan)
|
744
1128
|
if self.psid:
|
@@ -800,7 +1184,7 @@ class PmRelMachinePsidq(PmRelMachine):
|
|
800
1184
|
"""
|
801
1185
|
|
802
1186
|
def __init__(self, m, p, psid, psiq, r1, id, iq, ls=0, **kwargs):
|
803
|
-
super(self.__class__, self).__init__(m, p, r1, ls)
|
1187
|
+
super(self.__class__, self).__init__(m, p, r1, ls, **kwargs)
|
804
1188
|
|
805
1189
|
if isinstance(psid, (float, int)):
|
806
1190
|
self._psid = lambda id, iq: np.array([[psid]])
|
@@ -824,24 +1208,22 @@ class PmRelMachinePsidq(PmRelMachine):
|
|
824
1208
|
self._psiq = ip.interp2d(iq, id, psiq.T)
|
825
1209
|
return
|
826
1210
|
if len(id) == 1 or psid.shape[1] == 1:
|
827
|
-
|
828
|
-
iq,
|
829
|
-
self.
|
830
|
-
|
1211
|
+
def interp(x, q, d):
|
1212
|
+
return ip.InterpolatedUnivariateSpline(iq, x)(q)
|
1213
|
+
self._psid = partial(interp, psid)
|
1214
|
+
self._psiq = partial(interp, psiq)
|
831
1215
|
return
|
832
1216
|
if len(iq) == 1 or psid.shape[0] == 1:
|
833
|
-
|
834
|
-
id,
|
835
|
-
self.
|
836
|
-
|
1217
|
+
def interp(x, q, d):
|
1218
|
+
return ip.InterpolatedUnivariateSpline(id, x)(d)
|
1219
|
+
self._psid = partial(interp, psid)
|
1220
|
+
self._psiq = partial(interp, psiq)
|
837
1221
|
return
|
838
|
-
raise ValueError("unsupported array size {}
|
839
|
-
|
1222
|
+
raise ValueError("unsupported array size {}".format(
|
1223
|
+
psid.shape))
|
840
1224
|
|
841
|
-
self._psid =
|
842
|
-
|
843
|
-
self._psiq = lambda x, y: ip.RectBivariateSpline(
|
844
|
-
iq, id, psiq).ev(x, y)
|
1225
|
+
self._psid = ip.RectBivariateSpline(iq, id, psid).ev
|
1226
|
+
self._psiq = ip.RectBivariateSpline(iq, id, psiq).ev
|
845
1227
|
try:
|
846
1228
|
pfe = kwargs['losses']
|
847
1229
|
self._set_losspar(pfe)
|