Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/scipy/sparse/linalg/_isolve/_gcrotmk.py: 5%

199 statements  

« prev     ^ index     » next       coverage.py v7.3.1, created at 2023-09-23 06:43 +0000

1# Copyright (C) 2015, Pauli Virtanen <pav@iki.fi> 

2# Distributed under the same license as SciPy. 

3 

4import warnings 

5import numpy as np 

6from numpy.linalg import LinAlgError 

7from scipy.linalg import (get_blas_funcs, qr, solve, svd, qr_insert, lstsq) 

8from scipy.sparse.linalg._isolve.utils import make_system 

9from scipy._lib.deprecation import _deprecate_positional_args 

10 

11 

12__all__ = ['gcrotmk'] 

13 

14 

15def _fgmres(matvec, v0, m, atol, lpsolve=None, rpsolve=None, cs=(), outer_v=(), 

16 prepend_outer_v=False): 

17 """ 

18 FGMRES Arnoldi process, with optional projection or augmentation 

19 

20 Parameters 

21 ---------- 

22 matvec : callable 

23 Operation A*x 

24 v0 : ndarray 

25 Initial vector, normalized to nrm2(v0) == 1 

26 m : int 

27 Number of GMRES rounds 

28 atol : float 

29 Absolute tolerance for early exit 

30 lpsolve : callable 

31 Left preconditioner L 

32 rpsolve : callable 

33 Right preconditioner R 

34 cs : list of (ndarray, ndarray) 

35 Columns of matrices C and U in GCROT 

36 outer_v : list of ndarrays 

37 Augmentation vectors in LGMRES 

38 prepend_outer_v : bool, optional 

39 Whether augmentation vectors come before or after 

40 Krylov iterates 

41 

42 Raises 

43 ------ 

44 LinAlgError 

45 If nans encountered 

46 

47 Returns 

48 ------- 

49 Q, R : ndarray 

50 QR decomposition of the upper Hessenberg H=QR 

51 B : ndarray 

52 Projections corresponding to matrix C 

53 vs : list of ndarray 

54 Columns of matrix V 

55 zs : list of ndarray 

56 Columns of matrix Z 

57 y : ndarray 

58 Solution to ||H y - e_1||_2 = min! 

59 res : float 

60 The final (preconditioned) residual norm 

61 

62 """ 

63 

64 if lpsolve is None: 

65 def lpsolve(x): 

66 return x 

67 if rpsolve is None: 

68 def rpsolve(x): 

69 return x 

70 

71 axpy, dot, scal, nrm2 = get_blas_funcs(['axpy', 'dot', 'scal', 'nrm2'], (v0,)) 

72 

73 vs = [v0] 

74 zs = [] 

75 y = None 

76 res = np.nan 

77 

78 m = m + len(outer_v) 

79 

80 # Orthogonal projection coefficients 

81 B = np.zeros((len(cs), m), dtype=v0.dtype) 

82 

83 # H is stored in QR factorized form 

84 Q = np.ones((1, 1), dtype=v0.dtype) 

85 R = np.zeros((1, 0), dtype=v0.dtype) 

86 

87 eps = np.finfo(v0.dtype).eps 

88 

89 breakdown = False 

90 

91 # FGMRES Arnoldi process 

92 for j in range(m): 

93 # L A Z = C B + V H 

94 

95 if prepend_outer_v and j < len(outer_v): 

96 z, w = outer_v[j] 

97 elif prepend_outer_v and j == len(outer_v): 

98 z = rpsolve(v0) 

99 w = None 

100 elif not prepend_outer_v and j >= m - len(outer_v): 

101 z, w = outer_v[j - (m - len(outer_v))] 

102 else: 

103 z = rpsolve(vs[-1]) 

104 w = None 

105 

106 if w is None: 

107 w = lpsolve(matvec(z)) 

108 else: 

109 # w is clobbered below 

110 w = w.copy() 

111 

112 w_norm = nrm2(w) 

113 

114 # GCROT projection: L A -> (1 - C C^H) L A 

115 # i.e. orthogonalize against C 

116 for i, c in enumerate(cs): 

117 alpha = dot(c, w) 

118 B[i,j] = alpha 

119 w = axpy(c, w, c.shape[0], -alpha) # w -= alpha*c 

120 

121 # Orthogonalize against V 

122 hcur = np.zeros(j+2, dtype=Q.dtype) 

123 for i, v in enumerate(vs): 

124 alpha = dot(v, w) 

125 hcur[i] = alpha 

126 w = axpy(v, w, v.shape[0], -alpha) # w -= alpha*v 

127 hcur[i+1] = nrm2(w) 

128 

129 with np.errstate(over='ignore', divide='ignore'): 

130 # Careful with denormals 

131 alpha = 1/hcur[-1] 

132 

133 if np.isfinite(alpha): 

134 w = scal(alpha, w) 

135 

136 if not (hcur[-1] > eps * w_norm): 

137 # w essentially in the span of previous vectors, 

138 # or we have nans. Bail out after updating the QR 

139 # solution. 

140 breakdown = True 

141 

142 vs.append(w) 

143 zs.append(z) 

144 

145 # Arnoldi LSQ problem 

146 

147 # Add new column to H=Q@R, padding other columns with zeros 

148 Q2 = np.zeros((j+2, j+2), dtype=Q.dtype, order='F') 

149 Q2[:j+1,:j+1] = Q 

150 Q2[j+1,j+1] = 1 

151 

152 R2 = np.zeros((j+2, j), dtype=R.dtype, order='F') 

153 R2[:j+1,:] = R 

154 

155 Q, R = qr_insert(Q2, R2, hcur, j, which='col', 

156 overwrite_qru=True, check_finite=False) 

157 

158 # Transformed least squares problem 

159 # || Q R y - inner_res_0 * e_1 ||_2 = min! 

160 # Since R = [R'; 0], solution is y = inner_res_0 (R')^{-1} (Q^H)[:j,0] 

161 

162 # Residual is immediately known 

163 res = abs(Q[0,-1]) 

164 

165 # Check for termination 

166 if res < atol or breakdown: 

167 break 

168 

169 if not np.isfinite(R[j,j]): 

170 # nans encountered, bail out 

171 raise LinAlgError() 

172 

173 # -- Get the LSQ problem solution 

174 

175 # The problem is triangular, but the condition number may be 

176 # bad (or in case of breakdown the last diagonal entry may be 

177 # zero), so use lstsq instead of trtrs. 

178 y, _, _, _, = lstsq(R[:j+1,:j+1], Q[0,:j+1].conj()) 

179 

180 B = B[:,:j+1] 

181 

182 return Q, R, B, vs, zs, y, res 

183 

184 

185@_deprecate_positional_args(version="1.14.0") 

186def gcrotmk(A, b, x0=None, *, tol=1e-5, maxiter=1000, M=None, callback=None, 

187 m=20, k=None, CU=None, discard_C=False, truncate='oldest', 

188 atol=None): 

189 """ 

190 Solve a matrix equation using flexible GCROT(m,k) algorithm. 

191 

192 Parameters 

193 ---------- 

194 A : {sparse matrix, ndarray, LinearOperator} 

195 The real or complex N-by-N matrix of the linear system. 

196 Alternatively, ``A`` can be a linear operator which can 

197 produce ``Ax`` using, e.g., 

198 ``scipy.sparse.linalg.LinearOperator``. 

199 b : ndarray 

200 Right hand side of the linear system. Has shape (N,) or (N,1). 

201 x0 : ndarray 

202 Starting guess for the solution. 

203 tol, atol : float, optional 

204 Tolerances for convergence, ``norm(residual) <= max(tol*norm(b), atol)``. 

205 The default for ``atol`` is `tol`. 

206 

207 .. warning:: 

208 

209 The default value for `atol` will be changed in a future release. 

210 For future compatibility, specify `atol` explicitly. 

211 maxiter : int, optional 

212 Maximum number of iterations. Iteration will stop after maxiter 

213 steps even if the specified tolerance has not been achieved. 

214 M : {sparse matrix, ndarray, LinearOperator}, optional 

215 Preconditioner for A. The preconditioner should approximate the 

216 inverse of A. gcrotmk is a 'flexible' algorithm and the preconditioner 

217 can vary from iteration to iteration. Effective preconditioning 

218 dramatically improves the rate of convergence, which implies that 

219 fewer iterations are needed to reach a given error tolerance. 

220 callback : function, optional 

221 User-supplied function to call after each iteration. It is called 

222 as callback(xk), where xk is the current solution vector. 

223 m : int, optional 

224 Number of inner FGMRES iterations per each outer iteration. 

225 Default: 20 

226 k : int, optional 

227 Number of vectors to carry between inner FGMRES iterations. 

228 According to [2]_, good values are around m. 

229 Default: m 

230 CU : list of tuples, optional 

231 List of tuples ``(c, u)`` which contain the columns of the matrices 

232 C and U in the GCROT(m,k) algorithm. For details, see [2]_. 

233 The list given and vectors contained in it are modified in-place. 

234 If not given, start from empty matrices. The ``c`` elements in the 

235 tuples can be ``None``, in which case the vectors are recomputed 

236 via ``c = A u`` on start and orthogonalized as described in [3]_. 

237 discard_C : bool, optional 

238 Discard the C-vectors at the end. Useful if recycling Krylov subspaces 

239 for different linear systems. 

240 truncate : {'oldest', 'smallest'}, optional 

241 Truncation scheme to use. Drop: oldest vectors, or vectors with 

242 smallest singular values using the scheme discussed in [1,2]. 

243 See [2]_ for detailed comparison. 

244 Default: 'oldest' 

245 

246 Returns 

247 ------- 

248 x : ndarray 

249 The solution found. 

250 info : int 

251 Provides convergence information: 

252 

253 * 0 : successful exit 

254 * >0 : convergence to tolerance not achieved, number of iterations 

255 

256 Examples 

257 -------- 

258 >>> import numpy as np 

259 >>> from scipy.sparse import csc_matrix 

260 >>> from scipy.sparse.linalg import gcrotmk 

261 >>> R = np.random.randn(5, 5) 

262 >>> A = csc_matrix(R) 

263 >>> b = np.random.randn(5) 

264 >>> x, exit_code = gcrotmk(A, b, atol=1e-5) 

265 >>> print(exit_code) 

266 0 

267 >>> np.allclose(A.dot(x), b) 

268 True 

269 

270 References 

271 ---------- 

272 .. [1] E. de Sturler, ''Truncation strategies for optimal Krylov subspace 

273 methods'', SIAM J. Numer. Anal. 36, 864 (1999). 

274 .. [2] J.E. Hicken and D.W. Zingg, ''A simplified and flexible variant 

275 of GCROT for solving nonsymmetric linear systems'', 

276 SIAM J. Sci. Comput. 32, 172 (2010). 

277 .. [3] M.L. Parks, E. de Sturler, G. Mackey, D.D. Johnson, S. Maiti, 

278 ''Recycling Krylov subspaces for sequences of linear systems'', 

279 SIAM J. Sci. Comput. 28, 1651 (2006). 

280 

281 """ 

282 A,M,x,b,postprocess = make_system(A,M,x0,b) 

283 

284 if not np.isfinite(b).all(): 

285 raise ValueError("RHS must contain only finite numbers") 

286 

287 if truncate not in ('oldest', 'smallest'): 

288 raise ValueError(f"Invalid value for 'truncate': {truncate!r}") 

289 

290 if atol is None: 

291 warnings.warn("scipy.sparse.linalg.gcrotmk called without specifying `atol`. " 

292 "The default value will change in the future. To preserve " 

293 "current behavior, set ``atol=tol``.", 

294 category=DeprecationWarning, stacklevel=2) 

295 atol = tol 

296 

297 matvec = A.matvec 

298 psolve = M.matvec 

299 

300 if CU is None: 

301 CU = [] 

302 

303 if k is None: 

304 k = m 

305 

306 axpy, dot, scal = None, None, None 

307 

308 if x0 is None: 

309 r = b.copy() 

310 else: 

311 r = b - matvec(x) 

312 

313 axpy, dot, scal, nrm2 = get_blas_funcs(['axpy', 'dot', 'scal', 'nrm2'], (x, r)) 

314 

315 b_norm = nrm2(b) 

316 if b_norm == 0: 

317 x = b 

318 return (postprocess(x), 0) 

319 

320 if discard_C: 

321 CU[:] = [(None, u) for c, u in CU] 

322 

323 # Reorthogonalize old vectors 

324 if CU: 

325 # Sort already existing vectors to the front 

326 CU.sort(key=lambda cu: cu[0] is not None) 

327 

328 # Fill-in missing ones 

329 C = np.empty((A.shape[0], len(CU)), dtype=r.dtype, order='F') 

330 us = [] 

331 j = 0 

332 while CU: 

333 # More memory-efficient: throw away old vectors as we go 

334 c, u = CU.pop(0) 

335 if c is None: 

336 c = matvec(u) 

337 C[:,j] = c 

338 j += 1 

339 us.append(u) 

340 

341 # Orthogonalize 

342 Q, R, P = qr(C, overwrite_a=True, mode='economic', pivoting=True) 

343 del C 

344 

345 # C := Q 

346 cs = list(Q.T) 

347 

348 # U := U P R^-1, back-substitution 

349 new_us = [] 

350 for j in range(len(cs)): 

351 u = us[P[j]] 

352 for i in range(j): 

353 u = axpy(us[P[i]], u, u.shape[0], -R[i,j]) 

354 if abs(R[j,j]) < 1e-12 * abs(R[0,0]): 

355 # discard rest of the vectors 

356 break 

357 u = scal(1.0/R[j,j], u) 

358 new_us.append(u) 

359 

360 # Form the new CU lists 

361 CU[:] = list(zip(cs, new_us))[::-1] 

362 

363 if CU: 

364 axpy, dot = get_blas_funcs(['axpy', 'dot'], (r,)) 

365 

366 # Solve first the projection operation with respect to the CU 

367 # vectors. This corresponds to modifying the initial guess to 

368 # be 

369 # 

370 # x' = x + U y 

371 # y = argmin_y || b - A (x + U y) ||^2 

372 # 

373 # The solution is y = C^H (b - A x) 

374 for c, u in CU: 

375 yc = dot(c, r) 

376 x = axpy(u, x, x.shape[0], yc) 

377 r = axpy(c, r, r.shape[0], -yc) 

378 

379 # GCROT main iteration 

380 for j_outer in range(maxiter): 

381 # -- callback 

382 if callback is not None: 

383 callback(x) 

384 

385 beta = nrm2(r) 

386 

387 # -- check stopping condition 

388 beta_tol = max(atol, tol * b_norm) 

389 

390 if beta <= beta_tol and (j_outer > 0 or CU): 

391 # recompute residual to avoid rounding error 

392 r = b - matvec(x) 

393 beta = nrm2(r) 

394 

395 if beta <= beta_tol: 

396 j_outer = -1 

397 break 

398 

399 ml = m + max(k - len(CU), 0) 

400 

401 cs = [c for c, u in CU] 

402 

403 try: 

404 Q, R, B, vs, zs, y, pres = _fgmres(matvec, 

405 r/beta, 

406 ml, 

407 rpsolve=psolve, 

408 atol=max(atol, tol*b_norm)/beta, 

409 cs=cs) 

410 y *= beta 

411 except LinAlgError: 

412 # Floating point over/underflow, non-finite result from 

413 # matmul etc. -- report failure. 

414 break 

415 

416 # 

417 # At this point, 

418 # 

419 # [A U, A Z] = [C, V] G; G = [ I B ] 

420 # [ 0 H ] 

421 # 

422 # where [C, V] has orthonormal columns, and r = beta v_0. Moreover, 

423 # 

424 # || b - A (x + Z y + U q) ||_2 = || r - C B y - V H y - C q ||_2 = min! 

425 # 

426 # from which y = argmin_y || beta e_1 - H y ||_2, and q = -B y 

427 # 

428 

429 # 

430 # GCROT(m,k) update 

431 # 

432 

433 # Define new outer vectors 

434 

435 # ux := (Z - U B) y 

436 ux = zs[0]*y[0] 

437 for z, yc in zip(zs[1:], y[1:]): 

438 ux = axpy(z, ux, ux.shape[0], yc) # ux += z*yc 

439 by = B.dot(y) 

440 for cu, byc in zip(CU, by): 

441 c, u = cu 

442 ux = axpy(u, ux, ux.shape[0], -byc) # ux -= u*byc 

443 

444 # cx := V H y 

445 hy = Q.dot(R.dot(y)) 

446 cx = vs[0] * hy[0] 

447 for v, hyc in zip(vs[1:], hy[1:]): 

448 cx = axpy(v, cx, cx.shape[0], hyc) # cx += v*hyc 

449 

450 # Normalize cx, maintaining cx = A ux 

451 # This new cx is orthogonal to the previous C, by construction 

452 try: 

453 alpha = 1/nrm2(cx) 

454 if not np.isfinite(alpha): 

455 raise FloatingPointError() 

456 except (FloatingPointError, ZeroDivisionError): 

457 # Cannot update, so skip it 

458 continue 

459 

460 cx = scal(alpha, cx) 

461 ux = scal(alpha, ux) 

462 

463 # Update residual and solution 

464 gamma = dot(cx, r) 

465 r = axpy(cx, r, r.shape[0], -gamma) # r -= gamma*cx 

466 x = axpy(ux, x, x.shape[0], gamma) # x += gamma*ux 

467 

468 # Truncate CU 

469 if truncate == 'oldest': 

470 while len(CU) >= k and CU: 

471 del CU[0] 

472 elif truncate == 'smallest': 

473 if len(CU) >= k and CU: 

474 # cf. [1,2] 

475 D = solve(R[:-1,:].T, B.T).T 

476 W, sigma, V = svd(D) 

477 

478 # C := C W[:,:k-1], U := U W[:,:k-1] 

479 new_CU = [] 

480 for j, w in enumerate(W[:,:k-1].T): 

481 c, u = CU[0] 

482 c = c * w[0] 

483 u = u * w[0] 

484 for cup, wp in zip(CU[1:], w[1:]): 

485 cp, up = cup 

486 c = axpy(cp, c, c.shape[0], wp) 

487 u = axpy(up, u, u.shape[0], wp) 

488 

489 # Reorthogonalize at the same time; not necessary 

490 # in exact arithmetic, but floating point error 

491 # tends to accumulate here 

492 for cp, up in new_CU: 

493 alpha = dot(cp, c) 

494 c = axpy(cp, c, c.shape[0], -alpha) 

495 u = axpy(up, u, u.shape[0], -alpha) 

496 alpha = nrm2(c) 

497 c = scal(1.0/alpha, c) 

498 u = scal(1.0/alpha, u) 

499 

500 new_CU.append((c, u)) 

501 CU[:] = new_CU 

502 

503 # Add new vector to CU 

504 CU.append((cx, ux)) 

505 

506 # Include the solution vector to the span 

507 CU.append((None, x.copy())) 

508 if discard_C: 

509 CU[:] = [(None, uz) for cz, uz in CU] 

510 

511 return postprocess(x), j_outer + 1