femagtools 1.8.2__py3-none-any.whl → 1.8.3__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- femagtools/__init__.py +1 -1
- femagtools/dxfsl/area.py +65 -0
- femagtools/dxfsl/conv.py +5 -0
- femagtools/dxfsl/converter.py +34 -1
- femagtools/dxfsl/functions.py +14 -6
- femagtools/dxfsl/geom.py +12 -12
- femagtools/dxfsl/journal.py +1 -1
- femagtools/dxfsl/symmetry.py +28 -8
- femagtools/femag.py +64 -61
- femagtools/fsl.py +5 -2
- femagtools/isa7.py +3 -2
- femagtools/machine/afpm.py +43 -23
- femagtools/machine/effloss.py +29 -18
- femagtools/machine/sizing.py +4 -3
- femagtools/machine/sm.py +34 -36
- femagtools/mcv.py +56 -26
- femagtools/multiproc.py +79 -80
- femagtools/parstudy.py +10 -4
- femagtools/semi_fea.py +108 -0
- femagtools/templates/basic_modpar.mako +0 -3
- femagtools/templates/fe-contr.mako +18 -18
- femagtools/templates/ld_lq_fast.mako +3 -0
- femagtools/templates/mult_cal_fast.mako +3 -0
- femagtools/templates/pm_sym_f_cur.mako +4 -1
- femagtools/templates/pm_sym_fast.mako +3 -0
- femagtools/templates/pm_sym_loss.mako +3 -0
- femagtools/templates/psd_psq_fast.mako +3 -0
- femagtools/templates/torq_calc.mako +3 -0
- femagtools/tks.py +23 -20
- femagtools/zmq.py +213 -0
- {femagtools-1.8.2.dist-info → femagtools-1.8.3.dist-info}/METADATA +3 -3
- {femagtools-1.8.2.dist-info → femagtools-1.8.3.dist-info}/RECORD +40 -38
- {femagtools-1.8.2.dist-info → femagtools-1.8.3.dist-info}/WHEEL +1 -1
- tests/test_afpm.py +15 -6
- tests/test_femag.py +1 -1
- tests/test_fsl.py +4 -4
- tests/test_mcv.py +20 -14
- {femagtools-1.8.2.dist-info → femagtools-1.8.3.dist-info}/LICENSE +0 -0
- {femagtools-1.8.2.dist-info → femagtools-1.8.3.dist-info}/entry_points.txt +0 -0
- {femagtools-1.8.2.dist-info → femagtools-1.8.3.dist-info}/top_level.txt +0 -0
femagtools/machine/afpm.py
CHANGED
@@ -33,16 +33,21 @@ def num_agnodes(Q, p, pw, ag):
|
|
33
33
|
pw: (float) pole width
|
34
34
|
ag: (float) airgap height
|
35
35
|
"""
|
36
|
-
num_nodes = np.arange(
|
36
|
+
num_nodes = np.arange(6, 120, 6)
|
37
37
|
i = np.argmin(np.abs(pw/num_nodes - ag/2))
|
38
|
-
|
38
|
+
nag = num_nodes[i-1]
|
39
|
+
if p*nag % Q:
|
39
40
|
lcm = np.lcm(Q, 2*p)//p
|
40
|
-
nmin, nmax =
|
41
|
+
nmin, nmax = 1, num_nodes[-1]//lcm
|
41
42
|
num_nodes = np.array(
|
42
43
|
[i*lcm for i in range(nmin, nmax) if i*lcm % 6 == 0])
|
43
44
|
i = np.argmin(np.abs(pw/num_nodes - ag/2))
|
44
|
-
|
45
|
-
|
45
|
+
if i > 0:
|
46
|
+
nag = num_nodes[i-1]
|
47
|
+
else:
|
48
|
+
nag = num_nodes[i]
|
49
|
+
# TODO nodedist 0.5, 2, 4, 6
|
50
|
+
return nag
|
46
51
|
|
47
52
|
def _integrate(radius, pos, val):
|
48
53
|
interp = RegularGridInterpolator((radius, pos), val)
|
@@ -162,7 +167,6 @@ def parident(workdir, engine, temp, machine,
|
|
162
167
|
for pw in pole_width:
|
163
168
|
machine['num_agnodes'] = num_agnodes(Q1, p//2, pw,
|
164
169
|
machine['airgap'])
|
165
|
-
|
166
170
|
nlparvardef = {
|
167
171
|
"decision_vars": [
|
168
172
|
{"values": pole_width,
|
@@ -182,13 +186,13 @@ def parident(workdir, engine, temp, machine,
|
|
182
186
|
parvardef = {
|
183
187
|
"decision_vars": [
|
184
188
|
# {"values": sorted(2*temp), "name": "magn_temp"},
|
185
|
-
{"steps": num_beta_steps, "bounds": [beta_min, 0],
|
189
|
+
{"steps": num_beta_steps, "bounds": [beta_min, 0],
|
190
|
+
"name": "angl_i_up"},
|
186
191
|
{"steps": num_cur_steps,
|
187
192
|
"bounds": [i1_max/num_cur_steps, i1_max], "name": "current"}
|
188
193
|
]
|
189
194
|
}
|
190
195
|
|
191
|
-
|
192
196
|
ldq = []
|
193
197
|
for magtemp in temp:
|
194
198
|
nlcalc = dict(
|
@@ -381,7 +385,7 @@ def process(lfe, pole_width, machine, bch):
|
|
381
385
|
num_slots = machine['stator']['num_slots']
|
382
386
|
mmod = model.MachineModel(machine)
|
383
387
|
slots_gen = mmod.stator['num_slots_gen']
|
384
|
-
scale_factor =_get_scale_factor(model_type, num_slots, slots_gen)
|
388
|
+
scale_factor = _get_scale_factor(model_type, num_slots, slots_gen)
|
385
389
|
endpos = [2*pw*1e3 for pw in pole_width]
|
386
390
|
displ = [[d for d in r['linearForce'][0]['displ']
|
387
391
|
if d < e*(1+1/len(r['linearForce'][0]['displ']))]
|
@@ -392,11 +396,14 @@ def process(lfe, pole_width, machine, bch):
|
|
392
396
|
currents = [bch[0]['flux'][k][0]['current_k'][:n]
|
393
397
|
for k in bch[0]['flux']]
|
394
398
|
if len(pole_width) > 1:
|
399
|
+
# check homogenity:
|
400
|
+
if np.diff([len(d) for d in displ]).any():
|
401
|
+
raise ValueError(
|
402
|
+
f"inhomogenous number of steps: {[len(d) for d in displ]}")
|
403
|
+
lfx = [r['linearForce'][0]['force_x'] for r in bch]
|
395
404
|
torque = _integrate(radius, rotpos[0], np.array(
|
396
405
|
[r*scale_factor*np.array(fx[:-1])/l
|
397
|
-
for l, r, fx in zip(lfe, radius,
|
398
|
-
[r['linearForce'][0]['force_x']
|
399
|
-
for r in bch])]))
|
406
|
+
for l, r, fx in zip(lfe, radius, lfx)]))
|
400
407
|
|
401
408
|
voltage = {k: [scale_factor * np.array(ux[:-1])/l
|
402
409
|
for l, ux in zip(lfe, [r['flux'][k][0]['voltage_dpsi']
|
@@ -404,6 +411,13 @@ def process(lfe, pole_width, machine, bch):
|
|
404
411
|
for k in bch[0]['flux']}
|
405
412
|
emf = [_integrate(radius, rotpos[0], np.array(voltage[k]))
|
406
413
|
for k in voltage]
|
414
|
+
|
415
|
+
fluxxy = {k: [scale_factor * np.array(flx[:-1])/l
|
416
|
+
for l, flx in zip(lfe, [r['flux'][k][0]['flux_k']
|
417
|
+
for r in bch])]
|
418
|
+
for k in bch[0]['flux']}
|
419
|
+
flux = [_integrate(radius, rotpos[0], np.array(fluxxy[k]))
|
420
|
+
for k in fluxxy]
|
407
421
|
else:
|
408
422
|
r = radius[0]
|
409
423
|
torque = [r*scale_factor*fx
|
@@ -412,6 +426,7 @@ def process(lfe, pole_width, machine, bch):
|
|
412
426
|
for ux in bch[0]['flux'][k][0]['voltage_dpsi'][:-1]]
|
413
427
|
for k in bch[0]['flux']}
|
414
428
|
emf = [voltage[k][:n] for k in voltage]
|
429
|
+
flux = [fluxxy[k][:n] for k in fluxxy]
|
415
430
|
|
416
431
|
pos = (rotpos[0]/np.pi*180)
|
417
432
|
emffft = utils.fft(pos, emf[0])
|
@@ -474,6 +489,7 @@ def process(lfe, pole_width, machine, bch):
|
|
474
489
|
'weights': weights.tolist(),
|
475
490
|
'pos': pos.tolist(), 'r1': r1,
|
476
491
|
'torque': torque,
|
492
|
+
'flux_k': flux,
|
477
493
|
'emf': emf,
|
478
494
|
'emf_amp': emffft['a'], 'emf_angle': emffft['alfa0'],
|
479
495
|
'freq': freq,
|
@@ -591,10 +607,10 @@ class AFPM:
|
|
591
607
|
condMat: conductor material
|
592
608
|
"""
|
593
609
|
def __init__(self, workdir, magnetizingCurves='.', magnetMat='',
|
594
|
-
condMat=''):
|
610
|
+
condMat='', cmd=None):
|
595
611
|
self.parstudy = parstudy.List(
|
596
612
|
workdir, condMat=condMat, magnets=magnetMat,
|
597
|
-
magnetizingCurves=magnetizingCurves)
|
613
|
+
magnetizingCurves=magnetizingCurves, cmd=cmd)
|
598
614
|
|
599
615
|
def __call__(self, engine, machine, simulation, num_slices):
|
600
616
|
""" run FE simulation
|
@@ -648,21 +664,24 @@ class AFPM:
|
|
648
664
|
}
|
649
665
|
machine['pole_width'] = np.pi * machine['inner_diam']/machine['poles']
|
650
666
|
machine['lfe'] = machine['outer_diam'] - machine['inner_diam']
|
651
|
-
|
667
|
+
simulation['skew_displ'] = (simulation.get('skew_angle', 0)/180 * np.pi
|
668
|
+
* machine['inner_diam'])
|
652
669
|
nlresults = {}
|
653
|
-
if (simulation['calculationMode'] != 'cogg_calc'
|
654
|
-
'poc' not in simulation):
|
670
|
+
if (simulation['calculationMode'] != 'cogg_calc'
|
671
|
+
and 'poc' not in simulation):
|
655
672
|
nlcalc = dict(
|
656
673
|
calculationMode="cogg_calc",
|
657
674
|
magn_temp=simulation.get('magn_temp', 20),
|
658
675
|
num_move_steps=60,
|
676
|
+
skew_linear=0,
|
677
|
+
skew_steps=0,
|
659
678
|
poc=poc.Poc(machine['pole_width']),
|
660
679
|
speed=0)
|
661
680
|
logging.info("Noload simulation")
|
662
681
|
nlresults = self.parstudy(parvardef,
|
663
682
|
machine, nlcalc, engine)
|
664
683
|
if nlresults['status'].count('C') != len(nlresults['status']):
|
665
|
-
raise ValueError('Noload simulation failed
|
684
|
+
raise ValueError(f'Noload simulation failed {nlresults["status"]}')
|
666
685
|
nlresults.update(process(lfe, pole_width, machine, nlresults['f']))
|
667
686
|
|
668
687
|
current_angles = nlresults['f'][0]['current_angles']
|
@@ -671,14 +690,15 @@ class AFPM:
|
|
671
690
|
parameters={
|
672
691
|
'phi_voltage_winding': current_angles})
|
673
692
|
logger.info("Current angles: %s", current_angles)
|
674
|
-
elif (simulation['calculationMode'] == 'cogg_calc'
|
675
|
-
'poc' not in simulation):
|
693
|
+
elif (simulation['calculationMode'] == 'cogg_calc'
|
694
|
+
and 'poc' not in simulation):
|
676
695
|
simulation['poc'] = poc.Poc(machine['pole_width'])
|
677
696
|
|
678
697
|
lresults = self.parstudy(
|
679
|
-
parvardef,
|
680
|
-
|
681
|
-
|
698
|
+
parvardef, machine, simulation, engine)
|
699
|
+
if lresults['status'].count('C') != len(lresults['status']):
|
700
|
+
raise ValueError(
|
701
|
+
f'{simulation["calculationMode"]} failed {lresults["status"]}')
|
682
702
|
|
683
703
|
results = process(lfe, pole_width, machine, lresults['f'])
|
684
704
|
if nlresults:
|
femagtools/machine/effloss.py
CHANGED
@@ -39,7 +39,7 @@ def iqd_tmech_umax(m, u1, with_mtpa, progress, speed_torque, iq, id, iex):
|
|
39
39
|
finally:
|
40
40
|
progress.close()
|
41
41
|
|
42
|
-
def iqd_tmech_umax_multi(num_proc, ntmesh, m, u1, with_mtpa):
|
42
|
+
def iqd_tmech_umax_multi(num_proc, ntmesh, m, u1, with_mtpa, publish=0):
|
43
43
|
"""calculate iqd for sm and pm using multiproc
|
44
44
|
"""
|
45
45
|
progress_readers = []
|
@@ -79,6 +79,16 @@ def iqd_tmech_umax_multi(num_proc, ntmesh, m, u1, with_mtpa):
|
|
79
79
|
progress_readers.remove(r)
|
80
80
|
else:
|
81
81
|
if i % len(progress_readers) == 0:
|
82
|
+
if publish:
|
83
|
+
numTot = len(collected_msg)
|
84
|
+
numOf = f"{num_proc-numTot} of {num_proc}"
|
85
|
+
workdone = sum([float(i[:-1])
|
86
|
+
for i in collected_msg]) / numTot
|
87
|
+
logger.debug("numTot %d numOf %s workdone %s, %s",
|
88
|
+
numTot, numOf, workdone, collected_msg)
|
89
|
+
publish(('progress_logger',
|
90
|
+
f"{numTot}:{numOf}:{workdone}:"))
|
91
|
+
# {' '.join(collected_msg)}"))
|
82
92
|
logger.info("Losses/Eff Map: %s",
|
83
93
|
', '.join(collected_msg))
|
84
94
|
collected_msg = []
|
@@ -197,7 +207,7 @@ def efficiency_losses_map(eecpars, u1, T, temp, n, npoints=(60, 40),
|
|
197
207
|
with_mtpa -- (optional) use mtpa if True (default), disables mtpv if False
|
198
208
|
with_tmech -- (optional) use friction and windage losses (default)
|
199
209
|
num_proc -- (optional) number of parallel processes (default 0)
|
200
|
-
progress -- (optional) custom function for progress logging
|
210
|
+
progress -- (optional) custom function for progress logging (publishing)
|
201
211
|
with_torque_corr -- (optional) T is corrected if out of range (default False)
|
202
212
|
|
203
213
|
Returns:
|
@@ -250,41 +260,42 @@ def efficiency_losses_map(eecpars, u1, T, temp, n, npoints=(60, 40),
|
|
250
260
|
logger.info("total speed,torque samples %d", ntmesh.shape[1])
|
251
261
|
if isinstance(m, (PmRelMachine, SynchronousMachine)):
|
252
262
|
if num_proc > 1:
|
253
|
-
iqd = iqd_tmech_umax_multi(num_proc, ntmesh, m, u1, with_mtpa
|
263
|
+
iqd = iqd_tmech_umax_multi(num_proc, ntmesh, m, u1, with_mtpa,
|
264
|
+
publish=progress)
|
254
265
|
else:
|
255
266
|
class ProgressLogger:
|
256
|
-
def __init__(self, nsamples):
|
267
|
+
def __init__(self, nsamples, publish):
|
257
268
|
self.n = 0
|
269
|
+
self.publish = publish
|
258
270
|
self.nsamples = nsamples
|
259
271
|
self.num_iv = round(nsamples/15)
|
260
272
|
def __call__(self, iqd):
|
261
273
|
self.n += 1
|
262
274
|
if self.n % self.num_iv == 0:
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
progress = ProgressLogger(ntmesh.shape[1])
|
275
|
+
workdone=round(100*self.n/self.nsamples)
|
276
|
+
if self.publish:
|
277
|
+
self.publish(
|
278
|
+
('progress_logger',
|
279
|
+
f"{self.n}:{self.n} of {self.nsamples}:{workdone}"))
|
280
|
+
logger.info("Losses/Eff Map: %d%%", workdone)
|
281
|
+
|
282
|
+
progress_logger = ProgressLogger(ntmesh.shape[1], progress)
|
283
|
+
progress_logger.nsamples = ntmesh.shape[1]
|
284
|
+
progress_logger(0) # To check conformity
|
285
|
+
progress_logger.n = 0
|
275
286
|
if with_tmech:
|
276
287
|
iqd = np.array([
|
277
288
|
m.iqd_tmech_umax(
|
278
289
|
nt[1],
|
279
290
|
2*np.pi*nt[0]*m.p,
|
280
|
-
u1, log=
|
291
|
+
u1, log=progress_logger, with_mtpa=with_mtpa)[:-1]
|
281
292
|
for nt in ntmesh.T]).T
|
282
293
|
else:
|
283
294
|
iqd = np.array([
|
284
295
|
m.iqd_torque_umax(
|
285
296
|
nt[1],
|
286
297
|
2*np.pi*nt[0]*m.p,
|
287
|
-
u1, log=
|
298
|
+
u1, log=progress_logger, with_mtpa=with_mtpa)[:-1]
|
288
299
|
for nt in ntmesh.T]).T
|
289
300
|
|
290
301
|
beta, i1 = betai1(iqd[0], iqd[1])
|
femagtools/machine/sizing.py
CHANGED
@@ -628,8 +628,9 @@ def get_sm_rotor_dimensions(A, psi1, lfe, Da2, par):
|
|
628
628
|
mue0 = 4*np.pi*1e-7
|
629
629
|
|
630
630
|
wf = wp - wc
|
631
|
-
|
632
|
-
|
631
|
+
NI = par['airgap']*par['Ba']/mue0
|
632
|
+
anr = NI/par['J']/par['kqr']
|
633
|
+
r['NI'] = NI
|
633
634
|
a = np.tan(alphap/2)/2
|
634
635
|
b = (wp/2 - wc + np.tan(alphap/2)*(Da2/2 - hp))/2
|
635
636
|
hc = min(max(4*anr/(wc+wp), 0.75*hfmax), hfmax)
|
@@ -827,7 +828,7 @@ def afpm(pnom: float, speed: float, p: int, afmtype: str, **kwargs) -> dict:
|
|
827
828
|
bns = taus*(Di+2*hs1) - bds
|
828
829
|
|
829
830
|
hns = (-bns + np.sqrt(bns**2 + 4*ans*np.tan(taus)))/2/np.tan(taus)/kps
|
830
|
-
hys = psi1/lfe/par['By']/kps
|
831
|
+
hys = psi1/2/lfe/par['By']/kps
|
831
832
|
|
832
833
|
aw = ans * par['kq'] / layers / num_wires * kps
|
833
834
|
|
femagtools/machine/sm.py
CHANGED
@@ -434,9 +434,9 @@ class SynchronousMachine(object):
|
|
434
434
|
def iqd_tmech(self, torque, n, disp=False, maxiter=500):
|
435
435
|
"""return currents for shaft torque with minimal losses"""
|
436
436
|
if torque > 0:
|
437
|
-
startvals = self.bounds[0][1]
|
437
|
+
startvals = self.bounds[0][1], 0, self.bounds[-1][1]
|
438
438
|
else:
|
439
|
-
startvals = -self.bounds[0][1]/2, 0,
|
439
|
+
startvals = -self.bounds[0][1]/2, 0, self.bounds[-1][1]
|
440
440
|
|
441
441
|
with warnings.catch_warnings():
|
442
442
|
warnings.simplefilter("ignore")
|
@@ -463,9 +463,9 @@ class SynchronousMachine(object):
|
|
463
463
|
def iqd_torque(self, torque, disp=False, maxiter=500):
|
464
464
|
"""return currents for torque with minimal losses"""
|
465
465
|
if torque > 0:
|
466
|
-
startvals = self.bounds[0][1]/2, 0,
|
466
|
+
startvals = self.bounds[0][1]/2, 0, self.bounds[-1][1]
|
467
467
|
else:
|
468
|
-
startvals = -self.bounds[0][1]/2, 0,
|
468
|
+
startvals = -self.bounds[0][1]/2, 0, self.bounds[-1][1]
|
469
469
|
|
470
470
|
with warnings.catch_warnings():
|
471
471
|
warnings.simplefilter("ignore")
|
@@ -483,14 +483,14 @@ class SynchronousMachine(object):
|
|
483
483
|
#options={'disp': disp, 'maxiter': maxiter})
|
484
484
|
if res['success']:
|
485
485
|
return res.x
|
486
|
-
|
487
|
-
|
488
|
-
|
486
|
+
logger.warning("%s: torque=%f %f, io=%s",
|
487
|
+
res['message'], torque, self.torque_iqd(*startvals),
|
488
|
+
startvals)
|
489
489
|
raise ValueError(res['message'])
|
490
490
|
|
491
491
|
def mtpa(self, i1max):
|
492
492
|
"""return iq, id, iex currents and maximum torque per current """
|
493
|
-
T0 = self.torque_iqd(np.sqrt(2)*i1max
|
493
|
+
T0 = self.torque_iqd(np.sqrt(2)*i1max, 0, self.bounds[-1][1])
|
494
494
|
def i1tq(tq):
|
495
495
|
return abs(i1max) - np.linalg.norm(self.iqd_torque(tq)[:2])/np.sqrt(2)
|
496
496
|
with warnings.catch_warnings():
|
@@ -501,7 +501,7 @@ class SynchronousMachine(object):
|
|
501
501
|
|
502
502
|
def mtpa_tmech(self, i1max, n):
|
503
503
|
"""return iq, id, iex currents and maximum torque per current """
|
504
|
-
T0 = self.torque_iqd(np.sqrt(2)*i1max
|
504
|
+
T0 = self.torque_iqd(np.sqrt(2)*i1max, 0, self.bounds[-1][1])
|
505
505
|
def i1tq(tq):
|
506
506
|
return i1max - np.linalg.norm(self.iqd_tmech(tq, n)[:2])/np.sqrt(2)
|
507
507
|
tq = so.fsolve(i1tq, T0)[0]
|
@@ -517,14 +517,11 @@ class SynchronousMachine(object):
|
|
517
517
|
if log:
|
518
518
|
log(iqde)
|
519
519
|
return (*iqde, torque)
|
520
|
-
beta, i1 = betai1(iqde[0], iqde[1])
|
521
|
-
iex = iqde[2]
|
520
|
+
#beta, i1 = betai1(iqde[0], iqde[1])
|
521
|
+
#iex = iqde[2]
|
522
522
|
|
523
|
-
|
524
|
-
|
525
|
-
self.uqd(w1, *iqd(b, i1), iex))
|
526
|
-
beta = -np.pi/4 if torque>0 else -3*np.pi/4
|
527
|
-
io = *iqd(beta, i1), iex
|
523
|
+
#beta = 0 if torque>0 else np.pi
|
524
|
+
io = iqde[0], 0, iqde[2] #*iqd(beta, i1), iex
|
528
525
|
|
529
526
|
# logger.debug("--- torque %g io %s", torque, io)
|
530
527
|
with warnings.catch_warnings():
|
@@ -540,17 +537,18 @@ class SynchronousMachine(object):
|
|
540
537
|
bounds=self.bounds,
|
541
538
|
constraints=[
|
542
539
|
{'type': 'eq',
|
543
|
-
'fun': lambda iqd: self.tmech_iqd(*iqd, n)
|
540
|
+
'fun': lambda iqd: torque - self.tmech_iqd(*iqd, n)},
|
544
541
|
{'type': 'eq',
|
545
|
-
'fun': lambda iqd: np.
|
546
|
-
|
542
|
+
'fun': lambda iqd: u1max*np.sqrt(2)
|
543
|
+
- np.linalg.norm(self.uqd(w1, *iqd))}])
|
547
544
|
#if res['success']:
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
545
|
+
if log:
|
546
|
+
log(res.x)
|
547
|
+
return *res.x, self.tmech_iqd(*res.x, n)
|
548
|
+
#logger.warning("%s: w1=%f torque=%f, u1max=%f, io=%s",
|
549
|
+
# res['message'], w1, torque, u1max, io)
|
550
|
+
#raise ValueError(res['message'])
|
551
|
+
#return [float('nan')]*4
|
554
552
|
|
555
553
|
def iqd_torque_umax(self, torque, w1, u1max,
|
556
554
|
disp=False, maxiter=500, log=0, **kwargs):
|
@@ -579,16 +577,15 @@ class SynchronousMachine(object):
|
|
579
577
|
{'type': 'eq',
|
580
578
|
'fun': lambda iqd: self.torque_iqd(*iqd) - torque},
|
581
579
|
{'type': 'eq',
|
582
|
-
'fun': lambda iqd: np.linalg.norm(
|
583
|
-
self.uqd(w1, *iqd))
|
584
|
-
if res['success']:
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
raise ValueError(res['message'])
|
580
|
+
'fun': lambda iqd: u1max*np.sqrt(2) - np.linalg.norm(
|
581
|
+
self.uqd(w1, *iqd))}])
|
582
|
+
#if res['success']:
|
583
|
+
if log:
|
584
|
+
log(res.x)
|
585
|
+
return *res.x, self.torque_iqd(*res.x)
|
586
|
+
#logger.warning("%s: w1=%f torque=%f, u1max=%f, io=%s",
|
587
|
+
# res['message'], w1, torque, u1max, io)
|
588
|
+
#raise ValueError(res['message'])
|
592
589
|
|
593
590
|
def w1_imax_umax(self, i1max, u1max):
|
594
591
|
"""return frequency w1 and shaft torque at voltage u1max and current i1max
|
@@ -672,9 +669,10 @@ class SynchronousMachine(object):
|
|
672
669
|
|
673
670
|
wmtab = []
|
674
671
|
dw = 0
|
675
|
-
wmMax =
|
672
|
+
wmMax = 5*wmType
|
676
673
|
if n > 0:
|
677
674
|
wmMax = min(wmMax, 2*np.pi*n)
|
675
|
+
|
678
676
|
if wmType > wmMax:
|
679
677
|
wmrange = sorted([0, wmMax])
|
680
678
|
wmtab = np.linspace(0, wmMax, nsamples).tolist()
|
femagtools/mcv.py
CHANGED
@@ -8,6 +8,7 @@ import sys
|
|
8
8
|
import copy
|
9
9
|
import logging
|
10
10
|
import os.path
|
11
|
+
import pathlib
|
11
12
|
import struct
|
12
13
|
import math
|
13
14
|
import numpy as np
|
@@ -292,7 +293,12 @@ class Mcv(object):
|
|
292
293
|
self.MC1_CW_FREQ_FACTOR = 0.0
|
293
294
|
self.MC1_INDUCTION_FACTOR = 0.0
|
294
295
|
self.MC1_INDUCTION_BETA_FACTOR = 0.0
|
295
|
-
|
296
|
+
self.jordan = {}
|
297
|
+
# {'ch': 0, 'cw': 0, 'ch_freq':0, 'cw_freq':0}
|
298
|
+
self.steinmetz = {}
|
299
|
+
# {'ch': 0, 'cw': 0, 'ch_freq':0, 'cw_freq':0}
|
300
|
+
self.bertotti = {}
|
301
|
+
# {'ch': 0, 'cw': 0, 'ce':0, 'ch_freq':0, 'cw_freq':0}
|
296
302
|
self.MC1_FE_SPEZ_WEIGTH = 7.65
|
297
303
|
self.MC1_FE_SAT_MAGNETIZATION = 2.15
|
298
304
|
|
@@ -354,6 +360,9 @@ class Mcv(object):
|
|
354
360
|
for k in wtrans:
|
355
361
|
if wtrans[k] in data.keys():
|
356
362
|
self.__setattr__(k, data[wtrans[k]])
|
363
|
+
for k in ('bertotti', 'jordan', 'steinmetz'):
|
364
|
+
if k in data:
|
365
|
+
self.__setattr__(k, data[k])
|
357
366
|
self.curve = data['curve']
|
358
367
|
try:
|
359
368
|
self.mc1_angle = [c['angle'] for c in data['curve']]
|
@@ -363,6 +372,9 @@ class Mcv(object):
|
|
363
372
|
self.losses = data['losses']
|
364
373
|
except Exception:
|
365
374
|
pass
|
375
|
+
# assume jordan iron loss parameters
|
376
|
+
for k in self.jordan:
|
377
|
+
self.jordan[k] = getattr(self, transl[k])
|
366
378
|
return
|
367
379
|
|
368
380
|
def rtrimValueList(self, vlist):
|
@@ -470,13 +482,25 @@ class Writer(Mcv):
|
|
470
482
|
for c in curve]
|
471
483
|
return curve
|
472
484
|
|
473
|
-
def writeBinaryFile(self, fillfac=None, recsin=''):
|
485
|
+
def writeBinaryFile(self, fillfac=None, recsin='', feloss=''):
|
474
486
|
"""write binary file after conversion if requested.
|
475
487
|
arguments:
|
476
488
|
fillfac: (float) fill actor
|
477
489
|
recsin: (str) either 'flux' or 'cur'
|
490
|
+
feloss: (str) iron loss method (bertotti, jordan)
|
478
491
|
"""
|
479
492
|
curve = self._prepare(fillfac, recsin)
|
493
|
+
try:
|
494
|
+
if feloss.lower() == 'bertotti':
|
495
|
+
for k in self.bertotti:
|
496
|
+
setattr(self, transl[k], self.bertotti[k])
|
497
|
+
del self.losses
|
498
|
+
else:
|
499
|
+
for k in self.jordan:
|
500
|
+
setattr(self, transl[k], self.jordan[k])
|
501
|
+
except AttributeError as e:
|
502
|
+
logger.warning("%s", e)
|
503
|
+
pass
|
480
504
|
mc1_type = self.mc1_type
|
481
505
|
mc1_recalc = self.mc1_recalc
|
482
506
|
mc1_fillfac = self.mc1_fillfac
|
@@ -556,6 +580,7 @@ class Writer(Mcv):
|
|
556
580
|
|
557
581
|
try:
|
558
582
|
if not (self.mc1_ch_factor or self.mc1_cw_factor) and self.losses:
|
583
|
+
# fit loss parameters
|
559
584
|
pfe = self.losses['pfe']
|
560
585
|
f = self.losses['f']
|
561
586
|
B = self.losses['B']
|
@@ -598,7 +623,8 @@ class Writer(Mcv):
|
|
598
623
|
return
|
599
624
|
|
600
625
|
try:
|
601
|
-
|
626
|
+
freq = [x for x in self.losses['f'] if x > 0]
|
627
|
+
nfreq = len(freq)
|
602
628
|
nind = len(self.losses['B'])
|
603
629
|
if nind < 1 or nfreq < 1:
|
604
630
|
return
|
@@ -631,26 +657,28 @@ class Writer(Mcv):
|
|
631
657
|
self.writeBlock([nfreq, nind])
|
632
658
|
self.writeBlock([float(b) for b in B] + [0.0]*(M_LOSS_INDUCT - nind))
|
633
659
|
|
660
|
+
nrec = 0
|
634
661
|
for f, p in zip(self.losses['f'], pfe):
|
635
662
|
if f > 0:
|
636
663
|
y = np.array(p)
|
637
664
|
losses = [float(x) for x in y[y != np.array(None)]]
|
638
|
-
|
639
|
-
|
665
|
+
nloss = len(losses)
|
666
|
+
if nloss == nind:
|
667
|
+
pl = list(p)
|
640
668
|
else:
|
641
|
-
n = len(losses)
|
642
669
|
cw, alfa, beta = lc.fitsteinmetz(
|
643
|
-
f, B[:
|
670
|
+
f, B[:nloss], losses, Bo, fo)
|
644
671
|
pl = losses + [lc.pfe_steinmetz(
|
645
672
|
f, b, cw, alfa, beta,
|
646
673
|
self.losses['fo'],
|
647
674
|
self.losses['Bo'])
|
648
|
-
for b in B[
|
675
|
+
for b in B[nloss:]]
|
649
676
|
logger.debug("%s", pl)
|
650
677
|
self.writeBlock(pl +
|
651
678
|
[0.0]*(M_LOSS_INDUCT - len(pl)))
|
652
679
|
self.writeBlock(float(f))
|
653
|
-
|
680
|
+
nrec += 1
|
681
|
+
for m in range(M_LOSS_FREQ - nrec):
|
654
682
|
self.writeBlock([0.0]*M_LOSS_INDUCT)
|
655
683
|
self.writeBlock(0.0)
|
656
684
|
|
@@ -662,21 +690,21 @@ class Writer(Mcv):
|
|
662
690
|
except Exception as e:
|
663
691
|
logger.error("Exception %s", e, exc_info=True)
|
664
692
|
|
665
|
-
def writeMcv(self, filename, fillfac=None, recsin=''):
|
693
|
+
def writeMcv(self, filename, fillfac=None, recsin='', feloss='Jordan'):
|
666
694
|
# windows needs this strip to remove '\r'
|
667
|
-
filename =
|
668
|
-
self.name =
|
695
|
+
filename = pathlib.Path(filename)
|
696
|
+
self.name = filename.stem
|
669
697
|
|
670
|
-
if filename.upper()
|
671
|
-
filename.upper().endswith('.MC'):
|
698
|
+
if filename.suffix.upper() in ('.MCV', '.MC'):
|
672
699
|
binary = True
|
673
|
-
self.fp = open(
|
700
|
+
self.fp = filename.open(mode="wb")
|
674
701
|
else:
|
675
702
|
binary = False
|
676
|
-
self.fp = open(
|
677
|
-
logger.info("Write File %s, binary format",
|
703
|
+
self.fp = filename.open(mode="w")
|
704
|
+
logger.info("Write File %s, binary format (feloss '%s')",
|
705
|
+
filename, feloss)
|
678
706
|
|
679
|
-
self.writeBinaryFile(fillfac, recsin)
|
707
|
+
self.writeBinaryFile(fillfac, recsin, feloss)
|
680
708
|
self.fp.close()
|
681
709
|
|
682
710
|
|
@@ -737,17 +765,16 @@ class Reader(Mcv):
|
|
737
765
|
|
738
766
|
def readMcv(self, filename):
|
739
767
|
# intens bug : windows needs this strip to remove '\r'
|
740
|
-
filename =
|
768
|
+
filename = pathlib.Path(filename)
|
741
769
|
|
742
|
-
if filename.
|
743
|
-
filename.upper().endswith('.MC'):
|
770
|
+
if filename.suffix in ('.MCV', '.MC'):
|
744
771
|
binary = True
|
745
|
-
self.fp = open(
|
772
|
+
self.fp = filename.open(mode="rb")
|
746
773
|
else:
|
747
774
|
binary = False
|
748
|
-
self.fp = open(
|
775
|
+
self.fp = filename.open(mode="r")
|
749
776
|
|
750
|
-
self.name =
|
777
|
+
self.name = filename.stem
|
751
778
|
# read curve version (INTEGER)
|
752
779
|
if binary:
|
753
780
|
self.version_mc_curve = self.readBlock(int)
|
@@ -903,6 +930,8 @@ class Reader(Mcv):
|
|
903
930
|
if self.MC1_INDUCTION_FACTOR > 2.0:
|
904
931
|
self.MC1_INDUCTION_FACTOR = 2.0
|
905
932
|
|
933
|
+
# TODO: handle self.mc1_ce_factor, self.mc1_induction_beta_factor
|
934
|
+
|
906
935
|
self.losses = {}
|
907
936
|
try:
|
908
937
|
(nfreq, njind) = self.readBlock([int, int])
|
@@ -1085,13 +1114,14 @@ class MagnetizingCurve(object):
|
|
1085
1114
|
repls.items(), name)
|
1086
1115
|
|
1087
1116
|
def writefile(self, name, directory='.',
|
1088
|
-
fillfac=None, recsin=''):
|
1117
|
+
fillfac=None, recsin='', feloss='jordan'):
|
1089
1118
|
"""find magnetic curve by name or id and write binary file
|
1090
1119
|
Arguments:
|
1091
1120
|
name: key of mcv dict (name or id)
|
1092
1121
|
directory: destination directory (must be writable)
|
1093
1122
|
fillfac: (float) new fill factor (curves will be recalulated if not None or 0)
|
1094
1123
|
recsin: (str) either 'flux' or 'cur' recalculates for eddy current calculation (dynamic simulation)
|
1124
|
+
feloss: (str) iron loss calc method ('jordan', 'bertotti', 'steinmetz')
|
1095
1125
|
|
1096
1126
|
returns filename if found else None
|
1097
1127
|
"""
|
@@ -1125,7 +1155,7 @@ class MagnetizingCurve(object):
|
|
1125
1155
|
filename = ''.join((bname, ext))
|
1126
1156
|
writer = Writer(mcv)
|
1127
1157
|
writer.writeMcv(os.path.join(directory, filename),
|
1128
|
-
fillfac=fillfac, recsin=recsin)
|
1158
|
+
fillfac=fillfac, recsin=recsin, feloss=feloss)
|
1129
1159
|
return filename
|
1130
1160
|
|
1131
1161
|
def fitLossCoeffs(self):
|