Introduction
The elliptic filter (also known as the Cauer filter) is an IIR filter that exhibits equiripple behavior in both the passband and the stopband. Among all IIR filters of the same order, it achieves the steepest transition band:
\[\text{Elliptic} > \text{Chebyshev (Type I \& II)} > \text{Butterworth}\]The trade-off is that both the passband and stopband contain ripples. This article covers the mathematical background and practical design using SciPy.
Frequency Response
The squared magnitude response of an elliptic filter is:
\[|H(j\Omega)|^2 = \frac{1}{1 + \varepsilon^2 R_n^2(\xi, \Omega/\Omega_p)} \tag{1}\]where:
- \(\varepsilon\): passband ripple parameter (\(\varepsilon^2 = 10^{R_p/10} - 1\))
- \(R_n(\xi, x)\): Jacobi elliptic rational function of order \(n\)
- \(\xi\): selectivity parameter (\(\xi = \Omega_p / \Omega_s > 1\))
- \(n\): filter order
Jacobi Elliptic Rational Function
The function \(R_n(\xi, x)\) is constructed from Jacobi elliptic functions (\(\text{sn}\), \(\text{cn}\), \(\text{dn}\)) and satisfies:
- \(|x| \le 1 \Rightarrow |R_n(\xi, x)| \le 1\) (passband)
- \(|x| \ge \xi \Rightarrow |R_n(\xi, x)| \ge \xi\) (stopband)
This property ensures equiripple behavior in both bands simultaneously.
Order Determination
The minimum order required to meet specifications (\(\Omega_p\), \(\Omega_s\), \(R_p\), \(R_s\)) is:
\[n \ge \frac{K(1/\xi) \cdot K(\sqrt{1-1/\xi_s^2})}{K(\xi_s) \cdot K(\sqrt{1-1/\xi^2})} \tag{2}\]where \(K(\cdot)\) is the complete elliptic integral of the first kind. In practice, scipy.signal.ellipord computes this automatically.
Comparison with Butterworth and Chebyshev
| Filter | Passband | Stopband | Transition Band (same order) | Phase Response |
|---|---|---|---|---|
| Butterworth | Maximally flat | Monotone | Widest | Best |
| Chebyshev Type I | Equiripple | Monotone | Moderate | Moderate |
| Chebyshev Type II | Maximally flat | Equiripple | Moderate | Moderate |
| Elliptic | Equiripple | Equiripple | Narrowest | Worst |
The elliptic filter maximizes transition band steepness at the cost of nonlinear phase response (poor group delay flatness).
SciPy Implementation
import numpy as np
import matplotlib.pyplot as plt
from scipy import signal
# ===== Design Parameters =====
fs = 1000 # Sampling frequency [Hz]
f_pass = 100 # Passband edge [Hz]
f_stop = 150 # Stopband edge [Hz]
rp = 1.0 # Passband ripple [dB]
rs = 60.0 # Stopband attenuation [dB]
# Normalize to Nyquist frequency
nyq = fs / 2
wp = f_pass / nyq
ws = f_stop / nyq
# ===== Compute Minimum Order =====
n, wn = signal.ellipord(wp, ws, rp, rs)
print(f"Minimum elliptic filter order: {n}")
# ===== Design Lowpass Elliptic Filter =====
b, a = signal.ellip(n, rp, rs, wn, btype='low')
# ===== Compute Frequency Response =====
w, h = signal.freqz(b, a, worN=2048, fs=fs)
# ===== Plot =====
fig, axes = plt.subplots(2, 1, figsize=(10, 8))
# Magnitude response
axes[0].plot(w, 20 * np.log10(np.abs(h)), label=f'Elliptic (n={n})', color='royalblue')
axes[0].axvline(f_pass, color='green', linestyle='--', alpha=0.7, label=f'Passband edge {f_pass}Hz')
axes[0].axvline(f_stop, color='red', linestyle='--', alpha=0.7, label=f'Stopband edge {f_stop}Hz')
axes[0].axhline(-rp, color='orange', linestyle=':', alpha=0.7, label=f'Ripple -{rp}dB')
axes[0].axhline(-rs, color='purple', linestyle=':', alpha=0.7, label=f'Attenuation -{rs}dB')
axes[0].set_ylim(-80, 5)
axes[0].set_xlabel('Frequency [Hz]')
axes[0].set_ylabel('Gain [dB]')
axes[0].set_title('Elliptic Filter: Magnitude Response')
axes[0].legend()
axes[0].grid(True)
# Phase response
angles = np.unwrap(np.angle(h))
axes[1].plot(w, np.degrees(angles), color='royalblue')
axes[1].set_xlabel('Frequency [Hz]')
axes[1].set_ylabel('Phase [degrees]')
axes[1].set_title('Elliptic Filter: Phase Response')
axes[1].grid(True)
plt.tight_layout()
plt.show()
Order Comparison Across Filter Types
Using the same specification (\(f_p=100\) Hz, \(f_s=150\) Hz, \(R_p=1\) dB, \(R_s=60\) dB):
from scipy import signal
fs = 1000
nyq = fs / 2
wp = 100 / nyq
ws = 150 / nyq
rp, rs = 1.0, 60.0
n_butter, _ = signal.buttord(wp, ws, rp, rs)
n_cheby1, _ = signal.cheb1ord(wp, ws, rp, rs)
n_cheby2, _ = signal.cheb2ord(wp, ws, rp, rs)
n_ellip, _ = signal.ellipord(wp, ws, rp, rs)
print(f"Butterworth : order {n_butter}")
print(f"Chebyshev I : order {n_cheby1}")
print(f"Chebyshev II : order {n_cheby2}")
print(f"Elliptic : order {n_ellip}")
Typical output:
Butterworth : order 11
Chebyshev I : order 6
Chebyshev II : order 6
Elliptic : order 4
The elliptic filter achieves the same specification with roughly 1/3 the order of a Butterworth filter.
Highpass, Bandpass, and Bandstop Design
from scipy import signal
fs = 1000
nyq = fs / 2
rp, rs = 1.0, 60.0
# ===== Highpass Filter =====
n_hp, wn_hp = signal.ellipord(200/nyq, 100/nyq, rp, rs)
b_hp, a_hp = signal.ellip(n_hp, rp, rs, wn_hp, btype='high')
# ===== Bandpass Filter =====
n_bp, wn_bp = signal.ellipord(
[100/nyq, 300/nyq], [50/nyq, 400/nyq], rp, rs
)
b_bp, a_bp = signal.ellip(n_bp, rp, rs, wn_bp, btype='bandpass')
# ===== Bandstop Filter =====
n_bs, wn_bs = signal.ellipord(
[50/nyq, 400/nyq], [100/nyq, 300/nyq], rp, rs
)
b_bs, a_bs = signal.ellip(n_bs, rp, rs, wn_bs, btype='bandstop')
Numerical Stability: Use SOS Format
For higher-order filters (roughly n > 6), direct-form coefficients (b, a) can suffer from numerical precision issues. Use Second-Order Sections (SOS) format instead:
# SOS format: more numerically stable
sos = signal.ellip(n, rp, rs, wn, btype='low', output='sos')
# Zero-phase filtering (offline use)
y = signal.sosfiltfilt(sos, x)
# Causal filtering (real-time use)
y = signal.sosfilt(sos, x)
| Function | Phase delay | Real-time | Recommended when |
|---|---|---|---|
signal.lfilter | Yes | Yes | Real-time, low-order filters |
signal.filtfilt | None (zero-phase) | No | Offline analysis |
signal.sosfilt | Yes | Yes | Higher-order, real-time |
signal.sosfiltfilt | None | No | Higher-order, offline |
When to Choose Elliptic Filters
| Scenario | Recommended Filter |
|---|---|
| Steepest possible transition band | Elliptic |
| Minimize filter order (computation cost) | Elliptic |
| Passband ripple must be zero | Chebyshev Type II or Butterworth |
| Flat group delay (linear phase) | Butterworth or Bessel |
| Low latency real-time processing | Butterworth (low order) |
Related Articles
- Butterworth Filter Design and Python Implementation - Maximally flat IIR filter design
- Chebyshev Filter Design and Python Implementation - Equiripple IIR filter design
- FIR vs IIR Filter Comparison - Overview of IIR filter characteristics
- Lowpass Filter Design Comparison - Comparing various lowpass filter types
- Highpass Filter Design and Python Implementation - Highpass filter design
- Bandpass Filter Design and Python Implementation - Bandpass and bandstop filter design
- Notch Filter Design and Python Implementation - Narrow-band rejection filter design
- Exponential Moving Average (EMA) Filter Frequency Response - EMA as a first-order IIR filter