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