# -*- coding: utf-8 -*-
"""
femagtools.bch
~~~~~~~~~~~~~~
Reading BCH/BATCH files
"""
import sys
import numpy as np
import re
import logging
import logging.config
logger = logging.getLogger('femagtools.bch')
alpha20 = 3.93e-3 # temperature coeff of copper
_indxOpPattern = re.compile(r'[a-zA-Z0-9_]+(\[[-0-9]+\])')
_statloss = re.compile(r'Fe-Losses\s*Stator')
_rotloss = re.compile(r'Fe-Losses\s*Rotor')
[docs]def splitindex(name):
"returns listname and index if name contains index"
m = _indxOpPattern.search(name)
if m:
return (name.split('[')[0],
int(''.join(list(m.group(1))[1:-1])))
return (name, None)
[docs]def floatnan(s):
"""converts string to float
returns NaN on conversion error"""
try:
return float(s)
except ValueError:
return float('NaN')
[docs]def r1_20(r1, theta):
return r1/(1+alpha20*(theta-20))
[docs]def get_si_factor(contentline):
"extract the first pattern and return conversion factor for SI unit"
pattern = re.compile("\[([A-Za-z/0-9]+)\]")
search = pattern.search(contentline)
if search:
if search.group(1) in ('kW', 'kNm'):
return 1e3
return 1.0
def _readSections(f):
"""return list of bch sections
sections are surrounded by lines starting with '[***'
Args:
param f (file) BCH file to be read
Returns:
list of sections
"""
section = []
for line in f:
if line.startswith('[****'):
if section:
yield section
section = []
else:
section.append(line.strip())
yield section
[docs]class Reader:
"""Reads a BCH/BATCH-File"""
_numPattern = re.compile(r'([+-]?\d+(?:\.\d+)?(?:[eE][+-]\d+)?)\s*')
def __init__(self):
self._fft = None
self.type = None
self.filename = ''
self.external_rotor = False
self.project = ''
self.date = ''
self.version = ''
# known types:
# MULTIPLE CALCULATION OF FORCES AND FLUX
# Fast cogging calculation OF FORCES AND FLUX
# Fast LD-LQ-Identification
# Fast Psid-Psiq-Identification
# Fast PM-Synchronous-Motor Simulation
# Characteristics of Permanent-Magnet-Motors
self.wdg = None
self.wdgfactors = []
self.torque = []
self.torque_fft = []
self.psidq = {}
self.psidq_ldq = None
self.machine = {}
self.lossPar = {}
self.flux = {}
self.magnet = {}
self.airgapInduction = {}
self.flux_fft = {}
self.linearForce = []
self.linearForce_fft = []
self.scData = {}
self.dqPar = {}
self.ldq = {}
self.losses = []
self.demag = []
self.weights = []
self.weight = {}
self.characteristics = {}
self.areas = []
self.current_angles = []
self.dispatch = {
'General Machine Data': Reader.__read_general_machine_data,
'Weigths': Reader.__read_weights,
'Number of Nodes': Reader.__read_nodes_and_mesh,
'Windings Data': Reader.__read_dummy,
'Winding-Factors': Reader.__read_winding_factors,
'Machine Data': Reader.__read_machine_data,
'CAD-Parameter Data': Reader.__read_dummy,
'Torque-Force': Reader.__read_torque_force,
'Airgap Induction Br': Reader.__read_airgapInduction,
'Fourier Analysis': Reader.__read_fft,
'Machine excitation': Reader.__read_dummy,
'DQ-Parameter for open Winding Modell': Reader.__read_dq_parameter,
'Magnet Data': Reader.__read_magnet_data,
'Date': Reader.__read_date,
'Inertia': Reader.__read_inertia,
'Losses': Reader.__read_dummy,
'Fe-Hysteresis- and Eddy current Losses[W] > 0.1 % Max':
Reader.__read_hysteresis_eddy_current_losses,
'Losses [W]': Reader.__read_losses,
'Losses for speed [1/min]': Reader.__read_losses_tab,
'Losses from PSID-Psiq-Identification for speed [1/min]':
Reader.__read_losses_tab,
'Magnet loss data': Reader.__read_dummy,
'Project File name': Reader.__read_project_filename,
'File name': Reader.__read_filename,
'Windings input data': Reader.__read_windings,
'Control parameters for Loss calculation': Reader.__read_lossPar,
'PSID-Psiq-Identification': Reader.__read_psidq,
'Ld-Lq-Identifikation aus PSID-Psiq-Identification':
Reader.__read_psidq_ldq,
'Ld-Lq-Identification RMS-values': Reader.__read_ldq,
'Machine Data Rotor': Reader.__read_dummy,
'Current Angles defined from no-load test':
Reader.__read_current_angles,
'FEMAG Version': Reader.__read_version,
'Simulation Data': Reader.__read_simulation_data,
'Area [mm**2]': Reader.__read_areas,
'Basic Machine parameters': Reader.__read_dummy,
'Winding': Reader.__read_dummy,
'Calculation time [sec]': Reader.__read_dummy,
'Results for Angle I-Up [Degree]': Reader.__read_dummy,
'Demagnetisation': Reader.__read_demagnetization,
'Transient short circuit': Reader.__read_short_circuit,
'Flux observed': Reader.__read_flux,
'Linear Force': Reader.__read_linear_force,
'Demagnetization Data': Reader.__read_demagnetization,
'** Characteristics of Permanent-Magnet-Motors **':
Reader.__read_characteristics}
[docs] def getStep(self):
"""@returns displacement step of flux values"""
if len(self.flux) > 0:
return self.flux[0]['displ'][1]-self.flux[0]['displ'][0]
return None
[docs] def read(self, lines):
"""read bch file
Args:
lines (list of str) the text lines of the BCH file
"""
for s in _readSections(lines):
if not s:
continue
title = s[0].split(':')[0].strip()
if title == 'Function':
title = s[0].split(':')[1].strip()
logger.debug("'%s': %d", title, len(s[1:]))
# Check if we are finished with the fourier analysis part(s)
if title != 'Fourier Analysis':
self._fft = None
else:
title2 = s[0].split(':')[1].strip()
for k in ['Airgap Induction Br']:
if k == title2[:len(k)]:
title = title2[:len(k)]
if title in self.dispatch:
self.dispatch[title](self, s)
if len(self.weights) > 0:
w = list(zip(*self.weights))
self.weight['iron'] = sum(w[0])
self.weight['conductor'] = sum(w[1])
self.weight['magnet'] = sum(w[2])
self.weight['total'] = sum([sum(l) for l in w])
def __read_version(self, content):
self.version = content[0].split(' ')[3]
def __read_project_filename(self, content):
self.project = content[1].strip()
def __read_filename(self, content):
self.filename = content[0].split(':')[1].strip()
def __read_date(self, content):
d = content[0].split(':')[1].strip().split()
dd, MM, yy = d[0].split('.')
hh, mm = ''.join(d[1:-1]).split('.')
self.date = '{}-{}-{}T{:02}:{:02}'.format(yy, MM, dd, int(hh), int(mm))
def __read_nodes_and_mesh(self, content):
self.nodes, self.elements, self.quality = \
[floatnan(r[0]) for r in [self._numPattern.findall(l)
for l in content[:3]]]
for l in content[3:]:
m = re.match(r'\*+([^\*]+)\*+', l)
if m:
self.type = m.group(1).strip()
return
def __read_magnet_data(self, content):
"""read magnet data section"""
for l in content:
for v in [["Remanence Br [T]", 'Br'],
["Co. Force Hc", 'Hc'],
["Rel. Permeability", 'muer'],
["Magn.Temperatur[grad]", 'Tmag'],
["Temp.coeficient Br", 'alpha'],
["Limit demagnetization [%]", 'demag_pc'],
["Limit demagnetization Hx [kA/m]", 'demag_hx'],
["Total magnet area", 'area']]:
if l.startswith(v[0]):
rec = self._numPattern.findall(l)
if len(rec) > 0:
self.magnet[v[1]] = floatnan(rec[-1])
break
def __read_windings(self, content):
"read winding section"
index = False
wdgs = []
for line in content:
rec = line.split()
if len(rec) > 0 and not index:
index = rec[0].startswith('Index')
elif len(rec) == 5 and index:
if rec[0].startswith('Number'):
index = False
else:
wdgs.append((abs(int(rec[1])),
1 if int(rec[1]) > 0 else -1,
float(rec[2]), float(rec[3]),
float(rec[4])))
else:
index = False
phases = set(list(zip(*wdgs))[0])
self.windings = dict([(k, dict([(j, [])
for j in ('dir',
'N',
'R',
'PHI')]))
for k in phases])
for w in wdgs:
self.windings[w[0]]['dir'].append(w[1])
self.windings[w[0]]['N'].append(w[2])
self.windings[w[0]]['R'].append(w[3]/1e3)
self.windings[w[0]]['PHI'].append(w[4])
def __read_winding_factors(self, content):
"read winding factors section"
self.wdgfactors = []
for line in content:
if line.startswith('Winding-Key'):
self.wdgfactors.append(dict(order=[], wfac=[],
skewf=[], total=[]))
else:
rec = self._numPattern.findall(line)
if len(rec) == 4:
self.wdgfactors[-1]['order'].append(int(rec[0]))
self.wdgfactors[-1]['wfac'].append(float(rec[1]))
self.wdgfactors[-1]['skewf'].append(float(rec[2]))
self.wdgfactors[-1]['total'].append(float(rec[3]))
def __read_dummy(self, content):
return
def __read_simulation_data(self, content):
for line in content:
if line.startswith('Number of Phases m'):
self.machine['m'] = int(float((line.split()[-1])))
return
def __read_current_angles(self, content):
self.current_angles = []
for l in content:
rec = self._numPattern.findall(l)
if len(rec) == 3:
self.current_angles.append(floatnan(rec[-1]))
return
def __read_lossPar(self, content):
self.lossPar = {
'fo': [],
'Bo': [],
'ch': [],
'cw': [],
'hf': [],
'ef': [],
'ic': [],
'gamfe': [],
'thetaw': [],
'fillfe': [],
'mat': []}
for l in content:
for v in [["Base Frequency", 'fo'],
["Base Induction", 'Bo'],
["Hysteresis-Coefficient", 'ch'],
["Eddycurrent-Coefficient", 'cw'],
["Hysteresis-Frequency-Coefficient", 'hf'],
["Eddycurrent-Frequency-Coefficient", 'ef'],
["Induction-Coefficient", 'ic'],
["Specific Weight Iron", 'gamfe'],
["Conductor Temperature", 'thetaw'],
["Fillfactor Iron", 'fillfe'],
["Material factor", 'mat']]:
if l.find(v[0]) > -1:
rec = self._numPattern.findall(l)
if len(rec) > 0:
self.lossPar[v[1]].append(floatnan(rec[-1]))
break
return l
def __read_demagnetization(self, content):
keys = [('displ', 'current_1', 'current_2', 'current_3',
'H_max', 'H_av', 'area'),
('displ', 'current', 'beta',
'H_max', 'H_av', 'area')]
for l in content:
rec = self._numPattern.findall(l)
if len(rec) == 7 or len(rec) == 6:
i = 0 if len(rec) == 7 else 1
self.demag.append(
{k: floatnan(r) for r, k in zip(rec, keys[i])})
else:
d = dict()
for v in [["Limit Hc value", "lim_hc"],
["Max. Magnetization", "br_max"],
["Min. Magnetization", "br_min"],
["Area demagnetized", "area"]]:
if l.find(v[0]) > -1:
rec = self._numPattern.findall(l)
if len(rec) > 0:
d[v[1]] = floatnan(rec[-1])
if d:
self.demag.append(d)
def __read_short_circuit(self, content):
"read short circuit section"
if content[2].startswith('Time'):
m = []
for l in content:
rec = l.split()
if len(rec) == 5 and not rec[0].startswith('Time'):
m.append([floatnan(x) for x in rec])
m = np.array(m).T
self.scData['time'] = m[0].tolist()
self.scData['ia'] = m[1].tolist()
self.scData['ib'] = m[2].tolist()
self.scData['ic'] = m[3].tolist()
self.scData['torque'] = m[4].tolist()
return
l = content[-1]
rec = l.split()
self.scData['speed'] = floatnan(rec[0])
self.scData['ikd'] = floatnan(rec[1])
self.scData['tkd'] = floatnan(rec[2])
self.scData['iks'] = floatnan(rec[3])
self.scData['tks'] = floatnan(rec[4])
def __read_general_machine_data(self, content):
for l in content:
if l.find('Armature Length [mm]:') > -1:
self.armatureLength = floatnan(l.split()[-1])
elif l.find('Magn. Fluss Psim RMS') > 0:
self.machine['psim'] = floatnan(l.split()[-1])
elif l.find('Number of Pole pairs') > -1:
self.machine['p'] = int(l.split()[-1])
elif l.find('Number of Poles simulated') > -1:
self.machine['p_sim'] = int(l.split()[-1])
elif l.find('Total Number of Slots') > -1:
self.machine['Q'] = int(l.split()[-1])
elif l.find('Number of Slot-Sides sim.') > -1:
self.machine['qs_sim'] = int(l.split()[-1])
elif l.find('POC-File used in calculation') > -1:
self.machine['pocfile'] = l.split(':')[-1].strip()
def __read_characteristics(self, content):
for i, l in enumerate(content):
if l.startswith('[[***'):
break
for v in [['Voltage (operat. limit)', 'u1nom'],
['Current (operat. limit)', 'i1nom'],
['Angle I VS Up (speed = 0)', 'beta0'],
['Resistance stator winding', 'r1'],
['Inductance(I,Angle I-Up)', 'Ldnom'],
['Inductance(I,Angle I-Up)', 'Lqnom'],
['Power (operating limit)', 'Pnom'],
['Stator ewdg inductance', 'Le'],
['Stator external inductance Lex', 'Lex'],
['Magn. flux (RMS)', 'psimnom'],
['Effect. armature length', 'lfe'],
['Power cut-off speed NC', 'nc'],
['Number of Pole pairs', 'p'],
['Max. current (RMS)', 'i1max'],
['Rel. number wdg turns(wdg.1)', 'relw'],
['Number of Phases', 'm'],
['Min. speed', 'nmin'],
['Max. speed', 'nmax']]:
if l.find(v[0]) > -1:
rec = self._numPattern.findall(l)
if len(rec) > 0:
self.characteristics[v[1]] = floatnan(rec[-1])
break
self.characteristics['ldq'] = {}
self.armatureLength = self.characteristics['lfe']
m = []
for k, l in enumerate(content[i+3:]):
if l.startswith('[[***'):
break
rec = l.split('\t')
if len(rec) == 6:
m.append([floatnan(x) for x in rec])
if m:
m = np.array(m).T
ncols = len(set(m[1]))
i1 = np.reshape(m[0], (-1, ncols)).T[0]
nrows = len(i1)
logger.info('characteristics ld-lq %d x %d', nrows, ncols)
self.characteristics['ldq'] = {
'beta': m[1][:ncols][::-1].tolist(),
'i1': i1.tolist(),
'ld': (self.armatureLength*np.reshape(
m[2], (nrows, ncols)).T[::-1]).tolist(),
'lq': (self.armatureLength*np.reshape(
m[3], (nrows, ncols)).T[::-1]).tolist(),
'psim': (self.armatureLength*np.reshape(
m[4], (nrows, ncols)).T[::-1]).tolist(),
'torque': (self.armatureLength*np.reshape(
m[5], (nrows, ncols)).T[::-1]).tolist()}
m = []
columns = [['n', 'id', 'iq', 'torque', 'p2'],
['beta', 'cos_phi', 'u1', 'um'],
['lang', 'ud', 'uq', 'i1'],
['lang', 'ld', 'lq', 'psim']]
nsec = 0
self.characteristics['speed_torque'] = {}
for l in content[k+i+6:]:
if l.startswith('[[***'):
break
if not l:
continue
if l.startswith('Speed') and m:
if nsec == 0:
m = np.array(m).T
else:
m = np.array(m).T[1:]
for j, k in enumerate(columns[nsec]):
self.characteristics['speed_torque'][k] = m[j].tolist()
m = []
nsec += 1
else:
rec = self._numPattern.findall(l)
if len(rec) > 3:
m.append([floatnan(x) for x in rec])
def __read_flux(self, content):
"read and append flux section"
f = {'displ': [], 'flux_k': [], 'voltage_dpsi': [],
'voltage_four': [], 'current_k': [], 'voltage_ir': []}
for l in content:
rec = l.split()
if l.startswith('Flux-Area'):
areas = self._numPattern.findall(l)
if not areas:
continue
self.wdg = areas[0] if len(areas)==1 else '{}-{}'.format(
areas[0], areas[1])
if self.wdg not in self.flux:
self.flux[self.wdg] = []
elif len(rec) == 7:
f['displ'].append(floatnan(rec[1].strip()))
f['flux_k'].append(floatnan(rec[2].strip()))
f['voltage_dpsi'].append(floatnan(rec[3].strip()))
f['voltage_four'].append(floatnan(rec[4].strip()))
f['current_k'].append(floatnan(rec[5].strip()))
f['voltage_ir'].append(floatnan(rec[6].strip()))
elif rec and rec[0].startswith('['):
f['displunit'] = re.search(r"\[([^\]]*)\]", l).group(1).strip()
self.flux[self.wdg].append(f)
self._fft = Reader.__read_flux_fft
def __read_linear_force(self, content):
"read and append linear force section"
f = {'displ': [], 'magnet_1': [], 'force_x': [],
'force_y': [], 'f_idpsi': []}
for l in content:
rec = self._numPattern.findall(l)
if len(rec) > 4:
f['displ'].append(floatnan(rec[1].strip()))
f['magnet_1'].append(floatnan(rec[2].strip()))
f['force_x'].append(floatnan(rec[3].strip()))
f['force_y'].append(floatnan(rec[4].strip()))
# TODO f['f_idpsi'].append(floatnan(rec[5].strip()))
if len(f['displ']) > 0:
ripple_x = max(f['force_x']) - min(f['force_x'])
ripple_y = max(f['force_y']) - min(f['force_y'])
f['ripple_x'] = ripple_x
f['ripple_y'] = ripple_y
self.linearForce.append(f)
self._fft = Reader.__read_linearForce_fft
def __read_linearForce_fft(self, content):
"read and append linear force fft section"
if not self._fft:
return
linearForce_fft = dict(order=[], force=[], force_perc=[],
a=[], b=[])
for l in content:
rec = self._numPattern.findall(l)
if len(rec) > 2:
linearForce_fft['order'].append(int(rec[0].strip()))
linearForce_fft['force'].append(floatnan(rec[1].strip()))
linearForce_fft['force_perc'].append(floatnan(rec[2].strip()))
if len(rec) > 4:
linearForce_fft['a'].append(floatnan(rec[3].strip()))
linearForce_fft['b'].append(floatnan(rec[4].strip()))
else:
linearForce_fft['a'].append(0.0)
linearForce_fft['b'].append(0.0)
if linearForce_fft['order']:
self.linearForce_fft.append(linearForce_fft)
def __read_fft(self, content):
if self._fft:
self._fft(self, content)
def __read_flux_fft(self, content):
"read and append flux fft section"
flux_fft = dict(order=[], flux=[], flux_perc=[],
voltage=[], voltage_perc=[], a=[], b=[])
for l in content:
rec = self._numPattern.findall(l)
if len(rec) > 4:
flux_fft['order'].append(int(rec[0].strip()))
flux_fft['flux'].append(floatnan(rec[1].strip()))
flux_fft['flux_perc'].append(floatnan(rec[2].strip()))
flux_fft['voltage'].append(floatnan(rec[3].strip()))
flux_fft['voltage_perc'].append(floatnan(rec[4].strip()))
if len(rec) > 3:
flux_fft['a'].append(floatnan(rec[3].strip())*1e-2)
flux_fft['b'].append(floatnan(rec[4].strip())*1e-2)
else:
flux_fft['a'].append(0.0)
flux_fft['b'].append(0.0)
if self.wdg not in self.flux_fft:
self.flux_fft[self.wdg] = []
self.flux_fft[self.wdg].append(flux_fft)
def __read_airgapInduction(self, content):
"read and append airgapInduction section"
import scipy.integrate as si
logger.info('read airgapInduction')
i1beta = False # format is either i1/beta or id/iq
if 'i1' in self.ldq and 'beta' in self.ldq:
i1 = self.ldq['i1']
beta = self.ldq['beta']
elif 'id' in self.psidq and 'iq' in self.psidq:
id = self.psidq['id']
iq = self.psidq['iq']
else:
i1 = []
beta = []
id = []
iq = []
an = [[], [], [], []]
bn = [[], [], [], []]
Bm = []
Ba = []
for line in content[5:]:
if line.startswith('[****'):
break
if line.startswith("Current"):
i1beta = True
continue
if line.startswith("C_STEP"):
return # ignore this section
try:
rec = self._numPattern.findall(line)
if len(rec) == 10:
f = [float(s) for s in rec]
an[0].append(f[2])
bn[0].append(f[3])
an[1].append(f[4])
bn[1].append(f[5])
an[2].append(f[6])
bn[2].append(f[7])
an[3].append(f[8])
bn[3].append(f[9])
a = (an[0][-1], an[1][-1], an[2][-1], an[3][-1])
b = (bn[0][-1], bn[1][-1], bn[2][-1], an[3][-1])
def B(x):
return sum(a[i] * np.cos((2 * i + 1) * x) +
b[i] * np.sin((2 * i + 1) * x)
for i in (0, 1, 2, 3))
def Bdc(x):
return abs(B(x))
Ba.append(si.quad(Bdc, 0, 2 * np.pi,
limit=250)[0] / (2 * np.pi))
Bm.append(max([B(x) for x in np.linspace(
0, 2 * np.pi, 100)]))
except Exception as e:
logger.debug("Conversion error: {} :: {}".format(e, line))
self.airgapInduction = dict()
if i1beta:
ncols = len(beta)
self.airgapInduction['beta'] = beta
self.airgapInduction['i1'] = i1
nrows = len(self.airgapInduction['i1'])
else:
ncols = len(iq)
self.airgapInduction['iq'] = iq
self.airgapInduction['id'] = id
nrows = len(self.airgapInduction['id'])
self.airgapInduction['an'] = [np.reshape(an[j][:nrows*ncols],
(nrows, ncols)).T.tolist()
for j in (0, 1, 2, 3)]
self.airgapInduction['bn'] = [np.reshape(bn[j][:nrows*ncols],
(nrows, ncols)).T.tolist()
for j in (0, 1, 2, 3)]
self.airgapInduction['Bm'] = np.reshape(Bm[:nrows*ncols],
(nrows, ncols)).T.tolist()
self.airgapInduction['Ba'] = np.reshape(Ba[:nrows*ncols],
(nrows, ncols)).T.tolist()
# check for nan:
if len(self.airgapInduction['an'][0]) > 1 and \
len(self.airgapInduction['an'][0][0]) != len(self.airgapInduction['an'][0][1]):
self.airgapInduction['an'] = [self.airgapInduction['an'][i][1:]
for i in range(3)]
self.airgapInduction['bn'] = [self.airgapInduction['bn'][i][1:]
for i in range(3)]
self.airgapInduction['Ba'] = self.airgapInduction['Ba'][1:]
self.airgapInduction['Bm'] = self.airgapInduction['Bm'][1:]
if len(self.airgapInduction['an'][0]) > 1 and \
len(list(filter(lambda x: np.isnan(x),
list(zip(*self.airgapInduction['an'][0]))[0]))) > 0:
self.airgapInduction['an'] = [self.airgapInduction['an'][i][1:]
for i in range(3)]
self.airgapInduction['bn'] = [self.airgapInduction['bn'][i][1:]
for i in range(3)]
self.airgapInduction['Ba'] = zip(*zip(*self.airgapInduction['Ba'])
[1:])
self.airgapInduction['Bm'] = zip(*zip(*self.airgapInduction['Bm'])
[1:])
def __read_torque_force(self, content):
"read and append force/torque section"
torque = {
'angle': [],
'current_1': [],
'force_x': [],
'force_y': [],
't_idpsi': [],
'torque': []}
for l in content:
rec = self._numPattern.findall(l)
if len(rec) == 7:
torque['angle'].append(floatnan(rec[1].strip()))
torque['current_1'].append(floatnan(rec[2].strip()))
torque['force_x'].append(floatnan(rec[3].strip()))
torque['force_y'].append(floatnan(rec[4].strip()))
torque['t_idpsi'].append(floatnan(rec[5].strip()))
torque['torque'].append(floatnan(rec[6].strip()))
if len(torque['angle']) > 0:
ripple = max(torque['torque']) - min(torque['torque'])
torque['ripple'] = ripple
self.torque.append(torque)
self._fft = Reader.__read_torque_force_fft
def __read_torque_force_fft(self, content):
"read and append force/torque fft section"
columns = content[3].split()
if len(columns) > 1 and columns[1] == 'Torque':
torque_fft = dict(order=[],
torque=[],
torque_perc=[],
a=[],
b=[])
for l in content:
rec = self._numPattern.findall(l)
if len(rec) > 2:
torque_fft['order'].append(int(rec[0].strip()))
torque_fft['torque'].append(floatnan(rec[1].strip()))
torque_fft['torque_perc'].append(floatnan(rec[2].strip()))
if len(rec) > 3:
torque_fft['a'].append(floatnan(rec[3].strip())*1e-2)
torque_fft['b'].append(floatnan(rec[4].strip())*1e-2)
else:
torque_fft['a'].append(0.0)
torque_fft['b'].append(0.0)
if torque_fft['order']:
self.torque_fft.append(torque_fft)
def __removeTrailingZero(self, idList):
'''if id list is inhomogeneous, remove a trailing '0'
e.g. : idList[-450, -350, -250, -150, -50, 0]
idList[-500, -400, -300, -200, -100, 0, 0]
'''
if idList[-1] == 0 and len(idList) > 2 and \
int(idList[-1] - idList[0]) / (len(idList)-1) != \
int(idList[-2] - idList[0]) / (len(idList)-2):
idList = idList[:-1]
return idList
def __read_psidq(self, content):
"read psid-psiq section"
for i, l in enumerate(content):
if l.find('[A') > -1:
break
m = []
for l in content[i+2:]:
rec = l.split('\t')
if len(rec) == 7:
m.append([floatnan(x) for x in rec])
m = np.array(m).T
ncols = np.argmax(np.abs(m[1][1:]-m[1][:-1]))+1
if ncols == 1 and len(m[1]) > 1 and m[1][0] != m[1][1]: # simple correction
ncols = 2
iq = np.reshape(m[1], (-1, ncols))[0]
if ncols > 1 and (iq[ncols-1] < iq[ncols-2] or
len(m[0]) % ncols != 0):
ncols = ncols-1
id = np.reshape(m[0], (-1, ncols)).T[0]
id = self.__removeTrailingZero(id)
nrows = len(id)
if nrows > 1 and id[nrows-1] < id[nrows-2]:
nrows = nrows-1
logger.info('psid-psiq %d x %d', nrows, ncols)
mlen = nrows*ncols
self.psidq = {k: (self.armatureLength*np.reshape(
v[:mlen], (nrows, ncols))).T.tolist()
for k, v in zip(('psid', 'psiq', 'torque_fe', 'torque'),
m[3:])}
self.psidq['iq'] = iq[:ncols].tolist()
self.psidq['id'] = id[:nrows].tolist()
def __read_psidq_ldq(self, content):
"read ldq from psid-psiq section"
for i, l in enumerate(content):
if l.find('[A') > -1:
break
m = []
for l in content[i+2:]:
rec = l.split('\t')
if len(rec) == 7:
m.append([floatnan(x) for x in rec])
m = np.array(m).T
ncols = np.argmax(np.abs(m[1][1:]-m[1][:-1]))+1
if ncols == 1 and len(m[1]) > 1 and m[1][0] != m[1][1]: # simple correction
ncols = 2
iq = np.linspace(np.min(m[1]), np.max(m[1]), ncols)
if ncols > 1 and (iq[ncols-1] < iq[ncols-2] or
len(m[0]) % ncols != 0):
ncols = ncols-1
id = np.reshape(m[0], (-1, ncols)).T[0]
nrows = len(id)
if nrows > 1 and id[nrows-1] < id[nrows-2]:
nrows = nrows-1
logger.info('psid-psiq ldq %d x %d', nrows, ncols)
mlen = nrows*ncols
self.psidq_ldq = {
'iq': iq[:ncols].tolist(),
'id': id[:nrows].tolist(),
'ld': (self.armatureLength*np.reshape(
m[2][:mlen], (nrows, ncols)).T).tolist(),
'lq': (self.armatureLength*np.reshape(
m[3][:mlen], (nrows, ncols)).T).tolist(),
'psim': (self.armatureLength*np.reshape(
m[4][:mlen], (nrows, ncols)).T).tolist(),
'torque': (self.armatureLength*np.reshape(
m[6][:mlen], (nrows, ncols)).T).tolist()}
def __read_ldq(self, content):
"read ld-lq section"
for i, l in enumerate(content):
if l.find('[A') > -1:
break
m = []
k = i+2
for l in content[i+2:]:
rec = l.split('\t')
if len(rec) > 7:
m.append([floatnan(x) for x in rec[:8]])
elif rec and rec[0].startswith('Curr Id'):
break
k += 1
m = np.array(m).T
ncols = len(set(m[1]))
i1 = np.reshape(m[0], (-1, ncols)).T[0]
nrows = len(i1)
logger.info('ld-lq %d x %d', nrows, ncols)
self.ldq = {k: (self.armatureLength*np.reshape(
v, (nrows, ncols)).T[::-1]).tolist() for k, v in zip(
('ld', 'lq', 'psim', 'psid', 'psiq', 'torque'),
m[2:])}
self.ldq['beta'] = m[1][:ncols][::-1].tolist()
self.ldq['i1'] = i1.tolist()
# skip d-q table
i = k+3
for l in content[k+3:]:
if l.startswith('Losses for'):
self.__read_losses_tab(content[i:])
i += 1
def __read_losses_tab(self, content):
"read losses of psidq or ldq"
m = []
speed = float(content[0].split()[-1])/60.
logger.info('losses for speed %f', speed)
nl = 4
for l in content[4:]:
rec = l.split('\t')
if len(rec) == 6:
m.append([floatnan(x) for x in rec])
elif rec[0].startswith('P fe'):
break
nl += 1
if not m:
return
m = np.array(m).T
ncols = np.argmax(np.abs(m[1][1:]-m[1][:-1]))+1
if ncols == 1 and len(m[1]) > 1 and m[1][0] != m[1][1]:
ncols = 2
nrows = len(m[2])//ncols
if ncols * nrows % len(m[3]) != 0:
if ncols > nrows:
ncols = ncols-1
else:
nrows = nrows-1
ls = {k: np.reshape(v,
(nrows, ncols)).T[::-1].tolist()
for k, v in zip(('styoke', 'stteeth', 'rotor', 'magnet'),
m[2:])}
m = []
for l in content[nl+3:]:
rec = l.split('\t')
if len(rec) == 8:
m.append([floatnan(x) for x in rec])
elif not rec and m:
break
if m:
m = np.array(m).T
ls.update({k: np.reshape(v,
(nrows, ncols)).T[::-1].tolist()
for k, v in zip(('styoke_hyst', 'styoke_eddy',
'stteeth_hyst', 'stteeth_eddy',
'rotor_hyst', 'rotor_eddy'),
m[2:])})
ls['speed'] = speed
if self.ldq:
self.ldq['losses'] = ls
elif self.psidq:
self.psidq['losses'] = ls
def __read_machine_data(self, content):
"read machine data section"
for k in ('beta', 'plfe1', 'plfe2', 'plmag'):
self.machine[k] = []
for l in content:
contentline = l.strip()
if contentline.startswith('Number of phases'):
rec = l.split()
self.machine['m'] = int(rec[-1])
continue
if contentline.startswith("Mechanical Power"):
rec = l.split()
self.machine['p2'] = floatnan(rec[-1])
unit = rec[2]
if unit == '[kW]':
self.machine['p2'] *= 1e3
continue
for v in [["Phase Current", 'i1'],
["Number of phases", 'm'],
["Current loading", 'A'],
["Current Density", 'J'],
["Cu fillfactor", 'kcu'],
["Therm.Loading", 'AJ'],
["torque", 'torque'],
["Force Density", 'fd'],
['Inductance Ld', 'ld'],
['Inductance Lq', 'lq'],
['Stator Resistance', 'r1'],
['Magn. Flux Psim RMS', 'psim'],
["Speed", 'n'],
['Beta-angle', 'beta'],
["Stator-Fe-Losses noload", 'plfe1_0'],
["Rotor-Fe-Losses noload", 'plfe2_0'],
["Magnet-Losses noload", 'plmag_0'],
["Stator-Fe-Losses ", 'plfe1'],
["Rotor-Fe-Losses ", 'plfe2'],
["Magnet-Losses", 'plmag'],
["Cu-Losses", 'plcu'],
["Armature", 'lfe'],
["Efficiency", 'eff']]:
if contentline.find(v[0]) > -1:
si = get_si_factor(contentline)
rec = l.split()
if v[1] in self.machine and isinstance(
self.machine[v[1]], list):
self.machine[v[1]].append(si*floatnan(rec[-1]))
else:
self.machine[v[1]] = si*floatnan(rec[-1])
break
if self.machine['beta']:
self.machine['beta'] = self.machine['beta'][1:]
self.machine['n'] = self.machine['n']/60
self.machine['lfe'] = 1e-3*self.machine['lfe']
if self.machine['plfe1']: # calc sum of losses
plfe1 = self.machine['plfe1']
plcu = self.machine.get('plcu', 0.0)
if np.isscalar(plcu):
self.machine['plcu'] = [plcu]*len(plfe1)
self.machine['pltotal'] = [sum(pl)
for pl in zip(*[self.machine[k]
for k in ('plfe1',
'plfe2',
'plmag',
'plcu')])]
self.machine['plfe'] = [sum(pl)
for pl in zip(*[self.machine[k]
for k in ('plfe1',
'plfe2')])]
def __read_dq_parameter(self, content):
if content[1].find('Windings') > -1:
for l in content[1:]:
for v in [['Windings Current', 'i1'],
['Angle I vs. Up', 'beta'],
["LD", 'ld'],
["LQ at nom. current", 'lq'],
["Torque TO", 'torque'],
["Force", 'force'],
["Torque constant Kt", 'kt'],
["Magn.Flux no-load", 'psim0'],
["Voltage Up (RMS) no-load", 'up0'],
["Magn.Flux load", 'psim'],
["Voltage Up load", 'up'],
["Speed", 'speed'],
["Number of Poles", 'npoles'],
["Armature length", 'lfe'],
["Airgap diameter", 'dag']]:
if l.find(v[0]) > -1:
rec = l.split()
self.dqPar[v[1]] = [floatnan(rec[-1])]
# pdb.set_trace()
for k in ('speed', 'npoles', 'lfe', 'dag', 'up0', 'up', 'psim0'):
if k in self.dqPar:
self.dqPar[k] = self.dqPar[k][0]
lfe = self.dqPar['lfe']
self.dqPar['lfe'] = 1e-3*self.dqPar['lfe']
for k in ('ld', 'lq', 'psim', 'torque', 'force'):
if k in self.dqPar:
self.dqPar[k][0] = lfe*self.dqPar[k][0]
if 'torque' in self.dqPar:
self.dqPar['speed'] = self.dqPar['speed']/60
if 'dag' in self.dqPar:
self.dqPar['dag'] = 1e-3*self.dqPar['dag']
if 'npoles' in self.dqPar:
self.dqPar['npoles'] = int(self.dqPar['npoles'])
self.dqPar['i1'] = [self.dqPar['i1'][0]/np.sqrt(2)]
beta = np.pi*self.dqPar['beta'][0]/180
iq = np.cos(beta)*self.dqPar['i1'][0]
id = np.sin(beta)*self.dqPar['i1'][0]
try:
w1 = np.pi*self.dqPar['speed']*self.dqPar['npoles']
r1 = self.machine.get('r1', 0.0)
uq, ud = (r1*iq + self.dqPar['up'] + id*w1*self.dqPar['ld'][0],
r1*id - iq*w1*self.dqPar['lq'][0])
self.dqPar['u1'] = [np.sqrt(uq**2 + ud**2)]
self.dqPar['gamma'] = [-np.arctan2(ud, uq)*180/np.pi]
self.dqPar['psim0'] = lfe*self.dqPar['psim0']
self.dqPar['phi'] = [self.dqPar['beta'][0] +
self.dqPar['gamma'][0]]
self.dqPar['cosphi'] = [np.cos(np.pi*phi/180)
for phi in self.dqPar['phi']]
self.dqPar['i1'].insert(0, 0)
self.dqPar['u1'].insert(0, self.dqPar.get('up0', 0))
except KeyError:
pass
# if next section is absent
try:
self.dqPar['psid'] = [self.dqPar['psim'][0]]
self.dqPar['psiq'] = [self.dqPar['lq'][0] *
self.dqPar['i1'][-1]]
except KeyError:
pass
return
for k in ('i1', 'beta', 'ld', 'lq', 'psim', 'up',
'psid', 'psiq', 'torque', 'torquefe',
'p2', 'u1', 'gamma', 'phi'):
self.dqPar[k] = []
lfe = 1e3*self.dqPar['lfe']
keys = []
for l in content:
rec = self._numPattern.findall(l)
if len(rec) == 8:
for k, r in zip(*[keys, rec]):
self.dqPar[k].append(floatnan(r))
self.dqPar['p2'].append(self.dqPar['torque'][-1] *
lfe*2*np.pi*self.dqPar['speed'])
for k in ('ld', 'lq', 'psim', 'psid', 'up', 'psiq', 'torque'):
if self.dqPar[k]:
self.dqPar[k][-1] = lfe * self.dqPar[k][-1]
elif len(rec) == 7:
self.dqPar['torquefe'].append(floatnan(rec[2]))
self.dqPar['u1'].append(floatnan(rec[4]))
self.dqPar['gamma'].append(floatnan(rec[6]))
self.dqPar['phi'].append(floatnan(rec[1]) +
self.dqPar['gamma'][-1])
else:
headers = l.split()
try: # must distinguish ee and pm
if headers[5] == 'Voltage':
keys = ('i1', 'beta', 'ld', 'lq', 'up',
'psid', 'psiq', 'torque')
if headers[5] == 'Psi_magn':
keys = ('i1', 'beta', 'ld', 'lq', 'psim',
'psid', 'psiq', 'torque')
except:
pass
if self.dqPar['psim']:
self.dqPar.pop('up', None)
else:
self.dqPar.pop('psim', None)
self.dqPar['cosphi'] = [np.cos(np.pi*phi/180)
for phi in self.dqPar['phi']]
self.dqPar['i1'].insert(0, 0)
self.dqPar['u1'].insert(0, self.dqPar.get('up0', 0))
def __read_weights(self, content):
# Stator-Iron - Conductors - Magnets
# 105.408 22.542 0.000
# Rotor-Iron - Conductors - Magnets
# 45.041 0.000 17.556
if self.weights:
return
for line in content[2:]:
rec = line.split()
if rec[0] != 'Stator-Iron' and rec[0] != 'Rotor-Iron':
self.weights.append([floatnan(x) for x in rec])
def __read_areas(self, content):
# Area [mm**2]: Stator-Iron - slots - Magnets
# 16585.2 2383.1 0.0
# Area [mm**2]: Rotor-Iron - slots - Magnets
# 6372.7 0.0 1930.2
for line in content:
rec = line.split()
if rec and rec[0] != 'Area':
self.areas.append([floatnan(x) for x in rec])
def __read_inertia(self, content):
# Inertia: GD**2/4 [gr m**2/mm]
# Stator : 0.230195
# Rotor : 0.011774
self.inertia = []
pat = re.compile(r'\[(\w\w) .+\]')
unit = pat.findall(content[0])
f = 1
if unit and unit[-1] == 'gr':
f = 1e-3
for line in content[1:]:
x = line.split()
if x:
self.inertia.append(f*floatnan(x[-1]))
def __read_losses(self, content):
losses = {}
# find results for angle:
for i, l in enumerate(content):
if l.startswith('Results for Angle I-Up'):
losses['beta'] = floatnan(l.split(':')[-1])
losses['current'] = floatnan(
content[i+1].split(':')[-1])/np.sqrt(2)
for k in ('winding', 'staza', 'stajo', 'rotfe',
'magnetJ', 'magnetB', 'r1', 'total'):
losses[k] = 0.0
continue
if l.find('Cu-losses') > -1:
rec = self._numPattern.findall(content[i+1])
if len(rec) > 0:
losses['winding'] += floatnan(rec[0])
losses['total'] += floatnan(rec[0])
if len(rec) > 2:
losses['r1'] += floatnan(rec[2])
continue
elif l.startswith('StZa') or l.startswith('RoZa'):
rec = self._numPattern.findall(content[i+2])
if len(rec) == 2:
losses['stajo'] = floatnan(rec[1])
losses['staza'] = floatnan(rec[0])
losses['total'] += losses['staza']+losses['stajo']
continue
if l.startswith('StJo') or l.startswith('RoJo') or \
_statloss.search(l):
rec = self._numPattern.findall(content[i+2])
if len(rec) == 2:
losses['stajo'] = floatnan(rec[0])
losses['staza'] = floatnan(rec[1])
losses['total'] += losses['staza']+losses['stajo']
elif len(rec) == 1:
t = l.split(':')[-1].strip()
if t == 'Iron':
losses['staza'] = floatnan(rec[0])
else:
losses['stajo'] += floatnan(rec[0])
losses['total'] += losses['staza']+losses['stajo']
continue
if _rotloss.search(l):
rec = self._numPattern.findall(content[i+2])
if len(rec) == 1:
if floatnan(rec[0]) > losses['rotfe']:
losses['rotfe'] = floatnan(rec[0])
losses['total'] += losses['rotfe']
continue
if l.find('Fe-Losses-Rotor') > -1:
rec = self._numPattern.findall(content[i+3])
if len(rec) == 2:
if content[i+1].find('Iron') > -1 and content[i+1].find('StJo') > 0:
self.external_rotor = True
# TODO: there might be better places to check this
losses['stajo'] = floatnan(rec[0])
losses['staza'] = floatnan(rec[1])
losses['total'] += losses['staza']+losses['stajo']
else:
losses['rotfe'] = floatnan(rec[1])
losses['total'] += losses['rotfe']
continue
if l.find('Magnet-Losses') > -1:
rec = self._numPattern.findall(content[i+1])
if len(rec) == 1:
losses['magnetJ'] = float(rec[0])
#losses['magnetB'] = float(Nan)
if len(rec) == 2:
losses['magnetJ'] = float(rec[0])
losses['magnetB'] = float(rec[1])
losses['total'] += losses['magnetJ']
if 'total' in losses:
losses['totalfe'] = sum([losses[k] for k in ('staza',
'stajo',
'rotfe')])
self.losses.append(losses)
def __read_hysteresis_eddy_current_losses(self, content):
losses = dict(staza=[], stajo=[], rotor=[])
k = ''
for i, l in enumerate(content):
if l.startswith('*************'):
for k in losses:
for x in losses[k]:
x[0] = int(x[0])
self.losses[-1]['fft'] = {k: {k1: l
for k1, l in zip(['order',
'freq',
'hyst',
'eddy'],
zip(*losses[k]))}
for k in losses}
self.__read_losses(content[i+1:])
break
if l.find('StJo') > -1 or \
l.find('RoJo') > -1:
k = 'stajo'
elif l.find('StZa') > -1 or \
l.find('StatorIron') > -1 or \
l.find('RoZa') > -1:
k = 'staza'
elif l.find('Iron') > -1 and l.find('Stator') > -1:
k = 'staza'
elif l.find('Iron') > -1:
if self.external_rotor:
k = 'staza'
else:
k = 'rotor'
elif l.find('Roto') > -1 or \
l.find('Ring') < -1:
k = 'rotor'
elif l.find('Stat') > -1:
k = 'stajo'
else:
try:
rec = self._numPattern.findall(l)
if len(rec) == 4:
losses[k].append([floatnan(x) for x in rec])
elif len(rec) == 5: # FEMAG Rel 8.3 with el/mech order
losses[k].append([floatnan(x)
for i, x in enumerate(rec)
if not i == 1])
except:
pass
[docs] def get(self, name, r=None):
"""return value of key name
name can be a list such as ['torque[1]', 'ripple']
or a string: 'dqPar'
"""
try:
if isinstance(name, str): # ignore r
lname, indx = splitindex(name)
if indx is None:
return self.__getattr__(name)
return self.__getattr__(lname).__getitem__(indx)
if len(name) > 1:
lname, indx = splitindex(name[0])
if r:
if indx is None:
return self.get(name[1:],
getattr(r, lname))
return self.get(name[1:],
getattr(r, lname).__getitem__(indx))
if indx is None:
return self.get(name[1:],
getattr(self, lname))
return self.get(name[1:],
getattr(self, lname).__getitem__(indx))
lname, indx = splitindex(name[0])
if r:
if indx is None:
return r.get(name[0])
return r.get(lname).__getitem__(indx)
if indx is None:
return self.__getattr__(name[0])
return self.__getattr__(lname).__getitem__(indx)
except (KeyError, IndexError, AttributeError):
return None
def __getattr__(self, k):
return self.__dict__[k]
[docs] def items(self):
return [(k, self.get(k)) for k in ('version',
'type',
'filename',
'date',
'torque',
'torque_fft',
'psidq',
'psidq_ldq',
'machine',
'lossPar',
'flux',
'flux_fft',
'wdgfactors',
'airgapInduction',
'magnet',
'scData',
'dqPar',
'ldq',
'losses',
'demag',
'linearForce',
'linearForce_fft',
'characteristics') if self.get(k)]
def __str__(self):
"return string format of this object"
if self.type:
return "\n".join([
'FEMAG {}: {}'.format(self.version, self.type),
'File: {} {}'.format(self.filename, self.date)] +
['{}: {}'.format(k, v)
for k, v in self.items()])
return "{}"
def __repr__(self):
"representation of this object"
return self.__str__()
[docs]def read(filename):
"""Read BCH/BATCH results from file *filename*."""
import io
bchresults = Reader()
with io.open(filename, encoding='latin1', errors='ignore') as f:
bchresults.read(f.readlines())
return bchresults
if __name__ == "__main__":
import json
if len(sys.argv) == 2:
filename = sys.argv[1]
else:
filename = sys.stdin.readline().strip()
b = read(filename)
json.dump({k: v for k, v in b.items()}, sys.stdout)
#print(b)