femagtools 1.8.1__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 +110 -1
- femagtools/dxfsl/areabuilder.py +93 -45
- femagtools/dxfsl/conv.py +5 -0
- femagtools/dxfsl/converter.py +85 -27
- femagtools/dxfsl/fslrenderer.py +5 -4
- femagtools/dxfsl/functions.py +14 -6
- femagtools/dxfsl/geom.py +135 -149
- femagtools/dxfsl/journal.py +1 -1
- femagtools/dxfsl/machine.py +161 -9
- femagtools/dxfsl/shape.py +46 -1
- femagtools/dxfsl/svgparser.py +1 -1
- femagtools/dxfsl/symmetry.py +143 -38
- femagtools/femag.py +64 -61
- femagtools/fsl.py +15 -12
- femagtools/isa7.py +3 -2
- femagtools/machine/__init__.py +5 -4
- femagtools/machine/afpm.py +79 -33
- femagtools/machine/effloss.py +29 -18
- femagtools/machine/sizing.py +192 -13
- femagtools/machine/sm.py +34 -36
- femagtools/machine/utils.py +2 -2
- femagtools/mcv.py +58 -29
- femagtools/model.py +4 -3
- femagtools/multiproc.py +79 -80
- femagtools/parstudy.py +11 -5
- femagtools/plot/nc.py +2 -2
- 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.1.dist-info → femagtools-1.8.3.dist-info}/METADATA +3 -3
- {femagtools-1.8.1.dist-info → femagtools-1.8.3.dist-info}/RECORD +49 -47
- {femagtools-1.8.1.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 +21 -15
- {femagtools-1.8.1.dist-info → femagtools-1.8.3.dist-info}/LICENSE +0 -0
- {femagtools-1.8.1.dist-info → femagtools-1.8.3.dist-info}/entry_points.txt +0 -0
- {femagtools-1.8.1.dist-info → femagtools-1.8.3.dist-info}/top_level.txt +0 -0
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
@@ -1,6 +1,6 @@
|
|
1
1
|
"""general design of an AC electrical machine
|
2
2
|
|
3
|
-
Types: spm, ipm, eesm, im
|
3
|
+
Types: spm, ipm, eesm, im, afpm
|
4
4
|
|
5
5
|
"""
|
6
6
|
import numpy as np
|
@@ -10,6 +10,25 @@ from .utils import wdg_resistance
|
|
10
10
|
|
11
11
|
logger = logging.getLogger("femagools.machine.sizing")
|
12
12
|
|
13
|
+
AFPM_DEFAULTS = dict(
|
14
|
+
airgap=2e-3,
|
15
|
+
kd=0.7, # ratio of inner vs outer diameter 0.6 .. 0.8
|
16
|
+
eta=0.92, # efficiency
|
17
|
+
cos_phi=0.95, # power factor
|
18
|
+
m=3, # number of phases
|
19
|
+
ui_u=0.8, # U ind / U
|
20
|
+
J=8.5e6, # current density A/mm²
|
21
|
+
sigmas=40e3, # shear force 30 .. 65 kN/m2
|
22
|
+
Ba=0.7, # flux density in airgap
|
23
|
+
Bth=1.5, # flux density in teeth 1.5 .. 2 T
|
24
|
+
By=1.2, # flux density in yoke 1.2 .. 1.5 T
|
25
|
+
kq=0.6, # stator winding fill factor 0.35 .. 0.75
|
26
|
+
mag_width=0.95, # rel magnet width 0.6 .. 1
|
27
|
+
Hc=700, # max. coercitive field strength, 500 .. 900 kA/m
|
28
|
+
brem=1.2, # remanence 0.3 .. 1.3 T
|
29
|
+
demag=6 # safety factor for demagnetisation (nom current)
|
30
|
+
)
|
31
|
+
"""default sizing parameters for AFPM"""
|
13
32
|
|
14
33
|
PM_DEFAULTS = dict(
|
15
34
|
airgap=1.5e-3, # airgap width m
|
@@ -217,7 +236,7 @@ def _stator_slots(par, slots):
|
|
217
236
|
# check sim factor, height/width ratio
|
218
237
|
if qhbmin[1] == len(q)-1:
|
219
238
|
# last has smallest sim factor
|
220
|
-
|
239
|
+
return q[qhbmin[1]][0]
|
221
240
|
elif q[qhbmin[1]][2] < q[qhbmin[1]+1][2]:
|
222
241
|
# select ideal height/width ratio (3.2)
|
223
242
|
return q[qhbmin[1]][0]
|
@@ -270,7 +289,7 @@ def get_stator_dimensions(par, slots=[]):
|
|
270
289
|
Ui_U = par['ui_u']
|
271
290
|
|
272
291
|
# design parameters
|
273
|
-
# AJ =
|
292
|
+
# AJ = par['AJ'] # 100*1e9 # thermal load, typically 100 .. 300 A²/mm³
|
274
293
|
J = par['J'] # current density 3 .. 6 A/mm²
|
275
294
|
Ba = par['Ba'] # airgap flux density, typically 0.6 .. 1.5 T
|
276
295
|
kq = par['kq'] # winding fill factor, typically 0.35 .. 0.75
|
@@ -431,15 +450,13 @@ def get_stator_dimensions(par, slots=[]):
|
|
431
450
|
|
432
451
|
return r
|
433
452
|
|
434
|
-
|
435
453
|
def _get_magnet_height(I1, N, kw, par):
|
436
454
|
airgap = par['airgap']
|
437
455
|
Ba = par['Ba']
|
438
456
|
p = par['p']
|
439
457
|
m = par['m']
|
440
458
|
|
441
|
-
Hc = par['Hc']
|
442
|
-
Hc = Hc*1e3 # unit kA/m
|
459
|
+
Hc = par['Hc']*1e3 # unit kA/m -> A/m
|
443
460
|
# Safety Factor for demagnetization
|
444
461
|
demag = par['demag']
|
445
462
|
THETA1 = m/np.pi*np.sqrt(2)*I1*N*kw/p
|
@@ -611,8 +628,9 @@ def get_sm_rotor_dimensions(A, psi1, lfe, Da2, par):
|
|
611
628
|
mue0 = 4*np.pi*1e-7
|
612
629
|
|
613
630
|
wf = wp - wc
|
614
|
-
|
615
|
-
|
631
|
+
NI = par['airgap']*par['Ba']/mue0
|
632
|
+
anr = NI/par['J']/par['kqr']
|
633
|
+
r['NI'] = NI
|
616
634
|
a = np.tan(alphap/2)/2
|
617
635
|
b = (wp/2 - wc + np.tan(alphap/2)*(Da2/2 - hp))/2
|
618
636
|
hc = min(max(4*anr/(wc+wp), 0.75*hfmax), hfmax)
|
@@ -648,6 +666,10 @@ def _set_defaults(par, defaults):
|
|
648
666
|
par[k] = defaults[k]
|
649
667
|
|
650
668
|
|
669
|
+
def _set_afpm_defaults(par):
|
670
|
+
_set_defaults(par, AFPM_DEFAULTS)
|
671
|
+
|
672
|
+
|
651
673
|
def _set_pm_defaults(par):
|
652
674
|
_set_defaults(par, PM_DEFAULTS)
|
653
675
|
|
@@ -708,6 +730,163 @@ def spm(pnom: float, speed: float, p: int, **kwargs) -> dict:
|
|
708
730
|
return r
|
709
731
|
|
710
732
|
|
733
|
+
def afpm(pnom: float, speed: float, p: int, afmtype: str, **kwargs) -> dict:
|
734
|
+
"""returns dimension of a AFPM machine
|
735
|
+
|
736
|
+
Args:
|
737
|
+
pnom: power at rated speed (W)
|
738
|
+
speed: rotation speed (1/s)
|
739
|
+
p: number of pole pairs
|
740
|
+
afmtype: one of 'S1R1', 'S2R1', 'S1R2'
|
741
|
+
|
742
|
+
udc: (optional) DC link voltage (V)
|
743
|
+
u1: (optional) phase voltage (Vrms)
|
744
|
+
Q1: (optional) total number of stator slots
|
745
|
+
brem: (optional) remanence of magnet (T)
|
746
|
+
"""
|
747
|
+
par = dict(
|
748
|
+
pnom=pnom, speed=speed, p=p)
|
749
|
+
par.update(kwargs)
|
750
|
+
_set_afpm_defaults(par)
|
751
|
+
|
752
|
+
kp = 1 if afmtype == 'S1R1' else 2
|
753
|
+
kps = 1 if afmtype in ('S1R1', 'S1R2') else 2
|
754
|
+
kpr = 2 if afmtype == 'S1R2' else 1
|
755
|
+
kd = par['kd'] # ratio of inner_diam/outer_diam
|
756
|
+
tnom = pnom/(2*np.pi*speed)
|
757
|
+
sigmas = par['sigmas'] # shear force
|
758
|
+
# outer diameter:
|
759
|
+
# https://web.mit.edu/kirtley/binlustuff/literature/electric%20machine/designOfAxialFluxPMM.pdf
|
760
|
+
Do = 2*np.power(tnom/(kp*sigmas*np.pi*kd*(1-kd**2)), 1/3)
|
761
|
+
Di = Do*kd
|
762
|
+
# pole width and iron length
|
763
|
+
Davg = (Do+Di)/2
|
764
|
+
taup = np.pi * Davg/(2*p)
|
765
|
+
lfe = (Do-Di)/2
|
766
|
+
# flux density in airgap
|
767
|
+
Bd1 = 4.0/np.pi*par['Ba']*np.sin(np.pi/2.0*par['mag_width'])
|
768
|
+
|
769
|
+
# rated phase voltage
|
770
|
+
if 'udc' in par:
|
771
|
+
u1nom = 0.9*par['udc']/np.sqrt(2)/np.sqrt(3)
|
772
|
+
else:
|
773
|
+
u1nom = par['u1']
|
774
|
+
f1 = speed*p
|
775
|
+
# flux linkage
|
776
|
+
psi1 = kpr*2.0/np.pi*taup*lfe*Bd1
|
777
|
+
|
778
|
+
# winding factor
|
779
|
+
Q1 = par['Q1']
|
780
|
+
m = par['m']
|
781
|
+
yd = par.get('coil_span', 0)
|
782
|
+
if yd:
|
783
|
+
wdg = Winding({'Q': par['Q1'], 'p': par['p'], 'm': 3,
|
784
|
+
'yd': yd, 'l': 2})
|
785
|
+
else:
|
786
|
+
wdg = Winding({'Q': par['Q1'], 'p': par['p'], 'm': 3, 'l': 2})
|
787
|
+
|
788
|
+
kw = wdg.kw()
|
789
|
+
|
790
|
+
Ui = par['ui_u'] * u1nom
|
791
|
+
N = np.sqrt(2)*Ui/(2*np.pi*f1*kw*psi1)
|
792
|
+
|
793
|
+
# feasible number of turns per coil...
|
794
|
+
layers = wdg.l
|
795
|
+
# coils per phase (kps: number of stators)
|
796
|
+
ncoils = kps*Q1 // 2 // m * layers
|
797
|
+
ngroups = [1] + [g for g in range(2, layers*p + 1) if layers * p % g == 0]
|
798
|
+
ndiff = [abs(N - ncoils // a * a * round(N / ncoils))
|
799
|
+
for a in ngroups]
|
800
|
+
logger.debug("N %f ngroups %s ndiffs %s", N, ngroups, ndiff)
|
801
|
+
# parallel groups
|
802
|
+
a_calc = ngroups[np.argmin(ndiff)]
|
803
|
+
a = par.get("a", a_calc)
|
804
|
+
if a not in ngroups:
|
805
|
+
logger.warning("Check given number %s of parallel wdg groups. Valid ngroups are: %s",
|
806
|
+
a, ngroups)
|
807
|
+
# num wires per coil side (number of coil groups a)
|
808
|
+
num_wires = round(a * N / ncoils)
|
809
|
+
|
810
|
+
# correction of number of turns per phase
|
811
|
+
N_old = N
|
812
|
+
N = num_wires * ncoils / a
|
813
|
+
|
814
|
+
# correction of voltage
|
815
|
+
Ui = Ui/N_old*N
|
816
|
+
u1nom = Ui/par['ui_u']
|
817
|
+
|
818
|
+
# current loading
|
819
|
+
# A = np.sqrt(2)*sigmas/kw/Ba
|
820
|
+
I1 = pnom/(m*par['eta']*par['cos_phi']*u1nom)
|
821
|
+
A = 2*m*N*I1/np.pi/Di
|
822
|
+
# slot area
|
823
|
+
# J = AJ/A
|
824
|
+
hs1 = 1e-3 # slot opening height
|
825
|
+
taus = np.pi/Q1
|
826
|
+
ans = taus*Di*A/(par['kq']*par['J'])
|
827
|
+
bds = taus*(Di+2*hs1)*Bd1/par['Bth']
|
828
|
+
bns = taus*(Di+2*hs1) - bds
|
829
|
+
|
830
|
+
hns = (-bns + np.sqrt(bns**2 + 4*ans*np.tan(taus)))/2/np.tan(taus)/kps
|
831
|
+
hys = psi1/2/lfe/par['By']/kps
|
832
|
+
|
833
|
+
aw = ans * par['kq'] / layers / num_wires * kps
|
834
|
+
|
835
|
+
r = {'outer_diam': Do, 'inner_diam': Di, 'airgap': par['airgap']/kp,
|
836
|
+
'afmtype': afmtype, 'lfe': lfe,
|
837
|
+
'poles': 2*p,
|
838
|
+
'ans': round(ans, 6),
|
839
|
+
'hns': round(hns, 4),
|
840
|
+
'bns': round(bns, 4),
|
841
|
+
'A': round(A, 3),
|
842
|
+
'AJ': round(par['J']*A, 0),
|
843
|
+
'w1': int(N),
|
844
|
+
'kw': round(kw, 4),
|
845
|
+
'ess': round(1e-3*pnom/(60*speed)/(Do**2*lfe), 4),
|
846
|
+
'q': wdg.q,
|
847
|
+
'i1': round(I1, 3), # np.pi*Da1*A/2/m/N
|
848
|
+
'psi1': round(psi1, 5),
|
849
|
+
'u1': u1nom,
|
850
|
+
'ui': Ui}
|
851
|
+
hs1 = 0
|
852
|
+
hs2 = 0
|
853
|
+
r['stator'] = dict(
|
854
|
+
u1nom=round(u1nom, 1), f1nom=round(f1, 1),
|
855
|
+
num_slots=Q1,
|
856
|
+
nodedist=1,
|
857
|
+
# num_slots_gen = req_poles*Q1/2/p,
|
858
|
+
afm_stator=dict(
|
859
|
+
slot_width=r['bns'],
|
860
|
+
slot_height=hs1+r['hns'],
|
861
|
+
slot_h1=hs1,
|
862
|
+
slot_h2=hs1,
|
863
|
+
slot_open_width=r['bns'],
|
864
|
+
slot_r1=0,
|
865
|
+
slot_r2=0, # bns2/2,
|
866
|
+
yoke_height=round(hys, 4) if kpr == 1 else 0))
|
867
|
+
|
868
|
+
relculen = 1.4
|
869
|
+
r['winding'] = dict(
|
870
|
+
#wire_diam=round(dwire, 5),
|
871
|
+
num_phases=m,
|
872
|
+
cufilfact=par['kq'],
|
873
|
+
culength=relculen,
|
874
|
+
num_par_wdgs=a,
|
875
|
+
num_layers=layers,
|
876
|
+
#resistance=round(r1, 4),
|
877
|
+
coil_span=wdg.yd,
|
878
|
+
num_wires=int(num_wires))
|
879
|
+
|
880
|
+
hm = _get_magnet_height(r['i1'], r['w1'], r['kw'], par)/kpr
|
881
|
+
r['magnet'] = dict(
|
882
|
+
afm_rotor=dict(
|
883
|
+
yoke_height=round(hys/kpr, 4) if kps == 1 else 0,
|
884
|
+
rel_magn_width=par['mag_width'],
|
885
|
+
magn_height=round(hm, 4))
|
886
|
+
)
|
887
|
+
return r
|
888
|
+
|
889
|
+
|
711
890
|
def ipm(pnom: float, speed: float, p: int, **kwargs) -> dict:
|
712
891
|
"""returns dimension of a IPM machine
|
713
892
|
|
@@ -808,11 +987,11 @@ def eesm(pnom: float, speed: float, p: int, **kwargs) -> dict:
|
|
808
987
|
|
809
988
|
|
810
989
|
if __name__ == "__main__":
|
811
|
-
|
812
|
-
pnom = 10e3
|
813
|
-
speed = 4400/60
|
814
|
-
p = 4
|
815
|
-
udc = 600
|
990
|
+
# sizing example with SPM
|
991
|
+
pnom = 10e3 # shaft power in W
|
992
|
+
speed = 4400/60 # speed in 1/s
|
993
|
+
p = 4 # number of pole pairs
|
994
|
+
udc = 600 # DC voltage in V
|
816
995
|
|
817
996
|
r = spm(pnom, speed, p, udc=udc)
|
818
997
|
|
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/machine/utils.py
CHANGED
@@ -449,8 +449,8 @@ def dqparident(workdir, engine, temp, machine,
|
|
449
449
|
leakfile.unlink(missing_ok=True)
|
450
450
|
|
451
451
|
period_frac = kwargs.get('period_frac', 6)
|
452
|
-
if machine.get('external_rotor', False):
|
453
|
-
|
452
|
+
if machine.get('external_rotor', False) and period_frac > 1:
|
453
|
+
logger.warning("period frac for external rotor requires GT femag version >= 2024")
|
454
454
|
|
455
455
|
if dqtype == 'ldq':
|
456
456
|
simulation = dict(
|