Source code for PySpice.Spice.HighLevelElement

# -*- coding: utf-8 -*-

####################################################################################################
#
# PySpice - A Spice Package for Python
# Copyright (C) 2014 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
####################################################################################################

"""This module implements high level elements built on top of Spice elements."""

# Fixme: check NgSpice for discrepancies

####################################################################################################

from ..Math import rms_to_amplitude, amplitude_to_rms
from ..Tools.StringTools import join_list, join_dict, str_spice, str_spice_list
from ..Unit import as_s, as_V, as_A, as_Hz
from .BasicElement import VoltageSource, CurrentSource

####################################################################################################

class SourceMixinAbc:

    __as_unit__ = None

####################################################################################################

class VoltageSourceMixinAbc:

    __as_unit__ = as_V

####################################################################################################

class CurrentSourceMixinAbc:

    __as_unit__ = as_A

####################################################################################################

[docs]class SinusoidalMixin(SourceMixinAbc): r"""This class implements a sinusoidal waveform. +------+----------------+---------------+-------+ | Name + Parameter + Default Value + Units | +------+----------------+---------------+-------+ | Vo + offset + + V, A | +------+----------------+---------------+-------+ | Va + amplitude + + V, A | +------+----------------+---------------+-------+ | f + frequency + 1 / TStop + Hz | +------+----------------+---------------+-------+ | Td + delay + 0.0 + sec | +------+----------------+---------------+-------+ | Df + damping factor + 0.01 + 1/sec | +------+----------------+---------------+-------+ The shape of the waveform is described by the following formula: .. math:: V(t) = \begin{cases} V_o & \text{if}\ 0 \leq t < T_d, \\ V_o + V_a e^{-D_f(t-T_d)} \sin\left(2\pi f (t-T_d)\right) & \text{if}\ T_d \leq t < T_{stop}. \end{cases} Spice syntax:: SIN ( Voffset Vamplitude Freq Tdelay DampingFactor ) Public Attributes: :attr:`ac_magnitude` :attr:`amplitude` :attr:`damping_factor` :attr:`dc_offset` :attr:`delay` :attr:`frequency` :attr:`offset` """ ############################################## def __init__(self, dc_offset=0, ac_magnitude=1, offset=0, amplitude=1, frequency=50, delay=0, damping_factor=0): self.dc_offset = self.__as_unit__(dc_offset) self.ac_magnitude = self.__as_unit__(ac_magnitude) self.offset = self.__as_unit__(offset) self.amplitude = self.__as_unit__(amplitude) self.frequency = as_Hz(frequency) # Fixme: protect by setter? self.delay = as_s(delay) self.damping_factor = as_Hz(damping_factor) ############################################## @property def rms_voltage(self): # Fixme: ok ??? return amplitude_to_rms(self.amplitude * self.ac_magnitude) ############################################## @property def period(self): return self.frequency.period ############################################## def format_spice_parameters(self): sin_part = join_list((self.offset, self.amplitude, self.frequency, self.delay, self.damping_factor)) return join_list(( 'DC {} AC {}'.format(*str_spice_list(self.dc_offset, self.ac_magnitude)), 'SIN({})'.format(sin_part), ))
####################################################################################################
[docs]class PulseMixin(SourceMixinAbc): """This class implements a pulse waveform. Nomenclature: +--------+---------------+---------------+-------+ | Name + Parameter + Default Value + Units | +--------+---------------+---------------+-------+ | V1 + initial value + + V, A | +--------+---------------+---------------+-------+ | V2 + pulsed value + + V, A | +--------+---------------+---------------+-------+ | Td + delay time + 0.0 + sec | +--------+---------------+---------------+-------+ | Tr + rise time + Tstep + sec | +--------+---------------+---------------+-------+ | Tf + fall time + Tstep + sec | +--------+---------------+---------------+-------+ | Pw + pulse width + Tstop + sec | +--------+---------------+---------------+-------+ | Period + period + Tstop + sec | +--------+---------------+---------------+-------+ | Phase + phase + 0.0 + sec | +--------+---------------+---------------+-------+ Phase is only possible when XSPICE is enabled Spice Syntax:: PULSE ( V1 V2 Td Tr Tf Pw Period Phase ) A single pulse so specified is described by the following table: +-------------+-------+ | Time | Value | +-------------+-------+ | 0 | V1 | +-------------+-------+ | Td | V1 | +-------------+-------+ | Td+Tr | V2 | +-------------+-------+ | Td+Tr+Pw | V2 | +-------------+-------+ | Td+Tr+Pw+Tf | V1 | +-------------+-------+ | Tstop | V1 | +-------------+-------+ Note: default value in Spice for rise and fall time is the simulation transient step, pulse width and period is the simulation stop time. Public Attributes: :attr:`delay_time` :attr:`fall_time` :attr:`initial_value` :attr:`period` :attr:`phase` :attr:`pulse_width` :attr:`pulsed_value` :attr:`rise_time` """ ############################################## def __init__(self, initial_value, pulsed_value, pulse_width, period, delay_time=0, rise_time=0, fall_time=0, phase=None, dc_offset=0): # Fixme: default # rise_time, fall_time = Tstep # pulse_width, period = Tstop self.dc_offset = self.__as_unit__(dc_offset) # Fixme: -> SourceMixinAbc self.initial_value = self.__as_unit__(initial_value) self.pulsed_value = self.__as_unit__(pulsed_value) self.delay_time = as_s(delay_time) self.rise_time = as_s(rise_time) self.fall_time = as_s(fall_time) self.pulse_width = as_s(pulse_width) self.period = as_s(period) # Fixme: protect by setter? # XSPICE if phase is not None: self.phase = as_s(phase) else: self.phase = None # # Fixme: to func? # # Check parameters # found_none = False # for parameter in ('rise_time', 'fall_time', 'pulse_width', 'period'): # parameter_value = getattr(self, parameter) # if found_none: # if parameter_value is not None: # raise ValueError("Parameter {} is set but some previous parameters was not set".format(parameter)) # else: # found_none = parameter_value is None ############################################## @property def frequency(self): return self.period.frequency ############################################## def format_spice_parameters(self): # if DC is not provided, ngspice complains # Warning: vpulse: no DC value, transient time 0 value used # Fixme: to func? return join_list(( 'DC {}'.format(str_spice(self.dc_offset)), 'PULSE(' + join_list((self.initial_value, self.pulsed_value, self.delay_time, self.rise_time, self.fall_time, self.pulse_width, self.period, self.phase)) + ')'))
####################################################################################################
[docs]class ExponentialMixin(SourceMixinAbc): r"""This class implements a Exponential waveform. Nomenclature: +------+--------------------+---------------+-------+ | Name + Parameter + Default Value + Units | +------+--------------------+---------------+-------+ | V1 + Initial value + + V, A | +------+--------------------+---------------+-------+ | V2 + pulsed value + + V, A | +------+--------------------+---------------+-------+ | Td1 + rise delay time + 0.0 + sec | +------+--------------------+---------------+-------+ | tau1 + rise time constant + Tstep + sec | +------+--------------------+---------------+-------+ | Td2 + fall delay time + Td1+Tstep + sec | +------+--------------------+---------------+-------+ | tau2 + fall time constant + Tstep + sec | +------+--------------------+---------------+-------+ Spice Syntax:: EXP ( V1 V2 TD1 TAU1 TD2 TAU2 ) The shape of the waveform is described by the following formula: Let V21 = V2 - V1 and V12 = V1 - V2. .. math:: V(t) = \begin{cases} V_1 & \text{if}\ 0 \leq t < T_{d1}, \\ V_1 + V_{21} ( 1 − e^{-\frac{t-T_{d1}}{\tau_1}} ) & \text{if}\ T_{d1} \leq t < T_{d2}, \\ V_1 + V_{21} ( 1 − e^{-\frac{t-T_{d1}}{\tau_1}} ) + V_{12} ( 1 − e^{-\frac{t-T_{d2}}{\tau_2}} ) & \text{if}\ T_{d2} \leq t < T_{stop} \end{cases} """ ############################################## def __init__(self, initial_value, pulsed_value, rise_delay_time=.0, rise_time_constant=None, fall_delay_time=None, fall_time_constant=None): # Fixme: default self.initial_value = self.__as_unit__(initial_value) self.pulsed_value = self.__as_unit__(pulsed_value) self.rise_delay_time = as_s(rise_delay_time) self.rise_time_constant = as_s(rise_time_constant) self.fall_delay_time = as_s(fall_delay_time) self.fall_time_constant = as_s(fall_time_constant) ############################################## def format_spice_parameters(self): # Fixme: to func? return ('EXP(' + join_list((self.initial_value, self.pulsed_value, self.rise_delay_time, self.rise_time_constant, self.fall_delay_time, self.fall_time_constant, )) + ')')
####################################################################################################
[docs]class PieceWiseLinearMixin(SourceMixinAbc): r"""This class implements a Piece-Wise Linear waveform. Spice Syntax:: PWL( T1 V1 <T2 V2 T3 V3 T4 V4 ... > ) <r=value> <td=value> 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. `values` should be given as a list of (`Time`, `Value`)-tuples, e.g.:: PieceWiseLinearVoltageSource( circuit, 'pwl1', '1', '0', values=[(0, 0), (10@u_ms, 0), (11@u_ms, 5@u_V), (20@u_ms, 5@u_V)], ) """ ############################################## def __init__(self, values, repeate_time=0, delay_time=.0): # Fixme: default self.values = sum(([as_s(t), self.__as_unit__(x)] for (t, x) in values), []) self.repeate_time = as_s(repeate_time) self.delay_time = as_s(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( ')')
####################################################################################################
[docs]class SingleFrequencyFMMixin(SourceMixinAbc): 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, offset, amplitude, carrier_frequency, modulation_index, signal_frequency): self.offset = self.__as_unit__(offset) self.amplitude = self.__as_unit__(amplitude) self.carrier_frequency = as_Hz(carrier_frequency) self.modulation_index = modulation_index self.signal_frequency = as_Hz(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)) + ')')
####################################################################################################
[docs]class AmplitudeModulatedMixin(SourceMixinAbc): 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, offset, amplitude, modulating_frequency, carrier_frequency, signal_delay): # Fixme: default self.offset = self.__as_unit__(offset) self.amplitude = self.__as_unit__(amplitude) self.carrier_frequency = as_Hz(carrier_frequency) self.modulating_frequency = as_Hz(modulating_frequency) self.signal_delay = as_s(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)) + ')')
####################################################################################################
[docs]class RandomMixin(SourceMixinAbc): 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 <TD <PARAM1 <PARAM2> > >) 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, random_type, duration=0, time_delay=0, parameter1=1, parameter2=0): # Fixme: random_type and parameters self.random_type = random_type self.duration = as_s(duration) self.time_delay = as_s(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)) + ')')
####################################################################################################
[docs]class SinusoidalVoltageSource(VoltageSource, VoltageSourceMixinAbc, SinusoidalMixin): r"""This class implements a sinusoidal waveform voltage source. See :class:`SinusoidalMixin` for documentation. """ ############################################## def __init__(self, netlist, name, node_plus, node_minus, *args, **kwargs): VoltageSource.__init__(self, netlist, name, node_plus, node_minus) SinusoidalMixin.__init__(self, *args, **kwargs) ############################################## format_spice_parameters = SinusoidalMixin.format_spice_parameters
####################################################################################################
[docs]class SinusoidalCurrentSource(CurrentSource, CurrentSourceMixinAbc, SinusoidalMixin): r"""This class implements a sinusoidal waveform current source. See :class:`SinusoidalMixin` for documentation. """ ############################################## def __init__(self, netlist, name, node_plus, node_minus, *args, **kwargs): CurrentSource.__init__(self, netlist, name, node_plus, node_minus) SinusoidalMixin.__init__(self, *args, **kwargs) ############################################## format_spice_parameters = SinusoidalMixin.format_spice_parameters
####################################################################################################
[docs]class AcLine(SinusoidalVoltageSource): ############################################## def __init__(self, netlist, name, node_plus, node_minus, rms_voltage=230, frequency=50): super().__init__(netlist, name, node_plus, node_minus, amplitude=rms_to_amplitude(rms_voltage), frequency=frequency)
####################################################################################################
[docs]class PulseVoltageSource(VoltageSource, VoltageSourceMixinAbc, PulseMixin): r"""This class implements a pulse waveform voltage source. See :class:`PulseMixin` for documentation. """ ############################################## def __init__(self, netlist, name, node_plus, node_minus, *args, **kwargs): VoltageSource.__init__(self, netlist, name, node_plus, node_minus) PulseMixin.__init__(self, *args, **kwargs) ############################################## format_spice_parameters = PulseMixin.format_spice_parameters
####################################################################################################
[docs]class PulseCurrentSource(CurrentSource, CurrentSourceMixinAbc, PulseMixin): r"""This class implements a pulse waveform current source. See :class:`PulseMixin` for documentation. """ ############################################## def __init__(self, netlist, name, node_plus, node_minus, *args, **kwargs): CurrentSource.__init__(self, netlist, name, node_plus, node_minus) PulseMixin.__init__(self, *args, **kwargs) ############################################## format_spice_parameters = PulseMixin.format_spice_parameters
####################################################################################################
[docs]class ExponentialVoltageSource(VoltageSource, VoltageSourceMixinAbc, ExponentialMixin): r"""This class implements a exponential waveform voltage source. See :class:`ExponentialMixin` for documentation. """ ############################################## def __init__(self, netlist, name, node_plus, node_minus, *args, **kwargs): VoltageSource.__init__(self, netlist, name, node_plus, node_minus) ExponentialMixin.__init__(self, *args, **kwargs) ############################################## format_spice_parameters = ExponentialMixin.format_spice_parameters
####################################################################################################
[docs]class ExponentialCurrentSource(CurrentSource, CurrentSourceMixinAbc, ExponentialMixin): r"""This class implements a exponential waveform current source. See :class:`ExponentialMixin` for documentation. """ ############################################## def __init__(self, netlist, name, node_plus, node_minus, *args, **kwargs): CurrentSource.__init__(self, netlist, name, node_plus, node_minus) ExponentialMixin.__init__(self, *args, **kwargs) ############################################## format_spice_parameters = ExponentialMixin.format_spice_parameters
####################################################################################################
[docs]class PieceWiseLinearVoltageSource(VoltageSource, VoltageSourceMixinAbc, PieceWiseLinearMixin): r"""This class implements a piece wise linear waveform voltage source. See :class:`PieceWiseLinearMixin` for documentation. """ ############################################## def __init__(self, netlist, name, node_plus, node_minus, *args, **kwargs): VoltageSource.__init__(self, netlist, name, node_plus, node_minus) PieceWiseLinearMixin.__init__(self, *args, **kwargs) ############################################## format_spice_parameters = PieceWiseLinearMixin.format_spice_parameters
####################################################################################################
[docs]class PieceWiseLinearCurrentSource(CurrentSource, CurrentSourceMixinAbc, PieceWiseLinearMixin): r"""This class implements a piece wise linear waveform current source. See :class:`PieceWiseLinearMixin` for documentation. """ ############################################## def __init__(self, netlist, name, node_plus, node_minus, *args, **kwargs): CurrentSource.__init__(self, netlist, name, node_plus, node_minus) PieceWiseLinearMixin.__init__(self, *args, **kwargs) ############################################## format_spice_parameters = PieceWiseLinearMixin.format_spice_parameters
####################################################################################################
[docs]class SingleFrequencyFMVoltageSource(VoltageSource, VoltageSourceMixinAbc, SingleFrequencyFMMixin): r"""This class implements a single frequency FM waveform voltage source. See :class:`SingleFrequencyFMMixin` for documentation. """ ############################################## def __init__(self, netlist, name, node_plus, node_minus, *args, **kwargs): VoltageSource.__init__(self, netlist, name, node_plus, node_minus) SingleFrequencyFMMixin.__init__(self, *args, **kwargs) ############################################## format_spice_parameters = SingleFrequencyFMMixin.format_spice_parameters
####################################################################################################
[docs]class SingleFrequencyFMCurrentSource(CurrentSource, CurrentSourceMixinAbc, SingleFrequencyFMMixin): r"""This class implements a single frequency FM waveform current source. See :class:`SingleFrequencyFMMixin` for documentation. """ ############################################## def __init__(self, netlist, name, node_plus, node_minus, *args, **kwargs): CurrentSource.__init__(self, netlist, name, node_plus, node_minus) SingleFrequencyFMMixin.__init__(self, *args, **kwargs) ############################################## format_spice_parameters = SingleFrequencyFMMixin.format_spice_parameters
####################################################################################################
[docs]class AmplitudeModulatedVoltageSource(VoltageSource, VoltageSourceMixinAbc, AmplitudeModulatedMixin): r"""This class implements a amplitude modulated waveform voltage source. See :class:`AmplitudeModulatedMixin` for documentation. """ ############################################## def __init__(self, netlist, name, node_plus, node_minus, *args, **kwargs): VoltageSource.__init__(self, netlist, name, node_plus, node_minus) AmplitudeModulatedMixin.__init__(self, *args, **kwargs) ############################################## format_spice_parameters = AmplitudeModulatedMixin.format_spice_parameters
####################################################################################################
[docs]class AmplitudeModulatedCurrentSource(CurrentSource, CurrentSourceMixinAbc, AmplitudeModulatedMixin): r"""This class implements a amplitude modulated waveform current source. See :class:`AmplitudeModulatedMixin` for documentation. """ ############################################## def __init__(self, netlist, name, node_plus, node_minus, *args, **kwargs): CurrentSource.__init__(self, netlist, name, node_plus, node_minus) AmplitudeModulatedMixin.__init__(self, *args, **kwargs) ############################################## format_spice_parameters = AmplitudeModulatedMixin.format_spice_parameters
####################################################################################################
[docs]class RandomVoltageSource(VoltageSource, VoltageSourceMixinAbc, RandomMixin): r"""This class implements a random waveform voltage source. See :class:`RandomMixin` for documentation. """ ############################################## def __init__(self, netlist, name, node_plus, node_minus, *args, **kwargs): VoltageSource.__init__(self, netlist, name, node_plus, node_minus) RandomMixin.__init__(self, *args, **kwargs) ############################################## format_spice_parameters = RandomMixin.format_spice_parameters
####################################################################################################
[docs]class RandomCurrentSource(CurrentSource, CurrentSourceMixinAbc, RandomMixin): r"""This class implements a random waveform current source. See :class:`RandomMixin` for documentation. """ ############################################## def __init__(self, netlist, name, node_plus, node_minus, *args, **kwargs): CurrentSource.__init__(self, netlist, name, node_plus, node_minus) RandomMixin.__init__(self, *args, **kwargs) ############################################## format_spice_parameters = RandomMixin.format_spice_parameters