Esempio n. 1
0
 def graph(self, new_graph):
     if isinstance(new_graph, GraphLib):
         self._graph = GraphObject.to_graph_object(new_graph)
     elif isinstance(new_graph, GraphObject):
         self._graph = new_graph
     else:
         raise TypeError("The object passed is not a \
             GraphObject but a {}".format(new_graph.__class__.__name__))
Esempio n. 2
0
    def __init__(self, nodes=0, name="Graph",
                  weighted=True, directed=True, libgraph=None, **kwargs):
        '''
        Initialize Graph instance

        Parameters
        ----------
        nodes : int, optional (default: 0)
            Number of nodes in the graph.
        name : string, optional (default: "Graph")
            The name of this :class:`Graph` instance.
        weighted : bool, optional (default: True)
            Whether the graph edges have weight properties.
        directed : bool, optional (default: True)
            Whether the graph is directed or undirected.
        libgraph : :class:`~nngt.core.GraphObject`, optional
            An optional :class:`~nngt.core.GraphObject` to serve as base.
        
        Returns
        -------
        self : :class:`~nggt.core.Graph`
        '''
        self.__id = self.__class__.__max_id
        self._name = name
        self._directed = directed
        self._edges = []
        # create the graphlib graph
        if libgraph is not None:
            self._graph = GraphObject.to_graph_object(libgraph)
        else:
            self._graph = GraphObject(nodes=nodes, directed=directed)
        # take care of the weights @todo: use those of the libgraph
        if weighted:
            if "weight_prop" in kwargs.keys():
                self._w = kwargs["weight_prop"]
            else:
                self._w = {"distrib": "constant"}
            self.set_weights()
        # update the counters
        self.__class__.__num_graphs += 1
        self.__class__.__max_id += 1
Esempio n. 3
0
class Graph(object):
    
    """
    The basic class that contains a :class:`graph_tool.Graph` and some
    of is properties or methods to easily access them.

    :ivar id: :class:`int`
        unique id that identifies the instance.
    :ivar graph: :class:`~nngt.core.GraphObject`
        main attribute of the class instance.
    """

    #-------------------------------------------------------------------------#
    # Class properties

    __num_graphs = 0
    __max_id = 0
    #~ __di_property_func = {
            #~ "reciprocity": reciprocity, "clustering": clustering,
            #~ "assortativity": assortativity, "diameter": diameter,
            #~ "scc": num_scc, "wcc": num_wcc, "radius": spectral_radius, 
            #~ "num_iedges": num_iedges }
    #~ __properties = __di_property_func.keys()
    
    @classmethod
    def num_graphs(cls):
        ''' Returns the number of alive instances. '''
        return cls.__num_graphs
    
    @classmethod
    def from_matrix(cls, matrix, weighted=True, directed=True):
        '''
        Creates a :class:`~nngt.Graph` from a :class:`scipy.sparse` matrix or
        a dense matrix.
        
        Parameters
        ----------
        matrix : :class:`scipy.sparse` matrix or :class:`numpy.array`
            Adjacency matrix.
        weighted : bool, optional (default: True)
            Whether the graph edges have weight properties.
        directed : bool, optional (default: True)
            Whether the graph is directed or undirected.
        
        Returns
        -------
        :class:`~nngt.Graph`
        '''
        shape = matrix.shape
        if shape[0] != shape[1]:
            raise InvalidArgument('A square matrix is required')
        nodes = shape[0]
        if not directed:
            if issubclass(matrix.__class__, ssp.spmatrix):
                if not (matrix.T != matrix).nnz == 0:
                    raise InvalidArgument('Incompatible directed=False option \
with non symmetric matrix provided.')
            else:
                if not (matrix.T == matrix).all():
                    raise InvalidArgument('Incompatible directed=False option \
with non symmetric matrix provided.')
        edges = np.array(matrix.nonzero()).T
        graph = cls(nodes,name='FromNpArray_{}'.format(cls.__num_graphs),
                    weighted=weighted, directed=directed)
        graph.add_edges(edges)
        if weighted:
            weights = None
            if issubclass(matrix.__class__, ssp.spmatrix):
                weights = np.array(matrix[edges[:,0],edges[:,1]])[0]
            else:
                weights = matrix[dges[:,0],dges[:,1]]
                
            graph.set_weights(elist=edges, wlist=weights)
        return graph
            

    #-------------------------------------------------------------------------#
    # Constructor/destructor and properties
    
    def __init__(self, nodes=0, name="Graph",
                  weighted=True, directed=True, libgraph=None, **kwargs):
        '''
        Initialize Graph instance

        Parameters
        ----------
        nodes : int, optional (default: 0)
            Number of nodes in the graph.
        name : string, optional (default: "Graph")
            The name of this :class:`Graph` instance.
        weighted : bool, optional (default: True)
            Whether the graph edges have weight properties.
        directed : bool, optional (default: True)
            Whether the graph is directed or undirected.
        libgraph : :class:`~nngt.core.GraphObject`, optional
            An optional :class:`~nngt.core.GraphObject` to serve as base.
        
        Returns
        -------
        self : :class:`~nggt.core.Graph`
        '''
        self.__id = self.__class__.__max_id
        self._name = name
        self._directed = directed
        self._edges = []
        # create the graphlib graph
        if libgraph is not None:
            self._graph = GraphObject.to_graph_object(libgraph)
        else:
            self._graph = GraphObject(nodes=nodes, directed=directed)
        # take care of the weights @todo: use those of the libgraph
        if weighted:
            if "weight_prop" in kwargs.keys():
                self._w = kwargs["weight_prop"]
            else:
                self._w = {"distrib": "constant"}
            self.set_weights()
        # update the counters
        self.__class__.__num_graphs += 1
        self.__class__.__max_id += 1

    def __del__(self):
        self.__class__.__num_graphs -= 1

    @property
    def id(self):
        ''' unique :class:`int` identifying the instance '''
        return self.__id
    
    @property
    def graph(self):
        ''' :class:`graph_tool.Graph` attribute of the instance '''
        return self._graph

    @graph.setter
    def graph(self, new_graph):
        if isinstance(new_graph, GraphLib):
            self._graph = GraphObject.to_graph_object(new_graph)
        elif isinstance(new_graph, GraphObject):
            self._graph = new_graph
        else:
            raise TypeError("The object passed is not a \
                GraphObject but a {}".format(new_graph.__class__.__name__))
    
    @property
    def name(self):
        ''' name of the graph '''
        return self._name

    @property
    def edges(self):
        return self._edges

    #-------------------------------------------------------------------------#
    # Graph actions
    
    def copy(self):
        '''
        Returns a deepcopy of the current :class:`~nngt.core.Graph`
        instance
        '''
        gc_instance = Graph(name=self._name+'_copy',
                            weighted=self.is_weighted(),
                            graph=self._graph.copy())
        return gc_instance
    
    def add_edges(self, lst_edges):
        '''
        Add a list of edges to the graph.
        
        Parameters
        ----------
        lst_edges : list of 2-tuples or np.array of shape (edge_nb, 2)
            List of the edges that should be added as tuples (source, target)
            
        @todo: add example, check the edges for self-loops and multiple edges
        '''
        self._graph.new_edges(lst_edges)
        self._edges.extend(lst_edges)

    def inhibitory_subgraph(self):
        ''' Create a :class:`~nngt.core.Graph` instance which graph
        contains only the inhibitory edges of the current instance's
        :class:`graph_tool.Graph` '''
        eprop_b_type = self._graph.new_edge_property(
                       "bool",-self._graph.edge_properties[TYPE].a+1)
        self._graph.set_edge_filter(eprop_b_type)
        inhib_graph = Graph( name=self._name + '_inhib',
                             weighted=self.is_weighted(),
                             graph=GraphObject(self._graph,prune=True) )
        self._graph.clear_filters()
        return inhib_graph

    def excitatory_subgraph(self):
        ''' create a :class:`~nngt.core.Graph` instance which graph
        contains only the excitatory edges of the current instance's
        :class:`GraphObject` '''
        eprop_b_type = self._graph.new_edge_property(
                       "bool",self._graph.edge_properties[TYPE].a+1)
        self._graph.set_edge_filter(eprop_b_type)
        exc_graph = Graph( name=self._name + '_exc',
                             weighted=self.is_weighted(),
                             graph=GraphObject(self._graph,prune=True) )
        self._graph.clear_filters()
        return exc_graph

    def adjacency_matrix(self, typed=True, weighted=True):
        '''
        Returns the adjacency matrix of the graph as a
        :class:`scipy.sparse.csr_matrix`.
        
        Parameters
        ----------
        weighted : bool or string, optional (default: True)
            If True, each entry ``adj[i,j] = w_ij`` where ``w_ij`` is the
            strength of the connection from `i` to `j`, if False, ``adj[i,j] = 
            0. or 1.``. Weighted can also be a string describing an edge
            attribute (e.g. if "distance" refers to an edge attribute ``dist``,
            then ``ajacency_matrix("distance")`` will return
            ``adj[i,i] = dist_ij``).
            
        Returns
        -------
        adj : :class:`scipy.sparse.csr_matrix`
            The adjacency matrix of the graph.
        '''
        return na.adjacency_matrix(self, typed=typed, weighted=weighted)

    def clear_edges(self):
        ''' Remove all the edges in the graph. '''
        self._graph.clear_edges()

    def clear_edges(self):
        ''' Remove all the edges in the graph. '''
        self._graph.clear_edges()
        n = self.node_nb()

    #-------------------------------------------------------------------------#
    # Setters
        
    def set_name(self, name=""):
        ''' set graph name '''
        if name != "":
            self._name = name
        else:
            self._name = "Graph_" + str(self.__id)

    def set_edge_attribute(self, attribute, values=None, val=None,
                           value_type=None):
        if attribute not in self.attributes():
            self._graph.new_edge_attribute(attribute, value_type, values, val)
        else:
            num_edges = self.edge_nb()
            if values is None:
                if val is not None:
                    values = np.repeat(val,num_edges)
                else:
                    raise InvalidArgument("At least one of the `values` and \
`val` arguments should not be ``None``.")
            self._graph._eattr[attribute] = values
    
    def set_weights(self, elist=None, wlist=None, distrib=None,
                    distrib_prop=None, correl=None, noise_scale=None):
        '''
        Set the synaptic weights.
        @todo: take elist into account in Connections.weights
        
        Parameters
        ----------
        elist : class:`numpy.array`, optional (default: None)
            List of the edges (for user defined weights).
        wlist : class:`numpy.array`, optional (default: None)
            List of the weights (for user defined weights).
        distrib : class:`string`, optional (default: None)
            Type of distribution (choose among "constant", "uniform", 
            "gaussian", "lognormal", "lin_corr", "log_corr").
        distrib_prop : dict, optional (default: {})
            Dictoinary containing the properties of the weight distribution.
        correl : class:`string`, optional (default: None)
            Property to which the weights should be correlated.
        noise_scale : class:`int`, optional (default: None)
            Scale of the multiplicative Gaussian noise that should be applied
            on the weights.
        '''
        if distrib is None:
            distrib = self._w["distrib"]
        if distrib_prop is None:
            distrib_prop = (self._w["distrib_prop"] if "distrib_prop" in 
                            self._w.keys() else {})
        if correl is None:
            correl = self._w["correl"] if "correl" in self._w.keys() else None
        Connections.weights(self, elist=elist, wlist=wlist, distrib=distrib,
            correl=correl, distrib_prop=distrib_prop, noise_scale=noise_scale)
        

    #-------------------------------------------------------------------------#
    # Getters
    
    def attributes(self):
        ''' List of the graph's attributes (synaptic weights, delays...) '''
        return self._graph._eattr.keys()
    
    def get_name(self):
        ''' Get the name of the graph '''
        return self.__di_prop["name"]
    
    def node_nb(self):
        ''' Number of nodes in the graph '''
        return self._graph.node_nb()
    
    def edge_nb(self):
        ''' Number of edges in the graph '''
        return self._graph.edge_nb()
    
    def get_density(self):
        '''
        Density of the graph: :math:`\\frac{E}{N^2}`, where `E` is the number of
        edges and `N` the number of nodes.
        '''
        return self._graph.edge_nb()/float(self._graph.node_nb()**2)

    def is_weighted(self):
        ''' Whether the edges have weights '''
        return "weight" in self.attributes()

    def is_directed(self):
        ''' Whether the graph is directed or not '''
        return self._directed

    #~ def get_property(self, s_property):
        #~ ''' Return the desired property or None for an incorrect one. '''
        #~ if s_property in Graph.__properties:
            #~ return Graph.__di_property_func[s_property](self._graph)
        #~ else:
            #~ warnings.warn("Ignoring request for unknown property \
                          #~ '{}'".format(s_property))
            #~ return None

    #~ def get_properties(self, a_properties):
        #~ '''
        #~ Return a dictionary containing the desired properties
#~ 
        #~ Parameters
        #~ ----------
        #~ a_properties : sequence
            #~ List or tuple of strings of the property names.
#~ 
        #~ Returns
        #~ -------
        #~ di_result : dict
            #~ A dictionary of values with the property names as keys.
        #~ '''
        #~ di_result = { prop: self.get_property(prop) for prop in a_properties }
        #~ return di_result

    def get_degrees(self, node_list=None, deg_type="total", use_weights=True):
        '''
        Degree sequence of all the nodes.
        
        Parameters
        ----------
        node_list : list, optional (default: None)
            List of the nodes which degree should be returned
        deg_type : string, optional (default: "total")
            Degree type (among 'in', 'out' or 'total').
        use_weights : bool, optional (default: True)
            Whether to use weighted (True) or simple degrees (False).
        
        Returns
        -------
        :class:`numpy.array` or None (if an invalid type is asked).
        '''
        valid_types = ("in", "out", "total")
        if deg_type in valid_types:
            return self._graph.degree_list(node_list, deg_type, use_weights)
        else:
            warnings.warn("Ignoring invalid degree type '{}'".format(strType))
            return None

    def get_betweenness(self, use_weights=True):
        '''
        Betweenness centrality sequence of all nodes and edges.
        
        Parameters
        ----------
        use_weights : bool, optional (default: True)
            Whether to use weighted (True) or simple degrees (False).
        
        Returns
        -------
        node_betweenness : :class:`numpy.array`
            Betweenness of the nodes.
        edge_betweenness : :class:`numpy.array`
            Betweenness of the edges.
        '''
        return self._graph.betweenness(use_weights)

    def get_edge_types(self):
        if TYPE in self._graph.edge_properties.keys():
            return self._graph.edge_properties[TYPE].a
        else:
            return repeat(1, self._graph.edge_nb())
    
    def get_weights(self):
        ''' Returns the weighted adjacency matrix as a
        :class:`scipy.sparse.lil_matrix`.
        '''
        return self._graph.eproperties["weight"]

    def is_spatial(self):
        '''
        Whether the graph is embedded in space (has a :class:`~nngt.Shape`
        attribute).
        '''
        return True if issubclass(self.__class__, SpatialGraph) else False