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.
@@ -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,
@@ -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['winding']['num_phases']
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
- if with_pmconst:
687
- iq, id, tq = self.iqd_pmech_imax_umax(
688
- speedmax, Pmax, i1max, u1max,
689
- with_mtpa, with_tmech)
690
- else:
691
- iq, id, tq = self.iqd_imax_umax(
692
- i1max, w1max, u1max,
693
- T, with_mtpv=False,
694
- with_tmech=with_tmech)
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.io = iqd((np.min(beta)+max(beta))/2, np.max(i1)/2)
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.