Each pair of values (Ti , Vi) specifies that the value of the source is Vi (in Volts or Amps) at
time = Ti . The value of the source at intermediate values of time is determined by using linear
interpolation on the input values. The parameter r determines a repeat time point. If r is not
given, the whole sequence of values (Ti , Vi ) is issued once, then the output stays at its
final value. If r = 0, the whole sequence from time = 0 to time = Tn is repeated forever. If r =
10ns, the sequence between 10ns and 50ns is repeated forever. the r value has to be one of the
time points T1 to Tn of the PWL sequence. If td is given, the whole PWL sequence is delayed by a
delay time time = td. The current source still needs to be patched, td and r are not yet
available.
"""
##############################################
def __init__(self, name, node_plus, node_minus,
values,
repeate_time=0, delay_time=.0,
):
# Fixme: default
super().__init__(name, node_plus, node_minus)
self.values = values
self.repeate_time = repeate_time
self.delay_time = delay_time
##############################################
def format_spice_parameters(self):
# Fixme: to func?
return ('PWL(' +
join_list(self.values) +
join_dict({'r':self.repeate_time, 'td':self.delay_time}) + # OrderedDict(
')')
####################################################################################################
class SingleFrequencyFM(VoltageSource):
r"""This class implements a Single-Frequency FM waveform.
Spice Syntax::
SFFM (VO VA FC MDI FS )
+------+-------------------+---------------+-------+
| Name + Parameter + Default Value + Units |
+------+-------------------+---------------+-------+
| Vo + offset + + V, A |
+------+-------------------+---------------+-------+
| Va + amplitude + + V, A |
+------+-------------------+---------------+-------+
| Fc + carrier frequency + 1 / Tstop + Hz |
+------+-------------------+---------------+-------+
| Mdi + modulation index + + |
+------+-------------------+---------------+-------+
| Fs + signal frequency + 1 / Tstop + Hz |
+------+-------------------+---------------+-------+
The shape of the waveform is described by the following equation:
.. math::
V(t) = V_o + V_a \sin (2\pi F_c\, t + M_{di} \sin (2\pi F_s\,t))
"""
##############################################
def __init__(self, name, node_plus, node_minus,
offset, amplitude, carrier_frequency, modulation_index, signal_frequency):
super().__init__(name, node_plus, node_minus)
self.offset = offset
self.amplitude = amplitude
self.carrier_frequency = Frequency(carrier_frequency)
self.modulation_index = modulation_index
self.signal_frequency = Frequency(signal_frequency)
##############################################
def format_spice_parameters(self):
# Fixme: to func?
return ('SFFM(' +
join_list((self.offset, self.amplitude, self.carrier_frequency,
self.modulation_index, self.signal_frequency)) +
')')
####################################################################################################
class AmplitudeModulated(VoltageSource):
r"""This class implements a Amplitude Modulated source.
+------+----------------------+---------------+-------+
| Name + Parameter + Default Value + Units |
+------+----------------------+---------------+-------+
| Vo + offset + + V, A |
+------+----------------------+---------------+-------+
| Va + amplitude + + V, A |
+------+----------------------+---------------+-------+
| Mf + modulating frequency + + Hz |
+------+----------------------+---------------+-------+
| Fc + carrier frequency + 1 / Tstop + Hz |
+------+----------------------+---------------+-------+
| Td + signal delay + + s |
+------+----------------------+---------------+-------+
Spice Syntax::
AM(VA VO MF FC TD)
The shape of the waveform is described by the following equation:
.. math::
V(t) = V_a (V_o + \sin (2\pi M_f\,t)) \sin (2\pi F_c\,t)
"""
##############################################
def __init__(self, name, node_plus, node_minus,
offset, amplitude, modulating_frequency, carrier_frequency, signal_delay):
# Fixme: default
super().__init__(name, node_plus, node_minus)
self.offset = offset
self.amplitude = amplitude
self.carrier_frequency = Frequency(carrier_frequency)
self.modulating_frequency = Frequency(modulating_frequency)
self.signal_delay = signal_delay
##############################################
def format_spice_parameters(self):
# Fixme: to func?
return ('AM(' +
join_list((self.offset, self.amplitude, self.carrier_frequency,
self.modulating_frequency, self.signal_delay)) +
')')
####################################################################################################
class RandomVoltage(VoltageSource):
r"""This class implements a Random Voltage source.
The TRRANDOM option yields statistically distributed voltage values, derived from the ngspice
random number generator. These values may be used in the transient simulation directly within a
circuit, e.g. for generating a specific noise voltage, but especially they may be used in the
control of behavioral sources (B, E, G sources, voltage controllable A sources, capacitors,
inductors, or resistors) to simulate the circuit dependence on statistically varying device
parameters. A Monte-Carlo simulation may thus be handled in a single simulation run.
Spice Syntax::
TRRANDOM( TYPE TS
> >)
TYPE determines the random variates generated: 1 is uniformly distributed, 2 Gaussian, 3
exponential, 4 Poisson. TS is the duration of an individual voltage value. TD is a time delay
with 0 V output before the random voltage values start up. PARAM1 and PARAM2 depend on the type
selected.
+-------------+---------------+---------+-------------+---------+
| Type + Parameter 1 + Default + Parameter 2 + Default |
+-------------+---------------+---------+-------------+---------+
| uniform + range + 1 + offset + 0 |
+-------------+---------------+---------+-------------+---------+
| gaussian + standard dev. + 1 + mean + 0 |
+-------------+---------------+---------+-------------+---------+
| exponential + mean + 1 + offset + 0 |
+-------------+---------------+---------+-------------+---------+
| poisson + lambda + 1 + offset + 0 |
+-------------+---------------+---------+-------------+---------+
"""
##############################################
def __init__(self, name, node_plus, node_minus,
random_type, duration=0, time_delay=0, parameter1=1, parameter2=0):
# Fixme: random_type and parameters
super().__init__(name, node_plus, node_minus)
self.random_type = random_type
self.duration = duration
self.time_delay = time_delay
self.parameter1 = parameter1
self.parameter2 = parameter2
##############################################
def format_spice_parameters(self):
if self.random_type == 'uniform':
random_type = 1
elif self.random_type == 'exponential':
random_type = 2
elif self.random_type == 'gaussian':
random_type = 3
elif self.random_type == 'poisson':
random_type = 4
else:
raise ValueError("Wrong random type {}".format(self.random_type))
# Fixme: to func?
return ('TRRANDOM(' +
join_list((random_type, self.duration, self.time_delay,
self.parameter1, self.parameter2)) +
')')
####################################################################################################
#
# End
#
####################################################################################################
PySpice-v0.2.4/PySpice/Spice/Library.py 0000664 0000000 0000000 00000006516 12625125254 0017670 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# 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 .
#
####################################################################################################
####################################################################################################
from ..Tools.File import Directory
from .Parser import SpiceParser
####################################################################################################
class SpiceLibrary:
""" This class implements a Spice sub-circuits and models library.
A library is a directory which is recursively scanned for '.lib' file and parsed for sub-circuit
and models definitions.
Example of usage::
spice_library = SpiceLibrary('/some/path/')
If the directory hierarchy contains a file that define a 1N4148 sub-circuit then we can retrieve
the file path using::
spice_library['1N4148']
"""
##############################################
def __init__(self, root_path):
self._directory = Directory(root_path).expand_vars_and_user()
self._subcircuits = {}
self._models = {}
for path in self._directory.iter_file():
if path.extension == '.lib':
spice_parser = SpiceParser(path)
if spice_parser.is_only_subcircuit():
for subcircuit in spice_parser.subcircuits:
self._subcircuits[subcircuit.name] = path
elif spice_parser.is_only_model():
for model in spice_parser.models:
self._models[model.name] = path
##############################################
def __getitem__(self, name):
if name in self._subcircuits:
return self._subcircuits[name]
elif name in self._models:
return self._models[name]
else:
raise KeyError(name)
##############################################
@property
def subcircuits(self):
""" Dictionary of sub-circuits """
return self._subcircuits
@property
def models(self):
""" Dictionary of models """
return self._models
# ##############################################
# def iter_on_subcircuits(self):
# return self._subcircuits.itervalues()
# ##############################################
# def iter_on_models(self):
# return self._models.itervalues()
####################################################################################################
#
# End
#
####################################################################################################
PySpice-v0.2.4/PySpice/Spice/Netlist.py 0000664 0000000 0000000 00000062346 12625125254 0017711 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# 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 .
#
####################################################################################################
"""This modules implements circuit and subcircuit.
The definition of a netlist follows the same conventions as SPICE. For example this SPICE netlist
is translated to Python like this:
.. code-block:: spice
.title Voltage Divider
Vinput in 0 10V
R1 in out 9k
R2 out 0 1k
.end
.. code-block:: py
circuit = Circuit('Voltage Divider')
circuit.V('input', 'in', circuit.gnd, 10)
circuit.R(1, 'in', 'out', kilo(9))
circuit.R(2, 'out', circuit.gnd, kilo(1))
or as a class definition:
.. code-block:: py
class VoltageDivider(Circuit):
def __init__(self, **kwargs):
super().__init__(title='Voltage Divider', **kwargs)
self.V('input', 'in', self.gnd, '10V')
self.R(1, 'in', 'out', kilo(9))
self.R(2, 'out', self.gnd, kilo(1))
The circuit attribute :attr:`gnd` represents the ground of the circuit or subcircuit, usually set to
0.
We can get an element or a model using its name using these two possibilities::
circuit['R1'] # dictionnary style
circuit.R1 # attribute style
The dictionnary style always works, but the attribute only works if it complies with the Python
syntax, i.e. the element or model name is a valide attribute name (identifier), i.e. starting by a
letter and not a keyword like 'in', cf. `Python Language Reference
`_.
We can update an element parameter like this::
circuit.R1.resistance = kilo(1)
To simulate the circuit, we must create a simulator instance using the :meth:`Circuit.simulator`::
simulator = circuit.simulator()
"""
####################################################################################################
#
# Graph:
# dipole
# n-pole: transistor (be, bc) ?
#
# circuit -> element -> node
# circuit.Q1.b
# Element -> ElementQ
# use prefix?
#
####################################################################################################
####################################################################################################
####################################################################################################
from collections import OrderedDict
import keyword
import logging
import os
# import networkx
####################################################################################################
from ..Tools.StringTools import join_lines, join_list, join_dict
from .ElementParameter import (ParameterDescriptor,
PositionalElementParameter,
FlagParameter, KeyValueParameter)
from .Simulation import SubprocessCircuitSimulator, NgSpiceSharedCircuitSimulator
####################################################################################################
_module_logger = logging.getLogger(__name__)
####################################################################################################
class DeviceModel:
""" This class implements a device model.
Ngspice model types:
+------+-------------------------------+
| Code + Model Type |
+------+-------------------------------+
| R + Semiconductor resistor model |
+------+-------------------------------+
| C + Semiconductor capacitor model |
+------+-------------------------------+
| L + Inductor model |
+------+-------------------------------+
| SW + Voltage controlled switch |
+------+-------------------------------+
| CSW + Current controlled switch |
+------+-------------------------------+
| URC + Uniform distributed RC model |
+------+-------------------------------+
| LTRA + Lossy transmission line model |
+------+-------------------------------+
| D + Diode model |
+------+-------------------------------+
| NPN + NPN BJT model |
+------+-------------------------------+
| PNP + PNP BJT model |
+------+-------------------------------+
| NJF + N-channel JFET model |
+------+-------------------------------+
| PJF + P-channel JFET model |
+------+-------------------------------+
| NMOS + N-channel MOSFET model |
+------+-------------------------------+
| PMOS + P-channel MOSFET model |
+------+-------------------------------+
| NMF + N-channel MESFET model |
+------+-------------------------------+
| PMF + P-channel MESFET model |
+------+-------------------------------+
"""
##############################################
def __init__(self, name, modele_type, **parameters):
self._name = str(name)
self._model_type = str(modele_type)
self._parameters = dict(**parameters)
##############################################
@property
def name(self):
return self._name
##############################################
@property
def model_type(self):
return self._model_type
##############################################
def __repr__(self):
return str(self.__class__) + ' ' + self.name
##############################################
def __str__(self):
return ".model {} {} ({})".format(self._name, self._model_type, join_dict(self._parameters))
####################################################################################################
class Pin:
"""This class implements a pin of an element. It stores a reference to the element, the name of the
pin and the node.
"""
_logger = _module_logger.getChild('Pin')
##############################################
def __init__(self, element, name, node):
if keyword.iskeyword(node):
self._logger.warning("Node {} is a Python keyword".format(node))
self._element = element
self._name = name
self._node = node # Fixme: name, not a Node instance, cf. Netlist.nodes
##############################################
@property
def element(self):
return self._element
##############################################
@property
def name(self):
return self._name
##############################################
@property
def node(self):
return self._node
##############################################
def __repr__(self):
return "Pin {} of {} on node {}".format(self._name, self._element.name, self._node)
##############################################
def add_current_probe(self, circuit):
""" Add a current probe between the node and the pin.
The ammeter is named *ElementName_PinName*.
"""
# Fixme: require a reference to circuit
# Fixme: add it to a list
node = self._node
self._node = '_'.join((self._element.name, self._name))
circuit.V(self._node, node, self._node, '0')
####################################################################################################
class ElementParameterMetaClass(type):
""" Metaclass to implements the element parameter machinery. """
##############################################
def __new__(cls, name, bases, attributes):
positional_parameters = {}
parameters = {}
for attribute_name, obj in attributes.items():
if isinstance(obj, ParameterDescriptor):
obj.attribute_name = attribute_name
if isinstance(obj, PositionalElementParameter):
d = positional_parameters
elif isinstance(obj, (FlagParameter, KeyValueParameter)):
d = parameters
d[attribute_name] = obj
attributes['positional_parameters'] = OrderedDict(sorted(list(positional_parameters.items()),
key=lambda t: t[1].position))
# optional parameter order is not required for SPICE, but for unit test
attributes['optional_parameters'] = OrderedDict(sorted(list(parameters.items()), key=lambda t: t[0]))
attributes['parameters_from_args'] = [parameter
for parameter in sorted(positional_parameters.values())
if not parameter.key_parameter]
# Implement alias for parameters
attributes['spice_to_parameters'] = {parameter.spice_name:parameter
for parameter in attributes['optional_parameters'].values()}
for parameter in attributes['spice_to_parameters'].values():
if (parameter.spice_name in attributes
and parameter.spice_name != parameter.attribute_name):
_module_logger.error('Spice parameter "{}" clash with attributes'.format(parameter.spice_name))
return super().__new__(cls, name, bases, attributes)
####################################################################################################
class Element(metaclass=ElementParameterMetaClass):
""" This class implements a base class for an element.
It use a metaclass machinery for the declaration of the parameters.
"""
#: SPICE element prefix
prefix = None
##############################################
def __init__(self, name, pins, *args, **kwargs):
self._name = str(name)
self._pins = list(pins) # Fixme: pins is not a ordered dict, cf. property
# self._parameters = list(args)
for parameter, value in zip(self.parameters_from_args, args):
setattr(self, parameter.attribute_name, value)
for key, value in kwargs.items():
if key in self.positional_parameters or self.optional_parameters:
setattr(self, key, value)
##############################################
@property
def name(self):
return self.prefix + self._name
##############################################
@property
def pins(self):
return self._pins
##############################################
@property
def nodes(self):
return [pin.node for pin in self._pins]
##############################################
def __repr__(self):
return self.__class__.__name__ + ' ' + self.name
##############################################
def __setattr__(self, name, value):
# Implement alias for parameters
if name in self.spice_to_parameters:
parameter = self.spice_to_parameters[name]
object.__setattr__(self, parameter.attribute_name, value)
else:
object.__setattr__(self, name, value)
##############################################
def __getattr__(self, name):
# Implement alias for parameters
if name in self.spice_to_parameters:
parameter = self.spice_to_parameters[name]
return object.__getattribute__(self, parameter.attribute_name)
else:
raise AttributeError(name)
##############################################
def format_node_names(self):
""" Return the formatted list of nodes. """
return join_list((self.name, join_list(self.nodes)))
##############################################
def parameter_iterator(self):
""" This iterator returns the parameter in the right order. """
for parameter_dict in self.positional_parameters, self.optional_parameters:
for parameter in parameter_dict.values():
if parameter.nonzero(self):
yield parameter
##############################################
# @property
# def parameters(self):
# return self._parameters
##############################################
def format_spice_parameters(self):
""" Return the formatted list of parameters. """
return join_list([parameter.to_str(self) for parameter in self.parameter_iterator()])
##############################################
def __str__(self):
""" Return the SPICE element definition. """
return join_list((self.format_node_names(), self.format_spice_parameters()))
####################################################################################################
class TwoPinElement(Element):
""" This class implements a base class for a two-pin element. """
##############################################
def __init__(self, name, node_plus, node_minus, *args, **kwargs):
pins = (Pin(self, 'plus', node_plus), Pin(self, 'minus', node_minus))
super().__init__(name, pins, *args, **kwargs)
##############################################
@property
def plus(self):
return self.pins[0]
##############################################
@property
def minus(self):
return self.pins[1]
####################################################################################################
class TwoPortElement(Element):
""" This class implements a base class for a two-port element.
.. warning:: As opposite to Spice, the input nodes are specified before the output nodes.
"""
##############################################
# Fixme: Why the order the inverted ?
def __init__(self, name,
input_node_plus, input_node_minus,
output_node_plus, output_node_minus,
*args, **kwargs):
pins = (Pin(self, 'output_plus', output_node_plus),
Pin(self, 'output_minus', output_node_minus),
Pin(self, 'input_plus', input_node_plus),
Pin(self, 'input_minus', input_node_minus))
super().__init__(name, pins, *args, **kwargs)
##############################################
@property
def output_plus(self):
return self.pins[0]
##############################################
@property
def output_minus(self):
return self.pins[1]
##############################################
@property
def input_plus(self):
return self.pins[2]
##############################################
@property
def input_minus(self):
return self.pins[3]
####################################################################################################
class Node:
"""This class implements a node in the circuit. It stores a reference to the elements connected to
the node."""
# Fixme: but not directly to the pins!
##############################################
def __init__(self, name):
self._name = str(name)
self._elements = set()
##############################################
def __repr__(self):
return 'Node {}'.format(self._name)
##############################################
@property
def name(self):
return self._name
@property
def elements(self):
return self._elements
##############################################
def add_element(self, element):
self._elements.add(element)
####################################################################################################
class Netlist:
""" This class implements a base class for a netlist.
.. note:: This class is completed at running time with elements.
"""
##############################################
def __init__(self):
self._ground = None
self._elements = OrderedDict() # to keep the declaration order
self._models = {}
self._dirty = True
# self._nodes = set()
self._nodes = {}
# self._graph = networkx.Graph()
##############################################
def element_iterator(self):
return iter(self._elements.values())
##############################################
def element_names(self):
return [element.name for element in self.element_iterator()]
##############################################
def model_iterator(self):
return iter(self._models.values())
##############################################
def __str__(self):
""" Return the formatted list of element and model definitions. """
netlist = join_lines(self.element_iterator()) + '\n'
if self._models:
netlist += join_lines(self.model_iterator()) + '\n'
return netlist
##############################################
def _add_element(self, element):
""" Add an element. """
if element.name not in self._elements:
self._elements[element.name] = element
self._dirty = True
else:
raise NameError("Element name {} is already defined".format(element.name))
##############################################
def model(self, name, modele_type, **parameters):
""" Add a model. """
model = DeviceModel(name, modele_type, **parameters)
if model.name not in self._models:
self._models[model.name] = model
else:
raise NameError("Model name {} is already defined".format(name))
##############################################
@property
def nodes(self):
""" Return the nodes. """
if self._dirty:
# nodes = set()
# for element in self.element_iterator():
# nodes |= set(element.nodes)
# if self._ground is not None:
# nodes -= set((self._ground,))
# self._nodes = nodes
self._nodes.clear()
for element in self.element_iterator():
for node_name in element.nodes:
if node_name not in self._nodes:
node = Node(node_name)
self._nodes[node_name] = node
else:
node = self._nodes[node_name]
node.add_element(element)
return list(self._nodes.values())
##############################################
def node_names(self):
return [node.name for node in self.nodes]
##############################################
def __getitem__(self, attribute_name):
if attribute_name in self._elements:
return self._elements[attribute_name]
elif attribute_name in self._models:
return self._models[attribute_name]
elif attribute_name in self._nodes:
return attribute_name
else:
raise IndexError(attribute_name)
##############################################
def __getattr__(self, attribute_name):
try:
return self.__getitem__(attribute_name)
except IndexError:
raise AttributeError(attribute_name)
####################################################################################################
class SubCircuit(Netlist):
""" This class implements a sub-cicuit netlist. """
##############################################
def __init__(self, name, *nodes, **kwargs):
super().__init__()
self.name = str(name)
self._external_nodes = list(nodes)
# Fixme: ok ?
self._ground = kwargs.get('ground', 0)
if 'ground' in kwargs:
del kwargs['ground']
self._parameters = kwargs
##############################################
@property
def gnd(self):
""" Local ground """
return self._ground
##############################################
@property
def parameters(self):
""" Parameters """
return self._parameters
##############################################
def check_nodes(self):
""" Check for dangling nodes in the subcircuit. """
nodes = set(self._external_nodes)
connected_nodes = set()
for element in self.element_iterator():
connected_nodes.add(nodes & element.nodes)
not_connected_nodes = nodes - connected_nodes
if not_connected_nodes:
raise NameError("SubCircuit Nodes {} are not connected".format(not_connected_nodes))
##############################################
def __str__(self):
""" Return the formatted subcircuit definition. """
nodes = join_list(self._external_nodes)
parameters = join_list(['{}={}'.format(key, value)
for key, value in self._parameters.items()])
netlist = '.subckt ' + join_list((self.name, nodes, parameters)) + '\n'
netlist += super().__str__()
netlist += '.ends\n'
return netlist
####################################################################################################
class SubCircuitFactory(SubCircuit):
# Fixme : versus SubCircuit
__name__ = None
__nodes__ = None
##############################################
def __init__(self, **kwargs):
super().__init__(self.__name__, *self.__nodes__, **kwargs)
####################################################################################################
class Circuit(Netlist):
""" This class implements a cicuit netlist.
To get the corresponding Spice netlist use::
circuit = Circuit()
...
str(circuit)
"""
# .lib
# .func
# .csparam
##############################################
def __init__(self, title,
ground=0,
global_nodes=(),
):
super().__init__()
self.title = str(title)
self._ground = ground
self._global_nodes = set(global_nodes) # .global
self._includes = set() # .include
self._parameters = {} # .param
self._subcircuits = {}
# Fixme: not implemented
# .func
# .csparam
# .if
##############################################
@property
def gnd(self):
return self._ground
##############################################
def include(self, path):
""" Include a file. """
self._includes.add(path)
##############################################
def parameter(self, name, expression):
""" Set a parameter. """
self._parameters[str(name)] = str(expression)
##############################################
def subcircuit(self, subcircuit):
""" Add a sub-circuit. """
self._subcircuits[str(subcircuit.name)] = subcircuit
##############################################
def subcircuit_iterator(self):
""" Return a sub-circuit iterator. """
return iter(self._subcircuits.values())
##############################################
def __str__(self):
""" Return the formatted desk. """
netlist = '.title {}\n'.format(self.title)
if self._includes:
# ngspice don't like // in path, thus ensure we write real paths
real_paths = [os.path.realpath(str(path)) for path in self._includes]
netlist += join_lines(real_paths, prefix='.include ') + '\n'
if self._global_nodes:
netlist += '.global ' + join_list(self._global_nodes) + '\n'
if self._parameters:
netlist += join_lines(self._parameters, prefix='.param ') + '\n'
if self._subcircuits:
netlist += join_lines(self.subcircuit_iterator())
netlist += super().__str__()
return netlist
##############################################
def str_end(self):
return str(self) + '.end\n'
##############################################
def simulator(self, *args, **kwargs):
"""Return a :obj:`PySpice.Spice.Simulation.SubprocessCircuitSimulator` or
:obj:`PySpice.Spice.Simulation.NgSpiceSharedCircuitSimulator` instance depending of the
value of the *simulator* parameter: ``subprocess`` or ``shared``, respectively. If this
parameter is not specified then a subprocess simulator is returned.
"""
simulator = kwargs.get('simulator', 'subprocess')
if 'simulator' in kwargs:
simulator = kwargs['simulator']
del kwargs['simulator']
else:
simulator = 'subprocess'
if simulator == 'subprocess':
return SubprocessCircuitSimulator(self, *args, **kwargs)
elif simulator == 'shared':
return NgSpiceSharedCircuitSimulator(self, *args, **kwargs)
else:
return ValueError('Unknown simulator type')
####################################################################################################
#
# End
#
####################################################################################################
PySpice-v0.2.4/PySpice/Spice/NgSpice/ 0000775 0000000 0000000 00000000000 12625125254 0017232 5 ustar 00root root 0000000 0000000 PySpice-v0.2.4/PySpice/Spice/NgSpice/Shared.py 0000664 0000000 0000000 00000050347 12625125254 0021023 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# 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 .
#
####################################################################################################
"""This module provides a Python interface to the Ngspice shared library described in the *ngspice
as shared library or dynamic link library* section of the Ngspice user manual.
In comparison to the subprocess interface, it provides an interaction with the simulator through
commands and callbacks and it enables the usage of external voltage and current source in the
circuit.
.. This approach corresponds to the *standard way* to make an interface to a simulator code.
.. warning:: Since we don't simulate a circuit in a fresh environment on demand, this approach is
less safe than the subprocess approach. In case of bugs within Ngspice, we can expect some side
effects like memory leaks or worse unexpected things.
This interface use the CFFI module to interface with the shared library. It is thus suited to run
within the Pypy interpreter which implements JIT optimisation for Python.
It can also be used to experiment parallel simulation as explained in the Ngspice user manual. But
it seems the Ngspice source code was designed with global variables which imply to use one copy of
the shared library by worker as explained in the manual.
.. warning:: This interface can strongly slow down the simulation if the input or output callbacks
is used. If the simulation time goes wrong for you then you need to implement the callbacks at a
lower level than Python. You can have look to Pypy, Cython or a C extension module.
"""
####################################################################################################
import logging
import os
import time
import numpy as np
####################################################################################################
from cffi import FFI
ffi = FFI()
####################################################################################################
_module_logger = logging.getLogger(__name__)
####################################################################################################
from PySpice.Probe.WaveForm import (OperatingPoint, SensitivityAnalysis,
DcAnalysis, AcAnalysis, TransientAnalysis,
WaveForm)
from PySpice.Tools.EnumFactory import EnumFactory
####################################################################################################
class Vector:
""" This class implements a vector in a simulation output.
Public Attributes:
:attr:`data`
:attr:`name`
:attr:`simulation_type`
"""
##############################################
def __init__(self, name, simulation_type, data):
self.name = str(name)
self.simulation_type = simulation_type
self.data = data
##############################################
def __repr__(self):
return 'variable: {self.name} {self.simulation_type}'.format(self=self)
##############################################
def is_voltage_node(self):
return self.simulation_type == NgSpiceShared.simulation_type.voltage
##############################################
def is_branch_current(self):
return self.simulation_type == NgSpiceShared.simulation_type.current
##############################################
@property
def simplified_name(self):
if self.is_branch_current():
# return self.name.replace('#branch', '')
return self.name[:-7]
else:
return self.name
##############################################
@property
def unit(self):
if self.simulation_type == NgSpiceShared.simulation_type.voltage:
return 'V'
elif self.simulation_type == NgSpiceShared.simulation_type.current:
return 'A'
elif self.simulation_type == NgSpiceShared.simulation_type.time:
return 's'
elif self.simulation_type == NgSpiceShared.simulation_type.frequency:
return 'Hz'
else:
return ''
##############################################
def to_waveform(self, abscissa=None, to_real=False, to_float=False):
""" Return a :obj:`PySpice.Probe.WaveForm` instance. """
data = self.data
if to_real:
data = data.real
if to_float:
data = float(data[0])
return WaveForm(self.simplified_name, self.unit, data, abscissa=abscissa)
####################################################################################################
class Plot(dict):
""" This class implements a plot in a simulation output.
Public Attributes:
:attr:`plot_name`
"""
##############################################
def __init__(self, plot_name):
super().__init__()
self.plot_name = plot_name
##############################################
def nodes(self, to_float=False, abscissa=None):
return [variable.to_waveform(abscissa, to_float=to_float)
for variable in self.values()
if variable.is_voltage_node()]
##############################################
def branches(self, to_float=False, abscissa=None):
return [variable.to_waveform(abscissa, to_float=to_float)
for variable in self.values()
if variable.is_branch_current()]
##############################################
def elements(self, abscissa=None):
return [variable.to_waveform(abscissa, to_float=True)
for variable in self.values()]
##############################################
def to_analysis(self):
if self.plot_name.startswith('op'):
return self._to_operating_point_analysis()
elif self.plot_name.startswith('sens'):
return self._to_sensitivity_analysis()
elif self.plot_name.startswith('dc'):
return self._to_dc_analysis()
elif self.plot_name.startswith('ac'):
return self._to_ac_analysis()
elif self.plot_name.startswith('tran'):
return self._to_transient_analysis()
else:
raise NotImplementedError("Unsupported plot name {}".format(self.plot_name))
##############################################
def _to_operating_point_analysis(self):
return OperatingPoint(nodes=self.nodes(to_float=True), branches=self.branches(to_float=True))
##############################################
def _to_sensitivity_analysis(self):
# Fixme: separate v(vinput), analysis.R2.m
return SensitivityAnalysis(elements=self.elements())
##############################################
def _to_dc_analysis(self):
if 'v(v-sweep)' in self:
sweep_variable = self['v(v-sweep)']
elif 'v(i-sweep)' in self:
sweep_variable = self['v(i-sweep)']
else:
raise NotImplementedError
sweep = sweep_variable.to_waveform()
return DcAnalysis(sweep, nodes=self.nodes(), branches=self.branches())
##############################################
def _to_ac_analysis(self):
frequency = self['frequency'].to_waveform(to_real=True)
return AcAnalysis(frequency, nodes=self.nodes(), branches=self.branches())
##############################################
def _to_transient_analysis(self):
time = self['time'].to_waveform(to_real=True)
return TransientAnalysis(time, nodes=self.nodes(abscissa=time), branches=self.branches(abscissa=time))
####################################################################################################
class NgSpiceShared:
_logger = _module_logger.getChild('NgSpiceShared')
simulation_type = EnumFactory('SimulationType', (
'no_type',
'time',
'frequency',
'voltage',
'current',
'output_noise_density',
'output_noise',
'input_noise_density',
'input_noise',
'pole',
'zero',
's_parameter',
'temperature',
'res',
'impedance',
'admittance',
'power',
'phase',
'db',
'capacitance',
'charge'))
##############################################
def __init__(self, ngspice_id=0, send_data=False):
""" Set the *send_data* flag if you want to enable the output callback.
Set the *ngspice_id* to an integer value if you want to run NgSpice in parallel.
"""
self._ngspice_id = ngspice_id
self._load_library()
self._init_ngspice(send_data)
##############################################
def _load_library(self):
api_path = os.path.join(os.path.dirname(__file__), 'api.h')
with open(api_path) as f:
ffi.cdef(f.read())
if not self._ngspice_id:
library_prefix = ''
else:
library_prefix = '{}'.format(self._ngspice_id)
library_file = 'libngspice{}.so'.format(library_prefix)
self._ngspice_shared = ffi.dlopen(library_file)
##############################################
def _init_ngspice(self, send_data):
self._send_char_c = ffi.callback('int (char *, int, void *)', self._send_char)
self._send_stat_c = ffi.callback('int (char *, int, void *)', self._send_stat)
self._exit_c = ffi.callback('int (int, bool, bool, int, void *)', self._exit)
self._send_init_data_c = ffi.callback('int (pvecinfoall, int, void *)', self._send_init_data)
if send_data:
self._send_data_c = ffi.callback('int (pvecvaluesall, int, int, void *)', self._send_data)
else:
self._send_data_c = ffi.NULL
self._get_vsrc_data_c = ffi.callback('int (double *, double, char *, int, void *)', self._get_vsrc_data)
self._get_isrc_data_c = ffi.callback('int (double *, double, char *, int, void *)', self._get_isrc_data)
self_c = ffi.new_handle(self)
rc = self._ngspice_shared.ngSpice_Init(self._send_char_c,
self._send_stat_c,
self._exit_c,
self._send_data_c,
self._send_init_data_c,
ffi.NULL, # BGThreadRunning
self_c)
if rc:
raise NameError("Ngspice_Init returned {}".format(rc))
ngspice_id_c = ffi.new('int *', self._ngspice_id)
rc = self._ngspice_shared.ngSpice_Init_Sync(self._get_vsrc_data_c,
self._get_isrc_data_c,
ffi.NULL, # GetSyncData
ngspice_id_c,
self_c)
if rc:
raise NameError("Ngspice_Init_Sync returned {}".format(rc))
##############################################
@staticmethod
def _send_char(message, ngspice_id, user_data):
self = ffi.from_handle(user_data)
return self.send_char(ffi.string(message), ngspice_id)
##############################################
@staticmethod
def _send_stat(message, ngspice_id, user_data):
self = ffi.from_handle(user_data)
return self.send_stat(ffi.string(message), ngspice_id)
##############################################
@staticmethod
def _exit(exit_status, immediate_unloding, quit_exit, ngspice_id, user_data):
self = ffi.from_handle(user_data)
self._logger.debug('ngspice_id-{} exit {} {} {} {}'.format(ngspice_id,
exit_status,
immediate_unloding,
quit_exit))
return exit_status
##############################################
@staticmethod
def _send_data(data, number_of_vectors, ngspice_id, user_data):
self = ffi.from_handle(user_data)
self._logger.debug('ngspice_id-{} send_data [{}]'.format(ngspice_id, data.vecindex))
actual_vector_values = {}
for i in range(int(number_of_vectors)):
actual_vector_value = data.vecsa[i]
vector_name = ffi.string(actual_vector_value.name)
value = complex(actual_vector_value.creal, actual_vector_value.cimag)
actual_vector_values[vector_name] = value
self._logger.debug(' Vector: {} {}'.format(vector_name, value))
return self.send_data(actual_vector_values, number_of_vectors, ngspice_id)
##############################################
@staticmethod
def _send_init_data(data, ngspice_id, user_data):
self = ffi.from_handle(user_data)
if self._logger.isEnabledFor(logging.DEBUG):
self._logger.debug('ngspice_id-{} send_init_data'.format(ngspice_id))
number_of_vectors = data.veccount
for i in range(number_of_vectors):
self._logger.debug(' Vector: ' + ffi.string(data.vecs[i].vecname))
return self.send_init_data(data, ngspice_id) # Fixme: should be a Python object
##############################################
@staticmethod
def _get_vsrc_data(voltage, time, node, ngspice_id, user_data):
self = ffi.from_handle(user_data)
return self.get_vsrc_data(voltage, time, ffi.string(node), ngspice_id)
##############################################
@staticmethod
def _get_isrc_data(current, time, node, ngspice_id, user_data):
self = ffi.from_handle(user_data)
return self.get_isrc_data(current, time, ffi.string(node), ngspice_id)
##############################################
def send_char(self, message, ngspice_id):
""" Reimplement this callback in a subclass to process logging messages from the simulator. """
self._logger.debug('ngspice-{} send_char {}'.format(ngspice_id, message))
return 0
##############################################
def send_stat(self, message, ngspice_id):
""" Reimplement this callback in a subclass to process statistic messages from the simulator. """
self._logger.debug('ngspice-{} send_stat {}'.format(ngspice_id, message))
return 0
##############################################
def send_data(self, actual_vector_values, number_of_vectors, ngspice_id):
""" Reimplement this callback in a subclass to process the vector actual values. """
return 0
##############################################
def send_init_data(self, data, ngspice_id):
""" Reimplement this callback in a subclass to process the initial data. """
return 0
##############################################
def get_vsrc_data(self, voltage, time, node, ngspice_id):
""" Reimplement this callback in a subclass to provide external voltage source. """
self._logger.debug('ngspice_id-{} get_vsrc_data @{} node {}'.format(ngspice_id, time, node))
return 0
##############################################
def get_isrc_data(self, current, time, node, ngspice_id):
""" Reimplement this callback in a subclass to provide external current source. """
self._logger.debug('ngspice_id-{} get_isrc_data @{} node {}'.format(ngspice_id, time, node))
return 0
##############################################
def load_circuit(self, circuit):
""" Load the given circuit string. """
circuit_lines = [line for line in str(circuit).split('\n') if line]
circuit_lines_keepalive = [ffi.new("char[]", line) for line in circuit_lines] + [ffi.NULL]
circuit_array = ffi.new("char *[]", circuit_lines_keepalive)
rc = self._ngspice_shared.ngSpice_Circ(circuit_array)
if rc:
raise NameError("ngSpice_Circ returned {}".format(rc))
# for line in circuit_lines:
# rc = self._ngspice_shared.ngSpice_Command('circbyline ' + line)
# if rc:
# raise NameError("ngSpice_Command circbyline returned {}".format(rc))
##############################################
def run(self):
""" Run the simulation in the background thread and wait until the simulation is done. """
rc = self._ngspice_shared.ngSpice_Command('bg_run')
if rc:
raise NameError("ngSpice_Command bg_run returned {}".format(rc))
time.sleep(.1) # required before to test if the simulation is running
while (self._ngspice_shared.ngSpice_running()):
time.sleep(.1)
self._logger.debug("Simulation is done")
##############################################
def _convert_string_array(self, array):
strings = []
i = 0
while (True):
if array[i] == ffi.NULL:
break
else:
strings.append(ffi.string(array[i]))
i += 1
return strings
##############################################
@property
def plot_names(self):
""" Return the list of plot names. """
return self._convert_string_array(self._ngspice_shared.ngSpice_AllPlots())
##############################################
def plot(self, plot_name):
""" Return the corresponding plot. """
plot = Plot(plot_name)
all_vectors_c = self._ngspice_shared.ngSpice_AllVecs(plot_name)
i = 0
while (True):
if all_vectors_c[i] == ffi.NULL:
break
else:
vector_name = ffi.string(all_vectors_c[i])
vector_info = self._ngspice_shared.ngGet_Vec_Info('.'.join((plot_name, vector_name)))
vector_type = vector_info.v_type
length = vector_info.v_length
self._logger.debug("vector[{}] {} type {} flags {} length {}".format(i,
vector_name,
vector_type,
vector_info.v_flags,
length))
# flags: VF_REAL = 1 << 0, VF_COMPLEX = 1 << 1
if vector_info.v_compdata == ffi.NULL:
# for k in xrange(length):
# print " [{}] {}".format(k, vector_info.v_realdata[k])
array = np.frombuffer(ffi.buffer(vector_info.v_realdata, length*8), dtype=np.float64)
else:
# for k in xrange(length):
# value = vector_info.v_compdata[k]
# print ffi.addressof(value, field='cx_real'), ffi.addressof(value, field='cx_imag')
# print " [{}] {} + i {}".format(k, value.cx_real, value.cx_imag)
tmp_array = np.frombuffer(ffi.buffer(vector_info.v_compdata, length*8*2), dtype=np.float64)
array = np.array(tmp_array[0::2], dtype=np.complex64)
array.imag = tmp_array[1::2]
plot[vector_name] = Vector(vector_name, self.simulation_type[vector_type], array)
i += 1
return plot
####################################################################################################
#
# End
#
####################################################################################################
PySpice-v0.2.4/PySpice/Spice/NgSpice/__init__.py 0000664 0000000 0000000 00000001627 12625125254 0021351 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# 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 .
#
####################################################################################################
PySpice-v0.2.4/PySpice/Spice/NgSpice/api.h 0000664 0000000 0000000 00000003433 12625125254 0020157 0 ustar 00root root 0000000 0000000 /* Simplified Ngspice API for CFFI parser */
typedef struct ngcomplex
{
double cx_real;
double cx_imag;
} ngcomplex_t;
typedef struct vector_info
{
char *v_name;
int v_type;
short v_flags;
double *v_realdata;
ngcomplex_t *v_compdata;
int v_length;
} vector_info, *pvector_info;
typedef struct vecvalues
{
char *name;
double creal;
double cimag;
bool is_scale;
bool is_complex;
} vecvalues, *pvecvalues;
typedef struct vecvaluesall
{
int veccount;
int vecindex;
pvecvalues *vecsa;
} vecvaluesall, *pvecvaluesall;
typedef struct vecinfo
{
int number;
char *vecname;
bool is_real;
void *pdvec;
void *pdvecscale;
} vecinfo, *pvecinfo;
typedef struct vecinfoall
{
char *name;
char *title;
char *date;
char *type;
int veccount;
pvecinfo *vecs;
} vecinfoall, *pvecinfoall;
typedef int (SendChar) (char *, int, void *);
typedef int (SendStat) (char *, int, void *);
typedef int (ControlledExit) (int, bool, bool, int, void *);
typedef int (SendData) (pvecvaluesall, int, int, void *);
typedef int (SendInitData) (pvecinfoall, int, void *);
typedef int (BGThreadRunning) (bool, int, void *);
typedef int (GetVSRCData) (double *, double, char *, int, void *);
typedef int (GetISRCData) (double *, double, char *, int, void *);
typedef int (GetSyncData) (double, double *, double, int, int, int, void *);
int ngSpice_Init (SendChar *, SendStat *, ControlledExit *, SendData *, SendInitData *, BGThreadRunning *, void *);
int ngSpice_Init_Sync (GetVSRCData *, GetISRCData *, GetSyncData *, int *, void *);
int ngSpice_Command (char *);
pvector_info ngGet_Vec_Info (char *);
int ngSpice_Circ (char **);
char *ngSpice_CurPlot (void);
char **ngSpice_AllPlots (void);
char **ngSpice_AllVecs (char *);
bool ngSpice_running (void);
bool ngSpice_SetBkpt (double);
/* End */
PySpice-v0.2.4/PySpice/Spice/Parser.py 0000664 0000000 0000000 00000024344 12625125254 0017517 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# 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 .
#
####################################################################################################
####################################################################################################
"""This module implements a partial SPICE netlist parser.
It would be difficult to implement a full parser for Ngspice since the syntax is mainly contextual.
"""
####################################################################################################
class Token:
""" This class implements a token, in fact a line in a Spice netlist. """
##############################################
def __init__(self, line):
self._line = line
##############################################
def split_line(self, keyword):
""" Split the line according to the following pattern::
keyword parameter1 parameter2 ... key1=value1 key2=value2 ...
Return the list of parameters and the dictionnary.
"""
raw_parameters = str(self._line)[len(keyword):].split()
parameters = []
dict_parameters = {}
for parameter in raw_parameters:
if '=' in parameter:
key, value = parameter.split('=')
dict_parameters[key.strip()] = value.strip()
else:
parameters.append(parameter)
return parameters, dict_parameters
##############################################
def __repr__(self):
return "{} {}".format(self.__class__.__name__, repr(self._line))
####################################################################################################
class Comment(Token):
pass
####################################################################################################
class SubCircuit(Token):
""" This class implements a sub-circuit definition. """
##############################################
def __init__(self, line):
super().__init__(line)
parameters, dict_parameters = self.split_line('.subckt')
self._name, self._nodes = parameters[0], parameters[1:]
self._tokens = []
##############################################
@property
def name(self):
""" Name of the sub-circuit. """
return self._name
##############################################
def __repr__(self):
text = "SubCircuit {} {}\n".format(self._name, self._nodes)
text += '\n'.join([' ' + repr(token) for token in self._tokens])
return text
##############################################
def __iter__(self):
""" Return an iterator on the tokens. """
return iter(self._tokens)
##############################################
def append(self, token):
""" Append a token to the token's list. """
self._tokens .append(token)
####################################################################################################
class Title(Token):
""" This class implements a title definition. """
##############################################
def __init__(self, line):
super().__init__(line)
parameters, dict_parameters = self.split_line('.title')
self._title = parameters[0]
##############################################
def __repr__(self):
return "Title {}".format(self._title)
####################################################################################################
class Element(Token):
""" This class implements an element definition. """
##############################################
def __init__(self, line):
super().__init__(line)
parameters, dict_parameters = self.split_line('R')
self._element_type = str(line)[0]
self._name, self._nodes = parameters[0], parameters[1:]
self._parameters = parameters[2:]
self._dict_parameters = dict_parameters
##############################################
@property
def name(self):
""" Name of the element """
return self._name
##############################################
def __repr__(self):
return "Element {} {} {} {}".format(self._element_type, self._name, self._nodes, self._parameters)
####################################################################################################
class Model(Token):
""" This class implements a model definition. """
##############################################
def __init__(self, line):
super().__init__(line)
parameters, dict_parameters = self.split_line('.model')
self._name, self._model_type = parameters[:2]
self._parameters = dict_parameters
##############################################
@property
def name(self):
""" Name of the model """
return self._name
##############################################
def __repr__(self):
return "Model {} {} {}".format(self._name, self._model_type, self._parameters)
####################################################################################################
class Line:
""" This class implements a line in the netlist. """
##############################################
def __init__(self, text, line_range):
self._text = str(text)
self._line_range = line_range
##############################################
def __repr__(self):
return "{} {}".format(self._line_range, self._text)
##############################################
def __str__(self):
return self._text
####################################################################################################
class SpiceParser:
""" This class parse a Spice netlist file and build a syntax tree.
Public Attributes:
:attr:`circuit`
:attr:`models`
:attr:`subcircuits`
"""
##############################################
def __init__(self, path):
with open(str(path), 'r') as f:
raw_lines = f.readlines()
lines = self._merge_lines(raw_lines)
self._tokens = self._parse(lines)
self._find_sections()
##############################################
def _merge_lines(self, raw_lines):
""" Merge broken lines and return a new list of lines. """
lines = []
current_line = ''
current_line_index = None
for line_index, line in enumerate(raw_lines):
if line.startswith('+'):
current_line += ' ' + line[1:].strip()
else:
if current_line:
lines.append(Line(current_line, slice(current_line_index, line_index)))
current_line = line.strip()
current_line_index = line_index
if current_line:
lines.append(Line(current_line, slice(current_line_index, len(raw_lines))))
return lines
##############################################
def _parse(self, lines):
""" Parse the lines and return a list of tokens. """
tokens = []
sub_circuit = None
for line in lines:
# print repr(line)
text = str(line)
lower_case_text = text.lower()
if text.startswith('*'):
tokens.append(Comment(line))
elif lower_case_text.startswith('.'):
lower_case_text = lower_case_text[1:]
if lower_case_text.startswith('subckt'):
sub_circuit = SubCircuit(line)
tokens.append(sub_circuit)
elif lower_case_text.startswith('ends'):
sub_circuit = None
elif lower_case_text.startswith('title'):
tokens.append(Title(line))
elif lower_case_text.startswith('end'):
pass
elif lower_case_text.startswith('model'):
model = Model(line)
if sub_circuit:
sub_circuit.append(model)
else:
tokens.append(model)
else:
# options param ...
pass
else:
element = Element(line)
if sub_circuit:
sub_circuit.append(element)
else:
tokens.append(element)
return tokens
##############################################
def _find_sections(self):
""" Look for model, sub-circuit and circuit definitions in the token list. """
self.circuit = None
self.subcircuits = []
self.models = []
for token in self._tokens:
if isinstance(token, Title):
if self.circuit is None:
self.circuit = token
else:
raise NameError("More than one title")
elif isinstance(token, SubCircuit):
self.subcircuits.append(token)
elif isinstance(token, Model):
self.models.append(token)
##############################################
def is_only_subcircuit(self):
return bool(not self.circuit and self.subcircuits)
##############################################
def is_only_model(self):
return bool(not self.circuit and not self.subcircuits and self.models)
####################################################################################################
#
# End
#
####################################################################################################
PySpice-v0.2.4/PySpice/Spice/RawFile.py 0000664 0000000 0000000 00000033733 12625125254 0017616 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# 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 .
#
####################################################################################################
"""
Header
.. code::
Circuit: 230V Rectifier
Doing analysis at TEMP = 25.000000 and TNOM = 25.000000
Title: 230V Rectifier
Date: Thu Jun 4 23:40:58 2015
Plotname: Transient Analysis
Flags: real
No. Variables: 6
No. Points: 0
Variables:
No. of Data Columns : 6
0 time time
1 v(in) voltage
...
5 i(vinput) current
Binary:
Operating Point
Node voltages and source branch currents:
* v(node_name)
* i(vname)
Sensitivity Analysis
* v({element})
* v({element}_{parameter})
* v(v{source})
DC
* v(v-sweep)
* v({node})
* i(v{source})
AC
Frequency, node voltages and source branch currents:
* frequency
* v({node})
* i(v{name})
Transient Analysis
Time, node voltages and source branch currents:
* time
* v({node})
* i(v{source})
"""
# * v({element}:bv_max)
# * i(e.xdz1.ev1)
####################################################################################################
import logging
import numpy as np
####################################################################################################
from ..Probe.WaveForm import (OperatingPoint, SensitivityAnalysis,
DcAnalysis, AcAnalysis, TransientAnalysis,
WaveForm)
####################################################################################################
_module_logger = logging.getLogger(__name__)
####################################################################################################
class Variable:
""" 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):
self.index = int(index)
self.name = str(name)
self.unit = str(unit) # could be guessed from name also for voltage node and branch current
self.data = None
##############################################
def __repr__(self):
return 'variable[{self.index}]: {self.name} [{self.unit}]'.format(self=self)
##############################################
def is_voltage_node(self):
return self.name.startswith('v(')
##############################################
def is_branch_current(self):
# source branch current
return self.name.startswith('i(')
##############################################
@staticmethod
def to_voltage_name(node):
return 'v({})'.format(node)
##############################################
@staticmethod
def to_branch_name(element):
return 'i({})'.format(element)
##############################################
@property
def simplified_name(self):
if self.is_voltage_node() or self.is_branch_current():
return self.name[2:-1]
else:
return self.name
##############################################
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])
##############################################
def to_waveform(self, abscissa=None, to_real=False, to_float=False):
""" Return a :obj:`PySpice.Probe.WaveForm` instance. """
data = self.data
if to_real:
data = data.real
if to_float:
data = float(data[0])
return WaveForm(self.simplified_name, self.unit, data, abscissa=abscissa)
####################################################################################################
class RawFile:
""" This class parse the stdout of ngspice and the raw data output.
Public Attributes:
:attr:`circuit`
same as title
:attr:`data`
:attr:`date`
:attr:`flags`
'real' or 'complex'
:attr:`number_of_points`
:attr:`number_of_variables`
:attr:`plot_name`
AC Analysis, Operating Point, Sensitivity Analysis, DC transfer characteristic
:attr:`temperature`
:attr:`title`
:attr:`variables`
:attr:`warnings`
"""
_logger = _module_logger.getChild('RawFile')
##############################################
def __init__(self, stdout, number_of_points):
self.number_of_points = number_of_points
raw_data = self._read_header(stdout)
self._read_variable_data(raw_data)
# self._to_analysis()
##############################################
def _read_header(self, stdout):
""" Parse the header """
binary_line = b'Binary:\n'
binary_location = stdout.find(binary_line)
if binary_location < 0:
raise NameError('Cannot locate binary data')
raw_data_start = binary_location + len(binary_line)
# self._logger.debug('\n' + stdout[:raw_data_start].decode('utf-8'))
header_lines = stdout[:binary_location].splitlines()
raw_data = stdout[raw_data_start:]
header_line_iterator = iter(header_lines)
self.circuit = self._read_header_field_line(header_line_iterator, 'Circuit')
self.temperature = self._read_header_line(header_line_iterator, 'Doing analysis at TEMP')
self.warnings = [self._read_header_field_line(header_line_iterator, 'Warning')
for i in range(stdout.count(b'Warning'))]
for warning in self.warnings:
self._logger.warn(warning)
self.title = self._read_header_field_line(header_line_iterator, 'Title')
self.date = self._read_header_field_line(header_line_iterator, 'Date')
self.plot_name = self._read_header_field_line(header_line_iterator, 'Plotname')
self.flags = self._read_header_field_line(header_line_iterator, 'Flags')
self.number_of_variables = int(self._read_header_field_line(header_line_iterator, 'No. Variables'))
self._read_header_field_line(header_line_iterator, 'No. Points')
self._read_header_field_line(header_line_iterator, 'Variables', has_value=False)
self._read_header_field_line(header_line_iterator, 'No. of Data Columns ')
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]
self.variables[name] = Variable(index, name, unit)
# self._read_header_field_line(header_line_iterator, 'Binary', has_value=False)
return raw_data
##############################################
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 not line.startswith(head_line):
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_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='complex64')
input_data.imag = raw_data[1::2]
for variable in self.variables.values():
variable.data = input_data[variable.index]
##############################################
def fix_case(self, circuit):
""" Ngspice return lower case names. This method fixes the case of the variable names. """
element_translation = {element.lower():element for element in circuit.element_names()}
node_translation = {node.lower():node for node in circuit.node_names()}
for variable in self.variables.values():
variable.fix_case(element_translation, node_translation)
##############################################
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()]
##############################################
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()]
##############################################
def elements(self, abscissa=None):
return [variable.to_waveform(abscissa, to_float=True)
for variable in self.variables.values()]
##############################################
def to_analysis(self, circuit):
self.fix_case(circuit)
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(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(elements=self.elements())
##############################################
def _to_dc_analysis(self):
if 'v(v-sweep)' in self.variables:
sweep_variable = self.variables['v(v-sweep)']
elif 'v(i-sweep)' in self.variables:
sweep_variable = self.variables['v(i-sweep)']
else:
#
raise NotImplementedError
sweep = sweep_variable.to_waveform()
return DcAnalysis(sweep, nodes=self.nodes(), branches=self.branches())
##############################################
def _to_ac_analysis(self):
frequency = self.variables['frequency'].to_waveform(to_real=True)
return AcAnalysis(frequency, nodes=self.nodes(), branches=self.branches())
##############################################
def _to_transient_analysis(self):
time = self.variables['time'].to_waveform(to_real=True)
return TransientAnalysis(time, nodes=self.nodes(abscissa=time), branches=self.branches(abscissa=time))
####################################################################################################
#
# End
#
####################################################################################################
PySpice-v0.2.4/PySpice/Spice/Server.py 0000664 0000000 0000000 00000013210 12625125254 0017517 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# 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 .
#
####################################################################################################
"""This module provides an interface to run ngspice in server mode and get back the simulation
output.
When ngspice runs in server mode, it writes on the standard output an header and then the simulation
output in binary format. At the end of the simulation, it writes on the standard error a line of
the form:
.. code::
@@@ \d+ \d+
where the second number is the number of points of the simulation. Due to the iterative and
adaptive nature of a transient simulation, the number of points is only known at the end.
Any line starting with "Error" in the standard output indicates an error in the simulation process.
The line "run simulation(s) aborted" in the standard error indicates the simulation aborted.
Any line starting with *Warning* in the standard error indicates non critical error in the
simulation process.
"""
####################################################################################################
import logging
import re
import subprocess
####################################################################################################
from .RawFile import RawFile
####################################################################################################
_module_logger = logging.getLogger(__name__)
####################################################################################################
class SpiceServer:
"""This class wraps the execution of ngspice in server mode and convert the output to a Python data
structure.
Example of usage::
spice_server = SpiceServer(spice_command='/path/to/ngspice')
raw_file = spice_server(spice_input)
It returns a :obj:`PySpice.Spice.RawFile` instance.
"""
_logger = _module_logger.getChild('SpiceServer')
##############################################
def __init__(self, spice_command='ngspice'):
self._spice_command = spice_command
##############################################
def _decode_number_of_points(self, line):
"""Decode the number of points in the given line."""
match = re.match(r'@@@ (\d+) (\d+)', line)
if match is not None:
return int(match.group(2))
else:
raise NameError("Cannot decode the number of points")
##############################################
def _parse_stdout(self, stdout):
"""Parse stdout for errors."""
# self._logger.debug('\n' + stdout)
error_found = False
lines = stdout.splitlines()
for line_index, line in enumerate(lines):
if line.startswith(b'Error '):
error_found = True
self._logger.error('\n' + line + '\n' + lines[line_index+1].decode('utf-8'))
if error_found:
raise NameError("Errors was found by Spice")
##############################################
def _parse_stderr(self, stderr):
"""Parse stderr for warnings and return the number of points."""
self._logger.debug('\n' + stderr)
stderr_lines = stderr.splitlines()
number_of_points = None
for line in stderr_lines:
if line.startswith('Warning:'):
self._logger.warning(line[len('Warning :'):])
elif line == 'run simulation(s) aborted':
raise NameError("Simulation aborted\n" + stderr)
elif line.startswith('@@@'):
number_of_points = self._decode_number_of_points(line)
return number_of_points
##############################################
def __call__(self, spice_input):
"""Run SPICE in server mode as a subprocess for the given input and return a
:obj:`PySpice.RawFile.RawFile` instance.
"""
self._logger.info("Start the spice subprocess")
process = subprocess.Popen((self._spice_command, '-s'),
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
input_ = str(spice_input).encode('utf-8')
stdout, stderr = process.communicate(input_)
# stdout = stdout.decode('utf-8')
stderr = stderr.decode('utf-8')
self._parse_stdout(stdout)
number_of_points = self._parse_stderr(stderr)
if number_of_points is None:
raise NameError("The number of points was not found in the standard error buffer,"
" ngspice returned:\n" +
stderr)
return RawFile(stdout, number_of_points)
####################################################################################################
#
# End
#
####################################################################################################
PySpice-v0.2.4/PySpice/Spice/Simulation.py 0000664 0000000 0000000 00000034710 12625125254 0020405 0 ustar 00root root 0000000 0000000 ###################################################################################################
#
# 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 .
#
####################################################################################################
####################################################################################################
import logging
####################################################################################################
from ..Tools.StringTools import join_list, join_dict
from .NgSpice.Shared import NgSpiceShared
from .Server import SpiceServer
####################################################################################################
_module_logger = logging.getLogger(__name__)
####################################################################################################
class CircuitSimulation:
"""Define and generate the spice instruction to perform a circuit simulation.
.. warning:: In some cases NgSpice can perform several analyses one after the other. This case
is partially supported.
"""
_logger = _module_logger.getChild('CircuitSimulation')
##############################################
def __init__(self, circuit,
temperature=27,
nominal_temperature=27,
pipe=True,
):
self._circuit = circuit
self._options = {} # .options
self._initial_condition = {} # .ic
self._saved_nodes = ()
self._analysis_parameters = {}
self.temperature = temperature
self.nominal_temperature = nominal_temperature
if pipe:
self.options('NOINIT')
self.options(filetype='binary')
##############################################
@property
def circuit(self):
return self._circuit
##############################################
def options(self, *args, **kwargs):
for item in args:
self._options[str(item)] = None
for key, value in kwargs.items():
self._options[str(key)] = str(value)
##############################################
@property
def temperature(self):
return self._options['TEMP']
@temperature.setter
def temperature(self, value):
self._options['TEMP'] = value
##############################################
@property
def nominal_temperature(self):
return self._options['TNOM']
@nominal_temperature.setter
def nominal_temperature(self, value):
self._options['TNOM'] = value
##############################################
def initial_condition(self, **kwargs):
""" Set initial condition for voltage nodes.
Usage: initial_condition(node_name1=value, ...)
"""
for key, value in kwargs.items():
self._initial_condition['V({})'.format(str(key))] = str(value)
# Fixme: .nodeset
##############################################
def save(self, *args):
# Fixme: pass Node for voltage node, Element for source branch current, ...
"""Set the list of saved vectors.
If no *.save* line is given, then the default set of vectors is saved (node voltages and
voltage source branch currents). If *.save* lines are given, only those vectors specified
are saved.
Node voltages may be saved by giving the node_name or *v(node_name)*. Currents through an
independent voltage source (including inductor) are given by *i(source_name)* or
*source_name#branch*. Internal device data are accepted as *@dev[param]*.
If you want to save internal data in addition to the default vector set, add the parameter
*all* to the additional vectors to be saved.
"""
self._saved_nodes = list(args)
##############################################
@property
def save_currents(self):
""" Save all currents. """
return self._options.get('SAVECURRENTS', False)
@save_currents.setter
def save_currents(self, value):
if value:
self._options['SAVECURRENTS'] = True
else:
del self._options['SAVECURRENTS']
##############################################
def reset_analysis(self):
self._analysis_parameters.clear()
##############################################
def operating_point(self):
"""Compute the operating point of the circuit with capacitors open and inductors shorted."""
self._analysis_parameters['op'] = ''
##############################################
def dc_sensitivity(self, output_variable):
"""Compute the sensitivity of the DC operating point of a node voltage or voltage-source branch
current to all non-zero device parameters.
General form:
.. code::
.sens outvar
Examples:
.. code::
.SENS V(1, OUT)
.SENS I(VTEST)
"""
self._analysis_parameters['sens'] = (output_variable,)
##############################################
def ac_sensitivity(self, output_variable,
start_frequency, stop_frequency, number_of_points, variation):
"""Compute the sensitivity of the AC values of a node voltage or voltage-source branch
current to all non-zero device parameters.
General form:
.. code::
.sens outvar ac dec nd fstart fstop
.sens outvar ac oct no fstart fstop
.sens outvar ac lin np fstart fstop
Examples:
.. code::
.SENS V(OUT) AC DEC 10 100 100 k
"""
if variation not in ('dec', 'oct', 'lin'):
raise ValueError("Incorrect variation type")
self._analysis_parameters['sens'] = (output_variable,
variation, number_of_points, start_frequency, stop_frequency)
##############################################
def dc(self, **kwargs):
"""Compute the DC transfer fonction of the circuit with capacitors open and inductors shorted.
General form:
.. code::
.dc srcnam vstart vstop vincr [ src2 start2 stop2 incr2 ]
*srcnam* is the name of an independent voltage or current source, a resistor or the circuit
temperature. *vstart*, *vstop*, and *vincr* are the starting, final, and incrementing values
respectively.
A second source (*src2*) may optionally be specified with associated sweep parameters. In
this case, the first source is swept over its range for each value of the second source.
Examples:
.. code::
.dc VIN 0 .2 5 5.0 0.25
.dc VDS 0 10 .5 VGS 0 5 1
.dc VCE 0 10 .2 5 IB 0 10U 1U
.dc RLoad 1k 2k 100
.dc TEMP -15 75 5
"""
parameters = []
for variable, value_slice in kwargs.items():
variable_lower = variable.lower()
if variable_lower[0] in ('v', 'i', 'r') or variable_lower == 'temp':
parameters += [variable, value_slice.start, value_slice.stop, value_slice.step]
else:
raise NameError('Sweep variable must be a voltage/current source, '
'a resistor or the circuit temperature')
self._analysis_parameters['dc'] = parameters
##############################################
def ac(self, start_frequency, stop_frequency, number_of_points, variation):
# fixme: concise keyword ?
"""Perform a small-signal AC analysis of the circuit where all non-linear devices are linearized
around their actual DC operating point.
Note that in order for this analysis to be meaningful, at least one independent source must
have been specified with an AC value. Typically it does not make much sense to specify more
than one AC source. If you do, the result will be a superposition of all sources, thus
difficult to interpret.
Examples:
.. code::
.ac dec nd fstart fstop
.ac oct no fstart fstop
.ac lin np fstart fstop
The parameter *variation* must be either `dec`, `oct` or `lin`.
"""
if variation not in ('dec', 'oct', 'lin'):
raise ValueError("Incorrect variation type")
self._analysis_parameters['ac'] = (variation, number_of_points, start_frequency, stop_frequency)
##############################################
def transient(self, step_time, end_time, start_time=None, max_time=None,
use_initial_condition=False):
"""Perform a transient analysis of the circuit.
General Form:
.. code::
.tran tstep tstop >
"""
if use_initial_condition:
uic = 'uic'
else:
uic = None
self._analysis_parameters['tran'] = (step_time, end_time, start_time, max_time, uic)
##############################################
def __str__(self):
netlist = str(self._circuit)
if self.options:
for key, value in self._options.items():
if value is not None:
netlist += '.options {} = {}\n'.format(key, value)
else:
netlist += '.options {}\n'.format(key)
if self.initial_condition:
netlist += '.ic ' + join_dict(self._initial_condition) + '\n'
if self._saved_nodes:
netlist += '.save ' + join_list(self._saved_nodes) + '\n'
for analysis, analysis_parameters in self._analysis_parameters.items():
netlist += '.' + analysis + ' ' + join_list(analysis_parameters) + '\n'
netlist += '.end\n'
return netlist
####################################################################################################
class CircuitSimulator(CircuitSimulation):
""" This class implements a circuit simulator. Each analysis mode is performed by a method that
return the measured probes.
For *ac* and *transient* analyses, the user must specify a list of nodes using the *probes* key
argument.
"""
_logger = _module_logger.getChild('CircuitSimulator')
##############################################
def _run(self, analysis_method, *args, **kwargs):
self.reset_analysis()
if 'probes' in kwargs:
self.save(* kwargs.pop('probes'))
method = getattr(CircuitSimulation, analysis_method)
method(self, *args, **kwargs)
self._logger.debug('desk\n' + str(self))
##############################################
def operating_point(self, *args, **kwargs):
return self._run('operating_point', *args, **kwargs)
##############################################
def dc(self, *args, **kwargs):
return self._run('dc', *args, **kwargs)
##############################################
def dc_sensitivity(self, *args, **kwargs):
return self._run('dc_sensitivity', *args, **kwargs)
##############################################
def ac(self, *args, **kwargs):
return self._run('ac', *args, **kwargs)
##############################################
def transient(self, *args, **kwargs):
return self._run('transient', *args, **kwargs)
####################################################################################################
class SubprocessCircuitSimulator(CircuitSimulator):
_logger = _module_logger.getChild('SubprocessCircuitSimulator')
##############################################
def __init__(self, circuit,
temperature=27,
nominal_temperature=27,
spice_command='ngspice',
):
# Fixme: kwargs
super().__init__(circuit, temperature, nominal_temperature, pipe=True)
self._spice_server = SpiceServer()
##############################################
def _run(self, analysis_method, *args, **kwargs):
super()._run(analysis_method, *args, **kwargs)
raw_file = self._spice_server(str(self))
self.reset_analysis()
# for field in raw_file.variables:
# print field
return raw_file.to_analysis(self._circuit)
####################################################################################################
class NgSpiceSharedCircuitSimulator(CircuitSimulator):
_logger = _module_logger.getChild('NgSpiceSharedCircuitSimulator')
__ngspice_shared__ = None
##############################################
def __init__(self, circuit,
temperature=27,
nominal_temperature=27,
):
# Fixme: kwargs
super().__init__(circuit, temperature, nominal_temperature, pipe=False)
if self.__ngspice_shared__ is None:
self.__ngspice_shared__ = NgSpiceShared(send_data=False)
self._ngspice_shared = self.__ngspice_shared__
##############################################
def _run(self, analysis_method, *args, **kwargs):
super()._run(analysis_method, *args, **kwargs)
self._ngspice_shared.load_circuit(str(self))
self._ngspice_shared.run()
self._logger.debug(str(self._ngspice_shared.plot_names))
self.reset_analysis()
if analysis_method == 'dc':
plot_name = 'dc1'
elif analysis_method == 'ac':
plot_name = 'ac1'
elif analysis_method == 'tran':
plot_name = 'tran1'
else:
raise NotImplementedError
return self._ngspice_shared.plot(plot_name).to_analysis()
####################################################################################################
#
# End
#
####################################################################################################
PySpice-v0.2.4/PySpice/Spice/__init__.py 0000664 0000000 0000000 00000004726 12625125254 0020024 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# 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 .
#
####################################################################################################
####################################################################################################
from . import BasicElement
from . import HighLevelElement
from .Netlist import Netlist, ElementParameterMetaClass
####################################################################################################
def _get_elements(module):
element_classes = []
for item in module.__dict__.values():
if (type(item) is ElementParameterMetaClass
and item.prefix is not None
):
element_classes.append(item)
return element_classes
####################################################################################################
#
# Add a method to create elements to the Netlist class
#
spice_elements = _get_elements(BasicElement)
high_level_elements = _get_elements(HighLevelElement)
for element_class in spice_elements + high_level_elements:
def _make_function(element_class):
def function(self, *args, **kwargs):
element = element_class(*args, **kwargs)
self._add_element(element)
return element
return function
if element_class in spice_elements and hasattr(element_class, 'alias'):
function_name = element_class.alias
else:
function_name = element_class.__name__
setattr(Netlist, function_name, _make_function(element_class))
####################################################################################################
#
# End
#
####################################################################################################
PySpice-v0.2.4/PySpice/Tools/ 0000775 0000000 0000000 00000000000 12625125254 0015737 5 ustar 00root root 0000000 0000000 PySpice-v0.2.4/PySpice/Tools/EnumFactory.py 0000664 0000000 0000000 00000012126 12625125254 0020547 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# 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 .
#
####################################################################################################
""" This module provides an implementation for enumerate.
The enumerate factory :func:`EnumFactory` builds a enumerate from a list of names and assigns to
these constants a value from 0 to N-1, where N is the number of constants. For example::
enum = EnumFactory('Enum1', ('cst1', 'cst2'))
builds a enumerate with *cst1* set to 0 and *cst2* set to 1.
We can get a constant's value using an integer context like::
int(enum.cst1)
and the constant's name using::
repr(enum.cst1)
We can test constant equality using::
enum1.cst == enum2.cst
or with something that understand the *int* protocol::
enum1.cst == obj
# equivalent to
int(enum1.cst) == int(obj)
The number of constants could be retrieved with::
len(enum)
The enumerate factory :func:`ExplicitEnumFactory` is a variant that permits to specify the values of
the constants::
enum2 = ExplicitEnumFactory('Enum2', {'cst1':1, 'cst2':3})
We can test if a value is in the enumerate using::
constant_value in enum2
"""
####################################################################################################
# __all__ = ['EnumFactory', 'ExplicitEnumFactory']
####################################################################################################
class ReadOnlyMetaClass(type):
""" This meta class implements a class where attributes are read only. """
##############################################
def __setattr__(self, name, value):
raise NotImplementedError
####################################################################################################
class EnumMetaClass(ReadOnlyMetaClass):
""" This meta class implements the :func:`len` protocol. """
##############################################
def __len__(self):
return self._size
##############################################
def __getitem__(self, i):
return self._index[i]
####################################################################################################
class ExplicitEnumMetaClass(ReadOnlyMetaClass):
""" This meta class implements the operator ``in``. """
##############################################
def __contains__(self, item):
return item in self.constants
####################################################################################################
class EnumConstant:
""" Define an Enum Constant """
##############################################
def __init__(self, name, value):
self._name = name
self._value = value
##############################################
def __eq__(self, other):
return self._value == int(other)
##############################################
def __int__(self):
return self._value
##############################################
def __repr__(self):
return self._name
####################################################################################################
def EnumFactory(enum_name, enum_tuple):
""" Return an :class:`EnumMetaClass` instance, where *enum_name* is the class name and
*enum_tuple* is an iterable of constant's names.
"""
index = [EnumConstant(name, value) for value, name in enumerate(enum_tuple)]
obj_dict = {}
obj_dict['_size'] = len(enum_tuple)
obj_dict['_index'] = index
obj_dict.update({str(enum):enum for enum in index})
return EnumMetaClass(enum_name, (), obj_dict)
####################################################################################################
def ExplicitEnumFactory(enum_name, enum_dict):
""" Return an :class:`ExplicitEnumMetaClass` instance, where *enum_name* is the class name and
*enum_dict* is a dict of constant's names and their values.
"""
obj_dict = {}
obj_dict['constants'] = list(enum_dict.values())
for name, value in list(enum_dict.items()):
obj_dict[name] = EnumConstant(name, value)
return ExplicitEnumMetaClass(enum_name, (), obj_dict)
####################################################################################################
#
# End
#
####################################################################################################
PySpice-v0.2.4/PySpice/Tools/File.py 0000664 0000000 0000000 00000017204 12625125254 0017174 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# 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 .
#
####################################################################################################
####################################################################################################
import os
import subprocess
####################################################################################################
def file_name_has_extension(file_name, extension):
return file_name.endswith(extension)
####################################################################################################
def file_extension(filename):
# index = filename.rfind(os.path.extsep)
# if index == -1:
# return None
# else:
# return filename[index:]
return os.path.splitext(filename)[1]
####################################################################################################
def run_shasum(filename, algorithm=1, text=False, binary=False, portable=False):
if algorithm not in (1, 224, 256, 384, 512, 512224, 512256):
raise ValueError
args = ['shasum', '--algorithm=' + str(algorithm)]
if text:
args.append('--text')
elif binary:
args.append('--binary')
elif portable:
args.append('--portable')
args.append(filename)
output = subprocess.check_output(args)
shasum = output[:output.find(' ')]
return shasum
####################################################################################################
class Path:
##############################################
def __init__(self, path):
self._path = str(path)
##############################################
def __bool__(self):
return os.path.exists(self._path)
##############################################
def __str__(self):
return self._path
##############################################
@property
def path(self):
return self._path
##############################################
def is_absolut(self):
return os.path.isabs(self._path)
##############################################
def absolut(self):
return self.clone_for_path(os.path.abspath(self._path))
##############################################
def normalise(self):
return self.clone_for_path(os.path.normpath(self._path))
##############################################
def normalise_case(self):
return self.clone_for_path(os.path.normcase(self._path))
##############################################
def expand_vars_and_user(self):
return self.clone_for_path(os.path.expandvars(os.path.expanduser(self._path)))
##############################################
def real_path(self):
return self.clone_for_path(os.path.realpath(self._path))
##############################################
def relative_to(self, directory):
return self.clone_for_path(os.path.relpath(self._path, str(directory)))
##############################################
def clone_for_path(self, path):
return self.__class__(path)
##############################################
def split(self):
return self._path.split(os.path.sep)
##############################################
def directory_part(self):
return Directory(os.path.dirname(self._path))
##############################################
def filename_part(self):
return os.path.basename(self._path)
##############################################
def is_directory(self):
return os.path.isdir(self._path)
##############################################
def is_file(self):
return os.path.isfile(self._path)
##############################################
@property
def inode(self):
return os.stat(self._path).st_ino
##############################################
@property
def creation_time(self):
return os.stat(self._path).st_ctime
####################################################################################################
class Directory(Path):
##############################################
def __bool__(self):
return super().__nonzero__() and self.is_directory()
##############################################
def join_directory(self, directory):
return self.__class__(os.path.join(self._path, str(directory)))
##############################################
def join_filename(self, filename):
return File(filename, self._path)
##############################################
def iter_file(self, followlinks=False):
for root, directories, files in os.walk(self._path, followlinks=followlinks):
for filename in files:
yield File(filename, root)
##############################################
def iter_directories(self, followlinks=False):
for root, directories, files in os.walk(self._path, followlinks=followlinks):
for directory in directories:
yield Path(os.path.join(root, directory))
####################################################################################################
class File(Path):
default_shasum_algorithm = 256
##############################################
def __init__(self, filename, path=''):
super().__init__(os.path.join(str(path), str(filename)))
self._filename = self.filename_part()
if not self._filename:
raise ValueError
self._directory = self.directory_part()
self._shasum = None # lazy computation
##############################################
def __bool__(self):
return super().__nonzero__() and os.path.isfile(self._path)
##############################################
@property
def directory(self):
return self._directory
##############################################
@property
def filename(self):
return self._filename
##############################################
@property
def extension(self):
return file_extension(self._filename)
##############################################
@property
def shasum(self):
if self._shasum is None:
return self.compute_shasum()
else:
return self._shasum
##############################################
def compute_shasum(self, algorithm=None):
if algorithm is None:
algorithm = self.default_shasum_algorithm
self._shasum = run_shasum(self._path, algorithm, portable=True)
return self._shasum
####################################################################################################
#
# End
#
####################################################################################################
PySpice-v0.2.4/PySpice/Tools/Path.py 0000664 0000000 0000000 00000004367 12625125254 0017217 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# 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 .
#
####################################################################################################
####################################################################################################
import os
import types
####################################################################################################
def to_absolute_path(path):
# Expand ~ . and Remove trailing '/'
return os.path.abspath(os.path.expanduser(path))
####################################################################################################
def parent_directory_of(file_name, step=1):
directory = file_name
for i in range(step):
directory = os.path.dirname(directory)
return directory
####################################################################################################
def find(file_name, directories):
if isinstance(directories, bytes):
directories = (directories,)
for directory in directories:
for directory_path, sub_directories, file_names in os.walk(directory):
if file_name in file_names:
return os.path.join(directory_path, file_name)
raise NameError("File %s not found in directories %s" % (file_name, str(directories)))
####################################################################################################
#
# End
#
####################################################################################################
PySpice-v0.2.4/PySpice/Tools/StringTools.py 0000664 0000000 0000000 00000003350 12625125254 0020601 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# 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 .
#
####################################################################################################
####################################################################################################
def join_lines(items, prefix=''):
return '\n'.join([prefix + str(item) for item in items if item is not None])
####################################################################################################
def join_list(items):
return ' '.join([str(item) for item in items if item is not None])
####################################################################################################
def join_dict(d):
return ' '.join(["{}={}".format(key, value) for key, value in d.items() if value is not None])
####################################################################################################
#
# End
#
####################################################################################################
PySpice-v0.2.4/PySpice/Tools/__init__.py 0000664 0000000 0000000 00000001630 12625125254 0020050 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# 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 .
#
####################################################################################################
PySpice-v0.2.4/PySpice/Unit/ 0000775 0000000 0000000 00000000000 12625125254 0015556 5 ustar 00root root 0000000 0000000 PySpice-v0.2.4/PySpice/Unit/SiUnits.py 0000664 0000000 0000000 00000002756 12625125254 0017540 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# 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 .
#
####################################################################################################
####################################################################################################
from . import Units
from .Units import __units__
####################################################################################################
for unit in __units__:
unit_name = unit.__spice_suffix__ + 'V'
setattr(Units, unit_name, type(unit_name, (unit,), dict()))
####################################################################################################
#
# End
#
####################################################################################################
PySpice-v0.2.4/PySpice/Unit/Units.py 0000664 0000000 0000000 00000032076 12625125254 0017242 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# 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 .
#
####################################################################################################
####################################################################################################
#
# - unit suffix
# - simplify .001
#
####################################################################################################
""" This module implements units.
"""
####################################################################################################
import numbers
import math
####################################################################################################
class Unit(numbers.Real):
__power__ = 0
__spice_suffix__ = ''
__unit_suffix__ = ''
##############################################
def __init__(self, value):
if isinstance(value, Unit):
if self.__power__ == value.__power__:
self._value = value._value
else:
self._value = float(value) / self.scale
elif isinstance(value, int):
self._value = value # to keep as int
else:
self._value = float(value)
##############################################
@property
def power(self):
return self.__power__
##############################################
@property
def scale(self):
return 10**self.__power__
##############################################
@property
def value(self):
return self._value
##############################################
def clone(self):
return self.__class__(self._value)
##############################################
def is_same_scale(self, other):
return isinstance(other, Unit) and self.__power__ == other.__power__
##############################################
def convert_value(self, other):
if self.is_same_scale(other):
return other._value
else:
return float(other) / self.scale
##############################################
def convert(self, other):
return self.__class__(self.convert_value(other))
##############################################
def __int__(self):
return int(self._value * self.scale)
##############################################
def __float__(self):
return float(self._value * self.scale)
##############################################
def __str__(self):
return str(self._value) + self.__spice_suffix__
##############################################
def __bool__(self):
"""True if self != 0. Called for bool(self)."""
return self._value != 0
##############################################
def __add__(self, other):
"""self + other"""
new_obj = self.clone()
new_obj._value += self.convert_value(other)
return new_obj
##############################################
def __iadd__(self, other):
"""self += other"""
self._value += self.convert_value(other)
return self
##############################################
def __radd__(self, other):
"""other + self"""
return other.__add__(self)
##############################################
def __neg__(self):
"""-self"""
return self.__class__(-self._value)
##############################################
def __pos__(self):
"""+self"""
return self.clone()
##############################################
def __sub__(self, other):
"""self - other"""
new_obj = self.clone()
new_obj._value -= self.convert_value(other)
return new_obj
##############################################
def __isub__(self, other):
"""self -= other"""
self._value -= self.convert_value(other)
return self
##############################################
def __rsub__(self, other):
"""other - self"""
return other.__sub__(self)
##############################################
def __mul__(self, other):
"""self * other"""
new_obj = self.clone()
new_obj._value *= float(other)
return new_obj
##############################################
def __imul__(self, other):
"""self *= other"""
self._value *= self.convert_value(other)
return self
##############################################
def __rmul__(self, other):
"""other * self"""
return other.__mul__(self)
##############################################
def __floordiv__(self, other):
"""self // other """
new_obj = self.clone()
new_obj._value //= float(other)
return new_obj
##############################################
def __ifloordiv__(self, other):
"""self //= other """
self._value //= float(other)
return self
##############################################
def __rfloordiv__(self, other):
"""other // self"""
return other.__floordiv__(self)
##############################################
def __truediv__(self, other):
"""self / other"""
new_obj = self.clone()
new_obj._value /= float(other)
return new_obj
##############################################
def __itruediv__(self, other):
"""self /= other"""
self._value /= float(other)
return self
##############################################
def __rtruediv__(self, other):
"""other / self"""
return other.__div__(self)
##############################################
def __pow__(self, exponent):
"""self**exponent; should promote to float or complex when necessary."""
new_obj = self.clone()
new_obj._value **= float(exponent)
return new_obj
##############################################
def __ipow__(self, exponent):
self._value **= float(exponent)
return self
##############################################
def __rpow__(self, base):
"""base ** self"""
raise NotImplementedError
##############################################
def __abs__(self):
"""Returns the Real distance from 0. Called for abs(self)."""
return self.__class__(abs(self._value))
##############################################
def __eq__(self, other):
"""self == other"""
return float(self) == float(other)
##############################################
def __ne__(self, other):
"""self != other"""
# The default __ne__ doesn't negate __eq__ until 3.0.
return not (self == other)
##############################################
def __trunc__(self):
"""trunc(self): Truncates self to an Integral.
Returns an Integral i such that:
* i>0 iff self>0;
* abs(i) <= abs(self);
* for any Integral j satisfying the first two conditions,
abs(i) >= abs(j) [i.e. i has "maximal" abs among those].
i.e. "truncate towards 0".
"""
raise NotImplementedError
##############################################
def __divmod__(self, other):
"""divmod(self, other): The pair (self // other, self % other).
Sometimes this can be computed faster than the pair of
operations.
"""
return (self // other, self % other)
##############################################
def __rdivmod__(self, other):
"""divmod(other, self): The pair (self // other, self % other).
Sometimes this can be computed faster than the pair of
operations.
"""
return (other // self, other % self)
##############################################
def __mod__(self, other):
"""self % other"""
raise NotImplementedError
##############################################
def __rmod__(self, other):
"""other % self"""
raise NotImplementedError
##############################################
def __lt__(self, other):
"""self < other
< on Reals defines a total ordering, except perhaps for NaN."""
return float(self) < float(other)
##############################################
def __le__(self, other):
"""self <= other"""
return float(self) <= float(other)
##############################################
def __ceil__(self):
return math.ceil(float(self))
##############################################
def __floor__(self):
return math.floor(float(self))
##############################################
def __round__(self):
return round(float(self))
##############################################
def inverse(self, the_class=None):
inverse = 1. / float(self)
if the_class is None:
return self.__class__(inverse / self.scale) # Fixme: to func?
else:
return the_class(inverse)
##############################################
def canonise(self):
# log10(10**n) = n log10(1) = 0 log10(10**-n) = -n log10(0) = -oo
try:
abs_value = abs(float(self))
log = math.log(abs_value)/math.log(1000)
# if abs_value >= 1:
# power = 3 * int(log)
# else:
# if log - int(log): # frac
# power = 3 * (int(log) -1)
# else:
# power = 3 * int(log)
power = int(log)
if abs_value < 1 and (log - int(log)):
power -= 1
power *= 3
unit = __power_to_unit__[power]
return unit(float(self) / 10**power)
except:
return self
####################################################################################################
class tera(Unit):
""" T Tera 1e12 """
__power__ = 12
__spice_suffix__ = 'T'
class giga(Unit):
""" G Giga 1e9 """
__power__ = 9
__spice_suffix__ = 'G'
class mega(Unit):
""" Meg Mega 1e6 """
__power__ = 6
__spice_suffix__ = 'Meg'
class kilo(Unit):
""" K Kilo 1e3 """
__power__ = 3
__spice_suffix__ = 'k'
class milli(Unit):
""" m milli 1e-3 """
__power__ = -3
__spice_suffix__ = 'm'
class micro(Unit):
""" u micro 1e-6 """
__power__ = -6
__spice_suffix__ = 'u'
class nano(Unit):
""" n nano 1e-9 """
__power__ = -9
__spice_suffix__ = 'n'
class pico(Unit):
""" p pico 1e-12 """
__power__ = -12
__spice_suffix__ = 'p'
class femto(Unit):
""" f femto 1e-15 """
__power__ = -15
__spice_suffix__ = 'f'
# class mil(Unit):
# """ mil Mil 25.4e-6 """
# __scale__ = 25.4e-6
# __spice_suffix__ = 'mil'
__units__ = (tera,
giga,
mega,
kilo,
Unit,
milli,
micro,
nano,
pico,
femto,
)
__power_to_unit__ = {unit.__power__:unit for unit in __units__}
####################################################################################################
class Frequency(Unit):
""" This class implements a frequency unit. """
##############################################
@property
def period(self):
r""" Return the period :math:`T = \frac{1}{f}`. """
return self.inverse(Period)
##############################################
@property
def pulsation(self):
r""" Return the pulsation :math:`\omega = 2\pi f`. """
return float(self * 2 * math.pi)
####################################################################################################
class Period(Unit):
""" This class implements a period unit. """
##############################################
@property
def frequency(self):
r""" Return the period :math:`f = \frac{1}{T}`. """
return self.inverse(Frequency)
##############################################
@property
def pulsation(self):
r""" Return the pulsation :math:`\omega = \frac{2\pi}{T}`. """
return self.frequency.pulsation
####################################################################################################
#
# End
#
####################################################################################################
PySpice-v0.2.4/PySpice/Unit/__init__.py 0000664 0000000 0000000 00000002156 12625125254 0017673 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# 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 .
#
####################################################################################################
####################################################################################################
#
# End
#
####################################################################################################
PySpice-v0.2.4/PySpice/__init__.py 0000664 0000000 0000000 00000002156 12625125254 0016754 0 ustar 00root root 0000000 0000000 ####################################################################################################
#
# 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 .
#
####################################################################################################
####################################################################################################
#
# End
#
####################################################################################################
PySpice-v0.2.4/README.html 0000664 0000000 0000000 00000041163 12625125254 0015113 0 ustar 00root root 0000000 0000000
PySpice is a Python 3 library which interplay with Berkeley SPICE, the industrial circuit
simulator reference.
SPICE (Simulation Program with Integrated Circuit Emphasis) was developed at the Electronics
Research Laboratory of the University of California, Berkeley in 1973 by Laurence Nagel with
direction from his research advisor, Prof. Donald Pederson. Then Spice emerged as an industrial
standard through its descendants and is still the reference 40 years later.
PySpice is born as a personal project to relearn electronics where circuit simulation is a part of
this goal. Since I use the Python language every day, I quickly feel the need to plug SPICE and Python.
The aim of PySpice is to address several needs:
SPICE language is fine to describe circuits, but it lacks a real language for circuit
steering. By contrast Python is a powerful high level, oriented object and dynamic language which
is perfectly suited for steering and reusing circuit. But it comes at the price its more general
syntax is less fluent than SPICE for circuit description.
Ngspice provides some extension to Berkeley SPICE for data analysis, but its interactive
environment or TCL module are now outdated. By contrast Python has scientific framework like
Numpy and Matplotlib that compete with Matlab.
Ngspice source code is derived from Berkeley SPICE and thus has a very old basis. Moreover the
sources are poorly documented. So it is really difficult to understand how it works and modify
it. PySpice could serve to easily experiment extension.
As opposite to other SPICE derivatives, PySpice focus on programming and not on graphical user
interface. Thus it doesn't feature a schematic capture editor and we cannot pickup a node or an
element and plot the associated waveform. Moreover we can notice the Modelica language treats
diagrams as annotations. A choice which I consider judicious. Thus we can imagine to follow the
same principle and extend PySpice later.
an oriented-object API to describe circuit in a way similar to SPICE
a library and model manager that index recursively a directory
an incomplete SPICE parser (mainly used for the library and model indexer)
a circuit can be simulated using a subprocess (aka server mode) or using the NgSpice shared library,
NgSpice vectors are converted to Numpy array
the NgSpice shared library permits to interact with the simulator and provides Python callback
for external voltage and current source
some data analysis add-ons
Since PySpice is born with a learning goal, many examples are provided with the sources. These
examples could serve as learning materials. A tool to generate an HTML and PDF documentation is
included in the tools directory. This tool could be extended to generate IPython Notebook as well.
Planned Features
These features are planned in the future:
implement a SPICE to Python converted using the parser. It could be used for the following
workflow: quick circuit sketching using Kicad > SPICE netlist > spice2python > PySpice which
could help for complex circuit.
implement a basic simulator featuring passive element like resistor, capacitor and inductor.
implement a Modelica backend. Modelica is a very interesting solution for transient analysis.
Advertisements
Users should be aware of these advertisements.
Warning
The API is quite unstable until now. Some efforts is made to have a smooth API.
Warning
Ngspice and PySpice are provided without any warranty. Thus use it with care for real
design. Best is to cross check the simulation using an industrial grade simulator.
Warning
Simulation is a tool and not a perfect representation of the real world.
Installation
The installation of PySpice by itself is quite simple. However it will be easier to get the
dependencies on a Linux desktop.
Usually Ngspice is available as a package in the major Linux distributions. But I recommend to check
the compilation options before to use it extensively. For example the Fedora package enables too
many experimental codes that have side effects. The recommended way to compile Ngspice is given in
the manual and the INSTALLATION file. Ngspice is an example of complex software where we should
not enable everything without care.
Warning
For the following, the compilation option --enable-ndev is known to broke the server mode.