####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2017 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
####################################################################################################
####################################################################################################
import os
####################################################################################################
"""This module provide tools to read raw output.
"""
####################################################################################################
from PySpice.Unit import u_Degree, u_V, u_A, u_s, u_Hz
####################################################################################################
import logging
import numpy as np
####################################################################################################
from PySpice.Probe.WaveForm import (
OperatingPoint, SensitivityAnalysis,
DcAnalysis, AcAnalysis, TransientAnalysis,
WaveForm,
)
####################################################################################################
_module_logger = logging.getLogger(__name__)
####################################################################################################
[docs]class VariableAbc:
"""This class implements a variable or probe in a SPICE simulation output.
Public Attributes:
:attr:`index`
index in the array
:attr:`name`
:attr:`unit`
"""
##############################################
def __init__(self, index, name, unit):
# Fixme: self._ ?
self._index = int(index)
self.name = str(name)
self._unit = unit # could be guessed from name also for voltage node and branch current
self.data = None
##############################################
@property
def index(self):
return self._index
# @property
# def name(self):
# return self._name
# @name.setter
# def name(self, value):
# self._name = value
##############################################
def __repr__(self):
return 'variable[{0._index}]: {0.name} [{0._unit}]'.format(self)
##############################################
[docs] def is_voltage_node(self):
raise NotImplementedError
##############################################
[docs] def is_branch_current(self):
raise NotImplementedError
##############################################
@property
def is_interval_parameter(self):
return self.name.startswith('@') # Fixme: Xyce ???
##############################################
[docs] @staticmethod
def to_voltage_name(node):
return 'v({})'.format(node)
##############################################
[docs] @staticmethod
def to_branch_name(element):
return 'i({})'.format(element)
##############################################
[docs] def fix_case(self, element_translation, node_translation):
""" Update the name to the right case. """
if self.is_branch_current():
if self.simplified_name in element_translation:
self.name = self.to_branch_name(element_translation[self.simplified_name])
elif self.is_voltage_node():
if self.simplified_name in node_translation:
self.name = self.to_voltage_name(node_translation[self.simplified_name])
##############################################
@property
def simplified_name(self):
raise NotImplementedError
##############################################
####################################################################################################
[docs]class RawFileAbc:
""" This class parse the stdout of ngspice and the raw data output.
"""
_logger = _module_logger.getChild('RawFileAbc')
##############################################
@property
def simulation(self):
if self._simulation is not None:
return self._simulation
else:
raise NameError('Simulation is undefined')
@simulation.setter
def simulation(self, value):
self._simulation = value
##############################################
@property
def circuit(self):
return self._simulation.circuit
##############################################
_name_to_unit = {
'time': u_s,
'voltage': u_V,
'current': u_A,
'frequency': u_Hz,
}
##############################################
def _read_line(self, header_line_iterator):
""" Return the next line """
# Fixme: self._header_line_iterator, etc.
line = None
while not line:
line = next(header_line_iterator)
return line.decode('utf-8')
##############################################
def _read_header_line(self, header_line_iterator, head_line):
""" Read an header line and check it starts with *head_line*. """
line = self._read_line(header_line_iterator)
self._logger.debug(line)
if line.startswith(head_line):
return line
else:
raise NameError("Unexpected line: %s" % (line))
##############################################
def _read_header_field_line(self, header_line_iterator, expected_label, has_value=True):
""" Read an header line and check it starts with *expected_label*.
Return the values next to the label if the flag *has_value* is set.
"""
line = self._read_line(header_line_iterator)
self._logger.debug(line)
if has_value:
# a title can have ': ' after 'title: '
location = line.find(': ') # first occurence
label, value = line[:location], line[location+2:]
else:
label = line[:-1]
if label != expected_label:
raise NameError("Expected label %s instead of %s" % (expected_label, label))
if has_value:
return value.strip()
##############################################
def _read_temperature_line(self, header_line_iterator):
# Doing analysis at TEMP = 25.000000 and TNOM = 25.000000
line = self._read_header_line(header_line_iterator, 'Doing analysis at TEMP')
pattern1 = 'TEMP = '
pattern2 = ' and TNOM = '
pos1 = line.find(pattern1)
pos2 = line.find(pattern2)
if pos1 != -1 and pos2 != -1:
part1 = line[pos1+len(pattern1):pos2]
part2 = line[pos2+len(pattern2):].strip()
temperature = u_Degree(float(part1))
nominal_temperature = u_Degree(float(part2))
else:
temperature = None
nominal_temperature = None
return temperature, nominal_temperature
##############################################
def _read_header_variables(self, header_line_iterator):
self.variables = {}
for i in range(self.number_of_variables):
line = (next(header_line_iterator)).decode('utf-8')
self._logger.debug(line)
items = [x.strip() for x in line.split('\t') if x]
# 0 frequency frequency grid=3
index, name, unit = items[:3]
# unit = time, voltage, current
unit = self._name_to_unit[unit] # convert to Unit
self.variables[name] = self._variable_cls(index, name, unit)
# self._read_header_field_line(header_line_iterator, 'Binary', has_value=False)
##############################################
def _read_variable_data(self, raw_data):
""" Read the raw data and set the variable values. """
if self.flags == 'real':
number_of_columns = self.number_of_variables
elif self.flags == 'complex':
number_of_columns = 2*self.number_of_variables
else:
raise NotImplementedError
input_data = np.fromstring(raw_data, count=number_of_columns*self.number_of_points, dtype='f8')
input_data = input_data.reshape((self.number_of_points, number_of_columns))
input_data = input_data.transpose()
# np.savetxt('raw.txt', input_data)
if self.flags == 'complex':
raw_data = input_data
input_data = np.array(raw_data[0::2], dtype='complex128')
input_data.imag = raw_data[1::2]
for variable in self.variables.values():
variable.data = input_data[variable.index]
##############################################
[docs] def nodes(self, to_float=False, abscissa=None):
return [variable.to_waveform(abscissa, to_float=to_float)
for variable in self.variables.values()
if variable.is_voltage_node()]
##############################################
[docs] def branches(self, to_float=False, abscissa=None):
return [variable.to_waveform(abscissa, to_float=to_float)
for variable in self.variables.values()
if variable.is_branch_current()]
##############################################
[docs] def internal_parameters(self, to_float=False, abscissa=None):
return [variable.to_waveform(abscissa, to_float=to_float)
for variable in self.variables.values()
if variable.is_interval_parameter]
##############################################
[docs] def elements(self, abscissa=None):
return [variable.to_waveform(abscissa, to_float=True)
for variable in self.variables.values()]
##############################################
[docs] def to_analysis(self):
self.fix_case()
if self.plot_name == 'Operating Point':
return self._to_operating_point_analysis()
elif self.plot_name == 'Sensitivity Analysis':
return self._to_sensitivity_analysis()
elif self.plot_name == 'DC transfer characteristic':
return self._to_dc_analysis()
elif self.plot_name == 'AC Analysis':
return self._to_ac_analysis()
elif self.plot_name == 'Transient Analysis':
return self._to_transient_analysis()
else:
raise NotImplementedError("Unsupported plot name {}".format(self.plot_name))
##############################################
def _to_operating_point_analysis(self):
return OperatingPoint(
simulation=self.simulation,
nodes=self.nodes(to_float=True),
branches=self.branches(to_float=True),
)
##############################################
def _to_sensitivity_analysis(self):
# Fixme: test .SENS I (VTEST)
# Fixme: separate v(vinput), analysis.R2.m
return SensitivityAnalysis(
simulation=self.simulation,
elements=self.elements(),
)
##############################################
def _to_dc_analysis(self, sweep_variable):
sweep = sweep_variable.to_waveform()
return DcAnalysis(
simulation=self.simulation,
sweep=sweep,
nodes=self.nodes(),
branches=self.branches(),
internal_parameters=self.internal_parameters(),
)
##############################################
def _to_ac_analysis(self):
frequency = self.variables['frequency'].to_waveform(to_real=True)
return AcAnalysis(
simulation=self.simulation,
frequency=frequency,
nodes=self.nodes(),
branches=self.branches(),
internal_parameters=self.internal_parameters(),
)
##############################################
def _to_transient_analysis(self):
time = self.variables['time'].to_waveform(to_real=True)
return TransientAnalysis(
simulation=self.simulation,
time=time,
nodes=self.nodes(abscissa=time),
branches=self.branches(abscissa=time),
internal_parameters=self.internal_parameters(),
)