1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

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

# Distributed under the same license as Scipy. 

 

from __future__ import division, print_function, absolute_import 

 

import warnings 

import numpy as np 

from numpy.linalg import LinAlgError 

from scipy._lib.six import xrange 

from scipy.linalg import get_blas_funcs, get_lapack_funcs 

from .utils import make_system 

 

from ._gcrotmk import _fgmres 

 

__all__ = ['lgmres'] 

 

 

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

inner_m=30, outer_k=3, outer_v=None, store_outer_Av=True, 

prepend_outer_v=False, atol=None): 

""" 

Solve a matrix equation using the LGMRES algorithm. 

 

The LGMRES algorithm [1]_ [2]_ is designed to avoid some problems 

in the convergence in restarted GMRES, and often converges in fewer 

iterations. 

 

Parameters 

---------- 

A : {sparse matrix, dense matrix, LinearOperator} 

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

b : {array, matrix} 

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

x0 : {array, matrix} 

Starting guess for the solution. 

tol, atol : float, optional 

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

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

 

.. warning:: 

 

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

For future compatibility, specify `atol` explicitly. 

maxiter : int, optional 

Maximum number of iterations. Iteration will stop after maxiter 

steps even if the specified tolerance has not been achieved. 

M : {sparse matrix, dense matrix, LinearOperator}, optional 

Preconditioner for A. The preconditioner should approximate the 

inverse of A. Effective preconditioning dramatically improves the 

rate of convergence, which implies that fewer iterations are needed 

to reach a given error tolerance. 

callback : function, optional 

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

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

inner_m : int, optional 

Number of inner GMRES iterations per each outer iteration. 

outer_k : int, optional 

Number of vectors to carry between inner GMRES iterations. 

According to [1]_, good values are in the range of 1...3. 

However, note that if you want to use the additional vectors to 

accelerate solving multiple similar problems, larger values may 

be beneficial. 

outer_v : list of tuples, optional 

List containing tuples ``(v, Av)`` of vectors and corresponding 

matrix-vector products, used to augment the Krylov subspace, and 

carried between inner GMRES iterations. The element ``Av`` can 

be `None` if the matrix-vector product should be re-evaluated. 

This parameter is modified in-place by `lgmres`, and can be used 

to pass "guess" vectors in and out of the algorithm when solving 

similar problems. 

store_outer_Av : bool, optional 

Whether LGMRES should store also A*v in addition to vectors `v` 

in the `outer_v` list. Default is True. 

prepend_outer_v : bool, optional  

Whether to put outer_v augmentation vectors before Krylov iterates. 

In standard LGMRES, prepend_outer_v=False. 

 

Returns 

------- 

x : array or matrix 

The converged solution. 

info : int 

Provides convergence information: 

 

- 0 : successful exit 

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

- <0 : illegal input or breakdown 

 

Notes 

----- 

The LGMRES algorithm [1]_ [2]_ is designed to avoid the 

slowing of convergence in restarted GMRES, due to alternating 

residual vectors. Typically, it often outperforms GMRES(m) of 

comparable memory requirements by some measure, or at least is not 

much worse. 

 

Another advantage in this algorithm is that you can supply it with 

'guess' vectors in the `outer_v` argument that augment the Krylov 

subspace. If the solution lies close to the span of these vectors, 

the algorithm converges faster. This can be useful if several very 

similar matrices need to be inverted one after another, such as in 

Newton-Krylov iteration where the Jacobian matrix often changes 

little in the nonlinear steps. 

 

References 

---------- 

.. [1] A.H. Baker and E.R. Jessup and T. Manteuffel, "A Technique for 

Accelerating the Convergence of Restarted GMRES", SIAM J. Matrix 

Anal. Appl. 26, 962 (2005). 

.. [2] A.H. Baker, "On Improving the Performance of the Linear Solver 

restarted GMRES", PhD thesis, University of Colorado (2003). 

 

Examples 

-------- 

>>> from scipy.sparse import csc_matrix 

>>> from scipy.sparse.linalg import lgmres 

>>> A = csc_matrix([[3, 2, 0], [1, -1, 0], [0, 5, 1]], dtype=float) 

>>> b = np.array([2, 4, -1], dtype=float) 

>>> x, exitCode = lgmres(A, b) 

>>> print(exitCode) # 0 indicates successful convergence 

0 

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

True 

""" 

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

 

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

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

 

if atol is None: 

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

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

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

category=DeprecationWarning, stacklevel=2) 

atol = tol 

 

matvec = A.matvec 

psolve = M.matvec 

 

if outer_v is None: 

outer_v = [] 

 

axpy, dot, scal = None, None, None 

nrm2 = get_blas_funcs('nrm2', [b]) 

 

b_norm = nrm2(b) 

ptol_max_factor = 1.0 

 

for k_outer in xrange(maxiter): 

r_outer = matvec(x) - b 

 

# -- callback 

if callback is not None: 

callback(x) 

 

# -- determine input type routines 

if axpy is None: 

if np.iscomplexobj(r_outer) and not np.iscomplexobj(x): 

x = x.astype(r_outer.dtype) 

axpy, dot, scal, nrm2 = get_blas_funcs(['axpy', 'dot', 'scal', 'nrm2'], 

(x, r_outer)) 

 

# -- check stopping condition 

r_norm = nrm2(r_outer) 

if r_norm <= max(atol, tol * b_norm): 

break 

 

# -- inner LGMRES iteration 

v0 = -psolve(r_outer) 

inner_res_0 = nrm2(v0) 

 

if inner_res_0 == 0: 

rnorm = nrm2(r_outer) 

raise RuntimeError("Preconditioner returned a zero vector; " 

"|v| ~ %.1g, |M v| = 0" % rnorm) 

 

v0 = scal(1.0/inner_res_0, v0) 

 

ptol = min(ptol_max_factor, max(atol, tol*b_norm)/r_norm) 

 

try: 

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

v0, 

inner_m, 

lpsolve=psolve, 

atol=ptol, 

outer_v=outer_v, 

prepend_outer_v=prepend_outer_v) 

y *= inner_res_0 

if not np.isfinite(y).all(): 

# Overflow etc. in computation. There's no way to 

# recover from this, so we have to bail out. 

raise LinAlgError() 

except LinAlgError: 

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

# matmul etc. -- report failure. 

return postprocess(x), k_outer + 1 

 

# Inner loop tolerance control 

if pres > ptol: 

ptol_max_factor = min(1.0, 1.5 * ptol_max_factor) 

else: 

ptol_max_factor = max(1e-16, 0.25 * ptol_max_factor) 

 

# -- GMRES terminated: eval solution 

dx = zs[0]*y[0] 

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

dx = axpy(w, dx, dx.shape[0], yc) # dx += w*yc 

 

# -- Store LGMRES augmentation vectors 

nx = nrm2(dx) 

if nx > 0: 

if store_outer_Av: 

q = Q.dot(R.dot(y)) 

ax = vs[0]*q[0] 

for v, qc in zip(vs[1:], q[1:]): 

ax = axpy(v, ax, ax.shape[0], qc) 

outer_v.append((dx/nx, ax/nx)) 

else: 

outer_v.append((dx/nx, None)) 

 

# -- Retain only a finite number of augmentation vectors 

while len(outer_v) > outer_k: 

del outer_v[0] 

 

# -- Apply step 

x += dx 

else: 

# didn't converge ... 

return postprocess(x), maxiter 

 

return postprocess(x), 0