Repository: scipy/scipy

Issue:
BUG: unnecessary computations in iirpeak/iirnotch/iircomb filter gain could simplify to 1
### Describe your issue.

I was looking at resonance filter implementations in `scipy.signal`, specifically `iirpeak`, `iirnotch` and `iircomb` and I noticed that part of the formulas for gain computation could mathematically just simplify to 1.

Formulas are correct (double checked from the reference reported in the docs) however, this could lead to a small rounding error of the coefficients due to the extra computations.

Lines where this computations are performed can be found [here](https://github.com/scipy/scipy/blob/main/scipy/signal/_filter_design.py) at lines: `5154`, `5157`, `5348`.

### Reproducing Code Example

```python
import numpy as np


def notch_peak_rounding_error(w0, Q, ftype, fs=2.0):
    # Guarantee that the inputs are floats
    w0 = float(w0)
    Q = float(Q)
    w0 = 2*w0/fs

    # Checks if w0 is within the range
    if w0 > 1.0 or w0 < 0.0:
        raise ValueError("w0 should be such that 0 < w0 < 1")

    # Get bandwidth
    bw = w0/Q

    # Normalize inputs
    bw = bw*np.pi
    w0 = w0*np.pi

    # Compute -3dB attenuation
    gb = 1/np.sqrt(2)

    if ftype == "notch":
        # Compute beta: formula 11.3.4 (p.575) from reference [1]
        beta = (np.sqrt(1.0-gb**2.0)/gb)*np.tan(bw/2.0)
    elif ftype == "peak":
        # Compute beta: formula 11.3.19 (p.579) from reference [1]
        beta = (gb/np.sqrt(1.0-gb**2.0))*np.tan(bw/2.0)
    else:
        raise ValueError("Unknown ftype.")

    # This constants just simplify to 1:
    #     (np.sqrt(1.0-gb**2.0)/gb) = 1
    #     (gb/np.sqrt(1.0-gb**2.0)) = 1
    beta2 = 1.0 * np.tan(bw/2.0)
    return np.abs(beta - beta2)


def iircomb_rounding_error(w0, Q, ftype='notch', fs=2.0, *, pass_zero=False):
    # Convert w0, Q, and fs to float
    w0 = float(w0)
    Q = float(Q)

    # Check for invalid cutoff frequency or filter type
    ftype = ftype.lower()
    if not 0 < w0 < fs / 2:
        raise ValueError(f"w0 must be between 0 and {fs / 2}"
                         f" (nyquist), but given {w0}.")
    if ftype not in ('notch', 'peak'):
        raise ValueError('ftype must be either notch or peak.')

    # Compute the order of the filter
    N = round(fs / w0)

    # Check for cutoff frequency divisibility
    if abs(w0 - fs/N)/fs > 1e-14:
        raise ValueError('fs must be divisible by w0.')

    # Compute frequency in radians and filter bandwidth
    # Eq. 11.3.1 (p. 574) from reference [1]
    w0 = (2 * np.pi * w0) / fs
    w_delta = w0 / Q

    # Define base gain values depending on notch or peak filter
    # Compute -3dB attenuation
    # Eqs. 11.4.1 and 11.4.2 (p. 582) from reference [1]
    if ftype == 'notch':
        G0, G = 1, 0
    elif ftype == 'peak':
        G0, G = 0, 1
    GB = 1 / np.sqrt(2)

    # Compute beta
    # Eq. 11.5.3 (p. 591) from reference [1]
    beta = np.sqrt((GB**2 - G0**2) / (G**2 - GB**2)) * np.tan(N * w_delta / 4)

    # This constant just simplify to 1:
    #     np.sqrt((GB**2 - G0**2) / (G**2 - GB**2)) = 1
    beta2 = 1.0 * np.tan(N * w_delta / 4)
    return np.abs(beta - beta2)


print("IIR peak filter rounding error:", notch_peak_rounding_error(50, 10, "peak", 500))
print("IIR notch filter rounding error:", notch_peak_rounding_error(50, 10, "notch", 500))
print("IIR comb peak filter rounding error:", iircomb_rounding_error(50, 10, "peak", 500))
print("IIR comb notch filter rounding error:", iircomb_rounding_error(50, 10, "notch", 500))
```


### Error message

```shell
Output from code snippet:

IIR peak filter rounding error: 6.938893903907228e-18
IIR notch filter rounding error: 6.938893903907228e-18
IIR comb peak filter rounding error: 2.7755575615628914e-17
IIR comb notch filter rounding error: 2.7755575615628914e-17
```


### SciPy/NumPy/Python version and system information

```shell
1.10.1 1.23.0 sys.version_info(major=3, minor=9, micro=19, releaselevel='final', serial=0)
Build Dependencies:
  blas:
    detection method: pkgconfig
    found: true
    include directory: c:/opt/openblas/if_32/64/include
    lib directory: c:/opt/openblas/if_32/64/lib
    name: openblas
    openblas configuration: USE_64BITINT= DYNAMIC_ARCH=1 DYNAMIC_OLDER= NO_CBLAS=
      NO_LAPACK= NO_LAPACKE= NO_AFFINITY=1 USE_OPENMP= PRESCOTT MAX_THREADS=4
    pc file directory: c:/opt/openblas/if_32/64/lib/pkgconfig
    version: 0.3.18
  lapack:
    detection method: pkgconfig
    found: true
    include directory: c:/opt/openblas/if_32/64/include
    lib directory: c:/opt/openblas/if_32/64/lib
    name: openblas
      NO_LAPACK= NO_LAPACKE= NO_AFFINITY=1 USE_OPENMP= PRESCOTT MAX_THREADS=4
    pc file directory: c:/opt/openblas/if_32/64/lib/pkgconfig
    version: 0.3.18
Compilers:
  c:
    commands: cc
    linker: ld.bfd
    name: gcc
    version: 10.3.0
  c++:
    commands: c++
    linker: ld.bfd
    name: gcc
    version: 10.3.0
  cython:
    commands: cython
    linker: cython
    name: cython
    version: 0.29.33
  fortran:
    commands: gfortran
    linker: ld.bfd
    name: gcc
    version: 10.3.0
  pythran:
    include directory: C:\Users\runneradmin\AppData\Local\Temp\pip-build-env-u63ta2f1\overlay\Lib\site-packages\py
thran
    version: 0.12.1
Machine Information:
  build:
    cpu: x86_64
    endian: little
    family: x86_64
    system: windows
  cross-compiled: false
  host:
    cpu: x86_64
    endian: little
    family: x86_64
    system: windows
Python Information:
  path: C:\Users\runneradmin\AppData\Local\Temp\cibw-run-a1px0t3e\cp39-win_amd64\build\venv\Scripts\python.exe
  version: '3.9'
```



Discussion:
The relative errors produced by these formulas are small enough to be ignored:

```python
In [1]: import numpy as np

In [2]: gb = 1/np.sqrt(2)
   ...: c0 = (np.sqrt(1.0-gb**2.0)/gb)  # _filter_design.py Line 5154
   ...: c1 = (gb/np.sqrt(1.0-gb**2.0))  # _filter_design.py Line 5157
   ...: print(c0-1, c1-1)
2.220446049250313e-16 -1.1102230246251565e-16

In [3]: G0, G = 0, 1
   ...: GB = 1 / np.sqrt(2)
   ...: c3 = np.sqrt((GB**2 - G0**2) / (G**2 - GB**2)) # _filter_design.py Line 5348
   ...: print(c3 - 1)
-2.220446049250313e-16
```

Nonetheless, fixing this would not hurt either. @Gualor, would you care to make a pull request?

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
@@ -5146,18 +5146,16 @@ def _design_notch_peak_filter(w0, Q, ftype, fs=2.0):
     bw = bw*np.pi
     w0 = w0*np.pi
 
-    # Compute -3dB attenuation
-    gb = 1/np.sqrt(2)
-
-    if ftype == "notch":
-        # Compute beta: formula 11.3.4 (p.575) from reference [1]
-        beta = (np.sqrt(1.0-gb**2.0)/gb)*np.tan(bw/2.0)
-    elif ftype == "peak":
-        # Compute beta: formula 11.3.19 (p.579) from reference [1]
-        beta = (gb/np.sqrt(1.0-gb**2.0))*np.tan(bw/2.0)
-    else:
+    if ftype not in ("notch", "peak"):
         raise ValueError("Unknown ftype.")
 
+    # Compute beta according to Eqs. 11.3.4 (p.575) and 11.3.19 (p.579) from
+    # reference [1]. Due to assuming a -3 dB attenuation value, i.e, assuming
+    # gb = 1 / np.sqrt(2), the following terms simplify to:
+    #   (np.sqrt(1.0 - gb**2.0) / gb) = 1
+    #   (gb / np.sqrt(1.0 - gb**2.0)) = 1
+    beta = np.tan(bw/2.0)
+
     # Compute gain: formula 11.3.6 (p.575) from reference [1]
     gain = 1.0/(1.0+beta)
 
@@ -5341,11 +5339,12 @@ def iircomb(w0, Q, ftype='notch', fs=2.0, *, pass_zero=False):
         G0, G = 1, 0
     elif ftype == 'peak':
         G0, G = 0, 1
-    GB = 1 / np.sqrt(2)
 
-    # Compute beta
-    # Eq. 11.5.3 (p. 591) from reference [1]
-    beta = np.sqrt((GB**2 - G0**2) / (G**2 - GB**2)) * np.tan(N * w_delta / 4)
+    # Compute beta according to Eq. 11.5.3 (p. 591) from reference [1]. Due to
+    # assuming a -3 dB attenuation value, i.e, assuming GB = 1 / np.sqrt(2),
+    # the following term simplifies to:
+    #   np.sqrt((GB**2 - G0**2) / (G**2 - GB**2)) = 1
+    beta = np.tan(N * w_delta / 4)
 
     # Compute filter coefficients
     # Eq 11.5.1 (p. 590) variables a, b, c from reference [1]


Test patch: