femagtools 1.8.5__py3-none-any.whl → 1.8.7__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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 transientsc(bch, title=''):
499
- """creates a transient short circuit plot"""
500
- cols = 1
501
- rows = 2
502
- htitle = 1.5 if title else 0
503
- fig, ax = plt.subplots(nrows=rows, ncols=cols,
504
- figsize=(10, 3*rows + htitle))
505
- if title:
506
- fig.suptitle(title, fontsize=16)
507
-
508
- row = 1
509
- plt.subplot(rows, cols, row)
510
- ax = plt.gca()
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
- istat = np.array([bch.scData[i]
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
- ax.set_title('Currents / kA')
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.set_title('Currents / A')
579
+ ax.set_ylabel('Currents / A')
519
580
 
520
581
  for i, iph in zip(('ia', 'ib', 'ic'), istat):
521
- ax.plot(bch.scData['time'], iph, label=i)
522
- ax.set_xlabel('Time / s')
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
- row = 2
526
- plt.subplot(rows, cols, row)
527
- ax = plt.gca()
528
- scale = 1
529
- torque = np.array(bch.scData['torque'])
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
- ax.set_title('Torque / kNm')
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.set_title('Torque / Nm')
626
+ ax.set_ylabel('Torque / Nm')
535
627
 
536
628
  ax.grid(True)
537
- ax.plot(bch.scData['time'], torque)
538
- ax.set_xlabel('Time / s')
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
- pos = [d['displ'] for d in demag if 'displ' in d]
554
- hmax = [-d['H_max'] for d in demag if 'H_max' in d]
555
- havg = [-d['H_av'] for d in demag if 'H_av' in d]
556
- hclim = [-d['lim_hc'] for d in demag if 'lim_hc' in d]*2
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
- ax.plot([pos[0], pos[-1]], hclim, color='C3', linestyle='dashed',
564
- label='Hc {:4.2f} kA/m'.format(hclim[0]))
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 magnet:
568
- ax.legend(title=f"Magnet Temperature {magnet['Tmag']}°C")
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',