Source code for pyamg.graph

"""Algorithms related to graphs."""


from warnings import warn
import numpy as np
from scipy import sparse
from . import amg_core


[docs] def asgraph(G): """Return (square) matrix as sparse.""" if not (sparse.isspmatrix_csr(G) or sparse.isspmatrix_csc(G)): G = sparse.csr_matrix(G) if G.shape[0] != G.shape[1]: raise ValueError('expected square matrix') return G
[docs] def maximal_independent_set(G, algo='serial', k=None): """Compute a maximal independent vertex set for a graph. Parameters ---------- G : sparse matrix Symmetric matrix, preferably in sparse CSR or CSC format The nonzeros of G represent the edges of an undirected graph. algo : {'serial', 'parallel'} Algorithm used to compute the MIS * serial : greedy serial algorithm * parallel : variant of Luby's parallel MIS algorithm Returns ------- S : array S[i] = 1 if vertex i is in the MIS S[i] = 0 otherwise Notes ----- Diagonal entries in the G (self loops) will be ignored. Luby's algorithm is significantly more expensive than the greedy serial algorithm. """ G = asgraph(G) N = G.shape[0] mis = np.empty(N, dtype='intc') mis[:] = -1 if k is None: if algo == 'serial': fn = amg_core.maximal_independent_set_serial fn(N, G.indptr, G.indices, -1, 1, 0, mis) elif algo == 'parallel': fn = amg_core.maximal_independent_set_parallel fn(N, G.indptr, G.indices, -1, 1, 0, mis, np.random.rand(N), -1) else: raise ValueError(f'Unknown algorithm ({algo})') else: fn = amg_core.maximal_independent_set_k_parallel fn(N, G.indptr, G.indices, k, mis, np.random.rand(N), -1) return mis
[docs] def vertex_coloring(G, method='MIS'): """Compute a vertex coloring of a graph. Parameters ---------- G : sparse matrix Symmetric matrix, preferably in sparse CSR or CSC format The nonzeros of G represent the edges of an undirected graph. method : string Algorithm used to compute the vertex coloring: * 'MIS' - Maximal Independent Set * 'JP' - Jones-Plassmann (parallel) * 'LDF' - Largest-Degree-First (parallel) Returns ------- coloring : array An array of vertex colors (integers beginning at 0) Notes ----- Diagonal entries in the G (self loops) will be ignored. """ G = asgraph(G) N = G.shape[0] coloring = np.empty(N, dtype='intc') if method == 'MIS': fn = amg_core.vertex_coloring_mis fn(N, G.indptr, G.indices, coloring) elif method == 'JP': fn = amg_core.vertex_coloring_jones_plassmann fn(N, G.indptr, G.indices, coloring, np.random.rand(N)) elif method == 'LDF': fn = amg_core.vertex_coloring_LDF fn(N, G.indptr, G.indices, coloring, np.random.rand(N)) else: raise ValueError(f'Unknown method ({method})') return coloring
[docs] def bellman_ford(G, seeds): """Bellman-Ford iteration. Parameters ---------- G : sparse matrix Directed graph with positive weights. seeds : list Starting seeds Returns ------- distances : array Distance of each point to the nearest seed nearest_seed : array Index of the nearest seed Notes ----- This should be viewed as the transpose of Bellman-Ford in scipy.sparse.csgraph. Here, bellman_ford is used to find the shortest path from any point *to* the seeds. In csgraph, bellman_ford is used to find "the shortest distance from point i to point j". So csgraph.bellman_ford could be run `for seed in seeds`. Also note that `test_graph.py` tests against `csgraph.bellman_ford(G.T)`. See Also -------- scipy.sparse.csgraph.bellman_ford """ G = asgraph(G) N = G.shape[0] if G.nnz > 0: if G.data.min() < 0: raise ValueError('Bellman-Ford is defined only for positive weights.') if G.dtype == complex: raise ValueError('Bellman-Ford is defined only for real weights.') seeds = np.asarray(seeds, dtype='intc') distances = np.full(N, np.inf, dtype=G.dtype) distances[seeds] = 0 nearest_seed = np.full(N, -1, dtype='intc') nearest_seed[seeds] = seeds amg_core.bellman_ford(N, G.indptr, G.indices, G.data, distances, nearest_seed) return (distances, nearest_seed)
[docs] def lloyd_cluster(G, seeds, maxiter=10): """Perform Lloyd clustering on graph with weighted edges. Parameters ---------- G : csr_matrix, csc_matrix A sparse NxN matrix where each nonzero entry G[i,j] is the distance between nodes i and j. seeds : int array If seeds is an integer, then its value determines the number of clusters. Otherwise, seeds is an array of unique integers between 0 and N-1 that will be used as the initial seeds for clustering. maxiter : int The maximum number of iterations to perform. Returns ------- distances : array final distances clusters : int array id of each cluster of points seeds : int array index of each seed Notes ----- If G has complex values, abs(G) is used instead. """ G = asgraph(G) N = G.shape[0] if G.dtype.kind == 'c': # complex dtype G = np.abs(G) # interpret seeds argument if np.isscalar(seeds): seeds = np.random.permutation(N)[:seeds] seeds = seeds.astype('intc') else: seeds = np.array(seeds, dtype='intc') if len(seeds) < 1: raise ValueError('at least one seed is required') if seeds.min() < 0: raise ValueError(f'Invalid seed index ({seeds.min()})') if seeds.max() >= N: raise ValueError(f'Invalid seed index ({seeds.max()})') clusters = np.empty(N, dtype='intc') distances = np.empty(N, dtype=G.dtype) for _it in range(1, maxiter+1): last_seeds = seeds.copy() amg_core.lloyd_cluster(N, G.indptr, G.indices, G.data, len(seeds), distances, clusters, seeds) if (seeds == last_seeds).all(): break if _it == maxiter: warn('Lloyd clustering reached maxiter (did not converge)') return (distances, clusters, seeds)
[docs] def connected_components(G): """Compute the connected components of a graph. The connected components of a graph G, which is represented by a symmetric sparse matrix, are labeled with the integers 0,1,..(K-1) where K is the number of components. Parameters ---------- G : symmetric matrix, preferably in sparse CSR or CSC format The nonzeros of G represent the edges of an undirected graph. Returns ------- components : ndarray An array of component labels for each vertex of the graph. Notes ----- If the nonzero structure of G is not symmetric, then the result is undefined. Examples -------- >>> from pyamg.graph import connected_components >>> print(connected_components( [[0,1,0],[1,0,1],[0,1,0]] )) [0 0 0] >>> print(connected_components( [[0,1,0],[1,0,0],[0,0,0]] )) [0 0 1] >>> print(connected_components( [[0,0,0],[0,0,0],[0,0,0]] )) [0 1 2] >>> print(connected_components( [[0,1,0,0],[1,0,0,0],[0,0,0,1],[0,0,1,0]] )) [0 0 1 1] """ G = asgraph(G) N = G.shape[0] components = np.empty(N, G.indptr.dtype) fn = amg_core.connected_components fn(N, G.indptr, G.indices, components) return components
[docs] def symmetric_rcm(A): """Symmetric Reverse Cutthill-McKee. Parameters ---------- A : sparse matrix Sparse matrix Returns ------- B : sparse matrix Permuted matrix with reordering Notes ----- Get a pseudo-peripheral node, then call BFS Examples -------- >>> from pyamg import gallery >>> from pyamg.graph import symmetric_rcm >>> n = 200 >>> density = 1.0/n >>> A = gallery.sprand(n, n, density, format='csr') >>> S = A + A.T >>> # try the visualizations >>> # import matplotlib.pyplot as plt >>> # plt.figure() >>> # plt.subplot(121) >>> # plt.spy(S,marker='.') >>> # plt.subplot(122) >>> # plt.spy(symmetric_rcm(S),marker='.') See Also -------- pseudo_peripheral_node """ dummy_root, order, dummy_level = pseudo_peripheral_node(A) p = order[::-1] return A[p, :][:, p]
[docs] def pseudo_peripheral_node(A): """Find a pseudo peripheral node. Parameters ---------- A : sparse matrix Sparse matrix Returns ------- x : int Location of the node order : array BFS ordering level : array BFS levels Notes ----- Algorithm in Saad """ n = A.shape[0] valence = np.diff(A.indptr) # select an initial node x, set delta = 0 x = int(np.random.rand() * n) delta = 0 while True: # do a level-set traversal from x order, level = breadth_first_search(A, x) # select a node y in the last level with min degree maxlevel = level.max() lastnodes = np.where(level == maxlevel)[0] lastnodesvalence = valence[lastnodes] minlastnodesvalence = lastnodesvalence.min() y = np.where(lastnodesvalence == minlastnodesvalence)[0][0] y = lastnodes[y] # if d(x,y)>delta, set, and go to bfs above if level[y] > delta: x = y delta = level[y] else: return x, order, level