Source code for femorph_solver.solvers.eigen

"""Pluggable generalized-eigenvalue solvers.

All backends solve ``K φ = λ M φ`` for the ``n_modes`` smallest-λ
eigenpairs.  The default backend (``"arpack"``) uses
:func:`scipy.sparse.linalg.eigsh` in shift-invert mode with σ = 0 —
identical to femorph-solver's behaviour up to this release.  Alternative backends
let callers trade accuracy / convergence / memory against speed and
dependency footprint.

Usage::

    from femorph_solver.solvers.eigen import get_eigen_solver

    Solver = get_eigen_solver("arpack")
    w, v = Solver.solve(K, M, n_modes=10, linear_solver="cholmod")
"""

from __future__ import annotations

from ._arpack import ArpackSolver
from ._base import EigenSolverBase
from ._lapack_dense import LapackDenseSolver
from ._lobpcg import LobpcgSolver
from ._primme import PrimmeSolver

_REGISTRY: dict[str, type[EigenSolverBase]] = {
    cls.name: cls for cls in (ArpackSolver, LobpcgSolver, PrimmeSolver, LapackDenseSolver)
}


[docs] def get_eigen_solver(name: str) -> type[EigenSolverBase]: """Look up a registered eigen-solver class by name.""" try: cls = _REGISTRY[name] except KeyError as exc: known = ", ".join(sorted(_REGISTRY)) raise KeyError(f"unknown eigen solver {name!r}; registered: {known}") from exc if not cls.available(): from ..linear._base import SolverUnavailableError raise SolverUnavailableError( f"eigen solver {name!r} is registered but unavailable — {cls.install_hint}" ) return cls
[docs] def list_eigen_solvers() -> list[dict[str, object]]: """Return a list of ``{name, available, kind, spd_only, install_hint}`` rows.""" out: list[dict[str, object]] = [] for name, cls in _REGISTRY.items(): out.append( { "name": name, "available": cls.available(), "kind": cls.kind, "spd_only": cls.spd_only, "install_hint": cls.install_hint, } ) return out
[docs] def list_available() -> list[str]: """Return the names of eigen-solver backends importable in this process. Quick-pick API mirroring :func:`femorph_solver.solvers.linear.list_available` — returns just the names :func:`get_eigen_solver` accepts: >>> from femorph_solver.solvers.eigen import list_available >>> "arpack" in list_available() # always present (scipy ships it) True See Also -------- list_eigen_solvers : richer per-backend metadata. """ return [name for name, cls in _REGISTRY.items() if cls.available()]
__all__ = [ "ArpackSolver", "EigenSolverBase", "LapackDenseSolver", "LobpcgSolver", "PrimmeSolver", "get_eigen_solver", "list_available", "list_eigen_solvers", ]