Repository: scipy/scipy

Issue:
DEP: deprecate positional arguments in `sparse.gmres`
~Follow-up of #16392, informally [deprecated](https://github.com/scipy/scipy/commit/87c9baafaae3435ebe95571fc22b9c9141231255) since 0.11~ delayed in #18624 / #18723, now should follow #18703


Discussion:
This should probably wait for #18488

Closing as no longer actionable for this release


We still need to deprecate something after #18624 (see #18703), so I'm repurposing this issue to avoid creating a new one.

Resolving patch:
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
@@ -3,7 +3,7 @@
 from scipy.sparse.linalg._interface import LinearOperator
 from .utils import make_system
 from scipy.linalg import get_lapack_funcs
-from scipy._lib.deprecation import _NoValue
+from scipy._lib.deprecation import _NoValue, _deprecate_positional_args
 
 __all__ = ['bicg', 'bicgstab', 'cg', 'cgs', 'gmres', 'qmr']
 
@@ -32,7 +32,8 @@ def _get_atol(name, b, tol=_NoValue, atol=0., rtol=1e-5):
     return atol
 
 
-def bicg(A, b, x0=None, tol=_NoValue, maxiter=None, M=None, callback=None,
+@_deprecate_positional_args(version="1.14")
+def bicg(A, b, *, x0=None, tol=_NoValue, maxiter=None, M=None, callback=None,
          atol=0., rtol=1e-5):
     """Use BIConjugate Gradient iteration to solve ``Ax = b``.
 
@@ -160,8 +161,9 @@ def bicg(A, b, x0=None, tol=_NoValue, maxiter=None, M=None, callback=None,
         return postprocess(x), maxiter
 
 
-def bicgstab(A, b, x0=None, tol=_NoValue, maxiter=None, M=None, callback=None,
-             atol=0., rtol=1e-5):
+@_deprecate_positional_args(version="1.14")
+def bicgstab(A, b, *, x0=None, tol=_NoValue, maxiter=None, M=None,
+             callback=None, atol=0., rtol=1e-5):
     """Use BIConjugate Gradient STABilized iteration to solve ``Ax = b``.
 
     Parameters
@@ -302,7 +304,8 @@ def bicgstab(A, b, x0=None, tol=_NoValue, maxiter=None, M=None, callback=None,
         return postprocess(x), maxiter
 
 
-def cg(A, b, x0=None, tol=_NoValue, maxiter=None, M=None, callback=None,
+@_deprecate_positional_args(version="1.14")
+def cg(A, b, *, x0=None, tol=_NoValue, maxiter=None, M=None, callback=None,
        atol=0., rtol=1e-5):
     """Use Conjugate Gradient iteration to solve ``Ax = b``.
 
@@ -416,7 +419,8 @@ def cg(A, b, x0=None, tol=_NoValue, maxiter=None, M=None, callback=None,
         return postprocess(x), maxiter
 
 
-def cgs(A, b, x0=None, tol=_NoValue, maxiter=None, M=None, callback=None,
+@_deprecate_positional_args(version="1.14")
+def cgs(A, b, *, x0=None, tol=_NoValue, maxiter=None, M=None, callback=None,
         atol=0., rtol=1e-5):
     """Use Conjugate Gradient Squared iteration to solve ``Ax = b``.
 
@@ -568,7 +572,8 @@ def cgs(A, b, x0=None, tol=_NoValue, maxiter=None, M=None, callback=None,
         return postprocess(x), maxiter
 
 
-def gmres(A, b, x0=None, tol=_NoValue, restart=None, maxiter=None, M=None,
+@_deprecate_positional_args(version="1.14")
+def gmres(A, b, *, x0=None, tol=_NoValue, restart=None, maxiter=None, M=None,
           callback=None, restrt=_NoValue, atol=0., callback_type=None,
           rtol=1e-5):
     """
@@ -854,7 +859,8 @@ def gmres(A, b, x0=None, tol=_NoValue, restart=None, maxiter=None, M=None,
     return postprocess(x), info
 
 
-def qmr(A, b, x0=None, tol=_NoValue, maxiter=None, M1=None, M2=None,
+@_deprecate_positional_args(version="1.14")
+def qmr(A, b, *, x0=None, tol=_NoValue, maxiter=None, M1=None, M2=None,
         callback=None, atol=0., rtol=1e-5):
     """Use Quasi-Minimal Residual iteration to solve ``Ax = b``.
 


Test patch:
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
@@ -549,6 +549,21 @@ def cb(x):
     assert err == ""
 
 
+def test_positional_deprecation(solver):
+    if solver in (gcrotmk, lgmres, minres, tfqmr):
+        pytest.skip("positional arguments not yet deprecated")
+
+    # from test_x0_working
+    rng = np.random.default_rng(1685363802304750)
+    n = 10
+    A = rng.random(size=[n, n])
+    A = A @ A.T
+    b = rng.random(n)
+    x0 = rng.random(n)
+    with pytest.deprecated_call(match="use keyword arguments"):
+        solver(A, b, x0)
+
+
 class TestQMR:
     @pytest.mark.filterwarnings('ignore::scipy.sparse.SparseEfficiencyWarning')
     def test_leftright_precond(self):