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/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
|