femagtools 1.3.0__py3-none-any.whl → 1.3.2__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 (47) hide show
  1. femagtools/__init__.py +1 -1
  2. femagtools/airgap.py +11 -37
  3. femagtools/amela.py +148 -13
  4. femagtools/bch.py +19 -3
  5. femagtools/dxfsl/area.py +68 -15
  6. femagtools/dxfsl/converter.py +15 -6
  7. femagtools/dxfsl/fslrenderer.py +13 -8
  8. femagtools/dxfsl/functions.py +1 -1
  9. femagtools/dxfsl/geom.py +415 -62
  10. femagtools/dxfsl/machine.py +97 -5
  11. femagtools/dxfsl/shape.py +46 -2
  12. femagtools/ecloss.py +393 -0
  13. femagtools/femag.py +25 -1
  14. femagtools/fsl.py +3 -2
  15. femagtools/hxy.py +126 -0
  16. femagtools/isa7.py +37 -24
  17. femagtools/machine/__init__.py +14 -13
  18. femagtools/machine/effloss.py +153 -32
  19. femagtools/machine/im.py +137 -56
  20. femagtools/machine/pm.py +584 -202
  21. femagtools/machine/sm.py +218 -64
  22. femagtools/machine/utils.py +12 -8
  23. femagtools/mcv.py +6 -8
  24. femagtools/model.py +11 -1
  25. femagtools/parstudy.py +1 -1
  26. femagtools/plot.py +159 -35
  27. femagtools/templates/afm_rotor.mako +102 -0
  28. femagtools/templates/afm_stator.mako +141 -0
  29. femagtools/templates/airgapinduc.mako +3 -3
  30. femagtools/templates/basic_modpar.mako +23 -2
  31. femagtools/templates/cogg_calc.mako +28 -5
  32. femagtools/templates/cu_losses.mako +1 -1
  33. femagtools/templates/fieldcalc.mako +39 -0
  34. femagtools/templates/gen_winding.mako +52 -47
  35. femagtools/templates/mesh-airgap.mako +43 -0
  36. femagtools/templates/stator3Linear.mako +5 -4
  37. femagtools/templates/therm-dynamic.mako +12 -6
  38. femagtools/templates/therm-static.mako +12 -0
  39. femagtools/templates/torq_calc.mako +2 -4
  40. femagtools/utils.py +45 -0
  41. femagtools/windings.py +2 -1
  42. {femagtools-1.3.0.dist-info → femagtools-1.3.2.dist-info}/METADATA +1 -1
  43. {femagtools-1.3.0.dist-info → femagtools-1.3.2.dist-info}/RECORD +47 -41
  44. {femagtools-1.3.0.dist-info → femagtools-1.3.2.dist-info}/WHEEL +1 -1
  45. {femagtools-1.3.0.dist-info → femagtools-1.3.2.dist-info}/LICENSE +0 -0
  46. {femagtools-1.3.0.dist-info → femagtools-1.3.2.dist-info}/entry_points.txt +0 -0
  47. {femagtools-1.3.0.dist-info → femagtools-1.3.2.dist-info}/top_level.txt +0 -0
@@ -50,6 +50,10 @@ class Machine(object):
50
50
  self.endangle) + \
51
51
  "Mirror: {}\n".format(self.mirror_geom is not None)
52
52
 
53
+ def log_machine(self, what):
54
+ logger.info("Machine %s", what)
55
+ self.geom.log_geom()
56
+
53
57
  def is_a_machine(self):
54
58
  return self.radius > 0.0
55
59
 
@@ -428,7 +432,7 @@ class Machine(object):
428
432
  def airgap_y(self):
429
433
  return 0.1
430
434
 
431
- def part_of_circle(self, pos=3):
435
+ def part_of_circle(self, pos=2):
432
436
  return part_of_circle(self.startangle, self.endangle, pos)
433
437
 
434
438
  def delete_center_circle(self):
@@ -709,17 +713,105 @@ class Machine(object):
709
713
  def delete_tiny_elements(self, mindist):
710
714
  self.geom.delete_tiny_elements(mindist)
711
715
 
716
+ def create_arc(self, radius):
717
+ arc = Arc(Element(center=self.center,
718
+ radius=radius,
719
+ start_angle=(self.startangle-0.1)*180/np.pi,
720
+ end_angle=(self.endangle+0.1)*180/np.pi),
721
+ color='red')
722
+ pts = self.geom.split_and_get_intersect_points(arc)
723
+ if len(pts) != 2:
724
+ logger.warning("create_arc(): Bad Points: %s", pts)
725
+ # self.geom.add_edge(arc.node1(4), arc.node2(4), arc)
726
+ return False
727
+
728
+ arc = Arc(Element(center=self.center,
729
+ radius=radius,
730
+ start_angle=self.startangle*180/np.pi,
731
+ end_angle=self.endangle*180/np.pi))
732
+ n = self.geom.find_nodes(pts[0], pts[1])
733
+ self.geom.add_edge(n[0], n[1], arc)
734
+ return True
735
+
736
+ def get_iron_separator(self, radius_list):
737
+ if len(radius_list) < 2:
738
+ return 0.0
739
+
740
+ r_min = radius_list[0][0]
741
+ for r in radius_list[1:]:
742
+ if np.isclose(r[2], r_min, atol=0.001):
743
+ return r[2]
744
+ r_min = r[0]
745
+
746
+ return 0.0
747
+
712
748
  def create_mirror_lines_outside_windings(self):
749
+ logger.debug("create_mirror_lines_outside_windings")
750
+
713
751
  if not self.geom.has_areas_touching_both_sides():
752
+ logger.debug("end create_mirror_lines_outside_windings: not done")
714
753
  return
715
754
 
755
+ radius = self.radius+10
756
+ ag_list = self.geom.detect_airgaps(self.center,
757
+ self.startangle, self.endangle,
758
+ atol=0.001,
759
+ with_end=True)
760
+ radius_list = [(ag[0], (ag[0] + ag[1]) / 2, ag[1]) for ag in ag_list]
761
+ radius_list.sort(reverse=True)
762
+
716
763
  midangle = middle_angle(self.startangle, self.endangle)
717
- pts = self.geom.split_and_get_intersect_points(self.center,
718
- self.radius+10,
719
- midangle)
764
+ line = Line(
765
+ Element(start=self.center,
766
+ end=point(self.center, radius, midangle)))
767
+
768
+ pts = self.geom.split_and_get_intersect_points(line, aktion=False)
769
+ pts.sort()
770
+
771
+ p_critical = self.geom.critical_touch_point(pts)
772
+ if p_critical:
773
+ d_critical = distance(self.center, p_critical)
774
+ logger.info("Critical Point: %s, len=%s", p_critical, d_critical)
775
+ sep_radius = self.get_iron_separator(radius_list)
776
+ logger.debug("Iron Separator found: %s", sep_radius)
777
+ if sep_radius > 0.0 and sep_radius < d_critical:
778
+ radius = sep_radius
779
+ else:
780
+ for r in radius_list:
781
+ logger.debug("Gap Radius = %s", r[1])
782
+ if r[1] < d_critical:
783
+ if self.create_arc(r[1]):
784
+ radius = r[1]
785
+ break
786
+ # else:
787
+ # sep_radius = self.get_iron_separator(radius_list)
788
+ # if sep_radius > 0.0:
789
+ # logger.debug("Iron Separator found: %s", sep_radius)
790
+ # radius = sep_radius
791
+
792
+ # install line
793
+ line = Line(
794
+ Element(start=self.center,
795
+ end=point(self.center, radius, midangle)))
796
+
797
+ pts = self.geom.split_and_get_intersect_points(line)
720
798
  pts.sort()
799
+
721
800
  if self.geom.create_lines_outside_windings(pts):
722
801
  self.geom.area_list = []
723
802
  logger.debug("create subregions again")
724
- self.geom.create_list_of_areas(crunch=True)
803
+ self.geom.create_list_of_areas()
725
804
  self.geom.search_subregions()
805
+
806
+ logger.debug("end create_mirror_lines_outside_windings")
807
+
808
+ def check_and_correct_geom(self, what):
809
+ geom = self.geom.check_geom(what)
810
+ if geom:
811
+ logger.warning("=== Angle correction (%s) ===", what)
812
+ self.geom = geom
813
+ self.startangle = 0.0
814
+ self.endangle = self.geom.alfa
815
+ self.clear_cut_lines()
816
+ self.repair_hull()
817
+ self.set_alfa_and_corners()
femagtools/dxfsl/shape.py CHANGED
@@ -10,7 +10,7 @@
10
10
  from __future__ import print_function
11
11
  import numpy as np
12
12
  import logging
13
- from .functions import less_equal
13
+ from .functions import less_equal, greater_equal
14
14
  from .functions import distance, line_m, line_n
15
15
  from .functions import point, points_are_close, points_on_arc
16
16
  from .functions import alpha_line, alpha_angle, alpha_triangle
@@ -130,6 +130,32 @@ class Shape(object):
130
130
  round(n[1], ndec))
131
131
  return self
132
132
 
133
+ def correct(self, src_alpha, dest_alpha, ndec):
134
+ alpha_ref = min(src_alpha, dest_alpha)
135
+ alpha_p = alpha_line((0.0, 0.0), self.n1)
136
+ if greater_equal(alpha_p, alpha_ref):
137
+ delta = dest_alpha - alpha_p
138
+ T = np.array(((np.cos(delta), -np.sin(delta)),
139
+ (np.sin(delta), np.cos(delta))))
140
+ n = T.dot(np.array((self.p1[0], self.p1[1])))
141
+ self.p1 = (n[0], n[1])
142
+ n = T.dot(np.array((self.n1[0], self.n1[1])))
143
+ self.n1 = (round(n[0], ndec),
144
+ round(n[1], ndec))
145
+
146
+ alpha_p = alpha_line((0.0, 0.0), self.n2)
147
+ if greater_equal(alpha_p, alpha_ref):
148
+ delta = dest_alpha - alpha_p
149
+ T = np.array(((np.cos(delta), -np.sin(delta)),
150
+ (np.sin(delta), np.cos(delta))))
151
+ n = T.dot(np.array((self.p2[0], self.p2[1])))
152
+ self.p2 = (n[0], n[1])
153
+ n = T.dot(np.array((self.n2[0], self.n2[1])))
154
+ self.n2 = (round(n[0], ndec),
155
+ round(n[1], ndec))
156
+
157
+ return self
158
+
133
159
  def overlapping_shapes(self, n, e, rtol=1e-03, atol=1e-03):
134
160
  return False
135
161
 
@@ -596,7 +622,7 @@ class Arc(Circle):
596
622
 
597
623
  def length(self):
598
624
  """returns length of this arc"""
599
- d = abs(self.endangle - self.startangle)
625
+ d = alpha_angle(self.startangle, self.endangle)
600
626
  if d > 2*np.pi:
601
627
  d -= 2*np.pi
602
628
  return self.radius*abs(d)
@@ -825,6 +851,20 @@ class Arc(Circle):
825
851
  self.rtheta = self.rtheta + alpha
826
852
  return self
827
853
 
854
+ def correct(self, src_alpha, dest_alpha, ndec):
855
+ super(Arc, self).correct(src_alpha, dest_alpha, ndec)
856
+
857
+ p1, p2 = ((self.p1[0]-self.center[0],
858
+ self.p1[1]-self.center[1]),
859
+ (self.p2[0]-self.center[0],
860
+ self.p2[1]-self.center[1]))
861
+
862
+ self.startangle = np.arctan2(p1[1], p1[0])
863
+ self.endangle = np.arctan2(p2[1], p2[0])
864
+ if self.rtheta is not None:
865
+ self.rtheta = self.rtheta + alpha
866
+ return self
867
+
828
868
  def minmax(self):
829
869
  """ Die Funktion bestimmt das Minimum und Maximum auf der x- und der
830
870
  y-Achse (return [<min-x>, <max-x>, <min-y>, <max-y>])
@@ -1218,6 +1258,10 @@ class Point(Shape):
1218
1258
  def render(self, renderer):
1219
1259
  renderer.point(self.p1)
1220
1260
 
1261
+ def transform(self, T, alpha, ndec):
1262
+ n = T.dot(np.array((self.p1[0], self.p1[1])))
1263
+ self.p1 = (n[0], n[1])
1264
+ return self
1221
1265
 
1222
1266
  def is_Circle(e):
1223
1267
  return isinstance(e, Circle) and not isinstance(e, Arc)
femagtools/ecloss.py ADDED
@@ -0,0 +1,393 @@
1
+ '''
2
+ femagtools.ecloss
3
+ ~~~~~~~~~~~~~~~~
4
+
5
+ Calculate Magnet Losses with IALH Method
6
+ '''
7
+ __author__ = 'Max Hullmann, Dapu Zhang'
8
+
9
+ import logging
10
+ import warnings
11
+ from .amela import Amela
12
+ import numpy as np
13
+ from numpy import sinh, sin, cosh, cos, pi
14
+ from scipy.interpolate import RBFInterpolator
15
+
16
+ MUR0 = 4*np.pi*10**-7
17
+ # set logging
18
+ logger = logging.getLogger(__name__)
19
+
20
+ class FrequencyDomain:
21
+ def __init__(self, s):
22
+ self.cmplx_amp = s['cmplx_amp']
23
+ self.amp = s['amp']
24
+ self.freq = s['freq']
25
+ self.phase = s['phase']
26
+ self.order = s['order']
27
+
28
+ def fd(s):
29
+ f = FrequencyDomain(s)
30
+ return f
31
+
32
+ def fft(s, fs):
33
+ '''Calculate 1D FFT
34
+ Parameters
35
+ ----------
36
+ s: time signal
37
+ fs: sampling frequency
38
+ Returns
39
+ ----------
40
+ cmplx_amp: complex amplitude
41
+ amp: amplitude (abs)
42
+ freq: frequency
43
+ phase: phase offset
44
+ order: order
45
+ '''
46
+ l = len(s)
47
+ ll = l//2+1
48
+ mag = np.fft.fft(s)
49
+
50
+ tmp = 2*np.abs(mag)/l
51
+ amp = tmp[0:ll]
52
+ amp[0] = amp[0]/2
53
+ amp[-1] = amp[-1]/2
54
+
55
+ idx_max = np.argmax(amp[1:])+1
56
+ freq = np.array([i*fs/l for i in range(ll)])
57
+
58
+ return fd(dict(cmplx_amp=mag,
59
+ amp=amp,
60
+ freq=freq,
61
+ phase=np.angle(mag[0:ll]),
62
+ order=freq/freq[idx_max]))
63
+
64
+ def dfac(x):
65
+ return 6/(x)**3*(sinh(x)-sin(x))/ \
66
+ (cosh(x)+cos(x))
67
+
68
+ def nptns(x, x1, nx):
69
+ ne = len(x)
70
+ dx = 0.5/nx
71
+ exy = np.sort(x1)
72
+ ny = 2
73
+ for i in range(ne-1):
74
+ if 2*(exy[i+1] - exy[i])/(exy[i+1] + exy[i]) < dx:
75
+ ny = ny + 1
76
+ else:
77
+ break
78
+ return ny
79
+
80
+ def ngrid(wm, hm, elxy):
81
+ '''calculate number of grid points for the interpolation'''
82
+ be = np.sqrt(wm*hm/elxy['excp'].shape[0])
83
+ nx = np.around(wm/be) + 1
84
+ ny = np.around(hm/be) + 1
85
+
86
+ ny_new = nptns(elxy['excp'], elxy['excpl'], nx)
87
+ nx_new = nptns(elxy['eycp'], elxy['eycpl'], ny)
88
+
89
+ if np.abs(nx-nx_new)/nx < 0.5 and np.abs(ny-ny_new)/ny < 0.5:
90
+ nx = nx_new
91
+ ny = ny_new
92
+
93
+ return [int(nx), int(ny)]
94
+
95
+ def binterp(x, y, xq, yq, b):
96
+ '''interpolate flux density with Rbf interpolator'''
97
+ f = RBFInterpolator(np.array([[i, j] for i, j in zip(x, y)]), b)
98
+ inp = f(np.array([[i, j] for i, j in zip(xq, yq)]))
99
+ return inp.reshape(len(np.unique(yq)), len(np.unique(xq)))
100
+
101
+
102
+ class MagnLoss(Amela):
103
+ '''Calculate Magnet Losses with IALH Methode
104
+ Parameters
105
+ ----------
106
+ workdir: working directory
107
+ modelname: name of the femag model (*.nc)
108
+ ibeta: load cases [0, 1, 2]
109
+ '''
110
+ def __init__(self, workdir, modelname, ibeta, **kwargs):
111
+ super().__init__(workdir, magnet_data=dict(name=modelname))
112
+ self.pm = self.get_magnet_data_all(ibeta)
113
+ self.theta = self.pm[-1][-1]['phi'] # rotor pos
114
+ self.speed = kwargs.get('speed', self.pm[-1][-1]['speed'])
115
+ self.tgrid = 60/self.speed*(self.theta[-1] - self.theta[0])/360
116
+ self.lt = len(self.theta)
117
+ self.ls = self.pm[-1][-1]['ls']
118
+ self.th_loss = []
119
+ try:
120
+ self.numpoles = self.pm[-1][-1]['numpoles']
121
+ except KeyError:
122
+ self.numpoles = 1
123
+
124
+ try:
125
+ self.mur = kwargs.get('mur', self.pm[-1][-1]['mur'])
126
+ except KeyError:
127
+ self.mur = 1.05
128
+
129
+ self.sigma = kwargs.get('sigma', self.pm[-1][-1]['sigma'])
130
+ self.symmetry = kwargs.get('symmetry', True)
131
+ self.is_x = False
132
+ self.segx = kwargs.get('segx', [1])
133
+
134
+ try:
135
+ self.lm = self.pm[-1][-1]['lm']
136
+ except AttributeError:
137
+ self.lm = 0
138
+
139
+ self.segz = kwargs.get('segz', [0])
140
+ self.is_meter = False
141
+ # determine the number of segments in z direction
142
+ for i in range(len(self.segz)):
143
+ if self.segz[i] > 0:
144
+ self.lm = self.ls/self.segz[i]
145
+ elif self.lm > 0:
146
+ self.segz[i] = np.around(self.ls/self.lm)
147
+ else:
148
+ self.lm = self.ls
149
+ self.segz[i] = 1
150
+
151
+ def skin_depth(self, f):
152
+ '''calculate skin depth'''
153
+ return 1/np.sqrt(MUR0*self.mur*self.sigma*pi*f)
154
+
155
+ def periodicity_id(self, b):
156
+ '''identify the periodicity of a given signal'''
157
+ bx = b['bxl']
158
+ by = b['byl']
159
+ idx = bx.shape[1]
160
+ if self.symmetry:
161
+ ll = bx.shape[0]
162
+ ff = []
163
+
164
+ for i in range(idx):
165
+ r = idx//(i + 1)
166
+
167
+ if r > 1:
168
+ f = 0
169
+
170
+ for j in range(r-1):
171
+ f += np.sum(np.abs(bx[:, 0:(i+1)] - bx[:, (j+1)*(i+1)-1:(j+2)*(i+1)-1]))/((i+1)*ll)
172
+ f += np.sum(np.abs(by[:, 0:(i+1)] - by[:, (j+1)*(i+1)-1:(j+2)*(i+1)-1]))/((i+1)*ll)
173
+
174
+ ff.append(f/(r-1))
175
+
176
+ minf = np.amin(ff)
177
+ i = np.argmin(ff)
178
+ bxymax = np.amax([np.amax(np.abs(bx)), np.amax(np.abs(by))])
179
+
180
+ if minf < bxymax * 1e-4:
181
+ ishift = i
182
+ num_period = (idx - 1) // ishift
183
+
184
+ for j in range(num_period - 1):
185
+ bx[:, 0:i+1] += bx[:, i+j*ishift:(i+1)+(j+1)*ishift]
186
+ by[:, 0:i+1] += by[:, i+j*ishift:(i+1)+(j+1)*ishift]
187
+
188
+ bx[:, 0:i + 1] /= num_period
189
+ by[:, 0:i + 1] /= num_period
190
+ idx = i + 1
191
+ else:
192
+ ff = []
193
+
194
+ for i in range(idx - 1, 0, -1):
195
+ f1 = np.sum(np.abs(bx[:, 0] - bx[:, i]))/ll + np.sum(np.abs(by[:, 0] - by[:, i]))/ll
196
+ ff.append(f1)
197
+
198
+ minf = np.amin(ff)
199
+ i = np.argmin(ff)
200
+ idx = idx - i
201
+
202
+ bx_fft = fft(b['bxf'][0:idx-1], (idx-1)/self.tgrid)
203
+ by_fft = fft(b['byf'][0:idx-1], (idx-1)/self.tgrid)
204
+
205
+ if self.symmetry:
206
+ bxy_amp = bx_fft.amp + by_fft.amp
207
+ tmp_period = np.array([(idx-1)/i for i in range((idx-1)//2 + 1) if i > 0])
208
+ idx_nonzero = np.argwhere(tmp_period > 0.1*np.amax(bxy_amp)).squeeze()
209
+ period = tmp_period[idx_nonzero]
210
+
211
+ if np.sum(np.around([period[0]%i for i in period])) == 0:
212
+ idx = int(np.ceil(np.amax(period))+1)
213
+ if idx > bx.shape[1]:
214
+ idx = bx.shape[1]
215
+
216
+ self.tgrid = 60/self.speed*(self.theta[idx-1] - self.theta[0])/360
217
+
218
+ return [idx, bx_fft, by_fft]
219
+
220
+ def consider_bx(self, wm, hm, bx_fft, by_fft):
221
+ '''check if a caculation is necessary for the x direction'''
222
+ fft_freq = bx_fft.freq
223
+ fft_freq[fft_freq==0] = 0.5e-2
224
+
225
+ if not self.is_meter:
226
+ self.ls *= 1e-3
227
+ self.lm *= 1e-3
228
+ self.is_meter = True
229
+ # skin depth
230
+ delta = self.skin_depth(fft_freq)
231
+
232
+ def ampf(bfft, krf):
233
+ return np.sum(bfft.amp**2*bfft.freq*krf)
234
+
235
+ if hm > self.lm:
236
+ krfx = dfac(self.lm/delta)
237
+ px = ampf(bx_fft, krfx)*self.lm**3*wm
238
+ else:
239
+ krfx = dfac(hm/delta)
240
+ px = ampf(bx_fft, krfx)*hm**3*wm
241
+
242
+ if wm > self.lm:
243
+ krfy = dfac(self.lm/delta)
244
+ py = ampf(by_fft, krfy)*self.lm**3*hm
245
+ else:
246
+ krfy = dfac(wm/delta)
247
+ py = ampf(by_fft, krfy)*wm**3*hm
248
+
249
+ if px/py > 0.005:
250
+ self.is_x = True
251
+ return ' '
252
+
253
+ def bpm_fft(self, nx, ny, nt, elxy, bxy):
254
+ '''interpolate the flux density'''
255
+ x = np.linspace(np.amin(elxy['excpl']),np.amax(elxy['excpl']), nx)
256
+ y = np.linspace(np.amin(elxy['eycpl']),np.amax(elxy['eycpl']), ny)
257
+
258
+ xx, yy= np.meshgrid(x, y)
259
+ xx_, yy_ = xx.ravel(), yy.ravel()
260
+
261
+ bx_3d = np.zeros((ny,nx,nt))
262
+ by_3d = np.zeros((ny,nx,nt))
263
+
264
+ # regular grid
265
+ if self.is_x:
266
+ for t in range(nt):
267
+ bx_3d[:, :, t] = binterp(elxy['excpl'], elxy['eycpl'],
268
+ xx_, yy_, bxy['bxl'][:, t])
269
+ by_3d[:, :, t] = binterp(elxy['excpl'], elxy['eycpl'],
270
+ xx_, yy_, bxy['byl'][:, t])
271
+ else:
272
+ for t in range(nt):
273
+ by_3d[:, :, t] = binterp(elxy['excpl'], elxy['eycpl'],
274
+ xx_, yy_, bxy['byl'][:, t])
275
+
276
+ lfft = (nt-1)//2+1
277
+ by_fft = 2*np.abs(np.fft.rfftn(by_3d[:,:,0:-1]))/(nx*ny*(nt-1))
278
+
279
+ py_se = np.zeros((ny,nx,lfft))
280
+ px_se = np.zeros_like(py_se)
281
+
282
+ if self.is_x:
283
+ bx_fft = 2*np.abs(np.fft.rfftn(bx_3d[:,:,0:-1]))/(nx*ny*(nt-1))
284
+ else:
285
+ bx_fft = np.zeros_like(by_fft)
286
+
287
+ return [bx_fft, by_fft, px_se, py_se]
288
+
289
+ def calc_pvpm(self, bamp, f, nu, wm, hm, delta_eff):
290
+ '''calculate eddy current losses for each frequency'''
291
+ wm_nu = wm
292
+ k_xi = 1
293
+ k_eta = 1
294
+
295
+ if nu > 1:
296
+ wm_nu = 0.5*wm/(nu - 1)
297
+ k_xi = 0.895
298
+ k_eta = 1.15
299
+
300
+ alpha = (hm + delta_eff*self.mur)/hm
301
+ delta = np.sqrt(alpha)*self.skin_depth(f)
302
+ xi = k_xi*wm_nu/delta
303
+ eta = self.lm/(wm_nu*k_eta)
304
+
305
+ # caclulation correction factor geometry
306
+ if xi*eta < 500.:
307
+ c_ef_n0 = 32/pi**5/eta/dfac(xi)*6
308
+ sum_r = 0.0
309
+ sum_i = 0.0
310
+ n = np.array([i for i in range(100)])
311
+ lambda_n = (2*n + 1)*pi
312
+ beta_n = np.sqrt(lambda_n ** 2 + 2j*xi**2)
313
+ beta_nr = np.real(beta_n)
314
+ beta_ni = np.imag(beta_n)
315
+
316
+ deno = 1. / ((2*n + 1)**5*abs(beta_n)**6*(cosh(beta_nr*eta) + cos(beta_ni*eta)))
317
+ add_i = ((lambda_n**2 - 2*beta_ni**2)*beta_nr*lambda_n**3*sinh(beta_nr*eta))*deno
318
+ add_r = ((lambda_n**2 + 2*beta_nr**2)*beta_ni*lambda_n**3*sin(beta_ni*eta))*deno
319
+
320
+ sum_r = np.sum(np.nan_to_num(add_r, copy=True, nan=0))
321
+ sum_i = np.sum(np.nan_to_num(add_i, copy=True, nan=0))
322
+ c_ef = 1 - c_ef_n0*(sum_i + sum_r)
323
+
324
+ else:
325
+ c_ef = 1 + 1/eta
326
+
327
+ # calculation correction factor reaction field
328
+ c_rf = dfac(xi)
329
+ if np.isnan(c_rf):
330
+ c_rf = 0
331
+
332
+ result = self.sigma*f**2*bamp**2*wm**3*hm*self.lm*c_ef*c_rf
333
+ # calculation ec losses
334
+ if nu > 1:
335
+ return 0.5/(nu-1)**2*result
336
+ else:
337
+ return pi**2/6*result
338
+
339
+ def loss(self, bx_fft, by_fft, px_se, py_se, wm, hm):
340
+ '''calculate losses in x and y direction'''
341
+ (ny, nx, lfft) = px_se.shape
342
+ pec = np.zeros((len(self.segx), len(self.segz)))
343
+ nu = np.abs(np.fft.fftfreq(nx, 1/nx))+1
344
+ mu = np.abs(np.fft.fftfreq(ny, 1/ny))+1
345
+ for jj in range(len(self.segx)):
346
+ for kk in range(len(self.segz)):
347
+ for c in range(lfft):
348
+ for iy in range(ny):
349
+ for ix in range(nx):
350
+ if self.is_x and nu[ix] < 2:
351
+ with warnings.catch_warnings():
352
+ warnings.simplefilter('ignore')
353
+ px_se[iy,ix,c] = self.calc_pvpm(bx_fft[iy,ix,c], max(c/self.tgrid, 1e-6),
354
+ mu[iy], hm, wm/self.segx[jj], 0)
355
+
356
+ if mu[iy] < 2:
357
+ with warnings.catch_warnings():
358
+ warnings.simplefilter('ignore')
359
+ py_se[iy,ix,c] = self.calc_pvpm(by_fft[iy,ix,c], max(c/self.tgrid, 1e-6),
360
+ nu[ix], wm/self.segx[jj], hm, 0)
361
+ py_sum = np.sum(py_se)
362
+ px_sum = np.sum(px_se)
363
+ pec[jj,kk] = (py_sum + px_sum)*(self.ls/self.lm)*self.numpoles*self.segx[jj]
364
+
365
+ return np.sum(pec)
366
+
367
+ def calc_losses(self):
368
+ '''calculate magnet losses for every load case
369
+
370
+ Returns
371
+ --------------
372
+ all_load_cases: list of losses for all load cases
373
+ '''
374
+ all_load_cases = []
375
+ for k in self.pm:
376
+ ialh_loss = 0
377
+ loss_detail = []
378
+ for i in k:
379
+ logger.info(f'magnet width and height: {i["wm"]:.2f}mm {i["hm"]:.2f}mm')
380
+ [nt, bx_fft, by_fft] = self.periodicity_id(i['bl'])
381
+ [nx, ny] = ngrid(i['wm'], i['hm'], i['elcp'])
382
+ keyset = ('wm', 'hm')
383
+ for j in keyset:
384
+ i[j]*=1e-3
385
+ self.consider_bx(i['wm'], i['hm'], bx_fft, by_fft)
386
+ bfft = self.bpm_fft(nx, ny, nt, i['elcp'], i['bl'])
387
+ loss = self.loss(*bfft, i['wm'], i['hm'])
388
+ ialh_loss += loss
389
+ loss_detail.append([i['spel_key'], loss/self.numpoles])
390
+ self.th_loss.append(loss_detail)
391
+ all_load_cases.append(ialh_loss)
392
+
393
+ return all_load_cases
femagtools/femag.py CHANGED
@@ -31,6 +31,8 @@ import femagtools.asm
31
31
  import femagtools.airgap as ag
32
32
  import femagtools.fsl
33
33
  import femagtools.config
34
+ import femagtools.ecloss
35
+
34
36
  from femagtools import ntib
35
37
 
36
38
 
@@ -490,7 +492,8 @@ class Femag(BaseFemag):
490
492
  return {'t': ttemp[0], 'temperature': ttemp[1]}
491
493
 
492
494
  bch = self.read_bch(self.modelname)
493
- if simulation['calculationMode'] == 'pm_sym_fast':
495
+ if simulation['calculationMode'] == 'pm_sym_fast' or \
496
+ simulation['calculationMode'] == 'torq_calc':
494
497
  if simulation.get('shortCircuit', False):
495
498
  logger.info("short circuit simulation")
496
499
  simulation.update(
@@ -531,7 +534,28 @@ class Femag(BaseFemag):
531
534
  pmod = 0
532
535
  bch.airgap = ag.read(os.path.join(self.workdir, 'bag.dat'),
533
536
  pmod=pmod)
537
+
538
+ if simulation.get('magnet_loss', False):
539
+ logger.info('Evaluating magnet losses...')
540
+ ops = [k for k in range(len(bch.torque))]
541
+ m = femagtools.ecloss.MagnLoss(self.workdir, self.modelname, ibeta=ops)
542
+ try:
543
+ magn_losses = m.calc_losses()
544
+ except:
545
+ magn_losses = [0 for i in range(len(ops))]
534
546
 
547
+ if len(ops) != len(bch.losses):
548
+ magn_losses.insert(0, magn_losses[0])
549
+ try:
550
+ for i in range(len(bch.losses)):
551
+ bch.losses[i].update({"magnetH": magn_losses[i]})
552
+ except:
553
+ pass
554
+ # pass losses to bch object for th usage
555
+ try:
556
+ bch.magnet_loss_th = m.th_loss
557
+ except:
558
+ pass
535
559
  return bch
536
560
  return dict(status='ok', message=self.modelname)
537
561
 
femagtools/fsl.py CHANGED
@@ -308,7 +308,8 @@ class Builder:
308
308
  params['airgap'] = -1.0
309
309
  pos = 'out' if model.external_rotor else 'in'
310
310
  params['part'] = ('rotor', pos)
311
- logger.info("Conv rotor from %s", templ + '.dxf')
311
+ logger.info("Conv rotor from %s",
312
+ model.magnet[templ]['name'])
312
313
  conv = convert(model.magnet[templ]['name'], **params)
313
314
  model.set_value('poles', int(conv.get('num_poles')))
314
315
  self.set_diameter_parameter(model, conv)
@@ -427,7 +428,7 @@ class Builder:
427
428
  'leak_evol_wind',
428
429
  'leak_tooth_wind'}.intersection(model.windings))
429
430
  if k:
430
- logger.info("LEAK %s ---------------", k)
431
+ logger.info("Leakage type %s", k)
431
432
  if 'wiredia' not in model.windings[k[0]]:
432
433
  if 'wire_gauge' in model.windings:
433
434
  import numpy as np