use scirs2_core::numeric::Complex64;
use scirs2_fft::{fft, fftfreq, ifft, rfft, FFTResult};
use std::f64::consts::PI;
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("=== SciRS2 FFT Tutorial ===\n");
section_basic_fft()?;
section_frequency_bins()?;
section_inverse_fft()?;
section_real_fft()?;
section_spectral_analysis()?;
println!("\n=== Tutorial Complete ===");
Ok(())
}
fn section_basic_fft() -> FFTResult<()> {
println!("--- 1. Basic FFT ---\n");
let n = 8;
let signal: Vec<Complex64> = (0..n)
.map(|i| {
let t = i as f64 / n as f64;
Complex64::new((2.0 * PI * t).cos(), 0.0)
})
.collect();
println!("Input signal (real parts):");
for (i, s) in signal.iter().enumerate() {
println!(" x[{}] = {:.4}", i, s.re);
}
let spectrum = fft(&signal, None)?;
println!("\nFFT output (magnitude):");
for (i, s) in spectrum.iter().enumerate() {
let mag = (s.re * s.re + s.im * s.im).sqrt();
println!(" X[{}] = {:.4} (mag = {:.4})", i, s, mag);
}
println!();
Ok(())
}
fn section_frequency_bins() -> FFTResult<()> {
println!("--- 2. Frequency Bins ---\n");
let n = 8;
let sample_rate = 100.0;
let freqs = fftfreq(n, 1.0 / sample_rate)?;
println!("Frequencies for N={}, fs={} Hz:", n, sample_rate);
for (i, f) in freqs.iter().enumerate() {
println!(" bin[{}] = {:.1} Hz", i, f);
}
println!();
let freqs_arr = scirs2_core::ndarray::Array1::from_vec(freqs.clone());
let shifted = scirs2_fft::fftshift(&freqs_arr)?;
println!("After fftshift (zero-centered):");
for (i, f) in shifted.iter().enumerate() {
println!(" bin[{}] = {:.1} Hz", i, f);
}
println!();
Ok(())
}
fn section_inverse_fft() -> FFTResult<()> {
println!("--- 3. Inverse FFT ---\n");
let n = 16;
let signal: Vec<Complex64> = (0..n)
.map(|i| {
let t = i as f64 / n as f64;
let val = (2.0 * PI * 2.0 * t).cos() + 0.5 * (2.0 * PI * 5.0 * t).cos();
Complex64::new(val, 0.0)
})
.collect();
let spectrum = fft(&signal, None)?;
let recovered = ifft(&spectrum, None)?;
println!("Original vs Recovered (first 8 samples):");
for i in 0..8 {
let diff = (signal[i].re - recovered[i].re).abs();
println!(
" x[{}]: original={:.6}, recovered={:.6}, error={:.2e}",
i, signal[i].re, recovered[i].re, diff
);
}
println!(" (Round-trip error is near machine epsilon)\n");
Ok(())
}
fn section_real_fft() -> FFTResult<()> {
println!("--- 4. Real FFT (rfft) ---\n");
let n = 16;
let signal: Vec<f64> = (0..n)
.map(|i| {
let t = i as f64 / n as f64;
(2.0 * PI * 3.0 * t).cos() })
.collect();
let spectrum = rfft(&signal, None)?;
println!("Real signal with N={} samples:", n);
println!(
" rfft output length: {} (N/2+1 = {})",
spectrum.len(),
n / 2 + 1
);
println!("\n Magnitudes:");
for (i, s) in spectrum.iter().enumerate() {
let mag = (s.re * s.re + s.im * s.im).sqrt();
if mag > 0.01 {
println!(" bin[{}] magnitude = {:.4} (significant)", i, mag);
} else {
println!(" bin[{}] magnitude = {:.4}", i, mag);
}
}
println!();
Ok(())
}
fn section_spectral_analysis() -> FFTResult<()> {
println!("--- 5. Spectral Analysis ---\n");
let sample_rate = 1000.0; let n = 1024;
let dt = 1.0 / sample_rate;
let signal: Vec<Complex64> = (0..n)
.map(|i| {
let t = i as f64 * dt;
let val = 1.0 * (2.0 * PI * 50.0 * t).cos()
+ 0.5 * (2.0 * PI * 120.0 * t).cos()
+ 0.3 * (2.0 * PI * 300.0 * t).cos();
Complex64::new(val, 0.0)
})
.collect();
let spectrum = fft(&signal, None)?;
let freqs = fftfreq(n, dt)?;
println!("Signal: 50 Hz (A=1.0) + 120 Hz (A=0.5) + 300 Hz (A=0.3)");
println!("Sample rate: {} Hz, N = {}\n", sample_rate, n);
let mut peaks: Vec<(f64, f64)> = Vec::new();
for i in 0..n / 2 {
let mag = (spectrum[i].re * spectrum[i].re + spectrum[i].im * spectrum[i].im).sqrt()
/ (n as f64 / 2.0); if mag > 0.1 {
peaks.push((freqs[i], mag));
}
}
println!("Detected frequency peaks:");
for (freq, mag) in &peaks {
println!(" {:.1} Hz, amplitude = {:.3}", freq, mag);
}
println!();
Ok(())
}