class DenseGraph(AbstractMatrixGraph):
    def __init__(self, vertices, undirected=True, W=None, dtype=numpy.float):
        """
        Create a DenseGraph with a given AbstractVertexList or number of 
        vertices, and specify whether it is directed. One can optionally pass 
        in a numpy array W which is used as the weight matrix of the 
        graph. 

        :param vertices: the initial set of vertices as a AbstractVertexList object, or an int to specify the number of vertices in which case vertices are stored in a GeneralVertexList.  
        
        :param undirected: a boolean variable to indicate if the graph is undirected.
        :type undirected: :class:`boolean`

        :param W: a numpy array of the same size as vertices, or None to create the default one.
        
        :param dtype: the data type of the weight matrix if W is not specified e.g numpy.int8. 
        """
        Parameter.checkBoolean(undirected)

        if isinstance(vertices, AbstractVertexList):
            self.vList = vertices
        elif isinstance(vertices, int):
            self.vList = GeneralVertexList(vertices)
        else:
            raise ValueError("Invalid vList parameter: " + str(vertices))

        if W != None and not (isinstance(W, numpy.ndarray) and W.shape ==
                              (len(self.vList), len(self.vList))):
            raise ValueError(
                "Input argument W must be None or numpy array of size " +
                str(len(self.vList)))

        self.undirected = undirected

        if W == None:
            self.W = numpy.zeros((len(self.vList), len(self.vList)),
                                 dtype=dtype)
        else:
            self.W = W
            #The next line is for error checking mainly
            self.setWeightMatrix(W)

    def getNumEdges(self):
        """
        Returns the total number of edges in this graph.
        """
        if self.undirected:
            return (numpy.flatnonzero(self.W).shape[0] +
                    numpy.flatnonzero(numpy.diag(self.W)).shape[0]) / 2
        else:
            return numpy.flatnonzero(self.W).shape[0]

    def getNumDirEdges(self):
        """
        Returns the number of edges, taking this graph as a directed graph. 
        """
        return numpy.flatnonzero(self.W).shape[0]

    def getWeightMatrix(self):
        """
        Return the weight matrix as a numpy array. 
        """
        return self.W

    def neighbours(self, vertexIndex):
        """
        Return an array of the indices of the neighbours of the given vertex.
        
        :param vertexIndex: the index of a vertex.
        :type vertexIndex: :class:`int`
        """
        Parameter.checkIndex(vertexIndex, 0, self.vList.getNumVertices())
        nonZeroIndices = numpy.nonzero(self.W[vertexIndex, :])
        neighbourIndices = nonZeroIndices[0]

        return neighbourIndices

    def neighbourOf(self, vertexIndex):
        """
        Return an array of the indices of vertices than have an edge going to the input
        vertex.

        :param vertexIndex: the index of a vertex.
        :type vertexIndex: :class:`int`
        """
        Parameter.checkIndex(vertexIndex, 0, self.vList.getNumVertices())
        nonZeroIndices = numpy.nonzero(self.W[:, vertexIndex])
        neighbourIndices = nonZeroIndices[0]

        return neighbourIndices

    def complement(self):
        """
        Returns a graph with identical vertices (same reference) to the current one, but with the
        complement of the set of edges. Edges that do not exist have weight 1.
        """
        newGraph = DenseGraph(self.vList, self.undirected)
        newGraph.W = (self.W == 0).astype(self.W.dtype)
        return newGraph

    def outDegreeSequence(self):
        """
        Return a vector of the (out)degree for each vertex.
        """
        degrees = numpy.zeros(self.W.shape[0], dtype=numpy.int32)

        for i in range(0, self.W.shape[0]):
            degrees[i] = numpy.sum(self.W[i, :] != 0)

        return degrees

    def inDegreeSequence(self):
        """
        Return a vector of the (out)degree for each vertex.
        """
        degrees = numpy.zeros(self.W.shape[0], dtype=numpy.int32)

        for i in range(0, self.W.shape[0]):
            degrees[i] = numpy.sum(self.W[:, i] != 0)

        return degrees

    def subgraph(self, vertexIndices):
        """
        Pass in a list or set of vertexIndices and returns the subgraph containing
        those vertices only, and edges between them.

        :param vertexIndices: the indices of the subgraph vertices.
        :type vertexIndices: :class:`list`
        """
        Parameter.checkList(vertexIndices, Parameter.checkIndex,
                            (0, self.getNumVertices()))
        vertexIndices = numpy.unique(numpy.array(vertexIndices)).tolist()
        vList = self.vList.subList(vertexIndices)

        subGraph = DenseGraph(vList, self.undirected, self.W.dtype)
        subGraph.W = self.W[vertexIndices, :][:, vertexIndices]

        return subGraph

    def add(self, graph):
        """
        Add the edge weights of the input graph to the current one. Results in a
        union of the edges.

        :param graph: the input graph.
        :type graph: :class:`apgl.graph.DenseGraph`

        :returns: A new graph with same vertex list and addition of edge weights 
        """
        Parameter.checkClass(graph, DenseGraph)
        if graph.getNumVertices() != self.getNumVertices():
            raise ValueError(
                "Can only add edges from graph with same number of vertices")

        newGraph = DenseGraph(self.vList, self.undirected)
        newGraph.W = self.W + graph.W
        return newGraph

    def copy(self):
        """
        Returns a copy of this object, which also has a copy of the VertexList.
        """
        graph = DenseGraph(self.vList.copy(), self.undirected, self.W.dtype)
        graph.W = self.W.copy()
        return graph

    def multiply(self, graph):
        """
        Multiply the edge weights of the input graph to the current one. Results in an
        intersection of the edges.

        :param graph: the input graph.
        :type graph: :class:`apgl.graph.DenseGraph`

        :returns: A new graph with edge weights which are multiples of the current and graph
        """
        Parameter.checkClass(graph, DenseGraph)
        if graph.getNumVertices() != self.getNumVertices():
            raise ValueError(
                "Can only add edges from graph with same number of vertices")

        newGraph = DenseGraph(self.vList, self.undirected)
        newGraph.W = self.W * graph.W
        return newGraph

    def intersect(self, graph):
        """
        Take the intersection of the edges of this graph and the input graph.
        Resulting edge weights are ignored and only adjacencies are stored.

        :param graph: the input graph.
        :type graph: :class:`apgl.graph.DenseGraph`

        :returns: A new graph with the intersection of edges of the current plus graph
        """
        newGraph = self.multiply(graph)
        newGraph.W = (newGraph.W != 0).astype(newGraph.W.dtype)
        return newGraph

    def union(self, graph):
        """
        Take the union of the edges of this graph and the input graph. Resulting edge
        weights are ignored and only adjacencies are stored.

        :param graph: the input graph.
        :type graph: :class:`apgl.graph.DenseGraph`

        :returns: A new graph with the union of edges of the current one. 
        """
        newGraph = self.add(graph)
        newGraph.W = (newGraph.W != 0).astype(newGraph.W.dtype)

        return newGraph

    def weightMatrixDType(self):
        """
        :returns: the dtype of the matrix used to store edge weights.
        """
        return self.W.dtype

    def setDiff(self, graph):
        """
        Find the edges in the current graph which are not present in the input
        graph. Replaces the edges in the current graph with adjacencies.

        :param graph: the input graph.
        :type graph: :class:`apgl.graph.DenseGraph`

        :returns: The graph which is the set difference of the edges of this graph and graph.
        """
        Parameter.checkClass(graph, DenseGraph)
        if graph.getNumVertices() != self.getNumVertices():
            raise ValueError(
                "Can only add edges from graph with same number of vertices")
        if self.undirected != graph.undirected:
            raise ValueError(
                "Both graphs must be either undirected or directed")

        A1 = self.adjacencyMatrix()
        A2 = graph.adjacencyMatrix()
        A1 = A1 - A2
        A1 = (A1 + numpy.abs(A1**2)) / 2

        newGraph = DenseGraph(self.vList, self.undirected)
        newGraph.W = A1
        return newGraph

    def getAllDirEdges(self):
        """
        Returns the set of directed edges of the current graph as a matrix in which each
        row corresponds to an edge. For an undirected graph, there is an edge from
        v1 to v2 and from v2 to v1 if v2!=v1.

        :returns: A matrix with 2 columns, and each row corresponding to an edge.
        """
        (rows, cols) = numpy.nonzero(self.W)
        edges = numpy.c_[rows, cols]

        return edges

    @staticmethod
    def loadMatrix(filename):
        M = scipy.io.mmread(filename)
        if scipy.sparse.issparse(M):
            M = M.todense()
        return M

    def saveMatrix(self, W, filename):
        scipy.io.mmwrite(filename, W)

    def setWeightMatrix(self, W):
        """
        Set the weight matrix of this graph. Requires as input an ndarray or
        a scipy sparse matrix with the same dimensions as the current weight
        matrix. Edges are represented by non-zero edges.

        :param W: The weight matrix to use.
        :type W: :class:`ndarray` or :class:`scipy.sparse` matrix
        """
        if W.shape != (self.vList.getNumVertices(),
                       self.vList.getNumVertices()):
            raise ValueError("Weight matrix has wrong shape : " + str(W.shape))

        if self.undirected and type(W) == numpy.ndarray and (W != W.T).any():
            raise ValueError(
                "Weight matrix of undirected graph must be symmetric")

        if self.undirected and scipy.sparse.issparse(
                W) and not SparseUtils.equals(W, W.T):
            raise ValueError(
                "Weight matrix of undirected graph must be symmetric")

        if scipy.sparse.issparse(W):
            W = W.todense()

        self.W = numpy.array(W)

    undirected = None
    vList = None
    W = None
class CsArrayGraph(AbstractMatrixGraph):
    def __init__(self, vertices, undirected=True, dtype=numpy.float):
        """
        Create a sparse graph using sppy csarray with a given AbstractVertexList, and specify whether directed.

        :param vertices: the initial set of vertices as a AbstractVertexList object, or an int to specify the number of vertices in which case vertices are stored in a GeneralVertexList.  
        
        :param undirected: a boolean variable to indicate if the graph is undirected.
        :type undirected: :class:`boolean`

        :param dtype: the data type for the weight matrix, e.g numpy.int8.
        """
        Parameter.checkBoolean(undirected)

        if isinstance(vertices, AbstractVertexList):
            self.vList = vertices
        elif isinstance(vertices, int): 
            self.vList = GeneralVertexList(vertices)
        else: 
            raise ValueError("Invalid vList parameter: " + str(vertices))

        self.W = sppy.csarray((self.vList.getNumVertices(), self.vList.getNumVertices()), dtype)
        self.undirected = undirected

    def getNumEdges(self):
        """
        Returns the total number of edges in this graph.
        """
        if self.undirected:
            return (self.W.getnnz() + numpy.flatnonzero(self.W.diag()).shape[0])/2
        else: 
            return self.W.getnnz()

    def getNumDirEdges(self):
        """
        Returns the number of edges, taking this graph as a directed graph. 
        """
        return self.W.getnnz()
    
    def getWeightMatrix(self):
        """
        Return the weight matrix as a numpy array. 
        """
        return self.W.toarray()

    def neighbours(self, vertexIndex):
        """
        Return an array of the indices of the neighbours of the given vertex.
        
        :param vertexIndex: the index of a vertex.
        :type vertexIndex: :class:`int`
        """
        Parameter.checkIndex(vertexIndex, 0, self.vList.getNumVertices())
        neighbourIndices = self.W.rowInds(vertexIndex)
        
        return neighbourIndices

    def neighbourOf(self, vertexIndex):
        """
        Return an array of the indices of vertices than have an edge going to the input
        vertex.

        :param vertexIndex: the index of a vertex.
        :type vertexIndex: :class:`int`
        """
        Parameter.checkIndex(vertexIndex, 0, self.vList.getNumVertices())
        nonZeroIndices =  numpy.nonzero(self.W[:, vertexIndex])
        neighbourIndices = nonZeroIndices[0]

        return neighbourIndices

    def complement(self):
        """
        Returns a graph with identical vertices (same reference) to the current one, but with the
        complement of the set of edges. Edges that do not exist have weight 1.
        """
        newGraph = CsArrayGraph(self.vList, self.undirected)
        newGraph.W = sppy.ones((newGraph.W.shape)) 
        newGraph.W[self.W.nonzero()] = 0
        newGraph.W.prune()
        newGraph.W.compress()
        return newGraph

    def outDegreeSequence(self):
        """
        Return a vector of the (out)degree for each vertex.
        """
        degrees = numpy.zeros(self.W.shape[0], dtype=numpy.int32)

        for i in range(0, self.W.shape[0]):
            degrees[i] = self.W[i, :].getnnz()

        return degrees

    def inDegreeSequence(self):
        """
        Return a vector of the (out)degree for each vertex.
        """
        degrees = numpy.zeros(self.W.shape[0], dtype=numpy.int32)

        for i in range(0, self.W.shape[0]):
            degrees[i] = self.W[:, i].getnnz()

        return degrees 

    def subgraph(self, vertexIndices):
        """
        Pass in a list or set of vertexIndices and returns the subgraph containing
        those vertices only, and edges between them.

        :param vertexIndices: the indices of the subgraph vertices.
        :type vertexIndices: :class:`list`
        """
        Parameter.checkList(vertexIndices, Parameter.checkIndex, (0, self.getNumVertices()))
        vertexIndices = numpy.unique(numpy.array(vertexIndices)).tolist()
        vList = self.vList.subList(vertexIndices)

        subGraph = CsArrayGraph(vList, self.undirected, self.W.dtype)
        subGraph.W = self.W[vertexIndices, :][:, vertexIndices]

        return subGraph

    def add(self, graph):
        """
        Add the edge weights of the input graph to the current one. Results in a
        union of the edges.

        :param graph: the input graph.
        :type graph: :class:`apgl.graph.CsArrayGraph`

        :returns: A new graph with same vertex list and addition of edge weights 
        """
        Parameter.checkClass(graph, CsArrayGraph)
        if graph.getNumVertices() != self.getNumVertices():
            raise ValueError("Can only add edges from graph with same number of vertices")

        newGraph = CsArrayGraph(self.vList, self.undirected)
        newGraph.W = self.W + graph.W
        return newGraph

    def copy(self):
        """
        Returns a copy of this object, which also has a copy of the VertexList.
        """
        graph = CsArrayGraph(self.vList.copy(), self.undirected, self.W.dtype)
        graph.W = self.W.copy()
        return graph

    def multiply(self, graph):
        """
        Multiply the edge weights of the input graph to the current one. Results in an
        intersection of the edges.

        :param graph: the input graph.
        :type graph: :class:`apgl.graph.CsArrayGraph`

        :returns: A new graph with edge weights which are multiples of the current and graph
        """
        Parameter.checkClass(graph, CsArrayGraph)
        if graph.getNumVertices() != self.getNumVertices():
            raise ValueError("Can only add edges from graph with same number of vertices")

        newGraph = CsArrayGraph(self.vList, self.undirected)
        newGraph.W = self.W.hadamard(graph.W)
        return newGraph

    def intersect(self, graph):
        """
        Take the intersection of the edges of this graph and the input graph.
        Resulting edge weights are ignored and only adjacencies are stored.

        :param graph: the input graph.
        :type graph: :class:`apgl.graph.CsArrayGraph`

        :returns: A new graph with the intersection of edges of the current plus graph
        """
        newGraph = self.multiply(graph)
        newGraph.W[newGraph.W.nonzero()] = 1
        return newGraph 

    def union(self, graph):
        """
        Take the union of the edges of this graph and the input graph. Resulting edge
        weights are ignored and only adjacencies are stored.

        :param graph: the input graph.
        :type graph: :class:`apgl.graph.CsArrayGraph`

        :returns: A new graph with the union of edges of the current one. 
        """
        newGraph = self.add(graph)
        newGraph.W[newGraph.W.nonzero()] = 1

        return newGraph

    def weightMatrixDType(self):
        """
        :returns: the dtype of the matrix used to store edge weights.
        """
        return self.W.dtype

    def setDiff(self, graph):
        """
        Find the edges in the current graph which are not present in the input
        graph. Replaces the edges in the current graph with adjacencies.

        :param graph: the input graph.
        :type graph: :class:`apgl.graph.CsArrayGraph`

        :returns: The graph which is the set difference of the edges of this graph and graph.
        """
        Parameter.checkClass(graph, CsArrayGraph)
        if graph.getNumVertices() != self.getNumVertices():
            raise ValueError("Can only add edges from graph with same number of vertices")
        if self.undirected != graph.undirected:
            raise ValueError("Both graphs must be either undirected or directed")

        A1 = self.adjacencyMatrix()
        A2 = graph.adjacencyMatrix()
        A1 = A1 - A2
        A1 = (A1 + numpy.abs(A1**2))/2
        
        newGraph = CsArrayGraph(self.vList, self.undirected)
        newGraph.W = sppy.csarray(A1)
        return newGraph

    def getAllDirEdges(self):
        """
        Returns the set of directed edges of the current graph as a matrix in which each
        row corresponds to an edge. For an undirected graph, there is an edge from
        v1 to v2 and from v2 to v1 if v2!=v1.

        :returns: A matrix with 2 columns, and each row corresponding to an edge.
        """
        (rows, cols) = numpy.nonzero(self.W)
        edges = numpy.c_[rows, cols]

        return edges

    @staticmethod
    def loadMatrix(filename):
        M = scipy.io.mmread(filename)
        if type(M) == numpy.ndarray:
            M2 = sppy.csarray(M)
        elif scipy.sparse.issparse(M):
            M2 = sppy.csarray(M.shape, dtype=M.dtype)
            M2[M.nonzero()] = M.data 
                
        return M2 

    def saveMatrix(self, W, filename):
        W = W.toScipyCsc()
        scipy.io.mmwrite(filename, W)

    def setWeightMatrix(self, W):
        """
        Set the weight matrix of this graph. Requires as input an ndarray or
        a scipy sparse matrix with the same dimensions as the current weight
        matrix. Edges are represented by non-zero edges.

        :param W: The weight matrix to use.
        :type W: :class:`ndarray` or :class:`scipy.sparse` matrix
        """
        if W.shape != (self.vList.getNumVertices(), self.vList.getNumVertices()):
            raise ValueError("Weight matrix has wrong shape : " + str(W.shape))

        if self.undirected and type(W) == numpy.ndarray and (W != W.T).any():
            raise ValueError("Weight matrix of undirected graph must be symmetric")

        if self.undirected and scipy.sparse.issparse(W) and not SparseUtils.equals(W, W.T):
            raise ValueError("Weight matrix of undirected graph must be symmetric")

        if scipy.sparse.issparse(W):
            W = W.todense()

        self.W = numpy.array(W)

    def removeAllEdges(self):
        """
        Removes all edges from this graph. 
        """
        self.W.setZero()

    def setWeightMatrixSparse(self, W):
        """
        Set the weight matrix of this graph. Requires as input a scipy sparse matrix with the
        same dimensions as the current weight matrix. Edges are represented by
        non-zero edges.

        :param W:  The scipy sparse weight matrix to use. 
        """      
        self.W[W.nonzero()] = W.data

    def addVertices(self, n): 
        """
        Adds n vertices to the current graph. This is not an efficient operation
        as we create a new weight matrix and copy the old one. The old vertices 
        are the first m at the start of the new graph.  
        """        
        W2 = sppy.csarray((self.W.shape[0]+n, self.W.shape[0]+n), self.W.dtype)
        W2[self.W.nonzero()] = self.W.values()
        self.W = W2
        
        self.vList.addVertices(n)

    undirected = None
    vList = None
    W = None
    
Exemplo n.º 3
0
class CsArrayGraph(AbstractMatrixGraph):
    def __init__(self, vertices, undirected=True, dtype=numpy.float):
        """
        Create a sparse graph using sppy csarray with a given AbstractVertexList, and specify whether directed.

        :param vertices: the initial set of vertices as a AbstractVertexList object, or an int to specify the number of vertices in which case vertices are stored in a GeneralVertexList.  
        
        :param undirected: a boolean variable to indicate if the graph is undirected.
        :type undirected: :class:`boolean`

        :param dtype: the data type for the weight matrix, e.g numpy.int8.
        """
        Parameter.checkBoolean(undirected)

        if isinstance(vertices, AbstractVertexList):
            self.vList = vertices
        elif isinstance(vertices, int):
            self.vList = GeneralVertexList(vertices)
        else:
            raise ValueError("Invalid vList parameter: " + str(vertices))

        self.W = sppy.csarray(
            (self.vList.getNumVertices(), self.vList.getNumVertices()), dtype)
        self.undirected = undirected

    def getNumEdges(self):
        """
        Returns the total number of edges in this graph.
        """
        if self.undirected:
            return (self.W.getnnz() +
                    numpy.flatnonzero(self.W.diag()).shape[0]) / 2
        else:
            return self.W.getnnz()

    def getNumDirEdges(self):
        """
        Returns the number of edges, taking this graph as a directed graph. 
        """
        return self.W.getnnz()

    def getWeightMatrix(self):
        """
        Return the weight matrix as a numpy array. 
        """
        return self.W.toarray()

    def neighbours(self, vertexIndex):
        """
        Return an array of the indices of the neighbours of the given vertex.
        
        :param vertexIndex: the index of a vertex.
        :type vertexIndex: :class:`int`
        """
        Parameter.checkIndex(vertexIndex, 0, self.vList.getNumVertices())
        neighbourIndices = self.W.rowInds(vertexIndex)

        return neighbourIndices

    def neighbourOf(self, vertexIndex):
        """
        Return an array of the indices of vertices than have an edge going to the input
        vertex.

        :param vertexIndex: the index of a vertex.
        :type vertexIndex: :class:`int`
        """
        Parameter.checkIndex(vertexIndex, 0, self.vList.getNumVertices())
        nonZeroIndices = numpy.nonzero(self.W[:, vertexIndex])
        neighbourIndices = nonZeroIndices[0]

        return neighbourIndices

    def complement(self):
        """
        Returns a graph with identical vertices (same reference) to the current one, but with the
        complement of the set of edges. Edges that do not exist have weight 1.
        """
        newGraph = CsArrayGraph(self.vList, self.undirected)
        newGraph.W = sppy.ones((newGraph.W.shape))
        newGraph.W[self.W.nonzero()] = 0
        newGraph.W.prune()
        newGraph.W.compress()
        return newGraph

    def outDegreeSequence(self):
        """
        Return a vector of the (out)degree for each vertex.
        """
        degrees = numpy.zeros(self.W.shape[0], dtype=numpy.int32)

        for i in range(0, self.W.shape[0]):
            degrees[i] = self.W[i, :].getnnz()

        return degrees

    def inDegreeSequence(self):
        """
        Return a vector of the (out)degree for each vertex.
        """
        degrees = numpy.zeros(self.W.shape[0], dtype=numpy.int32)

        for i in range(0, self.W.shape[0]):
            degrees[i] = self.W[:, i].getnnz()

        return degrees

    def subgraph(self, vertexIndices):
        """
        Pass in a list or set of vertexIndices and returns the subgraph containing
        those vertices only, and edges between them.

        :param vertexIndices: the indices of the subgraph vertices.
        :type vertexIndices: :class:`list`
        """
        Parameter.checkList(vertexIndices, Parameter.checkIndex,
                            (0, self.getNumVertices()))
        vertexIndices = numpy.unique(numpy.array(vertexIndices)).tolist()
        vList = self.vList.subList(vertexIndices)

        subGraph = CsArrayGraph(vList, self.undirected, self.W.dtype)
        subGraph.W = self.W[vertexIndices, :][:, vertexIndices]

        return subGraph

    def add(self, graph):
        """
        Add the edge weights of the input graph to the current one. Results in a
        union of the edges.

        :param graph: the input graph.
        :type graph: :class:`apgl.graph.CsArrayGraph`

        :returns: A new graph with same vertex list and addition of edge weights 
        """
        Parameter.checkClass(graph, CsArrayGraph)
        if graph.getNumVertices() != self.getNumVertices():
            raise ValueError(
                "Can only add edges from graph with same number of vertices")

        newGraph = CsArrayGraph(self.vList, self.undirected)
        newGraph.W = self.W + graph.W
        return newGraph

    def copy(self):
        """
        Returns a copy of this object, which also has a copy of the VertexList.
        """
        graph = CsArrayGraph(self.vList.copy(), self.undirected, self.W.dtype)
        graph.W = self.W.copy()
        return graph

    def multiply(self, graph):
        """
        Multiply the edge weights of the input graph to the current one. Results in an
        intersection of the edges.

        :param graph: the input graph.
        :type graph: :class:`apgl.graph.CsArrayGraph`

        :returns: A new graph with edge weights which are multiples of the current and graph
        """
        Parameter.checkClass(graph, CsArrayGraph)
        if graph.getNumVertices() != self.getNumVertices():
            raise ValueError(
                "Can only add edges from graph with same number of vertices")

        newGraph = CsArrayGraph(self.vList, self.undirected)
        newGraph.W = self.W.hadamard(graph.W)
        return newGraph

    def intersect(self, graph):
        """
        Take the intersection of the edges of this graph and the input graph.
        Resulting edge weights are ignored and only adjacencies are stored.

        :param graph: the input graph.
        :type graph: :class:`apgl.graph.CsArrayGraph`

        :returns: A new graph with the intersection of edges of the current plus graph
        """
        newGraph = self.multiply(graph)
        newGraph.W[newGraph.W.nonzero()] = 1
        return newGraph

    def union(self, graph):
        """
        Take the union of the edges of this graph and the input graph. Resulting edge
        weights are ignored and only adjacencies are stored.

        :param graph: the input graph.
        :type graph: :class:`apgl.graph.CsArrayGraph`

        :returns: A new graph with the union of edges of the current one. 
        """
        newGraph = self.add(graph)
        newGraph.W[newGraph.W.nonzero()] = 1

        return newGraph

    def weightMatrixDType(self):
        """
        :returns: the dtype of the matrix used to store edge weights.
        """
        return self.W.dtype

    def setDiff(self, graph):
        """
        Find the edges in the current graph which are not present in the input
        graph. Replaces the edges in the current graph with adjacencies.

        :param graph: the input graph.
        :type graph: :class:`apgl.graph.CsArrayGraph`

        :returns: The graph which is the set difference of the edges of this graph and graph.
        """
        Parameter.checkClass(graph, CsArrayGraph)
        if graph.getNumVertices() != self.getNumVertices():
            raise ValueError(
                "Can only add edges from graph with same number of vertices")
        if self.undirected != graph.undirected:
            raise ValueError(
                "Both graphs must be either undirected or directed")

        A1 = self.adjacencyMatrix()
        A2 = graph.adjacencyMatrix()
        A1 = A1 - A2
        A1 = (A1 + numpy.abs(A1**2)) / 2

        newGraph = CsArrayGraph(self.vList, self.undirected)
        newGraph.W = sppy.csarray(A1)
        return newGraph

    def getAllDirEdges(self):
        """
        Returns the set of directed edges of the current graph as a matrix in which each
        row corresponds to an edge. For an undirected graph, there is an edge from
        v1 to v2 and from v2 to v1 if v2!=v1.

        :returns: A matrix with 2 columns, and each row corresponding to an edge.
        """
        (rows, cols) = numpy.nonzero(self.W)
        edges = numpy.c_[rows, cols]

        return edges

    @staticmethod
    def loadMatrix(filename):
        M = scipy.io.mmread(filename)
        if type(M) == numpy.ndarray:
            M2 = sppy.csarray(M)
        elif scipy.sparse.issparse(M):
            M2 = sppy.csarray(M.shape, dtype=M.dtype)
            M2[M.nonzero()] = M.data

        return M2

    def saveMatrix(self, W, filename):
        W = W.toScipyCsc()
        scipy.io.mmwrite(filename, W)

    def setWeightMatrix(self, W):
        """
        Set the weight matrix of this graph. Requires as input an ndarray or
        a scipy sparse matrix with the same dimensions as the current weight
        matrix. Edges are represented by non-zero edges.

        :param W: The weight matrix to use.
        :type W: :class:`ndarray` or :class:`scipy.sparse` matrix
        """
        if W.shape != (self.vList.getNumVertices(),
                       self.vList.getNumVertices()):
            raise ValueError("Weight matrix has wrong shape : " + str(W.shape))

        if self.undirected and type(W) == numpy.ndarray and (W != W.T).any():
            raise ValueError(
                "Weight matrix of undirected graph must be symmetric")

        if self.undirected and scipy.sparse.issparse(
                W) and not SparseUtils.equals(W, W.T):
            raise ValueError(
                "Weight matrix of undirected graph must be symmetric")

        if scipy.sparse.issparse(W):
            W = W.todense()

        self.W = numpy.array(W)

    def removeAllEdges(self):
        """
        Removes all edges from this graph. 
        """
        self.W.setZero()

    def setWeightMatrixSparse(self, W):
        """
        Set the weight matrix of this graph. Requires as input a scipy sparse matrix with the
        same dimensions as the current weight matrix. Edges are represented by
        non-zero edges.

        :param W:  The scipy sparse weight matrix to use. 
        """
        self.W[W.nonzero()] = W.data

    def addVertices(self, n):
        """
        Adds n vertices to the current graph. This is not an efficient operation
        as we create a new weight matrix and copy the old one. The old vertices 
        are the first m at the start of the new graph.  
        """
        W2 = sppy.csarray((self.W.shape[0] + n, self.W.shape[0] + n),
                          self.W.dtype)
        W2[self.W.nonzero()] = self.W.values()
        self.W = W2

        self.vList.addVertices(n)

    undirected = None
    vList = None
    W = None
class PySparseGraph(AbstractMatrixGraph):
    '''
    Represents a graph, which can be directed or undirected, and has weights
    on the edges. Memory usage is efficient for sparse graphs. The list of vertices
    is immutable (see VertexList), however edges can be added or removed. Only
    non-zero edges can be added.  Uses Pysparse as the underlying matrix
    representation.
    '''
    def __init__(self, vertices, undirected=True, W=None, sizeHint=1000):
        """
        Create a PySparseGraph with a given AbstractVertexList or number of 
        vertices, and specify whether it is directed. One can optionally pass 
        in a sparse matrix W which is used as the weight matrix of the 
        graph. Different kinds of sparse matrix can impact the speed of various
        operations. The currently supported sparse matrix types are: ll_mat. 

        :param vertices: the initial set of vertices as a AbstractVertexList object, or an int to specify the number of vertices in which case vertices are stored in a GeneralVertexList.  
        
        :param undirected: a boolean variable to indicate if the graph is undirected.
        :type undirected: :class:`boolean`

        :param W: a square sparse matrix of the same size as the number of vertices, or None to create the default one.

        :param sizeHint: the expected number of edges in the graph for efficient memory usage.
        :type sizeHint: :class:`int`
        """
        Parameter.checkBoolean(undirected)

        if isinstance(vertices, AbstractVertexList):
            self.vList = vertices
        elif isinstance(vertices, int): 
            self.vList = GeneralVertexList(vertices)
        else: 
            raise ValueError("Invalid vList parameter: " + str(vertices))
          
        if W != None and not (isinstance(W, spmatrix.LLMatType) and W.shape == (len(self.vList), len(self.vList))):
            raise ValueError("Input argument W must be None or spmatrix.ll_mat of size " + str(len(self.vList)))          
          
        self.undirected = undirected

        if W == None:
            #Should use ll_mat_sym for undirected graphs but it has several unimplemented methods 
            self.W = spmatrix.ll_mat(len(self.vList), len(self.vList), sizeHint)
        else:
            self.W = W 
            #The next line is for error checking mainly 
            self.setWeightMatrix(W)


    def neighbours(self, vertexIndex):
        """
        Return an array of the indices of neighbours. In the case of a directed
        graph it is an array of those vertices connected by an edge from the current
        one.

        :param vertexIndex: the index of a vertex.
        :type vertexIndex: :class:`int`

        :returns: An array of the indices of all neigbours of the input vertex.
        """
        Parameter.checkIndex(vertexIndex, 0, self.vList.getNumVertices())
        neighbours = PySparseUtils.nonzero(self.W[int(vertexIndex), :])[1]

        return numpy.array(neighbours)

    def neighbourOf(self, vertexIndex):
        """
        Return an array of the indices of vertices than have an edge going to the input
        vertex.

        :param vertexIndex: the index of a vertex.
        :type vertexIndex: :class:`int`

        :returns: An array of the indices of all vertices with an edge towards the input vertex.
        """
        Parameter.checkIndex(vertexIndex, 0, self.vList.getNumVertices())
        neighbours = PySparseUtils.nonzero(self.W[:, vertexIndex])[0]

        return numpy.array(neighbours)

    def addEdge(self, vertexIndex1, vertexIndex2, edge=1):
        """
        Add a non-zero edge between two vertices.

        :param vertexIndex1: The index of the first vertex.
        :type vertexIndex1: :class:`int`

        :param vertexIndex2: The index of the second vertex.
        :type vertexIndex2: :class:`int`

        :param edge: The value of the edge.
        :type edge: :class:`float`
        """
        Parameter.checkIndex(vertexIndex1, 0, self.vList.getNumVertices())
        Parameter.checkIndex(vertexIndex2, 0, self.vList.getNumVertices())
        vertexIndex1 = int(vertexIndex1)
        vertexIndex2 = int(vertexIndex2)

        if edge == 0 or edge == float('inf'):
            raise ValueError("Cannot add a zero or infinite edge")

        if self.undirected:
            self.W[vertexIndex1, vertexIndex2] = edge
            self.W[vertexIndex2, vertexIndex1] = edge
        else:
            self.W[vertexIndex1, vertexIndex2] = edge

    def getNumEdges(self):
        """
        Returns the total number of edges in this graph.
        """
        if self.getNumVertices()==0:
            return 0

        if self.undirected:
            inds = numpy.arange(self.getNumVertices())
            #d = spmatrix.ll_mat(self.getNumVertices(), 1)
            d = numpy.zeros(self.getNumVertices())
            self.W.take(d, inds, inds)

            return (self.W.nnz + numpy.sum(d!=0))/2
        else:
            return self.W.nnz

    def getNumDirEdges(self):
        """
        Returns the number of edges, taking this graph as a directed graph.
        """
        return self.W.nnz

    def outDegreeSequence(self):
        """
        Return a vector of the (out)degree for each vertex.
        """
        A = self.nativeAdjacencyMatrix()
        j = spmatrix.ll_mat(self.vList.getNumVertices(), 1)
        j[:, 0] = 1 

        degrees = spmatrix.matrixmultiply(A, j)
        degrees = PysparseMatrix(matrix=degrees)
        degrees = numpy.array(degrees.getNumpyArray().ravel(), numpy.int)
        return degrees

    def inDegreeSequence(self):
        """
        Return a vector of the (in)degree sequence for each vertex.
        """
        A = self.nativeAdjacencyMatrix()
        j = spmatrix.ll_mat(self.vList.getNumVertices(), 1)
        j[:, 0] = 1

        degrees = spmatrix.dot(A, j)
        degrees = PysparseMatrix(matrix=degrees)
        degrees = numpy.array(degrees.getNumpyArray().ravel(), numpy.int)
        return degrees

    def nativeAdjacencyMatrix(self):
        """
        Return the adjacency matrix in sparse format.
        """

        A = spmatrix.ll_mat(self.vList.getNumVertices(), self.vList.getNumVertices())

        nonzeros = PySparseUtils.nonzero(self.W)
        A.put(1, nonzeros[0], nonzeros[1])
        return A

    def subgraph(self, vertexIndices):
        """
        Pass in a list or set of vertexIndices and returns the subgraph containing
        those vertices only, and edges between them.

        :param vertexIndices: the indices of the subgraph vertices.
        :type vertexIndices: :class:`list`

        :returns: A new PySparseGraph containing only vertices and edges from vertexIndices
        """
        Parameter.checkList(vertexIndices, Parameter.checkIndex, (0, self.getNumVertices()))
        vertexIndices = numpy.unique(numpy.array(vertexIndices))
        vList = self.vList.subList(vertexIndices.tolist())

        subGraph = PySparseGraph(vList, self.undirected)
        
        if len(vertexIndices) != 0:
            subGraph.W = self.W[vertexIndices, vertexIndices]

        return subGraph

    def getWeightMatrix(self):
        """
        Return the weight matrix in dense format. Warning: should not be used
        unless sufficient memory is available to store the dense matrix.

        :returns: A numpy.ndarray weight matrix.
        """
        W = PysparseMatrix(matrix=self.W)
        return W.getNumpyArray()

    def getAllDirEdges(self):
        """
        Returns the set of directed edges of the current graph as a matrix in which each
        row corresponds to an edge. For an undirected graph, there is an edge from
        v1 to v2 and from v2 to v1 if v2!=v1.

        :returns: A matrix with 2 columns, and each row corresponding to an edge.
        """
        (rows, cols) = PySparseUtils.nonzero(self.W)
        edges = numpy.c_[rows, cols]

        return edges


    def add(self, graph):
        """
        Add the edge weights of the input graph to the current one. Results in a
        union of the edges.

        :param graph: the input graph.
        :type graph: :class:`apgl.graph.PySparseGraph`

        :returns: A new graph with same vertex list and addition of edge weights
        """
        Parameter.checkClass(graph, PySparseGraph)
        if graph.getNumVertices() != self.getNumVertices():
            raise ValueError("Can only add edges from graph with same number of vertices")
        if self.undirected != graph.undirected:
            raise ValueError("Both graphs must be either undirected or directed")

        newGraph = PySparseGraph(self.vList, self.undirected)
        newGraph.W = self.W.copy()
        newGraph.W.shift(1, graph.W)

        return newGraph

    def copy(self):
        """
        Returns a copy of this object, which also has a copy of the VertexList.
        """
        newGraph = PySparseGraph(self.vList.copy(), self.undirected)
        newGraph.W = self.W.copy()
        return newGraph

    def removeAllEdges(self):
        """
        Removes all edges from this graph.
        """
        #Not sure why this doesn't work 
        #self.W.scale(0)
        self.W = spmatrix.ll_mat(self.getNumVertices(), self.getNumVertices())

    def setWeightMatrix(self, W):
        """
        Set the weight matrix of this graph. Requires as input an ndarray with the
        same dimensions as the current weight matrix. Edges are represented by
        non-zero edges.

        :param W: The name of the file to load.
        :type W: :class:`ndarray`
        """
        #Parameter.checkClass(W, numpy.ndarray)

        if W.shape != (self.vList.getNumVertices(), self.vList.getNumVertices()):
            raise ValueError("Weight matrix has wrong shape : " + str(W.shape))

        if self.undirected and type(W) == numpy.ndarray and (W != W.T).any():
            raise ValueError("Weight matrix of undirected graph must be symmetric")

        self.W = spmatrix.ll_mat(self.getNumVertices(), self.getNumVertices())
        
        if type(W) == numpy.ndarray:         
            rowInds, colInds = numpy.nonzero(W)
            self.W.put(W[(rowInds, colInds)], rowInds, colInds)
        elif isinstance(W, spmatrix.LLMatType): 
            self.setWeightMatrixSparse(W)
        else: 
            raise ValueError("Invalid matrix type: " + str(type(W)))

    def weightMatrixType(self):
        """
        Returns the type of the sparse matrix used to store edge weights.
        """
        return type(self.W)

    def complement(self):
        """
        Returns a graph with identical vertices (same reference) to the current
        one, but with the complement of the set of edges. Edges that do not exist
        have weight 1. This makes a sparse graph dense.

        :returns: A new graph with edges complmenting the current one.
        """

        newGraph = PySparseGraph(self.vList, self.undirected)
        newGraph.W[:, :] = 1

        A = self.nativeAdjacencyMatrix()
        newGraph.W.shift(-1, A)

        return newGraph

    def multiply(self, graph):
        """
        Multiply the edge weights of the input graph to the current one. Results in an
        intersection of the edges.

        :param graph: the input graph.
        :type graph: :class:`apgl.graph.PySparseGraph`

        :returns: A new graph with edge weights which are multiples of the current and graph
        """
        Parameter.checkClass(graph, PySparseGraph)
        if graph.getNumVertices() != self.getNumVertices():
            raise ValueError("Can only add edges from graph with same number of vertices")
        if self.undirected != graph.undirected:
            raise ValueError("Both graphs must be either undirected or directed")

        if self.W.nnz < graph.W.nnz:
            (rows, cols) = PySparseUtils.nonzero(self.W)
        else:
            (rows, cols) = PySparseUtils.nonzero(graph.W)

        arr1 = numpy.zeros(len(rows))
        arr2 = numpy.zeros(len(rows))
        self.W.take(arr1, rows, cols)
        graph.W.take(arr2, rows, cols)

        arr1 = arr1 * arr2

        newGraph = PySparseGraph(self.vList, self.undirected)
        newGraph.W.put(arr1, rows, cols)
        return newGraph

    def setDiff(self, graph):
        """
        Find the edges in the current graph which are not present in the input
        graph.

        :param graph: the input graph.
        :type graph: :class:`apgl.graph.PySparseGraph`

        :returns: A new graph with edges from the current graph and not in the input graph.
        """
        Parameter.checkClass(graph, PySparseGraph)
        if graph.getNumVertices() != self.getNumVertices():
            raise ValueError("Can only add edges from graph with same number of vertices")
        if self.undirected != graph.undirected:
            raise ValueError("Both graphs must be either undirected or directed")

        A1 = self.nativeAdjacencyMatrix()
        A2 = graph.nativeAdjacencyMatrix()

        (rows, cols) = PySparseUtils.nonzero(A1)
        arr1 = numpy.zeros(len(rows))
        arr2 = numpy.zeros(len(rows))

        A1.take(arr1, rows, cols)
        A2.take(arr2, rows, cols)
        arr1 = arr1 - arr2

        A1.put(arr1, rows, cols)

        newGraph = PySparseGraph(self.vList, self.undirected)
        newGraph.W = A1
        return newGraph

    def getEdge(self, vertexIndex1, vertexIndex2):
        """
        Get the value of an edge, or None if no edge exists.

        :param vertexIndex1: The index of the first vertex.
        :type vertexIndex1: :class:`int`

        :param vertexIndex2: The index of the second vertex.
        :type vertexIndex2: :class:`int`

        :returns:  The value of the edge between the given vertex indices.
        """
        Parameter.checkIndex(vertexIndex1, 0, self.vList.getNumVertices())
        Parameter.checkIndex(vertexIndex2, 0, self.vList.getNumVertices())
        vertexIndex1 = int(vertexIndex1)
        vertexIndex2 = int(vertexIndex2)

        if self.W[vertexIndex1, vertexIndex2]==0:
            return None
        else:
            return self.W[vertexIndex1, vertexIndex2]

    @staticmethod
    def loadMatrix(filename):
        return spmatrix.ll_mat_from_mtx(filename)

    def saveMatrix(self, W, filename):
        W.export_mtx(filename)
        
    def setWeightMatrixSparse(self, W):
        """
        Set the weight matrix of this graph. Requires as input a scipy sparse matrix with the
        same dimensions as the current weight matrix. Edges are represented by
        non-zero edges.

        :param W:  The weight matrix to use. 
        """      
        
        if not isinstance(W, spmatrix.LLMatType) and not sparse.issparse(W):
            raise ValueError("Input must be a sparse matrix, not " + str(type(W)))

        if W.shape != (self.vList.getNumVertices(), self.vList.getNumVertices()):
            raise ValueError("Weight matrix has wrong shape : " + str(W.shape))
        
        try: 
            self.W = spmatrix.ll_mat(W.shape[0], W.shape[0], W.getnnz())
        except AttributeError: 
            self.W = spmatrix.ll_mat(W.shape[0], W.shape[0], W.nnz)
      
        if isinstance(W, spmatrix.LLMatType): 
            #Warning: no check for symmetric matrix 
            #if self.undirected:
            #    raise ValueError("Weight matrix of undirected graph must be symmetric")
        
            items = W.items()
            
            for inds, val in items:
                self.W[inds[0], inds[1]] = val
        else: 
            if self.undirected and (W - W.transpose()).nonzero()[0].shape[0]:
                raise ValueError("Weight matrix of undirected graph must be symmetric")
            
            rowInds, colInds = W.nonzero()
                  
            for i in range(rowInds.shape[0]):
                self.W[int(rowInds[i]), int(colInds[i])] = W[int(rowInds[i]), int(colInds[i])]
class PySparseGraph(AbstractMatrixGraph):
    '''
    Represents a graph, which can be directed or undirected, and has weights
    on the edges. Memory usage is efficient for sparse graphs. The list of vertices
    is immutable (see VertexList), however edges can be added or removed. Only
    non-zero edges can be added.  Uses Pysparse as the underlying matrix
    representation.
    '''
    def __init__(self, vertices, undirected=True, W=None, sizeHint=1000):
        """
        Create a PySparseGraph with a given AbstractVertexList or number of 
        vertices, and specify whether it is directed. One can optionally pass 
        in a sparse matrix W which is used as the weight matrix of the 
        graph. Different kinds of sparse matrix can impact the speed of various
        operations. The currently supported sparse matrix types are: ll_mat. 

        :param vertices: the initial set of vertices as a AbstractVertexList object, or an int to specify the number of vertices in which case vertices are stored in a GeneralVertexList.  
        
        :param undirected: a boolean variable to indicate if the graph is undirected.
        :type undirected: :class:`boolean`

        :param W: a square sparse matrix of the same size as the number of vertices, or None to create the default one.

        :param sizeHint: the expected number of edges in the graph for efficient memory usage.
        :type sizeHint: :class:`int`
        """
        Parameter.checkBoolean(undirected)

        if isinstance(vertices, AbstractVertexList):
            self.vList = vertices
        elif isinstance(vertices, int):
            self.vList = GeneralVertexList(vertices)
        else:
            raise ValueError("Invalid vList parameter: " + str(vertices))

        if W != None and not (isinstance(W, spmatrix.LLMatType) and W.shape ==
                              (len(self.vList), len(self.vList))):
            raise ValueError(
                "Input argument W must be None or spmatrix.ll_mat of size " +
                str(len(self.vList)))

        self.undirected = undirected

        if W == None:
            #Should use ll_mat_sym for undirected graphs but it has several unimplemented methods
            self.W = spmatrix.ll_mat(len(self.vList), len(self.vList),
                                     sizeHint)
        else:
            self.W = W
            #The next line is for error checking mainly
            self.setWeightMatrix(W)

    def neighbours(self, vertexIndex):
        """
        Return an array of the indices of neighbours. In the case of a directed
        graph it is an array of those vertices connected by an edge from the current
        one.

        :param vertexIndex: the index of a vertex.
        :type vertexIndex: :class:`int`

        :returns: An array of the indices of all neigbours of the input vertex.
        """
        Parameter.checkIndex(vertexIndex, 0, self.vList.getNumVertices())
        neighbours = PySparseUtils.nonzero(self.W[int(vertexIndex), :])[1]

        return numpy.array(neighbours)

    def neighbourOf(self, vertexIndex):
        """
        Return an array of the indices of vertices than have an edge going to the input
        vertex.

        :param vertexIndex: the index of a vertex.
        :type vertexIndex: :class:`int`

        :returns: An array of the indices of all vertices with an edge towards the input vertex.
        """
        Parameter.checkIndex(vertexIndex, 0, self.vList.getNumVertices())
        neighbours = PySparseUtils.nonzero(self.W[:, vertexIndex])[0]

        return numpy.array(neighbours)

    def addEdge(self, vertexIndex1, vertexIndex2, edge=1):
        """
        Add a non-zero edge between two vertices.

        :param vertexIndex1: The index of the first vertex.
        :type vertexIndex1: :class:`int`

        :param vertexIndex2: The index of the second vertex.
        :type vertexIndex2: :class:`int`

        :param edge: The value of the edge.
        :type edge: :class:`float`
        """
        Parameter.checkIndex(vertexIndex1, 0, self.vList.getNumVertices())
        Parameter.checkIndex(vertexIndex2, 0, self.vList.getNumVertices())
        vertexIndex1 = int(vertexIndex1)
        vertexIndex2 = int(vertexIndex2)

        if edge == 0 or edge == float('inf'):
            raise ValueError("Cannot add a zero or infinite edge")

        if self.undirected:
            self.W[vertexIndex1, vertexIndex2] = edge
            self.W[vertexIndex2, vertexIndex1] = edge
        else:
            self.W[vertexIndex1, vertexIndex2] = edge

    def getNumEdges(self):
        """
        Returns the total number of edges in this graph.
        """
        if self.getNumVertices() == 0:
            return 0

        if self.undirected:
            inds = numpy.arange(self.getNumVertices())
            #d = spmatrix.ll_mat(self.getNumVertices(), 1)
            d = numpy.zeros(self.getNumVertices())
            self.W.take(d, inds, inds)

            return (self.W.nnz + numpy.sum(d != 0)) / 2
        else:
            return self.W.nnz

    def getNumDirEdges(self):
        """
        Returns the number of edges, taking this graph as a directed graph.
        """
        return self.W.nnz

    def outDegreeSequence(self):
        """
        Return a vector of the (out)degree for each vertex.
        """
        A = self.nativeAdjacencyMatrix()
        j = spmatrix.ll_mat(self.vList.getNumVertices(), 1)
        j[:, 0] = 1

        degrees = spmatrix.matrixmultiply(A, j)
        degrees = PysparseMatrix(matrix=degrees)
        degrees = numpy.array(degrees.getNumpyArray().ravel(), numpy.int)
        return degrees

    def inDegreeSequence(self):
        """
        Return a vector of the (in)degree sequence for each vertex.
        """
        A = self.nativeAdjacencyMatrix()
        j = spmatrix.ll_mat(self.vList.getNumVertices(), 1)
        j[:, 0] = 1

        degrees = spmatrix.dot(A, j)
        degrees = PysparseMatrix(matrix=degrees)
        degrees = numpy.array(degrees.getNumpyArray().ravel(), numpy.int)
        return degrees

    def nativeAdjacencyMatrix(self):
        """
        Return the adjacency matrix in sparse format.
        """

        A = spmatrix.ll_mat(self.vList.getNumVertices(),
                            self.vList.getNumVertices())

        nonzeros = PySparseUtils.nonzero(self.W)
        A.put(1, nonzeros[0], nonzeros[1])
        return A

    def subgraph(self, vertexIndices):
        """
        Pass in a list or set of vertexIndices and returns the subgraph containing
        those vertices only, and edges between them.

        :param vertexIndices: the indices of the subgraph vertices.
        :type vertexIndices: :class:`list`

        :returns: A new PySparseGraph containing only vertices and edges from vertexIndices
        """
        Parameter.checkList(vertexIndices, Parameter.checkIndex,
                            (0, self.getNumVertices()))
        vertexIndices = numpy.unique(numpy.array(vertexIndices))
        vList = self.vList.subList(vertexIndices.tolist())

        subGraph = PySparseGraph(vList, self.undirected)

        if len(vertexIndices) != 0:
            subGraph.W = self.W[vertexIndices, vertexIndices]

        return subGraph

    def getWeightMatrix(self):
        """
        Return the weight matrix in dense format. Warning: should not be used
        unless sufficient memory is available to store the dense matrix.

        :returns: A numpy.ndarray weight matrix.
        """
        W = PysparseMatrix(matrix=self.W)
        return W.getNumpyArray()

    def getAllDirEdges(self):
        """
        Returns the set of directed edges of the current graph as a matrix in which each
        row corresponds to an edge. For an undirected graph, there is an edge from
        v1 to v2 and from v2 to v1 if v2!=v1.

        :returns: A matrix with 2 columns, and each row corresponding to an edge.
        """
        (rows, cols) = PySparseUtils.nonzero(self.W)
        edges = numpy.c_[rows, cols]

        return edges

    def add(self, graph):
        """
        Add the edge weights of the input graph to the current one. Results in a
        union of the edges.

        :param graph: the input graph.
        :type graph: :class:`apgl.graph.PySparseGraph`

        :returns: A new graph with same vertex list and addition of edge weights
        """
        Parameter.checkClass(graph, PySparseGraph)
        if graph.getNumVertices() != self.getNumVertices():
            raise ValueError(
                "Can only add edges from graph with same number of vertices")
        if self.undirected != graph.undirected:
            raise ValueError(
                "Both graphs must be either undirected or directed")

        newGraph = PySparseGraph(self.vList, self.undirected)
        newGraph.W = self.W.copy()
        newGraph.W.shift(1, graph.W)

        return newGraph

    def copy(self):
        """
        Returns a copy of this object, which also has a copy of the VertexList.
        """
        newGraph = PySparseGraph(self.vList.copy(), self.undirected)
        newGraph.W = self.W.copy()
        return newGraph

    def removeAllEdges(self):
        """
        Removes all edges from this graph.
        """
        #Not sure why this doesn't work
        #self.W.scale(0)
        self.W = spmatrix.ll_mat(self.getNumVertices(), self.getNumVertices())

    def setWeightMatrix(self, W):
        """
        Set the weight matrix of this graph. Requires as input an ndarray with the
        same dimensions as the current weight matrix. Edges are represented by
        non-zero edges.

        :param W: The name of the file to load.
        :type W: :class:`ndarray`
        """
        #Parameter.checkClass(W, numpy.ndarray)

        if W.shape != (self.vList.getNumVertices(),
                       self.vList.getNumVertices()):
            raise ValueError("Weight matrix has wrong shape : " + str(W.shape))

        if self.undirected and type(W) == numpy.ndarray and (W != W.T).any():
            raise ValueError(
                "Weight matrix of undirected graph must be symmetric")

        self.W = spmatrix.ll_mat(self.getNumVertices(), self.getNumVertices())

        if type(W) == numpy.ndarray:
            rowInds, colInds = numpy.nonzero(W)
            self.W.put(W[(rowInds, colInds)], rowInds, colInds)
        elif isinstance(W, spmatrix.LLMatType):
            self.setWeightMatrixSparse(W)
        else:
            raise ValueError("Invalid matrix type: " + str(type(W)))

    def weightMatrixType(self):
        """
        Returns the type of the sparse matrix used to store edge weights.
        """
        return type(self.W)

    def complement(self):
        """
        Returns a graph with identical vertices (same reference) to the current
        one, but with the complement of the set of edges. Edges that do not exist
        have weight 1. This makes a sparse graph dense.

        :returns: A new graph with edges complmenting the current one.
        """

        newGraph = PySparseGraph(self.vList, self.undirected)
        newGraph.W[:, :] = 1

        A = self.nativeAdjacencyMatrix()
        newGraph.W.shift(-1, A)

        return newGraph

    def multiply(self, graph):
        """
        Multiply the edge weights of the input graph to the current one. Results in an
        intersection of the edges.

        :param graph: the input graph.
        :type graph: :class:`apgl.graph.PySparseGraph`

        :returns: A new graph with edge weights which are multiples of the current and graph
        """
        Parameter.checkClass(graph, PySparseGraph)
        if graph.getNumVertices() != self.getNumVertices():
            raise ValueError(
                "Can only add edges from graph with same number of vertices")
        if self.undirected != graph.undirected:
            raise ValueError(
                "Both graphs must be either undirected or directed")

        if self.W.nnz < graph.W.nnz:
            (rows, cols) = PySparseUtils.nonzero(self.W)
        else:
            (rows, cols) = PySparseUtils.nonzero(graph.W)

        arr1 = numpy.zeros(len(rows))
        arr2 = numpy.zeros(len(rows))
        self.W.take(arr1, rows, cols)
        graph.W.take(arr2, rows, cols)

        arr1 = arr1 * arr2

        newGraph = PySparseGraph(self.vList, self.undirected)
        newGraph.W.put(arr1, rows, cols)
        return newGraph

    def setDiff(self, graph):
        """
        Find the edges in the current graph which are not present in the input
        graph.

        :param graph: the input graph.
        :type graph: :class:`apgl.graph.PySparseGraph`

        :returns: A new graph with edges from the current graph and not in the input graph.
        """
        Parameter.checkClass(graph, PySparseGraph)
        if graph.getNumVertices() != self.getNumVertices():
            raise ValueError(
                "Can only add edges from graph with same number of vertices")
        if self.undirected != graph.undirected:
            raise ValueError(
                "Both graphs must be either undirected or directed")

        A1 = self.nativeAdjacencyMatrix()
        A2 = graph.nativeAdjacencyMatrix()

        (rows, cols) = PySparseUtils.nonzero(A1)
        arr1 = numpy.zeros(len(rows))
        arr2 = numpy.zeros(len(rows))

        A1.take(arr1, rows, cols)
        A2.take(arr2, rows, cols)
        arr1 = arr1 - arr2

        A1.put(arr1, rows, cols)

        newGraph = PySparseGraph(self.vList, self.undirected)
        newGraph.W = A1
        return newGraph

    def getEdge(self, vertexIndex1, vertexIndex2):
        """
        Get the value of an edge, or None if no edge exists.

        :param vertexIndex1: The index of the first vertex.
        :type vertexIndex1: :class:`int`

        :param vertexIndex2: The index of the second vertex.
        :type vertexIndex2: :class:`int`

        :returns:  The value of the edge between the given vertex indices.
        """
        Parameter.checkIndex(vertexIndex1, 0, self.vList.getNumVertices())
        Parameter.checkIndex(vertexIndex2, 0, self.vList.getNumVertices())
        vertexIndex1 = int(vertexIndex1)
        vertexIndex2 = int(vertexIndex2)

        if self.W[vertexIndex1, vertexIndex2] == 0:
            return None
        else:
            return self.W[vertexIndex1, vertexIndex2]

    @staticmethod
    def loadMatrix(filename):
        return spmatrix.ll_mat_from_mtx(filename)

    def saveMatrix(self, W, filename):
        W.export_mtx(filename)

    def setWeightMatrixSparse(self, W):
        """
        Set the weight matrix of this graph. Requires as input a scipy sparse matrix with the
        same dimensions as the current weight matrix. Edges are represented by
        non-zero edges.

        :param W:  The weight matrix to use. 
        """

        if not isinstance(W, spmatrix.LLMatType) and not sparse.issparse(W):
            raise ValueError("Input must be a sparse matrix, not " +
                             str(type(W)))

        if W.shape != (self.vList.getNumVertices(),
                       self.vList.getNumVertices()):
            raise ValueError("Weight matrix has wrong shape : " + str(W.shape))

        try:
            self.W = spmatrix.ll_mat(W.shape[0], W.shape[0], W.getnnz())
        except AttributeError:
            self.W = spmatrix.ll_mat(W.shape[0], W.shape[0], W.nnz)

        if isinstance(W, spmatrix.LLMatType):
            #Warning: no check for symmetric matrix
            #if self.undirected:
            #    raise ValueError("Weight matrix of undirected graph must be symmetric")

            items = W.items()

            for inds, val in items:
                self.W[inds[0], inds[1]] = val
        else:
            if self.undirected and (W - W.transpose()).nonzero()[0].shape[0]:
                raise ValueError(
                    "Weight matrix of undirected graph must be symmetric")

            rowInds, colInds = W.nonzero()

            for i in range(rowInds.shape[0]):
                self.W[int(rowInds[i]), int(colInds[i])] = W[int(rowInds[i]),
                                                             int(colInds[i])]
class DenseGraph(AbstractMatrixGraph):
    def __init__(self, vertices, undirected=True, W=None, dtype=numpy.float):
        """
        Create a DenseGraph with a given AbstractVertexList or number of 
        vertices, and specify whether it is directed. One can optionally pass 
        in a numpy array W which is used as the weight matrix of the 
        graph. 

        :param vertices: the initial set of vertices as a AbstractVertexList object, or an int to specify the number of vertices in which case vertices are stored in a GeneralVertexList.  
        
        :param undirected: a boolean variable to indicate if the graph is undirected.
        :type undirected: :class:`boolean`

        :param W: a numpy array of the same size as vertices, or None to create the default one.
        
        :param dtype: the data type of the weight matrix if W is not specified e.g numpy.int8. 
        """
        Parameter.checkBoolean(undirected)

        if isinstance(vertices, AbstractVertexList):
            self.vList = vertices
        elif isinstance(vertices, int): 
            self.vList = GeneralVertexList(vertices)
        else: 
            raise ValueError("Invalid vList parameter: " + str(vertices))
          
        if W != None and not (isinstance(W, numpy.ndarray) and W.shape == (len(self.vList), len(self.vList))):
            raise ValueError("Input argument W must be None or numpy array of size " + str(len(self.vList)))          
          
        self.undirected = undirected

        if W == None:
            self.W = numpy.zeros((len(self.vList), len(self.vList)), dtype=dtype)
        else:
            self.W = W 
            #The next line is for error checking mainly 
            self.setWeightMatrix(W)


    def getNumEdges(self):
        """
        Returns the total number of edges in this graph.
        """
        if self.undirected:
            return (numpy.flatnonzero(self.W).shape[0] + numpy.flatnonzero(numpy.diag(self.W)).shape[0])/2
        else: 
            return numpy.flatnonzero(self.W).shape[0]

    def getNumDirEdges(self):
        """
        Returns the number of edges, taking this graph as a directed graph. 
        """
        return numpy.flatnonzero(self.W).shape[0]
    
    def getWeightMatrix(self):
        """
        Return the weight matrix as a numpy array. 
        """
        return self.W

    def neighbours(self, vertexIndex):
        """
        Return an array of the indices of the neighbours of the given vertex.
        
        :param vertexIndex: the index of a vertex.
        :type vertexIndex: :class:`int`
        """
        Parameter.checkIndex(vertexIndex, 0, self.vList.getNumVertices())
        nonZeroIndices =  numpy.nonzero(self.W[vertexIndex, :])
        neighbourIndices = nonZeroIndices[0]
        
        return neighbourIndices

    def neighbourOf(self, vertexIndex):
        """
        Return an array of the indices of vertices than have an edge going to the input
        vertex.

        :param vertexIndex: the index of a vertex.
        :type vertexIndex: :class:`int`
        """
        Parameter.checkIndex(vertexIndex, 0, self.vList.getNumVertices())
        nonZeroIndices =  numpy.nonzero(self.W[:, vertexIndex])
        neighbourIndices = nonZeroIndices[0]

        return neighbourIndices

    def complement(self):
        """
        Returns a graph with identical vertices (same reference) to the current one, but with the
        complement of the set of edges. Edges that do not exist have weight 1.
        """
        newGraph = DenseGraph(self.vList, self.undirected)
        newGraph.W = (self.W == 0).astype(self.W.dtype)
        return newGraph

    def outDegreeSequence(self):
        """
        Return a vector of the (out)degree for each vertex.
        """
        degrees = numpy.zeros(self.W.shape[0], dtype=numpy.int32)

        for i in range(0, self.W.shape[0]):
            degrees[i] = numpy.sum(self.W[i, :] != 0)

        return degrees

    def inDegreeSequence(self):
        """
        Return a vector of the (out)degree for each vertex.
        """
        degrees = numpy.zeros(self.W.shape[0], dtype=numpy.int32)

        for i in range(0, self.W.shape[0]):
            degrees[i] = numpy.sum(self.W[:, i] != 0)

        return degrees 

    def subgraph(self, vertexIndices):
        """
        Pass in a list or set of vertexIndices and returns the subgraph containing
        those vertices only, and edges between them.

        :param vertexIndices: the indices of the subgraph vertices.
        :type vertexIndices: :class:`list`
        """
        Parameter.checkList(vertexIndices, Parameter.checkIndex, (0, self.getNumVertices()))
        vertexIndices = numpy.unique(numpy.array(vertexIndices)).tolist()
        vList = self.vList.subList(vertexIndices)

        subGraph = DenseGraph(vList, self.undirected, self.W.dtype)
        subGraph.W = self.W[vertexIndices, :][:, vertexIndices]

        return subGraph

    def add(self, graph):
        """
        Add the edge weights of the input graph to the current one. Results in a
        union of the edges.

        :param graph: the input graph.
        :type graph: :class:`apgl.graph.DenseGraph`

        :returns: A new graph with same vertex list and addition of edge weights 
        """
        Parameter.checkClass(graph, DenseGraph)
        if graph.getNumVertices() != self.getNumVertices():
            raise ValueError("Can only add edges from graph with same number of vertices")

        newGraph = DenseGraph(self.vList, self.undirected)
        newGraph.W = self.W + graph.W
        return newGraph

    def copy(self):
        """
        Returns a copy of this object, which also has a copy of the VertexList.
        """
        graph = DenseGraph(self.vList.copy(), self.undirected, self.W.dtype)
        graph.W = self.W.copy()
        return graph

    def multiply(self, graph):
        """
        Multiply the edge weights of the input graph to the current one. Results in an
        intersection of the edges.

        :param graph: the input graph.
        :type graph: :class:`apgl.graph.DenseGraph`

        :returns: A new graph with edge weights which are multiples of the current and graph
        """
        Parameter.checkClass(graph, DenseGraph)
        if graph.getNumVertices() != self.getNumVertices():
            raise ValueError("Can only add edges from graph with same number of vertices")

        newGraph = DenseGraph(self.vList, self.undirected)
        newGraph.W = self.W * graph.W
        return newGraph

    def intersect(self, graph):
        """
        Take the intersection of the edges of this graph and the input graph.
        Resulting edge weights are ignored and only adjacencies are stored.

        :param graph: the input graph.
        :type graph: :class:`apgl.graph.DenseGraph`

        :returns: A new graph with the intersection of edges of the current plus graph
        """
        newGraph = self.multiply(graph)
        newGraph.W = (newGraph.W != 0).astype(newGraph.W.dtype)
        return newGraph 

    def union(self, graph):
        """
        Take the union of the edges of this graph and the input graph. Resulting edge
        weights are ignored and only adjacencies are stored.

        :param graph: the input graph.
        :type graph: :class:`apgl.graph.DenseGraph`

        :returns: A new graph with the union of edges of the current one. 
        """
        newGraph = self.add(graph)
        newGraph.W = (newGraph.W != 0).astype(newGraph.W.dtype)

        return newGraph

    def weightMatrixDType(self):
        """
        :returns: the dtype of the matrix used to store edge weights.
        """
        return self.W.dtype

    def setDiff(self, graph):
        """
        Find the edges in the current graph which are not present in the input
        graph. Replaces the edges in the current graph with adjacencies.

        :param graph: the input graph.
        :type graph: :class:`apgl.graph.DenseGraph`

        :returns: The graph which is the set difference of the edges of this graph and graph.
        """
        Parameter.checkClass(graph, DenseGraph)
        if graph.getNumVertices() != self.getNumVertices():
            raise ValueError("Can only add edges from graph with same number of vertices")
        if self.undirected != graph.undirected:
            raise ValueError("Both graphs must be either undirected or directed")

        A1 = self.adjacencyMatrix()
        A2 = graph.adjacencyMatrix()
        A1 = A1 - A2
        A1 = (A1 + numpy.abs(A1**2))/2
        
        newGraph = DenseGraph(self.vList, self.undirected)
        newGraph.W = A1
        return newGraph

    def getAllDirEdges(self):
        """
        Returns the set of directed edges of the current graph as a matrix in which each
        row corresponds to an edge. For an undirected graph, there is an edge from
        v1 to v2 and from v2 to v1 if v2!=v1.

        :returns: A matrix with 2 columns, and each row corresponding to an edge.
        """
        (rows, cols) = numpy.nonzero(self.W)
        edges = numpy.c_[rows, cols]

        return edges

    @staticmethod
    def loadMatrix(filename):
        M = scipy.io.mmread(filename)
        if scipy.sparse.issparse(M):
            M = M.todense()
        return M 

    def saveMatrix(self, W, filename):
        scipy.io.mmwrite(filename, W)

    def setWeightMatrix(self, W):
        """
        Set the weight matrix of this graph. Requires as input an ndarray or
        a scipy sparse matrix with the same dimensions as the current weight
        matrix. Edges are represented by non-zero edges.

        :param W: The weight matrix to use.
        :type W: :class:`ndarray` or :class:`scipy.sparse` matrix
        """
        if W.shape != (self.vList.getNumVertices(), self.vList.getNumVertices()):
            raise ValueError("Weight matrix has wrong shape : " + str(W.shape))

        if self.undirected and type(W) == numpy.ndarray and (W != W.T).any():
            raise ValueError("Weight matrix of undirected graph must be symmetric")

        if self.undirected and scipy.sparse.issparse(W) and not SparseUtils.equals(W, W.T):
            raise ValueError("Weight matrix of undirected graph must be symmetric")

        if scipy.sparse.issparse(W):
            W = W.todense()

        self.W = numpy.array(W)

    undirected = None
    vList = None
    W = None