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.
@@ -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
@@ -227,7 +227,7 @@ if __name__ == "__main__":
227
227
  # (tks.losses['cw'],
228
228
  # tks.losses['cw_freq'],
229
229
  # tks.losses['b_coeff']),
230
- title=filename, log=False)
230
+ title=filename, log=True)
231
231
  pl.show()
232
232
 
233
233
  mcv = tks.getValues()
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.6
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