Source code for femorph_solver.solvers.linear._cg
"""Conjugate Gradient iterative solver (SPD).
Uses :func:`scipy.sparse.linalg.cg` with a diagonal (Jacobi)
preconditioner. Always available; best for very large sparse SPD
systems where factorization memory would be prohibitive.
References
----------
- Hestenes, M. R. & Stiefel, E. "Methods of Conjugate Gradients
for Solving Linear Systems." J. Res. Natl. Bur. Stand. 49 (6),
409-436 (1952). [the original CG algorithm]
- Saad, Y. *Iterative Methods for Sparse Linear Systems*, 2nd ed.
SIAM (2003), §6. [modern treatment + convergence theory]
"""
from __future__ import annotations
from typing import ClassVar
import numpy as np
import scipy.sparse.linalg as spla
from ._base import LinearSolverBase
[docs]
class CGSolver(LinearSolverBase):
"""Preconditioned Conjugate Gradient for SPD ``A``."""
name: ClassVar[str] = "cg"
kind: ClassVar[str] = "iterative"
spd_only: ClassVar[bool] = True
install_hint: ClassVar[str] = "bundled with SciPy — no install needed"
def __init__(
self,
A,
*,
assume_spd: bool = True,
rtol: float = 1e-10,
atol: float = 0.0,
maxiter: int | None = None,
) -> None:
self._rtol = rtol
self._atol = atol
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.cg(
self._A, b, M=self._M, rtol=self._rtol, atol=self._atol, maxiter=self._maxiter
)
if info != 0:
raise RuntimeError(f"CG failed to converge (info={info})")
return x