femagtools 1.7.5__py3-none-any.whl → 1.7.7__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/bch.py +11 -1
- femagtools/dxfsl/area.py +108 -7
- femagtools/dxfsl/conv.py +15 -0
- femagtools/dxfsl/converter.py +44 -20
- femagtools/dxfsl/fslrenderer.py +93 -42
- femagtools/dxfsl/functions.py +8 -0
- femagtools/dxfsl/geom.py +126 -18
- femagtools/dxfsl/machine.py +30 -9
- femagtools/femag.py +3 -3
- femagtools/fsl.py +73 -48
- femagtools/isa7.py +2 -2
- femagtools/machine/effloss.py +2 -0
- femagtools/machine/pm.py +198 -42
- femagtools/machine/sm.py +294 -253
- femagtools/machine/utils.py +5 -14
- femagtools/model.py +32 -2
- femagtools/moo/algorithm.py +6 -0
- femagtools/nc.py +2 -0
- femagtools/opt.py +2 -1
- femagtools/plot/bch.py +19 -5
- femagtools/plot/char.py +4 -4
- femagtools/plot/nc.py +21 -4
- femagtools/plot/wdg.py +38 -26
- femagtools/templates/gen_hairpin_winding.mako +209 -0
- femagtools/templates/gen_winding.mako +8 -9
- femagtools/templates/magnetIron.mako +32 -6
- femagtools/templates/mesh-airgap.mako +9 -0
- femagtools/templates/rotor_winding.mako +10 -6
- femagtools/templates/statorRotor3.mako +8 -5
- femagtools/windings.py +31 -18
- {femagtools-1.7.5.dist-info → femagtools-1.7.7.dist-info}/METADATA +1 -1
- {femagtools-1.7.5.dist-info → femagtools-1.7.7.dist-info}/RECORD +38 -37
- {femagtools-1.7.5.dist-info → femagtools-1.7.7.dist-info}/WHEEL +1 -1
- tests/test_windings.py +1 -1
- {femagtools-1.7.5.dist-info → femagtools-1.7.7.dist-info}/LICENSE +0 -0
- {femagtools-1.7.5.dist-info → femagtools-1.7.7.dist-info}/entry_points.txt +0 -0
- {femagtools-1.7.5.dist-info → femagtools-1.7.7.dist-info}/top_level.txt +0 -0
femagtools/machine/sm.py
CHANGED
@@ -3,26 +3,27 @@
|
|
3
3
|
"""
|
4
4
|
import logging
|
5
5
|
import warnings
|
6
|
+
import pathlib
|
6
7
|
import numpy as np
|
7
8
|
import scipy.optimize as so
|
8
9
|
import scipy.interpolate as ip
|
9
|
-
from .utils import skin_resistance, wdg_resistance, betai1, iqd, KTH
|
10
|
+
from .utils import skin_resistance, wdg_resistance, betai1, iqd, KTH, create_wdg
|
10
11
|
from .. import parstudy, windings
|
11
12
|
import femagtools.bch
|
12
13
|
|
13
14
|
EPS = 1e-13
|
14
15
|
|
15
|
-
eecdefaults =
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
16
|
+
eecdefaults = {
|
17
|
+
'zeta1': 0.3,
|
18
|
+
'zeta2': 0,
|
19
|
+
'gam': 0.7,
|
20
|
+
'kh': 2,
|
21
|
+
'tcu1': 20,
|
22
|
+
'tcu2': 20,
|
23
|
+
'rotor_mass': 0,
|
24
|
+
'kfric_b': 1,
|
25
|
+
'kpfe': 1 # iron loss factor
|
26
|
+
}
|
26
27
|
|
27
28
|
logger = logging.getLogger('sm')
|
28
29
|
logging.captureWarnings(True)
|
@@ -31,7 +32,7 @@ logging.captureWarnings(True)
|
|
31
32
|
def parident(workdir, engine, machine,
|
32
33
|
magnetizingCurves, condMat,
|
33
34
|
**kwargs):
|
34
|
-
|
35
|
+
"""return dict of parameters of equivalent circuit for
|
35
36
|
electrically excited synchronous machines
|
36
37
|
|
37
38
|
arguments:
|
@@ -47,151 +48,187 @@ def parident(workdir, engine, machine,
|
|
47
48
|
i1_max: maximum current in A rms (default approx 3*i1nom)
|
48
49
|
beta_min: minimal current angle (default -180°)
|
49
50
|
beta_max: maximal current angle (default 0°)
|
50
|
-
num_move_steps: number of move steps
|
51
|
+
num_move_steps: number of move steps
|
52
|
+
cmd: femag command (default None, platform executable)
|
51
53
|
"""
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
54
|
+
cmd = kwargs.get('cmd', None)
|
55
|
+
|
56
|
+
wdgk = 'windings' if 'windings' in machine else 'winding'
|
57
|
+
g = machine[wdgk].get('num_par_wdgs', 1)
|
58
|
+
N = machine[wdgk]['num_wires']
|
59
|
+
if 'cufilfact' in machine[wdgk]:
|
60
|
+
fcu = machine[wdgk]['cufilfact']
|
61
|
+
elif 'fillfac' in machine[wdgk]:
|
62
|
+
fcu = machine[wdgk]['fillfac']
|
63
|
+
else:
|
64
|
+
fcu = 0.42
|
65
|
+
try: # calc basic dimensions if not fsl or dxf model
|
66
|
+
from ..model import MachineModel
|
67
|
+
wdg = create_wdg(machine)
|
68
|
+
Q1 = wdg.Q
|
69
|
+
model = MachineModel(machine)
|
70
|
+
Jmax = 20e6 # max current density in A/m2
|
71
|
+
Acu = fcu*model.slot_area() # approx. copper area of one slot
|
72
|
+
i1_max = round(g*Acu/wdg.l/N*Jmax/10)*10
|
73
|
+
except KeyError:
|
74
|
+
if kwargs.get('i1_max', 0) == 0:
|
75
|
+
raise ValueError('i1_max missing')
|
76
|
+
i1_max = kwargs['i1_max']
|
77
|
+
|
78
|
+
ifnom = machine['rotor']['ifnom']
|
79
|
+
exc_logspace = True
|
80
|
+
ifmin, ifmax = ifnom/4, 1.4*ifnom
|
81
|
+
if exc_logspace:
|
82
|
+
excur = np.logspace(np.log(ifmin), np.log(ifmax),
|
83
|
+
kwargs.get("num_exc_steps", 6),
|
84
|
+
base=np.exp(1)).tolist()
|
85
|
+
else:
|
86
|
+
excur = np.linspace(ifmin, ifmax,
|
87
|
+
kwargs.get("num_exc_steps", 6))
|
88
|
+
|
89
|
+
logger.info("Exc current %s", excur)
|
90
|
+
parvardef = {
|
91
|
+
"decision_vars": [
|
92
|
+
{"values": excur, "name": "load_ex_cur"}
|
93
|
+
]
|
94
|
+
}
|
95
|
+
|
96
|
+
parvar = parstudy.List(
|
97
|
+
workdir, condMat=condMat,
|
98
|
+
magnetizingCurves=magnetizingCurves, cmd=cmd)
|
99
|
+
|
100
|
+
simulation = dict(
|
101
|
+
calculationMode=kwargs.get('calculationMode',
|
102
|
+
'ld_lq_fast'),
|
103
|
+
wind_temp=20.0,
|
104
|
+
i1_max=kwargs.get('i1_max', i1_max),
|
105
|
+
maxid=kwargs.get('maxid', 0),
|
106
|
+
minid=kwargs.get('minid', -i1_max),
|
107
|
+
maxiq=kwargs.get('maxiq', i1_max),
|
108
|
+
miniq=kwargs.get('miniq', -i1_max),
|
109
|
+
delta_id=kwargs.get('delta_id', i1_max/5),
|
110
|
+
delta_iq=kwargs.get('delta_iq', i1_max/5),
|
111
|
+
beta_min=kwargs.get('beta_min', -180),
|
112
|
+
beta_max=kwargs.get('beta_max', 0),
|
113
|
+
num_move_steps=kwargs.get('num_move_steps', 31),
|
114
|
+
load_ex_cur=0.5,
|
115
|
+
num_cur_steps=kwargs.get('num_cur_steps', 5),
|
116
|
+
num_beta_steps=kwargs.get('num_beta_steps', 13),
|
117
|
+
num_par_wdgs=machine[wdgk].get('num_par_wdgs', 1),
|
118
|
+
skew_angle=kwargs.get('skew_angle', 0.0),
|
119
|
+
num_skew_steps=kwargs.get('num_skew_steps', 0.0),
|
120
|
+
period_frac=kwargs.get('period_frac', 6),
|
121
|
+
speed=kwargs.get('speed', 50))
|
122
|
+
|
123
|
+
###self.cleanup() # remove previously created files in workdir
|
124
|
+
results = parvar(parvardef, machine, simulation, engine)
|
125
|
+
b = results['f'][-1]
|
126
|
+
|
127
|
+
if 'poles' not in machine: # dxf model?
|
128
|
+
machine['poles'] = 2*results['f'][0]['machine']['p']
|
129
|
+
da1 = 2*results['f'][0]['machine']['fc_radius']
|
130
|
+
wdg = create_wdg(machine)
|
131
|
+
|
132
|
+
if simulation['calculationMode'] == 'ld_lq_fast':
|
133
|
+
idname = 'ldq'
|
134
|
+
else:
|
135
|
+
idname = 'psidq'
|
136
|
+
for r in results['f']:
|
137
|
+
for k in ('hf', 'ef'):
|
138
|
+
if k in r['lossPar']:
|
139
|
+
r[idname]['losses'][k] = r['lossPar'][k]
|
140
|
+
try:
|
141
|
+
rotor_mass = sum(results['f'][-1]['weights'][-1])
|
142
|
+
except KeyError:
|
143
|
+
rotor_mass = 0 # need femag classic > rel-9.3.x-48-gca42bbd0
|
144
|
+
|
145
|
+
losskeys = ('speed', 'ef', 'hf', 'cf',
|
146
|
+
'styoke_hyst', 'stteeth_hyst', 'styoke_eddy',
|
147
|
+
'stteeth_eddy', 'rotor_hyst', 'rotor_eddy',
|
148
|
+
'styoke_excess', 'stteeth_excess', 'rotor_excess')
|
149
|
+
|
150
|
+
# winding resistance
|
151
|
+
try:
|
152
|
+
r1 = machine[wdgk]['resistance']
|
153
|
+
except KeyError:
|
132
154
|
try:
|
133
|
-
|
134
|
-
except KeyError:
|
155
|
+
Q1 = machine['stator']['num_slots']
|
135
156
|
yd = machine[wdgk].get('coil_span', Q1/machine['poles'])
|
136
157
|
wdg = windings.Winding(
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
158
|
+
{'Q': Q1,
|
159
|
+
'm': machine[wdgk]['num_phases'],
|
160
|
+
'p': machine['poles']//2,
|
161
|
+
'l': machine[wdgk]['num_layers'],
|
162
|
+
'yd': yd})
|
142
163
|
|
143
164
|
lfe = machine['lfe']
|
144
|
-
|
165
|
+
da1 = machine['outer_diam']
|
145
166
|
if 'dia_wire' in machine[wdgk]:
|
146
167
|
aw = np.pi*machine[wdgk].get('dia_wire', 1e-3)**2/4
|
147
168
|
else: # wire diameter from slot area
|
148
|
-
aw = 0.75 *
|
149
|
-
|
169
|
+
aw = 0.75 * fcu * np.pi*da1*hs/Q1/wdg.l/N
|
170
|
+
r1 = wdg_resistance(wdg, N, g, aw, da1, hs, lfe)
|
171
|
+
except (KeyError, NameError):
|
172
|
+
from .. import nc
|
173
|
+
model = nc.read(str(pathlib.Path(workdir) / machine['name']))
|
174
|
+
try:
|
175
|
+
nlayers = wdg.l
|
176
|
+
except UnboundLocalError:
|
177
|
+
wdg = create_wdg(machine)
|
178
|
+
nlayers = wdg.l
|
179
|
+
da1 = 2*results['f'][0]['machine']['fc_radius']
|
180
|
+
Q1 = wdg.Q
|
181
|
+
istat = 0 if model.get_areas()[0]['slots'] else 1
|
182
|
+
asl = model.get_areas()[istat]['slots']
|
183
|
+
# diameter of wires
|
184
|
+
aw = fcu*asl/Q1/nlayers/N
|
185
|
+
hs = asl/(np.pi*da1/3)
|
150
186
|
r1 = wdg_resistance(wdg, N, g, aw, da1, hs, lfe)
|
151
187
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
+
if simulation['calculationMode'] == 'ld_lq_fast':
|
189
|
+
dqpars = dict(m=3, p=b['machine']['p'],
|
190
|
+
r1=r1,
|
191
|
+
r2=machine['rotor'].get('resistance', 1),
|
192
|
+
rotor_mass=rotor_mass, kfric_b=1,
|
193
|
+
ldq=[dict(
|
194
|
+
ex_current=b['machine']['ex_current'],
|
195
|
+
i1=b['ldq']['i1'],
|
196
|
+
beta=b['ldq']['beta'],
|
197
|
+
psid=b['ldq']['psid'],
|
198
|
+
psiq=b['ldq']['psiq'],
|
199
|
+
torque=b['ldq']['torque'],
|
200
|
+
ld=b['ldq']['ld'],
|
201
|
+
lq=b['ldq']['lq'],
|
202
|
+
losses={k: b['ldq']['losses'][k]
|
203
|
+
for k in losskeys if k in b['ldq']['losses']})
|
204
|
+
for b in results['f']])
|
205
|
+
else:
|
206
|
+
dqpars = dict(m=3, p=b['machine']['p'],
|
207
|
+
r1=r1,
|
208
|
+
r2=machine['rotor'].get('resistance', 1),
|
209
|
+
rotor_mass=rotor_mass, kfric_b=1,
|
210
|
+
psidq=[dict(
|
211
|
+
ex_current=b['machine']['ex_current'],
|
212
|
+
iq=b['psidq']['iq'],
|
213
|
+
id=b['psidq']['id'],
|
214
|
+
psidq_ldq=b['psidq_ldq'],
|
215
|
+
psid=b['psidq']['psid'],
|
216
|
+
psiq=b['psidq']['psiq'],
|
217
|
+
torque=b['psidq']['torque'],
|
218
|
+
losses={k: b['psidq']['losses'][k]
|
219
|
+
for k in losskeys if k in b['psidq']['losses']})
|
220
|
+
for b in results['f']])
|
221
|
+
|
222
|
+
if 'current_angles' in results['f'][0]:
|
223
|
+
dqpars['current_angles'] = results['f'][0]['current_angles']
|
224
|
+
return dqpars
|
188
225
|
|
189
226
|
def _linsampl(exc, excl, a):
|
190
227
|
"""auxiliary func for linear sampling of nonlinear sequence
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
228
|
+
arguments:
|
229
|
+
exc: (list) nonlinear sequence of excitation current
|
230
|
+
excl: (list) linear sequence of excitation current
|
231
|
+
a: (array) matrix to be resampled"""
|
195
232
|
z = []
|
196
233
|
b, i = a.shape[1:]
|
197
234
|
for x in range(b):
|
@@ -203,13 +240,13 @@ def _linsampl(exc, excl, a):
|
|
203
240
|
|
204
241
|
def _splinterp(beta, i1, betax, i1x, a):
|
205
242
|
"""auxiliary function to increase resolution of array a
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
243
|
+
using a cubic spline interpolation.
|
244
|
+
arguments:
|
245
|
+
beta: (list) n original up-i angles in rad
|
246
|
+
i1: (list) m original current in A
|
247
|
+
betax: (list) nx new up-i angles in rad
|
248
|
+
i1x: (list) mx new currents
|
249
|
+
a: (nxm array) to be interpolated"""
|
213
250
|
f = ip.RectBivariateSpline(
|
214
251
|
beta, i1, np.asarray(a),
|
215
252
|
kx=3, ky=3).ev
|
@@ -218,9 +255,9 @@ def _splinterp(beta, i1, betax, i1x, a):
|
|
218
255
|
|
219
256
|
|
220
257
|
def _islinear(exc):
|
221
|
-
|
222
|
-
|
223
|
-
|
258
|
+
d = np.diff(exc)
|
259
|
+
m = np.mean(d)
|
260
|
+
return np.max(np.abs(d**2 - m**2)) < 1e-9
|
224
261
|
|
225
262
|
|
226
263
|
def _gradient_respecting_bounds(bounds, fun, eps=1e-8):
|
@@ -241,8 +278,8 @@ class SynchronousMachine(object):
|
|
241
278
|
""" represent Synchronous machine with wound rotor (EESM)
|
242
279
|
|
243
280
|
Arguments:
|
244
|
-
|
245
|
-
|
281
|
+
eecpars: dict() electrical circuit parameters
|
282
|
+
"""
|
246
283
|
def __init__(self, eecpars, **kwargs):
|
247
284
|
self.kth1 = KTH
|
248
285
|
self.kth2 = KTH
|
@@ -273,12 +310,12 @@ class SynchronousMachine(object):
|
|
273
310
|
self.tfric = 0
|
274
311
|
|
275
312
|
self.fo = 50
|
276
|
-
self.plexp = {'styoke_hyst': 1.0,
|
277
|
-
'stteeth_hyst': 1.0,
|
278
|
-
'styoke_eddy': 2.0,
|
279
|
-
'stteeth_eddy': 2.0,
|
280
|
-
'rotor_hyst': 1.0,
|
281
|
-
'rotor_eddy': 2.0}
|
313
|
+
self.plexp = {'styoke_hyst': [1.0, 1.0],
|
314
|
+
'stteeth_hyst': [1.0, 1.0],
|
315
|
+
'styoke_eddy': [2.0,2.0],
|
316
|
+
'stteeth_eddy': [2.0,2.0],
|
317
|
+
'rotor_hyst': [1.0,1.0],
|
318
|
+
'rotor_eddy': [2.0, 2.0]}
|
282
319
|
|
283
320
|
def _set_losspar(self, speed, ef, hf):
|
284
321
|
self.fo = speed*self.p
|
@@ -291,10 +328,10 @@ class SynchronousMachine(object):
|
|
291
328
|
|
292
329
|
if self.bertotti:
|
293
330
|
self.plexp.update({
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
331
|
+
'styoke_excess': 1.5,
|
332
|
+
'stteeth_excess':1.5,
|
333
|
+
'rotor_excess': 1.5})
|
334
|
+
|
298
335
|
|
299
336
|
def pfric(self, n):
|
300
337
|
"""friction and windage losses"""
|
@@ -325,7 +362,7 @@ class SynchronousMachine(object):
|
|
325
362
|
|
326
363
|
def tloss_iqd(self, iq, id, iex, n):
|
327
364
|
"""return loss torque of d-q current, iron loss correction factor
|
328
|
-
|
365
|
+
and friction windage losses"""
|
329
366
|
if n > 1e-3:
|
330
367
|
f1 = self.p*n
|
331
368
|
plfe = self.kpfe * (self.iqd_plfe1(iq, id, iex, f1)
|
@@ -400,7 +437,7 @@ class SynchronousMachine(object):
|
|
400
437
|
constraints=[
|
401
438
|
{'type': 'eq',
|
402
439
|
'fun': lambda iqd: self.tmech_iqd(*iqd, n) - torque}])
|
403
|
-
|
440
|
+
#options={'disp': disp, 'maxiter': maxiter})
|
404
441
|
if res['success']:
|
405
442
|
return res.x
|
406
443
|
|
@@ -429,12 +466,12 @@ class SynchronousMachine(object):
|
|
429
466
|
constraints=[
|
430
467
|
{'type': 'eq',
|
431
468
|
'fun': lambda iqd: self.torque_iqd(*iqd) - torque}])
|
432
|
-
|
469
|
+
#options={'disp': disp, 'maxiter': maxiter})
|
433
470
|
if res['success']:
|
434
471
|
return res.x
|
435
|
-
|
436
|
-
|
437
|
-
|
472
|
+
logger.warning("%s: torque=%f %f, io=%s",
|
473
|
+
res['message'], torque, self.torque_iqd(*startvals),
|
474
|
+
startvals)
|
438
475
|
raise ValueError(res['message'])
|
439
476
|
|
440
477
|
def mtpa(self, i1max):
|
@@ -445,7 +482,7 @@ class SynchronousMachine(object):
|
|
445
482
|
with warnings.catch_warnings():
|
446
483
|
warnings.simplefilter("ignore")
|
447
484
|
tq = so.fsolve(i1tq, T0)[0]
|
448
|
-
|
485
|
+
iq, id, iex = self.iqd_torque(tq)
|
449
486
|
return iq, id, iex, tq
|
450
487
|
|
451
488
|
def mtpa_tmech(self, i1max, n):
|
@@ -462,7 +499,7 @@ class SynchronousMachine(object):
|
|
462
499
|
with minimal losses at max voltage"""
|
463
500
|
iqde = self.iqd_tmech(torque, w1/2/np.pi/self.p)
|
464
501
|
if np.linalg.norm(
|
465
|
-
|
502
|
+
self.uqd(w1, *iqde)) <= u1max*np.sqrt(2):
|
466
503
|
if log:
|
467
504
|
log(iqde)
|
468
505
|
return (*iqde, torque)
|
@@ -471,7 +508,7 @@ class SynchronousMachine(object):
|
|
471
508
|
|
472
509
|
def ubeta(b):
|
473
510
|
return np.sqrt(2)*u1max - np.linalg.norm(
|
474
|
-
|
511
|
+
self.uqd(w1, *iqd(b, i1), iex))
|
475
512
|
beta = -np.pi/4 if torque>0 else -3*np.pi/4
|
476
513
|
io = *iqd(beta, i1), iex
|
477
514
|
|
@@ -497,16 +534,16 @@ class SynchronousMachine(object):
|
|
497
534
|
if log:
|
498
535
|
log(res.x)
|
499
536
|
return *res.x, self.tmech_iqd(*res.x, n)
|
500
|
-
|
501
|
-
|
502
|
-
|
537
|
+
#logger.warning("%s: w1=%f torque=%f, u1max=%f, io=%s",
|
538
|
+
# res['message'], w1, torque, u1max, io)
|
539
|
+
#raise ValueError(res['message'])
|
503
540
|
|
504
541
|
def iqd_torque_umax(self, torque, w1, u1max,
|
505
542
|
disp=False, maxiter=500, log=0, **kwargs):
|
506
543
|
"""return currents for torque with minimal losses"""
|
507
544
|
iqde = self.iqd_torque(torque, disp, maxiter)
|
508
545
|
if np.linalg.norm(
|
509
|
-
|
546
|
+
self.uqd(w1, *iqde)) <= u1max*np.sqrt(2):
|
510
547
|
if log:
|
511
548
|
log(iqde)
|
512
549
|
return (*iqde, torque)
|
@@ -535,27 +572,27 @@ class SynchronousMachine(object):
|
|
535
572
|
if log:
|
536
573
|
log(res.x)
|
537
574
|
return *res.x, self.torque_iqd(*res.x)
|
538
|
-
|
539
|
-
|
575
|
+
logger.warning("%s: w1=%f torque=%f, u1max=%f, io=%s",
|
576
|
+
res['message'], w1, torque, u1max, io)
|
540
577
|
raise ValueError(res['message'])
|
541
578
|
|
542
579
|
def w1_imax_umax(self, i1max, u1max):
|
543
580
|
"""return frequency w1 and shaft torque at voltage u1max and current i1max
|
544
581
|
|
545
582
|
Keyword arguments:
|
546
|
-
|
547
|
-
|
583
|
+
u1max -- the maximum voltage (Vrms)
|
584
|
+
i1max -- the maximum current (Arms)"""
|
548
585
|
iq, id, iex, T = self.mtpa(i1max)
|
549
586
|
n0 = np.sqrt(2)*u1max/np.linalg.norm(
|
550
|
-
|
587
|
+
self.psi(iq, id, iex))/2/np.pi/self.p
|
551
588
|
return self.w1_umax(u1max, iq, id, iex), T
|
552
589
|
|
553
590
|
def w1_umax(self, u, iq, id, iex):
|
554
591
|
"""return frequency w1 at given voltage u and id, iq current
|
555
592
|
|
556
593
|
Keyword arguments:
|
557
|
-
|
558
|
-
|
594
|
+
u -- the maximum voltage (RMS)
|
595
|
+
iq, id -- the d-q currents"""
|
559
596
|
w10 = np.sqrt(2)*u/np.linalg.norm(self.psi(iq, id, iex))
|
560
597
|
return so.fsolve(
|
561
598
|
lambda w1: np.linalg.norm(self.uqd(w1, iq, id, iex))-u*np.sqrt(2),
|
@@ -697,17 +734,16 @@ class SynchronousMachine(object):
|
|
697
734
|
|
698
735
|
return r
|
699
736
|
|
700
|
-
|
701
737
|
class SynchronousMachinePsidq(SynchronousMachine):
|
702
738
|
|
703
739
|
def __init__(self, eecpars, lfe=1, wdg=1, **kwargs):
|
704
740
|
super(self.__class__, self).__init__(
|
705
|
-
|
741
|
+
eecpars, **kwargs)
|
706
742
|
self.iqrange = (eecpars['psidq'][0]['iq'][0],
|
707
743
|
eecpars['psidq'][0]['iq'][-1])
|
708
744
|
self.idrange = (eecpars['psidq'][0]['id'][0],
|
709
745
|
eecpars['psidq'][0]['id'][-1])
|
710
|
-
|
746
|
+
|
711
747
|
self.betarange = (-np.pi if min(self.iqrange) < 0 else -np.pi/2,
|
712
748
|
0 if max(self.iqrange) > 0 else -np.pi/2)
|
713
749
|
self.i1range = (0, betai1(np.max(self.iqrange), 0)[1])
|
@@ -722,44 +758,44 @@ class SynchronousMachinePsidq(SynchronousMachine):
|
|
722
758
|
if _islinear(iexc):
|
723
759
|
exc = iexc
|
724
760
|
psid = wdg*lfe*np.array([
|
725
|
-
|
726
|
-
|
761
|
+
_splinterp(iq, id, iqx, idx, l['psid'])
|
762
|
+
for l in eecpars['psidq']])
|
727
763
|
psiq = wdg*lfe*np.array([
|
728
|
-
|
729
|
-
|
764
|
+
_splinterp(iq, id, iqx, idx, l['psiq'])
|
765
|
+
for l in eecpars['psidq']])
|
730
766
|
else:
|
731
767
|
islinear = False
|
732
768
|
nsamples = 10
|
733
769
|
iexcl = np.linspace(iexc[0], iexc[-1], nsamples)
|
734
770
|
exc = iexcl
|
735
771
|
psid = wdg*lfe*_linsampl(iexc, iexcl, np.array(
|
736
|
-
|
737
|
-
|
772
|
+
[_splinterp(iq, id, iqx, idx, l['psid'])
|
773
|
+
for l in eecpars['psidq']]))
|
738
774
|
psiq = wdg*lfe*_linsampl(iexc, iexcl, np.array(
|
739
|
-
|
740
|
-
|
775
|
+
[_splinterp(iq, id, iqx, idx, l['psiq'])
|
776
|
+
for l in eecpars['psidq']]))
|
741
777
|
|
742
778
|
self.psidf = ip.RegularGridInterpolator(
|
743
|
-
|
744
|
-
|
779
|
+
(exc, iqx, idx), psid,
|
780
|
+
method='cubic', bounds_error=False, fill_value=None)
|
745
781
|
self.psiqf = ip.RegularGridInterpolator(
|
746
|
-
|
747
|
-
|
782
|
+
(exc, iqx, idx), psiq,
|
783
|
+
method='cubic', bounds_error=False, fill_value=None)
|
748
784
|
self.bounds = [(min(iq), max(iq)),
|
749
785
|
(min(id), 0),
|
750
786
|
(iexc[0], iexc[-1])]
|
751
|
-
|
752
|
-
|
787
|
+
# iron losses
|
788
|
+
idname = 'psidq'
|
789
|
+
keys = [k for k in self.plexp.keys() if k in eecpars[idname][0]['losses']]
|
753
790
|
try:
|
754
|
-
|
755
|
-
# check if bertotti
|
791
|
+
# check if bertotti
|
756
792
|
if 'styoke_excess' in eecpars[idname][0]['losses'] and \
|
757
|
-
|
793
|
+
np.any(np.array(eecpars[idname][0]['losses']['styoke_excess'])):
|
758
794
|
self.bertotti = True
|
759
795
|
keys.update({{
|
760
|
-
|
761
|
-
|
762
|
-
|
796
|
+
'styoke_excess': 1.5,
|
797
|
+
'stteeth_excess':1.5,
|
798
|
+
'rotor_excess': 1.5}})
|
763
799
|
if islinear:
|
764
800
|
pfe = {k: np.array([l['losses'][k]
|
765
801
|
for l in eecpars[idname]])
|
@@ -769,13 +805,13 @@ class SynchronousMachinePsidq(SynchronousMachine):
|
|
769
805
|
np.array([l['losses'][k]
|
770
806
|
for l in eecpars[idname]]))
|
771
807
|
for k in keys}
|
772
|
-
|
773
|
-
(exc, iq, id), lfe*
|
808
|
+
self._losses = {k: ip.RegularGridInterpolator(
|
809
|
+
(exc, iq, id), lfe*pfe[k],
|
774
810
|
method='cubic', bounds_error=False, fill_value=None)
|
775
811
|
for k in keys}
|
776
|
-
|
777
|
-
|
778
|
-
|
812
|
+
self._set_losspar(eecpars[idname][0]['losses']['speed'],
|
813
|
+
eecpars[idname][0]['losses']['ef'],
|
814
|
+
eecpars[idname][0]['losses']['hf'])
|
779
815
|
except KeyError:
|
780
816
|
logger.warning("loss map missing")
|
781
817
|
self._losses = {k: lambda x: 0 for k in (
|
@@ -793,17 +829,18 @@ class SynchronousMachinePsidq(SynchronousMachine):
|
|
793
829
|
raise ex
|
794
830
|
|
795
831
|
def plfe1(self, iq, id, iex, f1):
|
796
|
-
losskeys = [
|
797
|
-
|
798
|
-
|
832
|
+
losskeys = [k for k in self._losses if k in [
|
833
|
+
'styoke_eddy', 'styoke_hyst',
|
834
|
+
'stteeth_eddy', 'stteeth_hyst']]
|
835
|
+
if self.bertotti:
|
799
836
|
losskeys += ['styoke_excess', 'stteeth_excess']
|
800
837
|
return np.sum([
|
801
838
|
self._losses[k]((iex, iq, id))*(f1/self.fo)**self.plexp[k][0]
|
802
839
|
for k in losskeys], axis=0)
|
803
|
-
|
840
|
+
|
804
841
|
def plfe2(self, iq, id, iex, f1):
|
805
842
|
losskeys = ['rotor_hyst', 'rotor_eddy']
|
806
|
-
if self.bertotti:
|
843
|
+
if self.bertotti:
|
807
844
|
losskeys += ['rotor_excess']
|
808
845
|
return np.sum([
|
809
846
|
self._losses[k]((iex, iq, id))*(f1/self.fo)**self.plexp[k][0]
|
@@ -812,7 +849,7 @@ class SynchronousMachinePsidq(SynchronousMachine):
|
|
812
849
|
def iqd_plfe1(self, iq, id, iex, f1):
|
813
850
|
return self.plfe1(iq, id, iex, f1)
|
814
851
|
|
815
|
-
def iqd_plfe2(self, iq, id, iex, f1):
|
852
|
+
def iqd_plfe2(self, iq, id, iex, f1):
|
816
853
|
return self.plfe2(iq, id, iex, f1)
|
817
854
|
|
818
855
|
class SynchronousMachineLdq(SynchronousMachine):
|
@@ -830,51 +867,55 @@ class SynchronousMachineLdq(SynchronousMachine):
|
|
830
867
|
betax = np.linspace(beta[0], beta[-1], 20)
|
831
868
|
|
832
869
|
if _islinear(iexc):
|
870
|
+
logger.info("Linear sampled ex current: %s",
|
871
|
+
iexc)
|
833
872
|
exc = iexc
|
834
873
|
psid = wdg*lfe*np.array([
|
835
|
-
|
874
|
+
_splinterp(beta, i1, betax, i1x, l['psid'])
|
836
875
|
for l in eecpars['ldq']])
|
837
876
|
psiq = wdg*lfe*np.array([
|
838
|
-
|
877
|
+
_splinterp(beta, i1, betax, i1x, l['psiq'])
|
839
878
|
for l in eecpars['ldq']])
|
840
879
|
else:
|
841
880
|
islinear = False
|
881
|
+
logger.info("Non Linear sampled ex current %s",
|
882
|
+
iexc)
|
842
883
|
nsamples = 10
|
843
884
|
iexcl = np.linspace(iexc[0], iexc[-1], nsamples)
|
844
885
|
exc = iexcl
|
845
886
|
psid = wdg*lfe*_linsampl(iexc, iexcl, np.array(
|
846
|
-
|
887
|
+
[_splinterp(beta, i1, betax, i1x, l['psid'])
|
847
888
|
for l in eecpars['ldq']]))
|
848
889
|
psiq = wdg*lfe*_linsampl(iexc, iexcl, np.array(
|
849
|
-
|
890
|
+
[_splinterp(beta, i1, betax, i1x, l['psiq'])
|
850
891
|
for l in eecpars['ldq']]))
|
851
892
|
|
852
893
|
# extrapolate outside range
|
853
894
|
self.psidf = ip.RegularGridInterpolator(
|
854
|
-
|
855
|
-
|
856
|
-
|
895
|
+
(exc, betax, i1x), np.sqrt(2)*psid,
|
896
|
+
method='cubic',
|
897
|
+
bounds_error=False, fill_value=None)
|
857
898
|
self.psiqf = ip.RegularGridInterpolator(
|
858
|
-
|
859
|
-
|
860
|
-
|
899
|
+
(exc, betax, i1x), np.sqrt(2)*psiq,
|
900
|
+
method='cubic'
|
901
|
+
, bounds_error=False, fill_value=None)
|
861
902
|
i1max = np.sqrt(2)*(max(i1))
|
862
903
|
self.bounds = [(np.cos(min(beta))*i1max, i1max),
|
863
904
|
(-i1max, 0),
|
864
905
|
(iexc[0], iexc[-1])]
|
865
906
|
|
866
|
-
# iron losses
|
867
|
-
|
907
|
+
# iron losses
|
908
|
+
idname = 'ldq'
|
909
|
+
keys = [k for k in self.plexp.keys() if k in eecpars[idname][0]['losses']]
|
868
910
|
try:
|
869
|
-
idname = 'ldq'
|
870
911
|
# check bertotti losses
|
871
912
|
if 'styoke_excess' in eecpars[idname][0]['losses'] and \
|
872
|
-
|
913
|
+
np.any(np.array(eecpars[idname][0]['losses']['styoke_excess'])):
|
873
914
|
self.bertotti = True
|
874
915
|
keys.update({{
|
875
|
-
|
876
|
-
|
877
|
-
|
916
|
+
'styoke_excess': 1.5,
|
917
|
+
'stteeth_excess':1.5,
|
918
|
+
'rotor_excess': 1.5}})
|
878
919
|
|
879
920
|
if islinear:
|
880
921
|
pfe = {k: np.array([l['losses'][k]
|
@@ -888,9 +929,9 @@ class SynchronousMachineLdq(SynchronousMachine):
|
|
888
929
|
|
889
930
|
# fill value with nan outside range
|
890
931
|
self._losses = {k: ip.RegularGridInterpolator(
|
891
|
-
|
892
|
-
|
893
|
-
for k in keys}
|
932
|
+
(exc, beta, i1), lfe*pfe[k],
|
933
|
+
method='cubic', bounds_error=False, fill_value=None)
|
934
|
+
for k in pfe.keys()}
|
894
935
|
self._set_losspar(eecpars[idname][0]['losses']['speed'],
|
895
936
|
eecpars[idname][0]['losses']['ef'],
|
896
937
|
eecpars[idname][0]['losses']['hf'])
|
@@ -916,22 +957,22 @@ class SynchronousMachineLdq(SynchronousMachine):
|
|
916
957
|
raise ex
|
917
958
|
|
918
959
|
def plfe1(self, beta, i1, iex, f1):
|
919
|
-
losskeys =
|
920
|
-
|
921
|
-
if self.bertotti:
|
960
|
+
losskeys = ['styoke_eddy', 'styoke_hyst',
|
961
|
+
'stteeth_eddy', 'stteeth_hyst']
|
962
|
+
if self.bertotti:
|
922
963
|
losskeys += ['styoke_excess', 'stteeth_excess']
|
923
964
|
return np.sum([
|
924
965
|
self._losses[k]((iex, beta, i1))*(f1/self.fo)**self.plexp[k][0]
|
925
|
-
for k in losskeys], axis=0)
|
926
|
-
|
966
|
+
for k in losskeys if k in self._losses], axis=0)
|
967
|
+
|
927
968
|
def plfe2(self, beta, i1, iex, f1):
|
928
969
|
losskeys = ['rotor_hyst', 'rotor_eddy']
|
929
|
-
if self.bertotti:
|
970
|
+
if self.bertotti:
|
930
971
|
losskeys += ['rotor_excess']
|
931
972
|
return np.sum([
|
932
973
|
self._losses[k]((iex, beta, i1))*(f1/self.fo)**self.plexp[k][0]
|
933
974
|
for k in losskeys], axis=0)
|
934
|
-
|
975
|
+
|
935
976
|
def iqd_plfe1(self, iq, id, iex, f1):
|
936
977
|
beta = np.arctan2(id, iq)
|
937
978
|
if np.isscalar(beta):
|
@@ -964,9 +1005,9 @@ if __name__ == '__main__':
|
|
964
1005
|
m = SynchronousMachineLdq(eecpar)
|
965
1006
|
else:
|
966
1007
|
m = SynchronousMachinePsidq(eecpar)
|
967
|
-
|
968
|
-
|
969
|
-
|
970
|
-
|
971
|
-
|
972
|
-
|
1008
|
+
T = 240
|
1009
|
+
u1max = 163
|
1010
|
+
nmax = 1000
|
1011
|
+
r = m.characteristics(T, 0, u1max)
|
1012
|
+
femagtools.plot.characteristics(r, 'SynchronousMachine')
|
1013
|
+
plt.show()
|