femagtools 1.7.9__py3-none-any.whl → 1.8.0__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.
Files changed (42) hide show
  1. femagtools/__init__.py +1 -1
  2. femagtools/amela.py +2 -2
  3. femagtools/dxfsl/area.py +129 -25
  4. femagtools/dxfsl/conv.py +2 -9
  5. femagtools/dxfsl/converter.py +33 -10
  6. femagtools/dxfsl/fslrenderer.py +6 -12
  7. femagtools/dxfsl/geom.py +31 -9
  8. femagtools/dxfsl/journal.py +2 -2
  9. femagtools/dxfsl/machine.py +14 -13
  10. femagtools/dxfsl/shape.py +3 -0
  11. femagtools/ecloss.py +381 -2
  12. femagtools/femag.py +41 -0
  13. femagtools/fsl.py +31 -50
  14. femagtools/machine/pm.py +1 -1
  15. femagtools/machine/sm.py +14 -0
  16. femagtools/mcv.py +128 -124
  17. femagtools/me.py +13 -13
  18. femagtools/model.py +8 -2
  19. femagtools/plot/fieldlines.py +1 -1
  20. femagtools/plot/mcv.py +18 -0
  21. femagtools/plot/wdg.py +2 -2
  22. femagtools/svgfsl/converter.py +1 -1
  23. femagtools/templates/gen_hairpin_winding.mako +36 -45
  24. femagtools/templates/magnetIron.mako +1 -1
  25. femagtools/templates/magnetIron2.mako +1 -1
  26. femagtools/templates/magnetIron3.mako +1 -1
  27. femagtools/templates/magnetIron4.mako +1 -1
  28. femagtools/templates/magnetIron5.mako +1 -1
  29. femagtools/templates/magnetIronV.mako +1 -1
  30. femagtools/templates/magnetSector.mako +1 -1
  31. femagtools/templates/mesh-airgap.mako +12 -6
  32. femagtools/templates/prepare_thermal.mako +148 -13
  33. femagtools/windings.py +25 -20
  34. {femagtools-1.7.9.dist-info → femagtools-1.8.0.dist-info}/METADATA +20 -20
  35. {femagtools-1.7.9.dist-info → femagtools-1.8.0.dist-info}/RECORD +41 -42
  36. {femagtools-1.7.9.dist-info → femagtools-1.8.0.dist-info}/WHEEL +1 -1
  37. tests/test_mcv.py +106 -1
  38. tests/test_windings.py +5 -0
  39. tests/test_mcvwriter.py +0 -96
  40. {femagtools-1.7.9.dist-info → femagtools-1.8.0.dist-info}/LICENSE +0 -0
  41. {femagtools-1.7.9.dist-info → femagtools-1.8.0.dist-info}/entry_points.txt +0 -0
  42. {femagtools-1.7.9.dist-info → femagtools-1.8.0.dist-info}/top_level.txt +0 -0
femagtools/ecloss.py CHANGED
@@ -1,7 +1,7 @@
1
1
  '''Calculate Magnet Losses with IALH Method
2
2
 
3
3
  '''
4
- __author__ = 'Max Hullmann, Dapu Zhang'
4
+ __author__ = 'Max Hullmann, Dapu Zhang, Ivan Solc'
5
5
 
6
6
  import logging
7
7
  import warnings
@@ -95,6 +95,59 @@ def binterp(x, y, xq, yq, b):
95
95
  inp = f(np.array([[i, j] for i, j in zip(xq, yq)]))
96
96
  return inp.reshape(len(np.unique(yq)), len(np.unique(xq)), -1)
97
97
 
98
+ def binterp_ialh2(x, y, xq, yq, b):
99
+ '''interpolate flux density with Rbf interpolator'''
100
+ f = RBFInterpolator(np.array([[i, j] for i, j in zip(x, y)]), b)
101
+ inp = f(np.array([[i, j] for i, j in zip(xq, yq)]))
102
+ return inp.reshape(len(np.unique(xq)), -1)
103
+
104
+ def Segmentation(wm, hm, lm, elxy, nsegx, nsegy, nsegz):
105
+ ''' Creates new magnet elements' center points' x,y coordinates based on the number of segments and the original data resolution
106
+
107
+ Inputs: total magnet dimensions (width - wm, height - hm, axial length - lm)
108
+ elxy - dictionary with global and local reference frame x,y coordinates of elements centerpoints
109
+ number of magnet segments : nsegx - number of segments in x (wm), nsegy - segments in y = 1 (hm), nsegz - segments in axial direction (lm)
110
+ Returns: number of elements in x direction per magnet segment - nx_new
111
+ number of elements in y direction per magnet segment - ny new
112
+ number of elements in z direction per magnet segment - nz new
113
+ x,y coordinates of each element's centerpoint in local reference frame, for entire magnet - xx, yy
114
+ '''
115
+ # Default nx,ny,nz without considering the segmentation
116
+ be = np.sqrt(wm*hm/elxy['excp'].shape[0]) #square elements
117
+ nx_new = int(np.around(wm/be))
118
+ ny_new = int(np.around(hm/be))
119
+ nz_new = int(np.around(lm/be))
120
+
121
+ if (nsegx > 1) or (nsegy > 1):
122
+ # limits the number of elements per segment to 10 to avoid unnecessarily detailed resolution
123
+ nx_new = int(max(10,int(np.around(nx_new*np.sqrt(1/nsegx)))))
124
+ ny_new = int(max(10,int(np.around(ny_new*np.sqrt(1/nsegy)))))
125
+ nz_new = int(max(10,int(np.around(nz_new*np.sqrt(1/nsegz)))))
126
+
127
+ wms = wm/nsegx
128
+ hms = hm/nsegy
129
+ x0 = 0 # offset for excpl
130
+ y0 = 0 # offset for eycpl
131
+
132
+ segxcpl = np.linspace(wms/2/nx_new, wms - wms/nx_new/2, nx_new) + x0 # x center points of wanted elements distribution, local ref frame, 1st segment
133
+ segycpl = np.linspace(hms/2/ny_new, hms - hms/ny_new/2, ny_new) + y0 # y center points of wanted elements distribution, local ref frame, 1st segment
134
+
135
+ x,y = np.meshgrid(segycpl, segxcpl)
136
+ xx = np.zeros((x.shape[0]*nsegx, x.shape[1]*nsegy)) # new centerpoint coordinates for entire magnet
137
+ yy = np.zeros_like(xx)
138
+ a=np.zeros((x.shape[0]*nsegx))
139
+ b=np.zeros((y.shape[1]*nsegy))
140
+
141
+ for ii in range(nsegx):
142
+ a[ii*nx_new: (ii+1)*nx_new] = segxcpl + wms*ii # generation of x coordinates of elements for each magnet segment
143
+ for jj in range(ny_new*nsegy):
144
+ xx[:,jj] = np.atleast_2d(a)
145
+ for jj in range(nsegy):
146
+ b[jj*ny_new: (jj+1)*ny_new] = segycpl + hms*jj # generation of y coordinates of elements for each magnet segment
147
+ for ii in range(nx_new*nsegx):
148
+ yy[ii,:] = np.atleast_2d(b)
149
+
150
+ return nx_new, ny_new, nz_new, xx, yy
98
151
 
99
152
  class MagnLoss(Amela):
100
153
  '''Calculate Magnet Losses with IALH Methode
@@ -135,7 +188,7 @@ class MagnLoss(Amela):
135
188
 
136
189
  self.segz = kwargs.get('segz', [0])
137
190
  self.is_meter = False
138
- # determine the number of segments in z direction
191
+ # Determine the number of segments in z direction
139
192
  for i in range(len(self.segz)):
140
193
  if self.segz[i] > 0:
141
194
  self.lm = self.ls/self.segz[i]
@@ -386,3 +439,329 @@ class MagnLoss(Amela):
386
439
  all_load_cases.append(ialh_loss)
387
440
 
388
441
  return all_load_cases
442
+
443
+
444
+
445
+ def ialh2(self, sx_ampl, sy_ampl, sx_phase, sy_phase, freq, wm, hm, lm, delta_eff):
446
+ ''' Calculates eddy current losses for each point (sx, sy, f) in one magnet segment, using ialh2 method
447
+
448
+ Inputs: sx_ampl - dBx/dt amplitudes for each element, for each frequency, for that magnet segment
449
+ sy_ampl - dBy/dt amplitudes for each element, for each frequency, for that magnet segment
450
+ sx_phase - x phase of each element for each frequency, for that magnet segment
451
+ sy_phase - y phase of each element for each frequency, for that magnet segment
452
+ freq - every considered frequency in Hertz
453
+ nx, ny - number of elements in each magnet segment
454
+ wm,hm,lm - width (x), height (y), axial length (z) of each magnet segment
455
+ delta_eff - effective airgap between magnet and rotor iron
456
+ Returns: Ploss_x - losses [W] generated from x component of magnetic flux, for a given magnet segment
457
+ Ploss_y - losses [W] generated from y component of magnetic flux, for a given magnet segment
458
+ '''
459
+ sigma_x = self.sigma
460
+ sigma_y = self.sigma
461
+ sigma_z = self.sigma
462
+ delta_xy_x = delta_eff
463
+ delta_xy_y = delta_eff
464
+ delta_yz_y = delta_eff
465
+ delta_xz_x = delta_eff
466
+ epsilon_z_y = 1. + self.mur*delta_xy_y / hm
467
+ epsilon_z_x = 1. + self.mur*delta_xy_x / hm
468
+ epsilon_x_y = 1. + self.mur*delta_yz_y / hm
469
+ epsilon_y_x = 1. + self.mur*delta_xz_x / hm
470
+ rx = sigma_z/sigma_x
471
+ ry = sigma_z/sigma_y
472
+ p0 = 0.5*hm*lm*wm**3*sigma_z
473
+
474
+ (nx, ny, nf) = sx_ampl.shape
475
+
476
+ Ploss_x = np.zeros((nx,ny,nf))
477
+ Ploss_y = np.zeros_like(Ploss_x)
478
+
479
+ for f in range (nf): #loop over each frequency
480
+ epsilon = wm*np.sqrt(MUR0*self.mur*sigma_z/epsilon_z_y*np.pi*freq[f])
481
+
482
+ for n in range (int(nx/2)):
483
+ for m in range (int(ny/2)):
484
+ symmetry = 4 if (m > 0) & (n > 0) else 2 # symmetry factor 4 due to double symmetry utilization.
485
+
486
+ for k in range (0,1000,2): # loop is symmetrical on one side
487
+ complex_xnmkf = sx_ampl[n,m,f]*2/(np.pi*(k+1))*epsilon_z_x/epsilon_z_y*(m*np.pi*wm/hm)/(2j *epsilon**2 + (n*np.pi)**2 + (m*np.pi*wm/hm)**2 + ry*((k+1)*np.pi*wm/lm)**2)
488
+ complex_ynmkf = sy_ampl[n,m,f]*2/(np.pi*(k+1)) *n*np.pi/(2j *epsilon**2 + (n*np.pi)**2 + (m*np.pi*wm/hm)**2 + rx*((k+1)*np.pi*wm/lm)**2)
489
+ real_x_nmkf = np.real(complex_xnmkf)
490
+ imag_x_nmkf = np.imag(complex_xnmkf)
491
+ real_y_nmkf = np.real(complex_ynmkf)
492
+ imag_y_nmkf = np.imag(complex_ynmkf)
493
+
494
+ # Loss component x
495
+ Plossxnmkf = 2.*symmetry*p0*((sx_ampl[n,m,f]*2/(np.pi*(k+1))*epsilon_y_x/epsilon_z_y)**2*ry
496
+ *((k+1)*np.pi*wm/lm)**2/(4*epsilon**4 + ((n*np.pi)**2 + (m*np.pi*wm/hm)**2 + ry*((k+1)*np.pi*wm/lm)**2)**2) + real_x_nmkf**2 + imag_x_nmkf**2
497
+ - 2*np.cos(sy_phase[n,m,f] - sx_phase[n,m,f])*(real_y_nmkf*real_x_nmkf + imag_y_nmkf*imag_x_nmkf)
498
+ - 2*np.sin(sy_phase[n,m,f] - sx_phase[n,m,f])*(real_y_nmkf*imag_x_nmkf - imag_y_nmkf*real_x_nmkf))
499
+ # Loss component y
500
+ Plossynmkf = 2.*symmetry*p0*((sy_ampl[n,m,f]*2/(np.pi*(k+1))*epsilon_x_y/epsilon_z_y)**2*rx
501
+ *((k+1)*np.pi*wm/lm)**2/(4*epsilon**4 + ((n*np.pi)**2 + (m*np.pi*wm/hm)**2 + rx*((k+1)*np.pi*wm/lm)**2)**2) + real_y_nmkf**2 + imag_y_nmkf**2)
502
+
503
+ if (Ploss_x[n,m,f] + Ploss_y[n,m,f]) == 0: # preventing division by zero in termination criteria evaluation
504
+ Ploss_x[n,m,f] += Plossxnmkf
505
+ Ploss_y[n,m,f] += Plossynmkf
506
+ continue
507
+ # termination criteria for k loop -> amplitude proportional to 1/k^2
508
+ if (k > 1) & ((Plossxnmkf + Plossynmkf)/(Ploss_x[n,m,f] + Ploss_y[n,m,f]) < 1e-4):
509
+ Ploss_x[n,m,f] += Plossxnmkf
510
+ Ploss_y[n,m,f] += Plossynmkf
511
+ break
512
+
513
+ Ploss_x[n,m,f] += Plossxnmkf
514
+ Ploss_y[n,m,f] += Plossynmkf
515
+
516
+ return np.sum(Ploss_x), np.sum(Ploss_y)
517
+
518
+ def diffDQ(self, bx_pm_3D, by_pm_3D, T):
519
+ ''' Calculates the time derivative of Bx, By
520
+
521
+ Inputs: bx_pm_3D - bx (AC component) for each element for one magnet segment, for each simulation step
522
+ by_pm_3D - by (AC component) for each element for one magnet segment, for each simulation step
523
+ T - total simulation time for periodic simulation (see periodicity_id fcn for more)
524
+ Returns: sx_pm - dBx/dt for each element for one magnet segment, for each simulation step
525
+ sy_pm - dBy/dt for each element for one magnet segment, for each simulation step
526
+ '''
527
+ (nx, ny, nt) = bx_pm_3D.shape
528
+ sx_pm_3D = np.zeros((nx, ny, nt))
529
+ sy_pm_3D = np.zeros_like(sx_pm_3D)
530
+
531
+ ti = np.linspace(0, T, nt)
532
+ timestep = ti[1] - ti[0]
533
+
534
+ sx_pm_3D = -np.diff(bx_pm_3D, n=1, axis=-1, append=np.reshape(bx_pm_3D[:,:,1], (nx, ny, 1)))/timestep
535
+ sy_pm_3D = -np.diff(by_pm_3D, n=1, axis=-1, append=np.reshape(by_pm_3D[:,:,1], (nx, ny, 1)))/timestep
536
+
537
+ return sx_pm_3D, sy_pm_3D
538
+
539
+ def diffFreqD(self, bx_pm_3D, by_pm_3D, T):
540
+ ''' Calculates the time derivative of Bx, By, advanced method
541
+
542
+ Inputs: bx_pm_3D - bx (AC component) for each element for one magnet segment, for each simulation step
543
+ by_pm_3D - by (AC component) for each element for one magnet segment, for each simulation step
544
+ T - total simulation time for periodic simulation (see periodicity_id fcn for more)
545
+ Returns: sx_pm - dBx/dt for each element for one magnet segment, for each simulation step
546
+ sy_pm - dBy/dt for each element for one magnet segment, for each simulation step
547
+ '''
548
+
549
+ (nx, ny, nt) = bx_pm_3D.shape
550
+ sx_pm_3D = np.zeros((nx, ny, nt))
551
+ sy_pm_3D = np.zeros((nx, ny, nt))
552
+ ti = np.linspace(0, T, nt)
553
+ timestep = ti[1] - ti[0]
554
+
555
+ freq = np.fft.rfftfreq(nt-1, timestep)
556
+ nf = freq.shape[0]
557
+ amplbx = np.zeros((freq.shape))
558
+ amplby = np.zeros((freq.shape))
559
+ complbx = np.zeros((nx, ny, nf)).astype(complex)
560
+ complby = np.zeros((nx, ny, nf)).astype(complex)
561
+
562
+ for ii in range(nx):
563
+ for jj in range (ny):
564
+ complbx[ii,jj,:] = np.fft.rfftn(bx_pm_3D[ii,jj,0:nt-1])
565
+ complby[ii,jj,:] = np.fft.rfftn(by_pm_3D[ii,jj,0:nt-1])
566
+ amplbx = amplbx + abs(complbx[ii,jj,0:nf])/nf
567
+ amplby = amplby + abs(complby[ii,jj,0:nf])/nf
568
+
569
+ amplbx = amplbx/(nx*ny)
570
+ amplby = amplby/(nx*ny)
571
+ amplb = np.sqrt(amplbx**2 + amplby**2)
572
+ fmax2 = 0.5*freq[-1]
573
+
574
+ if sum(amplb) > 0:
575
+ pec = (np.multiply(amplb,freq))**2
576
+ pecmax = np.max(pec)
577
+ pec = pec/pecmax
578
+
579
+ fecmax = fmax2
580
+ imax = int(np.floor(nf/2))
581
+ filt0 = np.ones((nf))
582
+ for ii in range(imax,nf):
583
+ filt0[ii] = fecmax/freq[ii]
584
+
585
+ # determine the last significant frequency
586
+ pecf = pec*filt0
587
+ ilim = 0
588
+ feclim = 0
589
+ Ath = 0.05
590
+ for ii in range(1,nf):
591
+ jj = nf - 1 - ii
592
+ if pecf[jj] - pecf[jj + 1] > Ath:
593
+ ilim = jj
594
+ feclim = freq[jj]
595
+ break
596
+
597
+ filt = np.ones((nf))
598
+ for ii in range(ilim,nf):
599
+ filt[ii] = (feclim/freq[ii])
600
+ for ii in range(nx): # Derivation in frequency domain
601
+ for jj in range(ny):
602
+ complbx[ii,jj,:] = -complbx[ii,jj,:]*freq*filt*np.pi*2j
603
+ complby[ii,jj,:] = -complby[ii,jj,:]*freq*filt*np.pi*2j
604
+
605
+ for ii in range (nx): # Inverse Fourier-Transformation
606
+ for jj in range (ny):
607
+ sx_pm_3D[ii,jj,0:-1] = np.fft.irfftn(complbx[ii,jj,:])
608
+ sy_pm_3D[ii,jj,0:-1] = np.fft.irfftn(complby[ii,jj,:])
609
+ sx_pm_3D[ii,jj,nt-1] = sx_pm_3D[ii,jj,0]
610
+ sy_pm_3D[ii,jj,nt-1] = sy_pm_3D[ii,jj,0]
611
+
612
+ return sx_pm_3D, sy_pm_3D
613
+
614
+ def Process_B_data(self, nx, ny, nsegx, nsegy, nt, elxy, bxy, excpl_new, eycpl_new):
615
+ ''' Processes flux density data: interpolates Bx, By to new resolution defined in Segmentation fcn
616
+ calculates the dB/dt for x,y axes
617
+ calculates the FFT of those derivations
618
+
619
+ Inputs: nx,ny - number of elements for each magnet segment (inherited from Segmentation fcn)
620
+ nsegx,nsegy - number of magnet segments in x,y axis
621
+ nt - number of time steps (inherited from result of periodicity function), corresponds to 1 period
622
+ elxy - dictionary with original excpl,eycpl
623
+ bxy - dictionary with original flux denisities bxl, byl - bx, by in local reference frame
624
+ excpl_new, eycpl_new - x,y coordinates for new elements (inherited from Segmentation fcn)
625
+ Returns: sx_abs - dBx/dt amplitudes for each element, for each frequency, for entire magnet
626
+ sy_abs - dBy/dt amplitudes for each element, for each frequency, for entire magnet
627
+ sx_phase - x phase of each element for each frequency, for entire magnet
628
+ sy_phase - y phase of each element for each frequency, for entire magnet
629
+ freq_range - every considered frequency in Hertz
630
+ '''
631
+ nx_tot = int(nx*nsegx)
632
+ ny_tot = int(ny*nsegy)
633
+
634
+ Bxl_ac = np.zeros((np.asarray(bxy['bxl']).shape))
635
+ Byl_ac = np.zeros_like(Bxl_ac)
636
+
637
+ # Remove the DC component of the original bxl, byl
638
+ for ii in range(Bxl_ac.shape[0]):
639
+ Bxl_ac[ii,:] = bxy['bxl'][ii,:] - np.mean(bxy['bxl'][ii,:])
640
+ for ii in range(Byl_ac.shape[0]):
641
+ Byl_ac[ii,:] = bxy['byl'][ii,:] - np.mean(bxy['byl'][ii,:])
642
+
643
+ xx_ = excpl_new.ravel()
644
+ yy_ = eycpl_new.ravel()
645
+ bx_3d_ac = np.zeros((nx_tot,ny_tot,nt))
646
+ by_3d_ac = np.zeros_like(bx_3d_ac)
647
+
648
+ # Interpolation to the new resolution -> [nx*nsegx, ny*nsegy, nt]
649
+ by_3d_ac = binterp_ialh2(elxy['excpl'], elxy['eycpl'], xx_, yy_, Byl_ac[:, 0:nt])
650
+ if self.is_x:
651
+ bx_3d_ac = binterp_ialh2(elxy['excpl'], elxy['eycpl'], xx_, yy_, Bxl_ac[:, 0:nt])
652
+ bx_3d_ac = bx_3d_ac.reshape(nx_tot,ny_tot,nt)
653
+ by_3d_ac = by_3d_ac.reshape(nx_tot,ny_tot,nt)
654
+
655
+ xx_ = xx_.reshape(nx_tot, ny_tot)
656
+ yy_ = yy_.reshape(nx_tot, ny_tot)
657
+
658
+ if nt % 2:
659
+ nf = int((nt - 1)/2 + 1)
660
+ else:
661
+ nf = int(nt / 2)
662
+
663
+ sx_abs = np.zeros((2*nx_tot, 2*ny_tot, nf))
664
+ sy_abs = np.zeros_like(sx_abs)
665
+ sx_phase = np.zeros_like(sx_abs)
666
+ sy_phase = np.zeros_like(sx_phase)
667
+ for ii in range (nsegx):
668
+ for jj in range (nsegy):
669
+
670
+ diffgrad = 3 # choose the derivation metod. diffFreqD method recommended
671
+ if diffgrad == 1:
672
+ sx_pm, sy_pm = self.diffDQ(bx_3d_ac[ii*nx:(ii+1)*nx, jj*ny:(jj+1)*ny,:],
673
+ by_3d_ac[ii*nx:(ii+1)*nx, jj*ny:(jj+1)*ny,:],
674
+ self.tgrid)
675
+ else:
676
+ sx_pm, sy_pm = self.diffFreqD(bx_3d_ac[ii*nx:(ii+1)*nx, jj*ny:(jj+1)*ny,:],
677
+ by_3d_ac[ii*nx:(ii+1)*nx, jj*ny:(jj+1)*ny,:],
678
+ self.tgrid)
679
+
680
+ Gx_seg = np.zeros((2*nx, 2*ny, nt-1)) # omit the last step of the time vector -> duplicated with the first step
681
+ Gx_seg[0:nx, 0:ny,:] = +sx_pm[:,:, 0:-1]
682
+ Gx_seg[0:nx,ny:,:] = -sx_pm[:,::-1, 0:-1] # this section flips and "doubles" the data for each segment, so FFT can include every point properly
683
+ Gx_seg[nx:,:,:] = np.flipud(Gx_seg[0:nx,:,:])
684
+
685
+ Gy_seg = np.zeros((2*nx, 2*ny, nt-1)) # omit the last step of time vector -> duplicated with the first step
686
+ Gy_seg[0:nx, 0:ny,:] = +sy_pm[:,:, 0:-1]
687
+ Gy_seg[nx:,0:ny,:] = -sy_pm[::-1,:, 0:-1] # this section flips and "doubles" the data for each segment, so FFT can include every point properly
688
+ Gy_seg[:,ny:,:] = np.fliplr(Gy_seg[:,0:ny,:])
689
+
690
+ sx_FFT_seg = np.fft.rfftn(Gx_seg)
691
+ sy_FFT_seg = np.fft.rfftn(Gy_seg)
692
+ sx_abs_seg = 2*abs(sx_FFT_seg /(2*nx*2*ny*(nt - 1))) # dBx/dt amplitudes for each segment element, for each frequency for that magnet segment
693
+ sy_abs_seg = 2*abs(sy_FFT_seg /(2*nx*2*ny*(nt - 1))) # dBy/dt amplitudes for each segment element, for each frequency for that magnet segment
694
+ sx_phase_seg = np.angle(sx_FFT_seg)
695
+ sy_phase_seg = np.angle(sy_FFT_seg)
696
+ freq_range = np.fft.rfftfreq(nt-1, self.tgrid/(nt-1)) # every considered frequency in Hertz
697
+
698
+ sx_abs[2*ii*nx:2*(ii+1)*nx, 2*jj*ny:2*(jj+1)*ny,:] = sx_abs_seg # dBx/dt amplitudes for each element, for each frequency, for entire magnet
699
+ sy_abs[2*ii*nx:2*(ii+1)*nx, 2*jj*ny:2*(jj+1)*ny,:] = sy_abs_seg # dBy/dt amplitudes for each element, for each frequency, for entire magnet
700
+ sx_phase[2*ii*nx:2*(ii+1)*nx, 2*jj*ny:2*(jj+1)*ny,:] = sx_phase_seg # x phase of each element for each frequency, for entire magnet
701
+ sy_phase[2*ii*nx:2*(ii+1)*nx, 2*jj*ny:2*(jj+1)*ny,:] = sy_phase_seg # y phase of each element for each frequency, for entire magnet
702
+
703
+ return sx_abs, sy_abs, sx_phase, sy_phase, freq_range
704
+
705
+ def loss_ialh2(self, sx_abs, sy_abs, sx_phase, sy_phase, freq_range, nx, ny, wm, hm, lm, nsegx, nsegy, nsegz, delta_eff):
706
+ ''' Loops over each magnet segment and calculates the losses for the entire magnet
707
+
708
+ Inputs: sx_abs, sy_abs - dBx/dt, dBy/dt amplitudes for each element, for each frequency, in entire magnet
709
+ sx_phase, sy_phase - corresponding phases of each freq for each element in entire magnet
710
+ freq_range - every considered frequency in Hz
711
+ nx, ny - number of elements in each magnet segment in x and y direction
712
+ wm, hm, lm - total magnet width (x), height (y), axial length (z)
713
+ nsegx, nsegy, nsegz - number of magnet segments in x,y,z direction
714
+ delta_eff - needed for ialh2 losses calculation, effective airgap between magnet and rotor iron
715
+ Returns: total eddy current losses for entire magnet (all segments), for 1 magnet of the machine
716
+ '''
717
+ pec = np.zeros((nsegx, nsegy, nsegz))
718
+
719
+ for ii in range(nsegx):
720
+ for jj in range(nsegy): # nsegy is always = 1
721
+ for kk in range(nsegz):
722
+ Plossx, Plossy = self.ialh2(sx_abs[2*ii*nx:2*(ii+1)*nx, 2*jj*ny:2*(jj+1)*ny,:], sy_abs[2*ii*nx:2*(ii+1)*nx, 2*jj*ny:2*(jj+1)*ny,:],
723
+ sx_phase[2*ii*nx:2*(ii+1)*nx, 2*jj*ny:2*(jj+1)*ny,:], sy_phase[2*ii*nx:2*(ii+1)*nx, 2*jj*ny:2*(jj+1)*ny,:],
724
+ freq_range, wm/nsegx, hm/nsegy, lm/nsegz, delta_eff)
725
+ pec[ii,jj,kk] = (Plossx + Plossy)
726
+
727
+ return np.sum(pec) # total eddy current losses for the entire magnet (all segments)
728
+
729
+
730
+ def calc_losses_ialh2(self, nsegx=1, nsegy=1, nsegz=1):
731
+ ''' Calculates magnet losses for every load case
732
+
733
+ Inputs: number of magnet segments in x,y,z direction
734
+ Returns: all_load_cases: list of losses for all load cases
735
+ '''
736
+
737
+ nsegx = max(1,nsegx) # 1 = no segmentation
738
+ nsegz = max(1,nsegz) # 1 = no segmentation
739
+ if nsegy != 1:
740
+ nsegy = 1 # y segmentation not supported, nsegy is always = 1
741
+
742
+ delta_eff = 0
743
+
744
+ all_load_cases = []
745
+ for k in self.pm: # loop for each load case
746
+ ialh_loss = 0
747
+ loss_detail = []
748
+ for i in k: # loop for each superelement in a case
749
+ logger.info(f'magnet width and height: {i["wm"]:.2f}mm {i["hm"]:.2f}mm')
750
+ logger.info(f'number of magnet segments: x: {nsegx:.0f} y: {nsegy:.0f} z: {nsegz:.0f}')
751
+ (nt, bx_fft, by_fft) = self.periodicity_id(i['bl']) # finds the time periodic part of the simulation
752
+ (nx, ny, nz, excpl_new, eycpl_new) = Segmentation(i['wm'], i['hm'], i['lm'], i['elcp'], nsegx, nsegy, nsegz)
753
+ # conversion from mm to m for wm,hm,lm
754
+ wm = i['wm']/1000
755
+ hm = i['hm']/1000
756
+ lm = i['lm']/1000
757
+ self.consider_bx(wm, hm, bx_fft, by_fft)
758
+ (sx_abs, sy_abs, sx_phase, sy_phase, freq_range) = self.Process_B_data(nx, ny, nsegx, nsegy, nt, i['elcp'], i['bl'], excpl_new, eycpl_new)
759
+ loss = self.loss_ialh2(sx_abs, sy_abs, sx_phase, sy_phase, freq_range, nx, ny, wm, hm, lm, nsegx, nsegy, nsegz, delta_eff) * self.numpoles
760
+ ialh_loss += loss
761
+ #print(f'Loadcase {i['loadcase']}, Superelement {i['spel_key']}, Total losses = {loss:.3f} W')
762
+ loss_detail.append([i['spel_key'], loss/self.numpoles])
763
+ self.th_loss.append(loss_detail)
764
+ all_load_cases.append(ialh_loss)
765
+
766
+ return all_load_cases
767
+
femagtools/femag.py CHANGED
@@ -359,6 +359,43 @@ class BaseFemag(object):
359
359
 
360
360
  return dict()
361
361
 
362
+ def read_hsn(self, modelname=None):
363
+ "read heat network result"
364
+ _map = {
365
+ "StZa": "plfe1",
366
+ "outs": "-",
367
+ "StJo": "plfe1",
368
+ "Slot": "-",
369
+ "Shaf": "-",
370
+ "Iron": "plfe2",
371
+ "PMag": "plmag",
372
+ "PMag_1": "plmag",
373
+ "PMag_2": "plmag",
374
+ "PMag_3": "plmag",
375
+ "PMag_4": "plmag",
376
+ "W1 ": "plcu1",
377
+ "W2 ": "plcu1",
378
+ "W3 ": "plcu1"
379
+ }
380
+ if not modelname:
381
+ modelname = self._get_modelname_from_log()
382
+ hsn_list = sorted(glob.glob(os.path.join(
383
+ self.workdir, modelname+'.hsn')))
384
+ with open(hsn_list[-1], 'r') as f:
385
+ hsn_data = json.load(f)
386
+ pmag_index = []
387
+ if "Nodes" in hsn_data:
388
+ for k ,i in enumerate(hsn_data['Nodes']):
389
+ i.update({"mass": i['weight'], "losses": _map[i['Name']]})
390
+ if "PMag" in i['Name']:
391
+ pmag_index.append(k)
392
+ if pmag_index:
393
+ for i in range(len(pmag_index)):
394
+ hsn_data["Nodes"][pmag_index[i]]['Name'] = f"PMag_{i+1}"
395
+ with open(hsn_list[-1], 'w') as f:
396
+ json.dump(hsn_data, f)
397
+ return hsn_data
398
+
362
399
  def _get_modelname_from_log(self):
363
400
  """
364
401
  Read the modelname from the Femag Log file
@@ -402,6 +439,10 @@ class BaseFemag(object):
402
439
  return {'t': ttemp[0], 'temperature': ttemp[1]}
403
440
 
404
441
  if simulation['calculationMode'] == 'hsn':
442
+ try:
443
+ hsn_result = self.read_hsn()
444
+ except:
445
+ pass
405
446
  model = self.read_nc()
406
447
  return model.get_minmax_temp()
407
448
 
femagtools/fsl.py CHANGED
@@ -95,12 +95,12 @@ class Builder:
95
95
  if templ in ('statorFsl', 'dxf'):
96
96
  self.fsl_stator = True
97
97
 
98
- if templ != 'dxffile':
98
+ if templ not in ('dxffile', 'svgfile'):
99
99
  return
100
100
 
101
101
  from femagtools.dxfsl.converter import convert
102
102
  logger.info("Conv stator from %s",
103
- model.stator['dxffile']['name'])
103
+ model.stator[templ]['name'])
104
104
  params = {}
105
105
  params['split'] = model.stator[templ].get('split', False)
106
106
  params['show_plots'] = model.stator[templ].get('plot', False)
@@ -109,7 +109,7 @@ class Builder:
109
109
  params['nodedist'] = model.stator.get('nodedist', 1)
110
110
  pos = 'in' if model.external_rotor else 'out'
111
111
  params['part'] = ('stator', pos)
112
- conv = convert(model.stator['dxffile']['name'], **params)
112
+ conv = convert(model.stator[templ]['name'], **params)
113
113
 
114
114
  model.stator['num_slots'] = conv.get('tot_num_slot')
115
115
  model.set_value('poles', conv.get('num_poles'))
@@ -311,7 +311,7 @@ class Builder:
311
311
  if templ == 'dxf':
312
312
  # reuse dxfsl model
313
313
  self.fsl_rotor = True
314
- if templ != 'dxffile':
314
+ if templ not in ('dxffile', 'svgfile'):
315
315
  return
316
316
 
317
317
  from femagtools.dxfsl.converter import convert
@@ -367,7 +367,9 @@ class Builder:
367
367
  'beta = 360*m.npols_gen/m.num_poles',
368
368
  'x3,y3 = pd2c(dy1/2,beta+m.zeroangl)',
369
369
  'x4,y4 = pd2c(dy2/2,beta+m.zeroangl)',
370
- 'def_bcond_tp(x1,y1,x2,y2,x3,y3,x4,y4, 4)',
370
+ 'if m.b_min == 0 then',
371
+ ' def_bcond_tp(x1,y1,x2,y2,x3,y3,x4,y4, 4)',
372
+ 'end',
371
373
  'state_of_problem("mag_static")']
372
374
  return fslcmds
373
375
  return []
@@ -433,12 +435,12 @@ class Builder:
433
435
  return []
434
436
 
435
437
  def create_gen_winding(self, model):
436
- try:
438
+ try:
437
439
  model.winding['wire'].update(
438
440
  {"num_layers": model.winding["num_layers"]})
439
441
  genwdg = self.__render(model.winding,
440
442
  'gen_' + model.winding['wire'].get('name'))
441
- except:
443
+ except:
442
444
  genwdg = self.__render(model, 'gen_winding')
443
445
 
444
446
  k = list({'leak_dist_wind',
@@ -467,30 +469,31 @@ class Builder:
467
469
  'file_leak:close()'])
468
470
  return genwdg
469
471
 
470
- def prepare_model_with_dxf(self, model):
472
+ def prepare_model_with_dxf_or_svg(self, model):
471
473
  from femagtools.dxfsl.converter import convert
472
- dxfname = model.dxffile.get('name', None)
473
- if not dxfname:
474
- logger.error('Name of dxf-file expected')
474
+ fmt = model.dxffile if hasattr(model, 'dxffile') else model.svgfile
475
+ fname = fmt.get('name', None)
476
+ if not fname:
477
+ logger.error('Name of dxf or svg file expected')
475
478
  return []
476
479
 
477
- if dxfname.split('.')[-1] not in ('dxf', 'svg'): # add svg support
478
- dxfname += '.dxf'
479
- if not os.path.isfile(dxfname):
480
- logger.error('File "%s" not found', dxfname)
481
- raise ValueError(f'File {dxfname} not found')
480
+ if fname.split('.')[-1] not in ('dxf', 'svg'): # add svg support
481
+ fname += fmt[:3]
482
+ if not os.path.isfile(fname):
483
+ logger.error('File "%s" not found', fname)
484
+ raise ValueError(f'File {fname} not found')
482
485
 
483
486
  params = {}
484
- params['split'] = model.dxffile.get('split', False)
485
- params['show_plots'] = model.dxffile.get('plot', False)
487
+ params['split'] = fmt.get('split', False)
488
+ params['show_plots'] = fmt.get('plot', False)
486
489
  params['write_fsl'] = True
487
- params['airgap'] = model.dxffile.get('airgap', 0.0)
488
- params['nodedist'] = model.dxffile.get('nodedist', 1)
489
- params['full_model'] = model.dxffile.get('full_model', False)
490
- params['EESM'] = model.dxffile.get('type', 'PMSM') == 'EESM'
490
+ params['airgap'] = fmt.get('airgap', 0.0)
491
+ params['nodedist'] = fmt.get('nodedist', 1)
492
+ params['full_model'] = fmt.get('full_model', False)
493
+ params['EESM'] = fmt.get('type', 'PMSM') == 'EESM'
491
494
  if params['EESM']:
492
495
  model.rotor['EESM'] = {}
493
- conv = convert(dxfname, **params)
496
+ conv = convert(fname, **params)
494
497
 
495
498
  model.set_value('poles', conv.get('num_poles'))
496
499
  model.set_value('outer_diam', conv.get('dy1') * 1e-3)
@@ -524,7 +527,7 @@ class Builder:
524
527
  self.fsl_stator = True
525
528
  th_props = [' ']
526
529
  if model.stator.get('thcond', 0):
527
- th_props = [f'stator_density = {1e3*model.stator["density"]}',
530
+ th_props = [f'stator_density = {model.stator["density"]}',
528
531
  f'stator_thcond = {model.stator["thcond"]}',
529
532
  f'stator_thcap = {model.stator["thcap"]}',
530
533
  ]
@@ -538,10 +541,10 @@ class Builder:
538
541
  if 'fsl_rotor' in conv:
539
542
  self.fsl_rotor = True
540
543
  th_props = ['']
544
+ logger.info(model['magnet'])
541
545
  if hasattr(model, 'magnet'):
542
546
  if model['magnet'].get('thcond', 0):
543
- logger.info(model['magnet'])
544
- th_props = [f'rotor_density = {1e3*model["magnet"]["density"]}',
547
+ th_props = [f'rotor_density = {model["magnet"]["density"]}',
545
548
  f'rotor_thcond = {model["magnet"]["thcond"]}',
546
549
  f'rotor_thcap = {model["magnet"]["thcap"]}'
547
550
  ]
@@ -575,8 +578,8 @@ class Builder:
575
578
  magnetMat['magntemp'] = 20
576
579
  if model.is_complete():
577
580
  logger.info("create new model '%s'", model.name)
578
- if model.is_dxffile():
579
- self.prepare_model_with_dxf(model)
581
+ if model.is_dxffile() or model.is_svgfile():
582
+ self.prepare_model_with_dxf_or_svg(model)
580
583
  else:
581
584
  self.prepare_stator(model)
582
585
  if hasattr(model, 'magnet'):
@@ -617,28 +620,6 @@ class Builder:
617
620
  f'magn_thcap = {model["magnet"]["thcap_magnet"]}'
618
621
  ]
619
622
  rotor += th_props
620
- if model.is_dxffile() or 'dxf' in model['magnet']:
621
- rotor += ['if x0_shaft == 0.0 then',
622
- '-- add air layer (inside) for heat transfer',
623
- ' h = dy2/2/3',
624
- ' if h > 5 then',
625
- ' h = 3.8',
626
- ' end ',
627
- ' if m.zeroangl == nil then ',
628
- ' m.zeroangl = 0.0',
629
- ' end',
630
- ' beta = 360*m.npols_gen/m.num_poles',
631
- ' x0, y0 = pd2c(dy2/2, m.zeroangl)',
632
- ' x1, y1 = pd2c(dy2/2-h, m.zeroangl)',
633
- ' x2, y2 = pd2c(dy2/2-h, beta+m.zeroangl)',
634
- ' x3, y3 = pd2c(dy2/2, beta+m.zeroangl)',
635
- ' nc_line(x0, y0, x1, y1, 0)',
636
- ' nc_circle(x1, y1, x2, y2, 0)',
637
- ' nc_line(x2, y2, x3, y3, 0)',
638
- ' x0, y0 = pd2c(dy2/2-h/2, beta/2+m.zeroangl)',
639
- ' create_mesh_se(x0, y0)',
640
- 'end'
641
- ]
642
623
  else:
643
624
  rotor = self.create_rotor_model(
644
625
  model, condMat, ignore_material)
femagtools/machine/pm.py CHANGED
@@ -148,7 +148,7 @@ class PmRelMachine(object):
148
148
  kr = self.zeta1[0]*freq**3 + self.zeta1[1]*freq**2 + \
149
149
  self.zeta1[2]*freq + self.zeta1[3]
150
150
  kr[kr<1] = 1.
151
- return self.r1*(1 - self.kth1*(self.tcu1 - 20))*kr # ref 20°C
151
+ return self.r1*(1 + self.kth1*(self.tcu1 - 20))*kr # ref 20°C
152
152
  if self.skin_resistance is not None:
153
153
  return self.skin_resistance(self.r1, w, self.tcu1, kth=self.kth1)
154
154
 
femagtools/machine/sm.py CHANGED
@@ -339,6 +339,20 @@ class SynchronousMachine(object):
339
339
 
340
340
  def rstat(self, w):
341
341
  """stator resistance"""
342
+ if isinstance(self.zeta1, list):
343
+ # polyfit from ac loss calculation
344
+ freq = w/2/np.pi
345
+ kr = self.zeta1[0]*freq**3 + self.zeta1[1]*freq**2 + \
346
+ self.zeta1[2]*freq + self.zeta1[3]
347
+ if isinstance(kr, list):
348
+ kr = np.array(kr)
349
+ kr[kr<1.0] = 1.0
350
+ elif isinstance(kr, np.ndarray):
351
+ kr[kr<1.0] = 1.0
352
+ else:
353
+ if kr < 1.0:
354
+ kr = 1.0
355
+ return self.r1*(1 + self.kth1*(self.tcu1 - 20))*kr # ref 20°C
342
356
  sr = self.skin_resistance[0]
343
357
  if sr is not None:
344
358
  return sr(self.r1, w, self.tcu1, kth=self.kth1)