femagtools 1.8.1__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 +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(
|