Source code for pyamg.aggregation.aggregate

"""Aggregation methods."""


from warnings import warn
import numpy as np
from scipy import sparse
from .. import amg_core
from ..graph import lloyd_cluster
from ..strength import classical_strength_of_connection


[docs] def standard_aggregation(C): """Compute the sparsity pattern of the tentative prolongator. Parameters ---------- C : csr_matrix strength of connection matrix Returns ------- AggOp : csr_matrix aggregation operator which determines the sparsity pattern of the tentative prolongator Cpts : array array of Cpts, i.e., Cpts[i] = root node of aggregate i Examples -------- >>> from scipy.sparse import csr_matrix >>> from pyamg.gallery import poisson >>> from pyamg.aggregation.aggregate import standard_aggregation >>> A = poisson((4,), format='csr') # 1D mesh with 4 vertices >>> A.toarray() array([[ 2., -1., 0., 0.], [-1., 2., -1., 0.], [ 0., -1., 2., -1.], [ 0., 0., -1., 2.]]) >>> standard_aggregation(A)[0].toarray() # two aggregates array([[1, 0], [1, 0], [0, 1], [0, 1]], dtype=int8) >>> A = csr_matrix([[1,0,0],[0,1,1],[0,1,1]]) >>> A.toarray() # first vertex is isolated array([[1, 0, 0], [0, 1, 1], [0, 1, 1]]) >>> standard_aggregation(A)[0].toarray() # one aggregate array([[0], [1], [1]], dtype=int8) See Also -------- amg_core.standard_aggregation """ if not sparse.isspmatrix_csr(C): raise TypeError('expected csr_matrix') if C.shape[0] != C.shape[1]: raise ValueError('expected square matrix') index_type = C.indptr.dtype num_rows = C.shape[0] Tj = np.empty(num_rows, dtype=index_type) # stores the aggregate #s Cpts = np.empty(num_rows, dtype=index_type) # stores the Cpts fn = amg_core.standard_aggregation num_aggregates = fn(num_rows, C.indptr, C.indices, Tj, Cpts) Cpts = Cpts[:num_aggregates] # no nodes aggregated if num_aggregates == 0: # return all zero matrix and no Cpts return sparse.csr_matrix((num_rows, 1), dtype='int8'), \ np.array([], dtype=index_type) shape = (num_rows, num_aggregates) # some nodes not aggregated if Tj.min() == -1: mask = Tj != -1 row = np.arange(num_rows, dtype=index_type)[mask] col = Tj[mask] data = np.ones(len(col), dtype='int8') return sparse.coo_matrix((data, (row, col)), shape=shape).tocsr(), Cpts # all nodes aggregated Tp = np.arange(num_rows+1, dtype=index_type) Tx = np.ones(len(Tj), dtype='int8') return sparse.csr_matrix((Tx, Tj, Tp), shape=shape), Cpts
[docs] def naive_aggregation(C): """Compute the sparsity pattern of the tentative prolongator. Parameters ---------- C : csr_matrix strength of connection matrix Returns ------- AggOp : csr_matrix aggregation operator which determines the sparsity pattern of the tentative prolongator Cpts : array array of Cpts, i.e., Cpts[i] = root node of aggregate i Examples -------- >>> from scipy.sparse import csr_matrix >>> from pyamg.gallery import poisson >>> from pyamg.aggregation.aggregate import naive_aggregation >>> A = poisson((4,), format='csr') # 1D mesh with 4 vertices >>> A.toarray() array([[ 2., -1., 0., 0.], [-1., 2., -1., 0.], [ 0., -1., 2., -1.], [ 0., 0., -1., 2.]]) >>> naive_aggregation(A)[0].toarray() # two aggregates array([[1, 0], [1, 0], [0, 1], [0, 1]], dtype=int8) >>> A = csr_matrix([[1,0,0],[0,1,1],[0,1,1]]) >>> A.toarray() # first vertex is isolated array([[1, 0, 0], [0, 1, 1], [0, 1, 1]]) >>> naive_aggregation(A)[0].toarray() # two aggregates array([[1, 0], [0, 1], [0, 1]], dtype=int8) See Also -------- amg_core.naive_aggregation Notes ----- Differs from standard aggregation. Each dof is considered. If it has been aggregated, skip over. Otherwise, put dof and any unaggregated neighbors in an aggregate. Results in possibly much higher complexities than standard aggregation. """ if not sparse.isspmatrix_csr(C): raise TypeError('expected csr_matrix') if C.shape[0] != C.shape[1]: raise ValueError('expected square matrix') index_type = C.indptr.dtype num_rows = C.shape[0] Tj = np.empty(num_rows, dtype=index_type) # stores the aggregate #s Cpts = np.empty(num_rows, dtype=index_type) # stores the Cpts fn = amg_core.naive_aggregation num_aggregates = fn(num_rows, C.indptr, C.indices, Tj, Cpts) Cpts = Cpts[:num_aggregates] Tj = Tj - 1 if num_aggregates == 0: # all zero matrix return sparse.csr_matrix((num_rows, 1), dtype='int8'), Cpts shape = (num_rows, num_aggregates) # all nodes aggregated Tp = np.arange(num_rows+1, dtype=index_type) Tx = np.ones(len(Tj), dtype='int8') return sparse.csr_matrix((Tx, Tj, Tp), shape=shape), Cpts
def pairwise_aggregation(A, matchings=2, theta=0.25, norm='min', compute_P=False): """Compute the sparsity pattern of the tentative prolongator. Parameters ---------- A : csr_matrix or bsr_matrix level matrix matchings : int, default 2 number of times to perform pairwise aggregation; each matching increases coarsening factor by about two. theta : float, default 0.25 Strength tolerance used in computing classical SOC. norm : string, default 'min' Norm type used in computing classical SOC. compute_P : bool; default False Compute pairwise interpolation directly; if False, return integer aggregation matrix for smoothed_aggregation_solver. If True, return float interpolation P, converting to BSR form with identity of size bsize x bsize on each aggregate if A is BSR. Examples -------- >>> from scipy.sparse import csr_matrix >>> from pyamg.gallery import poisson >>> from pyamg.aggregation.aggregate import pairwise_aggregation >>> A = poisson((4,), format='csr') # 1D mesh with 4 vertices >>> A.toarray() array([[ 2., -1., 0., 0.], [-1., 2., -1., 0.], [ 0., -1., 2., -1.], [ 0., 0., -1., 2.]]) >>> pairwise_aggregation(A, matchings=1)[0].toarray() # two aggregates array([[1, 0], [1, 0], [0, 1], [0, 1]], dtype=int8) >>> pairwise_aggregation(A, matchings=2)[0].toarray() # one aggregate array([[1], [1], [1], [1]], dtype=int8) See Also -------- amg_core.pairwise_aggregation References ---------- [0] Notay, Y. (2010). An aggregation-based algebraic multigrid method. Electronic transactions on numerical analysis, 37(6), 123-146. """ # Get SOC matrix if not (sparse.isspmatrix_bsr(A) or sparse.isspmatrix_csr(A)): try: A = A.tocsr() warn('Implicit conversion of A to csr', sparse.SparseEfficiencyWarning) except Exception as e: raise TypeError('Invalid matrix type, must be CSR or BSR.') from e index_type = A.indptr.dtype Ac = A # Let Ac reference A for loop purposes T = None Cpts = None # Loop over the number of pairwise matchings to be done for i in range(0, matchings): # Compute SOC matrix for this matching if sparse.isspmatrix_bsr(A): C = classical_strength_of_connection(A=Ac, theta=theta, block=True, norm=norm) else: C = classical_strength_of_connection(A=Ac, theta=theta, block=False, norm=norm) # Form pairwise aggregation matrix num_rows = C.shape[0] Tj = np.empty(num_rows, dtype=index_type) # stores the aggregate #s new_cpts = np.empty(num_rows, dtype=index_type) # stores the new_cpts fn = amg_core.pairwise_aggregation num_aggregates = fn(num_rows, C.indptr, C.indices, C.data, Tj, new_cpts) if Cpts is None: Cpts = new_cpts[:num_aggregates] else: Cpts = Cpts[new_cpts[:num_aggregates]] Tj = Tj - 1 # Construct sparse T if num_aggregates == 0: # all zero matrix T_temp = sparse.csr_matrix((num_rows, 1), dtype='int8') warn('No pairwise aggregates found, T = 0.') else: shape = (num_rows, num_aggregates) Tp = np.arange(num_rows+1, dtype=index_type) # If A is not BSR if not sparse.isspmatrix_bsr(A): Tx = np.ones(len(Tj), dtype='int8') T_temp = sparse.csr_matrix((Tx, Tj, Tp), shape=shape) else: shape = (shape[0]*A.blocksize[0], shape[1]*A.blocksize[1]) Tx = np.array(len(Tj)*[np.identity(A.blocksize[0])], dtype='int8') T_temp = sparse.bsr_matrix((Tx, Tj, Tp), blocksize=A.blocksize, shape=shape) # Form aggregation matrix, need to make sure is CSR/BSR if i == 0: T = T_temp else: if sparse.isspmatrix_bsr(A): T = sparse.bsr_matrix(T * T_temp) else: T = sparse.csr_matrix(T * T_temp) # Break loop if zero aggregates were found if num_aggregates == 0: break # Form coarse grid operator for next matching if i < (matchings-1): if sparse.isspmatrix_csr(T_temp): Ac = T_temp.T.tocsr() * Ac * T_temp else: Ac = T_temp.T * Ac * T_temp # Convert T to dtype int if only used for aggregation if compute_P: T = T.astype(A.dtype, copy=False) return T, Cpts
[docs] def lloyd_aggregation(C, ratio=0.03, distance='unit', maxiter=10): """Aggregate nodes using Lloyd Clustering. Parameters ---------- C : csr_matrix strength of connection matrix ratio : scalar Fraction of the nodes which will be seeds. distance : ['unit','abs','inv',None] Distance assigned to each edge of the graph G used in Lloyd clustering For each nonzero value C[i,j]: ======= =========================== 'unit' G[i,j] = 1 'abs' G[i,j] = abs(C[i,j]) 'inv' G[i,j] = 1.0/abs(C[i,j]) 'same' G[i,j] = C[i,j] 'sub' G[i,j] = C[i,j] - min(C) ======= =========================== maxiter : int Maximum number of iterations to perform Returns ------- AggOp : csr_matrix aggregation operator which determines the sparsity pattern of the tentative prolongator seeds : array array of Cpts, i.e., Cpts[i] = root node of aggregate i See Also -------- amg_core.standard_aggregation Examples -------- >>> from scipy.sparse import csr_matrix >>> from pyamg.gallery import poisson >>> from pyamg.aggregation.aggregate import lloyd_aggregation >>> A = poisson((4,), format='csr') # 1D mesh with 4 vertices >>> A.toarray() array([[ 2., -1., 0., 0.], [-1., 2., -1., 0.], [ 0., -1., 2., -1.], [ 0., 0., -1., 2.]]) >>> lloyd_aggregation(A)[0].toarray() # one aggregate array([[1], [1], [1], [1]], dtype=int8) >>> # more seeding for two aggregates >>> Agg = lloyd_aggregation(A,ratio=0.5)[0].toarray() """ if ratio <= 0 or ratio > 1: raise ValueError('ratio must be > 0.0 and <= 1.0') if not (sparse.isspmatrix_csr(C) or sparse.isspmatrix_csc(C)): raise TypeError('expected csr_matrix or csc_matrix') if distance == 'unit': data = np.ones_like(C.data).astype(float) elif distance == 'abs': data = abs(C.data) elif distance == 'inv': data = 1.0/abs(C.data) elif distance == 'same': data = C.data elif distance == 'min': data = C.data - C.data.min() else: raise ValueError(f'Unrecognized value distance={distance}') if C.dtype == complex: data = np.real(data) assert data.min() >= 0 G = C.__class__((data, C.indices, C.indptr), shape=C.shape) num_seeds = int(min(max(ratio * G.shape[0], 1), G.shape[0])) _, clusters, seeds = lloyd_cluster(G, num_seeds, maxiter=maxiter) row = (clusters >= 0).nonzero()[0] col = clusters[row] data = np.ones(len(row), dtype='int8') AggOp = sparse.coo_matrix((data, (row, col)), shape=(G.shape[0], num_seeds)).tocsr() return AggOp, seeds
[docs] def balanced_lloyd_aggregation(C, num_clusters=None): """Aggregate nodes using Balanced Lloyd Clustering. Parameters ---------- C : csr_matrix strength of connection matrix with positive weights num_clusters : int Number of seeds or clusters expected (default: C.shape[0] / 10) Returns ------- AggOp : csr_matrix aggregation operator which determines the sparsity pattern of the tentative prolongator seeds : array array of Cpts, i.e., Cpts[i] = root node of aggregate i See Also -------- amg_core.standard_aggregation Examples -------- >>> import pyamg >>> from pyamg.aggregation.aggregate import balanced_lloyd_aggregation >>> data = pyamg.gallery.load_example('unit_square') >>> G = data['A'] >>> xy = data['vertices'][:,:2] >>> G.data[:] = np.ones(len(G.data)) >>> np.random.seed(787888) >>> AggOp, seeds = balanced_lloyd_aggregation(G) """ if num_clusters is None: num_clusters = int(C.shape[0] / 10) if num_clusters < 1 or num_clusters > C.shape[0]: raise ValueError('num_clusters must be between 1 and n') if not (sparse.isspmatrix_csr(C) or sparse.isspmatrix_csc(C)): raise TypeError('expected csr_matrix or csc_matrix') if C.data.min() <= 0: raise ValueError('positive edge weights required') if C.dtype == complex: data = np.real(C.data) else: data = C.data G = C.__class__((data, C.indices, C.indptr), shape=C.shape) num_nodes = G.shape[0] seeds = np.random.permutation(num_nodes)[:num_clusters] seeds = seeds.astype(np.int32) mv = np.finfo(G.dtype).max d = mv * np.ones(num_nodes, dtype=G.dtype) d[seeds] = 0 cm = -1 * np.ones(num_nodes, dtype=np.int32) cm[seeds] = seeds amg_core.lloyd_cluster_exact(num_nodes, G.indptr, G.indices, G.data, num_clusters, d, cm, seeds) col = cm row = np.arange(len(cm)) data = np.ones(len(row), dtype=np.int32) AggOp = sparse.coo_matrix((data, (row, col)), shape=(G.shape[0], num_clusters)).tocsr() return AggOp, seeds