Skip to content

BUG: unnecessary computations in iirpeak/iirnotch/iircomb filter gain could simplify to 1 #20394

@Gualor

Description

@Gualor

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 at lines: 5154, 5157, 5348.

Reproducing Code Example

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

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

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'

Metadata

Metadata

Assignees

No one assigned

    Labels

    defectA clear bug or issue that prevents SciPy from being installed or used as expectedscipy.signal

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions