femagtools 1.8.2__py3-none-any.whl → 1.8.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- femagtools/__init__.py +1 -1
- femagtools/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):
|