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.
Files changed (47) hide show
  1. femagtools/__init__.py +1 -1
  2. femagtools/airgap.py +11 -37
  3. femagtools/amela.py +148 -13
  4. femagtools/bch.py +19 -3
  5. femagtools/dxfsl/area.py +68 -15
  6. femagtools/dxfsl/converter.py +15 -6
  7. femagtools/dxfsl/fslrenderer.py +13 -8
  8. femagtools/dxfsl/functions.py +1 -1
  9. femagtools/dxfsl/geom.py +415 -62
  10. femagtools/dxfsl/machine.py +97 -5
  11. femagtools/dxfsl/shape.py +46 -2
  12. femagtools/ecloss.py +393 -0
  13. femagtools/femag.py +25 -1
  14. femagtools/fsl.py +3 -2
  15. femagtools/hxy.py +126 -0
  16. femagtools/isa7.py +37 -24
  17. femagtools/machine/__init__.py +14 -13
  18. femagtools/machine/effloss.py +153 -32
  19. femagtools/machine/im.py +137 -56
  20. femagtools/machine/pm.py +584 -202
  21. femagtools/machine/sm.py +218 -64
  22. femagtools/machine/utils.py +12 -8
  23. femagtools/mcv.py +6 -8
  24. femagtools/model.py +11 -1
  25. femagtools/parstudy.py +1 -1
  26. femagtools/plot.py +159 -35
  27. femagtools/templates/afm_rotor.mako +102 -0
  28. femagtools/templates/afm_stator.mako +141 -0
  29. femagtools/templates/airgapinduc.mako +3 -3
  30. femagtools/templates/basic_modpar.mako +23 -2
  31. femagtools/templates/cogg_calc.mako +28 -5
  32. femagtools/templates/cu_losses.mako +1 -1
  33. femagtools/templates/fieldcalc.mako +39 -0
  34. femagtools/templates/gen_winding.mako +52 -47
  35. femagtools/templates/mesh-airgap.mako +43 -0
  36. femagtools/templates/stator3Linear.mako +5 -4
  37. femagtools/templates/therm-dynamic.mako +12 -6
  38. femagtools/templates/therm-static.mako +12 -0
  39. femagtools/templates/torq_calc.mako +2 -4
  40. femagtools/utils.py +45 -0
  41. femagtools/windings.py +2 -1
  42. {femagtools-1.3.0.dist-info → femagtools-1.3.2.dist-info}/METADATA +1 -1
  43. {femagtools-1.3.0.dist-info → femagtools-1.3.2.dist-info}/RECORD +47 -41
  44. {femagtools-1.3.0.dist-info → femagtools-1.3.2.dist-info}/WHEEL +1 -1
  45. {femagtools-1.3.0.dist-info → femagtools-1.3.2.dist-info}/LICENSE +0 -0
  46. {femagtools-1.3.0.dist-info → femagtools-1.3.2.dist-info}/entry_points.txt +0 -0
  47. {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.zeta1 = 0.3
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
- self._losses = {k: lambda x, y: 0 for k in (
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 torque > 0:
82
- iqd0 = self.i1range[1]/2, 0
110
+ if np.isscalar(iqd0):
111
+ i0 = self.io
83
112
  else:
84
- iqd0 = -self.i1range[1]/2, 0
85
- res = so.minimize(
86
- lambda iqd: la.norm(iqd), self.io, method='SLSQP',
87
- constraints=({'type': 'eq',
88
- 'fun': lambda iqd:
89
- self.torque_iqd(*iqd) - torque}))
90
- if not res.success:
91
- raise ValueError(f'Torque {torque} out of current range')
92
- return res.x
93
-
94
- def tmech_iqd(self, iq, id, n, kpfe, pfw):
95
- """return shaft torque with given d-q current, iron loss correction factor
96
- and friction windage losses"""
97
- f1 = self.p*n
98
- plfe1 = self.iqd_plfe1(iq, id, f1)
99
- plfe2 = self.iqd_plfe2(iq, id, f1)
100
- plfe = kpfe * (plfe1 + plfe2)
101
- pmag = self.iqd_plmag(iq, id, f1)
102
- return self.torque_iqd(iq, id) - (plfe + pmag + pfw)/(2*np.pi*n)
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.io[0],
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 iqd_torque_umax(self, torque, w1, u1max, log=0):
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 minmal current"""
196
- res = so.minimize(lambda iqd: la.norm(iqd), self.io, method='SLSQP',
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
- w1, torque, self.torque_iqd(*res.x), res.x[0], res.x[1],
208
- u1max, np.linalg.norm(
209
- self.uqd(w1, *res.x))/np.sqrt(2))
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 iqd_torque_imax_umax(self, torque, n, u1max, log=0):
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
- iq, id = self.iqd_torque(torque)
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, with_mtpv=False)
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 iqd_maxtorque_imax_umax(self, i1max, w1, u1max):
230
- """return d-q current and max torque at stator frequency w1,
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
- iq, id, tq = self.iqd_torque_umax_imin(torque, w1, u1max)
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
- iq, id = self.iqd_torque(torque)
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
- def voltw1(wx):
299
- return np.linalg.norm(
300
- self.mtpv(wx, u1max,
301
- maxtorque=torque>0)[:2]) - np.sqrt(2)*i1,
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("MTPV w1=%f i1max=%f, u1max %f %s",
309
- w1, i1, u1max, e)
310
- self.check_extrapolation = True
311
-
509
+ logger.warning(e)
312
510
  iq, id = iqd(beta, abs(i1))
313
- tq = self.torque_iqd(iq, id)
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
- w1, torque, tq, iq, id, u1max, np.linalg.norm(
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
- res = so.minimize(
339
- lambda iqd: sign*self.torque_iqd(*iqd),
340
- i0, method='SLSQP',
341
- #bounds=((0, self.i1range[1]),
342
- # (-self.i1range[1], 0)),
343
- constraints=(
344
- {'type': 'eq',
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), 2*np.pi*f], axis=0)
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, with_mtpv, weps=1e-4):
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
- iq, id, T = self.mtpa(i1max)
394
- logger.debug("speedrange i1 %f T %f", i1max, T)
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
- w1type = self.w1_umax(u1max, iq, id)
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
- iq, id = self.iqd_imax_umax(i1max, wx, u1max,
408
- T, with_mtpv=False)[:2]
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
- def voltw1(wx):
412
- return np.linalg.norm(
413
- self.mtpv(wx, u1max,
414
- maxtorque=T > 0)[:2]) - np.sqrt(2)*i1
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("3: ier %d i1 %f w %f w1 %f", ier, i1, w, wx)
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
- wl = w[0]
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
- k, wx, wl, wu, 100*(wu-wl)/wl)
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 characteristics(self, T, n, u1max, nsamples=60, with_mtpv=True):
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 true (default)
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
- iq, id = self.iqd_torque(T)
456
- i1max = betai1(iq, id)[1]
457
- if T < 0:
458
- i1max = -i1max
459
- w1 = self.w1_umax(u1max, iq, id)
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 %f n: %f nmax %f",
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
- speedrange = [0] + self.speedranges(i1max, u1max,
469
- nmax, with_mtpv)
470
- if len(speedrange) > 3:
471
- interv = 'MTPA', 'MTPV'
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
- interv = 'MTPA',
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
- logger.info("Speedrange T=%g %s", T, speedrange)
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(T)
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 == 0:
493
- ns = 1
494
- dn = (nu - r['n'][-1])/ns
495
- logger.info("RANGE %s %d: %f -- %f",
496
- iv, ns, r['n'][-1] + dn, nu)
497
- try:
498
- for nn in np.linspace(r['n'][-1]+dn, nu, ns):
499
- w1 = 2*np.pi*nn*self.p
500
- logger.debug("fieldweakening: n %g T %g i1max %g w1 %g u1 %g",
501
- nn*60, T, i1max, w1, u1max)
502
- if iv == 'MTPA':
503
- iq, id, tq = self.iqd_imax_umax(i1max, w1, u1max, T,
504
- with_mtpv=False)
505
- else:
506
- iq, id, tq = self.mtpv(w1, u1max,
507
- maxtorque=T > 0)
508
- if (T > 0 and tq > 0) or (T < 0 and tq < 0):
509
- r['id'].append(id)
510
- r['iq'].append(iq)
511
- r['n'].append(nn)
512
- r['T'].append(tq)
513
- else:
514
- logger.warning("fieldweakening: n %g T %g tq %g i1max %g w1 %g u1 %g",
515
- nn*60, T, tq, i1max, w1, u1max)
516
- except ValueError as e:
517
- nmax = r['n'][-1]
518
- logger.warning("%s: adjusted nmax %f", e, nmax)
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
- pmech = np.array([2*np.pi*nx*tq for nx, tq in zip(r['n'], r['T'])])
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
- pltotal = plfe + plcu
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.torque_iqd(iq, id)
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
- self.ld = lambda b, i: ld
644
- self.psim = lambda b, i: psim
645
- self.lq = lambda b, i: lq
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
- self.ld = lambda b, i: ld[0]
655
- self.psim = lambda b, i: psim[0]
656
- self.lq = lambda b, i: lq[0]
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 = lambda x, y: ip.RectBivariateSpline(
1071
+ self.psid = ip.RectBivariateSpline(
686
1072
  beta, i1, np.sqrt(2)*np.asarray(kwargs['psid']),
687
- kx=kx, ky=ky).ev(x, y)
688
- self.psiq = lambda x, y: ip.RectBivariateSpline(
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(x, y)
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 = lambda x, y: ip.interp2d(beta, i1, ld.T)(x, y)
697
- self.psim = lambda x, y: ip.interp2d(beta, i1, psim.T)(x, y)
698
- self.lq = lambda x, y: ip.interp2d(beta, i1, lq.T)(x, y)
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
- self.ld = lambda x, y: ip.InterpolatedUnivariateSpline(
703
- beta, ld, k=1)(x)
704
- self.psim = lambda x, y: ip.InterpolatedUnivariateSpline(
705
- beta, psim, k=1)(x)
706
- self.lq = lambda x, y: ip.InterpolatedUnivariateSpline(
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
- self.ld = lambda x, y: ip.InterpolatedUnivariateSpline(
712
- i1, ld, k=1)(y)
713
- self.psim = lambda x, y: ip.InterpolatedUnivariateSpline(
714
- i1, ld, k=1)(y)
715
- self.lq = lambda x, y: ip.InterpolatedUnivariateSpline(
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
- self.ld = lambda x, y: ip.RectBivariateSpline(
726
- beta, i1, np.asarray(ld)).ev(x, y)
727
- self.psim = lambda x, y: ip.RectBivariateSpline(
728
- beta, i1, np.asarray(psim)).ev(x, y)
729
- self.lq = lambda x, y: ip.RectBivariateSpline(
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
- self._psid = lambda x, y: ip.InterpolatedUnivariateSpline(
828
- iq, psid)(x)
829
- self._psiq = lambda x, y: ip.InterpolatedUnivariateSpline(
830
- iq, psiq)(x)
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
- self._psid = lambda x, y: ip.InterpolatedUnivariateSpline(
834
- id, psid)(y)
835
- self._psiq = lambda x, y: ip.InterpolatedUnivariateSpline(
836
- id, psiq)(y)
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 {}x{}".format(
839
- len(psid.shape[0]), psid.shape[1]))
1222
+ raise ValueError("unsupported array size {}".format(
1223
+ psid.shape))
840
1224
 
841
- self._psid = lambda x, y: ip.RectBivariateSpline(
842
- iq, id, psid).ev(x, y)
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)