femagtools 1.8.6__py3-none-any.whl → 1.8.8__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 +2 -2
- femagtools/amela.py +18 -286
- femagtools/bch.py +11 -7
- femagtools/ecloss.py +121 -105
- femagtools/femag.py +13 -73
- femagtools/fsl.py +11 -8
- femagtools/isa7.py +174 -10
- femagtools/leakinduc.py +63 -0
- femagtools/losscoeffs.py +29 -3
- femagtools/machine/afpm.py +138 -60
- femagtools/machine/effloss.py +13 -1
- femagtools/mcv.py +173 -9
- femagtools/nc.py +16 -14
- femagtools/parstudy.py +3 -1
- femagtools/plot/bch.py +126 -44
- femagtools/plot/nc.py +13 -0
- femagtools/shortcircuit.py +378 -0
- femagtools/templates/psi-torq-rem-rot.mako +127 -0
- femagtools/templates/psi-torq-rot.mako +98 -0
- femagtools/tks.py +1 -1
- femagtools/windings.py +65 -0
- {femagtools-1.8.6.dist-info → femagtools-1.8.8.dist-info}/METADATA +1 -1
- {femagtools-1.8.6.dist-info → femagtools-1.8.8.dist-info}/RECORD +31 -27
- {femagtools-1.8.6.dist-info → femagtools-1.8.8.dist-info}/WHEEL +1 -1
- tests/test_afpm.py +2 -2
- tests/test_amela.py +1 -3
- tests/test_fsl.py +4 -4
- tests/test_nc.py +10 -0
- {femagtools-1.8.6.dist-info → femagtools-1.8.8.dist-info}/LICENSE +0 -0
- {femagtools-1.8.6.dist-info → femagtools-1.8.8.dist-info}/entry_points.txt +0 -0
- {femagtools-1.8.6.dist-info → femagtools-1.8.8.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,378 @@
|
|
1
|
+
import pathlib
|
2
|
+
import logging
|
3
|
+
import json
|
4
|
+
import numpy as np
|
5
|
+
import scipy.optimize as so
|
6
|
+
from scipy.interpolate import make_interp_spline
|
7
|
+
import scipy.integrate as ig
|
8
|
+
import femagtools.parstudy
|
9
|
+
|
10
|
+
logger = logging.getLogger('shortcircuit')
|
11
|
+
|
12
|
+
def _parstudy_list(femag, result_func):
|
13
|
+
workdir = femag.workdir
|
14
|
+
magnetMat = femag.magnets
|
15
|
+
magnetizingCurves = femag.magnetizingCurves
|
16
|
+
condMat = femag.condMat
|
17
|
+
templatedirs = femag.templatedirs
|
18
|
+
cmd = femag.cmd
|
19
|
+
return femagtools.parstudy.List(
|
20
|
+
workdir, condMat=condMat, magnets=magnetMat,
|
21
|
+
magnetizingCurves=magnetizingCurves,
|
22
|
+
cmd=cmd, result_func=result_func)
|
23
|
+
|
24
|
+
def get_shortCircuit_parameters(bch, nload):
|
25
|
+
"""extracts shortciruit parameters from bch"""
|
26
|
+
try:
|
27
|
+
if nload < 0:
|
28
|
+
nload = 0
|
29
|
+
if nload > 2:
|
30
|
+
nload = 2
|
31
|
+
if nload > 0:
|
32
|
+
dqld = bch.dqPar['ld']
|
33
|
+
dqlq = bch.dqPar['lq']
|
34
|
+
dqpsim = bch.dqPar['psim']
|
35
|
+
if len(dqld) <= nload or len(dqlq) <= nload or len(dqpsim) <= nload:
|
36
|
+
ld = dqld[-1]/bch.armatureLength
|
37
|
+
lq = dqlq[-1]/bch.armatureLength
|
38
|
+
psim = dqpsim[-1]/bch.armatureLength
|
39
|
+
else:
|
40
|
+
ld = dqld[nload-1]/bch.armatureLength
|
41
|
+
lq = dqlq[nload-1]/bch.armatureLength
|
42
|
+
psim = dqpsim[nload-1]/bch.armatureLength
|
43
|
+
else:
|
44
|
+
ld = bch.machine['ld']/bch.armatureLength
|
45
|
+
lq = bch.machine['lq']/bch.armatureLength
|
46
|
+
psim = bch.machine['psim']/bch.armatureLength
|
47
|
+
return dict(
|
48
|
+
r1=bch.machine['r1'],
|
49
|
+
ld=ld,
|
50
|
+
lq=lq,
|
51
|
+
Hk=bch.magnet['demag_hx'],
|
52
|
+
Tmag=bch.magnet['Tmag'],
|
53
|
+
psim=psim,
|
54
|
+
num_pol_pair=bch.machine['p'],
|
55
|
+
fc_radius=bch.machine['fc_radius'],
|
56
|
+
lfe=bch.armatureLength/1e3,
|
57
|
+
pocfilename=bch.machine['pocfile'],
|
58
|
+
num_par_wdgs=bch.machine.get('num_par_wdgs', 1),
|
59
|
+
calculationMode='shortcircuit')
|
60
|
+
except (KeyError, AttributeError, IndexError):
|
61
|
+
raise ValueError("missing pm/Rel-Sim results")
|
62
|
+
|
63
|
+
|
64
|
+
def find_peaks_and_valleys(t, y):
|
65
|
+
""" return peak and valley of y with maximum amplitude
|
66
|
+
"""
|
67
|
+
peaks = (np.diff(np.sign(np.diff(y))) < 0).nonzero()[0] + 1
|
68
|
+
if len(peaks>0):
|
69
|
+
ip = np.argmax(y[peaks])
|
70
|
+
pv = {'ip': y[peaks][ip], 'tp': t[peaks][ip]}
|
71
|
+
else:
|
72
|
+
pv = {'ip': [], 'tp': []}
|
73
|
+
valleys = (np.diff(np.sign(np.diff(y))) > 0).nonzero()[0] + 1
|
74
|
+
if len(valleys>0):
|
75
|
+
iv = np.argmin(y[valleys])
|
76
|
+
pv.update({'iv': y[valleys][iv], 'tv': t[valleys][iv]})
|
77
|
+
else:
|
78
|
+
pv.update({'iv': [], 'tv': []})
|
79
|
+
pv.update({'peaks': y[peaks], 'valleys': y[valleys],
|
80
|
+
'tpeaks': t[peaks], 'tvalleys': t[valleys]})
|
81
|
+
return pv
|
82
|
+
|
83
|
+
def shortcircuit(femag, machine, bch, simulation, engine=0):
|
84
|
+
scdata = {}
|
85
|
+
calcmode = simulation.get('calculationMode', '')
|
86
|
+
simulation.update(
|
87
|
+
get_shortCircuit_parameters(bch,
|
88
|
+
simulation.get('initial', 2)))
|
89
|
+
if 'speed' not in simulation:
|
90
|
+
simulation['speed'] = bch.dqPar['speed']
|
91
|
+
if simulation.get('sc_type', 3) == 3:
|
92
|
+
logger.info("3phase short circuit simulation")
|
93
|
+
builder = femagtools.fsl.Builder(femag.templatedirs)
|
94
|
+
fslcmds = (builder.open_model(femag.model) +
|
95
|
+
builder.create_shortcircuit(simulation))
|
96
|
+
fslfile = 'shortcircuit.fsl'
|
97
|
+
(pathlib.Path(femag.workdir)/fslfile).write_text(
|
98
|
+
'\n'.join(fslcmds),
|
99
|
+
encoding='latin1', errors='ignore')
|
100
|
+
femag.run(fslfile) # options?
|
101
|
+
bchfile = femag.get_bch_file(femag.modelname)
|
102
|
+
if bchfile:
|
103
|
+
bchsc = femagtools.bch.Reader()
|
104
|
+
logger.info("Read BCH %s", bchfile)
|
105
|
+
bchsc.read(pathlib.Path(bchfile).read_text(
|
106
|
+
encoding='latin1', errors='ignore'))
|
107
|
+
bchsc.scData['demag'] = bchsc.demag
|
108
|
+
if simulation.get('sim_demagn', 0):
|
109
|
+
d = {'displ': [d['displ']
|
110
|
+
for d in bchsc.demag if 'displ' in d],
|
111
|
+
'H_max': [d['H_max']
|
112
|
+
for d in bchsc.demag if 'H_max' in d],
|
113
|
+
'H_av': [d['H_av']
|
114
|
+
for d in bchsc.demag if 'H_av' in d]}
|
115
|
+
simulation['i1max'] = bchsc.scData['iks']
|
116
|
+
bchsc.scData['demag'] = demag(
|
117
|
+
femag, machine, simulation, engine)
|
118
|
+
bchsc.scData['demag'].update(d)
|
119
|
+
scdata = bchsc.scData
|
120
|
+
#for w in bch.flux:
|
121
|
+
# try:
|
122
|
+
# bch.flux[w] += bchsc.flux[w]
|
123
|
+
# bch.flux_fft[w] += bchsc.flux_fft[w]
|
124
|
+
# except (KeyError, IndexError):
|
125
|
+
# logging.debug(
|
126
|
+
# "No additional flux data in sc simulation")
|
127
|
+
# break
|
128
|
+
|
129
|
+
if simulation.get('sc_type', 3) == 2:
|
130
|
+
if 'i1max' not in simulation:
|
131
|
+
# just a wild guess
|
132
|
+
simulation['i1max'] = 4.5*bch.machine['i1']
|
133
|
+
logger.info("2phase short circuit simulation i1max = %.0f",
|
134
|
+
simulation['i1max'])
|
135
|
+
scdata = shortcircuit_2phase(femag, machine, simulation, engine)
|
136
|
+
|
137
|
+
else:
|
138
|
+
logger.warning("Empty shortcircuit results for type %d",
|
139
|
+
simulation.get('sc_type', 'unknown'))
|
140
|
+
# must reset calcmode
|
141
|
+
if calcmode:
|
142
|
+
simulation['calculationMode'] = calcmode
|
143
|
+
else:
|
144
|
+
del simulation['calculationMode']
|
145
|
+
return scdata
|
146
|
+
|
147
|
+
def sc_result_func(task):
|
148
|
+
basedir = pathlib.Path(task.directory)
|
149
|
+
psitorq = np.loadtxt(basedir/'psi-torq-rot.dat')
|
150
|
+
pos = np.unique(psitorq[:, 0])
|
151
|
+
ncurs = psitorq[:, 0].shape[0]//pos.shape[0]
|
152
|
+
ire = psitorq[:ncurs, 1:4]
|
153
|
+
psire = np.reshape(psitorq[:, 4:7], (-1, ncurs, 3))
|
154
|
+
torq = np.reshape(psitorq[:, 7], (-1, ncurs))
|
155
|
+
return {'pos': pos.tolist(), 'ire': ire.tolist(),
|
156
|
+
'psire': psire.tolist(), 'torq': torq.tolist()}
|
157
|
+
|
158
|
+
|
159
|
+
def shortcircuit_2phase(femag, machine, simulation, engine=0):
|
160
|
+
i1max = simulation['i1max']
|
161
|
+
num_cur_steps = 4
|
162
|
+
i1 = np.linspace(0, i1max, num_cur_steps)
|
163
|
+
i1vec = np.concat((-i1[::-1], i1[1:]))
|
164
|
+
num_par_wdgs = machine['winding'].get('num_par_wdgs', 1)
|
165
|
+
flux_sim = {
|
166
|
+
'calculationMode': 'psi-torq-rot',
|
167
|
+
'i1max': i1max,
|
168
|
+
'curvec': [],
|
169
|
+
'num_par_wdgs': num_par_wdgs}
|
170
|
+
|
171
|
+
if engine:
|
172
|
+
parstudy = _parstudy_list(femag, sc_result_func)
|
173
|
+
parvardef = {
|
174
|
+
"decision_vars": [
|
175
|
+
{"values": i1vec, "name": "curvec"}]
|
176
|
+
}
|
177
|
+
results = parstudy(parvardef, machine, flux_sim, engine)
|
178
|
+
|
179
|
+
ire = np.array([r['ire'][0] for r in results['f']])
|
180
|
+
pos = np.array(results['f'][0]['pos'])
|
181
|
+
phi = pos*np.pi/180
|
182
|
+
torq = np.hstack([r['torq'] for r in results['f']])
|
183
|
+
psire = np.hstack([r['psire'] for r in results['f']])
|
184
|
+
else:
|
185
|
+
simulation.update(flux_sim)
|
186
|
+
simulation['curvec'] = i1vec.tolist()
|
187
|
+
results = femag(machine, simulation)
|
188
|
+
class Task:
|
189
|
+
def __init__(self, workdir):
|
190
|
+
self.directory = workdir
|
191
|
+
results = sc_result_func(Task(femag.workdir))
|
192
|
+
ire = np.array(results['ire'])
|
193
|
+
pos = np.array(results['pos'])
|
194
|
+
torq = np.array(results['torq'])
|
195
|
+
psire = np.array(results['psire'])
|
196
|
+
|
197
|
+
#with open('results.json', 'w') as fp:
|
198
|
+
# json.dump({'ire': ire.tolist(), 'pos': pos.tolist(),
|
199
|
+
# 'torq': torq.tolist(), 'psire': psire.tolist()}, fp)
|
200
|
+
logger.info("move steps %d currents %s", len(pos), ire[:,0])
|
201
|
+
|
202
|
+
Ai = [femagtools.utils.fft(pos, psire[:, k, 0])['a']
|
203
|
+
for k in range(np.shape(psire)[1])]
|
204
|
+
A = make_interp_spline(ire[:,0], Ai)
|
205
|
+
A0i = [femagtools.utils.fft(pos, psire[:, k, 0])['a0']
|
206
|
+
for k in range(np.shape(psire)[1])]
|
207
|
+
A0 = make_interp_spline(ire[:,0], A0i)
|
208
|
+
Bi = [femagtools.utils.fft(pos, psire[:, k, 1])['a']
|
209
|
+
for k in range(np.shape(psire)[1]-1, -1, -1)]
|
210
|
+
B = make_interp_spline(ire[::-1,1], Bi)
|
211
|
+
B0i = [femagtools.utils.fft(pos, psire[:, k, 1])['a0']
|
212
|
+
for k in range(np.shape(psire)[1]-1, -1, -1)]
|
213
|
+
B0 = make_interp_spline(ire[::-1,1], B0i)
|
214
|
+
alfa0_ai = [femagtools.utils.fft(pos, psire[:, k, 0])['alfa0']
|
215
|
+
for k in range(np.shape(psire)[1])]
|
216
|
+
alfa0_a = make_interp_spline(ire[:,0], alfa0_ai)
|
217
|
+
alfa0_bi = [femagtools.utils.fft(pos, psire[:, k, 1])['alfa0']
|
218
|
+
for k in range(np.shape(psire)[1]-1, -1, -1)]
|
219
|
+
alfa0_b = make_interp_spline(ire[::-1,1], alfa0_bi)
|
220
|
+
|
221
|
+
Tqi = [femagtools.utils.fft(pos, torq[:, k])['a']
|
222
|
+
for k in range(np.shape(torq)[1])]
|
223
|
+
Tq = make_interp_spline(ire[:, 0], Tqi)
|
224
|
+
Tq0i = [femagtools.utils.fft(pos, torq[:, k])['a0']
|
225
|
+
for k in range(np.shape(torq)[1])]
|
226
|
+
Tq0 = make_interp_spline(ire[:, 0], Tq0i)
|
227
|
+
alfa0_t = [femagtools.utils.fft(pos, torq[:, k])['alfa0']
|
228
|
+
for k in range(np.shape(torq)[1])]
|
229
|
+
|
230
|
+
T0 = np.mean([femagtools.utils.fft(pos, psire[:, k, 0])['T0']
|
231
|
+
for k in range(np.shape(psire)[1])])
|
232
|
+
pp = 360/T0
|
233
|
+
|
234
|
+
def torque(phi, i):
|
235
|
+
try:
|
236
|
+
alfa0 = np.ones(len(i))*np.mean(alfa0_t)
|
237
|
+
alfa0[i < 0] = alfa0_t[0]
|
238
|
+
alfa0[i > 0] = alfa0_t[-1]
|
239
|
+
except TypeError:
|
240
|
+
alfa0 = np.mean(alfa0_t)
|
241
|
+
if i < 0:
|
242
|
+
alfa0 = alfa0_t[0]
|
243
|
+
if i > 0:
|
244
|
+
alfa0 = alfa0_t[-1]
|
245
|
+
return Tq(i)*np.cos(pp*phi+alfa0) + Tq0(i)
|
246
|
+
|
247
|
+
def psia(phi, i):
|
248
|
+
return A(i)*np.cos(pp*phi+alfa0_a(i))+A0(i)
|
249
|
+
|
250
|
+
def psib(phi, i):
|
251
|
+
return B(i)*np.cos(pp*phi+alfa0_b(i))+B0(i)
|
252
|
+
|
253
|
+
def dpsiadi(phi,i):
|
254
|
+
return A(i, nu=1)*np.cos(pp*phi+alfa0_a(i))+A0(i,nu=1)
|
255
|
+
def dpsiadphi(phi,i):
|
256
|
+
return -pp*A(i)*np.sin(pp*phi+alfa0_a(i))
|
257
|
+
def dpsibdi(phi,i):
|
258
|
+
return B(i, nu=1)*np.cos(pp*phi+alfa0_b(i))+B0(i,nu=1)
|
259
|
+
def dpsibdphi(phi,i):
|
260
|
+
return -pp*B(i)*np.sin(pp*phi+alfa0_b(i))
|
261
|
+
|
262
|
+
speed = simulation['speed']
|
263
|
+
r1 = simulation['r1']
|
264
|
+
l1s = simulation.get('l1s',0)
|
265
|
+
wm = 2*np.pi*speed
|
266
|
+
w1 = pp*wm
|
267
|
+
|
268
|
+
def didt(t, y):
|
269
|
+
return [((2*r1*y[0] + wm*(
|
270
|
+
dpsiadphi(y[1],y[0]) - dpsibdphi(y[1],-y[0])))/
|
271
|
+
(-dpsiadi(y[1],y[0]) - dpsibdi(y[1],-y[0]) -2*l1s)),
|
272
|
+
wm]
|
273
|
+
tmin = simulation.get('tstart', 0)
|
274
|
+
tmax = simulation.get('simultime', 0.1)
|
275
|
+
nsamples = simulation.get('nsamples', 400)
|
276
|
+
t = np.linspace(tmin, tmax, nsamples)
|
277
|
+
|
278
|
+
def func(x):
|
279
|
+
return B(0)*np.sin(pp*x+alfa0_b(0)) - A(0)*np.sin(pp*x+alfa0_a(0))
|
280
|
+
phi0 = so.fsolve(func, [0])[0]
|
281
|
+
|
282
|
+
Y0 = [0, phi0]
|
283
|
+
sol = ig.solve_ivp(didt, (t[0], t[-1]), Y0, dense_output=True)
|
284
|
+
ia = sol.sol(t).T[:, 0]
|
285
|
+
pv = find_peaks_and_valleys(t, ia)
|
286
|
+
iap = pv['tp'], pv['ip']
|
287
|
+
iav = pv['tv'], pv['iv']
|
288
|
+
iac = pv['tpeaks'][-1], pv['peaks'][-1]
|
289
|
+
|
290
|
+
logger.info("Ia %.1f %.1f %.1f (phi0 %.4f)",
|
291
|
+
iap[1], iav[1], iac[1], phi0)
|
292
|
+
|
293
|
+
def func(x):
|
294
|
+
y = torque(wm*t+phi0+x, ia)
|
295
|
+
pv = find_peaks_and_valleys(t, y)
|
296
|
+
return pv['peaks'][-1] + pv['valleys'][-1]
|
297
|
+
|
298
|
+
dphi = so.fsolve(func, [0])[0]
|
299
|
+
torque = torque(wm*t+phi0+dphi, ia)
|
300
|
+
pv = find_peaks_and_valleys(t, torque)
|
301
|
+
tp = pv['tp'], pv['ip']
|
302
|
+
tv = pv['tv'], pv['iv']
|
303
|
+
tc = pv['tpeaks'][-1], pv['peaks'][-1]
|
304
|
+
logger.info("Torque %.1f %.1f %.1f (dphi %.4f)",
|
305
|
+
tp[1], tv[1], tc[1], dphi)
|
306
|
+
|
307
|
+
scData = {
|
308
|
+
'ia': ia.tolist(),
|
309
|
+
'ib': (-ia).tolist(),
|
310
|
+
'ic': np.zeros(ia.shape).tolist(),
|
311
|
+
'time': t.tolist(),
|
312
|
+
'torque': torque.tolist(),
|
313
|
+
'speed': speed,
|
314
|
+
'ikd': iac[1],
|
315
|
+
'tkd': tc[1],
|
316
|
+
'iks': iap[1] if iap[1] > abs(iav[1]) else iav[1],
|
317
|
+
'tks': tp[1] if tp[1] > abs(tv[1]) else tv[1]
|
318
|
+
}
|
319
|
+
scData['peakWindingCurrents'] = [scData['iks'],
|
320
|
+
-scData['iks'], 0]
|
321
|
+
if simulation.get('sim_demagn', 0):
|
322
|
+
scData['demag'] = demag(femag, machine, simulation, engine)
|
323
|
+
return scData
|
324
|
+
|
325
|
+
def dm_result_func(task):
|
326
|
+
basedir = pathlib.Path(task.directory)
|
327
|
+
i1rr = []
|
328
|
+
for f in sorted(basedir.glob('psi-torq-rem-rot-*.dat')):
|
329
|
+
ptr = np.loadtxt(f)
|
330
|
+
i1rr.append((np.max(ptr.T[1:4]), np.min(ptr.T[-1])))
|
331
|
+
return i1rr
|
332
|
+
|
333
|
+
def demag(femag, machine, simulation, engine=0):
|
334
|
+
"""demag simulation using psi-torq-rem-rot"""
|
335
|
+
logger.info("Demagnetization processing")
|
336
|
+
i1max = simulation['i1max']
|
337
|
+
i1min = simulation.get('i1min', i1max/4)
|
338
|
+
num_steps = 7
|
339
|
+
b = (i1min-i1max)/np.log(i1min/i1max)
|
340
|
+
a = i1max/b
|
341
|
+
i1tab = [b*(a+np.log(x))
|
342
|
+
for x in np.linspace(i1min/i1max, 1,
|
343
|
+
num_steps)]
|
344
|
+
|
345
|
+
if simulation.get('sc_type', 3) == 3:
|
346
|
+
curvec = [[-a/2, a, -a/2] for a in i1tab]
|
347
|
+
else:
|
348
|
+
curvec = [[a, -a, 0] for a in i1tab]
|
349
|
+
simulation.update({
|
350
|
+
'calculationMode': 'psi-torq-rem-rot',
|
351
|
+
'curvec': curvec})
|
352
|
+
if engine:
|
353
|
+
parstudy = _parstudy_list(femag, dm_result_func)
|
354
|
+
parvardef = {
|
355
|
+
"decision_vars": [
|
356
|
+
{"values": curvec, "name": "curvec"}]
|
357
|
+
}
|
358
|
+
results = parstudy(parvardef, machine, simulation, engine)
|
359
|
+
i1rr = np.vstack(
|
360
|
+
((0, 1),
|
361
|
+
np.array(results['f']).reshape((-1, 2))))
|
362
|
+
else:
|
363
|
+
class Task:
|
364
|
+
def __init__(self, workdir):
|
365
|
+
self.directory = workdir
|
366
|
+
_ = femag(machine, simulation)
|
367
|
+
i1rr = np.vstack(
|
368
|
+
[(0, 1), dm_result_func(Task(femag.workdir))])
|
369
|
+
i1, rr = np.array(i1rr).T
|
370
|
+
dmag = {'Hk': simulation['Hk'],
|
371
|
+
'Tmag': simulation['Tmag'],
|
372
|
+
'i1': i1.tolist(),
|
373
|
+
'rr': rr.tolist()}
|
374
|
+
# critical current
|
375
|
+
if np.min(rr) < 0.99:
|
376
|
+
k = np.where(rr < 0.99)[0][0]
|
377
|
+
dmag['i1c'] = i1[k]
|
378
|
+
return dmag
|
@@ -0,0 +1,127 @@
|
|
1
|
+
-- calculate flux linkages, torque, and rel remanence (magstatic mode)
|
2
|
+
--
|
3
|
+
-- model:
|
4
|
+
-- Hk (kA/m) knee point field strength
|
5
|
+
-- curvec (A) phase current amplitude samples (list of [ía, ib, ic])
|
6
|
+
-- num_par_wdgs (number of parallel winding groups, default 1)
|
7
|
+
-- fc_radius (m) radius of airgap center
|
8
|
+
--
|
9
|
+
-- creates file psi-torq-rem-rot.dat in current directory with columns:
|
10
|
+
-- displ curr1 curr2 curr3 psi1 psi2 psi3 torq rrem
|
11
|
+
|
12
|
+
function gcd(a, b)
|
13
|
+
return b==0 and a or gcd(b,a%b)
|
14
|
+
end
|
15
|
+
|
16
|
+
function dmg(ek, Br, alfam, murm, Bd, Bq, Hd, Hq, alfahm, Hk)
|
17
|
+
if Hd < Hk then
|
18
|
+
muem = murm*4*math.pi*1e-7
|
19
|
+
Brn = Bd - Hk*1e3*muem
|
20
|
+
if Br < 1e-5 then
|
21
|
+
Brn = 1e-5
|
22
|
+
end
|
23
|
+
return Brn, alfam, 1
|
24
|
+
end
|
25
|
+
return Br, alfam, 1
|
26
|
+
end
|
27
|
+
|
28
|
+
function calc_flux_torq_rem(phi, curvec)
|
29
|
+
for k=1,3 do
|
30
|
+
def_curr_wdg(k, curvec[k]/a, 0)
|
31
|
+
end
|
32
|
+
|
33
|
+
dRR = 0
|
34
|
+
RR = 0
|
35
|
+
maxit=m.num_nonl_it
|
36
|
+
maxcop=m.error_perm -- err_perm in %
|
37
|
+
permode='restore'
|
38
|
+
repeat
|
39
|
+
calc_field_single({
|
40
|
+
maxit=maxit, maxcop=maxcop, -- err_perm in %
|
41
|
+
permode=permode})
|
42
|
+
if(maxit > 1) then
|
43
|
+
maxit = 1
|
44
|
+
permode='actual'
|
45
|
+
maxcop = 0.05
|
46
|
+
end
|
47
|
+
stat, RR, dRR = calc_demag(5, Hk)
|
48
|
+
--printf("%g %g", RR, dRR)
|
49
|
+
until math.abs(dRR) < 1e-5
|
50
|
+
|
51
|
+
psi = {}
|
52
|
+
for k=1,3 do
|
53
|
+
psir, psii = flux_winding_wk(k)
|
54
|
+
psi[k] = {ksym*psir/a*m.arm_length, ksym*psii/a*m.arm_length}
|
55
|
+
end
|
56
|
+
|
57
|
+
fr, ft, tq, fx, fy = force_torque()
|
58
|
+
|
59
|
+
return psi, tq, RR
|
60
|
+
end
|
61
|
+
%if model.get('fc_radius', 0):
|
62
|
+
if m.fc_radius == nil then
|
63
|
+
m.fc_radius = ${model['fc_radius']*1e3}
|
64
|
+
end
|
65
|
+
%endif
|
66
|
+
%if type(model.get('curvec')[0]) is list:
|
67
|
+
curvec = {${','.join(['{'+','.join([str(x) for x in y])+'}' for y in model['curvec']])}} -- A
|
68
|
+
%else:
|
69
|
+
curvec = {{${','.join([str(x) for x in model['curvec']])}}} -- A
|
70
|
+
%endif
|
71
|
+
a=${model.get('num_par_wdgs', 1)} -- parallel branches
|
72
|
+
|
73
|
+
ksym = m.num_poles/m.npols_gen
|
74
|
+
|
75
|
+
if num_agnodes ~= nil then
|
76
|
+
dphi = 360/num_agnodes -- ndst[2] -- deg
|
77
|
+
else
|
78
|
+
post_models("nodedistance", "ndst" )
|
79
|
+
dphi = ndst[2] -- deg
|
80
|
+
end
|
81
|
+
nodes = math.floor(360/m.num_poles/dphi+0.5)
|
82
|
+
printf("Nodes in airgap total %g, Nodes per pole: %d", 360/dphi, nodes)
|
83
|
+
-- find a valid number of steps for a rotation:
|
84
|
+
nrot = nodes
|
85
|
+
while( nrot%2) == 0 do
|
86
|
+
nrot = nrot//2
|
87
|
+
end
|
88
|
+
-- HcB = Brem*tempcoefbr*(magn_temp-20)+1)/muerel/12.565e-7
|
89
|
+
-- Hcmin = HcJ*tempcoefhc*(magn_temp-20.0)+1)/HcB*1e2 -- limit of demagnetization in
|
90
|
+
Hk = ${model.get('Hk', -999)}
|
91
|
+
Q1 = get_dev_data("num_slots")
|
92
|
+
p = m.num_poles//2
|
93
|
+
dphi = 360//gcd(Q1, p)/nrot
|
94
|
+
print(string.format(" rotation steps: %d current steps: %d\n", nrot, #curvec))
|
95
|
+
|
96
|
+
phi = 0
|
97
|
+
-- initialize rotate
|
98
|
+
rotate({
|
99
|
+
airgap = m.fc_radius, -- air gap radius
|
100
|
+
region = "inside", -- region to rotate
|
101
|
+
mode = "save" -- save initial model state
|
102
|
+
})
|
103
|
+
|
104
|
+
for i=1, #curvec do
|
105
|
+
print(string.format(" current: %d/%d %g, %g, %g\n",
|
106
|
+
i, #curvec, curvec[i][1], curvec[i][2], curvec[i][3]))
|
107
|
+
|
108
|
+
file_psi = io.open("psi-torq-rem-rot-"..i..".dat","w")
|
109
|
+
for n=1,nrot+1 do
|
110
|
+
psi, tq, rr = calc_flux_torq_rem(phi, curvec[i])
|
111
|
+
file_psi:write(string.format("%g ", phi))
|
112
|
+
for k=1, 3 do
|
113
|
+
file_psi:write(string.format("%g ", curvec[i][k]))
|
114
|
+
end
|
115
|
+
for k=1, 3 do
|
116
|
+
file_psi:write(string.format("%g ", psi[k][1]))
|
117
|
+
end
|
118
|
+
file_psi:write(string.format("%g ", tq))
|
119
|
+
file_psi:write(string.format("%g ", rr))
|
120
|
+
file_psi:write("\n")
|
121
|
+
|
122
|
+
phi = n*dphi
|
123
|
+
rotate({angle=phi, mode="absolute"})
|
124
|
+
end
|
125
|
+
file_psi:close()
|
126
|
+
rotate({mode = "reset"}) -- restore the initial state (discard any changes)
|
127
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
-- calculate flux linkages and torque (magstatic mode)
|
2
|
+
--
|
3
|
+
-- model:
|
4
|
+
-- curvec (A) current samples (amplitudes)
|
5
|
+
-- num_par_wdgs (number of parallel winding groups, default 1)
|
6
|
+
--
|
7
|
+
-- creates file psi-torq-rot.dat in current directory with columns:
|
8
|
+
-- displ curr1 curr2 curr3 psi1 psi2 psi3 torq
|
9
|
+
--
|
10
|
+
function gcd(a, b)
|
11
|
+
return b==0 and a or gcd(b,a%b)
|
12
|
+
end
|
13
|
+
|
14
|
+
function calc_flux_torq(phi, curvec)
|
15
|
+
psivec={}
|
16
|
+
tqvec={}
|
17
|
+
for i=1, #curvec do
|
18
|
+
for k=1,3 do
|
19
|
+
def_curr_wdg(k, curvec[i][k], 0)
|
20
|
+
end
|
21
|
+
|
22
|
+
calc_field_single({
|
23
|
+
maxit=m.num_nonl_it, maxcop=m.error_perm, -- err_perm in %
|
24
|
+
permode='restore'})
|
25
|
+
psi = {}
|
26
|
+
for k=1,3 do
|
27
|
+
psir, psii = flux_winding_wk(k)
|
28
|
+
psi[k] = {ksym*psir/a*m.arm_length, ksym*psii/a*m.arm_length}
|
29
|
+
end
|
30
|
+
|
31
|
+
fr, ft, tq, fx, fy = force_torque()
|
32
|
+
tqvec[i] = tq
|
33
|
+
psivec[i] = psi
|
34
|
+
end
|
35
|
+
return psivec, tqvec
|
36
|
+
end
|
37
|
+
|
38
|
+
%if type(model['curvec']) is list:
|
39
|
+
curamp = {${','.join([str(x) for x in model['curvec']])}} -- A
|
40
|
+
% else:
|
41
|
+
curamp = {${model['curvec']}} -- A
|
42
|
+
% endif
|
43
|
+
a=${model.get('num_par_wdgs', 1)} -- parallel branches
|
44
|
+
|
45
|
+
curvec = {}
|
46
|
+
for i=1, #curamp do
|
47
|
+
amp = curamp[i]/a
|
48
|
+
curvec[i] = {amp, -amp, 0}
|
49
|
+
end
|
50
|
+
|
51
|
+
ksym = m.num_poles/m.npols_gen
|
52
|
+
|
53
|
+
if num_agnodes ~= nil then
|
54
|
+
dphi = 360/num_agnodes -- ndst[2] -- deg
|
55
|
+
else
|
56
|
+
post_models("nodedistance", "ndst" )
|
57
|
+
dphi = ndst[2] -- deg
|
58
|
+
end
|
59
|
+
nodes = math.floor(360/m.num_poles/dphi+0.5)
|
60
|
+
printf("Nodes in airgap total %g, Nodes per pole: %d", 360/dphi, nodes)
|
61
|
+
-- find a valid number of steps for a rotation:
|
62
|
+
nrot = nodes
|
63
|
+
while( nrot%2) == 0 do
|
64
|
+
nrot = nrot//2
|
65
|
+
end
|
66
|
+
Q1 = get_dev_data("num_slots")
|
67
|
+
p = m.num_poles//2
|
68
|
+
dphi = 360//gcd(Q1, p)/nrot
|
69
|
+
print(string.format(" rotation steps: %d current steps: %d\n", nrot, #curvec))
|
70
|
+
|
71
|
+
phi = 0
|
72
|
+
-- initialize rotate
|
73
|
+
rotate({
|
74
|
+
airgap = m.fc_radius, -- air gap radius
|
75
|
+
region = "inside", -- region to rotate
|
76
|
+
mode = "save" -- save initial model state
|
77
|
+
})
|
78
|
+
|
79
|
+
file_psi = io.open("psi-torq-rot.dat","w")
|
80
|
+
for n=1,nrot+1 do
|
81
|
+
psi, tq = calc_flux_torq(phi, curvec)
|
82
|
+
for i=1, #curvec do
|
83
|
+
file_psi:write(string.format("%g ", phi))
|
84
|
+
for k=1, 3 do
|
85
|
+
file_psi:write(string.format("%g ", a*curvec[i][k]))
|
86
|
+
end
|
87
|
+
for k=1, 3 do
|
88
|
+
file_psi:write(string.format("%g ", psi[i][k][1]))
|
89
|
+
end
|
90
|
+
file_psi:write(string.format("%g ", tq[i]))
|
91
|
+
file_psi:write("\n")
|
92
|
+
end
|
93
|
+
|
94
|
+
phi = n*dphi
|
95
|
+
rotate({angle=phi, mode="absolute"})
|
96
|
+
end
|
97
|
+
rotate({mode = "reset"}) -- restore the initial state (discard any changes)
|
98
|
+
file_psi:close()
|
femagtools/tks.py
CHANGED
femagtools/windings.py
CHANGED
@@ -46,6 +46,71 @@ def q1q2yk(Q, p, m, l=1):
|
|
46
46
|
Yk = (n*qbb + 1)//pb
|
47
47
|
return q1, q2, Yk, Qb
|
48
48
|
|
49
|
+
def end_wdg_length_round_wires(layers, Rp, Rb, r_wire, h, coil_span, Q, bore_diam, slot_h1, slot_height):
|
50
|
+
'''return length of a single winding head for 1 coil turn.
|
51
|
+
Multiply by 2 to get length for both winding heads'''
|
52
|
+
if layers == 2:
|
53
|
+
R_inner_lyr = bore_diam/2 + slot_h1 + slot_height/4
|
54
|
+
R_outer_lyr = bore_diam/2 + slot_h1 + 3*slot_height/4
|
55
|
+
elif layers == 1:
|
56
|
+
R_inner_lyr = bore_diam/2 + slot_h1 + slot_height/2
|
57
|
+
R_outer_lyr = bore_diam/2 + slot_h1 + slot_height/2
|
58
|
+
else:
|
59
|
+
raise ValueError("Round wire windings can only have 1 or 2 layers")
|
60
|
+
|
61
|
+
if Rb < 2*r_wire:
|
62
|
+
Rb = 2*r_wire
|
63
|
+
if Rp < R_outer_lyr + 2*(Rb + r_wire):
|
64
|
+
Rp = R_outer_lyr + 2*(Rb + r_wire) + 1e-5
|
65
|
+
if h < 2*(Rb + r_wire):
|
66
|
+
h = 2*(Rb + r_wire) + 0.002
|
67
|
+
|
68
|
+
l = np.pi*coil_span/Q * (Rp + R_inner_lyr)
|
69
|
+
z = Rp - R_outer_lyr - 2*(Rb + r_wire)
|
70
|
+
l_ew = 2*h + l + z + (Rb + r_wire)*(np.pi - 2)
|
71
|
+
return l_ew, h, Rp, Rb
|
72
|
+
|
73
|
+
def end_wdg_hairpin_check(alpha, h, dmin, l_h, wire_w, tooth_wmin):
|
74
|
+
alpha = alpha*np.pi/180 # ensure alpha is in radians
|
75
|
+
if alpha == 0 and h == 0: # end wdg parameters not set
|
76
|
+
alpha = np.arcsin((dmin + wire_w)/(tooth_wmin + wire_w))
|
77
|
+
h = np.tan(alpha)*l_h/2
|
78
|
+
elif alpha == 0 and h > 0: # imposed end wdg height
|
79
|
+
alpha = np.arctan(h/l_h)
|
80
|
+
dmin = np.sin(alpha)*(tooth_wmin + wire_w) - wire_w
|
81
|
+
if dmin < 0.0015: # imposed end wdg height is not feasible - calculate min end wdg parameters
|
82
|
+
dmin = 0.0015
|
83
|
+
alpha = np.arcsin((dmin + wire_w)/(tooth_wmin + wire_w))
|
84
|
+
h = np.tan(alpha)*l_h/2
|
85
|
+
elif alpha > 0: # imposed end wdg angle
|
86
|
+
dmin = np.sin(alpha)*(tooth_wmin + wire_w) - wire_w
|
87
|
+
if dmin < 0.0015: # imposed end wdg angle is not feasible - calculate min end wdg parameters
|
88
|
+
dmin = 0.0015
|
89
|
+
alpha = np.arcsin((dmin + wire_w)/(tooth_wmin + wire_w))
|
90
|
+
h = np.tan(alpha)*l_h/2
|
91
|
+
return h, alpha, dmin
|
92
|
+
|
93
|
+
def end_wdg_length_hairpins(wire_h, wire_w, wire_th, wire_gap,
|
94
|
+
layers, coil_pitch, Q, bore_diam, slot_h, slot_w,
|
95
|
+
h_bent=0, h_welded=0, h_conn=0.005, alpha_bent=0, alpha_welded=0, dmin=0.0015): # needs to be validated
|
96
|
+
'''return end wdg length of single pin for bent and welded side, average end wdg length,
|
97
|
+
bent and welded side end wdg heights, bending angles and min distances between pins'''
|
98
|
+
|
99
|
+
R_avg = bore_diam/2 + wire_th + layers/2*(wire_h + wire_gap) + wire_h/2
|
100
|
+
l_h = R_avg/2*coil_pitch/Q*np.pi*(bore_diam + wire_th + slot_h)
|
101
|
+
tooth_wmin = (bore_diam + 2*wire_th)*np.pi/Q - slot_w
|
102
|
+
|
103
|
+
h_bent, alpha_bent, dmin = end_wdg_hairpin_check(alpha_bent, h_bent, dmin, l_h, wire_w, tooth_wmin)
|
104
|
+
|
105
|
+
h_welded = h_welded - h_conn if h_welded - h_conn > 0 else 0
|
106
|
+
h_welded, alpha_welded, dmin = end_wdg_hairpin_check(alpha_welded, h_welded, dmin, l_h, wire_w, tooth_wmin)
|
107
|
+
|
108
|
+
l_bent = 2*(0.002 + wire_w/2*alpha_bent + wire_w*(np.pi/2 - alpha_bent) + np.sqrt((l_h/2)**2 + h_bent**2))
|
109
|
+
l_welded = 2*(0.002 + wire_w/2*alpha_bent + wire_w*(np.pi/2 - alpha_bent) + np.sqrt((l_h/2)**2 + h_welded**2)) + h_conn
|
110
|
+
l_ew = (l_bent + l_welded)/2
|
111
|
+
h_welded = h_welded + h_conn
|
112
|
+
return l_bent, l_welded, l_ew, h_bent, h_welded, alpha_bent*180/np.pi, alpha_welded*180/np.pi, dmin
|
113
|
+
|
49
114
|
|
50
115
|
class Winding(object):
|
51
116
|
# TODO: raise ValueError "Unbalanced winding" if Q % (m * gcd(Q, p)) != 0
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: femagtools
|
3
|
-
Version: 1.8.
|
3
|
+
Version: 1.8.8
|
4
4
|
Summary: Python API for FEMAG
|
5
5
|
Author-email: Ronald Tanner <tar@semafor.ch>, Dapu Zhang <dzhang@gtisoft.com>, Beat Holm <hob@semafor.ch>, Günther Amsler <amg@semafor.ch>, Nicolas Mauchle <mau@semafor.ch>
|
6
6
|
License: Copyright (c) 2016-2023, Semafor Informatik & Energie AG, Basel
|