Repository: scipy/scipy

Issue:
DEP: deprecate positional arguments in `firwin*`
~Follow-up of #16253 & #16315~ delayed in #18624, now should follow #18703


Discussion:

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/signal/_fir_filter_design.py b/scipy/signal/_fir_filter_design.py
--- a/scipy/signal/_fir_filter_design.py
+++ b/scipy/signal/_fir_filter_design.py
@@ -9,7 +9,7 @@
 from scipy.special import sinc
 from scipy.linalg import (toeplitz, hankel, solve, LinAlgError, LinAlgWarning,
                           lstsq)
-from scipy._lib.deprecation import _NoValue
+from scipy._lib.deprecation import _NoValue, _deprecate_positional_args
 
 from . import _sigtools
 
@@ -266,7 +266,8 @@ def kaiserord(ripple, width):
     return int(ceil(numtaps)), beta
 
 
-def firwin(numtaps, cutoff, width=None, window='hamming', pass_zero=True,
+@_deprecate_positional_args(version="1.14")
+def firwin(numtaps, cutoff, *, width=None, window='hamming', pass_zero=True,
            scale=True, nyq=_NoValue, fs=None):
     """
     FIR filter design using the window method.
@@ -493,8 +494,8 @@ def firwin(numtaps, cutoff, width=None, window='hamming', pass_zero=True,
 # Original version of firwin2 from scipy ticket #457, submitted by "tash".
 #
 # Rewritten by Warren Weckesser, 2010.
-
-def firwin2(numtaps, freq, gain, nfreqs=None, window='hamming', nyq=_NoValue,
+@_deprecate_positional_args(version="1.14")
+def firwin2(numtaps, freq, gain, *, nfreqs=None, window='hamming', nyq=_NoValue,
             antisymmetric=False, fs=None):
     """
     FIR filter design using the window method.
@@ -696,7 +697,8 @@ def firwin2(numtaps, freq, gain, nfreqs=None, window='hamming', nyq=_NoValue,
     return out
 
 
-def remez(numtaps, bands, desired, weight=None, Hz=_NoValue, type='bandpass',
+@_deprecate_positional_args(version="1.14")
+def remez(numtaps, bands, desired, *, weight=None, Hz=_NoValue, type='bandpass',
           maxiter=25, grid_density=16, fs=None):
     """
     Calculate the minimax optimal filter using the Remez exchange algorithm.
@@ -860,7 +862,7 @@ def remez(numtaps, bands, desired, weight=None, Hz=_NoValue, type='bandpass',
         if fs is not None:
             raise ValueError("Values cannot be given for both 'Hz' and 'fs'.")
         msg = ("'remez' keyword argument 'Hz' is deprecated in favour of 'fs'"
-               " and will be removed in SciPy 1.12.0.")
+               " and will be removed in SciPy 1.14.0.")
         warnings.warn(msg, DeprecationWarning, stacklevel=2)
         fs = Hz
 
@@ -880,7 +882,8 @@ def remez(numtaps, bands, desired, weight=None, Hz=_NoValue, type='bandpass',
                             maxiter, grid_density)
 
 
-def firls(numtaps, bands, desired, weight=None, nyq=_NoValue, fs=None):
+@_deprecate_positional_args(version="1.14")
+def firls(numtaps, bands, desired, *, weight=None, nyq=_NoValue, fs=None):
     """
     FIR filter design using least-squares error minimization.
 


Test patch:
diff --git a/scipy/signal/tests/test_fir_filter_design.py b/scipy/signal/tests/test_fir_filter_design.py
--- a/scipy/signal/tests/test_fir_filter_design.py
+++ b/scipy/signal/tests/test_fir_filter_design.py
@@ -274,11 +274,11 @@ def test_bad_pass_zero(self):
             with assert_raises(ValueError, match='must have at least two'):
                 firwin(41, [0.5], pass_zero=pass_zero)
 
-    def test_nyq_deprecation(self):
-        with pytest.warns(DeprecationWarning,
-                          match="Keyword argument 'nyq' is deprecated in "
-                          ):
+    def test_firwin_deprecations(self):
+        with pytest.deprecated_call(match="argument 'nyq' is deprecated"):
             firwin(1, 1, nyq=10)
+        with pytest.deprecated_call(match="use keyword arguments"):
+            firwin(58, 0.1, 0.03)
 
 class TestFirwin2:
 
@@ -430,11 +430,12 @@ def test_input_modyfication(self):
         firwin2(80, freq1, [1.0, 1.0, 0.0, 0.0])
         assert_equal(freq1, freq2)
 
-    def test_nyq_deprecation(self):
-        with pytest.warns(DeprecationWarning,
-                          match="Keyword argument 'nyq' is deprecated in "
-                          ):
+    def test_firwin2_deprecations(self):
+        with pytest.deprecated_call(match="argument 'nyq' is deprecated"):
             firwin2(1, [0, 10], [1, 1], nyq=10)
+        with pytest.deprecated_call(match="use keyword arguments"):
+            # from test04
+            firwin2(5, [0.0, 0.5, 0.5, 1.0], [1.0, 1.0, 0.0, 0.0], 8193, None)
 
 
 class TestRemez:
@@ -496,11 +497,12 @@ def test_compare(self):
             assert_allclose(remez(21, [0, 0.8, 0.9, 1], [0, 1], Hz=2.), h)
         assert_allclose(remez(21, [0, 0.8, 0.9, 1], [0, 1], fs=2.), h)
 
-    def test_Hz_deprecation(self):
-        with pytest.warns(DeprecationWarning,
-                          match="'remez' keyword argument 'Hz'"
-                          ):
+    def test_remez_deprecations(self):
+        with pytest.deprecated_call(match="'remez' keyword argument 'Hz'"):
             remez(12, [0, 0.3, 0.5, 1], [1, 0], Hz=2.)
+        with pytest.deprecated_call(match="use keyword arguments"):
+            # from test_hilbert
+            remez(11, [0.1, 0.4], [1], None)
 
 class TestFirls:
 
@@ -519,9 +521,9 @@ def test_bad_args(self):
         # negative desired
         assert_raises(ValueError, firls, 11, [0.1, 0.2], [-1, 1])
         # len(weight) != len(pairs)
-        assert_raises(ValueError, firls, 11, [0.1, 0.2], [0, 0], [1, 2])
+        assert_raises(ValueError, firls, 11, [0.1, 0.2], [0, 0], weight=[1, 2])
         # negative weight
-        assert_raises(ValueError, firls, 11, [0.1, 0.2], [0, 0], [-1])
+        assert_raises(ValueError, firls, 11, [0.1, 0.2], [0, 0], weight=[-1])
 
     def test_firls(self):
         N = 11  # number of taps in the filter
@@ -560,7 +562,7 @@ def test_firls(self):
 
     def test_compare(self):
         # compare to OCTAVE output
-        taps = firls(9, [0, 0.5, 0.55, 1], [1, 1, 0, 0], [1, 2])
+        taps = firls(9, [0, 0.5, 0.55, 1], [1, 1, 0, 0], weight=[1, 2])
         # >> taps = firls(8, [0 0.5 0.55 1], [1 1 0 0], [1, 2]);
         known_taps = [-6.26930101730182e-04, -1.03354450635036e-01,
                       -9.81576747564301e-03, 3.17271686090449e-01,
@@ -570,7 +572,7 @@ def test_compare(self):
         assert_allclose(taps, known_taps)
 
         # compare to MATLAB output
-        taps = firls(11, [0, 0.5, 0.5, 1], [1, 1, 0, 0], [1, 2])
+        taps = firls(11, [0, 0.5, 0.5, 1], [1, 1, 0, 0], weight=[1, 2])
         # >> taps = firls(10, [0 0.5 0.5 1], [1 1 0 0], [1, 2]);
         known_taps = [
             0.058545300496815, -0.014233383714318, -0.104688258464392,
@@ -614,11 +616,12 @@ def test_rank_deficient(self):
         assert mask.sum() > 3
         assert_allclose(np.abs(h[mask]), 0., atol=1e-4)
 
-    def test_nyq_deprecation(self):
-        with pytest.warns(DeprecationWarning,
-                          match="Keyword argument 'nyq' is deprecated in "
-                          ):
+    def test_firls_deprecations(self):
+        with pytest.deprecated_call(match="argument 'nyq' is deprecated"):
             firls(1, (0, 1), (0, 0), nyq=10)
+        with pytest.deprecated_call(match="use keyword arguments"):
+            # from test_firls
+            firls(11, [0, 0.1, 0.4, 0.5], [1, 1, 0, 0], None)
 
 
 class TestMinimumPhase: