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