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.
Files changed (49) hide show
  1. femagtools/__init__.py +1 -1
  2. femagtools/bch.py +10 -6
  3. femagtools/dxfsl/area.py +69 -1
  4. femagtools/dxfsl/conv.py +53 -16
  5. femagtools/dxfsl/converter.py +273 -76
  6. femagtools/dxfsl/fslrenderer.py +18 -22
  7. femagtools/dxfsl/functions.py +38 -8
  8. femagtools/dxfsl/geom.py +112 -35
  9. femagtools/dxfsl/journal.py +1 -1
  10. femagtools/dxfsl/machine.py +44 -7
  11. femagtools/dxfsl/shape.py +4 -0
  12. femagtools/dxfsl/symmetry.py +105 -32
  13. femagtools/femag.py +64 -61
  14. femagtools/fsl.py +4 -2
  15. femagtools/isa7.py +3 -2
  16. femagtools/machine/afpm.py +45 -25
  17. femagtools/machine/effloss.py +31 -20
  18. femagtools/machine/im.py +6 -8
  19. femagtools/machine/sizing.py +4 -3
  20. femagtools/machine/sm.py +35 -37
  21. femagtools/mcv.py +66 -37
  22. femagtools/multiproc.py +79 -80
  23. femagtools/parstudy.py +10 -4
  24. femagtools/semi_fea.py +108 -0
  25. femagtools/templates/basic_modpar.mako +0 -3
  26. femagtools/templates/fe-contr.mako +18 -18
  27. femagtools/templates/ld_lq_fast.mako +3 -0
  28. femagtools/templates/mesh-airgap.mako +6 -0
  29. femagtools/templates/mult_cal_fast.mako +3 -0
  30. femagtools/templates/pm_sym_f_cur.mako +4 -1
  31. femagtools/templates/pm_sym_fast.mako +3 -0
  32. femagtools/templates/pm_sym_loss.mako +3 -0
  33. femagtools/templates/psd_psq_fast.mako +3 -0
  34. femagtools/templates/torq_calc.mako +3 -0
  35. femagtools/tks.py +23 -20
  36. femagtools/utils.py +1 -1
  37. femagtools/windings.py +11 -4
  38. femagtools/zmq.py +213 -0
  39. {femagtools-1.8.2.dist-info → femagtools-1.8.4.dist-info}/METADATA +3 -3
  40. {femagtools-1.8.2.dist-info → femagtools-1.8.4.dist-info}/RECORD +49 -47
  41. {femagtools-1.8.2.dist-info → femagtools-1.8.4.dist-info}/WHEEL +1 -1
  42. tests/test_afpm.py +15 -6
  43. tests/test_femag.py +1 -1
  44. tests/test_fsl.py +4 -4
  45. tests/test_mcv.py +20 -14
  46. tests/test_parident.py +2 -1
  47. {femagtools-1.8.2.dist-info → femagtools-1.8.4.dist-info}/LICENSE +0 -0
  48. {femagtools-1.8.2.dist-info → femagtools-1.8.4.dist-info}/entry_points.txt +0 -0
  49. {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 = ${'%12.3f' % 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
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('nload_ex_cur', 0)} -- No Load Exciting current
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
@@ -2,6 +2,9 @@
2
2
  -- Torque/Force calculation
3
3
  --
4
4
  m.move_action = ${model.get('move_action', 0)}
5
+ % if model.get('calc_fe_loss', 0):
6
+ m.calc_fe_loss = ${model['calc_fe_loss']}
7
+ % endif
5
8
  % if model.get('loss_funct',0):
6
9
  m.loss_funct = ${model.get('loss_funct')}
7
10
  % 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
- 'alpha': 2.0, 'Bo': 1, 'fo': 1}
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
- def tableview(Reader):
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
- #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)
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
1
+ Metadata-Version: 2.2
2
2
  Name: femagtools
3
- Version: 1.8.2
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<=1.26.4
40
+ Requires-Dist: numpy
41
41
  Requires-Dist: scipy
42
42
  Requires-Dist: mako
43
43
  Requires-Dist: six