####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2014 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/>.
#
####################################################################################################
####################################################################################################
#
# Note:
# PR #136 has non understood changes
# https://github.com/FabriceSalvaire/PySpice/pull/136/files
#
####################################################################################################
####################################################################################################
"""This module implements classes to handle analysis output.
"""
# https://numpy.org/doc/stable/user/basics.subclassing.html#basics-subclassing
####################################################################################################
import logging
import os
# import numpy as np
####################################################################################################
_module_logger = logging.getLogger(__name__)
####################################################################################################
from PySpice.Unit.Unit import UnitValues
####################################################################################################
####################################################################################################
[docs]class Analysis:
"""Base class for the simulation output.
Depending of the simulation type, the simulator will return waveforms as a function of
* time
* frequency
* sweep
* ...
and corresponding to
* a node's voltage
* a source's current
* ...
The name of a waveform is
* node's voltage: node's name
* source's current: source'name
* ...
If the waveform name is a valid Python identifier, then you can get the corresponding waveform using::
analysis.waveforme_name
else you have to use this fallback::
analysis['waveforme_name']
Examples of usages::
# Operating point analysis
for node in analysis.nodes.values():
print('Node {}: {:5.2f} V'.format(str(node), float(node)))
for node in analysis.branches.values():
print('Node {}: {:5.2f} A'.format(str(node), float(node)))
# DC sensitivity analysis
for element in analysis.elements.values():
print(element, float(element))
# Plot the voltage of the "out" node
plt.plot(analysis.out.abscissa, analysis.out)
Public Attributes:
:attr:`nodes`
Dictionary for node voltages indexed by node names
:attr:`branches`
Dictionary for branch currents indexed by source names
:attr:`elements`
Dictionary for elements ...
"""
##############################################
def __init__(self, simulation, nodes=(), branches=(), elements=(), internal_parameters=()):
# Fixme: branches are elements in fact, and elements is not yet supported ...
self._simulation = simulation
# Fixme: to func?
self._nodes = {waveform.name:waveform for waveform in nodes}
self._branches = {waveform.name:waveform for waveform in branches}
self._elements = {waveform.name:waveform for waveform in elements}
self._internal_parameters = {waveform.name:waveform for waveform in internal_parameters}
##############################################
def __setstate__(self, state):
self.__dict__.update(state)
##############################################
@property
def simulation(self):
"""Return the simulation instance"""
return self._simulation
@property
def nodes(self):
return self._nodes
@property
def branches(self):
return self._branches
@property
def elements(self):
return self._elements
@property
def internal_parameters(self):
return self._internal_parameters
##############################################
def _get_item(self, name):
# Fixme: cache dict ???
if name in self._nodes:
return self._nodes[name]
elif name in self._branches:
return self._branches[name]
elif name in self._elements:
return self._elements[name]
elif name in self._internal_parameters:
return self._internal_parameters[name]
else:
raise IndexError(name)
##############################################
def __getitem__(self, name):
# handle analysis['foo']
try:
return self._get_item(name)
except IndexError:
return self._get_item(name.lower())
##############################################
@staticmethod
def _format_dict(d):
return os.linesep.join([' '*2 + str(x) for x in d])
##############################################
def __getattr__(self, name):
# handle analysis.foo
try:
return self.__getitem__(name)
except IndexError:
raise AttributeError(
name + os.linesep +
'Nodes :' + os.linesep + self._format_dict(self._nodes) + os.linesep +
'Branches :' + os.linesep + self._format_dict(self._branches) + os.linesep +
'Elements :' + os.linesep + self._format_dict(self._elements) + os.linesep +
'Internal Parameters :' + os.linesep + self._format_dict(self._internal_parameters)
)
####################################################################################################
[docs]class OperatingPoint(Analysis):
"""This class implements an operating point analysis."""
pass
####################################################################################################
[docs]class SensitivityAnalysis(Analysis):
"""This class implements an sensitivity analysis."""
##############################################
def __init__(self, simulation, elements, internal_parameters):
super().__init__(simulation=simulation, elements=elements,
internal_parameters=internal_parameters)
####################################################################################################
[docs]class DcAnalysis(Analysis):
"""This class implements a DC analysis.
When the DC analysis is performed with multiple sources, sweep is the last source.
The loop scheme is::
for v1 in vsource1:
for v2 in vsource2:
...
"""
##############################################
def __init__(self, simulation, sweep, nodes, branches, internal_parameters):
super().__init__(simulation=simulation, nodes=nodes, branches=branches,
internal_parameters=internal_parameters)
self._sweep = sweep
##############################################
@property
def sweep(self):
"""Return an Numpy array for the sweep abscissa"""
return self._sweep
####################################################################################################
[docs]class AcAnalysis(Analysis):
"""This class implements an AC analysis."""
##############################################
def __init__(self, simulation, frequency, nodes, branches, internal_parameters):
super().__init__(simulation=simulation, nodes=nodes, branches=branches,
internal_parameters=internal_parameters)
self._frequency = frequency
##############################################
@property
def frequency(self):
"""Return an Numpy array for the frequency abscissa"""
return self._frequency
####################################################################################################
[docs]class TransientAnalysis(Analysis):
"""This class implements a transient analysis."""
##############################################
def __init__(self, simulation, time, nodes, branches, internal_parameters):
super().__init__(simulation=simulation, nodes=nodes, branches=branches,
internal_parameters=internal_parameters)
self._time = time
##############################################
@property
def time(self):
"""Return an Numpy array for the time abscissa"""
return self._time
####################################################################################################
[docs]class PoleZeroAnalysis(Analysis):
"""This class implements a Pole-Zero analysis."""
##############################################
def __init__(self, simulation, nodes, branches, internal_parameters):
super().__init__(simulation=simulation, nodes=nodes, branches=branches,
internal_parameters=internal_parameters)
####################################################################################################
[docs]class NoiseAnalysis(Analysis):
"""This class implements Noise analysis."""
##############################################
def __init__(self, simulation, nodes, branches, internal_parameters):
super().__init__(simulation=simulation, nodes=nodes, branches=branches,
internal_parameters=internal_parameters)
####################################################################################################
[docs]class DistortionAnalysis(Analysis):
"""This class implements Distortion analysis."""
##############################################
def __init__(self, simulation, frequency, nodes, branches, internal_parameters):
super().__init__(simulation=simulation, nodes=nodes, branches=branches,
internal_parameters=internal_parameters)
self._frequency = frequency
##############################################
@property
def frequency(self):
"""Return an Numpy array for the frequency abscissa"""
return self._frequency
####################################################################################################
[docs]class TransferFunctionAnalysis(Analysis):
"""This class implements Transfer Function (TF) analysis."""
##############################################
def __init__(self, simulation, nodes, branches, internal_parameters):
super().__init__(simulation=simulation, nodes=nodes, branches=branches,
internal_parameters=internal_parameters)