feectools 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- feectools/__init__.py +0 -0
- feectools/accelerate/__init__.py +0 -0
- feectools/accelerate/accelerate.py +220 -0
- feectools/accelerate/compile_psydac.mk +52 -0
- feectools/api/__init__.py +0 -0
- feectools/api/essential_bc.py +122 -0
- feectools/api/fem_bilinear_form.py +2226 -0
- feectools/api/fem_common.py +286 -0
- feectools/api/fem_sum_form.py +123 -0
- feectools/api/settings.py +82 -0
- feectools/core/__init__.py +11 -0
- feectools/core/bsplines.py +1107 -0
- feectools/core/bsplines_kernels.py +1349 -0
- feectools/core/field_evaluation_kernels.py +5015 -0
- feectools/core/tests/__init__.py +0 -0
- feectools/core/tests/test_bsplines.py +263 -0
- feectools/core/tests/test_bsplines_kernel.py +40 -0
- feectools/core/tests/test_bsplines_pyccel.py +752 -0
- feectools/ddm/__init__.py +3 -0
- feectools/ddm/basic.py +78 -0
- feectools/ddm/blocking_data_exchanger.py +348 -0
- feectools/ddm/cart.py +1835 -0
- feectools/ddm/interface_data_exchanger.py +122 -0
- feectools/ddm/mpi.py +109 -0
- feectools/ddm/nonblocking_data_exchanger.py +331 -0
- feectools/ddm/partition.py +207 -0
- feectools/ddm/petsc.py +112 -0
- feectools/ddm/tests/__init__.py +0 -0
- feectools/ddm/tests/test_cart_1d.py +138 -0
- feectools/ddm/tests/test_cart_2d.py +164 -0
- feectools/ddm/tests/test_cart_3d.py +158 -0
- feectools/ddm/tests/test_multicart_2d.py +173 -0
- feectools/ddm/tests/test_partition.py +124 -0
- feectools/ddm/utilities.py +24 -0
- feectools/feec/__init__.py +0 -0
- feectools/feec/derivatives.py +780 -0
- feectools/feec/dof_kernels.py +210 -0
- feectools/feec/global_geometric_projectors.py +1073 -0
- feectools/feec/hodge.py +148 -0
- feectools/fem/__init__.py +0 -0
- feectools/fem/basic.py +465 -0
- feectools/fem/grid.py +181 -0
- feectools/fem/partitioning.py +344 -0
- feectools/fem/projectors.py +160 -0
- feectools/fem/splines.py +559 -0
- feectools/fem/tensor.py +1393 -0
- feectools/fem/tests/__init__.py +0 -0
- feectools/fem/tests/analytical_profiles_1d.py +100 -0
- feectools/fem/tests/analytical_profiles_base.py +34 -0
- feectools/fem/tests/splines_error_bounds.py +155 -0
- feectools/fem/tests/test_spline_histopolation.py +120 -0
- feectools/fem/tests/test_spline_interpolation.py +182 -0
- feectools/fem/tests/test_splines.py +184 -0
- feectools/fem/tests/test_splines_par.py +46 -0
- feectools/fem/tests/test_vector_spaces.py +150 -0
- feectools/fem/tests/utilities.py +47 -0
- feectools/fem/vector.py +729 -0
- feectools/linalg/__init__.py +0 -0
- feectools/linalg/basic.py +1386 -0
- feectools/linalg/block.py +1451 -0
- feectools/linalg/direct_solvers.py +201 -0
- feectools/linalg/fft.py +258 -0
- feectools/linalg/kernels/__init__.py +0 -0
- feectools/linalg/kernels/axpy_kernels.py +57 -0
- feectools/linalg/kernels/inner_kernels.py +100 -0
- feectools/linalg/kernels/matvec_kernels.py +206 -0
- feectools/linalg/kernels/stencil2IJV_kernels.py +227 -0
- feectools/linalg/kernels/stencil2coo_kernels.py +179 -0
- feectools/linalg/kernels/transpose_kernels.py +263 -0
- feectools/linalg/kron.py +911 -0
- feectools/linalg/solvers.py +1914 -0
- feectools/linalg/sparse.py +114 -0
- feectools/linalg/stencil.py +2923 -0
- feectools/linalg/stencil_dot_kernels.py +317 -0
- feectools/linalg/stencil_transpose_kernels.py +372 -0
- feectools/linalg/tests/__init__.py +0 -0
- feectools/linalg/tests/test_block.py +1588 -0
- feectools/linalg/tests/test_fft.py +106 -0
- feectools/linalg/tests/test_kron_stencil_matrix.py +114 -0
- feectools/linalg/tests/test_linalg.py +1065 -0
- feectools/linalg/tests/test_matrix_free.py +128 -0
- feectools/linalg/tests/test_solvers.py +213 -0
- feectools/linalg/tests/test_stencil_interface_matrix.py +379 -0
- feectools/linalg/tests/test_stencil_vector.py +1036 -0
- feectools/linalg/tests/test_stencil_vector_space.py +440 -0
- feectools/linalg/topetsc.py +522 -0
- feectools/linalg/utilities.py +200 -0
- feectools/utilities/__init__.py +0 -0
- feectools/utilities/quadratures.py +113 -0
- feectools/utilities/utils.py +166 -0
- feectools/version.py +1 -0
- feectools-0.1.0.dist-info/METADATA +66 -0
- feectools-0.1.0.dist-info/RECORD +98 -0
- feectools-0.1.0.dist-info/WHEEL +5 -0
- feectools-0.1.0.dist-info/entry_points.txt +3 -0
- feectools-0.1.0.dist-info/licenses/AUTHORS +22 -0
- feectools-0.1.0.dist-info/licenses/LICENSE +21 -0
- feectools-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1914 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
"""
|
|
3
|
+
This module provides iterative solvers and preconditioners.
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
import numpy as np
|
|
7
|
+
from math import sqrt
|
|
8
|
+
|
|
9
|
+
from feectools.utilities.utils import is_real
|
|
10
|
+
from feectools.linalg.utilities import _sym_ortho
|
|
11
|
+
from feectools.linalg.basic import (Vector, LinearOperator,
|
|
12
|
+
InverseLinearOperator, IdentityOperator, ScaledLinearOperator)
|
|
13
|
+
|
|
14
|
+
__all__ = (
|
|
15
|
+
'inverse',
|
|
16
|
+
'ConjugateGradient',
|
|
17
|
+
'PConjugateGradient',
|
|
18
|
+
'BiConjugateGradient',
|
|
19
|
+
'BiConjugateGradientStabilized',
|
|
20
|
+
'PBiConjugateGradientStabilized',
|
|
21
|
+
'MinimumResidual',
|
|
22
|
+
'LSMR',
|
|
23
|
+
'GMRES'
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
#===============================================================================
|
|
27
|
+
def inverse(A, solver, **kwargs):
|
|
28
|
+
"""
|
|
29
|
+
A function to create objects of all InverseLinearOperator subclasses.
|
|
30
|
+
|
|
31
|
+
These are, as of June 06, 2023:
|
|
32
|
+
ConjugateGradient, PConjugateGradient, BiConjugateGradient,
|
|
33
|
+
BiConjugateGradientStabilized, MinimumResidual, LSMR, GMRES.
|
|
34
|
+
|
|
35
|
+
The kwargs given must be compatible with the chosen solver subclass.
|
|
36
|
+
|
|
37
|
+
Parameters
|
|
38
|
+
----------
|
|
39
|
+
A : feectools.linalg.basic.LinearOperator
|
|
40
|
+
Left-hand-side matrix A of linear system; individual entries A[i,j]
|
|
41
|
+
can't be accessed, but A has 'shape' attribute and provides 'dot(p)'
|
|
42
|
+
function (e.g. a matrix-vector product A*p).
|
|
43
|
+
|
|
44
|
+
solver : str
|
|
45
|
+
Preferred iterative solver. Options are: 'cg', 'pcg', 'bicg',
|
|
46
|
+
'bicgstab', 'pbicgstab', 'minres', 'lsmr', 'gmres'.
|
|
47
|
+
|
|
48
|
+
Returns
|
|
49
|
+
-------
|
|
50
|
+
obj : feectools.linalg.basic.InverseLinearOperator
|
|
51
|
+
A linear operator acting as the inverse of A, of the chosen subclass
|
|
52
|
+
(for example feectools.linalg.solvers.ConjugateGradient).
|
|
53
|
+
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
# Map each possible value of the `solver` string with a specific
|
|
57
|
+
# `InverseLinearOperator` subclass in this module:
|
|
58
|
+
solvers_dict = {
|
|
59
|
+
'cg' : ConjugateGradient,
|
|
60
|
+
'pcg' : PConjugateGradient,
|
|
61
|
+
'bicg' : BiConjugateGradient,
|
|
62
|
+
'bicgstab' : BiConjugateGradientStabilized,
|
|
63
|
+
'pbicgstab': PBiConjugateGradientStabilized,
|
|
64
|
+
'minres' : MinimumResidual,
|
|
65
|
+
'lsmr' : LSMR,
|
|
66
|
+
'gmres' : GMRES,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# Check solver input
|
|
70
|
+
if solver not in solvers_dict:
|
|
71
|
+
raise ValueError(f"Required solver '{solver}' not understood.")
|
|
72
|
+
|
|
73
|
+
assert isinstance(A, LinearOperator)
|
|
74
|
+
|
|
75
|
+
if isinstance(A, IdentityOperator):
|
|
76
|
+
return A
|
|
77
|
+
elif isinstance(A, ScaledLinearOperator):
|
|
78
|
+
return ScaledLinearOperator(domain=A.codomain, codomain=A.domain, c=1/A.scalar, A=inverse(A, solver, **kwargs))
|
|
79
|
+
elif isinstance(A, InverseLinearOperator):
|
|
80
|
+
return A.linop
|
|
81
|
+
|
|
82
|
+
# Instantiate object of correct solver class
|
|
83
|
+
cls = solvers_dict[solver]
|
|
84
|
+
obj = cls(A, **kwargs)
|
|
85
|
+
|
|
86
|
+
return obj
|
|
87
|
+
|
|
88
|
+
#===============================================================================
|
|
89
|
+
class ConjugateGradient(InverseLinearOperator):
|
|
90
|
+
"""
|
|
91
|
+
Conjugate Gradient (CG).
|
|
92
|
+
|
|
93
|
+
A LinearOperator subclass. Objects of this class are meant to be created using :func:~`solvers.inverse`.
|
|
94
|
+
The .dot (and also the .solve) function are based on the
|
|
95
|
+
Conjugate gradient algorithm for solving linear system Ax=b.
|
|
96
|
+
Implementation from [1], page 137.
|
|
97
|
+
|
|
98
|
+
Parameters
|
|
99
|
+
----------
|
|
100
|
+
A : feectools.linalg.basic.LinearOperator
|
|
101
|
+
Left-hand-side matrix A of linear system; individual entries A[i,j]
|
|
102
|
+
can't be accessed, but A has 'shape' attribute and provides 'dot(p)'
|
|
103
|
+
function (i.e. matrix-vector product A*p).
|
|
104
|
+
|
|
105
|
+
x0 : feectools.linalg.basic.Vector
|
|
106
|
+
First guess of solution for iterative solver (optional).
|
|
107
|
+
|
|
108
|
+
tol : float
|
|
109
|
+
Absolute tolerance for L2-norm of residual r = A*x - b.
|
|
110
|
+
|
|
111
|
+
maxiter : int
|
|
112
|
+
Maximum number of iterations.
|
|
113
|
+
|
|
114
|
+
verbose : bool
|
|
115
|
+
If True, L2-norm of residual r is printed at each iteration.
|
|
116
|
+
|
|
117
|
+
recycle : bool
|
|
118
|
+
Stores a copy of the output in x0 to speed up consecutive calculations of slightly altered linear systems
|
|
119
|
+
|
|
120
|
+
References
|
|
121
|
+
----------
|
|
122
|
+
[1] A. Maister, Numerik linearer Gleichungssysteme, Springer ed. 2015.
|
|
123
|
+
|
|
124
|
+
"""
|
|
125
|
+
def __init__(self, A, *, x0=None, tol=1e-6, maxiter=1000, verbose=False, recycle=False):
|
|
126
|
+
|
|
127
|
+
self._options = {"x0":x0, "tol":tol, "maxiter":maxiter, "verbose":verbose, "recycle":recycle}
|
|
128
|
+
|
|
129
|
+
super().__init__(A, **self._options)
|
|
130
|
+
|
|
131
|
+
self._tmps = {key: self.domain.zeros() for key in ("v", "r", "p")}
|
|
132
|
+
self._info = None
|
|
133
|
+
|
|
134
|
+
def solve(self, b, out=None):
|
|
135
|
+
"""
|
|
136
|
+
Conjugate gradient algorithm for solving linear system Ax=b.
|
|
137
|
+
Only working if A is an hermitian and positive-definite linear operator.
|
|
138
|
+
Implementation from [1], page 137.
|
|
139
|
+
Info can be accessed using get_info(), see :func:~`basic.InverseLinearOperator.get_info`.
|
|
140
|
+
|
|
141
|
+
Parameters
|
|
142
|
+
----------
|
|
143
|
+
b : feectools.linalg.basic.Vector
|
|
144
|
+
Right-hand-side vector of linear system Ax = b. Individual entries b[i] need
|
|
145
|
+
not be accessed, but b has 'shape' attribute and provides 'copy()' and
|
|
146
|
+
'inner(p)' functions (b.inner(p) is the vector inner product b*p); moreover,
|
|
147
|
+
scalar multiplication and sum operations are available.
|
|
148
|
+
|
|
149
|
+
out : feectools.linalg.basic.Vector | NoneType
|
|
150
|
+
The output vector, or None (optional).
|
|
151
|
+
|
|
152
|
+
Returns
|
|
153
|
+
-------
|
|
154
|
+
x : feectools.linalg.basic.Vector
|
|
155
|
+
Numerical solution of the linear system. To check the convergence of the solver,
|
|
156
|
+
use the method InverseLinearOperator.get_info().
|
|
157
|
+
|
|
158
|
+
References
|
|
159
|
+
----------
|
|
160
|
+
[1] A. Maister, Numerik linearer Gleichungssysteme, Springer ed. 2015.
|
|
161
|
+
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
A = self._A
|
|
165
|
+
domain = self._domain
|
|
166
|
+
codomain = self._codomain
|
|
167
|
+
options = self._options
|
|
168
|
+
x0 = options["x0"]
|
|
169
|
+
tol = options["tol"]
|
|
170
|
+
maxiter = options["maxiter"]
|
|
171
|
+
verbose = options["verbose"]
|
|
172
|
+
recycle = options["recycle"]
|
|
173
|
+
|
|
174
|
+
assert isinstance(b, Vector)
|
|
175
|
+
assert b.space is domain
|
|
176
|
+
|
|
177
|
+
# First guess of solution
|
|
178
|
+
if out is not None:
|
|
179
|
+
assert isinstance(out, Vector)
|
|
180
|
+
assert out.space is codomain
|
|
181
|
+
|
|
182
|
+
x = x0.copy(out=out)
|
|
183
|
+
|
|
184
|
+
# Extract local storage
|
|
185
|
+
v = self._tmps["v"]
|
|
186
|
+
r = self._tmps["r"]
|
|
187
|
+
p = self._tmps["p"]
|
|
188
|
+
|
|
189
|
+
# First values
|
|
190
|
+
A.dot(x, out=v)
|
|
191
|
+
b.copy(out=r)
|
|
192
|
+
r -= v
|
|
193
|
+
am = r.inner(r).real
|
|
194
|
+
r.copy(out=p)
|
|
195
|
+
|
|
196
|
+
tol_sqr = tol**2
|
|
197
|
+
|
|
198
|
+
if verbose:
|
|
199
|
+
print( "CG solver:" )
|
|
200
|
+
print( "+---------+---------------------+")
|
|
201
|
+
print( "+ Iter. # | L2-norm of residual |")
|
|
202
|
+
print( "+---------+---------------------+")
|
|
203
|
+
template = "| {:7d} | {:19.2e} |"
|
|
204
|
+
print(template.format(1, sqrt(am)))
|
|
205
|
+
|
|
206
|
+
# Iterate to convergence
|
|
207
|
+
for m in range(2, maxiter+1):
|
|
208
|
+
if am < tol_sqr:
|
|
209
|
+
m -= 1
|
|
210
|
+
break
|
|
211
|
+
A.dot(p, out=v)
|
|
212
|
+
l = am / v.inner(p)
|
|
213
|
+
|
|
214
|
+
x.mul_iadd(l, p) # this is x += l*p
|
|
215
|
+
r.mul_iadd(-l, v) # this is r -= l*v
|
|
216
|
+
|
|
217
|
+
am1 = r.inner(r).real
|
|
218
|
+
p *= (am1/am)
|
|
219
|
+
p += r
|
|
220
|
+
am = am1
|
|
221
|
+
if verbose:
|
|
222
|
+
print(template.format(m, sqrt(am)))
|
|
223
|
+
|
|
224
|
+
if verbose:
|
|
225
|
+
print( "+---------+---------------------+")
|
|
226
|
+
|
|
227
|
+
# Convergence information
|
|
228
|
+
self._info = {'niter': m, 'success': am < tol_sqr, 'res_norm': sqrt(am) }
|
|
229
|
+
|
|
230
|
+
if recycle:
|
|
231
|
+
x.copy(out=self._options["x0"])
|
|
232
|
+
|
|
233
|
+
return x
|
|
234
|
+
|
|
235
|
+
def dot(self, b, out=None):
|
|
236
|
+
return self.solve(b, out=out)
|
|
237
|
+
|
|
238
|
+
#===============================================================================
|
|
239
|
+
class PConjugateGradient(InverseLinearOperator):
|
|
240
|
+
"""
|
|
241
|
+
Preconditioned Conjugate Gradient (PCG).
|
|
242
|
+
|
|
243
|
+
A LinearOperator subclass. Objects of this class are meant to be created using :func:~`solvers.inverse`.
|
|
244
|
+
The .dot (and also the .solve) function are based on a preconditioned conjugate gradient method.
|
|
245
|
+
The Preconditioned Conjugate Gradient (PCG) algorithm solves the linear
|
|
246
|
+
system A x = b where A is a symmetric and positive-definite matrix, i.e.
|
|
247
|
+
A = A^T and y A y > 0 for any vector y. The preconditioner P is a matrix
|
|
248
|
+
which approximates the inverse of A. The algorithm assumes that P is also
|
|
249
|
+
symmetric and positive definite.
|
|
250
|
+
|
|
251
|
+
Since this is a matrix-free iterative method, both A and P are provided as
|
|
252
|
+
`LinearOperator` objects which must implement the `dot` method.
|
|
253
|
+
|
|
254
|
+
Parameters
|
|
255
|
+
----------
|
|
256
|
+
A : feectools.linalg.basic.LinearOperator
|
|
257
|
+
Left-hand-side matrix A of the linear system. This should be symmetric
|
|
258
|
+
and positive definite.
|
|
259
|
+
|
|
260
|
+
pc: feectools.linalg.basic.LinearOperator
|
|
261
|
+
Preconditioner which should approximate the inverse of A (optional).
|
|
262
|
+
Like A, the preconditioner should be symmetric and positive definite.
|
|
263
|
+
|
|
264
|
+
x0 : feectools.linalg.basic.Vector
|
|
265
|
+
First guess of solution for iterative solver (optional).
|
|
266
|
+
|
|
267
|
+
tol : float
|
|
268
|
+
Absolute tolerance for L2-norm of residual r = A x - b. (Default: 1e-6)
|
|
269
|
+
|
|
270
|
+
maxiter: int
|
|
271
|
+
Maximum number of iterations. (Default: 1000)
|
|
272
|
+
|
|
273
|
+
verbose : bool
|
|
274
|
+
If True, the L2-norm of the residual r is printed at each iteration.
|
|
275
|
+
(Default: False)
|
|
276
|
+
|
|
277
|
+
recycle : bool
|
|
278
|
+
If True, a copy of the output is stored in x0 to speed up consecutive
|
|
279
|
+
calculations of slightly altered linear systems. (Default: False)
|
|
280
|
+
|
|
281
|
+
"""
|
|
282
|
+
def __init__(self, A, *, pc=None, x0=None, tol=1e-6, maxiter=1000, verbose=False, recycle=False):
|
|
283
|
+
|
|
284
|
+
self._options = {"x0":x0, "pc":pc, "tol":tol, "maxiter":maxiter, "verbose":verbose, "recycle":recycle}
|
|
285
|
+
|
|
286
|
+
super().__init__(A, **self._options)
|
|
287
|
+
|
|
288
|
+
if pc is None:
|
|
289
|
+
self._options['pc'] = IdentityOperator(self.domain)
|
|
290
|
+
else:
|
|
291
|
+
assert isinstance(pc, LinearOperator)
|
|
292
|
+
|
|
293
|
+
tmps_codomain = {key: self.codomain.zeros() for key in ("p", "s")}
|
|
294
|
+
tmps_domain = {key: self.domain.zeros() for key in ("v", "r")}
|
|
295
|
+
self._tmps = {**tmps_codomain, **tmps_domain}
|
|
296
|
+
self._info = None
|
|
297
|
+
|
|
298
|
+
def solve(self, b, out=None):
|
|
299
|
+
"""
|
|
300
|
+
Preconditioned Conjugate Gradient (PCG) solves the symetric positive definte
|
|
301
|
+
system Ax = b. It assumes that pc.dot(r) returns the solution to Ps = r,
|
|
302
|
+
where P is positive definite.
|
|
303
|
+
Info can be accessed using get_info(), see :func:~`basic.InverseLinearOperator.get_info`.
|
|
304
|
+
|
|
305
|
+
Parameters
|
|
306
|
+
----------
|
|
307
|
+
b : feectools.linalg.stencil.StencilVector
|
|
308
|
+
Right-hand-side vector of linear system.
|
|
309
|
+
|
|
310
|
+
out : feectools.linalg.basic.Vector | NoneType
|
|
311
|
+
The output vector, or None (optional).
|
|
312
|
+
|
|
313
|
+
Returns
|
|
314
|
+
-------
|
|
315
|
+
x : feectools.linalg.basic.Vector
|
|
316
|
+
Numerical solution of the linear system. To check the convergence of the solver,
|
|
317
|
+
use the method InverseLinearOperator.get_info().
|
|
318
|
+
|
|
319
|
+
"""
|
|
320
|
+
|
|
321
|
+
A = self._A
|
|
322
|
+
domain = self._domain
|
|
323
|
+
codomain = self._codomain
|
|
324
|
+
options = self._options
|
|
325
|
+
x0 = options["x0"]
|
|
326
|
+
pc = options["pc"]
|
|
327
|
+
tol = options["tol"]
|
|
328
|
+
maxiter = options["maxiter"]
|
|
329
|
+
verbose = options["verbose"]
|
|
330
|
+
recycle = options["recycle"]
|
|
331
|
+
|
|
332
|
+
assert isinstance(b, Vector)
|
|
333
|
+
assert b.space is domain
|
|
334
|
+
|
|
335
|
+
assert isinstance(pc, LinearOperator)
|
|
336
|
+
|
|
337
|
+
# First guess of solution
|
|
338
|
+
if out is not None:
|
|
339
|
+
assert isinstance(out, Vector)
|
|
340
|
+
assert out.space is codomain
|
|
341
|
+
|
|
342
|
+
x = x0.copy(out=out)
|
|
343
|
+
|
|
344
|
+
# Extract local storage
|
|
345
|
+
v = self._tmps["v"]
|
|
346
|
+
r = self._tmps["r"]
|
|
347
|
+
p = self._tmps["p"]
|
|
348
|
+
s = self._tmps["s"]
|
|
349
|
+
|
|
350
|
+
# First values
|
|
351
|
+
A.dot(x, out=v)
|
|
352
|
+
b.copy(out=r)
|
|
353
|
+
r -= v
|
|
354
|
+
nrmr_sqr = r.inner(r).real
|
|
355
|
+
pc.dot(r, out=s)
|
|
356
|
+
am = s.inner(r)
|
|
357
|
+
s.copy(out=p)
|
|
358
|
+
|
|
359
|
+
tol_sqr = tol**2
|
|
360
|
+
|
|
361
|
+
if verbose:
|
|
362
|
+
print( "Pre-conditioned CG solver:" )
|
|
363
|
+
print( "+---------+---------------------+")
|
|
364
|
+
print( "+ Iter. # | L2-norm of residual |")
|
|
365
|
+
print( "+---------+---------------------+")
|
|
366
|
+
template = "| {:7d} | {:19.2e} |"
|
|
367
|
+
print( template.format(1, sqrt(nrmr_sqr)))
|
|
368
|
+
|
|
369
|
+
# Iterate to convergence
|
|
370
|
+
for k in range(2, maxiter+1):
|
|
371
|
+
|
|
372
|
+
if nrmr_sqr < tol_sqr:
|
|
373
|
+
k -= 1
|
|
374
|
+
break
|
|
375
|
+
|
|
376
|
+
v = A.dot(p, out=v)
|
|
377
|
+
l = am / v.inner(p)
|
|
378
|
+
|
|
379
|
+
x.mul_iadd(l, p) # this is x += l*p
|
|
380
|
+
r.mul_iadd(-l, v) # this is r -= l*v
|
|
381
|
+
|
|
382
|
+
nrmr_sqr = r.inner(r).real
|
|
383
|
+
pc.dot(r, out=s)
|
|
384
|
+
|
|
385
|
+
am1 = s.inner(r)
|
|
386
|
+
|
|
387
|
+
# we are computing p = (am1 / am) * p + s by using axpy on s and exchanging the arrays
|
|
388
|
+
s.mul_iadd((am1/am), p)
|
|
389
|
+
s, p = p, s
|
|
390
|
+
|
|
391
|
+
am = am1
|
|
392
|
+
|
|
393
|
+
if verbose:
|
|
394
|
+
print( template.format(k, sqrt(nrmr_sqr)))
|
|
395
|
+
|
|
396
|
+
if verbose:
|
|
397
|
+
print( "+---------+---------------------+")
|
|
398
|
+
|
|
399
|
+
# Convergence information
|
|
400
|
+
self._info = {'niter': k, 'success': nrmr_sqr < tol_sqr, 'res_norm': sqrt(nrmr_sqr) }
|
|
401
|
+
|
|
402
|
+
if recycle:
|
|
403
|
+
x.copy(out=self._options["x0"])
|
|
404
|
+
|
|
405
|
+
return x
|
|
406
|
+
|
|
407
|
+
def dot(self, b, out=None):
|
|
408
|
+
return self.solve(b, out=out)
|
|
409
|
+
|
|
410
|
+
#===============================================================================
|
|
411
|
+
class BiConjugateGradient(InverseLinearOperator):
|
|
412
|
+
"""
|
|
413
|
+
Biconjugate Gradient (BiCG).
|
|
414
|
+
|
|
415
|
+
A LinearOperator subclass. Objects of this class are meant to be created using :func:~`solvers.inverse`.
|
|
416
|
+
The .dot (and also the .solve) function are based on the
|
|
417
|
+
Biconjugate gradient (BCG) algorithm for solving linear system Ax=b.
|
|
418
|
+
Implementation from [1], page 175.
|
|
419
|
+
|
|
420
|
+
Parameters
|
|
421
|
+
----------
|
|
422
|
+
A : feectools.linalg.basic.LinearOperator
|
|
423
|
+
Left-hand-side matrix A of linear system; individual entries A[i,j]
|
|
424
|
+
can't be accessed, but A has 'shape' attribute and provides 'dot(p)'
|
|
425
|
+
function (i.e. matrix-vector product A*p).
|
|
426
|
+
|
|
427
|
+
x0 : feectools.linalg.basic.Vector
|
|
428
|
+
First guess of solution for iterative solver (optional).
|
|
429
|
+
|
|
430
|
+
tol : float
|
|
431
|
+
Absolute tolerance for 2-norm of residual r = A*x - b.
|
|
432
|
+
|
|
433
|
+
maxiter: int
|
|
434
|
+
Maximum number of iterations.
|
|
435
|
+
|
|
436
|
+
verbose : bool
|
|
437
|
+
If True, 2-norm of residual r is printed at each iteration.
|
|
438
|
+
|
|
439
|
+
recycle : bool
|
|
440
|
+
Stores a copy of the output in x0 to speed up consecutive calculations of slightly altered linear systems
|
|
441
|
+
|
|
442
|
+
References
|
|
443
|
+
----------
|
|
444
|
+
[1] A. Maister, Numerik linearer Gleichungssysteme, Springer ed. 2015.
|
|
445
|
+
|
|
446
|
+
"""
|
|
447
|
+
def __init__(self, A, *, x0=None, tol=1e-6, maxiter=1000, verbose=False, recycle=False):
|
|
448
|
+
|
|
449
|
+
self._options = {"x0":x0, "tol":tol, "maxiter":maxiter, "verbose":verbose, "recycle":recycle}
|
|
450
|
+
|
|
451
|
+
super().__init__(A, **self._options)
|
|
452
|
+
|
|
453
|
+
self._Ah = A.H
|
|
454
|
+
self._tmps = {key: self.domain.zeros() for key in ("v", "r", "p", "vs", "rs", "ps")}
|
|
455
|
+
self._info = None
|
|
456
|
+
|
|
457
|
+
def solve(self, b, out=None):
|
|
458
|
+
"""
|
|
459
|
+
Biconjugate gradient (BCG) algorithm for solving linear system Ax=b.
|
|
460
|
+
Implementation from [1], page 175.
|
|
461
|
+
Info can be accessed using get_info(), see :func:~`basic.InverseLinearOperator.get_info`.
|
|
462
|
+
ToDo: Add optional preconditioner
|
|
463
|
+
|
|
464
|
+
Parameters
|
|
465
|
+
----------
|
|
466
|
+
b : feectools.linalg.basic.Vector
|
|
467
|
+
Right-hand-side vector of linear system. Individual entries b[i] need
|
|
468
|
+
not be accessed, but b has 'shape' attribute and provides 'copy()' and
|
|
469
|
+
'inner(p)' functions (b.inner(p) is the vector inner product b*p); moreover,
|
|
470
|
+
scalar multiplication and sum operations are available.
|
|
471
|
+
|
|
472
|
+
out : feectools.linalg.basic.Vector | NoneType
|
|
473
|
+
The output vector, or None (optional).
|
|
474
|
+
|
|
475
|
+
Returns
|
|
476
|
+
-------
|
|
477
|
+
x : feectools.linalg.basic.Vector
|
|
478
|
+
Numerical solution of linear system. To check the convergence of the solver,
|
|
479
|
+
use the method InverseLinearOperator.get_info().
|
|
480
|
+
|
|
481
|
+
References
|
|
482
|
+
----------
|
|
483
|
+
[1] A. Maister, Numerik linearer Gleichungssysteme, Springer ed. 2015.
|
|
484
|
+
|
|
485
|
+
"""
|
|
486
|
+
A = self._A
|
|
487
|
+
Ah = self._Ah
|
|
488
|
+
domain = self._domain
|
|
489
|
+
codomain = self._codomain
|
|
490
|
+
options = self._options
|
|
491
|
+
x0 = options["x0"]
|
|
492
|
+
tol = options["tol"]
|
|
493
|
+
maxiter = options["maxiter"]
|
|
494
|
+
verbose = options["verbose"]
|
|
495
|
+
recycle = options["recycle"]
|
|
496
|
+
|
|
497
|
+
assert isinstance(b, Vector)
|
|
498
|
+
assert b.space is domain
|
|
499
|
+
|
|
500
|
+
# First guess of solution
|
|
501
|
+
if out is not None:
|
|
502
|
+
assert isinstance(out, Vector)
|
|
503
|
+
assert out.space is codomain
|
|
504
|
+
|
|
505
|
+
x = x0.copy(out=out)
|
|
506
|
+
|
|
507
|
+
# Extract local storage
|
|
508
|
+
v = self._tmps["v"]
|
|
509
|
+
r = self._tmps["r"]
|
|
510
|
+
p = self._tmps["p"]
|
|
511
|
+
vs = self._tmps["vs"]
|
|
512
|
+
rs = self._tmps["rs"]
|
|
513
|
+
ps = self._tmps["ps"]
|
|
514
|
+
|
|
515
|
+
# First values
|
|
516
|
+
A.dot(x, out=v)
|
|
517
|
+
b.copy(out=r)
|
|
518
|
+
r -= v
|
|
519
|
+
r.copy(out=p)
|
|
520
|
+
v *= 0
|
|
521
|
+
|
|
522
|
+
r.copy(out=rs)
|
|
523
|
+
p.copy(out=ps)
|
|
524
|
+
v.copy(out=vs)
|
|
525
|
+
|
|
526
|
+
res_sqr = r.inner(r).real
|
|
527
|
+
tol_sqr = tol**2
|
|
528
|
+
|
|
529
|
+
if verbose:
|
|
530
|
+
print( "BiCG solver:" )
|
|
531
|
+
print( "+---------+---------------------+")
|
|
532
|
+
print( "+ Iter. # | L2-norm of residual |")
|
|
533
|
+
print( "+---------+---------------------+")
|
|
534
|
+
template = "| {:7d} | {:19.2e} |"
|
|
535
|
+
|
|
536
|
+
# Iterate to convergence
|
|
537
|
+
for m in range(1, maxiter + 1):
|
|
538
|
+
|
|
539
|
+
if res_sqr < tol_sqr:
|
|
540
|
+
m -= 1
|
|
541
|
+
break
|
|
542
|
+
|
|
543
|
+
#-----------------------
|
|
544
|
+
# MATRIX-VECTOR PRODUCTS
|
|
545
|
+
#-----------------------
|
|
546
|
+
A.dot(p, out=v)
|
|
547
|
+
Ah.dot(ps, out=vs)
|
|
548
|
+
#-----------------------
|
|
549
|
+
|
|
550
|
+
# c := (rs, r)
|
|
551
|
+
c = rs.inner(r)
|
|
552
|
+
|
|
553
|
+
# a := (rs, r) / (ps, v)
|
|
554
|
+
a = c / ps.inner(v)
|
|
555
|
+
|
|
556
|
+
#-----------------------
|
|
557
|
+
# SOLUTION UPDATE
|
|
558
|
+
#-----------------------
|
|
559
|
+
# x := x + a*p
|
|
560
|
+
x.mul_iadd(a, p)
|
|
561
|
+
#-----------------------
|
|
562
|
+
|
|
563
|
+
# r := r - a*v
|
|
564
|
+
r.mul_iadd(-a, v)
|
|
565
|
+
|
|
566
|
+
# rs := rs - conj(a)*vs
|
|
567
|
+
rs.mul_iadd(-a.conjugate(), vs)
|
|
568
|
+
|
|
569
|
+
# ||r||_2 := (r, r)
|
|
570
|
+
res_sqr = r.inner(r).real
|
|
571
|
+
|
|
572
|
+
# b := (rs, r)_{m+1} / (rs, r)_m
|
|
573
|
+
b = rs.inner(r) / c
|
|
574
|
+
|
|
575
|
+
# p := r + b*p
|
|
576
|
+
p *= b
|
|
577
|
+
p += r
|
|
578
|
+
|
|
579
|
+
# ps := rs + conj(b)*ps
|
|
580
|
+
ps *= b.conj()
|
|
581
|
+
ps += rs
|
|
582
|
+
|
|
583
|
+
if verbose:
|
|
584
|
+
print( template.format(m, sqrt(res_sqr)) )
|
|
585
|
+
|
|
586
|
+
if verbose:
|
|
587
|
+
print( "+---------+---------------------+")
|
|
588
|
+
|
|
589
|
+
# Convergence information
|
|
590
|
+
self._info = {'niter': m, 'success': res_sqr < tol_sqr, 'res_norm': sqrt(res_sqr)}
|
|
591
|
+
|
|
592
|
+
if recycle:
|
|
593
|
+
x.copy(out=self._options["x0"])
|
|
594
|
+
|
|
595
|
+
return x
|
|
596
|
+
|
|
597
|
+
def dot(self, b, out=None):
|
|
598
|
+
return self.solve(b, out=out)
|
|
599
|
+
|
|
600
|
+
#===============================================================================
|
|
601
|
+
class BiConjugateGradientStabilized(InverseLinearOperator):
|
|
602
|
+
"""
|
|
603
|
+
Biconjugate Gradient Stabilized (BiCGStab).
|
|
604
|
+
|
|
605
|
+
A LinearOperator subclass. Objects of this class are meant to be created using :func:~`solvers.inverse`.
|
|
606
|
+
The .dot (and also the .solve) function are based on the
|
|
607
|
+
Biconjugate gradient Stabilized (BCGSTAB) algorithm for solving linear system Ax=b.
|
|
608
|
+
Implementation from [1], page 175.
|
|
609
|
+
|
|
610
|
+
Parameters
|
|
611
|
+
----------
|
|
612
|
+
A : feectools.linalg.basic.LinearOperator
|
|
613
|
+
Left-hand-side matrix A of linear system; individual entries A[i,j]
|
|
614
|
+
can't be accessed, but A has 'shape' attribute and provides 'dot(p)'
|
|
615
|
+
function (i.e. matrix-vector product A*p).
|
|
616
|
+
|
|
617
|
+
x0 : feectools.linalg.basic.Vector
|
|
618
|
+
First guess of solution for iterative solver (optional).
|
|
619
|
+
|
|
620
|
+
tol : float
|
|
621
|
+
Absolute tolerance for 2-norm of residual r = A*x - b.
|
|
622
|
+
|
|
623
|
+
maxiter: int
|
|
624
|
+
Maximum number of iterations.
|
|
625
|
+
|
|
626
|
+
verbose : bool
|
|
627
|
+
If True, 2-norm of residual r is printed at each iteration.
|
|
628
|
+
|
|
629
|
+
recycle : bool
|
|
630
|
+
Stores a copy of the output in x0 to speed up consecutive calculations of slightly altered linear systems
|
|
631
|
+
|
|
632
|
+
References
|
|
633
|
+
----------
|
|
634
|
+
[1] A. Maister, Numerik linearer Gleichungssysteme, Springer ed. 2015.
|
|
635
|
+
|
|
636
|
+
"""
|
|
637
|
+
def __init__(self, A, *, x0=None, tol=1e-6, maxiter=1000, verbose=False, recycle=False):
|
|
638
|
+
|
|
639
|
+
self._options = {"x0": x0, "tol": tol, "maxiter": maxiter, "verbose": verbose, "recycle":recycle}
|
|
640
|
+
|
|
641
|
+
super().__init__(A, **self._options)
|
|
642
|
+
|
|
643
|
+
self._tmps = {key: self.domain.zeros() for key in ("v", "r", "p", "vr", "r0")}
|
|
644
|
+
self._info = None
|
|
645
|
+
|
|
646
|
+
def solve(self, b, out=None):
|
|
647
|
+
"""
|
|
648
|
+
Biconjugate gradient stabilized method (BCGSTAB) algorithm for solving linear system Ax=b.
|
|
649
|
+
Implementation from [1], page 175.
|
|
650
|
+
ToDo: Add optional preconditioner
|
|
651
|
+
|
|
652
|
+
Parameters
|
|
653
|
+
----------
|
|
654
|
+
b : feectools.linalg.basic.Vector
|
|
655
|
+
Right-hand-side vector of linear system. Individual entries b[i] need
|
|
656
|
+
not be accessed, but b has 'shape' attribute and provides 'copy()' and
|
|
657
|
+
'inner(p)' functions (b.inner(p) is the vector inner product b*p); moreover,
|
|
658
|
+
scalar multiplication and sum operations are available.
|
|
659
|
+
out : feectools.linalg.basic.Vector | NoneType
|
|
660
|
+
The output vector, or None (optional).
|
|
661
|
+
|
|
662
|
+
Returns
|
|
663
|
+
-------
|
|
664
|
+
x : feectools.linalg.basic.Vector
|
|
665
|
+
Numerical solution of linear system. To check the convergence of the solver,
|
|
666
|
+
use the method InverseLinearOperator.get_info().
|
|
667
|
+
|
|
668
|
+
info : dict
|
|
669
|
+
Dictionary containing convergence information:
|
|
670
|
+
- 'niter' = (int) number of iterations
|
|
671
|
+
- 'success' = (boolean) whether convergence criteria have been met
|
|
672
|
+
- 'res_norm' = (float) 2-norm of residual vector r = A*x - b.
|
|
673
|
+
|
|
674
|
+
References
|
|
675
|
+
----------
|
|
676
|
+
[1] H. A. van der Vorst. Bi-CGSTAB: A fast and smoothly converging variant of Bi-CG for the
|
|
677
|
+
solution of nonsymmetric linear systems. SIAM J. Sci. Stat. Comp., 13(2):631–644, 1992.
|
|
678
|
+
"""
|
|
679
|
+
|
|
680
|
+
A = self._A
|
|
681
|
+
domain = self._domain
|
|
682
|
+
codomain = self._codomain
|
|
683
|
+
options = self._options
|
|
684
|
+
x0 = options["x0"]
|
|
685
|
+
tol = options["tol"]
|
|
686
|
+
maxiter = options["maxiter"]
|
|
687
|
+
verbose = options["verbose"]
|
|
688
|
+
recycle = options["recycle"]
|
|
689
|
+
|
|
690
|
+
assert isinstance(b, Vector)
|
|
691
|
+
assert b.space is domain
|
|
692
|
+
|
|
693
|
+
# First guess of solution
|
|
694
|
+
if out is not None:
|
|
695
|
+
assert isinstance(out, Vector)
|
|
696
|
+
assert out.space is codomain
|
|
697
|
+
|
|
698
|
+
x = x0.copy(out=out)
|
|
699
|
+
|
|
700
|
+
# Extract local storage
|
|
701
|
+
v = self._tmps["v"]
|
|
702
|
+
r = self._tmps["r"]
|
|
703
|
+
p = self._tmps["p"]
|
|
704
|
+
vr = self._tmps["vr"]
|
|
705
|
+
r0 = self._tmps["r0"]
|
|
706
|
+
|
|
707
|
+
# First values
|
|
708
|
+
A.dot(x, out=v)
|
|
709
|
+
b.copy(out=r)
|
|
710
|
+
r -= v
|
|
711
|
+
#r = b - A.dot(x)
|
|
712
|
+
r.copy(out=p)
|
|
713
|
+
v *= 0.0
|
|
714
|
+
vr *= 0.0
|
|
715
|
+
|
|
716
|
+
r.copy(out=r0)
|
|
717
|
+
|
|
718
|
+
res_sqr = r.inner(r).real
|
|
719
|
+
tol_sqr = tol ** 2
|
|
720
|
+
|
|
721
|
+
if verbose:
|
|
722
|
+
print("BiCGSTAB solver:")
|
|
723
|
+
print("+---------+---------------------+")
|
|
724
|
+
print("+ Iter. # | L2-norm of residual |")
|
|
725
|
+
print("+---------+---------------------+")
|
|
726
|
+
template = "| {:7d} | {:19.2e} |"
|
|
727
|
+
|
|
728
|
+
# Iterate to convergence
|
|
729
|
+
for m in range(1, maxiter + 1):
|
|
730
|
+
|
|
731
|
+
if res_sqr < tol_sqr:
|
|
732
|
+
m -= 1
|
|
733
|
+
break
|
|
734
|
+
|
|
735
|
+
# -----------------------
|
|
736
|
+
# MATRIX-VECTOR PRODUCTS
|
|
737
|
+
# -----------------------
|
|
738
|
+
v = A.dot(p, out=v)
|
|
739
|
+
# -----------------------
|
|
740
|
+
|
|
741
|
+
# c := (r0, r)
|
|
742
|
+
c = r0.inner(r)
|
|
743
|
+
|
|
744
|
+
# a := (r0, r) / (r0, v)
|
|
745
|
+
a = c / (r0.inner(v))
|
|
746
|
+
|
|
747
|
+
# r := r - a*v
|
|
748
|
+
r.mul_iadd(-a, v)
|
|
749
|
+
|
|
750
|
+
# vr := A*r
|
|
751
|
+
vr = A.dot(r, out=vr)
|
|
752
|
+
|
|
753
|
+
# w := (r, A*r) / (A*r, A*r)
|
|
754
|
+
w = r.inner(vr) / vr.inner(vr)
|
|
755
|
+
|
|
756
|
+
# -----------------------
|
|
757
|
+
# SOLUTION UPDATE
|
|
758
|
+
# -----------------------
|
|
759
|
+
# x := x + a*p +w*r
|
|
760
|
+
x.mul_iadd(a, p)
|
|
761
|
+
x.mul_iadd(w, r)
|
|
762
|
+
# -----------------------
|
|
763
|
+
|
|
764
|
+
# r := r - w*A*r
|
|
765
|
+
r.mul_iadd(-w, vr)
|
|
766
|
+
|
|
767
|
+
# ||r||_2 := (r, r)
|
|
768
|
+
res_sqr = r.inner(r).real
|
|
769
|
+
|
|
770
|
+
if res_sqr < tol_sqr:
|
|
771
|
+
break
|
|
772
|
+
|
|
773
|
+
# b := a / w * (r0, r)_{m+1} / (r0, r)_m
|
|
774
|
+
b = r0.inner(r) * a / (c * w)
|
|
775
|
+
|
|
776
|
+
# p := r + b*p- b*w*v
|
|
777
|
+
p *= b
|
|
778
|
+
p += r
|
|
779
|
+
p.mul_iadd(-b * w, v)
|
|
780
|
+
|
|
781
|
+
if verbose:
|
|
782
|
+
print(template.format(m, sqrt(res_sqr)))
|
|
783
|
+
|
|
784
|
+
if verbose:
|
|
785
|
+
print("+---------+---------------------+")
|
|
786
|
+
|
|
787
|
+
# Convergence information
|
|
788
|
+
self._info = {'niter': m, 'success': res_sqr < tol_sqr, 'res_norm': sqrt(res_sqr)}
|
|
789
|
+
|
|
790
|
+
if recycle:
|
|
791
|
+
x.copy(out=self._options["x0"])
|
|
792
|
+
|
|
793
|
+
return x
|
|
794
|
+
|
|
795
|
+
def dot(self, b, out=None):
|
|
796
|
+
return self.solve(b, out=out)
|
|
797
|
+
|
|
798
|
+
#===============================================================================
|
|
799
|
+
class PBiConjugateGradientStabilized(InverseLinearOperator):
|
|
800
|
+
"""
|
|
801
|
+
Preconditioned Biconjugate Gradient Stabilized (PBiCGStab).
|
|
802
|
+
|
|
803
|
+
A LinearOperator subclass. Objects of this class are meant to be created using :func:~`solvers.inverse`.
|
|
804
|
+
The .dot (and also the .solve) function are based on the
|
|
805
|
+
preconditioned Biconjugate gradient Stabilized (PBCGSTAB) algorithm for solving linear system Ax=b.
|
|
806
|
+
Implementation from [1], page 251.
|
|
807
|
+
|
|
808
|
+
Parameters
|
|
809
|
+
----------
|
|
810
|
+
A : feectools.linalg.basic.LinearOperator
|
|
811
|
+
Left-hand-side matrix A of linear system; individual entries A[i,j]
|
|
812
|
+
can't be accessed, but A has 'shape' attribute and provides 'dot(p)'
|
|
813
|
+
function (i.e. matrix-vector product A*p).
|
|
814
|
+
pc: feectools.linalg.basic.LinearOperator
|
|
815
|
+
Preconditioner for A, it should approximate the inverse of A (can be None).
|
|
816
|
+
x0 : feectools.linalg.basic.Vector
|
|
817
|
+
First guess of solution for iterative solver (optional).
|
|
818
|
+
tol : float
|
|
819
|
+
Absolute tolerance for 2-norm of residual r = A*x - b.
|
|
820
|
+
maxiter: int
|
|
821
|
+
Maximum number of iterations.
|
|
822
|
+
verbose : bool
|
|
823
|
+
If True, 2-norm of residual r is printed at each iteration.
|
|
824
|
+
|
|
825
|
+
References
|
|
826
|
+
----------
|
|
827
|
+
[1] A. Maister, Numerik linearer Gleichungssysteme, Springer ed. 2015.
|
|
828
|
+
"""
|
|
829
|
+
def __init__(self, A, *, pc=None, x0=None, tol=1e-6, maxiter=1000, verbose=False, recycle=False):
|
|
830
|
+
|
|
831
|
+
self._options = {"pc": pc, "x0": x0, "tol": tol, "maxiter": maxiter, "verbose": verbose, "recycle": recycle}
|
|
832
|
+
|
|
833
|
+
super().__init__(A, **self._options)
|
|
834
|
+
|
|
835
|
+
if pc is None:
|
|
836
|
+
self._options['pc'] = IdentityOperator(self.domain)
|
|
837
|
+
else:
|
|
838
|
+
assert isinstance(pc, LinearOperator)
|
|
839
|
+
|
|
840
|
+
self._tmps = {key: self.domain.zeros() for key in ("v", "r", "s", "t",
|
|
841
|
+
"vp", "rp", "sp", "tp",
|
|
842
|
+
"pp", "av", "app", "osp",
|
|
843
|
+
"rp0")}
|
|
844
|
+
self._info = None
|
|
845
|
+
|
|
846
|
+
def solve(self, b, out=None):
|
|
847
|
+
"""
|
|
848
|
+
Preconditioned biconjugate gradient stabilized method (PBCGSTAB) algorithm for solving linear system Ax=b.
|
|
849
|
+
Implementation from [1], page 251.
|
|
850
|
+
|
|
851
|
+
Parameters
|
|
852
|
+
----------
|
|
853
|
+
b : feectools.linalg.basic.Vector
|
|
854
|
+
Right-hand-side vector of linear system. Individual entries b[i] need
|
|
855
|
+
not be accessed, but b has 'shape' attribute and provides 'copy()' and
|
|
856
|
+
'inner(p)' functions (b.inner(p) is the vector inner product b*p); moreover,
|
|
857
|
+
scalar multiplication and sum operations are available.
|
|
858
|
+
out : feectools.linalg.basic.Vector | NoneType
|
|
859
|
+
The output vector, or None (optional).
|
|
860
|
+
|
|
861
|
+
Returns
|
|
862
|
+
-------
|
|
863
|
+
x : feectools.linalg.basic.Vector
|
|
864
|
+
Numerical solution of linear system. To check the convergence of the solver,
|
|
865
|
+
use the method InverseLinearOperator.get_info().
|
|
866
|
+
|
|
867
|
+
info : dict
|
|
868
|
+
Dictionary containing convergence information:
|
|
869
|
+
- 'niter' = (int) number of iterations
|
|
870
|
+
- 'success' = (boolean) whether convergence criteria have been met
|
|
871
|
+
- 'res_norm' = (float) 2-norm of residual vector r = A*x - b.
|
|
872
|
+
|
|
873
|
+
References
|
|
874
|
+
----------
|
|
875
|
+
[1] A. Maister, Numerik linearer Gleichungssysteme, Springer ed. 2015.
|
|
876
|
+
|
|
877
|
+
"""
|
|
878
|
+
|
|
879
|
+
A = self._A
|
|
880
|
+
domain = self._domain
|
|
881
|
+
codomain = self._codomain
|
|
882
|
+
options = self._options
|
|
883
|
+
pc = options["pc"]
|
|
884
|
+
x0 = options["x0"]
|
|
885
|
+
tol = options["tol"]
|
|
886
|
+
maxiter = options["maxiter"]
|
|
887
|
+
verbose = options["verbose"]
|
|
888
|
+
recycle = options["recycle"]
|
|
889
|
+
|
|
890
|
+
assert isinstance(b, Vector)
|
|
891
|
+
assert b.space is domain
|
|
892
|
+
|
|
893
|
+
assert isinstance(pc, LinearOperator)
|
|
894
|
+
|
|
895
|
+
# first guess of solution
|
|
896
|
+
if out is not None:
|
|
897
|
+
assert isinstance(out, Vector)
|
|
898
|
+
assert out.space == codomain
|
|
899
|
+
out *= 0
|
|
900
|
+
if x0 is None:
|
|
901
|
+
x = out
|
|
902
|
+
else:
|
|
903
|
+
assert x0.shape == (A.shape[0],)
|
|
904
|
+
out += x0
|
|
905
|
+
x = out
|
|
906
|
+
else:
|
|
907
|
+
if x0 is None:
|
|
908
|
+
x = b.copy()
|
|
909
|
+
x *= 0.0
|
|
910
|
+
else:
|
|
911
|
+
assert x0.shape == (A.shape[0],)
|
|
912
|
+
x = x0.copy()
|
|
913
|
+
|
|
914
|
+
# preconditioner (must have a .solve method)
|
|
915
|
+
assert isinstance(pc, LinearOperator)
|
|
916
|
+
|
|
917
|
+
# extract temporary vectors
|
|
918
|
+
v = self._tmps['v']
|
|
919
|
+
r = self._tmps['r']
|
|
920
|
+
s = self._tmps['s']
|
|
921
|
+
t = self._tmps['t']
|
|
922
|
+
|
|
923
|
+
vp = self._tmps['vp']
|
|
924
|
+
rp = self._tmps['rp']
|
|
925
|
+
pp = self._tmps['pp']
|
|
926
|
+
sp = self._tmps['sp']
|
|
927
|
+
tp = self._tmps['tp']
|
|
928
|
+
|
|
929
|
+
av = self._tmps['av']
|
|
930
|
+
|
|
931
|
+
app = self._tmps['app']
|
|
932
|
+
osp = self._tmps['osp']
|
|
933
|
+
|
|
934
|
+
# first values: r = b - A @ x, rp = pp = PC @ r, rhop = |rp|^2
|
|
935
|
+
A.dot(x, out=v)
|
|
936
|
+
b.copy(out=r)
|
|
937
|
+
r -= v
|
|
938
|
+
|
|
939
|
+
pc.dot(r, out=rp)
|
|
940
|
+
rp.copy(out=pp)
|
|
941
|
+
|
|
942
|
+
rhop = rp.inner(rp)
|
|
943
|
+
|
|
944
|
+
# save initial residual vector rp0
|
|
945
|
+
rp0 = self._tmps['rp0']
|
|
946
|
+
rp.copy(out=rp0)
|
|
947
|
+
|
|
948
|
+
# squared residual norm and squared tolerance
|
|
949
|
+
res_sqr = r.inner(r).real
|
|
950
|
+
tol_sqr = tol**2
|
|
951
|
+
|
|
952
|
+
if verbose:
|
|
953
|
+
print("Pre-conditioned BICGSTAB solver:")
|
|
954
|
+
print("+---------+---------------------+")
|
|
955
|
+
print("+ Iter. # | L2-norm of residual |")
|
|
956
|
+
print("+---------+---------------------+")
|
|
957
|
+
template = "| {:7d} | {:19.2e} |"
|
|
958
|
+
|
|
959
|
+
# iterate to convergence or maximum number of iterations
|
|
960
|
+
niter = 0
|
|
961
|
+
|
|
962
|
+
while res_sqr > tol_sqr and niter < maxiter:
|
|
963
|
+
|
|
964
|
+
# v = A @ pp, vp = PC @ v, alphap = rhop/(vp.rp0)
|
|
965
|
+
A.dot(pp, out=v)
|
|
966
|
+
pc.dot(v, out=vp)
|
|
967
|
+
alphap = rhop / vp.inner(rp0)
|
|
968
|
+
|
|
969
|
+
# s = r - alphap*v, sp = PC @ s
|
|
970
|
+
r.copy(out=s)
|
|
971
|
+
v.copy(out=av)
|
|
972
|
+
av *= alphap
|
|
973
|
+
s -= av
|
|
974
|
+
pc.dot(s, out=sp)
|
|
975
|
+
|
|
976
|
+
# t = A @ sp, tp = PC @ t, omegap = (tp.sp)/(tp.tp)
|
|
977
|
+
A.dot(sp, out=t)
|
|
978
|
+
pc.dot(t, out=tp)
|
|
979
|
+
omegap = tp.inner(sp) / tp.inner(tp)
|
|
980
|
+
|
|
981
|
+
# x = x + alphap*pp + omegap*sp
|
|
982
|
+
pp.copy(out=app)
|
|
983
|
+
sp.copy(out=osp)
|
|
984
|
+
app *= alphap
|
|
985
|
+
osp *= omegap
|
|
986
|
+
x += app
|
|
987
|
+
x += osp
|
|
988
|
+
|
|
989
|
+
# r = s - omegap*t, rp = sp - omegap*tp
|
|
990
|
+
s.copy(out=r)
|
|
991
|
+
t *= omegap
|
|
992
|
+
r -= t
|
|
993
|
+
|
|
994
|
+
sp.copy(out=rp)
|
|
995
|
+
tp *= omegap
|
|
996
|
+
rp -= tp
|
|
997
|
+
|
|
998
|
+
# rhop_new = rp.rp0, betap = (alphap*rhop_new)/(omegap*rhop)
|
|
999
|
+
rhop_new = rp.inner(rp0)
|
|
1000
|
+
betap = (alphap*rhop_new) / (omegap*rhop)
|
|
1001
|
+
rhop = 1*rhop_new
|
|
1002
|
+
|
|
1003
|
+
# pp = rp + betap*(pp - omegap*vp)
|
|
1004
|
+
vp *= omegap
|
|
1005
|
+
pp -= vp
|
|
1006
|
+
pp *= betap
|
|
1007
|
+
pp += rp
|
|
1008
|
+
|
|
1009
|
+
# new residual norm
|
|
1010
|
+
res_sqr = r.inner(r).real
|
|
1011
|
+
|
|
1012
|
+
niter += 1
|
|
1013
|
+
|
|
1014
|
+
if verbose:
|
|
1015
|
+
print(template.format(niter, sqrt(res_sqr)))
|
|
1016
|
+
|
|
1017
|
+
if verbose:
|
|
1018
|
+
print("+---------+---------------------+")
|
|
1019
|
+
|
|
1020
|
+
# convergence information
|
|
1021
|
+
self._info = {'niter': niter, 'success': res_sqr <
|
|
1022
|
+
tol_sqr, 'res_norm': sqrt(res_sqr)}
|
|
1023
|
+
|
|
1024
|
+
if recycle:
|
|
1025
|
+
x.copy(out=self._options["x0"])
|
|
1026
|
+
|
|
1027
|
+
return x
|
|
1028
|
+
|
|
1029
|
+
def dot(self, b, out=None):
|
|
1030
|
+
return self.solve(b, out=out)
|
|
1031
|
+
|
|
1032
|
+
#===============================================================================
|
|
1033
|
+
class MinimumResidual(InverseLinearOperator):
|
|
1034
|
+
"""
|
|
1035
|
+
Minimum Residual (MinRes).
|
|
1036
|
+
|
|
1037
|
+
A LinearOperator subclass. Objects of this class are meant to be created using :func:~`solvers.inverse`.
|
|
1038
|
+
The .dot (and also the .solve) function
|
|
1039
|
+
Use MINimum RESidual iteration to solve Ax=b
|
|
1040
|
+
|
|
1041
|
+
MINRES minimizes norm(A*x - b) for a real symmetric matrix A. Unlike
|
|
1042
|
+
the Conjugate Gradient method, A can be indefinite or singular.
|
|
1043
|
+
|
|
1044
|
+
Parameters
|
|
1045
|
+
----------
|
|
1046
|
+
A : feectools.linalg.basic.LinearOperator
|
|
1047
|
+
Left-hand-side matrix A of linear system; individual entries A[i,j]
|
|
1048
|
+
can't be accessed, but A has 'shape' attribute and provides 'dot(p)'
|
|
1049
|
+
function (i.e. matrix-vector product A*p).
|
|
1050
|
+
|
|
1051
|
+
x0 : feectools.linalg.basic.Vector
|
|
1052
|
+
First guess of solution for iterative solver (optional).
|
|
1053
|
+
|
|
1054
|
+
tol : float
|
|
1055
|
+
Absolute tolerance for 2-norm of residual r = A*x - b.
|
|
1056
|
+
|
|
1057
|
+
maxiter: int
|
|
1058
|
+
Maximum number of iterations.
|
|
1059
|
+
|
|
1060
|
+
verbose : bool
|
|
1061
|
+
If True, 2-norm of residual r is printed at each iteration.
|
|
1062
|
+
|
|
1063
|
+
recycle : bool
|
|
1064
|
+
Stores a copy of the output in x0 to speed up consecutive calculations of slightly altered linear systems
|
|
1065
|
+
|
|
1066
|
+
Notes
|
|
1067
|
+
-----
|
|
1068
|
+
This is an adaptation of the MINRES Solver in Scipy, where the method is modified to accept Psydac data structures,
|
|
1069
|
+
https://github.com/scipy/scipy/blob/v1.7.1/scipy/sparse/linalg/isolve/minres.py
|
|
1070
|
+
|
|
1071
|
+
References
|
|
1072
|
+
----------
|
|
1073
|
+
Solution of sparse indefinite systems of linear equations,
|
|
1074
|
+
C. C. Paige and M. A. Saunders (1975),
|
|
1075
|
+
SIAM J. Numer. Anal. 12(4), pp. 617-629.
|
|
1076
|
+
https://web.stanford.edu/group/SOL/software/minres/
|
|
1077
|
+
|
|
1078
|
+
"""
|
|
1079
|
+
def __init__(self, A, *, x0=None, tol=1e-6, maxiter=1000, verbose=False, recycle=False):
|
|
1080
|
+
|
|
1081
|
+
self._options = {"x0":x0, "tol":tol, "maxiter":maxiter, "verbose":verbose, "recycle":recycle}
|
|
1082
|
+
|
|
1083
|
+
super().__init__(A, **self._options)
|
|
1084
|
+
|
|
1085
|
+
self._tmps = {key: self.domain.zeros() for key in ("res_old", "res_new", "w_new", "w_work", "w_old", "v", "y")}
|
|
1086
|
+
self._info = None
|
|
1087
|
+
|
|
1088
|
+
def solve(self, b, out=None):
|
|
1089
|
+
"""
|
|
1090
|
+
Use MINimum RESidual iteration to solve Ax=b
|
|
1091
|
+
MINRES minimizes norm(A*x - b) for a real symmetric matrix A. Unlike
|
|
1092
|
+
the Conjugate Gradient method, A can be indefinite or singular.
|
|
1093
|
+
Info can be accessed using get_info(), see :func:~`basic.InverseLinearOperator.get_info`.
|
|
1094
|
+
|
|
1095
|
+
Parameters
|
|
1096
|
+
----------
|
|
1097
|
+
b : feectools.linalg.basic.Vector
|
|
1098
|
+
Right-hand-side vector of linear system. Individual entries b[i] need
|
|
1099
|
+
not be accessed, but b has 'shape' attribute and provides 'copy()' and
|
|
1100
|
+
'inner(p)' functions (b.inner(p) is the vector inner product b*p); moreover,
|
|
1101
|
+
scalar multiplication and sum operations are available.
|
|
1102
|
+
|
|
1103
|
+
out : feectools.linalg.basic.Vector | NoneType
|
|
1104
|
+
The output vector, or None (optional).
|
|
1105
|
+
|
|
1106
|
+
Returns
|
|
1107
|
+
-------
|
|
1108
|
+
x : feectools.linalg.basic.Vector
|
|
1109
|
+
Numerical solution of linear system. To check the convergence of the solver,
|
|
1110
|
+
use the method InverseLinearOperator.get_info().
|
|
1111
|
+
|
|
1112
|
+
info : dict
|
|
1113
|
+
Dictionary containing convergence information:
|
|
1114
|
+
- 'niter' = (int) number of iterations
|
|
1115
|
+
- 'success' = (boolean) whether convergence criteria have been met
|
|
1116
|
+
- 'res_norm' = (float) 2-norm of residual vector r = A*x - b.
|
|
1117
|
+
|
|
1118
|
+
Notes
|
|
1119
|
+
-----
|
|
1120
|
+
This is an adaptation of the MINRES Solver in Scipy, where the method is modified to accept Psydac data structures,
|
|
1121
|
+
https://github.com/scipy/scipy/blob/v1.7.1/scipy/sparse/linalg/isolve/minres.py
|
|
1122
|
+
References
|
|
1123
|
+
----------
|
|
1124
|
+
Solution of sparse indefinite systems of linear equations,
|
|
1125
|
+
C. C. Paige and M. A. Saunders (1975),
|
|
1126
|
+
SIAM J. Numer. Anal. 12(4), pp. 617-629.
|
|
1127
|
+
https://web.stanford.edu/group/SOL/software/minres/
|
|
1128
|
+
"""
|
|
1129
|
+
|
|
1130
|
+
A = self._A
|
|
1131
|
+
domain = self._domain
|
|
1132
|
+
codomain = self._codomain
|
|
1133
|
+
options = self._options
|
|
1134
|
+
x0 = options["x0"]
|
|
1135
|
+
tol = options["tol"]
|
|
1136
|
+
maxiter = options["maxiter"]
|
|
1137
|
+
verbose = options["verbose"]
|
|
1138
|
+
recycle = options["recycle"]
|
|
1139
|
+
|
|
1140
|
+
assert isinstance(b, Vector)
|
|
1141
|
+
assert b.space is domain
|
|
1142
|
+
|
|
1143
|
+
# First guess of solution
|
|
1144
|
+
if out is not None:
|
|
1145
|
+
assert isinstance(out, Vector)
|
|
1146
|
+
assert out.space is codomain
|
|
1147
|
+
|
|
1148
|
+
x = x0.copy(out=out)
|
|
1149
|
+
|
|
1150
|
+
# Extract local storage
|
|
1151
|
+
v = self._tmps["v"]
|
|
1152
|
+
y = self._tmps["y"]
|
|
1153
|
+
w_new = self._tmps["w_new"]
|
|
1154
|
+
w_work = self._tmps["w_work"]
|
|
1155
|
+
w_old = self._tmps["w_old"]
|
|
1156
|
+
res_old = self._tmps["res_old"]
|
|
1157
|
+
res_new = self._tmps["res_new"]
|
|
1158
|
+
|
|
1159
|
+
istop = 0
|
|
1160
|
+
itn = 0
|
|
1161
|
+
rnorm = 0
|
|
1162
|
+
|
|
1163
|
+
eps = np.finfo(b.dtype).eps
|
|
1164
|
+
|
|
1165
|
+
A.dot(x, out=y)
|
|
1166
|
+
y -= b
|
|
1167
|
+
y *= -1.0
|
|
1168
|
+
y.copy(out=res_old) # res = b - A*x
|
|
1169
|
+
|
|
1170
|
+
beta = sqrt(res_old.inner(res_old))
|
|
1171
|
+
|
|
1172
|
+
# Initialize other quantities
|
|
1173
|
+
oldb = 0
|
|
1174
|
+
dbar = 0
|
|
1175
|
+
epsln = 0
|
|
1176
|
+
phibar = beta
|
|
1177
|
+
rhs1 = beta
|
|
1178
|
+
rhs2 = 0
|
|
1179
|
+
tnorm2 = 0
|
|
1180
|
+
gmax = 0
|
|
1181
|
+
gmin = np.finfo(b.dtype).max
|
|
1182
|
+
cs = -1
|
|
1183
|
+
sn = 0
|
|
1184
|
+
w_new *= 0.0
|
|
1185
|
+
w_work *= 0.0
|
|
1186
|
+
w_old *= 0.0
|
|
1187
|
+
res_old.copy(out=res_new)
|
|
1188
|
+
|
|
1189
|
+
if verbose:
|
|
1190
|
+
print( "MINRES solver:" )
|
|
1191
|
+
print( "+---------+---------------------+")
|
|
1192
|
+
print( "+ Iter. # | L2-norm of residual |")
|
|
1193
|
+
print( "+---------+---------------------+")
|
|
1194
|
+
template = "| {:7d} | {:19.2e} |"
|
|
1195
|
+
|
|
1196
|
+
# check whether solution is already converged:
|
|
1197
|
+
if beta < tol:
|
|
1198
|
+
istop = 1
|
|
1199
|
+
rnorm = beta
|
|
1200
|
+
if verbose:
|
|
1201
|
+
print( template.format(itn, rnorm ))
|
|
1202
|
+
|
|
1203
|
+
while istop == 0 and itn < maxiter:
|
|
1204
|
+
itn += 1
|
|
1205
|
+
|
|
1206
|
+
s = 1.0/beta
|
|
1207
|
+
y.copy(out=v)
|
|
1208
|
+
v *= s
|
|
1209
|
+
A.dot(v, out=y)
|
|
1210
|
+
|
|
1211
|
+
if itn >= 2:
|
|
1212
|
+
y.mul_iadd(-(beta/oldb), res_old)
|
|
1213
|
+
|
|
1214
|
+
alfa = v.inner(y)
|
|
1215
|
+
y.mul_iadd(-(alfa/beta), res_new)
|
|
1216
|
+
|
|
1217
|
+
# We put res_new in res_old and y in res_new
|
|
1218
|
+
res_new, res_old = res_old, res_new
|
|
1219
|
+
y.copy(out=res_new)
|
|
1220
|
+
|
|
1221
|
+
oldb = beta
|
|
1222
|
+
beta = sqrt(res_new.inner(res_new))
|
|
1223
|
+
tnorm2 += alfa**2 + oldb**2 + beta**2
|
|
1224
|
+
|
|
1225
|
+
# Apply previous rotation Qk-1 to get
|
|
1226
|
+
# [deltak epslnk+1] = [cs sn][dbark 0 ]
|
|
1227
|
+
# [gbar k dbar k+1] [sn -cs][alfak betak+1].
|
|
1228
|
+
|
|
1229
|
+
oldeps = epsln
|
|
1230
|
+
delta = cs * dbar + sn * alfa # delta1 = 0 deltak
|
|
1231
|
+
gbar = sn * dbar - cs * alfa # gbar 1 = alfa1 gbar k
|
|
1232
|
+
epsln = sn * beta # epsln2 = 0 epslnk+1
|
|
1233
|
+
dbar = - cs * beta # dbar 2 = beta2 dbar k+1
|
|
1234
|
+
root = sqrt(gbar**2 + dbar**2)
|
|
1235
|
+
|
|
1236
|
+
# Compute the next plane rotation Qk
|
|
1237
|
+
|
|
1238
|
+
gamma = sqrt(gbar**2 + beta**2) # gammak
|
|
1239
|
+
gamma = max(gamma, eps)
|
|
1240
|
+
cs = gbar / gamma # ck
|
|
1241
|
+
sn = beta / gamma # sk
|
|
1242
|
+
phi = cs * phibar # phik
|
|
1243
|
+
phibar = sn * phibar # phibark+1
|
|
1244
|
+
|
|
1245
|
+
# Update x.
|
|
1246
|
+
denom = 1.0/gamma
|
|
1247
|
+
|
|
1248
|
+
# We put w_old in w_work and w_new in w_old
|
|
1249
|
+
w_work, w_old = w_old, w_work
|
|
1250
|
+
w_new.copy(out=w_old)
|
|
1251
|
+
|
|
1252
|
+
w_new *= delta
|
|
1253
|
+
w_new.mul_iadd(oldeps, w_work)
|
|
1254
|
+
w_new -= v
|
|
1255
|
+
w_new *= -denom
|
|
1256
|
+
x.mul_iadd(phi, w_new)
|
|
1257
|
+
|
|
1258
|
+
# Go round again.
|
|
1259
|
+
|
|
1260
|
+
gmax = max(gmax, gamma)
|
|
1261
|
+
gmin = min(gmin, gamma)
|
|
1262
|
+
z = rhs1 / gamma
|
|
1263
|
+
rhs1 = rhs2 - delta*z
|
|
1264
|
+
rhs2 = - epsln*z
|
|
1265
|
+
|
|
1266
|
+
# Estimate various norms and test for convergence.
|
|
1267
|
+
|
|
1268
|
+
Anorm = sqrt(tnorm2)
|
|
1269
|
+
ynorm = sqrt(x.inner(x))
|
|
1270
|
+
|
|
1271
|
+
rnorm = phibar
|
|
1272
|
+
if ynorm == 0 or Anorm == 0:test1 = inf
|
|
1273
|
+
#else:test1 = rnorm / (Anorm*ynorm) # ||r|| / (||A|| ||x||)
|
|
1274
|
+
else:test1 = rnorm # ||r||
|
|
1275
|
+
|
|
1276
|
+
if Anorm == 0:test2 = inf
|
|
1277
|
+
else:test2 = root / Anorm # ||Ar|| / (||A|| ||r||)
|
|
1278
|
+
|
|
1279
|
+
# Estimate cond(A).
|
|
1280
|
+
# In this version we look at the diagonals of R in the
|
|
1281
|
+
# factorization of the lower Hessenberg matrix, Q * H = R,
|
|
1282
|
+
# where H is the tridiagonal matrix from Lanczos with one
|
|
1283
|
+
# extra row, beta(k+1) e_k^T.
|
|
1284
|
+
|
|
1285
|
+
Acond = gmax/gmin
|
|
1286
|
+
|
|
1287
|
+
if verbose:
|
|
1288
|
+
print( template.format(itn, rnorm ))
|
|
1289
|
+
|
|
1290
|
+
# See if any of the stopping criteria are satisfied.
|
|
1291
|
+
if istop == 0:
|
|
1292
|
+
t1 = 1 + test1 # These tests work if tol < eps
|
|
1293
|
+
t2 = 1 + test2
|
|
1294
|
+
if t2 <= 1:istop = 2
|
|
1295
|
+
if t1 <= 1:istop = 1
|
|
1296
|
+
|
|
1297
|
+
if Acond >= 0.1/eps:istop = 4
|
|
1298
|
+
|
|
1299
|
+
if test2 <= tol:istop = 2
|
|
1300
|
+
if test1 <= tol:istop = 1
|
|
1301
|
+
|
|
1302
|
+
if istop != 0:
|
|
1303
|
+
break
|
|
1304
|
+
|
|
1305
|
+
if verbose:
|
|
1306
|
+
print( "+---------+---------------------+")
|
|
1307
|
+
|
|
1308
|
+
# Convergence information
|
|
1309
|
+
self._info = {'niter': itn, 'success': rnorm<tol, 'res_norm': rnorm }
|
|
1310
|
+
|
|
1311
|
+
if recycle:
|
|
1312
|
+
x.copy(out=self._options["x0"])
|
|
1313
|
+
|
|
1314
|
+
return x
|
|
1315
|
+
|
|
1316
|
+
def dot(self, b, out=None):
|
|
1317
|
+
return self.solve(b, out=out)
|
|
1318
|
+
|
|
1319
|
+
#===============================================================================
|
|
1320
|
+
class LSMR(InverseLinearOperator):
|
|
1321
|
+
"""
|
|
1322
|
+
Least Squares Minimal Residual (LSMR).
|
|
1323
|
+
|
|
1324
|
+
A LinearOperator subclass. Objects of this class are meant to be created using :func:~`solvers.inverse`.
|
|
1325
|
+
The .dot (and also the .solve) function are based on the
|
|
1326
|
+
Iterative solver for least-squares problems.
|
|
1327
|
+
lsmr solves the system of linear equations ``Ax = b``. If the system
|
|
1328
|
+
is inconsistent, it solves the least-squares problem ``min ||b - Ax||_2``.
|
|
1329
|
+
``A`` is a rectangular matrix of dimension m-by-n, where all cases are
|
|
1330
|
+
allowed: m = n, m > n, or m < n. ``b`` is a vector of length m.
|
|
1331
|
+
The matrix A may be dense or sparse (usually sparse).
|
|
1332
|
+
|
|
1333
|
+
Parameters
|
|
1334
|
+
----------
|
|
1335
|
+
A : feectools.linalg.basic.LinearOperator
|
|
1336
|
+
Left-hand-side matrix A of linear system; individual entries A[i,j]
|
|
1337
|
+
can't be accessed, but A has 'shape' attribute and provides 'dot(p)'
|
|
1338
|
+
function (i.e. matrix-vector product A*p).
|
|
1339
|
+
|
|
1340
|
+
x0 : feectools.linalg.basic.Vector
|
|
1341
|
+
First guess of solution for iterative solver (optional).
|
|
1342
|
+
|
|
1343
|
+
tol : float
|
|
1344
|
+
Absolute tolerance for 2-norm of residual r = A*x - b.
|
|
1345
|
+
|
|
1346
|
+
atol : float
|
|
1347
|
+
Absolute tolerance for 2-norm of residual r = A*x - b.
|
|
1348
|
+
|
|
1349
|
+
btol : float
|
|
1350
|
+
Relative tolerance for 2-norm of residual r = A*x - b.
|
|
1351
|
+
|
|
1352
|
+
maxiter: int
|
|
1353
|
+
Maximum number of iterations.
|
|
1354
|
+
|
|
1355
|
+
conlim : float
|
|
1356
|
+
lsmr terminates if an estimate of cond(A) exceeds
|
|
1357
|
+
conlim.
|
|
1358
|
+
|
|
1359
|
+
verbose : bool
|
|
1360
|
+
If True, 2-norm of residual r is printed at each iteration.
|
|
1361
|
+
|
|
1362
|
+
recycle : bool
|
|
1363
|
+
Stores a copy of the output in x0 to speed up consecutive calculations of slightly altered linear systems
|
|
1364
|
+
|
|
1365
|
+
Notes
|
|
1366
|
+
-----
|
|
1367
|
+
This is an adaptation of the LSMR Solver in Scipy, where the method is modified to accept Psydac data structures,
|
|
1368
|
+
https://github.com/scipy/scipy/blob/v1.7.1/scipy/sparse/linalg/isolve/lsmr.py
|
|
1369
|
+
|
|
1370
|
+
References
|
|
1371
|
+
----------
|
|
1372
|
+
.. [1] D. C.-L. Fong and M. A. Saunders,
|
|
1373
|
+
"LSMR: An iterative algorithm for sparse least-squares problems",
|
|
1374
|
+
SIAM J. Sci. Comput., vol. 33, pp. 2950-2971, 2011.
|
|
1375
|
+
arxiv:`1006.0758`
|
|
1376
|
+
.. [2] LSMR Software, https://web.stanford.edu/group/SOL/software/lsmr/
|
|
1377
|
+
|
|
1378
|
+
"""
|
|
1379
|
+
def __init__(self, A, *, x0=None, tol=None, atol=None, btol=None, maxiter=1000, conlim=1e8, verbose=False, recycle=False):
|
|
1380
|
+
|
|
1381
|
+
self._options = {"x0":x0, "tol":tol, "atol":atol, "btol":btol,
|
|
1382
|
+
"maxiter":maxiter, "conlim":conlim, "verbose":verbose, "recycle":recycle}
|
|
1383
|
+
|
|
1384
|
+
super().__init__(A, **self._options)
|
|
1385
|
+
|
|
1386
|
+
# check additional options
|
|
1387
|
+
if atol is not None:
|
|
1388
|
+
assert is_real(atol), "atol must be a real number"
|
|
1389
|
+
assert atol >= 0, "atol must not be negative"
|
|
1390
|
+
if btol is not None:
|
|
1391
|
+
assert is_real(btol), "btol must be a real number"
|
|
1392
|
+
assert btol >= 0, "btol must not be negative"
|
|
1393
|
+
assert is_real(conlim), "conlim must be a real number" # actually an integer?
|
|
1394
|
+
assert conlim > 0, "conlim must be positive" # supposedly
|
|
1395
|
+
|
|
1396
|
+
self._info = None
|
|
1397
|
+
self._successful = None
|
|
1398
|
+
tmps_domain = {key: self.domain.zeros() for key in ("u", "u_work")}
|
|
1399
|
+
tmps_codomain = {key: self.codomain.zeros() for key in ("v", "v_work", "h", "hbar")}
|
|
1400
|
+
self._tmps = {**tmps_codomain, **tmps_domain}
|
|
1401
|
+
|
|
1402
|
+
def get_success(self):
|
|
1403
|
+
return self._successful
|
|
1404
|
+
|
|
1405
|
+
def solve(self, b, out=None):
|
|
1406
|
+
"""Iterative solver for least-squares problems.
|
|
1407
|
+
lsmr solves the system of linear equations ``Ax = b``. If the system
|
|
1408
|
+
is inconsistent, it solves the least-squares problem ``min ||b - Ax||_2``.
|
|
1409
|
+
``A`` is a rectangular matrix of dimension m-by-n, where all cases are
|
|
1410
|
+
allowed: m = n, m > n, or m < n. ``b`` is a vector of length m.
|
|
1411
|
+
The matrix A may be dense or sparse (usually sparse).
|
|
1412
|
+
Info can be accessed using get_info(), see :func:~`basic.InverseLinearOperator.get_info`.
|
|
1413
|
+
|
|
1414
|
+
Parameters
|
|
1415
|
+
----------
|
|
1416
|
+
b : feectools.linalg.basic.Vector
|
|
1417
|
+
Right-hand-side vector of linear system. Individual entries b[i] need
|
|
1418
|
+
not be accessed, but b has 'shape' attribute and provides 'copy()' and
|
|
1419
|
+
'inner(p)' functions (b.inner(p) is the vector inner product b*p); moreover,
|
|
1420
|
+
scalar multiplication and sum operations are available.
|
|
1421
|
+
|
|
1422
|
+
out : feectools.linalg.basic.Vector | NoneType
|
|
1423
|
+
The output vector, or None (optional).
|
|
1424
|
+
|
|
1425
|
+
Returns
|
|
1426
|
+
-------
|
|
1427
|
+
x : feectools.linalg.basic.Vector
|
|
1428
|
+
Numerical solution of linear system. To check the convergence of the solver,
|
|
1429
|
+
use the method InverseLinearOperator.get_info().
|
|
1430
|
+
|
|
1431
|
+
Notes
|
|
1432
|
+
-----
|
|
1433
|
+
This is an adaptation of the LSMR Solver in Scipy, where the method is modified to accept Psydac data structures,
|
|
1434
|
+
https://github.com/scipy/scipy/blob/v1.7.1/scipy/sparse/linalg/isolve/lsmr.py
|
|
1435
|
+
|
|
1436
|
+
References
|
|
1437
|
+
----------
|
|
1438
|
+
.. [1] D. C.-L. Fong and M. A. Saunders,
|
|
1439
|
+
"LSMR: An iterative algorithm for sparse least-squares problems",
|
|
1440
|
+
SIAM J. Sci. Comput., vol. 33, pp. 2950-2971, 2011.
|
|
1441
|
+
arxiv:`1006.0758`
|
|
1442
|
+
.. [2] LSMR Software, https://web.stanford.edu/group/SOL/software/lsmr/
|
|
1443
|
+
"""
|
|
1444
|
+
|
|
1445
|
+
A = self._A
|
|
1446
|
+
At = A.H
|
|
1447
|
+
domain = self._domain
|
|
1448
|
+
codomain = self._codomain
|
|
1449
|
+
options = self._options
|
|
1450
|
+
x0 = options["x0"]
|
|
1451
|
+
tol = options["tol"]
|
|
1452
|
+
atol = options["atol"]
|
|
1453
|
+
btol = options["btol"]
|
|
1454
|
+
maxiter = options["maxiter"]
|
|
1455
|
+
conlim = options["conlim"]
|
|
1456
|
+
verbose = options["verbose"]
|
|
1457
|
+
recycle = options["recycle"]
|
|
1458
|
+
|
|
1459
|
+
assert isinstance(b, Vector)
|
|
1460
|
+
assert b.space is domain
|
|
1461
|
+
|
|
1462
|
+
# First guess of solution
|
|
1463
|
+
if out is not None:
|
|
1464
|
+
assert isinstance(out, Vector)
|
|
1465
|
+
assert out.space is codomain
|
|
1466
|
+
|
|
1467
|
+
x = x0.copy(out=out)
|
|
1468
|
+
|
|
1469
|
+
# Extract local storage
|
|
1470
|
+
u = self._tmps["u"]
|
|
1471
|
+
v = self._tmps["v"]
|
|
1472
|
+
h = self._tmps["h"]
|
|
1473
|
+
hbar = self._tmps["hbar"]
|
|
1474
|
+
# Not strictly needed by the LSMR, but necessary to avoid temporaries
|
|
1475
|
+
u_work = self._tmps["u_work"]
|
|
1476
|
+
v_work = self._tmps["v_work"]
|
|
1477
|
+
|
|
1478
|
+
if atol is None:atol = 1e-6
|
|
1479
|
+
if btol is None:btol = 1e-6
|
|
1480
|
+
if tol is not None:
|
|
1481
|
+
atol = tol
|
|
1482
|
+
btol = tol
|
|
1483
|
+
|
|
1484
|
+
b.copy(out=u)
|
|
1485
|
+
normb = sqrt(b.inner(b).real)
|
|
1486
|
+
|
|
1487
|
+
A.dot(x, out=u_work)
|
|
1488
|
+
u -= u_work
|
|
1489
|
+
beta = sqrt(u.inner(u).real)
|
|
1490
|
+
|
|
1491
|
+
if beta > 0:
|
|
1492
|
+
u *= (1 / beta)
|
|
1493
|
+
At.dot(u, out=v)
|
|
1494
|
+
alpha = sqrt(v.inner(v).real)
|
|
1495
|
+
else:
|
|
1496
|
+
x.copy(out=v)
|
|
1497
|
+
alpha = 0
|
|
1498
|
+
|
|
1499
|
+
if alpha > 0:
|
|
1500
|
+
v *= (1 / alpha)
|
|
1501
|
+
|
|
1502
|
+
# Initialize variables for 1st iteration.
|
|
1503
|
+
itn = 0
|
|
1504
|
+
zetabar = alpha * beta
|
|
1505
|
+
alphabar = alpha
|
|
1506
|
+
rho = 1
|
|
1507
|
+
rhobar = 1
|
|
1508
|
+
cbar = 1
|
|
1509
|
+
sbar = 0
|
|
1510
|
+
|
|
1511
|
+
v.copy(out=h)
|
|
1512
|
+
x.copy(out=hbar)
|
|
1513
|
+
hbar *= 0.0
|
|
1514
|
+
|
|
1515
|
+
# Initialize variables for estimation of ||r||.
|
|
1516
|
+
|
|
1517
|
+
betadd = beta
|
|
1518
|
+
betad = 0
|
|
1519
|
+
rhodold = 1
|
|
1520
|
+
tautildeold = 0
|
|
1521
|
+
thetatilde = 0
|
|
1522
|
+
zeta = 0
|
|
1523
|
+
d = 0
|
|
1524
|
+
|
|
1525
|
+
# Initialize variables for estimation of ||A|| and cond(A)
|
|
1526
|
+
|
|
1527
|
+
normA2 = alpha * alpha
|
|
1528
|
+
maxrbar = 0
|
|
1529
|
+
minrbar = 1e+100
|
|
1530
|
+
|
|
1531
|
+
# Items for use in stopping rules, normb set earlier
|
|
1532
|
+
istop = 0
|
|
1533
|
+
ctol = 0
|
|
1534
|
+
if conlim > 0:ctol = 1 / conlim
|
|
1535
|
+
normr = beta
|
|
1536
|
+
|
|
1537
|
+
# Reverse the order here from the original matlab code because
|
|
1538
|
+
|
|
1539
|
+
if verbose:
|
|
1540
|
+
print( "LSMR solver:" )
|
|
1541
|
+
print( "+---------+---------------------+")
|
|
1542
|
+
print( "+ Iter. # | L2-norm of residual |")
|
|
1543
|
+
print( "+---------+---------------------+")
|
|
1544
|
+
template = "| {:7d} | {:19.2e} |"
|
|
1545
|
+
|
|
1546
|
+
# Main iteration loop.
|
|
1547
|
+
for itn in range(1, maxiter + 1):
|
|
1548
|
+
|
|
1549
|
+
# Perform the next step of the bidiagonalization to obtain the
|
|
1550
|
+
# next beta, u, alpha, v. These satisfy the relations
|
|
1551
|
+
# beta*u = a*v - alpha*u,
|
|
1552
|
+
# alpha*v = A'*u - beta*v.
|
|
1553
|
+
|
|
1554
|
+
u *= -alpha
|
|
1555
|
+
A.dot(v, out=u_work)
|
|
1556
|
+
u += u_work
|
|
1557
|
+
beta = sqrt(u.inner(u).real)
|
|
1558
|
+
|
|
1559
|
+
if beta > 0:
|
|
1560
|
+
u *= (1 / beta)
|
|
1561
|
+
v *= -beta
|
|
1562
|
+
At.dot(u, out=v_work)
|
|
1563
|
+
v += v_work
|
|
1564
|
+
alpha = sqrt(v.inner(v).real)
|
|
1565
|
+
if alpha > 0:v *= (1 / alpha)
|
|
1566
|
+
|
|
1567
|
+
# At this point, beta = beta_{k+1}, alpha = alpha_{k+1}.
|
|
1568
|
+
|
|
1569
|
+
# Construct rotation Qhat_{k,2k+1}.
|
|
1570
|
+
|
|
1571
|
+
chat, shat, alphahat = _sym_ortho(alphabar, 0.)
|
|
1572
|
+
|
|
1573
|
+
# Use a plane rotation (Q_i) to turn B_i to R_i
|
|
1574
|
+
|
|
1575
|
+
rhoold = rho
|
|
1576
|
+
c, s, rho = _sym_ortho(alphahat, beta)
|
|
1577
|
+
thetanew = s*alpha
|
|
1578
|
+
alphabar = c*alpha
|
|
1579
|
+
|
|
1580
|
+
# Use a plane rotation (Qbar_i) to turn R_i^T to R_i^bar
|
|
1581
|
+
|
|
1582
|
+
rhobarold = rhobar
|
|
1583
|
+
zetaold = zeta
|
|
1584
|
+
thetabar = sbar * rho
|
|
1585
|
+
rhotemp = cbar * rho
|
|
1586
|
+
cbar, sbar, rhobar = _sym_ortho(cbar * rho, thetanew)
|
|
1587
|
+
zeta = cbar * zetabar
|
|
1588
|
+
zetabar = - sbar * zetabar
|
|
1589
|
+
|
|
1590
|
+
# Update h, h_hat, x.
|
|
1591
|
+
|
|
1592
|
+
hbar *= - (thetabar * rho / (rhoold * rhobarold))
|
|
1593
|
+
hbar += h
|
|
1594
|
+
|
|
1595
|
+
x.mul_iadd((zeta / (rho * rhobar)), hbar)
|
|
1596
|
+
|
|
1597
|
+
h *= - (thetanew / rho)
|
|
1598
|
+
h += v
|
|
1599
|
+
|
|
1600
|
+
# Estimate of ||r||.
|
|
1601
|
+
|
|
1602
|
+
# Apply rotation Qhat_{k,2k+1}.
|
|
1603
|
+
betaacute = chat * betadd
|
|
1604
|
+
betacheck = -shat * betadd
|
|
1605
|
+
|
|
1606
|
+
# Apply rotation Q_{k,k+1}.
|
|
1607
|
+
betahat = c * betaacute
|
|
1608
|
+
betadd = -s * betaacute
|
|
1609
|
+
|
|
1610
|
+
# Apply rotation Qtilde_{k-1}.
|
|
1611
|
+
# betad = betad_{k-1} here.
|
|
1612
|
+
|
|
1613
|
+
thetatildeold = thetatilde
|
|
1614
|
+
ctildeold, stildeold, rhotildeold = _sym_ortho(rhodold, thetabar)
|
|
1615
|
+
thetatilde = stildeold * rhobar
|
|
1616
|
+
rhodold = ctildeold * rhobar
|
|
1617
|
+
betad = - stildeold * betad + ctildeold * betahat
|
|
1618
|
+
|
|
1619
|
+
# betad = betad_k here.
|
|
1620
|
+
# rhodold = rhod_k here.
|
|
1621
|
+
|
|
1622
|
+
tautildeold = (zetaold - thetatildeold * tautildeold) / rhotildeold
|
|
1623
|
+
taud = (zeta - thetatilde * tautildeold) / rhodold
|
|
1624
|
+
d = d + betacheck * betacheck
|
|
1625
|
+
normr = sqrt(d + (betad - taud)**2 + betadd * betadd)
|
|
1626
|
+
|
|
1627
|
+
# Estimate ||A||.
|
|
1628
|
+
normA2 = normA2 + beta * beta
|
|
1629
|
+
normA = sqrt(normA2)
|
|
1630
|
+
normA2 = normA2 + alpha * alpha
|
|
1631
|
+
|
|
1632
|
+
# Estimate cond(A).
|
|
1633
|
+
maxrbar = max(maxrbar, rhobarold)
|
|
1634
|
+
if itn > 1:minrbar = min(minrbar, rhobarold)
|
|
1635
|
+
condA = max(maxrbar, rhotemp) / min(minrbar, rhotemp)
|
|
1636
|
+
|
|
1637
|
+
# Test for convergence.
|
|
1638
|
+
|
|
1639
|
+
# Compute norms for convergence testing.
|
|
1640
|
+
normar = abs(zetabar)
|
|
1641
|
+
normx = sqrt(x.inner(x).real)
|
|
1642
|
+
|
|
1643
|
+
# Now use these norms to estimate certain other quantities,
|
|
1644
|
+
# some of which will be small near a solution.
|
|
1645
|
+
|
|
1646
|
+
test1 = normr / normb
|
|
1647
|
+
if (normA * normr) != 0:test2 = normar / (normA * normr)
|
|
1648
|
+
else:test2 = np.infty
|
|
1649
|
+
test3 = 1 / condA
|
|
1650
|
+
t1 = test1 / (1 + normA * normx / normb)
|
|
1651
|
+
rtol = btol + atol * normA * normx / normb
|
|
1652
|
+
|
|
1653
|
+
# The following tests guard against extremely small values of
|
|
1654
|
+
# atol, btol or ctol. (The user may have set any or all of
|
|
1655
|
+
# the parameters atol, btol, conlim to 0.)
|
|
1656
|
+
# The effect is equivalent to the normAl tests using
|
|
1657
|
+
# atol = eps, btol = eps, conlim = 1/eps.
|
|
1658
|
+
|
|
1659
|
+
if itn >= maxiter:istop = 7
|
|
1660
|
+
if 1 + test3 <= 1:istop = 6
|
|
1661
|
+
if 1 + test2 <= 1:istop = 5
|
|
1662
|
+
if 1 + t1 <= 1:istop = 4
|
|
1663
|
+
|
|
1664
|
+
# Allow for tolerances set by the user.
|
|
1665
|
+
|
|
1666
|
+
if test3 <= ctol:istop = 3
|
|
1667
|
+
if test2 <= atol:istop = 2
|
|
1668
|
+
if test1 <= rtol:istop = 1
|
|
1669
|
+
|
|
1670
|
+
if verbose:
|
|
1671
|
+
print( template.format(itn, normr ))
|
|
1672
|
+
|
|
1673
|
+
if istop > 0:
|
|
1674
|
+
break
|
|
1675
|
+
|
|
1676
|
+
|
|
1677
|
+
if verbose:
|
|
1678
|
+
print( "+---------+---------------------+")
|
|
1679
|
+
|
|
1680
|
+
# Convergence information
|
|
1681
|
+
self._info = {'niter': itn, 'success': istop in [1,2,3], 'res_norm': normr }
|
|
1682
|
+
# Seems necessary, as algorithm might terminate even though rnorm > tol.
|
|
1683
|
+
self._successful = istop in [1,2,3]
|
|
1684
|
+
|
|
1685
|
+
if recycle:
|
|
1686
|
+
x.copy(out=self._options["x0"])
|
|
1687
|
+
|
|
1688
|
+
return x
|
|
1689
|
+
|
|
1690
|
+
def dot(self, b, out=None):
|
|
1691
|
+
return self.solve(b, out=out)
|
|
1692
|
+
|
|
1693
|
+
#===============================================================================
|
|
1694
|
+
class GMRES(InverseLinearOperator):
|
|
1695
|
+
"""
|
|
1696
|
+
Generalized Minimal Residual (GMRES).
|
|
1697
|
+
|
|
1698
|
+
A LinearOperator subclass. Objects of this class are meant to be created using :func:~`solvers.inverse`.
|
|
1699
|
+
The .dot (and also the .solve) function are based on the
|
|
1700
|
+
generalized minimal residual algorithm for solving linear system Ax=b.
|
|
1701
|
+
Implementation from Wikipedia
|
|
1702
|
+
|
|
1703
|
+
Parameters
|
|
1704
|
+
----------
|
|
1705
|
+
A : feectools.linalg.basic.LinearOperator
|
|
1706
|
+
Left-hand-side matrix A of linear system; individual entries A[i,j]
|
|
1707
|
+
can't be accessed, but A has 'shape' attribute and provides 'dot(p)'
|
|
1708
|
+
function (i.e. matrix-vector product A*p).
|
|
1709
|
+
|
|
1710
|
+
x0 : feectools.linalg.basic.Vector
|
|
1711
|
+
First guess of solution for iterative solver (optional).
|
|
1712
|
+
|
|
1713
|
+
tol : float
|
|
1714
|
+
Absolute tolerance for L2-norm of residual r = A*x - b.
|
|
1715
|
+
|
|
1716
|
+
maxiter: int
|
|
1717
|
+
Maximum number of iterations.
|
|
1718
|
+
|
|
1719
|
+
verbose : bool
|
|
1720
|
+
If True, L2-norm of residual r is printed at each iteration.
|
|
1721
|
+
|
|
1722
|
+
recycle : bool
|
|
1723
|
+
Stores a copy of the output in x0 to speed up consecutive calculations of slightly altered linear systems
|
|
1724
|
+
|
|
1725
|
+
References
|
|
1726
|
+
----------
|
|
1727
|
+
[1] Y. Saad and M.H. Schultz, "GMRES: A generalized minimal residual algorithm for solving nonsymmetric linear systems", SIAM J. Sci. Stat. Comput., 7:856–869, 1986.
|
|
1728
|
+
|
|
1729
|
+
"""
|
|
1730
|
+
def __init__(self, A, *, x0=None, tol=1e-6, maxiter=100, verbose=False, recycle=False):
|
|
1731
|
+
|
|
1732
|
+
self._options = {"x0":x0, "tol":tol, "maxiter":maxiter, "verbose":verbose, "recycle":recycle}
|
|
1733
|
+
|
|
1734
|
+
super().__init__(A, **self._options)
|
|
1735
|
+
|
|
1736
|
+
self._tmps = {key: self.domain.zeros() for key in ("r", "p")}
|
|
1737
|
+
|
|
1738
|
+
# Initialize upper Hessenberg matrix
|
|
1739
|
+
self._H = np.zeros((self._options["maxiter"] + 1, self._options["maxiter"]), dtype=A.domain.dtype)
|
|
1740
|
+
self._Q = []
|
|
1741
|
+
self._info = None
|
|
1742
|
+
|
|
1743
|
+
def solve(self, b, out=None):
|
|
1744
|
+
"""
|
|
1745
|
+
Generalized minimal residual algorithm for solving linear system Ax=b.
|
|
1746
|
+
Implementation from Wikipedia.
|
|
1747
|
+
Info can be accessed using get_info(), see :func:~`basic.InverseLinearOperator.get_info`.
|
|
1748
|
+
|
|
1749
|
+
Parameters
|
|
1750
|
+
----------
|
|
1751
|
+
b : feectools.linalg.basic.Vector
|
|
1752
|
+
Right-hand-side vector of linear system Ax = b. Individual entries b[i] need
|
|
1753
|
+
not be accessed, but b has 'shape' attribute and provides 'copy()' and
|
|
1754
|
+
'inner(p)' functions (b.inner(p) is the vector inner product b*p); moreover,
|
|
1755
|
+
scalar multiplication and sum operations are available.
|
|
1756
|
+
|
|
1757
|
+
out : feectools.linalg.basic.Vector | NoneType
|
|
1758
|
+
The output vector, or None (optional).
|
|
1759
|
+
|
|
1760
|
+
Returns
|
|
1761
|
+
-------
|
|
1762
|
+
x : feectools.linalg.basic.Vector
|
|
1763
|
+
Numerical solution of the linear system. To check the convergence of the solver,
|
|
1764
|
+
use the method InverseLinearOperator.get_info().
|
|
1765
|
+
|
|
1766
|
+
References
|
|
1767
|
+
----------
|
|
1768
|
+
[1] Y. Saad and M.H. Schultz, "GMRES: A generalized minimal residual algorithm for solving nonsymmetric linear systems", SIAM J. Sci. Stat. Comput., 7:856–869, 1986.
|
|
1769
|
+
|
|
1770
|
+
"""
|
|
1771
|
+
|
|
1772
|
+
A = self._A
|
|
1773
|
+
domain = self._domain
|
|
1774
|
+
codomain = self._codomain
|
|
1775
|
+
options = self._options
|
|
1776
|
+
x0 = options["x0"]
|
|
1777
|
+
tol = options["tol"]
|
|
1778
|
+
maxiter = options["maxiter"]
|
|
1779
|
+
verbose = options["verbose"]
|
|
1780
|
+
recycle = options["recycle"]
|
|
1781
|
+
|
|
1782
|
+
assert isinstance(b, Vector)
|
|
1783
|
+
assert b.space is domain
|
|
1784
|
+
|
|
1785
|
+
# First guess of solution
|
|
1786
|
+
if out is not None:
|
|
1787
|
+
assert isinstance(out, Vector)
|
|
1788
|
+
assert out.space is codomain
|
|
1789
|
+
|
|
1790
|
+
x = x0.copy(out=out)
|
|
1791
|
+
|
|
1792
|
+
# Extract local storage
|
|
1793
|
+
r = self._tmps["r"]
|
|
1794
|
+
p = self._tmps["p"]
|
|
1795
|
+
|
|
1796
|
+
# Internal objects of GMRES
|
|
1797
|
+
self._H[:,:] = 0.
|
|
1798
|
+
beta = []
|
|
1799
|
+
sn = []
|
|
1800
|
+
cn = []
|
|
1801
|
+
|
|
1802
|
+
# First values
|
|
1803
|
+
A.dot( x , out=r)
|
|
1804
|
+
r -= b
|
|
1805
|
+
|
|
1806
|
+
am = sqrt(r.inner(r).real)
|
|
1807
|
+
if am < tol:
|
|
1808
|
+
self._info = {'niter': 1, 'success': am < tol, 'res_norm': am }
|
|
1809
|
+
return x
|
|
1810
|
+
|
|
1811
|
+
beta.append(am)
|
|
1812
|
+
r *= - 1 / am
|
|
1813
|
+
|
|
1814
|
+
if len(self._Q) == 0:
|
|
1815
|
+
self._Q.append(r)
|
|
1816
|
+
else:
|
|
1817
|
+
r.copy(out=self._Q[0])
|
|
1818
|
+
|
|
1819
|
+
if verbose:
|
|
1820
|
+
print( "GMRES solver:" )
|
|
1821
|
+
print( "+---------+---------------------+")
|
|
1822
|
+
print( "+ Iter. # | L2-norm of residual |")
|
|
1823
|
+
print( "+---------+---------------------+")
|
|
1824
|
+
template = "| {:7d} | {:19.2e} |"
|
|
1825
|
+
print( template.format( 1, am ) )
|
|
1826
|
+
|
|
1827
|
+
# Iterate to convergence
|
|
1828
|
+
for k in range(maxiter):
|
|
1829
|
+
if am < tol:
|
|
1830
|
+
break
|
|
1831
|
+
|
|
1832
|
+
# run Arnoldi
|
|
1833
|
+
self.arnoldi(k, p)
|
|
1834
|
+
|
|
1835
|
+
# make the last diagonal entry in H equal to 0, so that H becomes upper triangular
|
|
1836
|
+
self.apply_givens_rotation(k, sn, cn)
|
|
1837
|
+
|
|
1838
|
+
# update the residual vector
|
|
1839
|
+
beta.append(- sn[k] * beta[k])
|
|
1840
|
+
beta[k] *= cn[k]
|
|
1841
|
+
|
|
1842
|
+
am = abs(beta[k+1])
|
|
1843
|
+
if verbose:
|
|
1844
|
+
print( template.format( k+2, am ) )
|
|
1845
|
+
|
|
1846
|
+
if verbose:
|
|
1847
|
+
print( "+---------+---------------------+")
|
|
1848
|
+
# calculate result
|
|
1849
|
+
y = self.solve_triangular(self._H[:k, :k], beta[:k]) # system of upper triangular matrix
|
|
1850
|
+
|
|
1851
|
+
for i in range(k):
|
|
1852
|
+
x.mul_iadd(y[i], self._Q[i])
|
|
1853
|
+
|
|
1854
|
+
# Convergence information
|
|
1855
|
+
self._info = {'niter': k+1, 'success': am < tol, 'res_norm': am }
|
|
1856
|
+
|
|
1857
|
+
if recycle:
|
|
1858
|
+
x.copy(out=self._options["x0"])
|
|
1859
|
+
|
|
1860
|
+
return x
|
|
1861
|
+
|
|
1862
|
+
def solve_triangular(self, T, d):
|
|
1863
|
+
# Backwards substitution. Assumes T is upper triangular
|
|
1864
|
+
k = T.shape[0]
|
|
1865
|
+
y = np.zeros((k,), dtype=self._A.domain.dtype)
|
|
1866
|
+
|
|
1867
|
+
for k1 in range(k):
|
|
1868
|
+
temp = 0.
|
|
1869
|
+
for k2 in range(1, k1 + 1):
|
|
1870
|
+
temp += T[k - 1 - k1, k - 1 - k1 + k2] * y[k - 1 - k1 + k2]
|
|
1871
|
+
y[k - 1 - k1] = ( d[k - 1 - k1] - temp ) / T[k - 1 - k1, k - 1 - k1]
|
|
1872
|
+
|
|
1873
|
+
return y
|
|
1874
|
+
|
|
1875
|
+
def arnoldi(self, k, p):
|
|
1876
|
+
h = self._H[:k+2, k]
|
|
1877
|
+
self._A.dot( self._Q[k] , out=p) # Krylov vector
|
|
1878
|
+
|
|
1879
|
+
for i in range(k + 1): # Modified Gram-Schmidt, keeping Hessenberg matrix
|
|
1880
|
+
h[i] = p.inner(self._Q[i])
|
|
1881
|
+
p.mul_iadd(-h[i], self._Q[i])
|
|
1882
|
+
|
|
1883
|
+
h[k+1] = sqrt(p.inner(p).real)
|
|
1884
|
+
p /= h[k+1] # Normalize vector
|
|
1885
|
+
|
|
1886
|
+
if len(self._Q) > k + 1:
|
|
1887
|
+
p.copy(out=self._Q[k+1])
|
|
1888
|
+
else:
|
|
1889
|
+
self._Q.append(p.copy())
|
|
1890
|
+
|
|
1891
|
+
def apply_givens_rotation(self, k, sn, cn):
|
|
1892
|
+
# Apply Givens rotation to last column of H
|
|
1893
|
+
h = self._H[:k+2, k]
|
|
1894
|
+
|
|
1895
|
+
for i in range(k):
|
|
1896
|
+
h_i_prev = h[i]
|
|
1897
|
+
|
|
1898
|
+
h[i] *= cn[i]
|
|
1899
|
+
h[i] += sn[i] * h[i+1]
|
|
1900
|
+
|
|
1901
|
+
h[i+1] *= cn[i]
|
|
1902
|
+
h[i+1] -= sn[i] * h_i_prev
|
|
1903
|
+
|
|
1904
|
+
mod = (h[k]**2 + h[k+1]**2)**0.5
|
|
1905
|
+
cn.append( h[k] / mod )
|
|
1906
|
+
sn.append( h[k+1] / mod )
|
|
1907
|
+
|
|
1908
|
+
h[k] *= cn[k]
|
|
1909
|
+
h[k] += sn[k] * h[k+1]
|
|
1910
|
+
h[k+1] = 0. # becomes triangular
|
|
1911
|
+
|
|
1912
|
+
def dot(self, b, out=None):
|
|
1913
|
+
return self.solve(b, out=out)
|
|
1914
|
+
|