def testGenerate2(self):
        """
        Make sure that the generated degree is less than or equal to the given degree
        
        """
        numVertices = 10

        for i in range(10):
            degSequence = numpy.random.randint(0, 3, numVertices)
            generator = ConfigModelGenerator(degSequence)
            graph = SparseGraph(GeneralVertexList(numVertices))
            graph = generator.generate(graph)

            self.assertTrue((graph.outDegreeSequence() <= degSequence).all())

        #We try to match an evolving degree sequence
        degSequence1 = numpy.array([0, 0, 1, 1, 1, 2, 2, 2, 3, 4])
        degSequence2 = numpy.array([2, 0, 3, 1, 2, 2, 2, 2, 3, 4])
        degSequence3 = numpy.array([2, 1, 4, 1, 2, 2, 2, 2, 3, 6])

        generator = ConfigModelGenerator(degSequence1)
        graph = SparseGraph(GeneralVertexList(numVertices))
        graph = generator.generate(graph)
        self.assertTrue((degSequence1 >= graph.outDegreeSequence()).all())

        deltaSequence = degSequence2 - graph.outDegreeSequence()
        generator = ConfigModelGenerator(deltaSequence)
        graph = generator.generate(graph, False)
        self.assertTrue((degSequence2 >= graph.outDegreeSequence()).all())

        deltaSequence = degSequence3 - graph.outDegreeSequence()
        generator = ConfigModelGenerator(deltaSequence)
        graph = generator.generate(graph, False)
        self.assertTrue((degSequence3 >= graph.outDegreeSequence()).all())
    def testConcat(self):
        numVertices = 5
        graph = SparseGraph(GeneralVertexList(numVertices))
        graph.addEdge(1, 1, 0.1)
        graph.addEdge(1, 3, 0.5)
        graph.addEdge(2, 4, 1)
        graph.addEdge(2, 3, 2)
        graph.setVertex(0, "abc")

        graph2 = SparseGraph(GeneralVertexList(numVertices))
        graph2.addEdge(1, 1)
        graph2.addEdge(1, 4)
        graph2.setVertex(1, "def")

        graph3 = graph.concat(graph2)

        self.assertTrue(graph3.getNumVertices, 10)
        self.assertEquals(graph3.getVertex(0), "abc")
        self.assertEquals(graph3.getVertex(6), "def")
        self.assertEquals(graph3.getEdge(1, 1), 0.1)
        self.assertEquals(graph3.getEdge(1, 3), 0.5)
        self.assertEquals(graph3.getEdge(2, 4), 1)
        self.assertEquals(graph3.getEdge(2, 3), 2)

        self.assertEquals(graph3.getEdge(6, 6), 1)
        self.assertEquals(graph3.getEdge(6, 9), 1)
    def testNativeAdjacencyMatrix(self):
        numVertices = 10
        graph = SparseGraph(GeneralVertexList(numVertices))

        graph.addEdge(1, 1, 0.1)
        graph.addEdge(1, 3, 0.5)
        graph.addEdge(2, 5, 1)
        graph.addEdge(7, 0, 2)

        A = graph.nativeAdjacencyMatrix()
        self.assertEquals(A[0, 7], 1)
        self.assertEquals(A[7, 0], 1)
        self.assertEquals(A[1, 3], 1)
        self.assertEquals(A[3, 1], 1)
        self.assertEquals(A[1, 1], 1)
        self.assertEquals(A[2, 5], 1)
        self.assertEquals(A[5, 2], 1)
        self.assertEquals(A.getnnz(), 7)

        graph = SparseGraph(GeneralVertexList(numVertices), False)
        graph.addEdge(1, 1, 0.1)
        graph.addEdge(1, 3, 0.5)
        graph.addEdge(2, 5, 1)

        A = graph.nativeAdjacencyMatrix()
        self.assertEquals(A[1, 3], 1)
        self.assertEquals(A[1, 1], 1)
        self.assertEquals(A[2, 5], 1)
        self.assertEquals(A.getnnz(), 3)
    def testAddVertices(self):
        vList = GeneralVertexList(10)
        vList.setVertex(1, 2)
        self.assertEquals(vList.getNumVertices(), 10)
        self.assertEquals(vList[1], 2)

        vList.addVertices(5)
        self.assertEquals(vList.getNumVertices(), 15)
        vList.setVertex(11, 2)
        self.assertEquals(vList[1], 2)
        self.assertEquals(vList[1], 2)
 def testAddVertices(self): 
     vList = GeneralVertexList(10)
     vList.setVertex(1, 2)
     self.assertEquals(vList.getNumVertices(), 10)
     self.assertEquals(vList[1], 2)
     
     vList.addVertices(5)
     self.assertEquals(vList.getNumVertices(), 15)
     vList.setVertex(11, 2)
     self.assertEquals(vList[1], 2)
     self.assertEquals(vList[1], 2)
class GeneralVertexListTest(unittest.TestCase, AbstractVertexListTest):
    def setUp(self):
        self.VListType = GeneralVertexList
        self.numVertices = 10
        self.vList = GeneralVertexList(self.numVertices)
        self.emptyVertex = None
        self.initialise()

    def testConstructor(self):
        self.assertEquals(self.vList.getNumVertices(), self.numVertices)

    def testSaveLoad(self):
        try:
            vList = GeneralVertexList(self.numVertices)
            vList.setVertex(0, "abc")
            vList.setVertex(1, 12)
            vList.setVertex(2, "num")

            tempDir = PathDefaults.getTempDir()
            fileName = tempDir + "vList"

            vList.save(fileName)

            vList2 = GeneralVertexList.load(fileName)

            for i in range(self.numVertices):
                self.assertEquals(vList.getVertex(i), vList2.getVertex(i))
        except IOError as e:
            logging.warn(e)
            pass 
    def concat(self, graph):
        """
        Take a new graph and concatenate it to the current one. Returns a new graph
        of the concatenated graphs with this graphs vertices first in the new list of
        vertices.

        :param graph: the input graph.
        :type graph: :class:`apgl.graph.SparseGraph`
        """
        Parameter.checkClass(graph, SparseGraph)
        if type(graph.getVertexList()) != type(self.getVertexList()):
            raise ValueError("Vertex lists must be of same type")
        if graph.isUndirected() != self.isUndirected():
            raise ValueError("Graphs must be of the same directed type")

        numVertices = self.getNumVertices() + graph.getNumVertices()
        vList = GeneralVertexList(numVertices)
        vList.setVertices(self.getVertexList().getVertices(),
                          list(range(self.getNumVertices())))
        vList.setVertices(graph.getVertexList().getVertices(),
                          list(range(self.getNumVertices(), numVertices)))
        newGraph = SparseGraph(vList)

        W = scipy.sparse.bmat([[self.W, None], [None, graph.W]], format="csr")
        newGraph.setWeightMatrixSparse(W)

        return newGraph
Beispiel #8
0
    def fromNetworkXGraph(cls, networkXGraph):
        """
        Take a networkx Graph or DiGraph object, and return a subclass of AbstractMatrixGraph. Notice
        that networkx must be installed to use this function. The networkXGraph graph
        dict must have an attribute VListType which is the type of the VertexList used to
        construct the SparseGraph. Furthermore, only node attributes index by "label"
        are stored in the VertexList, and edge values are currently ignored.

        :returns: A networkx Graph or DiGraph object.
        """
        try:
            import networkx
        except ImportError:
            raise ImportError("toNetworkXGraph() requires networkx")

        if type(networkXGraph) == networkx.classes.graph.Graph:
            undirected = True
        elif type(networkXGraph) == networkx.classes.digraph.DiGraph:
            undirected = False
        else:
            raise ValueError("Unsupported NetworkX graph type")

        numVertices = networkXGraph.number_of_nodes()

        if "VListType" in networkXGraph.graph and networkXGraph.graph["VListType"] == VertexList:
            vList = networkXGraph.graph["VListType"](numVertices, networkXGraph.graph["numFeatures"])
        else:
            vList = GeneralVertexList(numVertices)

        graph = cls(vList, undirected)

        #Map from networkx nodes to an index
        nodeDict = {}

        #Set the vertices - note that vertex names are ignored
        for i in range(len(networkXGraph.nodes())):
            if "label" in networkXGraph.node[networkXGraph.nodes()[i]]:
                graph.setVertex(i, networkXGraph.node[networkXGraph.nodes()[i]]["label"])
            else:
                graph.setVertex(i, None)
            nodeDict[networkXGraph.nodes()[i]] = i

        #Set edges
        for i in range(len(networkXGraph.edges())):
            vertexIndex1 = nodeDict[networkXGraph.edges()[i][0]]
            vertexIndex2 = nodeDict[networkXGraph.edges()[i][1]]
            graph.addEdge(vertexIndex1, vertexIndex2)

        return graph
Beispiel #9
0
    def __init__(self):
        numVertices = 1000
        graph = SparseGraph(GeneralVertexList(numVertices))

        p = 0.1
        generator = ErdosRenyiGenerator(p)
        graph = generator.generate(graph)

        subgraphIndicesList = []
        for i in range(100, numVertices, 10):
            subgraphIndicesList.append(range(i))

        k1 = 5
        k2 = 100

        self.graph = graph
        self.subgraphIndicesList = subgraphIndicesList
        self.clusterer = IterativeSpectralClustering(k1, k2, T=10, alg="IASC")
    def testKwayNormalisedCut(self):
        numVertices = 6
        graph = SparseGraph(GeneralVertexList(numVertices))

        graph.addEdge(0, 1)
        graph.addEdge(0, 2)
        graph.addEdge(2, 1)

        graph.addEdge(3, 4)
        graph.addEdge(3, 5)
        graph.addEdge(5, 4)

        W = graph.getWeightMatrix()
        clustering = numpy.array([0, 0, 0, 1, 1, 1])

        self.assertEquals(GraphUtils.kwayNormalisedCut(W, clustering), 0.0)

        #Try sparse W
        Ws = scipy.sparse.csr_matrix(W)
        self.assertEquals(GraphUtils.kwayNormalisedCut(Ws, clustering), 0.0)

        graph.addEdge(2, 3)
        W = graph.getWeightMatrix()
        self.assertEquals(GraphUtils.kwayNormalisedCut(W, clustering), 1.0 / 7)

        Ws = scipy.sparse.csr_matrix(W)
        self.assertEquals(GraphUtils.kwayNormalisedCut(Ws, clustering),
                          1.0 / 7)

        clustering = numpy.array([0, 0, 0, 1, 1, 2])
        self.assertEquals(GraphUtils.kwayNormalisedCut(W, clustering),
                          61.0 / 105)

        self.assertEquals(GraphUtils.kwayNormalisedCut(Ws, clustering),
                          61.0 / 105)

        #Test two vertices without any edges
        W = numpy.zeros((2, 2))
        clustering = numpy.array([0, 1])
        self.assertEquals(GraphUtils.kwayNormalisedCut(W, clustering), 0.0)

        Ws = scipy.sparse.csr_matrix(W)
        self.assertEquals(GraphUtils.kwayNormalisedCut(Ws, clustering), 0.0)
class GeneralVertexListTest(unittest.TestCase, AbstractVertexListTest):
    def setUp(self):
        self.VListType = GeneralVertexList
        self.numVertices = 10
        self.vList = GeneralVertexList(self.numVertices)
        self.emptyVertex = None
        self.initialise()

    def testConstructor(self):
        self.assertEquals(self.vList.getNumVertices(), self.numVertices)

    def testSaveLoad(self):
        try:
            vList = GeneralVertexList(self.numVertices)
            vList.setVertex(0, "abc")
            vList.setVertex(1, 12)
            vList.setVertex(2, "num")

            tempDir = PathDefaults.getTempDir()
            fileName = tempDir + "vList"

            vList.save(fileName)

            vList2 = GeneralVertexList.load(fileName)

            for i in range(self.numVertices):
                self.assertEquals(vList.getVertex(i), vList2.getVertex(i))
        except IOError as e:
            logging.warn(e)
            pass

    def testAddVertices(self):
        vList = GeneralVertexList(10)
        vList.setVertex(1, 2)
        self.assertEquals(vList.getNumVertices(), 10)
        self.assertEquals(vList[1], 2)

        vList.addVertices(5)
        self.assertEquals(vList.getNumVertices(), 15)
        vList.setVertex(11, 2)
        self.assertEquals(vList[1], 2)
        self.assertEquals(vList[1], 2)
Beispiel #12
0
    def clusterFromIterator(self, graphListIterator, timeIter=False):
        """
        Find a set of clusters for the graphs given by the iterator. 
        """
        clustersList = []
        timeList = [] 

        for subW in graphListIterator:
            logging.debug("Clustering graph of size " + str(subW.shape))
            #Create a SparseGraph
            startTime = time.time()
            graph = SparseGraph(GeneralVertexList(subW.shape[0]))
            graph.setWeightMatrixSparse(subW)
            iGraph = graph.toIGraph()

            vertexCluster = iGraph.community_leading_eigenvector(self.k)
            clustersList.append(vertexCluster.membership)
            timeList.append(time.time()-startTime)

        if timeIter:
            return clustersList, timeList
        else:
            return clustersList
    def testModularity(self):
        numVertices = 6
        graph = SparseGraph(GeneralVertexList(numVertices))

        graph.addEdge(0, 0)
        graph.addEdge(1, 1)
        graph.addEdge(2, 2)
        graph.addEdge(0, 1)
        graph.addEdge(0, 2)
        graph.addEdge(2, 1)

        graph.addEdge(3, 4, 2)
        graph.addEdge(3, 5, 2)
        graph.addEdge(4, 5, 2)
        graph.addEdge(3, 3, 2)
        graph.addEdge(4, 4, 2)
        graph.addEdge(5, 5, 2)

        W = graph.getWeightMatrix()
        clustering = numpy.array([0, 0, 0, 1, 1, 1])

        #This is the same as the igraph result
        Q = GraphUtils.modularity(W, clustering)
        self.assertEquals(Q, 4.0 / 9.0)

        Ws = scipy.sparse.csr_matrix(W)
        Q = GraphUtils.modularity(Ws, clustering)
        self.assertEquals(Q, 4.0 / 9.0)

        W = numpy.ones((numVertices, numVertices))
        Q = GraphUtils.modularity(W, clustering)

        self.assertEquals(Q, 0.0)

        Ws = scipy.sparse.csr_matrix(W)
        Q = GraphUtils.modularity(Ws, clustering)
        self.assertEquals(Q, 0.0)
"""
Name: Generate Graph:
Author: Jia_qiu Wang(王佳秋)
Data: December, 2016
function:
"""

import numpy
import scipy.sparse as sps
from apgl.graph.GeneralVertexList import GeneralVertexList
from apgl.graph.SparseGraph import SparseGraph

numVertices = 10
vList = GeneralVertexList(numVertices)
wght = sps.csc_matrix(numVertices, numVertices)
graph = SparseGraph(vList, W=wght, undirected=False)

graph[0, 1] = 1
graph[0, 2] = 1
graph.setVertex(0, "abc")
graph.setVertex(1, 123)

print(graph)
Beispiel #15
0
"""
Name: Generate Graph:
Author: Jia_qiu Wang(王佳秋)
Data: December, 2016
function:
"""

from apgl.graph.DictGraph import DictGraph
from apgl.graph.SparseGraph import SparseGraph
from apgl.graph.GeneralVertexList import GeneralVertexList

graph = DictGraph()
graph.addEdge("a", "b")
graph.addEdge("a", "c")
graph.addEdge("a", "d")

edgeIndices = graph.getAllEdgeIndices()
graph2 = SparseGraph(GeneralVertexList(graph.getNumVertices()))
graph2.addEdges(edgeIndices)

 def setUp(self):
     self.VListType = GeneralVertexList
     self.numVertices = 10
     self.vList = GeneralVertexList(self.numVertices)
     self.emptyVertex = None
     self.initialise()
"""
Name: Generate Graph:
Author: Jia_qiu Wang(王佳秋)
Data: December, 2016
function:
"""

from apgl.graph.DenseGraph import DenseGraph
from apgl.graph.GeneralVertexList import GeneralVertexList
from apgl.generator.ErdosRenyiGenerator import *

numVertices = 20
graph = DenseGraph(GeneralVertexList(numVertices))
p = 0.2
generator = ErdosRenyiGenerator(p)
graph = generator.generate(graph)


    def testSaveLoad(self):
        try:
            vList = GeneralVertexList(self.numVertices)
            vList.setVertex(0, "abc")
            vList.setVertex(1, 12)
            vList.setVertex(2, "num")

            tempDir = PathDefaults.getTempDir()
            fileName = tempDir + "vList"

            vList.save(fileName)

            vList2 = GeneralVertexList.load(fileName)

            for i in range(self.numVertices):
                self.assertEquals(vList.getVertex(i), vList2.getVertex(i))
        except IOError as e:
            logging.warn(e)
            pass 
class SparseGraph(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 scipy.sparse for the underlying matrix
    representation. 
    '''
    def __init__(self,
                 vertices,
                 undirected=True,
                 W=None,
                 dtype=numpy.float,
                 frmt="csr"):
        """
        Create a SparseGraph 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: lil_matrix, 
        csr_matrix, csc_matrix and dok_matrix. The default sparse matrix is 
        csr_matrix. 

        :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 dtype: the data type of the sparse matrix if W is not specified. 
        
        :param frmt: the format of the sparse matrix: lil, csr or csc if W is not specified 
        """
        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 (sparse.issparse(W) and W.shape == (
                self.vList.getNumVertices(), self.vList.getNumVertices())):
            raise ValueError(
                "Input argument W must be None or sparse matrix of size " +
                str(self.vList.getNumVertices()))

        self.undirected = undirected

        if frmt == "lil":
            matrix = sparse.lil_matrix
        elif frmt == "csr":
            matrix = sparse.csr_matrix
        elif frmt == "csc":
            matrix = sparse.csc_matrix
        else:
            raise ValueError("Invalid sparse matrix format: " + frmt)

        #Terrible hack alert:  can't create a zero size sparse matrix, so we settle
        #for one of size 1. Better is to create a new class.
        if self.vList.getNumVertices() == 0 and W == None:
            self.W = matrix((1, 1), dtype=dtype)
        elif W == None:
            self.W = matrix(
                (self.vList.getNumVertices(), self.vList.getNumVertices()),
                dtype=dtype)
        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 = self.W[vertexIndex, :].nonzero()[1]
        neighbours = self.W.getrow(vertexIndex).nonzero()[1]
        #neighbours = numpy.nonzero(self.W.getrow(vertexIndex).toarray())[1]

        return 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())
        nonZeroInds = self.W[:, vertexIndex].nonzero()
        neighbours = nonZeroInds[0]

        return neighbours

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

        #Note that self.W.getnnz() doesn't seem to work correctly
        if self.undirected == True:
            return (self.W.nonzero()[0].shape[0] +
                    numpy.sum(SparseUtils.diag(self.W) != 0)) / 2
        else:
            return self.W.nonzero()[0].shape[0]

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

    def outDegreeSequence(self):
        """
        :returns: a vector of the (out)degree sequence for each vertex.
        """
        A = self.nativeAdjacencyMatrix()
        degrees = numpy.array(A.sum(1), dtype=numpy.int32).ravel()

        return degrees

    def inDegreeSequence(self):
        """
        :returns: a vector of the (in)degree sequence for each vertex.
        """
        A = self.nativeAdjacencyMatrix()
        degrees = numpy.array(A.sum(0), dtype=numpy.int32).ravel()

        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. The subgraph indices correspond
        to the sorted input indices. 

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

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

        subGraph = SparseGraph(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.
        """
        if self.getVertexList().getNumVertices() != 0:
            return self.W.toarray()
        else:
            return numpy.zeros((0, 0))

    def getSparseWeightMatrix(self):
        """
        Returns the original sparse weight matrix.

        :returns: A scipy.sparse weight matrix.
        """

        return self.W

    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.SparseGraph`

        :returns: A new graph with same vertex list and addition of edge weights 
        """
        Parameter.checkClass(graph, SparseGraph)
        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")

        #The ideal way is to add both weight matrices together, but this results in a csr
        #We'll just do this manually
        nonZeros = numpy.nonzero(graph.W)
        newGraph = SparseGraph(self.vList, self.undirected)
        newGraph.W = self.W.copy()

        for i in range(len(nonZeros[0])):
            ind1 = nonZeros[0][i]
            ind2 = nonZeros[1][i]
            newGraph.W[ind1, ind2] = self.W[ind1, ind2] + graph.W[ind1, ind2]

        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.SparseGraph`

        :returns: A new graph with edge weights which are multiples of the current and graph
        """
        Parameter.checkClass(graph, SparseGraph)
        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 = SparseGraph(self.vList, self.undirected)
        newGraph.W = self.W.multiply(graph.W)
        return newGraph

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

    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 = SparseGraph(self.vList, self.undirected)
        newGraph.W = self.weightMatrixType()(numpy.ones(
            (self.vList.getNumVertices(), self.vList.getNumVertices())))

        A = self.nativeAdjacencyMatrix()
        newGraph.W = newGraph.W - A

        return newGraph

    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
        """
        #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")

        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")

        self.W = self.weightMatrixType()(W)

    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 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))

        if self.undirected and (W - W.transpose()).nonzero()[0].shape[0]:
            raise ValueError(
                "Weight matrix of undirected graph must be symmetric")

        self.W = W

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

    def removeEdge(self, vertexIndex1, vertexIndex2):
        """
        Remove an 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`
        """
        super(SparseGraph, self).removeEdge(vertexIndex1, vertexIndex2)

        self.W.eliminate_zeros()

    def nativeAdjacencyMatrix(self):
        """
        :returns: the adjacency matrix in the native sparse format.
        """
        try:
            self.W.eliminate_zeros()
        except AttributeError:
            pass

        A = self.W / self.W
        return A

    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.SparseGraph`

        :returns: A new graph with edges from the current graph and not in the input graph. 
        """
        Parameter.checkClass(graph, SparseGraph)
        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()
        A1 = A1 - A2

        A = (A1 + A1.multiply(A1)) / 2
        A.prune()

        newGraph = SparseGraph(self.vList, self.undirected)
        newGraph.W = A
        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):
        W = scipy.io.mmread(filename)
        return W.tolil()

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

    def removeAllEdges(self):
        """
        Removes all edges from this graph.
        """
        self.W = self.W * 0

        #Weirdly we get nan values for the edges after doing the above line
        if sparse.isspmatrix_csr(self.W) or sparse.isspmatrix_csc(self.W):
            self.W.eliminate_zeros()

    def concat(self, graph):
        """
        Take a new graph and concatenate it to the current one. Returns a new graph
        of the concatenated graphs with this graphs vertices first in the new list of
        vertices.

        :param graph: the input graph.
        :type graph: :class:`apgl.graph.SparseGraph`
        """
        Parameter.checkClass(graph, SparseGraph)
        if type(graph.getVertexList()) != type(self.getVertexList()):
            raise ValueError("Vertex lists must be of same type")
        if graph.isUndirected() != self.isUndirected():
            raise ValueError("Graphs must be of the same directed type")

        numVertices = self.getNumVertices() + graph.getNumVertices()
        vList = GeneralVertexList(numVertices)
        vList.setVertices(self.getVertexList().getVertices(),
                          list(range(self.getNumVertices())))
        vList.setVertices(graph.getVertexList().getVertices(),
                          list(range(self.getNumVertices(), numVertices)))
        newGraph = SparseGraph(vList)

        W = scipy.sparse.bmat([[self.W, None], [None, graph.W]], format="csr")
        newGraph.setWeightMatrixSparse(W)

        return newGraph

    def normalisedLaplacianSym(self, outDegree=True, sparse=False):
        """
        Compute the normalised symmetric laplacian matrix using L = I - D^-1/2 W D^-1/2,
        in which W is the weight matrix and D_ii is the sum of the ith vertices weights.

        :param outDegree: whether to use the out-degree for the computation of the degree matrix
        :type outDegree: :class:`bool`

        :param sparse: whether to return a sparse matrix or numpy array
        :type sparse: :class:`bool`

        :returns:  A normalised symmetric laplacian matrix
        """
        W = self.getSparseWeightMatrix()

        if outDegree:
            degrees = numpy.array(W.sum(1)).ravel()
        else:
            degrees = numpy.array(W.sum(1)).ravel()

        L = self.weightMatrixType()(
            (self.getNumVertices(), self.getNumVertices()))
        L.setdiag(numpy.ones(self.getNumVertices()))

        D2 = self.weightMatrixType()(
            (self.getNumVertices(), self.getNumVertices()))
        D2.setdiag((degrees + (degrees == 0))**-0.5)

        L = L - D2.dot(W).dot(D2)

        if sparse == True:
            return L
        else:
            return L.toarray()

    def laplacianMatrix(self, outDegree=True, sparse=False):
        """
        Return the Laplacian matrix of this graph, which is defined as L_{ii} = deg(i)
        L_{ij} = -1 if an edge between i and j, otherwise L_{ij} = 0 . For a directed
        graph one can specify whether to use the out-degree or in-degree.

        :param outDegree: whether to use the out-degree for the computation of the degree matrix
        :type outDegree: :class:`bool`

        :param sparse: whether to return a sparse matrix or numpy array
        :type sparse: :class:`bool`

        :returns:  A laplacian adjacency matrix.
        """
        A = self.nativeAdjacencyMatrix()
        L = self.weightMatrixType()(
            (self.getNumVertices(), self.getNumVertices()))

        if outDegree:
            L.setdiag(self.outDegreeSequence())
        else:
            L.setdiag(self.inDegreeSequence())

        L = L - A

        if sparse == True:
            return L
        else:
            return L.toarray()

    def toCsr(self):
        """
        Convert the internal matrix representation to csr format (compressed sparse row)
        in order to improve the efficiency of certain operations. 
        """
        self.W = self.W.tocsr()

    def toCsc(self):
        """
        Convert the internal matrix representation to csc format (compressed sparse column)
        in order to improve the efficiency of certain operations. 
        """
        self.W = self.W.tocsc()

    def __str__(self):
        output = super(SparseGraph, self).__str__()
        output += ", edge storage " + str(type(self.W))
        return output

    #Class data
    W = None
    vList = None
    undirected = None
    def __init__(self,
                 vertices,
                 undirected=True,
                 W=None,
                 dtype=numpy.float,
                 frmt="csr"):
        """
        Create a SparseGraph 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: lil_matrix, 
        csr_matrix, csc_matrix and dok_matrix. The default sparse matrix is 
        csr_matrix. 

        :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 dtype: the data type of the sparse matrix if W is not specified. 
        
        :param frmt: the format of the sparse matrix: lil, csr or csc if W is not specified 
        """
        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 (sparse.issparse(W) and W.shape == (
                self.vList.getNumVertices(), self.vList.getNumVertices())):
            raise ValueError(
                "Input argument W must be None or sparse matrix of size " +
                str(self.vList.getNumVertices()))

        self.undirected = undirected

        if frmt == "lil":
            matrix = sparse.lil_matrix
        elif frmt == "csr":
            matrix = sparse.csr_matrix
        elif frmt == "csc":
            matrix = sparse.csc_matrix
        else:
            raise ValueError("Invalid sparse matrix format: " + frmt)

        #Terrible hack alert:  can't create a zero size sparse matrix, so we settle
        #for one of size 1. Better is to create a new class.
        if self.vList.getNumVertices() == 0 and W == None:
            self.W = matrix((1, 1), dtype=dtype)
        elif W == None:
            self.W = matrix(
                (self.vList.getNumVertices(), self.vList.getNumVertices()),
                dtype=dtype)
        else:
            self.W = W
            #The next line is for error checking mainly
            self.setWeightMatrix(W)
 def setUp(self):
     self.VListType = GeneralVertexList
     self.numVertices = 10
     self.vList = GeneralVertexList(self.numVertices)
     self.emptyVertex = None
     self.initialise()
    def testGenerate(self):
        degSequence = numpy.array([2, 1, 3, 0, 0, 0, 0, 0, 0, 1])
        generator = ConfigModelGenerator(degSequence)

        numVertices = 10
        graph = SparseGraph(GeneralVertexList(numVertices))
        graph = generator.generate(graph)

        tol = 3
        self.assertTrue(
            numpy.linalg.norm(degSequence - graph.degreeSequence()) < tol)

        degSequence = numpy.array([2, 1, 3, 0, 2, 1, 4, 0, 0, 1])
        generator.setOutDegSequence(degSequence)
        graph.removeAllEdges()
        graph = generator.generate(graph)

        self.assertTrue(
            numpy.linalg.norm(degSequence - graph.degreeSequence()) < tol)

        #Test using a non-empty graph
        degSequence = numpy.array([0, 0, 0, 2, 0, 0, 0, 1, 1, 0])
        generator.setOutDegSequence(degSequence)
        oldDegSequence = graph.degreeSequence()

        self.assertRaises(ValueError, generator.generate, graph, True)
        graph = generator.generate(graph, False)

        diffSequence = graph.degreeSequence() - oldDegSequence
        self.assertTrue(numpy.linalg.norm(degSequence - diffSequence) < tol)

        #Test the case where we also have an in-degree sequence
        degSequence = numpy.array([2, 1, 3, 0, 0, 0, 0, 0, 0, 1])
        inDegSequence = numpy.array([1, 1, 1, 1, 1, 1, 1, 0, 0, 0])
        generator = ConfigModelGenerator(degSequence, inDegSequence)

        graph = SparseGraph(GeneralVertexList(numVertices))
        self.assertRaises(ValueError, generator.generate, graph)

        graph = SparseGraph(GeneralVertexList(numVertices), False)
        graph = generator.generate(graph)

        self.assertTrue(
            numpy.linalg.norm(degSequence - graph.outDegreeSequence()) < tol)
        self.assertTrue(
            numpy.linalg.norm(inDegSequence - graph.inDegreeSequence()) < tol)

        outDegSequence = numpy.array([2, 1, 3, 0, 2, 1, 4, 0, 0, 1])
        inDegSequence = numpy.array([1, 2, 1, 1, 2, 1, 2, 1, 2, 1])
        generator.setOutDegSequence(outDegSequence)
        generator.setInDegSequence(inDegSequence)
        graph.removeAllEdges()
        graph = generator.generate(graph)

        self.assertTrue(
            numpy.linalg.norm(outDegSequence -
                              graph.outDegreeSequence()) < tol)
        self.assertTrue(
            numpy.linalg.norm(inDegSequence - graph.inDegreeSequence()) < tol)

        #In the case that the in-degree sequence sum larger than that of the out-degree it is
        #not satisfied, but the out-degree should be.
        inDegSequence = numpy.array([1, 2, 1, 1, 2, 1, 2, 1, 5, 6])
        generator.setInDegSequence(inDegSequence)
        graph.removeAllEdges()
        graph = generator.generate(graph)
        self.assertTrue(
            numpy.linalg.norm(outDegSequence -
                              graph.outDegreeSequence()) < tol)

        #Now try the other way around
        generator.setOutDegSequence(inDegSequence)
        generator.setInDegSequence(outDegSequence)
        graph.removeAllEdges()
        graph = generator.generate(graph)
        self.assertTrue(
            numpy.linalg.norm(outDegSequence - graph.inDegreeSequence()) < tol)

        #Test growing graph
        outDegSequence = numpy.array([2, 1, 3, 0, 2, 1, 4, 0, 0, 1])
        inDegSequence = numpy.array([1, 2, 1, 1, 2, 1, 2, 1, 2, 1])

        generator.setOutDegSequence(outDegSequence)
        generator.setInDegSequence(inDegSequence)
        graph.removeAllEdges()
        graph = generator.generate(graph)

        newOutDegreeSequence = numpy.array([2, 1, 3, 5, 2, 1, 4, 0, 0, 1])
        newInDegreeSequence = numpy.array([2, 3, 2, 2, 3, 1, 2, 1, 2, 1])
        diffOutSequence = newOutDegreeSequence - graph.outDegreeSequence()
        diffInSequence = newInDegreeSequence - graph.inDegreeSequence()
        generator.setOutDegSequence(diffOutSequence)
        generator.setInDegSequence(diffInSequence)
        graph = generator.generate(graph, False)

        self.assertTrue(
            numpy.linalg.norm(newOutDegreeSequence -
                              graph.outDegreeSequence()) < tol)
        self.assertTrue(
            numpy.linalg.norm(newInDegreeSequence -
                              graph.inDegreeSequence()) < tol)
Beispiel #23
0
"""
Name: Generate Graph:
Author: Jia_qiu Wang(王佳秋)
Data: December, 2016
function:
"""

from apgl.graph.GeneralVertexList import GeneralVertexList
from apgl.graph.SparseGraph import SparseGraph

numVertices = 10
graph = SparseGraph(GeneralVertexList(numVertices))

graph[0, 1] = 1
graph[0, 2] = 1

P = graph.floydWarshall()
print("geodesicDistance:", graph.geodesicDistance(P=P))
print("harmonicGeodesicDistance:", graph.harmonicGeodesicDistance(P=P))

    def testSaveLoad(self):
        try:
            vList = GeneralVertexList(self.numVertices)
            vList.setVertex(0, "abc")
            vList.setVertex(1, 12)
            vList.setVertex(2, "num")

            tempDir = PathDefaults.getTempDir()
            fileName = tempDir + "vList"

            vList.save(fileName)

            vList2 = GeneralVertexList.load(fileName)

            for i in range(self.numVertices):
                self.assertEquals(vList.getVertex(i), vList2.getVertex(i))
        except IOError as e:
            logging.warn(e)
            pass