Source code for femorph_solver.solvers.linear._gmres
"""GMRES iterative solver (general).
Nonsymmetric Krylov solver — use when ``A`` is not SPD. Jacobi
preconditioner by default.
References
----------
- Saad, Y. & Schultz, M. H. "GMRES: A Generalized Minimal Residual
Algorithm for Solving Nonsymmetric Linear Systems." SIAM J.
Sci. Stat. Comput. 7 (3), 856-869 (1986). [the original GMRES
algorithm implemented by ``scipy.sparse.linalg.gmres``]
- Saad, Y. *Iterative Methods for Sparse Linear Systems*, 2nd ed.
SIAM (2003), §6.5. [modern treatment including restart + the
restart-length trade-off scipy exposes via its ``restart`` kwarg]
"""
from __future__ import annotations
from typing import ClassVar
import numpy as np
import scipy.sparse.linalg as spla
from ._base import LinearSolverBase
[docs]
class GMRESSolver(LinearSolverBase):
"""Preconditioned GMRES (general sparse)."""
name: ClassVar[str] = "gmres"
kind: ClassVar[str] = "iterative"
spd_only: ClassVar[bool] = False
install_hint: ClassVar[str] = "bundled with SciPy — no install needed"
def __init__(
self,
A,
*,
assume_spd: bool = False,
rtol: float = 1e-10,
atol: float = 0.0,
restart: int = 50,
maxiter: int | None = None,
) -> None:
self._rtol = rtol
self._atol = atol
self._restart = restart
self._maxiter = maxiter
super().__init__(A, assume_spd=assume_spd)
def _factor(self, A):
self._A = A.tocsr()
d = self._A.diagonal()
inv = np.where(np.abs(d) > 0, 1.0 / d, 1.0)
self._M = spla.LinearOperator(self._A.shape, matvec=lambda x: inv * x)
[docs]
def solve(self, b: np.ndarray) -> np.ndarray:
x, info = spla.gmres(
self._A,
b,
M=self._M,
rtol=self._rtol,
atol=self._atol,
restart=self._restart,
maxiter=self._maxiter,
)
if info != 0:
raise RuntimeError(f"GMRES failed to converge (info={info})")
return x