Complex Convolution/Correlation Example — Radar Matched Filter

This example demonstrates complex convolution and cross-correlation using a radar matched-filter scenario:

A complex exponential signal \(x = e^{2\pi i f_0 t}\) is convolved with a decaying complex kernel \(h = e^{-5t} \cdot e^{2\pi i f_0 t}\) to model channel filtering.

The matched-filter application transmits a short complex linear-FM (chirp) pulse and embeds a delayed, noise-corrupted echo in a longer receive buffer. Cross-correlating the received signal with the chirp template via correlate_complex yields a peak at the true echo delay, demonstrating time-of-flight (range) estimation.

Example Code

"""IMSL complex convolution/correlation example: radar matched filter.

Demonstrates:
  - convolve_complex   (IMSL CCONV)  — complex linear convolution
  - correlate_complex  (IMSL CCORL) — complex cross-correlation

Application: matched-filter radar signal processing.  The transmitted pulse
is a complex chirp; the return echo is a delayed, attenuated copy embedded in
noise.  The matched filter (cross-correlation with the template) peaks at the
echo delay, allowing range estimation.

Outputs:
  - Detection statistics printed to stdout
  - SVG plot saved to test_output/example_imsl_complex_conv.svg
"""

from __future__ import annotations

from pathlib import Path
from typing import Dict

import matplotlib.pyplot as plt
import numpy as np

from transforms import convolve_complex, correlate_complex


def run_demo_imsl_complex_conv() -> Dict[str, object]:
    """Run IMSL complex convolution and correlation (matched filter) example.

    Constructs a complex exponential signal x = exp(2πi·f0·t) and a
    decaying complex kernel h = exp(-5t)·exp(2πi·f0·t), then computes
    convolve_complex(x, h) to model channel filtering.  A separate radar
    scenario builds a complex chirp pulse, embeds a delayed copy in noise,
    and uses correlate_complex as a matched filter whose peak identifies the
    true echo delay.

    Args:
        None

    Returns:
        Dict[str, object]: Result dict with keys ``detected_delay`` (int),
            ``true_delay`` (int), and ``plot_path`` (str).
    """
    fs = 200          # sample rate (Hz)
    T  = 0.5          # signal duration (s)
    f0 = 10.0         # carrier frequency (Hz)
    t  = np.arange(int(fs * T)) / fs

    # Complex exponential signal
    x = np.exp(2j * np.pi * f0 * t)

    # Decaying complex kernel (shorter than signal)
    t_h = np.arange(int(fs * 0.2)) / fs
    h   = np.exp(-5.0 * t_h) * np.exp(2j * np.pi * f0 * t_h)

    # --- Complex convolution: x * h ---
    conv_out = convolve_complex(x, h)

    # --- Radar matched filter ---
    # Transmitted chirp: short complex linear-FM pulse
    n_pulse = 20
    t_pulse = np.arange(n_pulse) / fs
    chirp   = np.exp(2j * np.pi * (f0 + 30.0 * t_pulse) * t_pulse)

    # Received signal: delayed echo buried in noise
    true_delay = 45
    rng        = np.random.default_rng(42)
    received   = np.zeros(len(t) + n_pulse - 1, dtype=complex)
    received[true_delay : true_delay + n_pulse] = chirp
    noise_amp  = 0.3
    received  += noise_amp * (
        rng.standard_normal(len(received)) + 1j * rng.standard_normal(len(received))
    )

    # Matched filter: cross-correlate received with chirp template
    mf_out = correlate_complex(received, chirp)

    # Trim leading edge (length n_pulse-1) before finding peak
    n_trim         = n_pulse - 1
    detected_delay = int(np.argmax(np.abs(mf_out[n_trim:])))

    print("\nIMSL Complex Convolution/Correlation Example")
    print("-" * 55)
    print(f"{'Signal length (samples)':<42} {len(x):>8}")
    print(f"{'Kernel length (samples)':<42} {len(h):>8}")
    print(f"{'Convolution output length':<42} {len(conv_out):>8}")
    print(f"{'Radar pulse length (samples)':<42} {n_pulse:>8}")
    print(f"{'True delay (samples)':<42} {true_delay:>8}")
    print(f"{'Detected delay (samples)':<42} {detected_delay:>8}")
    print("-" * 55)

    output_dir = Path("test_output")
    output_dir.mkdir(parents=True, exist_ok=True)
    plot_path = output_dir / "example_imsl_complex_conv.svg"

    fig, axes = plt.subplots(3, 1, figsize=(10, 10))

    # Convolution magnitude
    t_conv = np.arange(len(conv_out)) / fs
    axes[0].plot(t_conv, np.abs(conv_out), color="#7c3aed", linewidth=1.5)
    axes[0].set_title("Complex Convolution |x * h|(t)")
    axes[0].set_xlabel("Time (s)")
    axes[0].set_ylabel("|conv(x, h)|")
    axes[0].grid(True, alpha=0.3)

    # Received radar signal
    t_recv = np.arange(len(received)) / fs
    axes[1].plot(t_recv, np.abs(received), color="#6d28d9", linewidth=1.0,
                 alpha=0.7, label="Received (noisy)")
    axes[1].axvline(true_delay / fs, color="#dc2626", linestyle="--",
                    linewidth=1.5, label=f"True delay ({true_delay} samples)")
    axes[1].set_title("Received Radar Signal (delayed echo + noise)")
    axes[1].set_xlabel("Time (s)")
    axes[1].set_ylabel("|r(t)|")
    axes[1].legend()
    axes[1].grid(True, alpha=0.3)

    # Matched filter output
    mf_plot = np.abs(mf_out[n_trim:])
    t_mf    = np.arange(len(mf_plot)) / fs
    axes[2].plot(t_mf, mf_plot, color="#059669", linewidth=1.5, label="MF output")
    axes[2].axvline(true_delay / fs, color="#dc2626", linestyle="--",
                    linewidth=1.5, label=f"True delay ({true_delay} samples)")
    axes[2].axvline(detected_delay / fs, color="#f59e0b", linestyle=":",
                    linewidth=2.0, label=f"Detected ({detected_delay} samples)")
    axes[2].set_title("Matched Filter Output |correlate_complex(r, chirp)|")
    axes[2].set_xlabel("Time (s)")
    axes[2].set_ylabel("|MF(t)|")
    axes[2].legend()
    axes[2].grid(True, alpha=0.3)

    fig.tight_layout()
    fig.savefig(plot_path, format="svg")
    plt.close(fig)

    return {
        "detected_delay": detected_delay,
        "true_delay": true_delay,
        "plot_path": str(plot_path),
    }


if __name__ == "__main__":
    run_demo_imsl_complex_conv()

Plot Output

Three-panel plot: convolution magnitude, received radar signal, matched filter output

Top: magnitude of the complex convolution output |x ∗ h|(t). Middle: magnitude of the noisy received radar signal with the true echo delay marked. Bottom: matched filter output magnitude showing the detection peak at the correct delay.

Console Output

IMSL Complex Convolution/Correlation Example
-------------------------------------------------------
Signal length (samples)                         100
Kernel length (samples)                          40
Convolution output length                       139
Radar pulse length (samples)                     20
True delay (samples)                             45
Detected delay (samples)                         45
-------------------------------------------------------