Ejemplo n.º 1
0
    def __init__(self, *args, **kvargs):
        if 'toolkit' in kvargs:
            self.toolkit = kvargs['toolkit']
            del kvargs['toolkit']
        else:
            self.toolkit = default_toolkit

        self.nodenames = {}

        ## Add terminal nodes
        self.add_terminals(self.terminals)

        ## Set temporary terminal mapping information for use by instantiation
        ## method in higher hierarchy
        self.terminalhook = dict(zip(self.terminals, args))

        ## Create instance parameters
        self.iparv = ParameterDict(*self.instparams)
        self.ipar = ParameterDict(*self.instparams)

        ## Subscribe to changes on ipar 
        self.ipar.attach(self, updatemethod='_ipar_changed')

        ## set instance parameters from arguments
        self.ipar.set(**kvargs)
        
        ## Subscribe to updates of instance parameters
        if hasattr(self, 'update'):
            self.iparv.attach(self)
            self.update(self.ipar)
Ejemplo n.º 2
0
    def __init__(self, *args, **kvargs):
        if 'toolkit' in kvargs:
            self.toolkit = kvargs['toolkit']
            del kvargs['toolkit']
        else:
            self.toolkit = default_toolkit

        self.nodenames = {}

        ## Add terminal nodes
        self.add_terminals(self.terminals)

        ## Set temporary terminal mapping information for use by instantiation
        ## method in higher hierarchy
        self.terminalhook = dict(zip(self.terminals, args))

        ## Create instance parameters
        self.iparv = ParameterDict(*self.instparams)
        self.ipar = ParameterDict(*self.instparams)

        ## Subscribe to changes on ipar
        self.ipar.attach(self, updatemethod='_ipar_changed')

        ## set instance parameters from arguments
        self.ipar.set(**kvargs)

        ## Subscribe to updates of instance parameters
        if hasattr(self, 'update'):
            self.iparv.attach(self)
            self.update(self.ipar)
Ejemplo n.º 3
0
class Circuit(object):
    """Basic circuit class 

    The circuit class models electric circuits but could be used for
    any conservative system. A circuit object contains a list of nodes and
    branches which are associated with node voltages and branch currents in the
    modelled system. Note that the values of these quantities are
    stored in separate analysis classes, never inside a circuit object.

    The nodes are connected to the outside through terminals. When the circuit
    is instanciated, the outside nodes are passed to the object via the 
    terminals.

    **Attributes**
        *nodes*
          A list that contains Node objects. The first k nodes are terminal 
          nodes where k is the number of terminals.

        *branches*
          list of Branch objects. The solver will solve for the 
          currents through the branches.

        *terminals*
          list of terminal names

        *instparams*
          A list of valid instance parameters (Parameter objects)

        *ipar*
          A ParameterDict containing instance specific parameters

        *nodenames*
          A dictionary that maps a local node name to the node object in
          nodes. If the node is connnected to superior hierarchy levels
          through a terminal the terminal name must be the same as the
          local node name

        *terminalhook*
          Temporary storage of information about what nodes in the superior
          hierarchy level the terminals should be connected to during 
          instantiation. It is a dictionary where the keys are terminal names
          and the values are node objects. The value is None when it is not 
          used. The only reason for this attribute is to allow for bottom-up 
          instantiations like: cir1['I1'] = R('n1', gnd)

        *linear* 
          A boolean value that is true if i(x) and q(x) are linear 
          functions

    """

    
    nodes = []
    branches = []
    terminals = []
    instparams = []
    linear = True
    
    def __init__(self, *args, **kvargs):
        if 'toolkit' in kvargs:
            self.toolkit = kvargs['toolkit']
            del kvargs['toolkit']
        else:
            self.toolkit = default_toolkit

        self.nodenames = {}

        ## Add terminal nodes
        self.add_terminals(self.terminals)

        ## Set temporary terminal mapping information for use by instantiation
        ## method in higher hierarchy
        self.terminalhook = dict(zip(self.terminals, args))

        ## Create instance parameters
        self.iparv = ParameterDict(*self.instparams)
        self.ipar = ParameterDict(*self.instparams)

        ## Subscribe to changes on ipar 
        self.ipar.attach(self, updatemethod='_ipar_changed')

        ## set instance parameters from arguments
        self.ipar.set(**kvargs)
        
        ## Subscribe to updates of instance parameters
        if hasattr(self, 'update'):
            self.iparv.attach(self)
            self.update(self.ipar)

    def __eq__(self, a):
        return self.__class__ == a.__class__ and \
            self.nodes == a.nodes and \
            self.nodenames == a.nodenames and self.branches == a.branches and \
            self.iparv == a.iparv
        
    def __copy__(self):
        newc = self.__class__()
        newc.toolkit = self.toolkit
        newc.nodes = copy(self.nodes)    
        newc.nodenames = copy(self.nodenames)    
        newc.branches = copy(self.branches)    
        newc.instparams = copy(self.instparams)
        newc.ipar = copy(self.ipar)        
        newc.ipar.detach(self)
        newc.ipar.attach(newc, updatemethod='_ipar_changed')
        newc.iparv = copy(self.iparv)
        if hasattr(newc, 'update'):
            newc.iparv.detach(self)
            newc.iparv.attach(newc)
            newc.update(newc.ipar)
        newc.linear = copy(self.linear)        
        newc.terminals = copy(self.terminals)
        return newc

    def _ipar_changed(self, subject):
        self.update_iparv(ignore_errors=True)

    def add_nodes(self, *names):
        """Create internal nodes in the circuit and return the new nodes

        >>> c = Circuit()
        >>> n1, n2 = c.add_nodes("n1", "n2")
        >>> c.nodes
        [Node('n1'), Node('n2')]
        >>> 'n1' in c.nodenames and 'n2' in c.nodenames
        True
        
        """
        newnodes = []
        for name in names:
            newnodes.append(Node(name))
            self.append_node(newnodes[-1])

        return tuple(newnodes)

    def add_node(self, name):
        """Create and internal node in the circuit and return the new node"""
        return self.add_nodes(name)[0]

    def append_node(self, node):
        """Append node object to circuit"""
        ## Make a copy of node list so the class is unchanged
        if self.__class__.nodes is self.nodes:
            self.nodes = list(self.nodes)

        if node not in self.nodes:
            self.nodes.append(node)
        self.nodenames[node.name] = node

    def append_branches(self, *branches):
        """Append node object to circuit"""
        ## Make a copy of branch list so the class is unchanged
        if self.__class__.branches is self.branches:
            self.branches = list(self.branches)

        self.branches.extend(branches)

    def get_terminal_branch(self, terminalname):
        """Find the branch that is connected to the given terminal

        If no branch is found or if there are more branches than one, None is 
        returned

        Returns
        -------
        Tuple of Branch object and an integer indicating if terminal is 
        connected to the positive or negative side of the branch. 
        1 == positive side, -1 == negative side
        
        >>> from elements import *
        >>> net1 = Node('net1')
        >>> net2 = Node('net2')
        >>> VS = VS(net1, net2)
        >>> VS.get_terminal_branch("minus")
        (Branch(Node('plus'),Node('minus')), -1)
        
        """
        plusbranches = [] ## Branches with positive side connected to the
                          ## terminal
        minusbranches = [] ## Branches with negative side connected to the
                           ## terminal
        for branch in self.branches:
            if branch.plus == self.nodenames[terminalname]:
                plusbranches.append(branch)
            elif branch.minus == self.nodenames[terminalname]:
                minusbranches.append(branch)

        if len(plusbranches + minusbranches) != 1:
            return None
        elif len(plusbranches) == 1:
            return plusbranches[0], 1
        elif len(minusbranches) == 1:
            return minusbranches[0], -1            

    def get_node_index(self, node, refnode=None):
        """Get row in the x vector of a node instance

           If the refnode argument is given the reference node
           is assumed to be removed
        """

        if not isinstance(node, Node):
            node = Node(str(node))
        if refnode and not isinstance(refnode, Node):
            refnode = Node(str(refnode))

        if node in self.nodes:
            index = self.nodes.index(node)
            if refnode != None:
                irefnode = self.nodes.index(refnode)
                if index == irefnode:
                    return None
                if index > irefnode:
                    return index - 1
            return index
        else:
            raise ValueError('Node %s is not in circuit node list (%s)'%
                             (str(node), str(self.nodes)))

    def get_branch_index(self, branch):
        """Get row in the x vector of a branch instance"""
        if branch in self.branches:
            return len(self.nodes) + self.branches.index(branch)
        else:
            raise ValueError('Branch %s is not present in circuit (%s)'%
                             (str(branch), str(self.branches)))

    def get_node(self, name):
        """Find a node by name.
        
        >>> c = Circuit()
        >>> n1 = c.add_node("n1")
        >>> c.get_node('n1')
        Node('n1')
        
        """
        return self.nodenames[name]

    def get_node_name(self, node):
        """Find the name of a node
        
        >>> c = Circuit()
        >>> n1 = c.add_node("n1")
        >>> c.get_node_name(n1)
        'n1'

        """
        for k, v in self.nodenames.items():
            if v == node:
                return k

    def add_terminals(self, terminals):
        """Add terminals to circuit 

        >>> c = Circuit()
        >>> c.add_terminals(["n1"])
        >>> c.terminals
        ['n1']

        """

        if self.__class__.terminals is self.terminals:
            self.terminals = list(self.terminals)

        for terminal in terminals:
            # add terminal to terminal list if it is not included
            if terminal not in self.terminals:
                self.terminals.append(terminal)

            ## If no node with terminal name exists create node
            if not self.nodenames.has_key(terminal):                
                self.add_node(terminal) 

            node = self.nodenames[terminal]

            ## move node to position k in nodes as it is
            ## now a terminal node
            if not self.nodes.index(node) < self._nterminalnodes:
                self.nodes.remove(node)
                self.nodes.insert(self._nterminalnodes-1, node)
  
    def connect_terminals(self, **kvargs):
        """Connect nodes to terminals by using keyword arguments

        """
        for terminal, node in kvargs.items():
            ## Sanity check
            if type(terminal) is not types.StringType:
                raise Exception("%s should be string"%str(terminal))
            if terminal not in self.terminals:
                raise ValueError('terminal '+str(terminal)+' is not defined')
            
            if not isinstance(node, Node):
                node = Node(str(node))
            
            if terminal in self.nodenames:
                oldterminalnode = self.nodenames[terminal]
                if oldterminalnode != node:
                    self.nodes.remove(self.nodenames[terminal])
            
            if node not in self.nodes:
                self.nodes.insert(self._nterminalnodes, node)
            
            self.nodenames[terminal] = node            
            
    def save_current(self, terminal):
        """Returns a circuit where a current probe is added at a terminal
        
        >>> from elements import *
        >>> import numpy as np
        >>> cir = R(Node('n1'), gnd, r=1e3)
        >>> newcir = cir.save_current('plus')
        >>> newcir.G(np.zeros(4))
        array([[ 0.   ,  0.   ,  0.   ,  1.   ],
               [ 0.   ,  0.001, -0.001,  0.   ],
               [ 0.   , -0.001,  0.001, -1.   ],
               [ 1.   ,  0.   , -1.   ,  0.   ]])
        """
        
        if self.get_terminal_branch(terminal) == None:
            return ProbeWrapper(self, terminals = (terminal,))
        else:
            return self            

    @property
    def n(self):
        """Return size of x vector"""
        return len(self.nodes) + len(self.branches)

    def terminal_nodes(self):
        """Return a list of all terminal nodes"""
        return self.nodes[0:self._nterminalnodes]

    def non_terminal_nodes(self, instancename = None):
        """Return a list of all non-terminal nodes. 

        If the instancename is set, the local nodes
        will have a instancename<dot> prefix added to the node name

        """
        if instancename == None:
            return self.nodes[self._nterminalnodes:]
        else:
            result = []
            for node in self.nodes[len(self.terminals):]:
                if node.isglobal:
                    result.append(node)
                else:
                    result.append(Node(instancename + '.' + node.name))
            return result

    def G(self, x, epar=defaultepar):
        """Calculate the G (trans)conductance matrix given the x-vector"""
        return self.toolkit.zeros((self.n, self.n))

    def C(self, x, epar=defaultepar):
        """Calculate the C (transcapacitance) matrix given the x-vector"""
        return self.toolkit.zeros((self.n, self.n))

    def u(self, t=0.0, epar=defaultepar, analysis=None):
        """Calculate the u column-vector of the circuit at time t

        Arguments
        ---------

        epar -- ParameterDict with environment parameters such as temperature
        analysis -- This argument gives the possibility to have analysis 
                    dependent sources.
                    for normal time dependent and dc sources this argument 
                    should be None
        
        """
        return self.toolkit.zeros(self.n)

    def i(self, x, epar=defaultepar):
        """Calculate the i vector as a function of the x-vector

        For linear circuits i(x(t)) = G*x
        """
        return self.toolkit.dot(self.G(x), x)

    def q(self, x, epar=defaultepar):
        """Calculate the q vector as a function of the x-vector

        For linear circuits q(x(t)) = C*x
        """
        return self.toolkit.dot(self.C(x), x)

    def CY(self, x, w, epar=defaultepar):
        """Calculate the noise sources correlation matrix

        Arguments
        ---------
        x -- (numpy array) the state vector
        w -- Angular frequency
        epar -- (ParameterDict) Environment parameters

        """
        return self.toolkit.zeros((self.n, self.n))

    def next_event(self, t):
        """Returns the time of the next event given the current time t"""
        return inf
    
    def name_state_vector(self, x, analysis=''):
        """Return a dictionary of the x-vector keyed by node and branch names

        >>> from elements import *
        >>> import numpy as np
        >>> c = SubCircuit()
        >>> n1 = c.add_node('net1')
        >>> c['is'] = IS(gnd, n1, i=1e-3)
        >>> c['R'] = R(n1, gnd, r=1e3)
        >>> c.name_state_vector(np.array([[1.0]]))
        {'net1': 1.0}

        >>> 

        """
        result = {}
        for xvalue, node in zip(x[:len(self.nodes)][0], self.nodes):
            result[self.get_node_name(node)] = xvalue

        nnodes = len(self.nodes)
        for i, xvalue, branch in enumerate(zip(x[nnodes:], self.branches)):
            result['i' + analysis + str(i) + ')'] = xvalue            

        return result

    def stamp_v(self, x, value, nodep, noden=None, refnode=None):
        """Stamp value in vector such that x[nodep] += value, x[noden] -= value
        
        If refnode is not None the reference node is assumed to be removed
        from vector
        """
        x[self.get_node_index(nodep, refnode)] += value
        x[self.get_node_index(noden, refnode)] -= value

    def remove_refnode(self, matrices, refnode):
        """Remove refnode from vectors or matrices"""
        n = self.get_node_index(refnode)
        result = []
        
        for A in matrices:
            for axis in range(len(A.shape)):
                A=self.toolkit.delete(A, [n], axis=axis)
            result.append(A)
        return tuple(result)

    def extract_v(self, x, nodep, noden=None, refnode=gnd, 
                  refnode_removed=False):
        """Extract voltage between nodep and noden from the given x-vector.

        If noden is not given the voltage is taken between nodep and refnode. 
        x-vectors with the reference node removed can be handled by setting 
        the refnode_removed to True.

        *x*
          x-vector

        *nodep*
          Node object or node reference in text format of positive node

        *noden*
          Node object or node reference in text format of negative node

        *refnode*
          reference node

        *refnode_removed*
          If set the refernce node is expected to be removed from the x-vector

        >>> from elements import *
        >>> import numpy as np
        >>> c = SubCircuit()
        >>> n1, n2 = c.add_nodes('n1','n2')
        >>> c['R1'] = R(n1, n2, r=1e3)
        >>> c['R2'] = R(n2, gnd, r=1e3)
        >>> c.extract_v(np.array([1.0, 0.5, 0.0]), 'n1', 'n2')
        0.5
        >>> c.extract_v(np.array([1.0, 0.5, 0.0]), c.nodes[0])
        1.0
        >>> c.extract_v(np.array([1.0, 0.5]), c.nodes[0], refnode_removed = True)
        1.0
        
        """
        v = []
        for node in nodep, noden:
            if type(node) is types.StringType:
                node = self.get_node(node)
            elif node == None:
                node = refnode

            if refnode_removed:
                nodeindex = self.get_node_index(node, refnode)
            else:
                nodeindex = self.get_node_index(node, None)

            if nodeindex == None: ## When node == refnode
                v.append(0)
                continue
                    
            v.append(x[nodeindex])

        return v[0] - v[1]

        

    def extract_i(self, x, branch_or_term, xdot = None,
                  refnode = gnd, refnode_removed = False,
                  t = 0,
                  linearized = False, xdcop = None):
        """Extract branch or terminal current from the given x-vector.

        *x* 
           x-vector

        *branch_or_term*
           Branch object or terminal name

        *xdot*
           dx/dt vector. this is needed if dx/dt is non-zero and there is no branch defined at the
           terminal

        *refnode*
           reference node

        *refnode_removed*
           If set the refernce node is expected to be removed from the x-vector

        *t*
           Time when the sources are to be evaluated
        
        *linearized*
           Set to True if the AC current is wanted

        *xdcop*
           *xcdop* is the DC operation point x-vector if linearized == True

        >>> from elements import *
        >>> import numpy as np
        >>> c = SubCircuit()
        >>> net1 = c.add_node('net1')
        >>> c['vs'] = VS(net1, gnd)
        >>> c.extract_i(np.array([1.0, 0, -1e-3]), 'vs.minus')
        0.001
        >>> c.extract_i(np.array([1.0, -1e-3]), 'vs.minus', refnode_removed = True)
        0.001
        
        """
        dot = self.toolkit.dot
        
        if type(branch_or_term) is types.StringType:
            ## Calculate current going in to the terminal as
            ## self.i(x)[terminal_node] + u(t) + dq(x)/dt. 
            ## This will work since i(x) returns
            ## the sum of all the currents going out from the
            ## terminal node that originates from devices within
            ## the circuit. According to Kirchoff's current law
            ## of the terminal node
            ## -I_external + sum(I_internal_k) = 0
            ## Where I_external represents the current coming from
            ## outside the circuit going *in* to the terminal node,
            ## I_internal_k represents one of the currents that flows
            ## from the terminal node to a device within the circuit.
            ## So now we can calculate I_external as 
            ## I_external = sum(I_internal_k) = 
            ## self.I(x)[terminal_node] + u(t) + dq(x)/dt =
            ## self.I(x)[terminal_node] + u(t) + sum(dq(x)/dx_k * dx_k/dt) =
            ## self.I(x)[terminal_node] + u(t) + C(x) * dx/dt

            branch_sign = self.get_terminal_branch(branch_or_term)

            if branch_sign != None:
                branch, sign = branch_sign
            else:
                terminal_node = self.nodenames[branch_or_term]
                
                terminal_node_index = self.get_node_index(terminal_node)

                if xdot != None:
                    if linearized:
                        return dot(self.G(xdcop)[terminal_node_index], x) + \
                            dot(self.C(xdcop)[terminal_node_index], xdot) + \
                            self.u(t, analysis = 'ac')[terminal_node_index]

                    else:
                        return self.i(x)[terminal_node_index] + \
                            dot(self.C(x)[terminal_node_index], xdot) + \
                            self.u(t)[terminal_node_index]
                else:
                    if linearized:
                        return dot(self.G(xdcop)[terminal_node_index], x) + \
                            self.u(t, analysis = 'ac')[terminal_node_index]

                    else:
                        return self.i(x)[terminal_node_index] + \
                            self.u(t)[terminal_node_index]

        else:
            branch = branch_or_term
            sign = 1

        branchindex = self.get_branch_index(branch)

        if refnode_removed:
            branchindex -= 1

        return sign * x[branchindex]      

    def update_iparv(self, parent_ipar=None, globalparams=None,
                     ignore_errors=False):
        """Calculate numeric values of instance parameters"""
        
        substvalues = tuple(p for p in (globalparams, parent_ipar) if p)
            
        newipar = self.ipar.eval_expressions(substvalues, 
                                             ignore_errors=ignore_errors)

        self.iparv.update_values(newipar)

    def __repr__(self):
        return self.__class__.__name__ + \
               '(' + \
               ','.join([repr(self.nodenames[term].name) for term in self.terminals] +
                        ['%s=%s'%(par.name, self.ipar.get(par)) 
                         for par in self.ipar.parameters]) + ')'

    def _instance_nodes(self, instancenodes, instance, instancename):
        """Return circuit nodes from instance nodes
        """
        for instancenode in instancenodes:
            if instancenode.isglobal:
                yield instancenode
            elif instancenode.name in instance.terminals:
                terminal = instancenode.name
                yield self.term_node_map[instancename][terminal]
            else:
                yield Node(instancename + '.' + instancenode.name)

    def _instance_branches(self, instance, instancename, 
                           instancebranches = None):
        """Return circuit branches from instance branches
        """
        if instancebranches == None:
            instancebranches = instance.branches

        for instancebranch in instancebranches:
            plus, minus = self._instance_nodes([instancebranch.plus, 
                                                instancebranch.minus],
                                               instance, instancename)
            yield Branch(plus,minus)

    @property
    def _nterminalnodes(self):
        """Return number of terminal nodes"""
        return len(self.terminals)
Ejemplo n.º 4
0
class Circuit(object):
    """Basic circuit class 

    The circuit class models electric circuits but could be used for
    any conservative system. A circuit object contains a list of nodes and
    branches which are associated with node voltages and branch currents in the
    modelled system. Note that the values of these quantities are
    stored in separate analysis classes, never inside a circuit object.

    The nodes are connected to the outside through terminals. When the circuit
    is instanciated, the outside nodes are passed to the object via the 
    terminals.

    **Attributes**
        *nodes*
          A list that contains Node objects. The first k nodes are terminal 
          nodes where k is the number of terminals.

        *branches*
          list of Branch objects. The solver will solve for the 
          currents through the branches.

        *terminals*
          list of terminal names

        *instparams*
          A list of valid instance parameters (Parameter objects)

        *ipar*
          A ParameterDict containing instance specific parameters

        *nodenames*
          A dictionary that maps a local node name to the node object in
          nodes. If the node is connnected to superior hierarchy levels
          through a terminal the terminal name must be the same as the
          local node name

        *terminalhook*
          Temporary storage of information about what nodes in the superior
          hierarchy level the terminals should be connected to during 
          instantiation. It is a dictionary where the keys are terminal names
          and the values are node objects. The value is None when it is not 
          used. The only reason for this attribute is to allow for bottom-up 
          instantiations like: cir1['I1'] = R('n1', gnd)

        *linear* 
          A boolean value that is true if i(x) and q(x) are linear 
          functions

    """

    nodes = []
    branches = []
    terminals = []
    instparams = []
    linear = True

    def __init__(self, *args, **kvargs):
        if 'toolkit' in kvargs:
            self.toolkit = kvargs['toolkit']
            del kvargs['toolkit']
        else:
            self.toolkit = default_toolkit

        self.nodenames = {}

        ## Add terminal nodes
        self.add_terminals(self.terminals)

        ## Set temporary terminal mapping information for use by instantiation
        ## method in higher hierarchy
        self.terminalhook = dict(zip(self.terminals, args))

        ## Create instance parameters
        self.iparv = ParameterDict(*self.instparams)
        self.ipar = ParameterDict(*self.instparams)

        ## Subscribe to changes on ipar
        self.ipar.attach(self, updatemethod='_ipar_changed')

        ## set instance parameters from arguments
        self.ipar.set(**kvargs)

        ## Subscribe to updates of instance parameters
        if hasattr(self, 'update'):
            self.iparv.attach(self)
            self.update(self.ipar)

    def __eq__(self, a):
        return self.__class__ == a.__class__ and \
            self.nodes == a.nodes and \
            self.nodenames == a.nodenames and self.branches == a.branches and \
            self.iparv == a.iparv

    def __copy__(self):
        newc = self.__class__()
        newc.toolkit = self.toolkit
        newc.nodes = copy(self.nodes)
        newc.nodenames = copy(self.nodenames)
        newc.branches = copy(self.branches)
        newc.instparams = copy(self.instparams)
        newc.ipar = copy(self.ipar)
        newc.ipar.detach(self)
        newc.ipar.attach(newc, updatemethod='_ipar_changed')
        newc.iparv = copy(self.iparv)
        if hasattr(newc, 'update'):
            newc.iparv.detach(self)
            newc.iparv.attach(newc)
            newc.update(newc.ipar)
        newc.linear = copy(self.linear)
        newc.terminals = copy(self.terminals)
        return newc

    def _ipar_changed(self, subject):
        self.update_iparv(ignore_errors=True)

    def add_nodes(self, *names):
        """Create internal nodes in the circuit and return the new nodes

        >>> c = Circuit()
        >>> n1, n2 = c.add_nodes("n1", "n2")
        >>> c.nodes
        [Node('n1'), Node('n2')]
        >>> 'n1' in c.nodenames and 'n2' in c.nodenames
        True
        
        """
        newnodes = []
        for name in names:
            newnodes.append(Node(name))
            self.append_node(newnodes[-1])

        return tuple(newnodes)

    def add_node(self, name):
        """Create and internal node in the circuit and return the new node"""
        return self.add_nodes(name)[0]

    def append_node(self, node):
        """Append node object to circuit"""
        ## Make a copy of node list so the class is unchanged
        if self.__class__.nodes is self.nodes:
            self.nodes = list(self.nodes)

        if node not in self.nodes:
            self.nodes.append(node)
        self.nodenames[node.name] = node

    def append_branches(self, *branches):
        """Append node object to circuit"""
        ## Make a copy of branch list so the class is unchanged
        if self.__class__.branches is self.branches:
            self.branches = list(self.branches)

        self.branches.extend(branches)

    def get_terminal_branch(self, terminalname):
        """Find the branch that is connected to the given terminal

        If no branch is found or if there are more branches than one, None is 
        returned

        Returns
        -------
        Tuple of Branch object and an integer indicating if terminal is 
        connected to the positive or negative side of the branch. 
        1 == positive side, -1 == negative side
        
        >>> from elements import *
        >>> net1 = Node('net1')
        >>> net2 = Node('net2')
        >>> VS = VS(net1, net2)
        >>> VS.get_terminal_branch("minus")
        (Branch(Node('plus'),Node('minus')), -1)
        
        """
        plusbranches = []  ## Branches with positive side connected to the
        ## terminal
        minusbranches = []  ## Branches with negative side connected to the
        ## terminal
        for branch in self.branches:
            if branch.plus == self.nodenames[terminalname]:
                plusbranches.append(branch)
            elif branch.minus == self.nodenames[terminalname]:
                minusbranches.append(branch)

        if len(plusbranches + minusbranches) != 1:
            return None
        elif len(plusbranches) == 1:
            return plusbranches[0], 1
        elif len(minusbranches) == 1:
            return minusbranches[0], -1

    def get_node_index(self, node, refnode=None):
        """Get row in the x vector of a node instance

           If the refnode argument is given the reference node
           is assumed to be removed
        """

        if not isinstance(node, Node):
            node = Node(str(node))
        if refnode and not isinstance(refnode, Node):
            refnode = Node(str(refnode))

        if node in self.nodes:
            index = self.nodes.index(node)
            if refnode is not None:
                irefnode = self.nodes.index(refnode)
                if index == irefnode:
                    return None
                if index > irefnode:
                    return index - 1
            return index
        else:
            raise ValueError('Node %s is not in circuit node list (%s)' %
                             (str(node), str(self.nodes)))

    def get_branch_index(self, branch):
        """Get row in the x vector of a branch instance"""
        if branch in self.branches:
            return len(self.nodes) + self.branches.index(branch)
        else:
            raise ValueError('Branch %s is not present in circuit (%s)' %
                             (str(branch), str(self.branches)))

    def get_node(self, name):
        """Find a node by name.
        
        >>> c = Circuit()
        >>> n1 = c.add_node("n1")
        >>> c.get_node('n1')
        Node('n1')
        
        """
        return self.nodenames[name]

    def get_node_name(self, node):
        """Find the name of a node
        
        >>> c = Circuit()
        >>> n1 = c.add_node("n1")
        >>> c.get_node_name(n1)
        'n1'

        """
        for k, v in self.nodenames.items():
            if v == node:
                return k

    def add_terminals(self, terminals):
        """Add terminals to circuit 

        >>> c = Circuit()
        >>> c.add_terminals(["n1"])
        >>> c.terminals
        ['n1']

        """

        if self.__class__.terminals is self.terminals:
            self.terminals = list(self.terminals)

        for terminal in terminals:
            # add terminal to terminal list if it is not included
            if terminal not in self.terminals:
                self.terminals.append(terminal)

            ## If no node with terminal name exists create node
            if not self.nodenames.has_key(terminal):
                self.add_node(terminal)

            node = self.nodenames[terminal]

            ## move node to position k in nodes as it is
            ## now a terminal node
            if not self.nodes.index(node) < self._nterminalnodes:
                self.nodes.remove(node)
                self.nodes.insert(self._nterminalnodes - 1, node)

    def connect_terminals(self, **kvargs):
        """Connect nodes to terminals by using keyword arguments

        """
        for terminal, node in kvargs.items():
            ## Sanity check
            if type(terminal) is not types.StringType:
                raise Exception("%s should be string" % str(terminal))
            if terminal not in self.terminals:
                raise ValueError('terminal ' + str(terminal) +
                                 ' is not defined')

            if not isinstance(node, Node):
                node = Node(str(node))

            if terminal in self.nodenames:
                oldterminalnode = self.nodenames[terminal]
                if oldterminalnode != node:
                    self.nodes.remove(self.nodenames[terminal])

            if node not in self.nodes:
                self.nodes.insert(self._nterminalnodes, node)

            self.nodenames[terminal] = node

    def save_current(self, terminal):
        """Returns a circuit where a current probe is added at a terminal
        
        >>> from elements import *
        >>> import numpy as np
        >>> cir = R(Node('n1'), gnd, r=1e3)
        >>> newcir = cir.save_current('plus')
        >>> newcir.G(np.zeros(4))
        array([[ 0.   ,  0.   ,  0.   ,  1.   ],
               [ 0.   ,  0.001, -0.001,  0.   ],
               [ 0.   , -0.001,  0.001, -1.   ],
               [ 1.   ,  0.   , -1.   ,  0.   ]])
        """

        if self.get_terminal_branch(terminal) is None:
            return ProbeWrapper(self, terminals=(terminal, ))
        else:
            return self

    @property
    def n(self):
        """Return size of x vector"""
        return len(self.nodes) + len(self.branches)

    def terminal_nodes(self):
        """Return a list of all terminal nodes"""
        return self.nodes[0:self._nterminalnodes]

    def non_terminal_nodes(self, instancename=None):
        """Return a list of all non-terminal nodes. 

        If the instancename is set, the local nodes
        will have a instancename<dot> prefix added to the node name

        """
        if instancename is None:
            return self.nodes[self._nterminalnodes:]
        else:
            result = []
            for node in self.nodes[len(self.terminals):]:
                if node.isglobal:
                    result.append(node)
                else:
                    result.append(Node(instancename + '.' + node.name))
            return result

    def G(self, x, epar=defaultepar):
        """Calculate the G (trans)conductance matrix given the x-vector"""
        return self.toolkit.zeros((self.n, self.n))

    def C(self, x, epar=defaultepar):
        """Calculate the C (transcapacitance) matrix given the x-vector"""
        return self.toolkit.zeros((self.n, self.n))

    def u(self, t=0.0, epar=defaultepar, analysis=None):
        """Calculate the u column-vector of the circuit at time t

        Arguments
        ---------

        epar -- ParameterDict with environment parameters such as temperature
        analysis -- This argument gives the possibility to have analysis 
                    dependent sources.
                    for normal time dependent and dc sources this argument 
                    should be None
        
        """
        return self.toolkit.zeros(self.n)

    def i(self, x, epar=defaultepar):
        """Calculate the i vector as a function of the x-vector

        For linear circuits i(x(t)) = G*x
        """
        return self.toolkit.dot(self.G(x), x)

    def q(self, x, epar=defaultepar):
        """Calculate the q vector as a function of the x-vector

        For linear circuits q(x(t)) = C*x
        """
        return self.toolkit.dot(self.C(x), x)

    def CY(self, x, w, epar=defaultepar):
        """Calculate the noise sources correlation matrix

        Arguments
        ---------
        x -- (numpy array) the state vector
        w -- Angular frequency
        epar -- (ParameterDict) Environment parameters

        """
        return self.toolkit.zeros((self.n, self.n))

    def next_event(self, t):
        """Returns the time of the next event given the current time t"""
        return inf

    def name_state_vector(self, x, analysis=''):
        """Return a dictionary of the x-vector keyed by node and branch names

        >>> from elements import *
        >>> import numpy as np
        >>> c = SubCircuit()
        >>> n1 = c.add_node('net1')
        >>> c['is'] = IS(gnd, n1, i=1e-3)
        >>> c['R'] = R(n1, gnd, r=1e3)
        >>> c.name_state_vector(np.array([[1.0]]))
        {'net1': 1.0}

        >>> 

        """
        result = {}
        for xvalue, node in zip(x[:len(self.nodes)][0], self.nodes):
            result[self.get_node_name(node)] = xvalue

        nnodes = len(self.nodes)
        for i, xvalue, branch in enumerate(zip(x[nnodes:], self.branches)):
            result['i' + analysis + str(i) + ')'] = xvalue

        return result

    def stamp_v(self, x, value, nodep, noden=None, refnode=None):
        """Stamp value in vector such that x[nodep] += value, x[noden] -= value
        
        If refnode is not None the reference node is assumed to be removed
        from vector
        """
        x[self.get_node_index(nodep, refnode)] += value
        x[self.get_node_index(noden, refnode)] -= value

    def remove_refnode(self, matrices, refnode):
        """Remove refnode from vectors or matrices"""
        n = self.get_node_index(refnode)
        result = []

        for A in matrices:
            for axis in range(len(A.shape)):
                A = self.toolkit.delete(A, [n], axis=axis)
            result.append(A)
        return tuple(result)

    def extract_v(self,
                  x,
                  nodep,
                  noden=None,
                  refnode=gnd,
                  refnode_removed=False):
        """Extract voltage between nodep and noden from the given x-vector.

        If noden is not given the voltage is taken between nodep and refnode. 
        x-vectors with the reference node removed can be handled by setting 
        the refnode_removed to True.

        *x*
          x-vector

        *nodep*
          Node object or node reference in text format of positive node

        *noden*
          Node object or node reference in text format of negative node

        *refnode*
          reference node

        *refnode_removed*
          If set the refernce node is expected to be removed from the x-vector

        >>> from elements import *
        >>> import numpy as np
        >>> c = SubCircuit()
        >>> n1, n2 = c.add_nodes('n1','n2')
        >>> c['R1'] = R(n1, n2, r=1e3)
        >>> c['R2'] = R(n2, gnd, r=1e3)
        >>> c.extract_v(np.array([1.0, 0.5, 0.0]), 'n1', 'n2')
        0.5
        >>> c.extract_v(np.array([1.0, 0.5, 0.0]), c.nodes[0])
        1.0
        >>> c.extract_v(np.array([1.0, 0.5]), c.nodes[0], refnode_removed = True)
        1.0
        
        """
        v = []
        for node in nodep, noden:
            if type(node) is types.StringType:
                node = self.get_node(node)
            elif node is None:
                node = refnode

            if refnode_removed:
                nodeindex = self.get_node_index(node, refnode)
            else:
                nodeindex = self.get_node_index(node, None)

            if nodeindex is None:  ## When node == refnode
                v.append(0)
                continue

            v.append(x[nodeindex])

        return v[0] - v[1]

    def extract_i(self,
                  x,
                  branch_or_term,
                  xdot=None,
                  refnode=gnd,
                  refnode_removed=False,
                  t=0,
                  linearized=False,
                  xdcop=None):
        """Extract branch or terminal current from the given x-vector.

        *x* 
           x-vector

        *branch_or_term*
           Branch object or terminal name

        *xdot*
           dx/dt vector. this is needed if dx/dt is non-zero and there is no branch defined at the
           terminal

        *refnode*
           reference node

        *refnode_removed*
           If set the refernce node is expected to be removed from the x-vector

        *t*
           Time when the sources are to be evaluated
        
        *linearized*
           Set to True if the AC current is wanted

        *xdcop*
           *xcdop* is the DC operation point x-vector if linearized == True

        >>> from elements import *
        >>> import numpy as np
        >>> c = SubCircuit()
        >>> net1 = c.add_node('net1')
        >>> c['vs'] = VS(net1, gnd)
        >>> c.extract_i(np.array([1.0, 0, -1e-3]), 'vs.minus')
        0.001
        >>> c.extract_i(np.array([1.0, -1e-3]), 'vs.minus', refnode_removed = True)
        0.001
        
        """
        dot = self.toolkit.dot

        if type(branch_or_term) is types.StringType:
            ## Calculate current going in to the terminal as
            ## self.i(x)[terminal_node] + u(t) + dq(x)/dt.
            ## This will work since i(x) returns
            ## the sum of all the currents going out from the
            ## terminal node that originates from devices within
            ## the circuit. According to Kirchoff's current law
            ## of the terminal node
            ## -I_external + sum(I_internal_k) = 0
            ## Where I_external represents the current coming from
            ## outside the circuit going *in* to the terminal node,
            ## I_internal_k represents one of the currents that flows
            ## from the terminal node to a device within the circuit.
            ## So now we can calculate I_external as
            ## I_external = sum(I_internal_k) =
            ## self.I(x)[terminal_node] + u(t) + dq(x)/dt =
            ## self.I(x)[terminal_node] + u(t) + sum(dq(x)/dx_k * dx_k/dt) =
            ## self.I(x)[terminal_node] + u(t) + C(x) * dx/dt

            branch_sign = self.get_terminal_branch(branch_or_term)

            if branch_sign is not None:
                branch, sign = branch_sign
            else:
                terminal_node = self.nodenames[branch_or_term]

                terminal_node_index = self.get_node_index(terminal_node)

                if xdot is not None:
                    if linearized:
                        return dot(self.G(xdcop)[terminal_node_index], x) + \
                            dot(self.C(xdcop)[terminal_node_index], xdot) + \
                            self.u(t, analysis = 'ac')[terminal_node_index]

                    else:
                        return self.i(x)[terminal_node_index] + \
                            dot(self.C(x)[terminal_node_index], xdot) + \
                            self.u(t)[terminal_node_index]
                else:
                    if linearized:
                        return dot(self.G(xdcop)[terminal_node_index], x) + \
                            self.u(t, analysis = 'ac')[terminal_node_index]

                    else:
                        return self.i(x)[terminal_node_index] + \
                            self.u(t)[terminal_node_index]

        else:
            branch = branch_or_term
            sign = 1

        branchindex = self.get_branch_index(branch)

        if refnode_removed:
            branchindex -= 1

        return sign * x[branchindex]

    def update_iparv(self,
                     parent_ipar=None,
                     globalparams=None,
                     ignore_errors=False):
        """Calculate numeric values of instance parameters"""

        substvalues = tuple(p for p in (globalparams, parent_ipar) if p)

        newipar = self.ipar.eval_expressions(substvalues,
                                             ignore_errors=ignore_errors)

        self.iparv.update_values(newipar)

    def __repr__(self):
        return self.__class__.__name__ + \
               '(' + \
               ','.join([repr(self.nodenames[term].name) for term in self.terminals] +
                        ['%s=%s'%(par.name, self.ipar.get(par))
                         for par in self.ipar.parameters]) + ')'

    def _instance_nodes(self, instancenodes, instance, instancename):
        """Return circuit nodes from instance nodes
        """
        for instancenode in instancenodes:
            if instancenode.isglobal:
                yield instancenode
            elif instancenode.name in instance.terminals:
                terminal = instancenode.name
                yield self.term_node_map[instancename][terminal]
            else:
                yield Node(instancename + '.' + instancenode.name)

    def _instance_branches(self,
                           instance,
                           instancename,
                           instancebranches=None):
        """Return circuit branches from instance branches
        """
        if instancebranches is None:
            instancebranches = instance.branches

        for instancebranch in instancebranches:
            plus, minus = self._instance_nodes(
                [instancebranch.plus, instancebranch.minus], instance,
                instancename)
            yield Branch(plus, minus)

    @property
    def _nterminalnodes(self):
        """Return number of terminal nodes"""
        return len(self.terminals)
Ejemplo n.º 5
0
    @property
    def V(self):
        return Quantity('V', self)

    @property
    def I(self):
        return Quantity('I', self)

    def __repr__(self):
        return 'Branch(' + repr(self.plus) + ',' + repr(self.minus) + ')'


### Default reference node
gnd = Node("gnd", isglobal=True)

defaultepar = ParameterDict(
    Parameter("T", "Temperature", unit="K", default=300))


class Circuit(object):
    """Basic circuit class 

    The circuit class models electric circuits but could be used for
    any conservative system. A circuit object contains a list of nodes and
    branches which are associated with node voltages and branch currents in the
    modelled system. Note that the values of these quantities are
    stored in separate analysis classes, never inside a circuit object.

    The nodes are connected to the outside through terminals. When the circuit
    is instanciated, the outside nodes are passed to the object via the 
    terminals.
Ejemplo n.º 6
0
 def __init__(self, sim, **parvalues):
     self.sim = sim
     self.par = ParameterDict(*self.parameters, **parvalues)