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.
Files changed (98) hide show
  1. feectools/__init__.py +0 -0
  2. feectools/accelerate/__init__.py +0 -0
  3. feectools/accelerate/accelerate.py +220 -0
  4. feectools/accelerate/compile_psydac.mk +52 -0
  5. feectools/api/__init__.py +0 -0
  6. feectools/api/essential_bc.py +122 -0
  7. feectools/api/fem_bilinear_form.py +2226 -0
  8. feectools/api/fem_common.py +286 -0
  9. feectools/api/fem_sum_form.py +123 -0
  10. feectools/api/settings.py +82 -0
  11. feectools/core/__init__.py +11 -0
  12. feectools/core/bsplines.py +1107 -0
  13. feectools/core/bsplines_kernels.py +1349 -0
  14. feectools/core/field_evaluation_kernels.py +5015 -0
  15. feectools/core/tests/__init__.py +0 -0
  16. feectools/core/tests/test_bsplines.py +263 -0
  17. feectools/core/tests/test_bsplines_kernel.py +40 -0
  18. feectools/core/tests/test_bsplines_pyccel.py +752 -0
  19. feectools/ddm/__init__.py +3 -0
  20. feectools/ddm/basic.py +78 -0
  21. feectools/ddm/blocking_data_exchanger.py +348 -0
  22. feectools/ddm/cart.py +1835 -0
  23. feectools/ddm/interface_data_exchanger.py +122 -0
  24. feectools/ddm/mpi.py +109 -0
  25. feectools/ddm/nonblocking_data_exchanger.py +331 -0
  26. feectools/ddm/partition.py +207 -0
  27. feectools/ddm/petsc.py +112 -0
  28. feectools/ddm/tests/__init__.py +0 -0
  29. feectools/ddm/tests/test_cart_1d.py +138 -0
  30. feectools/ddm/tests/test_cart_2d.py +164 -0
  31. feectools/ddm/tests/test_cart_3d.py +158 -0
  32. feectools/ddm/tests/test_multicart_2d.py +173 -0
  33. feectools/ddm/tests/test_partition.py +124 -0
  34. feectools/ddm/utilities.py +24 -0
  35. feectools/feec/__init__.py +0 -0
  36. feectools/feec/derivatives.py +780 -0
  37. feectools/feec/dof_kernels.py +210 -0
  38. feectools/feec/global_geometric_projectors.py +1073 -0
  39. feectools/feec/hodge.py +148 -0
  40. feectools/fem/__init__.py +0 -0
  41. feectools/fem/basic.py +465 -0
  42. feectools/fem/grid.py +181 -0
  43. feectools/fem/partitioning.py +344 -0
  44. feectools/fem/projectors.py +160 -0
  45. feectools/fem/splines.py +559 -0
  46. feectools/fem/tensor.py +1393 -0
  47. feectools/fem/tests/__init__.py +0 -0
  48. feectools/fem/tests/analytical_profiles_1d.py +100 -0
  49. feectools/fem/tests/analytical_profiles_base.py +34 -0
  50. feectools/fem/tests/splines_error_bounds.py +155 -0
  51. feectools/fem/tests/test_spline_histopolation.py +120 -0
  52. feectools/fem/tests/test_spline_interpolation.py +182 -0
  53. feectools/fem/tests/test_splines.py +184 -0
  54. feectools/fem/tests/test_splines_par.py +46 -0
  55. feectools/fem/tests/test_vector_spaces.py +150 -0
  56. feectools/fem/tests/utilities.py +47 -0
  57. feectools/fem/vector.py +729 -0
  58. feectools/linalg/__init__.py +0 -0
  59. feectools/linalg/basic.py +1386 -0
  60. feectools/linalg/block.py +1451 -0
  61. feectools/linalg/direct_solvers.py +201 -0
  62. feectools/linalg/fft.py +258 -0
  63. feectools/linalg/kernels/__init__.py +0 -0
  64. feectools/linalg/kernels/axpy_kernels.py +57 -0
  65. feectools/linalg/kernels/inner_kernels.py +100 -0
  66. feectools/linalg/kernels/matvec_kernels.py +206 -0
  67. feectools/linalg/kernels/stencil2IJV_kernels.py +227 -0
  68. feectools/linalg/kernels/stencil2coo_kernels.py +179 -0
  69. feectools/linalg/kernels/transpose_kernels.py +263 -0
  70. feectools/linalg/kron.py +911 -0
  71. feectools/linalg/solvers.py +1914 -0
  72. feectools/linalg/sparse.py +114 -0
  73. feectools/linalg/stencil.py +2923 -0
  74. feectools/linalg/stencil_dot_kernels.py +317 -0
  75. feectools/linalg/stencil_transpose_kernels.py +372 -0
  76. feectools/linalg/tests/__init__.py +0 -0
  77. feectools/linalg/tests/test_block.py +1588 -0
  78. feectools/linalg/tests/test_fft.py +106 -0
  79. feectools/linalg/tests/test_kron_stencil_matrix.py +114 -0
  80. feectools/linalg/tests/test_linalg.py +1065 -0
  81. feectools/linalg/tests/test_matrix_free.py +128 -0
  82. feectools/linalg/tests/test_solvers.py +213 -0
  83. feectools/linalg/tests/test_stencil_interface_matrix.py +379 -0
  84. feectools/linalg/tests/test_stencil_vector.py +1036 -0
  85. feectools/linalg/tests/test_stencil_vector_space.py +440 -0
  86. feectools/linalg/topetsc.py +522 -0
  87. feectools/linalg/utilities.py +200 -0
  88. feectools/utilities/__init__.py +0 -0
  89. feectools/utilities/quadratures.py +113 -0
  90. feectools/utilities/utils.py +166 -0
  91. feectools/version.py +1 -0
  92. feectools-0.1.0.dist-info/METADATA +66 -0
  93. feectools-0.1.0.dist-info/RECORD +98 -0
  94. feectools-0.1.0.dist-info/WHEEL +5 -0
  95. feectools-0.1.0.dist-info/entry_points.txt +3 -0
  96. feectools-0.1.0.dist-info/licenses/AUTHORS +22 -0
  97. feectools-0.1.0.dist-info/licenses/LICENSE +21 -0
  98. 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
+