Source code for femorph_solver.solvers.linear._pyamg
"""Algebraic multigrid (AMG) preconditioned CG via ``pyamg``.
AMG is the standard industrial preconditioner for elliptic problems like
structural stiffness — often 10-100× fewer iterations than Jacobi-CG on
large unstructured SPD systems.
References
----------
- Ruge, J. W. & Stüben, K. "Algebraic multigrid." In *Multigrid
Methods* (S. F. McCormick, ed.), SIAM Frontiers in Applied
Mathematics, Chapter 4, pp. 73-130 (1987). [the foundational
classical-AMG algorithm]
- Vaněk, P., Mandel, J., & Brezina, M. "Algebraic multigrid by
smoothed aggregation for second and fourth order elliptic
problems." Computing 56 (3), 179-196 (1996).
[smoothed-aggregation variant, pyamg's default ``smoothed_aggregation_solver``]
- Bell, N., Olson, L. N., & Schroder, J. "PyAMG: Algebraic
Multigrid Solvers in Python v5.0." Journal of Open Source
Software 8 (81), 5495 (2023). [the shipped implementation]
"""
from __future__ import annotations
from typing import ClassVar
import numpy as np
from ._base import LinearSolverBase
try:
import pyamg
_HAVE = True
except ImportError:
_HAVE = False
[docs]
class PyAMGSolver(LinearSolverBase):
"""Smoothed-aggregation AMG preconditioned CG (SPD)."""
name: ClassVar[str] = "pyamg"
kind: ClassVar[str] = "iterative"
spd_only: ClassVar[bool] = True
install_hint: ClassVar[str] = "`pip install pyamg`"
def __init__(
self,
A,
*,
assume_spd: bool = True,
rtol: float = 1e-10,
maxiter: int = 200,
) -> None:
self._rtol = rtol
self._maxiter = maxiter
super().__init__(A, assume_spd=assume_spd)
[docs]
@staticmethod
def available() -> bool:
return _HAVE
def _factor(self, A):
# pyamg's C++ kernels are bound to int32 indices only; coerce
# here so callers can pass the int64-indexed CSR/CSC that SciPy
# produces on 64-bit systems.
A_csr = A.tocsr().astype(np.float64)
A_csr = A_csr.__class__(
(A_csr.data, A_csr.indices.astype(np.int32), A_csr.indptr.astype(np.int32)),
shape=A_csr.shape,
)
self._A = A_csr
self._ml = pyamg.smoothed_aggregation_solver(self._A)
[docs]
def solve(self, b: np.ndarray) -> np.ndarray:
return self._ml.solve(
np.ascontiguousarray(b, dtype=np.float64),
tol=self._rtol,
maxiter=self._maxiter,
accel="cg",
)