def test_solver_parameters(self): A = poisson((50, 50), format='csr') for method in methods: #method = ('richardson', {'omega':4.0/3.0}) ml = smoothed_aggregation_solver(A, presmoother=method, postsmoother=method, max_coarse=10) residuals = profile_solver(ml) #print "method",method #print "residuals",residuals #print "convergence rate:",(residuals[-1]/residuals[0])**(1.0/len(residuals)) assert ((residuals[-1] / residuals[0])**(1.0 / len(residuals)) < 0.95) for method in methods2: ml = smoothed_aggregation_solver(A, max_coarse=10) change_smoothers(ml, presmoother=method[0], postsmoother=method[1]) residuals = profile_solver(ml) #print "method",method #print "residuals",residuals #print "convergence rate:",(residuals[-1]/residuals[0])**(1.0/len(residuals)) assert ((residuals[-1] / residuals[0])**(1.0 / len(residuals)) < 0.95)
def SetupGMG(self, maxLevels=None, maxCoarse=None, smoother=('block_jacobi', { 'omega': 0.5, 'withrho': False })): """ Sets up the geometric multigrid Parameters ---------- maxLevels : int, optional Maximum levels in the hierarchy maxCoarse : int, optional Maximum nodes on the coarse grid smoother : tuple or list of tuple, optional Describes the smoothers to use on each level Returns ------- it : integer Number of solver iterations """ if maxLevels is None: maxLevels = self.maxLevels if maxCoarse is None: maxCoarse = self.maxCoarse levels = [] levels.append(pyamg.multilevel_solver.level()) levels[-1].A = self.K for P in self.P: levels[-1].P = P levels[-1].R = P.T levels.append(pyamg.multilevel_solver.level()) levels[-1].A = levels[-2].R * levels[-2].A * levels[-2].P if (len(levels) == maxLevels or levels[-1].A.shape[0] // levels[1].A.blocksize[0] < maxCoarse): break self.ml_GMG = pyamg.multilevel_solver(levels, coarse_solver='splu') from pyamg.relaxation.smoothing import change_smoothers change_smoothers(self.ml_GMG, presmoother=smoother, postsmoother=smoother)
def load(file): """ Deserialize a file containing various numpy matrices to a PyAMG multigrid solver. :param file: """ class level: def __init__(self): pass def load_level(item): l = level() for key, value in item.iteritems(): if key == 'presmoother': continue if type(value) is np.ndarray: arr = value else: matrix_func_name = "%s_matrix" % value["format"] matrix_func = getattr(sparse, matrix_func_name) arr = matrix_func(( value["data"], value["indices"], value["indptr"]), shape=value["shape"] ) setattr(l, key, arr) return l data = np.load(file) levels = map(load_level, data["levels"]) ml = pyamg.multilevel.multilevel_solver(levels, coarse_solver='pinv2') change_smoothers(ml, 'gauss_seidel', 'gauss_seidel') return ml
def test_solver_parameters(self): A = poisson((50, 50), format='csr') for method in methods: # method = ('richardson', {'omega':4.0/3.0}) ml = smoothed_aggregation_solver(A, presmoother=method, postsmoother=method, max_coarse=10) residuals = profile_solver(ml) assert((residuals[-1]/residuals[0])**(1.0/len(residuals)) < 0.95) for method in methods2: ml = smoothed_aggregation_solver(A, max_coarse=10) change_smoothers(ml, presmoother=method[0], postsmoother=method[1]) residuals = profile_solver(ml) assert((residuals[-1]/residuals[0])**(1.0/len(residuals)) < 0.95)
def test_solver_parameters(self): A = poisson((50, 50), format='csr') for method in methods: # method = ('richardson', {'omega':4.0/3.0}) ml = smoothed_aggregation_solver(A, presmoother=method, postsmoother=method, max_coarse=10) residuals = profile_solver(ml) assert ((residuals[-1] / residuals[0])**(1.0 / len(residuals)) < 0.95) assert (ml.symmetric_smoothing) for method in methods2: ml = smoothed_aggregation_solver(A, max_coarse=10) change_smoothers(ml, presmoother=method[0], postsmoother=method[1]) residuals = profile_solver(ml) assert ((residuals[-1] / residuals[0])**(1.0 / len(residuals)) < 0.95) assert (not ml.symmetric_smoothing) for method in methods3: ml = smoothed_aggregation_solver(A, max_coarse=10) change_smoothers(ml, presmoother=method[0], postsmoother=method[1]) assert (ml.symmetric_smoothing) for method in methods4: ml = smoothed_aggregation_solver(A, max_coarse=10) change_smoothers(ml, presmoother=method[0], postsmoother=method[1]) assert (not ml.symmetric_smoothing)
def test_cycle_complexity(self): # four levels levels = [] levels.append(multilevel_solver.level()) levels[0].A = csr_matrix(ones((10, 10))) levels[0].P = csr_matrix(ones((10, 5))) levels.append(multilevel_solver.level()) levels[1].A = csr_matrix(ones((5, 5))) levels[1].P = csr_matrix(ones((5, 3))) levels.append(multilevel_solver.level()) levels[2].A = csr_matrix(ones((3, 3))) levels[2].P = csr_matrix(ones((3, 2))) levels.append(multilevel_solver.level()) levels[3].A = csr_matrix(ones((2, 2))) # one level hierarchy mg = multilevel_solver(levels[:1]) assert_equal(mg.cycle_complexity(cycle='V'), 100.0 / 100.0) # 1 assert_equal(mg.cycle_complexity(cycle='W'), 100.0 / 100.0) # 1 assert_equal(mg.cycle_complexity(cycle='AMLI'), 100.0 / 100.0) # 1 assert_equal(mg.cycle_complexity(cycle='F'), 100.0 / 100.0) # 1 # two level hierarchy mg = multilevel_solver(levels[:2]) change_smoothers(mg, 'gauss_seidel', 'gauss_seidel') assert_equal(mg.cycle_complexity(cycle='V'), 4.0) assert_equal(mg.cycle_complexity(cycle='W'), 4.0) assert_equal(mg.cycle_complexity(cycle='AMLI'), 4.0) assert_equal(mg.cycle_complexity(cycle='F'), 4.0) # three level hierarchy mg = multilevel_solver(levels[:3]) change_smoothers(mg, ('gauss_seidel', { 'iterations': 2 }), 'gauss_seidel') assert_equal(mg.cycle_complexity(cycle='V'), 6.3) assert_equal(mg.cycle_complexity(cycle='W'), 7.6) assert_equal(mg.cycle_complexity(cycle='AMLI'), 7.6) assert_equal(mg.cycle_complexity(cycle='F'), 7.6) # four level hierarchy mg = multilevel_solver(levels[:4]) change_smoothers(mg, ('gauss_seidel', { 'sweep': 'symmetric' }), 'gauss_seidel') assert_equal(mg.cycle_complexity(cycle='V'), 6.78) assert_equal(mg.cycle_complexity(cycle='W'), 9.52) assert_equal(mg.cycle_complexity(cycle='AMLI'), 9.52) assert_equal(mg.cycle_complexity(cycle='F'), 9.04)
def test_cycle_complexity(self): # four levels levels = [] levels.append(multilevel_solver.level()) levels[0].A = csr_matrix(ones((10, 10))) levels[0].P = csr_matrix(ones((10, 5))) levels.append(multilevel_solver.level()) levels[1].A = csr_matrix(ones((5, 5))) levels[1].P = csr_matrix(ones((5, 3))) levels.append(multilevel_solver.level()) levels[2].A = csr_matrix(ones((3, 3))) levels[2].P = csr_matrix(ones((3, 2))) levels.append(multilevel_solver.level()) levels[3].A = csr_matrix(ones((2, 2))) # one level hierarchy mg = multilevel_solver(levels[:1]) assert_equal(mg.cycle_complexity(cycle='V'), 100.0/100.0) # 1 assert_equal(mg.cycle_complexity(cycle='W'), 100.0/100.0) # 1 assert_equal(mg.cycle_complexity(cycle='AMLI'), 100.0/100.0) # 1 assert_equal(mg.cycle_complexity(cycle='F'), 100.0/100.0) # 1 # two level hierarchy mg = multilevel_solver(levels[:2]) change_smoothers(mg, 'gauss_seidel', 'gauss_seidel') assert_equal(mg.cycle_complexity(cycle='V'), 4.0) assert_equal(mg.cycle_complexity(cycle='W'), 4.0) assert_equal(mg.cycle_complexity(cycle='AMLI'), 4.0) assert_equal(mg.cycle_complexity(cycle='F'), 4.0) # three level hierarchy mg = multilevel_solver(levels[:3]) change_smoothers(mg, ('gauss_seidel', {'iterations':2}) , 'gauss_seidel') assert_equal(mg.cycle_complexity(cycle='V'), 6.3) assert_equal(mg.cycle_complexity(cycle='W'), 7.6) assert_equal(mg.cycle_complexity(cycle='AMLI'), 7.6) assert_equal(mg.cycle_complexity(cycle='F'), 7.6) # four level hierarchy mg = multilevel_solver(levels[:4]) change_smoothers(mg, ('gauss_seidel', {'sweep':'symmetric'}), 'gauss_seidel') assert_equal(mg.cycle_complexity(cycle='V'), 6.78) assert_equal(mg.cycle_complexity(cycle='W'), 9.52) assert_equal(mg.cycle_complexity(cycle='AMLI'), 9.52) assert_equal(mg.cycle_complexity(cycle='F'), 9.04)
def ruge_stuben_solver(A, strength=('classical', { 'theta': 0.25 }), CF='RS', presmoother=('gauss_seidel', { 'sweep': 'symmetric' }), postsmoother=('gauss_seidel', { 'sweep': 'symmetric' }), max_levels=10, max_coarse=500, keep=False, **kwargs): """Create a multilevel solver using Classical AMG (Ruge-Stuben AMG) Parameters ---------- A : csr_matrix Square matrix in CSR format strength : ['symmetric', 'classical', 'evolution', 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' Method used for coarse grid selection (C/F splitting) Supported methods are RS, PMIS, PMISc, CLJP, and CLJPc presmoother : {string or dict} Method used for presmoothing at each level. Method-specific parameters may be passed in using a tuple, e.g. presmoother=('gauss_seidel',{'sweep':'symmetric}), the default. postsmoother : {string or dict} Postsmoothing method with the same usage as presmoother max_levels: {integer} : default 10 Maximum number of levels to be used in the multilevel solver. max_coarse: {integer} : default 500 Maximum number of variables permitted on the coarse grid. keep: {bool} : default False Flag to indicate keeping extra operators in the hierarchy for diagnostics. For example, if True, then strength of connection (C) and tentative prolongation (T) are kept. Returns ------- ml : multilevel_solver Multigrid hierarchy of matrices and prolongation operators Examples -------- >>> from pyamg.gallery import poisson >>> from pyamg import ruge_stuben_solver >>> A = poisson((10,),format='csr') >>> ml = ruge_stuben_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. References ---------- .. [1] Trottenberg, U., Oosterlee, C. W., and Schuller, A., "Multigrid" San Diego: Academic Press, 2001. Appendix A See Also -------- aggregation.smoothed_aggregation_solver, multilevel_solver, aggregation.rootnode_solver """ levels = [multilevel_solver.level()] # convert A to csr if not isspmatrix_csr(A): try: A = csr_matrix(A) warn("Implicit conversion of A to CSR", SparseEfficiencyWarning) except: raise TypeError('Argument A must have type csr_matrix, \ or be convertible to csr_matrix') # preprocess A A = A.asfptype() if A.shape[0] != A.shape[1]: raise ValueError('expected square matrix') levels[-1].A = A while len(levels) < max_levels and levels[-1].A.shape[0] > max_coarse: extend_hierarchy(levels, strength, CF, keep) ml = multilevel_solver(levels, **kwargs) change_smoothers(ml, presmoother, postsmoother) return ml
def SetupHybrid(self, maxLevels=None, maxCoarse=None, smoother=('block_jacobi', { 'omega': 0.5, 'withrho': False }), nG=1): """ Sets up the hybrid multigrid Parameters ---------- maxLevels : int, optional Maximum levels in the hierarchy maxCoarse : int, optional Maximum nodes on the coarse grid smoother : tuple or list of tuple, optional Describes the smoothers to use on each level nG : int, optional Number of levels of geometric coarsening to use Returns ------- it : integer Number of solver iterations """ if maxLevels is None: maxLevels = self.maxLevels if maxCoarse is None: maxCoarse = self.maxCoarse if self.nDof == 1: Nullspace = np.ones((self.nodes.shape[0], 1)) elif self.nDof == 2: Nullspace = np.zeros((self.nodes.size, 3)) Nullspace[::2, 0] = 1 Nullspace[1::2, 1] = 1 Nullspace[::2, 2] = -self.nodes[:, 1] Nullspace[1::2, 2] = self.nodes[:, 0] elif self.nDof == 3: Nullspace = np.zeros((self.nodes.size, 6)) Nullspace[::3, 0] = 1 Nullspace[1::3, 1] = 1 Nullspace[2::3, 2] = 1 Nullspace[::3, 3] = -self.nodes[:, 1] Nullspace[1::3, 3] = self.nodes[:, 0] Nullspace[1::3, 4] = -self.nodes[:, 2] Nullspace[2::3, 4] = self.nodes[:, 1] Nullspace[0::3, 5] = -self.nodes[:, 2] Nullspace[2::3, 5] = self.nodes[:, 0] levels = [] A = self.K for i in range(nG): levels.append(pyamg.multilevel_solver.level()) levels[-1].A = A levels[-1].P = self.P[i] levels[-1].R = self.P[i].T A = levels[-1].R * A * levels[-1].P if nG > 0: cDof = (self.nDof * self.cNodes[nG - 1].reshape(-1, 1) + np.arange(self.nDof).reshape(1, -1)).ravel() Nullspace = Nullspace[cDof] Nullspace = np.linalg.solve( np.linalg.cholesky(np.dot(Nullspace.T, Nullspace)), Nullspace.T).T AMG = pyamg.smoothed_aggregation_solver(A, B=Nullspace, max_coarse=maxCoarse, max_levels=maxLevels - nG + 1, strength=('symmetric', { 'theta': 0.003 }), smooth=('jacobi', { 'omega': 4.0 / 3.0, 'spectral_radius': self.nDof }), keep=True) levels += AMG.levels self.ml_HYBRID = pyamg.multilevel_solver(levels, coarse_solver='splu') from pyamg.relaxation.smoothing import change_smoothers change_smoothers(self.ml_HYBRID, presmoother=smoother, postsmoother=smoother)
def general_setup_stage(ml, symmetry, candidate_iters, prepostsmoother, smooth, eliminate_local, coarse_solver, work): """ Computes additional candidates and improvements following Algorithm 4 in Brezina et al. Parameters ---------- candidate_iters number of test relaxation iterations epsilon minimum acceptable relaxation convergence factor References ---------- .. [1] Brezina, Falgout, MacLachlan, Manteuffel, McCormick, and Ruge "Adaptive Smoothed Aggregation (alphaSA) Multigrid" SIAM Review Volume 47, Issue 2 (2005) http://www.cs.umn.edu/~maclach/research/aSA2.pdf """ def make_bridge(T): M, N = T.shape K = T.blocksize[0] bnnz = T.indptr[-1] # the K+1 represents the new dof introduced by the new candidate. the # bridge 'T' ignores this new dof and just maps zeros there data = np.zeros((bnnz, K + 1, K), dtype=T.dtype) data[:, :-1, :] = T.data return bsr_matrix((data, T.indices, T.indptr), shape=((K + 1) * int(M / K), N)) def expand_candidates(B_old, nodesize): # insert a new dof that is always zero, to create NullDim+1 dofs per # node in B NullDim = B_old.shape[1] nnodes = int(B_old.shape[0] / nodesize) Bnew = np.zeros((nnodes, nodesize + 1, NullDim), dtype=B_old.dtype) Bnew[:, :-1, :] = B_old.reshape(nnodes, nodesize, NullDim) return Bnew.reshape(-1, NullDim) levels = ml.levels x = sp.rand(levels[0].A.shape[0], 1) if levels[0].A.dtype.name.startswith('complex'): x = x + 1.0j * sp.rand(levels[0].A.shape[0], 1) b = np.zeros_like(x) x = ml.solve(b, x0=x, tol=float(np.finfo(np.float).tiny), maxiter=candidate_iters) work[:] += ml.operator_complexity( ) * ml.levels[0].A.nnz * candidate_iters * 2 T0 = levels[0].T.copy() # TEST FOR CONVERGENCE HERE for i in range(len(ml.levels) - 2): # alpha-SA paper does local elimination here, but after talking # to Marian, its not clear that this helps things # fn, kwargs = unpack_arg(eliminate_local) # if fn == True: # eliminate_local_candidates(x,levels[i].AggOp,levels[i].A, # levels[i].T, **kwargs) # add candidate to B B = np.hstack((levels[i].B, x.reshape(-1, 1))) # construct Ptent T, R = fit_candidates(levels[i].AggOp, B) levels[i].T = T x = R[:, -1].reshape(-1, 1) # smooth P fn, kwargs = unpack_arg(smooth[i]) if fn == 'jacobi': levels[i].P = jacobi_prolongation_smoother(levels[i].A, T, levels[i].C, R, **kwargs) elif fn == 'richardson': levels[i].P = richardson_prolongation_smoother( levels[i].A, T, **kwargs) elif fn == 'energy': levels[i].P = energy_prolongation_smoother(levels[i].A, T, levels[i].C, R, None, (False, {}), **kwargs) x = R[:, -1].reshape(-1, 1) elif fn is None: levels[i].P = T else: raise ValueError('unrecognized prolongation smoother method %s' % str(fn)) # construct R if symmetry == 'symmetric': # R should reflect A's structure levels[i].R = levels[i].P.T.asformat(levels[i].P.format) elif symmetry == 'hermitian': levels[i].R = levels[i].P.H.asformat(levels[i].P.format) # construct coarse A levels[i + 1].A = levels[i].R * levels[i].A * levels[i].P # construct bridging P T_bridge = make_bridge(levels[i + 1].T) R_bridge = levels[i + 2].B # smooth bridging P fn, kwargs = unpack_arg(smooth[i + 1]) if fn == 'jacobi': levels[i + 1].P = jacobi_prolongation_smoother( levels[i + 1].A, T_bridge, levels[i + 1].C, R_bridge, **kwargs) elif fn == 'richardson': levels[i + 1].P = richardson_prolongation_smoother( levels[i + 1].A, T_bridge, **kwargs) elif fn == 'energy': levels[i + 1].P = energy_prolongation_smoother( levels[i + 1].A, T_bridge, levels[i + 1].C, R_bridge, None, (False, {}), **kwargs) elif fn is None: levels[i + 1].P = T_bridge else: raise ValueError('unrecognized prolongation smoother method %s' % str(fn)) # construct the "bridging" R if symmetry == 'symmetric': # R should reflect A's structure levels[i + 1].R = levels[i + 1].P.T.asformat(levels[i + 1].P.format) elif symmetry == 'hermitian': levels[i + 1].R = levels[i + 1].P.H.asformat(levels[i + 1].P.format) # run solver on candidate solver = multilevel_solver(levels[i + 1:], coarse_solver=coarse_solver) change_smoothers(solver, presmoother=prepostsmoother, postsmoother=prepostsmoother) x = solver.solve(np.zeros_like(x), x0=x, tol=float(np.finfo(np.float).tiny), maxiter=candidate_iters) work[:] += 2 * solver.operator_complexity() * solver.levels[0].A.nnz *\ candidate_iters*2 # update values on next level levels[i + 1].B = R[:, :-1].copy() levels[i + 1].T = T_bridge # note that we only use the x from the second coarsest level fn, kwargs = unpack_arg(prepostsmoother) for lvl in reversed(levels[:-2]): x = lvl.P * x work[:] += lvl.A.nnz * candidate_iters * 2 if fn == 'gauss_seidel': # only relax at nonzeros, so as not to mess up any locally dropped # candidates indices = np.ravel(x).nonzero()[0] gauss_seidel_indexed(lvl.A, x, np.zeros_like(x), indices, iterations=candidate_iters, sweep='symmetric') elif fn == 'gauss_seidel_ne': gauss_seidel_ne(lvl.A, x, np.zeros_like(x), iterations=candidate_iters, sweep='symmetric') elif fn == 'gauss_seidel_nr': gauss_seidel_nr(lvl.A, x, np.zeros_like(x), iterations=candidate_iters, sweep='symmetric') elif fn == 'jacobi': jacobi(lvl.A, x, np.zeros_like(x), iterations=1, omega=1.0 / rho_D_inv_A(lvl.A)) elif fn == 'richardson': polynomial(lvl.A, x, np.zeros_like(x), iterations=1, coefficients=[1.0 / approximate_spectral_radius(lvl.A)]) elif fn == 'gmres': x[:] = (gmres(lvl.A, np.zeros_like(x), x0=x, maxiter=candidate_iters)[0]).reshape(x.shape) else: raise TypeError('Unrecognized smoother') # x will be dense again, so we have to drop locally again elim, elim_kwargs = unpack_arg(eliminate_local) if elim is True: x = x / norm(x, 'inf') eliminate_local_candidates(x, levels[0].AggOp, levels[0].A, T0, **elim_kwargs) return x.reshape(-1, 1)
def smoothed_aggregation_solver(A, B=None, BH=None, symmetry='hermitian', strength='symmetric', aggregate='standard', smooth=('jacobi', { 'omega': 4.0 / 3.0 }), presmoother=('block_gauss_seidel', { 'sweep': 'symmetric' }), postsmoother=('block_gauss_seidel', { 'sweep': 'symmetric' }), improve_candidates=[('block_gauss_seidel', { 'sweep': 'symmetric', 'iterations': 4 }), None], max_levels=10, max_coarse=10, diagonal_dominance=False, keep=False, **kwargs): """Create a multilevel solver using classical-style Smoothed Aggregation (SA). Parameters ---------- A : csr_matrix, bsr_matrix Sparse NxN matrix in CSR or BSR format B : None, array_like Right near-nullspace candidates stored in the columns of an NxK array. The default value B=None is equivalent to B=ones((N,1)) BH : None, array_like Left near-nullspace candidates stored in the columns of an NxK array. BH is only used if symmetry is 'nonsymmetric'. The default value B=None is equivalent to BH=B.copy() symmetry : string 'symmetric' refers to both real and complex symmetric 'hermitian' refers to both complex Hermitian and real Hermitian 'nonsymmetric' i.e. nonsymmetric in a hermitian sense Note, in the strictly real case, symmetric and hermitian are the same. Note, this flag does not denote definiteness of the operator. strength : string or list 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. Choose from 'symmetric', 'classical', 'evolution', 'algebraic_distance', 'affinity', ('predefined', {'C' : csr_matrix}), None aggregate : string or list Method used to aggregate nodes. Choose from 'standard', 'lloyd', 'naive', ('predefined', {'AggOp' : csr_matrix}) smooth : list Method used to smooth the tentative prolongator. Method-specific parameters may be passed in using a tuple, e.g. smooth= ('jacobi',{'filter' : True }). Choose from 'jacobi', 'richardson', 'energy', None presmoother : tuple, string, list Defines the presmoother for the multilevel cycling. The default block Gauss-Seidel option defaults to point-wise Gauss-Seidel, if the matrix is CSR or is a BSR matrix with blocksize of 1. postsmoother : tuple, string, list Same as presmoother, except defines the postsmoother. improve_candidates : tuple, string, list The ith entry defines the method used to improve the candidates B on level i. If the list is shorter than max_levels, then the last entry will define the method for all levels lower. If tuple or string, then this single relaxation descriptor defines improve_candidates on all levels. The list elements are relaxation descriptors of the form used for presmoother and postsmoother. A value of None implies no action on B. max_levels : integer Maximum number of levels to be used in the multilevel solver. max_coarse : integer Maximum number of variables permitted on the coarse grid. diagonal_dominance : bool, tuple If True (or the first tuple entry is True), then avoid coarsening diagonally dominant rows. The second tuple entry requires a dictionary, where the key value 'theta' is used to tune the diagonal dominance threshold. keep : bool Flag to indicate keeping extra operators in the hierarchy for diagnostics. For example, if True, then strength of connection (C), tentative prolongation (T), and aggregation (AggOp) are kept. Other Parameters ---------------- cycle_type : ['V','W','F'] Structrure of multigrid cycle coarse_solver : ['splu', 'lu', 'cholesky, 'pinv', 'gauss_seidel', ... ] Solver used at the coarsest level of the MG hierarchy. Optionally, 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. Returns ------- ml : multilevel_solver Multigrid hierarchy of matrices and prolongation operators See Also -------- multilevel_solver, classical.ruge_stuben_solver, aggregation.smoothed_aggregation_solver Notes ----- - This method implements classical-style SA, not root-node style SA (see aggregation.rootnode_solver). - The additional parameters are passed through as arguments to multilevel_solver. Refer to pyamg.multilevel_solver for additional documentation. - At each level, four steps are executed in order to define the coarser level operator. 1. Matrix A is given and used to derive a strength matrix, C. 2. Based on the strength matrix, indices are grouped or aggregated. 3. The aggregates define coarse nodes and a tentative prolongation operator T is defined by injection 4. The tentative prolongation operator is smoothed by a relaxation scheme to improve the quality and extent of interpolation from the aggregates to fine nodes. - The parameters smooth, strength, aggregate, presmoother, postsmoother can be varied on a per level basis. For different methods on different levels, use a list as input so that the i-th entry defines the method at the i-th level. If there are more levels in the hierarchy than list entries, the last entry will define the method for all levels lower. Examples are: smooth=[('jacobi', {'omega':1.0}), None, 'jacobi'] presmoother=[('block_gauss_seidel', {'sweep':symmetric}), 'sor'] aggregate=['standard', 'naive'] strength=[('symmetric', {'theta':0.25}), ('symmetric', {'theta':0.08})] - Predefined strength of connection and aggregation schemes can be specified. These options are best used together, but aggregation can be predefined while strength of connection is not. For predefined strength of connection, use a list consisting of tuples of the form ('predefined', {'C' : C0}), where C0 is a csr_matrix and each degree-of-freedom in C0 represents a supernode. For instance to predefine a three-level hierarchy, use [('predefined', {'C' : C0}), ('predefined', {'C' : C1}) ]. Similarly for predefined aggregation, use a list of tuples. For instance to predefine a three-level hierarchy, use [('predefined', {'AggOp' : Agg0}), ('predefined', {'AggOp' : Agg1}) ], where the dimensions of A, Agg0 and Agg1 are compatible, i.e. Agg0.shape[1] == A.shape[0] and Agg1.shape[1] == Agg0.shape[0]. Each AggOp is a csr_matrix. Examples -------- >>> from pyamg import smoothed_aggregation_solver >>> from pyamg.gallery import poisson >>> from scipy.sparse.linalg import cg >>> import numpy as np >>> A = poisson((100,100), format='csr') # matrix >>> b = np.ones((A.shape[0])) # RHS >>> ml = smoothed_aggregation_solver(A) # AMG solver >>> M = ml.aspreconditioner(cycle='V') # preconditioner >>> x,info = cg(A, b, tol=1e-8, maxiter=30, M=M) # solve with CG References ---------- .. [1996VaMaBr] Vanek, P. and Mandel, J. and Brezina, M., "Algebraic Multigrid by Smoothed Aggregation for Second and Fourth Order Elliptic Problems", Computing, vol. 56, no. 3, pp. 179--196, 1996. http://citeseer.ist.psu.edu/vanek96algebraic.html """ if not (isspmatrix_csr(A) or isspmatrix_bsr(A)): try: A = csr_matrix(A) warn("Implicit conversion of A to CSR", SparseEfficiencyWarning) except BaseException: raise TypeError( 'Argument A must have type csr_matrix or bsr_matrix, or be convertible to csr_matrix' ) A = A.asfptype() if (symmetry != 'symmetric') and (symmetry != 'hermitian') and\ (symmetry != 'nonsymmetric'): raise ValueError( 'expected \'symmetric\', \'nonsymmetric\' or \'hermitian\' for the symmetry parameter ' ) A.symmetry = symmetry if A.shape[0] != A.shape[1]: raise ValueError('expected square matrix') # Right near nullspace candidates use constant for each variable as default if B is None: B = np.kron( np.ones((int(A.shape[0] / blocksize(A)), 1), dtype=A.dtype), np.eye(blocksize(A), dtype=A.dtype)) else: B = np.asarray(B, dtype=A.dtype) if len(B.shape) == 1: B = B.reshape(-1, 1) if B.shape[0] != A.shape[0]: raise ValueError( 'The near null-space modes B have incorrect dimensions for matrix A' ) if B.shape[1] < blocksize(A): warn( 'Having less target vectors, B.shape[1], than blocksize of A can degrade convergence factors.' ) # Left near nullspace candidates if A.symmetry == 'nonsymmetric': if BH is None: BH = B.copy() else: BH = np.asarray(BH, dtype=A.dtype) if len(BH.shape) == 1: BH = BH.reshape(-1, 1) if BH.shape[1] != B.shape[1]: raise ValueError( 'The number of left and right near null-space modes B and BH, must be equal' ) if BH.shape[0] != A.shape[0]: raise ValueError( 'The near null-space modes BH have incorrect dimensions for matrix A' ) # Levelize the user parameters, so that they become lists describing the # desired user option on each level. max_levels, max_coarse, strength =\ levelize_strength_or_aggregation(strength, max_levels, max_coarse) max_levels, max_coarse, aggregate =\ levelize_strength_or_aggregation(aggregate, max_levels, max_coarse) improve_candidates =\ levelize_smooth_or_improve_candidates(improve_candidates, max_levels) smooth = levelize_smooth_or_improve_candidates(smooth, max_levels) # Construct multilevel structure levels = [] levels.append(multilevel_solver.level()) levels[-1].A = A # matrix # Append near nullspace candidates levels[-1].B = B # right candidates if A.symmetry == 'nonsymmetric': levels[-1].BH = BH # left candidates while len(levels) < max_levels and\ int(levels[-1].A.shape[0]/blocksize(levels[-1].A)) > max_coarse: extend_hierarchy(levels, strength, aggregate, smooth, improve_candidates, diagonal_dominance, keep) ml = multilevel_solver(levels, **kwargs) change_smoothers(ml, presmoother, postsmoother) return ml
def AIR_solver(A, strength=('classical', {'theta': 0.3 ,'norm': 'min'}), CF='RS', interp='one_point', restrict='neumann', presmoother=None, postsmoother=('FC_jacobi', {'omega': 1.0, 'iterations': 1, 'withrho': False, 'F_iterations': 2, 'C_iterations': 0} ), filter_operator=None, coarse_grid_P=None, coarse_grid_R=None, max_levels=20, max_coarse=20, keep=False, **kwargs): """Create a multilevel solver using Classical AMG (Ruge-Stuben AMG) Parameters ---------- A : csr_matrix Square nonsymmetric 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' Method used for coarse grid selection (C/F splitting) Supported methods are RS, PMIS, PMISc, CLJP, CLJPc, and CR. interp : {string} : default 'one-point' Options include 'direct', 'standard', 'inject' and 'one-point'. restrict : {string} : default 'neumann' Options include 'air' for approximate ideal restriction. presmoother : {string or dict} : default None Method used for presmoothing at each level. Method-specific parameters may be passed in using a tuple, e.g. presmoother=('gauss_seidel',{'sweep':'symmetric}), the default. postsmoother : {string or dict} : default F-Jacobi Postsmoothing method with the same usage as presmoother 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|. coarse_grid_P : {string} : default None Option to specify a different construction of P used in computing RAP vs. for interpolation in an actual solve. 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 extra operators in the hierarchy for diagnostics. For example, if True, then strength of connection (C) and tentative prolongation (T) are kept. Returns ------- ml : multilevel_solver Multigrid hierarchy of matrices and prolongation operators Other Parameters ---------------- coarse_solver : ['splu', 'lu', 'cholesky, 'pinv', 'gauss_seidel', ... ] Solver used at the coarsest level of the MG hierarchy. Optionally, 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. setup_complexity : bool For a detailed, more accurate setup complexity, pass in 'setup_complexity' = True. This will slow down performance, but increase accuracy of complexity count. Notes ----- References ---------- .. [1] See Also -------- aggregation.smoothed_aggregation_solver, multilevel_solver, aggregation.rootnode_solver """ if ('setup_complexity' in kwargs): if kwargs['setup_complexity'] == True: mat_mat_complexity.__detailed__ = True del kwargs['setup_complexity'] # preprocess A A = A.asfptype() if A.shape[0] != A.shape[1]: raise ValueError('expected square matrix') levels = [multilevel_solver.level()] levels[-1].A = A while len(levels) < max_levels and levels[-1].A.shape[0] > max_coarse: bottom = extend_hierarchy(levels, strength, CF, interp, restrict, filter_operator, coarse_grid_P, coarse_grid_R, keep) if bottom: break ml = multilevel_solver(levels, **kwargs) change_smoothers(ml, presmoother, postsmoother) return ml
def AIR_solver(A, strength=('classical', { 'theta': 0.3, 'norm': 'min' }), CF='RS', interp='one_point', restrict='neumann', presmoother=None, postsmoother=('FC_jacobi', { 'omega': 1.0, 'iterations': 1, 'withrho': False, 'F_iterations': 2, 'C_iterations': 0 }), filter_operator=None, coarse_grid_P=None, coarse_grid_R=None, max_levels=20, max_coarse=20, keep=False, **kwargs): """Create a multilevel solver using Classical AMG (Ruge-Stuben AMG) Parameters ---------- A : csr_matrix Square nonsymmetric 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' Method used for coarse grid selection (C/F splitting) Supported methods are RS, PMIS, PMISc, CLJP, CLJPc, and CR. interp : {string} : default 'one-point' Options include 'direct', 'standard', 'inject' and 'one-point'. restrict : {string} : default 'neumann' Options include 'air' for approximate ideal restriction. presmoother : {string or dict} : default None Method used for presmoothing at each level. Method-specific parameters may be passed in using a tuple, e.g. presmoother=('gauss_seidel',{'sweep':'symmetric}), the default. postsmoother : {string or dict} : default F-Jacobi Postsmoothing method with the same usage as presmoother 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|. coarse_grid_P : {string} : default None Option to specify a different construction of P used in computing RAP vs. for interpolation in an actual solve. 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 extra operators in the hierarchy for diagnostics. For example, if True, then strength of connection (C) and tentative prolongation (T) are kept. Returns ------- ml : multilevel_solver Multigrid hierarchy of matrices and prolongation operators Other Parameters ---------------- coarse_solver : ['splu', 'lu', 'cholesky, 'pinv', 'gauss_seidel', ... ] Solver used at the coarsest level of the MG hierarchy. Optionally, 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. setup_complexity : bool For a detailed, more accurate setup complexity, pass in 'setup_complexity' = True. This will slow down performance, but increase accuracy of complexity count. Notes ----- References ---------- .. [1] See Also -------- aggregation.smoothed_aggregation_solver, multilevel_solver, aggregation.rootnode_solver """ if ('setup_complexity' in kwargs): if kwargs['setup_complexity'] == True: mat_mat_complexity.__detailed__ = True del kwargs['setup_complexity'] # preprocess A A = A.asfptype() if A.shape[0] != A.shape[1]: raise ValueError('expected square matrix') levels = [multilevel_solver.level()] levels[-1].A = A while len(levels) < max_levels and levels[-1].A.shape[0] > max_coarse: bottom = extend_hierarchy(levels, strength, CF, interp, restrict, filter_operator, coarse_grid_P, coarse_grid_R, keep) if bottom: break ml = multilevel_solver(levels, **kwargs) change_smoothers(ml, presmoother, postsmoother) return ml
def ruge_stuben_solver(A, strength=('classical', {'theta': 0.25}), CF='RS', interpolation='direct', restriction='galerkin', presmoother=('gauss_seidel', {'sweep': 'symmetric'}), postsmoother=('gauss_seidel', {'sweep': 'symmetric'}), max_levels=10, max_coarse=10, keep=False, **kwargs): """Create a multilevel solver using Classical AMG (Ruge-Stuben AMG) Parameters ---------- A : csr_matrix Square 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' Method used for coarse grid selection (C/F splitting) Supported methods are RS, PMIS, PMISc, CLJP, CLJPc, and CR. interpolation : {string} : default 'direct' Method for interpolation. Options include 'direct', 'standard', 'injection', 'one_point', and 'distance_two'. restriction : {string or dict} : default 'galerkin' 'Galerkin' means set R := P^T for a Galerkin coarse-grid operator. Can also specify an interpolation method as above, to build the restriciton operator based on A^T. presmoother : {string or dict} Method used for presmoothing at each level. Method-specific parameters may be passed in using a tuple, e.g. presmoother=('gauss_seidel',{'sweep':'symmetric}), the default. postsmoother : {string or dict} Postsmoothing method with the same usage as presmoother max_levels: {integer} : default 10 Maximum number of levels to be used in the multilevel solver. max_coarse: {integer} : default 500 Maximum number of variables permitted on the coarse grid. keep: {bool} : default False Flag to indicate keeping extra operators in the hierarchy for diagnostics. For example, if True, then strength of connection (C) and tentative prolongation (T) are kept. Returns ------- ml : multilevel_solver Multigrid hierarchy of matrices and prolongation operators Other Parameters ---------------- cycle_type : ['V','W','F'] Structrure of multigrid cycle coarse_solver : ['splu', 'lu', 'cholesky, 'pinv', 'gauss_seidel', ... ] Solver used at the coarsest level of the MG hierarchy. Optionally, 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. setup_complexity : bool For a detailed, more accurate setup complexity, pass in 'setup_complexity' = True. This will slow down performance, but increase accuracy of complexity count. Examples -------- >>> from pyamg.gallery import poisson >>> from pyamg import ruge_stuben_solver >>> A = poisson((10,),format='csr') >>> ml = ruge_stuben_solver(A,max_coarse=3) Notes ----- Standard interpolation is generally considered more robust than direct, but direct is the currently the default until our new implementation of standard has been more rigorously tested. "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. References ---------- .. [1] Trottenberg, U., Oosterlee, C. W., and Schuller, A., "Multigrid" San Diego: Academic Press, 2001. Appendix A See Also -------- aggregation.smoothed_aggregation_solver, multilevel_solver, aggregation.rootnode_solver """ if ('setup_complexity' in kwargs): if kwargs['setup_complexity'] == True: mat_mat_complexity.__detailed__ = True del kwargs['setup_complexity'] # Convert A to csr if not (isspmatrix_csr(A) or isspmatrix_bsr(A)): try: A = csr_matrix(A) warn("Implicit conversion of A to CSR", SparseEfficiencyWarning) except: raise TypeError('Argument A must have type csr_matrix, bsr_matrix, \ or be convertible to csr_matrix') # if isspmatrix_bsr(A): # warn("Classical AMG is often more effective on CSR matrices.") # preprocess A A = A.asfptype() if A.shape[0] != A.shape[1]: raise ValueError('expected square matrix') levels = [multilevel_solver.level()] levels[-1].A = A while len(levels) < max_levels and levels[-1].A.shape[0] > max_coarse: extend_hierarchy(levels, strength, CF, interpolation, restriction, keep) ml = multilevel_solver(levels, **kwargs) change_smoothers(ml, presmoother, postsmoother) return ml
def ruge_stuben_solver(A, strength=('classical', { 'theta': 0.25 }), CF='RS', interpolation='direct', restriction='galerkin', presmoother=('gauss_seidel', { 'sweep': 'symmetric' }), postsmoother=('gauss_seidel', { 'sweep': 'symmetric' }), max_levels=10, max_coarse=10, keep=False, **kwargs): """Create a multilevel solver using Classical AMG (Ruge-Stuben AMG) Parameters ---------- A : csr_matrix Square 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' Method used for coarse grid selection (C/F splitting) Supported methods are RS, PMIS, PMISc, CLJP, CLJPc, and CR. interpolation : {string} : default 'direct' Method for interpolation. Options include 'direct', 'standard', 'injection', 'one_point', and 'distance_two'. restriction : {string or dict} : default 'galerkin' 'Galerkin' means set R := P^T for a Galerkin coarse-grid operator. Can also specify an interpolation method as above, to build the restriciton operator based on A^T. presmoother : {string or dict} Method used for presmoothing at each level. Method-specific parameters may be passed in using a tuple, e.g. presmoother=('gauss_seidel',{'sweep':'symmetric}), the default. postsmoother : {string or dict} Postsmoothing method with the same usage as presmoother max_levels: {integer} : default 10 Maximum number of levels to be used in the multilevel solver. max_coarse: {integer} : default 500 Maximum number of variables permitted on the coarse grid. keep: {bool} : default False Flag to indicate keeping extra operators in the hierarchy for diagnostics. For example, if True, then strength of connection (C) and tentative prolongation (T) are kept. Returns ------- ml : multilevel_solver Multigrid hierarchy of matrices and prolongation operators Other Parameters ---------------- cycle_type : ['V','W','F'] Structrure of multigrid cycle coarse_solver : ['splu', 'lu', 'cholesky, 'pinv', 'gauss_seidel', ... ] Solver used at the coarsest level of the MG hierarchy. Optionally, 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. setup_complexity : bool For a detailed, more accurate setup complexity, pass in 'setup_complexity' = True. This will slow down performance, but increase accuracy of complexity count. Examples -------- >>> from pyamg.gallery import poisson >>> from pyamg import ruge_stuben_solver >>> A = poisson((10,),format='csr') >>> ml = ruge_stuben_solver(A,max_coarse=3) Notes ----- Standard interpolation is generally considered more robust than direct, but direct is the currently the default until our new implementation of standard has been more rigorously tested. "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. References ---------- .. [1] Trottenberg, U., Oosterlee, C. W., and Schuller, A., "Multigrid" San Diego: Academic Press, 2001. Appendix A See Also -------- aggregation.smoothed_aggregation_solver, multilevel_solver, aggregation.rootnode_solver """ if ('setup_complexity' in kwargs): if kwargs['setup_complexity'] == True: mat_mat_complexity.__detailed__ = True del kwargs['setup_complexity'] # Convert A to csr if not (isspmatrix_csr(A) or isspmatrix_bsr(A)): try: A = csr_matrix(A) warn("Implicit conversion of A to CSR", SparseEfficiencyWarning) except: raise TypeError( 'Argument A must have type csr_matrix, bsr_matrix, \ or be convertible to csr_matrix') # if isspmatrix_bsr(A): # warn("Classical AMG is often more effective on CSR matrices.") # preprocess A A = A.asfptype() if A.shape[0] != A.shape[1]: raise ValueError('expected square matrix') levels = [multilevel_solver.level()] levels[-1].A = A while len(levels) < max_levels and levels[-1].A.shape[0] > max_coarse: extend_hierarchy(levels, strength, CF, interpolation, restriction, keep) ml = multilevel_solver(levels, **kwargs) change_smoothers(ml, presmoother, postsmoother) return ml
def smoothed_aggregation_helmholtz_solver(A, planewaves, use_constant=(True, { 'last_level': 0 }), symmetry='symmetric', strength='symmetric', aggregate='standard', smooth=('energy', { 'krylov': 'gmres' }), presmoother=('gauss_seidel_nr', { 'sweep': 'symmetric' }), postsmoother=('gauss_seidel_nr', { 'sweep': 'symmetric' }), improve_candidates='default', max_levels=10, max_coarse=100, **kwargs): """ Create a multilevel solver using Smoothed Aggregation (SA) for a 2D Helmholtz operator Parameters ---------- A : {csr_matrix, bsr_matrix} Sparse NxN matrix in CSR or BSR format planewaves : { list } [pw_0, pw_1, ..., pw_n], where the k-th tuple pw_k is of the form (fn, args). fn is a callable and args is a dictionary of arguments for fn. This k-th tuple is used to define any new planewaves (i.e., new coarse grid basis functions) to be appended to the existing B_k at that level. The function fn must return functions defined on the finest level, i.e., a collection of vector(s) of length A.shape[0]. These vectors are then restricted to the appropriate level, where they enrich the coarse space. Instead of a tuple, None can be used to stipulate no introduction of planewaves at that level. If len(planewaves) < max_levels, the last entry is used to define coarser level planewaves. use_constant : {tuple} Tuple of the form (bool, {'last_level':int}). The boolean denotes whether to introduce the constant in B at level 0. 'last_level' denotes the final level to use the constant in B. That is, if 'last_level' is 1, then the vector in B corresponding to the constant on level 0 is dropped from B at level 2. This is important, because using constant based interpolation beyond the Nyquist rate will result in poor solver performance. symmetry : {string} 'symmetric' refers to both real and complex symmetric 'hermitian' refers to both complex Hermitian and real Hermitian 'nonsymmetric' i.e. nonsymmetric in a hermitian sense Note that for the strictly real case, symmetric and hermitian are the same Note that this flag does not denote definiteness of the operator. strength : ['symmetric', 'classical', 'evolution', ('predefined', {'C' : csr_matrix}), 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. See notes below for varying this parameter on a per level basis. Also, see notes below for using a predefined strength matrix on each level. aggregate : ['standard', 'lloyd', 'naive', ('predefined', {'AggOp' : csr_matrix})] Method used to aggregate nodes. See notes below for varying this parameter on a per level basis. Also, see notes below for using a predefined aggregation on each level. smooth : ['jacobi', 'richardson', 'energy', None] Method used to smooth the tentative prolongator. Method-specific parameters may be passed in using a tuple, e.g. smooth= ('jacobi',{'filter' : True }). See notes below for varying this parameter on a per level basis. presmoother : {tuple, string, list} : default ('block_gauss_seidel', {'sweep':'symmetric'}) Defines the presmoother for the multilevel cycling. The default block Gauss-Seidel option defaults to point-wise Gauss-Seidel, if the matrix is CSR or is a BSR matrix with blocksize of 1. See notes below for varying this parameter on a per level basis. postsmoother : {tuple, string, list} Same as presmoother, except defines the postsmoother. improve_candidates : {list} : default [('block_gauss_seidel', {'sweep':'symmetric'}), None] The ith entry defines the method used to improve the candidates B on level i. If the list is shorter than max_levels, then the last entry will define the method for all levels lower. The list elements are relaxation descriptors of the form used for presmoother and postsmoother. A value of None implies no action on B. max_levels : {integer} : default 10 Maximum number of levels to be used in the multilevel solver. max_coarse : {integer} : default 500 Maximum number of variables permitted on the coarse grid. Other Parameters ---------------- coarse_solver : ['splu','lu', ... ] Solver used at the coarsest level of the MG hierarchy Returns ------- ml : multilevel_solver Multigrid hierarchy of matrices and prolongation operators See Also -------- multilevel_solver, smoothed_aggregation_solver Notes ----- - The additional parameters are passed through as arguments to multilevel_solver. Refer to pyamg.multilevel_solver for additional documentation. - The parameters smooth, strength, aggregate, presmoother, postsmoother can be varied on a per level basis. For different methods on different levels, use a list as input so that the ith entry defines the method at the ith level. If there are more levels in the hierarchy than list entries, the last entry will define the method for all levels lower. Examples are: smooth=[('jacobi', {'omega':1.0}), None, 'jacobi'] presmoother=[('block_gauss_seidel', {'sweep':symmetric}), 'sor'] aggregate=['standard', 'naive'] strength=[('symmetric', {'theta':0.25}), ('symmetric',{'theta':0.08})] - Predefined strength of connection and aggregation schemes can be specified. These options are best used together, but aggregation can be predefined while strength of connection is not. For predefined strength of connection, use a list consisting of tuples of the form ('predefined', {'C' : C0}), where C0 is a csr_matrix and each degree-of-freedom in C0 represents a supernode. For instance to predefine a three-level hierarchy, use [('predefined', {'C' : C0}), ('predefined', {'C' : C1}) ]. Similarly for predefined aggregation, use a list of tuples. For instance to predefine a three-level hierarchy, use [('predefined', {'AggOp' : Agg0}), ('predefined', {'AggOp' : Agg1}) ], where the dimensions of A, Agg0 and Agg1 are compatible, i.e. Agg0.shape[1] == A.shape[0] and Agg1.shape[1] == Agg0.shape[0]. Each AggOp is a csr_matrix. Examples -------- >>> from pyamg import smoothed_aggregation_helmholtz_solver, poisson >>> from scipy.sparse.linalg import cg >>> from scipy import rand >>> A = poisson((100,100), format='csr') # matrix >>> b = rand(A.shape[0]) # random RHS >>> ml = smoothed_aggregation_solver(A) # AMG solver >>> M = ml.aspreconditioner(cycle='V') # preconditioner >>> x,info = cg(A, b, tol=1e-8, maxiter=30, M=M) # solve with CG References ---------- .. [1] L. N. Olson and J. B. Schroder. Smoothed Aggregation for Helmholtz Problems. Numerical Linear Algebra with Applications. pp. 361--386. 17 (2010). """ if not (isspmatrix_csr(A) or isspmatrix_bsr(A)): raise TypeError('argument A must have type csr_matrix or bsr_matrix') A = A.asfptype() if (symmetry != 'symmetric') and (symmetry != 'hermitian') and ( symmetry != 'nonsymmetric'): raise ValueError( 'expected \'symmetric\', \'nonsymmetric\' or \'hermitian\' for the symmetry parameter ' ) A.symmetry = symmetry if A.shape[0] != A.shape[1]: raise ValueError('expected square matrix') ## # Preprocess and extend planewaves to length max_levels planewaves = preprocess_planewaves(planewaves, max_levels) # Check that the user has defined functions for B at each level use_const, args = unpack_arg(use_constant) first_planewave_level = -1 for pw in planewaves: first_planewave_level += 1 if pw is not None: break ## if (use_const == False) and (planewaves[0] == None): raise ValueError('No functions defined for B on the finest level, ' + \ 'either use_constant must be true, or planewaves must be defined for level 0') elif (use_const == True) and (args['last_level'] < first_planewave_level - 1): raise ValueError('Some levels have no function(s) defined for B. ' + \ 'Change use_constant and/or planewave arguments.') ## # Levelize the user parameters, so that they become lists describing the # desired user option on each level. max_levels, max_coarse, strength =\ levelize_strength_or_aggregation(strength, max_levels, max_coarse) max_levels, max_coarse, aggregate =\ levelize_strength_or_aggregation(aggregate, max_levels, max_coarse) improve_candidates = levelize_smooth_or_improve_candidates( improve_candidates, max_levels) smooth = levelize_smooth_or_improve_candidates(smooth, max_levels) ## # Start first level levels = [] levels.append(multilevel_solver.level()) levels[-1].A = A # matrix levels[-1].B = numpy.zeros( (A.shape[0], 0)) # place-holder for near-nullspace candidates zeros_0 = numpy.zeros((levels[0].A.shape[0], ), dtype=A.dtype) while len(levels) < max_levels and levels[-1].A.shape[0] > max_coarse: A = levels[0].A A_l = levels[-1].A zeros_l = numpy.zeros((levels[-1].A.shape[0], ), dtype=A.dtype) ## # Generate additions to n-th level candidates if planewaves[len(levels) - 1] != None: fn, args = unpack_arg(planewaves[len(levels) - 1]) Bcoarse2 = numpy.array(fn(**args)) ## # As in alpha-SA, relax the candidates before restriction if improve_candidates[0] is not None: Bcoarse2 = relaxation_as_linear_operator( improve_candidates[0], A, zeros_0) * Bcoarse2 ## # Restrict Bcoarse2 to current level for i in range(len(levels) - 1): Bcoarse2 = levels[i].R * Bcoarse2 # relax after restriction if improve_candidates[len(levels) - 1] is not None: Bcoarse2 = relaxation_as_linear_operator( improve_candidates[len(levels) - 1], A_l, zeros_l) * Bcoarse2 else: Bcoarse2 = numpy.zeros((A_l.shape[0], 0), dtype=A.dtype) ## # Deal with the use of constant in interpolation use_const, args = unpack_arg(use_constant) if use_const and len(levels) == 1: # If level 0, and the constant is to be used in interpolation levels[0].B = numpy.hstack((numpy.ones((A.shape[0], 1), dtype=A.dtype), Bcoarse2)) elif use_const and args['last_level'] == len(levels) - 2: # If the previous level was the last level to use the constant, then remove the # coarse grid function based on the constant from B levels[-1].B = numpy.hstack((levels[-1].B[:, 1:], Bcoarse2)) else: levels[-1].B = numpy.hstack((levels[-1].B, Bcoarse2)) ## # Create and Append new level extend_hierarchy(levels, strength, aggregate, smooth, [None for i in range(max_levels)], keep=True) ml = multilevel_solver(levels, **kwargs) change_smoothers(ml, presmoother, postsmoother) return ml
def smoothed_aggregation_solver(A, B=None, BH=None, symmetry='hermitian', strength='symmetric', aggregate='standard', smooth=('jacobi', {'omega': 4.0/3.0}), presmoother=('block_gauss_seidel', {'sweep': 'symmetric'}), postsmoother=('block_gauss_seidel', {'sweep': 'symmetric'}), improve_candidates=[('block_gauss_seidel', {'sweep': 'symmetric', 'iterations': 4}), None], max_levels = 10, max_coarse = 10, diagonal_dominance=False, keep=False, **kwargs): """ Create a multilevel solver using classical-style Smoothed Aggregation (SA) Parameters ---------- A : {csr_matrix, bsr_matrix} Sparse NxN matrix in CSR or BSR format B : {None, array_like} Right near-nullspace candidates stored in the columns of an NxK array. The default value B=None is equivalent to B=ones((N,1)) BH : {None, array_like} Left near-nullspace candidates stored in the columns of an NxK array. BH is only used if symmetry is 'nonsymmetric'. The default value B=None is equivalent to BH=B.copy() symmetry : {string} 'symmetric' refers to both real and complex symmetric 'hermitian' refers to both complex Hermitian and real Hermitian 'nonsymmetric' i.e. nonsymmetric in a hermitian sense Note, in the strictly real case, symmetric and hermitian are the same Note, this flag does not denote definiteness of the operator. strength : {list} : default ['symmetric', 'classical', 'evolution', 'algebraic_distance', 'affinity', ('predefined', {'C' : csr_matrix}), 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. See notes below for varying this parameter on a per level basis. Also, see notes below for using a predefined strength matrix on each level. aggregate : {list} : default ['standard', 'lloyd', 'naive', ('predefined', {'AggOp' : csr_matrix})] Method used to aggregate nodes. See notes below for varying this parameter on a per level basis. Also, see notes below for using a predefined aggregation on each level. smooth : {list} : default ['jacobi', 'richardson', 'energy', None] Method used to smooth the tentative prolongator. Method-specific parameters may be passed in using a tuple, e.g. smooth= ('jacobi',{'filter' : True }). See notes below for varying this parameter on a per level basis. presmoother : {tuple, string, list} : default ('block_gauss_seidel', {'sweep':'symmetric'}) Defines the presmoother for the multilevel cycling. The default block Gauss-Seidel option defaults to point-wise Gauss-Seidel, if the matrix is CSR or is a BSR matrix with blocksize of 1. See notes below for varying this parameter on a per level basis. postsmoother : {tuple, string, list} Same as presmoother, except defines the postsmoother. improve_candidates : {tuple, string, list} : default [('block_gauss_seidel', {'sweep': 'symmetric', 'iterations': 4}), None] The ith entry defines the method used to improve the candidates B on level i. If the list is shorter than max_levels, then the last entry will define the method for all levels lower. If tuple or string, then this single relaxation descriptor defines improve_candidates on all levels. The list elements are relaxation descriptors of the form used for presmoother and postsmoother. A value of None implies no action on B. max_levels : {integer} : default 10 Maximum number of levels to be used in the multilevel solver. max_coarse : {integer} : default 500 Maximum number of variables permitted on the coarse grid. diagonal_dominance : {bool, tuple} : default False If True (or the first tuple entry is True), then avoid coarsening diagonally dominant rows. The second tuple entry requires a dictionary, where the key value 'theta' is used to tune the diagonal dominance threshold. keep : {bool} : default False Flag to indicate keeping extra operators in the hierarchy for diagnostics. For example, if True, then strength of connection (C), tentative prolongation (T), and aggregation (AggOp) are kept. Other Parameters ---------------- cycle_type : ['V','W','F'] Structrure of multigrid cycle coarse_solver : ['splu', 'lu', 'cholesky, 'pinv', 'gauss_seidel', ... ] Solver used at the coarsest level of the MG hierarchy. Optionally, 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. setup_complexity : bool For a detailed, more accurate setup complexity, pass in 'setup_complexity' = True. This will slow down performance, but increase accuracy of complexity count. Returns ------- ml : multilevel_solver Multigrid hierarchy of matrices and prolongation operators See Also -------- multilevel_solver, classical.ruge_stuben_solver, aggregation.smoothed_aggregation_solver Notes ----- - This method implements classical-style SA, not root-node style SA (see aggregation.rootnode_solver). - The additional parameters are passed through as arguments to multilevel_solver. Refer to pyamg.multilevel_solver for additional documentation. - At each level, four steps are executed in order to define the coarser level operator. 1. Matrix A is given and used to derive a strength matrix, C. 2. Based on the strength matrix, indices are grouped or aggregated. 3. The aggregates define coarse nodes and a tentative prolongation operator T is defined by injection 4. The tentative prolongation operator is smoothed by a relaxation scheme to improve the quality and extent of interpolation from the aggregates to fine nodes. - The parameters smooth, strength, aggregate, presmoother, postsmoother can be varied on a per level basis. For different methods on different levels, use a list as input so that the i-th entry defines the method at the i-th level. If there are more levels in the hierarchy than list entries, the last entry will define the method for all levels lower. Examples are: smooth=[('jacobi', {'omega':1.0}), None, 'jacobi'] presmoother=[('block_gauss_seidel', {'sweep':symmetric}), 'sor'] aggregate=['standard', 'naive'] strength=[('symmetric', {'theta':0.25}), ('symmetric', {'theta':0.08})] - Predefined strength of connection and aggregation schemes can be specified. These options are best used together, but aggregation can be predefined while strength of connection is not. For predefined strength of connection, use a list consisting of tuples of the form ('predefined', {'C' : C0}), where C0 is a csr_matrix and each degree-of-freedom in C0 represents a supernode. For instance to predefine a three-level hierarchy, use [('predefined', {'C' : C0}), ('predefined', {'C' : C1}) ]. Similarly for predefined aggregation, use a list of tuples. For instance to predefine a three-level hierarchy, use [('predefined', {'AggOp' : Agg0}), ('predefined', {'AggOp' : Agg1}) ], where the dimensions of A, Agg0 and Agg1 are compatible, i.e. Agg0.shape[1] == A.shape[0] and Agg1.shape[1] == Agg0.shape[0]. Each AggOp is a csr_matrix. Examples -------- >>> from pyamg import smoothed_aggregation_solver >>> from pyamg.gallery import poisson >>> from scipy.sparse.linalg import cg >>> import numpy as np >>> A = poisson((100,100), format='csr') # matrix >>> b = np.ones((A.shape[0])) # RHS >>> ml = smoothed_aggregation_solver(A) # AMG solver >>> M = ml.aspreconditioner(cycle='V') # preconditioner >>> x,info = cg(A, b, tol=1e-8, maxiter=30, M=M) # solve with CG References ---------- .. [1] Vanek, P. and Mandel, J. and Brezina, M., "Algebraic Multigrid by Smoothed Aggregation for Second and Fourth Order Elliptic Problems", Computing, vol. 56, no. 3, pp. 179--196, 1996. http://citeseer.ist.psu.edu/vanek96algebraic.html """ if ('setup_complexity' in kwargs): if kwargs['setup_complexity'] == True: mat_mat_complexity.__detailed__ = True del kwargs['setup_complexity'] if not (isspmatrix_csr(A) or isspmatrix_bsr(A)): try: A = csr_matrix(A) warn("Implicit conversion of A to CSR", SparseEfficiencyWarning) except: raise TypeError('Argument A must have type csr_matrix or ' 'bsr_matrix, or be convertible to csr_matrix') A = A.asfptype() if (symmetry != 'symmetric') and (symmetry != 'hermitian') and\ (symmetry != 'nonsymmetric'): raise ValueError('expected \'symmetric\', \'nonsymmetric\' or ' 'hermitian\' for the symmetry parameter ') A.symmetry = symmetry if A.shape[0] != A.shape[1]: raise ValueError('expected square matrix') # Right near nullspace candidates use constant for each variable as default if B is None: B = np.kron(np.ones((int(A.shape[0]/blocksize(A)), 1), dtype=A.dtype), np.eye(blocksize(A))) else: B = np.asarray(B, dtype=A.dtype) if len(B.shape) == 1: B = B.reshape(-1, 1) if B.shape[0] != A.shape[0]: raise ValueError('The near null-space modes B have incorrect \ dimensions for matrix A') if B.shape[1] < blocksize(A): warn('Having less target vectors, B.shape[1], than \ blocksize of A can degrade convergence factors.') # Left near nullspace candidates if A.symmetry == 'nonsymmetric': if BH is None: BH = B.copy() else: BH = np.asarray(BH, dtype=A.dtype) if len(BH.shape) == 1: BH = BH.reshape(-1, 1) if BH.shape[1] != B.shape[1]: raise ValueError('The number of left and right near \ null-space modes B and BH, must be equal') if BH.shape[0] != A.shape[0]: raise ValueError('The near null-space modes BH have \ incorrect dimensions for matrix A') # Levelize the user parameters, so that they become lists describing the # desired user option on each level. max_levels, max_coarse, strength =\ levelize_strength_or_aggregation(strength, max_levels, max_coarse) max_levels, max_coarse, aggregate =\ levelize_strength_or_aggregation(aggregate, max_levels, max_coarse) improve_candidates =\ levelize_smooth_or_improve_candidates(improve_candidates, max_levels) smooth = levelize_smooth_or_improve_candidates(smooth, max_levels) # Construct multilevel structure levels = [] levels.append(multilevel_solver.level()) levels[-1].A = A # matrix # Append near nullspace candidates levels[-1].B = B # right candidates if A.symmetry == 'nonsymmetric': levels[-1].BH = BH # left candidates while len(levels) < max_levels and\ int(levels[-1].A.shape[0]/blocksize(levels[-1].A)) > max_coarse: extend_hierarchy(levels, strength, aggregate, smooth, improve_candidates, diagonal_dominance, keep) # Construct and return multilevel hierarchy ml = multilevel_solver(levels, **kwargs) change_smoothers(ml, presmoother, postsmoother) return ml
def rootnode_solver(A, B=None, BH=None, symmetry='hermitian', strength='symmetric', aggregate='standard', smooth='energy', presmoother=('block_gauss_seidel', {'sweep': 'symmetric'}), postsmoother=('block_gauss_seidel', {'sweep': 'symmetric'}), improve_candidates=('block_gauss_seidel', {'sweep': 'symmetric', 'iterations': 4}), max_levels=10, max_coarse=10, diagonal_dominance=False, keep=False, **kwargs): """Create a multilevel solver using root-node based Smoothed Aggregation (SA). See the notes below, for the major differences with the classical-style smoothed aggregation solver in aggregation.smoothed_aggregation_solver. Parameters ---------- A : csr_matrix, bsr_matrix Sparse NxN matrix in CSR or BSR format B : None, array_like Right near-nullspace candidates stored in the columns of an NxK array. K must be >= the blocksize of A (see reference [2011OlScTu]_). The default value B=None is equivalent to choosing the constant over each block-variable, B=np.kron(np.ones((A.shape[0]/blocksize(A), 1)), np.eye(blocksize(A))) BH : None, array_like Left near-nullspace candidates stored in the columns of an NxK array. BH is only used if symmetry is 'nonsymmetric'. K must be >= the blocksize of A (see reference [2011OlScTu]_). The default value B=None is equivalent to choosing the constant over each block-variable, B=np.kron(np.ones((A.shape[0]/blocksize(A), 1)), np.eye(blocksize(A))) symmetry : string 'symmetric' refers to both real and complex symmetric 'hermitian' refers to both complex Hermitian and real Hermitian 'nonsymmetric' i.e. nonsymmetric in a hermitian sense Note that for the strictly real case, symmetric and hermitian are the same Note that this flag does not denote definiteness of the operator. strength : list 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. aggregate : list Method used to aggregate nodes. smooth : list Method used to smooth the tentative prolongator. Method-specific parameters may be passed in using a tuple, e.g. smooth= ('energy',{'krylov' : 'gmres'}). Only 'energy' and None are valid prolongation smoothing options. presmoother : tuple, string, list Defines the presmoother for the multilevel cycling. The default block Gauss-Seidel option defaults to point-wise Gauss-Seidel, if the matrix is CSR or is a BSR matrix with blocksize of 1. See notes below for varying this parameter on a per level basis. postsmoother : tuple, string, list Same as presmoother, except defines the postsmoother. improve_candidates : tuple, string, list The ith entry defines the method used to improve the candidates B on level i. If the list is shorter than max_levels, then the last entry will define the method for all levels lower. If tuple or string, then this single relaxation descriptor defines improve_candidates on all levels. The list elements are relaxation descriptors of the form used for presmoother and postsmoother. A value of None implies no action on B. max_levels : integer Maximum number of levels to be used in the multilevel solver. max_coarse : integer Maximum number of variables permitted on the coarse grid. diagonal_dominance : bool, tuple If True (or the first tuple entry is True), then avoid coarsening diagonally dominant rows. The second tuple entry requires a dictionary, where the key value 'theta' is used to tune the diagonal dominance threshold. keep : bool Flag to indicate keeping extra operators in the hierarchy for diagnostics. For example, if True, then strength of connection (C), tentative prolongation (T), aggregation (AggOp), and arrays storing the C-points (Cpts) and F-points (Fpts) are kept at each level. Other Parameters ---------------- cycle_type : ['V','W','F'] Structrure of multigrid cycle coarse_solver : ['splu', 'lu', 'cholesky, 'pinv', 'gauss_seidel', ... ] Solver used at the coarsest level of the MG hierarchy. Optionally, 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. Returns ------- ml : multilevel_solver Multigrid hierarchy of matrices and prolongation operators See Also -------- multilevel_solver, aggregation.smoothed_aggregation_solver, classical.ruge_stuben_solver Notes ----- - Root-node style SA differs from classical SA primarily by preserving and identity block in the interpolation operator, P. Each aggregate has a "root-node" or "center-node" associated with it, and this root-node is injected from the coarse grid to the fine grid. The injection corresponds to the identity block. - Only smooth={'energy', None} is supported for prolongation smoothing. See reference [2011OlScTu]_ below for more details on why the 'energy' prolongation smoother is the natural counterpart to root-node style SA. - The additional parameters are passed through as arguments to multilevel_solver. Refer to pyamg.multilevel_solver for additional documentation. - At each level, four steps are executed in order to define the coarser level operator. 1. Matrix A is given and used to derive a strength matrix, C. 2. Based on the strength matrix, indices are grouped or aggregated. 3. The aggregates define coarse nodes and a tentative prolongation operator T is defined by injection 4. The tentative prolongation operator is smoothed by a relaxation scheme to improve the quality and extent of interpolation from the aggregates to fine nodes. - The parameters smooth, strength, aggregate, presmoother, postsmoother can be varied on a per level basis. For different methods on different levels, use a list as input so that the i-th entry defines the method at the i-th level. If there are more levels in the hierarchy than list entries, the last entry will define the method for all levels lower. Examples are: smooth=[('jacobi', {'omega':1.0}), None, 'jacobi'] presmoother=[('block_gauss_seidel', {'sweep':symmetric}), 'sor'] aggregate=['standard', 'naive'] strength=[('symmetric', {'theta':0.25}), ('symmetric', {'theta':0.08})] - Predefined strength of connection and aggregation schemes can be specified. These options are best used together, but aggregation can be predefined while strength of connection is not. For predefined strength of connection, use a list consisting of tuples of the form ('predefined', {'C' : C0}), where C0 is a csr_matrix and each degree-of-freedom in C0 represents a supernode. For instance to predefine a three-level hierarchy, use [('predefined', {'C' : C0}), ('predefined', {'C' : C1}) ]. Similarly for predefined aggregation, use a list of tuples. For instance to predefine a three-level hierarchy, use [('predefined', {'AggOp' : Agg0}), ('predefined', {'AggOp' : Agg1}) ], where the dimensions of A, Agg0 and Agg1 are compatible, i.e. Agg0.shape[1] == A.shape[0] and Agg1.shape[1] == Agg0.shape[0]. Each AggOp is a csr_matrix. Because this is a root-nodes solver, if a member of the predefined aggregation list is predefined, it must be of the form ('predefined', {'AggOp' : Agg, 'Cnodes' : Cnodes}). Examples -------- >>> from pyamg import rootnode_solver >>> from pyamg.gallery import poisson >>> from scipy.sparse.linalg import cg >>> import numpy as np >>> A = poisson((100, 100), format='csr') # matrix >>> b = np.ones((A.shape[0])) # RHS >>> ml = rootnode_solver(A) # AMG solver >>> M = ml.aspreconditioner(cycle='V') # preconditioner >>> x, info = cg(A, b, tol=1e-8, maxiter=30, M=M) # solve with CG References ---------- .. [1996VaMa] Vanek, P. and Mandel, J. and Brezina, M., "Algebraic Multigrid by Smoothed Aggregation for Second and Fourth Order Elliptic Problems", Computing, vol. 56, no. 3, pp. 179--196, 1996. http://citeseer.ist.psu.edu/vanek96algebraic.html .. [2011OlScTu] Olson, L. and Schroder, J. and Tuminaro, R., "A general interpolation strategy for algebraic multigrid using energy minimization", SIAM Journal on Scientific Computing (SISC), vol. 33, pp. 966--991, 2011. """ if not (isspmatrix_csr(A) or isspmatrix_bsr(A)): try: A = csr_matrix(A) warn("Implicit conversion of A to CSR", SparseEfficiencyWarning) except BaseException: raise TypeError('Argument A must have type csr_matrix, \ bsr_matrix, or be convertible to csr_matrix') A = A.asfptype() if (symmetry != 'symmetric') and (symmetry != 'hermitian') and \ (symmetry != 'nonsymmetric'): raise ValueError('expected \'symmetric\', \'nonsymmetric\' \ or \'hermitian\' for the symmetry parameter ') A.symmetry = symmetry if A.shape[0] != A.shape[1]: raise ValueError('expected square matrix') # Right near nullspace candidates use constant for each variable as default if B is None: B = np.kron(np.ones((int(A.shape[0]/blocksize(A)), 1), dtype=A.dtype), np.eye(blocksize(A))) else: B = np.asarray(B, dtype=A.dtype) if len(B.shape) == 1: B = B.reshape(-1, 1) if B.shape[0] != A.shape[0]: raise ValueError('The near null-space modes B have incorrect \ dimensions for matrix A') if B.shape[1] < blocksize(A): raise ValueError('B.shape[1] must be >= the blocksize of A') # Left near nullspace candidates if A.symmetry == 'nonsymmetric': if BH is None: BH = B.copy() else: BH = np.asarray(BH, dtype=A.dtype) if len(BH.shape) == 1: BH = BH.reshape(-1, 1) if BH.shape[1] != B.shape[1]: raise ValueError('The number of left and right near \ null-space modes B and BH, must be equal') if BH.shape[0] != A.shape[0]: raise ValueError('The near null-space modes BH have \ incorrect dimensions for matrix A') # Levelize the user parameters, so that they become lists describing the # desired user option on each level. max_levels, max_coarse, strength =\ levelize_strength_or_aggregation(strength, max_levels, max_coarse) max_levels, max_coarse, aggregate =\ levelize_strength_or_aggregation(aggregate, max_levels, max_coarse) improve_candidates =\ levelize_smooth_or_improve_candidates(improve_candidates, max_levels) smooth = levelize_smooth_or_improve_candidates(smooth, max_levels) # Construct multilevel structure levels = [] levels.append(multilevel_solver.level()) levels[-1].A = A # matrix # Append near nullspace candidates levels[-1].B = B # right candidates if A.symmetry == 'nonsymmetric': levels[-1].BH = BH # left candidates while len(levels) < max_levels and \ int(levels[-1].A.shape[0]/blocksize(levels[-1].A)) > max_coarse: extend_hierarchy(levels, strength, aggregate, smooth, improve_candidates, diagonal_dominance, keep) ml = multilevel_solver(levels, **kwargs) change_smoothers(ml, presmoother, postsmoother) return ml
def smoothed_aggregation_helmholtz_solver(A, planewaves, use_constant=(True, {'last_level':0}), symmetry='symmetric', strength='symmetric', aggregate='standard', smooth=('energy', {'krylov': 'gmres'}), presmoother=('gauss_seidel_nr',{'sweep':'symmetric'}), postsmoother=('gauss_seidel_nr',{'sweep':'symmetric'}), improve_candidates='default', max_levels = 10, max_coarse = 100, **kwargs): """ Create a multilevel solver using Smoothed Aggregation (SA) for a 2D Helmholtz operator Parameters ---------- A : {csr_matrix, bsr_matrix} Sparse NxN matrix in CSR or BSR format planewaves : { list } [pw_0, pw_1, ..., pw_n], where the k-th tuple pw_k is of the form (fn, args). fn is a callable and args is a dictionary of arguments for fn. This k-th tuple is used to define any new planewaves (i.e., new coarse grid basis functions) to be appended to the existing B_k at that level. The function fn must return functions defined on the finest level, i.e., a collection of vector(s) of length A.shape[0]. These vectors are then restricted to the appropriate level, where they enrich the coarse space. Instead of a tuple, None can be used to stipulate no introduction of planewaves at that level. If len(planewaves) < max_levels, the last entry is used to define coarser level planewaves. use_constant : {tuple} Tuple of the form (bool, {'last_level':int}). The boolean denotes whether to introduce the constant in B at level 0. 'last_level' denotes the final level to use the constant in B. That is, if 'last_level' is 1, then the vector in B corresponding to the constant on level 0 is dropped from B at level 2. This is important, because using constant based interpolation beyond the Nyquist rate will result in poor solver performance. symmetry : {string} 'symmetric' refers to both real and complex symmetric 'hermitian' refers to both complex Hermitian and real Hermitian 'nonsymmetric' i.e. nonsymmetric in a hermitian sense Note that for the strictly real case, symmetric and hermitian are the same Note that this flag does not denote definiteness of the operator. strength : ['symmetric', 'classical', 'evolution', ('predefined', {'C' : csr_matrix}), 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. See notes below for varying this parameter on a per level basis. Also, see notes below for using a predefined strength matrix on each level. aggregate : ['standard', 'lloyd', 'naive', ('predefined', {'AggOp' : csr_matrix})] Method used to aggregate nodes. See notes below for varying this parameter on a per level basis. Also, see notes below for using a predefined aggregation on each level. smooth : ['jacobi', 'richardson', 'energy', None] Method used to smooth the tentative prolongator. Method-specific parameters may be passed in using a tuple, e.g. smooth= ('jacobi',{'filter' : True }). See notes below for varying this parameter on a per level basis. presmoother : {tuple, string, list} : default ('block_gauss_seidel', {'sweep':'symmetric'}) Defines the presmoother for the multilevel cycling. The default block Gauss-Seidel option defaults to point-wise Gauss-Seidel, if the matrix is CSR or is a BSR matrix with blocksize of 1. See notes below for varying this parameter on a per level basis. postsmoother : {tuple, string, list} Same as presmoother, except defines the postsmoother. improve_candidates : {list} : default [('block_gauss_seidel', {'sweep':'symmetric'}), None] The ith entry defines the method used to improve the candidates B on level i. If the list is shorter than max_levels, then the last entry will define the method for all levels lower. The list elements are relaxation descriptors of the form used for presmoother and postsmoother. A value of None implies no action on B. max_levels : {integer} : default 10 Maximum number of levels to be used in the multilevel solver. max_coarse : {integer} : default 500 Maximum number of variables permitted on the coarse grid. Other Parameters ---------------- coarse_solver : ['splu','lu', ... ] Solver used at the coarsest level of the MG hierarchy Returns ------- ml : multilevel_solver Multigrid hierarchy of matrices and prolongation operators See Also -------- multilevel_solver, smoothed_aggregation_solver Notes ----- - The additional parameters are passed through as arguments to multilevel_solver. Refer to pyamg.multilevel_solver for additional documentation. - The parameters smooth, strength, aggregate, presmoother, postsmoother can be varied on a per level basis. For different methods on different levels, use a list as input so that the ith entry defines the method at the ith level. If there are more levels in the hierarchy than list entries, the last entry will define the method for all levels lower. Examples are: smooth=[('jacobi', {'omega':1.0}), None, 'jacobi'] presmoother=[('block_gauss_seidel', {'sweep':symmetric}), 'sor'] aggregate=['standard', 'naive'] strength=[('symmetric', {'theta':0.25}), ('symmetric',{'theta':0.08})] - Predefined strength of connection and aggregation schemes can be specified. These options are best used together, but aggregation can be predefined while strength of connection is not. For predefined strength of connection, use a list consisting of tuples of the form ('predefined', {'C' : C0}), where C0 is a csr_matrix and each degree-of-freedom in C0 represents a supernode. For instance to predefine a three-level hierarchy, use [('predefined', {'C' : C0}), ('predefined', {'C' : C1}) ]. Similarly for predefined aggregation, use a list of tuples. For instance to predefine a three-level hierarchy, use [('predefined', {'AggOp' : Agg0}), ('predefined', {'AggOp' : Agg1}) ], where the dimensions of A, Agg0 and Agg1 are compatible, i.e. Agg0.shape[1] == A.shape[0] and Agg1.shape[1] == Agg0.shape[0]. Each AggOp is a csr_matrix. Examples -------- >>> from pyamg import smoothed_aggregation_helmholtz_solver, poisson >>> from scipy.sparse.linalg import cg >>> from scipy import rand >>> A = poisson((100,100), format='csr') # matrix >>> b = rand(A.shape[0]) # random RHS >>> ml = smoothed_aggregation_solver(A) # AMG solver >>> M = ml.aspreconditioner(cycle='V') # preconditioner >>> x,info = cg(A, b, tol=1e-8, maxiter=30, M=M) # solve with CG References ---------- .. [1] L. N. Olson and J. B. Schroder. Smoothed Aggregation for Helmholtz Problems. Numerical Linear Algebra with Applications. pp. 361--386. 17 (2010). """ if not (isspmatrix_csr(A) or isspmatrix_bsr(A)): raise TypeError('argument A must have type csr_matrix or bsr_matrix') A = A.asfptype() if (symmetry != 'symmetric') and (symmetry != 'hermitian') and (symmetry != 'nonsymmetric'): raise ValueError('expected \'symmetric\', \'nonsymmetric\' or \'hermitian\' for the symmetry parameter ') A.symmetry = symmetry if A.shape[0] != A.shape[1]: raise ValueError('expected square matrix') ## # Preprocess and extend planewaves to length max_levels planewaves = preprocess_planewaves(planewaves, max_levels) # Check that the user has defined functions for B at each level use_const, args = unpack_arg(use_constant) first_planewave_level = -1 for pw in planewaves: first_planewave_level += 1 if pw is not None: break ## if (use_const == False) and (planewaves[0] == None): raise ValueError('No functions defined for B on the finest level, ' + \ 'either use_constant must be true, or planewaves must be defined for level 0') elif (use_const == True) and (args['last_level'] < first_planewave_level-1): raise ValueError('Some levels have no function(s) defined for B. ' + \ 'Change use_constant and/or planewave arguments.') ## # Levelize the user parameters, so that they become lists describing the # desired user option on each level. max_levels, max_coarse, strength =\ levelize_strength_or_aggregation(strength, max_levels, max_coarse) max_levels, max_coarse, aggregate =\ levelize_strength_or_aggregation(aggregate, max_levels, max_coarse) improve_candidates = levelize_smooth_or_improve_candidates(improve_candidates, max_levels) smooth = levelize_smooth_or_improve_candidates(smooth, max_levels) ## # Start first level levels = [] levels.append( multilevel_solver.level() ) levels[-1].A = A # matrix levels[-1].B = numpy.zeros((A.shape[0],0)) # place-holder for near-nullspace candidates zeros_0 = numpy.zeros((levels[0].A.shape[0],), dtype=A.dtype) while len(levels) < max_levels and levels[-1].A.shape[0] > max_coarse: A = levels[0].A A_l = levels[-1].A zeros_l = numpy.zeros((levels[-1].A.shape[0],), dtype=A.dtype) ## # Generate additions to n-th level candidates if planewaves[len(levels)-1] != None: fn, args = unpack_arg(planewaves[len(levels)-1]) Bcoarse2 = numpy.array(fn(**args)) ## # As in alpha-SA, relax the candidates before restriction if improve_candidates[0] is not None: Bcoarse2 = relaxation_as_linear_operator(improve_candidates[0], A, zeros_0)*Bcoarse2 ## # Restrict Bcoarse2 to current level for i in range(len(levels)-1): Bcoarse2 = levels[i].R*Bcoarse2 # relax after restriction if improve_candidates[len(levels)-1] is not None: Bcoarse2 =relaxation_as_linear_operator(improve_candidates[len(levels)-1],A_l,zeros_l)*Bcoarse2 else: Bcoarse2 = numpy.zeros((A_l.shape[0],0),dtype=A.dtype) ## # Deal with the use of constant in interpolation use_const, args = unpack_arg(use_constant) if use_const and len(levels) == 1: # If level 0, and the constant is to be used in interpolation levels[0].B = numpy.hstack( (numpy.ones((A.shape[0],1), dtype=A.dtype), Bcoarse2) ) elif use_const and args['last_level'] == len(levels)-2: # If the previous level was the last level to use the constant, then remove the # coarse grid function based on the constant from B levels[-1].B = numpy.hstack( (levels[-1].B[:,1:], Bcoarse2) ) else: levels[-1].B = numpy.hstack((levels[-1].B, Bcoarse2)) ## # Create and Append new level extend_hierarchy(levels, strength, aggregate, smooth, [None for i in range(max_levels)] ,keep=True) ml = multilevel_solver(levels, **kwargs) change_smoothers(ml, presmoother, postsmoother) return ml
def rootnode_solver(A, B=None, BH=None, symmetry='hermitian', strength='symmetric', aggregate='standard', smooth='energy', presmoother=('block_gauss_seidel', {'sweep': 'symmetric'}), postsmoother=('block_gauss_seidel', {'sweep': 'symmetric'}), improve_candidates=('block_gauss_seidel', {'sweep': 'symmetric', 'iterations': 4}), max_levels = 10, max_coarse = 10, diagonal_dominance=False, keep=False, **kwargs): """ Create a multilevel solver using root-node based Smoothed Aggregation (SA). See the notes below, for the major differences with the classical-style smoothed aggregation solver in aggregation.smoothed_aggregation_solver. Parameters ---------- A : {csr_matrix, bsr_matrix} Sparse NxN matrix in CSR or BSR format B : {None, array_like} Right near-nullspace candidates stored in the columns of an NxK array. K must be >= the blocksize of A (see reference [2]). The default value B=None is equivalent to choosing the constant over each block-variable, B=np.kron(np.ones((A.shape[0]/blocksize(A), 1)), np.eye(blocksize(A))) BH : {None, array_like} Left near-nullspace candidates stored in the columns of an NxK array. BH is only used if symmetry is 'nonsymmetric'. K must be >= the blocksize of A (see reference [2]). The default value B=None is equivalent to choosing the constant over each block-variable, B=np.kron(np.ones((A.shape[0]/blocksize(A), 1)), np.eye(blocksize(A))) symmetry : {string} 'symmetric' refers to both real and complex symmetric 'hermitian' refers to both complex Hermitian and real Hermitian 'nonsymmetric' i.e. nonsymmetric in a hermitian sense Note that for the strictly real case, symmetric and hermitian are the same Note that this flag does not denote definiteness of the operator. strength : {list} : default ['symmetric', 'classical', 'evolution', 'algebraic_distance', 'affinity', ('predefined', {'C' : csr_matrix}), 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. See notes below for varying this parameter on a per level basis. Also, see notes below for using a predefined strength matrix on each level. aggregate : {list} : default ['standard', 'lloyd', 'naive', ('predefined', {'AggOp' : csr_matrix})] Method used to aggregate nodes. See notes below for varying this parameter on a per level basis. Also, see notes below for using a predefined aggregation on each level. smooth : {list} : default ['energy', None] Method used to smooth the tentative prolongator. Method-specific parameters may be passed in using a tuple, e.g. smooth= ('energy',{'krylov' : 'gmres'}). Only 'energy' and None are valid prolongation smoothing options. See notes below for varying this parameter on a per level basis. presmoother : {tuple, string, list} : default ('block_gauss_seidel', {'sweep':'symmetric'}) Defines the presmoother for the multilevel cycling. The default block Gauss-Seidel option defaults to point-wise Gauss-Seidel, if the matrix is CSR or is a BSR matrix with blocksize of 1. See notes below for varying this parameter on a per level basis. postsmoother : {tuple, string, list} Same as presmoother, except defines the postsmoother. improve_candidates : {tuple, string, list} : default [('block_gauss_seidel', {'sweep': 'symmetric', 'iterations': 4}), None] The ith entry defines the method used to improve the candidates B on level i. If the list is shorter than max_levels, then the last entry will define the method for all levels lower. If tuple or string, then this single relaxation descriptor defines improve_candidates on all levels. The list elements are relaxation descriptors of the form used for presmoother and postsmoother. A value of None implies no action on B. max_levels : {integer} : default 10 Maximum number of levels to be used in the multilevel solver. max_coarse : {integer} : default 500 Maximum number of variables permitted on the coarse grid. diagonal_dominance : {bool, tuple} : default False If True (or the first tuple entry is True), then avoid coarsening diagonally dominant rows. The second tuple entry requires a dictionary, where the key value 'theta' is used to tune the diagonal dominance threshold. keep : {bool} : default False Flag to indicate keeping extra operators in the hierarchy for diagnostics. For example, if True, then strength of connection (C), tentative prolongation (T), aggregation (AggOp), and arrays storing the C-points (Cpts) and F-points (Fpts) are kept at each level. Other Parameters ---------------- cycle_type : ['V','W','F'] Structrure of multigrid cycle coarse_solver : ['splu', 'lu', 'cholesky, 'pinv', 'gauss_seidel', ... ] Solver used at the coarsest level of the MG hierarchy. Optionally, 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. setup_complexity : bool For a detailed, more accurate setup complexity, pass in 'setup_complexity' = True. This will slow down performance, but increase accuracy of complexity count. Returns ------- ml : multilevel_solver Multigrid hierarchy of matrices and prolongation operators See Also -------- multilevel_solver, aggregation.smoothed_aggregation_solver, classical.ruge_stuben_solver Notes ----- - Root-node style SA differs from classical SA primarily by preserving and identity block in the interpolation operator, P. Each aggregate has a "root-node" or "center-node" associated with it, and this root-node is injected from the coarse grid to the fine grid. The injection corresponds to the identity block. - Only smooth={'energy', None} is supported for prolongation smoothing. See reference [2] below for more details on why the 'energy' prolongation smoother is the natural counterpart to root-node style SA. - The additional parameters are passed through as arguments to multilevel_solver. Refer to pyamg.multilevel_solver for additional documentation. - At each level, four steps are executed in order to define the coarser level operator. 1. Matrix A is given and used to derive a strength matrix, C. 2. Based on the strength matrix, indices are grouped or aggregated. 3. The aggregates define coarse nodes and a tentative prolongation operator T is defined by injection 4. The tentative prolongation operator is smoothed by a relaxation scheme to improve the quality and extent of interpolation from the aggregates to fine nodes. - The parameters smooth, strength, aggregate, presmoother, postsmoother can be varied on a per level basis. For different methods on different levels, use a list as input so that the i-th entry defines the method at the i-th level. If there are more levels in the hierarchy than list entries, the last entry will define the method for all levels lower. Examples are: smooth=[('jacobi', {'omega':1.0}), None, 'jacobi'] presmoother=[('block_gauss_seidel', {'sweep':symmetric}), 'sor'] aggregate=['standard', 'naive'] strength=[('symmetric', {'theta':0.25}), ('symmetric', {'theta':0.08})] - Predefined strength of connection and aggregation schemes can be specified. These options are best used together, but aggregation can be predefined while strength of connection is not. For predefined strength of connection, use a list consisting of tuples of the form ('predefined', {'C' : C0}), where C0 is a csr_matrix and each degree-of-freedom in C0 represents a supernode. For instance to predefine a three-level hierarchy, use [('predefined', {'C' : C0}), ('predefined', {'C' : C1}) ]. Similarly for predefined aggregation, use a list of tuples. For instance to predefine a three-level hierarchy, use [('predefined', {'AggOp' : Agg0}), ('predefined', {'AggOp' : Agg1}) ], where the dimensions of A, Agg0 and Agg1 are compatible, i.e. Agg0.shape[1] == A.shape[0] and Agg1.shape[1] == Agg0.shape[0]. Each AggOp is a csr_matrix. Because this is a root-nodes solver, if a member of the predefined aggregation list is predefined, it must be of the form ('predefined', {'AggOp' : Agg, 'Cnodes' : Cnodes}). Examples -------- >>> from pyamg import rootnode_solver >>> from pyamg.gallery import poisson >>> from scipy.sparse.linalg import cg >>> import numpy as np >>> A = poisson((100, 100), format='csr') # matrix >>> b = np.ones((A.shape[0])) # RHS >>> ml = rootnode_solver(A) # AMG solver >>> M = ml.aspreconditioner(cycle='V') # preconditioner >>> x, info = cg(A, b, tol=1e-8, maxiter=30, M=M) # solve with CG References ---------- .. [1] Vanek, P. and Mandel, J. and Brezina, M., "Algebraic Multigrid by Smoothed Aggregation for Second and Fourth Order Elliptic Problems", Computing, vol. 56, no. 3, pp. 179--196, 1996. http://citeseer.ist.psu.edu/vanek96algebraic.html .. [2] Olson, L. and Schroder, J. and Tuminaro, R., "A general interpolation strategy for algebraic multigrid using energy minimization", SIAM Journal on Scientific Computing (SISC), vol. 33, pp. 966--991, 2011. """ if ('setup_complexity' in kwargs): if kwargs['setup_complexity'] == True: mat_mat_complexity.__detailed__ = True del kwargs['setup_complexity'] if not (isspmatrix_csr(A) or isspmatrix_bsr(A)): try: A = csr_matrix(A) warn("Implicit conversion of A to CSR", SparseEfficiencyWarning) except: raise TypeError('Argument A must have type csr_matrix, \ bsr_matrix, or be convertible to csr_matrix') A = A.asfptype() if (symmetry != 'symmetric') and (symmetry != 'hermitian') and \ (symmetry != 'nonsymmetric'): raise ValueError('expected \'symmetric\', \'nonsymmetric\' \ or \'hermitian\' for the symmetry parameter ') A.symmetry = symmetry if A.shape[0] != A.shape[1]: raise ValueError('expected square matrix') # Right near nullspace candidates use constant for each variable as default if B is None: B = np.kron(np.ones((int(A.shape[0]/blocksize(A)), 1), dtype=A.dtype), np.eye(blocksize(A))) else: B = np.asarray(B, dtype=A.dtype) if len(B.shape) == 1: B = B.reshape(-1, 1) if B.shape[0] != A.shape[0]: raise ValueError('The near null-space modes B have incorrect \ dimensions for matrix A') if B.shape[1] < blocksize(A): raise ValueError('B.shape[1] must be >= the blocksize of A') # Left near nullspace candidates if A.symmetry == 'nonsymmetric': if BH is None: BH = B.copy() else: BH = np.asarray(BH, dtype=A.dtype) if len(BH.shape) == 1: BH = BH.reshape(-1, 1) if BH.shape[1] != B.shape[1]: raise ValueError('The number of left and right near \ null-space modes B and BH, must be equal') if BH.shape[0] != A.shape[0]: raise ValueError('The near null-space modes BH have \ incorrect dimensions for matrix A') # Levelize the user parameters, so that they become lists describing the # desired user option on each level. max_levels, max_coarse, strength =\ levelize_strength_or_aggregation(strength, max_levels, max_coarse) max_levels, max_coarse, aggregate =\ levelize_strength_or_aggregation(aggregate, max_levels, max_coarse) improve_candidates =\ levelize_smooth_or_improve_candidates(improve_candidates, max_levels) smooth = levelize_smooth_or_improve_candidates(smooth, max_levels) # Construct multilevel structure levels = [] levels.append(multilevel_solver.level()) levels[-1].A = A # matrix # Append near nullspace candidates levels[-1].B = B # right candidates if A.symmetry == 'nonsymmetric': levels[-1].BH = BH # left candidates while len(levels) < max_levels and \ int(levels[-1].A.shape[0]/blocksize(levels[-1].A)) > max_coarse: extend_hierarchy(levels, strength, aggregate, smooth, improve_candidates, diagonal_dominance, keep) # Construct and return multilevel hierarchy ml = multilevel_solver(levels, **kwargs) change_smoothers(ml, presmoother, postsmoother) return ml
def ruge_stuben_solver(A, strength=('classical', {'theta': 0.25}), CF='RS', presmoother=('gauss_seidel', {'sweep': 'symmetric'}), postsmoother=('gauss_seidel', {'sweep': 'symmetric'}), max_levels=10, max_coarse=500, keep=False, **kwargs): """Create a multilevel solver using Classical AMG (Ruge-Stuben AMG) Parameters ---------- A : csr_matrix Square matrix in CSR format strength : ['symmetric', 'classical', 'evolution', 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' Method used for coarse grid selection (C/F splitting) Supported methods are RS, PMIS, PMISc, CLJP, and CLJPc presmoother : {string or dict} Method used for presmoothing at each level. Method-specific parameters may be passed in using a tuple, e.g. presmoother=('gauss_seidel',{'sweep':'symmetric}), the default. postsmoother : {string or dict} Postsmoothing method with the same usage as presmoother max_levels: {integer} : default 10 Maximum number of levels to be used in the multilevel solver. max_coarse: {integer} : default 500 Maximum number of variables permitted on the coarse grid. keep: {bool} : default False Flag to indicate keeping extra operators in the hierarchy for diagnostics. For example, if True, then strength of connection (C) and tentative prolongation (T) are kept. Returns ------- ml : multilevel_solver Multigrid hierarchy of matrices and prolongation operators Examples -------- >>> from pyamg.gallery import poisson >>> from pyamg import ruge_stuben_solver >>> A = poisson((10,),format='csr') >>> ml = ruge_stuben_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. References ---------- .. [1] Trottenberg, U., Oosterlee, C. W., and Schuller, A., "Multigrid" San Diego: Academic Press, 2001. Appendix A See Also -------- aggregation.smoothed_aggregation_solver, multilevel_solver, aggregation.rootnode_solver """ levels = [multilevel_solver.level()] # convert A to csr if not isspmatrix_csr(A): try: A = csr_matrix(A) warn("Implicit conversion of A to CSR", SparseEfficiencyWarning) except: raise TypeError('Argument A must have type csr_matrix, \ or be convertible to csr_matrix') # preprocess A A = A.asfptype() if A.shape[0] != A.shape[1]: raise ValueError('expected square matrix') levels[-1].A = A while len(levels) < max_levels and levels[-1].A.shape[0] > max_coarse: extend_hierarchy(levels, strength, CF, keep) ml = multilevel_solver(levels, **kwargs) change_smoothers(ml, presmoother, postsmoother) return ml
def general_setup_stage(ml, symmetry, candidate_iters, prepostsmoother, smooth, eliminate_local, coarse_solver, work): """Compute additional candidates and improvements following Algorithm 4 in Brezina et al. Parameters ---------- candidate_iters number of test relaxation iterations epsilon minimum acceptable relaxation convergence factor References ---------- .. [1] Brezina, Falgout, MacLachlan, Manteuffel, McCormick, and Ruge "Adaptive Smoothed Aggregation (alphaSA) Multigrid" SIAM Review Volume 47, Issue 2 (2005) http://www.cs.umn.edu/~maclach/research/aSA2.pdf """ def make_bridge(T): M, N = T.shape K = T.blocksize[0] bnnz = T.indptr[-1] # the K+1 represents the new dof introduced by the new candidate. the # bridge 'T' ignores this new dof and just maps zeros there data = np.zeros((bnnz, K+1, K), dtype=T.dtype) data[:, :-1, :] = T.data return bsr_matrix((data, T.indices, T.indptr), shape=((K + 1) * int(M / K), N)) def expand_candidates(B_old, nodesize): # insert a new dof that is always zero, to create NullDim+1 dofs per # node in B NullDim = B_old.shape[1] nnodes = int(B_old.shape[0] / nodesize) Bnew = np.zeros((nnodes, nodesize+1, NullDim), dtype=B_old.dtype) Bnew[:, :-1, :] = B_old.reshape(nnodes, nodesize, NullDim) return Bnew.reshape(-1, NullDim) levels = ml.levels x = sp.rand(levels[0].A.shape[0], 1) if levels[0].A.dtype.name.startswith('complex'): x = x + 1.0j*sp.rand(levels[0].A.shape[0], 1) b = np.zeros_like(x) x = ml.solve(b, x0=x, tol=float(np.finfo(np.float).tiny), maxiter=candidate_iters) work[:] += ml.operator_complexity()*ml.levels[0].A.nnz*candidate_iters*2 T0 = levels[0].T.copy() # TEST FOR CONVERGENCE HERE for i in range(len(ml.levels) - 2): # alpha-SA paper does local elimination here, but after talking # to Marian, its not clear that this helps things # fn, kwargs = unpack_arg(eliminate_local) # if fn == True: # eliminate_local_candidates(x,levels[i].AggOp,levels[i].A, # levels[i].T, **kwargs) # add candidate to B B = np.hstack((levels[i].B, x.reshape(-1, 1))) # construct Ptent T, R = fit_candidates(levels[i].AggOp, B) levels[i].T = T x = R[:, -1].reshape(-1, 1) # smooth P fn, kwargs = unpack_arg(smooth[i]) if fn == 'jacobi': levels[i].P = jacobi_prolongation_smoother(levels[i].A, T, levels[i].C, R, **kwargs) elif fn == 'richardson': levels[i].P = richardson_prolongation_smoother(levels[i].A, T, **kwargs) elif fn == 'energy': levels[i].P = energy_prolongation_smoother(levels[i].A, T, levels[i].C, R, None, (False, {}), **kwargs) x = R[:, -1].reshape(-1, 1) elif fn is None: levels[i].P = T else: raise ValueError('unrecognized prolongation smoother method %s' % str(fn)) # construct R if symmetry == 'symmetric': # R should reflect A's structure levels[i].R = levels[i].P.T.asformat(levels[i].P.format) elif symmetry == 'hermitian': levels[i].R = levels[i].P.H.asformat(levels[i].P.format) # construct coarse A levels[i+1].A = levels[i].R * levels[i].A * levels[i].P # construct bridging P T_bridge = make_bridge(levels[i+1].T) R_bridge = levels[i+2].B # smooth bridging P fn, kwargs = unpack_arg(smooth[i+1]) if fn == 'jacobi': levels[i+1].P = jacobi_prolongation_smoother(levels[i+1].A, T_bridge, levels[i+1].C, R_bridge, **kwargs) elif fn == 'richardson': levels[i+1].P = richardson_prolongation_smoother(levels[i+1].A, T_bridge, **kwargs) elif fn == 'energy': levels[i+1].P = energy_prolongation_smoother(levels[i+1].A, T_bridge, levels[i+1].C, R_bridge, None, (False, {}), **kwargs) elif fn is None: levels[i+1].P = T_bridge else: raise ValueError('unrecognized prolongation smoother method %s' % str(fn)) # construct the "bridging" R if symmetry == 'symmetric': # R should reflect A's structure levels[i+1].R = levels[i+1].P.T.asformat(levels[i+1].P.format) elif symmetry == 'hermitian': levels[i+1].R = levels[i+1].P.H.asformat(levels[i+1].P.format) # run solver on candidate solver = multilevel_solver(levels[i+1:], coarse_solver=coarse_solver) change_smoothers(solver, presmoother=prepostsmoother, postsmoother=prepostsmoother) x = solver.solve(np.zeros_like(x), x0=x, tol=float(np.finfo(np.float).tiny), maxiter=candidate_iters) work[:] += 2 * solver.operator_complexity() * solver.levels[0].A.nnz *\ candidate_iters*2 # update values on next level levels[i+1].B = R[:, :-1].copy() levels[i+1].T = T_bridge # note that we only use the x from the second coarsest level fn, kwargs = unpack_arg(prepostsmoother) for lvl in reversed(levels[:-2]): x = lvl.P * x work[:] += lvl.A.nnz*candidate_iters*2 if fn == 'gauss_seidel': # only relax at nonzeros, so as not to mess up any locally dropped # candidates indices = np.ravel(x).nonzero()[0] gauss_seidel_indexed(lvl.A, x, np.zeros_like(x), indices, iterations=candidate_iters, sweep='symmetric') elif fn == 'gauss_seidel_ne': gauss_seidel_ne(lvl.A, x, np.zeros_like(x), iterations=candidate_iters, sweep='symmetric') elif fn == 'gauss_seidel_nr': gauss_seidel_nr(lvl.A, x, np.zeros_like(x), iterations=candidate_iters, sweep='symmetric') elif fn == 'jacobi': jacobi(lvl.A, x, np.zeros_like(x), iterations=1, omega=1.0 / rho_D_inv_A(lvl.A)) elif fn == 'richardson': polynomial(lvl.A, x, np.zeros_like(x), iterations=1, coefficients=[1.0/approximate_spectral_radius(lvl.A)]) elif fn == 'gmres': x[:] = (gmres(lvl.A, np.zeros_like(x), x0=x, maxiter=candidate_iters)[0]).reshape(x.shape) else: raise TypeError('Unrecognized smoother') # x will be dense again, so we have to drop locally again elim, elim_kwargs = unpack_arg(eliminate_local) if elim is True: x = x/norm(x, 'inf') eliminate_local_candidates(x, levels[0].AggOp, levels[0].A, T0, **elim_kwargs) return x.reshape(-1, 1)