# -*- coding: iso-8859-1 -*-
# switch.py
# Implementation of the voltage controlled switch model
# Copyright 2013 Giuseppe Venturini
#
# This file is part of the ahkab simulator.
#
# Ahkab 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, version 2 of the License.
#
# Ahkab 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 v2
# along with ahkab. If not, see <http://www.gnu.org/licenses/>.
"""
Implementation of a voltage controlled switch.
This module defines two classes: switch_device, switch_model
"""
# sn1 o--+ +--o n1
# | |
# +-+ \ o
# |R| \
# +-+ +
# | |
# sn2 o--+ +--o n2
from __future__ import (unicode_literals, absolute_import,
division, print_function)
import math
from . import options
from . import printing
[docs]class switch_device:
"""This is a general switch element.
It has the following structure:
.. image:: images/elem/switch1.svg
|
In ASCII for those who are consulting the documentation from the
Python command line:
::
sn1 o--+ +--o n1
| |
+-+ \ o
|R| \\
+-+ +
| |
sn2 o--+ +--o n2
The behavior is set by the model supplied.
The device instance calls the following methods in the model:
* ``get_i(ports_v, device)`` - output current
* ``get_go(ports_v, device)`` - ouput conductance
* ``get_gm(ports_v, device)`` - output transconductance
* ``get_dc_guess(self, is_on)`` - guesses for OP
The device instance accesses the following attributes:
``part_id`` (a string), the device label.
"""
def __init__(self, n1, n2, sn1, sn2, model, ic=None, part_id='S'):
"""
**Parameters:**
n1 : str
Positive output node (+)
n2 : str
Negative output node (-)
sn1 : str
Positive input node (+)
sn2 : str
Negative input node (-)
model : model obj
An instance of (v)switch_model
ic : bool, optional
The initial conditions: ``True`` stands for on, ``False`` for off.
Selected methods:
- :func:`get_output_ports` -> (n1, n2)
- :func:`get_drive_ports` -> (n1, n2), (ns1, ns2)
"""
class dev_class:
pass
self.device = dev_class()
self.device.is_on = ic if ic is not None else False
self.sn1 = sn1
self.sn2 = sn2
self.n1 = n1
self.n2 = n2
self.ports = ((self.n1, self.n2), (self.sn1, self.sn2))
self.model = model
self.opdict = {}
self.opdict.update({'state': (float('nan'), float('nan'))})
self.part_id = part_id
self.is_nonlinear = True
self.is_symbolic = True
self.dc_guess = self.model.get_dc_guess(self.device.is_on)
[docs] def get_drive_ports(self, op):
"""Get the ports that drive the output ports.
**Parameters:**
op : op solution
The OP where the drive ports are used.
**Returns:**
pts : tuple of tuples of ports nodes, as: ``(port0, port1, port2 ... )``
Where each port is in the form: ``port0 = (nplus, nminus)``
"""
return self.ports
[docs] def get_output_ports(self):
"""Get the output port.
The output port is ``(n1, n2)`` for the voltage-controlled switch case.
**Returns:**
pts : tuple of tuples of ports nodes
Such as: ``(port0, port1, port2 ... )``.
Where each port is in the form: ``port0 = (nplus, nminus)``
"""
return ((self.n1, self.n2),)
def __str__(self):
rep = self.model.name + " " + str(self.device.is_on)
return rep
[docs] def i(self, op_index, ports_v, time=0):
"""Returns the current flowing in the element.
The element is assumed to be biased with the voltages
applied as specified in the ``ports_v`` vector.
**Parameters:**
op_index : int
The index of the output port for which the current is evaluated.
ports_v : tuple
A tuple constructed such as ``(voltage_across_port0, voltage_across_port1, ... )``
time : float, optional
The simulation time at which the evaluation is performed. It is
needed by time-variant elements, and it has no effect here. Set it
to ``None`` during DC analysis.
**Returns:**
i : int
The output current.
"""
ret = self.model.get_i(ports_v, self.device)
# This may be used for debugging
# print str(ports_v)+" Isw: %g\tRo: %g\tgm: %g" % (ret, 1/self.g(0,
# ports_v, 0), self.g(0, ports_v, 1))
return ret
[docs] def update_status_dictionary(self, ports_v):
"""Updates an internal dictionary that can then be used to provide
information to the user regarding the status of the element.
Normally, one would call :func:`get_op_info`.
**Returns:**
``None``.
"""
if self.opdict is None:
self.opdict = {}
if not (self.opdict['state'] == ports_v[0] and 'R' in self.opdict):
self.opdict['state'] = ports_v[0]
self.opdict['R'] = float(1.0 / self.g(0, ports_v[0], 0))
self.opdict['I'] = float(self.i(0, ports_v[0]))
self.opdict['STATUS'] = self.device.is_on
[docs] def get_op_info(self, ports_v):
"""Information regarding the Operating Point (OP)
**Parameters:**
ports_v : list of lists
The parameter is to be set to ``[[v]]``, where ``v`` is the voltage
applied to the switch terminals.
**Returns:**
op_keys : list of strings
The labels corresponding to the numeric values in ``op_info``.
op_info : list of floats
The values corresponding to ``op_keys``.
"""
self.update_status_dictionary(ports_v)
status = "ON" if self.opdict['STATUS'] else "OFF"
op_keys = ['Part ID', 'STATUS', "VO [V]", "VS [V]", u"R [\u2126]",
"I [A]"]
op_info = [self.part_id, status, float(self.opdict['state'][0]),
float(self.opdict['state'][1]), self.opdict["R"],
self.opdict['I']]
return op_keys, op_info
[docs] def g(self, op_index, ports_v, port_index, time=0):
"""Returns the differential (trans)conductance.
The transconductance is computed wrt the port specified by
``port_index`` when the element has the voltages specified in
``ports_v`` across its ports, at (simulation) ``time``.
**Parameters:**
ports_v : list
Voltages applied to the switch. The list should be in the form:
``[voltage_across_port0, voltage_across_port1, ... ]``
port_index : int
The index of the output port.
time : float
The simulation time at which the evaluation is performed. Set it to
``None`` during DC analysis.
**Returns:**
g : float
The transconductance.
"""
assert op_index == 0
assert port_index < 2
if port_index == 0:
return self.model.get_go(ports_v, self.device)
if port_index == 1:
return self.model.get_gm(ports_v, self.device)
else:
raise Exception("Unknown port index passed to switch: bug")
[docs] def get_value_function(self, identifier):
def get_value(self):
return self.opdict[identifier]
return get_value
[docs] def get_netlist_elem_line(self, nodes_dict):
"""Return a netlist line corresponding to the switch."""
return "%s %s %s %s %s %s %s" % (self.part_id, nodes_dict[self.n1],
nodes_dict[self.n2], nodes_dict[self.sn1],
nodes_dict[self.sn2], self.model.name, \
str(self.device.is_on))
VT_DEFAULT = 0.0
VH_DEFAULT = 0.0
RON_DEFAULT = 1.
ROFF_DEFAULT = 1. / options.gmin
[docs]class vswitch_model:
"""Voltage-controlled switch model.
::
sn1 o--+ +--o n1
| |
+-+ \ o
|R| \\
+-+ +
| |
sn2 o--+ +--o n2
Note that:
* R is infinite.
* The voltage needed to close the switch is:
:math:`V(s_{n1})-V(s_{n2}) > V_T+V_H`.
* To re-open it, one needs to satisfy the relationship:
:math:`V(s_{n1})-V(s_{n2}) < V_T-V_H`.
The switch commutes between two statuses:
* :math:`R_{OUT} = R_{OFF}`
* :math:`R_{OUT} = R_{ON}`
None of which can be set to zero or infinite.
The switching characteristics are modeled with :math:`tanh(x)`.
"""
def __init__(self, name, VT=None, VH=None, VON=None, VOFF=None, RON=None, ROFF=None):
self.name = name
# convert to VT and VH
if VON is not None or VOFF is not None:
VT, VH = self._get_VTVH_from_VONVOFF(float(VON), float(VOFF))
self.VT = float(VT) if VT is not None else VT_DEFAULT
self.VH = float(VH) if VH is not None else VH_DEFAULT
self.RON = float(RON) if RON is not None else RON_DEFAULT
self.ROFF = float(ROFF) if ROFF is not None else ROFF_DEFAULT
self.A = (self.RON - self.ROFF) / 2
self.B = (self.RON + self.ROFF) / 2.
self.is_on = False
self._set_status(self.is_on)
self.SLOPE = 1e2
def _get_VTVH_from_VONVOFF(self, VON, VOFF):
if VON is None or VOFF is None:
raise ValueError
VT = (VON - VOFF) / 2.0 + VOFF
return VT, VT * 1e-3
def _get_V(self, is_on):
"""Get the effective switching voltage (hyst taken into account)
"""
return self.VT + self.VH * 2 * (not is_on) - self.VH
def _set_status(self, is_on):
"""Set the switch status, which meeans setting the effective
switching voltage self.V (w hyst taken into account)
"""
self.V = self.VT + self.VH * 2 * (not is_on) - self.VH
def _update_status(self, vin, dev, debug=False):
"""Check the switch status and move to the other if needed.
"""
Vtest = self._get_V(dev.is_on)
R1 = self.A * math.tanh((vin - Vtest) * self.SLOPE) + self.B
Vtest = self._get_V(not dev.is_on)
R2 = self.A * math.tanh((vin - Vtest) * self.SLOPE) + self.B
self._set_status(dev.is_on)
if vin > self.V and not dev.is_on and R1 - R2 == 0.0:
if debug:
print("Switching ON: %g" % (vin,))
dev.is_on = True
self._set_status(dev.is_on)
if vin < self.V and dev.is_on and R1 - R2 == 0.0:
if debug:
print("Switching OFF: %g" % (vin,))
dev.is_on = False
self._set_status(dev.is_on)
self.is_on = dev.is_on
[docs] def get_dc_guess(self, is_on):
"""Returns a list of two floats to be used as initial guesses for the OP analysis
"""
return [self.VT * (.9 + is_on * .2)] * 2
[docs] def print_model(self):
"""All the internal parameters of the model get printed out,
for visual inspection.
"""
arr = []
arr.append(
[self.name, "", "", "SWITCH MODEL", "", "", "", "", "", "", "", ""])
arr.append(["VT", "[V]", self.VT, "VH", "[V]:", self.VH,
"RON", "[ohm]", self.RON, "ROFF", "[ohm]", self.ROFF])
print(printing.table(arr))
[docs] def get_i(self, xxx_todo_changeme, dev, debug=False):
"""Returns the output current.
"""
(vout, vin) = xxx_todo_changeme
self._update_status(vin, dev)
R = self.A * math.tanh((vin - self.V) * self.SLOPE) + self.B
return vout / R
[docs] def get_go(self, xxx_todo_changeme1, dev, debug=False):
"""Returns the output conductance d(I)/d(Vn1-Vn2)."""
(vout, vin) = xxx_todo_changeme1
self._update_status(vin, dev)
R = self.A * math.tanh((vin - self.V) * self.SLOPE) + self.B
return 1. / R
[docs] def get_gm(self, xxx_todo_changeme2, dev, debug=False):
"""Returns the source to output transconductance or d(I)/d(Vsn1-Vsn2)."""
(vout, vin) = xxx_todo_changeme2
self._update_status(vin, dev)
gm = self.A * self.SLOPE * (math.tanh(self.SLOPE * (self.V - vin)) ** 2 - 1) / (
self.A * math.tanh(self.SLOPE * (self.V - vin)) - self.B) ** 2
return gm + options.gmin