Repository: scipy/scipy

Issue:
ellipord does returns bogus values if gstop or gpass are negative (Trac #928)
_Original ticket http://projects.scipy.org/scipy/ticket/928 on 2009-04-20 by @cournape, assigned to unknown._

```
#!python
from scipy.signal import ellipord
ellipord(0.2, 0.5, -5, -3)
```

Return a minimal order of 0. Potentially similar functions for other types of filters are buggy as well.



Discussion:
This now gives `ValueError: cannot convert float NaN to integer`, which isn't understandable. Could be easily fixed by checking the input values and raising `ValueError`s with informative error messages.

Note, also for positive values if `gpass > gstop`:
```
In [26]: ellipord(0.2, 0.5, 1, 2)                                                     
Out[26]: (1, 0.2)

In [27]: ellipord(0.2, 0.5, 3, 2)                                                     
---------------------------------------------------------------------------
ValueError 
```
And that should apply to `cheb2ord`, etc. too.
I'll reopen this because, as @endolith pointed out, there are a number of functions with the same `gpass, gstop` parameters that don't yet have input validation. It would be best to take the checks from gh-11156 and put them in a utility validation function, and use that in all other funcs in `filter_design.py` with those parameters.

@AtsushiSakai if you'd want to do that, that would be very welcome.
@rgommers OK. I will do that : ).

Resolving patch:
diff --git a/scipy/signal/filter_design.py b/scipy/signal/filter_design.py
--- a/scipy/signal/filter_design.py
+++ b/scipy/signal/filter_design.py
@@ -2046,6 +2046,16 @@ def bilinear(b, a, fs=1.0):
     return normalize(bprime, aprime)
 
 
+def _validate_gpass_gstop(gpass, gstop):
+
+    if gpass <= 0.0:
+        raise ValueError("gpass should be larger than 0.0")
+    elif gstop <= 0.0:
+        raise ValueError("gstop should be larger than 0.0")
+    elif gpass > gstop:
+        raise ValueError("gpass should be smaller than gstop")
+
+
 def iirdesign(wp, ws, gpass, gstop, analog=False, ftype='ellip', output='ba',
               fs=None):
     """Complete IIR digital and analog filter design.
@@ -2160,6 +2170,8 @@ def iirdesign(wp, ws, gpass, gstop, analog=False, ftype='ellip', output='ba',
         raise ValueError(("%s does not have order selection. Use "
                           "iirfilter function.") % ftype)
 
+    _validate_gpass_gstop(gpass, gstop)
+
     wp = atleast_1d(wp)
     ws = atleast_1d(ws)
 
@@ -3421,6 +3433,9 @@ def band_stop_obj(wp, ind, passb, stopb, gpass, gstop, type):
         Filter order (possibly non-integer).
 
     """
+
+    _validate_gpass_gstop(gpass, gstop)
+
     passbC = passb.copy()
     passbC[ind] = wp
     nat = (stopb * (passbC[0] - passbC[1]) /
@@ -3525,6 +3540,9 @@ def buttord(wp, ws, gpass, gstop, analog=False, fs=None):
     >>> plt.show()
 
     """
+
+    _validate_gpass_gstop(gpass, gstop)
+
     wp = atleast_1d(wp)
     ws = atleast_1d(ws)
     if fs is not None:
@@ -3693,6 +3711,9 @@ def cheb1ord(wp, ws, gpass, gstop, analog=False, fs=None):
     >>> plt.show()
 
     """
+
+    _validate_gpass_gstop(gpass, gstop)
+
     wp = atleast_1d(wp)
     ws = atleast_1d(ws)
     if fs is not None:
@@ -3833,6 +3854,9 @@ def cheb2ord(wp, ws, gpass, gstop, analog=False, fs=None):
     >>> plt.show()
 
     """
+
+    _validate_gpass_gstop(gpass, gstop)
+
     wp = atleast_1d(wp)
     ws = atleast_1d(ws)
     if fs is not None:
@@ -3994,12 +4018,7 @@ def ellipord(wp, ws, gpass, gstop, analog=False, fs=None):
 
     """
 
-    if gpass <= 0.0:
-        raise ValueError("gpass should be larger than 0.0")
-    elif gstop <= 0.0:
-        raise ValueError("gstop should be larger than 0.0")
-    elif gpass > gstop:
-        raise ValueError("gpass should be smaller than gstop")
+    _validate_gpass_gstop(gpass, gstop)
 
     wp = atleast_1d(wp)
     ws = atleast_1d(ws)


Test patch:
diff --git a/scipy/signal/tests/test_filter_design.py b/scipy/signal/tests/test_filter_design.py
--- a/scipy/signal/tests/test_filter_design.py
+++ b/scipy/signal/tests/test_filter_design.py
@@ -1447,6 +1447,19 @@ def test_fs_param(self):
         assert_allclose(Wn, [4409.722701715714, 11025.47178084662],
                         rtol=1e-15)
 
+    def test_invalid_input(self):
+        with pytest.raises(ValueError) as exc_info:
+            buttord([20, 50], [14, 60], 3, 2)
+        assert "gpass should be smaller than gstop" in str(exc_info.value)
+
+        with pytest.raises(ValueError) as exc_info:
+            buttord([20, 50], [14, 60], -1, 2)
+        assert "gpass should be larger than 0.0" in str(exc_info.value)
+
+        with pytest.raises(ValueError) as exc_info:
+            buttord([20, 50], [14, 60], 1, -2)
+        assert "gstop should be larger than 0.0" in str(exc_info.value)
+
 
 class TestCheb1ord(object):
 
@@ -1545,6 +1558,19 @@ def test_fs_param(self):
         assert_equal(N, 8)
         assert_allclose(Wn, 4800, rtol=1e-15)
 
+    def test_invalid_input(self):
+        with pytest.raises(ValueError) as exc_info:
+            cheb1ord(0.2, 0.3, 3, 2)
+        assert "gpass should be smaller than gstop" in str(exc_info.value)
+
+        with pytest.raises(ValueError) as exc_info:
+            cheb1ord(0.2, 0.3, -1, 2)
+        assert "gpass should be larger than 0.0" in str(exc_info.value)
+
+        with pytest.raises(ValueError) as exc_info:
+            cheb1ord(0.2, 0.3, 1, -2)
+        assert "gstop should be larger than 0.0" in str(exc_info.value)
+
 
 class TestCheb2ord(object):
 
@@ -1646,6 +1672,19 @@ def test_fs_param(self):
         assert_equal(N, 9)
         assert_allclose(Wn, 103.4874609145164, rtol=1e-15)
 
+    def test_invalid_input(self):
+        with pytest.raises(ValueError) as exc_info:
+            cheb2ord([0.1, 0.6], [0.2, 0.5], 3, 2)
+        assert "gpass should be smaller than gstop" in str(exc_info.value)
+
+        with pytest.raises(ValueError) as exc_info:
+            cheb2ord([0.1, 0.6], [0.2, 0.5], -1, 2)
+        assert "gpass should be larger than 0.0" in str(exc_info.value)
+
+        with pytest.raises(ValueError) as exc_info:
+            cheb2ord([0.1, 0.6], [0.2, 0.5], 1, -2)
+        assert "gstop should be larger than 0.0" in str(exc_info.value)
+
 
 class TestEllipord(object):