Chebyshev Design of an FIR Filter

Author

CVXPY Developers and Balasubramanian Narasimhan

Introduction

This example is adapted from the CVX example of the same name, by Almir Mutapcic (2/2/2006) and the CVXPY adaptation by Judson Wilson (5/27/2014).

This program designs an FIR filter, given a desired frequency response Hdes(ω). The design is judged by the maximum absolute error (Chebyshev norm). This is a convex problem (after sampling it can be formulated as an SOCP), which may be written in the form:

minimizemax|H(ω)Hdes(ω)|for 0ωπ,

where the variable H is the frequency response function, corresponding to an impulse response h.

Topic reference: “Filter design” lecture notes (EE364) by S. Boyd.

Problem Data

## Number of FIR coefficients (including the zeroth one)
n <- 20

## Rule-of-thumb frequency discretization (Cheney's Approx. Theory book)
m <- 15 * n
w <- seq(0, pi, length.out = m)

## Construct the desired filter: fractional delay
D <- 8.25              # Delay value
Hdes <- exp(-1i * D * w)  # Desired frequency response

Solve the Minimax (Chebyshev) Design Problem

The frequency response from a vector of filter coefficients h is computed as H(ω)=k=0n1hkejkω, which can be expressed in matrix form as H=Ah where Aω,k=ejkω.

Since CVXR works with real-valued math, we split the problem into real and imaginary parts. The objective becomes:

minimizemaxω[(Re(A)hRe(Hdes))2+(Im(A)hIm(Hdes))2].

## A is the matrix used to compute the frequency response
## A[w,:] = [1, exp(-j*w), exp(-j*2*w), ..., exp(-j*(n-1)*w)]
A <- exp(-1i * outer(w, 0:(n-1)))

## Split into real and imaginary parts
Hdes_r <- Re(Hdes)
Hdes_i <- Im(Hdes)
A_R <- Re(A)
A_I <- Im(A)

## h is the (real) FIR coefficient vector
h <- Variable(n)

## The objective minimizes max(|A*h - Hdes|^2)
## = max( (Re(A)*h - Re(Hdes))^2 + (Im(A)*h - Im(Hdes))^2 )
obj <- Minimize(
    max_entries(
        (A_R %*% h - Hdes_r)^2 + (A_I %*% h - Hdes_i)^2
    )
)

## Solve problem
prob <- Problem(obj)
result <- psolve(prob)

cat(sprintf("Problem status: %s\n", status(prob)))
cat(sprintf("Final objective value: %f\n", result))
Problem status: optimal
Final objective value: 0.500000

Result Plots

We plot the FIR impulse response, and the frequency response magnitude and phase.

h_val <- value(h)
df_impulse <- data.frame(n = 0:(n-1), h = as.numeric(h_val))

ggplot(df_impulse, aes(x = n, y = h)) +
    geom_segment(aes(xend = n, yend = 0)) +
    geom_point() +
    labs(x = "n", y = "h(n)", title = "FIR Filter Impulse Response") +
    theme_minimal()

FIR filter impulse response
## Compute the frequency response
H <- as.vector(A %*% h_val)

df_mag <- data.frame(
    omega = rep(w, 2),
    magnitude = c(20 * log10(Mod(H)), 20 * log10(Mod(Hdes))),
    type = rep(c("Optimized", "Desired"), each = m)
)

ggplot(df_mag, aes(x = omega, y = magnitude, color = type, linetype = type)) +
    geom_line() +
    scale_linetype_manual(values = c("Desired" = "dashed", "Optimized" = "solid")) +
    coord_cartesian(xlim = c(0, pi), ylim = c(-30, 10)) +
    labs(x = expression(omega), y = expression(paste("|H(", omega, ")| in dB")),
         title = "FIR Filter Frequency Response Magnitude",
         color = "", linetype = "") +
    theme_minimal() +
    theme(legend.position = "bottom")

FIR filter frequency response magnitude
df_phase <- data.frame(omega = w, phase = Arg(H))

ggplot(df_phase, aes(x = omega, y = phase)) +
    geom_line() +
    coord_cartesian(xlim = c(0, pi), ylim = c(-pi, pi)) +
    labs(x = expression(omega), y = expression(paste(symbol("\xd0"), " H(", omega, ")")),
         title = "FIR Filter Frequency Response Phase") +
    theme_minimal()

FIR filter frequency response phase

Session Info

R version 4.5.2 (2025-10-31)
Platform: aarch64-apple-darwin20
Running under: macOS Tahoe 26.3

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRblas.0.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.1

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: America/Los_Angeles
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] ggplot2_4.0.2 CVXR_1.8.1   

loaded via a namespace (and not attached):
 [1] gmp_0.7-5.1        generics_0.1.4     clarabel_0.11.2    slam_0.1-55       
 [5] lattice_0.22-9     digest_0.6.39      magrittr_2.0.4     evaluate_1.0.5    
 [9] grid_4.5.2         RColorBrewer_1.1-3 fastmap_1.2.0      jsonlite_2.0.0    
[13] Matrix_1.7-4       ECOSolveR_0.6.1    backports_1.5.0    scs_3.2.7         
[17] Rmosek_11.1.1      scales_1.4.0       codetools_0.2-20   cli_3.6.5         
[21] rlang_1.1.7        Rglpk_0.6-5.1      withr_3.0.2        yaml_2.3.12       
[25] otel_0.2.0         tools_4.5.2        osqp_1.0.0         Rcplex_0.3-8      
[29] checkmate_2.3.4    dplyr_1.2.0        gurobi_13.0-1      vctrs_0.7.1       
[33] R6_2.6.1           lifecycle_1.0.5    htmlwidgets_1.6.4  pkgconfig_2.0.3   
[37] cccp_0.3-3         pillar_1.11.1      gtable_0.3.6       glue_1.8.0        
[41] Rcpp_1.1.1         xfun_0.56          tibble_3.3.1       tidyselect_1.2.1  
[45] knitr_1.51         dichromat_2.0-0.1  highs_1.12.0-3     farver_2.1.2      
[49] htmltools_0.5.9    rmarkdown_2.30     labeling_0.4.3     piqp_0.6.2        
[53] compiler_4.5.2     S7_0.2.1          

References

  • Boyd, S. “Filter design” lecture notes (EE364), Stanford University.
  • Mutapcic, A. (2006). CVX example: Chebyshev design of an FIR filter.