FFT Example — Multi-Tone Sinusoidal Signal

This example builds a two-tone signal \(x(t) = \sin(2\pi \cdot 5 \cdot t) + 0.5 \cdot \sin(2\pi \cdot 12 \cdot t)\) with N = 256 samples and computes its one-sided FFT using transforms.fft_real_forward(), then detects the dominant peak frequencies.

Example Code

"""IMSL FFT example: compute FFT of a multi-tone sinusoidal signal.

Signal: x(t) = sin(2π*5*t) + 0.5*sin(2π*12*t), N=256, dt=1/256

Outputs:
- Table printed to stdout showing peak frequencies detected
- SVG plot saved to test_output/demo_imsl_fft.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 fft_real_forward


def run_demo_imsl_fft() -> Dict[str, object]:
    """Run IMSL FFT example: FFT of a multi-tone sinusoidal signal.

    Computes the one-sided FFT of x(t) = sin(2π·5·t) + 0.5·sin(2π·12·t)
    sampled at N=256 points with dt=1/256 s.

    Args:
        None

    Returns:
        Dict[str, object]: Result dict with keys ``peak_freqs`` (list of
            float), ``magnitudes`` (list of float), and ``plot_path`` (str).
    """
    N = 256
    dt = 1.0 / N
    t = np.arange(N) * dt
    x = np.sin(2 * np.pi * 5 * t) + 0.5 * np.sin(2 * np.pi * 12 * t)

    X = fft_real_forward(x)
    freqs = np.fft.rfftfreq(N, d=dt)
    magnitude = np.abs(X)

    # Find top-3 peaks
    sorted_idx = np.argsort(magnitude)[::-1]
    top_idx = sorted_idx[:3]
    top_freqs = freqs[top_idx].tolist()
    top_mags = magnitude[top_idx].tolist()

    print("\nIMSL FFT Example: x(t) = sin(2π·5·t) + 0.5·sin(2π·12·t), N=256")
    print("-" * 55)
    print(f"{'Rank':<6} {'Frequency (Hz)':>16} {'Magnitude':>12}")
    print("-" * 55)
    for i, (f, m) in enumerate(zip(top_freqs, top_mags), 1):
        print(f"{i:<6} {f:>16.2f} {m:>12.4f}")
    print("-" * 55)

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

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

    axes[0].plot(t, x, color="#6d28d9", linewidth=1.5)
    axes[0].set_xlabel("Time (s)")
    axes[0].set_ylabel("Amplitude")
    axes[0].set_title("Time-Domain Signal: sin(2π·5·t) + 0.5·sin(2π·12·t)")
    axes[0].grid(True, alpha=0.3)

    axes[1].plot(freqs, magnitude, color="#7c3aed", linewidth=1.5)
    axes[1].axvline(5, color="#dc2626", linestyle="--", linewidth=1.2, label="f=5 Hz")
    axes[1].axvline(12, color="#d97706", linestyle="--", linewidth=1.2, label="f=12 Hz")
    axes[1].set_xlabel("Frequency (Hz)")
    axes[1].set_ylabel("|X(f)|")
    axes[1].set_title("Frequency Spectrum (One-sided)")
    axes[1].set_xlim(0, 40)
    axes[1].legend()
    axes[1].grid(True, alpha=0.3)

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

    return {"peak_freqs": top_freqs, "magnitudes": top_mags, "plot_path": str(plot_path)}


if __name__ == "__main__":
    run_demo_imsl_fft()

Plot Output

Time-domain signal and one-sided frequency spectrum

Top: time-domain signal showing two superimposed sinusoids. Bottom: one-sided magnitude spectrum with peaks at 5 Hz and 12 Hz.

Console Output

IMSL FFT Example: x(t) = sin(2π·5·t) + 0.5·sin(2π·12·t), N=256
-------------------------------------------------------
Rank     Frequency (Hz)    Magnitude
-------------------------------------------------------
1                  5.00     128.0000
2                 12.00      64.0000
3                 89.00       0.0000
-------------------------------------------------------