femagtools 1.8.4__py3-none-any.whl → 1.8.6__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.
@@ -14,6 +14,8 @@ from .. import femag
14
14
  from scipy.interpolate import make_interp_spline, RegularGridInterpolator, RectBivariateSpline
15
15
  from scipy.integrate import quad
16
16
  import copy
17
+ from matplotlib.colors import to_rgb
18
+ from multiprocessing import Pool
17
19
 
18
20
  logger = logging.getLogger(__name__)
19
21
 
@@ -53,7 +55,8 @@ def _integrate(radius, pos, val):
53
55
  interp = RegularGridInterpolator((radius, pos), val)
54
56
  def func(x, y):
55
57
  return interp((x, y))
56
- return [quad(func, radius[0], radius[-1], args=(p,))[0]
58
+ return [quad(func, radius[0], radius[-1],
59
+ args=(p,), limit=200)[0]
57
60
  for p in pos]
58
61
 
59
62
 
@@ -61,7 +64,7 @@ def _integrate1d(radius, val):
61
64
  interp = make_interp_spline(radius, val, k=1)
62
65
  def func(x):
63
66
  return interp((x))
64
- return quad(func, radius[0], radius[-1])[0]
67
+ return quad(func, radius[0], radius[-1], limit=100)[0]
65
68
 
66
69
  def ld_interpol(i1, beta, v):
67
70
  '''interpolate Ld at beta angle 0°, -180°'''
@@ -151,7 +154,17 @@ def parident(workdir, engine, temp, machine,
151
154
  machine[wdgk].get('num_par_wdgs', 1))
152
155
 
153
156
  p = machine['poles']
154
- num_slices = kwargs.get('num_slices', 3)
157
+ if np.isscalar(machine['magnet']['afm_rotor']['rel_magn_width']):
158
+ num_slices = kwargs.get('num_slices', 3)
159
+ rmagw = num_slices*[machine['magnet']['afm_rotor']['rel_magn_width']]
160
+ else:
161
+ rmagw = machine['magnet']['afm_rotor']['rel_magn_width']
162
+ if len(rmagw) == 1:
163
+ num_slices = kwargs.get('num_slices', 3)
164
+ rmagw = num_slices*list(rmagw)
165
+ else:
166
+ num_slices = len(rmagw)
167
+
155
168
  lfe = get_arm_lengths(machine['outer_diam'],
156
169
  machine['inner_diam'],
157
170
  num_slices)
@@ -171,6 +184,8 @@ def parident(workdir, engine, temp, machine,
171
184
  "decision_vars": [
172
185
  {"values": pole_width,
173
186
  "name": "pole_width"},
187
+ {"values": rmagw,
188
+ "name": "magnet.afm_rotor.rel_magn_width"},
174
189
  {"values": lfe,
175
190
  "name": "lfe"},
176
191
  {"values": linspeed, "name": "speed"}
@@ -215,9 +230,11 @@ def parident(workdir, engine, temp, machine,
215
230
  else:
216
231
  nlresults = {"x": [], "f": []}
217
232
  i = 0
218
- for pw, le, sp in zip(pole_width, lfe, linspeed):
233
+
234
+ for pw, le, sp, rmw in zip(pole_width, lfe, linspeed, rmagw):
219
235
  nlmachine = {k: machine[k] for k in machine}
220
236
  nlmachine['pole_width'] = pw
237
+ nlmachine['magnet']['afm_rotor']['rel_magn_width'] = rmw
221
238
  nlmachine['lfe'] = le
222
239
  nlcalc.update({"speed": sp})
223
240
  nlsubdir = f'{workdir}/{i}'
@@ -236,10 +253,11 @@ def parident(workdir, engine, temp, machine,
236
253
  current_angles = nlresults['f'][0]['current_angles']
237
254
  results = []
238
255
  i = 0
239
- for l, pw in zip(lfe, pole_width):
256
+ for l, pw, rmw in zip(lfe, pole_width, rmagw):
240
257
  mpart = {k: machine[k] for k in machine if k != 'afm_rotor'}
241
258
  mpart['pole_width'] = pw
242
259
  mpart['lfe'] = l
260
+ mpart['magnet']['afm_rotor']['rel_magn_width'] = rmw
243
261
  subdir = f"{workdir}/{i}"
244
262
 
245
263
  simulation = dict(
@@ -295,12 +313,23 @@ def parident(workdir, engine, temp, machine,
295
313
  i += 1
296
314
 
297
315
  postp = []
298
- for results in [process(lfe, pole_width, machine, bch)
299
- for bch in zip(*[r['f'] for r in results])]:
300
- torque = np.mean(results.pop('torque'))
301
- results['torque'] = torque
302
- results.update(_psidq_ldq(results, nlresults))
303
- postp.append(results)
316
+ if kwargs.get('use_multiprocessing', True):
317
+ with Pool() as p:
318
+ for r in p.starmap(process,
319
+ [(lfe, pole_width, machine, bch)
320
+ for bch in zip(*[r['f']
321
+ for r in results])]):
322
+ torque = np.mean(r.pop('torque'))
323
+ r['torque'] = torque
324
+ r.update(_psidq_ldq(r, nlresults))
325
+ postp.append(r)
326
+ else:
327
+ for r in [process(lfe, pole_width, machine, bch)
328
+ for bch in zip(*[r['f'] for r in results])]:
329
+ torque = np.mean(r.pop('torque'))
330
+ r['torque'] = torque
331
+ r.update(_psidq_ldq(r, nlresults))
332
+ postp.append(r)
304
333
 
305
334
  r1 = postp[0]['r1']
306
335
  i1 = [r['i1'] for r in postp][::num_beta_steps]
@@ -395,6 +424,7 @@ def process(lfe, pole_width, machine, bch):
395
424
  n = len(rotpos[0])
396
425
  currents = [bch[0]['flux'][k][0]['current_k'][:n]
397
426
  for k in bch[0]['flux']]
427
+
398
428
  if len(pole_width) > 1:
399
429
  # check homogenity:
400
430
  if np.diff([len(d) for d in displ]).any():
@@ -426,6 +456,9 @@ def process(lfe, pole_width, machine, bch):
426
456
  for ux in bch[0]['flux'][k][0]['voltage_dpsi'][:-1]]
427
457
  for k in bch[0]['flux']}
428
458
  emf = [voltage[k][:n] for k in voltage]
459
+ fluxxy = {k: [scale_factor * np.array(flx)
460
+ for flx in bch[0]['flux'][k][0]['flux_k']]
461
+ for k in bch[0]['flux']}
429
462
  flux = [fluxxy[k][:n] for k in fluxxy]
430
463
 
431
464
  pos = (rotpos[0]/np.pi*180)
@@ -484,7 +517,6 @@ def process(lfe, pole_width, machine, bch):
484
517
  except KeyError as exc:
485
518
  #logger.warning("missing key %s", exc)
486
519
  pass
487
-
488
520
  return {
489
521
  'weights': weights.tolist(),
490
522
  'pos': pos.tolist(), 'r1': r1,
@@ -598,6 +630,287 @@ def _get_copper_losses(scale_factor, bch):
598
630
  return 0 # noload calc has no winding losses
599
631
 
600
632
 
633
+ def _set_plot_attributes(ax):
634
+ ax.set_aspect('equal')
635
+ for loc, spine in ax.spines.items():
636
+ spine.set_color('none') # don't draw spine
637
+ ax.yaxis.set_ticks([])
638
+ ax.xaxis.set_ticks([])
639
+
640
+
641
+ def _get_colors(colors, delta):
642
+ if delta == 0.0:
643
+ return colors
644
+ new_colors = []
645
+ for col in colors:
646
+ rgb = to_rgb(col)
647
+ r, g, b = rgb
648
+ col = (max(0.0, min(r+delta, 1.0)),
649
+ max(0.0, min(g+delta, 1.0)),
650
+ max(0.0, min(b+delta, 1.0)))
651
+ new_colors.append(col)
652
+ return new_colors
653
+
654
+
655
+ def _draw_vertical_magnets(ax,
656
+ poles,
657
+ xr, yr,
658
+ Rr,
659
+ xoff, yoff,
660
+ delta=0.0):
661
+ color = ['green', 'red']
662
+ color = _get_colors(color, delta)
663
+ for i in range(poles):
664
+ ax.fill(xr+xoff, yr+yoff,
665
+ facecolor=color[i%2], edgecolor=color[i%2])
666
+ xr, yr = np.dot(Rr, [xr, yr])
667
+ return
668
+
669
+
670
+ def _draw_vertical_slots(ax,
671
+ Q,
672
+ r,
673
+ alpha,
674
+ xoff, yoff,
675
+ delta=0.0):
676
+ color = ['skyblue', 'blue']
677
+ color = _get_colors(color, delta)
678
+ taus = 2*np.pi/Q
679
+ for n in range(Q):
680
+ beta0 = np.linspace(n*taus, n*taus + taus/2-alpha[0], 5)
681
+ beta1 = np.linspace(n*taus, n*taus + taus/2-alpha[1], 5)
682
+ xr = np.concatenate((
683
+ r[0]*np.cos(beta0), r[1]*np.cos(beta1[::-1])))+xoff
684
+ yr = np.concatenate((
685
+ r[0]*np.sin(beta0), r[1]*np.sin(beta1[::-1])))+yoff
686
+ ax.fill(xr, yr, color=color[0])
687
+ beta0 = np.linspace(n*taus + taus/2+alpha[0], (n+1)*taus, 5)
688
+ beta1 = np.linspace(n*taus + taus/2+alpha[1], (n+1)*taus, 5)
689
+ xr = np.concatenate((
690
+ r[0]*np.cos(beta0), r[1]*np.cos(beta1[::-1])))
691
+ yr = np.concatenate((
692
+ r[0]*np.sin(beta0), r[1]*np.sin(beta1[::-1])))
693
+ ax.fill(xr, yr, color=color[0])
694
+
695
+
696
+ def vertical_plot(machine, ax):
697
+ """plots afpm stator and rotor (vertical section)
698
+ Args:
699
+ dy1, dy1: float outer, inner diameter
700
+ rel_magn_width: float rel magnet width 0..1
701
+ Q: number of stator slots
702
+ poles: number of poles
703
+ slot_width: width of stator slot
704
+ """
705
+ logger.debug("begin of vertical_plot()")
706
+
707
+ model_type = machine['afmtype'][0:4]
708
+ dy1 = machine['outer_diam']*1e3
709
+ dy2 = machine['inner_diam']*1e3
710
+ rel_magn_width = max(machine['magnet']['afm_rotor']['rel_magn_width'])
711
+ Q = machine['stator']['num_slots']
712
+ slot_width = machine['stator']['afm_stator']['slot_width']*1e3
713
+ poles = machine['poles']
714
+
715
+ # prepare Magnets
716
+ theta = np.linspace(np.pi/poles*(1-rel_magn_width),
717
+ np.pi/poles*(1+rel_magn_width),
718
+ 10)
719
+ xr = np.concatenate((dy1/2 * np.cos(theta), dy2/2 * np.cos(theta[::-1])))
720
+ yr = np.concatenate((dy1/2 * np.sin(theta), dy2/2 * np.sin(theta[::-1])))
721
+ rtheta = 2*np.pi/poles
722
+ Rr = np.array([
723
+ [np.cos(rtheta), -np.sin(rtheta)],
724
+ [np.sin(rtheta), np.cos(rtheta)]
725
+ ])
726
+
727
+ # prepare Slots
728
+ taus = 2*np.pi/Q
729
+ r = np.array([dy2/2, dy1/2])
730
+ alpha = np.arctan2(slot_width/2, r)
731
+
732
+ yoff = 0.0
733
+ xoff = 0.0
734
+ y_shift = -2
735
+ x_shift = dy1-dy2
736
+ ma_delta = 0.0 # color
737
+ sl_delta = 0.0 # color
738
+
739
+ # Draw
740
+ if model_type in ("S1R2"): # 2 rotor
741
+ _draw_vertical_magnets(ax, poles, xr, yr, Rr, xoff, yoff, delta=-0.1)
742
+ yoff += y_shift
743
+ xoff += x_shift
744
+
745
+ if model_type in ("S2R1"): # 2 stator
746
+ sl_delta = -0.1
747
+
748
+ _draw_vertical_slots(ax, Q, r, alpha, xoff, yoff, delta=sl_delta)
749
+ yoff += y_shift
750
+ xoff += x_shift
751
+
752
+ if model_type in ("S1R2"): # 2 rotor
753
+ ma_delta = 0.1
754
+
755
+ _draw_vertical_magnets(ax, poles, xr, yr, Rr, xoff, yoff, delta=ma_delta)
756
+ yoff += y_shift
757
+ xoff += x_shift
758
+
759
+ if model_type in ("S2R1"): # 2 stator
760
+ sl_delta = 0.0
761
+ _draw_vertical_slots(ax, Q, r, alpha, xoff, yoff, delta=sl_delta)
762
+
763
+ _set_plot_attributes(ax)
764
+ logger.debug("end of vertical_plot()")
765
+
766
+
767
+ IRON_NO = 0
768
+ IRON_UP = 1
769
+ IRON_DOWN = 2
770
+
771
+ def _draw_horizontal_magnets(ax,
772
+ poles,
773
+ magn_height,
774
+ magn_width,
775
+ yoke_height,
776
+ Q,
777
+ g,
778
+ taus,
779
+ dy2,
780
+ yoff=0.0,
781
+ iron=IRON_NO
782
+ ):
783
+ color = ['green', 'red']
784
+ xy = (0, Q//g*taus, Q//g*taus, 0)
785
+
786
+ if iron == IRON_UP:
787
+ yy = (yoff-yoke_height,
788
+ yoff-yoke_height,
789
+ yoff,
790
+ yoff)
791
+ yoff -= yoke_height
792
+ ax.fill(xy, yy, color='skyblue')
793
+
794
+ taum = dy2*np.pi/poles
795
+ ym = np.array([yoff-magn_height,
796
+ yoff-magn_height,
797
+ yoff,
798
+ yoff])
799
+ yoff -= magn_height
800
+
801
+ for n in range(poles//g):
802
+ xl = taum*n + taum*(1 - magn_width)
803
+ xr = taum*(n + 1) - taum*(1 - magn_width)
804
+ xm = (xl, xr, xr, xl)
805
+ ax.fill(xm, ym, color=color[n%2])
806
+
807
+ if iron == IRON_DOWN:
808
+ yy = (yoff-yoke_height,
809
+ yoff-yoke_height,
810
+ yoff,
811
+ yoff)
812
+ yoff -= yoke_height
813
+ ax.fill(xy, yy, color='skyblue')
814
+ return yoff
815
+
816
+
817
+ TOOTH_UP = 0
818
+ TOOTH_DOWN = 1
819
+ TOOTH_ONLY = 2
820
+
821
+
822
+ def _draw_horizontal_slots(ax,
823
+ slot_height, slot_width, yoke_height,
824
+ Q, g, taus,
825
+ yoff=0.0,
826
+ tooth=TOOTH_DOWN):
827
+ if not tooth == TOOTH_ONLY:
828
+ xx = (0, Q//g*taus, Q//g*taus, 0)
829
+ if tooth == TOOTH_DOWN:
830
+ yy = (yoff-yoke_height,
831
+ yoff-yoke_height,
832
+ yoff,
833
+ yoff)
834
+ else:
835
+ yy = (yoff-slot_height,
836
+ yoff-slot_height,
837
+ yoff-slot_height-yoke_height,
838
+ yoff-slot_height-yoke_height)
839
+ ax.fill(xx, yy, color='skyblue')
840
+
841
+ yt = (yoff-slot_height-yoke_height,
842
+ yoff-slot_height-yoke_height,
843
+ yoff, yoff)
844
+ for n in range(Q//g):
845
+ xt = np.array((n*taus, n*taus+(taus-slot_width)/2,
846
+ n*taus+(taus-slot_width)/2, n*taus))
847
+ ax.fill(xt, yt, color='skyblue')
848
+ xt += slot_width + (taus-slot_width)/2
849
+ ax.fill(xt, yt, color='skyblue')
850
+ return yoff - slot_height - yoke_height
851
+
852
+
853
+ def horizontal_plot(machine, ax):
854
+ logger.debug("begin of horizontal_plot()")
855
+
856
+ model_type = machine['afmtype'][0:4]
857
+ dy1 = machine['outer_diam']*1e3
858
+ dy2 = machine['inner_diam']*1e3
859
+ rel_magn_width = max(machine['magnet']['afm_rotor']['rel_magn_width'])
860
+ magn_height = machine['magnet']['afm_rotor']['magn_height']*1e3
861
+ magn_yoke_height = machine['magnet']['afm_rotor']['yoke_height']*1e3
862
+
863
+ Q = machine['stator']['num_slots']
864
+ slot_width = machine['stator']['afm_stator']['slot_width']*1e3
865
+ poles = machine['poles']
866
+ m = 3
867
+ slot_height = machine['stator']['afm_stator']['slot_height']*1e3
868
+ if model_type in ('S2R1', 'S2R1_all'):
869
+ slot_height /= 2
870
+ yoke_height = machine['stator']['afm_stator']['yoke_height']*1e3
871
+ ag = machine['airgap']*1e3
872
+
873
+ g = np.gcd(Q, m*poles)//m
874
+ taus = dy2*np.pi/Q
875
+
876
+ yoff = 0.0
877
+ if model_type in ('S1R2', 'S1R2_all'): # 2 rotor
878
+ yoff = _draw_horizontal_magnets(ax, poles,
879
+ magn_height, rel_magn_width,
880
+ magn_yoke_height,
881
+ Q, g, taus, dy2,
882
+ yoff=yoff,
883
+ iron=IRON_UP)
884
+ yoff -= ag
885
+
886
+ tooth = TOOTH_ONLY if model_type in ('S1R2', 'S1R2_all') else TOOTH_DOWN
887
+ yoff = _draw_horizontal_slots(ax,
888
+ slot_height, slot_width, yoke_height,
889
+ Q, g, taus,
890
+ yoff=yoff,
891
+ tooth=tooth)
892
+ yoff -= ag
893
+
894
+ iron = IRON_DOWN if model_type in ('S1R1', 'S1R2', 'S1R2_all') else IRON_NO
895
+ yoff = _draw_horizontal_magnets(ax, poles,
896
+ magn_height, rel_magn_width,
897
+ magn_yoke_height,
898
+ Q, g, taus, dy2,
899
+ yoff=yoff,
900
+ iron=iron)
901
+ yoff -= ag
902
+
903
+ if model_type in ('S2R1', 'S2R1_all'): # 2 rotor
904
+ yoff = _draw_horizontal_slots(ax,
905
+ slot_height, slot_width, yoke_height,
906
+ Q, g, taus,
907
+ yoff=yoff,
908
+ tooth=TOOTH_UP)
909
+
910
+ _set_plot_attributes(ax)
911
+ logger.debug("end of horizontal_plot()")
912
+
913
+
601
914
  class AFPM:
602
915
  """Axial Flux PM
603
916
  Arguments:
@@ -636,6 +949,14 @@ class AFPM:
636
949
  except KeyError:
637
950
  raise ValueError("missing key afmtype")
638
951
 
952
+ if np.isscalar(machine['magnet']['afm_rotor']['rel_magn_width']):
953
+ rmagw = num_slices*[machine['magnet']['afm_rotor']['rel_magn_width']]
954
+ else:
955
+ rmagw = machine['magnet']['afm_rotor']['rel_magn_width']
956
+ if len(rmagw) == 1:
957
+ rmagw = num_slices*list(rmagw)
958
+ elif num_slices != len(rmagw):
959
+ num_slices = len(rmagw)
639
960
  lfe = get_arm_lengths(machine['outer_diam'],
640
961
  machine['inner_diam'],
641
962
  num_slices)
@@ -659,11 +980,17 @@ class AFPM:
659
980
  "name": "pole_width"},
660
981
  {"values": lfe,
661
982
  "name": "lfe"},
983
+ {"values": rmagw,
984
+ "name": "magnet.afm_rotor.rel_magn_width"},
662
985
  {"values": linspeed, "name": "speed"}
663
986
  ]
664
987
  }
988
+
665
989
  machine['pole_width'] = np.pi * machine['inner_diam']/machine['poles']
666
990
  machine['lfe'] = machine['outer_diam'] - machine['inner_diam']
991
+ machine['magnet']['afm_rotor']['rel_magn_width'] = max(
992
+ machine['magnet']['afm_rotor']['rel_magn_width'])
993
+
667
994
  simulation['skew_displ'] = (simulation.get('skew_angle', 0)/180 * np.pi
668
995
  * machine['inner_diam'])
669
996
  nlresults = {}
@@ -366,7 +366,8 @@ def dqparident(workdir, engine, temp, machine,
366
366
  num_beta_steps: number of current steps (default 7 per quadrant)
367
367
  speed: rotor speed in 1/s (default 160/p)
368
368
  i1_max: maximum current in A rms (default approx 3*i1nom)
369
- period_frac: fraction of rotating angle (default 6)
369
+ period_frac: (int) fraction of rotating angle (default 6)
370
+ dqtype: (str) type of identification: 'ldq' (default), 'psidq'
370
371
  cmd: femag executable
371
372
  """
372
373
  import pathlib
femagtools/multiproc.py CHANGED
@@ -56,7 +56,8 @@ class ProgressLogger(threading.Thread):
56
56
  ["progress_logger",
57
57
  f"{self.numTot}:{numOf}:{percent}:{' '.join(summary)}"])
58
58
  else:
59
- logger.info('collecting FE losses ...')
59
+ # TODO: log message might be misleading
60
+ logger.debug('collecting FE losses ...')
60
61
  return
61
62
 
62
63
  def stop(self):
@@ -1,6 +1,7 @@
1
1
  """Creating plots
2
2
 
3
3
  """
4
+ from .machine import machine
4
5
  from .fluxdens import airgap, airgap_fft
5
6
  from .bch import torque, torque_fft, force, force_fft, \
6
7
  fluxdens_surface, winding_current, winding_flux, \
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
@@ -511,30 +531,62 @@ def transientsc(bch, title=''):
511
531
  ax.grid(True)
512
532
  istat = np.array([bch.scData[i]
513
533
  for i in ('ia', 'ib', 'ic')])
534
+ pv = [find_peaks_and_valleys(
535
+ np.array(bch.scData['time']), i1)
536
+ for i1 in istat]
537
+ try:
538
+ ipvmax = np.argmax(
539
+ [y['yp'] if np.abs(y['yp']) > np.abs(y['yv']) else y['yv']
540
+ for y in pv])
541
+ imax = pv[ipvmax]['yp'] if np.abs(pv[ipvmax]['yp']) > np.abs(pv[ipvmax]['yv']) else pv[ipvmax]['yv']
542
+ except KeyError:
543
+ pass
514
544
  if np.max(istat) > 4000:
515
545
  istat *= 1e-3
546
+ imax *= 1e-3
516
547
  ax.set_title('Currents / kA')
517
548
  else:
518
549
  ax.set_title('Currents / A')
519
550
 
520
551
  for i, iph in zip(('ia', 'ib', 'ic'), istat):
521
552
  ax.plot(bch.scData['time'], iph, label=i)
553
+ try:
554
+ ax.plot([pv[ipvmax]['tp']], [imax], '.')
555
+ ax.annotate(f'Imax = {imax:.1f}',
556
+ xy=(pv[ipvmax]['tp'], imax),
557
+ xytext=(pv[ipvmax]['tp']+0.01, imax))
558
+ except NameError:
559
+ pass
522
560
  ax.set_xlabel('Time / s')
523
561
  ax.legend()
524
562
 
525
563
  row = 2
526
564
  plt.subplot(rows, cols, row)
527
565
  ax = plt.gca()
528
- scale = 1
566
+ pv = find_peaks_and_valleys(
567
+ np.array(bch.scData['time']), np.array(bch.scData['torque']))
568
+ try:
569
+ tqmax = pv['yp'] if np.abs(pv['yp']) > np.abs(pv['yv']) else pv['yv']
570
+ tp = pv['tp'] if np.abs(pv['yp']) > np.abs(pv['yv']) else pv['tv']
571
+ except KeyError:
572
+ pass
529
573
  torque = np.array(bch.scData['torque'])
530
574
  if np.max(torque) > 4000:
531
575
  torque *= 1e-3
576
+ tqmax *= 1e-3
532
577
  ax.set_title('Torque / kNm')
533
578
  else:
534
579
  ax.set_title('Torque / Nm')
535
580
 
536
581
  ax.grid(True)
537
582
  ax.plot(bch.scData['time'], torque)
583
+ try:
584
+ ax.plot([tp], [tqmax], '.')
585
+ ax.annotate(f'Tmax = {tqmax:.1f}',
586
+ xy=(tp, tqmax),
587
+ xytext=(tp+0.01, tqmax))
588
+ except NameError:
589
+ pass
538
590
  ax.set_xlabel('Time / s')
539
591
 
540
592
  fig.tight_layout(h_pad=2)
@@ -560,8 +612,10 @@ def transientsc_demag(demag, magnet=0, title='', ax=0):
560
612
  label='H Max {:4.2f} kA/m'.format(max(hmax)))
561
613
  ax.plot(pos, havg,
562
614
  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]))
615
+ if len(hclim) > 1:
616
+ ax.plot([pos[0], pos[-1]], hclim, color='C3', linestyle='dashed',
617
+ label='Hc {:4.2f} kA/m'.format(hclim[0]))
618
+
565
619
  ax.set_xlabel('Rotor Position / °')
566
620
  ax.grid(True)
567
621
  if magnet:
femagtools/plot/char.py CHANGED
@@ -333,10 +333,14 @@ def _plot_contour(speed, torque, z, ax, title='', levels=[],
333
333
  clippath = Path(_get_nT_boundary(x, y))
334
334
  patch = PathPatch(clippath, facecolor='none')
335
335
  ax.add_patch(patch)
336
- for c in cont.collections:
337
- c.set_clip_path(patch)
338
- for c in contf.collections:
339
- c.set_clip_path(patch)
336
+ try:
337
+ for c in cont.collections:
338
+ c.set_clip_path(patch)
339
+ for c in contf.collections:
340
+ c.set_clip_path(patch)
341
+ except AttributeError: # matplotlib >= 3.10
342
+ cont.set_clip_path(patch)
343
+ contf.set_clip_path(patch)
340
344
 
341
345
  if xscale > 1:
342
346
  def format_fn(tick_val, tick_pos):
@@ -367,9 +371,16 @@ def efficiency_map(rmap, ax=0, title='', clabel=True,
367
371
 
368
372
 
369
373
  def losses_map(rmap, ax=0, title='Losses Map / kW', clabel=True,
370
- cmap='YlOrRd', cbar=False):
374
+ cmap='YlOrRd', cbar=False, key='losses'):
375
+ """
376
+ plot losses map
377
+ Args:
378
+ rmap: (dict) result of efficiency_losses_map
379
+ key: (str) type of losses: 'plfe1', 'plfe2', 'plmag', 'plcu1', 'plcu2', 'plfric', 'losses';
380
+ """
381
+
371
382
  if ax == 0:
372
383
  fig, ax = plt.subplots(figsize=(12, 12))
373
- return _plot_contour(rmap['n'], rmap['T'], np.asarray(rmap['losses'])/1e3, ax,
374
- title=title, levels=14, clabel=clabel,
375
- cmap=cmap, cbar=cbar)
384
+ return _plot_contour(rmap['n'], rmap['T'], np.asarray(rmap[key])/1e3, ax,
385
+ title=title, levels=14, clabel=clabel,
386
+ cmap=cmap, cbar=cbar)