def _binopt(self, other, op): """apply the binary operation fn to two sparse matrices.""" other = self.__class__(other) # e.g. csr_plus_csr, csr_minus_csr, etc. fn = getattr(_sparsetools, self.format + op + self.format) maxnnz = self.nnz + other.nnz idx_dtype = get_index_dtype( (self.indptr, self.indices, other.indptr, other.indices), maxval=maxnnz) indptr = np.empty(self.indptr.shape, dtype=idx_dtype) indices = np.empty(maxnnz, dtype=idx_dtype) bool_ops = ['_ne_', '_lt_', '_gt_', '_le_', '_ge_'] if op in bool_ops: data = np.empty(maxnnz, dtype=np.bool_) else: data = np.empty(maxnnz, dtype=upcast(self.dtype, other.dtype)) fn(self.shape[0], self.shape[1], np.asarray(self.indptr, dtype=idx_dtype), np.asarray(self.indices, dtype=idx_dtype), self.data, np.asarray(other.indptr, dtype=idx_dtype), np.asarray(other.indices, dtype=idx_dtype), other.data, indptr, indices, data) A = self.__class__((data, indices, indptr), shape=self.shape) A.prune() return A
def tocsr(self, copy=False): """ Convert this matrix to CSRSymMatrix format. Remains symmetric Returns ------- CSRSymMatrix """ from pyomo.contrib.pynumero.sparse.csr import CSRSymMatrix if self.nnz == 0: return CSRSymMatrix(self.shape, dtype=self.dtype) else: M, N = self.shape idx_dtype = get_index_dtype((self.row, self.col), maxval=max(self.nnz, N)) row = self.row.astype(idx_dtype, copy=False) col = self.col.astype(idx_dtype, copy=False) indptr = np.empty(M + 1, dtype=idx_dtype) indices = np.empty_like(col, dtype=idx_dtype) data = np.empty_like(self.data, dtype=upcast(self.dtype)) coo_tocsr(M, N, self.nnz, row, col, self.data, indptr, indices, data) x = CSRSymMatrix((data, indices, indptr), shape=self.shape) if not self.has_canonical_format: x.sum_duplicates() return x
def _mul_sparse_matrix(self, other): M, K1 = self.shape K2, N = other.shape major_axis = self._swap((M, N))[0] other = self.__class__(other) # convert to this format idx_dtype = get_index_dtype( (self.indptr, self.indices, other.indptr, other.indices)) fn = getattr(_sparsetools, self.format + '_matmat_maxnnz') nnz = fn(M, N, np.asarray(self.indptr, dtype=idx_dtype), np.asarray(self.indices, dtype=idx_dtype), np.asarray(other.indptr, dtype=idx_dtype), np.asarray(other.indices, dtype=idx_dtype)) idx_dtype = get_index_dtype( (self.indptr, self.indices, other.indptr, other.indices), maxval=nnz) indptr = np.empty(major_axis + 1, dtype=idx_dtype) indices = np.empty(nnz, dtype=idx_dtype) data = np.empty(nnz, dtype=upcast(self.dtype, other.dtype)) fn = getattr(_sparsetools, self.format + '_matmat') fn(M, N, np.asarray(self.indptr, dtype=idx_dtype), np.asarray(self.indices, dtype=idx_dtype), self.data, np.asarray(other.indptr, dtype=idx_dtype), np.asarray(other.indices, dtype=idx_dtype), other.data, indptr, indices, data) return self.__class__((data, indices, indptr), shape=(M, N))
def __add__(self, other): # First check if argument is a scalar if isscalarlike(other): res_dtype = upcast_scalar(self.dtype, other) new = dok_matrix(self.shape, dtype=res_dtype) # Add this scalar to every element. M, N = self.shape for i in xrange(M): for j in xrange(N): aij = self.get((i, j), 0) + other if aij != 0: new[i, j] = aij # new.dtype.char = self.dtype.char elif isinstance(other, dok_matrix): if other.shape != self.shape: raise ValueError("matrix dimensions are not equal") # We could alternatively set the dimensions to the largest of # the two matrices to be summed. Would this be a good idea? res_dtype = upcast(self.dtype, other.dtype) new = dok_matrix(self.shape, dtype=res_dtype) new.update(self) for key in other.keys(): new[key] += other[key] elif isspmatrix(other): csc = self.tocsc() new = csc + other elif isdense(other): new = self.todense() + other else: raise TypeError("data type not understood") return new
def tocsc(self, copy=False): """ Convert this matrix to Compressed Sparse Column format Returns ------- CSCMatrix """ from pyomo.contrib.pynumero.sparse.csc import CSCMatrix if self.nnz == 0: return CSCMatrix(self.shape, dtype=self.dtype) else: M, N = self.shape idx_dtype = get_index_dtype((self.col, self.row), maxval=max(self.nnz, M)) row = self.row.astype(idx_dtype, copy=False) col = self.col.astype(idx_dtype, copy=False) indptr = np.empty(N + 1, dtype=idx_dtype) indices = np.empty_like(row, dtype=idx_dtype) data = np.empty_like(self.data, dtype=upcast(self.dtype)) # TODO: check why scipy does this and not coo_tocsc coo_tocsr(N, M, self.nnz, col, row, self.data, indptr, indices, data) x = CSCMatrix((data, indices, indptr), shape=self.shape) if not self.has_canonical_format: x.sum_duplicates() return x
def diagonal(self): """Returns the main diagonal of the matrix """ # TODO support k-th diagonal fn = getattr(_sparsetools, self.format + "_diagonal") y = np.empty(min(self.shape), dtype=upcast(self.dtype)) fn(self.shape[0], self.shape[1], self.indptr, self.indices, self.data, y) return y
def _mul_multivector(self, other): # matrix * multivector M, N = self.shape n_vecs = other.shape[1] # number of column vectors result = np.zeros((M, n_vecs), dtype=upcast(self.dtype, other.dtype)) for (i, j), v in iteritems(self): result[i, :] += v * other[j, :] return result
def dtype(self): """ Returns data type of the matrix. """ # ToDo: decide if this is the right way of doing this all_dtypes = [blk.dtype for blk in self._blocks[self._block_mask]] dtype = upcast(*all_dtypes) if all_dtypes else None return dtype
def get_projection(b, W, AW, # TODO: AW optional, supply A x0 = None, inner_product = ip): """Get projection and appropriate initial guess for use in deflated methods. Arguments: W: the basis vectors used for deflation (Nxk array). AW: A*W, where A is the operator of the linear algebraic system to be deflated. A has to be self-adjoint w.r.t. inner_product. Do not include the positive-definite preconditioner (argument M in MINRES) here. Let N be the dimension of the vector space the operator is defined on. b: the right hand side of the linear system (array of length N). x0: the initial guess (array of length N). inner_product: the inner product also used for the deflated iterative method. Returns: P: the projection to be used as _right_ preconditioner (e.g. Mr=P in MINRES). The preconditioned operator A*P is self-adjoint w.r.t. inner_product. P(x)=x - W*inner_product(W, A*W)^{-1}*inner_product(A*W, x) x0new: an adapted initial guess s.t. the deflated iterative solver does not break down (in exact arithmetics). AW: AW=A*W. This is returned in order to reduce the total number of matrix-vector multiplications with A. For nW = W.shape[1] = AW.shape[1] the computational cost is cost(get_projection): 2*cost(Pfun) + (nW^2)*IP cost(Pfun): nW*IP + (2/3)*nW^3 + nW*AXPY """ # -------------------------------------------------------------------------- def Pfun(x): '''Computes x - W * E\<AW,x>.''' return x - numpy.dot(W, numpy.dot(Einv, inner_product(AW, x))) # -------------------------------------------------------------------------- # cost: (nW^2)*IP E = inner_product(W, AW) Einv = numpy.linalg.inv(E) # cost: nW*IP + (2/3)*nW^3 EWb = numpy.dot(Einv, inner_product(W, b)) # Define projection operator. N = len(b) if x0 is None: x0 = numpy.zeros((N,1)) dtype = upcast(W.dtype, AW.dtype, b.dtype, x0.dtype) P = LinearOperator( [N,N], Pfun, matmat=Pfun, dtype=dtype) # Get updated x0. # cost: nW*AXPY + cost(Pfun) x0new = P*x0 + numpy.dot(W, EWb) return P, x0new
def _mul_sparse_matrix(self, other): """ Do the sparse matrix mult returning fast_csr_matrix only when other is also fast_csr_matrix. """ M, _ = self.shape _, N = other.shape major_axis = self._swap((M, N))[0] if isinstance(other, fast_csr_matrix): A = zcsr_mult(self, other, sorted=1) return A other = csr_matrix(other) # convert to this format idx_dtype = get_index_dtype( (self.indptr, self.indices, other.indptr, other.indices), maxval=M * N) # scipy 1.5 renamed the older csr_matmat_pass1 to the much more # descriptive csr_matmat_maxnnz, but also changed the call and logic # structure of constructing the indices. try: fn = getattr(_sparsetools, self.format + '_matmat_maxnnz') nnz = fn(M, N, np.asarray(self.indptr, dtype=idx_dtype), np.asarray(self.indices, dtype=idx_dtype), np.asarray(other.indptr, dtype=idx_dtype), np.asarray(other.indices, dtype=idx_dtype)) idx_dtype = get_index_dtype( (self.indptr, self.indices, other.indptr, other.indices), maxval=nnz) indptr = np.empty(major_axis + 1, dtype=idx_dtype) except AttributeError: indptr = np.empty(major_axis + 1, dtype=idx_dtype) fn = getattr(_sparsetools, self.format + '_matmat_pass1') fn(M, N, np.asarray(self.indptr, dtype=idx_dtype), np.asarray(self.indices, dtype=idx_dtype), np.asarray(other.indptr, dtype=idx_dtype), np.asarray(other.indices, dtype=idx_dtype), indptr) nnz = indptr[-1] idx_dtype = get_index_dtype( (self.indptr, self.indices, other.indptr, other.indices), maxval=nnz) indices = np.empty(nnz, dtype=idx_dtype) data = np.empty(nnz, dtype=upcast(self.dtype, other.dtype)) try: fn = getattr(_sparsetools, self.format + '_matmat') except AttributeError: fn = getattr(_sparsetools, self.format + '_matmat_pass2') fn(M, N, np.asarray(self.indptr, dtype=idx_dtype), np.asarray(self.indices, dtype=idx_dtype), self.data, np.asarray(other.indptr, dtype=idx_dtype), np.asarray(other.indices, dtype=idx_dtype), other.data, indptr, indices, data) A = csr_matrix((data, indices, indptr), shape=(M, N)) return A
def diagonal(self, k=0): rows, cols = self.shape if k <= -rows or k >= cols: return np.empty(0, dtype=self.data.dtype) fn = getattr(_sparsetools, self.format + "_diagonal") y = np.empty(min(rows + min(k, 0), cols - max(k, 0)), dtype=upcast(self.dtype)) fn(k, self.shape[0], self.shape[1], self.indptr, self.indices, self.data, y) return y
def tocsc(self, copy=False): idx_dtype = get_index_dtype((self.indptr, self.indices), maxval=max(self.nnz, self.shape[0])) indptr = np.empty(self.shape[1] + 1, dtype=idx_dtype) indices = np.empty(self.nnz, dtype=idx_dtype) data = np.empty(self.nnz, dtype=upcast(self.dtype)) csr_tocsc(self.shape[0], self.shape[1], self.indptr.astype(idx_dtype), self.indices.astype(idx_dtype), self.data, indptr, indices, data) from scipy.sparse.csc import csc_matrix A = csc_matrix((data, indices, indptr), shape=self.shape) A.has_sorted_indices = True return A
def tocsr(self, copy=False): M, N = self.shape idx_dtype = get_index_dtype((self.indptr, self.indices), maxval=max(self.nnz, N)) indptr = np.empty(M + 1, dtype=idx_dtype) indices = np.empty(self.nnz, dtype=idx_dtype) data = np.empty(self.nnz, dtype=upcast(self.dtype)) csc_tocsr(M, N, self.indptr.astype(idx_dtype), self.indices.astype(idx_dtype), self.data, indptr, indices, data) from pyomo.contrib.pynumero.sparse.csr import CSRMatrix A = CSRMatrix((data, indices, indptr), shape=self.shape, copy=False) A.has_sorted_indices = True return A
def _binopt(self, other, op): """ Do the binary operation fn to two sparse matrices using fast_csr_matrix only when other is also a fast_csr_matrix. """ # e.g. csr_plus_csr, csr_minus_csr, etc. if not isinstance(other, fast_csr_matrix): other = csr_matrix(other) # e.g. csr_plus_csr, csr_minus_csr, etc. fn = getattr(_sparsetools, self.format + op + self.format) maxnnz = self.nnz + other.nnz idx_dtype = get_index_dtype( (self.indptr, self.indices, other.indptr, other.indices), maxval=maxnnz) indptr = np.empty(self.indptr.shape, dtype=idx_dtype) indices = np.empty(maxnnz, dtype=idx_dtype) bool_ops = ['_ne_', '_lt_', '_gt_', '_le_', '_ge_'] if op in bool_ops: data = np.empty(maxnnz, dtype=np.bool_) else: data = np.empty(maxnnz, dtype=upcast(self.dtype, other.dtype)) fn(self.shape[0], self.shape[1], np.asarray(self.indptr, dtype=idx_dtype), np.asarray(self.indices, dtype=idx_dtype), self.data, np.asarray(other.indptr, dtype=idx_dtype), np.asarray(other.indices, dtype=idx_dtype), other.data, indptr, indices, data) actual_nnz = indptr[-1] indices = indices[:actual_nnz] data = data[:actual_nnz] if actual_nnz < maxnnz // 2: # too much waste, trim arrays indices = indices.copy() data = data.copy() if isinstance(other, fast_csr_matrix) and (not op in bool_ops): A = fast_csr_matrix((data, indices, indptr), dtype=data.dtype, shape=self.shape) else: A = csr_matrix((data, indices, indptr), dtype=data.dtype, shape=self.shape) return A
def _mul_sparse_matrix(self, other): """ Do the sparse matrix mult returning fast_csr_matrix only when other is also fast_csr_matrix. """ M, K1 = self.shape K2, N = other.shape major_axis = self._swap((M,N))[0] if isinstance(other, fast_csr_matrix): A = zcsr_mult(self, other) A.sort_indices() return A other = csr_matrix(other) # convert to this format idx_dtype = get_index_dtype((self.indptr, self.indices, other.indptr, other.indices), maxval=M*N) indptr = np.empty(major_axis + 1, dtype=idx_dtype) fn = getattr(_sparsetools, self.format + '_matmat_pass1') fn(M, N, np.asarray(self.indptr, dtype=idx_dtype), np.asarray(self.indices, dtype=idx_dtype), np.asarray(other.indptr, dtype=idx_dtype), np.asarray(other.indices, dtype=idx_dtype), indptr) nnz = indptr[-1] idx_dtype = get_index_dtype((self.indptr, self.indices, other.indptr, other.indices), maxval=nnz) indptr = np.asarray(indptr, dtype=idx_dtype) indices = np.empty(nnz, dtype=idx_dtype) data = np.empty(nnz, dtype=upcast(self.dtype, other.dtype)) fn = getattr(_sparsetools, self.format + '_matmat_pass2') fn(M, N, np.asarray(self.indptr, dtype=idx_dtype), np.asarray(self.indices, dtype=idx_dtype), self.data, np.asarray(other.indptr, dtype=idx_dtype), np.asarray(other.indices, dtype=idx_dtype), other.data, indptr, indices, data) A = csr_matrix((data,indices,indptr),shape=(M,N)) return A
def coo_tocsr(coo_mat): M, N = coo_mat.shape idx_dtype = get_index_dtype((coo_mat.row, coo_mat.col), maxval=max(coo_mat.nnz, N)) indptr = np.empty(M + 1, dtype=idx_dtype) indices = np.empty(coo_mat.nnz, dtype=idx_dtype) data = np.empty(coo_mat.nnz, dtype=upcast(coo_mat.dtype)) scipy_coo_tocsr(M, N, coo_mat.nnz, coo_mat.row.astype(idx_dtype), coo_mat.col.astype(idx_dtype), coo_mat.data, indptr, indices, data) A = scipy.sparse.csr_matrix((data, indices, indptr), shape=coo_mat.shape) A.sort_indices() csr_max_duplicates(M, N, A.indptr, A.indices, A.data) A.prune() A.has_canonical_format = True return A
def _binopt(self, other, op): """ Do the binary operation fn to two sparse matrices using fast_csr_matrix only when other is also a fast_csr_matrix. """ # e.g. csr_plus_csr, csr_minus_csr, etc. if not isinstance(other, fast_csr_matrix): other = csr_matrix(other) # e.g. csr_plus_csr, csr_minus_csr, etc. fn = getattr(_sparsetools, self.format + op + self.format) maxnnz = self.nnz + other.nnz idx_dtype = get_index_dtype((self.indptr, self.indices, other.indptr, other.indices), maxval=maxnnz) indptr = np.empty(self.indptr.shape, dtype=idx_dtype) indices = np.empty(maxnnz, dtype=idx_dtype) bool_ops = ['_ne_', '_lt_', '_gt_', '_le_', '_ge_'] if op in bool_ops: data = np.empty(maxnnz, dtype=np.bool_) else: data = np.empty(maxnnz, dtype=upcast(self.dtype, other.dtype)) fn(self.shape[0], self.shape[1], np.asarray(self.indptr, dtype=idx_dtype), np.asarray(self.indices, dtype=idx_dtype), self.data, np.asarray(other.indptr, dtype=idx_dtype), np.asarray(other.indices, dtype=idx_dtype), other.data, indptr, indices, data) actual_nnz = indptr[-1] indices = indices[:actual_nnz] data = data[:actual_nnz] if actual_nnz < maxnnz // 2: # too much waste, trim arrays indices = indices.copy() data = data.copy() if isinstance(other, fast_csr_matrix) and (not op in bool_ops): A = fast_csr_matrix((data, indices, indptr), dtype=data.dtype, shape=self.shape) else: A = csr_matrix((data, indices, indptr), dtype=data.dtype, shape=self.shape) return A
def _mul_sparse_matrix(self, other): """ Do the sparse matrix mult returning fast_csr_matrix only when other is also fast_csr_matrix. """ M, _ = self.shape _, N = other.shape major_axis = self._swap((M, N))[0] if isinstance(other, fast_csr_matrix): A = zcsr_mult(self, other, sorted=1) return A other = csr_matrix(other) # convert to this format idx_dtype = get_index_dtype((self.indptr, self.indices, other.indptr, other.indices), maxval=M * N) indptr = np.empty(major_axis + 1, dtype=idx_dtype) fn = getattr(_sparsetools, self.format + '_matmat_pass1') fn(M, N, np.asarray(self.indptr, dtype=idx_dtype), np.asarray(self.indices, dtype=idx_dtype), np.asarray(other.indptr, dtype=idx_dtype), np.asarray(other.indices, dtype=idx_dtype), indptr) nnz = indptr[-1] idx_dtype = get_index_dtype((self.indptr, self.indices, other.indptr, other.indices), maxval=nnz) indptr = np.asarray(indptr, dtype=idx_dtype) indices = np.empty(nnz, dtype=idx_dtype) data = np.empty(nnz, dtype=upcast(self.dtype, other.dtype)) fn = getattr(_sparsetools, self.format + '_matmat_pass2') fn(M, N, np.asarray(self.indptr, dtype=idx_dtype), np.asarray(self.indices, dtype=idx_dtype), self.data, np.asarray(other.indptr, dtype=idx_dtype), np.asarray(other.indices, dtype=idx_dtype), other.data, indptr, indices, data) A = csr_matrix((data, indices, indptr), shape=(M, N)) return A
def tocsr(self): """Return a copy of this matrix in Compressed Sparse Row format Duplicate entries will be summed together. Examples -------- >>> from numpy import array >>> from scipy.sparse import coo_matrix >>> row = array([0, 0, 1, 3, 1, 0, 0]) >>> col = array([0, 2, 1, 3, 1, 0, 0]) >>> data = array([1, 1, 1, 1, 1, 1, 1]) >>> A = coo_matrix((data, (row, col)), shape=(4, 4)).tocsr() >>> A.toarray() array([[3, 0, 1, 0], [0, 2, 0, 0], [0, 0, 0, 0], [0, 0, 0, 1]]) """ from .csr import csr_matrix if self.nnz == 0: return csr_matrix(self.shape, dtype=self.dtype) else: M, N = self.shape idx_dtype = get_index_dtype((self.row, self.col), maxval=max(self.nnz, N)) indptr = np.empty(M + 1, dtype=idx_dtype) indices = np.empty(self.nnz, dtype=idx_dtype) data = np.empty(self.nnz, dtype=upcast(self.dtype)) coo_tocsr(M, N, self.nnz, self.row.astype(idx_dtype), self.col.astype(idx_dtype), self.data, indptr, indices, data) A = csr_matrix((data, indices, indptr), shape=self.shape) A.sum_duplicates() return A
def test_upcast(self): assert_equal(sputils.upcast('intc'),np.intc) assert_equal(sputils.upcast('int32','float32'),np.float64) assert_equal(sputils.upcast('bool',complex,float),np.complex128) assert_equal(sputils.upcast('i','d'),np.float64)
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 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)
m2 = np.random.randint(0, 100, (m, n)) m2[m2 > 50] = 0 m1x = sparse.csr_matrix(m1) m2x = sparse.csr_matrix(m2) maxnnz = m1x.nnz + m2x.nnz idx_dtype = get_index_dtype((m1x.indptr, m1x.indices, m2x.indptr, m2x.indices), maxval=maxnnz) indptr = np.empty(m1x.indptr.shape, dtype=idx_dtype) indices = np.empty(maxnnz, dtype=idx_dtype) bool_ops = ['_ne_', '_lt_', '_gt_', '_le_', '_ge_'] if 'enul' in bool_ops: data = np.empty(maxnnz, dtype=np.bool_) else: data = np.empty(maxnnz, dtype=upcast(m1x.dtype, m2x.dtype)) n_row, n_col = m1.shape Ap = m1x.indptr Aj = m1x.indices Ax = m1x.data Bp = m2x.indptr Bj = m2x.indices Bx = m2x.data Cp = indptr Cj = indices Cx = data loops = [100, 500, 1000] tb = PrettyTable() tb.field_names = [""] + ["{} loops".format(loop) for loop in loops]
def gmres_mgs(A, x, b, R, tol, max_iter): """ Generalized Minimum Residual Method (GMRES) GMRES iteratively refines the initial solution guess to the system Ax = b Modified Gram-Schmidt version Parameter --------- A : matrix, n x n, matrix from the linear system to solve. x : array, initial guess. b : array, right hand side. R : int, number of iterations for GMRES to do restart. tol : float, convergence tolerance. max_iter: int, maximum number of GMRES iterations. Returns ------- x : array, an updated guess to the solution of Ax = b. """ # Choose type if not hasattr(A, 'dtype'): Atype = upcast(x.dtype, b.dtype) else: Atype = A.dtype xtype = upcast(Atype, x.dtype, b.dtype) # Get fast access to underlying BLAS routines # dotc is the conjugate dot, dotu does no conjugation if numpy.iscomplexobj(numpy.zeros((1, ), dtype=xtype)): [axpy, dotu, dotc, scal, rotg] =\ get_blas_funcs(['axpy', 'dotu', 'dotc', 'scal', 'rotg'], [x]) else: # real type [axpy, dotu, dotc, scal, rotg] =\ get_blas_funcs(['axpy', 'dot', 'dot', 'scal', 'rotg'], [x]) # Make full use of direct access to BLAS by defining own norm def norm(z): return numpy.sqrt(numpy.real(dotc(z, z))) #Defining dimension dimen = A.shape[0] # Set number of outer and inner iterations max_outer = max_iter if R > dimen: warn('Setting number of inner iterations (restrt) to maximum\ allowed, which is A.shape[0] ') R = dimen max_inner = R # Prep for method r = b - scipy.dot(A, x) normr = norm(r) # 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 x iteration = 0 #Here start the 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 = [] # Initialzing Givens Rotations # Upper Hessenberg matrix, which is then # converted to upper triagonal with Givens Rotations H = numpy.zeros((max_inner + 1, max_inner + 1), dtype=xtype) V = numpy.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) # scal wrapper of dscal --> x = a*x vs.append(V[0, :]) #Saving initial residual to be used to calculate the rel_resid if iteration == 0: res_0 = normb #RHS vector in the Krylov space g = numpy.zeros((dimen, ), dtype=xtype) g[0] = normr for inner in range(max_inner): #New search direction v = V[inner + 1, :] v[:] = scipy.dot(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, dimen, -alpha) # y := a*x + y #axpy is a wrapper for daxpy (blas function) normv = norm(v) H[inner, inner + 1] = normv #Check for breakdown if H[inner, inner + 1] != 0.0: v[:] = scal(1.0 / H[inner, inner + 1], v) #Apply for Givens rotations to H if inner > 0: apply_givens(Q, H[inner, :], inner) #Calculate and apply next complex-valued Givens rotations #If max_inner = dimen, we don't need to calculate, this #is unnecessary for the last inner iteration when inner = dimen -1 if inner != dimen - 1: if H[inner, inner + 1] != 0: #rotg is a blas function that computes the parameters #for a Givens rotation [c, s] = rotg(H[inner, inner], H[inner, inner + 1]) Qblock = numpy.array([[c, s], [-numpy.conjugate(s), c]], dtype=xtype) Q.append(Qblock) #Apply Givens Rotations to RHS for the linear system in # the krylov space. g[inner:inner + 2] = scipy.dot(Qblock, g[inner:inner + 2]) #Apply Givens rotations to H H[inner, inner] = dotu(Qblock[0, :], H[inner, inner:inner + 2]) H[inner, inner + 1] = 0.0 iteration += 1 if inner < max_inner - 1: normr = abs(g[inner + 1]) rel_resid = normr / res_0 if rel_resid < tol: break if iteration % 1 == 0: print('Iteration: %i, relative residual: %s' % (iteration, rel_resid)) if (inner + 1 == R): print('Residual: %f. Restart...' % rel_resid) # end inner loop, back to outer loop # Find best update to x in Krylov Space V. Solve inner x inner system. y = scipy.linalg.solve(H[0:inner + 1, 0:inner + 1].T, g[0:inner + 1]) update = numpy.ravel(scipy.mat(V[:inner + 1, :]).T * y.reshape(-1, 1)) x = x + update r = b - scipy.dot(A, x) normr = norm(r) rel_resid = normr / res_0 # test for convergence if rel_resid < tol: print('Converged after %i iterations to a residual of %s' % (iteration, rel_resid)) return x #end outer loop return x
def gmres(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 For robustness, Householder reflections are used to orthonormalize the Krylov Space Givens Rotations are used to provide the residual norm each iteration Parameters ---------- A : array, matrix or sparse matrix n x n, linear system to solve b : array n x 1, right hand side x0 : array n x 1, initial guess default is a vector of zeros tol : float convergence tolerance restrt : int number of restarts total iterations = restrt*maxiter maxiter : int maximum number of allowed inner iterations xtype : type dtype for the solution M : matrix-like n x n, inverted preconditioner, i.e. solve M A x = b. For preconditioning with a mat-vec routine, set A.psolve = func, where func := M y callback : function callback( ||resid||_2 ) is called each iteration, residuals : {None, empty-list} If empty-list, residuals holds the residual norm history, including the initial residual, upon completion 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 ----- Examples -------- >>>from pyamg.krylov import * >>>from scipy import rand >>>import pyamg >>>A = pyamg.poisson((50,50)) >>>b = rand(A.shape[0],) >>>(x,flag) = gmres(A,b) >>>print pyamg.util.linalg.norm(b - A*x) References ---------- Yousef Saad, "Iterative Methods for Sparse Linear Systems, Second Edition", SIAM, pp. 151-172, pp. 272-275, 2003 ''' # Convert inputs to linear system, with error checking A,M,x,b,postprocess = make_system(A,M,x0,b,xtype) dimen = A.shape[0] # Choose type xtype = upcast(A.dtype, x.dtype, b.dtype, M.dtype) # We assume henceforth that shape=(n,) for all arrays b = ravel(array(b,xtype)) x = ravel(array(x,xtype)) # Should norm(r) be kept if residuals == []: keep_r = True else: keep_r = False # check number of iterations if restrt == None: restrt = 1 elif restrt < 1: raise ValueError('Number of restarts must be positive') if maxiter == None: maxiter = int(max(ceil(dimen/restrt))) elif maxiter < 1: raise ValueError('Number of iterations must be positive') elif maxiter > dimen: warn('maximimum allowed inner iterations (maxiter) are the number of degress of freedom') maxiter = dimen # Scale tol by normb normb = linalg.norm(b) if normb == 0: pass # if callback != None: # callback(0.0) # # return (postprocess(zeros((dimen,)), dtype=xtype),0) else: tol = tol*normb # 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 = linalg.norm(r) if keep_r: residuals.append(normr) # Is initial guess sufficient? if normr <= tol: if callback != None: callback(norm(r)) return (postprocess(x), 0) #Apply preconditioner r = ravel(M*r) normr = linalg.norm(r) # Check for nan, inf if any(isnan(r)) or any(isinf(r)): warn('inf or nan after application of preconditioner') return(postprocess(x), -1) # Use separate variable to track iterations. If convergence fails, we cannot # simply report niter = (outer-1)*maxiter + inner. Numerical error could cause # the inner loop to halt before reaching maxiter while the actual ||r|| > tol. niter = 0 # Begin GMRES for outer in range(restrt): # 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 = w / linalg.norm(w) # Preallocate for Krylov vectors, Householder reflectors and Hessenberg matrix # Space required is O(dimen*maxiter) H = zeros( (maxiter, maxiter), dtype=xtype) # upper Hessenberg matrix (actually made upper tri with Given's Rotations) W = zeros( (dimen, maxiter), dtype=xtype) # Householder reflectors 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(maxiter): # Calcute 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] # Calculate new search direction v = ravel(A*v) #Apply preconditioner v = ravel(M*v) # Check for nan, inf if any(isnan(v)) or any(isinf(v)): 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 = v - 2.0*dot(conjugate(W[:,j]), v)*W[:,j] # Calculate next Householder reflector, w # w = v[inner+1:] + sign(v[inner+1])*||v[inner+1:]||_2*e_{inner+1) # Note that if maxiter = 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 Given's rotation because nnz(v) is already the # desired length, i.e. we do not need to zero anything out. if inner != dimen-1: w = zeros((dimen,), dtype=xtype) vslice = v[inner+1:] alpha = linalg.norm(vslice) if alpha != 0: alpha = mysign(vslice[0])*alpha # We do not need the final reflector for future calculations if inner < (maxiter-1): w[inner+1:] = vslice w[inner+1] += alpha w = w / linalg.norm(w) W[:,inner+1] = w # Apply new reflector to v # v = v - 2.0*w*(w.T*v) v[inner+1] = -alpha v[inner+2:] = 0.0 # Apply all previous Given's Rotations to v if inner == 0: # Q will store the cumulative effect of all Given's Rotations Q = scipy.sparse.eye(dimen, dimen, format='csr', dtype=xtype) # Declare initial Qj, which will be the current Given's Rotation rowptr = hstack( (array([0, 2, 4],int), arange(5,dimen+3,dtype=int)) ) colindices = hstack( (array([0, 1, 0, 1],int), arange(2, dimen,dtype=int)) ) data = ones((dimen+2,), dtype=xtype) Qj = csr_matrix( (data, colindices, rowptr), shape=(dimen,dimen), dtype=xtype) else: # Could avoid building a global Given's Rotation, by storing # and applying each 2x2 matrix individually. # But that would require looping, the bane of wonderful Python Q = Qj*Q v = Q*v # Calculate Qj, the next Given's rotation, where j = inner # Note that if maxiter = 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 Given's 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: # Calculate terms for complex 2x2 Given's Rotation # Note that abs(x) takes the complex modulus h1 = v[inner]; h2 = v[inner+1]; h1_mag = abs(h1); h2_mag = abs(h2); if h1_mag < h2_mag: mu = h1/h2 tau = conjugate(mu)/abs(mu) else: mu = h2/h1 tau = mu/abs(mu) denom = sqrt( h1_mag**2 + h2_mag**2 ) c = h1_mag/denom; s = h2_mag*tau/denom; Qblock = array([[c, conjugate(s)], [-s, c]], dtype=xtype) # Modify Qj in csr-format so that it represents the current # global Given's Rotation equivalent to Qblock if inner != 0: Qj.data[inner-1] = 1.0 Qj.indices[inner-1] = inner-1 Qj.indptr[inner-1] = inner-1 Qj.data[inner:inner+4] = ravel(Qblock) Qj.indices[inner:inner+4] = [inner, inner+1, inner, inner+1] Qj.indptr[inner:inner+3] = [inner, inner+2, inner+4] # Apply Given's 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 Given's 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:maxiter] # Don't update normr if last inner iteration, because # normr is calculated directly after this loop ends. if inner < maxiter-1: normr = abs(g[inner+1]) if normr < tol: break # Allow user access to residual if callback != 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) # 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 = update - 2.0*dot(conjugate(W[:,j]), update)*W[:,j] x = x + update r = b - ravel(A*x) #Apply preconditioner r = ravel(M*r) normr = linalg.norm(r) # Check for nan, inf if any(isnan(r)) or any(isinf(r)): warn('inf or nan after application of preconditioner') return(postprocess(x), -1) # Allow user access to residual if callback != 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_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, 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 gmres_mgs(surf_array, field_array, X, b, param, ind0, timing, kernel): """ GMRES solver. Arguments ---------- surf_array : array, contains the surface classes of each region on the surface. field_array: array, contains the Field classes of each region on the surface. X : array, initial guess. b : array, right hand side. param : class, parameters related to the surface. ind0 : class, it contains the indices related to the treecode computation. timing : class, it contains timing information for different parts of the code. kernel : pycuda source module. Returns -------- X : array, an updated guess to the solution. iteration : int, number of outer iterations for convergence 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 """ # Defining xtype as dtype of the problem, to decide which BLAS functions # import. xtype = upcast(X.dtype, b.dtype) # Get fast access to underlying BLAS routines # dotc is the conjugate dot, dotu does no conjugation [lartg] = get_lapack_funcs(['lartg'], [X]) if numpy.iscomplexobj(numpy.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 numpy.sqrt(numpy.real(dotc(z, z))) # Defining dimension dimen = len(X) max_iter = param.max_iter R = param.restart tol = param.tol # Set number of outer and inner iterations if R > dimen: warn('Setting number of inner iterations (restrt) to maximum\ allowed, which is A.shape[0] ') R = dimen max_inner = R #max_outer should be max_iter/max_inner but this might not be an integer #so we get the ceil of the division. #In the inner loop there is a if statement to break in case max_iter is #reached. max_outer = int(numpy.ceil(max_iter / max_inner)) # Prep for method aux = gmres_dot(X, surf_array, field_array, ind0, param, timing, kernel) r = b - aux normr = norm(r) # 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 X, 0 iteration = 0 # Here start the 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 = [] # Initialzing Givens Rotations # Upper Hessenberg matrix, which is then # converted to upper triagonal with Givens Rotations H = numpy.zeros((max_inner + 1, max_inner + 1), dtype=xtype) V = numpy.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) # scal wrapper of dscal --> x = a*x vs.append(V[0, :]) #Saving initial residual to be used to calculate the rel_resid if iteration == 0: res_0 = normb #RHS vector in the Krylov space g = numpy.zeros((dimen, ), dtype=xtype) g[0] = normr for inner in range(max_inner): #New search direction v = V[inner + 1, :] v[:] = gmres_dot(vs[-1], surf_array, field_array, ind0, param, timing, kernel) vs.append(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, dimen, -alpha) # y := a*x + y #axpy is a wrapper for daxpy (blas function) normv = norm(v) H[inner, inner + 1] = normv #Check for breakdown if H[inner, inner + 1] != 0.0: v[:] = scal(1.0 / H[inner, inner + 1], v) #Apply for Givens rotations to H if inner > 0: apply_givens(Q, H[inner, :], inner) #Calculate and apply next complex-valued Givens rotations #If max_inner = dimen, we don't need to calculate, this #is unnecessary for the last inner iteration when inner = dimen -1 if inner != dimen - 1: if H[inner, inner + 1] != 0: #lartg is a lapack function that computes the parameters #for a Givens rotation [c, s, _] = lartg(H[inner, inner], H[inner, inner + 1]) Qblock = numpy.array([[c, s], [-numpy.conjugate(s), c]], dtype=xtype) Q.append(Qblock) #Apply Givens Rotations to RHS for the linear system in # the krylov space. g[inner:inner + 2] = scipy.dot(Qblock, g[inner:inner + 2]) #Apply Givens rotations to H H[inner, inner] = dotu(Qblock[0, :], H[inner, inner:inner + 2]) H[inner, inner + 1] = 0.0 iteration += 1 if inner < max_inner - 1: normr = abs(g[inner + 1]) rel_resid = normr / res_0 if rel_resid < tol: break if iteration % 1 == 0: print('Iteration: {}, relative residual: {}'.format( iteration, rel_resid)) if (inner + 1 == R): print('Residual: {}. Restart...'.format(rel_resid)) if iteration == max_iter: print( 'Warning!!!!' 'You have reached the maximum number of iterations : {}.'. format(iteration)) print( 'The run will stop. Check the residual behaviour you might have a bug.' 'For future runs you might consider changing the tolerance or' ' increasing the number of max_iter.') break # end inner loop, back to outer loop # Find best update to X in Krylov Space V. Solve inner X inner system. y = scipy.linalg.solve(H[0:inner + 1, 0:inner + 1].T, g[0:inner + 1]) update = numpy.ravel(scipy.mat(V[:inner + 1, :]).T * y.reshape(-1, 1)) X = X + update aux = gmres_dot(X, surf_array, field_array, ind0, param, timing, kernel) r = b - aux normr = norm(r) rel_resid = normr / res_0 # test for convergence if rel_resid < tol: print('GMRES solve') print('Converged after {} iterations to a residual of {}'.format( iteration, rel_resid)) print('Time weight vector: {}'.format(timing.time_mass)) print('Time sort : {}'.format(timing.time_sort)) print('Time data transfer: {}'.format(timing.time_trans)) print('Time P2M : {}'.format(timing.time_P2M)) print('Time M2M : {}'.format(timing.time_M2M)) print('Time M2P : {}'.format(timing.time_M2P)) print('Time P2P : {}'.format(timing.time_P2P)) print('\tTime analy: {}'.format(timing.time_an)) return X, iteration #end outer loop return X, iteration
def test_upcast(self): assert_equal(sputils.upcast("intc"), np.intc) assert_equal(sputils.upcast("int32", "float32"), np.float64) assert_equal(sputils.upcast("bool", complex, float), np.complex128) assert_equal(sputils.upcast("i", "d"), np.float64)
def jfnk(fluxName, b, U, Dt, Dx, x0=None, Uref=1., tol=1e-5, restrt=None, maxiter=None, xtype=None, M=None, callback=None, residuals=None): ''' Jacobian free Newton-Krylov method : For robustness, Householder reflections are used to orthonormalize the Krylov Space Givens Rotations are used to provide the residual norm each iteration Parameters ---------- fluxName : string name of the flux to use to compute the jacobian product b : array n x 1, right hand side U : array n x 1, right hand side Dt : integer time step for the approximation of the jacobian product x0 : array n x 1, initial guess default is a vector of zeros tol : float convergence tolerance restrt : int number of restarts total iterations = restrt*maxiter maxiter : int maximum number of allowed inner iterations xtype : type dtype for the solution M : matrix-like n x n, inverted preconditioner, i.e. solve M A x = b. For preconditioning with a mat-vec routine, set A.psolve = func, where func := M y callback : function callback( ||resid||_2 ) is called each iteration, residuals : {None, empty-list} If empty-list, residuals holds the residual norm history, including the initial residual, upon completion Returns ------- xNew -- an updated guess to the solution of Ax = b References ---------- Yousef Saad, "Iterative Methods for Sparse Linear Systems, Second Edition", SIAM, pp. 151-172, pp. 272-275, 2003 ''' dimen = b.shape[0] # Choose type xtype = upcast(b.dtype) # We assume henceforth that shape=(n,) for all arrays b = ravel(array(b, xtype)) x = ravel(array(x0, xtype)) # Should norm(r) be kept if residuals == []: keep_r = True else: keep_r = False # check number of iterations if restrt == None: restrt = 1 elif restrt < 1: raise ValueError('Number of restarts must be positive') if maxiter == None: maxiter = int(max(ceil(dimen / restrt))) elif maxiter < 1: raise ValueError('Number of iterations must be positive') elif maxiter > dimen: warn( 'maximimum allowed inner iterations (maxiter) are the number of degress of freedom' ) maxiter = dimen # Scale tol by normb normb = linalg.norm(b) if normb == 0: pass else: tol = tol * normb # Prep for method r = b - ravel(jacobianProduct(fluxName, U, x, Dt, Dx, Uref)) normr = linalg.norm(r) if keep_r: residuals.append(normr) # Is initial guess sufficient? if normr <= tol: if callback != None: callback(norm(r)) return x # Use separate variable to track iterations. If convergence fails, we cannot # simply report niter = (outer-1)*maxiter + inner. Numerical error could cause # the inner loop to halt before reaching maxiter while the actual ||r|| > tol. niter = 0 # Begin GMRES for outer in range(restrt): # 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 = w / linalg.norm(w) # Preallocate for Krylov vectors, Householder reflectors and Hessenberg matrix # Space required is O(dimen*maxiter) H = zeros( (maxiter, maxiter), dtype=xtype ) # upper Hessenberg matrix (actually made upper tri with Given's Rotations) W = zeros((dimen, maxiter), dtype=xtype) # Householder reflectors 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(maxiter): # Calcute 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] # Calculate new search direction (A*v) v = ravel(jacobianProduct(fluxName, U, v, Dt, Dx, Uref)) # 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] # Calculate next Householder reflector, w # w = v[inner+1:] + sign(v[inner+1])*||v[inner+1:]||_2*e_{inner+1) # Note that if maxiter = 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 Given's rotation because nnz(v) is already the # desired length, i.e. we do not need to zero anything out. if inner != dimen - 1: w = zeros((dimen, ), dtype=xtype) vslice = v[inner + 1:] alpha = linalg.norm(vslice) if alpha != 0: alpha = mysign(vslice[0]) * alpha # We do not need the final reflector for future calculations if inner < (maxiter - 1): w[inner + 1:] = vslice w[inner + 1] += alpha w = w / linalg.norm(w) W[:, inner + 1] = w # Apply new reflector to v # v = v - 2.0*w*(w.T*v) v[inner + 1] = -alpha v[inner + 2:] = 0.0 # Apply all previous Given's Rotations to v if inner == 0: # Q will store the cumulative effect of all Given's Rotations Q = scipy.sparse.eye(dimen, dimen, format='csr', dtype=xtype) # Declare initial Qj, which will be the current Given's Rotation rowptr = hstack((array([0, 2, 4], int), arange(5, dimen + 3, dtype=int))) colindices = hstack((array([0, 1, 0, 1], int), arange(2, dimen, dtype=int))) data = ones((dimen + 2, ), dtype=xtype) Qj = csr_matrix((data, colindices, rowptr), shape=(dimen, dimen), dtype=xtype) else: # Could avoid building a global Given's Rotation, by storing # and applying each 2x2 matrix individually. # But that would require looping, the bane of wonderful Python Q = Qj * Q v = Q * v # Calculate Qj, the next Given's rotation, where j = inner # Note that if maxiter = 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 Given's 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: # Calculate terms for complex 2x2 Given's Rotation # Note that abs(x) takes the complex modulus h1 = v[inner] h2 = v[inner + 1] h1_mag = abs(h1) h2_mag = abs(h2) if h1_mag < h2_mag: mu = h1 / h2 tau = conjugate(mu) / abs(mu) else: mu = h2 / h1 tau = mu / abs(mu) denom = sqrt(h1_mag**2 + h2_mag**2) c = h1_mag / denom s = h2_mag * tau / denom Qblock = array([[c, conjugate(s)], [-s, c]], dtype=xtype) # Modify Qj in csr-format so that it represents the current # global Given's Rotation equivalent to Qblock if inner != 0: Qj.data[inner - 1] = 1.0 Qj.indices[inner - 1] = inner - 1 Qj.indptr[inner - 1] = inner - 1 Qj.data[inner:inner + 4] = ravel(Qblock) Qj.indices[inner:inner + 4] = [inner, inner + 1, inner, inner + 1] Qj.indptr[inner:inner + 3] = [inner, inner + 2, inner + 4] # Apply Given's 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 Given's 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:maxiter] # Don't update normr if last inner iteration, because # normr is calculated directly after this loop ends. if inner < maxiter - 1: normr = abs(g[inner + 1]) if normr < tol: break # Allow user access to residual if callback != 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) # 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 = update - 2.0 * dot(conjugate(W[:, j]), update) * W[:, j] x = x + update r = b - ravel(jacobianProduct(fluxName, U, x, Dt, Dx, Uref)) # Allow user access to residual if callback != 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 x # test for convergence if normr < tol: return x # end outer loop return x
def gmres_mgs(surf_array, field_array, X, b, param, ind0, timing, kernel): """ GMRES solver. Arguments ---------- surf_array : array, contains the surface classes of each region on the surface. field_array: array, contains the Field classes of each region on the surface. X : array, initial guess. b : array, right hand side. param : class, parameters related to the surface. ind0 : class, it contains the indices related to the treecode computation. timing : class, it contains timing information for different parts of the code. kernel : pycuda source module. Returns -------- X : array, an updated guess to the solution. iteration : int, number of outer iterations for convergence 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 """ # Defining xtype as dtype of the problem, to decide which BLAS functions # import. xtype = upcast(X.dtype, b.dtype) # Get fast access to underlying BLAS routines # dotc is the conjugate dot, dotu does no conjugation [lartg] = get_lapack_funcs(['lartg'], [X] ) if numpy.iscomplexobj(numpy.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 numpy.sqrt(numpy.real(dotc(z, z))) # Defining dimension dimen = len(X) max_iter = param.max_iter R = param.restart tol = param.tol # Set number of outer and inner iterations if R > dimen: warn('Setting number of inner iterations (restrt) to maximum\ allowed, which is A.shape[0] ') R = dimen max_inner = R #max_outer should be max_iter/max_inner but this might not be an integer #so we get the ceil of the division. #In the inner loop there is a if statement to break in case max_iter is #reached. max_outer = int(numpy.ceil(max_iter/max_inner)) # Prep for method aux = gmres_dot(X, surf_array, field_array, ind0, param, timing, kernel) r = b - aux normr = norm(r) # 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 X iteration = 0 # Here start the 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 = [] # Initialzing Givens Rotations # Upper Hessenberg matrix, which is then # converted to upper triagonal with Givens Rotations H = numpy.zeros((max_inner+1, max_inner+1), dtype=xtype) V = numpy.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) # scal wrapper of dscal --> x = a*x vs.append(V[0, :]) #Saving initial residual to be used to calculate the rel_resid if iteration==0: res_0 = normb #RHS vector in the Krylov space g = numpy.zeros((dimen, ), dtype=xtype) g[0] = normr for inner in range(max_inner): #New search direction v= V[inner+1, :] v[:] = gmres_dot(vs[-1], surf_array, field_array, ind0, param, timing, kernel) vs.append(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, dimen, -alpha) # y := a*x + y #axpy is a wrapper for daxpy (blas function) normv = norm(v) H[inner, inner+1] = normv #Check for breakdown if H[inner, inner+1] != 0.0: v[:] = scal(1.0/H[inner, inner+1], v) #Apply for Givens rotations to H if inner > 0: apply_givens(Q, H[inner, :], inner) #Calculate and apply next complex-valued Givens rotations #If max_inner = dimen, we don't need to calculate, this #is unnecessary for the last inner iteration when inner = dimen -1 if inner != dimen - 1: if H[inner, inner+1] != 0: #lartg is a lapack function that computes the parameters #for a Givens rotation [c, s, _] = lartg(H[inner, inner], H[inner, inner+1]) Qblock = numpy.array([[c, s], [-numpy.conjugate(s),c]], dtype=xtype) Q.append(Qblock) #Apply Givens Rotations to RHS for the linear system in # the krylov space. g[inner:inner+2] = scipy.dot(Qblock, g[inner:inner+2]) #Apply Givens rotations to H H[inner, inner] = dotu(Qblock[0,:], H[inner, inner:inner+2]) H[inner, inner+1] = 0.0 iteration+= 1 if inner < max_inner-1: normr = abs(g[inner+1]) rel_resid = normr/res_0 if rel_resid < tol: break if iteration%1==0: print('Iteration: {}, relative residual: {}'.format(iteration,rel_resid)) if (inner + 1 == R): print('Residual: {}. Restart...'.format(rel_resid)) if iteration==max_iter: print('Warning!!!!' 'You have reached the maximum number of iterations : {}.'.format(iteration)) print('The run will stop. Check the residual behaviour you might have a bug.' 'For future runs you might consider changing the tolerance or' ' increasing the number of max_iter.') break # end inner loop, back to outer loop # Find best update to X in Krylov Space V. Solve inner X inner system. y = scipy.linalg.solve (H[0:inner+1, 0:inner+1].T, g[0:inner+1]) update = numpy.ravel(scipy.mat(V[:inner+1, :]).T * y.reshape(-1,1)) X= X + update aux = gmres_dot(X, surf_array, field_array, ind0, param, timing, kernel) r = b - aux normr = norm(r) rel_resid = normr/res_0 # test for convergence if rel_resid < tol: print('GMRES solve') print('Converged after {} iterations to a residual of {}'.format(iteration,rel_resid)) print('Time weight vector: {}'.format(timing.time_mass)) print('Time sort : {}'.format(timing.time_sort)) print('Time data transfer: {}'.format(timing.time_trans)) print('Time P2M : {}'.format(timing.time_P2M)) print('Time M2M : {}'.format(timing.time_M2M)) print('Time M2P : {}'.format(timing.time_M2P)) print('Time P2P : {}'.format(timing.time_P2P)) print('\tTime analy: {}'.format(timing.time_an)) return X, iteration #end outer loop return X, iteration
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 gmres_mgs(surf_array, field_array, X, b, param, ind0, timing, kernel): """ GMRES solver. Arguments ---------- surf_array : array, contains the surface classes of each region on the surface. field_array: array, contains the Field classes of each region on the surface. X : array, initial guess. b : array, right hand side. param : class, parameters related to the surface. ind0 : class, it contains the indices related to the treecode computation. timing : class, it contains timing information for different parts of the code. kernel : pycuda source module. Returns -------- X : array, an updated guess to the solution. """ output_path = os.path.join( os.environ.get('PYGBE_PROBLEM_FOLDER'), 'OUTPUT') #Defining xtype as dtype of the problem, to decide which BLAS functions #import. xtype = upcast(X.dtype, b.dtype) # Get fast access to underlying BLAS routines # dotc is the conjugate dot, dotu does no conjugation if numpy.iscomplexobj(numpy.zeros((1,), dtype=xtype)): [axpy, dotu, dotc, scal, rotg] =\ get_blas_funcs(['axpy', 'dotu', 'dotc', 'scal', 'rotg'], [X]) else: # real type [axpy, dotu, dotc, scal, rotg] =\ get_blas_funcs(['axpy', 'dot', 'dot', 'scal', 'rotg'], [X]) # Make full use of direct access to BLAS by defining own norm def norm(z): return numpy.sqrt(numpy.real(dotc(z, z))) #Defining dimension dimen = len(X) max_iter = param.max_iter R = param.restart tol = param.tol # Set number of outer and inner iterations max_outer = max_iter if R > dimen: warn('Setting number of inner iterations (restrt) to maximum\ allowed, which is A.shape[0] ') R = dimen max_inner = R # Prep for method aux = gmres_dot(X, surf_array, field_array, ind0, param, timing, kernel) r = b - aux normr = norm(r) # 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 X iteration = 0 #Here start the 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 = [] # Initialzing Givens Rotations # Upper Hessenberg matrix, which is then # converted to upper triagonal with Givens Rotations H = numpy.zeros((max_inner+1, max_inner+1), dtype=xtype) V = numpy.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) # scal wrapper of dscal --> x = a*x vs.append(V[0, :]) #Saving initial residual to be used to calculate the rel_resid if iteration==0: res_0 = normb #RHS vector in the Krylov space g = numpy.zeros((dimen, ), dtype=xtype) g[0] = normr for inner in range(max_inner): #New search direction v= V[inner+1, :] v[:] = gmres_dot(vs[-1], surf_array, field_array, ind0, param, timing, kernel) 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, dimen, -alpha) # y := a*x + y #axpy is a wrapper for daxpy (blas function) normv = norm(v) H[inner, inner+1] = normv #Check for breakdown if H[inner, inner+1] != 0.0: v[:] = scal(1.0/H[inner, inner+1], v) #Apply for Givens rotations to H if inner > 0: apply_givens(Q, H[inner, :], inner) #Calculate and apply next complex-valued Givens rotations #If max_inner = dimen, we don't need to calculate, this #is unnecessary for the last inner iteration when inner = dimen -1 if inner != dimen - 1: if H[inner, inner+1] != 0: #rotg is a blas function that computes the parameters #for a Givens rotation [c, s] = rotg(H[inner, inner], H[inner, inner+1]) Qblock = numpy.array([[c, s], [-numpy.conjugate(s),c]], dtype=xtype) Q.append(Qblock) #Apply Givens Rotations to RHS for the linear system in # the krylov space. g[inner:inner+2] = scipy.dot(Qblock, g[inner:inner+2]) #Apply Givens rotations to H H[inner, inner] = dotu(Qblock[0,:], H[inner, inner:inner+2]) H[inner, inner+1] = 0.0 iteration+= 1 if inner < max_inner-1: normr = abs(g[inner+1]) rel_resid = normr/res_0 if rel_resid < tol: break if iteration%1==0: print ('Iteration: %i, relative residual: %s'%(iteration,rel_resid)) if (inner + 1 == R): print('Residual: %f. Restart...' % rel_resid) # end inner loop, back to outer loop # Find best update to X in Krylov Space V. Solve inner X inner system. y = scipy.linalg.solve (H[0:inner+1, 0:inner+1].T, g[0:inner+1]) update = numpy.ravel(scipy.mat(V[:inner+1, :]).T * y.reshape(-1,1)) X= X + update aux = gmres_dot(X, surf_array, field_array, ind0, param, timing, kernel) r = b - aux normr = norm(r) rel_resid = normr/res_0 # test for convergence if rel_resid < tol: print 'GMRES solve' print('Converged after %i iterations to a residual of %s'%(iteration,rel_resid)) print 'Time weight vector: %f'%timing.time_mass print 'Time sort : %f'%timing.time_sort print 'Time data transfer: %f'%timing.time_trans print 'Time P2M : %f'%timing.time_P2M print 'Time M2M : %f'%timing.time_M2M print 'Time M2P : %f'%timing.time_M2P print 'Time P2P : %f'%timing.time_P2P print '\tTime analy: %f'%timing.time_an return X #end outer loop return X
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 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 test_upcast(self): assert_equal(sputils.upcast('intc'), np.intc) assert_equal(sputils.upcast('int32', 'float32'), np.float64) assert_equal(sputils.upcast('bool', complex, float), np.complex128) assert_equal(sputils.upcast('i', 'd'), np.float64)
def _mul_vector(self, other): # matrix * vector result = np.zeros(self.shape[0], dtype=upcast(self.dtype, other.dtype)) for (i, j), v in iteritems(self): result[i] += v * other[j] return result
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 solve(self, b, x0=None, tol=1e-5, maxiter=100, cycle='V', accel=None, callback=None, residuals=None, return_info=False): """Execute multigrid cycling. Parameters ---------- b : array Right hand side. x0 : array Initial guess. tol : float Stopping criteria: relative residual r[k]/||b|| tolerance. If `accel` is used, the stopping criteria is set by the Krylov method. 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. The residuals will be the residuals from the Krylov iteration -- see the `accel` method to see verify whether this ||r|| or ||Mr|| (as in the case of GMRES). return_info : bool If true, will return (x, info) If false, will return x (default) Returns ------- x : array Approximate solution to Ax=b after k iterations info : string Halting status == ======================================= 0 successful exit >0 convergence to tolerance not achieved, return iteration count instead. == ======================================= 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 """ if x0 is None: x = np.zeros_like(b) else: x = np.array(x0) # copy A = self.levels[0].A cycle = str(cycle).upper() # AMLI cycles require hermitian matrix if (cycle == 'AMLI') and hasattr(A, 'symmetry'): if 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') # Acceleration is being used kwargs = {} if isinstance(accel, str): kwargs = {} if hasattr(krylov, accel): accel = getattr(krylov, accel) else: accel = getattr(_isolve, accel) kwargs['atol'] = 'legacy' M = self.aspreconditioner(cycle=cycle) try: # try PyAMG style interface which has a residuals parameter x, info = accel(A, b, x0=x0, tol=tol, maxiter=maxiter, M=M, callback=callback, residuals=residuals, **kwargs) if return_info: return x, info return x except TypeError: # try the scipy.sparse.linalg._isolve style interface, # which requires a callback function if a residual # history is desired if residuals is not None: residuals[:] = [np.linalg.norm(b - A @ x)] def callback_wrapper(x): if np.isscalar(x): residuals.append(x) else: residuals.append(np.linalg.norm(b - A @ x)) if callback is not None: callback(x) else: callback_wrapper = callback x, info = accel(A, b, x0=x0, tol=tol, maxiter=maxiter, M=M, callback=callback_wrapper, **kwargs) if return_info: return x, info return x else: # Scale tol by normb # Don't scale tol earlier. The accel routine should also scale tol normb = np.linalg.norm(b) if normb == 0.0: normb = 1.0 # set so that we have an absolute tolerance # Start cycling (no acceleration) normr = np.linalg.norm(b - A @ x) if residuals is not None: residuals[:] = [normr] # initial residual # Create uniform types for A, x and b # Clearly, this logic doesn't handle the case of real A and complex b tp = upcast(b.dtype, x.dtype, A.dtype) [b, x] = to_type(tp, [b, x]) b = np.ravel(b) x = np.ravel(x) it = 0 while True: # it <= maxiter and normr >= tol: if len(self.levels) == 1: # hierarchy has only 1 level x = self.coarse_solver(A, b) else: self.__solve(0, x, b, cycle) it += 1 normr = np.linalg.norm(b - A @ x) if residuals is not None: residuals.append(normr) if callback is not None: callback(x) if normr < tol * normb: if return_info: return x, 0 return x if it == maxiter: if return_info: return x, it return x
def mybmat(blocks, format=None, dtype=None): # bugfix-Zeile if len(blocks) > 0: numcols = [None for _ in blocks[0]] for row in blocks: for colnr, col in enumerate(row): if col is not None: numcols[colnr] = col.shape[1] assert not (None in numcols) blocks.append([np.zeros(shape=(0, i)) for i in numcols]) blocks = np.asarray(blocks, dtype='object') if np.ndim(blocks) != 2: raise ValueError('blocks must have rank 2') M, N = blocks.shape block_mask = np.zeros(blocks.shape, dtype=np.bool) brow_lengths = np.zeros(blocks.shape[0], dtype=np.intc) bcol_lengths = np.zeros(blocks.shape[1], dtype=np.intc) # convert everything to COO format for i in range(M): for j in range(N): if blocks[i, j] is not None: A = coo_matrix(blocks[i, j]) blocks[i, j] = A block_mask[i, j] = True if brow_lengths[i] == 0: brow_lengths[i] = A.shape[0] else: if brow_lengths[i] != A.shape[0]: raise ValueError('blocks[%d,:] has incompatible row dimensions' % i) if bcol_lengths[j] == 0: bcol_lengths[j] = A.shape[1] else: if bcol_lengths[j] != A.shape[1]: raise ValueError('blocks[:,%d] has incompatible column dimensions' % j) nnz = sum([A.nnz for A in blocks[block_mask]]) if dtype is None: dtype = upcast(*tuple([A.dtype for A in blocks[block_mask]])) row_offsets = np.concatenate(([0], np.cumsum(brow_lengths))) col_offsets = np.concatenate(([0], np.cumsum(bcol_lengths))) data = np.empty(nnz, dtype=dtype) row = np.empty(nnz, dtype=np.intc) col = np.empty(nnz, dtype=np.intc) nnz = 0 for i in range(M): for j in range(N): if blocks[i, j] is not None: A = blocks[i, j] data[nnz:nnz + A.nnz] = A.data row[nnz:nnz + A.nnz] = A.row col[nnz:nnz + A.nnz] = A.col row[nnz:nnz + A.nnz] += row_offsets[i] col[nnz:nnz + A.nnz] += col_offsets[j] nnz += A.nnz shape = (np.sum(brow_lengths), np.sum(bcol_lengths)) return coo_matrix((data, (row, col)), shape=shape).asformat(format)
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)