femagtools 1.8.1__py3-none-any.whl → 1.8.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/dxfsl/shape.py CHANGED
@@ -11,7 +11,7 @@ from __future__ import print_function
11
11
  import numpy as np
12
12
  import logging
13
13
  from .functions import less_equal, greater_equal
14
- from .functions import distance, line_m, line_n
14
+ from .functions import distance, line_m, line_n, mirror_point
15
15
  from .functions import point, points_are_close, points_on_arc
16
16
  from .functions import alpha_line, alpha_angle, alpha_triangle
17
17
  from .functions import normalise_angle, min_angle, max_angle, get_angle_of_arc
@@ -54,6 +54,9 @@ class Shape(object):
54
54
  def classname(self):
55
55
  return "Shape"
56
56
 
57
+ def clone(self):
58
+ return None
59
+
57
60
  def set_my_color(self, color):
58
61
  self.my_color = color
59
62
 
@@ -314,6 +317,32 @@ class Shape(object):
314
317
  def is_near(self, n):
315
318
  return False
316
319
 
320
+ def mirror_shape(self, geom_center, axis_m, axis_n):
321
+ n2 = mirror_point(self.start(), geom_center, axis_m, axis_n)
322
+ n1 = mirror_point(self.end(), geom_center, axis_m, axis_n)
323
+
324
+ el = None
325
+ if isinstance(self, Line):
326
+ el = Line(Element(start=n1, end=n2))
327
+
328
+ elif isinstance(self, Arc):
329
+ c = mirror_point(self.center, geom_center, axis_m, axis_n)
330
+ alpha1 = alpha_line(c, n1)
331
+ alpha2 = alpha_line(c, n2)
332
+ el = Arc(Element(center=c,
333
+ radius=self.radius,
334
+ start_angle=alpha1*180/np.pi,
335
+ end_angle=alpha2*180/np.pi))
336
+
337
+ elif isinstance(self, Circle):
338
+ c = mirror_point(self.center, geom_center, axis_m, axis_n)
339
+ el = Circle(Element(center=c,
340
+ radius=self.radius))
341
+
342
+ if el:
343
+ el.copy_attributes(self)
344
+ return el
345
+
317
346
  def print_nodes(self):
318
347
  return " n1={}/n2={}".format(self.n1, self.n2)
319
348
 
@@ -354,6 +383,10 @@ class Circle(Shape):
354
383
  def classname(self):
355
384
  return "Circle"
356
385
 
386
+ def clone(self):
387
+ return Circle(Element(center=self.center,
388
+ radius=self.radius))
389
+
357
390
  def render(self, renderer, color='blue', with_nodes=False):
358
391
  tmp_color = self.get_my_color()
359
392
  if not tmp_color:
@@ -742,6 +775,14 @@ class Arc(Circle):
742
775
  def classname(self):
743
776
  return "Arc"
744
777
 
778
+ def clone(self):
779
+ alpha_start = alpha_line(self.center, self.p1)
780
+ alpha_end = alpha_line(self.center, self.p2)
781
+ return Arc(Element(center=self.center,
782
+ radius=self.radius,
783
+ start_angle=alpha_start*180/np.pi,
784
+ end_angle=alpha_end*180/np.pi))
785
+
745
786
  def render(self, renderer, color='blue', with_nodes=False):
746
787
  tmp_color = self.get_my_color()
747
788
  if not tmp_color:
@@ -1285,6 +1326,10 @@ class Line(Shape):
1285
1326
  def classname(self):
1286
1327
  return "Line"
1287
1328
 
1329
+ def clone(self):
1330
+ return Line(Element(start=self.p1,
1331
+ end=self.p2))
1332
+
1288
1333
  def render(self, renderer, color='blue', with_nodes=False):
1289
1334
  tmp_color = self.get_my_color()
1290
1335
  if not tmp_color:
@@ -106,7 +106,7 @@ def svgshapes(svgfile):
106
106
  for p in svg.findall(".//{http://www.w3.org/2000/svg}path"):
107
107
  m = bcolor.search(p.get('style'))
108
108
  if m:
109
- logger.info("subregion %d: %s", sr, m.groups()[0])
109
+ logger.debug("subregion %d: %s", sr, m.groups()[0])
110
110
  yield from get_shapes(p.get('d'))
111
111
  sr += 1
112
112
  for p in svg.findall(".//{http://www.w3.org/2000/svg}line"):
@@ -12,6 +12,7 @@ import logging
12
12
  import sys
13
13
  from femagtools.dxfsl.shape import Element, Line
14
14
  from femagtools.dxfsl.area import Area
15
+ import femagtools.dxfsl.area as AREA
15
16
  from femagtools.dxfsl.functions import alpha_angle, positive_angle, is_same_angle
16
17
  from femagtools.dxfsl.functions import min_angle, max_angle, gcd, point
17
18
  from femagtools.dxfsl.functions import less_equal, less, points_are_close
@@ -100,24 +101,25 @@ class Symmetry(object):
100
101
  return positive_angle(alpha_angle(self.startangle,
101
102
  a.get_mid_angle(self.geom.center)))
102
103
 
103
- def find_symmetry(self):
104
- arealist = self.geom.list_of_areas()
105
- logger.debug("begin of Symmetry::find_symmetry: %s areas available", len(arealist))
106
- if len(arealist) == 0:
107
- logger.debug("end of find_symmetry: no areas")
108
- return 0
104
+ def area_list_entry(self, a):
105
+ return (round(a.get_alpha(self.geom.center), 3),
106
+ round(a.min_dist, 1),
107
+ round(a.height, 1),
108
+ self.calc_mid_angle(a),
109
+ a)
109
110
 
110
- logger.debug("startangle=%s, endangle=%s", self.startangle, self.endangle)
111
+ def build_area_list(self, types=()):
112
+ arealist = self.geom.list_of_areas()
113
+ if types:
114
+ arealist = [a for a in arealist if a.type in types]
111
115
 
112
116
  areas = []
113
117
  for a in arealist:
114
- areas.append((round(a.get_alpha(self.geom.center), 3),
115
- round(a.min_dist, 1),
116
- round(a.height, 1),
117
- self.calc_mid_angle(a),
118
- a))
118
+ areas.append(self.area_list_entry(a))
119
119
  areas.sort(reverse=True)
120
+ return areas
120
121
 
122
+ def build_results(self, areas):
121
123
  a0_alpha, a0_min_dist, a0_height, a0_mid_angle, a0 = areas[0]
122
124
  equal_areas = [(a0_mid_angle, a0)]
123
125
  check_rslt = []
@@ -146,6 +148,86 @@ class Symmetry(object):
146
148
  rslt['area'] = a0
147
149
  rslt['areasize'] = areasize
148
150
  check_rslt.append((areasize, rslt))
151
+ return check_rslt
152
+
153
+ def get_winding_symmetry(self, inside=False):
154
+ if inside:
155
+ areas = [self.area_list_entry(a) for a in self.geom.list_of_areas()
156
+ if not a.close_to_ag]
157
+ else:
158
+ areas = self.build_area_list((AREA.TYPE_WINDINGS,))
159
+
160
+ logger.debug("begin of Symmetry::get_winding_symmetry: %s areas available", len(areas))
161
+ if not areas:
162
+ logger.debug("end of Symmetry::get_winding_symmetry: no areas")
163
+ return 0
164
+
165
+ check_rslt = self.build_results(areas)
166
+ logger.debug("%s results available", len(check_rslt))
167
+ [logger.debug("Result: %s", rslt) for rslt in check_rslt]
168
+
169
+ parts, start_delta = self.get_symmetry_parts(check_rslt)
170
+ if parts <= 1:
171
+ return 0
172
+ self.create_cut_lines(parts, start_delta)
173
+
174
+ sym = self.geom_part * parts
175
+ delta = 2*np.pi/sym
176
+ self.set_symmetry_parameters(self.startangle, parts, delta)
177
+
178
+ logger.debug("end of Symmetry::get_winding_symmetry: parts=%s", parts)
179
+ return parts
180
+
181
+ def get_magnet_symmetry(self):
182
+ areas = self.build_area_list((AREA.TYPE_MAGNET_AIRGAP, AREA.TYPE_MAGNET_RECT,))
183
+ air = self.build_area_list((AREA.TYPE_AIR,))
184
+ mag_list = [a for a in self.geom.list_of_areas() if a.is_magnet()]
185
+ air_list = [a for a in self.geom.list_of_areas() if a.is_air()]
186
+ sz_list = [a.area_size() for a in self.geom.list_of_areas()]
187
+ max_sz = max(sz_list)
188
+ for a in air_list:
189
+ if a.area_size() < max_sz * 0.005:
190
+ continue
191
+ for m in mag_list:
192
+ if a.is_touching(m):
193
+ areas.append(self.area_list_entry(a))
194
+ break
195
+
196
+ logger.debug("begin of Symmetry::get_magnet_symmetry: %s areas available", len(areas))
197
+ if not areas:
198
+ logger.debug("end of Symmetry::get_magnet_symmetry: no areas")
199
+ return 0
200
+
201
+ check_rslt = self.build_results(areas)
202
+ logger.debug("%s results available", len(check_rslt))
203
+ [logger.debug("Result: %s", rslt) for rslt in check_rslt]
204
+ for sz, rslt in check_rslt:
205
+ if not rslt.get('startdelta', 0.0) == 0.0:
206
+ return 0 # not proper
207
+ if rslt.get('halfslice', None):
208
+ return 0 # not proper
209
+
210
+ parts, start_delta = self.get_symmetry_parts(check_rslt)
211
+ if parts <= 1:
212
+ return 0
213
+ self.create_cut_lines(parts, start_delta)
214
+
215
+ sym = self.geom_part * parts
216
+ delta = 2*np.pi/sym
217
+ self.set_symmetry_parameters(self.startangle, parts, delta)
218
+
219
+ logger.debug("end of Symmetry::get_magnet_symmetry: parts=%s", parts)
220
+ return parts
221
+
222
+ def find_symmetry(self):
223
+ areas = self.build_area_list()
224
+
225
+ logger.debug("begin of Symmetry::find_symmetry: %s areas available", len(areas))
226
+ if not areas:
227
+ logger.debug("end of Symmetry::find_symmetry: no areas")
228
+ return 0
229
+
230
+ check_rslt = self.build_results(areas)
149
231
 
150
232
  parts, start_delta = self.get_symmetry_parts(check_rslt)
151
233
  if parts < 2:
@@ -156,18 +238,7 @@ class Symmetry(object):
156
238
  self.startangle = self.startangle - self.delta_angle_corr
157
239
  self.endangle = self.endangle - self.delta_angle_corr
158
240
 
159
- self.geom.clear_cut_lines()
160
- for alpha in self.symmetry_lines(parts,
161
- self.startangle,
162
- start_delta,
163
- self.endangle):
164
- plus = self.geom.max_radius / 10
165
- min_radius = max(10, self.geom.min_radius - plus)
166
- p1 = point(self.geom.center, min_radius, alpha)
167
- p2 = point(self.geom.center, self.geom.max_radius + plus, alpha)
168
- line = Line(Element(start=p1, end=p2))
169
- line.init_attributes(color='green')
170
- self.geom.add_cut_line(line)
241
+ self.create_cut_lines(parts, start_delta)
171
242
 
172
243
  logger.debug("end of Symmetry::find_symmetry: -> %s", parts)
173
244
  return parts
@@ -375,9 +446,6 @@ class Symmetry(object):
375
446
 
376
447
  dlist = []
377
448
  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
449
 
382
450
  for i in inx:
383
451
  for n in range(x, i):
@@ -833,6 +901,20 @@ class Symmetry(object):
833
901
  logger.debug("return %s parts", parts)
834
902
  return parts
835
903
 
904
+ def create_cut_lines(self, parts, start_delta):
905
+ self.geom.clear_cut_lines()
906
+ for alpha in self.symmetry_lines(parts,
907
+ self.startangle,
908
+ start_delta,
909
+ self.endangle):
910
+ plus = self.geom.max_radius / 10
911
+ min_radius = max(10, self.geom.min_radius - plus)
912
+ p1 = point(self.geom.center, min_radius, alpha)
913
+ p2 = point(self.geom.center, self.geom.max_radius + plus, alpha)
914
+ line = Line(Element(start=p1, end=p2))
915
+ line.init_attributes(color='green')
916
+ self.geom.add_cut_line(line)
917
+
836
918
  def symmetry_lines(self, parts, startangle, start_delta, endangle):
837
919
  logger.debug("begin symmetry_lines from %s to %s with start %s",
838
920
  startangle,
@@ -858,14 +940,17 @@ class Symmetry(object):
858
940
  yield start
859
941
 
860
942
  # Damit man anschliessend ohne Umstände schneiden kann.
861
- self.geom.sym_startangle = sym_startangle
862
- self.geom.sym_endangle = sym_startangle + delta
943
+ self.set_symmetry_parameters(sym_startangle, parts, delta)
944
+ logger.debug("end symmetry_lines")
945
+
946
+ def set_symmetry_parameters(self, startangle, parts, delta):
947
+ self.geom.sym_startangle = startangle
948
+ self.geom.sym_endangle = startangle + delta
863
949
  self.geom.sym_slices = parts
864
950
  self.geom.sym_slice_angle = delta
865
951
  self.geom.sym_area = Area([], (0,0), 0.0)
866
952
  self.geom.sym_area.sym_startangle = self.geom.sym_startangle
867
953
  self.geom.sym_area.sym_endangle = self.geom.sym_endangle
868
- logger.debug("end symmetry_lines")
869
954
 
870
955
  def check_symmetry_of_mirror(self, mirror_geom, mirrorangle):
871
956
  logger.debug("begin of Symmetry::check_symmetry_of_mirror")
femagtools/fsl.py CHANGED
@@ -158,13 +158,13 @@ class Builder:
158
158
  if templ == 'statorFsl':
159
159
  # obsolete
160
160
  th_props = [' ']
161
- try:
161
+ try:
162
162
  th_props = [f'stator_density = {model.stator["density"]}',
163
163
  f'stator_thcond = {model.stator["thcond"]}',
164
164
  f'stator_thcap = {model.stator["thcap"]}',
165
165
  ]
166
- except:
167
- pass
166
+ except:
167
+ pass
168
168
  if 'parameter' in model.stator['statorFsl']:
169
169
  return self.render_template(
170
170
  model.stator['statorFsl']['content_template'],
@@ -226,20 +226,20 @@ class Builder:
226
226
  self.fsl_rotor = True
227
227
  # obsolete
228
228
  th_props = [' ']
229
- try:
229
+ try:
230
230
  logger.info(model.magnet)
231
231
  th_props = [f'rotor_density = {model["magnet"]["density"]}',
232
232
  f'rotor_thcond = {model["magnet"]["thcond"]}',
233
233
  f'rotor_thcap = {model["magnet"]["thcap"]}'
234
234
  ]
235
- except:
236
- pass
235
+ except:
236
+ pass
237
237
  if 'parameter' in model.magnet['magnetFsl']:
238
238
  return mcv + self.render_template(
239
239
  model.magnet['magnetFsl']['content_template'],
240
240
  model.magnet['magnetFsl']['parameter']) + th_props
241
241
  elif model.magnet['magnetFsl'].get('content'):
242
- return mcv + model.magnet['magnetFsl']['content'].split('\n')
242
+ return mcv + model.magnet['magnetFsl']['content'].split('\n')
243
243
  if isinstance(model.magnet['magnetFsl']
244
244
  ['content_template'], str):
245
245
  with open(model.magnet['magnetFsl']
@@ -248,7 +248,7 @@ class Builder:
248
248
  else:
249
249
  templ = model.magnet['magnetFsl']['content_template']
250
250
  return mcv + self.render_template(
251
- '\n'.join(templ),
251
+ '\n'.join(templ),
252
252
  model.magnet['magnetFsl'])
253
253
 
254
254
  templ = model.magnettype()
@@ -364,8 +364,8 @@ class Builder:
364
364
  """return connect_model if rotating machine and incomplete model
365
365
  (Note: femag bug with connect model)"
366
366
  """
367
- if (model.get('move_action') == 0 and (
368
- model.connect_full or
367
+ if (model.connect_full or (
368
+ model.get('move_action', 0) == 0 and
369
369
  model.stator['num_slots'] > model.stator['num_slots_gen'])):
370
370
  fslcmds = ['pre_models("connect_models")\n']
371
371
  if 'thcond' in model.stator:
@@ -68,8 +68,9 @@ def create_from_eecpars(temp, eecpars, lfe=1, wdg=1):
68
68
  psid = rwdg*rlfe*dqp['psid']
69
69
  psiq = rwdg*rlfe*dqp['psiq']
70
70
  losses = __scale_losses(dqp['losses'], rlfe)
71
- losses['ef'] = dqpars[-1]['losses']['ef']
72
- losses['eh'] = dqpars[-1]['losses']['ef']
71
+ losses['ef'] = dqpars[-1]['losses'].get('ef', [2.0, 2.0])
72
+ losses['hf'] = dqpars[-1]['losses'].get('hf', [1.0, 1.0])
73
+ # TODO handle bertotti excess loss factor
73
74
 
74
75
  if 'psidq' in eecpars:
75
76
  machine = PmRelMachinePsidq(
@@ -140,7 +141,7 @@ def create(bch, r1, ls, lfe=1, wdg=1):
140
141
  try:
141
142
  losses = __scale_losses(bch.psidq['losses'], rlfe)
142
143
  losses['ef'] = bch.lossPar.get('ef', [2.0, 2.0])
143
- losses['eh'] = bch.lossPar.get('eh', [1.0, 1.0])
144
+ losses['hf'] = bch.lossPar.get('hf', [1.0, 1.0])
144
145
  except KeyError:
145
146
  losses = {}
146
147
  if 'ex_current' in bch.machine:
@@ -162,7 +163,7 @@ def create(bch, r1, ls, lfe=1, wdg=1):
162
163
  try:
163
164
  losses = __scale_losses(bch.ldq['losses'], rlfe)
164
165
  losses['ef'] = bch.lossPar.get('ef', [2.0, 2.0])
165
- losses['eh'] = bch.lossPar.get('eh', [1.0, 1.0])
166
+ losses['hf'] = bch.lossPar.get('hf', [1.0, 1.0])
166
167
  except KeyError:
167
168
  losses = {}
168
169
  if 'ex_current' in bch.machine:
@@ -25,6 +25,25 @@ AFM_TYPES = (
25
25
  "S2R1_all" # 2 stator, 1 rotor, all simulated
26
26
  )
27
27
 
28
+ def num_agnodes(Q, p, pw, ag):
29
+ """return total number of nodes in airgap per pole
30
+ Args:
31
+ Q: (int) number of slots
32
+ p: (int) number of pole pairs
33
+ pw: (float) pole width
34
+ ag: (float) airgap height
35
+ """
36
+ num_nodes = np.arange(12, 120, 6)
37
+ i = np.argmin(np.abs(pw/num_nodes - ag/2))
38
+ if p*num_nodes[i-1] % Q:
39
+ lcm = np.lcm(Q, 2*p)//p
40
+ nmin, nmax = num_nodes[0]//lcm, num_nodes[-1]//lcm
41
+ num_nodes = np.array(
42
+ [i*lcm for i in range(nmin, nmax) if i*lcm % 6 == 0])
43
+ i = np.argmin(np.abs(pw/num_nodes - ag/2))
44
+ # nodedist 0.5, 2, 4, 6
45
+ return num_nodes[i-1]
46
+
28
47
  def _integrate(radius, pos, val):
29
48
  interp = RegularGridInterpolator((radius, pos), val)
30
49
  def func(x, y):
@@ -141,10 +160,8 @@ def parident(workdir, engine, temp, machine,
141
160
 
142
161
  if "num_agnodes" not in machine:
143
162
  for pw in pole_width:
144
- machine['num_agnodes'] = 6*round(pw/machine['airgap']/4)
145
-
146
- #if machine['num_agnodes'] < nper:
147
- # machine['num_agnodes'] = 8*round(pw/machine['airgap']/4)
163
+ machine['num_agnodes'] = num_agnodes(Q1, p//2, pw,
164
+ machine['airgap'])
148
165
 
149
166
  nlparvardef = {
150
167
  "decision_vars": [
@@ -189,7 +206,8 @@ def parident(workdir, engine, temp, machine,
189
206
 
190
207
  nlresults = pstudy(nlparvardef, machine, nlcalc, engine)
191
208
  if nlresults['status'].count('C') != len(nlresults['status']):
192
- raise ValueError('Noload simulation failed %s', nlresults['status'])
209
+ raise ValueError(
210
+ f"Noload simulation failed {nlresults['status']}")
193
211
  else:
194
212
  nlresults = {"x": [], "f": []}
195
213
  i = 0
@@ -210,7 +228,7 @@ def parident(workdir, engine, temp, machine,
210
228
  nlresults['f'].append({k: v for k, v in r.items()})
211
229
  i = i + 1
212
230
  nlresults.update(process(lfe, pole_width, machine, nlresults['f']))
213
-
231
+ weights = nlresults['weights']
214
232
  current_angles = nlresults['f'][0]['current_angles']
215
233
  results = []
216
234
  i = 0
@@ -325,7 +343,7 @@ def parident(workdir, engine, temp, machine,
325
343
  (-1, num_beta_steps)),
326
344
  axis=1).T.tolist()})
327
345
  ldq.append({'temperature': magtemp,
328
- 'i1':i1, 'beta':beta,
346
+ 'i1': i1, 'beta': beta,
329
347
  'psid': psid.tolist(), 'psiq': psiq.tolist(),
330
348
  'ld': ld, 'lq': lq,
331
349
  'torque': torque.tolist(),
@@ -334,7 +352,8 @@ def parident(workdir, engine, temp, machine,
334
352
  #iq, id = femagtools.machine.utils.iqd(*np.meshgrid(beta, i1))
335
353
 
336
354
  return {'m': machine[wdgk]['num_phases'],
337
- 'p': machine['poles']//2,
355
+ 'p': machine['poles']//2, 'weights': weights,
356
+ 'rotor_mass': sum(weights[1]), "kfric_b": 1,
338
357
  'ls1': 0, 'r1': r1, 'ldq': ldq}
339
358
 
340
359
 
@@ -442,8 +461,17 @@ def process(lfe, pole_width, machine, bch):
442
461
  mmod.outer_diam, mmod.inner_diam)
443
462
  i1 = np.mean([np.max(c) for c in currents])/np.sqrt(2)
444
463
  plcu = mmod.winding['num_phases']*i1**2*r1
464
+ weights = np.array([[0, 0, 0], [0, 0, 0]])
465
+ try:
466
+ for b in bch:
467
+ weights = weights + b['weights']
468
+ weights *= scale_factor
469
+ except KeyError as exc:
470
+ #logger.warning("missing key %s", exc)
471
+ pass
445
472
 
446
473
  return {
474
+ 'weights': weights.tolist(),
447
475
  'pos': pos.tolist(), 'r1': r1,
448
476
  'torque': torque,
449
477
  'emf': emf,
@@ -603,14 +631,12 @@ class AFPM:
603
631
  for pw in pole_width]
604
632
 
605
633
  if "num_agnodes" not in machine:
634
+ Q1 = machine['stator']['num_slots']
635
+ p = machine['poles']
606
636
  for pw in pole_width:
607
- machine['num_agnodes'] = 6*round(pw/machine['airgap']/4)
608
- #Q = machine['stator']['num_slots']
609
- #p = machine['poles']
610
- #nper = np.lcm(Q, p)
611
- #if machine['num_agnodes'] < nper:
612
- # machine['num_agnodes'] = 8*round(pw/machine['airgap']/4)
613
-
637
+ machine['num_agnodes'] = num_agnodes(Q1, p//2, pw,
638
+ machine['airgap'])
639
+ logger.info("Num agnodes/pole %d", machine['num_agnodes'])
614
640
  parvardef = {
615
641
  "decision_vars": [
616
642
  {"values": pole_width,