Repository: scipy/scipy

Issue:
BUG: test_maxiter_worsening[lgmres] failed on riscv
### Describe your issue.

This test case fails on riscv64:

```
FAILED sparse/linalg/_isolve/tests/test_iterative.py::test_maxiter_worsening[lgmres] - AssertionError
```

This error looks similar to https://github.com/scipy/scipy/pull/13022 and https://github.com/scipy/scipy/commit/4383318f9a3a16e4e697a9920269a6947632b601. These errors are skipped on aarch64 and ppc64le, what about riscv64?


full build log: https://bugs.gentoo.org/attachment.cgi?id=847590


### Reproducing Code Example

```python
No code example, just test case.
```


### Error message

```shell
==================================================================================================== FAILURES =====================================================================================================_________________________________________________________________________________________ test_maxiter_worsening[lgmres] __________________________________________________________________________________________[gw1] linux -- Python 3.10.9 /var/tmp/portage/dev-python/scipy-1.10.0/work/scipy-1.10.0-python3_10/install/usr/bin/python3.10

solver = <function lgmres at 0x3fae312950>

    @pytest.mark.parametrize("solver", [
        pytest.param(gmres, marks=pytest.mark.xfail(platform.machine() == 'aarch64'
                                                    and sys.version_info[1] == 9,
                                                    reason="gh-13019")),
        qmr,
        pytest.param(lgmres, marks=pytest.mark.xfail(platform.machine() == 'ppc64le',
                                                     reason="fails on ppc64le")),
        pytest.param(cgs, marks=pytest.mark.xfail),
        pytest.param(bicg, marks=pytest.mark.xfail),
        pytest.param(bicgstab, marks=pytest.mark.xfail),
        pytest.param(gcrotmk, marks=pytest.mark.xfail),
        pytest.param(tfqmr, marks=pytest.mark.xfail)])
    def test_maxiter_worsening(solver):
        # Check error does not grow (boundlessly) with increasing maxiter.
        # This can occur due to the solvers hitting close to breakdown,
        # which they should detect and halt as necessary.
        # cf. gh-9100

        # Singular matrix, rhs numerically not in range
        A = np.array([[-0.1112795288033378, 0, 0, 0.16127952880333685],
                      [0, -0.13627952880333782+6.283185307179586j, 0, 0],
                      [0, 0, -0.13627952880333782-6.283185307179586j, 0],
                      [0.1112795288033368, 0j, 0j, -0.16127952880333785]])
        v = np.ones(4)
        best_error = np.inf
        tol = 7 if platform.machine() == 'aarch64' else 5

        for maxiter in range(1, 20):
            x, info = solver(A, v, maxiter=maxiter, tol=1e-8, atol=0)

            if info == 0:
                assert_(np.linalg.norm(A.dot(x) - v) <= 1e-8*np.linalg.norm(v))

            error = np.linalg.norm(A.dot(x) - v)
            best_error = min(best_error, error)

            # Check with slack
>           assert_(error <= tol*best_error)
E           AssertionError
A          = array([[-0.11127953+0.j        ,  0.        +0.j        ,
         0.        +0.j        ,  0.16127953+0.j        ],
       [ 0.        +0.j        , -0.13627953+6.28318531j,
         0.        +0.j        ,  0.        +0.j        ],
       [ 0.        +0.j        ,  0.        +0.j        ,
        -0.13627953-6.28318531j,  0.        +0.j        ],
       [ 0.11127953+0.j        ,  0.        +0.j        ,
         0.        +0.j        , -0.16127953+0.j        ]])
best_error = 1.414213562373095
error      = 67.94531422934396
info       = 8
maxiter    = 8
solver     = <function lgmres at 0x3fae312950>
tol        = 5
v          = array([1., 1., 1., 1.])
x          = array([-1.17813344e+15+0.j        , -6.61429022e+00-3.99819143j,
       -6.61429022e+00+3.99819143j, -8.12887631e+14+0.j        ])

../work/scipy-1.10.0-python3_10/install/usr/lib/python3.10/site-packages/scipy/sparse/linalg/_isolve/tests/test_iterative.py:487: AssertionError
```


### SciPy/NumPy/Python version information

1.10.0 1.23.5 sys.version_info(major=3, minor=10, micro=9, releaselevel='final', serial=0)


Discussion:

Resolving patch:
diff --git a/scipy/sparse/linalg/_eigen/arpack/arpack.py b/scipy/sparse/linalg/_eigen/arpack/arpack.py
--- a/scipy/sparse/linalg/_eigen/arpack/arpack.py
+++ b/scipy/sparse/linalg/_eigen/arpack/arpack.py
@@ -950,7 +950,7 @@ def gmres_loose(A, b, tol):
     """
     b = np.asarray(b)
     min_tol = 1000 * np.sqrt(b.size) * np.finfo(b.dtype).eps
-    return gmres(A, b, tol=max(tol, min_tol), atol=0)
+    return gmres(A, b, rtol=max(tol, min_tol), atol=0)
 
 
 class IterInv(LinearOperator):
diff --git a/scipy/sparse/linalg/_isolve/iterative.py b/scipy/sparse/linalg/_isolve/iterative.py
--- a/scipy/sparse/linalg/_isolve/iterative.py
+++ b/scipy/sparse/linalg/_isolve/iterative.py
@@ -1,496 +1,575 @@
-"""Iterative methods for solving linear systems"""
-
-__all__ = ['bicg','bicgstab','cg','cgs','gmres','qmr']
-
 import warnings
-from textwrap import dedent
 import numpy as np
-
-from . import _iterative
-
 from scipy.sparse.linalg._interface import LinearOperator
 from .utils import make_system
-from scipy._lib._util import _aligned_zeros
-from scipy._lib._threadsafety import non_reentrant
-
-_type_conv = {'f':'s', 'd':'d', 'F':'c', 'D':'z'}
-
-
-# Part of the docstring common to all iterative solvers
-common_doc1 = \
-"""
-Parameters
-----------
-A : {sparse matrix, ndarray, LinearOperator}"""
-
-common_doc2 = \
-"""b : ndarray
-    Right hand side of the linear system. Has shape (N,) or (N,1).
-
-Returns
--------
-x : ndarray
-    The converged solution.
-info : integer
-    Provides convergence information:
-        0  : successful exit
-        >0 : convergence to tolerance not achieved, number of iterations
-        <0 : illegal input or breakdown
-
-Other Parameters
-----------------
-x0 : ndarray
-    Starting guess for the solution.
-tol, atol : float, optional
-    Tolerances for convergence, ``norm(residual) <= max(tol*norm(b), atol)``.
-    The default for ``atol`` is ``'legacy'``, which emulates
-    a different legacy behavior.
-
-    .. warning::
-
-       The default value for `atol` will be changed in a future release.
-       For future compatibility, specify `atol` explicitly.
-maxiter : integer
-    Maximum number of iterations.  Iteration will stop after maxiter
-    steps even if the specified tolerance has not been achieved.
-M : {sparse matrix, ndarray, LinearOperator}
-    Preconditioner for A.  The preconditioner should approximate the
-    inverse of A.  Effective preconditioning dramatically improves the
-    rate of convergence, which implies that fewer iterations are needed
-    to reach a given error tolerance.
-callback : function
-    User-supplied function to call after each iteration.  It is called
-    as callback(xk), where xk is the current solution vector.
-"""
-
-
-def _stoptest(residual, atol):
+from scipy.linalg import get_lapack_funcs
+
+__all__ = ['bicg', 'bicgstab', 'cg', 'cgs', 'gmres', 'qmr']
+
+
+def _get_atol(name, b, tol=None, atol=0., rtol=1e-5):
     """
-    Successful termination condition for the solvers.
+    A helper function to handle tolerance deprecations and normalization
     """
-    resid = np.linalg.norm(residual)
-    if resid <= atol:
-        return resid, 1
-    else:
-        return resid, 0
+    if tol is not None:
+        msg = (f"'scipy.sparse.linalg.{name}' keyword argument 'tol' is "
+               "deprecated in favor of 'rtol' and will be removed in SciPy "
+               "v.1.14.0. Until then, if set, will override 'rtol'.")
+        warnings.warn(msg, category=DeprecationWarning, stacklevel=4)
+        rtol = float(tol)
 
+    if atol == 'legacy':
+        warnings.warn("scipy.sparse.linalg.{name} called with `atol` set to "
+                      "string. This behavior is deprecated and atol parameter"
+                      " only excepts floats. In SciPy 1.14, this will result"
+                      " with an error.", category=DeprecationWarning,
+                      stacklevel=4)
+        atol = 0
+
+    atol = max(float(atol), float(rtol) * float(np.linalg.norm(b)))
+
+    return atol
 
-def _get_atol(tol, atol, bnrm2, get_residual, routine_name):
-    """
-    Parse arguments for absolute tolerance in termination condition.
+
+def bicg(A, b, x0=None, tol=None, maxiter=None, M=None, callback=None,
+         atol=0., rtol=1e-5):
+    """Use BIConjugate Gradient iteration to solve ``Ax = b``.
 
     Parameters
     ----------
-    tol, atol : object
-        The arguments passed into the solver routine by user.
-    bnrm2 : float
-        2-norm of the rhs vector.
-    get_residual : callable
-        Callable ``get_residual()`` that returns the initial value of
-        the residual.
-    routine_name : str
-        Name of the routine.
-    """
+    A : {sparse matrix, ndarray, LinearOperator}
+        The real or complex N-by-N matrix of the linear system.
+        Alternatively, ``A`` can be a linear operator which can
+        produce ``Ax`` and ``A^T x`` using, e.g.,
+        ``scipy.sparse.linalg.LinearOperator``.
+    b : ndarray
+        Right hand side of the linear system. Has shape (N,) or (N,1).
+    x0 : ndarray
+        Starting guess for the solution.
+    rtol, atol : float, optional
+        Parameters for the convergence test. For convergence,
+        ``norm(b - A @ x) <= max(rtol*norm(b), atol)`` should be satisfied.
+        The default is ``atol=0.`` and ``rtol=1e-5``.
+    maxiter : integer
+        Maximum number of iterations.  Iteration will stop after maxiter
+        steps even if the specified tolerance has not been achieved.
+    M : {sparse matrix, ndarray, LinearOperator}
+        Preconditioner for A.  The preconditioner should approximate the
+        inverse of A.  Effective preconditioning dramatically improves the
+        rate of convergence, which implies that fewer iterations are needed
+        to reach a given error tolerance.
+    callback : function
+        User-supplied function to call after each iteration.  It is called
+        as callback(xk), where xk is the current solution vector.
+    tol : float, optional, deprecated
 
-    if atol is None:
-        warnings.warn("scipy.sparse.linalg.{name} called without specifying `atol`. "
-                      "The default value will be changed in a future release. "
-                      "For compatibility, specify a value for `atol` explicitly, e.g., "
-                      "``{name}(..., atol=0)``, or to retain the old behavior "
-                      "``{name}(..., atol='legacy')``".format(name=routine_name),
-                      category=DeprecationWarning, stacklevel=4)
-        atol = 'legacy'
+        .. deprecated 1.12.0
+           `bicg` keyword argument `tol` is deprecated in favor of `rtol` and
+           will be removed in SciPy 1.14.0.
 
-    tol = float(tol)
+    Returns
+    -------
+    x : ndarray
+        The converged solution.
+    info : integer
+        Provides convergence information:
+            0  : successful exit
+            >0 : convergence to tolerance not achieved, number of iterations
+            <0 : parameter breakdown
 
-    if atol == 'legacy':
-        # emulate old legacy behavior
-        resid = get_residual()
-        if resid <= tol:
-            return 'exit'
-        if bnrm2 == 0:
-            return tol
-        else:
-            return tol * float(bnrm2)
-    else:
-        return max(float(atol), tol * float(bnrm2))
-
-
-def set_docstring(header, Ainfo, footer='', atol_default='0'):
-    def combine(fn):
-        fn.__doc__ = '\n'.join((header, common_doc1,
-                                '    ' + Ainfo.replace('\n', '\n    '),
-                                common_doc2, dedent(footer)))
-        return fn
-    return combine
-
-
-@set_docstring('Use BIConjugate Gradient iteration to solve ``Ax = b``.',
-               'The real or complex N-by-N matrix of the linear system.\n'
-               'Alternatively, ``A`` can be a linear operator which can\n'
-               'produce ``Ax`` and ``A^T x`` using, e.g.,\n'
-               '``scipy.sparse.linalg.LinearOperator``.',
-               footer="""\
-               Examples
-               --------
-               >>> import numpy as np
-               >>> from scipy.sparse import csc_matrix
-               >>> from scipy.sparse.linalg import bicg
-               >>> A = csc_matrix([[3, 2, 0], [1, -1, 0], [0, 5, 1]], dtype=float)
-               >>> b = np.array([2, 4, -1], dtype=float)
-               >>> x, exitCode = bicg(A, b)
-               >>> print(exitCode)            # 0 indicates successful convergence
-               0
-               >>> np.allclose(A.dot(x), b)
-               True
-
-               """
-               )
-@non_reentrant()
-def bicg(A, b, x0=None, tol=1e-5, maxiter=None, M=None, callback=None, atol=None):
-    A,M,x,b,postprocess = make_system(A, M, x0, b)
+    Examples
+    --------
+    >>> import numpy as np
+    >>> from scipy.sparse import csc_matrix
+    >>> from scipy.sparse.linalg import bicg
+    >>> A = csc_matrix([[3, 2, 0], [1, -1, 0], [0, 5, 1.]])
+    >>> b = np.array([2., 4., -1.])
+    >>> x, exitCode = bicg(A, b)
+    >>> print(exitCode)  # 0 indicates successful convergence
+    0
+    >>> np.allclose(A.dot(x), b)
+    True
+
+    """
+    A, M, x, b, postprocess = make_system(A, M, x0, b)
+    bnrm2 = np.linalg.norm(b)
+
+    if bnrm2 == 0:
+        return postprocess(b), 0
+
+    atol = _get_atol('bicg', b, tol, atol, rtol)
 
     n = len(b)
+    dotprod = np.vdot if np.iscomplexobj(x) else np.dot
+
     if maxiter is None:
         maxiter = n*10
 
     matvec, rmatvec = A.matvec, A.rmatvec
     psolve, rpsolve = M.matvec, M.rmatvec
-    ltr = _type_conv[x.dtype.char]
-    revcom = getattr(_iterative, ltr + 'bicgrevcom')
-
-    def get_residual():
-        return np.linalg.norm(matvec(x) - b)
-    atol = _get_atol(tol, atol, np.linalg.norm(b), get_residual, 'bicg')
-    if atol == 'exit':
-        return postprocess(x), 0
-
-    resid = atol
-    ndx1 = 1
-    ndx2 = -1
-    # Use _aligned_zeros to work around a f2py bug in Numpy 1.9.1
-    work = _aligned_zeros(6*n,dtype=x.dtype)
-    ijob = 1
-    info = 0
-    ftflag = True
-    iter_ = maxiter
-    while True:
-        olditer = iter_
-        x, iter_, resid, info, ndx1, ndx2, sclr1, sclr2, ijob = \
-           revcom(b, x, work, iter_, resid, info, ndx1, ndx2, ijob)
-        if callback is not None and iter_ > olditer:
+
+    rhotol = np.finfo(x.dtype.char).eps**2
+
+    # Dummy values to initialize vars, silence linter warnings
+    rho_prev, p, ptilde = None, None, None
+
+    r = b - matvec(x) if x.any() else b.copy()
+    rtilde = r.copy()
+
+    for iteration in range(maxiter):
+        if np.linalg.norm(r) < atol:  # Are we done?
+            return postprocess(x), 0
+
+        z = psolve(r)
+        ztilde = rpsolve(rtilde)
+        # order matters in this dot product
+        rho_cur = dotprod(rtilde, z)
+
+        if np.abs(rho_cur) < rhotol:  # Breakdown case
+            return postprocess, -10
+
+        if iteration > 0:
+            beta = rho_cur / rho_prev
+            p *= beta
+            p += z
+            ptilde *= beta.conj()
+            ptilde += ztilde
+        else:  # First spin
+            p = z.copy()
+            ptilde = ztilde.copy()
+
+        q = matvec(p)
+        qtilde = rmatvec(ptilde)
+        rv = dotprod(ptilde, q)
+
+        if rv == 0:
+            return postprocess(x), -11
+
+        alpha = rho_cur / rv
+        x += alpha*p
+        r -= alpha*q
+        rtilde -= alpha.conj()*qtilde
+        rho_prev = rho_cur
+
+        if callback:
             callback(x)
-        slice1 = slice(ndx1-1, ndx1-1+n)
-        slice2 = slice(ndx2-1, ndx2-1+n)
-        if (ijob == -1):
-            if callback is not None:
-                callback(x)
-            break
-        elif (ijob == 1):
-            work[slice2] *= sclr2
-            work[slice2] += sclr1*matvec(work[slice1])
-        elif (ijob == 2):
-            work[slice2] *= sclr2
-            work[slice2] += sclr1*rmatvec(work[slice1])
-        elif (ijob == 3):
-            work[slice1] = psolve(work[slice2])
-        elif (ijob == 4):
-            work[slice1] = rpsolve(work[slice2])
-        elif (ijob == 5):
-            work[slice2] *= sclr2
-            work[slice2] += sclr1*matvec(x)
-        elif (ijob == 6):
-            if ftflag:
-                info = -1
-                ftflag = False
-            resid, info = _stoptest(work[slice1], atol)
-        ijob = 2
-
-    if info > 0 and iter_ == maxiter and not (resid <= atol):
-        # info isn't set appropriately otherwise
-        info = iter_
 
-    return postprocess(x), info
+    else:  # for loop exhausted
+        # Return incomplete progress
+        return postprocess(x), maxiter
+
+
+def bicgstab(A, b, x0=None, tol=None, maxiter=None, M=None, callback=None,
+             atol=0., rtol=1e-5):
+    """Use BIConjugate Gradient STABilized iteration to solve ``Ax = b``.
+
+    Parameters
+    ----------
+    A : {sparse matrix, ndarray, LinearOperator}
+        The real or complex N-by-N matrix of the linear system.
+        Alternatively, ``A`` can be a linear operator which can
+        produce ``Ax`` and ``A^T x`` using, e.g.,
+        ``scipy.sparse.linalg.LinearOperator``.
+    b : ndarray
+        Right hand side of the linear system. Has shape (N,) or (N,1).
+    x0 : ndarray
+        Starting guess for the solution.
+    rtol, atol : float, optional
+        Parameters for the convergence test. For convergence,
+        ``norm(b - A @ x) <= max(rtol*norm(b), atol)`` should be satisfied.
+        The default is ``atol=0.`` and ``rtol=1e-5``.
+    maxiter : integer
+        Maximum number of iterations.  Iteration will stop after maxiter
+        steps even if the specified tolerance has not been achieved.
+    M : {sparse matrix, ndarray, LinearOperator}
+        Preconditioner for A.  The preconditioner should approximate the
+        inverse of A.  Effective preconditioning dramatically improves the
+        rate of convergence, which implies that fewer iterations are needed
+        to reach a given error tolerance.
+    callback : function
+        User-supplied function to call after each iteration.  It is called
+        as callback(xk), where xk is the current solution vector.
+    tol : float, optional, deprecated
 
+        .. deprecated 1.12.0
+           `bicgstab` keyword argument `tol` is deprecated in favor of `rtol`
+           and will be removed in SciPy 1.14.0.
 
-@set_docstring('Use BIConjugate Gradient STABilized iteration to solve '
-               '``Ax = b``.',
-               'The real or complex N-by-N matrix of the linear system.\n'
-               'Alternatively, ``A`` can be a linear operator which can\n'
-               'produce ``Ax`` using, e.g.,\n'
-               '``scipy.sparse.linalg.LinearOperator``.',
-               footer="""\
-               Examples
-               --------
-               >>> import numpy as np
-               >>> from scipy.sparse import csc_matrix
-               >>> from scipy.sparse.linalg import bicgstab
-               >>> R = np.array([[4, 2, 0, 1],
-               ...               [3, 0, 0, 2],
-               ...               [0, 1, 1, 1],
-               ...               [0, 2, 1, 0]])
-               >>> A = csc_matrix(R)
-               >>> b = np.array([-1, -0.5, -1, 2])
-               >>> x, exit_code = bicgstab(A, b)
-               >>> print(exit_code)  # 0 indicates successful convergence
-               0
-               >>> np.allclose(A.dot(x), b)
-               True
-               """)
-@non_reentrant()
-def bicgstab(A, b, x0=None, tol=1e-5, maxiter=None, M=None, callback=None, atol=None):
+    Returns
+    -------
+    x : ndarray
+        The converged solution.
+    info : integer
+        Provides convergence information:
+            0  : successful exit
+            >0 : convergence to tolerance not achieved, number of iterations
+            <0 : parameter breakdown
+
+    Examples
+    --------
+    >>> import numpy as np
+    >>> from scipy.sparse import csc_matrix
+    >>> from scipy.sparse.linalg import bicgstab
+    >>> R = np.array([[4, 2, 0, 1],
+    ...               [3, 0, 0, 2],
+    ...               [0, 1, 1, 1],
+    ...               [0, 2, 1, 0]])
+    >>> A = csc_matrix(R)
+    >>> b = np.array([-1, -0.5, -1, 2])
+    >>> x, exit_code = bicgstab(A, b)
+    >>> print(exit_code)  # 0 indicates successful convergence
+    0
+    >>> np.allclose(A.dot(x), b)
+    True
+
+    """
     A, M, x, b, postprocess = make_system(A, M, x0, b)
+    bnrm2 = np.linalg.norm(b)
+
+    if bnrm2 == 0:
+        return postprocess(b), 0
+
+    atol = _get_atol('bicgstab', b, tol, atol, rtol)
 
     n = len(b)
+
+    dotprod = np.vdot if np.iscomplexobj(x) else np.dot
+
     if maxiter is None:
         maxiter = n*10
 
     matvec = A.matvec
     psolve = M.matvec
-    ltr = _type_conv[x.dtype.char]
-    revcom = getattr(_iterative, ltr + 'bicgstabrevcom')
-
-    def get_residual():
-        return np.linalg.norm(matvec(x) - b)
-    atol = _get_atol(tol, atol, np.linalg.norm(b), get_residual, 'bicgstab')
-    if atol == 'exit':
-        return postprocess(x), 0
-
-    resid = atol
-    ndx1 = 1
-    ndx2 = -1
-    # Use _aligned_zeros to work around a f2py bug in Numpy 1.9.1
-    work = _aligned_zeros(7*n,dtype=x.dtype)
-    ijob = 1
-    info = 0
-    ftflag = True
-    iter_ = maxiter
-    while True:
-        olditer = iter_
-        x, iter_, resid, info, ndx1, ndx2, sclr1, sclr2, ijob = \
-           revcom(b, x, work, iter_, resid, info, ndx1, ndx2, ijob)
-        if callback is not None and iter_ > olditer:
+
+    # These values make no sense but coming from original Fortran code
+    # sqrt might have been meant instead.
+    rhotol = np.finfo(x.dtype.char).eps**2
+    omegatol = rhotol
+
+    # Dummy values to initialize vars, silence linter warnings
+    rho_prev, omega, alpha, p, v = None, None, None, None, None
+
+    r = b - matvec(x) if x.any() else b.copy()
+    rtilde = r.copy()
+
+    for iteration in range(maxiter):
+        if np.linalg.norm(r) < atol:  # Are we done?
+            return postprocess(x), 0
+
+        rho = dotprod(rtilde, r)
+        if np.abs(rho) < rhotol:  # rho breakdown
+            return postprocess(x), -10
+
+        if iteration > 0:
+            if np.abs(omega) < omegatol:  # omega breakdown
+                return postprocess(x), -11
+
+            beta = (rho / rho_prev) * (alpha / omega)
+            p -= omega*v
+            p *= beta
+            p += r
+        else:  # First spin
+            s = np.empty_like(r)
+            p = r.copy()
+
+        phat = psolve(p)
+        v = matvec(phat)
+        rv = dotprod(rtilde, v)
+        if rv == 0:
+            return postprocess(x), -11
+        alpha = rho / rv
+        r -= alpha*v
+        s[:] = r[:]
+
+        if np.linalg.norm(s) < atol:
+            x += alpha*phat
+            return postprocess(x), 0
+
+        shat = psolve(s)
+        t = matvec(shat)
+        omega = dotprod(t, s) / dotprod(t, t)
+        x += alpha*phat
+        x += omega*shat
+        r -= omega*t
+        rho_prev = rho
+
+        if callback:
             callback(x)
-        slice1 = slice(ndx1-1, ndx1-1+n)
-        slice2 = slice(ndx2-1, ndx2-1+n)
-        if (ijob == -1):
-            if callback is not None:
-                callback(x)
-            break
-        elif (ijob == 1):
-            work[slice2] *= sclr2
-            work[slice2] += sclr1*matvec(work[slice1])
-        elif (ijob == 2):
-            work[slice1] = psolve(work[slice2])
-        elif (ijob == 3):
-            work[slice2] *= sclr2
-            work[slice2] += sclr1*matvec(x)
-        elif (ijob == 4):
-            if ftflag:
-                info = -1
-                ftflag = False
-            resid, info = _stoptest(work[slice1], atol)
-        ijob = 2
-
-    if info > 0 and iter_ == maxiter and not (resid <= atol):
-        # info isn't set appropriately otherwise
-        info = iter_
 
-    return postprocess(x), info
+    else:  # for loop exhausted
+        # Return incomplete progress
+        return postprocess(x), maxiter
+
 
+def cg(A, b, x0=None, tol=None, maxiter=None, M=None, callback=None, atol=0.,
+       rtol=1e-5):
+    """Use Conjugate Gradient iteration to solve ``Ax = b``.
 
-@set_docstring('Use Conjugate Gradient iteration to solve ``Ax = b``.',
-               'The real or complex N-by-N matrix of the linear system.\n'
-               '``A`` must represent a hermitian, positive definite matrix.\n'
-               'Alternatively, ``A`` can be a linear operator which can\n'
-               'produce ``Ax`` using, e.g.,\n'
-               '``scipy.sparse.linalg.LinearOperator``.',
-               footer="""\
-               Examples
-               --------
-               >>> import numpy as np
-               >>> from scipy.sparse import csc_matrix
-               >>> from scipy.sparse.linalg import cg
-               >>> P = np.array([[4, 0, 1, 0],
-               ...               [0, 5, 0, 0],
-               ...               [1, 0, 3, 2],
-               ...               [0, 0, 2, 4]])
-               >>> A = csc_matrix(P)
-               >>> b = np.array([-1, -0.5, -1, 2])
-               >>> x, exit_code = cg(A, b)
-               >>> print(exit_code)    # 0 indicates successful convergence
-               0
-               >>> np.allclose(A.dot(x), b)
-               True
-
-               """)
-@non_reentrant()
-def cg(A, b, x0=None, tol=1e-5, maxiter=None, M=None, callback=None, atol=None):
+    Parameters
+    ----------
+    A : {sparse matrix, ndarray, LinearOperator}
+        The real or complex N-by-N matrix of the linear system.
+        ``A`` must represent a hermitian, positive definite matrix.
+        Alternatively, ``A`` can be a linear operator which can
+        produce ``Ax`` using, e.g.,
+        ``scipy.sparse.linalg.LinearOperator``.
+    b : ndarray
+        Right hand side of the linear system. Has shape (N,) or (N,1).
+    x0 : ndarray
+        Starting guess for the solution.
+    rtol, atol : float, optional
+        Parameters for the convergence test. For convergence,
+        ``norm(b - A @ x) <= max(rtol*norm(b), atol)`` should be satisfied.
+        The default is ``atol=0.`` and ``rtol=1e-5``.
+    maxiter : integer
+        Maximum number of iterations.  Iteration will stop after maxiter
+        steps even if the specified tolerance has not been achieved.
+    M : {sparse matrix, ndarray, LinearOperator}
+        Preconditioner for A.  The preconditioner should approximate the
+        inverse of A.  Effective preconditioning dramatically improves the
+        rate of convergence, which implies that fewer iterations are needed
+        to reach a given error tolerance.
+    callback : function
+        User-supplied function to call after each iteration.  It is called
+        as callback(xk), where xk is the current solution vector.
+    tol : float, optional, deprecated
+
+        .. deprecated 1.12.0
+           `cg` keyword argument `tol` is deprecated in favor of `rtol` and
+           will be removed in SciPy 1.14.0.
+
+    Returns
+    -------
+    x : ndarray
+        The converged solution.
+    info : integer
+        Provides convergence information:
+            0  : successful exit
+            >0 : convergence to tolerance not achieved, number of iterations
+
+    Examples
+    --------
+    >>> import numpy as np
+    >>> from scipy.sparse import csc_matrix
+    >>> from scipy.sparse.linalg import cg
+    >>> P = np.array([[4, 0, 1, 0],
+    ...               [0, 5, 0, 0],
+    ...               [1, 0, 3, 2],
+    ...               [0, 0, 2, 4]])
+    >>> A = csc_matrix(P)
+    >>> b = np.array([-1, -0.5, -1, 2])
+    >>> x, exit_code = cg(A, b)
+    >>> print(exit_code)    # 0 indicates successful convergence
+    0
+    >>> np.allclose(A.dot(x), b)
+    True
+
+    """
     A, M, x, b, postprocess = make_system(A, M, x0, b)
+    bnrm2 = np.linalg.norm(b)
+
+    if bnrm2 == 0:
+        return postprocess(b), 0
+
+    atol = _get_atol('cg', b, tol, atol, rtol)
 
     n = len(b)
+
     if maxiter is None:
         maxiter = n*10
 
+    dotprod = np.vdot if np.iscomplexobj(x) else np.dot
+
     matvec = A.matvec
     psolve = M.matvec
-    ltr = _type_conv[x.dtype.char]
-    revcom = getattr(_iterative, ltr + 'cgrevcom')
-
-    def get_residual():
-        return np.linalg.norm(matvec(x) - b)
-    atol = _get_atol(tol, atol, np.linalg.norm(b), get_residual, 'cg')
-    if atol == 'exit':
-        return postprocess(x), 0
-
-    resid = atol
-    ndx1 = 1
-    ndx2 = -1
-    # Use _aligned_zeros to work around a f2py bug in Numpy 1.9.1
-    work = _aligned_zeros(4*n,dtype=x.dtype)
-    ijob = 1
-    info = 0
-    ftflag = True
-    iter_ = maxiter
-    while True:
-        olditer = iter_
-        x, iter_, resid, info, ndx1, ndx2, sclr1, sclr2, ijob = \
-           revcom(b, x, work, iter_, resid, info, ndx1, ndx2, ijob)
-        if callback is not None and iter_ > olditer:
+    r = b - matvec(x) if x.any() else b.copy()
+
+    # Dummy value to initialize var, silences warnings
+    rho_prev, p = None, None
+
+    for iteration in range(maxiter):
+        if np.linalg.norm(r) < atol:  # Are we done?
+            return postprocess(x), 0
+
+        z = psolve(r)
+        rho_cur = dotprod(r, z)
+        if iteration > 0:
+            beta = rho_cur / rho_prev
+            p *= beta
+            p += z
+        else:  # First spin
+            p = np.empty_like(r)
+            p[:] = z[:]
+
+        q = matvec(p)
+        alpha = rho_cur / dotprod(p, q)
+        x += alpha*p
+        r -= alpha*q
+        rho_prev = rho_cur
+
+        if callback:
             callback(x)
-        slice1 = slice(ndx1-1, ndx1-1+n)
-        slice2 = slice(ndx2-1, ndx2-1+n)
-        if (ijob == -1):
-            if callback is not None:
-                callback(x)
-            break
-        elif (ijob == 1):
-            work[slice2] *= sclr2
-            work[slice2] += sclr1*matvec(work[slice1])
-        elif (ijob == 2):
-            work[slice1] = psolve(work[slice2])
-        elif (ijob == 3):
-            work[slice2] *= sclr2
-            work[slice2] += sclr1*matvec(x)
-        elif (ijob == 4):
-            if ftflag:
-                info = -1
-                ftflag = False
-            resid, info = _stoptest(work[slice1], atol)
-            if info == 1 and iter_ > 1:
-                # recompute residual and recheck, to avoid
-                # accumulating rounding error
-                work[slice1] = b - matvec(x)
-                resid, info = _stoptest(work[slice1], atol)
-        ijob = 2
-
-    if info > 0 and iter_ == maxiter and not (resid <= atol):
-        # info isn't set appropriately otherwise
-        info = iter_
 
-    return postprocess(x), info
+    else:  # for loop exhausted
+        # Return incomplete progress
+        return postprocess(x), maxiter
 
 
-@set_docstring('Use Conjugate Gradient Squared iteration to solve ``Ax = b``.',
-               'The real-valued N-by-N matrix of the linear system.\n'
-               'Alternatively, ``A`` can be a linear operator which can\n'
-               'produce ``Ax`` using, e.g.,\n'
-               '``scipy.sparse.linalg.LinearOperator``.',
-               footer="""\
-               Examples
-               --------
-               >>> import numpy as np
-               >>> from scipy.sparse import csc_matrix
-               >>> from scipy.sparse.linalg import cgs
-               >>> R = np.array([[4, 2, 0, 1],
-               ...               [3, 0, 0, 2],
-               ...               [0, 1, 1, 1],
-               ...               [0, 2, 1, 0]])
-               >>> A = csc_matrix(R)
-               >>> b = np.array([-1, -0.5, -1, 2])
-               >>> x, exit_code = cgs(A, b)
-               >>> print(exit_code)  # 0 indicates successful convergence
-               0
-               >>> np.allclose(A.dot(x), b)
-               True
-               """
-               )
-@non_reentrant()
-def cgs(A, b, x0=None, tol=1e-5, maxiter=None, M=None, callback=None, atol=None):
+def cgs(A, b, x0=None, tol=None, maxiter=None, M=None, callback=None,
+        atol=0., rtol=1e-5):
+    """Use Conjugate Gradient Squared iteration to solve ``Ax = b``.
+
+    Parameters
+    ----------
+    A : {sparse matrix, ndarray, LinearOperator}
+        The real-valued N-by-N matrix of the linear system.
+        Alternatively, ``A`` can be a linear operator which can
+        produce ``Ax`` using, e.g.,
+        ``scipy.sparse.linalg.LinearOperator``.
+    b : ndarray
+        Right hand side of the linear system. Has shape (N,) or (N,1).
+    x0 : ndarray
+        Starting guess for the solution.
+    rtol, atol : float, optional
+        Parameters for the convergence test. For convergence,
+        ``norm(b - A @ x) <= max(rtol*norm(b), atol)`` should be satisfied.
+        The default is ``atol=0.`` and ``rtol=1e-5``.
+    maxiter : integer
+        Maximum number of iterations.  Iteration will stop after maxiter
+        steps even if the specified tolerance has not been achieved.
+    M : {sparse matrix, ndarray, LinearOperator}
+        Preconditioner for A.  The preconditioner should approximate the
+        inverse of A.  Effective preconditioning dramatically improves the
+        rate of convergence, which implies that fewer iterations are needed
+        to reach a given error tolerance.
+    callback : function
+        User-supplied function to call after each iteration.  It is called
+        as callback(xk), where xk is the current solution vector.
+    tol : float, optional, deprecated
+
+        .. deprecated 1.12.0
+           `cgs` keyword argument `tol` is deprecated in favor of `rtol` and
+           will be removed in SciPy 1.14.0.
+
+    Returns
+    -------
+    x : ndarray
+        The converged solution.
+    info : integer
+        Provides convergence information:
+            0  : successful exit
+            >0 : convergence to tolerance not achieved, number of iterations
+            <0 : parameter breakdown
+
+    Examples
+    --------
+    >>> import numpy as np
+    >>> from scipy.sparse import csc_matrix
+    >>> from scipy.sparse.linalg import cgs
+    >>> R = np.array([[4, 2, 0, 1],
+    ...               [3, 0, 0, 2],
+    ...               [0, 1, 1, 1],
+    ...               [0, 2, 1, 0]])
+    >>> A = csc_matrix(R)
+    >>> b = np.array([-1, -0.5, -1, 2])
+    >>> x, exit_code = cgs(A, b)
+    >>> print(exit_code)  # 0 indicates successful convergence
+    0
+    >>> np.allclose(A.dot(x), b)
+    True
+
+    """
     A, M, x, b, postprocess = make_system(A, M, x0, b)
+    bnrm2 = np.linalg.norm(b)
+
+    if bnrm2 == 0:
+        return postprocess(b), 0
+
+    atol = _get_atol('cgs', b, tol, atol, rtol)
 
     n = len(b)
+
+    dotprod = np.vdot if np.iscomplexobj(x) else np.dot
+
     if maxiter is None:
         maxiter = n*10
 
     matvec = A.matvec
     psolve = M.matvec
-    ltr = _type_conv[x.dtype.char]
-    revcom = getattr(_iterative, ltr + 'cgsrevcom')
-
-    def get_residual():
-        return np.linalg.norm(matvec(x) - b)
-    atol = _get_atol(tol, atol, np.linalg.norm(b), get_residual, 'cgs')
-    if atol == 'exit':
-        return postprocess(x), 0
-
-    resid = atol
-    ndx1 = 1
-    ndx2 = -1
-    # Use _aligned_zeros to work around a f2py bug in Numpy 1.9.1
-    work = _aligned_zeros(7*n,dtype=x.dtype)
-    ijob = 1
-    info = 0
-    ftflag = True
-    iter_ = maxiter
-    while True:
-        olditer = iter_
-        x, iter_, resid, info, ndx1, ndx2, sclr1, sclr2, ijob = \
-           revcom(b, x, work, iter_, resid, info, ndx1, ndx2, ijob)
-        if callback is not None and iter_ > olditer:
+
+    rhotol = np.finfo(x.dtype.char).eps**2
+
+    r = b - matvec(x) if x.any() else b.copy()
+
+    rtilde = r.copy()
+    bnorm = np.linalg.norm(b)
+    if bnorm == 0:
+        bnorm = 1
+
+    # Dummy values to initialize vars, silence linter warnings
+    rho_prev, p, u, q = None, None, None, None
+
+    for iteration in range(maxiter):
+        rnorm = np.linalg.norm(r)
+        if rnorm < atol:  # Are we done?
+            return postprocess(x), 0
+
+        rho_cur = dotprod(rtilde, r)
+        if np.abs(rho_cur) < rhotol:  # Breakdown case
+            return postprocess, -10
+
+        if iteration > 0:
+            beta = rho_cur / rho_prev
+
+            # u = r + beta * q
+            # p = u + beta * (q + beta * p);
+            u[:] = r[:]
+            u += beta*q
+
+            p *= beta
+            p += q
+            p *= beta
+            p += u
+
+        else:  # First spin
+            p = r.copy()
+            u = r.copy()
+            q = np.empty_like(r)
+
+        phat = psolve(p)
+        vhat = matvec(phat)
+        rv = dotprod(rtilde, vhat)
+
+        if rv == 0:  # Dot product breakdown
+            return postprocess(x), -11
+
+        alpha = rho_cur / rv
+        q[:] = u[:]
+        q -= alpha*vhat
+        uhat = psolve(u + q)
+        x += alpha*uhat
+
+        # Due to numerical error build-up the actual residual is computed
+        # instead of the following two lines that were in the original
+        # FORTRAN templates, still using a single matvec.
+
+        # qhat = matvec(uhat)
+        # r -= alpha*qhat
+        r = b - matvec(x)
+
+        rho_prev = rho_cur
+
+        if callback:
             callback(x)
-        slice1 = slice(ndx1-1, ndx1-1+n)
-        slice2 = slice(ndx2-1, ndx2-1+n)
-        if (ijob == -1):
-            if callback is not None:
-                callback(x)
-            break
-        elif (ijob == 1):
-            work[slice2] *= sclr2
-            work[slice2] += sclr1*matvec(work[slice1])
-        elif (ijob == 2):
-            work[slice1] = psolve(work[slice2])
-        elif (ijob == 3):
-            work[slice2] *= sclr2
-            work[slice2] += sclr1*matvec(x)
-        elif (ijob == 4):
-            if ftflag:
-                info = -1
-                ftflag = False
-            resid, info = _stoptest(work[slice1], atol)
-            if info == 1 and iter_ > 1:
-                # recompute residual and recheck, to avoid
-                # accumulating rounding error
-                work[slice1] = b - matvec(x)
-                resid, info = _stoptest(work[slice1], atol)
-        ijob = 2
-
-    if info == -10:
-        # termination due to breakdown: check for convergence
-        resid, ok = _stoptest(b - matvec(x), atol)
-        if ok:
-            info = 0
-
-    if info > 0 and iter_ == maxiter and not (resid <= atol):
-        # info isn't set appropriately otherwise
-        info = iter_
 
-    return postprocess(x), info
+    else:  # for loop exhausted
+        # Return incomplete progress
+        return postprocess(x), maxiter
 
 
-@non_reentrant()
-def gmres(A, b, x0=None, tol=1e-5, restart=None, maxiter=None, M=None, callback=None,
-          restrt=None, atol=None, callback_type=None):
+def gmres(A, b, x0=None, tol=None, restart=None, maxiter=None, M=None,
+          callback=None, restrt=None, atol=0., callback_type=None,
+          rtol=1e-5):
     """
     Use Generalized Minimal RESidual iteration to solve ``Ax = b``.
 
@@ -503,38 +582,20 @@ def gmres(A, b, x0=None, tol=1e-5, restart=None, maxiter=None, M=None, callback=
         ``scipy.sparse.linalg.LinearOperator``.
     b : ndarray
         Right hand side of the linear system. Has shape (N,) or (N,1).
-
-    Returns
-    -------
-    x : ndarray
-        The converged solution.
-    info : int
-        Provides convergence information:
-          * 0  : successful exit
-          * >0 : convergence to tolerance not achieved, number of iterations
-          * <0 : illegal input or breakdown
-
-    Other parameters
-    ----------------
     x0 : ndarray
         Starting guess for the solution (a vector of zeros by default).
-    tol, atol : float, optional
-        Tolerances for convergence, ``norm(residual) <= max(tol*norm(b), atol)``.
-        The default for ``atol`` is ``'legacy'``, which emulates
-        a different legacy behavior.
-
-        .. warning::
-
-           The default value for `atol` will be changed in a future release.
-           For future compatibility, specify `atol` explicitly.
+    atol, rtol : float
+        Parameters for the convergence test. For convergence,
+        ``norm(b - A @ x) <= max(rtol*norm(b), atol)`` should be satisfied.
+        The default is ``atol=0.`` and ``rtol=1e-5``.
     restart : int, optional
         Number of iterations between restarts. Larger values increase
         iteration cost, but may be necessary for convergence.
-        Default is 20.
+        If omitted, ``min(20, n)`` is used.
     maxiter : int, optional
         Maximum number of iterations (restart cycles).  Iteration will stop
         after maxiter steps even if the specified tolerance has not been
-        achieved.
+        achieved. See `callback_type`.
     M : {sparse matrix, ndarray, LinearOperator}
         Inverse of the preconditioner of A.  M should approximate the
         inverse of A and be easy to solve for (see Notes).  Effective
@@ -542,7 +603,8 @@ def gmres(A, b, x0=None, tol=1e-5, restart=None, maxiter=None, M=None, callback=
         which implies that fewer iterations are needed to reach a given
         error tolerance.  By default, no preconditioner is used.
         In this implementation, left preconditioning is used,
-        and the preconditioned residual is minimized.
+        and the preconditioned residual is minimized. However, the final
+        convergence is tested with respect to the ``b - A @ x`` residual.
     callback : function
         User-supplied function to call after each iteration.  It is called
         as `callback(args)`, where `args` are selected by `callback_type`.
@@ -552,13 +614,29 @@ def gmres(A, b, x0=None, tol=1e-5, restart=None, maxiter=None, M=None, callback=
           - ``pr_norm``: relative (preconditioned) residual norm (float),
             called on every inner iteration
           - ``legacy`` (default): same as ``pr_norm``, but also changes the
-            meaning of 'maxiter' to count inner iterations instead of restart
+            meaning of `maxiter` to count inner iterations instead of restart
             cycles.
+
+        This keyword has no effect if `callback` is not set.
     restrt : int, optional, deprecated
 
         .. deprecated:: 0.11.0
-           `gmres` keyword argument `restrt` is deprecated infavour of
+           `gmres` keyword argument `restrt` is deprecated in favor of
            `restart` and will be removed in SciPy 1.12.0.
+    tol : float, optional, deprecated
+
+        .. deprecated 1.12.0
+           `gmres` keyword argument `tol` is deprecated in favor of `rtol` and
+           will be removed in SciPy 1.14.0
+
+    Returns
+    -------
+    x : ndarray
+        The converged solution.
+    info : int
+        Provides convergence information:
+            0  : successful exit
+            >0 : convergence to tolerance not achieved, number of iterations
 
     See Also
     --------
@@ -590,26 +668,31 @@ def gmres(A, b, x0=None, tol=1e-5, restart=None, maxiter=None, M=None, callback=
     True
     """
 
-    # Change 'restrt' keyword to 'restart'
-    if restrt is None:
-        restrt = restart
-    elif restart is not None:
-        raise ValueError("Cannot specify both restart and restrt keywords. "
-                         "Preferably use 'restart' only.")
-    else:
-        msg = ("'gmres' keyword argument 'restrt' is deprecated infavour of "
-               "'restart' and will be removed in SciPy 1.12.0.")
-        warnings.warn(msg, DeprecationWarning, stacklevel=2)
+    # Handle the deprecation frenzy
+    if restrt and restart:
+        raise ValueError("Cannot specify both restart and restrt"
+                         " keywords. Also 'rstrt' is deprecated."
+                         " and will be removed in SciPy 1.12.0. Use "
+                         "'restart' instad.")
+    if restrt is not None:
+        msg = ("'gmres' keyword argument 'restrt' is deprecated "
+               "in favor of 'restart' and will be removed in SciPy"
+               " 1.12.0. Until then, if set, 'rstrt' will override 'restart'."
+               )
+        warnings.warn(msg, DeprecationWarning, stacklevel=3)
+        restart = restrt
 
     if callback is not None and callback_type is None:
         # Warn about 'callback_type' semantic changes.
         # Probably should be removed only in far future, Scipy 2.0 or so.
-        warnings.warn("scipy.sparse.linalg.gmres called without specifying `callback_type`. "
-                      "The default value will be changed in a future release. "
-                      "For compatibility, specify a value for `callback_type` explicitly, e.g., "
-                      "``{name}(..., callback_type='pr_norm')``, or to retain the old behavior "
-                      "``{name}(..., callback_type='legacy')``",
-                      category=DeprecationWarning, stacklevel=3)
+        msg = ("scipy.sparse.linalg.gmres called without specifying "
+               "`callback_type`. The default value will be changed in"
+               " a future release. For compatibility, specify a value "
+               "for `callback_type` explicitly, e.g., "
+               "``gmres(..., callback_type='pr_norm')``, or to retain the "
+               "old behavior ``gmres(..., callback_type='legacy')``"
+               )
+        warnings.warn(msg, category=DeprecationWarning, stacklevel=3)
 
     if callback_type is None:
         callback_type = 'legacy'
@@ -618,123 +701,160 @@ def gmres(A, b, x0=None, tol=1e-5, restart=None, maxiter=None, M=None, callback=
         raise ValueError(f"Unknown callback_type: {callback_type!r}")
 
     if callback is None:
-        callback_type = 'none'
-
-    A, M, x, b,postprocess = make_system(A, M, x0, b)
+        callback_type = None
 
+    A, M, x, b, postprocess = make_system(A, M, x0, b)
+    matvec = A.matvec
+    psolve = M.matvec
     n = len(b)
+    bnrm2 = np.linalg.norm(b)
+
+    if bnrm2 == 0:
+        return postprocess(b), 0
+
+    eps = np.finfo(x.dtype.char).eps
+
+    dotprod = np.vdot if np.iscomplexobj(x) else np.dot
+
     if maxiter is None:
         maxiter = n*10
 
-    if restrt is None:
-        restrt = 20
-    restrt = min(restrt, n)
+    if restart is None:
+        restart = 20
+    restart = min(restart, n)
 
-    matvec = A.matvec
-    psolve = M.matvec
-    ltr = _type_conv[x.dtype.char]
-    revcom = getattr(_iterative, ltr + 'gmresrevcom')
+    atol = _get_atol('gmres', b, tol, atol, rtol)
 
-    bnrm2 = np.linalg.norm(b)
     Mb_nrm2 = np.linalg.norm(psolve(b))
-    def get_residual():
-        return np.linalg.norm(matvec(x) - b)
-    atol = _get_atol(tol, atol, bnrm2, get_residual, 'gmres')
-    if atol == 'exit':
-        return postprocess(x), 0
 
-    if bnrm2 == 0:
-        return postprocess(b), 0
-
-    # Tolerance passed to GMRESREVCOM applies to the inner iteration
-    # and deals with the left-preconditioned residual.
-    ptol_max_factor = 1.0
+    # ====================================================
+    # =========== Tolerance control from gh-8400 =========
+    # ====================================================
+    # Tolerance passed to GMRESREVCOM applies to the inner
+    # iteration and deals with the left-preconditioned
+    # residual.
+    ptol_max_factor = 1.
     ptol = Mb_nrm2 * min(ptol_max_factor, atol / bnrm2)
-    resid = np.nan
-    presid = np.nan
-    ndx1 = 1
-    ndx2 = -1
-    # Use _aligned_zeros to work around a f2py bug in Numpy 1.9.1
-    work = _aligned_zeros((6+restrt)*n,dtype=x.dtype)
-    work2 = _aligned_zeros((restrt+1)*(2*restrt+2),dtype=x.dtype)
-    ijob = 1
-    info = 0
-    ftflag = True
-    iter_ = maxiter
-    old_ijob = ijob
-    first_pass = True
-    resid_ready = False
-    iter_num = 1
-    while True:
-        olditer = iter_
-        x, iter_, presid, info, ndx1, ndx2, sclr1, sclr2, ijob = \
-           revcom(b, x, restrt, work, work2, iter_, presid, info, ndx1, ndx2, ijob, ptol)
-        if callback_type == 'x' and iter_ != olditer:
-            callback(x)
-        slice1 = slice(ndx1-1, ndx1-1+n)
-        slice2 = slice(ndx2-1, ndx2-1+n)
-        if (ijob == -1):  # gmres success, update last residual
-            if callback_type in ('pr_norm', 'legacy'):
-                if resid_ready:
-                    callback(presid / bnrm2)
-            elif callback_type == 'x':
-                callback(x)
-            break
-        elif (ijob == 1):
-            work[slice2] *= sclr2
-            work[slice2] += sclr1*matvec(x)
-        elif (ijob == 2):
-            work[slice1] = psolve(work[slice2])
-            if not first_pass and old_ijob == 3:
-                resid_ready = True
-
-            first_pass = False
-        elif (ijob == 3):
-            work[slice2] *= sclr2
-            work[slice2] += sclr1*matvec(work[slice1])
-            if resid_ready:
-                if callback_type in ('pr_norm', 'legacy'):
-                    callback(presid / bnrm2)
-                resid_ready = False
-                iter_num = iter_num+1
-
-        elif (ijob == 4):
-            if ftflag:
-                info = -1
-                ftflag = False
-            resid, info = _stoptest(work[slice1], atol)
-
-            # Inner loop tolerance control
-            if info or presid > ptol:
-                ptol_max_factor = min(1.0, 1.5 * ptol_max_factor)
+    presid = 0.
+    # ====================================================
+    lartg = get_lapack_funcs('lartg', dtype=x.dtype)
+
+    # allocate internal variables
+    v = np.empty([restart+1, n], dtype=x.dtype)
+    h = np.zeros([restart, restart+1], dtype=x.dtype)
+    givens = np.zeros([restart, 2], dtype=x.dtype)
+
+    # legacy iteration count
+    inner_iter = 0
+
+    for iteration in range(maxiter):
+        if iteration == 0:
+            r = b - matvec(x) if x.any() else b.copy()
+
+        v[0, :] = psolve(r)
+        tmp = np.linalg.norm(v[0, :])
+        v[0, :] *= (1 / tmp)
+        # RHS of the Hessenberg problem
+        S = np.zeros(restart+1, dtype=x.dtype)
+        S[0] = tmp
+
+        breakdown = False
+        for col in range(restart):
+            av = matvec(v[col, :])
+            w = psolve(av)
+
+            # Modified Gram-Schmidt
+            h0 = np.linalg.norm(w)
+            for k in range(col+1):
+                tmp = dotprod(v[k, :], w)
+                h[col, k] = tmp
+                w -= tmp*v[k, :]
+
+            h1 = np.linalg.norm(w)
+            h[col, col + 1] = h1
+            v[col + 1, :] = w[:]
+
+            # Exact solution indicator
+            if h1 <= eps*h0:
+                h[col, col + 1] = 0
+                breakdown = True
             else:
-                # Inner loop tolerance OK, but outer loop not.
-                ptol_max_factor = max(1e-16, 0.25 * ptol_max_factor)
+                v[col + 1, :] *= (1 / h1)
+
+            # apply past Givens rotations to current h column
+            for k in range(col):
+                c, s = givens[k, 0], givens[k, 1]
+                n0, n1 = h[col, [k, k+1]]
+                h[col, [k, k + 1]] = [c*n0 + s*n1, -s.conj()*n0 + c*n1]
+
+            # get and apply current rotation to h and S
+            c, s, mag = lartg(h[col, col], h[col, col+1])
+            givens[col, :] = [c, s]
+            h[col, [col, col+1]] = mag, 0
+
+            # S[col+1] component is always 0
+            tmp = -np.conjugate(s)*S[col]
+            S[[col, col + 1]] = [c*S[col], tmp]
+            presid = np.abs(tmp)
+            inner_iter += 1
+
+            if callback_type in ('legacy', 'pr_norm'):
+                callback(presid / bnrm2)
+            # Legacy behavior
+            if callback_type == 'legacy' and inner_iter == maxiter:
+                break
+            if presid <= ptol or breakdown:
+                break
 
-            if resid != 0:
-                ptol = presid * min(ptol_max_factor, atol / resid)
-            else:
-                ptol = presid * ptol_max_factor
+        # Solve h(col, col) upper triangular system and allow pseudo-solve
+        # singular cases as in (but without the f2py copies):
+        # y = trsv(h[:col+1, :col+1].T, S[:col+1])
 
-        old_ijob = ijob
-        ijob = 2
+        if h[col, col] == 0:
+            S[col] = 0
 
-        if callback_type == 'legacy':
-            # Legacy behavior
-            if iter_num > maxiter:
-                info = maxiter
-                break
+        y = np.zeros([col+1], dtype=x.dtype)
+        y[:] = S[:col+1]
+        for k in range(col, 0, -1):
+            if y[k] != 0:
+                y[k] /= h[k, k]
+                tmp = y[k]
+                y[:k] -= tmp*h[k, :k]
+        if y[0] != 0:
+            y[0] /= h[0, 0]
+
+        x += y @ v[:col+1, :]
+
+        r = b - matvec(x)
+        rnorm = np.linalg.norm(r)
+
+        # Legacy exit
+        if callback_type == 'legacy' and inner_iter == maxiter:
+            return postprocess(x), 0 if rnorm <= atol else maxiter
 
-    if info >= 0 and not (resid <= atol):
-        # info isn't set appropriately otherwise
-        info = maxiter
+        if callback_type == 'x':
+            callback(x)
+
+        if rnorm <= atol:
+            break
+        elif breakdown:
+            # Reached breakdown (= exact solution), but the external
+            # tolerance check failed. Bail out with failure.
+            break
+        elif presid <= ptol:
+            # Inner loop passed but outer didn't
+            ptol_max_factor = max(eps, 0.25 * ptol_max_factor)
+        else:
+            ptol_max_factor = min(1.0, 1.5 * ptol_max_factor)
 
+        ptol = presid * min(ptol_max_factor, atol / rnorm)
+
+    info = 0 if (rnorm <= atol) else maxiter
     return postprocess(x), info
 
 
-@non_reentrant()
-def qmr(A, b, x0=None, tol=1e-5, maxiter=None, M1=None, M2=None, callback=None,
-        atol=None):
+def qmr(A, b, x0=None, tol=None, maxiter=None, M1=None, M2=None, callback=None,
+        atol=0., rtol=1e-5):
     """Use Quasi-Minimal Residual iteration to solve ``Ax = b``.
 
     Parameters
@@ -746,30 +866,12 @@ def qmr(A, b, x0=None, tol=1e-5, maxiter=None, M1=None, M2=None, callback=None,
         ``scipy.sparse.linalg.LinearOperator``.
     b : ndarray
         Right hand side of the linear system. Has shape (N,) or (N,1).
-
-    Returns
-    -------
-    x : ndarray
-        The converged solution.
-    info : integer
-        Provides convergence information:
-            0  : successful exit
-            >0 : convergence to tolerance not achieved, number of iterations
-            <0 : illegal input or breakdown
-
-    Other Parameters
-    ----------------
     x0 : ndarray
         Starting guess for the solution.
-    tol, atol : float, optional
-        Tolerances for convergence, ``norm(residual) <= max(tol*norm(b), atol)``.
-        The default for ``atol`` is ``'legacy'``, which emulates
-        a different legacy behavior.
-
-        .. warning::
-
-           The default value for `atol` will be changed in a future release.
-           For future compatibility, specify `atol` explicitly.
+    atol, rtol : float, optional
+        Parameters for the convergence test. For convergence,
+        ``norm(b - A @ x) <= max(rtol*norm(b), atol)`` should be satisfied.
+        The default is ``atol=0.`` and ``rtol=1e-5``.
     maxiter : integer
         Maximum number of iterations.  Iteration will stop after maxiter
         steps even if the specified tolerance has not been achieved.
@@ -782,6 +884,21 @@ def qmr(A, b, x0=None, tol=1e-5, maxiter=None, M1=None, M2=None, callback=None,
     callback : function
         User-supplied function to call after each iteration.  It is called
         as callback(xk), where xk is the current solution vector.
+    tol : float, optional, deprecated
+
+        .. deprecated 1.12.0
+           `qmr` keyword argument `tol` is deprecated in favor of `rtol` and
+           will be removed in SciPy 1.14.0.
+
+    Returns
+    -------
+    x : ndarray
+        The converged solution.
+    info : integer
+        Provides convergence information:
+            0  : successful exit
+            >0 : convergence to tolerance not achieved, number of iterations
+            <0 : parameter breakdown
 
     See Also
     --------
@@ -792,8 +909,8 @@ def qmr(A, b, x0=None, tol=1e-5, maxiter=None, M1=None, M2=None, callback=None,
     >>> import numpy as np
     >>> from scipy.sparse import csc_matrix
     >>> from scipy.sparse.linalg import qmr
-    >>> A = csc_matrix([[3, 2, 0], [1, -1, 0], [0, 5, 1]], dtype=float)
-    >>> b = np.array([2, 4, -1], dtype=float)
+    >>> A = csc_matrix([[3., 2., 0.], [1., -1., 0.], [0., 5., 1.]])
+    >>> b = np.array([2., 4., -1.])
     >>> x, exitCode = qmr(A, b)
     >>> print(exitCode)            # 0 indicates successful convergence
     0
@@ -802,22 +919,32 @@ def qmr(A, b, x0=None, tol=1e-5, maxiter=None, M1=None, M2=None, callback=None,
     """
     A_ = A
     A, M, x, b, postprocess = make_system(A, None, x0, b)
+    bnrm2 = np.linalg.norm(b)
+
+    if bnrm2 == 0:
+        return postprocess(b), 0
+
+    atol = _get_atol('qmr', b, tol, atol, rtol)
 
     if M1 is None and M2 is None:
-        if hasattr(A_,'psolve'):
+        if hasattr(A_, 'psolve'):
             def left_psolve(b):
-                return A_.psolve(b,'left')
+                return A_.psolve(b, 'left')
 
             def right_psolve(b):
-                return A_.psolve(b,'right')
+                return A_.psolve(b, 'right')
 
             def left_rpsolve(b):
-                return A_.rpsolve(b,'left')
+                return A_.rpsolve(b, 'left')
 
             def right_rpsolve(b):
-                return A_.rpsolve(b,'right')
-            M1 = LinearOperator(A.shape, matvec=left_psolve, rmatvec=left_rpsolve)
-            M2 = LinearOperator(A.shape, matvec=right_psolve, rmatvec=right_rpsolve)
+                return A_.rpsolve(b, 'right')
+            M1 = LinearOperator(A.shape,
+                                matvec=left_psolve,
+                                rmatvec=left_rpsolve)
+            M2 = LinearOperator(A.shape,
+                                matvec=right_psolve,
+                                rmatvec=right_rpsolve)
         else:
             def id(b):
                 return b
@@ -828,62 +955,108 @@ def id(b):
     if maxiter is None:
         maxiter = n*10
 
-    ltr = _type_conv[x.dtype.char]
-    revcom = getattr(_iterative, ltr + 'qmrrevcom')
-
-    def get_residual():
-        return np.linalg.norm(A.matvec(x) - b)
-    atol = _get_atol(tol, atol, np.linalg.norm(b), get_residual, 'qmr')
-    if atol == 'exit':
-        return postprocess(x), 0
-
-    resid = atol
-    ndx1 = 1
-    ndx2 = -1
-    # Use _aligned_zeros to work around a f2py bug in Numpy 1.9.1
-    work = _aligned_zeros(11*n,x.dtype)
-    ijob = 1
-    info = 0
-    ftflag = True
-    iter_ = maxiter
-    while True:
-        olditer = iter_
-        x, iter_, resid, info, ndx1, ndx2, sclr1, sclr2, ijob = \
-           revcom(b, x, work, iter_, resid, info, ndx1, ndx2, ijob)
-        if callback is not None and iter_ > olditer:
+    dotprod = np.vdot if np.iscomplexobj(x) else np.dot
+
+    rhotol = np.finfo(x.dtype.char).eps
+    betatol = rhotol
+    gammatol = rhotol
+    deltatol = rhotol
+    epsilontol = rhotol
+    xitol = rhotol
+
+    r = b - A.matvec(x) if x.any() else b.copy()
+
+    vtilde = r.copy()
+    y = M1.matvec(vtilde)
+    rho = np.linalg.norm(y)
+    wtilde = r.copy()
+    z = M2.rmatvec(wtilde)
+    xi = np.linalg.norm(z)
+    gamma, eta, theta = 1, -1, 0
+    v = np.empty_like(vtilde)
+    w = np.empty_like(wtilde)
+
+    # Dummy values to initialize vars, silence linter warnings
+    epsilon, q, d, p, s = None, None, None, None, None
+
+    for iteration in range(maxiter):
+        if np.linalg.norm(r) < atol:  # Are we done?
+            return postprocess(x), 0
+        if np.abs(rho) < rhotol:  # rho breakdown
+            return postprocess(x), -10
+        if np.abs(xi) < xitol:  # xi breakdown
+            return postprocess(x), -15
+
+        v[:] = vtilde[:]
+        v *= (1 / rho)
+        y *= (1 / rho)
+        w[:] = wtilde[:]
+        w *= (1 / xi)
+        z *= (1 / xi)
+        delta = dotprod(z, y)
+
+        if np.abs(delta) < deltatol:  # delta breakdown
+            return postprocess(x), -13
+
+        ytilde = M2.matvec(y)
+        ztilde = M1.rmatvec(z)
+
+        if iteration > 0:
+            ytilde -= (xi * delta / epsilon) * p
+            p[:] = ytilde[:]
+            ztilde -= (rho * (delta / epsilon).conj()) * q
+            q[:] = ztilde[:]
+        else:  # First spin
+            p = ytilde.copy()
+            q = ztilde.copy()
+
+        ptilde = A.matvec(p)
+        epsilon = dotprod(q, ptilde)
+        if np.abs(epsilon) < epsilontol:  # epsilon breakdown
+            return postprocess(x), -14
+
+        beta = epsilon / delta
+        if np.abs(beta) < betatol:  # beta breakdown
+            return postprocess(x), -11
+
+        vtilde[:] = ptilde[:]
+        vtilde -= beta*v
+        y = M1.matvec(vtilde)
+
+        rho_prev = rho
+        rho = np.linalg.norm(y)
+        wtilde[:] = w[:]
+        wtilde *= - beta.conj()
+        wtilde += A.rmatvec(q)
+        z = M2.rmatvec(wtilde)
+        xi = np.linalg.norm(z)
+        gamma_prev = gamma
+        theta_prev = theta
+        theta = rho / (gamma_prev * np.abs(beta))
+        gamma = 1 / np.sqrt(1 + theta**2)
+
+        if np.abs(gamma) < gammatol:  # gamma breakdown
+            return postprocess(x), -12
+
+        eta *= -(rho_prev / beta) * (gamma / gamma_prev)**2
+
+        if iteration > 0:
+            d *= (theta_prev * gamma) ** 2
+            d += eta*p
+            s *= (theta_prev * gamma) ** 2
+            s += eta*ptilde
+        else:
+            d = p.copy()
+            d *= eta
+            s = ptilde.copy()
+            s *= eta
+
+        x += d
+        r -= s
+
+        if callback:
             callback(x)
-        slice1 = slice(ndx1-1, ndx1-1+n)
-        slice2 = slice(ndx2-1, ndx2-1+n)
-        if (ijob == -1):
-            if callback is not None:
-                callback(x)
-            break
-        elif (ijob == 1):
-            work[slice2] *= sclr2
-            work[slice2] += sclr1*A.matvec(work[slice1])
-        elif (ijob == 2):
-            work[slice2] *= sclr2
-            work[slice2] += sclr1*A.rmatvec(work[slice1])
-        elif (ijob == 3):
-            work[slice1] = M1.matvec(work[slice2])
-        elif (ijob == 4):
-            work[slice1] = M2.matvec(work[slice2])
-        elif (ijob == 5):
-            work[slice1] = M1.rmatvec(work[slice2])
-        elif (ijob == 6):
-            work[slice1] = M2.rmatvec(work[slice2])
-        elif (ijob == 7):
-            work[slice2] *= sclr2
-            work[slice2] += sclr1*A.matvec(x)
-        elif (ijob == 8):
-            if ftflag:
-                info = -1
-                ftflag = False
-            resid, info = _stoptest(work[slice1], atol)
-        ijob = 2
-
-    if info > 0 and iter_ == maxiter and not (resid <= atol):
-        # info isn't set appropriately otherwise
-        info = iter_
 
-    return postprocess(x), info
+    else:  # for loop exhausted
+        # Return incomplete progress
+        return postprocess(x), maxiter
diff --git a/scipy/sparse/linalg/_isolve/setup.py b/scipy/sparse/linalg/_isolve/setup.py
--- a/scipy/sparse/linalg/_isolve/setup.py
+++ b/scipy/sparse/linalg/_isolve/setup.py
@@ -1,48 +1,8 @@
-from os.path import join
-
-
-def configuration(parent_package='',top_path=None):
-    from numpy.distutils.system_info import get_info
+def configuration(parent_package='', top_path=None):
     from numpy.distutils.misc_util import Configuration
-    from scipy._build_utils import (get_g77_abi_wrappers, uses_blas64,
-                                    blas_ilp64_pre_build_hook, get_f2py_int64_options)
-
-    config = Configuration('_isolve',parent_package,top_path)
-
-    if uses_blas64():
-        lapack_opt = get_info('lapack_ilp64_opt')
-        f2py_options = get_f2py_int64_options()
-        pre_build_hook = blas_ilp64_pre_build_hook(lapack_opt)
-    else:
-        lapack_opt = get_info('lapack_opt')
-        f2py_options = None
-        pre_build_hook = None
-
-    # iterative methods
-    methods = ['BiCGREVCOM.f.src',
-               'BiCGSTABREVCOM.f.src',
-               'CGREVCOM.f.src',
-               'CGSREVCOM.f.src',
-#               'ChebyREVCOM.f.src',
-               'GMRESREVCOM.f.src',
-#               'JacobiREVCOM.f.src',
-               'QMRREVCOM.f.src',
-#               'SORREVCOM.f.src'
-               ]
-
-    Util = ['getbreak.f.src']
-    sources = Util + methods + ['_iterative.pyf.src']
-    sources = [join('iterative', x) for x in sources]
-    sources += get_g77_abi_wrappers(lapack_opt)
-
-    ext = config.add_extension('_iterative',
-                               sources=sources,
-                               f2py_options=f2py_options,
-                               extra_info=lapack_opt)
-    ext._pre_build_hook = pre_build_hook
 
+    config = Configuration('_isolve', parent_package, top_path)
     config.add_data_dir('tests')
-
     return config
 
 


Test patch:
diff --git a/scipy/optimize/tests/test_nonlin.py b/scipy/optimize/tests/test_nonlin.py
--- a/scipy/optimize/tests/test_nonlin.py
+++ b/scipy/optimize/tests/test_nonlin.py
@@ -12,27 +12,30 @@
 
 from .test_minpack import pressure_network
 
-SOLVERS = {'anderson': nonlin.anderson, 'diagbroyden': nonlin.diagbroyden,
-           'linearmixing': nonlin.linearmixing, 'excitingmixing': nonlin.excitingmixing,
-           'broyden1': nonlin.broyden1, 'broyden2': nonlin.broyden2,
+SOLVERS = {'anderson': nonlin.anderson,
+           'diagbroyden': nonlin.diagbroyden,
+           'linearmixing': nonlin.linearmixing,
+           'excitingmixing': nonlin.excitingmixing,
+           'broyden1': nonlin.broyden1,
+           'broyden2': nonlin.broyden2,
            'krylov': nonlin.newton_krylov}
 MUST_WORK = {'anderson': nonlin.anderson, 'broyden1': nonlin.broyden1,
              'broyden2': nonlin.broyden2, 'krylov': nonlin.newton_krylov}
 
-#-------------------------------------------------------------------------------
+# ----------------------------------------------------------------------------
 # Test problems
-#-------------------------------------------------------------------------------
+# ----------------------------------------------------------------------------
 
 
 def F(x):
     x = np.asarray(x).T
-    d = diag([3,2,1.5,1,0.5])
+    d = diag([3, 2, 1.5, 1, 0.5])
     c = 0.01
     f = -d @ x - c * float(x.T @ x) * x
     return f
 
 
-F.xin = [1,1,1,1,1]
+F.xin = [1, 1, 1, 1, 1]
 F.KNOWN_BAD = {}
 F.JAC_KSP_BAD = {}
 F.ROOT_JAC_KSP_BAD = {}
@@ -42,7 +45,7 @@ def F2(x):
     return x
 
 
-F2.xin = [1,2,3,4,5,6]
+F2.xin = [1, 2, 3, 4, 5, 6]
 F2.KNOWN_BAD = {'linearmixing': nonlin.linearmixing,
                 'excitingmixing': nonlin.excitingmixing}
 F2.JAC_KSP_BAD = {}
@@ -53,7 +56,7 @@ def F2_lucky(x):
     return x
 
 
-F2_lucky.xin = [0,0,0,0,0,0]
+F2_lucky.xin = [0, 0, 0, 0, 0, 0]
 F2_lucky.KNOWN_BAD = {}
 F2_lucky.JAC_KSP_BAD = {}
 F2_lucky.ROOT_JAC_KSP_BAD = {}
@@ -65,7 +68,7 @@ def F3(x):
     return A @ x - b
 
 
-F3.xin = [1,2,3]
+F3.xin = [1, 2, 3]
 F3.KNOWN_BAD = {}
 F3.JAC_KSP_BAD = {}
 F3.ROOT_JAC_KSP_BAD = {}
@@ -105,7 +108,7 @@ def F5(x):
 def F6(x):
     x1, x2 = x
     J0 = np.array([[-4.256, 14.7],
-                [0.8394989, 0.59964207]])
+                   [0.8394989, 0.59964207]])
     v = np.array([(x1 + 3) * (x2**5 - 7) + 3*6,
                   np.sin(x2 * np.exp(x1) - 1)])
     return -np.linalg.solve(J0, v)
@@ -119,9 +122,9 @@ def F6(x):
 F6.ROOT_JAC_KSP_BAD = {}
 
 
-#-------------------------------------------------------------------------------
+# ----------------------------------------------------------------------------
 # Tests
-#-------------------------------------------------------------------------------
+# ----------------------------------------------------------------------------
 
 
 class TestNonlin:
@@ -168,6 +171,7 @@ def _check_root(self, f, method, f_tol=1e-2):
     def _check_func_fail(self, *a, **kw):
         pass
 
+    @pytest.mark.filterwarnings('ignore::DeprecationWarning')
     def test_problem_nonlin(self):
         for f in [F, F2, F2_lucky, F3, F4_powell, F5, F6]:
             for func in SOLVERS.values():
@@ -177,6 +181,7 @@ def test_problem_nonlin(self):
                     continue
                 self._check_nonlin_func(f, func)
 
+    @pytest.mark.filterwarnings('ignore::DeprecationWarning')
     @pytest.mark.parametrize("method", ['lgmres', 'gmres', 'bicgstab', 'cgs',
                                         'minres', 'tfqmr'])
     def test_tol_norm_called(self, method):
@@ -192,6 +197,7 @@ def local_norm_func(x):
                              tol_norm=local_norm_func)
         assert_(self._tol_norm_used)
 
+    @pytest.mark.filterwarnings('ignore::DeprecationWarning')
     def test_problem_root(self):
         for f in [F, F2, F2_lucky, F3, F4_powell, F5, F6]:
             for meth in SOLVERS:
@@ -205,14 +211,14 @@ def test_problem_root(self):
 class TestSecant:
     """Check that some Jacobian approximations satisfy the secant condition"""
 
-    xs = [np.array([1,2,3,4,5], float),
-          np.array([2,3,4,5,1], float),
-          np.array([3,4,5,1,2], float),
-          np.array([4,5,1,2,3], float),
-          np.array([9,1,9,1,3], float),
-          np.array([0,1,9,1,3], float),
-          np.array([5,5,7,1,1], float),
-          np.array([1,2,7,5,1], float),]
+    xs = [np.array([1., 2., 3., 4., 5.]),
+          np.array([2., 3., 4., 5., 1.]),
+          np.array([3., 4., 5., 1., 2.]),
+          np.array([4., 5., 1., 2., 3.]),
+          np.array([9., 1., 9., 1., 3.]),
+          np.array([0., 1., 9., 1., 3.]),
+          np.array([5., 5., 7., 1., 1.]),
+          np.array([1., 2., 7., 5., 1.]),]
     fs = [x**2 - 1 for x in xs]
 
     def _check_secant(self, jac_cls, npoints=1, **kw):
@@ -252,7 +258,7 @@ def test_broyden1_update(self):
         for last_j, (x, f) in enumerate(zip(self.xs[1:], self.fs[1:])):
             df = f - self.fs[last_j]
             dx = x - self.xs[last_j]
-            B += (df - dot(B, dx))[:,None] * dx[None,:] / dot(dx, dx)
+            B += (df - dot(B, dx))[:, None] * dx[None, :] / dot(dx, dx)
             jac.update(x, f)
             assert_(np.allclose(jac.todense(), B, rtol=1e-10, atol=1e-13))
 
@@ -266,7 +272,7 @@ def test_broyden2_update(self):
         for last_j, (x, f) in enumerate(zip(self.xs[1:], self.fs[1:])):
             df = f - self.fs[last_j]
             dx = x - self.xs[last_j]
-            H += (dx - dot(H, df))[:,None] * df[None,:] / dot(df, df)
+            H += (dx - dot(H, df))[:, None] * df[None, :] / dot(df, df)
             jac.update(x, f)
             assert_(np.allclose(jac.todense(), inv(H), rtol=1e-10, atol=1e-13))
 
@@ -321,7 +327,9 @@ def test_krylov(self):
 
 
 class TestJacobianDotSolve:
-    """Check that solve/dot methods in Jacobian approximations are consistent"""
+    """
+    Check that solve/dot methods in Jacobian approximations are consistent
+    """
 
     def _func(self, x):
         return x**2 - 1 + np.dot(self.A, x)
@@ -422,31 +430,31 @@ class TestNonlinOldTests:
     """
 
     def test_broyden1(self):
-        x = nonlin.broyden1(F,F.xin,iter=12,alpha=1)
+        x = nonlin.broyden1(F, F.xin, iter=12, alpha=1)
         assert_(nonlin.norm(x) < 1e-9)
         assert_(nonlin.norm(F(x)) < 1e-9)
 
     def test_broyden2(self):
-        x = nonlin.broyden2(F,F.xin,iter=12,alpha=1)
+        x = nonlin.broyden2(F, F.xin, iter=12, alpha=1)
         assert_(nonlin.norm(x) < 1e-9)
         assert_(nonlin.norm(F(x)) < 1e-9)
 
     def test_anderson(self):
-        x = nonlin.anderson(F,F.xin,iter=12,alpha=0.03,M=5)
+        x = nonlin.anderson(F, F.xin, iter=12, alpha=0.03, M=5)
         assert_(nonlin.norm(x) < 0.33)
 
     def test_linearmixing(self):
-        x = nonlin.linearmixing(F,F.xin,iter=60,alpha=0.5)
+        x = nonlin.linearmixing(F, F.xin, iter=60, alpha=0.5)
         assert_(nonlin.norm(x) < 1e-7)
         assert_(nonlin.norm(F(x)) < 1e-7)
 
     def test_exciting(self):
-        x = nonlin.excitingmixing(F,F.xin,iter=20,alpha=0.5)
+        x = nonlin.excitingmixing(F, F.xin, iter=20, alpha=0.5)
         assert_(nonlin.norm(x) < 1e-5)
         assert_(nonlin.norm(F(x)) < 1e-5)
 
     def test_diagbroyden(self):
-        x = nonlin.diagbroyden(F,F.xin,iter=11,alpha=1)
+        x = nonlin.diagbroyden(F, F.xin, iter=11, alpha=1)
         assert_(nonlin.norm(x) < 1e-8)
         assert_(nonlin.norm(F(x)) < 1e-8)
 
diff --git a/scipy/sparse/linalg/_isolve/tests/test_iterative.py b/scipy/sparse/linalg/_isolve/tests/test_iterative.py
--- a/scipy/sparse/linalg/_isolve/tests/test_iterative.py
+++ b/scipy/sparse/linalg/_isolve/tests/test_iterative.py
@@ -4,16 +4,14 @@
 import itertools
 import platform
 import sys
-import numpy as np
-
-from numpy.testing import (assert_array_equal, assert_allclose,
-                           suppress_warnings)
 import pytest
 
-
+import numpy as np
+from numpy.testing import assert_array_equal, assert_allclose
 from numpy import zeros, arange, array, ones, eye, iscomplexobj
-from scipy.linalg import norm
-from scipy.sparse import spdiags, csr_matrix, SparseEfficiencyWarning, kronsum
+from numpy.linalg import norm
+
+from scipy.sparse import spdiags, csr_matrix, kronsum
 
 from scipy.sparse.linalg import LinearOperator, aslinearoperator
 from scipy.sparse.linalg._isolve import (bicg, bicgstab, cg, cgs,
@@ -27,6 +25,13 @@
 # list of all solvers under test
 _SOLVERS = [bicg, bicgstab, cg, cgs, gcrotmk, gmres, lgmres,
             minres, qmr, tfqmr]
+
+pytestmark = [
+    pytest.mark.filterwarnings("ignore:.* keyword argument 'tol'.*"),
+    pytest.mark.filterwarnings("ignore:.*called without specifying.*")
+    ]
+
+
 # create parametrized fixture for easy reuse in tests
 @pytest.fixture(params=_SOLVERS, scope="session")
 def solver(request):
@@ -53,6 +58,15 @@ def __init__(self, name, A, b=None, skip=None, nonconvergence=None):
         else:
             self.nonconvergence = nonconvergence
 
+
+class SingleTest:
+    def __init__(self, A, b, solver, casename, convergence=True):
+        self.A = A
+        self.b = b
+        self.solver = solver
+        self.name = casename + '-' + solver.__name__
+        self.convergence = convergence
+
     def __repr__(self):
         return f"<{self.name}>"
 
@@ -184,9 +198,25 @@ def __init__(self):
                                )
                           )
 
+    def generate_tests(self):
+        # generate test cases with skips applied
+        tests = []
+        for case in self.cases:
+            for solver in _SOLVERS:
+                if (solver in case.skip):
+                    continue
+                if solver in case.nonconvergence:
+                    tests += [SingleTest(case.A, case.b, solver, case.name,
+                                         convergence=False)]
+                else:
+                    tests += [SingleTest(case.A, case.b, solver, case.name)]
+        return tests
+
+
+cases = IterativeParams().generate_tests()
 
-cases = IterativeParams().cases
-@pytest.fixture(params=cases, ids=[x.name for x in cases], scope="session")
+
+@pytest.fixture(params=cases, ids=[x.name for x in cases], scope="module")
 def case(request):
     """
     Fixture for all cases in IterativeParams
@@ -194,7 +224,9 @@ def case(request):
     return request.param
 
 
-def check_maxiter(solver, case):
+def test_maxiter(case):
+    if not case.convergence:
+        pytest.skip("Solver - Breakdown case, see gh-8829")
     A = case.A
     tol = 1e-12
 
@@ -206,27 +238,19 @@ def check_maxiter(solver, case):
     def callback(x):
         residuals.append(norm(b - case.A * x))
 
-    x, info = solver(A, b, x0=x0, tol=tol, maxiter=1, callback=callback)
+    x, info = case.solver(A, b, x0=x0, tol=tol, maxiter=1, callback=callback)
 
     assert len(residuals) == 1
     assert info == 1
 
 
-def test_maxiter(solver, case):
-    if solver in case.skip + case.nonconvergence:
-        pytest.skip("unsupported combination")
-    with suppress_warnings() as sup:
-        sup.filter(DeprecationWarning, ".*called without specifying.*")
-        check_maxiter(solver, case)
-
-
 def assert_normclose(a, b, tol=1e-8):
     residual = norm(a - b)
     tolerance = tol * norm(b)
     assert residual < tolerance
 
 
-def check_convergence(solver, case):
+def test_convergence(case):
     A = case.A
 
     if A.dtype.char in "dD":
@@ -237,26 +261,21 @@ def check_convergence(solver, case):
     b = case.b
     x0 = 0 * b
 
-    x, info = solver(A, b, x0=x0, tol=tol)
+    x, info = case.solver(A, b, x0=x0, tol=tol)
 
     assert_array_equal(x0, 0 * b)  # ensure that x0 is not overwritten
-    if solver not in case.nonconvergence:
+    if case.convergence:
         assert info == 0
-        assert_normclose(A @ x, b, tol=tol)
+        assert norm(A @ x - b) <= norm(b)*tol
     else:
         assert info != 0
-        assert np.linalg.norm(A @ x - b) <= np.linalg.norm(b)
-
+        assert norm(A @ x - b) <= norm(b)
 
-def test_convergence(solver, case):
-    if solver in case.skip:
-        pytest.skip("unsupported combination")
-    with suppress_warnings() as sup:
-        sup.filter(DeprecationWarning, ".*called without specifying.*")
-        check_convergence(solver, case)
 
+def test_precond_dummy(case):
+    if not case.convergence:
+        pytest.skip("Solver - Breakdown case, see gh-8829")
 
-def check_precond_dummy(solver, case):
     tol = 1e-8
 
     def identity(b, which=None):
@@ -277,124 +296,99 @@ def identity(b, which=None):
 
     precond = LinearOperator(A.shape, identity, rmatvec=identity)
 
-    if solver is qmr:
-        x, info = solver(A, b, M1=precond, M2=precond, x0=x0, tol=tol)
+    if case.solver is qmr:
+        x, info = case.solver(A, b, M1=precond, M2=precond, x0=x0, tol=tol)
     else:
-        x, info = solver(A, b, M=precond, x0=x0, tol=tol)
+        x, info = case.solver(A, b, M=precond, x0=x0, tol=tol)
     assert info == 0
-    assert_normclose(A @ x, b, tol)
+    assert norm(A @ x - b) <= norm(b)*tol
 
     A = aslinearoperator(A)
     A.psolve = identity
     A.rpsolve = identity
 
-    x, info = solver(A, b, x0=x0, tol=tol)
+    x, info = case.solver(A, b, x0=x0, tol=tol)
     assert info == 0
-    assert_normclose(A @ x, b, tol=tol)
-
-
-def test_precond_dummy(solver, case):
-    if solver in case.skip + case.nonconvergence:
-        pytest.skip("unsupported combination")
-    with suppress_warnings() as sup:
-        sup.filter(DeprecationWarning, ".*called without specifying.*")
-        check_precond_dummy(solver, case)
-
-
-def check_precond_inverse(solver, case):
-    tol = 1e-8
-
-    def inverse(b, which=None):
-        """inverse preconditioner"""
-        A = case.A
-        if not isinstance(A, np.ndarray):
-            A = A.toarray()
-        return np.linalg.solve(A, b)
-
-    def rinverse(b, which=None):
-        """inverse preconditioner"""
-        A = case.A
-        if not isinstance(A, np.ndarray):
-            A = A.toarray()
-        return np.linalg.solve(A.T, b)
-
-    matvec_count = [0]
+    assert norm(A @ x - b) <= norm(b)*tol
 
-    def matvec(b):
-        matvec_count[0] += 1
-        return case.A @ b
 
-    def rmatvec(b):
-        matvec_count[0] += 1
-        return case.A.T @ b
-
-    b = case.b
-    x0 = 0 * b
+# Specific test for poisson1d and poisson2d cases
+@pytest.mark.parametrize('case', [x for x in IterativeParams().cases
+                                  if x.name in ('poisson1d', 'poisson2d')],
+                         ids=['poisson1d', 'poisson2d'])
+def test_precond_inverse(case):
+    for solver in _SOLVERS:
+        if solver in case.skip or solver is qmr:
+            continue
 
-    A = LinearOperator(case.A.shape, matvec, rmatvec=rmatvec)
-    precond = LinearOperator(case.A.shape, inverse, rmatvec=rinverse)
+        tol = 1e-8
 
-    # Solve with preconditioner
-    matvec_count = [0]
-    x, info = solver(A, b, M=precond, x0=x0, tol=tol)
+        def inverse(b, which=None):
+            """inverse preconditioner"""
+            A = case.A
+            if not isinstance(A, np.ndarray):
+                A = A.toarray()
+            return np.linalg.solve(A, b)
 
-    assert info == 0
-    assert_normclose(case.A @ x, b, tol)
+        def rinverse(b, which=None):
+            """inverse preconditioner"""
+            A = case.A
+            if not isinstance(A, np.ndarray):
+                A = A.toarray()
+            return np.linalg.solve(A.T, b)
 
-    # Solution should be nearly instant
-    assert matvec_count[0] <= 3
+        matvec_count = [0]
 
+        def matvec(b):
+            matvec_count[0] += 1
+            return case.A @ b
 
-def test_precond_inverse(solver, case):
-    if (solver in case.skip or solver is qmr
-            or case.name not in ("poisson1d", "poisson2d")):
-        pytest.skip("unsupported combination")
-    with suppress_warnings() as sup:
-        sup.filter(DeprecationWarning, ".*called without specifying.*")
-        check_precond_inverse(solver, case)
+        def rmatvec(b):
+            matvec_count[0] += 1
+            return case.A.T @ b
 
+        b = case.b
+        x0 = 0 * b
 
-def test_reentrancy(solver):
-    reentrant = [lgmres, minres, gcrotmk, tfqmr]
-    with suppress_warnings() as sup:
-        sup.filter(DeprecationWarning, ".*called without specifying.*")
-        _check_reentrancy(solver, solver in reentrant)
+        A = LinearOperator(case.A.shape, matvec, rmatvec=rmatvec)
+        precond = LinearOperator(case.A.shape, inverse, rmatvec=rinverse)
 
+        # Solve with preconditioner
+        matvec_count = [0]
+        x, info = solver(A, b, M=precond, x0=x0, tol=tol)
 
-def _check_reentrancy(solver, is_reentrant):
-    def matvec(x):
-        A = np.array([[1.0, 0, 0], [0, 2.0, 0], [0, 0, 3.0]])
-        y, info = solver(A, x)
         assert info == 0
-        return y
-    b = np.array([1, 1. / 2, 1. / 3])
-    op = LinearOperator((3, 3), matvec=matvec, rmatvec=matvec,
-                        dtype=b.dtype)
+        assert norm(case.A @ x - b) <= norm(b)*tol
 
-    if not is_reentrant:
-        pytest.raises(RuntimeError, solver, op, b)
-    else:
-        y, info = solver(op, b)
-        assert info == 0
-        assert_allclose(y, [1, 1, 1])
+        # Solution should be nearly instant
+        assert matvec_count[0] <= 3
 
 
 def test_atol(solver):
     # TODO: minres / tfqmr. It didn't historically use absolute tolerances, so
     # fixing it is less urgent.
     if solver in (minres, tfqmr):
-        pytest.skip("TODO")
+        pytest.skip("TODO: Add atol to minres/tfqmr")
+
+    # Historically this is tested as below, all pass but for some reason
+    # gcrotmk is over-sensitive to difference between random.seed/rng.random
+    # Hence tol lower bound is changed from -10 to -9
+    # np.random.seed(1234)
+    # A = np.random.rand(10, 10)
+    # A = A @ A.T + 10 * np.eye(10)
+    # b = 1e3*np.random.rand(10)
+
+    rng = np.random.default_rng(168441431005389)
+    A = rng.uniform(size=[10, 10])
+    A = A @ A.T + 10*np.eye(10)
+    b = 1e3 * rng.uniform(size=10)
 
-    np.random.seed(1234)
-    A = np.random.rand(10, 10)
-    A = A @ A.T + 10 * np.eye(10)
-    b = 1e3 * np.random.rand(10)
     b_norm = np.linalg.norm(b)
 
-    tols = np.r_[0, np.logspace(np.log10(1e-10), np.log10(1e2), 7), np.inf]
+    tols = np.r_[0, np.logspace(-9, 2, 7), np.inf]
 
     # Check effect of badly scaled preconditioners
-    M0 = np.random.randn(10, 10)
+    M0 = rng.standard_normal(size=(10, 10))
     M0 = M0 @ M0.T
     Ms = [None, 1e-6 * M0, 1e6 * M0]
 
@@ -422,43 +416,40 @@ def test_atol(solver):
 
 
 def test_zero_rhs(solver):
-    np.random.seed(1234)
-    A = np.random.rand(10, 10)
+    rng = np.random.default_rng(1684414984100503)
+    A = rng.random(size=[10, 10])
     A = A @ A.T + 10 * np.eye(10)
 
     b = np.zeros(10)
-    tols = np.r_[np.logspace(np.log10(1e-10), np.log10(1e2), 7)]
+    tols = np.r_[np.logspace(-10, 2, 7)]
 
     for tol in tols:
-        with suppress_warnings() as sup:
-            sup.filter(DeprecationWarning, ".*called without specifying.*")
-
-            x, info = solver(A, b, tol=tol)
-            assert info == 0
-            assert_allclose(x, 0., atol=1e-15)
+        x, info = solver(A, b, tol=tol)
+        assert info == 0
+        assert_allclose(x, 0., atol=1e-15)
 
-            x, info = solver(A, b, tol=tol, x0=ones(10))
-            assert info == 0
-            assert_allclose(x, 0., atol=tol)
+        x, info = solver(A, b, tol=tol, x0=ones(10))
+        assert info == 0
+        assert_allclose(x, 0., atol=tol)
 
-            if solver is not minres:
-                x, info = solver(A, b, tol=tol, atol=0, x0=ones(10))
-                if info == 0:
-                    assert_allclose(x, 0)
+        if solver is not minres:
+            x, info = solver(A, b, tol=tol, atol=0, x0=ones(10))
+            if info == 0:
+                assert_allclose(x, 0)
 
-                x, info = solver(A, b, tol=tol, atol=tol)
-                assert info == 0
-                assert_allclose(x, 0, atol=1e-300)
+            x, info = solver(A, b, tol=tol, atol=tol)
+            assert info == 0
+            assert_allclose(x, 0, atol=1e-300)
 
-                x, info = solver(A, b, tol=tol, atol=0)
-                assert info == 0
-                assert_allclose(x, 0, atol=1e-300)
+            x, info = solver(A, b, tol=tol, atol=0)
+            assert info == 0
+            assert_allclose(x, 0, atol=1e-300)
 
 
 def test_maxiter_worsening(solver):
-    if solver not in (gmres, lgmres):
+    if solver not in (gmres, lgmres, qmr):
         # these were skipped from the very beginning, see gh-9201; gh-14160
-        pytest.skip("unsupported combination")
+        pytest.skip("Solver breakdown case")
     # Check error does not grow (boundlessly) with increasing maxiter.
     # This can occur due to the solvers hitting close to breakdown,
     # which they should detect and halt as necessary.
@@ -478,29 +469,34 @@ def test_maxiter_worsening(solver):
                   [0.1112795288033368, 0j, 0j, -0.16127952880333785]])
     v = np.ones(4)
     best_error = np.inf
-    tol = 7 if platform.machine() == 'aarch64' else 5
+
+    # Unable to match the Fortran code tolerance levels with this example
+    # Original tolerance values
+
+    # slack_tol = 7 if platform.machine() == 'aarch64' else 5
+    slack_tol = 9
 
     for maxiter in range(1, 20):
         x, info = solver(A, v, maxiter=maxiter, tol=1e-8, atol=0)
 
         if info == 0:
-            assert np.linalg.norm(A @ x - v) <= 1e-8 * np.linalg.norm(v)
+            assert norm(A @ x - v) <= 1e-8 * norm(v)
 
         error = np.linalg.norm(A @ x - v)
         best_error = min(best_error, error)
 
         # Check with slack
-        assert error <= tol * best_error
+        assert error <= slack_tol * best_error
 
 
 def test_x0_working(solver):
     # Easy problem
-    np.random.seed(1)
+    rng = np.random.default_rng(1685363802304750)
     n = 10
-    A = np.random.rand(n, n)
+    A = rng.random(size=[n, n])
     A = A @ A.T
-    b = np.random.rand(n)
-    x0 = np.random.rand(n)
+    b = rng.random(n)
+    x0 = rng.random(n)
 
     if solver is minres:
         kw = dict(tol=1e-6)
@@ -509,29 +505,29 @@ def test_x0_working(solver):
 
     x, info = solver(A, b, **kw)
     assert info == 0
-    assert np.linalg.norm(A @ x - b) <= 1e-6 * np.linalg.norm(b)
+    assert norm(A @ x - b) <= 1e-6 * norm(b)
 
     x, info = solver(A, b, x0=x0, **kw)
     assert info == 0
-    assert np.linalg.norm(A @ x - b) <= 2e-6 * np.linalg.norm(b)
+    assert norm(A @ x - b) <= 2e-6*norm(b)
 
 
-def test_x0_equals_Mb(solver, case):
-    if solver in case.skip or solver is tfqmr:
-        pytest.skip("unsupported combination")
-    with suppress_warnings() as sup:
-        sup.filter(DeprecationWarning, ".*called without specifying.*")
-        A = case.A
-        b = case.b
-        x0 = 'Mb'
-        tol = 1e-8
-        x, info = solver(A, b, x0=x0, tol=tol)
+def test_x0_equals_Mb(case):
+    if case.solver is tfqmr:
+        pytest.skip("Solver does not support x0='Mb'")
+    A = case.A
+    b = case.b
+    x0 = 'Mb'
+    tol = 1e-8
+    x, info = case.solver(A, b, x0=x0, tol=tol)
 
-        assert_array_equal(x0, 'Mb')  # ensure that x0 is not overwritten
-        assert info == 0
-        assert_normclose(A @ x, b, tol=tol)
+    assert_array_equal(x0, 'Mb')  # ensure that x0 is not overwritten
+    assert info == 0
+    assert norm(A @ x - b) <= tol*norm(b)
 
 
+# Specific tfqmr test
+@pytest.mark.parametrize('case', IterativeParams().cases)
 def test_show(case, capsys):
     def cb(x):
         pass
@@ -552,9 +548,8 @@ def cb(x):
     assert err == ""
 
 
-# -----------------------------------------------------------------------------
-
 class TestQMR:
+    @pytest.mark.filterwarnings('ignore::scipy.sparse.SparseEfficiencyWarning')
     def test_leftright_precond(self):
         """Check that QMR works with left and right preconditioners"""
 
@@ -569,12 +564,8 @@ def test_leftright_precond(self):
 
         L = spdiags([-dat / 2, dat], [-1, 0], n, n)
         U = spdiags([4 * dat, -dat], [0, 1], n, n)
-
-        with suppress_warnings() as sup:
-            sup.filter(SparseEfficiencyWarning,
-                       "splu converted its input to CSC format")
-            L_solver = splu(L)
-            U_solver = splu(U)
+        L_solver = splu(L)
+        U_solver = splu(U)
 
         def L_solve(b):
             return L_solver.solve(b)
@@ -591,9 +582,7 @@ def UT_solve(b):
         M1 = LinearOperator((n, n), matvec=L_solve, rmatvec=LT_solve)
         M2 = LinearOperator((n, n), matvec=U_solve, rmatvec=UT_solve)
 
-        with suppress_warnings() as sup:
-            sup.filter(DeprecationWarning, ".*called without specifying.*")
-            x, info = qmr(A, b, tol=1e-8, maxiter=15, M1=M1, M2=M2)
+        x, info = qmr(A, b, tol=1e-8, maxiter=15, M1=M1, M2=M2)
 
         assert info == 0
         assert_normclose(A @ x, b, tol=1e-8)
@@ -605,9 +594,7 @@ def test_basic(self):
         b = np.zeros(10)
         b[0] = 1
 
-        with suppress_warnings() as sup:
-            sup.filter(DeprecationWarning, ".*called without specifying.*")
-            x_gm, err = gmres(A, b, restart=5, maxiter=1)
+        x_gm, err = gmres(A, b, restart=5, maxiter=1)
 
         assert_allclose(x_gm[0], 0.359, rtol=1e-2)
 
@@ -631,10 +618,8 @@ def store_residual(r, rvec):
         def callback(r):
             return store_residual(r, rvec)
 
-        with suppress_warnings() as sup:
-            sup.filter(DeprecationWarning, ".*called without specifying.*")
-            x, flag = gmres(A, b, x0=zeros(A.shape[0]), tol=1e-16,
-                            maxiter=maxiter, callback=callback)
+        x, flag = gmres(A, b, x0=zeros(A.shape[0]), tol=1e-16,
+                        maxiter=maxiter, callback=callback)
 
         # Expected output from SciPy 1.0.0
         assert_allclose(rvec, array([1.0, 0.81649658092772603]), rtol=1e-10)
@@ -643,10 +628,8 @@ def callback(r):
         M = 1e-3 * np.eye(A.shape[0])
         rvec = zeros(maxiter + 1)
         rvec[0] = 1.0
-        with suppress_warnings() as sup:
-            sup.filter(DeprecationWarning, ".*called without specifying.*")
-            x, flag = gmres(A, b, M=M, tol=1e-16, maxiter=maxiter,
-                            callback=callback)
+        x, flag = gmres(A, b, M=M, tol=1e-16, maxiter=maxiter,
+                        callback=callback)
 
         # Expected output from SciPy 1.0.0
         # (callback has preconditioned residual!)
@@ -657,39 +640,27 @@ def test_abi(self):
         # Check we don't segfault on gmres with complex argument
         A = eye(2)
         b = ones(2)
-        with suppress_warnings() as sup:
-            sup.filter(DeprecationWarning, ".*called without specifying.*")
-            r_x, r_info = gmres(A, b)
-            r_x = r_x.astype(complex)
-
-            x, info = gmres(A.astype(complex), b.astype(complex))
+        r_x, r_info = gmres(A, b)
+        r_x = r_x.astype(complex)
+        x, info = gmres(A.astype(complex), b.astype(complex))
 
         assert iscomplexobj(x)
         assert_allclose(r_x, x)
         assert r_info == info
 
     def test_atol_legacy(self):
-        with suppress_warnings() as sup:
-            sup.filter(DeprecationWarning, ".*called without specifying.*")
-
-            # Check the strange legacy behavior: the tolerance is interpreted
-            # as atol, but only for the initial residual
-            A = eye(2)
-            b = 1e-6 * ones(2)
-            x, info = gmres(A, b, tol=1e-5)
-            assert_array_equal(x, np.zeros(2))
-
-            A = eye(2)
-            b = ones(2)
-            x, info = gmres(A, b, tol=1e-5)
-            assert np.linalg.norm(A @ x - b) <= 1e-5 * np.linalg.norm(b)
-            assert_allclose(x, b, atol=0, rtol=1e-8)
-
-            rndm = np.random.RandomState(12345)
-            A = rndm.rand(30, 30)
-            b = 1e-6 * ones(30)
-            x, info = gmres(A, b, tol=1e-7, restart=20)
-            assert np.linalg.norm(A @ x - b) > 1e-7
+
+        A = eye(2)
+        b = ones(2)
+        x, info = gmres(A, b, tol=1e-5)
+        assert np.linalg.norm(A @ x - b) <= 1e-5 * np.linalg.norm(b)
+        assert_allclose(x, b, atol=0, rtol=1e-8)
+
+        rndm = np.random.RandomState(12345)
+        A = rndm.rand(30, 30)
+        b = 1e-6 * ones(30)
+        x, info = gmres(A, b, tol=1e-7, restart=20)
+        assert np.linalg.norm(A @ x - b) > 1e-7
 
         A = eye(2)
         b = 1e-10 * ones(2)
@@ -745,14 +716,12 @@ def x_cb(x):
             cb_count[0] += 1
             assert isinstance(x, np.ndarray)
 
-        with suppress_warnings() as sup:
-            sup.filter(DeprecationWarning, ".*called without specifying.*")
-            # 2 iterations is not enough to solve the problem
-            cb_count = [0]
-            x, info = gmres(A, b, tol=1e-6, atol=0, callback=pr_norm_cb, maxiter=2,
-                            restart=50)
-            assert info == 2
-            assert cb_count[0] == 2
+        # 2 iterations is not enough to solve the problem
+        cb_count = [0]
+        x, info = gmres(A, b, tol=1e-6, atol=0, callback=pr_norm_cb,
+                        maxiter=2, restart=50)
+        assert info == 2
+        assert cb_count[0] == 2
 
         # With `callback_type` specified, no warning should be raised
         cb_count = [0]
@@ -773,7 +742,7 @@ def x_cb(x):
         x, info = gmres(A, b, tol=1e-6, atol=0, callback=x_cb, maxiter=2,
                         restart=50, callback_type='x')
         assert info == 0
-        assert cb_count[0] == 2
+        assert cb_count[0] == 1
 
     def test_callback_x_monotonic(self):
         # Check that callback_type='x' gives monotonic norm decrease
@@ -790,11 +759,10 @@ def x_cb(x):
             prev_r[0] = r
             count[0] += 1
 
-        x, info = gmres(A, b, tol=1e-6, atol=0, callback=x_cb, maxiter=20, restart=10,
-                        callback_type='x')
+        x, info = gmres(A, b, tol=1e-6, atol=0, callback=x_cb, maxiter=20,
+                        restart=10, callback_type='x')
         assert info == 20
-        assert count[0] == 21
-        x_cb(x)
+        assert count[0] == 20
 
     def test_restrt_dep(self):
         with pytest.warns(