Source code for maboss.network

"""Definitions of the different classes provided to the user."""

from __future__ import print_function
import collections
from . import logic
from sys import stderr, stdout, version_info


class Node(object):
    """
    Represent a node of a boolean network.

     .. py:attribute:: name

       the name of the node

     .. py:attribute:: rt_up

        A string, an expression that will be evaluated by *MaBoSS* to determine
        the probability of the node to switch from down to up

     .. py:attribute:: rt_down

        A string, similar to rt_up but determine the probability to go from up
        to down

     .. py:attribute:: logExp

        A string, the value that will be attributed to the internal node
        variable ``@logic`` in the bnd file

    Node objects have a ``__str__`` method that prints their representation in the
    *MaBoSS* language.
    """

    def __init__(self, name, logExp=None, rt_up=1, rt_down=1,
                 is_internal=False, internal_var={}, in_graph=False, is_mutant=False, schedule=None):
        """
        Create a node not yet inserted in a network.

        logic, rt_up and rt_down are optional when creating the node but
        must be set before making a network.
        
        The is_internal attribute determines wether a node will appear in the 
        MaBoSS output or not.
        
        internal_var is a dictionary of the node's internal variables.
        """
        self.name = name
        self.set_logic(logExp)
        self.rt_up = rt_up
        self.rt_down = rt_down
        self.is_internal = is_internal
        self.internal_var = internal_var.copy()
        self.in_graph = in_graph
        self.is_mutant=is_mutant
        self.schedule = schedule

    def set_rate(self, rate_up, rate_down):
        """
        Set the value of rate_up and rate_down.
        
        :param double rate_up: the value of ``rt_up`` in below expression
        :param double rate_down: the value of ``rt_down`` in below expression

        This function will write a simple formula of the form

            ``rate_up = @logic ? rt_up : 0 ;``
            ``rate_down = @logic ? 0 : rt_down ;``
        """
        self.rt_up = "@logic ? " + rate_up + " : 0"
        self.rt_down = "@logic ? 0 : " + rate_down

    def set_logic(self, string):
        """Set logExp to string if string is a valid boolean expression.
        
        :param str string: the boolean expression to be attributed to ``self.logExp``
        """
        if not string:
            self.logExp = None
        else:
            self.logExp = string

    def __str__(self):
        rt_up_str = str(self.rt_up)
        rt_down_str = str(self.rt_down)
        internal_var_decl = "\n".join(map(lambda v: "%s = %s;" % (v, self.internal_var[v]),
                                          self.internal_var.keys()))
        string = "\n".join(["Node " + self.name + " {",
                            internal_var_decl,
                            ("\tlogic = " + self.logExp + ";") if self.logExp else "",
                            ("\trate_up = " + rt_up_str + ";"),
                            ("\trate_down = " + rt_down_str + ";"),
                            "}"])
        return string

    def copy(self):
        return Node(self.name, self.logExp, self.rt_up, self.rt_down,
                    self.is_internal, self.internal_var, self.in_graph, self.is_mutant, self.schedule)

    def set_schedule(self, schedule):
        """Set the update schedule of the node.
        
        :param str schedule: the update schedule to be attributed to ``self.schedule``
        """
        self.schedule = schedule
        
    def get_schedule(self):
        """Get the update schedule of the node.
        
        :return: the update schedule of the node
        :rtype: str
        """
        return self.schedule
[docs] class Network(collections.OrderedDict): """ Represent a boolean network. Initialised with a list of Nodes whose ``logExp`` s must contain only names present in the list. Network objects are in charge of carrying the initial states of each node. .. py:attribute:: names the list of names of the nodes in the network """ def __init__(self, nodeList): if version_info[0] < 3: super(Network, self).__init__([(nd.name, nd) for nd in nodeList]) else: super().__init__([(nd.name, nd) for nd in nodeList]) self.names = [nd.name for nd in nodeList] self.logicExp = {nd.name: nd.logExp for nd in nodeList} # _attribution gives for each node the list of node with which it is # binded. self._attribution = collections.OrderedDict([(nd.name, nd.name) for nd in nodeList]) # _initState gives for each list of binded node the initial state # probabilities. self._initState = collections.OrderedDict([(l, {0: 0.5, 1: 0.5}) for l in self._attribution]) def add_node(self, name): node = Node(name) collections.OrderedDict.update(self, {name: node}) self.names.append(name) self.logicExp.update({name: node.logExp}) self._attribution.update({name: name}) self._initState.update({name: {0: 0.5, 1: 0.5}}) def remove_node(self, name): del self[name] self.names.remove(name) del self.logicExp[name] del self._attribution[name] del self._initState[name]
[docs] def copy(self): new_ndList = [self[name].copy() for name in self.names] new_network = Network(new_ndList) new_network._attribution = self._attribution.copy() new_network._initState = self._initState.copy() return new_network
[docs] def set_istate(self, nodes, probDict, warnings=True): """ Change the inital states probability of one or several nodes. :param nodes: the node(s) whose initial states are to be modified :type nodes: a :py:class:`Node` or a list or tuple of :py:class:`Node` :param dict probDict: the probability distribution of intial states If nodes is a Node object or a singleton, probDict must be a probability distribution over {0, 1}, it can be expressed by a list [P(0), P(1)] or a dictionary: {0: P(0), 1: P(1)}. If nodes is a tuple or a list of several Node objects, the Node object will be bound, and probDict must be a probability distribution over a part of {0, 1}^n. It must be expressed in the form of a dictionary {(b1, ..., bn): P(b1,..,bn),...}. States that do not appear in the dictionary will be considered to be impossible. If a state has a 0 probability of being an intial state but might be reached later, it must explicitly appear as a key in probDict. **Example** >>> my_network.set_istate('node1', [0.3, 0.7]) # node1 will have a probability of 0.7 of being up >>> my_network.set_istate(['node1', 'node2'], {(0, 0): 0.4, (1, 0): 0.6, (0, 1): 0}) # node1 and node2 can never be both up because (1, 1) is not in the dictionary """ if not (isinstance(nodes, list) or isinstance(nodes, tuple)): if not len(probDict) in [1, 2]: print("Error, must provide a list or dictionary of size 1 or 2", file=stderr) return if isinstance(self._attribution[nodes], tuple): if warnings: print("Warning, node %s was previously bound to other nodes" % nodes, file=stderr) self._erase_binding(nodes) self._initState[nodes] = {0: probDict[0], 1: probDict[1]} elif _testStateDict(probDict, len(nodes)): for node in nodes: if isinstance(self._attribution[node], tuple): if warnings: print("Warning, node %s was previously bound to other" "nodes" % node, file=stderr) self._erase_binding(node) # Now, forall node in nodes, self_attribution[node] is a singleton for node in nodes: self._initState.pop(node) self._attribution[node] = tuple(nodes) self._initState[tuple(nodes)] = probDict
def _erase_binding(self, node): self._initState.pop(self._attribution[node]) for nd in self._attribution[node]: self._attribution[nd] = [nd] self.set_istate(nd, [0.5, 0.5]) def __str__(self): ndList = list(self.values()) string = str(ndList[0]) if len(ndList) > 1: string += "\n" string += "\n\n".join(str(nd) for nd in ndList[1:]) return string def get_istate(self): return self._initState def print_istate(self, out=stdout): print(self.str_istate(), file=out) def str_istate(self): stringList = [] for binding in self._initState: string = '' if isinstance(binding, tuple): string += '[' + ", ".join(list(binding)) + '].istate = ' string += ' , '.join( [str(self._initState[binding][t]) + ' ' + str(list(t)) for t in sorted(self._initState[binding])] ) string += ';' else: if self._initState[binding][0] == 1: string += binding + ".istate = FALSE;" elif self._initState[binding][1] == 1: string += binding + ".istate = TRUE;" elif self._initState[binding][0] == 0.5 and self._initState[binding][1] == 0.5: pass else: string += '[' + binding + '].istate = ' string += str(self._initState[binding][0]) + '[0] , ' string += str(self._initState[binding][1]) + '[1];' if len(string) > 0: stringList.append(string) return '\n'.join(stringList)
[docs] def set_output(self, output_list): """Set all the nodes that are not in the output_list as internal. :param output_list: the nodes to remain external :type output_list: list of :py:class:`Node` """ assert len(set(output_list) - set(self.keys())) == 0, "Node(s) %s not defined !" % str(set(output_list) - set(self.keys()))[1:-1] for nd in self: self[nd].is_internal = nd not in output_list
[docs] def get_output(self): """Get all the nodes that are not in the output_list as internal. """ return [name for name, node in self.items() if not node.is_internal]
[docs] def set_observed_graph_nodes(self, nodes_list): """Set all the nodes to be included in the observed state transition graph. :param nodes_list: the nodes to remain external :type nodes_list: list of :py:class:`Node` """ assert len(set(nodes_list) - set(self.keys())) == 0, "Node(s) %s not defined !" % str(set(nodes_list) - set(self.keys()))[1:-1] for nd in self: self[nd].in_graph = nd in nodes_list
def _testStateDict(stDict, nbState): """Check if stateDict is a good parameter for set_istate.""" def goodTuple(t): return len(t) == nbState and all(x == 0 or x == 1 for x in t) if (not all(isinstance(t, tuple) for t in stDict) or not all(goodTuple(t) for t in stDict)): print("Error, not all keys are good tuples of length %s" % nbState, file=stderr) return False else: return True