Skip to content

openseespy_solvers.scipy

CPU sparse linear and eigen solver constructors.

This module follows the naming and keyword conventions of scipy.sparse.linalg where possible. The main difference is that constructors return OpenSeesPy-compatible solver objects; OpenSeesPy supplies A, b, K, and M at solve time.

Solving Linear Problems

Direct methods:

Constructor scipy analogue Description
spsolve scipy.sparse.linalg.spsolve / splu Sparse direct solver using SuperLU
umfpack scikits.umfpack Sparse direct solver using UMFPACK

Iterative methods:

Constructor scipy analogue Description
cg scipy.sparse.linalg.cg Conjugate Gradient
gmres scipy.sparse.linalg.gmres Generalized Minimal Residual

Eigenvalue Problems

Constructor scipy analogue Description
eigsh scipy.sparse.linalg.eigsh ARPACK-based generalized symmetric eigen solve
lobpcg scipy.sparse.linalg.lobpcg Locally Optimal Block Preconditioned Conjugate Gradient

OpenSeesPy Usage

from openseespy_solvers.scipy import spsolve

solver = spsolve()
ops.system("PythonSparse", solver.to_openseespy())

For eigen analysis:

from openseespy_solvers.scipy import eigsh

solver = eigsh(tol=1e-8)
lam = ops.eigen("PythonSparse", num_modes, solver.to_openseespy())

Notes

spsolve and umfpack are both sparse direct solvers. spsolve uses scipy.sparse.linalg's SuperLU path and is available with the base install. umfpack requires the optional scikit-umfpack package:

python -m pip install "openseespy-solvers[umfpack]"

On Windows, install scikit-umfpack from conda-forge instead.

umfpack defaults to scheme='CSC' and prefers the di (int32) family — matching OpenSees native UmfPack — widening to dl (int64) only when needed (index_dtype='auto'). For shift-invert eigsh, consider scheme='CSC' so OpenSees assembles K/M for SciPy's SuperLU/UMFPACK factor; plain ARPACK matvecs are usually happier with CSR (the default).

Parameters documented below follow scipy naming where possible. Extra parameters such as scheme, writable, debug, record_stats, and dtype control the OpenSeesPy integration.

Function Reference

Sparse linear algebra solvers for OpenSeesPy (scipy.sparse.linalg backend).

This module provides solver objects that wrap :mod:scipy.sparse.linalg for OpenSeesPy's PythonSparse linear and eigen commands. Factory signatures match scipy.sparse.linalg except that A and b (or K and M) are supplied by OpenSees at solve time.

Each factory returns a solver object. Register it with OpenSees using :meth:~openseespy_solvers._base.BaseOpenSeesSolver.to_openseespy::

from openseespy_solvers.scipy import cg
solver = cg(rtol=1e-8, M=precond.jacobi)
ops.system("PythonSparse", solver.to_openseespy())
Submodules

precond Preconditioner factories for use with M=.

See Also

scipy.sparse.linalg Underlying sparse linear algebra routines. openseespy_solvers.cupy GPU backend with the same interface.

spsolve

spsolve(*, permc_spec: str = 'COLAMD', scheme: str | None = None, writable: str | list[str] = 'none', debug: bool = False, dtype: Any = float64, record_stats: bool = False) -> _SpSolve
Source code in src/openseespy_solvers/scipy/__init__.py
def spsolve(
    *,
    permc_spec: str = "COLAMD",
    scheme: str | None = None,
    writable: str | list[str] = "none",
    debug: bool = False,
    dtype: Any = np.float64,
    record_stats: bool = False,
) -> _SpSolve:
    r"""Configure a sparse direct solver for OpenSees ``PythonSparse``.

    Uses :func:`scipy.sparse.linalg.splu` internally. The LU factorization is
    reused when OpenSees reports ``matrix_status='UNCHANGED'``.

    Parameters
    ----------
    permc_spec : str, optional
        Column permutation applied during LU factorization. Passed to
        :func:`scipy.sparse.linalg.splu`. Default is ``'COLAMD'``.
    """ + _OPENSEES_LINEAR + _LINEAR_RETURNS + _LINEAR_NOTES + """
    See Also
    --------
    scipy.sparse.linalg.spsolve
    scipy.sparse.linalg.splu

    Examples
    --------
    >>> from openseespy_solvers.scipy import spsolve
    >>> solver = spsolve(permc_spec="COLAMD")
    >>> cfg = solver.to_openseespy()
    >>> cfg["scheme"]
    'CSR'
    """
    return _SpSolve(
        permc_spec=permc_spec,
        scheme=scheme or "CSR",
        writable=writable,
        debug=debug,
        dtype=dtype,
        record_stats=record_stats,
    )

umfpack

umfpack(*, scheme: str | None = None, writable: str | list[str] = 'none', debug: bool = False, dtype: Any = float64, index_dtype: str = 'auto', record_stats: bool = False) -> _Umfpack
Source code in src/openseespy_solvers/scipy/__init__.py
def umfpack(
    *,
    scheme: str | None = None,
    writable: str | list[str] = "none",
    debug: bool = False,
    dtype: Any = np.float64,
    index_dtype: str = "auto",
    record_stats: bool = False,
) -> _Umfpack:
    r"""Configure a UMFPACK direct solver for OpenSees ``PythonSparse``.

    Wraps ``scikits.umfpack.UmfpackContext``. By default prefers the ``di``
    (int32) family — matching OpenSees native ``UmfPack`` — and only widens to
    ``dl`` (int64) when indices require it (``index_dtype='auto'``). Prefer
    ``scheme='CSC'`` so OpenSees stores UMFPACK's native layout (avoids a
    CSR→CSC conversion).

    The symbolic factorization is reused while sparsity is unchanged and is
    recomputed only on ``matrix_status='STRUCTURE_CHANGED'``; numeric is
    refreshed on ``'COEFFICIENTS_CHANGED'`` and reused on ``'UNCHANGED'``.

    Requires the optional ``scikit-umfpack`` package
    (``python -m pip install "openseespy-solvers[umfpack]"``).

    Parameters
    ----------
    index_dtype : {'auto', 'int32', 'int64', 'di', 'dl'}, optional
        UMFPACK index width. ``auto`` (default) uses int32 when safe.
    """ + _OPENSEES_LINEAR + _LINEAR_RETURNS + _LINEAR_NOTES + """
    Raises
    ------
    ImportError
        If ``scikit-umfpack`` is not installed (raised when the solver is
        instantiated, not on import).

    See Also
    --------
    spsolve
    scipy.sparse.linalg.spsolve

    Examples
    --------
    >>> from openseespy_solvers.scipy import umfpack  # doctest: +SKIP
    >>> solver = umfpack(scheme="CSC")  # doctest: +SKIP
    >>> solver.to_openseespy()["scheme"]  # doctest: +SKIP
    'CSC'
    """
    return _Umfpack(
        scheme=scheme or "CSC",
        writable=writable,
        debug=debug,
        dtype=dtype,
        index_dtype=index_dtype,
        record_stats=record_stats,
    )

cg

cg(x0: Any = None, *, rtol: float = 1e-05, atol: float = 0.0, maxiter: int | None = None, M: Any = None, callback: Any = None, scheme: str | None = None, writable: str | list[str] = 'none', debug: bool = False, dtype: Any = float64, record_stats: bool = False) -> _CG
Source code in src/openseespy_solvers/scipy/__init__.py
def cg(
    x0: Any = None,
    *,
    rtol: float = 1e-5,
    atol: float = 0.0,
    maxiter: int | None = None,
    M: Any = None,
    callback: Any = None,
    scheme: str | None = None,
    writable: str | list[str] = "none",
    debug: bool = False,
    dtype: Any = np.float64,
    record_stats: bool = False,
) -> _CG:
    r"""Configure a Conjugate Gradient solver for OpenSees ``PythonSparse``.

    Uses :func:`scipy.sparse.linalg.cg` internally to solve ``Ax = b`` for
    symmetric, positive-definite ``A``.

    Parameters
    ----------
    x0 : ndarray, optional
        Starting guess for the solution. Forwarded to `scipy.sparse.linalg` when not ``None``.
    rtol, atol : float, optional
        Relative and absolute tolerances for convergence. For convergence,
        ``norm(b - A @ x) <= max(rtol*norm(b), atol)`` must hold. Defaults are
        ``rtol=1e-5`` and ``atol=0.0``.
    maxiter : int, optional
        Maximum number of iterations.
    M : {sparse matrix, LinearOperator, callable}, optional
        Preconditioner for ``A``. If ``M`` is callable, it must accept the
        assembled matrix ``A`` and return a preconditioner; see
        :mod:`openseespy_solvers.scipy.precond`.
    callback : callable, optional
        User-supplied function called after each iteration as ``callback()``.
    """ + _OPENSEES_LINEAR + _LINEAR_RETURNS + _LINEAR_NOTES + """
    See Also
    --------
    scipy.sparse.linalg.cg
    openseespy_solvers.scipy.precond.jacobi
    openseespy_solvers.scipy.precond.ilu

    Examples
    --------
    >>> from openseespy_solvers.scipy import cg, precond
    >>> solver = cg(rtol=1e-8, M=precond.jacobi)
    >>> solver.to_openseespy()["scheme"]
    'CSR'
    """
    return _CG(
        x0,
        rtol=rtol,
        atol=atol,
        maxiter=maxiter,
        M=M,
        callback=callback,
        scheme=scheme or "CSR",
        writable=writable,
        debug=debug,
        dtype=dtype,
        record_stats=record_stats,
    )

gmres

gmres(x0: Any = None, *, rtol: float = 1e-05, atol: float = 0.0, restart: int | None = None, maxiter: int | None = None, M: Any = None, callback: Any = None, scheme: str | None = None, writable: str | list[str] = 'none', debug: bool = False, dtype: Any = float64, record_stats: bool = False) -> _GMRES
Source code in src/openseespy_solvers/scipy/__init__.py
def gmres(
    x0: Any = None,
    *,
    rtol: float = 1e-5,
    atol: float = 0.0,
    restart: int | None = None,
    maxiter: int | None = None,
    M: Any = None,
    callback: Any = None,
    scheme: str | None = None,
    writable: str | list[str] = "none",
    debug: bool = False,
    dtype: Any = np.float64,
    record_stats: bool = False,
) -> _GMRES:
    r"""Configure a GMRES solver for OpenSees ``PythonSparse``.

    Uses :func:`scipy.sparse.linalg.gmres` internally to solve ``Ax = b``.

    Parameters
    ----------
    x0 : ndarray, optional
        Starting guess for the solution.
    rtol, atol : float, optional
        Relative and absolute tolerances for convergence. Defaults are
        ``rtol=1e-5`` and ``atol=0.0``.
    restart : int, optional
        Number of iterations between restarts.
    maxiter : int, optional
        Maximum number of iterations.
    M : {sparse matrix, LinearOperator, callable}, optional
        Preconditioner for ``A``.
    callback : callable, optional
        User-supplied function called after each iteration.
    """ + _OPENSEES_LINEAR + _LINEAR_RETURNS + _LINEAR_NOTES + """
    See Also
    --------
    scipy.sparse.linalg.gmres
    cg

    Examples
    --------
    >>> from openseespy_solvers.scipy import gmres
    >>> solver = gmres(rtol=1e-8, restart=30)
    >>> solver.backend
    'scipy'
    """
    return _GMRES(
        x0,
        rtol=rtol,
        atol=atol,
        restart=restart,
        maxiter=maxiter,
        M=M,
        callback=callback,
        scheme=scheme or "CSR",
        writable=writable,
        debug=debug,
        dtype=dtype,
        record_stats=record_stats,
    )

eigsh

eigsh(*, sigma: float | None = None, which: str = 'LM', v0: Any = None, ncv: int | None = None, maxiter: int | None = None, tol: float = 0.0, mode: str = 'normal', scheme: str | None = None, debug: bool = False, dtype: Any = float64) -> _Eigsh
Source code in src/openseespy_solvers/scipy/__init__.py
def eigsh(
    *,
    sigma: float | None = None,
    which: str = "LM",
    v0: Any = None,
    ncv: int | None = None,
    maxiter: int | None = None,
    tol: float = 0.0,
    mode: str = "normal",
    scheme: str | None = None,
    debug: bool = False,
    dtype: Any = np.float64,
) -> _Eigsh:
    r"""Configure a sparse eigenvalue solver for OpenSees ``PythonSparse``.

    Matches OpenSees ``eigsh``: shift-invert with ``sigma=0`` when
    ``find_smallest=True``; plain ARPACK when ``find_smallest=False`` unless
    ``sigma`` is set. ARPACK always uses ``which='LM'`` (OpenSees reference).

    For shift-invert (smallest modes), prefer ``scheme='CSC'`` so OpenSees
    assembles ``K``/``M`` in the layout SciPy's SuperLU/UMFPACK factor expects.
    Default remains ``'CSR'`` (better for plain ARPACK matvecs).

    Parameters
    ----------
    sigma : float, optional
        Shift for shift-invert. Default ``0.0`` when ``find_smallest=True``.
    which : {'LM', 'SM', ...}, optional
        Accepted for API compatibility; solves always use ``'LM'`` like OpenSees.
    v0 : ndarray, optional
        Starting vector for ARPACK.
    ncv : int, optional
        Number of Lanczos vectors.
    maxiter : int, optional
        Maximum number of iterations.
    tol : float, optional
        Convergence tolerance. Default is ``0.0``.
    mode : {'normal', 'buckling', 'cayley'}, optional
        Eigenproblem mode. Default is ``'normal'``.
    """ + _OPENSEES_EIGEN + _EIGEN_RETURNS + _EIGEN_NOTES + """
    See Also
    --------
    scipy.sparse.linalg.eigsh
    lobpcg

    Examples
    --------
    >>> from openseespy_solvers.scipy import eigsh
    >>> solver = eigsh(tol=1e-8)
    >>> solver.scheme
    'CSR'
    """
    return _Eigsh(
        sigma=sigma,
        which=which,
        v0=v0,
        ncv=ncv,
        maxiter=maxiter,
        tol=tol,
        mode=mode,
        scheme=scheme or "CSR",
        debug=debug,
        dtype=dtype,
    )

lobpcg

lobpcg(*, X: Any = None, M: Any = None, tol: float | None = None, maxiter: int = 20, largest: bool = False, rng: Any = None, scheme: str | None = None, debug: bool = False, dtype: Any = float64) -> _Lobpcg
Source code in src/openseespy_solvers/scipy/__init__.py
def lobpcg(
    *,
    X: Any = None,
    M: Any = None,
    tol: float | None = None,
    maxiter: int = 20,
    largest: bool = False,
    rng: Any = None,
    scheme: str | None = None,
    debug: bool = False,
    dtype: Any = np.float64,
) -> _Lobpcg:
    r"""Configure a LOBPCG eigenvalue solver for OpenSees ``PythonSparse``.

    Uses :func:`scipy.sparse.linalg.lobpcg` internally to solve the generalized
    eigenproblem ``K x = \lambda M x``.

    Parameters
    ----------
    X : ndarray, optional
        Initial approximation to the eigenvectors. If ``None``, a random matrix
        with ``num_modes`` columns is generated using ``rng``.
    M : {sparse matrix, LinearOperator, callable}, optional
        Optional preconditioner passed to LOBPCG as ``M=``. A callable is
        invoked as ``M(K)``; see :mod:`openseespy_solvers.scipy.precond`.
    tol : float, optional
        Convergence tolerance.
    maxiter : int, optional
        Maximum number of iterations. Default is ``20``.
    largest : bool, optional
        If ``True``, compute largest eigenvalues. Default is ``False``.
    rng : {None, int, `numpy.random.Generator`}, optional
        Random number generator used when ``X`` is ``None``.
    """ + _OPENSEES_EIGEN + _EIGEN_RETURNS + _EIGEN_NOTES + """
    See Also
    --------
    scipy.sparse.linalg.lobpcg
    eigsh

    Examples
    --------
    >>> from openseespy_solvers.scipy import lobpcg
    >>> solver = lobpcg(tol=1e-8, maxiter=40)
    >>> solver.backend
    'scipy'
    """
    return _Lobpcg(
        X=X,
        M=M,
        tol=tol,
        maxiter=maxiter,
        largest=largest,
        rng=rng,
        scheme=scheme or "CSR",
        debug=debug,
        dtype=dtype,
    )