Skip to content

openseespy_solvers.hybrid

Hybrid linear solver constructor.

hybrid() reuses a direct factorization (from a solver such as spsolve()) as a GMRES preconditioner. It is useful for analyses where the tangent changes slowly and a full refactorization at every step is unnecessarily expensive.

Solving Linear Problems

Constructor Description
hybrid Direct factorization reused as a GMRES preconditioner

Usage

from openseespy_solvers import hybrid
from openseespy_solvers.scipy import spsolve

solver = hybrid(spsolve(), rtol=1e-6, restart=50)
ops.system("PythonSparse", solver.to_openseespy())

Function Reference

Hybrid direct-iterative linear solvers for OpenSeesPy.

hybrid

hybrid(direct: LinearSolver, *, rtol: float = 1e-06, atol: float = 0.0, restart: int | None = None, maxiter: int | None = None, x0: Any = None, refresh_every: int | None = None, debug: bool = False) -> _Hybrid

Configure a hybrid direct-iterative solver for OpenSees PythonSparse.

On the first solve (or when the number of equations changes), the inner direct solver builds and factorizes the matrix and returns a direct solution. On later solves with the same system size, the cached factorization is kept frozen and used as a GMRES preconditioner while the current matrix coefficients are applied to A. The factorization is refreshed when:

  • the number of equations changes,
  • GMRES fails to converge,
  • refresh_every steps elapse (if set).

The frozen factorization lives on the Python solver object, not inside OpenSees. Reuse the same solver instance across wipeAnalysis() or wipe()/model rebuilds (for example multiple ground motions with the same mesh): as long as num_eqn is unchanged, the first run's factorization is reused even when OpenSees reports matrix_status='STRUCTURE_CHANGED'. Creating a new hybrid(...) or copy.copy(solver) starts with an empty factorization cache.

Parameters:

Name Type Description Default
direct LinearSolver

Inner direct solver from :func:~openseespy_solvers.scipy.spsolve, :func:~openseespy_solvers.scipy.umfpack, :func:~openseespy_solvers.cupy.spsolve, or :func:~openseespy_solvers.nvmath.direct_solver. GMRES runs on the same device as the inner solver (CPU or GPU).

required
rtol float

GMRES convergence tolerances. Defaults are rtol=1e-6 and atol=0.0.

1e-06
atol float

GMRES convergence tolerances. Defaults are rtol=1e-6 and atol=0.0.

1e-06
restart int

GMRES restart length.

None
maxiter int

Maximum GMRES iterations.

None
x0 ndarray

Initial guess for GMRES. When omitted, the previous solution is reused when the system size is unchanged.

None
refresh_every int

Force a factorization refresh every this many solves. Default is None (refresh only on size change or GMRES failure).

None
debug bool

Re-raise exceptions instead of returning a failure code.

False

Returns:

Name Type Description
solver _Hybrid

Solver object for :meth:~openseespy_solvers._base.BaseOpenSeesSolver.to_openseespy.

Examples:

>>> from openseespy_solvers import hybrid
>>> from openseespy_solvers.scipy import spsolve
>>> solver = hybrid(spsolve(), rtol=1e-6, restart=50)
>>> solver.backend
'scipy'
Source code in src/openseespy_solvers/_hybrid.py
def hybrid(
    direct: LinearSolver,
    *,
    rtol: float = 1e-6,
    atol: float = 0.0,
    restart: int | None = None,
    maxiter: int | None = None,
    x0: Any = None,
    refresh_every: int | None = None,
    debug: bool = False,
) -> _Hybrid:
    r"""Configure a hybrid direct-iterative solver for OpenSees ``PythonSparse``.

    On the first solve (or when the number of equations changes), the inner
    *direct* solver builds and factorizes the matrix and returns a direct
    solution. On later solves with the same system size, the cached
    factorization is kept frozen and used as a GMRES preconditioner while the
    current matrix coefficients are applied to ``A``. The factorization is
    refreshed when:

    * the number of equations changes,
    * GMRES fails to converge,
    * ``refresh_every`` steps elapse (if set).

    The frozen factorization lives on the Python solver object, not inside
    OpenSees. Reuse the **same** ``solver`` instance across ``wipeAnalysis()``
    or ``wipe()``/model rebuilds (for example multiple ground motions with the
    same mesh): as long as ``num_eqn`` is unchanged, the first run's
    factorization is reused even when OpenSees reports
    ``matrix_status='STRUCTURE_CHANGED'``. Creating a new ``hybrid(...)`` or
    ``copy.copy(solver)`` starts with an empty factorization cache.

    Parameters
    ----------
    direct : LinearSolver
        Inner direct solver from :func:`~openseespy_solvers.scipy.spsolve`,
        :func:`~openseespy_solvers.scipy.umfpack`,
        :func:`~openseespy_solvers.cupy.spsolve`, or
        :func:`~openseespy_solvers.nvmath.direct_solver`. GMRES runs on the
        same device as the inner solver (CPU or GPU).
    rtol, atol : float, optional
        GMRES convergence tolerances. Defaults are ``rtol=1e-6`` and ``atol=0.0``.
    restart : int, optional
        GMRES restart length.
    maxiter : int, optional
        Maximum GMRES iterations.
    x0 : ndarray, optional
        Initial guess for GMRES. When omitted, the previous solution is reused
        when the system size is unchanged.
    refresh_every : int, optional
        Force a factorization refresh every this many solves. Default is
        ``None`` (refresh only on size change or GMRES failure).
    debug : bool, optional
        Re-raise exceptions instead of returning a failure code.

    Returns
    -------
    solver : _Hybrid
        Solver object for :meth:`~openseespy_solvers._base.BaseOpenSeesSolver.to_openseespy`.

    Examples
    --------
    >>> from openseespy_solvers import hybrid
    >>> from openseespy_solvers.scipy import spsolve
    >>> solver = hybrid(spsolve(), rtol=1e-6, restart=50)
    >>> solver.backend
    'scipy'
    """
    return _Hybrid(
        direct,
        rtol=rtol,
        atol=atol,
        restart=restart,
        maxiter=maxiter,
        x0=x0,
        refresh_every=refresh_every,
        debug=debug,
    )