femagtools 1.6.7__py3-none-any.whl → 1.7.0__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 +2 -2
- femagtools/bch.py +1 -1
- femagtools/dxfsl/area.py +334 -332
- femagtools/dxfsl/areabuilder.py +131 -10
- femagtools/dxfsl/conv.py +27 -9
- femagtools/dxfsl/converter.py +390 -125
- femagtools/dxfsl/corner.py +3 -0
- femagtools/dxfsl/femparser.py +1 -1
- femagtools/dxfsl/fslrenderer.py +290 -246
- femagtools/dxfsl/functions.py +4 -2
- femagtools/dxfsl/geom.py +1120 -886
- femagtools/dxfsl/journal.py +53 -22
- femagtools/dxfsl/machine.py +250 -74
- femagtools/dxfsl/plotrenderer.py +34 -3
- femagtools/dxfsl/shape.py +380 -103
- femagtools/dxfsl/symmetry.py +679 -110
- femagtools/femag.py +27 -2
- femagtools/forcedens.py +65 -40
- femagtools/fsl.py +71 -28
- femagtools/losscoeffs.py +46 -0
- femagtools/machine/effloss.py +8 -1
- femagtools/machine/im.py +3 -1
- femagtools/machine/pm.py +12 -11
- femagtools/machine/sizing.py +14 -11
- femagtools/machine/sm.py +114 -33
- femagtools/machine/utils.py +38 -34
- femagtools/model.py +12 -2
- femagtools/moo/population.py +1 -1
- femagtools/parstudy.py +17 -1
- femagtools/plot/__init__.py +1 -1
- femagtools/plot/char.py +24 -7
- femagtools/plot/forcedens.py +56 -29
- femagtools/plot/mcv.py +4 -1
- femagtools/plot/phasor.py +6 -1
- femagtools/poc.py +17 -10
- femagtools/templates/cogg_calc.mako +7 -1
- femagtools/templates/displ_stator_rotor.mako +33 -0
- femagtools/templates/fieldcalc.mako +10 -16
- femagtools/templates/pm_sym_f_cur.mako +1 -1
- femagtools/tks.py +3 -9
- {femagtools-1.6.7.dist-info → femagtools-1.7.0.dist-info}/LICENSE +1 -0
- {femagtools-1.6.7.dist-info → femagtools-1.7.0.dist-info}/METADATA +7 -4
- {femagtools-1.6.7.dist-info → femagtools-1.7.0.dist-info}/RECORD +50 -49
- tests/engines/__init__.py +0 -20
- tests/geom/__init__.py +0 -20
- tests/moo/__init__.py +0 -20
- tests/test_model.py +8 -1
- {femagtools-1.6.7.dist-info → femagtools-1.7.0.dist-info}/WHEEL +0 -0
- {femagtools-1.6.7.dist-info → femagtools-1.7.0.dist-info}/entry_points.txt +0 -0
- {femagtools-1.6.7.dist-info → femagtools-1.7.0.dist-info}/top_level.txt +0 -0
femagtools/femag.py
CHANGED
@@ -26,6 +26,7 @@ import femagtools.airgap as ag
|
|
26
26
|
import femagtools.fsl
|
27
27
|
import femagtools.config
|
28
28
|
import femagtools.ecloss
|
29
|
+
import femagtools.forcedens
|
29
30
|
|
30
31
|
from femagtools import ntib
|
31
32
|
|
@@ -248,6 +249,9 @@ class BaseFemag(object):
|
|
248
249
|
def get_bch_file(self, modelname, offset=0):
|
249
250
|
return self.get_result_file(modelname, 'B(AT)?CH', offset)
|
250
251
|
|
252
|
+
def get_plt_file(self, modelname, offset=0):
|
253
|
+
return self.get_result_file(modelname, 'PLT0', offset)
|
254
|
+
|
251
255
|
def get_asm_file(self, modelname, offset=0):
|
252
256
|
return self.get_result_file(modelname, 'ASM', offset)
|
253
257
|
|
@@ -317,6 +321,17 @@ class BaseFemag(object):
|
|
317
321
|
result.read(f)
|
318
322
|
return result
|
319
323
|
|
324
|
+
def read_forcedens(self, modelname=None, offset=0):
|
325
|
+
"read most recent PLT0 file and return result"
|
326
|
+
if not modelname:
|
327
|
+
modelname = self._get_modelname_from_log()
|
328
|
+
|
329
|
+
pltfile = self.get_plt_file(modelname, offset)
|
330
|
+
if pltfile:
|
331
|
+
logger.info("Read PLT0 {}".format(pltfile))
|
332
|
+
return femagtools.forcedens.read(pltfile)
|
333
|
+
return None
|
334
|
+
|
320
335
|
def read_isa(self, modelname=None):
|
321
336
|
"read most recent I7/ISA7 file and return result"
|
322
337
|
if not modelname:
|
@@ -555,6 +570,13 @@ class Femag(BaseFemag):
|
|
555
570
|
simulation)))
|
556
571
|
if simulation:
|
557
572
|
stateofproblem = simulation.get('stateofproblem', 'mag_static')
|
573
|
+
if 'eccentricity' in simulation:
|
574
|
+
#assert (self.model.stator['num_slots']
|
575
|
+
# == self.model.stator['num_slots_gen'])
|
576
|
+
for k in ('bore_diam', 'airgap'):
|
577
|
+
if k not in simulation['eccentricity']:
|
578
|
+
simulation['eccentricity'][k] = self.model.get(k)
|
579
|
+
|
558
580
|
if 'poc' in simulation:
|
559
581
|
with open(os.path.join(self.workdir,
|
560
582
|
simulation['pocfilename']), 'w') as f:
|
@@ -579,8 +601,9 @@ class Femag(BaseFemag):
|
|
579
601
|
self.run(fslfile, options, fsl_args, stateofproblem=stateofproblem)
|
580
602
|
if simulation:
|
581
603
|
return self.readResult(simulation)
|
582
|
-
return dict(status='ok', message=self.modelname)
|
583
604
|
|
605
|
+
return {'status': 'ok', 'message': self.modelname,
|
606
|
+
'model': self.model.props()}
|
584
607
|
|
585
608
|
class FemagTask(threading.Thread):
|
586
609
|
def __init__(self, port, args, workdir, logdir):
|
@@ -1096,7 +1119,9 @@ class ZmqFemag(BaseFemag):
|
|
1096
1119
|
raise FemagError(r['message'])
|
1097
1120
|
|
1098
1121
|
if not simulation:
|
1099
|
-
|
1122
|
+
model = self.model.props()
|
1123
|
+
model['name'] = machine['name']
|
1124
|
+
return [r, model]
|
1100
1125
|
|
1101
1126
|
result_file = r['result_file'][0]
|
1102
1127
|
if simulation['calculationMode'] == "pm_sym_loss":
|
femagtools/forcedens.py
CHANGED
@@ -51,6 +51,22 @@ def _readSections(f):
|
|
51
51
|
yield section
|
52
52
|
|
53
53
|
|
54
|
+
def _complete(positions):
|
55
|
+
"""return FN and FT Matrix completed to full model"""
|
56
|
+
ntiles = int(360/positions[0]['X'][-1])
|
57
|
+
try:
|
58
|
+
FN = np.tile(
|
59
|
+
np.array([p['FN'][:-1] for p in positions[:-1]]), (1, ntiles))
|
60
|
+
FT = np.tile(
|
61
|
+
np.array([p['FT'][:-1] for p in positions[:-1]]), (1, ntiles))
|
62
|
+
except AttributeError:
|
63
|
+
return [[], []]
|
64
|
+
|
65
|
+
rotangle = positions[-1]['position'] - positions[0]['position']
|
66
|
+
mtiles = int(round(360/rotangle))
|
67
|
+
return np.tile(FN, (mtiles, 1)).T, np.tile(FT, (mtiles, 1)).T
|
68
|
+
|
69
|
+
|
54
70
|
class ForceDensity(object):
|
55
71
|
|
56
72
|
def __init__(self):
|
@@ -109,7 +125,9 @@ class ForceDensity(object):
|
|
109
125
|
rec = l.split()[1:]
|
110
126
|
if len(rec) == len(labels):
|
111
127
|
m.append([float(x) for x in rec])
|
112
|
-
|
128
|
+
logging.debug("POSITION %s len %d: %s", d['position'],
|
129
|
+
len(m), ','.join(labels))
|
130
|
+
d.update(dict(zip(labels, list(zip(*m)))))
|
113
131
|
|
114
132
|
self.positions.append(d)
|
115
133
|
|
@@ -129,36 +147,33 @@ class ForceDensity(object):
|
|
129
147
|
self.__read_date_and_title(s)
|
130
148
|
elif s[0].startswith('POSITION'):
|
131
149
|
self.__read_position(s)
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
(
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
150
|
+
pos = [p['position'] for p in self.positions]
|
151
|
+
labels = self.positions[0].keys()
|
152
|
+
fn = [p['FN'] for p in self.positions]
|
153
|
+
fnmax = max([max(x) for x in fn])
|
154
|
+
fnmin = min([min(x) for x in fn])
|
155
|
+
flen = set([len(x) for x in fn])
|
156
|
+
if len(flen) > 1:
|
157
|
+
logging.warning("inhomogenous samples: %s", flen)
|
158
|
+
logging.info("%s shape %s: pos %s, FN %s %s",
|
159
|
+
filename, (len(pos), min(list(flen))),
|
160
|
+
(min(pos), max(pos)),
|
161
|
+
(fnmin, fnmax),
|
162
|
+
','.join(labels))
|
163
|
+
self.FN, self.FT = _complete(self.positions)
|
146
164
|
|
147
165
|
def fft(self):
|
148
166
|
"""return 2D FFT of the Force Density"""
|
149
|
-
|
150
|
-
|
151
|
-
N = FN.size
|
152
|
-
dim = FN.shape[0]//2
|
167
|
+
N = self.FN.size
|
168
|
+
dim = self.FN.shape[0]//2
|
153
169
|
# Dimension [Position °] X [Time Steps s]
|
154
|
-
FN_MAG = np.fft.fft2(FN)[0:dim, :]/N
|
155
|
-
FT_MAG = np.fft.fft2(FT)[0:dim, :]/N
|
170
|
+
FN_MAG = np.fft.fft2(self.FN)[0:dim, :]/N
|
171
|
+
FT_MAG = np.fft.fft2(self.FT)[0:dim, :]/N
|
156
172
|
|
157
|
-
return
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
)
|
173
|
+
return {'fn_harm': {'amplitude': np.abs(FN_MAG),
|
174
|
+
'phase': np.angle(FN_MAG)},
|
175
|
+
'ft_harm': {'amplitude': np.abs(FT_MAG),
|
176
|
+
'phase': np.angle(FT_MAG)}}
|
162
177
|
|
163
178
|
def items(self):
|
164
179
|
return [(k, getattr(self, k)) for k in ('version',
|
@@ -210,9 +225,13 @@ def readall(workdir='.'):
|
|
210
225
|
|
211
226
|
|
212
227
|
if __name__ == "__main__":
|
213
|
-
import matplotlib.pyplot as
|
228
|
+
import matplotlib.pyplot as plt
|
214
229
|
import femagtools.plot
|
215
230
|
import sys
|
231
|
+
|
232
|
+
logging.basicConfig(level=logging.INFO,
|
233
|
+
format='%(asctime)s %(message)s')
|
234
|
+
|
216
235
|
if len(sys.argv) == 2:
|
217
236
|
filename = sys.argv[1]
|
218
237
|
else:
|
@@ -221,16 +240,22 @@ if __name__ == "__main__":
|
|
221
240
|
fdens = read(filename)
|
222
241
|
|
223
242
|
# Show the results
|
224
|
-
print(fdens.version)
|
225
|
-
|
226
|
-
title = '{}, Rotor position {}'.format(
|
227
|
-
|
228
|
-
pos = fdens.positions[0]['X']
|
229
|
-
FT_FN = (fdens.positions[0]['FT'],
|
230
|
-
|
231
|
-
femagtools.plot.forcedens(title, pos, FT_FN)
|
232
|
-
pl.show()
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
243
|
+
#print(fdens.version)
|
244
|
+
|
245
|
+
#title = '{}, Rotor position {}'.format(
|
246
|
+
# fdens.title, fdens.positions[0]['position'])
|
247
|
+
#pos = fdens.positions[0]['X']
|
248
|
+
#FT_FN = (fdens.positions[0]['FT'],
|
249
|
+
# fdens.positions[0]['FN'])
|
250
|
+
#femagtools.plot.forcedens(title, pos, FT_FN)
|
251
|
+
#pl.show()
|
252
|
+
|
253
|
+
#femagtools.plot.forcedens_surface(fdens)
|
254
|
+
#pl.show()
|
255
|
+
|
256
|
+
title = 'Radial Force Density'
|
257
|
+
fig, ax = plt.subplots()
|
258
|
+
fig.suptitle(title)
|
259
|
+
femagtools.plot.forcedens_contour(fdens, ax=ax)
|
260
|
+
plt.tight_layout()
|
261
|
+
plt.show()
|
femagtools/fsl.py
CHANGED
@@ -7,7 +7,7 @@ import mako.lookup
|
|
7
7
|
import os
|
8
8
|
import re
|
9
9
|
import sys
|
10
|
-
import
|
10
|
+
import numpy as np
|
11
11
|
import femagtools.windings
|
12
12
|
from femagtools.poc import Poc
|
13
13
|
from . import __version__
|
@@ -73,7 +73,7 @@ class Builder:
|
|
73
73
|
|
74
74
|
g = femagtools.gmsh.Gmsh(model.stator['mshfile']['name'])
|
75
75
|
phi = g.get_section_angles()
|
76
|
-
model.stator['num_slots'] = round(
|
76
|
+
model.stator['num_slots'] = round(np.pi/(phi[1]-phi[0]))
|
77
77
|
r = g.get_section_radius()
|
78
78
|
model.set_value('outer_diam', 2*r[1])
|
79
79
|
model.set_value('bore_diam', 2*r[0])
|
@@ -92,7 +92,7 @@ class Builder:
|
|
92
92
|
self.fsl_stator = True
|
93
93
|
return
|
94
94
|
|
95
|
-
if templ
|
95
|
+
if templ in ('statorFsl', 'dxf'):
|
96
96
|
self.fsl_stator = True
|
97
97
|
|
98
98
|
if templ != 'dxffile':
|
@@ -111,10 +111,13 @@ class Builder:
|
|
111
111
|
conv = convert(model.stator['dxffile']['name'], **params)
|
112
112
|
|
113
113
|
model.stator['num_slots'] = conv.get('tot_num_slot')
|
114
|
+
model.set_value('poles', conv.get('num_poles'))
|
114
115
|
self.set_diameter_parameter(model, conv)
|
115
116
|
if model.get('dy1'):
|
116
117
|
model.set_value('outer_diam', model.get('dy1'))
|
117
118
|
model.set_value('bore_diam', model.get('da1'))
|
119
|
+
if 'agndst' in conv:
|
120
|
+
model.set_value('agndst', model.get('agndst'))
|
118
121
|
|
119
122
|
model.stator['dxf'] = dict(fsl=conv['fsl'])
|
120
123
|
self.fsl_stator = True
|
@@ -126,6 +129,8 @@ class Builder:
|
|
126
129
|
model.set_value(v, conv[v])
|
127
130
|
except KeyError:
|
128
131
|
pass
|
132
|
+
if conv.keys() >= {'da1', 'da2'}:
|
133
|
+
model.set_value('airgap', abs(model.da1 - model.da2)/2)
|
129
134
|
|
130
135
|
def prepare_diameter(self, model):
|
131
136
|
dy1 = model.get('dy1', 0.0)
|
@@ -144,10 +149,11 @@ class Builder:
|
|
144
149
|
def render_stator(self, model):
|
145
150
|
templ = model.statortype()
|
146
151
|
if templ == 'dxf':
|
147
|
-
|
148
|
-
|
149
|
-
'
|
150
|
-
|
152
|
+
agndst = []
|
153
|
+
if model.get('agndst', 0) > 0:
|
154
|
+
agndst = ['agndst = {}'.format(model.agndst*1e3)]
|
155
|
+
return (agndst + ['ndt(agndst)']
|
156
|
+
+ model.stator['dxf']['fsl'])
|
151
157
|
if templ == 'statorFsl':
|
152
158
|
# obsolete
|
153
159
|
if 'parameter' in model.stator['statorFsl']:
|
@@ -190,8 +196,8 @@ class Builder:
|
|
190
196
|
if fslcode:
|
191
197
|
if self.fsl_stator:
|
192
198
|
return (['agndst = {}'.format(model.agndst*1e3),
|
193
|
-
'alfa = 2*
|
194
|
-
'num_agnodes =
|
199
|
+
'alfa = 2*np.pi*m.num_sl_gen/m.tot_num_slot',
|
200
|
+
'num_agnodes = np.floor(m.fc_radius*alfa/agndst + 0.5)'] +
|
195
201
|
fslcode)
|
196
202
|
if hasattr(model, 'num_agnodes'):
|
197
203
|
return fslcode
|
@@ -276,7 +282,7 @@ class Builder:
|
|
276
282
|
g = femagtools.gmsh.Gmsh(model.magnet['mshfile']['name'])
|
277
283
|
r = g.get_section_radius()
|
278
284
|
phi = g.get_section_angles()
|
279
|
-
p = round(
|
285
|
+
p = round(np.pi/(phi[1]-phi[0]))
|
280
286
|
model.set_value('poles', p)
|
281
287
|
model.set_value('inner_diam', 2*r[0])
|
282
288
|
ag = (model.get('bore_diam') - 2*r[1])/2
|
@@ -298,7 +304,7 @@ class Builder:
|
|
298
304
|
return
|
299
305
|
|
300
306
|
if templ == 'dxf':
|
301
|
-
# reuse dxfsl model
|
307
|
+
# reuse dxfsl model
|
302
308
|
self.fsl_magnet = True
|
303
309
|
if templ != 'dxffile':
|
304
310
|
return
|
@@ -330,7 +336,7 @@ class Builder:
|
|
330
336
|
if fslcode:
|
331
337
|
return fslcode
|
332
338
|
|
333
|
-
logger.error('File
|
339
|
+
logger.error('File %s.fsl not found', templ)
|
334
340
|
return []
|
335
341
|
|
336
342
|
def create_connect_models(self, model):
|
@@ -346,7 +352,7 @@ class Builder:
|
|
346
352
|
'-- thermal properties in airgap',
|
347
353
|
'ag_cond = 0.063',
|
348
354
|
'thcap = 1007',
|
349
|
-
'beta =
|
355
|
+
'beta = np.pi*m.npols_gen/m.num_poles + m.zeroangl/180*np.pi',
|
350
356
|
'xai, yai = pr2c((da1+da2)/4, beta)',
|
351
357
|
'def_mat_therm(xai,yai,"cyan",1.19,ag_cond,thcap,1)',
|
352
358
|
'xai, yai = pr2c((da1+da2)/4-ag/4, beta)',
|
@@ -434,13 +440,11 @@ class Builder:
|
|
434
440
|
logger.info("Leakage type %s", k)
|
435
441
|
if 'wiredia' not in model.winding[k[0]]:
|
436
442
|
if 'wire_gauge' in model.winding:
|
437
|
-
import numpy as np
|
438
443
|
d = 2*np.sqrt(model.winding['wire_gauge']/np.pi)
|
439
444
|
model.winding[k[0]]['wiredia'] = d
|
440
445
|
elif 'dia_wire' in model.winding:
|
441
446
|
model.winding[k[0]]['wiredia'] = model.winding['dia_wire']
|
442
447
|
elif 'wire_width' in model.winding:
|
443
|
-
import numpy as np
|
444
448
|
w = model.winding['wire_width']
|
445
449
|
h = model.winding['wire_height']
|
446
450
|
d = 2*np.sqrt(h*w/np.pi)
|
@@ -462,10 +466,10 @@ class Builder:
|
|
462
466
|
logger.error('Name of dxf-file expected')
|
463
467
|
return []
|
464
468
|
|
465
|
-
if dxfname.split('.')[-1] not in ('dxf'):
|
469
|
+
if dxfname.split('.')[-1] not in ('dxf', 'svg'): # add svg support
|
466
470
|
dxfname += '.dxf'
|
467
471
|
if not os.path.isfile(dxfname):
|
468
|
-
logger.error('File
|
472
|
+
logger.error('File "%s" not found', dxfname)
|
469
473
|
raise ValueError(f'File {dxfname} not found')
|
470
474
|
|
471
475
|
params = {}
|
@@ -474,7 +478,7 @@ class Builder:
|
|
474
478
|
params['write_fsl'] = True
|
475
479
|
params['airgap'] = model.dxffile.get('airgap', 0.0)
|
476
480
|
params['nodedist'] = model.dxffile.get('nodedist', 1)
|
477
|
-
|
481
|
+
params['full_model'] = model.dxffile.get('full_model', False)
|
478
482
|
conv = convert(dxfname, **params)
|
479
483
|
|
480
484
|
model.set_value('poles', conv.get('num_poles'))
|
@@ -482,16 +486,20 @@ class Builder:
|
|
482
486
|
model.set_value('bore_diam', conv.get('da1') * 1e-3)
|
483
487
|
model.set_value('inner_diam', conv.get('dy2') * 1e-3)
|
484
488
|
model.set_value('airgap', (conv.get('da1') - conv.get('da2'))/2/1e3)
|
485
|
-
model.set_value('
|
489
|
+
model.set_value('external_rotor', conv.get('external_rotor'))
|
486
490
|
|
487
491
|
if not hasattr(model, 'stator'):
|
488
492
|
setattr(model, 'stator', {})
|
489
493
|
model.stator['num_slots'] = conv.get('tot_num_slot')
|
490
|
-
if
|
491
|
-
|
492
|
-
model.stator['num_slots_gen'] = conv.get('num_sl_gen')
|
494
|
+
if params['full_model']:
|
495
|
+
model.stator['num_slots_gen'] = model.stator['num_slots']
|
493
496
|
else:
|
494
|
-
model.stator
|
497
|
+
if model.stator.get('num_slots_gen', 0):
|
498
|
+
if model.stator['num_slots'] % model.stator['num_slots_gen'] > 0:
|
499
|
+
model.stator['num_slots_gen'] = conv.get('num_sl_gen')
|
500
|
+
else:
|
501
|
+
model.stator['num_slots_gen'] = conv.get('num_sl_gen')
|
502
|
+
|
495
503
|
if 'fsl_stator' in conv:
|
496
504
|
self.fsl_stator = True
|
497
505
|
model.stator['dxf'] = dict(fsl=conv['fsl_stator'])
|
@@ -523,12 +531,27 @@ class Builder:
|
|
523
531
|
logger.info("create new model '%s'", model.name)
|
524
532
|
if model.is_dxffile():
|
525
533
|
self.prepare_model_with_dxf(model)
|
534
|
+
if model.get('num_agnodes', 0) == 0:
|
535
|
+
from femagtools.dxfsl.fslrenderer import agndst
|
536
|
+
ag = model.get('airgap')
|
537
|
+
model.set_value(
|
538
|
+
'agndst',
|
539
|
+
agndst(model.get('bore_diam'),
|
540
|
+
model.get('bore_diam') - 2*ag,
|
541
|
+
model.stator.get('num_slots'),
|
542
|
+
model.get('poles'),
|
543
|
+
model.stator.get('nodedist') or 1.0))
|
544
|
+
|
545
|
+
logger.info(" num poles %d num slots %d outer diameter %.4f m",
|
546
|
+
model.poles, model.stator['num_slots'],
|
547
|
+
model.outer_diam)
|
548
|
+
|
526
549
|
else:
|
527
550
|
self.prepare_stator(model)
|
528
551
|
if hasattr(model, 'magnet'):
|
529
552
|
self.prepare_magnet(model)
|
530
553
|
self.prepare_diameter(model)
|
531
|
-
if self.fsl_stator:
|
554
|
+
if self.fsl_stator and model.get('num_agnodes', 0) == 0:
|
532
555
|
from femagtools.dxfsl.fslrenderer import agndst
|
533
556
|
ag = model.get('airgap')
|
534
557
|
model.set_value(
|
@@ -638,6 +661,11 @@ class Builder:
|
|
638
661
|
except:
|
639
662
|
return []
|
640
663
|
|
664
|
+
def create_displ_stator_rotor(self, exc):
|
665
|
+
if exc.keys() >= {'type', 'bore_diam', 'airgap', 'ecc'}:
|
666
|
+
return self.__render(exc, 'displ_stator_rotor')
|
667
|
+
return []
|
668
|
+
|
641
669
|
def create_analysis(self, sim):
|
642
670
|
pfefunc = sim.get('loss_funct', '')
|
643
671
|
custom_fefunc = ['']
|
@@ -650,9 +678,23 @@ class Builder:
|
|
650
678
|
|
651
679
|
airgap_induc = (self.create_airgap_induc()
|
652
680
|
if sim.get('airgap_induc', 0) else [])
|
681
|
+
displ_stator_rotor = self.create_displ_stator_rotor(
|
682
|
+
sim.get('eccentricity', {}))
|
683
|
+
revert_displ = []
|
684
|
+
if displ_stator_rotor:
|
685
|
+
sim['eval_force'] = 1
|
686
|
+
sim['period_frac'] = 1
|
687
|
+
if sim['eccentricity']['type'] == 'static':
|
688
|
+
sim['explicit_mode'] = 1
|
689
|
+
sim['range_phi'] = 180
|
690
|
+
sim['eccentricity']['ecc'] = -sim['eccentricity']['ecc']
|
691
|
+
revert_displ = self.create_displ_stator_rotor(
|
692
|
+
sim['eccentricity'])
|
693
|
+
|
653
694
|
felosses = custom_fefunc + self.create_fe_losses(sim)
|
654
|
-
fslcalc = (
|
655
|
-
|
695
|
+
fslcalc = (displ_stator_rotor
|
696
|
+
+ self.__render(sim, sim.get('calculationMode'))
|
697
|
+
+ airgap_induc + revert_displ)
|
656
698
|
'''
|
657
699
|
if pfefunc:
|
658
700
|
sim['loss_funct'] = pfefunc
|
@@ -687,8 +729,8 @@ class Builder:
|
|
687
729
|
def create(self, model, sim, magnets=None, condMat=[]):
|
688
730
|
"create model and analysis function"
|
689
731
|
try:
|
690
|
-
sim['lfe'] = model.get('lfe')
|
691
732
|
num_poles = model.get('poles')
|
733
|
+
sim['lfe'] = model.get('lfe')
|
692
734
|
except AttributeError:
|
693
735
|
pass
|
694
736
|
try:
|
@@ -713,10 +755,11 @@ class Builder:
|
|
713
755
|
try:
|
714
756
|
poc = Poc(2*360/num_poles)
|
715
757
|
sim['poc'] = poc
|
716
|
-
sim['pocfilename'] = poc.filename()
|
758
|
+
sim['pocfilename'] = poc.filename(model.get('name'))
|
717
759
|
except UnboundLocalError:
|
718
760
|
logger.warning("unknown number of poles")
|
719
761
|
pass
|
762
|
+
logger.info("Poc file %s", sim.get('pocfilename', '<undefined>'))
|
720
763
|
|
721
764
|
if 'phi_start' not in sim:
|
722
765
|
sim['phi_start'] = 0.0
|
femagtools/losscoeffs.py
CHANGED
@@ -83,6 +83,52 @@ def fitjordan(f, B, losses, Bo, fo):
|
|
83
83
|
return fitp
|
84
84
|
|
85
85
|
|
86
|
+
def fit_bertotti(f, B, losses):
|
87
|
+
"""fit coeffs of
|
88
|
+
losses(f,B)=(ch*f + ch*f**2)*B**2 + ce*f**1.5*B**1.5
|
89
|
+
returns (ch, cw, ce)
|
90
|
+
|
91
|
+
Args:
|
92
|
+
f: list of n strictly increasing frequency values
|
93
|
+
B: list of n or list of list of induction values (nxm)
|
94
|
+
losses: list of list of fe loss values (nxm)
|
95
|
+
"""
|
96
|
+
i0 = 0
|
97
|
+
if np.isclose(f[i0], 0):
|
98
|
+
i0 = 1
|
99
|
+
v = []
|
100
|
+
# collect losses and flux density along the frequency
|
101
|
+
for k in range(len(losses[i0])):
|
102
|
+
y = []
|
103
|
+
bb = []
|
104
|
+
if isinstance(B[0], tuple) or isinstance(B[0], list):
|
105
|
+
for fx, bx, p in zip(f[i0:], B[i0:], losses[i0:]):
|
106
|
+
if k < len(p):
|
107
|
+
y.append(p[k]/fx)
|
108
|
+
bb.append(bx[k])
|
109
|
+
else:
|
110
|
+
for fx, p in zip(f[i0:], losses[i0:]):
|
111
|
+
if k < len(p):
|
112
|
+
y.append(p[k]/fx)
|
113
|
+
bb.append(B[k])
|
114
|
+
j = len(y)
|
115
|
+
if j > 2:
|
116
|
+
v.append(np.array((f[i0:j+i0], bb, y)).T.tolist())
|
117
|
+
|
118
|
+
def wbert(f, b, ch, cw, cx):
|
119
|
+
return (ch + cw*f)*b**2 + cx*f**0.5*b**1.5
|
120
|
+
|
121
|
+
z = np.array([b for a in v for b in a]).T
|
122
|
+
fbx = z[0:2]
|
123
|
+
y = z[2]
|
124
|
+
fitp, _ = so.curve_fit(
|
125
|
+
lambda x, ch, cw, ce: wbert(
|
126
|
+
x[0], x[1], ch, cw, ce),
|
127
|
+
fbx, y,
|
128
|
+
bounds=[(0, 0, 0),
|
129
|
+
(np.inf, np.inf, np.inf)])
|
130
|
+
return fitp
|
131
|
+
|
86
132
|
def fit_bertotti0(f, B, losses, generate=False):
|
87
133
|
"""fit coeffs of
|
88
134
|
losses(f,B)=(ch*f + ch*f**2)*B**2 + ce*f**1.5*B**1.5
|
femagtools/machine/effloss.py
CHANGED
@@ -309,13 +309,20 @@ def efficiency_losses_map(eecpars, u1, T, temp, n, npoints=(60, 40),
|
|
309
309
|
r['plcu1'].append(m.m*np.abs(i1)**2*m.rstat(w1))
|
310
310
|
r['plcu2'].append(m.m*np.abs(i2)**2*m.rrot(w1-m.p*wm))
|
311
311
|
|
312
|
-
if isinstance(m,
|
312
|
+
if isinstance(m, PmRelMachine):
|
313
313
|
plfe1 = m.kpfe*m.iqd_plfe1(*iqd, f1)
|
314
314
|
plfe2 = m.kpfe*m.iqd_plfe2(iqd[0], iqd[1], f1)
|
315
315
|
plmag = m.kpmag*m.iqd_plmag(iqd[0], iqd[1], f1)
|
316
316
|
plcu1 = m.iqd_plcu1(iqd[0], iqd[1], 2*np.pi*f1)
|
317
317
|
plcu2 = m.iqd_plcu2(*iqd)
|
318
318
|
tfric = m.tfric
|
319
|
+
elif isinstance(m, SynchronousMachine):
|
320
|
+
plfe1 = m.kpfe*m.iqd_plfe1(*iqd, f1)
|
321
|
+
plfe2 = m.kpfe*m.iqd_plfe2(*iqd, f1)
|
322
|
+
plmag = np.zeros_like(plfe2)
|
323
|
+
plcu1 = m.iqd_plcu1(iqd[0], iqd[1], 2*np.pi*f1)
|
324
|
+
plcu2 = m.iqd_plcu2(*iqd)
|
325
|
+
tfric = m.tfric
|
319
326
|
else:
|
320
327
|
plfe1 = np.array(r['plfe1'])
|
321
328
|
plfe2 = np.zeros(ntmesh.shape[1])
|
femagtools/machine/im.py
CHANGED
@@ -230,7 +230,7 @@ class InductionMachine(Component):
|
|
230
230
|
|
231
231
|
def i1(self, w1, psi, wm):
|
232
232
|
"""stator current"""
|
233
|
-
imag =
|
233
|
+
imag = self.imag(psi)+0j
|
234
234
|
if abs(w1) > 0:
|
235
235
|
imag += w1*psi/self.rfe(w1, psi)*1j
|
236
236
|
return self.i2(w1, psi, wm) + imag
|
@@ -577,6 +577,8 @@ class InductionMachine(Component):
|
|
577
577
|
r['n'].append(wm/2/np.pi)
|
578
578
|
r['s'].append(float((w1 - self.p * wm) / w1))
|
579
579
|
r['sk'].append(self.sk(w1, np.abs(u1)/w1))
|
580
|
+
# add n_type to result dict
|
581
|
+
r['n_type'] = wmType/2/np.pi
|
580
582
|
# except ValueError as ex:
|
581
583
|
# break
|
582
584
|
r['plfw'] = [self.pfric(n) for n in r['n']]
|
femagtools/machine/pm.py
CHANGED
@@ -268,9 +268,11 @@ class PmRelMachine(object):
|
|
268
268
|
u -- the maximum voltage (RMS)
|
269
269
|
iq, id -- the d-q currents"""
|
270
270
|
w10 = np.sqrt(2)*u/la.norm(self.psi(iq, id))
|
271
|
-
|
272
|
-
|
273
|
-
|
271
|
+
with warnings.catch_warnings():
|
272
|
+
warnings.simplefilter("ignore")
|
273
|
+
return so.fsolve(
|
274
|
+
lambda w1: la.norm(self.uqd(w1, iq, id))-u*np.sqrt(2),
|
275
|
+
w10)[0]
|
274
276
|
|
275
277
|
def w1_imax_umax(self, i1max, u1max):
|
276
278
|
"""return frequency w1 and torque at voltage u1max and current i1max
|
@@ -584,7 +586,7 @@ class PmRelMachine(object):
|
|
584
586
|
i1max=i1max)
|
585
587
|
return iq, id, tq
|
586
588
|
except ValueError as e:
|
587
|
-
logger.
|
589
|
+
logger.debug(e)
|
588
590
|
|
589
591
|
if with_tmech:
|
590
592
|
tq = self.tmech_iqd(iq, id, w1/2/np.pi/self.p)
|
@@ -798,8 +800,8 @@ class PmRelMachine(object):
|
|
798
800
|
k, wx, wl, wu, 100*(wu-wl)/wl)
|
799
801
|
|
800
802
|
self.check_extrapolation = True
|
801
|
-
w1max = min(w1max, self.w1_umax(
|
802
|
-
u1max, *iqd(-np.pi/2, abs(i1max))))
|
803
|
+
w1max = min(w1max, np.floor(self.w1_umax(
|
804
|
+
u1max, *iqd(-np.pi/2, abs(i1max)))))
|
803
805
|
return [w/2/np.pi/self.p for w in (w1type, w1max)] # ['MTPA']
|
804
806
|
|
805
807
|
|
@@ -907,6 +909,8 @@ class PmRelMachine(object):
|
|
907
909
|
with_mtpa -- (optional) use mtpa if True (default) in const speed range, set id=0 if false
|
908
910
|
with_tmech -- (optional) use friction and windage losses if True (default)
|
909
911
|
with_torque_corr -- (optional) T is corrected if out of range
|
912
|
+
Optional arguments:
|
913
|
+
i1max: max. phase current (RMS)
|
910
914
|
"""
|
911
915
|
r = dict(id=[], iq=[], uq=[], ud=[], u1=[], i1=[], T=[],
|
912
916
|
beta=[], gamma=[], phi=[], cosphi=[], pmech=[], n=[])
|
@@ -1396,11 +1400,8 @@ class PmRelMachinePsidq(PmRelMachine):
|
|
1396
1400
|
'rotor_excess']
|
1397
1401
|
self._set_losspar(pfe)
|
1398
1402
|
self._losses = {k: ip.RectBivariateSpline(
|
1399
|
-
iq, id, np.array(pfe[k])).ev for k in
|
1400
|
-
|
1401
|
-
'styoke_eddy', 'stteeth_eddy',
|
1402
|
-
'rotor_hyst', 'rotor_eddy',
|
1403
|
-
'magnet')}
|
1403
|
+
iq, id, np.array(pfe[k])).ev for k in self.losskeys
|
1404
|
+
if k in pfe}
|
1404
1405
|
except KeyError as e:
|
1405
1406
|
logger.warning("loss map missing: %s", e)
|
1406
1407
|
pass
|