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/parstudy.py
CHANGED
@@ -105,10 +105,10 @@ class ParameterStudy(object):
|
|
105
105
|
raise ValueError("directory {} is not empty".format(dirname))
|
106
106
|
self.reportdir = dirname
|
107
107
|
|
108
|
-
def setup_model(self, builder, model, recsin=''):
|
108
|
+
def setup_model(self, builder, model, recsin='', feloss=''):
|
109
109
|
"""builds model in current workdir and returns its filenames"""
|
110
110
|
# get and write mag curves
|
111
|
-
mc_files = self.femag.copy_magnetizing_curves(model, recsin=recsin)
|
111
|
+
mc_files = self.femag.copy_magnetizing_curves(model, recsin=recsin, feloss=feloss)
|
112
112
|
|
113
113
|
if model.is_complete():
|
114
114
|
logger.info("setup model in %s", self.femag.workdir)
|
@@ -192,7 +192,8 @@ class ParameterStudy(object):
|
|
192
192
|
objective_vars)
|
193
193
|
|
194
194
|
if immutable_model:
|
195
|
-
modelfiles = self.setup_model(builder, model, recsin=fea.recsin
|
195
|
+
modelfiles = self.setup_model(builder, model, recsin=fea.recsin,
|
196
|
+
feloss=simulation.get('feloss', ''))
|
196
197
|
logger.info("Files %s", modelfiles+extra_files)
|
197
198
|
logger.info("model %s", model.props())
|
198
199
|
for k in ('name', 'poles', 'outer_diam', 'airgap', 'bore_diam',
|
@@ -262,6 +263,10 @@ class ParameterStudy(object):
|
|
262
263
|
p, int(np.ceil(len(par_range)/popsize)),
|
263
264
|
np.shape(f))
|
264
265
|
job.cleanup()
|
266
|
+
try:
|
267
|
+
feloss = fea.calc_fe_loss
|
268
|
+
except AttributeError:
|
269
|
+
feloss = ''
|
265
270
|
for k, x in enumerate(population):
|
266
271
|
task = job.add_task(self.result_func)
|
267
272
|
for fn in extra_files:
|
@@ -284,7 +289,8 @@ class ParameterStudy(object):
|
|
284
289
|
for mc in self.femag.copy_magnetizing_curves(
|
285
290
|
model,
|
286
291
|
dir=task.directory,
|
287
|
-
recsin=fea.recsin
|
292
|
+
recsin=fea.recsin,
|
293
|
+
feloss=feloss):
|
288
294
|
task.add_file(mc)
|
289
295
|
set_magnet_properties(model, fea, self.femag.magnets)
|
290
296
|
task.add_file(
|
femagtools/semi_fea.py
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
import numpy as np
|
2
|
+
from .utils import fft
|
3
|
+
import logging
|
4
|
+
|
5
|
+
logger = logging.getLogger('femagtools.semi_fea')
|
6
|
+
|
7
|
+
def shift_array(v, idx):
|
8
|
+
'''shift array by index'''
|
9
|
+
return v[idx::] + v[0:idx]
|
10
|
+
|
11
|
+
def fft_filter(result_fft, perc=0.01):
|
12
|
+
'''filter FFT result with amplitude'''
|
13
|
+
result = {"order": [], "y":[]}
|
14
|
+
base_amp = result_fft['a']
|
15
|
+
for i, j in enumerate(result_fft['nue']):
|
16
|
+
if j >= perc*base_amp:
|
17
|
+
result['order'].append(i)
|
18
|
+
result['y'].append(j)
|
19
|
+
return result
|
20
|
+
|
21
|
+
def fast_skew_cogg(result, skew_setup):
|
22
|
+
'''Calculate cogging torque/Back-EMF with step skewing based on unskewed result
|
23
|
+
Arguments:
|
24
|
+
result: BCH objects
|
25
|
+
skew_setup(dict): {"skew_angle": 10, "nu_skew_steps": 2}
|
26
|
+
'''
|
27
|
+
skew_angle = skew_setup['skew_angle']
|
28
|
+
num_skew_steps =skew_setup['num_skew_steps']
|
29
|
+
skew_angle_intern = 0.0
|
30
|
+
skew_angle_array = []
|
31
|
+
T_slice = []
|
32
|
+
bemf = {"1": [], "2": [], "3": []}
|
33
|
+
bemf_slice = {"1": [], "2": [], "3": []}
|
34
|
+
bemf_skew = {"1": [], "2": [], "3": []}
|
35
|
+
bemf_skew_fft = {"1": [], "2": [], "3": []}
|
36
|
+
|
37
|
+
keyset = ('1', '2', '3')
|
38
|
+
|
39
|
+
# check if skew steps equals 2
|
40
|
+
if num_skew_steps == 2:
|
41
|
+
skew_angle_intern = skew_angle
|
42
|
+
skew_angle_array = [-skew_angle_intern/2, skew_angle_intern/2]
|
43
|
+
else:
|
44
|
+
skew_angle_intern = skew_angle/num_skew_steps*(num_skew_steps-1)/2
|
45
|
+
skew_angle_array = np.linspace(-skew_angle_intern, skew_angle_intern,
|
46
|
+
num_skew_steps, endpoint=True).tolist()
|
47
|
+
|
48
|
+
angle = result.torque[-1]['angle']
|
49
|
+
T = result.torque[-1]['torque'][0:-1]
|
50
|
+
# get back-emf from BCH
|
51
|
+
for i in keyset:
|
52
|
+
bemf[i] = result.flux[i][-1]['voltage_dpsi'][0:-1]
|
53
|
+
|
54
|
+
angl_resl = angle[1]
|
55
|
+
tmp = np.unique(np.abs(skew_angle_array))
|
56
|
+
skew_angl_resl = 0.0
|
57
|
+
if np.amin(tmp) == 0.0:
|
58
|
+
skew_angl_resl = tmp[1]
|
59
|
+
else:
|
60
|
+
skew_angl_resl = tmp[0]
|
61
|
+
|
62
|
+
divider = skew_angl_resl/angl_resl
|
63
|
+
if divider - np.floor(divider) > 1e-15:
|
64
|
+
# TODO: Interpolation if angle resolution doesn't match
|
65
|
+
logger.warning("Wrong Mesh Size in the airgap mesh")
|
66
|
+
else:
|
67
|
+
logger.info(f"number of element shifted {divider}")
|
68
|
+
|
69
|
+
for i in skew_angle_array:
|
70
|
+
idx = int(i/angl_resl)
|
71
|
+
if i != 0:
|
72
|
+
T_slice.append(shift_array(T, idx))
|
73
|
+
for j in keyset:
|
74
|
+
bemf_slice[j].append(shift_array(bemf[j], idx))
|
75
|
+
else:
|
76
|
+
# do nothing
|
77
|
+
T_slice.append(T)
|
78
|
+
for j in keyset:
|
79
|
+
bemf_slice[j].append(bemf[j])
|
80
|
+
|
81
|
+
# average torque
|
82
|
+
T_sum = 0
|
83
|
+
for i in T_slice:
|
84
|
+
T_sum += np.array(i)
|
85
|
+
T_skew = (T_sum/num_skew_steps).tolist()
|
86
|
+
T_skew += [T_skew[0]]
|
87
|
+
T_fft = fft_filter(fft(angle, T_skew, pmod=2))
|
88
|
+
|
89
|
+
# average back-emf
|
90
|
+
for j in keyset:
|
91
|
+
flx_skew = 0
|
92
|
+
for k in bemf_slice[j]:
|
93
|
+
flx_skew+=np.array(k)
|
94
|
+
bemf_skew[j] = (flx_skew/num_skew_steps).tolist()
|
95
|
+
bemf_skew[j] += [bemf_skew[j][0]]
|
96
|
+
bemf_skew_fft[j] = fft_filter(fft(angle, bemf_skew[j], pmod=2))
|
97
|
+
|
98
|
+
for i in range(len(T_slice)):
|
99
|
+
T_slice[i] = (np.array(T_slice[i])/num_skew_steps).tolist()
|
100
|
+
T_slice[i]+=[T_slice[i][0]]
|
101
|
+
|
102
|
+
return {"angle": angle,
|
103
|
+
"cogging_torque": T_skew,
|
104
|
+
"cogging_torque_fft": T_fft,
|
105
|
+
"BEMF": bemf_skew,
|
106
|
+
"BEMF_fft": bemf_skew_fft,
|
107
|
+
"cogging_torque_slice": T_slice}
|
108
|
+
|
@@ -88,9 +88,6 @@ m.pole_width = ${model['pole_width']*1e3}
|
|
88
88
|
% if hasattr(model, 'lfe'):
|
89
89
|
m.arm_length = ${model.get(['lfe'])*1e3}
|
90
90
|
% endif
|
91
|
-
% if hasattr(model, 'lfe'):
|
92
|
-
m.arm_length = ${model.get(['lfe'])*1e3}
|
93
|
-
% endif
|
94
91
|
% if hasattr(model, 'winding'):
|
95
92
|
% if 'num_par_wdgs' in model.winding:
|
96
93
|
m.num_par_wdgs = ${model.winding['num_par_wdgs']}
|
@@ -1,20 +1,20 @@
|
|
1
|
-
m.hc_min = ${'%12.3f' % model.get('hc_min', 95.0)} -- Limit demagnetisa > 0:[%]Hc,<0:[kA/m]
|
2
|
-
m.con_hdcopy = ${'%12.3f' % model.get('con_hdcopy', 0)} -- Hc-copy:Name:auto:0,intact:1, none:-1
|
3
|
-
m.b_max = ${'%12.3f' % model.get('b_max', 2.4)} -- Max Induction [T] in colorgradation
|
4
|
-
m.b_min = ${'%12.3f' % model.get('move_inside')} -- Move inside: 0 , Move outside: > 0
|
5
|
-
m.calc_fe_loss = ${
|
6
|
-
m.eval_force = ${'%12.3f' % model.get('eval_force', 0)} -- Eval. force density > 0, no <= 0
|
7
|
-
m.allow_draw = ${'%12.3f' % model.get('allow_draw', 1)} -- Draw Graphics :> 0: yes, 0: no
|
8
|
-
m.fline_dens = ${'%12.3f' % model.get('fline_dens', 4)} -- F-Lines: 1: small, 2: medium, 3:thick
|
9
|
-
m.num_flines = ${'%12.3f' % model.get('num_flines', 20)} -- Number of Field-lines: < 100 > 2
|
10
|
-
m.name_bch_log = ${'%12.3f' % model.get('name_bch_log', 0)} -- Name bch-file in Logfile:> 0:yes,0:no
|
11
|
-
m.st_size_move = ${'%12.3f' % model.get('st_size_move', 0)} -- Step size move: r/ph:[degr], x/y:[mm]
|
12
|
-
m.num_nonl_it = ${'%12.3f' % model.get('num_nonl_it', 300)} -- Number of nonlinear Iterations < 99
|
13
|
-
m.perm_mode = ${'%12.3f' % model.get('perm_mode', 0)} -- Permeability mode:>0:restore,0:actual
|
14
|
-
m.error_perm = ${'%12.3f' % model.get('error_perm', 0.005)} -- Rel. Permeability error < 0.1 [%]
|
15
|
-
m.allow_demagn = ${'%12.3f' % model.get('allow_demagn', 0)} -- Allow Demagnetisation:= 1:yes,= 0:no
|
16
|
-
m.maenergy = ${'%12.3f' % model.get('maenergy', 0)} -- Force from magn energy 1 :yes,= 0:no
|
17
|
-
m.el_order_ag = ${'%12.3f' % model.get('el_order_ag', 1)} -- El. order in air gap: lin=1: quadr=2
|
18
|
-
m.export_scrpt = ${'%12.3f' % model.get('export_scrpt', 0)} -- Export parameters in script: yes > 0
|
1
|
+
m.hc_min = ${'%12.3f' % model.get('hc_min', 95.0)} -- Limit demagnetisa > 0:[%]Hc,<0:[kA/m]
|
2
|
+
m.con_hdcopy = ${'%12.3f' % model.get('con_hdcopy', 0)} -- Hc-copy:Name:auto:0,intact:1, none:-1
|
3
|
+
m.b_max = ${'%12.3f' % model.get('b_max', 2.4)} -- Max Induction [T] in colorgradation
|
4
|
+
m.b_min = ${'%12.3f' % model.get('move_inside')} -- Move inside: 0 , Move outside: > 0
|
5
|
+
m.calc_fe_loss = ${model.get('calc_fe_loss', 1)} -- Calc. FE-Loss:0:no, 1:yes, 2:m-output
|
6
|
+
m.eval_force = ${'%12.3f' % model.get('eval_force', 0)} -- Eval. force density > 0, no <= 0
|
7
|
+
m.allow_draw = ${'%12.3f' % model.get('allow_draw', 1)} -- Draw Graphics :> 0: yes, 0: no
|
8
|
+
m.fline_dens = ${'%12.3f' % model.get('fline_dens', 4)} -- F-Lines: 1: small, 2: medium, 3:thick
|
9
|
+
m.num_flines = ${'%12.3f' % model.get('num_flines', 20)} -- Number of Field-lines: < 100 > 2
|
10
|
+
m.name_bch_log = ${'%12.3f' % model.get('name_bch_log', 0)} -- Name bch-file in Logfile:> 0:yes,0:no
|
11
|
+
m.st_size_move = ${'%12.3f' % model.get('st_size_move', 0)} -- Step size move: r/ph:[degr], x/y:[mm]
|
12
|
+
m.num_nonl_it = ${'%12.3f' % model.get('num_nonl_it', 300)} -- Number of nonlinear Iterations < 99
|
13
|
+
m.perm_mode = ${'%12.3f' % model.get('perm_mode', 0)} -- Permeability mode:>0:restore,0:actual
|
14
|
+
m.error_perm = ${'%12.3f' % model.get('error_perm', 0.005)} -- Rel. Permeability error < 0.1 [%]
|
15
|
+
m.allow_demagn = ${'%12.3f' % model.get('allow_demagn', 0)} -- Allow Demagnetisation:= 1:yes,= 0:no
|
16
|
+
m.maenergy = ${'%12.3f' % model.get('maenergy', 0)} -- Force from magn energy 1 :yes,= 0:no
|
17
|
+
m.el_order_ag = ${'%12.3f' % model.get('el_order_ag', 1)} -- El. order in air gap: lin=1: quadr=2
|
18
|
+
m.export_scrpt = ${'%12.3f' % model.get('export_scrpt', 0)} -- Export parameters in script: yes > 0
|
19
19
|
|
20
20
|
pre_models("FE-contr-data")
|
@@ -21,6 +21,9 @@ m.num_cur_steps = ${model['num_cur_steps']}
|
|
21
21
|
m.nu_beta_steps = ${model['num_beta_steps']}
|
22
22
|
m.beta_max = ${model['beta_max']}
|
23
23
|
m.beta_min = ${model['beta_min']}
|
24
|
+
% if model.get('calc_fe_loss', 0):
|
25
|
+
m.calc_fe_loss = ${model['calc_fe_loss']}
|
26
|
+
% endif
|
24
27
|
% if model.get('loss_funct',0):
|
25
28
|
m.loss_funct = ${model.get('loss_funct')}
|
26
29
|
% endif
|
@@ -44,10 +44,16 @@ if not airgap_created then
|
|
44
44
|
nc_line(r2, 0.0, x2, y2, 0.0)
|
45
45
|
|
46
46
|
if m.tot_num_slot > m.num_sl_gen then
|
47
|
+
if inner_da_end == nil then
|
48
|
+
inner_da_end = inner_da_start
|
49
|
+
end
|
47
50
|
x3, y3 = pr2c(inner_da_end, alfa)
|
48
51
|
x4, y4 = pr2c(r1, alfa)
|
49
52
|
nc_line(x3, y3, x4, y4, 0, 0)
|
50
53
|
|
54
|
+
if outer_da_end == nil then
|
55
|
+
outer_da_end = outer_da_start
|
56
|
+
end
|
51
57
|
x3, y3 = pr2c(outer_da_end, alfa)
|
52
58
|
x4, y4 = pr2c(r2, alfa)
|
53
59
|
nc_line(x3, y3, x4, y4, 0, 0)
|
@@ -22,6 +22,9 @@ m.fc_mult_move_type = 1.0 -- Type of move path in air gap
|
|
22
22
|
m.fc_force_points = 0.0 -- number move points in air gap
|
23
23
|
m.loss_funct = ${model.get('loss_funct', 0)} -- loss functon 0: own 1: ext
|
24
24
|
m.loss_fact = ${model.get('loss_fact', 1)} -- loss multiplication factor
|
25
|
+
% if model.get('calc_fe_loss', 0):
|
26
|
+
m.calc_fe_loss = ${model['calc_fe_loss']}
|
27
|
+
% endif
|
25
28
|
|
26
29
|
% if model.get('vtu_movie', 0):
|
27
30
|
m.movie_type = 'vtu'
|
@@ -23,11 +23,14 @@ m.pocfilename = '${model.get('pocfilename', 'sin.poc')}'
|
|
23
23
|
% if model.get('vtu_movie', 0):
|
24
24
|
m.movie_type = 'vtu'
|
25
25
|
%endif
|
26
|
+
% if model.get('calc_fe_loss'):
|
27
|
+
m.calc_fe_loss = ${'%d' % model['calc_fe_loss']}
|
28
|
+
% endif
|
26
29
|
% if model.get('loss_funct',0):
|
27
30
|
m.loss_funct = ${model.get('loss_funct')}
|
28
31
|
% endif
|
29
32
|
-- Excitation current
|
30
|
-
m.nloa_ex_cur = ${model.get('
|
33
|
+
m.nloa_ex_cur = ${model.get('noload_ex_cur', 0)} -- No Load Exciting current
|
31
34
|
m.load_ex_cur = ${model.get('load_ex_cur', 0)} -- Load Exciting current
|
32
35
|
m.wdgkeyex = 0 -- automatic winding selection
|
33
36
|
|
@@ -7,6 +7,9 @@ set_sim_data("explicit_mode", ${model.get('explicit_mode',0)})
|
|
7
7
|
% if model.get('wind_temp',0):
|
8
8
|
set_dev_data("cond_temp", ${model.get('wind_temp')}, ${model.get('wind_temp')})
|
9
9
|
% endif
|
10
|
+
% if model.get('calc_fe_loss', 0):
|
11
|
+
m.calc_fe_loss = ${model['calc_fe_loss']}
|
12
|
+
% endif
|
10
13
|
m.move_action = ${model.get('move_action', 0)}
|
11
14
|
% if model.get('lfe',0):
|
12
15
|
m.arm_length = ${model.get('lfe')*1e3}
|
@@ -20,6 +20,9 @@ m.winding_temp = ${model.get('wind_temp')}
|
|
20
20
|
m.current = 1.0
|
21
21
|
m.ntibfilename = model..'.ntib'
|
22
22
|
m.period_frac = ${model.get('period_frac', 1)}
|
23
|
+
% if model.get('calc_fe_loss', 0):
|
24
|
+
m.calc_fe_loss = ${model['calc_fe_loss']}
|
25
|
+
% endif
|
23
26
|
% if model.get('loss_funct',0):
|
24
27
|
m.loss_funct = ${model.get('loss_funct')}
|
25
28
|
% endif
|
@@ -24,6 +24,9 @@ m.delta_iq = ${model['delta_iq']}/m.num_par_wdgs
|
|
24
24
|
% if model.get('load_ex_cur',0):
|
25
25
|
m.load_ex_cur = ${model['load_ex_cur']}
|
26
26
|
%endif
|
27
|
+
% if model.get('calc_fe_loss', 0):
|
28
|
+
m.calc_fe_loss = ${model['calc_fe_loss']}
|
29
|
+
% endif
|
27
30
|
% if model.get('loss_funct',0):
|
28
31
|
m.loss_funct = ${model.get('loss_funct')}
|
29
32
|
% endif
|
femagtools/tks.py
CHANGED
@@ -22,15 +22,15 @@ HBpattern = re.compile(r'H.+\s+B')
|
|
22
22
|
BPpattern = re.compile(r'B.+\s+P')
|
23
23
|
|
24
24
|
_tranlate = {
|
25
|
-
"ch": "Hysteresis Loss Factor",
|
26
|
-
"cw": "Eddy Current Loss Factor",
|
27
|
-
"ce": "Excess Loss Factor",
|
28
|
-
"ch_freq": "Hyteresis Exponent",
|
29
|
-
"cw_freq": "Eddy Current Exponent",
|
30
|
-
"b_coeff": "Induction Loss Exponent",
|
25
|
+
"ch": "Hysteresis Loss Factor",
|
26
|
+
"cw": "Eddy Current Loss Factor",
|
27
|
+
"ce": "Excess Loss Factor",
|
28
|
+
"ch_freq": "Hyteresis Exponent",
|
29
|
+
"cw_freq": "Eddy Current Exponent",
|
30
|
+
"b_coeff": "Induction Loss Exponent",
|
31
31
|
"alpha": "Induction Loss Exponent (Bertotti)",
|
32
|
-
"Bo": "Reference Induction",
|
33
|
-
"fo": "Reference Frequency",
|
32
|
+
"Bo": "Reference Induction",
|
33
|
+
"fo": "Reference Frequency",
|
34
34
|
}
|
35
35
|
|
36
36
|
def readlist(section):
|
@@ -134,15 +134,15 @@ class Reader(object):
|
|
134
134
|
self.losses['cw_freq'] = z[1]
|
135
135
|
self.losses['b_coeff'] = z[2]
|
136
136
|
|
137
|
-
self.steinmetz = {'cw': z[0], 'cw_freq': z[1], 'b_coeff': z[2],
|
137
|
+
self.steinmetz = {'cw': z[0], 'cw_freq': z[1], 'b_coeff': z[2],
|
138
138
|
'Bo': self.Bo, 'fo': self.fo}
|
139
139
|
|
140
140
|
self.losses['Bo'] = self.Bo
|
141
141
|
self.losses['fo'] = self.fo
|
142
142
|
z = lc.fit_bertotti(self.losses['f'],
|
143
143
|
self.losses['B'], pfe)
|
144
|
-
self.bertotti = {'ch': z[0], 'cw': z[1], 'ce': z[2],
|
145
|
-
'
|
144
|
+
self.bertotti = {'ch': z[0], 'cw': z[1], 'ce': z[2],
|
145
|
+
'b_coeff': 2.0, 'Bo': 1, 'fo': 1}
|
146
146
|
logger.info("Bertotti loss coeffs %s", z)
|
147
147
|
|
148
148
|
# must normalize pfe matrix:
|
@@ -171,9 +171,12 @@ class Reader(object):
|
|
171
171
|
'cw_freq': self.cw_freq,
|
172
172
|
'b_coeff': self.b_coeff,
|
173
173
|
'rho': self.rho,
|
174
|
-
'losses': self.losses
|
175
|
-
|
176
|
-
|
174
|
+
'losses': self.losses,
|
175
|
+
'bertotti': self.bertotti,
|
176
|
+
'steinmetz': self.steinmetz,
|
177
|
+
'jordan': self.jordan}
|
178
|
+
|
179
|
+
def tableview(Reader):
|
177
180
|
"""pretty print loss coeff table"""
|
178
181
|
losscoeff = [Reader.jordan, Reader.steinmetz, Reader.bertotti]
|
179
182
|
# Title
|
@@ -181,18 +184,18 @@ def tableview(Reader):
|
|
181
184
|
print('='*strlen)
|
182
185
|
print('| {:^34} '.format(' ') + '| {:^18} | {:^18} | {:^18} |'.format(*["Jordan", "Steinmetz", 'Bertotti']))
|
183
186
|
print('='*strlen)
|
184
|
-
# data
|
185
|
-
for key, item in _tranlate.items():
|
187
|
+
# data
|
188
|
+
for key, item in _tranlate.items():
|
186
189
|
fout = ''
|
187
|
-
for i in losscoeff:
|
188
|
-
if key in i:
|
190
|
+
for i in losscoeff:
|
191
|
+
if key in i:
|
189
192
|
fout += '| ' + f'{i[key]:^18.8e} '
|
190
|
-
else:
|
193
|
+
else:
|
191
194
|
tmp = '-'
|
192
195
|
fout += '| ' + f'{tmp:^18} '
|
193
196
|
print(f'| {item:^34}' + ' ' + fout + '|')
|
194
197
|
print('='*strlen)
|
195
|
-
return
|
198
|
+
return
|
196
199
|
|
197
200
|
def read(filename, filecontent=None):
|
198
201
|
"""read Thyssen File TKS and return mc dict"""
|
femagtools/utils.py
CHANGED
@@ -17,7 +17,7 @@ def fft(pos, y, pmod=0):
|
|
17
17
|
else:
|
18
18
|
#negative_periodic = np.abs(y[0] - y[-1])/np.max(y) > 1
|
19
19
|
# count zero crossings
|
20
|
-
ypos = np.asarray(y[:-1]) > 0
|
20
|
+
ypos = np.asarray(y[:-1])-np.mean(y[:-1]) > 0
|
21
21
|
nypos = ~ypos
|
22
22
|
nzc = len(((ypos[:-1] & nypos[1:])
|
23
23
|
| (nypos[:-1] & ypos[1:])).nonzero()[0])
|
femagtools/windings.py
CHANGED
@@ -179,10 +179,10 @@ class Winding(object):
|
|
179
179
|
nue = n
|
180
180
|
else:
|
181
181
|
nue = self.kw_order(n)
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
182
|
+
if q1 == q2: # integral slot winding
|
183
|
+
q = self.Q/2/self.m/self.p
|
184
|
+
nuep = nue/self.p
|
185
|
+
return np.sin(nuep*np.pi/2/self.m)/q/np.sin(nuep*np.pi/2/self.m/q)
|
186
186
|
k = 2 if self.l == 1 else 1
|
187
187
|
a = nue*k*np.pi/self.Q*Yk
|
188
188
|
t = self.Q//Qb
|
@@ -416,6 +416,13 @@ class Winding(object):
|
|
416
416
|
[[d*s for s, d in zip(l, ld)] for l, ld in zip(lower, ldirs)])
|
417
417
|
# complete if not basic winding:
|
418
418
|
Qb = self.Q//num_basic_windings(self.Q, self.p, self.l)
|
419
|
+
|
420
|
+
if not np.asarray(upper).size or not np.asarray(lower).size:
|
421
|
+
layers = 1
|
422
|
+
if layers == 1 and z[1]:
|
423
|
+
z = ([[d*s for s, d in zip(l, ld)] for l, ld in zip(lower, ldirs)],
|
424
|
+
[[d*s for s, d in zip(u, ud)] for u, ud in zip(upper, udirs)])
|
425
|
+
|
419
426
|
if max([abs(n) for m in z[0] for n in m]) < Qb:
|
420
427
|
return [[k + [-n+Qb//2 if n < 0 else -(n+Qb//2) for n in k]
|
421
428
|
for k in m] for m in z]
|
femagtools/zmq.py
ADDED
@@ -0,0 +1,213 @@
|
|
1
|
+
"""zmq functions for FEMAG
|
2
|
+
|
3
|
+
"""
|
4
|
+
import re
|
5
|
+
import pathlib
|
6
|
+
import threading
|
7
|
+
import json
|
8
|
+
import time
|
9
|
+
import logging
|
10
|
+
try:
|
11
|
+
import zmq
|
12
|
+
except ImportError:
|
13
|
+
pass
|
14
|
+
|
15
|
+
numpat = re.compile(r'([+-]?\d+(?:\.\d+)?(?:[eE][+-]\d+)?)\s*')
|
16
|
+
logger = logging.getLogger(__name__)
|
17
|
+
|
18
|
+
class ProtFile:
|
19
|
+
def __init__(self, dirname, num_cur_steps):
|
20
|
+
self.size = 0
|
21
|
+
self.looplen = 0
|
22
|
+
self.cur_steps = [1, num_cur_steps]
|
23
|
+
self.n = 0
|
24
|
+
self.num_loops = 0
|
25
|
+
import platform
|
26
|
+
self.dirname = dirname
|
27
|
+
self.name = 'samples'
|
28
|
+
|
29
|
+
def percent(self):
|
30
|
+
if self.looplen > 0:
|
31
|
+
return min(100 * self.n / self.looplen, 100)
|
32
|
+
return 0
|
33
|
+
|
34
|
+
def update(self):
|
35
|
+
if not self.dirname:
|
36
|
+
return ''
|
37
|
+
p = list(pathlib.Path(self.dirname).glob('*.PROT'))
|
38
|
+
if p:
|
39
|
+
buf = ''
|
40
|
+
if self.size < p[0].stat().st_size:
|
41
|
+
with p[0].open() as fp:
|
42
|
+
fp.seek(self.size)
|
43
|
+
buf = fp.read()
|
44
|
+
return self.append(buf)
|
45
|
+
return ''
|
46
|
+
|
47
|
+
def append(self, buf):
|
48
|
+
self.size += len(buf)
|
49
|
+
for line in [l.strip() for l in buf.split('\n') if l]:
|
50
|
+
if line.startswith('Loop'):
|
51
|
+
self.n = 0
|
52
|
+
try:
|
53
|
+
cur_steps = self.cur_steps[self.num_loops]
|
54
|
+
except IndexError:
|
55
|
+
cur_steps = 1
|
56
|
+
x0, x1, dx, nbeta = [float(f)
|
57
|
+
for f in re.findall(numpat, line)][:4]
|
58
|
+
move_steps = round((x1-x0)/dx+1)
|
59
|
+
beta_steps = int(nbeta)
|
60
|
+
self.looplen = cur_steps*beta_steps*move_steps
|
61
|
+
self.num_loops += 1
|
62
|
+
elif (line.startswith('Cur') or
|
63
|
+
line.startswith('Id')):
|
64
|
+
self.n += 1
|
65
|
+
elif line.startswith('Number movesteps Fe-Losses'):
|
66
|
+
return f'{self.percent():3.1f}%' # 100%
|
67
|
+
elif line.startswith('begin'):
|
68
|
+
self.name = line.split()[1].strip()
|
69
|
+
|
70
|
+
return f'{self.percent():3.1f}%' # {self.n}/{self.looplen}'
|
71
|
+
|
72
|
+
|
73
|
+
class SubscriberTask(threading.Thread):
|
74
|
+
ylabel_index = 1
|
75
|
+
curve_label = '{}.'
|
76
|
+
# used by static notify func
|
77
|
+
percent_list = []
|
78
|
+
notify_timerfunc = None
|
79
|
+
notify_send_loop = True
|
80
|
+
notify = None
|
81
|
+
notify_send_header = set()
|
82
|
+
notify_send_data = dict()
|
83
|
+
|
84
|
+
def __init__(self, **kwargs):
|
85
|
+
threading.Thread.__init__(self)
|
86
|
+
context = zmq.Context.instance()
|
87
|
+
self.subscriber = context.socket(zmq.SUB)
|
88
|
+
self.port = kwargs.get('port', None)
|
89
|
+
self.host = kwargs.get('host')
|
90
|
+
self.notify = kwargs.get('notify', None)
|
91
|
+
SubscriberTask.notify = kwargs.get('notify', None)
|
92
|
+
self.header = kwargs.get('header')
|
93
|
+
self.num_cur_steps = kwargs.get('num_cur_steps', None)
|
94
|
+
SubscriberTask.curve_label = kwargs.get('curve_label', '')
|
95
|
+
SubscriberTask.timestep = kwargs.get('timestep', 1)
|
96
|
+
if not self.host:
|
97
|
+
self.host = 'localhost'
|
98
|
+
if not self.header:
|
99
|
+
self.header = [b'']
|
100
|
+
self.running = True
|
101
|
+
|
102
|
+
# timer function
|
103
|
+
if not SubscriberTask.notify_timerfunc:
|
104
|
+
SubscriberTask.notify_timerfunc = threading.Timer(0.1, SubscriberTask.send_notify)
|
105
|
+
SubscriberTask.notify_send_loop = True
|
106
|
+
SubscriberTask.notify_timerfunc.start()
|
107
|
+
|
108
|
+
if b'xyplot' in self.header:
|
109
|
+
self.ylabel = self.curve_label.format(SubscriberTask.ylabel_index)
|
110
|
+
SubscriberTask.ylabel_index += 1
|
111
|
+
if b'progress' in self.header:
|
112
|
+
self.protfile = ProtFile(None, self.num_cur_steps)
|
113
|
+
self.protId = len(SubscriberTask.percent_list)
|
114
|
+
SubscriberTask.percent_list.append(0) # 0%
|
115
|
+
|
116
|
+
self.subscriber.connect(f'tcp://{self.host}:{self.port}')
|
117
|
+
self.subscriber.setsockopt(zmq.SUBSCRIBE, self.header[0] if len(self.header) == 1 else b'')
|
118
|
+
self.controller = zmq.Context.instance().socket(zmq.PULL)
|
119
|
+
self.controller_url = f'inproc://publisher{self.port}'
|
120
|
+
try:
|
121
|
+
self.controller.bind(self.controller_url)
|
122
|
+
except zmq.error.ZMQError:
|
123
|
+
pass # ignore
|
124
|
+
|
125
|
+
self.poller = zmq.Poller()
|
126
|
+
self.poller.register(self.subscriber, zmq.POLLIN)
|
127
|
+
self.poller.register(self.controller, zmq.POLLIN)
|
128
|
+
self.logger = logger
|
129
|
+
|
130
|
+
def stop(self):
|
131
|
+
socket = zmq.Context.instance().socket(zmq.PUSH)
|
132
|
+
socket.connect(self.controller_url)
|
133
|
+
socket.send(b"quit")
|
134
|
+
socket.close()
|
135
|
+
self.running = False
|
136
|
+
|
137
|
+
def clear():
|
138
|
+
SubscriberTask.ylabel_index = 1
|
139
|
+
SubscriberTask.curve_label = '{}.'
|
140
|
+
SubscriberTask.notify_timerfunc = None
|
141
|
+
SubscriberTask.notify_send_loop = False
|
142
|
+
SubscriberTask.notify = None
|
143
|
+
SubscriberTask.notify_send_header = set()
|
144
|
+
SubscriberTask.notify_send_data = dict()
|
145
|
+
SubscriberTask.percent_list = []
|
146
|
+
|
147
|
+
def send_notify():
|
148
|
+
logger.debug(f"Send loop: {SubscriberTask.notify_send_loop}")
|
149
|
+
while SubscriberTask.notify_send_loop:
|
150
|
+
logger.debug(f"Send data: {SubscriberTask.notify_send_header}")
|
151
|
+
if 'progress_logger' in SubscriberTask.notify_send_header:
|
152
|
+
# collect data from different threads
|
153
|
+
SubscriberTask.notify_send_header.remove('progress_logger')
|
154
|
+
numTot = len(SubscriberTask.percent_list)
|
155
|
+
d = json.loads(SubscriberTask.notify_send_data.get('progress_logger')[1])
|
156
|
+
d['percent'] = sum(SubscriberTask.percent_list) / numTot
|
157
|
+
d['subtitle'] = f"{SubscriberTask.percent_list.count(100)} of {numTot}"
|
158
|
+
SubscriberTask.notify(['progress_logger', json.dumps(d)])
|
159
|
+
if 'xyplot' in SubscriberTask.notify_send_header:
|
160
|
+
SubscriberTask.notify([s.decode('latin1')
|
161
|
+
for s in SubscriberTask.notify_send_data.get('xyplot')])
|
162
|
+
SubscriberTask.notify_send_header.remove('xyplot')
|
163
|
+
|
164
|
+
time.sleep(abs(SubscriberTask.timestep))
|
165
|
+
logger.debug(f"Send Finished loop: {SubscriberTask.notify_send_loop}")
|
166
|
+
|
167
|
+
def run(self):
|
168
|
+
self.logger.debug("subscriber is ready, port: %s", {self.port})
|
169
|
+
while self.running:
|
170
|
+
socks = dict(self.poller.poll())
|
171
|
+
if socks.get(self.subscriber) == zmq.POLLIN:
|
172
|
+
try:
|
173
|
+
response = self.subscriber.recv_multipart()
|
174
|
+
# Sometimes femag send messages with only len = 1. These messages must be ignored
|
175
|
+
if len(response) < 2:
|
176
|
+
continue
|
177
|
+
# header progress
|
178
|
+
if response[0] == b'progress' and b'progress' in self.header:
|
179
|
+
SubscriberTask.notify_send_header.add('progress_logger')
|
180
|
+
response[0] = b'progress_logger'
|
181
|
+
SubscriberTask.notify_send_data['progress_logger'] = response
|
182
|
+
SubscriberTask.percent_list[self.protId] = json.loads(response[1].decode()).get('percent')
|
183
|
+
continue
|
184
|
+
|
185
|
+
# header xyplot (add ylabel)
|
186
|
+
if response[0] == b'xyplot' and b'xyplot' in self.header :
|
187
|
+
d = json.loads(response[1].decode(), strict=False)
|
188
|
+
d['ylabel'] = f"{d.get('ylabel')}_{self.ylabel}" \
|
189
|
+
if d.get('ylabel') else self.ylabel
|
190
|
+
response[1] = json.dumps(d).encode()
|
191
|
+
|
192
|
+
# timestep negative, immediately update
|
193
|
+
if SubscriberTask.timestep < 0:
|
194
|
+
self.notify([s.decode('latin1') for s in response])
|
195
|
+
else:
|
196
|
+
SubscriberTask.notify_send_data['xyplot'] = response
|
197
|
+
SubscriberTask.notify_send_header.add('xyplot')
|
198
|
+
continue
|
199
|
+
|
200
|
+
if response[0] in self.header or b'' in self.header:
|
201
|
+
self.notify([s.decode('latin1') for s in response])
|
202
|
+
|
203
|
+
except Exception:
|
204
|
+
self.logger.error(
|
205
|
+
"error in subscription message processing", exc_info=True)
|
206
|
+
|
207
|
+
if socks.get(self.controller) == zmq.POLLIN:
|
208
|
+
req = self.controller.recv()
|
209
|
+
self.logger.info("subscriber %s", req)
|
210
|
+
break
|
211
|
+
self.subscriber.close()
|
212
|
+
self.controller.close()
|
213
|
+
self.logger.debug("subscriber stopped")
|
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.2
|
2
2
|
Name: femagtools
|
3
|
-
Version: 1.8.
|
3
|
+
Version: 1.8.4
|
4
4
|
Summary: Python API for FEMAG
|
5
5
|
Author-email: Ronald Tanner <tar@semafor.ch>, Dapu Zhang <dzhang@gtisoft.com>, Beat Holm <hob@semafor.ch>, Günther Amsler <amg@semafor.ch>, Nicolas Mauchle <mau@semafor.ch>
|
6
6
|
License: Copyright (c) 2016-2023, Semafor Informatik & Energie AG, Basel
|
@@ -37,7 +37,7 @@ Classifier: Topic :: Scientific/Engineering
|
|
37
37
|
Requires-Python: >=3.7
|
38
38
|
Description-Content-Type: text/markdown
|
39
39
|
License-File: LICENSE
|
40
|
-
Requires-Dist: numpy
|
40
|
+
Requires-Dist: numpy
|
41
41
|
Requires-Dist: scipy
|
42
42
|
Requires-Dist: mako
|
43
43
|
Requires-Dist: six
|