def __init__(self, vertex1Indices, vertex2Indices, converters, undirected=True): """ vertex1Indices is a list of fields for the first vertex, with the 1st index being the ID. """ if len(vertex1Indices) < 1 or len(vertex1Indices) < 1: raise ValueError("vertexIndices must have at least 1 index") if len(vertex1Indices) != len(vertex2Indices): raise ValueError("len(vertex1Indices)=" + str(len(vertex1Indices)) + "and len(vertex2Indices)=" + len(vertex2Indices)) Parameter.checkList(vertex1Indices, Parameter.checkInt, [0, float('inf')]) Parameter.checkList(vertex2Indices, Parameter.checkInt, [0, float('inf')]) self.vertex1IdIndex = vertex1Indices[0] self.vertex2IdIndex = vertex2Indices[0] self.vertex1Indices = copy.copy(vertex1Indices) self.vertex2Indices = copy.copy(vertex2Indices) self.vertex1Indices.remove(self.vertex1IdIndex) self.vertex2Indices.remove(self.vertex2IdIndex) self.converters = converters self.undirected = undirected self.edgeWeight = 1
def setVertices(self, vertices, indices=None): """ Set the vertices to the given list of vertices. If indices = None then all vertices are replaced, and if not the given indices are used. :param vertices: a list of vertices.. :type vertices: :class:`list` :param indices: a list of indices of the same length as vertices or None for all indices in this object. :type indices: :class:`list` """ if indices != None: Parameter.checkList(indices, Parameter.checkIndex, [0, len(self.V)]) if len(vertices) != len(indices): raise ValueError( "Length of indices list must be same as that of vertices list" ) if indices == None and len(vertices) != len(self.V): raise ValueError("Incorrect number of vertices " + str(len(vertices)) + ", expecting " + str(len(self.V))) if indices == None: for i in range(len(vertices)): self.V[i] = vertices[i] else: for i in range(len(indices)): self.V[indices[i]] = vertices[i]
def sequenceVectorStats(self, graph, subgraphIndices, treeStats=False, eigenStats=True): """ Pass in a list of graphs are returns a series of statistics. Each list element is a dict of vector statistics. """ Parameter.checkClass(graph, AbstractMatrixGraph) for inds in subgraphIndices: Parameter.checkList(inds, Parameter.checkInt, [0, graph.getNumVertices()]) Parameter.checkBoolean(treeStats) numGraphs = len(subgraphIndices) statsDictList = [] for i in range(numGraphs): Util.printIteration(i, self.vectorPrintStep, numGraphs) subgraph = graph.subgraph(subgraphIndices[i]) statsDictList.append( self.vectorStatistics(subgraph, treeStats, eigenStats)) return statsDictList
def sequenceScalarStats(self, graph, subgraphIndices, slowStats=True, treeStats=False): """ Pass in a graph and list of subgraph indices and returns a series of statistics. Each row corresponds to the statistics on the subgraph. """ Parameter.checkClass(graph, AbstractMatrixGraph) for inds in subgraphIndices: Parameter.checkList(inds, Parameter.checkInt, [0, graph.getNumVertices()]) Parameter.checkBoolean(slowStats) Parameter.checkBoolean(treeStats) numGraphs = len(subgraphIndices) statsMatrix = numpy.zeros((numGraphs, self.numStats)) for i in range(numGraphs): Util.printIteration(i, self.printStep, numGraphs) #logging.debug("Subgraph size: " + str(len(subgraphIndices[i]))) subgraph = graph.subgraph(subgraphIndices[i]) statsMatrix[i, :] = self.scalarStatistics(subgraph, slowStats, treeStats) return statsMatrix
def subList(self, indices): """ Returns a subset of this object, indicated by the given indices. """ Parameter.checkList(indices, Parameter.checkIndex, (0, self.getNumVertices())) vList = VertexList(len(indices), self.getNumFeatures()) vList.setVertices(self.getVertices(indices)) return vList
def subList(self, indices): """ Returns a subset of this object, indicated by the given indices. """ Parameter.checkList(indices, Parameter.checkIndex, (0, self.getNumVertices())) vList = GeneralVertexList(len(indices)) vList.setVertices(self.getVertices(indices)) return vList
def setOutDegSequence(self, outDegSequence): """ Set the (out)degree sequence of this object. :param outDegSequence: a vector of degrees for each vertex in the graph. :type outDegSequence: :class:`numpy.ndarray` """ Parameter.checkClass(outDegSequence, numpy.ndarray) if outDegSequence.ndim != 1: raise ValueError("Degree sequence must be one dimensional") Parameter.checkList(outDegSequence, Parameter.checkInt, [0, outDegSequence.shape[0]]) self.outDegSequence = outDegSequence
def setOutDegSequence(self, outDegSequence): ''' Set the (out)degree sequence of this object. :param outDegSequence: a vector of degrees for each vertex in the graph. :type outDegSequence: :class:`numpy.ndarray` ''' Parameter.checkClass(outDegSequence, numpy.ndarray) if outDegSequence.ndim != 1: raise ValueError("Degree sequence must be one dimensional") Parameter.checkList(outDegSequence, Parameter.checkInt, [0, outDegSequence.shape[0]]) self.outDegSequence = outDegSequence
def setInDegSequence(self, inDegSequence): """ Set the (in)degree sequence of this object. :param inDegSequence: a vector of degrees for each vertex in the graph. :type inDegSequence: :class:`numpy.ndarray` """ Parameter.checkClass(inDegSequence, numpy.ndarray) if inDegSequence.ndim != 1: raise ValueError("Degree sequence must be one dimensional") if inDegSequence.shape[0] != self.outDegSequence.shape[0]: raise ValueError("In-degree sequence must be same length as out-degree sequence") Parameter.checkList(inDegSequence, Parameter.checkInt, [0, inDegSequence.shape[0]]) self.inDegSequence = inDegSequence
def getVertices(self, vertexIndices=None): """ Returns a set of vertices specified by vertexIndices. If vertexIndices is None then all vertices are returned. :param vertexIndices: a list of vertex indices. :type vertexIndices: :class:`list` :returns: A set of vertices corresponding to the input indices. """ if vertexIndices == None: return self.V else: Parameter.checkList(vertexIndices, Parameter.checkIndex, (0, self.V.shape[0])) return self.V[vertexIndices]
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 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 expandIntArray(v): """ Take a vector of integers and expand it into a vector with counts of the corresponding integers. For example, with v = [1, 3, 2, 4], the expanded vector is [0, 1, 1, 1, 2, 2, 3, 3, 3, 3]. """ Parameter.checkClass(v, numpy.ndarray) Parameter.checkList(v, Parameter.checkInt, [0, float('inf')]) w = numpy.zeros(numpy.sum(v), numpy.int) currentInd = 0 for i in range(v.shape[0]): w[currentInd:currentInd + v[i]] = i currentInd += v[i] return w
def expandIntArray(v): """ Take a vector of integers and expand it into a vector with counts of the corresponding integers. For example, with v = [1, 3, 2, 4], the expanded vector is [0, 1, 1, 1, 2, 2, 3, 3, 3, 3]. """ Parameter.checkClass(v, numpy.ndarray) Parameter.checkList(v, Parameter.checkInt, [0, float('inf')]) w = numpy.zeros(numpy.sum(v), numpy.int) currentInd = 0 for i in range(v.shape[0]): w[currentInd:currentInd+v[i]] = i currentInd += v[i] return w
def harmonicGeodesicDistance(self, P=None, vertexInds=None): """ Compute the "harmonic mean" geodesic distance for a graph. This is denoted by the inverse of 1/(1/2 n(n+1)) \sum_{i<=j} d_ij^-1 where d_ij is the shortest path length between i and j for an undirected graph. The distance from a node to itself is infinite. For a directed graph, the inverse distance is 1/n^2 sum_{i,j} d_ij^-1. :param P: An optional nxn matrix whose ijth entry is the shortest path from i to j. :type P: :class:`ndarray` :param vertexInds: An optional list of vertices used to compute the mean geodesic distance. If this list is none, then all vertices are used. :type vertexInds: :class:`list` :returns: The mean harmonic geodesic distance of this graph. """ if P!=None and (type(P) != numpy.ndarray or P.shape != (self.getNumVertices(), self.getNumVertices())): raise ValueError("P must be array of same size as weight matrix of graph") if vertexInds!=None: Parameter.checkList(vertexInds, Parameter.checkInt, [0, self.getNumVertices()]) if self.getNumVertices() == 0 or (vertexInds != None and len(vertexInds)==0): return 0 if P == None: P = self.floydWarshall(True) else: P = P.copy() if vertexInds != None: P = P[vertexInds, :][:, vertexInds] n = P.shape[0] P = 1/(P + numpy.diag(numpy.ones(n)*numpy.inf)) if self.isUndirected(): distanceSum = numpy.sum(numpy.triu(P)) if distanceSum != 0: return (n*(n+1))/(2*distanceSum) else: distanceSum = numpy.sum(P) if distanceSum != 0: return n**2/distanceSum #Means that all vertices are disconnected return float('inf')
def setInDegSequence(self, inDegSequence): ''' Set the (in)degree sequence of this object. :param inDegSequence: a vector of degrees for each vertex in the graph. :type inDegSequence: :class:`numpy.ndarray` ''' Parameter.checkClass(inDegSequence, numpy.ndarray) if inDegSequence.ndim != 1: raise ValueError("Degree sequence must be one dimensional") if inDegSequence.shape[0] != self.outDegSequence.shape[0]: raise ValueError( "In-degree sequence must be same length as out-degree sequence" ) Parameter.checkList(inDegSequence, Parameter.checkInt, [0, inDegSequence.shape[0]]) self.inDegSequence = inDegSequence
def sequenceVectorStats(self, graph, subgraphIndices, treeStats=False, eigenStats=True): """ Pass in a list of graphs are returns a series of statistics. Each list element is a dict of vector statistics. """ Parameter.checkClass(graph, AbstractMatrixGraph) for inds in subgraphIndices: Parameter.checkList(inds, Parameter.checkInt, [0, graph.getNumVertices()]) Parameter.checkBoolean(treeStats) numGraphs = len(subgraphIndices) statsDictList = [] for i in range(numGraphs): Util.printIteration(i, self.vectorPrintStep, numGraphs) subgraph = graph.subgraph(subgraphIndices[i]) statsDictList.append(self.vectorStatistics(subgraph, treeStats, eigenStats)) return statsDictList
def getVertices(self, vertexIndices=None): """ Returns a list of vertices specified by vertexIndices, or all vertices if vertexIndices == None. :param vertexIndices: a list of vertex indices. :type vertexIndices: :class:`list` :returns: A set of vertices corresponding to the input indices. """ if vertexIndices != None: Parameter.checkList(vertexIndices, Parameter.checkIndex, (0, len(self.V))) else: vertexIndices = range(len(self.V)) vertices = [] for i in vertexIndices: vertices.append(self.V[i]) return vertices
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 __init__(self, initialGraph, k): """ Initialise with a starting graph, and number of iterations k. The weights of the initial graph correspond to probabilities. :param initialGraph: The intial graph to use. :type initialGraph: :class:`apgl.graph.AbstractMatrixGraph` :param k: The number of iterations. :type k: :class:`int` """ Parameter.checkInt(k, 1, float('inf')) edgeVals = initialGraph.getEdgeValues(initialGraph.getAllEdges()) Parameter.checkList(edgeVals, Parameter.checkFloat, [0.0, 1.0]) W = initialGraph.getWeightMatrix() if (numpy.diag(W) == numpy.zeros(W.shape[0])).any(): raise ValueError("Initial graph must have all self-edges") self.initialGraph = initialGraph self.k = k
def __init__(self, initialGraph, k): """ Initialise with a starting graph, and number of iterations k. The weights of the initial graph correspond to probabilities. :param initialGraph: The intial graph to use. :type initialGraph: :class:`apgl.graph.AbstractMatrixGraph` :param k: The number of iterations. :type k: :class:`int` """ Parameter.checkInt(k, 1, float('inf')) edgeVals = initialGraph.getEdgeValues(initialGraph.getAllEdges()) Parameter.checkList(edgeVals, Parameter.checkFloat, [0.0, 1.0]) W = initialGraph.getWeightMatrix() if (numpy.diag(W)==numpy.zeros(W.shape[0])).any(): raise ValueError("Initial graph must have all self-edges") self.initialGraph = initialGraph self.k = k
def categoricalToIndicator(self, X, indices): """ Convert a set of categorical variables to indicator ones. """ Parameter.checkList(indices, Parameter.checkIndex, (0, X.shape[1])) X2 = numpy.zeros((X.shape[0], 0)) for i in range(X.shape[1]): if i in indices: categories = numpy.unique(X[:, i]) Z = numpy.zeros((X.shape[0], categories.shape[0])) for j in range(categories.shape[0]): Z[:, j] = X[:, i] == categories[j] X2 = numpy.c_[X2, Z] else: X2 = numpy.c_[X2, X[:, i]] return X2
def sequenceScalarStats(self, graph, subgraphIndices, slowStats=True, treeStats=False): """ Pass in a graph and list of subgraph indices and returns a series of statistics. Each row corresponds to the statistics on the subgraph. """ Parameter.checkClass(graph, AbstractMatrixGraph) for inds in subgraphIndices: Parameter.checkList(inds, Parameter.checkInt, [0, graph.getNumVertices()]) Parameter.checkBoolean(slowStats) Parameter.checkBoolean(treeStats) numGraphs = len(subgraphIndices) statsMatrix = numpy.zeros((numGraphs, self.numStats)) for i in range(numGraphs): Util.printIteration(i, self.printStep, numGraphs) logging.debug("Subgraph size: " + str(len(subgraphIndices[i]))) subgraph = graph.subgraph(subgraphIndices[i]) statsMatrix[i, :] = self.scalarStatistics(subgraph, slowStats, treeStats) return statsMatrix
def testCheckList(self): lst = [1, 2, 3, 2, 2] Parameter.checkList(lst, Parameter.checkInt, [1, 3]) lst = [1, 2, 3, 2, 4] self.assertRaises(ValueError, Parameter.checkList, lst, Parameter.checkInt, [1, 3]) lst = [1, 2, 3, 2, 0] self.assertRaises(ValueError, Parameter.checkList, lst, Parameter.checkInt, [1, 3]) lst = [1, 2, 3, 2, 1.2] self.assertRaises(ValueError, Parameter.checkList, lst, Parameter.checkInt, [1, 3]) lst = "a" self.assertRaises(ValueError, Parameter.checkList, lst, Parameter.checkInt, [1, 3]) lst = [0.1, 0.6, 1.4] Parameter.checkList(lst, Parameter.checkFloat, [0.1, 3.0]) #Test use of array lst = numpy.array([0.1, 0.6, 1.4]) Parameter.checkList(lst, Parameter.checkFloat, [0.1, 3.0]) lst = numpy.array([[0.1, 0.6, 1.4]]) self.assertRaises(ValueError, Parameter.checkList, lst, Parameter.checkFloat, [0.1, 3.0])
def geodesicDistance(self, P=None, vertexInds=None): """ Compute the mean geodesic distance for a graph. This is denoted for an undirected graph by 1/(1/2 n(n+1)) \sum_{i<=j} d_ij where d_ij is the shortest path length between i and j. Note that if i and j are not connected we assume a path length of 0. If the graph is directed then the geodesic distance is 1/(n^2) sum_{i, j} d_ij. :param P: An optional nxn matrix whose ijth entry is the shortest path from i to j. :type P: :class:`ndarray` :param vertexInds: An optional list of vertices used to compute the mean geodesic distance. If this list is none, then all vertices are used. :type vertexInds: :class:`list` :returns: The mean geodesic distance of this graph. """ if P!=None and (type(P) != numpy.ndarray or P.shape != (self.getNumVertices(), self.getNumVertices())): raise ValueError("P must be array of same size as weight matrix of graph") if vertexInds!=None: Parameter.checkList(vertexInds, Parameter.checkInt, [0, self.getNumVertices()]) if self.getNumVertices() == 0 or (vertexInds != None and len(vertexInds)==0): return 0 if P == None: P = self.floydWarshall(True) else: P = P.copy() if vertexInds != None: P = P[vertexInds, :][:, vertexInds] n = P.shape[0] P[P==numpy.inf] = 0 P[numpy.diag_indices(P.shape[0])] = 0.0 distanceSum = numpy.sum(P) if self.isUndirected(): return distanceSum/(n*(n+1)) else: return distanceSum/(n**2)
def setVertices(self, vertices, indices=None): """ Set the vertices to the given list of vertices. If indices = None then all vertices are replaced, and if not the given indices are used. :param vertices: a list of vertices.. :type vertices: :class:`list` :param indices: a list of indices of the same length as vertices or None for all indices in this object. :type indices: :class:`list` """ if indices!=None: Parameter.checkList(indices, Parameter.checkIndex, [0, len(self.V)]) if len(vertices) != len(indices): raise ValueError("Length of indices list must be same as that of vertices list") if indices==None and len(vertices) != len(self.V): raise ValueError("Incorrect number of vertices " + str(len(vertices)) + ", expecting " + str(len(self.V))) if indices == None: for i in range(len(vertices)): self.V[i] = vertices[i] else: for i in range(len(indices)): self.V[indices[i]] = vertices[i]