def test_gmres(self): # Ensure repeatability random.seed(0) # For these small matrices, Householder and MGS GMRES should give the same result, # and for symmetric (but possibly indefinite) matrices CR and GMRES should give same result for maxiter in [1,2,3]: for case, symm_case in zip(self.cases, self.symm_cases): A = case['A'] b = case['b'] x0 = case['x0'] A_symm = symm_case['A'] b_symm = symm_case['b'] x0_symm = symm_case['x0'] # Test agreement between Householder and GMRES (x, flag) = gmres_householder(A,b,x0=x0,maxiter=min(A.shape[0],maxiter)) (x2, flag2) = gmres_mgs(A,b,x0=x0,maxiter=min(A.shape[0],maxiter)) assert_array_almost_equal(x/norm(x), x2/norm(x2), err_msg='Householder GMRES and MGS GMRES gave different results for small matrix') assert_equal(flag, flag2, err_msg='Householder GMRES and MGS GMRES returned different convergence flags for small matrix') # Test agreement between GMRES and CR if A_symm.shape[0] > 1: residuals2 = [] (x2, flag2) = gmres_mgs(A_symm, b_symm, x0=x0_symm, maxiter=min(A.shape[0],maxiter),residuals=residuals2) residuals3 = [] (x3, flag2) = cr(A_symm, b_symm, x0=x0_symm, maxiter=min(A.shape[0],maxiter),residuals=residuals3) residuals2 = array(residuals2) residuals3 = array(residuals3) assert_array_almost_equal(residuals3/norm(residuals3), residuals2/norm(residuals2), err_msg='CR and GMRES yield different residual vectors') assert_array_almost_equal(x2/norm(x2), x3/norm(x3), err_msg='CR and GMRES yield different answers')
def test_accel(self): from pyamg import smoothed_aggregation_solver from pyamg.krylov import cg, bicgstab A = poisson((50, 50), format='csr') b = rand(A.shape[0]) ml = smoothed_aggregation_solver(A) # cg halts based on the preconditioner norm for accel in ['cg', cg]: x = ml.solve(b, maxiter=30, tol=1e-8, accel=accel) assert(precon_norm(b - A*x, ml) < 1e-8*precon_norm(b, ml)) residuals = [] x = ml.solve(b, maxiter=30, tol=1e-8, residuals=residuals, accel=accel) assert(precon_norm(b - A*x, ml) < 1e-8*precon_norm(b, ml)) # print residuals assert_almost_equal(precon_norm(b - A*x, ml), residuals[-1]) # cgs and bicgstab use the Euclidean norm for accel in ['bicgstab', 'cgs', bicgstab]: x = ml.solve(b, maxiter=30, tol=1e-8, accel=accel) assert(norm(b - A*x) < 1e-8*norm(b)) residuals = [] x = ml.solve(b, maxiter=30, tol=1e-8, residuals=residuals, accel=accel) assert(norm(b - A*x) < 1e-8*norm(b)) # print residuals assert_almost_equal(norm(b - A*x), residuals[-1])
def test_steepest_descent(self): # Ensure repeatability np.random.seed(0) for case in self.spd_cases: A = case['A'] b = case['b'] x0 = case['x0'] maxiter = case['maxiter'] reduction_factor = case['reduction_factor'] # This function should always decrease fvals = [] def callback(x): fvals.append( 0.5 * np.dot(np.ravel(x), np.ravel(A.dot(x.reshape(-1, 1)))) - np.dot(np.ravel(b), np.ravel(x))) x, _ = steepest_descent(A, b, x0=x0, tol=1e-16, maxiter=maxiter, callback=callback) norm1 = norm(np.ravel(b) - np.ravel(A.dot(x.reshape(-1, 1)))) norm2 = norm(np.ravel(b) - np.ravel(A.dot(x0.reshape(-1, 1)))) actual_factor = norm1 / norm2 assert (actual_factor < reduction_factor) if A.dtype != complex: for i in range(len(fvals) - 1): assert (fvals[i + 1] <= fvals[i]) # Test preconditioning A = pyamg.gallery.poisson((10, 10), format='csr') b = np.random.rand(A.shape[0], 1) x0 = np.random.rand(A.shape[0], 1) fvals = [] def callback(x): fvals.append( 0.5 * np.dot(np.ravel(x), np.ravel(A.dot(x.reshape(-1, 1)))) - np.dot(np.ravel(b), np.ravel(x))) resvec = [] sa = pyamg.smoothed_aggregation_solver(A) x, _ = steepest_descent(A, b, x0, tol=1e-8, maxiter=20, residuals=resvec, M=sa.aspreconditioner(), callback=callback) assert (resvec[-1] / resvec[0] < 1e-8) for i in range(len(fvals) - 1): assert (fvals[i + 1] <= fvals[i])
def test_minimal_residual(self): # Ensure repeatability random.seed(0) self.definite_cases.extend(self.spd_cases) for case in self.definite_cases: A = case['A'] maxiter = case['maxiter'] x0 = rand(A.shape[0], ) b = zeros_like(x0) reduction_factor = case['reduction_factor'] if A.dtype != complex: # This function should always decrease (assuming zero RHS) fvals = [] def callback(x): fvals.append( sqrt(dot(ravel(x), ravel(A * x.reshape(-1, 1))))) # (x, flag) = minimal_residual(A, b, x0=x0, tol=1e-16, maxiter=maxiter, callback=callback) actual_factor = (norm(ravel(b) - ravel(A * x.reshape(-1, 1))) / norm(ravel(b) - ravel(A * x0.reshape(-1, 1)))) assert (actual_factor < reduction_factor) if A.dtype != complex: for i in range(len(fvals) - 1): assert (fvals[i + 1] <= fvals[i]) # Test preconditioning A = pyamg.gallery.poisson((10, 10), format='csr') x0 = rand(A.shape[0], 1) b = zeros_like(x0) fvals = [] def callback(x): fvals.append(sqrt(dot(ravel(x), ravel(A * x.reshape(-1, 1))))) # resvec = [] sa = pyamg.smoothed_aggregation_solver(A) (x, flag) = minimal_residual(A, b, x0, tol=1e-8, maxiter=20, residuals=resvec, M=sa.aspreconditioner(), callback=callback) assert (resvec[-1] < 1e-8) for i in range(len(fvals) - 1): assert (fvals[i + 1] <= fvals[i])
def test_minimal_residual(self): # Ensure repeatability np.random.seed(0) self.definite_cases.extend(self.spd_cases) for case in self.definite_cases: A = case['A'] maxiter = case['maxiter'] x0 = np.random.rand(A.shape[0],) b = np.zeros_like(x0) reduction_factor = case['reduction_factor'] if A.dtype != complex: # This function should always decrease (assuming zero RHS) fvals = [] def callback(x): fvals.append(np.sqrt(np.dot(np.ravel(x), np.ravel(A*x.reshape(-1, 1))))) # (x, flag) = minimal_residual(A, b, x0=x0, tol=1e-16, maxiter=maxiter, callback=callback) actual_factor = (norm(np.ravel(b) - np.ravel(A * x.reshape(-1, 1))) / norm(np.ravel(b) - np.ravel(A * x0.reshape(-1, 1)))) assert(actual_factor < reduction_factor) if A.dtype != complex: for i in range(len(fvals)-1): assert(fvals[i+1] <= fvals[i]) # Test preconditioning A = pyamg.gallery.poisson((10, 10), format='csr') x0 = np.random.rand(A.shape[0], 1) b = np.zeros_like(x0) fvals = [] def callback(x): fvals.append(np.sqrt(np.dot(np.ravel(x), np.ravel(A*x.reshape(-1, 1))))) # resvec = [] sa = pyamg.smoothed_aggregation_solver(A) (x, flag) = minimal_residual(A, b, x0, tol=1e-8, maxiter=20, residuals=resvec, M=sa.aspreconditioner(), callback=callback) assert(resvec[-1]/resvec[0] < 1e-8) for i in range(len(fvals)-1): assert(fvals[i+1] <= fvals[i])
def test_steepest_descent(self): # Ensure repeatability np.random.seed(0) for case in self.spd_cases: A = case['A'] b = case['b'] x0 = case['x0'] maxiter = case['maxiter'] reduction_factor = case['reduction_factor'] # This function should always decrease fvals = [] def callback(x): fvals.append(0.5*np.dot(np.ravel(x), np.ravel(A*x.reshape(-1, 1))) - np.dot(np.ravel(b), np.ravel(x))) (x, flag) = steepest_descent(A, b, x0=x0, tol=1e-16, maxiter=maxiter, callback=callback) actual_factor = (norm(np.ravel(b) - np.ravel(A*x.reshape(-1, 1))) / norm(np.ravel(b) - np.ravel(A*x0.reshape(-1, 1)))) assert(actual_factor < reduction_factor) if A.dtype != complex: for i in range(len(fvals)-1): assert(fvals[i+1] <= fvals[i]) # Test preconditioning A = pyamg.gallery.poisson((10, 10), format='csr') b = np.random.rand(A.shape[0], 1) x0 = np.random.rand(A.shape[0], 1) fvals = [] def callback(x): fvals.append(0.5*np.dot(np.ravel(x), np.ravel(A*x.reshape(-1, 1))) - np.dot(np.ravel(b), np.ravel(x))) resvec = [] sa = pyamg.smoothed_aggregation_solver(A) (x, flag) = steepest_descent(A, b, x0, tol=1e-8, maxiter=20, residuals=resvec, M=sa.aspreconditioner(), callback=callback) assert(resvec[-1]/resvec[0] < 1e-8) for i in range(len(fvals)-1): assert(fvals[i+1] <= fvals[i])
def solver_diagnostic(A): ## # Generate B B = ones((A.shape[0],1), dtype=A.dtype); BH = B.copy() ## # Random initial guess, zero right-hand side random.seed(0) b = zeros((A.shape[0],1)) x0 = rand(A.shape[0],1) ## # Create solver ml = smoothed_aggregation_solver(A, B=B, BH=BH, strength=('symmetric', {'theta': 0.0}), smooth=('energy', {'weighting': 'local', 'krylov': 'gmres', 'degree': 1, 'maxiter': 2}), improve_candidates=[('gauss_seidel_nr', {'sweep': 'symmetric', 'iterations': 4}), None], aggregate="standard", presmoother=('gauss_seidel_nr', {'sweep': 'symmetric', 'iterations': 2}), postsmoother=('gauss_seidel_nr', {'sweep': 'symmetric', 'iterations': 2}), max_levels=15, max_coarse=300, coarse_solver="pinv") ## # Solve system res = [] x = ml.solve(b, x0=x0, tol=1e-08, residuals=res, accel="gmres", maxiter=300, cycle="V") res_rate = (res[-1]/res[0])**(1.0/(len(res)-1.)) normr0 = norm(ravel(b) - ravel(A*x0)) print " " print ml print "System size: " + str(A.shape) print "Avg. Resid Reduction: %1.2f"%res_rate print "Iterations: %d"%len(res) print "Operator Complexity: %1.2f"%ml.operator_complexity() print "Work per DOA: %1.2f"%(ml.cycle_complexity()/abs(log10(res_rate))) print "Relative residual norm: %1.2e"%(norm(ravel(b) - ravel(A*x))/normr0) ## # Plot residual history pylab.semilogy(array(res)/normr0) pylab.title('Residual Histories') pylab.xlabel('Iteration') pylab.ylabel('Relative Residual Norm') pylab.show()
def test_krylov(self): # Oblique projectors reduce the residual for method in self.oblique: for case in self.cases: A = case['A']; b = case['b']; x0 = case['x0'] (xNew, flag) = method(A, b, x0=x0, tol=case['tol'], maxiter=case['maxiter']) xNew = xNew.reshape(-1,1) assert_equal( (norm(b - A*xNew)/norm(b - A*x0)) < case['reduction_factor'], True, err_msg='Oblique Krylov Method Failed Test') # Oblique projectors reduce the residual, here we consider oblique projectors for symmetric matrices for method in self.symm_oblique: for case in self.symm_cases: A = case['A']; b = case['b']; x0 = case['x0'] (xNew, flag) = method(A, b, x0=x0, tol=case['tol'], maxiter=case['maxiter']) xNew = xNew.reshape(-1,1) assert_equal( (norm(b - A*xNew)/norm(b - A*x0)) < case['reduction_factor'], True, err_msg='Symmetric oblique Krylov Method Failed Test') # Orthogonal projectors reduce the error for method in self.orth: for case in self.cases: A = case['A']; b = case['b']; x0 = case['x0'] (xNew, flag) = method(A, b, x0=x0, tol=case['tol'], maxiter=case['maxiter']) xNew = xNew.reshape(-1,1) soln = solve(A,b) assert_equal( (norm(soln - xNew)/norm(soln - x0)) < case['reduction_factor'], True, err_msg='Orthogonal Krylov Method Failed Test') # SPD Orthogonal projectors reduce the error for method in self.spd_orth: for case in self.spd_cases: A = case['A']; b = case['b']; x0 = case['x0'] (xNew, flag) = method(A, b, x0=x0, tol=case['tol'], maxiter=case['maxiter']) xNew = xNew.reshape(-1,1) soln = solve(A,b) assert_equal( (norm(soln - xNew)/norm(soln - x0)) < case['reduction_factor'], True, err_msg='Orthogonal Krylov Method Failed Test') # Assume that Inexact Methods reduce the residual for these examples for method in self.inexact: for case in self.cases: A = case['A']; b = case['b']; x0 = case['x0'] (xNew, flag) = method(A, b, x0=x0, tol=case['tol'], maxiter=A.shape[0]) xNew = xNew.reshape(-1,1) assert_equal( (norm(b - A*xNew)/norm(b - A*x0)) < 0.15, True, err_msg='Inexact Krylov Method Failed Test')
def test_aspreconditioner(self): from pyamg import smoothed_aggregation_solver from scipy.sparse.linalg import cg from pyamg.krylov import fgmres A = poisson((50, 50), format='csr') b = rand(A.shape[0]) ml = smoothed_aggregation_solver(A) for cycle in ['V', 'W', 'F']: M = ml.aspreconditioner(cycle=cycle) x, info = cg(A, b, tol=1e-8, maxiter=30, M=M) # cg satisfies convergence in the preconditioner norm assert (precon_norm(b - A * x, ml) < 1e-8 * precon_norm(b, ml)) for cycle in ['AMLI']: M = ml.aspreconditioner(cycle=cycle) x, info = fgmres(A, b, tol=1e-8, maxiter=30, M=M) # fgmres satisfies convergence in the 2-norm assert (norm(b - A * x) < 1e-8 * norm(b))
def test_aspreconditioner(self): from pyamg import smoothed_aggregation_solver from scipy.sparse.linalg import cg from pyamg.krylov import fgmres A = poisson((50, 50), format='csr') b = rand(A.shape[0]) ml = smoothed_aggregation_solver(A) for cycle in ['V', 'W', 'F']: M = ml.aspreconditioner(cycle=cycle) x, info = cg(A, b, tol=1e-8, maxiter=30, M=M) # cg satisfies convergence in the preconditioner norm assert(precon_norm(b - A*x, ml) < 1e-8*precon_norm(b, ml)) for cycle in ['AMLI']: M = ml.aspreconditioner(cycle=cycle) x, info = fgmres(A, b, tol=1e-8, maxiter=30, M=M) # fgmres satisfies convergence in the 2-norm assert(norm(b - A*x) < 1e-8*norm(b))
def test_norm(self): cases = [] cases.append(4) cases.append(-1) cases.append(2.5) cases.append(3 + 5j) cases.append(7 - 2j) cases.append([1 + 3j, 6]) cases.append([1 + 3j, 6 - 2j]) for A in cases: assert_almost_equal(norm(A), linalg.norm(A))
def test_accel(self): from pyamg import smoothed_aggregation_solver from pyamg.krylov import cg, bicgstab A = poisson((50, 50), format='csr') b = rand(A.shape[0]) ml = smoothed_aggregation_solver(A) # cg halts based on the preconditioner norm for accel in ['cg', cg]: x = ml.solve(b, maxiter=30, tol=1e-8, accel=accel) assert (precon_norm(b - A * x, ml) < 1e-8 * precon_norm(b, ml)) residuals = [] x = ml.solve(b, maxiter=30, tol=1e-8, residuals=residuals, accel=accel) assert (precon_norm(b - A * x, ml) < 1e-8 * precon_norm(b, ml)) # print residuals assert_almost_equal(precon_norm(b - A * x, ml), residuals[-1]) # cgs and bicgstab use the Euclidean norm for accel in ['bicgstab', 'cgs', bicgstab]: x = ml.solve(b, maxiter=30, tol=1e-8, accel=accel) assert (norm(b - A * x) < 1e-8 * norm(b)) residuals = [] x = ml.solve(b, maxiter=30, tol=1e-8, residuals=residuals, accel=accel) assert (norm(b - A * x) < 1e-8 * norm(b)) # print residuals assert_almost_equal(norm(b - A * x), residuals[-1])
def test_gmres(self): # Ensure repeatability random.seed(0) # For these small matrices, Householder and MGS GMRES should give the # same result, and for symmetric (but possibly indefinite) matrices CR # and GMRES should give same result for maxiter in [1, 2, 3]: for case, symm_case in zip(self.cases, self.symm_cases): A = case['A'] b = case['b'] x0 = case['x0'] A_symm = symm_case['A'] b_symm = symm_case['b'] x0_symm = symm_case['x0'] # Test agreement between Householder and GMRES (x, flag) = gmres_householder(A, b, x0=x0, maxiter=min(A.shape[0], maxiter)) (x2, flag2) = gmres_mgs(A, b, x0=x0, maxiter=min(A.shape[0], maxiter)) err_msg = ('Householder GMRES and MGS GMRES gave ' 'different results for small matrix') assert_array_almost_equal(x/norm(x), x2/norm(x2), err_msg=err_msg) err_msg = ('Householder GMRES and MGS GMRES returned ' 'different convergence flags for small matrix') assert_equal(flag, flag2, err_msg=err_msg) # Test agreement between GMRES and CR if A_symm.shape[0] > 1: residuals2 = [] (x2, flag2) = gmres_mgs(A_symm, b_symm, x0=x0_symm, maxiter=min(A.shape[0], maxiter), residuals=residuals2) residuals3 = [] (x3, flag2) = cr(A_symm, b_symm, x0=x0_symm, maxiter=min(A.shape[0], maxiter), residuals=residuals3) residuals2 = array(residuals2) residuals3 = array(residuals3) err_msg = 'CR and GMRES yield different residual vectors' assert_array_almost_equal(residuals3/norm(residuals3), residuals2/norm(residuals2), err_msg=err_msg) err_msg = 'CR and GMRES yield different answers' assert_array_almost_equal(x2/norm(x2), x3/norm(x3), err_msg=err_msg)
def cr(A, b, x0=None, tol=1e-5, maxiter=None, xtype=None, M=None, callback=None, residuals=None): '''Conjugate Residual algorithm Solves the linear system Ax = b. Left preconditioning is supported. The matrix A must be Hermitian symmetric (but not necessarily definite). Parameters ---------- A : {array, matrix, sparse matrix, LinearOperator} n x n, linear system to solve b : {array, matrix} right hand side, shape is (n,) or (n,1) x0 : {array, matrix} initial guess, default is a vector of zeros tol : float relative convergence tolerance, i.e. tol is scaled by the preconditioner norm of r_0, or ||r_0||_M. maxiter : int maximum number of allowed iterations xtype : type dtype for the solution, default is automatic type detection M : {array, matrix, sparse matrix, LinearOperator} n x n, inverted preconditioner, i.e. solve M A x = M b. callback : function User-supplied function is called after each iteration as callback(xk), where xk is the current solution vector residuals : list residuals contains the residual norm history, including the initial residual. The preconditioner norm is used, instead of the Euclidean norm. Returns ------- (xNew, info) xNew : an updated guess to the solution of Ax = b info : halting status of cr == ======================================= 0 successful exit >0 convergence to tolerance not achieved, return iteration count instead. <0 numerical breakdown, or illegal input == ======================================= Notes ----- The LinearOperator class is in scipy.sparse.linalg.interface. Use this class if you prefer to define A or M as a mat-vec routine as opposed to explicitly constructing the matrix. A.psolve(..) is still supported as a legacy. The 2-norm of the preconditioned residual is used both for halting and returned in the residuals list. Examples -------- >>> from pyamg.krylov.cr import cr >>> from pyamg.util.linalg import norm >>> import numpy >>> from pyamg.gallery import poisson >>> A = poisson((10,10)) >>> b = numpy.ones((A.shape[0],)) >>> (x,flag) = cr(A,b, maxiter=2, tol=1e-8) >>> print norm(b - A*x) 10.9370700187 References ---------- .. [1] Yousef Saad, "Iterative Methods for Sparse Linear Systems, Second Edition", SIAM, pp. 262-67, 2003 http://www-users.cs.umn.edu/~saad/books.html ''' A, M, x, b, postprocess = make_system(A, M, x0, b, xtype=None) n = len(b) ## # Ensure that warnings are always reissued from this function import warnings warnings.filterwarnings('always', module='pyamg\.krylov\._cr') # determine maxiter if maxiter is None: maxiter = int(1.3*len(b)) + 2 elif maxiter < 1: raise ValueError('Number of iterations must be positive') # choose tolerance for numerically zero values t = A.dtype.char eps = numpy.finfo(numpy.float).eps feps = numpy.finfo(numpy.single).eps geps = numpy.finfo(numpy.longfloat).eps _array_precision = {'f': 0, 'd': 1, 'g': 2, 'F': 0, 'D': 1, 'G': 2} numerically_zero = {0: feps*1e3, 1: eps*1e6, 2: geps*1e6}[_array_precision[t]] # setup method r = b - A*x z = M*r p = z.copy() zz = inner(z.conjugate(), z) # use preconditioner norm normr = sqrt(zz) if residuals is not None: residuals[:] = [normr] # initial residual # Check initial guess ( scaling by b, if b != 0, # must account for case when norm(b) is very small) normb = norm(b) if normb == 0.0: normb = 1.0 if normr < tol*normb: return (postprocess(x), 0) # Scale tol by ||r_0||_M if normr != 0.0: tol = tol*normr # How often should r be recomputed recompute_r = 8 iter = 0 Az = A*z rAz = inner(r.conjugate(), Az) Ap = A*p while True: rAz_old = rAz alpha = rAz / inner(Ap.conjugate(), Ap) # 3 x += alpha * p # 4 if mod(iter, recompute_r) and iter > 0: # 5 r -= alpha * Ap else: r = b - A*x z = M*r Az = A*z rAz = inner(r.conjugate(), Az) beta = rAz/rAz_old # 6 p *= beta # 7 p += z Ap *= beta # 8 Ap += Az iter += 1 zz = inner(z.conjugate(), z) normr = sqrt(zz) # use preconditioner norm if residuals is not None: residuals.append(normr) if callback is not None: callback(x) if normr < tol: return (postprocess(x), 0) elif zz == 0.0: # important to test after testing normr < tol. rz == 0.0 is an # indicator of convergence when r = 0.0 warn("\nSingular preconditioner detected in CR, ceasing \ iterations\n") return (postprocess(x), -1) if iter == maxiter: return (postprocess(x), iter)
def gmres_mgs(A, b, x0=None, tol=1e-5, restrt=None, maxiter=None, xtype=None, M=None, callback=None, residuals=None, reorth=False): ''' Generalized Minimum Residual Method (GMRES) GMRES iteratively refines the initial solution guess to the system Ax = b Modified Gram-Schmidt version Parameters ---------- A : {array, matrix, sparse matrix, LinearOperator} n x n, linear system to solve b : {array, matrix} right hand side, shape is (n,) or (n,1) x0 : {array, matrix} initial guess, default is a vector of zeros tol : float relative convergence tolerance, i.e. tol is scaled by the norm of the initial preconditioned residual restrt : {None, int} - if int, restrt is max number of inner iterations and maxiter is the max number of outer iterations - if None, do not restart GMRES, and max number of inner iterations is maxiter maxiter : {None, int} - if restrt is None, maxiter is the max number of inner iterations and GMRES does not restart - if restrt is int, maxiter is the max number of outer iterations, and restrt is the max number of inner iterations xtype : type dtype for the solution, default is automatic type detection M : {array, matrix, sparse matrix, LinearOperator} n x n, inverted preconditioner, i.e. solve M A x = M b. callback : function User-supplied function is called after each iteration as callback( ||rk||_2 ), where rk is the current preconditioned residual vector residuals : list residuals contains the preconditioned residual norm history, including the initial residual. reorth : boolean If True, then a check is made whether to re-orthogonalize the Krylov space each GMRES iteration Returns ------- (xNew, info) xNew : an updated guess to the solution of Ax = b info : halting status of gmres == ============================================= 0 successful exit >0 convergence to tolerance not achieved, return iteration count instead. This value is precisely the order of the Krylov space. <0 numerical breakdown, or illegal input == ============================================= Notes ----- - The LinearOperator class is in scipy.sparse.linalg.interface. Use this class if you prefer to define A or M as a mat-vec routine as opposed to explicitly constructing the matrix. A.psolve(..) is still supported as a legacy. - For robustness, modified Gram-Schmidt is used to orthogonalize the Krylov Space Givens Rotations are used to provide the residual norm each iteration Examples -------- >>> from pyamg.krylov import gmres >>> from pyamg.util.linalg import norm >>> import numpy as np >>> from pyamg.gallery import poisson >>> A = poisson((10,10)) >>> b = np.ones((A.shape[0],)) >>> (x,flag) = gmres(A,b, maxiter=2, tol=1e-8, orthog='mgs') >>> print norm(b - A*x) >>> 6.5428213057 References ---------- .. [1] Yousef Saad, "Iterative Methods for Sparse Linear Systems, Second Edition", SIAM, pp. 151-172, pp. 272-275, 2003 http://www-users.cs.umn.edu/~saad/books.html .. [2] C. T. Kelley, http://www4.ncsu.edu/~ctk/matlab_roots.html ''' # Convert inputs to linear system, with error checking A, M, x, b, postprocess = make_system(A, M, x0, b) dimen = A.shape[0] # Ensure that warnings are always reissued from this function import warnings warnings.filterwarnings('always', module='pyamg\.krylov\._gmres_mgs') # Choose type if not hasattr(A, 'dtype'): Atype = upcast(x.dtype, b.dtype) else: Atype = A.dtype if not hasattr(M, 'dtype'): Mtype = upcast(x.dtype, b.dtype) else: Mtype = M.dtype xtype = upcast(Atype, x.dtype, b.dtype, Mtype) if restrt is not None: restrt = int(restrt) if maxiter is not None: maxiter = int(maxiter) # Get fast access to underlying BLAS routines # dotc is the conjugate dot, dotu does no conjugation [lartg] = get_lapack_funcs(['lartg'], [x] ) if iscomplexobj(zeros((1,), dtype=xtype)): [axpy, dotu, dotc, scal] =\ get_blas_funcs(['axpy', 'dotu', 'dotc', 'scal'], [x]) else: # real type [axpy, dotu, dotc, scal] =\ get_blas_funcs(['axpy', 'dot', 'dot', 'scal'], [x]) # Make full use of direct access to BLAS by defining own norm def norm(z): return sqrt(real(dotc(z, z))) # Should norm(r) be kept if residuals == []: keep_r = True else: keep_r = False # Set number of outer and inner iterations if restrt: if maxiter: max_outer = maxiter else: max_outer = 1 if restrt > dimen: warn('Setting number of inner iterations (restrt) to maximum\ allowed, which is A.shape[0] ') restrt = dimen max_inner = restrt else: max_outer = 1 if maxiter > dimen: warn('Setting number of inner iterations (maxiter) to maximum\ allowed, which is A.shape[0] ') maxiter = dimen elif maxiter is None: maxiter = min(dimen, 40) max_inner = maxiter # Is this a one dimensional matrix? if dimen == 1: entry = ravel(A*array([1.0], dtype=xtype)) return (postprocess(b/entry), 0) # Prep for method r = b - ravel(A*x) # Apply preconditioner r = ravel(M*r) normr = norm(r) if keep_r: residuals.append(normr) # Check for nan, inf # if isnan(r).any() or isinf(r).any(): # warn('inf or nan after application of preconditioner') # return(postprocess(x), -1) # Check initial guess ( scaling by b, if b != 0, # must account for case when norm(b) is very small) normb = norm(b) if normb == 0.0: normb = 1.0 if normr < tol*normb: if callback is not None: callback(norm(r)) return (postprocess(x), 0) # Scale tol by ||r_0||_2, we use the preconditioned residual # because this is left preconditioned GMRES. if normr != 0.0: tol = tol*normr # Use separate variable to track iterations. If convergence fails, we # cannot simply report niter = (outer-1)*max_outer + inner. Numerical # error could cause the inner loop to halt while the actual ||r|| > tol. niter = 0 # Begin GMRES for outer in range(max_outer): # Preallocate for Givens Rotations, Hessenberg matrix and Krylov Space # Space required is O(dimen*max_inner). # NOTE: We are dealing with row-major matrices, so we traverse in a # row-major fashion, # i.e., H and V's transpose is what we store. Q = [] # Givens Rotations # Upper Hessenberg matrix, which is then # converted to upper tri with Givens Rots H = zeros((max_inner+1, max_inner+1), dtype=xtype) V = zeros((max_inner+1, dimen), dtype=xtype) # Krylov Space # vs store the pointers to each column of V. # This saves a considerable amount of time. vs = [] # v = r/normr V[0, :] = scal(1.0/normr, r) vs.append(V[0, :]) # This is the RHS vector for the problem in the Krylov Space g = zeros((dimen,), dtype=xtype) g[0] = normr for inner in range(max_inner): # New Search Direction v = V[inner+1, :] v[:] = ravel(M*(A*vs[-1])) vs.append(v) normv_old = norm(v) # Check for nan, inf # if isnan(V[inner+1, :]).any() or isinf(V[inner+1, :]).any(): # warn('inf or nan after application of preconditioner') # return(postprocess(x), -1) # Modified Gram Schmidt for k in range(inner+1): vk = vs[k] alpha = dotc(vk, v) H[inner, k] = alpha v[:] = axpy(vk, v, dimen, -alpha) normv = norm(v) H[inner, inner+1] = normv # Re-orthogonalize if (reorth is True) and (normv_old == normv_old + 0.001*normv): for k in range(inner+1): vk = vs[k] alpha = dotc(vk, v) H[inner, k] = H[inner, k] + alpha v[:] = axpy(vk, v, dimen, -alpha) # Check for breakdown if H[inner, inner+1] != 0.0: v[:] = scal(1.0/H[inner, inner+1], v) # Apply previous Givens rotations to H if inner > 0: apply_givens(Q, H[inner, :], inner) # Calculate and apply next complex-valued Givens Rotation # ==> Note that if max_inner = dimen, then this is unnecessary # for the last inner # iteration, when inner = dimen-1. if inner != dimen-1: if H[inner, inner+1] != 0: [c, s, r] = lartg(H[inner, inner], H[inner, inner+1]) Qblock = array([[c, s], [-conjugate(s), c]], dtype=xtype) Q.append(Qblock) # Apply Givens Rotation to g, # the RHS for the linear system in the Krylov Subspace. g[inner:inner+2] = sp.dot(Qblock, g[inner:inner+2]) # Apply effect of Givens Rotation to H H[inner, inner] = dotu(Qblock[0, :], H[inner, inner:inner+2]) H[inner, inner+1] = 0.0 niter += 1 # Don't update normr if last inner iteration, because # normr is calculated directly after this loop ends. if inner < max_inner-1: normr = abs(g[inner+1]) if normr < tol: break # Allow user access to residual if callback is not None: callback(normr) if keep_r: residuals.append(normr) # end inner loop, back to outer loop # Find best update to x in Krylov Space V. Solve inner x inner system. y = sp.linalg.solve(H[0:inner+1, 0:inner+1].T, g[0:inner+1]) update = ravel(sp.mat(V[:inner+1, :]).T*y.reshape(-1, 1)) x = x + update r = b - ravel(A*x) # Apply preconditioner r = ravel(M*r) normr = norm(r) # Check for nan, inf # if isnan(r).any() or isinf(r).any(): # warn('inf or nan after application of preconditioner') # return(postprocess(x), -1) # Allow user access to residual if callback is not None: callback(normr) if keep_r: residuals.append(normr) # Has GMRES stagnated? indices = (x != 0) if indices.any(): change = max(abs(update[indices] / x[indices])) if change < 1e-12: # No change, halt return (postprocess(x), -1) # test for convergence if normr < tol: return (postprocess(x), 0) # end outer loop return (postprocess(x), niter)
def cr(A, b, x0=None, tol=1e-5, maxiter=None, xtype=None, M=None, callback=None, residuals=None): '''Conjugate Residual algorithm Solves the linear system Ax = b. Left preconditioning is supported. The matrix A must be Hermitian symmetric (but not necessarily definite). Parameters ---------- A : {array, matrix, sparse matrix, LinearOperator} n x n, linear system to solve b : {array, matrix} right hand side, shape is (n,) or (n,1) x0 : {array, matrix} initial guess, default is a vector of zeros tol : float relative convergence tolerance, i.e. tol is scaled by the preconditioner norm of r_0, or ||r_0||_M. maxiter : int maximum number of allowed iterations xtype : type dtype for the solution, default is automatic type detection M : {array, matrix, sparse matrix, LinearOperator} n x n, inverted preconditioner, i.e. solve M A x = M b. callback : function User-supplied function is called after each iteration as callback(xk), where xk is the current solution vector residuals : list residuals contains the residual norm history, including the initial residual. The preconditioner norm is used, instead of the Euclidean norm. Returns ------- (xNew, info) xNew : an updated guess to the solution of Ax = b info : halting status of cr == ======================================= 0 successful exit >0 convergence to tolerance not achieved, return iteration count instead. <0 numerical breakdown, or illegal input == ======================================= Notes ----- The LinearOperator class is in scipy.sparse.linalg.interface. Use this class if you prefer to define A or M as a mat-vec routine as opposed to explicitly constructing the matrix. A.psolve(..) is still supported as a legacy. The 2-norm of the preconditioned residual is used both for halting and returned in the residuals list. Examples -------- >>> from pyamg.krylov.cr import cr >>> from pyamg.util.linalg import norm >>> import numpy as np >>> from pyamg.gallery import poisson >>> A = poisson((10,10)) >>> b = np.ones((A.shape[0],)) >>> (x,flag) = cr(A,b, maxiter=2, tol=1e-8) >>> print norm(b - A*x) 10.9370700187 References ---------- .. [1] Yousef Saad, "Iterative Methods for Sparse Linear Systems, Second Edition", SIAM, pp. 262-67, 2003 http://www-users.cs.umn.edu/~saad/books.html ''' A, M, x, b, postprocess = make_system(A, M, x0, b, xtype=None) # n = len(b) # Ensure that warnings are always reissued from this function import warnings warnings.filterwarnings('always', module='pyamg\.krylov\._cr') # determine maxiter if maxiter is None: maxiter = int(1.3*len(b)) + 2 elif maxiter < 1: raise ValueError('Number of iterations must be positive') # choose tolerance for numerically zero values # t = A.dtype.char # eps = np.finfo(np.float).eps # feps = np.finfo(np.single).eps # geps = np.finfo(np.longfloat).eps # _array_precision = {'f': 0, 'd': 1, 'g': 2, 'F': 0, 'D': 1, 'G': 2} # numerically_zero = {0: feps*1e3, 1: eps*1e6, # 2: geps*1e6}[_array_precision[t]] # setup method r = b - A*x z = M*r p = z.copy() zz = inner(z.conjugate(), z) # use preconditioner norm normr = sqrt(zz) if residuals is not None: residuals[:] = [normr] # initial residual # Check initial guess ( scaling by b, if b != 0, # must account for case when norm(b) is very small) normb = norm(b) if normb == 0.0: normb = 1.0 if normr < tol*normb: return (postprocess(x), 0) # Scale tol by ||r_0||_M if normr != 0.0: tol = tol*normr # How often should r be recomputed recompute_r = 8 iter = 0 Az = A*z rAz = inner(r.conjugate(), Az) Ap = A*p while True: rAz_old = rAz alpha = rAz / inner(Ap.conjugate(), Ap) # 3 x += alpha * p # 4 if mod(iter, recompute_r) and iter > 0: # 5 r -= alpha * Ap else: r = b - A*x z = M*r Az = A*z rAz = inner(r.conjugate(), Az) beta = rAz/rAz_old # 6 p *= beta # 7 p += z Ap *= beta # 8 Ap += Az iter += 1 zz = inner(z.conjugate(), z) normr = sqrt(zz) # use preconditioner norm if residuals is not None: residuals.append(normr) if callback is not None: callback(x) if normr < tol: return (postprocess(x), 0) elif zz == 0.0: # important to test after testing normr < tol. rz == 0.0 is an # indicator of convergence when r = 0.0 warn("\nSingular preconditioner detected in CR, ceasing \ iterations\n") return (postprocess(x), -1) if iter == maxiter: return (postprocess(x), iter)
def solve(self, b, x0=None, tol=1e-5, maxiter=100, cycle='V', accel=None, callback=None, residuals=None, return_residuals=False): """Execute multigrid cycling. Parameters ---------- b : array Right hand side. x0 : array Initial guess. tol : float Stopping criteria: relative residual r[k]/r[0] tolerance. maxiter : int Stopping criteria: maximum number of allowable iterations. cycle : {'V','W','F','AMLI'} Type of multigrid cycle to perform in each iteration. accel : string, function Defines acceleration method. Can be a string such as 'cg' or 'gmres' which is the name of an iterative solver in pyamg.krylov (preferred) or scipy.sparse.linalg.isolve. If accel is not a string, it will be treated like a function with the same interface provided by the iterative solvers in SciPy. callback : function User-defined function called after each iteration. It is called as callback(xk) where xk is the k-th iterate vector. residuals : list List to contain residual norms at each iteration. Returns ------- x : array Approximate solution to Ax=b See Also -------- aspreconditioner Examples -------- >>> from numpy import ones >>> from pyamg import ruge_stuben_solver >>> from pyamg.gallery import poisson >>> A = poisson((100, 100), format='csr') >>> b = A * ones(A.shape[0]) >>> ml = ruge_stuben_solver(A, max_coarse=10) >>> residuals = [] >>> x = ml.solve(b, tol=1e-12, residuals=residuals) # standalone solver """ from pyamg.util.linalg import residual_norm, norm if x0 is None: x = np.zeros_like(b) else: x = np.array(x0) # copy cycle = str(cycle).upper() # AMLI cycles require hermitian matrix if (cycle == 'AMLI') and hasattr(self.levels[0].A, 'symmetry'): if self.levels[0].A.symmetry != 'hermitian': raise ValueError('AMLI cycles require \ symmetry to be hermitian') if accel is not None: # Check for symmetric smoothing scheme when using CG if (accel == 'cg') and (not self.symmetric_smoothing): warn('Incompatible non-symmetric multigrid preconditioner ' 'detected, due to presmoother/postsmoother combination. ' 'CG requires SPD preconditioner, not just SPD matrix.') # Check for AMLI compatability if (accel != 'fgmres') and (cycle == 'AMLI'): raise ValueError('AMLI cycles require acceleration (accel) ' 'to be fgmres, or no acceleration') # py23 compatibility: try: basestring except NameError: basestring = str # Acceleration is being used kwargs = {} if isinstance(accel, basestring): from pyamg import krylov from scipy.sparse.linalg import isolve kwargs = {} if hasattr(krylov, accel): accel = getattr(krylov, accel) else: accel = getattr(isolve, accel) kwargs['atol'] = 'legacy' A = self.levels[0].A M = self.aspreconditioner(cycle=cycle) try: # try PyAMG style interface which has a residuals parameter return accel(A, b, x0=x0, tol=tol, maxiter=maxiter, M=M, callback=callback, residuals=residuals, **kwargs)[0] except BaseException: # try the scipy.sparse.linalg.isolve style interface, # which requires a call back function if a residual # history is desired cb = callback if residuals is not None: residuals[:] = [residual_norm(A, x, b)] def callback(x): if np.isscalar(x): residuals.append(x) else: residuals.append(residual_norm(A, x, b)) if cb is not None: cb(x) return accel(A, b, x0=x0, tol=tol, maxiter=maxiter, M=M, callback=callback, **kwargs)[0] else: # Scale tol by normb # Don't scale tol earlier. The accel routine should also scale tol normb = norm(b) if normb != 0: tol = tol * normb if return_residuals: warn('return_residuals is deprecated. Use residuals instead') residuals = [] if residuals is None: residuals = [] else: residuals[:] = [] # Create uniform types for A, x and b # Clearly, this logic doesn't handle the case of real A and complex b from scipy.sparse.sputils import upcast from pyamg.util.utils import to_type tp = upcast(b.dtype, x.dtype, self.levels[0].A.dtype) [b, x] = to_type(tp, [b, x]) b = np.ravel(b) x = np.ravel(x) A = self.levels[0].A residuals.append(residual_norm(A, x, b)) self.first_pass = True while len(residuals) <= maxiter and residuals[-1] > tol: if len(self.levels) == 1: # hierarchy has only 1 level x = self.coarse_solver(A, b) else: self.__solve(0, x, b, cycle) residuals.append(residual_norm(A, x, b)) self.first_pass = False if callback is not None: callback(x) if return_residuals: return x, residuals else: return x
def cgne(A, b, x0=None, tol=1e-5, maxiter=None, xtype=None, M=None, callback=None, residuals=None): '''Conjugate Gradient, Normal Error algorithm Applies CG to the normal equations, A.H A x = b. Left preconditioning is supported. Note that unless A is well-conditioned, the use of CGNE is inadvisable Parameters ---------- A : {array, matrix, sparse matrix, LinearOperator} n x n, linear system to solve b : {array, matrix} right hand side, shape is (n,) or (n,1) x0 : {array, matrix} initial guess, default is a vector of zeros tol : float relative convergence tolerance, i.e. tol is scaled by ||r_0||_2 maxiter : int maximum number of allowed iterations xtype : type dtype for the solution, default is automatic type detection M : {array, matrix, sparse matrix, LinearOperator} n x n, inverted preconditioner, i.e. solve M A A.H x = M b. callback : function User-supplied function is called after each iteration as callback(xk), where xk is the current solution vector residuals : list residuals has the residual norm history, including the initial residual, appended to it Returns ------- (xNew, info) xNew : an updated guess to the solution of Ax = b info : halting status of cgne == ======================================= 0 successful exit >0 convergence to tolerance not achieved, return iteration count instead. <0 numerical breakdown, or illegal input == ======================================= Notes ----- - The LinearOperator class is in scipy.sparse.linalg.interface. Use this class if you prefer to define A or M as a mat-vec routine as opposed to explicitly constructing the matrix. A.psolve(..) is still supported as a legacy. Examples -------- >>> from pyamg.krylov.cgne import cgne >>> from pyamg.util.linalg import norm >>> import numpy as np >>> from pyamg.gallery import poisson >>> A = poisson((10,10)) >>> b = np.ones((A.shape[0],)) >>> (x,flag) = cgne(A,b, maxiter=2, tol=1e-8) >>> print norm(b - A*x) 46.1547104367 References ---------- .. [1] Yousef Saad, "Iterative Methods for Sparse Linear Systems, Second Edition", SIAM, pp. 276-7, 2003 http://www-users.cs.umn.edu/~saad/books.html ''' # Store the conjugate transpose explicitly as it will be used much later on if isspmatrix(A): AH = A.H else: # TODO avoid doing this since A may be a different sparse type AH = aslinearoperator(np.asmatrix(A).H) # Convert inputs to linear system, with error checking A, M, x, b, postprocess = make_system(A, M, x0, b, xtype) dimen = A.shape[0] # Ensure that warnings are always reissued from this function import warnings warnings.filterwarnings('always', module='pyamg\.krylov\._cgne') # Choose type if not hasattr(A, 'dtype'): Atype = upcast(x.dtype, b.dtype) else: Atype = A.dtype if not hasattr(M, 'dtype'): Mtype = upcast(x.dtype, b.dtype) else: Mtype = M.dtype xtype = upcast(Atype, x.dtype, b.dtype, Mtype) # Should norm(r) be kept if residuals == []: keep_r = True else: keep_r = False # How often should r be recomputed recompute_r = 8 # Check iteration numbers. CGNE suffers from loss of orthogonality quite # easily, so we arbitrarily let the method go up to 130% over the # theoretically necessary limit of maxiter=dimen if maxiter is None: maxiter = int(np.ceil(1.3 * dimen)) + 2 elif maxiter < 1: raise ValueError('Number of iterations must be positive') elif maxiter > (1.3 * dimen): warn('maximum allowed inner iterations (maxiter) are the 130% times \ the number of dofs') maxiter = int(np.ceil(1.3 * dimen)) + 2 # Prep for method r = b - A * x normr = norm(r) if keep_r: residuals.append(normr) # Check initial guess ( scaling by b, if b != 0, # must account for case when norm(b) is very small) normb = norm(b) if normb == 0.0: normb = 1.0 if normr < tol * normb: if callback is not None: callback(x) return (postprocess(x), 0) # Scale tol by ||r_0||_2 if normr != 0.0: tol = tol * normr # Begin CGNE # Apply preconditioner and calculate initial search direction z = M * r p = AH * z old_zr = np.inner(z.conjugate(), r) for iter in range(maxiter): # alpha = (z_j, r_j) / (p_j, p_j) alpha = old_zr / np.inner(p.conjugate(), p) # x_{j+1} = x_j + alpha*p_j x += alpha * p # r_{j+1} = r_j - alpha*w_j, where w_j = A*p_j if np.mod(iter, recompute_r) and iter > 0: r -= alpha * (A * p) else: r = b - A * x # z_{j+1} = M*r_{j+1} z = M * r # beta = (z_{j+1}, r_{j+1}) / (z_j, r_j) new_zr = np.inner(z.conjugate(), r) beta = new_zr / old_zr old_zr = new_zr # p_{j+1} = A.H*z_{j+1} + beta*p_j p *= beta p += AH * z # Allow user access to residual if callback is not None: callback(x) # test for convergence normr = norm(r) if keep_r: residuals.append(normr) if normr < tol: return (postprocess(x), 0) # end loop return (postprocess(x), iter + 1)
def cgne(A, b, x0=None, tol=1e-5, criteria='rr', maxiter=None, M=None, callback=None, residuals=None): """Conjugate Gradient, Normal Error algorithm. Applies CG to the normal equations, A A.H x = b. Left preconditioning is supported. Note that unless A is well-conditioned, the use of CGNE is inadvisable Parameters ---------- A : array, matrix, sparse matrix, LinearOperator n x n, linear system to solve b : array, matrix right hand side, shape is (n,) or (n,1) x0 : array, matrix initial guess, default is a vector of zeros tol : float Tolerance for stopping criteria criteria : string Stopping criteria, let r=r_k, x=x_k 'rr': ||r|| < tol ||b|| 'rr+': ||r|| < tol (||b|| + ||A||_F ||x||) 'MrMr': ||M r|| < tol ||M b|| 'rMr': <r, Mr>^1/2 < tol if ||b||=0, then set ||b||=1 for these tests. maxiter : int maximum number of iterations allowed M : array, matrix, sparse matrix, LinearOperator n x n, inverted preconditioner, i.e. solve M A A.H x = M b. callback : function User-supplied function is called after each iteration as callback(xk), where xk is the current solution vector residuals : list residual history in the 2-norm, including the initial residual Returns ------- (xk, info) xk : an updated guess after k iterations to the solution of Ax = b info : halting status == ======================================= 0 successful exit >0 convergence to tolerance not achieved, return iteration count instead. <0 numerical breakdown, or illegal input == ======================================= Notes ----- The LinearOperator class is in scipy.sparse.linalg.interface. Use this class if you prefer to define A or M as a mat-vec routine as opposed to explicitly constructing the matrix. Examples -------- >>> from pyamg.krylov import cgne >>> from pyamg.util.linalg import norm >>> import numpy as np >>> from pyamg.gallery import poisson >>> A = poisson((10,10)) >>> b = np.ones((A.shape[0],)) >>> (x,flag) = cgne(A,b, maxiter=2, tol=1e-8) >>> print(f'{norm(b - A*x):.6}') 46.1547 References ---------- .. [1] Yousef Saad, "Iterative Methods for Sparse Linear Systems, Second Edition", SIAM, pp. 276-7, 2003 http://www-users.cs.umn.edu/~saad/books.html """ # Store the conjugate transpose explicitly as it will be used much later on if sparse.isspmatrix(A): AH = A.H else: # avoid doing this since A may be a different sparse type AH = aslinearoperator(np.asarray(A).conj().T) # Convert inputs to linear system, with error checking A, M, x, b, postprocess = make_system(A, M, x0, b) n = A.shape[0] # Ensure that warnings are always reissued from this function warnings.filterwarnings('always', module='pyamg.krylov._cgne') # How often should r be recomputed recompute_r = 8 # Check iteration numbers. CGNE suffers from loss of orthogonality quite # easily, so we arbitrarily let the method go up to 130% over the # theoretically necessary limit of maxiter=n if maxiter is None: maxiter = int(np.ceil(1.3 * n)) + 2 elif maxiter < 1: raise ValueError('Number of iterations must be positive') elif maxiter > (1.3 * n): warn('maximum allowed inner iterations (maxiter) are the 130% times' 'the number of dofs') maxiter = int(np.ceil(1.3 * n)) + 2 # Prep for method r = b - A @ x normr = norm(r) # Apply preconditioner and calculate initial search direction z = M @ r p = AH @ z old_zr = np.inner(z.conjugate(), r) if residuals is not None: residuals[:] = [normr] # initial residual # Check initial guess if b != 0, normb = norm(b) if normb == 0.0: normb = 1.0 # reset so that tol is unscaled # set the stopping criteria (see the docstring) if criteria == 'rr': rtol = tol * normb elif criteria == 'rr+': if sparse.issparse(A.A): normA = norm(A.A.data) elif isinstance(A.A, np.ndarray): normA = norm(np.ravel(A.A)) else: raise ValueError( 'Unable to use ||A||_F with the current matrix format.') rtol = tol * (normA * np.linalg.norm(x) + normb) elif criteria == 'MrMr': normr = norm(z) normMb = norm(M @ b) rtol = tol * normMb elif criteria == 'rMr': normr = np.sqrt(old_zr) rtol = tol else: raise ValueError('Invalid stopping criteria.') if normr < rtol: return (postprocess(x), 0) # Begin CGNE it = 0 while True: # Step number in Saad's pseudocode # alpha = (z_j, r_j) / (p_j, p_j) alpha = old_zr / np.inner(p.conjugate(), p) # x_{j+1} = x_j + alpha*p_j x += alpha * p # r_{j+1} = r_j - alpha*w_j, where w_j = A*p_j if np.mod(it, recompute_r) and it > 0: r -= alpha * (A @ p) else: r = b - A @ x # z_{j+1} = M*r_{j+1} z = M @ r # beta = (z_{j+1}, r_{j+1}) / (z_j, r_j) new_zr = np.inner(z.conjugate(), r) beta = new_zr / old_zr old_zr = new_zr # p_{j+1} = A.H*z_{j+1} + beta*p_j p *= beta p += AH @ z it += 1 normr = np.linalg.norm(r) if residuals is not None: residuals.append(normr) if callback is not None: callback(x) # set the stopping criteria (see the docstring) if criteria == 'rr': rtol = tol * normb elif criteria == 'rr+': rtol = tol * (normA * np.linalg.norm(x) + normb) elif criteria == 'MrMr': normr = norm(z) rtol = tol * normMb elif criteria == 'rMr': normr = np.sqrt(new_zr) rtol = tol if normr < rtol: return (postprocess(x), 0) if it == maxiter: return (postprocess(x), it)
def gmres_mgs(A, b, x0=None, tol=1e-5, restrt=None, maxiter=None, xtype=None, M=None, callback=None, residuals=None, reorth=False): ''' Generalized Minimum Residual Method (GMRES) GMRES iteratively refines the initial solution guess to the system Ax = b Modified Gram-Schmidt version Parameters ---------- A : {array, matrix, sparse matrix, LinearOperator} n x n, linear system to solve b : {array, matrix} right hand side, shape is (n,) or (n,1) x0 : {array, matrix} initial guess, default is a vector of zeros tol : float relative convergence tolerance, i.e. tol is scaled by the norm of the initial preconditioned residual restrt : {None, int} - if int, restrt is max number of inner iterations and maxiter is the max number of outer iterations - if None, do not restart GMRES, and max number of inner iterations is maxiter maxiter : {None, int} - if restrt is None, maxiter is the max number of inner iterations and GMRES does not restart - if restrt is int, maxiter is the max number of outer iterations, and restrt is the max number of inner iterations xtype : type dtype for the solution, default is automatic type detection M : {array, matrix, sparse matrix, LinearOperator} n x n, inverted preconditioner, i.e. solve M A x = M b. callback : function User-supplied function is called after each iteration as callback(xk), where xk is the current solution vector residuals : list residuals contains the preconditioned residual norm history, including the initial residual. reorth : boolean If True, then a check is made whether to re-orthogonalize the Krylov space each GMRES iteration Returns ------- (xNew, info) xNew : an updated guess to the solution of Ax = b info : halting status of gmres == ============================================= 0 successful exit >0 convergence to tolerance not achieved, return iteration count instead. This value is precisely the order of the Krylov space. <0 numerical breakdown, or illegal input == ============================================= Notes ----- - The LinearOperator class is in scipy.sparse.linalg.interface. Use this class if you prefer to define A or M as a mat-vec routine as opposed to explicitly constructing the matrix. A.psolve(..) is still supported as a legacy. - For robustness, modified Gram-Schmidt is used to orthogonalize the Krylov Space Givens Rotations are used to provide the residual norm each iteration Examples -------- >>> from pyamg.krylov import gmres >>> from pyamg.util.linalg import norm >>> import numpy as np >>> from pyamg.gallery import poisson >>> A = poisson((10,10)) >>> b = np.ones((A.shape[0],)) >>> (x,flag) = gmres(A,b, maxiter=2, tol=1e-8, orthog='mgs') >>> print norm(b - A*x) >>> 6.5428213057 References ---------- .. [1] Yousef Saad, "Iterative Methods for Sparse Linear Systems, Second Edition", SIAM, pp. 151-172, pp. 272-275, 2003 http://www-users.cs.umn.edu/~saad/books.html .. [2] C. T. Kelley, http://www4.ncsu.edu/~ctk/matlab_roots.html ''' # Convert inputs to linear system, with error checking A, M, x, b, postprocess = make_system(A, M, x0, b, xtype) dimen = A.shape[0] # Ensure that warnings are always reissued from this function import warnings warnings.filterwarnings('always', module='pyamg\.krylov\._gmres_mgs') # Choose type if not hasattr(A, 'dtype'): Atype = upcast(x.dtype, b.dtype) else: Atype = A.dtype if not hasattr(M, 'dtype'): Mtype = upcast(x.dtype, b.dtype) else: Mtype = M.dtype xtype = upcast(Atype, x.dtype, b.dtype, Mtype) if restrt is not None: restrt = int(restrt) if maxiter is not None: maxiter = int(maxiter) # Get fast access to underlying BLAS routines # dotc is the conjugate dot, dotu does no conjugation [lartg] = get_lapack_funcs(['lartg'], [x]) if np.iscomplexobj(np.zeros((1, ), dtype=xtype)): [axpy, dotu, dotc, scal] =\ get_blas_funcs(['axpy', 'dotu', 'dotc', 'scal'], [x]) else: # real type [axpy, dotu, dotc, scal] =\ get_blas_funcs(['axpy', 'dot', 'dot', 'scal'], [x]) # Make full use of direct access to BLAS by defining own norm def norm(z): return np.sqrt(np.real(dotc(z, z))) # Should norm(r) be kept if residuals == []: keep_r = True else: keep_r = False # Set number of outer and inner iterations if restrt: if maxiter: max_outer = maxiter else: max_outer = 1 if restrt > dimen: warn('Setting number of inner iterations (restrt) to maximum\ allowed, which is A.shape[0] ') restrt = dimen max_inner = restrt else: max_outer = 1 if maxiter > dimen: warn('Setting number of inner iterations (maxiter) to maximum\ allowed, which is A.shape[0] ') maxiter = dimen elif maxiter is None: maxiter = min(dimen, 40) max_inner = maxiter # Is this a one dimensional matrix? if dimen == 1: entry = np.ravel(A * np.array([1.0], dtype=xtype)) return (postprocess(b / entry), 0) # Prep for method r = b - np.ravel(A * x) # Apply preconditioner r = np.ravel(M * r) normr = norm(r) if keep_r: residuals.append(normr) # Check for nan, inf # if isnan(r).any() or isinf(r).any(): # warn('inf or nan after application of preconditioner') # return(postprocess(x), -1) # Check initial guess ( scaling by b, if b != 0, # must account for case when norm(b) is very small) normb = norm(b) if normb == 0.0: normb = 1.0 if normr < tol * normb: return (postprocess(x), 0) # Scale tol by ||r_0||_2, we use the preconditioned residual # because this is left preconditioned GMRES. if normr != 0.0: tol = tol * normr # Use separate variable to track iterations. If convergence fails, we # cannot simply report niter = (outer-1)*max_outer + inner. Numerical # error could cause the inner loop to halt while the actual ||r|| > tol. niter = 0 # Begin GMRES for outer in range(max_outer): # Preallocate for Givens Rotations, Hessenberg matrix and Krylov Space # Space required is O(dimen*max_inner). # NOTE: We are dealing with row-major matrices, so we traverse in a # row-major fashion, # i.e., H and V's transpose is what we store. Q = [] # Givens Rotations # Upper Hessenberg matrix, which is then # converted to upper tri with Givens Rots H = np.zeros((max_inner + 1, max_inner + 1), dtype=xtype) V = np.zeros((max_inner + 1, dimen), dtype=xtype) # Krylov Space # vs store the pointers to each column of V. # This saves a considerable amount of time. vs = [] # v = r/normr V[0, :] = scal(1.0 / normr, r) vs.append(V[0, :]) # This is the RHS vector for the problem in the Krylov Space g = np.zeros((dimen, ), dtype=xtype) g[0] = normr for inner in range(max_inner): # New Search Direction v = V[inner + 1, :] v[:] = np.ravel(M * (A * vs[-1])) vs.append(v) normv_old = norm(v) # Check for nan, inf # if isnan(V[inner+1, :]).any() or isinf(V[inner+1, :]).any(): # warn('inf or nan after application of preconditioner') # return(postprocess(x), -1) # Modified Gram Schmidt for k in range(inner + 1): vk = vs[k] alpha = dotc(vk, v) H[inner, k] = alpha v[:] = axpy(vk, v, dimen, -alpha) normv = norm(v) H[inner, inner + 1] = normv # Re-orthogonalize if (reorth is True) and (normv_old == normv_old + 0.001 * normv): for k in range(inner + 1): vk = vs[k] alpha = dotc(vk, v) H[inner, k] = H[inner, k] + alpha v[:] = axpy(vk, v, dimen, -alpha) # Check for breakdown if H[inner, inner + 1] != 0.0: v[:] = scal(1.0 / H[inner, inner + 1], v) # Apply previous Givens rotations to H if inner > 0: apply_givens(Q, H[inner, :], inner) # Calculate and apply next complex-valued Givens Rotation # ==> Note that if max_inner = dimen, then this is unnecessary # for the last inner # iteration, when inner = dimen-1. if inner != dimen - 1: if H[inner, inner + 1] != 0: [c, s, r] = lartg(H[inner, inner], H[inner, inner + 1]) Qblock = np.array([[c, s], [-np.conjugate(s), c]], dtype=xtype) Q.append(Qblock) # Apply Givens Rotation to g, # the RHS for the linear system in the Krylov Subspace. g[inner:inner + 2] = np.dot(Qblock, g[inner:inner + 2]) # Apply effect of Givens Rotation to H H[inner, inner] = dotu(Qblock[0, :], H[inner, inner:inner + 2]) H[inner, inner + 1] = 0.0 niter += 1 # Don't update normr if last inner iteration, because # normr is calculated directly after this loop ends. if inner < max_inner - 1: normr = np.abs(g[inner + 1]) if normr < tol: break # Allow user access to the iterates if callback is not None: callback(x) if keep_r: residuals.append(normr) # end inner loop, back to outer loop # Find best update to x in Krylov Space V. Solve inner x inner system. y = sp.linalg.solve(H[0:inner + 1, 0:inner + 1].T, g[0:inner + 1]) update = np.ravel(np.mat(V[:inner + 1, :]).T * y.reshape(-1, 1)) x = x + update r = b - np.ravel(A * x) # Apply preconditioner r = np.ravel(M * r) normr = norm(r) # Check for nan, inf # if isnan(r).any() or isinf(r).any(): # warn('inf or nan after application of preconditioner') # return(postprocess(x), -1) # Allow user access to the iterates if callback is not None: callback(x) if keep_r: residuals.append(normr) # Has GMRES stagnated? indices = (x != 0) if indices.any(): change = np.max(np.abs(update[indices] / x[indices])) if change < 1e-12: # No change, halt return (postprocess(x), -1) # test for convergence if normr < tol: return (postprocess(x), 0) # end outer loop return (postprocess(x), niter)
def test_krylov(self): # Oblique projectors reduce the residual for method in self.oblique: for case in self.cases: A = case["A"] b = case["b"] x0 = case["x0"] (xNew, flag) = method(A, b, x0=x0, tol=case["tol"], maxiter=case["maxiter"]) xNew = xNew.reshape(-1, 1) assert_equal( (norm(b - A * xNew) / norm(b - A * x0)) < case["reduction_factor"], True, err_msg="Oblique Krylov Method Failed Test", ) # Oblique projectors reduce the residual, here we consider oblique # projectors for symmetric matrices for method in self.symm_oblique: for case in self.symm_cases: A = case["A"] b = case["b"] x0 = case["x0"] (xNew, flag) = method(A, b, x0=x0, tol=case["tol"], maxiter=case["maxiter"]) xNew = xNew.reshape(-1, 1) assert_equal( (norm(b - A * xNew) / norm(b - A * x0)) < case["reduction_factor"], True, err_msg="Symmetric oblique Krylov Method Failed", ) # Orthogonal projectors reduce the error for method in self.orth: for case in self.cases: A = case["A"] b = case["b"] x0 = case["x0"] (xNew, flag) = method(A, b, x0=x0, tol=case["tol"], maxiter=case["maxiter"]) xNew = xNew.reshape(-1, 1) soln = solve(A, b) assert_equal( (norm(soln - xNew) / norm(soln - x0)) < case["reduction_factor"], True, err_msg="Orthogonal Krylov Method Failed Test", ) # SPD Orthogonal projectors reduce the error for method in self.spd_orth: for case in self.spd_cases: A = case["A"] b = case["b"] x0 = case["x0"] (xNew, flag) = method(A, b, x0=x0, tol=case["tol"], maxiter=case["maxiter"]) xNew = xNew.reshape(-1, 1) soln = solve(A, b) assert_equal( (norm(soln - xNew) / norm(soln - x0)) < case["reduction_factor"], True, err_msg="Orthogonal Krylov Method Failed Test", ) # Assume that Inexact Methods reduce the residual for these examples for method in self.inexact: for case in self.cases: A = case["A"] b = case["b"] x0 = case["x0"] (xNew, flag) = method(A, b, x0=x0, tol=case["tol"], maxiter=A.shape[0]) xNew = xNew.reshape(-1, 1) assert_equal( (norm(b - A * xNew) / norm(b - A * x0)) < 0.35, True, err_msg="Inexact Krylov Method Failed Test" )
def minimal_residual(A, b, x0=None, tol=1e-5, maxiter=None, M=None, callback=None, residuals=None): """Minimal residual (MR) algorithm. 1D projection method. Solves the linear system Ax = b. Left preconditioning is supported. Parameters ---------- A : array, matrix, sparse matrix, LinearOperator n x n, linear system to solve b : array, matrix right hand side, shape is (n,) or (n,1) x0 : array, matrix initial guess, default is a vector of zeros tol : float Tolerance for stopping criteria, let r=r_k ||M r|| < tol ||M b|| if ||b||=0, then set ||M b||=1 for these tests. maxiter : int maximum number of iterations allowed M : array, matrix, sparse matrix, LinearOperator n x n, inverted preconditioner, i.e. solve M A x = M b. callback : function User-supplied function is called after each iteration as callback(xk), where xk is the current solution vector residuals : list preconditioned residual history in the 2-norm, including the initial preconditioned residual Returns ------- (xk, info) xk : an updated guess after k iterations to the solution of Ax = b info : halting status == ======================================= 0 successful exit >0 convergence to tolerance not achieved, return iteration count instead. <0 numerical breakdown, or illegal input == ======================================= Notes ----- The LinearOperator class is in scipy.sparse.linalg.interface. Use this class if you prefer to define A or M as a mat-vec routine as opposed to explicitly constructing the matrix. minimal residual algorithm: Preconditioned version: r = b - A x r = b - A x, z = M r while not converged: while not converged: p = A r p = M A z alpha = (p,r) / (p,p) alpha = (p, z) / (p, p) x = x + alpha r x = x + alpha z r = r - alpha p z = z - alpha p See Also -------- _steepest_descent Examples -------- >>> from pyamg.krylov import minimal_residual >>> from pyamg.util.linalg import norm >>> import numpy as np >>> from pyamg.gallery import poisson >>> A = poisson((10,10)) >>> b = np.ones((A.shape[0],)) >>> (x,flag) = minimal_residual(A,b, maxiter=2, tol=1e-8) >>> print(f'{norm(b - A*x):.6}') 7.26369 References ---------- .. [1] Yousef Saad, "Iterative Methods for Sparse Linear Systems, Second Edition", SIAM, pp. 137--142, 2003 http://www-users.cs.umn.edu/~saad/books.html """ A, M, x, b, postprocess = make_system(A, M, x0, b) # Ensure that warnings are always reissued from this function warnings.filterwarnings('always', module='pyamg.krylov._minimal_residual') # determine maxiter if maxiter is None: maxiter = int(1.3 * len(b)) + 2 elif maxiter < 1: raise ValueError('Number of iterations must be positive') # setup method r = b - A @ x z = M @ r normr = norm(z) # store initial residual if residuals is not None: residuals[:] = [normr] # Check initial guess if b != 0, normb = norm(b) if normb == 0.0: normMb = 1.0 # reset so that tol is unscaled else: normMb = norm(M @ b) # set the stopping criteria (see the docstring) if normr < tol * normMb: return (postprocess(x), 0) # How often should r be recomputed recompute_r = 50 it = 0 while True: p = M @ (A @ z) # (p, z) = (M A M r, M r) = (M A z, z) pz = np.inner(p.conjugate(), z) # check curvature of M^-1 A if pz < 0.0: warn( '\nIndefinite matrix detected in minimal residual, stopping.\n' ) return (postprocess(x), -1) alpha = pz / np.inner(p.conjugate(), p) x = x + alpha * z it += 1 if np.mod(it, recompute_r) and it > 0: r = b - A @ x z = M @ r else: z = z - alpha * p normr = norm(z) if residuals is not None: residuals.append(normr) if callback is not None: callback(x) # set the stopping criteria (see the docstring) if normr < tol * normMb: return (postprocess(x), 0) if it == maxiter: return (postprocess(x), it)
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 adaptive_sa_solver(A, initial_candidates=None, symmetry='hermitian', pdef=True, num_candidates=1, candidate_iters=5, improvement_iters=0, epsilon=0.1, max_levels=10, max_coarse=10, aggregate='standard', prepostsmoother=('gauss_seidel', { 'sweep': 'symmetric' }), smooth=('jacobi', {}), strength='symmetric', coarse_solver='pinv2', eliminate_local=(False, { 'Ca': 1.0 }), keep=False, **kwargs): """ Create a multilevel solver using Adaptive Smoothed Aggregation (aSA) Parameters ---------- A : {csr_matrix, bsr_matrix} Square matrix in CSR or BSR format initial_candidates : {None, n x m dense matrix} If a matrix, then this forms the basis for the first m candidates. Also in this case, the initial setup stage is skipped, because this provides the first candidate(s). If None, then a random initial guess and relaxation are used to inform the initial candidate. symmetry : {string} 'symmetric' refers to both real and complex symmetric 'hermitian' refers to both complex Hermitian and real Hermitian Note that for the strictly real case, these two options are the same Note that this flag does not denote definiteness of the operator pdef : {bool} True or False, whether A is known to be positive definite. num_candidates : {integer} : default 1 Number of near-nullspace candidates to generate candidate_iters : {integer} : default 5 Number of smoothing passes/multigrid cycles used at each level of the adaptive setup phase improvement_iters : {integer} : default 0 Number of times each candidate is improved epsilon : {float} : default 0.1 Target convergence factor 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. prepostsmoother : {string or dict} Pre- and post-smoother used in the adaptive method strength : ['symmetric', 'classical', 'evolution', ('predefined', {'C': csr_matrix}), None] Method used to determine the strength of connection between unknowns of the linear system. See smoothed_aggregation_solver(...) documentation. aggregate : ['standard', 'lloyd', 'naive', ('predefined', {'AggOp': csr_matrix})] Method used to aggregate nodes. See smoothed_aggregation_solver(...) documentation. smooth : ['jacobi', 'richardson', 'energy', None] Method used used to smooth the tentative prolongator. See smoothed_aggregation_solver(...) documentation 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. eliminate_local : {tuple} Length 2 tuple. If the first entry is True, then eliminate candidates where they aren't needed locally, using the second entry of the tuple to contain arguments to local elimination routine. Given the rigid sparse data structures, this doesn't help much, if at all, with complexity. Its more of a diagnostic utility. 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. Returns ------- multilevel_solver : multilevel_solver Smoothed aggregation solver with adaptively generated candidates Notes ----- - Floating point value representing the "work" required to generate the solver. This value is the total cost of just relaxation, relative to the fine grid. The relaxation method used is assumed to symmetric Gauss-Seidel. - Unlike the standard Smoothed Aggregation (SA) method, adaptive SA does not require knowledge of near-nullspace candidate vectors. Instead, an adaptive procedure computes one or more candidates 'from scratch'. This approach is useful when no candidates are known or the candidates have been invalidated due to changes to matrix A. Examples -------- >>> from pyamg.gallery import stencil_grid >>> from pyamg.aggregation import adaptive_sa_solver >>> import numpy as np >>> A=stencil_grid([[-1,-1,-1],[-1,8.0,-1],[-1,-1,-1]],\ (31,31),format='csr') >>> [asa,work] = adaptive_sa_solver(A,num_candidates=1) >>> residuals=[] >>> x=asa.solve(b=np.ones((A.shape[0],)), x0=np.ones((A.shape[0],)),\ residuals=residuals) References ---------- .. [1] Brezina, Falgout, MacLachlan, Manteuffel, McCormick, and Ruge "Adaptive Smoothed Aggregation ($\alpha$SA) Multigrid" SIAM Review Volume 47, Issue 2 (2005) http://www.cs.umn.edu/~maclach/research/aSA2.pdf """ 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 A.shape[0] != A.shape[1]: raise ValueError('expected square matrix') # Track work in terms of relaxation work = np.zeros((1, )) # 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) smooth = levelize_smooth_or_improve_candidates(smooth, max_levels) # Develop initial candidate(s). Note that any predefined aggregation is # preserved. if initial_candidates is None: B, aggregate, strength =\ initial_setup_stage(A, symmetry, pdef, candidate_iters, epsilon, max_levels, max_coarse, aggregate, prepostsmoother, smooth, strength, work) # Normalize B B = (1.0 / norm(B, 'inf')) * B num_candidates -= 1 else: # Otherwise, use predefined candidates B = initial_candidates num_candidates -= B.shape[1] # Generate Aggregation and Strength Operators (the brute force way) sa = smoothed_aggregation_solver(A, B=B, symmetry=symmetry, presmoother=prepostsmoother, postsmoother=prepostsmoother, smooth=smooth, strength=strength, max_levels=max_levels, max_coarse=max_coarse, aggregate=aggregate, coarse_solver=coarse_solver, improve_candidates=None, keep=True, **kwargs) if len(sa.levels) > 1: # Set strength-of-connection and aggregation aggregate = [('predefined', { 'AggOp': sa.levels[i].AggOp.tocsr() }) for i in range(len(sa.levels) - 1)] strength = [('predefined', { 'C': sa.levels[i].C.tocsr() }) for i in range(len(sa.levels) - 1)] # Develop additional candidates for i in range(num_candidates): x = general_setup_stage( smoothed_aggregation_solver(A, B=B, symmetry=symmetry, presmoother=prepostsmoother, postsmoother=prepostsmoother, smooth=smooth, coarse_solver=coarse_solver, aggregate=aggregate, strength=strength, improve_candidates=None, keep=True, **kwargs), symmetry, candidate_iters, prepostsmoother, smooth, eliminate_local, coarse_solver, work) # Normalize x and add to candidate list x = x / norm(x, 'inf') if np.isinf(x[0]) or np.isnan(x[0]): raise ValueError('Adaptive candidate is all 0.') B = np.hstack((B, x.reshape(-1, 1))) # Improve candidates if B.shape[1] > 1 and improvement_iters > 0: b = np.zeros((A.shape[0], 1), dtype=A.dtype) for i in range(improvement_iters): for j in range(B.shape[1]): # Run a V-cycle built on everything except candidate j, while # using candidate j as the initial guess x0 = B[:, 0] B = B[:, 1:] sa_temp =\ smoothed_aggregation_solver(A, B=B, symmetry=symmetry, presmoother=prepostsmoother, postsmoother=prepostsmoother, smooth=smooth, coarse_solver=coarse_solver, aggregate=aggregate, strength=strength, improve_candidates=None, keep=True, **kwargs) x = sa_temp.solve(b, x0=x0, tol=float(np.finfo(np.float).tiny), maxiter=candidate_iters, cycle='V') work[:] += 2 * sa_temp.operator_complexity() *\ sa_temp.levels[0].A.nnz * candidate_iters # Apply local elimination elim, elim_kwargs = unpack_arg(eliminate_local) if elim is True: x = x / norm(x, 'inf') eliminate_local_candidates(x, sa_temp.levels[0].AggOp, A, sa_temp.levels[0].T, **elim_kwargs) # Normalize x and add to candidate list x = x / norm(x, 'inf') if np.isinf(x[0]) or np.isnan(x[0]): raise ValueError('Adaptive candidate is all 0.') B = np.hstack((B, x.reshape(-1, 1))) elif improvement_iters > 0: # Special case for improving a single candidate max_levels = len(aggregate) + 1 max_coarse = 0 for i in range(improvement_iters): B, aggregate, strength =\ initial_setup_stage(A, symmetry, pdef, candidate_iters, epsilon, max_levels, max_coarse, aggregate, prepostsmoother, smooth, strength, work, initial_candidate=B) # Normalize B B = (1.0 / norm(B, 'inf')) * B return [ smoothed_aggregation_solver(A, B=B, symmetry=symmetry, presmoother=prepostsmoother, postsmoother=prepostsmoother, smooth=smooth, coarse_solver=coarse_solver, aggregate=aggregate, strength=strength, improve_candidates=None, keep=keep, **kwargs), work[0] / A.nnz ]
def solve(A, b, x0=None, tol=1e-5, maxiter=400, return_solver=False, existing_solver=None, verb=True): """ Solve the arbitrary system Ax=b with the best out-of-the box choice for a solver. The matrix A can be non-Hermitian, indefinite, Hermitian positive-definite, complex, etc... Generic and robust settings for smoothed_aggregation_solver(..) are used to invert A. Parameters ---------- A : {array, matrix, csr_matrix, bsr_matrix} Matrix to invert, CSR or BSR format preferred for efficiency b : {array} Right hand side. x0 : {array} : default random vector Initial guess tol : {float} : default 1e-5 Stopping criteria: relative residual r[k]/r[0] tolerance maxiter : {int} : default 400 Stopping criteria: maximum number of allowable iterations return_solver : {bool} : default False True: return the solver generated existing_solver : {smoothed_aggregation_solver} : default None If instance of a multilevel solver, then existing_solver is used to invert A, thus saving time on setup cost. verb : {bool} If True, print verbose output during runtime Returns ------- x : {array} Solution to Ax = b ml : multilevel_solver Optional return of the multilevel structure used for the solve Notes ----- If calling solve(...) multiple times for the same matrix, A, solver reuse is easy and efficient. Set "return_solver=True", and the return value will be a tuple, (x,ml), where ml is the solver used to invert A, and x is the solution to Ax=b. Then, the next time solve(...) is called, set "existing_solver=ml". Examples -------- >>> from numpy import arange, array >>> from pyamg import solve >>> from pyamg.gallery import poisson >>> from pyamg.util.linalg import norm >>> A = poisson((40,40),format='csr') >>> b = array(arange(A.shape[0]), dtype=float) >>> x = solve(A,b,verb=False) >>> print "%1.2e"%(norm(b - A*x)/norm(b)) 6.28e-06 """ # Convert A to acceptable CSR/BSR format A = make_csr(A) # Generate solver if necessary if existing_solver is None: # Parameter dictionary for smoothed_aggregation_solver config = solver_configuration(A, B=None, verb=verb) # Generate solver existing_solver = solver(A, config) else: if existing_solver.levels[0].A.shape[0] != A.shape[0]: raise TypeError('Argument existing_solver must have level 0 matrix\ of same size as A') # Krylov acceleration depends on symmetry of A if existing_solver.levels[0].A.symmetry == 'hermitian': accel = 'cg' else: accel = 'gmres' # Initial guess if x0 is None: x0 = np.array(sp.rand(A.shape[0],), dtype=A.dtype) # Callback function to print iteration number if verb: iteration = np.zeros((1,)) print " maxiter = %d" % maxiter def callback(x, iteration): iteration[0] = iteration[0] + 1 print " iteration %d" % iteration[0] callback2 = lambda x: callback(x, iteration) else: callback2 = None # Solve with accelerated Krylov method x = existing_solver.solve(b, x0=x0, accel=accel, tol=tol, maxiter=maxiter, callback=callback2) if verb: r0 = norm(np.ravel(b) - np.ravel(A * x0)) rk = norm(np.ravel(b) - np.ravel(A * x)) if r0 != 0.0: print " Residual reduction ||r_k||/||r_0|| = %1.2e" % (rk / r0) else: print " Residuals ||r_k||, ||r_0|| = %1.2e, %1.2e" % (rk, r0) if return_solver: return (x.reshape(b.shape), existing_solver) else: return x.reshape(b.shape)
def adaptive_sa_solver(A, initial_candidates=None, symmetry='hermitian', pdef=True, num_candidates=1, candidate_iters=5, improvement_iters=0, epsilon=0.1, max_levels=10, max_coarse=10, aggregate='standard', prepostsmoother=('gauss_seidel', {'sweep': 'symmetric'}), smooth=('jacobi', {}), strength='symmetric', coarse_solver='pinv2', eliminate_local=(False, {'Ca': 1.0}), keep=False, **kwargs): """Create a multilevel solver using Adaptive Smoothed Aggregation (aSA). Parameters ---------- A : csr_matrix, bsr_matrix Square matrix in CSR or BSR format initial_candidates : None, n x m dense matrix If a matrix, then this forms the basis for the first m candidates. Also in this case, the initial setup stage is skipped, because this provides the first candidate(s). If None, then a random initial guess and relaxation are used to inform the initial candidate. symmetry : string 'symmetric' refers to both real and complex symmetric 'hermitian' refers to both complex Hermitian and real Hermitian Note that for the strictly real case, these two options are the same Note that this flag does not denote definiteness of the operator pdef : bool True or False, whether A is known to be positive definite. num_candidates : integer Number of near-nullspace candidates to generate candidate_iters : integer Number of smoothing passes/multigrid cycles used at each level of the adaptive setup phase improvement_iters : integer Number of times each candidate is improved epsilon : float Target convergence factor 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. prepostsmoother : string or dict Pre- and post-smoother used in the adaptive method strength : ['symmetric', 'classical', 'evolution', ('predefined', {'C': csr_matrix}), None] Method used to determine the strength of connection between unknowns of the linear system. See smoothed_aggregation_solver(...) documentation. aggregate : ['standard', 'lloyd', 'naive', ('predefined', {'AggOp': csr_matrix})] Method used to aggregate nodes. See smoothed_aggregation_solver(...) documentation. smooth : ['jacobi', 'richardson', 'energy', None] Method used used to smooth the tentative prolongator. See smoothed_aggregation_solver(...) documentation 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. eliminate_local : tuple Length 2 tuple. If the first entry is True, then eliminate candidates where they aren't needed locally, using the second entry of the tuple to contain arguments to local elimination routine. Given the rigid sparse data structures, this doesn't help much, if at all, with complexity. Its more of a diagnostic utility. 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. Returns ------- multilevel_solver : multilevel_solver Smoothed aggregation solver with adaptively generated candidates Notes ----- - Floating point value representing the "work" required to generate the solver. This value is the total cost of just relaxation, relative to the fine grid. The relaxation method used is assumed to symmetric Gauss-Seidel. - Unlike the standard Smoothed Aggregation (SA) method, adaptive SA does not require knowledge of near-nullspace candidate vectors. Instead, an adaptive procedure computes one or more candidates 'from scratch'. This approach is useful when no candidates are known or the candidates have been invalidated due to changes to matrix A. Examples -------- >>> from pyamg.gallery import stencil_grid >>> from pyamg.aggregation import adaptive_sa_solver >>> import numpy as np >>> A=stencil_grid([[-1,-1,-1],[-1,8.0,-1],[-1,-1,-1]], (31,31),format='csr') >>> [asa,work] = adaptive_sa_solver(A,num_candidates=1) >>> residuals=[] >>> x=asa.solve(b=np.ones((A.shape[0],)), x0=np.ones((A.shape[0],)), residuals=residuals) References ---------- .. [1] Brezina, Falgout, MacLachlan, Manteuffel, McCormick, and Ruge "Adaptive Smoothed Aggregation (alpha SA) Multigrid" SIAM Review Volume 47, Issue 2 (2005) """ 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 A.shape[0] != A.shape[1]: raise ValueError('expected square matrix') # Track work in terms of relaxation work = np.zeros((1,)) # 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) smooth = levelize_smooth_or_improve_candidates(smooth, max_levels) # Develop initial candidate(s). Note that any predefined aggregation is # preserved. if initial_candidates is None: B, aggregate, strength =\ initial_setup_stage(A, symmetry, pdef, candidate_iters, epsilon, max_levels, max_coarse, aggregate, prepostsmoother, smooth, strength, work) # Normalize B B = (1.0/norm(B, 'inf')) * B num_candidates -= 1 else: # Otherwise, use predefined candidates B = initial_candidates num_candidates -= B.shape[1] # Generate Aggregation and Strength Operators (the brute force way) sa = smoothed_aggregation_solver(A, B=B, symmetry=symmetry, presmoother=prepostsmoother, postsmoother=prepostsmoother, smooth=smooth, strength=strength, max_levels=max_levels, max_coarse=max_coarse, aggregate=aggregate, coarse_solver=coarse_solver, improve_candidates=None, keep=True, **kwargs) if len(sa.levels) > 1: # Set strength-of-connection and aggregation aggregate = [('predefined', {'AggOp': sa.levels[i].AggOp.tocsr()}) for i in range(len(sa.levels) - 1)] strength = [('predefined', {'C': sa.levels[i].C.tocsr()}) for i in range(len(sa.levels) - 1)] # Develop additional candidates for i in range(num_candidates): x = general_setup_stage( smoothed_aggregation_solver(A, B=B, symmetry=symmetry, presmoother=prepostsmoother, postsmoother=prepostsmoother, smooth=smooth, coarse_solver=coarse_solver, aggregate=aggregate, strength=strength, improve_candidates=None, keep=True, **kwargs), symmetry, candidate_iters, prepostsmoother, smooth, eliminate_local, coarse_solver, work) # Normalize x and add to candidate list x = x/norm(x, 'inf') if np.isinf(x[0]) or np.isnan(x[0]): raise ValueError('Adaptive candidate is all 0.') B = np.hstack((B, x.reshape(-1, 1))) # Improve candidates if B.shape[1] > 1 and improvement_iters > 0: b = np.zeros((A.shape[0], 1), dtype=A.dtype) for i in range(improvement_iters): for j in range(B.shape[1]): # Run a V-cycle built on everything except candidate j, while # using candidate j as the initial guess x0 = B[:, 0] B = B[:, 1:] sa_temp =\ smoothed_aggregation_solver(A, B=B, symmetry=symmetry, presmoother=prepostsmoother, postsmoother=prepostsmoother, smooth=smooth, coarse_solver=coarse_solver, aggregate=aggregate, strength=strength, improve_candidates=None, keep=True, **kwargs) x = sa_temp.solve(b, x0=x0, tol=float(np.finfo(np.float).tiny), maxiter=candidate_iters, cycle='V') work[:] += 2 * sa_temp.operator_complexity() *\ sa_temp.levels[0].A.nnz * candidate_iters # Apply local elimination elim, elim_kwargs = unpack_arg(eliminate_local) if elim is True: x = x/norm(x, 'inf') eliminate_local_candidates(x, sa_temp.levels[0].AggOp, A, sa_temp.levels[0].T, **elim_kwargs) # Normalize x and add to candidate list x = x/norm(x, 'inf') if np.isinf(x[0]) or np.isnan(x[0]): raise ValueError('Adaptive candidate is all 0.') B = np.hstack((B, x.reshape(-1, 1))) elif improvement_iters > 0: # Special case for improving a single candidate max_levels = len(aggregate) + 1 max_coarse = 0 for i in range(improvement_iters): B, aggregate, strength =\ initial_setup_stage(A, symmetry, pdef, candidate_iters, epsilon, max_levels, max_coarse, aggregate, prepostsmoother, smooth, strength, work, initial_candidate=B) # Normalize B B = (1.0/norm(B, 'inf'))*B return [smoothed_aggregation_solver(A, B=B, symmetry=symmetry, presmoother=prepostsmoother, postsmoother=prepostsmoother, smooth=smooth, coarse_solver=coarse_solver, aggregate=aggregate, strength=strength, improve_candidates=None, keep=keep, **kwargs), work[0]/A.nnz]
def gmres_householder(A, b, x0=None, tol=1e-5, restrt=None, maxiter=None, xtype=None, M=None, callback=None, residuals=None): ''' Generalized Minimum Residual Method (GMRES) GMRES iteratively refines the initial solution guess to the system Ax = b Householder reflections are used for orthogonalization Parameters ---------- A : {array, matrix, sparse matrix, LinearOperator} n x n, linear system to solve b : {array, matrix} right hand side, shape is (n,) or (n, 1) x0 : {array, matrix} initial guess, default is a vector of zeros tol : float relative convergence tolerance, i.e. tol is scaled by the norm of the initial preconditioned residual restrt : {None, int} - if int, restrt is max number of inner iterations and maxiter is the max number of outer iterations - if None, do not restart GMRES, and max number of inner iterations is maxiter maxiter : {None, int} - if restrt is None, maxiter is the max number of inner iterations and GMRES does not restart - if restrt is int, maxiter is the max number of outer iterations, and restrt is the max number of inner iterations xtype : type dtype for the solution, default is automatic type detection M : {array, matrix, sparse matrix, LinearOperator} n x n, inverted preconditioner, i.e. solve M A x = M b. callback : function User-supplied function is called after each iteration as callback( ||rk||_2 ), where rk is the current preconditioned residual vector residuals : list residuals contains the preconditioned residual norm history, including the initial residual. Returns ------- (xNew, info) xNew : an updated guess to the solution of Ax = b info : halting status of gmres == ============================================= 0 successful exit >0 convergence to tolerance not achieved, return iteration count instead. This value is precisely the order of the Krylov space. <0 numerical breakdown, or illegal input == ============================================= Notes ----- - The LinearOperator class is in scipy.sparse.linalg.interface. Use this class if you prefer to define A or M as a mat-vec routine as opposed to explicitly constructing the matrix. A.psolve(..) is still supported as a legacy. - For robustness, Householder reflections are used to orthonormalize the Krylov Space Givens Rotations are used to provide the residual norm each iteration Examples -------- >>> from pyamg.krylov import gmres >>> from pyamg.util.linalg import norm >>> import numpy as np >>> from pyamg.gallery import poisson >>> A = poisson((10, 10)) >>> b = np.ones((A.shape[0],)) >>> (x, flag) = gmres(A, b, maxiter=2, tol=1e-8, orthog='householder') >>> print norm(b - A*x) 6.5428213057 References ---------- .. [1] Yousef Saad, "Iterative Methods for Sparse Linear Systems, Second Edition", SIAM, pp. 151-172, pp. 272-275, 2003 http://www-users.cs.umn.edu/~saad/books.html ''' # Convert inputs to linear system, with error checking A, M, x, b, postprocess = make_system(A, M, x0, b) dimen = A.shape[0] # Ensure that warnings are always reissued from this function import warnings warnings.filterwarnings('always', module='pyamg\.krylov\._gmres_householder') # Choose type if not hasattr(A, 'dtype'): Atype = upcast(x.dtype, b.dtype) else: Atype = A.dtype if not hasattr(M, 'dtype'): Mtype = upcast(x.dtype, b.dtype) else: Mtype = M.dtype xtype = upcast(Atype, x.dtype, b.dtype, Mtype) if restrt is not None: restrt = int(restrt) if maxiter is not None: maxiter = int(maxiter) # Should norm(r) be kept if residuals == []: keep_r = True else: keep_r = False # Set number of outer and inner iterations if restrt: if maxiter: max_outer = maxiter else: max_outer = 1 if restrt > dimen: warn('Setting number of inner iterations (restrt) to maximum \ allowed, which is A.shape[0] ') restrt = dimen max_inner = restrt else: max_outer = 1 if maxiter > dimen: warn('Setting number of inner iterations (maxiter) to maximum \ allowed, which is A.shape[0] ') maxiter = dimen elif maxiter is None: maxiter = min(dimen, 40) max_inner = maxiter # Get fast access to underlying LAPACK routine [lartg] = get_lapack_funcs(['lartg'], [x]) # Is this a one dimensional matrix? if dimen == 1: entry = ravel(A * array([1.0], dtype=xtype)) return (postprocess(b / entry), 0) # Prep for method r = b - ravel(A * x) # Apply preconditioner r = ravel(M * r) normr = norm(r) if keep_r: residuals.append(normr) # Check for nan, inf # if isnan(r).any() or isinf(r).any(): # warn('inf or nan after application of preconditioner') # return(postprocess(x), -1) # Check initial guess ( scaling by b, if b != 0, # must account for case when norm(b) is very small) normb = norm(b) if normb == 0.0: normb = 1.0 if normr < tol * normb: if callback is not None: callback(norm(r)) return (postprocess(x), 0) # Scale tol by ||r_0||_2, we use the preconditioned residual # because this is left preconditioned GMRES. if normr != 0.0: tol = tol * normr # Use separate variable to track iterations. If convergence fails, we # cannot simply report niter = (outer-1)*max_outer + inner. Numerical # error could cause the inner loop to halt while the actual ||r|| > tol. niter = 0 # Begin GMRES for outer in range(max_outer): # Calculate vector w, which defines the Householder reflector # Take shortcut in calculating, # w = r + sign(r[1])*||r||_2*e_1 w = r beta = mysign(w[0]) * normr w[0] = w[0] + beta w[:] = w / norm(w) # Preallocate for Krylov vectors, Householder reflectors and # Hessenberg matrix # Space required is O(dimen*max_inner) # Givens Rotations Q = zeros((4 * max_inner, ), dtype=xtype) # upper Hessenberg matrix (made upper tri with Givens Rotations) H = zeros((max_inner, max_inner), dtype=xtype) # Householder reflectors W = zeros((max_inner + 1, dimen), dtype=xtype) W[0, :] = w # Multiply r with (I - 2*w*w.T), i.e. apply the Householder reflector # This is the RHS vector for the problem in the Krylov Space g = zeros((dimen, ), dtype=xtype) g[0] = -beta for inner in range(max_inner): # Calculate Krylov vector in two steps # (1) Calculate v = P_j = (I - 2*w*w.T)v, where k = inner v = -2.0 * conjugate(w[inner]) * w v[inner] = v[inner] + 1.0 # (2) Calculate the rest, v = P_1*P_2*P_3...P_{j-1}*ej. # for j in range(inner-1,-1,-1): # v -= 2.0*dot(conjugate(W[j,:]), v)*W[j,:] amg_core.apply_householders(v, ravel(W), dimen, inner - 1, -1, -1) # Calculate new search direction v = ravel(A * v) # Apply preconditioner v = ravel(M * v) # Check for nan, inf # if isnan(v).any() or isinf(v).any(): # warn('inf or nan after application of preconditioner') # return(postprocess(x), -1) # Factor in all Householder orthogonal reflections on new search # direction # for j in range(inner+1): # v -= 2.0*dot(conjugate(W[j,:]), v)*W[j,:] amg_core.apply_householders(v, ravel(W), dimen, 0, inner + 1, 1) # Calculate next Householder reflector, w # w = v[inner+1:] + sign(v[inner+1])*||v[inner+1:]||_2*e_{inner+1) # Note that if max_inner = dimen, then this is unnecessary for the # last inner iteration, when inner = dimen-1. Here we do not need # to calculate a Householder reflector or Givens rotation because # nnz(v) is already the desired length, i.e. we do not need to # zero anything out. if inner != dimen - 1: if inner < (max_inner - 1): w = W[inner + 1, :] vslice = v[inner + 1:] alpha = norm(vslice) if alpha != 0: alpha = mysign(vslice[0]) * alpha # do not need the final reflector for future calculations if inner < (max_inner - 1): w[inner + 1:] = vslice w[inner + 1] += alpha w[:] = w / norm(w) # Apply new reflector to v # v = v - 2.0*w*(w.T*v) v[inner + 1] = -alpha v[inner + 2:] = 0.0 if inner > 0: # Apply all previous Givens Rotations to v amg_core.apply_givens(Q, v, dimen, inner) # Calculate the next Givens rotation, where j = inner Note that if # max_inner = dimen, then this is unnecessary for the last inner # iteration, when inner = dimen-1. Here we do not need to # calculate a Householder reflector or Givens rotation because # nnz(v) is already the desired length, i.e. we do not need to zero # anything out. if inner != dimen - 1: if v[inner + 1] != 0: [c, s, r] = lartg(v[inner], v[inner + 1]) Qblock = array([[c, s], [-conjugate(s), c]], dtype=xtype) Q[(inner * 4):((inner + 1) * 4)] = ravel(Qblock).copy() # Apply Givens Rotation to g, the RHS for the linear system # in the Krylov Subspace. Note that this dot does a matrix # multiply, not an actual dot product where a conjugate # transpose is taken g[inner:inner + 2] = dot(Qblock, g[inner:inner + 2]) # Apply effect of Givens Rotation to v v[inner] = dot(Qblock[0, :], v[inner:inner + 2]) v[inner + 1] = 0.0 # Write to upper Hessenberg Matrix, # the LHS for the linear system in the Krylov Subspace H[:, inner] = v[0:max_inner] niter += 1 # Don't update normr if last inner iteration, because # normr is calculated directly after this loop ends. if inner < max_inner - 1: normr = abs(g[inner + 1]) if normr < tol: break # Allow user access to residual if callback is not None: callback(normr) if keep_r: residuals.append(normr) # end inner loop, back to outer loop # Find best update to x in Krylov Space, V. Solve inner+1 x inner+1 # system. Apparently this is the best way to solve a triangular system # in the magical world of scipy # piv = arange(inner+1) # y = lu_solve((H[0:(inner+1), 0:(inner+1)], piv), g[0:(inner+1)], # trans=0) y = sp.linalg.solve(H[0:(inner + 1), 0:(inner + 1)], g[0:(inner + 1)]) # Use Horner like Scheme to map solution, y, back to original space. # Note that we do not use the last reflector. update = zeros(x.shape, dtype=xtype) # for j in range(inner,-1,-1): # update[j] += y[j] # # Apply j-th reflector, (I - 2.0*w_j*w_j.T)*upadate # update -= 2.0*dot(conjugate(W[j,:]), update)*W[j,:] amg_core.householder_hornerscheme(update, ravel(W), ravel(y), dimen, inner, -1, -1) x[:] = x + update r = b - ravel(A * x) # Apply preconditioner r = ravel(M * r) normr = norm(r) # Check for nan, inf # if isnan(r).any() or isinf(r).any(): # warn('inf or nan after application of preconditioner') # return(postprocess(x), -1) # Allow user access to residual if callback is not None: callback(normr) if keep_r: residuals.append(normr) # Has GMRES stagnated? indices = (x != 0) if indices.any(): change = max(abs(update[indices] / x[indices])) if change < 1e-12: # No change, halt return (postprocess(x), -1) # test for convergence if normr < tol: return (postprocess(x), 0) # end outer loop return (postprocess(x), niter)
def gmres_mgs(A, b, x0=None, tol=1e-5, restrt=None, maxiter=None, M=None, callback=None, residuals=None, reorth=False): """Generalized Minimum Residual Method (GMRES) based on MGS. GMRES iteratively refines the initial solution guess to the system Ax = b. Modified Gram-Schmidt version. Left preconditioning, leading to preconditioned residuals. Parameters ---------- A : array, matrix, sparse matrix, LinearOperator n x n, linear system to solve b : array, matrix right hand side, shape is (n,) or (n,1) x0 : array, matrix initial guess, default is a vector of zeros tol : float Tolerance for stopping criteria, let r=r_k ||M r|| < tol ||M b|| if ||b||=0, then set ||M b||=1 for these tests. restrt : None, int - if int, restrt is max number of inner iterations and maxiter is the max number of outer iterations - if None, do not restart GMRES, and max number of inner iterations is maxiter maxiter : None, int - if restrt is None, maxiter is the max number of inner iterations and GMRES does not restart - if restrt is int, maxiter is the max number of outer iterations, and restrt is the max number of inner iterations - defaults to min(n,40) if restart=None M : array, matrix, sparse matrix, LinearOperator n x n, inverted preconditioner, i.e. solve M A x = M b. callback : function User-supplied function is called after each iteration as callback(xk), where xk is the current solution vector residuals : list preconditioned residual history in the 2-norm, including the initial preconditioned residual reorth : boolean If True, then a check is made whether to re-orthogonalize the Krylov space each GMRES iteration Returns ------- (xk, info) xk : an updated guess after k iterations to the solution of Ax = b info : halting status == ======================================= 0 successful exit >0 convergence to tolerance not achieved, return iteration count instead. <0 numerical breakdown, or illegal input == ======================================= Notes ----- The LinearOperator class is in scipy.sparse.linalg.interface. Use this class if you prefer to define A or M as a mat-vec routine as opposed to explicitly constructing the matrix. For robustness, modified Gram-Schmidt is used to orthogonalize the Krylov Space Givens Rotations are used to provide the residual norm each iteration The residual is the *preconditioned* residual. Examples -------- >>> from pyamg.krylov import gmres >>> from pyamg.util.linalg import norm >>> import numpy as np >>> from pyamg.gallery import poisson >>> A = poisson((10,10)) >>> b = np.ones((A.shape[0],)) >>> (x,flag) = gmres(A,b, maxiter=2, tol=1e-8, orthog='mgs') >>> print(f'{norm(b - A*x):.6}') 6.54282 References ---------- .. [1] Yousef Saad, "Iterative Methods for Sparse Linear Systems, Second Edition", SIAM, pp. 151-172, pp. 272-275, 2003 http://www-users.cs.umn.edu/~saad/books.html .. [2] C. T. Kelley, http://www4.ncsu.edu/~ctk/matlab_roots.html """ # Convert inputs to linear system, with error checking A, M, x, b, postprocess = make_system(A, M, x0, b) n = A.shape[0] # Ensure that warnings are always reissued from this function warnings.filterwarnings('always', module='pyamg.krylov._gmres_mgs') # Get fast access to underlying BLAS routines # dotc is the conjugate dot, dotu does no conjugation [lartg] = get_lapack_funcs(['lartg'], [x]) if np.iscomplexobj(np.zeros((1, ), dtype=x.dtype)): [axpy, dotu, dotc, scal] =\ get_blas_funcs(['axpy', 'dotu', 'dotc', 'scal'], [x]) else: # real type [axpy, dotu, dotc, scal] =\ get_blas_funcs(['axpy', 'dot', 'dot', 'scal'], [x]) # Set number of outer and inner iterations # If no restarts, # then set max_inner=maxiter and max_outer=n # If restarts are set, # then set max_inner=restart and max_outer=maxiter if restrt: if maxiter: max_outer = maxiter else: max_outer = 1 if restrt > n: warn('Setting restrt to maximum allowed, n.') restrt = n max_inner = restrt else: max_outer = 1 if maxiter > n: warn('Setting maxiter to maximum allowed, n.') maxiter = n elif maxiter is None: maxiter = min(n, 40) max_inner = maxiter # Is this a one dimensional matrix? if n == 1: entry = np.ravel(A @ np.array([1.0], dtype=x.dtype)) return (postprocess(b / entry), 0) # Prep for method r = b - A @ x # Apply preconditioner r = M @ r normr = norm(r) if residuals is not None: residuals[:] = [normr] # initial residual # Check initial guess if b != 0, normb = norm(b) if normb == 0.0: normMb = 1.0 # reset so that tol is unscaled else: normMb = norm(M @ b) # set the stopping criteria (see the docstring) if normr < tol * normMb: return (postprocess(x), 0) # Use separate variable to track iterations. If convergence fails, we # cannot simply report niter = (outer-1)*max_outer + inner. Numerical # error could cause the inner loop to halt while the actual ||r|| > tolerance. niter = 0 # Begin GMRES for _outer in range(max_outer): # Preallocate for Givens Rotations, Hessenberg matrix and Krylov Space # Space required is O(n*max_inner). # NOTE: We are dealing with row-major matrices, so we traverse in a # row-major fashion, # i.e., H and V's transpose is what we store. Q = [] # Givens Rotations # Upper Hessenberg matrix, which is then # converted to upper tri with Givens Rots H = np.zeros((max_inner + 1, max_inner + 1), dtype=x.dtype) V = np.zeros((max_inner + 1, n), dtype=x.dtype) # Krylov Space # vs store the pointers to each column of V. # This saves a considerable amount of time. vs = [] # v = r/normr V[0, :] = scal(1.0 / normr, r) vs.append(V[0, :]) # This is the RHS vector for the problem in the Krylov Space g = np.zeros((n, ), dtype=x.dtype) g[0] = normr for inner in range(max_inner): # New Search Direction v = V[inner + 1, :] v[:] = np.ravel(M @ (A @ vs[-1])) vs.append(v) normv_old = norm(v) # Modified Gram Schmidt for k in range(inner + 1): vk = vs[k] alpha = dotc(vk, v) H[inner, k] = alpha v[:] = axpy(vk, v, n, -alpha) normv = norm(v) H[inner, inner + 1] = normv # Re-orthogonalize if (reorth is True) and (normv_old == normv_old + 0.001 * normv): for k in range(inner + 1): vk = vs[k] alpha = dotc(vk, v) H[inner, k] = H[inner, k] + alpha v[:] = axpy(vk, v, n, -alpha) # Check for breakdown if H[inner, inner + 1] != 0.0: v[:] = scal(1.0 / H[inner, inner + 1], v) # Apply previous Givens rotations to H if inner > 0: apply_givens(Q, H[inner, :], inner) # Calculate and apply next complex-valued Givens Rotation # for the last inner iteration, when inner = n-1. # ==> Note that if max_inner = n, then this is unnecessary if inner != n - 1: if H[inner, inner + 1] != 0: [c, s, r] = lartg(H[inner, inner], H[inner, inner + 1]) Qblock = np.array([[c, s], [-np.conjugate(s), c]], dtype=x.dtype) Q.append(Qblock) # Apply Givens Rotation to g, # the RHS for the linear system in the Krylov Subspace. g[inner:inner + 2] = np.dot(Qblock, g[inner:inner + 2]) # Apply effect of Givens Rotation to H H[inner, inner] = dotu(Qblock[0, :], H[inner, inner:inner + 2]) H[inner, inner + 1] = 0.0 niter += 1 # Do not update normr if last inner iteration, because # normr is calculated directly after this loop ends. if inner < max_inner - 1: normr = np.abs(g[inner + 1]) if normr < tol * normMb: break if residuals is not None: residuals.append(normr) if callback is not None: y = sp.linalg.solve(H[0:inner + 1, 0:inner + 1].T, g[0:inner + 1]) update = np.ravel(V[:inner + 1, :].T.dot(y.reshape(-1, 1))) callback(x + update) # end inner loop, back to outer loop # Find best update to x in Krylov Space V. Solve inner x inner system. y = sp.linalg.solve(H[0:inner + 1, 0:inner + 1].T, g[0:inner + 1]) update = np.ravel(V[:inner + 1, :].T.dot(y.reshape(-1, 1))) x = x + update r = b - A @ x # Apply preconditioner r = M @ r normr = norm(r) # Allow user access to the iterates if callback is not None: callback(x) if residuals is not None: residuals.append(normr) # Has GMRES stagnated? indices = (x != 0) if indices.any(): change = np.max(np.abs(update[indices] / x[indices])) if change < 1e-12: # No change, halt return (postprocess(x), -1) # test for convergence if normr < tol * normMb: return (postprocess(x), 0) # end outer loop return (postprocess(x), niter)
def cg(A, b, x0=None, tol=1e-5, maxiter=None, xtype=None, M=None, callback=None, residuals=None): '''Conjugate Gradient algorithm Solves the linear system Ax = b. Left preconditioning is supported. Parameters ---------- A : {array, matrix, sparse matrix, LinearOperator} n x n, linear system to solve b : {array, matrix} right hand side, shape is (n,) or (n,1) x0 : {array, matrix} initial guess, default is a vector of zeros tol : float relative convergence tolerance, i.e. tol is scaled by ||b|| maxiter : int maximum number of allowed iterations xtype : type dtype for the solution, default is automatic type detection M : {array, matrix, sparse matrix, LinearOperator} n x n, inverted preconditioner, i.e. solve M A A.H x = b. callback : function User-supplied funtion is called after each iteration as callback(xk), where xk is the current solution vector residuals : list residuals has the residual norm history, including the initial residual, appended to it Returns ------- (xNew, info) xNew : an updated guess to the solution of Ax = b info : halting status of cg == ======================================= 0 successful exit >0 convergence to tolerance not achieved, return iteration count instead. <0 numerical breakdown, or illegal input == ======================================= Notes ----- The LinearOperator class is in scipy.sparse.linalg.interface. Use this class if you prefer to define A or M as a mat-vec routine as opposed to explicitly constructing the matrix. A.psolve(..) is still supported as a legacy. Examples -------- >>> from pyamg.krylov.cg import cg >>> from pyamg.util.linalg import norm >>> import numpy >>> from pyamg.gallery import poisson >>> A = poisson((10,10)) >>> b = numpy.ones((A.shape[0],)) >>> (x,flag) = cg(A,b, maxiter=2, tol=1e-8) >>> print norm(b - A*x) 10.9370700187 References ---------- .. [1] Yousef Saad, "Iterative Methods for Sparse Linear Systems, Second Edition", SIAM, pp. 262-67, 2003 http://www-users.cs.umn.edu/~saad/books.html ''' A,M,x,b,postprocess = make_system(A,M,x0,b,xtype=None) n = len(b) # Determine maxiter if maxiter is None: maxiter = int(1.3*len(b)) + 2 elif maxiter < 1: raise ValueError('Number of iterations must be positive') # Scale tol by normb normb = norm(b) if normb != 0: tol = tol*normb # setup method r = b - A*x z = M*r p = z.copy() rz = inner(conjugate(r), z) normr = norm(r) if residuals is not None: residuals[:] = [normr] #initial residual if normr < tol: return (postprocess(x), 0) iter = 0 while True: Ap = A*p rz_old = rz alpha = rz/inner(conjugate(Ap), p) # 3 (step # in Saad's pseudocode) x += alpha * p # 4 r -= alpha * Ap # 5 z = M*r # 6 rz = inner(conjugate(r), z) beta = rz/rz_old # 7 p *= beta # 8 p += z iter += 1 normr = norm(r) if residuals is not None: residuals.append(normr) if callback is not None: callback(x) if normr < tol: return (postprocess(x), 0) if iter == maxiter: return (postprocess(x), iter)
def obj_fcn(alpha): c = cos((omega + alpha) * x) Ac = (A * c)[1:-1] return norm(Ac) / norm(c[1:-1])
def polynomial(A, x, b, coefficients, iterations=1): """Apply a polynomial smoother to the system Ax=b Parameters ---------- A : sparse matrix Sparse NxN matrix x : ndarray Approximate solution (length N) b : ndarray Right-hand side (length N) coefficients : {array_like} Coefficients of the polynomial. See Notes section for details. iterations : int Number of iterations to perform Returns ------- Nothing, x will be modified in place. Notes ----- The smoother has the form x[:] = x + p(A) (b - A*x) where p(A) is a polynomial in A whose scalar coefficients are specified (in descending order) by argument 'coefficients'. - Richardson iteration p(A) = c_0: polynomial_smoother(A, x, b, [c_0]) - Linear smoother p(A) = c_1*A + c_0: polynomial_smoother(A, x, b, [c_1, c_0]) - Quadratic smoother p(A) = c_2*A^2 + c_1*A + c_0: polynomial_smoother(A, x, b, [c_2, c_1, c_0]) Here, Horner's Rule is applied to avoid computing A^k directly. For efficience, the method detects the case x = 0 one matrix-vector product is avoided (since (b - A*x) is b). Examples -------- >>> ## The polynomial smoother is not currently used directly >>> ## in PyAMG. It is only used by the chebyshev smoothing option, >>> ## which automatically calculates the correct coefficients. >>> from pyamg.gallery import poisson >>> from pyamg.util.linalg import norm >>> import numpy >>> from pyamg.aggregation import smoothed_aggregation_solver >>> A = poisson((10,10), format='csr') >>> b = numpy.ones((A.shape[0],1)) >>> sa = smoothed_aggregation_solver(A, B=numpy.ones((A.shape[0],1)), ... coarse_solver='pinv2', max_coarse=50, ... presmoother=('chebyshev', {'degree':3, 'iterations':1}), ... postsmoother=('chebyshev', {'degree':3, 'iterations':1})) >>> x0=numpy.zeros((A.shape[0],1)) >>> residuals=[] >>> x = sa.solve(b, x0=x0, tol=1e-8, residuals=residuals) """ A, x, b = make_system(A, x, b, formats=None) for i in range(iterations): from pyamg.util.linalg import norm if norm(x) == 0: residual = b else: residual = (b - A * x) h = coefficients[0] * residual for c in coefficients[1:]: h = c * residual + A * h x += h
def solve(self, b, x0=None, tol=1e-5, maxiter=100, cycle='V', accel=None, callback=None, residuals=None, return_residuals=False): """Execute multigrid cycling. Parameters ---------- b : array Right hand side. x0 : array Initial guess. tol : float Stopping criteria: relative residual r[k]/r[0] tolerance. maxiter : int Stopping criteria: maximum number of allowable iterations. cycle : {'V','W','F','AMLI'} Type of multigrid cycle to perform in each iteration. accel : string, function Defines acceleration method. Can be a string such as 'cg' or 'gmres' which is the name of an iterative solver in pyamg.krylov (preferred) or scipy.sparse.linalg.isolve. If accel is not a string, it will be treated like a function with the same interface provided by the iterative solvers in SciPy. callback : function User-defined function called after each iteration. It is called as callback(xk) where xk is the k-th iterate vector. residuals : list List to contain residual norms at each iteration. Returns ------- x : array Approximate solution to Ax=b See Also -------- aspreconditioner Examples -------- >>> from numpy import ones >>> from pyamg import ruge_stuben_solver >>> from pyamg.gallery import poisson >>> A = poisson((100, 100), format='csr') >>> b = A * ones(A.shape[0]) >>> ml = ruge_stuben_solver(A, max_coarse=10) >>> residuals = [] >>> x = ml.solve(b, tol=1e-12, residuals=residuals) # standalone solver """ from pyamg.util.linalg import residual_norm, norm if x0 is None: x = np.zeros_like(b) else: x = np.array(x0) # copy cycle = str(cycle).upper() # AMLI cycles require hermitian matrix if (cycle == 'AMLI') and hasattr(self.levels[0].A, 'symmetry'): if self.levels[0].A.symmetry != 'hermitian': raise ValueError('AMLI cycles require \ symmetry to be hermitian') if accel is not None: # Check for symmetric smoothing scheme when using CG if (accel is 'cg') and (not self.symmetric_smoothing): warn('Incompatible non-symmetric multigrid preconditioner ' 'detected, due to presmoother/postsmoother combination. ' 'CG requires SPD preconditioner, not just SPD matrix.') # Check for AMLI compatability if (accel != 'fgmres') and (cycle == 'AMLI'): raise ValueError('AMLI cycles require acceleration (accel) ' 'to be fgmres, or no acceleration') # py23 compatibility: try: basestring except NameError: basestring = str # Acceleration is being used kwargs = {} if isinstance(accel, basestring): from pyamg import krylov from scipy.sparse.linalg import isolve kwargs = {} if hasattr(krylov, accel): accel = getattr(krylov, accel) else: accel = getattr(isolve, accel) kwargs['atol'] = 'legacy' A = self.levels[0].A M = self.aspreconditioner(cycle=cycle) try: # try PyAMG style interface which has a residuals parameter return accel(A, b, x0=x0, tol=tol, maxiter=maxiter, M=M, callback=callback, residuals=residuals, **kwargs)[0] except BaseException: # try the scipy.sparse.linalg.isolve style interface, # which requires a call back function if a residual # history is desired cb = callback if residuals is not None: residuals[:] = [residual_norm(A, x, b)] def callback(x): if sp.isscalar(x): residuals.append(x) else: residuals.append(residual_norm(A, x, b)) if cb is not None: cb(x) return accel(A, b, x0=x0, tol=tol, maxiter=maxiter, M=M, callback=callback, **kwargs)[0] else: # Scale tol by normb # Don't scale tol earlier. The accel routine should also scale tol normb = norm(b) if normb != 0: tol = tol * normb if return_residuals: warn('return_residuals is deprecated. Use residuals instead') residuals = [] if residuals is None: residuals = [] else: residuals[:] = [] # Create uniform types for A, x and b # Clearly, this logic doesn't handle the case of real A and complex b from scipy.sparse.sputils import upcast from pyamg.util.utils import to_type tp = upcast(b.dtype, x.dtype, self.levels[0].A.dtype) [b, x] = to_type(tp, [b, x]) b = np.ravel(b) x = np.ravel(x) A = self.levels[0].A residuals.append(residual_norm(A, x, b)) self.first_pass = True while len(residuals) <= maxiter and residuals[-1] > tol: if len(self.levels) == 1: # hierarchy has only 1 level x = self.coarse_solver(A, b) else: self.__solve(0, x, b, cycle) residuals.append(residual_norm(A, x, b)) self.first_pass = False if callback is not None: callback(x) if return_residuals: return x, residuals else: return x
def bicgstab(A, b, x0=None, tol=1e-5, maxiter=None, xtype=None, M=None, callback=None, residuals=None): '''Biconjugate Gradient Algorithm with Stabilization Solves the linear system Ax = b. Left preconditioning is supported. Parameters ---------- A : {array, matrix, sparse matrix, LinearOperator} n x n, linear system to solve b : {array, matrix} right hand side, shape is (n,) or (n,1) x0 : {array, matrix} initial guess, default is a vector of zeros tol : float relative convergence tolerance, i.e. tol is scaled by ||r_0||_2 maxiter : int maximum number of allowed iterations xtype : type dtype for the solution, default is automatic type detection M : {array, matrix, sparse matrix, LinearOperator} n x n, inverted preconditioner, i.e. solve M A A.H x = M b. callback : function User-supplied function is called after each iteration as callback(xk), where xk is the current solution vector residuals : list residuals has the residual norm history, including the initial residual, appended to it Returns ------- (xNew, info) xNew : an updated guess to the solution of Ax = b info : halting status of bicgstab == ====================================== 0 successful exit >0 convergence to tolerance not achieved, return iteration count instead. <0 numerical breakdown, or illegal input == ====================================== Notes ----- The LinearOperator class is in scipy.sparse.linalg.interface. Use this class if you prefer to define A or M as a mat-vec routine as opposed to explicitly constructing the matrix. A.psolve(..) is still supported as a legacy. Examples -------- >>> from pyamg.krylov.bicgstab import bicgstab >>> from pyamg.util.linalg import norm >>> import numpy >>> from pyamg.gallery import poisson >>> A = poisson((10,10)) >>> b = numpy.ones((A.shape[0],)) >>> (x,flag) = bicgstab(A,b, maxiter=2, tol=1e-8) >>> print norm(b - A*x) 4.68163045309 References ---------- .. [1] Yousef Saad, "Iterative Methods for Sparse Linear Systems, Second Edition", SIAM, pp. 231-234, 2003 http://www-users.cs.umn.edu/~saad/books.html ''' # Convert inputs to linear system, with error checking A, M, x, b, postprocess = make_system(A, M, x0, b, xtype) ## # Ensure that warnings are always reissued from this function import warnings warnings.filterwarnings('always', module='pyamg\.krylov\._bicgstab') # Check iteration numbers if maxiter is None: maxiter = len(x) + 5 elif maxiter < 1: raise ValueError('Number of iterations must be positive') # Prep for method r = b - A*x normr = norm(r) if residuals is not None: residuals[:] = [normr] # Check initial guess ( scaling by b, if b != 0, # must account for case when norm(b) is very small) normb = norm(b) if normb == 0.0: normb = 1.0 if normr < tol*normb: return (postprocess(x), 0) # Scale tol by ||r_0||_2 if normr != 0.0: tol = tol*normr # Is this a one dimensional matrix? if A.shape[0] == 1: entry = ravel(A*array([1.0], dtype=xtype)) return (postprocess(b/entry), 0) rstar = r.copy() p = r.copy() rrstarOld = inner(rstar.conjugate(), r) iter = 0 # Begin BiCGStab while True: Mp = M*p AMp = A*Mp # alpha = (r_j, rstar) / (A*M*p_j, rstar) alpha = rrstarOld/inner(rstar.conjugate(), AMp) # s_j = r_j - alpha*A*M*p_j s = r - alpha*AMp Ms = M*s AMs = A*Ms # omega = (A*M*s_j, s_j)/(A*M*s_j, A*M*s_j) omega = inner(AMs.conjugate(), s)/inner(AMs.conjugate(), AMs) # x_{j+1} = x_j + alpha*M*p_j + omega*M*s_j x = x + alpha*Mp + omega*Ms # r_{j+1} = s_j - omega*A*M*s r = s - omega*AMs # beta_j = (r_{j+1}, rstar)/(r_j, rstar) * (alpha/omega) rrstarNew = inner(rstar.conjugate(), r) beta = (rrstarNew / rrstarOld) * (alpha / omega) rrstarOld = rrstarNew # p_{j+1} = r_{j+1} + beta*(p_j - omega*A*M*p) p = r + beta*(p - omega*AMp) iter += 1 normr = norm(r) if residuals is not None: residuals.append(normr) if callback is not None: callback(x) if normr < tol: return (postprocess(x), 0) if iter == maxiter: return (postprocess(x), iter)
def steepest_descent(A, b, x0=None, tol=1e-5, criteria='rr', maxiter=None, M=None, callback=None, residuals=None): """Steepest descent algorithm. Solves the linear system Ax = b. Left preconditioning is supported. Parameters ---------- A : array, matrix, sparse matrix, LinearOperator n x n, linear system to solve b : array, matrix right hand side, shape is (n,) or (n,1) x0 : array, matrix initial guess, default is a vector of zeros tol : float Tolerance for stopping criteria criteria : string Stopping criteria, let r=r_k, x=x_k 'rr': ||r|| < tol ||b|| 'rr+': ||r|| < tol (||b|| + ||A||_F ||x||) 'MrMr': ||M r|| < tol ||M b|| 'rMr': <r, Mr>^1/2 < tol if ||b||=0, then set ||b||=1 for these tests. maxiter : int maximum number of iterations allowed M : array, matrix, sparse matrix, LinearOperator n x n, inverted preconditioner, i.e. solve M A x = M b. callback : function User-supplied function is called after each iteration as callback(xk), where xk is the current solution vector residuals : list residual history in the 2-norm, including the initial residual Returns ------- (xk, info) xk : an updated guess after k iterations to the solution of Ax = b info : halting status of cg == ======================================= 0 successful exit >0 convergence to tolerance not achieved, return iteration count instead. <0 numerical breakdown, or illegal input == ======================================= Notes ----- The LinearOperator class is in scipy.sparse.linalg.interface. Use this class if you prefer to define A or M as a mat-vec routine as opposed to explicitly constructing the matrix. See Also -------- _minimal_residual Examples -------- >>> from pyamg.krylov import steepest_descent >>> from pyamg.util.linalg import norm >>> import numpy as np >>> from pyamg.gallery import poisson >>> A = poisson((10,10)) >>> b = np.ones((A.shape[0],)) >>> (x,flag) = steepest_descent(A,b, maxiter=2, tol=1e-8) >>> print(f'{norm(b - A*x):.6}') 7.89436 References ---------- .. [1] Yousef Saad, "Iterative Methods for Sparse Linear Systems, Second Edition", SIAM, pp. 137--142, 2003 http://www-users.cs.umn.edu/~saad/books.html """ A, M, x, b, postprocess = make_system(A, M, x0, b) # Ensure that warnings are always reissued from this function warnings.filterwarnings('always', module='pyamg.krylov._steepest_descent') # determine maxiter if maxiter is None: maxiter = int(len(b)) elif maxiter < 1: raise ValueError('Number of iterations must be positive') # setup method r = b - A @ x z = M @ r rz = np.inner(r.conjugate(), z) normr = np.linalg.norm(r) if residuals is not None: residuals[:] = [normr] # initial residual # Check initial guess if b != 0, normb = norm(b) if normb == 0.0: normb = 1.0 # reset so that tol is unscaled # set the stopping criteria (see the docstring) if criteria == 'rr': rtol = tol * normb elif criteria == 'rr+': if sparse.issparse(A.A): normA = norm(A.A.data) elif isinstance(A.A, np.ndarray): normA = norm(np.ravel(A.A)) else: raise ValueError( 'Unable to use ||A||_F with the current matrix format.') rtol = tol * (normA * np.linalg.norm(x) + normb) elif criteria == 'MrMr': normr = norm(z) normMb = norm(M @ b) rtol = tol * normMb elif criteria == 'rMr': normr = np.sqrt(rz) rtol = tol else: raise ValueError('Invalid stopping criteria.') # How often should r be recomputed recompute_r = 50 it = 0 while True: q = A @ z zAz = np.inner(z.conjugate(), q) # check curvature of A if zAz < 0.0: warn( '\nIndefinite matrix detected in steepest descent, aborting\n') return (postprocess(x), -1) alpha = rz / zAz # step size x = x + alpha * z it += 1 if np.mod(it, recompute_r) and it > 0: r = b - A @ x else: r = r - alpha * q z = M @ r rz = np.inner(r.conjugate(), z) if rz < 0.0: # check curvature of M warn( '\nIndefinite preconditioner detected in steepest descent, stopping.\n' ) return (postprocess(x), -1) normr = norm(r) if residuals is not None: residuals.append(normr) if callback is not None: callback(x) # set the stopping criteria (see the docstring) if criteria == 'rr': rtol = tol * normb elif criteria == 'rr+': rtol = tol * (normA * np.linalg.norm(x) + normb) elif criteria == 'MrMr': normr = norm(z) rtol = tol * normMb elif criteria == 'rMr': normr = np.sqrt(rz) rtol = tol if normr < rtol: return (postprocess(x), 0) if rz == 0.0: # important to test after testing normr < tol. rz == 0.0 is an # indicator of convergence when r = 0.0 warn( '\nSingular preconditioner detected in steepest descent, stopping.\n' ) return (postprocess(x), -1) if it == maxiter: return (postprocess(x), it)
def minimal_residual(A, b, x0=None, tol=1e-5, maxiter=None, xtype=None, M=None, callback=None, residuals=None): '''Minimal residual (MR) algorithm Solves the linear system Ax = b. Left preconditioning is supported. Parameters ---------- A : {array, matrix, sparse matrix, LinearOperator} n x n, linear system to solve b : {array, matrix} right hand side, shape is (n,) or (n,1) x0 : {array, matrix} initial guess, default is a vector of zeros tol : float relative convergence tolerance, i.e. tol is scaled by the preconditioner norm of r_0, or ||r_0||_M. maxiter : int maximum number of allowed iterations xtype : type dtype for the solution, default is automatic type detection M : {array, matrix, sparse matrix, LinearOperator} n x n, inverted preconditioner, i.e. solve M A x = M b. callback : function User-supplied function is called after each iteration as callback(xk), where xk is the current solution vector residuals : list residuals contains the residual norm history, including the initial residual. The preconditioner norm is used, instead of the Euclidean norm. Returns ------- (xNew, info) xNew : an updated guess to the solution of Ax = b info : halting status of cg == ======================================= 0 successful exit >0 convergence to tolerance not achieved, return iteration count instead. <0 numerical breakdown, or illegal input == ======================================= Notes ----- The LinearOperator class is in scipy.sparse.linalg.interface. Use this class if you prefer to define A or M as a mat-vec routine as opposed to explicitly constructing the matrix. A.psolve(..) is still supported as a legacy. The residual in the preconditioner norm is both used for halting and returned in the residuals list. Examples -------- >>> from pyamg.krylov import minimal_residual >>> from pyamg.util.linalg import norm >>> import numpy >>> from pyamg.gallery import poisson >>> A = poisson((10,10)) >>> b = numpy.ones((A.shape[0],)) >>> (x,flag) = minimal_residual(A,b, maxiter=2, tol=1e-8) >>> print norm(b - A*x) 7.26369350856 References ---------- .. [1] Yousef Saad, "Iterative Methods for Sparse Linear Systems, Second Edition", SIAM, pp. 137--142, 2003 http://www-users.cs.umn.edu/~saad/books.html ''' A,M,x,b,postprocess = make_system(A,M,x0,b,xtype=None) n = len(b) ## # Ensure that warnings are always reissued from this function import warnings warnings.filterwarnings('always', module='pyamg\.krylov\._minimal_residual') # determine maxiter if maxiter is None: maxiter = int(len(b)) elif maxiter < 1: raise ValueError('Number of iterations must be positive') # setup method r = M*(b - A*x) normr = norm(r) # store initial residual if residuals is not None: residuals[:] = [normr] # Check initial guess ( scaling by b, if b != 0, # must account for case when norm(b) is very small) normb = norm(b) if normb == 0.0: normb = 1.0 if normr < tol*normb: return (postprocess(x), 0) # Scale tol by ||r_0||_M if normr != 0.0: tol = tol*normr # How often should r be recomputed recompute_r = 50 iter = 0 while True: iter = iter+1 p = M*(A*r) rMAr = inner(p.conjugate(), r) # check curvature of M^-1 A if rMAr < 0.0: warn("\nIndefinite matrix detected in minimal residual, aborting\n") return (postprocess(x), -1) alpha = rMAr / inner(p.conjugate(), p) x = x + alpha*r if mod(iter, recompute_r) and iter > 0: r = M*(b - A*x) else: r = r - alpha*p normr = norm(r) if residuals is not None: residuals.append(normr) if callback is not None: callback(x) if normr < tol: return (postprocess(x), 0) if iter == maxiter: return (postprocess(x), iter)
import time from pyamg.krylov._gmres import gmres A = stencil_grid([[0, -1, 0], [-1, 4, -1], [0, -1, 0]], (100, 100), dtype=float, format='csr') b = random((A.shape[0],)) x0 = random((A.shape[0],)) print '\n\nTesting CR with %d x %d 2D Laplace Matrix' % \ (A.shape[0], A.shape[0]) t1 = time.time() r = [] (x, flag) = cr(A, b, x0, tol=1e-8, maxiter=100, residuals=r) t2 = time.time() print '%s took %0.3f ms' % ('cr', (t2-t1)*1000.0) print 'norm = %g' % (norm(b - A*x)) print 'info flag = %d' % (flag) t1 = time.time() r2 = [] (x, flag) = gmres(A, b, x0, tol=1e-8, maxiter=100, residuals=r2) t2 = time.time() print '%s took %0.3f ms' % ('gmres', (t2-t1)*1000.0) print 'norm = %g' % (norm(b - A*x)) print 'info flag = %d' % (flag) # from scipy.sparse.linalg.isolve import cg as icg # t1=time.time() # (y,flag) = icg(A,b,x0,tol=1e-8,maxiter=100) # t2=time.time() # print '\n%s took %0.3f ms' % ('linalg cg', (t2-t1)*1000.0)
def cgnr(A, b, x0=None, tol=1e-5, maxiter=None, xtype=None, M=None, callback=None, residuals=None): '''Conjugate Gradient, Normal Residual algorithm Applies CG to the normal equations, A.H A x = b. Left preconditioning is supported. Note that unless A is well-conditioned, the use of CGNR is inadvisable Parameters ---------- A : {array, matrix, sparse matrix, LinearOperator} n x n, linear system to solve b : {array, matrix} right hand side, shape is (n,) or (n,1) x0 : {array, matrix} initial guess, default is a vector of zeros tol : float relative convergence tolerance, i.e. tol is scaled by ||r_0||_2 maxiter : int maximum number of allowed iterations xtype : type dtype for the solution, default is automatic type detection M : {array, matrix, sparse matrix, LinearOperator} n x n, inverted preconditioner, i.e. solve M A.H A x = b. callback : function User-supplied function is called after each iteration as callback(xk), where xk is the current solution vector residuals : list residuals has the residual norm history, including the initial residual, appended to it Returns ------- (xNew, info) xNew : an updated guess to the solution of Ax = b info : halting status of cgnr == ======================================= 0 successful exit >0 convergence to tolerance not achieved, return iteration count instead. <0 numerical breakdown, or illegal input == ======================================= Notes ----- The LinearOperator class is in scipy.sparse.linalg.interface. Use this class if you prefer to define A or M as a mat-vec routine as opposed to explicitly constructing the matrix. A.psolve(..) is still supported as a legacy. Examples -------- >>> from pyamg.krylov.cgnr import cgnr >>> from pyamg.util.linalg import norm >>> import numpy as np >>> from pyamg.gallery import poisson >>> A = poisson((10,10)) >>> b = np.ones((A.shape[0],)) >>> (x,flag) = cgnr(A,b, maxiter=2, tol=1e-8) >>> print norm(b - A*x) 9.3910201849 References ---------- .. [1] Yousef Saad, "Iterative Methods for Sparse Linear Systems, Second Edition", SIAM, pp. 276-7, 2003 http://www-users.cs.umn.edu/~saad/books.html ''' # Store the conjugate transpose explicitly as it will be used much later on if isspmatrix(A): AH = A.H else: # TODO avoid doing this since A may be a different sparse type AH = aslinearoperator(asmatrix(A).H) # Convert inputs to linear system, with error checking A, M, x, b, postprocess = make_system(A, M, x0, b) dimen = A.shape[0] # Ensure that warnings are always reissued from this function import warnings warnings.filterwarnings('always', module='pyamg\.krylov\._cgnr') # Choose type if not hasattr(A, 'dtype'): Atype = upcast(x.dtype, b.dtype) else: Atype = A.dtype if not hasattr(M, 'dtype'): Mtype = upcast(x.dtype, b.dtype) else: Mtype = M.dtype xtype = upcast(Atype, x.dtype, b.dtype, Mtype) # Should norm(r) be kept if residuals == []: keep_r = True else: keep_r = False # How often should r be recomputed recompute_r = 8 # Check iteration numbers. CGNR suffers from loss of orthogonality quite # easily, so we arbitrarily let the method go up to 130% over the # theoretically necessary limit of maxiter=dimen if maxiter is None: maxiter = int(ceil(1.3*dimen)) + 2 elif maxiter < 1: raise ValueError('Number of iterations must be positive') elif maxiter > (1.3*dimen): warn('maximum allowed inner iterations (maxiter) are the 130% times \ the number of dofs') maxiter = int(ceil(1.3*dimen)) + 2 # Prep for method r = b - A*x rhat = AH*r normr = norm(r) if keep_r: residuals.append(normr) # Check initial guess ( scaling by b, if b != 0, # must account for case when norm(b) is very small) normb = norm(b) if normb == 0.0: normb = 1.0 if normr < tol*normb: if callback is not None: callback(x) return (postprocess(x), 0) # Scale tol by ||r_0||_2 if normr != 0.0: tol = tol*normr # Begin CGNR # Apply preconditioner and calculate initial search direction z = M*rhat p = z.copy() old_zr = inner(z.conjugate(), rhat) for iter in range(maxiter): # w_j = A p_j w = A*p # alpha = (z_j, rhat_j) / (w_j, w_j) alpha = old_zr / inner(w.conjugate(), w) # x_{j+1} = x_j + alpha*p_j x += alpha*p # r_{j+1} = r_j - alpha*w_j if mod(iter, recompute_r) and iter > 0: r -= alpha*w else: r = b - A*x # rhat_{j+1} = A.H*r_{j+1} rhat = AH*r # z_{j+1} = M*r_{j+1} z = M*rhat # beta = (z_{j+1}, rhat_{j+1}) / (z_j, rhat_j) new_zr = inner(z.conjugate(), rhat) beta = new_zr / old_zr old_zr = new_zr # p_{j+1} = A.H*z_{j+1} + beta*p_j p *= beta p += z # Allow user access to residual if callback is not None: callback(x) # test for convergence normr = norm(r) if keep_r: residuals.append(normr) if normr < tol: return (postprocess(x), 0) # end loop return (postprocess(x), iter+1)
# x0 = random((4,1)) # %timeit -n 15 (x,flag) = gmres(A,b,x0,tol=1e-8,maxiter=100) from pyamg.gallery import poisson from numpy.random import random from pyamg.util.linalg import norm A = poisson((125, 125), dtype=float, format='csr') # A.data = A.data + 0.001j*rand(A.data.shape[0]) b = random((A.shape[0],)) x0 = random((A.shape[0],)) import time from scipy.sparse.linalg.isolve import gmres as igmres print('\n\nTesting GMRES with %d x %d 2D Laplace Matrix' % (A.shape[0], A.shape[0])) t1 = time.time() (x, flag) = gmres_mgs(A, b, x0, tol=1e-8, maxiter=500) t2 = time.time() print('%s took %0.3f ms' % ('gmres', (t2-t1)*1000.0)) print('norm = %g' % (norm(b - A*x))) print('info flag = %d' % (flag)) t1 = time.time() # DON"T Enforce a maxiter as scipy gmres can't handle it correctly (y, flag) = igmres(A, b, x0, tol=1e-8) t2 = time.time() print('\n%s took %0.3f ms' % ('linalg gmres', (t2-t1)*1000.0)) print('norm = %g' % (norm(b - A*y))) print('info flag = %d' % (flag))
def solve(self, b, x0=None, tol=1e-5, maxiter=100, cycle='V', accel=None, callback=None, residuals=None, return_residuals=False, additive=False): if self.num_hierarchies == 0: raise ValueError("Cannot solve - zero hierarchies stored.") from pyamg.util.linalg import residual_norm, norm if x0 is None: x = np.zeros_like(b) else: x = np.array(x0) # copy cycle = str(cycle).upper() # AMLI cycles require hermitian matrix if (cycle == 'AMLI') and hasattr(self.levels[0].A, 'symmetry'): if self.levels[0].A.symmetry != 'hermitian': raise ValueError('AMLI cycles require \ symmetry to be hermitian') # Create uniform types for A, x and b # Clearly, this logic doesn't handle the case of real A and complex b from scipy.sparse.sputils import upcast from pyamg.util.utils import to_type A = self.hierarchy_set[0].levels[0].A tp = upcast(b.dtype, x.dtype, A.dtype) [b, x] = to_type(tp, [b, x]) b = np.ravel(b) x = np.ravel(x) if accel is not None: # Check for AMLI compatability if (accel != 'fgmres') and (cycle == 'AMLI'): raise ValueError('AMLI cycles require acceleration (accel) \ to be fgmres, or no acceleration') # Acceleration is being used if isinstance(accel, basestring): from pyamg import krylov from scipy.sparse.linalg import isolve if hasattr(krylov, accel): accel = getattr(krylov, accel) else: accel = getattr(isolve, accel) M = self.aspreconditioner(cycle=cycle) n = x.shape[0] try: # try PyAMG style interface which has a residuals parameter return accel(A, b, x0=x0, tol=tol, maxiter=maxiter, M=M, callback=callback, residuals=residuals)[0].reshape((n,1)) except: # try the scipy.sparse.linalg.isolve style interface, # which requires a call back function if a residual # history is desired cb = callback if residuals is not None: residuals[:] = [residual_norm(A, x, b)] def callback(x): if sp.isscalar(x): residuals.append(x) else: residuals.append(residual_norm(A, x, b)) if cb is not None: cb(x) return accel(A, b, x0=x0, tol=tol, maxiter=maxiter, M=M, callback=callback)[0].reshape((n,1)) else: # Scale tol by normb # Don't scale tol earlier. The accel routine should also scale tol normb = norm(b) if normb != 0: tol = tol * normb if return_residuals: warn('return_residuals is deprecated. Use residuals instead') residuals = [] if residuals is None: residuals = [] else: residuals[:] = [] residuals.append(residual_norm(A, x, b)) iter_num = 0 while iter_num < maxiter and residuals[-1] > tol: # ----------- Additive solve ----------- # # ------ This doesn't really work ------ # if additive: x_copy = deepcopy(x) for hierarchy in self.hierarchy_set: this_x = deepcopy(x_copy) if len(hierarchy.levels) == 1: this_x = hierarchy.coarse_solver(A, b) else: temp = hierarchy.test_solve(0, this_x, b, cycle) x += temp # ----------- Normal solve ----------- # else: # One solve for each hierarchy in set for hierarchy in self.hierarchy_set: # hierarchy has only 1 level if len(hierarchy.levels) == 1: x = hierarchy.coarse_solver(A, b) else: hierarchy.test_solve(0, x, b, cycle) residuals.append(residual_norm(A, x, b)) iter_num += 1 if callback is not None: callback(x) n = x.shape[0] if return_residuals: return x.reshape((n,1)), residuals else: return x.reshape((n,1))
def solve(A, b, x0=None, tol=1e-5, maxiter=400, return_solver=False, existing_solver=None, verb=True): """ Solve the arbitrary system Ax=b with the best out-of-the box choice for a solver. The matrix A can be non-Hermitian, indefinite, Hermitian positive-definite, complex, etc... Generic and robust settings for smoothed_aggregation_solver(..) are used to invert A. Parameters ---------- A : {array, matrix, csr_matrix, bsr_matrix} Matrix to invert, CSR or BSR format preferred for efficiency b : {array} Right hand side. x0 : {array} : default random vector Initial guess tol : {float} : default 1e-5 Stopping criteria: relative residual r[k]/r[0] tolerance maxiter : {int} : default 400 Stopping criteria: maximum number of allowable iterations return_solver : {bool} : default False True: return the solver generated existing_solver : {smoothed_aggregation_solver} : default None If instance of a multilevel solver, then existing_solver is used to invert A, thus saving time on setup cost. verb : {bool} If True, print verbose output during runtime Returns ------- x : {array} Solution to Ax = b ml : multilevel_solver Optional return of the multilevel structure used for the solve Notes ----- If calling solve(...) multiple times for the same matrix, A, solver reuse is easy and efficient. Set "return_solver=True", and the return value will be a tuple, (x,ml), where ml is the solver used to invert A, and x is the solution to Ax=b. Then, the next time solve(...) is called, set "existing_solver=ml". Examples -------- >>> import numpy as np >>> from pyamg import solve >>> from pyamg.gallery import poisson >>> from pyamg.util.linalg import norm >>> A = poisson((40,40),format='csr') >>> b = np.array(np.arange(A.shape[0]), dtype=float) >>> x = solve(A,b,verb=False) >>> print "%1.2e"%(norm(b - A*x)/norm(b)) 6.28e-06 """ # Convert A to acceptable CSR/BSR format A = make_csr(A) # Generate solver if necessary if existing_solver is None: # Parameter dictionary for smoothed_aggregation_solver config = solver_configuration(A, B=None, verb=verb) # Generate solver existing_solver = solver(A, config) else: if existing_solver.levels[0].A.shape[0] != A.shape[0]: raise TypeError('Argument existing_solver must have level 0 matrix\ of same size as A') # Krylov acceleration depends on symmetry of A if existing_solver.levels[0].A.symmetry == 'hermitian': accel = 'cg' else: accel = 'gmres' # Initial guess if x0 is None: x0 = np.array(sp.rand(A.shape[0], ), dtype=A.dtype) # Callback function to print iteration number if verb: iteration = np.zeros((1, )) print(" maxiter = %d" % maxiter) def callback(x, iteration): iteration[0] = iteration[0] + 1 print(" iteration %d" % iteration[0]) callback2 = lambda x: callback(x, iteration) else: callback2 = None # Solve with accelerated Krylov method x = existing_solver.solve(b, x0=x0, accel=accel, tol=tol, maxiter=maxiter, callback=callback2) if verb: r0 = norm(np.ravel(b) - np.ravel(A * x0)) rk = norm(np.ravel(b) - np.ravel(A * x)) if r0 != 0.0: print(" Residual reduction ||r_k||/||r_0|| = %1.2e" % (rk / r0)) else: print(" Residuals ||r_k||, ||r_0|| = %1.2e, %1.2e" % (rk, r0)) if return_solver: return (x.reshape(b.shape), existing_solver) else: return x.reshape(b.shape)
def polynomial(A, x, b, coefficients, iterations=1): """Apply a polynomial smoother to the system Ax=b Parameters ---------- A : sparse matrix Sparse NxN matrix x : ndarray Approximate solution (length N) b : ndarray Right-hand side (length N) coefficients : {array_like} Coefficients of the polynomial. See Notes section for details. iterations : int Number of iterations to perform Returns ------- Nothing, x will be modified in place. Notes ----- The smoother has the form x[:] = x + p(A) (b - A*x) where p(A) is a polynomial in A whose scalar coefficients are specified (in descending order) by argument 'coefficients'. - Richardson iteration p(A) = c_0: polynomial_smoother(A, x, b, [c_0]) - Linear smoother p(A) = c_1*A + c_0: polynomial_smoother(A, x, b, [c_1, c_0]) - Quadratic smoother p(A) = c_2*A^2 + c_1*A + c_0: polynomial_smoother(A, x, b, [c_2, c_1, c_0]) Here, Horner's Rule is applied to avoid computing A^k directly. For efficience, the method detects the case x = 0 one matrix-vector product is avoided (since (b - A*x) is b). Examples -------- >>> ## The polynomial smoother is not currently used directly >>> ## in PyAMG. It is only used by the chebyshev smoothing option, >>> ## which automatically calculates the correct coefficients. >>> from pyamg.gallery import poisson >>> from pyamg.util.linalg import norm >>> import numpy >>> from pyamg.aggregation import smoothed_aggregation_solver >>> A = poisson((10,10), format='csr') >>> b = numpy.ones((A.shape[0],1)) >>> sa = smoothed_aggregation_solver(A, B=numpy.ones((A.shape[0],1)), ... coarse_solver='pinv2', max_coarse=50, ... presmoother=('chebyshev', {'degree':3, 'iterations':1}), ... postsmoother=('chebyshev', {'degree':3, 'iterations':1})) >>> x0=numpy.zeros((A.shape[0],1)) >>> residuals=[] >>> x = sa.solve(b, x0=x0, tol=1e-8, residuals=residuals) """ A,x,b = make_system(A, x, b, formats=None) for i in range(iterations): from pyamg.util.linalg import norm if norm(x) == 0: residual = b else: residual = (b - A*x) h = coefficients[0]*residual for c in coefficients[1:]: h = c*residual + A*h x += h
def fgmres(A, b, x0=None, tol=1e-5, restrt=None, maxiter=None, xtype=None, M=None, callback=None, residuals=None): '''Flexible Generalized Minimum Residual Method (fGMRES) fGMRES iteratively refines the initial solution guess to the system Ax = b. fGMRES is flexible in the sense that the right preconditioner (M) can vary from iteration to iteration. Parameters ---------- A : {array, matrix, sparse matrix, LinearOperator} n x n, linear system to solve b : {array, matrix} right hand side, shape is (n,) or (n,1) x0 : {array, matrix} initial guess, default is a vector of zeros tol : float relative convergence tolerance, i.e. tol is scaled by ||r_0||_2 restrt : {None, int} - if int, restrt is max number of inner iterations and maxiter is the max number of outer iterations - if None, do not restart GMRES, and max number of inner iterations is maxiter maxiter : {None, int} - if restrt is None, maxiter is the max number of inner iterations and GMRES does not restart - if restrt is int, maxiter is the max number of outer iterations, and restrt is the max number of inner iterations xtype : type dtype for the solution, default is automatic type detection M : {array, matrix, sparse matrix, LinearOperator} n x n, inverted preconditioner, i.e. solve A M x = M b. M need not be stationary for fgmres callback : function User-supplied function is called after each iteration as callback( ||rk||_2 ), where rk is the current residual vector residuals : list residuals has the residual norm history, including the initial residual, appended to it Returns ------- (xNew, info) xNew : an updated guess to the solution of Ax = b info : halting status of gmres == ============================================= 0 successful exit >0 convergence to tolerance not achieved, return iteration count instead. This value is precisely the order of the Krylov space. <0 numerical breakdown, or illegal input == ============================================= Notes ----- - The LinearOperator class is in scipy.sparse.linalg.interface. Use this class if you prefer to define A or M as a mat-vec routine as opposed to explicitly constructing the matrix. A.psolve(..) is still supported as a legacy. - fGMRES allows for non-stationary preconditioners, as opposed to GMRES - For robustness, Householder reflections are used to orthonormalize the Krylov Space Givens Rotations are used to provide the residual norm each iteration Flexibility implies that the right preconditioner, M or A.psolve, can vary from iteration to iteration Examples -------- >>> from pyamg.krylov.fgmres import fgmres >>> from pyamg.util.linalg import norm >>> import numpy as np >>> from pyamg.gallery import poisson >>> A = poisson((10,10)) >>> b = np.ones((A.shape[0],)) >>> (x,flag) = fgmres(A,b, maxiter=2, tol=1e-8) >>> print norm(b - A*x) 6.5428213057 References ---------- .. [1] Yousef Saad, "Iterative Methods for Sparse Linear Systems, Second Edition", SIAM, pp. 151-172, pp. 272-275, 2003 http://www-users.cs.umn.edu/~saad/books.html ''' # Convert inputs to linear system, with error checking A, M, x, b, postprocess = make_system(A, M, x0, b, xtype) dimen = A.shape[0] # Ensure that warnings are always reissued from this function import warnings warnings.filterwarnings('always', module='pyamg\.krylov\._fgmres') # Choose type if not hasattr(A, 'dtype'): Atype = upcast(x.dtype, b.dtype) else: Atype = A.dtype if not hasattr(M, 'dtype'): Mtype = upcast(x.dtype, b.dtype) else: Mtype = M.dtype xtype = upcast(Atype, x.dtype, b.dtype, Mtype) # Should norm(r) be kept if residuals == []: keep_r = True else: keep_r = False # Set number of outer and inner iterations if restrt: if maxiter: max_outer = maxiter else: max_outer = 1 if restrt > dimen: warn('Setting number of inner iterations (restrt) to maximum \ allowed, which is A.shape[0] ') restrt = dimen max_inner = restrt else: max_outer = 1 if maxiter > dimen: warn('Setting number of inner iterations (maxiter) to maximum \ allowed, which is A.shape[0] ') maxiter = dimen elif maxiter is None: maxiter = min(dimen, 40) max_inner = maxiter # Get fast access to underlying BLAS routines [rotg] = get_blas_funcs(['rotg'], [x]) # Is this a one dimensional matrix? if dimen == 1: entry = ravel(A*array([1.0], dtype=xtype)) return (postprocess(b/entry), 0) # Prep for method r = b - ravel(A*x) normr = norm(r) if keep_r: residuals.append(normr) # Check initial guess ( scaling by b, if b != 0, # must account for case when norm(b) is very small) normb = norm(b) if normb == 0.0: normb = 1.0 if normr < tol*normb: if callback is not None: callback(norm(r)) return (postprocess(x), 0) # Scale tol by ||r_0||_2, we don't use the preconditioned # residual because this is right preconditioned GMRES. if normr != 0.0: tol = tol*normr # Use separate variable to track iterations. If convergence fails, # we cannot simply report niter = (outer-1)*max_outer + inner. Numerical # error could cause the inner loop to halt while the actual ||r|| > tol. niter = 0 # Begin fGMRES for outer in range(max_outer): # Calculate vector w, which defines the Householder reflector # Take shortcut in calculating, # w = r + sign(r[1])*||r||_2*e_1 w = r beta = mysign(w[0])*normr w[0] += beta w /= norm(w) # Preallocate for Krylov vectors, Householder reflectors and Hessenberg # matrix # Space required is O(dimen*max_inner) # Givens Rotations Q = zeros((4*max_inner,), dtype=xtype) # upper Hessenberg matrix (made upper tri with Givens Rotations) H = zeros((max_inner, max_inner), dtype=xtype) W = zeros((max_inner, dimen), dtype=xtype) # Householder reflectors # For fGMRES, preconditioned vectors must be stored # No Horner-like scheme exists that allow us to avoid this Z = zeros((dimen, max_inner), dtype=xtype) W[0, :] = w # Multiply r with (I - 2*w*w.T), i.e. apply the Householder reflector # This is the RHS vector for the problem in the Krylov Space g = zeros((dimen,), dtype=xtype) g[0] = -beta for inner in range(max_inner): # Calculate Krylov vector in two steps # (1) Calculate v = P_j = (I - 2*w*w.T)v, where k = inner v = -2.0*conjugate(w[inner])*w v[inner] += 1.0 # (2) Calculate the rest, v = P_1*P_2*P_3...P_{j-1}*ej. # for j in range(inner-1,-1,-1): # v = v - 2.0*dot(conjugate(W[j,:]), v)*W[j,:] amg_core.apply_householders(v, ravel(W), dimen, inner-1, -1, -1) # Apply preconditioner v = ravel(M*v) # Check for nan, inf # if isnan(v).any() or isinf(v).any(): # warn('inf or nan after application of preconditioner') # return(postprocess(x), -1) Z[:, inner] = v # Calculate new search direction v = ravel(A*v) # Factor in all Householder orthogonal reflections on new search # direction # for j in range(inner+1): # v = v - 2.0*dot(conjugate(W[j,:]), v)*W[j,:] amg_core.apply_householders(v, ravel(W), dimen, 0, inner+1, 1) # Calculate next Householder reflector, w # w = v[inner+1:] + sign(v[inner+1])*||v[inner+1:]||_2*e_{inner+1) # Note that if max_inner = dimen, then this is unnecessary for # the last inner iteration, when inner = dimen-1. Here we do # not need to calculate a Householder reflector or Givens # rotation because nnz(v) is already the desired length, # i.e. we do not need to zero anything out. if inner != dimen-1: if inner < (max_inner-1): w = W[inner+1, :] vslice = v[inner+1:] alpha = norm(vslice) if alpha != 0: alpha = mysign(vslice[0])*alpha # do not need the final reflector for future calculations if inner < (max_inner-1): w[inner+1:] = vslice w[inner+1] += alpha w /= norm(w) # Apply new reflector to v # v = v - 2.0*w*(w.T*v) v[inner+1] = -alpha v[inner+2:] = 0.0 if inner > 0: # Apply all previous Givens Rotations to v amg_core.apply_givens(Q, v, dimen, inner) # Calculate the next Givens rotation, where j = inner Note that if # max_inner = dimen, then this is unnecessary for the last inner # iteration, when inner = dimen-1. Here we do not need to # calculate a Householder reflector or Givens rotation because # nnz(v) is already the desired length, i.e. we do not need to zero # anything out. if inner != dimen-1: if v[inner+1] != 0: [c, s] = rotg(v[inner], v[inner+1]) Qblock = array([[c, s], [-conjugate(s), c]], dtype=xtype) Q[(inner*4): ((inner+1)*4)] = ravel(Qblock).copy() # Apply Givens Rotation to g, the RHS for the linear system # in the Krylov Subspace. Note that this dot does a matrix # multiply, not an actual dot product where a conjugate # transpose is taken g[inner:inner+2] = dot(Qblock, g[inner:inner+2]) # Apply effect of Givens Rotation to v v[inner] = dot(Qblock[0, :], v[inner:inner+2]) v[inner+1] = 0.0 # Write to upper Hessenberg Matrix, # the LHS for the linear system in the Krylov Subspace H[:, inner] = v[0:max_inner] # Don't update normr if last inner iteration, because # normr is calculated directly after this loop ends. if inner < max_inner-1: normr = abs(g[inner+1]) if normr < tol: break # Allow user access to residual if callback is not None: callback(normr) if keep_r: residuals.append(normr) niter += 1 # end inner loop, back to outer loop # Find best update to x in Krylov Space, V. Solve inner+1 x inner+1 # system. Apparently this is the best way to solve a triangular system # in the magical world of scipy # piv = arange(inner+1) # y = lu_solve((H[0:(inner+1),0:(inner+1)], piv), # g[0:(inner+1)], trans=0) y = sp.linalg.solve(H[0:(inner+1), 0:(inner+1)], g[0:(inner+1)]) # No Horner like scheme exists because the preconditioner can change # each iteration # Hence, we must store each preconditioned vector update = dot(Z[:, 0:inner+1], y) x = x + update r = b - ravel(A*x) normr = norm(r) # Allow user access to residual if callback is not None: callback(normr) if keep_r: residuals.append(normr) # Has fGMRES stagnated? indices = (x != 0) if indices.any(): change = max(abs(update[indices] / x[indices])) if change < 1e-12: # No change, halt return (postprocess(x), -1) # test for convergence if normr < tol: return (postprocess(x), 0) # end outer loop return (postprocess(x), niter)
def test_krylov(self): # Oblique projectors reduce the residual for method in self.oblique: for case in self.cases: A = case['A'] b = case['b'] x0 = case['x0'] factor = case['reduction_factor'] xNew, _ = method(A, b, x0=x0, tol=case['tol'], maxiter=case['maxiter']) xNew = xNew.reshape(-1, 1) assert_equal( (norm(b - A.dot(xNew)) / norm(b - A.dot(x0))) < factor, True, err_msg='Oblique Krylov Method Failed Test') # Oblique projectors reduce the residual, here we consider oblique # projectors for symmetric matrices for method in self.symm_oblique: for case in self.symm_cases: A = case['A'] b = case['b'] x0 = case['x0'] factor = case['reduction_factor'] xNew, _ = method(A, b, x0=x0, tol=case['tol'], maxiter=case['maxiter']) xNew = xNew.reshape(-1, 1) assert_equal( (norm(b - A.dot(xNew)) / norm(b - A.dot(x0))) < factor, True, err_msg='Symmetric oblique Krylov Method Failed') # Orthogonal projectors reduce the error for method in self.orth: for case in self.cases: A = case['A'] b = case['b'] x0 = case['x0'] factor = case['reduction_factor'] xNew, _ = method(A, b, x0=x0, tol=case['tol'], maxiter=case['maxiter']) xNew = xNew.reshape(-1, 1) soln = solve(A, b) assert_equal((norm(soln - xNew) / norm(soln - x0)) < factor, True, err_msg='Orthogonal Krylov Method Failed Test') # SPD Orthogonal projectors reduce the error for method in self.spd_orth: for case in self.spd_cases: A = case['A'] b = case['b'] x0 = case['x0'] factor = case['reduction_factor'] xNew, _ = method(A, b, x0=x0, tol=case['tol'], maxiter=case['maxiter']) xNew = xNew.reshape(-1, 1) soln = solve(A, b) assert_equal((norm(soln - xNew) / norm(soln - x0)) < factor, True, err_msg='Orthogonal Krylov Method Failed Test') # Assume that Inexact Methods reduce the residual for these examples for method in self.inexact: for case in self.cases: A = case['A'] b = case['b'] x0 = case['x0'] xNew, _ = method(A, b, x0=x0, tol=case['tol'], maxiter=A.shape[0]) xNew = xNew.reshape(-1, 1) assert_equal( (norm(b - A.dot(xNew)) / norm(b - A.dot(x0))) < 0.35, True, err_msg='Inexact Krylov Method Failed Test')
def fgmres(A, b, x0=None, tol=1e-5, restrt=None, maxiter=None, M=None, callback=None, residuals=None): """Flexible Generalized Minimum Residual Method (fGMRES). fGMRES iteratively refines the initial solution guess to the system Ax = b. fGMRES is flexible in the sense that the right preconditioner (M) can vary from iteration to iteration. Parameters ---------- A : array, matrix, sparse matrix, LinearOperator n x n, linear system to solve b : array, matrix right hand side, shape is (n,) or (n,1) x0 : array, matrix initial guess, default is a vector of zeros tol : float Tolerance for stopping criteria, let r=r_k ||r|| < tol ||b|| if ||b||=0, then set ||b||=1 for these tests. restrt : None, int - if int, restrt is max number of inner iterations and maxiter is the max number of outer iterations - if None, do not restart GMRES, and max number of inner iterations is maxiter maxiter : None, int - if restrt is None, maxiter is the max number of inner iterations and GMRES does not restart - if restrt is int, maxiter is the max number of outer iterations, and restrt is the max number of inner iterations - defaults to min(n,40) if restart=None M : array, matrix, sparse matrix, LinearOperator n x n, inverted preconditioner, i.e. solve M A x = M b. M need not be stationary for fgmres callback : function User-supplied function is called after each iteration as callback(xk), where xk is the current solution vector residuals : list residual history in the 2-norm, including the initial residual reorth : boolean If True, then a check is made whether to re-orthogonalize the Krylov space each GMRES iteration Returns ------- xk, info xk : an updated guess after k iterations to the solution of Ax = b info : halting status == ======================================= 0 successful exit >0 convergence to tolerance not achieved, return iteration count instead. <0 numerical breakdown, or illegal input == ======================================= Notes ----- The LinearOperator class is in scipy.sparse.linalg.interface. Use this class if you prefer to define A or M as a mat-vec routine as opposed to explicitly constructing the matrix. fGMRES allows for non-stationary preconditioners, as opposed to GMRES For robustness, Householder reflections are used to orthonormalize the Krylov Space Givens Rotations are used to provide the residual norm each iteration Flexibility implies that the right preconditioner, M, can vary from iteration to iteration Examples -------- >>> from pyamg.krylov import fgmres >>> from pyamg.util.linalg import norm >>> import numpy as np >>> from pyamg.gallery import poisson >>> A = poisson((10,10)) >>> b = np.ones((A.shape[0],)) >>> (x,flag) = fgmres(A,b, maxiter=2, tol=1e-8) >>> print(f'{norm(b - A*x):.6}') 6.54282 References ---------- .. [1] Yousef Saad, "Iterative Methods for Sparse Linear Systems, Second Edition", SIAM, pp. 151-172, pp. 272-275, 2003 http://www-users.cs.umn.edu/~saad/books.html """ # Convert inputs to linear system, with error checking A, M, x, b, postprocess = make_system(A, M, x0, b) n = A.shape[0] # Ensure that warnings are always reissued from this function warnings.filterwarnings('always', module='pyamg.krylov._fgmres') # Get fast access to underlying BLAS routines [lartg] = get_lapack_funcs(['lartg'], [x]) # Set number of outer and inner iterations # If no restarts, # then set max_inner=maxiter and max_outer=n # If restarts are set, # then set max_inner=restart and max_outer=maxiter if restrt: if maxiter: max_outer = maxiter else: max_outer = 1 if restrt > n: warn('Setting restrt to maximum allowed, n.') restrt = n max_inner = restrt else: max_outer = 1 if maxiter > n: warn('Setting maxiter to maximum allowed, n.') maxiter = n elif maxiter is None: maxiter = min(n, 40) max_inner = maxiter # Is this a one dimensional matrix? if n == 1: entry = np.ravel(A @ np.array([1.0], dtype=x.dtype)) return (postprocess(b / entry), 0) # Prep for method r = b - A @ x normr = norm(r) if residuals is not None: residuals[:] = [normr] # initial residual # Check initial guess if b != 0, normb = norm(b) if normb == 0.0: normb = 1.0 # reset so that tol is unscaled if normr < tol * normb: return (postprocess(x), 0) # Use separate variable to track iterations. If convergence fails, # we cannot simply report niter = (outer-1)*max_outer + inner. Numerical # error could cause the inner loop to halt while the actual ||r|| > tol. niter = 0 # Begin fGMRES for _outer in range(max_outer): # Calculate vector w, which defines the Householder reflector # Take shortcut in calculating, # w = r + sign(r[1])*||r||_2*e_1 w = r beta = _mysign(w[0]) * normr w[0] += beta w /= norm(w) # Preallocate for Krylov vectors, Householder reflectors and Hessenberg # matrix # Space required is O(n*max_inner) # Givens Rotations Q = np.zeros((4 * max_inner, ), dtype=x.dtype) # upper Hessenberg matrix (made upper tri with Givens Rotations) H = np.zeros((max_inner, max_inner), dtype=x.dtype) W = np.zeros((max_inner, n), dtype=x.dtype) # Householder reflectors # For fGMRES, preconditioned vectors must be stored # No Horner-like scheme exists that allow us to avoid this Z = np.zeros((n, max_inner), dtype=x.dtype) W[0, :] = w # Multiply r with (I - 2*w*w.T), i.e. apply the Householder reflector # This is the RHS vector for the problem in the Krylov Space g = np.zeros((n, ), dtype=x.dtype) g[0] = -beta for inner in range(max_inner): # Calculate Krylov vector in two steps # (1) Calculate v = P_j = (I - 2*w*w.T)v, where k = inner v = -2.0 * np.conjugate(w[inner]) * w v[inner] += 1.0 # (2) Calculate the rest, v = P_1*P_2*P_3...P_{j-1}*ej. # for j in range(inner-1,-1,-1): # v = v - 2.0*dot(conjugate(W[j,:]), v)*W[j,:] amg_core.apply_householders(v, np.ravel(W), n, inner - 1, -1, -1) # Apply preconditioner v = M @ v # Check for nan, inf # if isnan(v).any() or isinf(v).any(): # warn('inf or nan after application of preconditioner') # return(postprocess(x), -1) Z[:, inner] = v # Calculate new search direction v = A @ v # Factor in all Householder orthogonal reflections on new search # direction # for j in range(inner+1): # v = v - 2.0*dot(conjugate(W[j,:]), v)*W[j,:] amg_core.apply_householders(v, np.ravel(W), n, 0, inner + 1, 1) # Calculate next Householder reflector, w # w = v[inner+1:] + sign(v[inner+1])*||v[inner+1:]||_2*e_{inner+1) # Note that if max_inner = n, then this is unnecessary for # the last inner iteration, when inner = n-1. Here we do # not need to calculate a Householder reflector or Givens # rotation because nnz(v) is already the desired length, # i.e. we do not need to zero anything out. if inner != n - 1: if inner < (max_inner - 1): w = W[inner + 1, :] vslice = v[inner + 1:] alpha = norm(vslice) if alpha != 0: alpha = _mysign(vslice[0]) * alpha # do not need the final reflector for future calculations if inner < (max_inner - 1): w[inner + 1:] = vslice w[inner + 1] += alpha w /= norm(w) # Apply new reflector to v # v = v - 2.0*w*(w.T*v) v[inner + 1] = -alpha v[inner + 2:] = 0.0 if inner > 0: # Apply all previous Givens Rotations to v amg_core.apply_givens(Q, v, n, inner) # Calculate the next Givens rotation, where j = inner Note that if # max_inner = n, then this is unnecessary for the last inner # iteration, when inner = n-1. Here we do not need to # calculate a Householder reflector or Givens rotation because # nnz(v) is already the desired length, i.e. we do not need to zero # anything out. if inner != n - 1: if v[inner + 1] != 0: [c, s, r] = lartg(v[inner], v[inner + 1]) Qblock = np.array([[c, s], [-np.conjugate(s), c]], dtype=x.dtype) Q[(inner * 4):((inner + 1) * 4)] = np.ravel(Qblock).copy() # Apply Givens Rotation to g, the RHS for the linear system # in the Krylov Subspace. Note that this dot does a matrix # multiply, not an actual dot product where a conjugate # transpose is taken g[inner:inner + 2] = np.dot(Qblock, g[inner:inner + 2]) # Apply effect of Givens Rotation to v v[inner] = np.dot(Qblock[0, :], v[inner:inner + 2]) v[inner + 1] = 0.0 # Write to upper Hessenberg Matrix, # the LHS for the linear system in the Krylov Subspace H[:, inner] = v[0:max_inner] # Don't update normr if last inner iteration, because # normr is calculated directly after this loop ends. if inner < max_inner - 1: normr = np.abs(g[inner + 1]) if normr < tol * normb: break if residuals is not None: residuals.append(normr) if callback is not None: y = sp.linalg.solve(H[0:(inner + 1), 0:(inner + 1)], g[0:(inner + 1)]) update = np.dot(Z[:, 0:inner + 1], y) callback(x + update) niter += 1 # end inner loop, back to outer loop # Find best update to x in Krylov Space, V. Solve inner+1 x inner+1 # system. Apparently this is the best way to solve a triangular system # in the magical world of scipy # piv = arange(inner+1) # y = lu_solve((H[0:(inner+1),0:(inner+1)], piv), # g[0:(inner+1)], trans=0) y = sp.linalg.solve(H[0:(inner + 1), 0:(inner + 1)], g[0:(inner + 1)]) # No Horner like scheme exists because the preconditioner can change # each iteration # Hence, we must store each preconditioned vector update = np.dot(Z[:, 0:inner + 1], y) x = x + update r = b - A @ x # Allow user access to the iterates if callback is not None: callback(x) normr = norm(r) if residuals is not None: residuals.append(normr) # Has fGMRES stagnated? indices = (x != 0) if indices.any(): change = np.max(np.abs(update[indices] / x[indices])) if change < 1e-12: # No change, halt return (postprocess(x), -1) # test for convergence if normr < tol * normb: return (postprocess(x), 0) # end outer loop return (postprocess(x), niter)
# x0 = random((4,1)) # %timeit -n 15 (x,flag) = gmres(A,b,x0,tol=1e-8,maxiter=100) from pyamg.gallery import poisson from numpy.random import random from pyamg.util.linalg import norm A = poisson((125, 125), dtype=float, format='csr') # A.data = A.data + 0.001j*rand(A.data.shape[0]) b = random((A.shape[0], )) x0 = random((A.shape[0], )) import time from scipy.sparse.linalg.isolve import gmres as igmres print('\n\nTesting GMRES with %d x %d 2D Laplace Matrix' % (A.shape[0], A.shape[0])) t1 = time.time() (x, flag) = gmres_mgs(A, b, x0, tol=1e-8, maxiter=500) t2 = time.time() print('%s took %0.3f ms' % ('gmres', (t2 - t1) * 1000.0)) print('norm = %g' % (norm(b - A * x))) print('info flag = %d' % (flag)) t1 = time.time() # DON"T Enforce a maxiter as scipy gmres can't handle it correctly (y, flag) = igmres(A, b, x0, tol=1e-8) t2 = time.time() print('\n%s took %0.3f ms' % ('linalg gmres', (t2 - t1) * 1000.0)) print('norm = %g' % (norm(b - A * y))) print('info flag = %d' % (flag))
def test_approximate_spectral_radius(self): np.random.seed(3456) cases = [] cases.append(np.array([[-4]])) cases.append(np.array([[2, 0], [0, 1]])) cases.append(np.array([[-2, 0], [0, 1]])) cases.append(np.array([[100, 0, 0], [0, 101, 0], [0, 0, 99]])) for i in range(1, 5): cases.append(np.random.rand(i, i)) # method should be almost exact for small matrices for A in cases: A = A.astype(float) Asp = csr_matrix(A) [E, V] = linalg.eig(A) E = np.abs(E) largest_eig = (E == E.max()).nonzero()[0] expected_eig = E[largest_eig] expected_vec = V[:, largest_eig] assert_almost_equal(approximate_spectral_radius(A), expected_eig) assert_almost_equal(approximate_spectral_radius(Asp), expected_eig) vec = approximate_spectral_radius(A, return_vector=True)[1] minnorm = min(norm(expected_vec + vec), norm(expected_vec - vec)) diff = minnorm / norm(expected_vec) assert_almost_equal(diff, 0.0, decimal=4) vec = approximate_spectral_radius(Asp, return_vector=True)[1] minnorm = min(norm(expected_vec + vec), norm(expected_vec - vec)) diff = minnorm / norm(expected_vec) assert_almost_equal(diff, 0.0, decimal=4) # try symmetric matrices for A in cases: A = A + A.transpose() A = A.astype(float) Asp = csr_matrix(A) [E, V] = linalg.eig(A) E = np.abs(E) largest_eig = (E == E.max()).nonzero()[0] expected_eig = E[largest_eig] expected_vec = V[:, largest_eig] assert_almost_equal(approximate_spectral_radius(A), expected_eig) assert_almost_equal(approximate_spectral_radius(Asp), expected_eig) vec = approximate_spectral_radius(A, return_vector=True)[1] minnorm = min(norm(expected_vec + vec), norm(expected_vec - vec)) diff = minnorm / norm(expected_vec) assert_almost_equal(diff, 0.0, decimal=4) vec = approximate_spectral_radius(Asp, return_vector=True)[1] minnorm = min(norm(expected_vec + vec), norm(expected_vec - vec)) diff = minnorm / norm(expected_vec) assert_almost_equal(diff, 0.0, decimal=4) # test a larger matrix, and various parameter choices cases = [] A1 = gallery.poisson((50, 50), format='csr') cases.append((A1, 7.99241331495)) A2 = gallery.elasticity.linear_elasticity((32, 32), format='bsr')[0] cases.append((A2, 536549.922189)) for A, expected in cases: # test that increasing maxiter increases accuracy ans1 = approximate_spectral_radius(A, tol=1e-16, maxiter=5, restart=0) del A.rho ans2 = approximate_spectral_radius(A, tol=1e-16, maxiter=15, restart=0) del A.rho assert_equal(abs(ans2 - expected) < 0.5*abs(ans1 - expected), True) # test that increasing restart increases accuracy ans1 = approximate_spectral_radius(A, tol=1e-16, maxiter=10, restart=0) del A.rho ans2 = approximate_spectral_radius(A, tol=1e-16, maxiter=10, restart=1) del A.rho assert_equal(abs(ans2 - expected) < 0.8*abs(ans1 - expected), True) # test tol ans1 = approximate_spectral_radius(A, tol=0.1, maxiter=15, restart=5) del A.rho assert_equal(abs(ans1 - expected)/abs(expected) < 0.1, True) ans2 = approximate_spectral_radius(A, tol=0.001, maxiter=15, restart=5) del A.rho assert_equal(abs(ans2 - expected)/abs(expected) < 0.001, True) assert_equal(abs(ans2 - expected) < 0.1*abs(ans1 - expected), True)
def steepest_descent(A, b, x0=None, tol=1e-5, maxiter=None, xtype=None, M=None, callback=None, residuals=None): '''Steepest descent algorithm Solves the linear system Ax = b. Left preconditioning is supported. Parameters ---------- A : {array, matrix, sparse matrix, LinearOperator} n x n, linear system to solve b : {array, matrix} right hand side, shape is (n,) or (n,1) x0 : {array, matrix} initial guess, default is a vector of zeros tol : float relative convergence tolerance, i.e. tol is scaled by the preconditioner norm of r_0, or ||r_0||_M. maxiter : int maximum number of allowed iterations xtype : type dtype for the solution, default is automatic type detection M : {array, matrix, sparse matrix, LinearOperator} n x n, inverted preconditioner, i.e. solve M A x = M b. callback : function User-supplied function is called after each iteration as callback(xk), where xk is the current solution vector residuals : list residuals contains the residual norm history, including the initial residual. The preconditioner norm is used, instead of the Euclidean norm. Returns ------- (xNew, info) xNew : an updated guess to the solution of Ax = b info : halting status of cg == ======================================= 0 successful exit >0 convergence to tolerance not achieved, return iteration count instead. <0 numerical breakdown, or illegal input == ======================================= Notes ----- The LinearOperator class is in scipy.sparse.linalg.interface. Use this class if you prefer to define A or M as a mat-vec routine as opposed to explicitly constructing the matrix. A.psolve(..) is still supported as a legacy. The residual in the preconditioner norm is both used for halting and returned in the residuals list. Examples -------- >>> from pyamg.krylov import steepest_descent >>> from pyamg.util.linalg import norm >>> import numpy >>> from pyamg.gallery import poisson >>> A = poisson((10,10)) >>> b = numpy.ones((A.shape[0],)) >>> (x,flag) = steepest_descent(A,b, maxiter=2, tol=1e-8) >>> print norm(b - A*x) 7.89436429704 References ---------- .. [1] Yousef Saad, "Iterative Methods for Sparse Linear Systems, Second Edition", SIAM, pp. 137--142, 2003 http://www-users.cs.umn.edu/~saad/books.html ''' A, M, x, b, postprocess = make_system(A, M, x0, b, xtype=None) n = len(b) ## # Ensure that warnings are always reissued from this function import warnings warnings.filterwarnings('always', module='pyamg\.krylov\._steepest_descent') # determine maxiter if maxiter is None: maxiter = int(len(b)) elif maxiter < 1: raise ValueError('Number of iterations must be positive') # setup method r = b - A * x z = M * r rz = inner(r.conjugate(), z) # use preconditioner norm normr = sqrt(rz) if residuals is not None: residuals[:] = [normr] #initial residual # Check initial guess ( scaling by b, if b != 0, # must account for case when norm(b) is very small) normb = norm(b) if normb == 0.0: normb = 1.0 if normr < tol * normb: return (postprocess(x), 0) # Scale tol by ||r_0||_M if normr != 0.0: tol = tol * normr # How often should r be recomputed recompute_r = 50 iter = 0 while True: iter = iter + 1 q = A * z zAz = inner(z.conjugate(), q) # check curvature of A if zAz < 0.0: warn( "\nIndefinite matrix detected in steepest descent, aborting\n") return (postprocess(x), -1) alpha = rz / zAz # step size x = x + alpha * z if mod(iter, recompute_r) and iter > 0: r = b - A * x else: r = r - alpha * q z = M * r rz = inner(r.conjugate(), z) if rz < 0.0: # check curvature of M warn( "\nIndefinite preconditioner detected in steepest descent, aborting\n" ) return (postprocess(x), -1) normr = sqrt(rz) # use preconditioner norm if residuals is not None: residuals.append(normr) if callback is not None: callback(x) if normr < tol: return (postprocess(x), 0) elif rz == 0.0: # important to test after testing normr < tol. rz == 0.0 is an # indicator of convergence when r = 0.0 warn( "\nSingular preconditioner detected in steepest descent, ceasing iterations\n" ) return (postprocess(x), -1) if iter == maxiter: return (postprocess(x), iter)
def bicgstab(A, b, x0=None, tol=1e-5, maxiter=None, xtype=None, M=None, callback=None, residuals=None): '''Biconjugate Gradient Algorithm with Stabilization Solves the linear system Ax = b. Left preconditioning is supported. Parameters ---------- A : {array, matrix, sparse matrix, LinearOperator} n x n, linear system to solve b : {array, matrix} right hand side, shape is (n,) or (n,1) x0 : {array, matrix} initial guess, default is a vector of zeros tol : float relative convergence tolerance, i.e. tol is scaled by ||r_0||_2 maxiter : int maximum number of allowed iterations xtype : type dtype for the solution, default is automatic type detection M : {array, matrix, sparse matrix, LinearOperator} n x n, inverted preconditioner, i.e. solve M A A.H x = M b. callback : function User-supplied function is called after each iteration as callback(xk), where xk is the current solution vector residuals : list residuals has the residual norm history, including the initial residual, appended to it Returns ------- (xNew, info) xNew : an updated guess to the solution of Ax = b info : halting status of bicgstab == ====================================== 0 successful exit >0 convergence to tolerance not achieved, return iteration count instead. <0 numerical breakdown, or illegal input == ====================================== Notes ----- The LinearOperator class is in scipy.sparse.linalg.interface. Use this class if you prefer to define A or M as a mat-vec routine as opposed to explicitly constructing the matrix. A.psolve(..) is still supported as a legacy. Examples -------- >>> from pyamg.krylov.bicgstab import bicgstab >>> from pyamg.util.linalg import norm >>> import numpy as np >>> from pyamg.gallery import poisson >>> A = poisson((10,10)) >>> b = np.ones((A.shape[0],)) >>> (x,flag) = bicgstab(A,b, maxiter=2, tol=1e-8) >>> print norm(b - A*x) 4.68163045309 References ---------- .. [1] Yousef Saad, "Iterative Methods for Sparse Linear Systems, Second Edition", SIAM, pp. 231-234, 2003 http://www-users.cs.umn.edu/~saad/books.html ''' # Convert inputs to linear system, with error checking A, M, x, b, postprocess = make_system(A, M, x0, b, xtype) # Ensure that warnings are always reissued from this function import warnings warnings.filterwarnings('always', module='pyamg\.krylov\._bicgstab') # Check iteration numbers if maxiter is None: maxiter = len(x) + 5 elif maxiter < 1: raise ValueError('Number of iterations must be positive') # Prep for method r = b - A*x normr = norm(r) if residuals is not None: residuals[:] = [normr] # Check initial guess ( scaling by b, if b != 0, # must account for case when norm(b) is very small) normb = norm(b) if normb == 0.0: normb = 1.0 if normr < tol*normb: return (postprocess(x), 0) # Scale tol by ||r_0||_2 if normr != 0.0: tol = tol*normr # Is this a one dimensional matrix? if A.shape[0] == 1: entry = np.ravel(A*np.array([1.0], dtype=xtype)) return (postprocess(b/entry), 0) rstar = r.copy() p = r.copy() rrstarOld = np.inner(rstar.conjugate(), r) iter = 0 # Begin BiCGStab while True: Mp = M*p AMp = A*Mp # alpha = (r_j, rstar) / (A*M*p_j, rstar) alpha = rrstarOld/np.inner(rstar.conjugate(), AMp) # s_j = r_j - alpha*A*M*p_j s = r - alpha*AMp Ms = M*s AMs = A*Ms # omega = (A*M*s_j, s_j)/(A*M*s_j, A*M*s_j) omega = np.inner(AMs.conjugate(), s)/np.inner(AMs.conjugate(), AMs) # x_{j+1} = x_j + alpha*M*p_j + omega*M*s_j x = x + alpha*Mp + omega*Ms # r_{j+1} = s_j - omega*A*M*s r = s - omega*AMs # beta_j = (r_{j+1}, rstar)/(r_j, rstar) * (alpha/omega) rrstarNew = np.inner(rstar.conjugate(), r) beta = (rrstarNew / rrstarOld) * (alpha / omega) rrstarOld = rrstarNew # p_{j+1} = r_{j+1} + beta*(p_j - omega*A*M*p) p = r + beta*(p - omega*AMp) iter += 1 normr = norm(r) if residuals is not None: residuals.append(normr) if callback is not None: callback(x) if normr < tol: return (postprocess(x), 0) if iter == maxiter: return (postprocess(x), iter)
def bicgstab(A, b, x0=None, tol=1e-5, criteria='rr', maxiter=None, M=None, callback=None, residuals=None): """Biconjugate Gradient Algorithm with Stabilization. Solves the linear system Ax = b. Left preconditioning is supported. Parameters ---------- A : array, matrix, sparse matrix, LinearOperator n x n, linear system to solve b : array, matrix right hand side, shape is (n,) or (n,1) x0 : array, matrix initial guess, default is a vector of zeros tol : float Tolerance for stopping criteria criteria : string Stopping criteria, let r=r_k, x=x_k 'rr': ||r|| < tol ||b|| 'rr+': ||r|| < tol (||b|| + ||A||_F ||x||) if ||b||=0, then set ||b||=1 for these tests. maxiter : int maximum number of iterations allowed M : array, matrix, sparse matrix, LinearOperator n x n, inverted preconditioner, i.e. solve M A x = M b. callback : function User-supplied function is called after each iteration as callback(xk), where xk is the current solution vector residuals : list residual history in the 2-norm, including the initial residual Returns ------- (xk, info) xk : an updated guess after k iterations to the solution of Ax = b info : halting status == ======================================= 0 successful exit >0 convergence to tolerance not achieved, return iteration count instead. <0 numerical breakdown, or illegal input == ======================================= Notes ----- The LinearOperator class is in scipy.sparse.linalg.interface. Use this class if you prefer to define A or M as a mat-vec routine as opposed to explicitly constructing the matrix. Examples -------- >>> from pyamg.krylov import bicgstab >>> from pyamg.util.linalg import norm >>> import numpy as np >>> from pyamg.gallery import poisson >>> A = poisson((10,10)) >>> b = np.ones((A.shape[0],)) >>> (x,flag) = bicgstab(A,b, maxiter=2, tol=1e-8) >>> print(f'{norm(b - A*x):.6}') 4.68163 References ---------- .. [1] Yousef Saad, "Iterative Methods for Sparse Linear Systems, Second Edition", SIAM, pp. 231-234, 2003 http://www-users.cs.umn.edu/~saad/books.html """ # Convert inputs to linear system, with error checking A, M, x, b, postprocess = make_system(A, M, x0, b) # Ensure that warnings are always reissued from this function warnings.filterwarnings('always', module='pyamg.krylov._bicgstab') # Check iteration numbers if maxiter is None: maxiter = len(x) + 5 elif maxiter < 1: raise ValueError('Number of iterations must be positive') # Prep for method r = b - A @ x normr = norm(r) if residuals is not None: residuals[:] = [normr] # Check initial guess, if b != 0, normb = norm(b) normb = norm(b) if normb == 0.0: normb = 1.0 # reset so that tol is unscaled # set the stopping criteria (see the docstring) if criteria == 'rr': rtol = tol * normb elif criteria == 'rr+': if sparse.issparse(A.A): normA = norm(A.A.data) elif isinstance(A.A, np.ndarray): normA = norm(np.ravel(A.A)) else: raise ValueError( 'Unable to use ||A||_F with the current matrix format.') rtol = tol * (normA * np.linalg.norm(x) + normb) else: raise ValueError('Invalid stopping criteria.') if normr < rtol: return (postprocess(x), 0) # Is thisAa one dimensional matrix? # Use a matvec to access A[0,0] if A.shape[0] == 1: entry = np.ravel(A @ np.array([1.0], dtype=x.dtype)) return (postprocess(b / entry), 0) rstar = r.copy() p = r.copy() rrstarOld = np.inner(rstar.conjugate(), r) it = 0 # Begin BiCGStab while True: Mp = M @ p AMp = A @ Mp # alpha = (r_j, rstar) / (A*M*p_j, rstar) alpha = rrstarOld / np.inner(rstar.conjugate(), AMp) # s_j = r_j - alpha*A*M*p_j s = r - alpha * AMp Ms = M @ s AMs = A @ Ms # omega = (A*M*s_j, s_j)/(A*M*s_j, A*M*s_j) omega = np.inner(AMs.conjugate(), s) / np.inner(AMs.conjugate(), AMs) # x_{j+1} = x_j + alpha*M*p_j + omega*M*s_j x = x + alpha * Mp + omega * Ms # r_{j+1} = s_j - omega*A*M*s r = s - omega * AMs # beta_j = (r_{j+1}, rstar)/(r_j, rstar) * (alpha/omega) rrstarNew = np.inner(rstar.conjugate(), r) beta = (rrstarNew / rrstarOld) * (alpha / omega) rrstarOld = rrstarNew # p_{j+1} = r_{j+1} + beta*(p_j - omega*A*M*p) p = r + beta * (p - omega * AMp) it += 1 normr = norm(r) if residuals is not None: residuals.append(normr) if callback is not None: callback(x) # set the stopping criteria (see the docstring) if criteria == 'rr': rtol = tol * normb elif criteria == 'rr+': rtol = tol * (normA * np.linalg.norm(x) + normb) if normr < rtol: return (postprocess(x), 0) if it == maxiter: return (postprocess(x), it)
def obj_fcn(alpha): c = cos((omega+alpha)*x) Ac = (A*c)[1:-1] return norm(Ac)/norm(c[1:-1])
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)