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.
- femagtools/__init__.py +1 -1
- femagtools/amela.py +2 -2
- femagtools/dxfsl/area.py +129 -25
- femagtools/dxfsl/conv.py +2 -9
- femagtools/dxfsl/converter.py +33 -10
- femagtools/dxfsl/fslrenderer.py +6 -12
- femagtools/dxfsl/geom.py +31 -9
- femagtools/dxfsl/journal.py +2 -2
- femagtools/dxfsl/machine.py +14 -13
- femagtools/dxfsl/shape.py +3 -0
- femagtools/ecloss.py +381 -2
- femagtools/femag.py +41 -0
- femagtools/fsl.py +31 -50
- femagtools/machine/pm.py +1 -1
- femagtools/machine/sm.py +14 -0
- femagtools/mcv.py +128 -124
- femagtools/me.py +13 -13
- femagtools/model.py +8 -2
- femagtools/plot/fieldlines.py +1 -1
- femagtools/plot/mcv.py +18 -0
- femagtools/plot/wdg.py +2 -2
- femagtools/svgfsl/converter.py +1 -1
- femagtools/templates/gen_hairpin_winding.mako +36 -45
- femagtools/templates/magnetIron.mako +1 -1
- femagtools/templates/magnetIron2.mako +1 -1
- femagtools/templates/magnetIron3.mako +1 -1
- femagtools/templates/magnetIron4.mako +1 -1
- femagtools/templates/magnetIron5.mako +1 -1
- femagtools/templates/magnetIronV.mako +1 -1
- femagtools/templates/magnetSector.mako +1 -1
- femagtools/templates/mesh-airgap.mako +12 -6
- femagtools/templates/prepare_thermal.mako +148 -13
- femagtools/windings.py +25 -20
- {femagtools-1.7.9.dist-info → femagtools-1.8.0.dist-info}/METADATA +20 -20
- {femagtools-1.7.9.dist-info → femagtools-1.8.0.dist-info}/RECORD +41 -42
- {femagtools-1.7.9.dist-info → femagtools-1.8.0.dist-info}/WHEEL +1 -1
- tests/test_mcv.py +106 -1
- tests/test_windings.py +5 -0
- tests/test_mcvwriter.py +0 -96
- {femagtools-1.7.9.dist-info → femagtools-1.8.0.dist-info}/LICENSE +0 -0
- {femagtools-1.7.9.dist-info → femagtools-1.8.0.dist-info}/entry_points.txt +0 -0
- {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
|
-
#
|
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
|
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[
|
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[
|
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
|
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
|
-
'
|
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
|
472
|
+
def prepare_model_with_dxf_or_svg(self, model):
|
471
473
|
from femagtools.dxfsl.converter import convert
|
472
|
-
|
473
|
-
|
474
|
-
|
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
|
478
|
-
|
479
|
-
if not os.path.isfile(
|
480
|
-
logger.error('File "%s" not found',
|
481
|
-
raise ValueError(f'File {
|
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'] =
|
485
|
-
params['show_plots'] =
|
487
|
+
params['split'] = fmt.get('split', False)
|
488
|
+
params['show_plots'] = fmt.get('plot', False)
|
486
489
|
params['write_fsl'] = True
|
487
|
-
params['airgap'] =
|
488
|
-
params['nodedist'] =
|
489
|
-
params['full_model'] =
|
490
|
-
params['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(
|
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 = {
|
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
|
-
|
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.
|
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
|
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)
|