femagtools 1.6.4__py3-none-any.whl → 1.6.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- femagtools/__init__.py +1 -1
- femagtools/dxfsl/area.py +28 -2
- femagtools/dxfsl/areabuilder.py +152 -5
- femagtools/dxfsl/concat.py +7 -7
- femagtools/dxfsl/converter.py +20 -7
- femagtools/dxfsl/dxfparser.py +359 -0
- femagtools/dxfsl/femparser.py +78 -0
- femagtools/dxfsl/geom.py +14 -426
- femagtools/dxfsl/journal.py +15 -23
- femagtools/dxfsl/machine.py +13 -3
- femagtools/dxfsl/shape.py +13 -3
- femagtools/dxfsl/svgparser.py +90 -0
- femagtools/dxfsl/symmetry.py +370 -0
- femagtools/isa7.py +6 -5
- femagtools/machine/effloss.py +1 -1
- femagtools/machine/im.py +3 -2
- femagtools/machine/pm.py +71 -12
- femagtools/machine/sm.py +4 -2
- femagtools/machine/utils.py +5 -5
- femagtools/plot/nc.py +3 -3
- femagtools/svgfsl/converter.py +74 -0
- {femagtools-1.6.4.dist-info → femagtools-1.6.6.dist-info}/METADATA +5 -1
- {femagtools-1.6.4.dist-info → femagtools-1.6.6.dist-info}/RECORD +28 -22
- {femagtools-1.6.4.dist-info → femagtools-1.6.6.dist-info}/entry_points.txt +1 -0
- tests/test_dxfsl.py +12 -0
- {femagtools-1.6.4.dist-info → femagtools-1.6.6.dist-info}/LICENSE +0 -0
- {femagtools-1.6.4.dist-info → femagtools-1.6.6.dist-info}/WHEEL +0 -0
- {femagtools-1.6.4.dist-info → femagtools-1.6.6.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,90 @@
|
|
1
|
+
"""
|
2
|
+
|
3
|
+
Geometry Parser for SVG files
|
4
|
+
|
5
|
+
"""
|
6
|
+
import logging
|
7
|
+
import re
|
8
|
+
import lxml.etree as ET
|
9
|
+
from .shape import Circle, Arc, Line, Element
|
10
|
+
import numpy as np
|
11
|
+
|
12
|
+
logger = logging.getLogger(__name__)
|
13
|
+
|
14
|
+
def get_center(r, p1, p2, sweep):
|
15
|
+
"""return center point coordinates of arc"""
|
16
|
+
dp = p2-p1
|
17
|
+
s = np.linalg.norm(dp)
|
18
|
+
delta = np.arctan2(dp[1], dp[0])
|
19
|
+
if s < 2*r:
|
20
|
+
if sweep == 0:
|
21
|
+
alfa = delta - np.arctan2(np.sqrt(r**2-s**2/4), s/2)
|
22
|
+
else:
|
23
|
+
alfa = delta + np.arctan2(np.sqrt(r**2-s**2/4), s/2)
|
24
|
+
else:
|
25
|
+
alfa = delta
|
26
|
+
return p1[0] + r*np.cos(alfa), p1[1] + r*np.sin(alfa)
|
27
|
+
|
28
|
+
|
29
|
+
def get_angles(sweep, center, p1, p2):
|
30
|
+
x1, y1 = (p1-center)
|
31
|
+
x2, y2 = (p2-center)
|
32
|
+
if sweep == 0:
|
33
|
+
return np.arctan2(y2, x2), np.arctan2(y1, x1)
|
34
|
+
return np.arctan2(y1, x1), np.arctan2(y2, x2)
|
35
|
+
|
36
|
+
|
37
|
+
def get_shapes(path):
|
38
|
+
"""return list of node elements (A, L)"""
|
39
|
+
state = ''
|
40
|
+
p = []
|
41
|
+
for s in [s for s in re.split('([AML])|,|\\s+',path) if s]:
|
42
|
+
if state == '':
|
43
|
+
state = s[0]
|
44
|
+
elif state == 'M':
|
45
|
+
p.append(float(s))
|
46
|
+
if len(p) == 2:
|
47
|
+
p1 = np.array(p)
|
48
|
+
p = []
|
49
|
+
state = ''
|
50
|
+
elif state == 'L':
|
51
|
+
p.append(float(s))
|
52
|
+
if len(p) == 2:
|
53
|
+
p2 = np.array(p)
|
54
|
+
logger.debug("Line %s -> %s",
|
55
|
+
p1, p2)
|
56
|
+
yield Line(Element(start=p1, end=p2))
|
57
|
+
p1 = p2.copy()
|
58
|
+
p = []
|
59
|
+
state = ''
|
60
|
+
elif state == 'A':
|
61
|
+
p.append(float(s))
|
62
|
+
if len(p) == 7:
|
63
|
+
sweep = int(p[-3])
|
64
|
+
p2 = np.array(p[-2:])
|
65
|
+
r = p[0]
|
66
|
+
center = get_center(r, p1, p2, sweep)
|
67
|
+
start, end = get_angles(sweep, center, p1, p2)
|
68
|
+
logger.debug("Arc center %s r %f %f -> %f",
|
69
|
+
center, r, start, end)
|
70
|
+
yield Arc(Element(center=center,
|
71
|
+
radius=r,
|
72
|
+
start_angle=start*180/np.pi,
|
73
|
+
end_angle=end*180/np.pi))
|
74
|
+
p1 = p2.copy()
|
75
|
+
p = []
|
76
|
+
state = ''
|
77
|
+
else:
|
78
|
+
raise ValueError(f"unsupported path {state}")
|
79
|
+
|
80
|
+
|
81
|
+
def svgshapes(svgfile):
|
82
|
+
svg = ET.parse(svgfile)
|
83
|
+
for p in svg.findall(".//{http://www.w3.org/2000/svg}path"):
|
84
|
+
yield from get_shapes(p.get('d'))
|
85
|
+
for p in svg.findall(".//{http://www.w3.org/2000/svg}line"):
|
86
|
+
yield Line(Element(start=[float(p.get('x1')), float(p.get('y1'))],
|
87
|
+
end=[float(p.get('x2')), float(p.get('y2'))]))
|
88
|
+
for p in svg.findall(".//{http://www.w3.org/2000/svg}circle"):
|
89
|
+
center = float(p.get('cx')), float(p.get('cy'))
|
90
|
+
yield Circle(Element(center=center, radius=float(p.get('r'))))
|
@@ -0,0 +1,370 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
"""
|
3
|
+
femagtools.dxfsl.symmetry
|
4
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~
|
5
|
+
|
6
|
+
Authors: Ronald Tanner, Beat Holm
|
7
|
+
"""
|
8
|
+
from __future__ import print_function
|
9
|
+
|
10
|
+
import numpy as np
|
11
|
+
import logging
|
12
|
+
import sys
|
13
|
+
from femagtools.dxfsl.shape import Element, Line
|
14
|
+
from femagtools.dxfsl.area import Area
|
15
|
+
from femagtools.dxfsl.functions import alpha_angle, positive_angle, is_same_angle
|
16
|
+
from femagtools.dxfsl.functions import min_angle, max_angle, gcd, point
|
17
|
+
from femagtools.dxfsl.functions import less_equal, less, points_are_close
|
18
|
+
from femagtools.dxfsl.functions import line_m, line_n, mirror_point
|
19
|
+
|
20
|
+
logger = logging.getLogger('femagtools.symmetry')
|
21
|
+
|
22
|
+
|
23
|
+
#############################
|
24
|
+
# symmetry #
|
25
|
+
#############################
|
26
|
+
|
27
|
+
|
28
|
+
class Symmetry(object):
|
29
|
+
def __init__(self,
|
30
|
+
geom=None,
|
31
|
+
startangle=None,
|
32
|
+
endangle=None,
|
33
|
+
rtol=1e-04,
|
34
|
+
atol=1e-03):
|
35
|
+
assert(geom is not None)
|
36
|
+
assert(startangle is not None)
|
37
|
+
assert(endangle is not None)
|
38
|
+
|
39
|
+
self.geom = geom
|
40
|
+
self.startangle = startangle
|
41
|
+
self.endangle = endangle
|
42
|
+
self.delta_check_count = 0
|
43
|
+
self.delta_angle_korr = 0.0
|
44
|
+
self.rtol = rtol
|
45
|
+
self.atol = atol
|
46
|
+
self.full = False
|
47
|
+
if np.isclose(self.startangle, self.endangle):
|
48
|
+
self.alpha = 2.0*np.pi
|
49
|
+
self.full = True
|
50
|
+
else:
|
51
|
+
self.alpha = alpha_angle(self.startangle, self.endangle)
|
52
|
+
self.full = False
|
53
|
+
logger.debug("Symmetry(alpha=%s, rtol=%s, atol=%s)", self.alpha, rtol, atol)
|
54
|
+
|
55
|
+
def __str__(self):
|
56
|
+
return "rtol: {}\n".format(self.rtol) + \
|
57
|
+
"atol: {}\n".format(self.atol)
|
58
|
+
|
59
|
+
def equal_area(self,
|
60
|
+
d1, h1, a1,
|
61
|
+
d2, h2, a2,
|
62
|
+
rtol=1e-03, atol=1e-03):
|
63
|
+
if not np.isclose(d1, d2, rtol=rtol, atol=atol):
|
64
|
+
logger.debug("dist NOT close (%s/%s)", d1, d2)
|
65
|
+
return False
|
66
|
+
if not np.isclose(h1, h2, rtol=rtol, atol=atol):
|
67
|
+
logger.debug("height NOT close (%s/%s)", h1, h2)
|
68
|
+
return False
|
69
|
+
if not np.isclose(a1, a2, rtol=rtol, atol=atol):
|
70
|
+
logger.debug("alpha NOT close (%s/%s)", a1, a2)
|
71
|
+
return False
|
72
|
+
return True
|
73
|
+
|
74
|
+
def calc_mid_angle(self, a):
|
75
|
+
return positive_angle(alpha_angle(self.startangle,
|
76
|
+
a.get_mid_angle(self.geom.center)))
|
77
|
+
|
78
|
+
def find_symmetry(self):
|
79
|
+
arealist = self.geom.list_of_areas()
|
80
|
+
logger.debug("begin of Symmetry::find_symmetry: %s areas available", len(arealist))
|
81
|
+
if len(arealist) == 0:
|
82
|
+
logger.debug("end of find_symmetry: no areas")
|
83
|
+
return 0
|
84
|
+
|
85
|
+
areas = []
|
86
|
+
for a in arealist:
|
87
|
+
areas.append((round(a.get_alpha(self.geom.center), 3),
|
88
|
+
round(a.min_dist, 1),
|
89
|
+
round(a.height, 1),
|
90
|
+
self.calc_mid_angle(a),
|
91
|
+
a))
|
92
|
+
areas.sort(reverse=True)
|
93
|
+
|
94
|
+
a0_alpha, a0_min_dist, a0_height, a0_mid_angle, a0 = areas[0]
|
95
|
+
equal_areas = [(a0_mid_angle, a0)]
|
96
|
+
check_rslt = []
|
97
|
+
for a1_alpha, a1_min_dist, a1_height, a1_mid_angle, a1 in areas[1:]:
|
98
|
+
if self.equal_area(a0_min_dist, a0_height, a0_alpha,
|
99
|
+
a1_min_dist, a1_height, a1_alpha,
|
100
|
+
rtol=0.01, atol=0.05):
|
101
|
+
a0_min_dist = (a0_min_dist + a1_min_dist) / 2
|
102
|
+
a0_height = (a0_height + a1_height) / 2
|
103
|
+
a0_alpha = (a0_alpha + a1_alpha) / 2
|
104
|
+
equal_areas.append((a1_mid_angle, a1))
|
105
|
+
else:
|
106
|
+
parts = self.check_delta(equal_areas)
|
107
|
+
check_rslt.append((a0.area_size(), parts, len(equal_areas), a0))
|
108
|
+
|
109
|
+
equal_areas = [(a1_mid_angle, a1)]
|
110
|
+
a0_min_dist = a1_min_dist
|
111
|
+
a0_height = a1_height
|
112
|
+
a0_alpha = a1_alpha
|
113
|
+
a0 = a1
|
114
|
+
|
115
|
+
parts = self.check_delta(equal_areas)
|
116
|
+
check_rslt.append((a0.area_size(), parts, len(equal_areas), a0))
|
117
|
+
|
118
|
+
parts = self.get_symmetry_parts(check_rslt)
|
119
|
+
if parts < 2:
|
120
|
+
logger.debug("end of Symmetry::find_symmetry: no symmetry")
|
121
|
+
return parts
|
122
|
+
|
123
|
+
self.geom.clear_cut_lines()
|
124
|
+
for alpha in self.symmetry_lines(parts,
|
125
|
+
self.startangle,
|
126
|
+
self.endangle):
|
127
|
+
plus = self.geom.max_radius / 10
|
128
|
+
min_radius = max(10, self.geom.min_radius - plus)
|
129
|
+
p1 = point(self.geom.center, min_radius, alpha)
|
130
|
+
p2 = point(self.geom.center, self.geom.max_radius + plus, alpha)
|
131
|
+
line = Line(Element(start=p1, end=p2))
|
132
|
+
line.init_attributes(color='green')
|
133
|
+
self.geom.add_cut_line(line)
|
134
|
+
|
135
|
+
logger.debug("end of Symmetry::find_symmetry: -> %s", parts)
|
136
|
+
return parts
|
137
|
+
|
138
|
+
def check_delta(self, area_list):
|
139
|
+
logger.debug("begin of check_delta: %s equal areas", len(area_list))
|
140
|
+
if not area_list:
|
141
|
+
logger.debug("end of check_delta: no areas")
|
142
|
+
return None
|
143
|
+
|
144
|
+
if len(area_list) == 1:
|
145
|
+
mid_angle, a = area_list[0]
|
146
|
+
alpha = a.get_alpha(self.geom.center)
|
147
|
+
if np.isclose(alpha, self.alpha):
|
148
|
+
logger.debug("end of check_delta: one area from start to end")
|
149
|
+
return None # ok
|
150
|
+
|
151
|
+
self.delta_check_count += 1
|
152
|
+
area_list.sort()
|
153
|
+
|
154
|
+
mid_angle, a = area_list[0]
|
155
|
+
delta = positive_angle(mid_angle * 2)
|
156
|
+
delta_total = mid_angle
|
157
|
+
delta_list = [delta]
|
158
|
+
|
159
|
+
logger.debug("First mid = %s, delta = %s", mid_angle, delta)
|
160
|
+
logger.debug("%s: d=%s, h=%s, a=%s, mid=%s, delta=%s",
|
161
|
+
a.identifier(),
|
162
|
+
a.min_dist,
|
163
|
+
a.height,
|
164
|
+
a.get_alpha(self.geom.center),
|
165
|
+
mid_angle,
|
166
|
+
delta)
|
167
|
+
|
168
|
+
geom_alpha = alpha_angle(self.startangle, self.endangle)
|
169
|
+
geom_alpha = positive_angle(geom_alpha)
|
170
|
+
|
171
|
+
start_angle = mid_angle
|
172
|
+
for mid_angle, a in area_list[1:]:
|
173
|
+
delta_angle = alpha_angle(start_angle, mid_angle)
|
174
|
+
delta = positive_angle(delta_angle)
|
175
|
+
delta_total += delta_angle
|
176
|
+
delta_list.append(delta)
|
177
|
+
|
178
|
+
logger.debug("%s: d=%s, h=%s, a=%s, mid=%s, delta=%s",
|
179
|
+
a.identifier(),
|
180
|
+
a.min_dist,
|
181
|
+
a.height,
|
182
|
+
a.get_alpha(self.geom.center),
|
183
|
+
mid_angle,
|
184
|
+
delta)
|
185
|
+
start_angle = mid_angle
|
186
|
+
|
187
|
+
delta_angle = alpha_angle(start_angle, geom_alpha)
|
188
|
+
delta = positive_angle(delta_angle * 2)
|
189
|
+
delta_total += delta_angle
|
190
|
+
delta_list.append(delta)
|
191
|
+
logger.debug("final delta=%s", delta)
|
192
|
+
|
193
|
+
if not np.isclose(geom_alpha, delta_total):
|
194
|
+
logger.debug("-- deltas: %s", delta_list)
|
195
|
+
logger.debug("end of check_delta: BAD DELTA %s, (expected %s)",
|
196
|
+
delta_angle, geom_alpha)
|
197
|
+
return 0 # very bad
|
198
|
+
|
199
|
+
sz = len(delta_list)
|
200
|
+
mid = int(sz / 2)
|
201
|
+
ix1 = 0
|
202
|
+
ix2 = sz - 1
|
203
|
+
first_last_bad = False
|
204
|
+
for ix1 in range(0, mid):
|
205
|
+
if not np.isclose(delta_list[ix1], delta_list[ix2], rtol=1e-3, atol=1e-3):
|
206
|
+
if self.full and \
|
207
|
+
self.delta_check_count == 1 and \
|
208
|
+
ix1 == 0 and \
|
209
|
+
self.delta_angle_korr == 0.0:
|
210
|
+
first_last_bad = True
|
211
|
+
else:
|
212
|
+
logger.debug("end of check_delta: NO SYM")
|
213
|
+
return 0
|
214
|
+
ix2 -= 1
|
215
|
+
|
216
|
+
if first_last_bad:
|
217
|
+
delta_korr = (delta_list[0] + delta_list[-1]) / 2.0
|
218
|
+
logger.debug("STARTANGLE CORRECTION")
|
219
|
+
self.delta_angle_korr = (delta_korr - delta_list[0]) / 2
|
220
|
+
logger.debug("-- delta[0] from %s to %s", delta_list[0], delta_korr)
|
221
|
+
logger.debug("Delta Angle Korr = %s", self.delta_angle_korr)
|
222
|
+
delta_list[0] = delta_korr
|
223
|
+
delta_list[-1] = delta_korr
|
224
|
+
assert(self.full)
|
225
|
+
self.startangle = self.startangle - self.delta_angle_korr
|
226
|
+
self.endangle = self.endangle - self.delta_angle_korr
|
227
|
+
logger.debug("New startangle = %s", self.startangle)
|
228
|
+
logger.debug("Delta List: %s", delta_list)
|
229
|
+
|
230
|
+
d1 = delta_list[0]
|
231
|
+
d1_count = 1
|
232
|
+
inx_list = [0]
|
233
|
+
for x in range(1, len(delta_list)):
|
234
|
+
if np.isclose(d1, delta_list[x], rtol=1e-3, atol=1e-3):
|
235
|
+
inx_list.append(x)
|
236
|
+
d1_count += 1
|
237
|
+
|
238
|
+
if d1_count == len(delta_list):
|
239
|
+
logger.debug("end of check_delta: SYMMETRY FOUND")
|
240
|
+
return d1_count -1 # very simple
|
241
|
+
if len(delta_list) < 2:
|
242
|
+
logger.debug("end of check_delta: One delta only ?!")
|
243
|
+
return 0
|
244
|
+
|
245
|
+
logger.debug("index of delta %s = %s", d1, inx_list)
|
246
|
+
x1 = inx_list[0]
|
247
|
+
x2 = inx_list[1]
|
248
|
+
step = x2 - x1
|
249
|
+
x1 = x2
|
250
|
+
for x2 in inx_list[2:]:
|
251
|
+
if not (x2 - x1 == step):
|
252
|
+
return 0
|
253
|
+
x1 = x2
|
254
|
+
|
255
|
+
logger.debug("end of check_delta: SYMMETRY FOUND")
|
256
|
+
return len(inx_list) -1
|
257
|
+
|
258
|
+
def get_symmetry_parts(self, check_rslt):
|
259
|
+
max_size = 0
|
260
|
+
max_areas = 0
|
261
|
+
parts_possible = None
|
262
|
+
|
263
|
+
check_rslt.sort(reverse=True)
|
264
|
+
for size, parts, count, area in check_rslt:
|
265
|
+
logger.debug("Result: %s, %s, %s", size, parts, count)
|
266
|
+
|
267
|
+
for size, parts, count, area in check_rslt:
|
268
|
+
if parts is not None and parts > 0:
|
269
|
+
max_size = max(max_size, size)
|
270
|
+
max_areas = max(max_areas, count)
|
271
|
+
|
272
|
+
logger.debug("max size: %s, max areas: %s", max_size, max_areas)
|
273
|
+
|
274
|
+
for size, parts, count, area in check_rslt:
|
275
|
+
if parts is not None and parts <= 1: # critical
|
276
|
+
if count <= max(1, max_areas / 5):
|
277
|
+
if size < max_size / 25:
|
278
|
+
parts = None
|
279
|
+
|
280
|
+
parts_possible = self.calc_parts(parts_possible, parts)
|
281
|
+
|
282
|
+
if parts_possible is None:
|
283
|
+
parts_possible = 0
|
284
|
+
return parts_possible
|
285
|
+
|
286
|
+
def calc_parts(self, parts1, parts2):
|
287
|
+
logger.debug("Calc symmetry Parts (%s, %s)", parts1, parts2)
|
288
|
+
if parts2 is None:
|
289
|
+
logger.debug("return %s parts", parts1)
|
290
|
+
return parts1
|
291
|
+
if parts1 is None:
|
292
|
+
logger.debug("return %s parts", parts2)
|
293
|
+
return parts2
|
294
|
+
if parts1 == 0 or parts2 == 0:
|
295
|
+
logger.debug("return %s parts", 0)
|
296
|
+
return 0
|
297
|
+
parts = gcd(parts1, parts2)
|
298
|
+
logger.debug("return %s parts", parts)
|
299
|
+
return parts
|
300
|
+
|
301
|
+
def symmetry_lines(self, parts, startangle, endangle):
|
302
|
+
logger.debug("begin symmetry_lines from %s to %s",
|
303
|
+
startangle,
|
304
|
+
endangle)
|
305
|
+
if less_equal(endangle, startangle):
|
306
|
+
endangle += 2*np.pi
|
307
|
+
|
308
|
+
delta = alpha_angle(startangle, endangle) / parts
|
309
|
+
start = startangle + delta
|
310
|
+
while less(start, endangle):
|
311
|
+
yield start
|
312
|
+
start += delta
|
313
|
+
|
314
|
+
if is_same_angle(startangle, endangle):
|
315
|
+
yield start
|
316
|
+
|
317
|
+
# Damit man anschliessend ohne Umstände schneiden kann.
|
318
|
+
self.geom.sym_startangle = startangle
|
319
|
+
self.geom.sym_endangle = startangle + delta
|
320
|
+
self.geom.sym_slices = parts
|
321
|
+
self.geom.sym_slice_angle = delta
|
322
|
+
self.geom.sym_area = Area([], (0,0), 0.0)
|
323
|
+
self.geom.sym_area.sym_startangle = self.geom.sym_startangle
|
324
|
+
self.geom.sym_area.sym_endangle = self.geom.sym_endangle
|
325
|
+
logger.debug("end symmetry_lines")
|
326
|
+
|
327
|
+
def check_symmetry_of_mirror(self, mirror_geom, mirrorangle):
|
328
|
+
logger.debug("begin of Symmetry::check_symmetry_of_mirror")
|
329
|
+
assert(mirror_geom is not None)
|
330
|
+
|
331
|
+
axis_p = point(self.geom.center, self.geom.max_radius, mirrorangle)
|
332
|
+
axis_m = line_m(self.geom.center, axis_p)
|
333
|
+
axis_n = line_n(self.geom.center, axis_m)
|
334
|
+
|
335
|
+
def counterpart_found(node, nodes, rtol, atol):
|
336
|
+
hits = 0
|
337
|
+
for n in nodes:
|
338
|
+
if points_are_close(node, n, rtol, atol):
|
339
|
+
logger.debug(" ---- %s is %s", node, n)
|
340
|
+
return True
|
341
|
+
return False
|
342
|
+
|
343
|
+
def check_differences(geom, mirror_geom):
|
344
|
+
geom_ag_nodes = []
|
345
|
+
geom_nodes = [n for n in geom.g.nodes() if not (n in geom_ag_nodes)]
|
346
|
+
|
347
|
+
hit = 0
|
348
|
+
for n in geom_nodes:
|
349
|
+
mirror_n = mirror_point(n, geom.center, axis_m, axis_n)
|
350
|
+
if counterpart_found(mirror_n,
|
351
|
+
mirror_geom.g.nodes(),
|
352
|
+
self.rtol,
|
353
|
+
self.atol):
|
354
|
+
hit += 1
|
355
|
+
min_nodes = min(len(geom_nodes), int(len(geom_nodes) * 0.95) + 1)
|
356
|
+
logger.debug("Nodes=%s, Counterparts=%s", len(geom_nodes), hit)
|
357
|
+
if hit < min_nodes:
|
358
|
+
return hit / len(geom_nodes)
|
359
|
+
else:
|
360
|
+
return 1.0
|
361
|
+
|
362
|
+
# ----------------
|
363
|
+
logger.debug("check geom - mirror")
|
364
|
+
f1 = check_differences(self.geom, mirror_geom)
|
365
|
+
logger.debug("check mirror - geom")
|
366
|
+
f2 = check_differences(mirror_geom, self.geom)
|
367
|
+
logger.debug("Factor 1: %s, 2: %s", f1, f2)
|
368
|
+
ok = f1 > 0.97 and f2 > 0.97
|
369
|
+
logger.debug("end of Symmetry::check_symmetry_of_mirror => %s", ok)
|
370
|
+
return ok
|
femagtools/isa7.py
CHANGED
@@ -868,14 +868,14 @@ class Isa7(object):
|
|
868
868
|
except AttributeError:
|
869
869
|
pass
|
870
870
|
|
871
|
-
try:
|
871
|
+
try:
|
872
872
|
flx_fac = 1000
|
873
|
-
if isinstance(reader.el_fe_induction_1, list):
|
873
|
+
if isinstance(reader.el_fe_induction_1, list):
|
874
874
|
pass
|
875
|
-
else:
|
876
|
-
if reader.el_fe_induction_1.dtype == 'int16':
|
875
|
+
else:
|
876
|
+
if reader.el_fe_induction_1.dtype == 'int16':
|
877
877
|
flx_fac = 1000
|
878
|
-
else:
|
878
|
+
else:
|
879
879
|
flx_fac = 1
|
880
880
|
el_fe_ind = [np.array(reader.el_fe_induction_1).T/flx_fac,
|
881
881
|
np.array(reader.el_fe_induction_2).T/flx_fac]
|
@@ -1432,6 +1432,7 @@ class Element(BaseEntity):
|
|
1432
1432
|
"""return temperature of this element"""
|
1433
1433
|
return sum([v.vpot[1] for v in self.vertices])/len(self.vertices)
|
1434
1434
|
|
1435
|
+
|
1435
1436
|
class SuperElement(BaseEntity):
|
1436
1437
|
def __init__(self, key, sr_key, elements, nodechains, color,
|
1437
1438
|
nc_keys, mcvtype, condtype, conduc, length,
|
femagtools/machine/effloss.py
CHANGED
@@ -238,7 +238,7 @@ def efficiency_losses_map(eecpars, u1, T, temp, n, npoints=(60, 40),
|
|
238
238
|
with_pmconst=with_pmconst, with_tmech=with_tmech) # braking mode
|
239
239
|
|
240
240
|
if kwargs.get('mesh_func', 0):
|
241
|
-
ntmesh = kwargs['mesh_func'](r['n'], r['T'],
|
241
|
+
ntmesh = kwargs['mesh_func'](r['n_type'], r['n'], r['T'],
|
242
242
|
rb['n'], rb['T'], npoints)
|
243
243
|
else:
|
244
244
|
ntmesh = _generate_mesh(r['n'], r['T'],
|
femagtools/machine/im.py
CHANGED
@@ -63,7 +63,7 @@ def ring_leakage_inductance(machine):
|
|
63
63
|
mue0 = 4*np.pi*1e-7
|
64
64
|
|
65
65
|
Qr = machine['rotor']['num_slots']
|
66
|
-
m = machine['
|
66
|
+
m = machine['windings']['num_phases']
|
67
67
|
p = machine['poles']//2
|
68
68
|
lbar = machine['lfe']
|
69
69
|
ls = lbar
|
@@ -622,6 +622,7 @@ def parident(workdir, engine, f1, u1, wdgcon,
|
|
622
622
|
i_max_fact: (float) factor for maximum current to calculate no_load flux (default=2.5)
|
623
623
|
templatedirs: (list of str) names of directories to search for templates
|
624
624
|
"""
|
625
|
+
cmd = kwargs.get("cmd", None)
|
625
626
|
CON = {'open': 0, 'wye': 1, 'star': 1, 'delta': 2}
|
626
627
|
p = machine['poles']//2
|
627
628
|
slip = 1e-2
|
@@ -676,7 +677,7 @@ def parident(workdir, engine, f1, u1, wdgcon,
|
|
676
677
|
|
677
678
|
parstudy = femagtools.parstudy.ParameterStudy(
|
678
679
|
workdir, condMat=condMat,
|
679
|
-
magnetizingCurves=magnetizingCurves)
|
680
|
+
magnetizingCurves=magnetizingCurves, cmd=cmd)
|
680
681
|
|
681
682
|
builder = femagtools.fsl.Builder(kwargs.get('templatedirs', []))
|
682
683
|
model = femagtools.model.MachineModel(m)
|
femagtools/machine/pm.py
CHANGED
@@ -69,6 +69,7 @@ class PmRelMachine(object):
|
|
69
69
|
self.rotor_mass = 0
|
70
70
|
self.kth1 = KTH
|
71
71
|
self.bertotti = False
|
72
|
+
self.max_torque = 0.0
|
72
73
|
self.losskeys = ['styoke_hyst', 'stteeth_hyst',
|
73
74
|
'styoke_eddy', 'stteeth_eddy',
|
74
75
|
'rotor_hyst', 'rotor_eddy',
|
@@ -165,6 +166,46 @@ class PmRelMachine(object):
|
|
165
166
|
return iq, 0, self.torque_iqd(iq, 0)
|
166
167
|
|
167
168
|
def iqd_tmech(self, torque, n, iqd0=0, with_mtpa=True):
|
169
|
+
"""return minimum d-q-current for shaft torque"""
|
170
|
+
if np.abs(torque) < 1e-2:
|
171
|
+
return (0, 0)
|
172
|
+
if np.isscalar(iqd0):
|
173
|
+
tx = self.tmech_iqd(self.io[0], 0, n)
|
174
|
+
iq0 = min(0.9*self.i1range[1]/np.sqrt(2),
|
175
|
+
np.abs(torque)/tx*self.io[0])
|
176
|
+
if torque < 0:
|
177
|
+
i0 = (-iq0, 0)
|
178
|
+
else:
|
179
|
+
i0 = (iq0, 0)
|
180
|
+
logger.debug("initial guess i0 %f -> %s tx %f torque %f",
|
181
|
+
self.io[0], i0, tx, torque)
|
182
|
+
else:
|
183
|
+
i0 = iqd0
|
184
|
+
|
185
|
+
if with_mtpa:
|
186
|
+
k=0
|
187
|
+
while k < 6:
|
188
|
+
res = so.minimize(
|
189
|
+
lambda iqd: la.norm(iqd), i0, method='SLSQP',
|
190
|
+
constraints=({'type': 'eq',
|
191
|
+
'fun': lambda iqd:
|
192
|
+
self.tmech_iqd(*iqd, n) - torque}))
|
193
|
+
if res.success:
|
194
|
+
return res.x
|
195
|
+
# make new initial guess:
|
196
|
+
tx = self.tmech_iqd(*i0, n)
|
197
|
+
logger.debug("k %d new guess i0 %s tx %f torque %f",
|
198
|
+
k, i0, tx, torque)
|
199
|
+
i0=(min(0.9*self.i1range[1]/np.sqrt(2), torque/tx*i0[0]), 0)
|
200
|
+
k += 1
|
201
|
+
raise ValueError(
|
202
|
+
f'Torque {torque} speed {n} {i0} {res.message}')
|
203
|
+
def tqiq(iq):
|
204
|
+
return torque - self.tmech_iqd(float(iq), 0, n)
|
205
|
+
iq = so.fsolve(tqiq, (i0[0],))[0]
|
206
|
+
return iq, 0, self.tmech_iqd(iq, 0, n)
|
207
|
+
|
208
|
+
def iqd_tmech0(self, torque, n, iqd0=0, with_mtpa=True):
|
168
209
|
"""return minimum d-q-current for shaft torque"""
|
169
210
|
if np.abs(torque) < 1e-2:
|
170
211
|
return (0, 0)
|
@@ -681,17 +722,24 @@ class PmRelMachine(object):
|
|
681
722
|
iq, id, T = self.mtpa(i1max)
|
682
723
|
w1type = self.w1_umax(u1max, iq, id)
|
683
724
|
Pmax = w1type/self.p*T
|
684
|
-
w1max = 2*np.pi*speedmax*self.p
|
685
725
|
# check max speed:
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
726
|
+
sp = speedmax
|
727
|
+
while sp > w1type/2/np.pi/self.p:
|
728
|
+
w1max = 2*np.pi*sp*self.p
|
729
|
+
try:
|
730
|
+
if with_pmconst:
|
731
|
+
iq, id, tq = self.iqd_pmech_imax_umax(
|
732
|
+
sp, Pmax, i1max, u1max,
|
733
|
+
with_mtpa, with_tmech)
|
734
|
+
else:
|
735
|
+
iq, id, tq = self.iqd_imax_umax(
|
736
|
+
i1max, w1max, u1max,
|
737
|
+
T, with_mtpv=False,
|
738
|
+
with_tmech=with_tmech)
|
739
|
+
break
|
740
|
+
except ValueError:
|
741
|
+
sp -= 5e-2*speedmax
|
742
|
+
speedmax = sp
|
695
743
|
i1 = betai1(iq, id)[1]
|
696
744
|
if (abs(i1max) >= i1
|
697
745
|
and round(u1max, 1) >= round(np.linalg.norm(
|
@@ -843,7 +891,7 @@ class PmRelMachine(object):
|
|
843
891
|
def characteristics(self, T, n, u1max, nsamples=60,
|
844
892
|
with_mtpv=True, with_mtpa=True,
|
845
893
|
with_pmconst=True, with_tmech=True,
|
846
|
-
with_torque_corr=False):
|
894
|
+
with_torque_corr=False, **kwargs):
|
847
895
|
"""calculate torque speed characteristics.
|
848
896
|
return dict with list values of
|
849
897
|
id, iq, n, T, ud, uq, u1, i1,
|
@@ -863,6 +911,9 @@ class PmRelMachine(object):
|
|
863
911
|
r = dict(id=[], iq=[], uq=[], ud=[], u1=[], i1=[], T=[],
|
864
912
|
beta=[], gamma=[], phi=[], cosphi=[], pmech=[], n=[])
|
865
913
|
|
914
|
+
if kwargs.get('i1max', 0):
|
915
|
+
w1type, T = self.w1_imax_umax(kwargs['i1max'], u1max)
|
916
|
+
|
866
917
|
if np.isscalar(T):
|
867
918
|
tmax = self.torquemax(self.i1range[1])
|
868
919
|
tmin = 0
|
@@ -923,6 +974,9 @@ class PmRelMachine(object):
|
|
923
974
|
if speedrange[-1] < speedrange[-2]:
|
924
975
|
speedrange = speedrange[:-1]
|
925
976
|
logger.info("Speedrange T=%g Nm %s", Tf, speedrange)
|
977
|
+
if speedrange[-1] < nmax:
|
978
|
+
logger.warning("adjusted nmax %f -> %f", nmax, speedrange[-1])
|
979
|
+
|
926
980
|
n3 = speedrange[-1]
|
927
981
|
nstab = [int(nsamples*(x1-x2)/n3)
|
928
982
|
for x1, x2 in zip(speedrange[1:],
|
@@ -1135,7 +1189,12 @@ class PmRelMachineLdq(PmRelMachine):
|
|
1135
1189
|
if np.any(beta[beta > np.pi]):
|
1136
1190
|
beta[beta > np.pi] = beta - 2*np.pi
|
1137
1191
|
|
1138
|
-
self.
|
1192
|
+
self.betarange = min(beta), max(beta)
|
1193
|
+
if min(beta) < -np.pi/2 and max(beta) > -np.pi/2:
|
1194
|
+
self.io = iqd(-np.pi/4, np.max(i1)/2)
|
1195
|
+
else:
|
1196
|
+
self.io = iqd((np.min(beta)+max(beta))/2, np.max(i1)/2)
|
1197
|
+
|
1139
1198
|
kx = ky = 3
|
1140
1199
|
if len(i1) < 4:
|
1141
1200
|
ky = len(i1)-1
|
femagtools/machine/sm.py
CHANGED
@@ -46,6 +46,7 @@ def parident(workdir, engine, machine,
|
|
46
46
|
speed: rotor speed in 1/s (default 160/p)
|
47
47
|
i1_max: maximum current in A rms (default approx 3*i1nom)
|
48
48
|
"""
|
49
|
+
cmd = kwargs.get('cmd', None)
|
49
50
|
da1 = machine['outer_diam']
|
50
51
|
Q1 = machine['stator']['num_slots']
|
51
52
|
if 'statorRotor3' in machine['stator']:
|
@@ -59,7 +60,7 @@ def parident(workdir, engine, machine,
|
|
59
60
|
N = machine[wdgk]['num_wires']
|
60
61
|
i1_max = round(0.28*np.pi*hs*(da1+hs)/Q1/N*Jmax*1e5)*10 * \
|
61
62
|
machine[wdgk].get('num_par_wdgs', 1)
|
62
|
-
|
63
|
+
|
63
64
|
ifnom = machine['rotor']['ifnom']
|
64
65
|
exc_logspace = True
|
65
66
|
if exc_logspace:
|
@@ -78,7 +79,7 @@ def parident(workdir, engine, machine,
|
|
78
79
|
|
79
80
|
parvar = parstudy.List(
|
80
81
|
workdir, condMat=condMat,
|
81
|
-
magnetizingCurves=magnetizingCurves)
|
82
|
+
magnetizingCurves=magnetizingCurves, cmd=cmd)
|
82
83
|
|
83
84
|
simulation = dict(
|
84
85
|
calculationMode=kwargs.get('calculationMode',
|
@@ -240,6 +241,7 @@ class SynchronousMachine(object):
|
|
240
241
|
self.kth1 = KTH
|
241
242
|
self.kth2 = KTH
|
242
243
|
self.skin_resistance = [None, None]
|
244
|
+
self.kpmag = 1
|
243
245
|
# here you can set user defined functions for calculating the skin-resistance,
|
244
246
|
# according to the current frequency w. First function in list is for stator, second for rotor.
|
245
247
|
# If None, the femagtools intern default implementation is used.
|