femagtools 1.8.2__py3-none-any.whl → 1.8.4__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 +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
|
|