femagtools 1.8.5__py3-none-any.whl → 1.8.7__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/bch.py +16 -9
- femagtools/dxfsl/area.py +3 -0
- femagtools/dxfsl/conv.py +1 -8
- femagtools/dxfsl/converter.py +62 -144
- femagtools/dxfsl/geom.py +49 -0
- femagtools/femag.py +13 -72
- femagtools/fsl.py +6 -3
- femagtools/isa7.py +47 -3
- femagtools/losscoeffs.py +29 -3
- femagtools/machine/afpm.py +82 -21
- femagtools/mcv.py +162 -7
- femagtools/multiproc.py +2 -1
- femagtools/parstudy.py +3 -1
- femagtools/plot/__init__.py +1 -0
- femagtools/plot/bch.py +172 -36
- femagtools/plot/machine.py +100 -0
- femagtools/plot/nc.py +13 -0
- femagtools/poc.py +10 -0
- femagtools/shortcircuit.py +378 -0
- femagtools/templates/psi-torq-rem-rot.mako +127 -0
- femagtools/utils.py +2 -0
- femagtools/zmq.py +22 -4
- {femagtools-1.8.5.dist-info → femagtools-1.8.7.dist-info}/METADATA +1 -1
- {femagtools-1.8.5.dist-info → femagtools-1.8.7.dist-info}/RECORD +30 -27
- tests/test_nc.py +11 -0
- {femagtools-1.8.5.dist-info → femagtools-1.8.7.dist-info}/LICENSE +0 -0
- {femagtools-1.8.5.dist-info → femagtools-1.8.7.dist-info}/WHEEL +0 -0
- {femagtools-1.8.5.dist-info → femagtools-1.8.7.dist-info}/entry_points.txt +0 -0
- {femagtools-1.8.5.dist-info → femagtools-1.8.7.dist-info}/top_level.txt +0 -0
femagtools/plot/bch.py
CHANGED
@@ -22,6 +22,26 @@ except ImportError: # ModuleNotFoundError:
|
|
22
22
|
logger = logging.getLogger("femagtools.plot.bch")
|
23
23
|
|
24
24
|
|
25
|
+
def find_peaks_and_valleys(t, y):
|
26
|
+
""" return peaks and valleys of y with maximum amplitude
|
27
|
+
"""
|
28
|
+
peaks = (np.diff(np.sign(np.diff(y))) < 0).nonzero()[0] + 1
|
29
|
+
if len(peaks > 0):
|
30
|
+
ip = np.argmax(y[peaks])
|
31
|
+
pv = {'yp': y[peaks][ip], 'tp': t[peaks][ip]}
|
32
|
+
else:
|
33
|
+
pv = {'yp': [], 'tp': []}
|
34
|
+
valleys = (np.diff(np.sign(np.diff(y))) > 0).nonzero()[0] + 1
|
35
|
+
if len(valleys > 0):
|
36
|
+
iv = np.argmin(y[valleys])
|
37
|
+
pv.update({'yv': y[valleys][iv], 'tv': t[valleys][iv]})
|
38
|
+
else:
|
39
|
+
pv.update({'yv': [], 'tv': []})
|
40
|
+
pv.update({'peaks': y[peaks], 'valleys': y[valleys],
|
41
|
+
'tpeaks': t[peaks], 'tvalleys': t[valleys]})
|
42
|
+
return pv
|
43
|
+
|
44
|
+
|
25
45
|
def _create_3d_axis():
|
26
46
|
"""creates a subplot with 3d projection if one does not already exist"""
|
27
47
|
from matplotlib.projections import get_projection_class
|
@@ -495,48 +515,151 @@ def cogging(bch, title=''):
|
|
495
515
|
fig.subplots_adjust(top=0.92)
|
496
516
|
|
497
517
|
|
498
|
-
def
|
499
|
-
"""
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
518
|
+
def demagnetization(demag, ax=0):
|
519
|
+
"""plot rel. remanence vs. current"""
|
520
|
+
if ax == 0:
|
521
|
+
ax = plt.gca()
|
522
|
+
scale = 1
|
523
|
+
unit = 'A'
|
524
|
+
if np.max(demag['i1']) > 25e3:
|
525
|
+
scale = 1e-3
|
526
|
+
unit = 'kA'
|
527
|
+
i1 = [scale*x for x in demag['i1']]
|
528
|
+
ax.plot(i1, demag['rr'], 'o', color='C0')
|
529
|
+
ax.plot(i1, demag['rr'], color='C0')
|
530
|
+
rrmin = 0.6
|
531
|
+
if demag.get('i1c', 0):
|
532
|
+
Icrit = scale*demag['i1c']
|
533
|
+
Hk = demag['Hk']
|
534
|
+
Tmag = demag['Tmag']
|
535
|
+
di = 0.05*np.max(i1)
|
536
|
+
rrmin = min(0.6, np.min(demag['rr']))
|
537
|
+
ax.plot([Icrit, Icrit], [rrmin, 1], 'k--')
|
538
|
+
ax.annotate(
|
539
|
+
f'Icrit = {Icrit:.1f}{unit}\nHk = {Hk:.1f} kA/m\nTmag={Tmag:.1f} °C',
|
540
|
+
xy=(Icrit, rrmin),
|
541
|
+
xytext=(Icrit-di, rrmin+0.1*(1-rrmin)), ha='right',
|
542
|
+
bbox={'facecolor': 'white',
|
543
|
+
'edgecolor': 'white'})
|
544
|
+
ax.set_ylim([rrmin, 1.01])
|
545
|
+
ax.set_ylabel('Rel. Remanence')
|
546
|
+
ax.set_xlabel(f'Phase Current / {unit}')
|
547
|
+
ax.grid()
|
548
|
+
|
549
|
+
|
550
|
+
def transientsc_currents(scData, ax=0, title='', set_xlabel=True):
|
551
|
+
"""plot transient shortcircuit currents vs time"""
|
552
|
+
if ax == 0:
|
553
|
+
ax = plt.gca()
|
511
554
|
ax.grid(True)
|
512
|
-
|
555
|
+
if title:
|
556
|
+
ax.set_title(title)
|
557
|
+
istat = np.array([scData[i]
|
513
558
|
for i in ('ia', 'ib', 'ic')])
|
559
|
+
pv = [find_peaks_and_valleys(
|
560
|
+
np.array(scData['time']), i1)
|
561
|
+
for i1 in istat]
|
562
|
+
try:
|
563
|
+
ipvmax = np.argmax(
|
564
|
+
[y['yp'] if np.abs(y['yp']) > np.abs(y['yv']) else y['yv']
|
565
|
+
for y in pv if y['yp']])
|
566
|
+
imax = pv[ipvmax]['yp'] if np.abs(pv[ipvmax]['yp']) > np.abs(pv[ipvmax]['yv']) else pv[ipvmax]['yv']
|
567
|
+
iac = [pv[ipvmax]['tpeaks'][-1], pv[ipvmax]['peaks'][-1]]
|
568
|
+
except KeyError:
|
569
|
+
pass
|
514
570
|
if np.max(istat) > 4000:
|
515
571
|
istat *= 1e-3
|
516
|
-
|
572
|
+
try:
|
573
|
+
imax *= 1e-3
|
574
|
+
iac[1] *= 1e-3
|
575
|
+
except NameError:
|
576
|
+
pass
|
577
|
+
ax.set_ylabel('Currents / kA')
|
517
578
|
else:
|
518
|
-
ax.
|
579
|
+
ax.set_ylabel('Currents / A')
|
519
580
|
|
520
581
|
for i, iph in zip(('ia', 'ib', 'ic'), istat):
|
521
|
-
ax.plot(
|
522
|
-
|
582
|
+
ax.plot(scData['time'], iph, label=i)
|
583
|
+
try:
|
584
|
+
ax.plot([pv[ipvmax]['tp']], [imax], '.')
|
585
|
+
ax.plot([iac[0]], [iac[1]], '.')
|
586
|
+
dtx = (scData['time'][-1]-scData['time'][0])/75
|
587
|
+
dy = imax/25
|
588
|
+
ax.annotate(f'Imax = {imax:.1f}',
|
589
|
+
xy=(pv[ipvmax]['tp'], imax),
|
590
|
+
xytext=(pv[ipvmax]['tp']+dtx, imax-dy))
|
591
|
+
dy = iac[1]/25
|
592
|
+
ax.annotate(f'I = {iac[1]:.1f}',
|
593
|
+
xy=iac,
|
594
|
+
xytext=(iac[0]+dtx, iac[1]-dy))
|
595
|
+
except NameError:
|
596
|
+
pass
|
597
|
+
if set_xlabel:
|
598
|
+
ax.set_xlabel('Time / s')
|
523
599
|
ax.legend()
|
524
600
|
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
601
|
+
|
602
|
+
def transientsc_torque(scData, ax=0, title='', set_xlabel=True):
|
603
|
+
"""plot transient shortcircuit torque vs time"""
|
604
|
+
if ax == 0:
|
605
|
+
ax = plt.gca()
|
606
|
+
if title:
|
607
|
+
ax.set_title(title)
|
608
|
+
pv = find_peaks_and_valleys(
|
609
|
+
np.array(scData['time']), np.array(scData['torque']))
|
610
|
+
try:
|
611
|
+
tqmax = pv['yp'] if np.abs(pv['yp']) > np.abs(pv['yv']) else pv['yv']
|
612
|
+
tp = pv['tp'] if np.abs(pv['yp']) > np.abs(pv['yv']) else pv['tv']
|
613
|
+
tc = [pv['tpeaks'][-1], pv['peaks'][-1]]
|
614
|
+
except (KeyError, ValueError):
|
615
|
+
pass
|
616
|
+
torque = np.array(scData['torque'])
|
530
617
|
if np.max(torque) > 4000:
|
531
618
|
torque *= 1e-3
|
532
|
-
|
619
|
+
try:
|
620
|
+
tqmax *= 1e-3
|
621
|
+
tc[1] *= 1e-3
|
622
|
+
except NameError:
|
623
|
+
pass
|
624
|
+
ax.set_ylabel('Torque / kNm')
|
533
625
|
else:
|
534
|
-
ax.
|
626
|
+
ax.set_ylabel('Torque / Nm')
|
535
627
|
|
536
628
|
ax.grid(True)
|
537
|
-
ax.plot(
|
538
|
-
|
629
|
+
ax.plot(scData['time'], torque)
|
630
|
+
try:
|
631
|
+
ax.plot([tp], [tqmax], '.')
|
632
|
+
ax.plot([tc[0]], [tc[1]], '.')
|
633
|
+
dtx = (scData['time'][-1]-scData['time'][0])/75
|
634
|
+
dy = tqmax/25
|
635
|
+
ax.annotate(f'Tmax = {tqmax:.1f}',
|
636
|
+
xy=(tp, tqmax),
|
637
|
+
xytext=(tp+dtx, tqmax-dy))
|
638
|
+
dy = tc[1]/25
|
639
|
+
ax.annotate(f'T = {tc[1]:.1f}',
|
640
|
+
xy=tc,
|
641
|
+
xytext=(tc[0]+dtx, tc[1]))
|
642
|
+
except NameError:
|
643
|
+
pass
|
644
|
+
if set_xlabel:
|
645
|
+
ax.set_xlabel('Time / s')
|
646
|
+
|
647
|
+
def transientsc(bch, title=''):
|
648
|
+
"""creates a transient short circuit plot"""
|
649
|
+
try:
|
650
|
+
scData = bch.scData
|
651
|
+
except AttributeError:
|
652
|
+
scData = bch
|
653
|
+
cols = 1
|
654
|
+
rows = 2
|
655
|
+
htitle = 1.5 if title else 0
|
656
|
+
fig, axs = plt.subplots(nrows=rows, ncols=cols, sharex=True,
|
657
|
+
figsize=(10, 3*rows + htitle))
|
658
|
+
if title:
|
659
|
+
fig.suptitle(title, fontsize=16)
|
539
660
|
|
661
|
+
transientsc_currents(scData, axs[0], set_xlabel=False)
|
662
|
+
transientsc_torque(scData, axs[1])
|
540
663
|
fig.tight_layout(h_pad=2)
|
541
664
|
if title:
|
542
665
|
fig.subplots_adjust(top=0.92)
|
@@ -550,22 +673,35 @@ def transientsc_demag(demag, magnet=0, title='', ax=0):
|
|
550
673
|
"""
|
551
674
|
if ax == 0:
|
552
675
|
ax = plt.gca()
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
676
|
+
if type(d) == list:
|
677
|
+
pos = [d['displ'] for d in demag if 'displ' in d]
|
678
|
+
hmax = [-d['H_max'] for d in demag if 'H_max' in d]
|
679
|
+
havg = [-d['H_av'] for d in demag if 'H_av' in d]
|
680
|
+
hclim = [-d['lim_hc'] for d in demag if 'lim_hc' in d][0]
|
681
|
+
else:
|
682
|
+
pos = demag['displ']
|
683
|
+
hmax = demag['H_max']
|
684
|
+
havg = demag['H_av']
|
685
|
+
hclim = demag['Hk']
|
558
686
|
ax.set_title('Transient Short Circuit Demagnetization [kA/m]')
|
559
687
|
ax.plot(pos, hmax,
|
560
688
|
label='H Max {:4.2f} kA/m'.format(max(hmax)))
|
561
689
|
ax.plot(pos, havg,
|
562
690
|
label='H Avg {:4.2f} kA/m'.format(max(havg)))
|
563
|
-
|
564
|
-
|
691
|
+
if len(hclim) > 1:
|
692
|
+
ax.plot([pos[0], pos[-1]], [hclim,hclim], color='C3',
|
693
|
+
linestyle='dashed',
|
694
|
+
label='Hc {:4.2f} kA/m'.format(hclim))
|
695
|
+
if 'Tmag' in demag:
|
696
|
+
Tmag = demag['Tmag']
|
697
|
+
elif magnet:
|
698
|
+
Tmag = magnet['Tmag']
|
699
|
+
else:
|
700
|
+
Tmag = ''
|
565
701
|
ax.set_xlabel('Rotor Position / °')
|
566
702
|
ax.grid(True)
|
567
|
-
if
|
568
|
-
ax.legend(title=f"Magnet Temperature {
|
703
|
+
if Tmag:
|
704
|
+
ax.legend(title=f"Magnet Temperature {Tmag}°C")
|
569
705
|
else:
|
570
706
|
ax.legend()
|
571
707
|
|
@@ -0,0 +1,100 @@
|
|
1
|
+
"""
|
2
|
+
Create longitudinal drawing of radial flux machine
|
3
|
+
"""
|
4
|
+
import matplotlib.pyplot as plt
|
5
|
+
import matplotlib.patches as pch
|
6
|
+
|
7
|
+
def _draw_shaft(ax, dy2, lfe):
|
8
|
+
xx = (0.0, lfe, lfe, 0.0)
|
9
|
+
yy = (dy2/2, dy2/2, -dy2/2, -dy2/2)
|
10
|
+
ax.fill(xx, yy,
|
11
|
+
facecolor='lightgrey',
|
12
|
+
edgecolor='black',
|
13
|
+
linewidth=0)
|
14
|
+
xx = (-lfe/4, lfe+lfe/4, lfe+lfe/4, -lfe/4)
|
15
|
+
yy = (dy2/4, dy2/4, -dy2/4, -dy2/4)
|
16
|
+
ax.fill(xx, yy,
|
17
|
+
facecolor='lightgrey',
|
18
|
+
edgecolor='black',
|
19
|
+
linewidth=0)
|
20
|
+
|
21
|
+
def _draw_rotor(ax, da1, dy2, lfe):
|
22
|
+
ag = 0.02*da1
|
23
|
+
xx = (0.0, lfe, lfe, 0.0)
|
24
|
+
yy = (dy2/2, dy2/2, da1/2-ag, da1/2-ag)
|
25
|
+
ax.fill(xx, yy,
|
26
|
+
facecolor='skyblue',
|
27
|
+
edgecolor='black',
|
28
|
+
linewidth=0)
|
29
|
+
yy = (-dy2/2, -dy2/2, -da1/2+ag, -da1/2+ag)
|
30
|
+
ax.fill(xx, yy,
|
31
|
+
facecolor='skyblue',
|
32
|
+
edgecolor='black',
|
33
|
+
linewidth=0)
|
34
|
+
|
35
|
+
def _draw_stator(ax, da1, dy1, lfe):
|
36
|
+
xx = (0.0, lfe, lfe, 0.0)
|
37
|
+
yy = (da1/2, da1/2, dy1/2, dy1/2)
|
38
|
+
# yoke
|
39
|
+
ax.fill(xx, yy,
|
40
|
+
facecolor='skyblue',
|
41
|
+
edgecolor='black',
|
42
|
+
linewidth=0)
|
43
|
+
|
44
|
+
yy = (-da1/2, -da1/2, -dy1/2, -dy1/2)
|
45
|
+
ax.fill(xx, yy,
|
46
|
+
facecolor='skyblue',
|
47
|
+
edgecolor='black',
|
48
|
+
linewidth=0)
|
49
|
+
|
50
|
+
# winding
|
51
|
+
yh = (dy1-da1)/2
|
52
|
+
xx = (-yh/2, 0, 0, -yh/2)
|
53
|
+
yy = (da1/2, da1/2, dy1/2-yh/2, dy1/2-yh/2)
|
54
|
+
ax.fill(xx, yy, facecolor='gold',
|
55
|
+
edgecolor='black',
|
56
|
+
linewidth=0)
|
57
|
+
|
58
|
+
xx = (lfe, lfe+yh/2, lfe+yh/2, lfe)
|
59
|
+
ax.fill(xx, yy, facecolor='gold',
|
60
|
+
edgecolor='black',
|
61
|
+
linewidth=0)
|
62
|
+
|
63
|
+
yy = (-da1/2, -da1/2, -dy1/2+yh/2, -dy1/2+yh/2)
|
64
|
+
ax.fill(xx, yy, facecolor='gold',
|
65
|
+
edgecolor='black',
|
66
|
+
linewidth=0)
|
67
|
+
|
68
|
+
xx = (-yh/2, 0, 0, -yh/2)
|
69
|
+
ax.fill(xx, yy, facecolor='gold',
|
70
|
+
edgecolor='black',
|
71
|
+
linewidth=0)
|
72
|
+
|
73
|
+
def machine(machine, ax):
|
74
|
+
dy2 = machine['inner_diam']*1e3
|
75
|
+
dy1 = machine['outer_diam']*1e3
|
76
|
+
da1 = machine['bore_diam']*1e3
|
77
|
+
lfe = machine['lfe']*1e3
|
78
|
+
|
79
|
+
_draw_rotor(ax, da1, dy2, lfe)
|
80
|
+
_draw_stator(ax, da1, dy1, lfe)
|
81
|
+
_draw_shaft(ax, dy2, lfe)
|
82
|
+
ax.set_aspect('equal')
|
83
|
+
|
84
|
+
for loc, spine in ax.spines.items():
|
85
|
+
spine.set_color('none') # don't draw spine
|
86
|
+
#ax.yaxis.set_ticks([])
|
87
|
+
#ax.xaxis.set_ticks([])
|
88
|
+
|
89
|
+
|
90
|
+
if __name__ == '__main__':
|
91
|
+
machine1 = {
|
92
|
+
"outer_diam": 0.2442,
|
93
|
+
"bore_diam": 0.179,
|
94
|
+
"inner_diam": 0.06,
|
95
|
+
"airgap": 0.7e-3,
|
96
|
+
"lfe": 0.083,
|
97
|
+
}
|
98
|
+
fig, ax = plt.subplots()
|
99
|
+
machine(machine1, ax)
|
100
|
+
plt.show()
|
femagtools/plot/nc.py
CHANGED
@@ -130,6 +130,19 @@ def demag(isa, cmap=DEFAULT_CMAP, ax=0):
|
|
130
130
|
logger.info("Max demagnetization %f", np.max(demag))
|
131
131
|
|
132
132
|
|
133
|
+
def remanence(isa, cmap=DEFAULT_CMAP, ax=0):
|
134
|
+
"""plot remanence of NC/I7/ISA7 model
|
135
|
+
Args:
|
136
|
+
isa: Isa7/NC object
|
137
|
+
"""
|
138
|
+
emag = [e for e in isa.elements if e.is_magnet()]
|
139
|
+
rem = np.linalg.norm([e.remanence(isa.MAGN_TEMPERATURE)
|
140
|
+
for e in emag], axis=1)
|
141
|
+
_contour(ax, f'Remanence at {isa.MAGN_TEMPERATURE} °C (min {np.min(rem):.1f} T)',
|
142
|
+
emag, rem, 'T', cmap, isa)
|
143
|
+
logger.info("Min remanence %f", np.min(rem))
|
144
|
+
|
145
|
+
|
133
146
|
def demag_pos(isa, pos=-1, icur=-1, ibeta=-1, cmap=DEFAULT_CMAP, ax=0):
|
134
147
|
"""plot demag of NC/I7/ISA7 model at rotor position
|
135
148
|
Args:
|
femagtools/poc.py
CHANGED
@@ -75,6 +75,7 @@ class Poc:
|
|
75
75
|
if self.pole_pitch:
|
76
76
|
content.append("{0}".format(self.pole_pitch))
|
77
77
|
if self.pocType in ['fun', 'har', 'hsp']:
|
78
|
+
self.data_check()
|
78
79
|
func_steps = len(self.func_current)
|
79
80
|
content += [f"{self.pocType}", f"{func_steps}"]
|
80
81
|
if (self.pocType == 'fun' and
|
@@ -122,6 +123,7 @@ class Poc:
|
|
122
123
|
import re
|
123
124
|
for i in range(func_steps-1):
|
124
125
|
l = re.split(';|\t|,| ', pocfile.readline().strip())
|
126
|
+
l = [i for i in l if i] # remove empty items
|
125
127
|
if len(l) > 2:
|
126
128
|
self.harmonic_id.append(int(l[0]))
|
127
129
|
self.func_current.append(float(l[1]))
|
@@ -129,6 +131,7 @@ class Poc:
|
|
129
131
|
else:
|
130
132
|
self.func_current.append(float(l[0]))
|
131
133
|
self.func_phi.append(float(l[1]))
|
134
|
+
self.data_check()
|
132
135
|
else:
|
133
136
|
self.shape_current=self.pocType
|
134
137
|
self.pocType='Function'
|
@@ -139,6 +142,13 @@ class Poc:
|
|
139
142
|
except ValueError:
|
140
143
|
pass
|
141
144
|
|
145
|
+
def data_check( self ):
|
146
|
+
"""simple phi data increasing check"""
|
147
|
+
import numpy as np
|
148
|
+
if (not np.all(np.diff(self.func_phi) > 0)
|
149
|
+
and np.all(np.diff(self.func_current) > 0)):
|
150
|
+
self.func_phi, self.func_current = self.func_current, self.func_phi
|
151
|
+
|
142
152
|
def getProps( self ):
|
143
153
|
keys=['key_winding',
|
144
154
|
'phi_voltage_winding',
|