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/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',