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.
- femagtools/__init__.py +1 -1
- femagtools/airgap.py +11 -37
- femagtools/amela.py +148 -13
- femagtools/bch.py +19 -3
- femagtools/dxfsl/area.py +68 -15
- femagtools/dxfsl/converter.py +15 -6
- femagtools/dxfsl/fslrenderer.py +13 -8
- femagtools/dxfsl/functions.py +1 -1
- femagtools/dxfsl/geom.py +415 -62
- femagtools/dxfsl/machine.py +97 -5
- femagtools/dxfsl/shape.py +46 -2
- femagtools/ecloss.py +393 -0
- femagtools/femag.py +25 -1
- femagtools/fsl.py +3 -2
- femagtools/hxy.py +126 -0
- femagtools/isa7.py +37 -24
- femagtools/machine/__init__.py +14 -13
- femagtools/machine/effloss.py +153 -32
- femagtools/machine/im.py +137 -56
- femagtools/machine/pm.py +584 -202
- femagtools/machine/sm.py +218 -64
- femagtools/machine/utils.py +12 -8
- femagtools/mcv.py +6 -8
- femagtools/model.py +11 -1
- femagtools/parstudy.py +1 -1
- femagtools/plot.py +159 -35
- femagtools/templates/afm_rotor.mako +102 -0
- femagtools/templates/afm_stator.mako +141 -0
- femagtools/templates/airgapinduc.mako +3 -3
- femagtools/templates/basic_modpar.mako +23 -2
- femagtools/templates/cogg_calc.mako +28 -5
- femagtools/templates/cu_losses.mako +1 -1
- femagtools/templates/fieldcalc.mako +39 -0
- femagtools/templates/gen_winding.mako +52 -47
- femagtools/templates/mesh-airgap.mako +43 -0
- femagtools/templates/stator3Linear.mako +5 -4
- femagtools/templates/therm-dynamic.mako +12 -6
- femagtools/templates/therm-static.mako +12 -0
- femagtools/templates/torq_calc.mako +2 -4
- femagtools/utils.py +45 -0
- femagtools/windings.py +2 -1
- {femagtools-1.3.0.dist-info → femagtools-1.3.2.dist-info}/METADATA +1 -1
- {femagtools-1.3.0.dist-info → femagtools-1.3.2.dist-info}/RECORD +47 -41
- {femagtools-1.3.0.dist-info → femagtools-1.3.2.dist-info}/WHEEL +1 -1
- {femagtools-1.3.0.dist-info → femagtools-1.3.2.dist-info}/LICENSE +0 -0
- {femagtools-1.3.0.dist-info → femagtools-1.3.2.dist-info}/entry_points.txt +0 -0
- {femagtools-1.3.0.dist-info → femagtools-1.3.2.dist-info}/top_level.txt +0 -0
femagtools/dxfsl/machine.py
CHANGED
@@ -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=
|
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
|
-
|
718
|
-
|
719
|
-
|
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(
|
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 =
|
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",
|
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("
|
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
|