femagtools 1.6.8__py3-none-any.whl → 1.7.1__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 (51) hide show
  1. femagtools/__init__.py +2 -2
  2. femagtools/bch.py +1 -1
  3. femagtools/dxfsl/area.py +343 -406
  4. femagtools/dxfsl/areabuilder.py +139 -12
  5. femagtools/dxfsl/conv.py +27 -9
  6. femagtools/dxfsl/converter.py +406 -127
  7. femagtools/dxfsl/corner.py +3 -0
  8. femagtools/dxfsl/femparser.py +1 -1
  9. femagtools/dxfsl/fslrenderer.py +290 -246
  10. femagtools/dxfsl/functions.py +4 -2
  11. femagtools/dxfsl/geom.py +1204 -893
  12. femagtools/dxfsl/journal.py +58 -22
  13. femagtools/dxfsl/machine.py +254 -75
  14. femagtools/dxfsl/plotrenderer.py +38 -3
  15. femagtools/dxfsl/shape.py +380 -103
  16. femagtools/dxfsl/symmetry.py +679 -110
  17. femagtools/femag.py +27 -2
  18. femagtools/forcedens.py +65 -40
  19. femagtools/fsl.py +71 -28
  20. femagtools/losscoeffs.py +46 -0
  21. femagtools/machine/effloss.py +8 -1
  22. femagtools/machine/im.py +3 -1
  23. femagtools/machine/pm.py +11 -7
  24. femagtools/machine/sizing.py +15 -12
  25. femagtools/machine/sm.py +114 -33
  26. femagtools/machine/utils.py +38 -34
  27. femagtools/model.py +12 -2
  28. femagtools/moo/population.py +1 -1
  29. femagtools/parstudy.py +17 -1
  30. femagtools/plot/__init__.py +1 -1
  31. femagtools/plot/char.py +24 -7
  32. femagtools/plot/forcedens.py +56 -29
  33. femagtools/plot/mcv.py +4 -1
  34. femagtools/plot/phasor.py +6 -1
  35. femagtools/poc.py +17 -10
  36. femagtools/templates/cogg_calc.mako +7 -1
  37. femagtools/templates/displ_stator_rotor.mako +33 -0
  38. femagtools/templates/fieldcalc.mako +10 -16
  39. femagtools/templates/pm_sym_f_cur.mako +1 -1
  40. femagtools/tks.py +3 -9
  41. {femagtools-1.6.8.dist-info → femagtools-1.7.1.dist-info}/LICENSE +1 -0
  42. {femagtools-1.6.8.dist-info → femagtools-1.7.1.dist-info}/METADATA +7 -4
  43. {femagtools-1.6.8.dist-info → femagtools-1.7.1.dist-info}/RECORD +51 -50
  44. {femagtools-1.6.8.dist-info → femagtools-1.7.1.dist-info}/WHEEL +1 -1
  45. tests/engines/__init__.py +0 -20
  46. tests/geom/__init__.py +0 -20
  47. tests/moo/__init__.py +0 -20
  48. tests/test_model.py +8 -1
  49. tests/test_sizing.py +2 -2
  50. {femagtools-1.6.8.dist-info → femagtools-1.7.1.dist-info}/entry_points.txt +0 -0
  51. {femagtools-1.6.8.dist-info → femagtools-1.7.1.dist-info}/top_level.txt +0 -0
@@ -16,6 +16,7 @@ from femagtools.dxfsl.functions import alpha_angle, positive_angle, is_same_angl
16
16
  from femagtools.dxfsl.functions import min_angle, max_angle, gcd, point
17
17
  from femagtools.dxfsl.functions import less_equal, less, points_are_close
18
18
  from femagtools.dxfsl.functions import line_m, line_n, mirror_point
19
+ from femagtools.dxfsl.functions import part_of_circle, round_point
19
20
 
20
21
  logger = logging.getLogger('femagtools.symmetry')
21
22
 
@@ -30,8 +31,8 @@ class Symmetry(object):
30
31
  geom=None,
31
32
  startangle=None,
32
33
  endangle=None,
33
- rtol=1e-04,
34
- atol=1e-03):
34
+ rtol=1e-03,
35
+ atol=1e-02):
35
36
  assert(geom is not None)
36
37
  assert(startangle is not None)
37
38
  assert(endangle is not None)
@@ -39,17 +40,23 @@ class Symmetry(object):
39
40
  self.geom = geom
40
41
  self.startangle = startangle
41
42
  self.endangle = endangle
43
+ self.geom_part = part_of_circle(self.startangle, self.endangle, 1)
42
44
  self.delta_check_count = 0
43
- self.delta_angle_korr = 0.0
45
+ self.delta_angle_corr = None
44
46
  self.rtol = rtol
45
47
  self.atol = atol
46
48
  self.full = False
49
+ self.ag_radius = 0.0
47
50
  if np.isclose(self.startangle, self.endangle):
48
51
  self.alpha = 2.0*np.pi
49
52
  self.full = True
50
53
  else:
51
54
  self.alpha = alpha_angle(self.startangle, self.endangle)
52
55
  self.full = False
56
+ if geom.is_inner:
57
+ self.ag_radius = geom.max_radius
58
+ else:
59
+ self.ag_radius = geom.min_radius
53
60
  logger.debug("Symmetry(alpha=%s, rtol=%s, atol=%s)", self.alpha, rtol, atol)
54
61
 
55
62
  def __str__(self):
@@ -60,15 +67,33 @@ class Symmetry(object):
60
67
  d1, h1, a1,
61
68
  d2, h2, a2,
62
69
  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)
70
+ equal_d = np.isclose(d1, d2, rtol=rtol, atol=atol) # distance form c
71
+ equal_h = np.isclose(h1, h2, rtol=rtol, atol=atol) # height
72
+ equal_a = np.isclose(a1, a2, rtol=1e-4, atol=1e-2) # angle from c
73
+ if not equal_d:
74
+ logger.debug("equal_area: dist NOT close (%s/%s)", d1, d2)
75
+ if equal_h and equal_a: # but height and angle
76
+ if np.isclose(d1, d2, rtol=1e-2, atol=1.e-1):
77
+ logger.debug(" -- but with more tolerance")
78
+ return True
65
79
  return False
66
- if not np.isclose(h1, h2, rtol=rtol, atol=atol):
67
- logger.debug("height NOT close (%s/%s)", h1, h2)
80
+ if not equal_h:
81
+ logger.debug("equal_area: height NOT close (%s/%s)", h1, h2)
82
+ if equal_d and equal_a: # but distance and angle
83
+ if np.isclose(h1, h2, rtol=1e-2, atol=1e-1) :
84
+ logger.debug(" -- but with more tolerance")
85
+ return True
68
86
  return False
69
- if not np.isclose(a1, a2, rtol=rtol, atol=atol):
70
- logger.debug("alpha NOT close (%s/%s)", a1, a2)
87
+ if not np.isclose(a1, a2, rtol=1e-4, atol=1e-2):
88
+ logger.debug("equal_area: alpha NOT close (%s/%s)", a1, a2)
71
89
  return False
90
+ else:
91
+ if a1 > a2:
92
+ f = a2 / a1
93
+ else:
94
+ f = a1 / a2
95
+ if f < 0.9:
96
+ return False
72
97
  return True
73
98
 
74
99
  def calc_mid_angle(self, a):
@@ -82,6 +107,8 @@ class Symmetry(object):
82
107
  logger.debug("end of find_symmetry: no areas")
83
108
  return 0
84
109
 
110
+ logger.debug("startangle=%s, endangle=%s", self.startangle, self.endangle)
111
+
85
112
  areas = []
86
113
  for a in arealist:
87
114
  areas.append((round(a.get_alpha(self.geom.center), 3),
@@ -97,32 +124,42 @@ class Symmetry(object):
97
124
  for a1_alpha, a1_min_dist, a1_height, a1_mid_angle, a1 in areas[1:]:
98
125
  if self.equal_area(a0_min_dist, a0_height, a0_alpha,
99
126
  a1_min_dist, a1_height, a1_alpha,
100
- rtol=0.01, atol=0.05):
127
+ rtol=0.001, atol=0.05):
101
128
  a0_min_dist = (a0_min_dist + a1_min_dist) / 2
102
129
  a0_height = (a0_height + a1_height) / 2
103
130
  a0_alpha = (a0_alpha + a1_alpha) / 2
104
131
  equal_areas.append((a1_mid_angle, a1))
105
132
  else:
106
- parts = self.check_delta(equal_areas)
107
- check_rslt.append((a0.area_size(), parts, len(equal_areas), a0))
108
-
133
+ rslt = self.check_delta(equal_areas)
134
+ areasize = a0.area_size()
135
+ rslt['area'] = a0
136
+ rslt['areasize'] = areasize
137
+ check_rslt.append((areasize, rslt))
109
138
  equal_areas = [(a1_mid_angle, a1)]
110
139
  a0_min_dist = a1_min_dist
111
140
  a0_height = a1_height
112
141
  a0_alpha = a1_alpha
113
142
  a0 = a1
114
143
 
115
- parts = self.check_delta(equal_areas)
116
- check_rslt.append((a0.area_size(), parts, len(equal_areas), a0))
144
+ rslt = self.check_delta(equal_areas)
145
+ areasize = a0.area_size()
146
+ rslt['area'] = a0
147
+ rslt['areasize'] = areasize
148
+ check_rslt.append((areasize, rslt))
117
149
 
118
- parts = self.get_symmetry_parts(check_rslt)
150
+ parts, start_delta = self.get_symmetry_parts(check_rslt)
119
151
  if parts < 2:
120
152
  logger.debug("end of Symmetry::find_symmetry: no symmetry")
121
153
  return parts
122
154
 
155
+ if self.delta_angle_corr is not None and self.delta_angle_corr != 0.0:
156
+ self.startangle = self.startangle - self.delta_angle_corr
157
+ self.endangle = self.endangle - self.delta_angle_corr
158
+
123
159
  self.geom.clear_cut_lines()
124
160
  for alpha in self.symmetry_lines(parts,
125
161
  self.startangle,
162
+ start_delta,
126
163
  self.endangle):
127
164
  plus = self.geom.max_radius / 10
128
165
  min_radius = max(10, self.geom.min_radius - plus)
@@ -134,19 +171,29 @@ class Symmetry(object):
134
171
 
135
172
  logger.debug("end of Symmetry::find_symmetry: -> %s", parts)
136
173
  return parts
137
-
174
+
138
175
  def check_delta(self, area_list):
139
176
  logger.debug("begin of check_delta: %s equal areas", len(area_list))
177
+ result = {'areas': len(area_list),
178
+ 'startdelta': 0.0,
179
+ 'slices': None}
140
180
  if not area_list:
141
181
  logger.debug("end of check_delta: no areas")
142
- return None
182
+ return result
143
183
 
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
184
+ rtol = 1e-3
185
+ atol = 1e-2
186
+
187
+ logger.debug("Geometry: Alpha=%s, Center=%s", self.alpha, self.geom.center)
188
+ mid_angle, a = area_list[0]
189
+ result['height'] = a.height
190
+ result['alpha'] = a.get_alpha(self.geom.center)
191
+ if self.geom.is_inner:
192
+ result['airgap'] = np.isclose(a.max_dist, self.ag_radius)
193
+ else:
194
+ result['airgap'] = np.isclose(a.min_dist, self.ag_radius)
195
+ if len(area_list) == 1: # one area
196
+ return self.check_one_area(mid_angle, a, result, rtol=rtol, atol=atol)
150
197
 
151
198
  self.delta_check_count += 1
152
199
  area_list.sort()
@@ -154,8 +201,9 @@ class Symmetry(object):
154
201
  mid_angle, a = area_list[0]
155
202
  delta = positive_angle(mid_angle * 2)
156
203
  delta_total = mid_angle
204
+ logger.debug("first delta=%s, total=%s", delta, delta_total)
157
205
  delta_list = [delta]
158
-
206
+ mid_delta_list = [(mid_angle, delta)]
159
207
  logger.debug("First mid = %s, delta = %s", mid_angle, delta)
160
208
  logger.debug("%s: d=%s, h=%s, a=%s, mid=%s, delta=%s",
161
209
  a.identifier(),
@@ -173,7 +221,10 @@ class Symmetry(object):
173
221
  delta_angle = alpha_angle(start_angle, mid_angle)
174
222
  delta = positive_angle(delta_angle)
175
223
  delta_total += delta_angle
224
+ logger.debug("next delta=%s, total=%s", delta, delta_total)
225
+
176
226
  delta_list.append(delta)
227
+ mid_delta_list.append((mid_angle, delta))
177
228
 
178
229
  logger.debug("%s: d=%s, h=%s, a=%s, mid=%s, delta=%s",
179
230
  a.identifier(),
@@ -185,103 +236,587 @@ class Symmetry(object):
185
236
  start_angle = mid_angle
186
237
 
187
238
  delta_angle = alpha_angle(start_angle, geom_alpha)
239
+ if np.isclose(delta_angle, np.pi*2, rtol=1e-4, atol=1e-4):
240
+ logger.debug("Last Area is in the middle of endangle")
241
+ delta_angle = 0.0
188
242
  delta = positive_angle(delta_angle * 2)
189
243
  delta_total += delta_angle
190
244
  delta_list.append(delta)
191
- logger.debug("final delta=%s", delta)
245
+ mid_delta_list.append((0.0, delta))
246
+
247
+ logger.debug("final delta=%s, total=%s", delta, delta_total)
248
+ logger.debug("Delta List: %s", delta_list)
249
+ logger.debug("Mid Delta List")
250
+ [logger.debug("-- Mid angle: %s, Delta: %s", a, d) for a, d in mid_delta_list]
192
251
 
193
- if not np.isclose(geom_alpha, delta_total):
252
+ if not np.isclose(geom_alpha, delta_total, rtol=rtol, atol=atol):
194
253
  logger.debug("-- deltas: %s", delta_list)
195
254
  logger.debug("end of check_delta: BAD DELTA %s, (expected %s)",
196
255
  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
-
256
+ result['slices'] = 0
257
+ return result # very bad
258
+
259
+ deltas = self.create_deltas(delta_list, rtol=rtol, atol=atol)
260
+
261
+ logger.debug("Start with looking for symmetry")
262
+
263
+ logger.debug(">> %s Deltas <<", len(deltas))
264
+ [logger.debug(" -- n=%s, delta=%s", n, d) for n, d in deltas]
265
+
266
+ if len(deltas) == 2:
267
+ n1, d1 = deltas[0]
268
+ n2, d2 = deltas[1]
269
+ logger.debug("delta 1: n=%s, delta=%s", n1, d1)
270
+ logger.debug("delta 2: n=%s, delta=%s", n2, d2)
271
+
272
+ if n2 == 2 and n1 > 2 and \
273
+ np.isclose(d1, d2 / 2.0, rtol=rtol, atol=atol):
274
+ if np.isclose(d2, delta_list[0], rtol=rtol, atol=atol) and \
275
+ np.isclose(d2, delta_list[-1], rtol=rtol, atol=atol):
276
+ slices = n1 + n2
277
+ if slices > 4:
278
+ result['slices'] = slices
279
+ else:
280
+ result['slices'] = 1
281
+ result['slices_half'] = slices
282
+ result['halfslice'] = 1
283
+ result['startdelta'] = d1 / 2
284
+ logger.debug("#3: end of check_delta: SYMMETRY FOUND [%s] halfslice",
285
+ result['slices'])
286
+ return result
287
+
288
+ elif n1 == 2 and n2 == 1:
289
+ assert(len(area_list) == 2)
290
+ semi_alpha = result['alpha'] / 2
291
+ a0_mid_angle, a0 = area_list[0]
292
+ a1_mid_angle, a1 = area_list[1]
293
+ a0_start = np.isclose(a0_mid_angle - semi_alpha,
294
+ 0.0,
295
+ rtol=rtol, atol=atol)
296
+ a1_end = np.isclose(a1_mid_angle + semi_alpha,
297
+ self.alpha,
298
+ rtol=rtol, atol=atol)
299
+ if a0_start and a1_end and d1 < d2:
300
+ result['slices'] = 0
301
+ result['halfslice'] = 2
302
+ logger.debug("#4: end of check_delta: half slices")
303
+ return result
304
+
305
+ if not a0_start and not a1_end and d1 > d2:
306
+ parts_in_circ = part_of_circle(0.0, d2, 1)
307
+ parts_in_geom = float(round(parts_in_circ / self.geom_part, 2))
308
+ if parts_in_geom.is_integer():
309
+ parts_in_geom = int(parts_in_geom)
310
+ else:
311
+ parts_in_geom = 0
312
+ result['slices'] = parts_in_geom
313
+ result['slices_half'] = parts_in_geom
314
+ result['halfslice'] = 1
315
+ logger.debug("#5: end of check_delta: SYMMETRY FOUND [%s] halfslice",
316
+ result['slices'])
317
+ return result
318
+
319
+ elif abs(n1 - n2) == 1:
320
+ if d1 < d2:
321
+ if self.check_pairs_in_delta_list(delta_list,
322
+ deltas,
323
+ rtol=rtol, atol=atol):
324
+ result['slices'] = int(len(area_list) / 2)
325
+ delta_angle_corr = (d1 + d2) / 2
326
+ result['delta_corr'] = delta_angle_corr
327
+ logger.debug("Startangle correction by %s", delta_angle_corr)
328
+ logger.debug("#6: end of check_delta: SYMMETRY FOUND [%s]",
329
+ result['slices'])
330
+ return result
331
+
332
+ if len(deltas) > 1:
333
+ n1, d1 = deltas[0]
334
+ n2, d2 = deltas[1]
335
+ if n1 + n2 + 1 == len(area_list) and abs(n1 - n2) == 1:
336
+ dlist = self.check_pairs_of_areas(mid_delta_list,
337
+ min(d1, d2),
338
+ geom_alpha,
339
+ rtol=rtol, atol=atol)
340
+ if dlist:
341
+ delta_list = dlist
342
+ deltas = self.create_deltas(delta_list, rtol=rtol, atol=atol)
343
+
344
+ if False:
345
+ parts_in_circ = part_of_circle(0.0, d1, 1)
346
+ parts_in_geom = float(round(parts_in_circ / self.geom_part, 2))
347
+ if parts_in_geom.is_integer():
348
+ parts_in_geom = int(parts_in_geom)
349
+ else:
350
+ parts_in_geom = 0
351
+
352
+ if parts_in_geom / n1 > 0.75 and parts_in_circ > 15:
353
+ result['slices'] = parts_in_geom
354
+ logger.debug("#7: end of check_delta: SYMMETRY FOUND [%s]",
355
+ result['slices'])
356
+ return result
357
+
358
+ missing_middle = []
359
+ if len(deltas) == 2:
360
+ logger.debug("looking for holes in delta list")
361
+
362
+ delta_n, delta_value = deltas[0]
363
+ logger.debug("First n,v == (%s, %s)", delta_n, delta_value)
364
+ for n, v in deltas[1:]:
365
+ logger.debug("Next n,v == (%s, %s)", n, v)
366
+ if n < delta_n / 4 and \
367
+ np.isclose(delta_value, v / 2.0, rtol=1e-04, atol=1e-03):
368
+ logger.debug("Hole found")
369
+ inx = [i for i, x in enumerate(delta_list)
370
+ if np.isclose(x, v, rtol=rtol, atol=atol)]
371
+ if len(inx) != n:
372
+ logger.debug("Hole missmatch: %s <> %s", len(inx), n)
373
+ result['slices'] = 0
374
+ return result
375
+
376
+ dlist = []
377
+ x = 0
378
+ # logger.info("inx: %s", inx)
379
+ # [logger.info("%s deltas: %s", n, d) for n, d in deltas]
380
+ # [logger.info("area: %s", m) for m, a in area_list]
381
+
382
+ for i in inx:
383
+ for n in range(x, i):
384
+ logger.debug("set value of index %s", n)
385
+ dlist.append(delta_list[n])
386
+ m1, a = area_list[i-1]
387
+ if i < len(area_list):
388
+ m2, a = area_list[i]
389
+ else:
390
+ m2, a = area_list[0]
391
+ mid = (m1 + m2) / 2
392
+ logger.debug("Missing mid is %s", mid)
393
+ missing_middle.append((mid, i))
394
+ x = i+1
395
+ logger.debug("set value in hole")
396
+ dlist.append(delta_value)
397
+ dlist.append(delta_value)
398
+ for n in range(x, len(delta_list)):
399
+ logger.debug("set value of index %s", n)
400
+ dlist.append(delta_list[n])
401
+ logger.debug("New List: %s", dlist)
402
+ delta_list = dlist
403
+
404
+ result['middlelist'] = [m for m, a in area_list]
405
+ result['missing_middles'] = missing_middle
406
+
407
+ if not np.isclose(delta_list[0], delta_list[-1], rtol=rtol, atol=atol):
408
+ logger.debug("First and Last delta not equal")
409
+ if self.full:
410
+ d0 = (delta_list[0] + delta_list[-1]) / 2
411
+ n1, d1 = deltas[0]
412
+ if np.isclose(d0, d1, rtol=rtol, atol=atol):
413
+ delta_angle_corr = (d0 - delta_list[0]) / 2
414
+ result['delta_corr'] = delta_angle_corr
415
+ logger.debug("Startangle correction by %s", delta_angle_corr)
416
+ delta_list[0] = d0
417
+ delta_list[-1] = d0
418
+ else:
419
+ parts = self.check_first_last_difference(delta_list, deltas)
420
+ if parts > 0:
421
+ result['slices'] = parts
422
+ logger.debug("#8: end of check_delta: SYMMETRY FOUND [%s]",
423
+ result['slices'])
424
+ return result
425
+
426
+ logger.debug("Final Delta List: %s", delta_list)
230
427
  d1 = delta_list[0]
231
428
  d1_count = 1
232
429
  inx_list = [0]
233
430
  for x in range(1, len(delta_list)):
234
- if np.isclose(d1, delta_list[x], rtol=1e-3, atol=1e-3):
431
+ if np.isclose(d1, delta_list[x], rtol=rtol, atol=atol):
235
432
  inx_list.append(x)
236
433
  d1_count += 1
237
434
 
238
435
  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:
436
+ result['slices'] = d1_count -1
437
+ logger.debug("#9: end of check_delta: SYMMETRY FOUND [%s]",
438
+ result['slices'])
439
+ return result # very simple
440
+ if len(delta_list) < 3:
242
441
  logger.debug("end of check_delta: One delta only ?!")
243
- return 0
442
+ result['slices'] = 0
443
+ return result
444
+
445
+ logger.debug("index of delta %s: %s", d1, inx_list)
446
+ if len(inx_list) < 2:
447
+ logger.debug("end of check_delta: NO SYMMETRY")
448
+ result['slices'] = 0
449
+ return result
244
450
 
245
- logger.debug("index of delta %s = %s", d1, inx_list)
246
451
  x1 = inx_list[0]
247
452
  x2 = inx_list[1]
248
453
  step = x2 - x1
249
454
  x1 = x2
250
455
  for x2 in inx_list[2:]:
251
456
  if not (x2 - x1 == step):
252
- return 0
457
+ logger.debug("end of check_delta: NO SYMMETRY")
458
+ result['slices'] = 0
459
+ return result
253
460
  x1 = x2
254
-
255
- logger.debug("end of check_delta: SYMMETRY FOUND")
256
- return len(inx_list) -1
461
+
462
+ logger.debug("length of delta %s: %s",
463
+ len(inx_list),
464
+ inx_list)
465
+ result['slices'] = len(inx_list) -1
466
+ logger.debug("#10: end of check_delta: SYMMETRY FOUND [%s]", result['slices'])
467
+ return result
468
+
469
+ def create_deltas(self, delta_list, rtol=1e-3, atol=1e-2):
470
+ delta_list_sorted = [d for d in delta_list]
471
+ delta_list_sorted.sort()
472
+ delta = delta_list_sorted[0]
473
+ delta_n = 1
474
+ deltas = []
475
+ for i in range(1,len(delta_list_sorted)):
476
+ if np.isclose(delta_list_sorted[i], delta, rtol=rtol, atol=atol):
477
+ delta = (delta + delta_list_sorted[i]) / 2
478
+ delta_n += 1
479
+ else:
480
+ deltas.append((delta_n, delta))
481
+ delta = delta_list_sorted[i]
482
+ delta_n = 1
483
+ deltas.append((delta_n, delta))
484
+ deltas.sort(reverse=True)
485
+ return deltas
486
+
487
+ def check_one_area(self, mid_angle, a, result, rtol=1e-3, atol=1e-2):
488
+ logger.debug("begin of check_one_area")
489
+
490
+ alpha = a.get_alpha(self.geom.center)
491
+ logger.debug("Single %s: d=%s, h=%s, a=%s, mid=%s",
492
+ a.identifier(),
493
+ a.min_dist,
494
+ a.height,
495
+ a.get_alpha(self.geom.center),
496
+ mid_angle)
497
+ if np.isclose(alpha, self.alpha, rtol=rtol, atol=atol):
498
+ logger.debug("end of check_one_area: area %s from start to end",
499
+ a.identifier())
500
+ result['slices'] = None
501
+ return result # ok
502
+
503
+ if self.full:
504
+ result['slices'] = 1
505
+ logger.debug("end of check_one_area: full with 1 slice")
506
+ return result
507
+
508
+ delta_angle = alpha_angle(self.startangle, mid_angle)
509
+ delta1 = positive_angle(delta_angle)
510
+ delta_angle = alpha_angle(mid_angle, self.endangle)
511
+ delta2 = positive_angle(delta_angle)
512
+ if np.isclose(delta1, delta2, rtol=rtol, atol=atol):
513
+ result['slices'] = 1
514
+ result['slices_half'] = 2
515
+ result['halfslice'] = 1
516
+ logger.debug("end of check_delta: One Area in the middle")
517
+ else:
518
+ result['middlelist'] = [mid_angle]
519
+ result['slices'] = 0
520
+ logger.debug("end of check_one_area: Area somewhere")
521
+ return result
522
+
523
+ def check_pairs_in_delta_list(self,
524
+ delta_list,
525
+ deltas,
526
+ rtol=1e-3, atol=1e-2):
527
+ if len(deltas) < 2:
528
+ return False
529
+ if len(delta_list) < 5:
530
+ return False
531
+ n1, d1 = deltas[0]
532
+ n2, d2 = deltas[1]
533
+ if not abs(n1 - n2) == 1:
534
+ return False
535
+ # check without first/last
536
+ delta0 = delta_list[1]
537
+ delta1 = delta_list[2]
538
+ for delta2 in delta_list[3:-1]:
539
+ if not np.isclose(delta0, delta2, rtol=rtol, atol=atol):
540
+ return False
541
+ delta0 = delta1
542
+ delta1 = delta2
543
+ logger.debug("** Pairs available **")
544
+ return True
545
+
546
+ def check_pairs_of_areas(self,
547
+ mid_delta_list,
548
+ delta,
549
+ geom_alpha,
550
+ rtol=1e-3, atol=1e-2):
551
+ logger.debug("begin of check_pairs_of_areas")
552
+ if len(mid_delta_list) < 2:
553
+ return None
554
+
555
+ logger.debug("Mid-Delta-List")
556
+ [logger.debug(" -- mid=%s, delta=%s",m, d) for m, d in mid_delta_list]
557
+
558
+ # check
559
+ mid_list = []
560
+ m0, d0 = mid_delta_list[0]
561
+ m1, d1 = mid_delta_list[1]
562
+ if np.isclose(delta, d1, rtol=rtol, atol=atol):
563
+ mx = (m0 + m1) / 2
564
+ mid_list.append(mx)
565
+
566
+ m0, d0 = mid_delta_list[2]
567
+ dx = d1
568
+ for m2, d2 in mid_delta_list[2:-1]:
569
+ #logger.debug("compare %s and %s", d0, d2)
570
+ if not np.isclose(d0, d2, rtol=rtol, atol=atol):
571
+ logger.debug("end of check_pairs_of_areas: bad pairs")
572
+ return None
573
+ if np.isclose(delta, d2, rtol=rtol, atol=atol):
574
+ mx = (m1 + m2) / 2
575
+ mid_list.append(mx)
576
+ d0 = d1
577
+ m1 = m2
578
+ d1 = d2
579
+
580
+ logger.debug("New Mids: %s", mid_list)
581
+ delta = positive_angle(mid_list[0] * 2)
582
+ delta_list = [delta]
583
+ m0 = mid_list[0]
584
+ for m1 in mid_list[1:]:
585
+ delta = positive_angle(alpha_angle(m0, m1))
586
+ delta_list.append(delta)
587
+ m0 = m1
588
+
589
+ delta_angle = alpha_angle(m1, geom_alpha)
590
+ if np.isclose(delta_angle, np.pi*2, rtol=1e-4, atol=1e-4):
591
+ logger.debug("Last Area is in the middle of endangle")
592
+ delta_angle = 0.0
593
+ delta = positive_angle(delta_angle * 2)
594
+ delta_list.append(delta)
595
+ logger.debug("New delta-list: %s", delta_list)
596
+ logger.debug("end of check_pairs_of_areas")
597
+ return delta_list
598
+
599
+ def check_first_last_difference(self, delta_list, deltas):
600
+ logger.debug("begin check_first_last_difference")
601
+ if np.isclose(delta_list[0], delta_list[-1],
602
+ rtol=self.rtol, atol=self.atol):
603
+ logger.debug("end check_first_last_difference: first/last equal")
604
+ return 0
605
+ if len(deltas) != 3:
606
+ logger.debug("end check_first_last_difference: not 3 deltas")
607
+ return 0
608
+ logger.debug(">> 3 Deltas <<")
609
+ n1, d1 = deltas[0]
610
+ n2, d2 = deltas[1]
611
+ n3, d3 = deltas[2]
612
+ if not (n2 == 1 and n3 == 1):
613
+ logger.debug("end check_first_last_difference: first/last diff")
614
+ return 0
615
+ dx = (d2 + d3) / 2
616
+ if not np.isclose(dx, d1, rtol=self.rtol, atol=self.atol):
617
+ logger.debug("end check_first_last_difference: bad deltas")
618
+ return 0
619
+ dx = (delta_list[0] + delta_list[-1]) / 2
620
+ if not np.isclose(dx, d1, rtol=self.rtol, atol=self.atol):
621
+ logger.debug("end check_first_last_difference: bad deltas")
622
+ return 0
623
+
624
+ logger.debug("end check_first_last_difference => %s", n1 + 1)
625
+ return n1 + 1
257
626
 
258
627
  def get_symmetry_parts(self, check_rslt):
259
628
  max_size = 0
260
629
  max_areas = 0
630
+ max_slices = 0
261
631
  parts_possible = None
632
+ self.delta_angle_corr = None
633
+ unsure_sym = False
262
634
 
635
+ check_rslt = [(size, n, rslt) for n, (size, rslt) in enumerate(check_rslt)]
263
636
  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
-
637
+ for size, n, rslt in check_rslt:
638
+ logger.debug("Result: %s, %s", size, rslt)
639
+
640
+ rtol = 1e-3
641
+ atol = 1e-2
642
+
643
+ missing_middles = []
644
+ halfslice = []
645
+ start_delta = None
646
+ start_delta_corr = 0.0
647
+
648
+ with_angle_corr = 0
649
+ without_angle_corr = 0
650
+ maybe_angle_korr = 0
651
+
652
+ for size, n, rslt in check_rslt:
653
+ areas = rslt['areas']
654
+ slices = rslt['slices']
655
+ size = rslt['areasize']
656
+ angle_corr = rslt.get('delta_corr', 0.0)
657
+
658
+ if rslt.get('halfslice', 0) == 1:
659
+ halfslice.append(rslt)
660
+
661
+ if slices is not None:
662
+ if slices > 0:
663
+ max_size = max(max_size, size)
664
+ max_areas = max(max_areas, areas)
665
+ max_slices = max(max_slices, slices)
666
+ missing_middles += rslt.get('missing_middles', [])
667
+ area = rslt['area']
668
+ if not (np.isclose(area.min_dist,
669
+ self.geom.min_radius,
670
+ rtol=1e-4, atol=1e-3) and \
671
+ np.isclose(area.max_dist,
672
+ self.geom.max_radius,
673
+ rtol=1e-4, atol=1e-3)):
674
+ if rslt.get('delta_corr', 0.0) == 0.0:
675
+ without_angle_corr += areas * size
676
+ else:
677
+ with_angle_corr += areas * size
678
+ else:
679
+ maybe_angle_korr += areas * size
272
680
  logger.debug("max size: %s, max areas: %s", max_size, max_areas)
273
681
 
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):
682
+ logger.debug("Angle-Corrections: %s Yes, %s No",
683
+ with_angle_corr, without_angle_corr)
684
+ if np.isclose(with_angle_corr, without_angle_corr):
685
+ with_angle_corr = (maybe_angle_korr > 0)
686
+ else:
687
+ with_angle_corr = (with_angle_corr > without_angle_corr)
688
+
689
+ def get_halfslice_counterpart(rslt):
690
+ if rslt.get('halfslice', 0) != 2:
691
+ return None
692
+ for half in halfslice:
693
+ alpha1 = half['alpha']
694
+ height1 = half['height']
695
+ alpha2 = rslt['alpha'] * 2.0
696
+ height2 = rslt['height']
697
+ logger.debug("-- height: %s / %s", height1, height2)
698
+ logger.debug("-- alpha: %s / %s", alpha1, alpha2)
699
+ if np.isclose(height1, height2, rtol=rtol, atol=atol) and \
700
+ np.isclose(alpha1, alpha2, rtol=rtol, atol=atol):
701
+ return half
702
+ return None
703
+
704
+ if halfslice:
705
+ logger.debug("%s halfslice [1] found", len(halfslice))
706
+
707
+ for size, n, rslt in check_rslt:
708
+ half = get_halfslice_counterpart(rslt)
709
+ if half:
710
+ logger.debug("Halfslice counterpart found")
711
+ alpha1 = half['alpha']
712
+ height1 = half['height']
713
+ alpha2 = rslt['alpha'] * 2.0
714
+ height2 = rslt['height']
715
+ logger.debug("-- height: %s / %s", height1, height2)
716
+ logger.debug("-- alpha: %s / %s", alpha1, alpha2)
717
+ if np.isclose(height1, height2, rtol=rtol, atol=atol) and \
718
+ np.isclose(alpha1, alpha2, rtol=rtol, atol=atol):
719
+ logger.debug("halfslice result: %s", half)
720
+ slices = None
721
+ rslt['slices'] = slices
722
+ half['slices'] = half['slices_half']
723
+
724
+ angle_corr_slices = 0
725
+ angle_corr_size = 0
726
+ angle_corr_areas = 0
727
+ angle_corr_airgap = False
728
+
729
+ for size, n, rslt in check_rslt:
730
+ areas = rslt['areas']
731
+ slices = rslt['slices']
732
+ size = rslt['areasize']
733
+
734
+ if slices is None: # ignore it
735
+ continue
736
+
737
+ delta_angle_corr = rslt.get('delta_corr', 0.0)
738
+ # Angle Correction
739
+ if with_angle_corr and self.delta_angle_corr is None:
740
+ self.delta_angle_corr = delta_angle_corr
741
+ angle_corr_slices = slices
742
+ angle_corr_size = size
743
+ angle_corr_areas = areas
744
+ angle_corr_airgap = rslt.get('airgap', False)
745
+ else:
746
+ if with_angle_corr and self.delta_angle_corr != delta_angle_corr:
747
+ unsure_sym = True
748
+ if slices > angle_corr_slices and \
749
+ size > angle_corr_size * 0.5:
750
+ logger.debug("Angle Correction")
751
+ self.delta_angle_corr = delta_angle_corr
752
+ angle_corr_slices = slices
753
+ angle_corr_size = size
754
+ angle_corr_areas = areas
755
+ angle_corr_airgap = rslt.get('airgap', False)
756
+ elif angle_corr_airgap and \
757
+ slices >= angle_corr_slices and \
758
+ areas > angle_corr_areas and \
759
+ size > angle_corr_size * 0.2:
760
+ logger.debug("Angle Correction")
761
+ self.delta_angle_corr = delta_angle_corr
762
+ angle_corr_slices = slices
763
+ angle_corr_size = size
764
+ angle_corr_areas = areas
765
+ angle_corr_airgap = rslt.get('airgap', False)
766
+ if slices:
767
+ if start_delta is None:
768
+ start_delta = rslt['startdelta']
769
+ start_delta_corr = start_delta
770
+ elif not rslt['startdelta'] != 0.0:
771
+ if start_delta_corr == 0.0:
772
+ start_delta_corr = rslt['startdelta']
773
+ elif not np.isclose(rslt['startdelta'], start_delta_corr,
774
+ rtol=rtol, atol=atol):
775
+ slices = 0 # bad
776
+ rslt['slices'] = slices
777
+
778
+ if slices is not None and slices <= 1: # critical
779
+ if areas <= max(1, max_areas / 5):
277
780
  if size < max_size / 25:
278
- parts = None
781
+ slices = None
782
+ if slices == 0:
783
+ middles = rslt.get("middlelist", [])
784
+ if self.check_missing_areas(middles,
785
+ missing_middles):
786
+ logger.debug("Symmetry-Destroyer destroyed")
787
+ slices = None
279
788
 
280
- parts_possible = self.calc_parts(parts_possible, parts)
789
+ if slices == 1:
790
+ # symmetry killer
791
+ if areas < max(2, max_areas / 6):
792
+ if size < max_size * 0.05:
793
+ slices = None # ignore tiny areas
794
+
795
+
796
+ parts_possible = self.calc_parts(parts_possible, slices)
797
+
798
+ if unsure_sym:
799
+ logger.warning("Warning: unsure symmetry")
281
800
 
282
801
  if parts_possible is None:
283
802
  parts_possible = 0
284
- return parts_possible
803
+ return parts_possible, start_delta
804
+
805
+ def check_missing_areas(self, middles, missing_middles):
806
+ logger.debug("check_missing_areas")
807
+ logger.debug(" -- mids = %s", middles)
808
+ logger.debug(" -- missing mids = %s", missing_middles)
809
+ if not missing_middles:
810
+ return False
811
+ if len(middles) == 0 or len(middles) > 2:
812
+ return False
813
+
814
+ for m in middles:
815
+ mlist = [mm for mm, i in missing_middles
816
+ if np.isclose(m, mm, rtol=1e-3, atol=1e-3)]
817
+ if not mlist:
818
+ return False
819
+ return True
285
820
 
286
821
  def calc_parts(self, parts1, parts2):
287
822
  logger.debug("Calc symmetry Parts (%s, %s)", parts1, parts2)
@@ -298,15 +833,23 @@ class Symmetry(object):
298
833
  logger.debug("return %s parts", parts)
299
834
  return parts
300
835
 
301
- def symmetry_lines(self, parts, startangle, endangle):
302
- logger.debug("begin symmetry_lines from %s to %s",
836
+ def symmetry_lines(self, parts, startangle, start_delta, endangle):
837
+ logger.debug("begin symmetry_lines from %s to %s with start %s",
303
838
  startangle,
304
- endangle)
839
+ endangle,
840
+ start_delta)
841
+
305
842
  if less_equal(endangle, startangle):
306
843
  endangle += 2*np.pi
307
844
 
308
- delta = alpha_angle(startangle, endangle) / parts
309
- start = startangle + delta
845
+ sym = self.geom_part * parts
846
+ delta = 2*np.pi/sym
847
+ if start_delta == 0.0:
848
+ if not is_same_angle(startangle, endangle):
849
+ start_delta = delta
850
+
851
+ sym_startangle = startangle + start_delta
852
+ start = startangle + start_delta
310
853
  while less(start, endangle):
311
854
  yield start
312
855
  start += delta
@@ -315,8 +858,8 @@ class Symmetry(object):
315
858
  yield start
316
859
 
317
860
  # Damit man anschliessend ohne Umstände schneiden kann.
318
- self.geom.sym_startangle = startangle
319
- self.geom.sym_endangle = startangle + delta
861
+ self.geom.sym_startangle = sym_startangle
862
+ self.geom.sym_endangle = sym_startangle + delta
320
863
  self.geom.sym_slices = parts
321
864
  self.geom.sym_slice_angle = delta
322
865
  self.geom.sym_area = Area([], (0,0), 0.0)
@@ -332,39 +875,65 @@ class Symmetry(object):
332
875
  axis_m = line_m(self.geom.center, axis_p)
333
876
  axis_n = line_n(self.geom.center, axis_m)
334
877
 
335
- def counterpart_found(node, nodes, rtol, atol):
336
- hits = 0
878
+ def counterpart_found(node, mirror_node, nodes, rtol, atol):
879
+ hit_sloppy = 0
337
880
  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
881
+ if points_are_close(mirror_node, n, rtol, atol):
882
+ logger.debug(" ---- %s is %s", node, mirror_node)
883
+ return 1, 1
884
+ if points_are_close(round_point(mirror_node, 1),
885
+ round_point(n, 1),
886
+ rtol, atol):
887
+ logger.debug(" ++++ %s is %s", node, mirror_node)
888
+ hit_sloppy = 1
889
+
890
+ logger.debug(" >>>> %s is NOT %s (%s)",
891
+ node, mirror_node, hit_sloppy)
892
+ return 0, hit_sloppy
342
893
 
343
894
  def check_differences(geom, mirror_geom):
344
895
  geom_ag_nodes = []
345
896
  geom_nodes = [n for n in geom.g.nodes() if not (n in geom_ag_nodes)]
346
897
 
347
- hit = 0
898
+ hits = 0
899
+ hits_sloppy = 0
348
900
  for n in geom_nodes:
349
901
  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
902
+ hit, hit_sloppy = counterpart_found(n, mirror_n,
903
+ mirror_geom.g.nodes(),
904
+ # self.rtol,
905
+ 1e-3,
906
+ # self.atol):
907
+ 1e-2)
908
+ hits += hit
909
+ hits_sloppy += hit_sloppy
910
+
355
911
  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)
912
+ logger.debug("Nodes=%s, Counterparts=%s (sloppy=%s)",
913
+ len(geom_nodes), hits, hits_sloppy)
914
+ if hits < min_nodes:
915
+ f = hits / len(geom_nodes)
359
916
  else:
360
- return 1.0
917
+ f = 1.0
918
+ if hits_sloppy < min_nodes:
919
+ f_sloppy = hits_sloppy / len(geom_nodes)
920
+ else:
921
+ f_sloppy = 1.0
922
+ return f, f_sloppy
361
923
 
362
924
  # ----------------
363
925
  logger.debug("check geom - mirror")
364
- f1 = check_differences(self.geom, mirror_geom)
926
+ f1, f1_sloppy = check_differences(self.geom, mirror_geom)
365
927
  logger.debug("check mirror - geom")
366
- f2 = check_differences(mirror_geom, self.geom)
928
+ f2, f2_sloppy = check_differences(mirror_geom, self.geom)
367
929
  logger.debug("Factor 1: %s, 2: %s", f1, f2)
368
- ok = f1 > 0.97 and f2 > 0.97
930
+ if f1 >= 0.99 or f2 >= 0.99:
931
+ ok = not (f1 < 0.9 or f2 < 0.9)
932
+ else:
933
+ ok = f1 > 0.97 and f2 > 0.97
934
+ if not ok:
935
+ if f1_sloppy > 0.97 and f2_sloppy > 0.97:
936
+ logger.debug(" (A sloppy mirror found, but ignored)")
937
+ ok = True
369
938
  logger.debug("end of Symmetry::check_symmetry_of_mirror => %s", ok)
370
939
  return ok