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.
Files changed (38) hide show
  1. femagtools/__init__.py +1 -1
  2. femagtools/bch.py +11 -1
  3. femagtools/dxfsl/area.py +108 -7
  4. femagtools/dxfsl/conv.py +15 -0
  5. femagtools/dxfsl/converter.py +44 -20
  6. femagtools/dxfsl/fslrenderer.py +93 -42
  7. femagtools/dxfsl/functions.py +8 -0
  8. femagtools/dxfsl/geom.py +126 -18
  9. femagtools/dxfsl/machine.py +30 -9
  10. femagtools/femag.py +3 -3
  11. femagtools/fsl.py +73 -48
  12. femagtools/isa7.py +2 -2
  13. femagtools/machine/effloss.py +2 -0
  14. femagtools/machine/pm.py +198 -42
  15. femagtools/machine/sm.py +294 -253
  16. femagtools/machine/utils.py +5 -14
  17. femagtools/model.py +32 -2
  18. femagtools/moo/algorithm.py +6 -0
  19. femagtools/nc.py +2 -0
  20. femagtools/opt.py +2 -1
  21. femagtools/plot/bch.py +19 -5
  22. femagtools/plot/char.py +4 -4
  23. femagtools/plot/nc.py +21 -4
  24. femagtools/plot/wdg.py +38 -26
  25. femagtools/templates/gen_hairpin_winding.mako +209 -0
  26. femagtools/templates/gen_winding.mako +8 -9
  27. femagtools/templates/magnetIron.mako +32 -6
  28. femagtools/templates/mesh-airgap.mako +9 -0
  29. femagtools/templates/rotor_winding.mako +10 -6
  30. femagtools/templates/statorRotor3.mako +8 -5
  31. femagtools/windings.py +31 -18
  32. {femagtools-1.7.5.dist-info → femagtools-1.7.7.dist-info}/METADATA +1 -1
  33. {femagtools-1.7.5.dist-info → femagtools-1.7.7.dist-info}/RECORD +38 -37
  34. {femagtools-1.7.5.dist-info → femagtools-1.7.7.dist-info}/WHEEL +1 -1
  35. tests/test_windings.py +1 -1
  36. {femagtools-1.7.5.dist-info → femagtools-1.7.7.dist-info}/LICENSE +0 -0
  37. {femagtools-1.7.5.dist-info → femagtools-1.7.7.dist-info}/entry_points.txt +0 -0
  38. {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 = dict(
16
- zeta1=0.3,
17
- zeta2=0,
18
- gam=0.7,
19
- kh=2,
20
- tcu1=20,
21
- tcu2=20,
22
- rotor_mass=0,
23
- kfric_b=1,
24
- kpfe = 1 # iron loss factor
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
- """return dict of parameters of equivalent circuit for
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
- cmd = kwargs.get('cmd', None)
53
- da1 = machine['outer_diam']
54
- Q1 = machine['stator']['num_slots']
55
- if 'statorRotor3' in machine['stator']:
56
- hs = machine['stator']['statorRotor3']['slot_height']
57
- elif 'stator1' in machine['stator']:
58
- hs = machine['stator']['stator1']['slot_rf1'] - machine['stator']['stator1']['tip_rh1']
59
- elif 'stator4' in machine['stator']:
60
- hs = machine['stator']['stator4']['slot_height']
61
- Jmax = 15 # max current density in A/mm2
62
- wdgk = 'windings' if 'windings' in machine else 'winding'
63
- N = machine[wdgk]['num_wires']
64
- i1_max = round(0.28*np.pi*hs*(da1+hs)/Q1/N*Jmax*1e5)*10 * \
65
- machine[wdgk].get('num_par_wdgs', 1)
66
-
67
- ifnom = machine['rotor']['ifnom']
68
- exc_logspace = True
69
- if exc_logspace:
70
- excur = np.logspace(np.log(ifnom/10), np.log(1.5*ifnom),
71
- kwargs.get("num_exc_steps", 6),
72
- base=np.exp(1)).tolist()
73
- else:
74
- excur = np.linspace(ifnom/10, 1.5*ifnom,
75
- kwargs.get("num_exc_steps", 6))
76
-
77
- parvardef = {
78
- "decision_vars": [
79
- {"values": excur, "name": "load_ex_cur"}
80
- ]
81
- }
82
-
83
- parvar = parstudy.List(
84
- workdir, condMat=condMat,
85
- magnetizingCurves=magnetizingCurves, cmd=cmd)
86
-
87
- simulation = dict(
88
- calculationMode=kwargs.get('calculationMode',
89
- 'ld_lq_fast'),
90
- wind_temp=20.0,
91
- i1_max=kwargs.get('i1_max', i1_max),
92
- maxid=kwargs.get('maxid', 0),
93
- minid=kwargs.get('minid', -i1_max),
94
- maxiq=kwargs.get('maxiq', i1_max),
95
- miniq=kwargs.get('miniq', -i1_max),
96
- delta_id=kwargs.get('delta_id', i1_max/5),
97
- delta_iq=kwargs.get('delta_iq', i1_max/5),
98
- beta_min=kwargs.get('beta_min', -180),
99
- beta_max=kwargs.get('beta_max', 0),
100
- num_move_steps=kwargs.get('num_move_steps', 31),
101
- load_ex_cur=0.5,
102
- num_cur_steps=kwargs.get('num_cur_steps', 5),
103
- num_beta_steps=kwargs.get('num_beta_steps', 13),
104
- num_par_wdgs=machine[wdgk].get('num_par_wdgs', 1),
105
- skew_angle=kwargs.get('skew_angle', 0.0),
106
- num_skew_steps=kwargs.get('num_skew_steps', 0.0),
107
- period_frac=kwargs.get('period_frac', 6),
108
- speed=kwargs.get('speed', 50))
109
-
110
- ###self.cleanup() # remove previously created files in workdir
111
- results = parvar(parvardef, machine, simulation, engine)
112
- if simulation['calculationMode'] == 'ld_lq_fast':
113
- idname = 'ldq'
114
- else:
115
- idname = 'psidq'
116
- for r in results['f']:
117
- for k in ('hf', 'ef'):
118
- if k in r['lossPar']:
119
- r[idname]['losses'][k] = r['lossPar'][k]
120
- try:
121
- rotor_mass = sum(results['f'][-1]['weights'][-1])
122
- except KeyError:
123
- rotor_mass = 0 # need femag classic > rel-9.3.x-48-gca42bbd0
124
- b = results['f'][-1]
125
-
126
- losskeys = ('speed', 'ef', 'hf', 'cf',
127
- 'styoke_hyst', 'stteeth_hyst', 'styoke_eddy',
128
- 'stteeth_eddy', 'rotor_hyst', 'rotor_eddy',
129
- 'styoke_excess', 'stteeth_excess', 'rotor_excess')
130
-
131
- # winding resistance
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
- r1 = machine[wdgk]['resistance']
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
- {'Q': machine['stator']['num_slots'],
138
- 'm': machine[wdgk]['num_phases'],
139
- 'p': machine['poles']//2,
140
- 'l': machine[wdgk]['num_layers'],
141
- 'yd': yd})
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
- g = machine[wdgk].get('num_par_wdgs', 1)
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
- machine[wdgk].get('cufilfact', 0.45)*np.pi*da1*hs/Q1/2/N
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
- if simulation['calculationMode'] == 'ld_lq_fast':
153
- dqpars = dict(m=3, p=b['machine']['p'],
154
- r1=r1,
155
- r2=machine['rotor'].get('resistance', 1),
156
- rotor_mass=rotor_mass, kfric_b=1,
157
- ldq=[dict(
158
- ex_current=b['machine']['ex_current'],
159
- i1=b['ldq']['i1'],
160
- beta=b['ldq']['beta'],
161
- psid=b['ldq']['psid'],
162
- psiq=b['ldq']['psiq'],
163
- torque=b['ldq']['torque'],
164
- ld=b['ldq']['ld'],
165
- lq=b['ldq']['lq'],
166
- losses={k: b['ldq']['losses'][k]
167
- for k in losskeys if k in b['ldq']['losses']})
168
- for b in results['f']])
169
- else:
170
- dqpars = dict(m=3, p=b['machine']['p'],
171
- r1=r1,
172
- r2=machine['rotor'].get('resistance', 1),
173
- rotor_mass=rotor_mass, kfric_b=1,
174
- psidq=[dict(
175
- ex_current=b['machine']['ex_current'],
176
- iq=b['psidq']['iq'],
177
- id=b['psidq']['id'],
178
- psidq_ldq=b['psidq_ldq'],
179
- psid=b['psidq']['psid'],
180
- psiq=b['psidq']['psiq'],
181
- torque=b['psidq']['torque'],
182
- losses={k: b['psidq']['losses'][k]
183
- for k in losskeys if k in b['psidq']['losses']})
184
- for b in results['f']])
185
- if 'current_angles' in results['f'][0]:
186
- dqpars['current_angles'] = results['f'][0]['current_angles']
187
- return dqpars
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
- arguments:
192
- exc: (list) nonlinear sequence of excitation current
193
- excl: (list) linear sequence of excitation current
194
- a: (array) matrix to be resampled"""
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
- using a cubic spline interpolation.
207
- arguments:
208
- beta: (list) n original up-i angles in rad
209
- i1: (list) m original current in A
210
- betax: (list) nx new up-i angles in rad
211
- i1x: (list) mx new currents
212
- a: (nxm array) to be interpolated"""
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
- return np.abs(np.sum(
222
- np.asarray(exc)**2 -
223
- np.linspace(exc[0], exc[-1], len(exc))**2)) < 1e-2
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
- eecpars: dict() electrical circuit parameters
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
- 'styoke_excess': 1.5,
295
- 'stteeth_excess':1.5,
296
- 'rotor_excess': 1.5})
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
- and friction windage losses"""
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
- #options={'disp': disp, 'maxiter': maxiter})
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
- #options={'disp': disp, 'maxiter': maxiter})
469
+ #options={'disp': disp, 'maxiter': maxiter})
433
470
  if res['success']:
434
471
  return res.x
435
- logger.warning("%s: torque=%f %f, io=%s",
436
- res['message'], torque, self.torque_iqd(*startvals),
437
- startvals)
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
- iq, id, iex = self.iqd_torque(tq)
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
- self.uqd(w1, *iqde)) <= u1max*np.sqrt(2):
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
- self.uqd(w1, *iqd(b, i1), iex))
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
- #logger.warning("%s: w1=%f torque=%f, u1max=%f, io=%s",
501
- # res['message'], w1, torque, u1max, io)
502
- #raise ValueError(res['message'])
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
- self.uqd(w1, *iqde)) <= u1max*np.sqrt(2):
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
- logger.warning("%s: w1=%f torque=%f, u1max=%f, io=%s",
539
- res['message'], w1, torque, u1max, io)
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
- u1max -- the maximum voltage (Vrms)
547
- i1max -- the maximum current (Arms)"""
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
- self.psi(iq, id, iex))/2/np.pi/self.p
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
- u -- the maximum voltage (RMS)
558
- iq, id -- the d-q currents"""
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
- eecpars, **kwargs)
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
- _splinterp(iq, id, iqx, idx, l['psid'])
726
- for l in eecpars['psidq']])
761
+ _splinterp(iq, id, iqx, idx, l['psid'])
762
+ for l in eecpars['psidq']])
727
763
  psiq = wdg*lfe*np.array([
728
- _splinterp(iq, id, iqx, idx, l['psiq'])
729
- for l in eecpars['psidq']])
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
- [_splinterp(iq, id, iqx, idx, l['psid'])
737
- for l in eecpars['psidq']]))
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
- [_splinterp(iq, id, iqx, idx, l['psiq'])
740
- for l in eecpars['psidq']]))
775
+ [_splinterp(iq, id, iqx, idx, l['psiq'])
776
+ for l in eecpars['psidq']]))
741
777
 
742
778
  self.psidf = ip.RegularGridInterpolator(
743
- (exc, iqx, idx), psid,
744
- method='cubic', bounds_error=False, fill_value=None)
779
+ (exc, iqx, idx), psid,
780
+ method='cubic', bounds_error=False, fill_value=None)
745
781
  self.psiqf = ip.RegularGridInterpolator(
746
- (exc, iqx, idx), psiq,
747
- method='cubic', bounds_error=False, fill_value=None)
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
- keys = self.plexp.keys()
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
- idname = 'psidq'
755
- # check if bertotti
791
+ # check if bertotti
756
792
  if 'styoke_excess' in eecpars[idname][0]['losses'] and \
757
- np.any(np.array(eecpars[idname][0]['losses']['styoke_excess'])):
793
+ np.any(np.array(eecpars[idname][0]['losses']['styoke_excess'])):
758
794
  self.bertotti = True
759
795
  keys.update({{
760
- 'styoke_excess': 1.5,
761
- 'stteeth_excess':1.5,
762
- 'rotor_excess': 1.5}})
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
- self._losses = {k: ip.RegularGridInterpolator(
773
- (exc, iq, id), lfe*np.array(pfe[k]),
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
- self._set_losspar(eecpars[idname][0]['losses']['speed'],
777
- eecpars[idname][0]['losses']['ef'],
778
- eecpars[idname][0]['losses']['hf'])
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 = ['styoke_eddy', 'styoke_hyst',
797
- 'stteeth_eddy', 'stteeth_hyst']
798
- if self.bertotti:
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
- _splinterp(beta, i1, betax, i1x, l['psid'])
874
+ _splinterp(beta, i1, betax, i1x, l['psid'])
836
875
  for l in eecpars['ldq']])
837
876
  psiq = wdg*lfe*np.array([
838
- _splinterp(beta, i1, betax, i1x, l['psiq'])
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
- [_splinterp(beta, i1, betax, i1x, l['psid'])
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
- [_splinterp(beta, i1, betax, i1x, l['psiq'])
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
- (exc, betax, i1x), np.sqrt(2)*psid,
855
- method='cubic',
856
- bounds_error=False, fill_value=None)
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
- (exc, betax, i1x), np.sqrt(2)*psiq,
859
- method='cubic'
860
- , bounds_error=False, fill_value=None)
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
- keys = self.plexp.keys()
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
- np.any(np.array(eecpars[idname][0]['losses']['styoke_excess'])):
913
+ np.any(np.array(eecpars[idname][0]['losses']['styoke_excess'])):
873
914
  self.bertotti = True
874
915
  keys.update({{
875
- 'styoke_excess': 1.5,
876
- 'stteeth_excess':1.5,
877
- 'rotor_excess': 1.5}})
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
- (exc, beta, i1), lfe*np.array(pfe[k]),
892
- method='cubic', bounds_error=False, fill_value=None)
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 = ['styoke_eddy', 'styoke_hyst',
920
- 'stteeth_eddy', 'stteeth_hyst']
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
- T = 240
968
- u1max = 163
969
- nmax = 1000
970
- r = m.characteristics(T, 0, u1max)
971
- femagtools.plot.characteristics(r, 'SynchronousMachine')
972
- plt.show()
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()