femagtools 1.8.2__py3-none-any.whl → 1.8.4__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- femagtools/__init__.py +1 -1
- femagtools/bch.py +10 -6
- femagtools/dxfsl/area.py +69 -1
- femagtools/dxfsl/conv.py +53 -16
- femagtools/dxfsl/converter.py +273 -76
- femagtools/dxfsl/fslrenderer.py +18 -22
- femagtools/dxfsl/functions.py +38 -8
- femagtools/dxfsl/geom.py +112 -35
- femagtools/dxfsl/journal.py +1 -1
- femagtools/dxfsl/machine.py +44 -7
- femagtools/dxfsl/shape.py +4 -0
- femagtools/dxfsl/symmetry.py +105 -32
- femagtools/femag.py +64 -61
- femagtools/fsl.py +4 -2
- femagtools/isa7.py +3 -2
- femagtools/machine/afpm.py +45 -25
- femagtools/machine/effloss.py +31 -20
- femagtools/machine/im.py +6 -8
- femagtools/machine/sizing.py +4 -3
- femagtools/machine/sm.py +35 -37
- femagtools/mcv.py +66 -37
- femagtools/multiproc.py +79 -80
- femagtools/parstudy.py +10 -4
- femagtools/semi_fea.py +108 -0
- femagtools/templates/basic_modpar.mako +0 -3
- femagtools/templates/fe-contr.mako +18 -18
- femagtools/templates/ld_lq_fast.mako +3 -0
- femagtools/templates/mesh-airgap.mako +6 -0
- femagtools/templates/mult_cal_fast.mako +3 -0
- femagtools/templates/pm_sym_f_cur.mako +4 -1
- femagtools/templates/pm_sym_fast.mako +3 -0
- femagtools/templates/pm_sym_loss.mako +3 -0
- femagtools/templates/psd_psq_fast.mako +3 -0
- femagtools/templates/torq_calc.mako +3 -0
- femagtools/tks.py +23 -20
- femagtools/utils.py +1 -1
- femagtools/windings.py +11 -4
- femagtools/zmq.py +213 -0
- {femagtools-1.8.2.dist-info → femagtools-1.8.4.dist-info}/METADATA +3 -3
- {femagtools-1.8.2.dist-info → femagtools-1.8.4.dist-info}/RECORD +49 -47
- {femagtools-1.8.2.dist-info → femagtools-1.8.4.dist-info}/WHEEL +1 -1
- tests/test_afpm.py +15 -6
- tests/test_femag.py +1 -1
- tests/test_fsl.py +4 -4
- tests/test_mcv.py +20 -14
- tests/test_parident.py +2 -1
- {femagtools-1.8.2.dist-info → femagtools-1.8.4.dist-info}/LICENSE +0 -0
- {femagtools-1.8.2.dist-info → femagtools-1.8.4.dist-info}/entry_points.txt +0 -0
- {femagtools-1.8.2.dist-info → femagtools-1.8.4.dist-info}/top_level.txt +0 -0
femagtools/femag.py
CHANGED
@@ -27,6 +27,7 @@ import femagtools.fsl
|
|
27
27
|
import femagtools.config
|
28
28
|
import femagtools.ecloss
|
29
29
|
import femagtools.forcedens
|
30
|
+
import femagtools.zmq
|
30
31
|
|
31
32
|
from femagtools import ntib
|
32
33
|
|
@@ -180,15 +181,22 @@ class BaseFemag(object):
|
|
180
181
|
def copy_winding_file(self, name, wdg):
|
181
182
|
wdg.write(name, self.workdir)
|
182
183
|
|
183
|
-
def copy_magnetizing_curves(self, model, dir=None, recsin=''):
|
184
|
+
def copy_magnetizing_curves(self, model, dir=None, recsin='', feloss=''):
|
184
185
|
"""extract mc names from model and write files into workdir or dir if given
|
185
186
|
|
186
187
|
Return:
|
187
188
|
list of extracted mc names (:obj:`list`)
|
188
189
|
"""
|
189
190
|
dest = dir if dir else self.workdir
|
191
|
+
if isinstance(feloss, (int, float)):
|
192
|
+
try:
|
193
|
+
feloss = {1: 'jordan', 11: 'bertotti'}[int(feloss)]
|
194
|
+
except KeyError:
|
195
|
+
feloss = ''
|
190
196
|
return [self.magnetizingCurves.writefile(m[0], dest,
|
191
|
-
fillfac=m[1],
|
197
|
+
fillfac=m[1],
|
198
|
+
recsin=recsin,
|
199
|
+
feloss=feloss)
|
192
200
|
for m in model.set_magcurves(
|
193
201
|
self.magnetizingCurves, self.magnets)]
|
194
202
|
|
@@ -212,9 +220,11 @@ class BaseFemag(object):
|
|
212
220
|
self.model = femagtools.model.MachineModel(machine)
|
213
221
|
self.modelname = self.model.name
|
214
222
|
recsin = ''
|
223
|
+
feloss = ''
|
215
224
|
if simulation:
|
216
225
|
recsin = simulation.get('recsin', '')
|
217
|
-
|
226
|
+
feloss = simulation.get('calc_fe_loss', '')
|
227
|
+
self.copy_magnetizing_curves(self.model, recsin=recsin, feloss=feloss)
|
218
228
|
try:
|
219
229
|
if 'wdgdef' in self.model.winding:
|
220
230
|
self.model.winding['wdgfile'] = self.create_wdg_def(
|
@@ -429,12 +439,15 @@ class BaseFemag(object):
|
|
429
439
|
"""
|
430
440
|
Read the modelname from the Femag Log file
|
431
441
|
"""
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
442
|
+
try:
|
443
|
+
with open(os.path.join(self.workdir, 'FEMAG-FSL.log')) as f:
|
444
|
+
for l in f:
|
445
|
+
if l.startswith('New model') or l.startswith('Load model'):
|
446
|
+
return l.split('"')[1]
|
447
|
+
except FileNotFoundError:
|
448
|
+
pass
|
436
449
|
|
437
|
-
|
450
|
+
return list(pathlib.Path(self.workdir).glob('*.PROT'))[0].stem
|
438
451
|
|
439
452
|
def readResult(self, simulation, bch=None):
|
440
453
|
if simulation:
|
@@ -715,55 +728,6 @@ class FemagTask(threading.Thread):
|
|
715
728
|
self.returncode = self.proc.wait()
|
716
729
|
|
717
730
|
|
718
|
-
class SubscriberTask(threading.Thread):
|
719
|
-
def __init__(self, port, host, notify):
|
720
|
-
threading.Thread.__init__(self)
|
721
|
-
context = zmq.Context.instance()
|
722
|
-
self.subscriber = context.socket(zmq.SUB)
|
723
|
-
if not host:
|
724
|
-
host = 'localhost'
|
725
|
-
self.subscriber.connect(f'tcp://{host}:{port}')
|
726
|
-
self.subscriber.setsockopt(zmq.SUBSCRIBE, b'')
|
727
|
-
self.controller = zmq.Context.instance().socket(zmq.PULL)
|
728
|
-
self.controller_url = 'inproc://publisher'
|
729
|
-
self.controller.bind(self.controller_url)
|
730
|
-
self.poller = zmq.Poller()
|
731
|
-
self.poller.register(self.subscriber, zmq.POLLIN)
|
732
|
-
self.poller.register(self.controller, zmq.POLLIN)
|
733
|
-
self.logger = logger
|
734
|
-
self.notify = notify
|
735
|
-
|
736
|
-
def stop(self):
|
737
|
-
socket = zmq.Context.instance().socket(zmq.PUSH)
|
738
|
-
socket.connect(self.controller_url)
|
739
|
-
socket.send(b"quit")
|
740
|
-
socket.close()
|
741
|
-
|
742
|
-
def run(self):
|
743
|
-
self.logger.info("subscriber is ready")
|
744
|
-
while True:
|
745
|
-
socks = dict(self.poller.poll())
|
746
|
-
if socks.get(self.subscriber) == zmq.POLLIN:
|
747
|
-
try:
|
748
|
-
response = self.subscriber.recv_multipart()
|
749
|
-
# Sometimes femag send messages with only len = 1. These messages must be ignored
|
750
|
-
if len(response) < 2:
|
751
|
-
continue
|
752
|
-
self.notify([s.decode('latin1') for s in response])
|
753
|
-
|
754
|
-
except Exception:
|
755
|
-
self.logger.error(
|
756
|
-
"error in subscription message processing", exc_info=True)
|
757
|
-
|
758
|
-
if socks.get(self.controller) == zmq.POLLIN:
|
759
|
-
req = self.controller.recv()
|
760
|
-
self.logger.info("subscriber %s", req)
|
761
|
-
break
|
762
|
-
self.subscriber.close()
|
763
|
-
self.controller.close()
|
764
|
-
self.logger.debug("subscriber stopped")
|
765
|
-
|
766
|
-
|
767
731
|
class ZmqFemag(BaseFemag):
|
768
732
|
"""Invoke and control execution of FEMAG with ZeroMQ
|
769
733
|
|
@@ -838,8 +802,8 @@ class ZmqFemag(BaseFemag):
|
|
838
802
|
"""attaches a notify function"""
|
839
803
|
logger.info("Subscribe on '%s' port %d", self.femaghost, self.port+1)
|
840
804
|
if self.subscriber is None:
|
841
|
-
self.subscriber = SubscriberTask(
|
842
|
-
self.port+1, self.femaghost, notify)
|
805
|
+
self.subscriber = femagtools.zmq.SubscriberTask(
|
806
|
+
port=self.port+1, host=self.femaghost, notify=notify)
|
843
807
|
self.subscriber.start()
|
844
808
|
else:
|
845
809
|
# reattach?
|
@@ -1149,6 +1113,38 @@ class ZmqFemag(BaseFemag):
|
|
1149
1113
|
logger.warning(response[0])
|
1150
1114
|
return [s.decode('latin1') for s in response]
|
1151
1115
|
|
1116
|
+
def airgap_flux_density(self, pmod):
|
1117
|
+
# try to read bag.dat
|
1118
|
+
agr = self.getfile("bag.dat")
|
1119
|
+
status = json.loads(agr[0])['status']
|
1120
|
+
if status == 'ok':
|
1121
|
+
datfile = os.path.join(self.workdir, 'bag.dat')
|
1122
|
+
with open(datfile, 'wb') as bagfile:
|
1123
|
+
bagfile.write(agr[1])
|
1124
|
+
agi = ag.read(datfile, pmod)
|
1125
|
+
else:
|
1126
|
+
import numpy as np
|
1127
|
+
# try to read model file (TODO download with getfile)
|
1128
|
+
nc = self.read_nc()
|
1129
|
+
ag_elmnts = nc.airgap_center_elements
|
1130
|
+
logger.info("Airgap elements %d scale_factor %f",
|
1131
|
+
len(ag_elmnts), nc.scale_factor())
|
1132
|
+
if len(ag_elmnts) < 1:
|
1133
|
+
raise ValueError("Missing airgap elements")
|
1134
|
+
scf = 360/nc.scale_factor()/ag_elmnts[-1].center[0]
|
1135
|
+
pos = np.array([e.center[0]*scf for e in ag_elmnts])
|
1136
|
+
bxy = np.array([e.flux_density() for e in ag_elmnts]).T
|
1137
|
+
if np.max(bxy[0]) > np.max(bxy[1]):
|
1138
|
+
agi = ag.fft(pos, bxy[0], pmod)
|
1139
|
+
else:
|
1140
|
+
agi = ag.fft(pos, bxy[1], pmod)
|
1141
|
+
return dict(Bamp=agi['Bamp'],
|
1142
|
+
phi0=agi['phi0'],
|
1143
|
+
angle=agi['pos'],
|
1144
|
+
angle_fft=agi['pos'],
|
1145
|
+
B=agi['B'],
|
1146
|
+
B_fft=agi['B_fft'])
|
1147
|
+
|
1152
1148
|
def interrupt(self):
|
1153
1149
|
"""send push message to control port to stop current calculation"""
|
1154
1150
|
context = zmq.Context.instance()
|
@@ -1165,7 +1161,7 @@ class ZmqFemag(BaseFemag):
|
|
1165
1161
|
wdg.write(name, self.workdir)
|
1166
1162
|
self.upload(os.path.join(self.workdir, name+'.WID'))
|
1167
1163
|
|
1168
|
-
def copy_magnetizing_curves(self, model, dir=None, recsin=''):
|
1164
|
+
def copy_magnetizing_curves(self, model, dir=None, recsin='', feloss=''):
|
1169
1165
|
"""extract mc names from model and write files into workdir or dir if given
|
1170
1166
|
and upload to Femag
|
1171
1167
|
|
@@ -1175,9 +1171,16 @@ class ZmqFemag(BaseFemag):
|
|
1175
1171
|
dest = dir if dir else self.workdir
|
1176
1172
|
mc_names = [m for m in model.set_magcurves(
|
1177
1173
|
self.magnetizingCurves, self.magnets)]
|
1174
|
+
if isinstance(feloss, (int, float)):
|
1175
|
+
try:
|
1176
|
+
feloss = {1: 'jordan', 11: 'bertotti'}[int(feloss)]
|
1177
|
+
except KeyError:
|
1178
|
+
feloss = ''
|
1178
1179
|
for m in mc_names:
|
1179
1180
|
f = self.magnetizingCurves.writefile(m[0], dest,
|
1180
|
-
fillfac=m[1],
|
1181
|
+
fillfac=m[1],
|
1182
|
+
recsin=recsin,
|
1183
|
+
feloss=feloss)
|
1181
1184
|
self.upload(os.path.join(dest, f))
|
1182
1185
|
return mc_names
|
1183
1186
|
|
femagtools/fsl.py
CHANGED
@@ -556,7 +556,6 @@ class Builder:
|
|
556
556
|
if 'fsl_rotor' in conv:
|
557
557
|
self.fsl_rotor = True
|
558
558
|
th_props = ['']
|
559
|
-
logger.info(model['magnet'])
|
560
559
|
if hasattr(model, 'magnet'):
|
561
560
|
if model['magnet'].get('thcond', 0):
|
562
561
|
th_props = [f'rotor_density = {model["magnet"]["density"]}',
|
@@ -730,7 +729,7 @@ class Builder:
|
|
730
729
|
custom_fefunc = ['']
|
731
730
|
if pfefunc:
|
732
731
|
sim['loss_funct'] = 1 # 3?
|
733
|
-
if pfefunc
|
732
|
+
if pfefunc in ('bertotti', 'modified_steinmetz'):
|
734
733
|
custom_fefunc = self.__render(sim['PVFE_FSL'], pfefunc)
|
735
734
|
else:
|
736
735
|
custom_fefunc = pfefunc.split('\n')
|
@@ -750,6 +749,9 @@ class Builder:
|
|
750
749
|
revert_displ = self.create_displ_stator_rotor(
|
751
750
|
sim['eccentricity'])
|
752
751
|
|
752
|
+
if sim.get('calculationMode') == 'pm_sym_f_cur':
|
753
|
+
if 'nload_ex_cur' in sim.keys(): # convert obsolete key
|
754
|
+
sim['noload_ex_cur'] = sim.pop('nload_ex_cur')
|
753
755
|
felosses = custom_fefunc + self.create_fe_losses(sim)
|
754
756
|
fslcalc = (displ_stator_rotor
|
755
757
|
+ self.__render(sim, sim.get('calculationMode'))
|
femagtools/isa7.py
CHANGED
@@ -815,8 +815,9 @@ class Isa7(object):
|
|
815
815
|
airgap_positions.append(axy[0][0])
|
816
816
|
self.airgap_center_elements.append(se.elements)
|
817
817
|
|
818
|
-
if
|
819
|
-
|
818
|
+
if self.airgap_center_elements:
|
819
|
+
# TODO check airgap center
|
820
|
+
self.airgap_center_elements = self.airgap_center_elements[-1]
|
820
821
|
if horiz:
|
821
822
|
airgap_positions.append(np.min(nxy[:, 1]))
|
822
823
|
else:
|
femagtools/machine/afpm.py
CHANGED
@@ -11,7 +11,7 @@ from .. import model
|
|
11
11
|
from .. import utils
|
12
12
|
from .. import windings
|
13
13
|
from .. import femag
|
14
|
-
from scipy.interpolate import
|
14
|
+
from scipy.interpolate import make_interp_spline, RegularGridInterpolator, RectBivariateSpline
|
15
15
|
from scipy.integrate import quad
|
16
16
|
import copy
|
17
17
|
|
@@ -33,16 +33,21 @@ def num_agnodes(Q, p, pw, ag):
|
|
33
33
|
pw: (float) pole width
|
34
34
|
ag: (float) airgap height
|
35
35
|
"""
|
36
|
-
num_nodes = np.arange(
|
36
|
+
num_nodes = np.arange(6, 120, 6)
|
37
37
|
i = np.argmin(np.abs(pw/num_nodes - ag/2))
|
38
|
-
|
38
|
+
nag = num_nodes[i-1]
|
39
|
+
if p*nag % Q:
|
39
40
|
lcm = np.lcm(Q, 2*p)//p
|
40
|
-
nmin, nmax =
|
41
|
+
nmin, nmax = 1, num_nodes[-1]//lcm
|
41
42
|
num_nodes = np.array(
|
42
43
|
[i*lcm for i in range(nmin, nmax) if i*lcm % 6 == 0])
|
43
44
|
i = np.argmin(np.abs(pw/num_nodes - ag/2))
|
44
|
-
|
45
|
-
|
45
|
+
if i > 0:
|
46
|
+
nag = num_nodes[i-1]
|
47
|
+
else:
|
48
|
+
nag = num_nodes[i]
|
49
|
+
# TODO nodedist 0.5, 2, 4, 6
|
50
|
+
return nag
|
46
51
|
|
47
52
|
def _integrate(radius, pos, val):
|
48
53
|
interp = RegularGridInterpolator((radius, pos), val)
|
@@ -53,7 +58,7 @@ def _integrate(radius, pos, val):
|
|
53
58
|
|
54
59
|
|
55
60
|
def _integrate1d(radius, val):
|
56
|
-
interp =
|
61
|
+
interp = make_interp_spline(radius, val, k=1)
|
57
62
|
def func(x):
|
58
63
|
return interp((x))
|
59
64
|
return quad(func, radius[0], radius[-1])[0]
|
@@ -162,7 +167,6 @@ def parident(workdir, engine, temp, machine,
|
|
162
167
|
for pw in pole_width:
|
163
168
|
machine['num_agnodes'] = num_agnodes(Q1, p//2, pw,
|
164
169
|
machine['airgap'])
|
165
|
-
|
166
170
|
nlparvardef = {
|
167
171
|
"decision_vars": [
|
168
172
|
{"values": pole_width,
|
@@ -182,13 +186,13 @@ def parident(workdir, engine, temp, machine,
|
|
182
186
|
parvardef = {
|
183
187
|
"decision_vars": [
|
184
188
|
# {"values": sorted(2*temp), "name": "magn_temp"},
|
185
|
-
{"steps": num_beta_steps, "bounds": [beta_min, 0],
|
189
|
+
{"steps": num_beta_steps, "bounds": [beta_min, 0],
|
190
|
+
"name": "angl_i_up"},
|
186
191
|
{"steps": num_cur_steps,
|
187
192
|
"bounds": [i1_max/num_cur_steps, i1_max], "name": "current"}
|
188
193
|
]
|
189
194
|
}
|
190
195
|
|
191
|
-
|
192
196
|
ldq = []
|
193
197
|
for magtemp in temp:
|
194
198
|
nlcalc = dict(
|
@@ -381,7 +385,7 @@ def process(lfe, pole_width, machine, bch):
|
|
381
385
|
num_slots = machine['stator']['num_slots']
|
382
386
|
mmod = model.MachineModel(machine)
|
383
387
|
slots_gen = mmod.stator['num_slots_gen']
|
384
|
-
scale_factor =_get_scale_factor(model_type, num_slots, slots_gen)
|
388
|
+
scale_factor = _get_scale_factor(model_type, num_slots, slots_gen)
|
385
389
|
endpos = [2*pw*1e3 for pw in pole_width]
|
386
390
|
displ = [[d for d in r['linearForce'][0]['displ']
|
387
391
|
if d < e*(1+1/len(r['linearForce'][0]['displ']))]
|
@@ -392,11 +396,14 @@ def process(lfe, pole_width, machine, bch):
|
|
392
396
|
currents = [bch[0]['flux'][k][0]['current_k'][:n]
|
393
397
|
for k in bch[0]['flux']]
|
394
398
|
if len(pole_width) > 1:
|
399
|
+
# check homogenity:
|
400
|
+
if np.diff([len(d) for d in displ]).any():
|
401
|
+
raise ValueError(
|
402
|
+
f"inhomogenous number of steps: {[len(d) for d in displ]}")
|
403
|
+
lfx = [r['linearForce'][0]['force_x'] for r in bch]
|
395
404
|
torque = _integrate(radius, rotpos[0], np.array(
|
396
405
|
[r*scale_factor*np.array(fx[:-1])/l
|
397
|
-
for l, r, fx in zip(lfe, radius,
|
398
|
-
[r['linearForce'][0]['force_x']
|
399
|
-
for r in bch])]))
|
406
|
+
for l, r, fx in zip(lfe, radius, lfx)]))
|
400
407
|
|
401
408
|
voltage = {k: [scale_factor * np.array(ux[:-1])/l
|
402
409
|
for l, ux in zip(lfe, [r['flux'][k][0]['voltage_dpsi']
|
@@ -404,6 +411,13 @@ def process(lfe, pole_width, machine, bch):
|
|
404
411
|
for k in bch[0]['flux']}
|
405
412
|
emf = [_integrate(radius, rotpos[0], np.array(voltage[k]))
|
406
413
|
for k in voltage]
|
414
|
+
|
415
|
+
fluxxy = {k: [scale_factor * np.array(flx[:-1])/l
|
416
|
+
for l, flx in zip(lfe, [r['flux'][k][0]['flux_k']
|
417
|
+
for r in bch])]
|
418
|
+
for k in bch[0]['flux']}
|
419
|
+
flux = [_integrate(radius, rotpos[0], np.array(fluxxy[k]))
|
420
|
+
for k in fluxxy]
|
407
421
|
else:
|
408
422
|
r = radius[0]
|
409
423
|
torque = [r*scale_factor*fx
|
@@ -412,6 +426,7 @@ def process(lfe, pole_width, machine, bch):
|
|
412
426
|
for ux in bch[0]['flux'][k][0]['voltage_dpsi'][:-1]]
|
413
427
|
for k in bch[0]['flux']}
|
414
428
|
emf = [voltage[k][:n] for k in voltage]
|
429
|
+
flux = [fluxxy[k][:n] for k in fluxxy]
|
415
430
|
|
416
431
|
pos = (rotpos[0]/np.pi*180)
|
417
432
|
emffft = utils.fft(pos, emf[0])
|
@@ -474,6 +489,7 @@ def process(lfe, pole_width, machine, bch):
|
|
474
489
|
'weights': weights.tolist(),
|
475
490
|
'pos': pos.tolist(), 'r1': r1,
|
476
491
|
'torque': torque,
|
492
|
+
'flux_k': flux,
|
477
493
|
'emf': emf,
|
478
494
|
'emf_amp': emffft['a'], 'emf_angle': emffft['alfa0'],
|
479
495
|
'freq': freq,
|
@@ -591,10 +607,10 @@ class AFPM:
|
|
591
607
|
condMat: conductor material
|
592
608
|
"""
|
593
609
|
def __init__(self, workdir, magnetizingCurves='.', magnetMat='',
|
594
|
-
condMat=''):
|
610
|
+
condMat='', cmd=None):
|
595
611
|
self.parstudy = parstudy.List(
|
596
612
|
workdir, condMat=condMat, magnets=magnetMat,
|
597
|
-
magnetizingCurves=magnetizingCurves)
|
613
|
+
magnetizingCurves=magnetizingCurves, cmd=cmd)
|
598
614
|
|
599
615
|
def __call__(self, engine, machine, simulation, num_slices):
|
600
616
|
""" run FE simulation
|
@@ -648,21 +664,24 @@ class AFPM:
|
|
648
664
|
}
|
649
665
|
machine['pole_width'] = np.pi * machine['inner_diam']/machine['poles']
|
650
666
|
machine['lfe'] = machine['outer_diam'] - machine['inner_diam']
|
651
|
-
|
667
|
+
simulation['skew_displ'] = (simulation.get('skew_angle', 0)/180 * np.pi
|
668
|
+
* machine['inner_diam'])
|
652
669
|
nlresults = {}
|
653
|
-
if (simulation['calculationMode'] != 'cogg_calc'
|
654
|
-
'poc' not in simulation):
|
670
|
+
if (simulation['calculationMode'] != 'cogg_calc'
|
671
|
+
and 'poc' not in simulation):
|
655
672
|
nlcalc = dict(
|
656
673
|
calculationMode="cogg_calc",
|
657
674
|
magn_temp=simulation.get('magn_temp', 20),
|
658
675
|
num_move_steps=60,
|
676
|
+
skew_linear=0,
|
677
|
+
skew_steps=0,
|
659
678
|
poc=poc.Poc(machine['pole_width']),
|
660
679
|
speed=0)
|
661
680
|
logging.info("Noload simulation")
|
662
681
|
nlresults = self.parstudy(parvardef,
|
663
682
|
machine, nlcalc, engine)
|
664
683
|
if nlresults['status'].count('C') != len(nlresults['status']):
|
665
|
-
raise ValueError('Noload simulation failed
|
684
|
+
raise ValueError(f'Noload simulation failed {nlresults["status"]}')
|
666
685
|
nlresults.update(process(lfe, pole_width, machine, nlresults['f']))
|
667
686
|
|
668
687
|
current_angles = nlresults['f'][0]['current_angles']
|
@@ -671,14 +690,15 @@ class AFPM:
|
|
671
690
|
parameters={
|
672
691
|
'phi_voltage_winding': current_angles})
|
673
692
|
logger.info("Current angles: %s", current_angles)
|
674
|
-
elif (simulation['calculationMode'] == 'cogg_calc'
|
675
|
-
'poc' not in simulation):
|
693
|
+
elif (simulation['calculationMode'] == 'cogg_calc'
|
694
|
+
and 'poc' not in simulation):
|
676
695
|
simulation['poc'] = poc.Poc(machine['pole_width'])
|
677
696
|
|
678
697
|
lresults = self.parstudy(
|
679
|
-
parvardef,
|
680
|
-
|
681
|
-
|
698
|
+
parvardef, machine, simulation, engine)
|
699
|
+
if lresults['status'].count('C') != len(lresults['status']):
|
700
|
+
raise ValueError(
|
701
|
+
f'{simulation["calculationMode"]} failed {lresults["status"]}')
|
682
702
|
|
683
703
|
results = process(lfe, pole_width, machine, lresults['f'])
|
684
704
|
if nlresults:
|
femagtools/machine/effloss.py
CHANGED
@@ -39,7 +39,7 @@ def iqd_tmech_umax(m, u1, with_mtpa, progress, speed_torque, iq, id, iex):
|
|
39
39
|
finally:
|
40
40
|
progress.close()
|
41
41
|
|
42
|
-
def iqd_tmech_umax_multi(num_proc, ntmesh, m, u1, with_mtpa):
|
42
|
+
def iqd_tmech_umax_multi(num_proc, ntmesh, m, u1, with_mtpa, publish=0):
|
43
43
|
"""calculate iqd for sm and pm using multiproc
|
44
44
|
"""
|
45
45
|
progress_readers = []
|
@@ -79,6 +79,16 @@ def iqd_tmech_umax_multi(num_proc, ntmesh, m, u1, with_mtpa):
|
|
79
79
|
progress_readers.remove(r)
|
80
80
|
else:
|
81
81
|
if i % len(progress_readers) == 0:
|
82
|
+
if publish:
|
83
|
+
numTot = len(collected_msg)
|
84
|
+
numOf = f"{num_proc-numTot} of {num_proc}"
|
85
|
+
workdone = sum([float(i[:-1])
|
86
|
+
for i in collected_msg]) / numTot
|
87
|
+
logger.debug("numTot %d numOf %s workdone %s, %s",
|
88
|
+
numTot, numOf, workdone, collected_msg)
|
89
|
+
publish(('progress_logger',
|
90
|
+
f"{numTot}:{numOf}:{workdone}:"))
|
91
|
+
# {' '.join(collected_msg)}"))
|
82
92
|
logger.info("Losses/Eff Map: %s",
|
83
93
|
', '.join(collected_msg))
|
84
94
|
collected_msg = []
|
@@ -156,9 +166,9 @@ def _generate_mesh(n, T, nb, Tb, npoints):
|
|
156
166
|
nmax = max(n)
|
157
167
|
tmin, tmax = 0, max(T)
|
158
168
|
tnum = npoints[1]
|
159
|
-
tip = ip.
|
169
|
+
tip = ip.make_interp_spline(n, T, k=1)
|
160
170
|
if nb and Tb:
|
161
|
-
tbip = ip.
|
171
|
+
tbip = ip.make_interp_spline(nb, Tb, k=1)
|
162
172
|
else:
|
163
173
|
def tbip(x): return 0
|
164
174
|
|
@@ -197,7 +207,7 @@ def efficiency_losses_map(eecpars, u1, T, temp, n, npoints=(60, 40),
|
|
197
207
|
with_mtpa -- (optional) use mtpa if True (default), disables mtpv if False
|
198
208
|
with_tmech -- (optional) use friction and windage losses (default)
|
199
209
|
num_proc -- (optional) number of parallel processes (default 0)
|
200
|
-
progress -- (optional) custom function for progress logging
|
210
|
+
progress -- (optional) custom function for progress logging (publishing)
|
201
211
|
with_torque_corr -- (optional) T is corrected if out of range (default False)
|
202
212
|
|
203
213
|
Returns:
|
@@ -250,41 +260,42 @@ def efficiency_losses_map(eecpars, u1, T, temp, n, npoints=(60, 40),
|
|
250
260
|
logger.info("total speed,torque samples %d", ntmesh.shape[1])
|
251
261
|
if isinstance(m, (PmRelMachine, SynchronousMachine)):
|
252
262
|
if num_proc > 1:
|
253
|
-
iqd = iqd_tmech_umax_multi(num_proc, ntmesh, m, u1, with_mtpa
|
263
|
+
iqd = iqd_tmech_umax_multi(num_proc, ntmesh, m, u1, with_mtpa,
|
264
|
+
publish=progress)
|
254
265
|
else:
|
255
266
|
class ProgressLogger:
|
256
|
-
def __init__(self, nsamples):
|
267
|
+
def __init__(self, nsamples, publish):
|
257
268
|
self.n = 0
|
269
|
+
self.publish = publish
|
258
270
|
self.nsamples = nsamples
|
259
271
|
self.num_iv = round(nsamples/15)
|
260
272
|
def __call__(self, iqd):
|
261
273
|
self.n += 1
|
262
274
|
if self.n % self.num_iv == 0:
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
progress = ProgressLogger(ntmesh.shape[1])
|
275
|
+
workdone=round(100*self.n/self.nsamples)
|
276
|
+
if self.publish:
|
277
|
+
self.publish(
|
278
|
+
('progress_logger',
|
279
|
+
f"{self.n}:{self.n} of {self.nsamples}:{workdone}"))
|
280
|
+
logger.info("Losses/Eff Map: %d%%", workdone)
|
281
|
+
|
282
|
+
progress_logger = ProgressLogger(ntmesh.shape[1], progress)
|
283
|
+
progress_logger.nsamples = ntmesh.shape[1]
|
284
|
+
progress_logger(0) # To check conformity
|
285
|
+
progress_logger.n = 0
|
275
286
|
if with_tmech:
|
276
287
|
iqd = np.array([
|
277
288
|
m.iqd_tmech_umax(
|
278
289
|
nt[1],
|
279
290
|
2*np.pi*nt[0]*m.p,
|
280
|
-
u1, log=
|
291
|
+
u1, log=progress_logger, with_mtpa=with_mtpa)[:-1]
|
281
292
|
for nt in ntmesh.T]).T
|
282
293
|
else:
|
283
294
|
iqd = np.array([
|
284
295
|
m.iqd_torque_umax(
|
285
296
|
nt[1],
|
286
297
|
2*np.pi*nt[0]*m.p,
|
287
|
-
u1, log=
|
298
|
+
u1, log=progress_logger, with_mtpa=with_mtpa)[:-1]
|
288
299
|
for nt in ntmesh.T]).T
|
289
300
|
|
290
301
|
beta, i1 = betai1(iqd[0], iqd[1])
|
femagtools/machine/im.py
CHANGED
@@ -35,6 +35,9 @@ def log_interp1d(x, y, kind='cubic'):
|
|
35
35
|
logy = np.log10(yy)
|
36
36
|
lin_interp = ip.interp1d(logx, logy, kind=kind, fill_value="extrapolate")
|
37
37
|
def log_interp(zz): return np.power(10.0, lin_interp(np.log10(zz)))
|
38
|
+
# TODO check replace interp1d
|
39
|
+
#lin_interp = ip.make_interp_spline(logx, logy, bc_type='not-a-knot')
|
40
|
+
#def log_interp(zz): return np.power(10.0, lin_interp(np.log10(zz), nu=1))
|
38
41
|
return log_interp
|
39
42
|
|
40
43
|
|
@@ -792,15 +795,10 @@ def parident(workdir, engine, f1, u1, wdgcon,
|
|
792
795
|
tend = time.time()
|
793
796
|
logger.info("Elapsed time %d s Status %s",
|
794
797
|
(tend-tstart), status)
|
798
|
+
if any([x != 'C' for x in status]):
|
799
|
+
raise ValueError("AC simulation failed")
|
795
800
|
# collect results
|
796
|
-
results = []
|
797
|
-
errors = []
|
798
|
-
for t in job.tasks:
|
799
|
-
if t.status == 'C':
|
800
|
-
results.append(t.get_results())
|
801
|
-
else:
|
802
|
-
logger.warning("Status %s", t.status)
|
803
|
-
results.append({})
|
801
|
+
results = [t.get_results() for t in job.tasks]
|
804
802
|
|
805
803
|
i1_0 = results[0]['i1_0'].tolist()
|
806
804
|
psi1_0 = results[0]['psi1_0'].tolist()
|
femagtools/machine/sizing.py
CHANGED
@@ -628,8 +628,9 @@ def get_sm_rotor_dimensions(A, psi1, lfe, Da2, par):
|
|
628
628
|
mue0 = 4*np.pi*1e-7
|
629
629
|
|
630
630
|
wf = wp - wc
|
631
|
-
|
632
|
-
|
631
|
+
NI = par['airgap']*par['Ba']/mue0
|
632
|
+
anr = NI/par['J']/par['kqr']
|
633
|
+
r['NI'] = NI
|
633
634
|
a = np.tan(alphap/2)/2
|
634
635
|
b = (wp/2 - wc + np.tan(alphap/2)*(Da2/2 - hp))/2
|
635
636
|
hc = min(max(4*anr/(wc+wp), 0.75*hfmax), hfmax)
|
@@ -827,7 +828,7 @@ def afpm(pnom: float, speed: float, p: int, afmtype: str, **kwargs) -> dict:
|
|
827
828
|
bns = taus*(Di+2*hs1) - bds
|
828
829
|
|
829
830
|
hns = (-bns + np.sqrt(bns**2 + 4*ans*np.tan(taus)))/2/np.tan(taus)/kps
|
830
|
-
hys = psi1/lfe/par['By']/kps
|
831
|
+
hys = psi1/2/lfe/par['By']/kps
|
831
832
|
|
832
833
|
aw = ans * par['kq'] / layers / num_wires * kps
|
833
834
|
|