Source code for pyamg.classical.air

"""Approximate idealkrestriction AMG."""

from copy import deepcopy
import numpy as np
from scipy.sparse import isspmatrix_csr, isspmatrix_bsr

from ..multilevel import MultilevelSolver
from ..relaxation.smoothing import change_smoothers
from ..strength import (classical_strength_of_connection,
                        symmetric_strength_of_connection, evolution_strength_of_connection,
                        distance_strength_of_connection, algebraic_distance,
                        affinity_distance, energy_based_strength_of_connection)
from ..util.utils import filter_matrix_rows
from ..classical.interpolate import (direct_interpolation, classical_interpolation,
                                     injection_interpolation, one_point_interpolation,
                                     local_air)
from .split import RS, PMIS, PMISc, CLJP, CLJPc
from .cr import CR


[docs] def air_solver(A, strength=('classical', {'theta': 0.3, 'norm': 'min'}), CF=('RS', {'second_pass': True}), interpolation='one_point', restrict=('air', {'theta': 0.05, 'degree': 2}), presmoother=None, postsmoother=('fc_jacobi', {'omega': 1.0, 'iterations': 1, 'withrho': False, 'f_iterations': 2, 'c_iterations': 1}), filter_operator=None, max_levels=20, max_coarse=20, keep=False, **kwargs): """Create a multilevel solver using approximate ideal restriction (AIR) AMG. Parameters ---------- A : csr_matrix Square (non)symmetric matrix in CSR format strength : ['symmetric', 'classical', 'evolution', 'distance', 'algebraic_distance','affinity', 'energy_based', None] Method used to determine the strength of connection between unknowns of the linear system. Method-specific parameters may be passed in using a tuple, e.g. strength=('symmetric',{'theta' : 0.25 }). If strength=None, all nonzero entries of the matrix are considered strong. CF : {string} : default 'RS' with second pass Method used for coarse grid selection (C/F splitting) Supported methods are RS, PMIS, PMISc, CLJP, CLJPc, and CR. interpolation : {string} : default 'one_point' Options include 'direct', 'classical', 'inject' and 'one-point'. restrict : {string} : default distance-2 AIR, with theta = 0.05. Option is 'air' for local approximate ideal restriction (lAIR), with inner options specifying degree, strength tolerance, etc. presmoother : {string or dict} : default None Method used for presmoothing at each level. Method-specific parameters may be passed in using a tuple. postsmoother : {string or dict} Postsmoothing method with the same usage as presmoother. postsmoother=('fc_jacobi', ... ) with 2 F-sweeps, 1 C-sweep is default. filter_operator : (bool, tol) : default None Remove small entries in operators on each level if True. Entries are considered "small" if |a_ij| < tol |a_ii|. max_levels: {integer} : default 20 Maximum number of levels to be used in the multilevel solver. max_coarse: {integer} : default 20 Maximum number of variables permitted on the coarse grid. keep: {bool} : default False Flag to indicate keeping strength of connection matrix (C) in hierarchy. Returns ------- ml : multilevel_solver Multigrid hierarchy of matrices and prolongation operators Examples -------- >>> from pyamg.gallery import poisson >>> from pyamg import air_solver >>> A = poisson((10,),format='csr') >>> ml = air_solver(A,max_coarse=3) Notes ----- "coarse_solver" is an optional argument and is the solver used at the coarsest grid. The default is a pseudo-inverse. Most simply, coarse_solver can be one of ['splu', 'lu', 'cholesky, 'pinv', 'gauss_seidel', ... ]. Additionally, coarse_solver may be a tuple (fn, args), where fn is a string such as ['splu', 'lu', ...] or a callable function, and args is a dictionary of arguments to be passed to fn. See [2001TrOoSc]_ for additional details. References ---------- [1] Manteuffel, T. A., Münzenmaier, S., Ruge, J., & Southworth, B. S. (2019). Nonsymmetric reduction-based algebraic multigrid. SIAM Journal on Scientific Computing, 41(5), S242-S268. [2] Manteuffel, T. A., Ruge, J., & Southworth, B. S. (2018). Nonsymmetric algebraic multigrid based on local approximate ideal restriction (lAIR). SIAM Journal on Scientific Computing, 40(6), A4105-A4130. See Also -------- aggregation.smoothed_aggregation_solver, multilevel_solver, aggregation.rootnode_solver, ruge_stuben_solver """ # preprocess A A = A.asfptype() if A.shape[0] != A.shape[1]: raise ValueError('expected square matrix') if np.iscomplexobj(A.data): raise ValueError('AIR solver not verified for complex matrices') levels = [MultilevelSolver.Level()] levels[-1].A = A while len(levels) < max_levels and levels[-1].A.shape[0] > max_coarse: bottom = extend_hierarchy(levels, strength, CF, interpolation, restrict, filter_operator, keep) if bottom: break ml = MultilevelSolver(levels, **kwargs) change_smoothers(ml, presmoother, postsmoother) return ml
def extend_hierarchy(levels, strength, CF, interpolation, restrict, filter_operator, keep): """Extend the multigrid hierarchy.""" def unpack_arg(v): if isinstance(v, tuple): return v[0], v[1] return v, {} # Filter operator. Need to keep original matrix on finest level for # computing residuals if (filter_operator is not None) and (filter_operator[1] != 0): if len(levels) == 1: A = deepcopy(levels[-1].A) else: A = levels[-1].A filter_matrix_rows(A, filter_operator[1], diagonal=True, lump=filter_operator[0]) else: A = levels[-1].A # Check if matrix was filtered to be diagonal --> coarsest grid if A.nnz == A.shape[0]: return 1 # Compute the strength-of-connection matrix C, where larger # C[i,j] denote stronger couplings between i and j. fn, kwargs = unpack_arg(strength) if fn == 'symmetric': C = symmetric_strength_of_connection(A, **kwargs) elif fn == 'classical': C = classical_strength_of_connection(A, **kwargs) elif fn == 'distance': C = distance_strength_of_connection(A, **kwargs) elif fn in ('ode', 'evolution'): C = evolution_strength_of_connection(A, **kwargs) elif fn == 'energy_based': C = energy_based_strength_of_connection(A, **kwargs) elif fn == 'algebraic_distance': C = algebraic_distance(A, **kwargs) elif fn == 'affinity': C = affinity_distance(A, **kwargs) elif fn is None: C = A else: raise ValueError(f'Unrecognized strength of connection method: {fn}') # Generate the C/F splitting fn, kwargs = unpack_arg(CF) if fn == 'RS': splitting = RS(C, **kwargs) elif fn == 'PMIS': splitting = PMIS(C, **kwargs) elif fn == 'PMISc': splitting = PMISc(C, **kwargs) elif fn == 'CLJP': splitting = CLJP(C, **kwargs) elif fn == 'CLJPc': splitting = CLJPc(C, **kwargs) elif fn == 'CR': splitting = CR(C, **kwargs) else: raise ValueError(f'Unknown C/F splitting method {CF}') # Make sure all points were not declared as C- or F-points num_fpts = np.sum(splitting) if (num_fpts == len(splitting)) or (num_fpts == 0): return 1 # Generate the interpolation matrix that maps from the coarse-grid to the # fine-grid fn, kwargs = unpack_arg(interpolation) if fn == 'classical': P = classical_interpolation(A, C, splitting, **kwargs) elif fn == 'direct': P = direct_interpolation(A, C, splitting, **kwargs) elif fn == 'one_point': P = one_point_interpolation(A, C, splitting, **kwargs) elif fn == 'inject': P = injection_interpolation(A, splitting, **kwargs) else: raise ValueError(f'Unknown interpolation method {fn}') # Build restriction operator fn, kwargs = unpack_arg(restrict) if fn == 'air': R = local_air(A, splitting, **kwargs) else: raise ValueError(f'Unknown restriction method {fn}') # Store relevant information for this level if keep: levels[-1].C = C # strength of connection matrix levels[-1].splitting = splitting.astype(bool) # C/F splitting levels[-1].P = P # prolongation operator levels[-1].R = R # restriction operator # RAP = R*(A*P) A = R * A * P # Make sure coarse-grid operator is in correct sparse format if (isspmatrix_csr(P) and (not isspmatrix_csr(A))): A = A.tocsr() elif (isspmatrix_bsr(P) and (not isspmatrix_bsr(A))): A = A.tobsr() levels.append(MultilevelSolver.Level()) levels[-1].A = A return 0