Repository: scipy/scipy

Issue:
BUG: iirdesign doesn't fail for frequencies above Nyquist
Hi all,

`iirdesign` and the underlying filter order functions (`buttord` and cie) don't seem to check that the `wp` and `wn` input frequencies (normalized by the Nyquist frequency) are within [0, 1], and interpret those normalized frequencies modulo 2.
This might be expected behavior (?) but I think this is extremely error prone, could be made more explicit in the documentation and should at least raise a warning -if not fail- when a sampling rate in provided, since it is quite unlikely anyone is reasoning "modulo 2*fs/2" in this case, isn't it?

```python
import scipy.signal
import numpy as np

fs = 100  # the signal is 100Hz, but maybe I thought it was 1000Hz and didn't check?

# That's fine
wp1, ws1 = np.array([25, 30]), np.array([20, 40])
sos1 = scipy.signal.iirdesign(wp1, ws1, 1, 30, fs=fs, output='sos')

# Now I'm bandpassing between 125,130Hz a signal sampled at 100Hz :(
k = 2
wp2, ws2 = wp1 + k*(fs/2), ws1 + k*(fs/2)
print(wp2, ws2)
sos2 = scipy.signal.iirdesign(wp2, ws2, 1, 30, fs=fs, output='sos')
# No error

# Same results (+- numerical errors?)
print(np.all(sos1-sos2<1e-14))
```


The lack of thorough checking of wp and ws also results in https://github.com/scipy/scipy/issues/11567

#### Scipy/Numpy/Python version information:
1.4.1 1.18.1 sys.version_info(major=3, minor=7, micro=5, releaselevel='final', serial=0)



Discussion:
This is kind of expected since otherwise it is yet another annoyance that a user should slice an array (often with unknown contents say generated via `np.logspace`) until it falls below the nyquist freq. I have the opinion that this should be documented properly but kept as valid input. 

Same applies to Bode plots for example, a user can still pump frequencies beyond the Nyquist but they probably should realize that it is meaningless beyond the dashed line. 

Of course our handling here is still substandard and needs some clarification.
@TomBugnon up for making a PR like https://github.com/scipy/scipy/pull/12061 to fix this?
@larsoner so what's to be changed in conclusion? wp and ws above [0, 1] should remain valid input but we just add a warning to the doc? Or should we add a kwarg that makes values above 1 fail or raise a warning?
I think we should throw an error. The likelihood that someone intentionally puts a frequency above Nyquist is way lower than them doing it unintentionally, and thus potentially getting an errant result and not realizing it. If anyone really did put a frequency above Nyquist intentionally, it's easy enough for them to alias it back with modulo.
I agree. Ok with you @ilayn ?

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
@@ -2214,7 +2214,9 @@ def iirdesign(wp, ws, gpass, gstop, analog=False, ftype='ellip', output='ba',
     if wp.shape[0] == 2:
         if wp[0] < 0 or ws[0] < 0:
             raise ValueError("Values for wp, ws can't be negative")
-        if not((ws[0] < wp[0] and wp[1] < ws[1]) or
+        elif 1 < wp[1] or 1 < ws[1]:
+            raise ValueError("Values for wp, ws can't be larger than 1")
+        elif not((ws[0] < wp[0] and wp[1] < ws[1]) or
             (wp[0] < ws[0] and ws[1] < wp[1])):
             raise ValueError("Passband must lie strictly inside stopband"
                          " or vice versa")


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
@@ -3690,6 +3690,8 @@ def test_exceptions(self):
                       np.array([[0.4, 0.5], [0.4, 0.5]]), 1, 40)
         with pytest.raises(ValueError, match="can't be negative"):
             iirdesign([0.1, 0.3], [-0.1, 0.5], 1, 40)
+        with pytest.raises(ValueError, match="can't be larger than 1"):
+            iirdesign([0.1, 1.3], [0.1, 0.5], 1, 40)
         with pytest.raises(ValueError, match="strictly inside stopband"):
             iirdesign([0.1, 0.4], [0.5, 0.6], 1, 40)
         with pytest.raises(ValueError, match="strictly inside stopband"):