Crate welch_sde[−][src]
Expand description
Spectral density and power spectrum estimation with Welch method
The Welch’s method for the estimation of the periodogram of a stationnary and zero-mean signal involves splitting the signal in overlapping segments, multiplying each segment with a predeterming window and computing the discrete Fourier transform of each windowed segment. The periodogram is then given by the average of the squared magnitude of each Fourier transform. Only the halve of the power spectrum that corresponds to positive frequencies is returned, both halves beeing symmetric with respect to the zero frequency.
From the periodogram, one can derive either the spectral density or the power spectrum. Both differs with respect to the scaling of the periodogram. For the spectral density, the periodogram is divided by the product of the sampling frequency with the sum of the squared window samples. For the power spectrum, the periodogram is divided by the square of the sum of the window samples.
The Welch algorithm is implemented in the Welch structure. For convenience, 2 new types (that encapsulates Welch) are provided, SpectralDensity and PowerSpectrum, to compute the spectral density and the power spectrum, respectively. SpectralDensity uses a Hann window whereas PowerSpectrum does not use any window.
Custom windows can be used with Welch if they implement the Window trait. The signal is either a single or double floating point array.
Examples
Power spectrum
use rand::prelude::*;
use rand_distr::StandardNormal;
use std::time::Instant;
use welch_sde::{Build, PowerSpectrum};
fn main() {
let n = 1e6 as usize;
let signal: Vec<f64> = (0..n)
.map(|_| 1.234_f64.sqrt() * thread_rng().sample::<f64, StandardNormal>(StandardNormal))
.collect();
{
let mean = signal.iter().cloned().sum::<f64>() / n as f64;
let variance = signal.iter().map(|s| *s - mean).map(|x| x * x).sum::<f64>() / n as f64;
println!("Signal variance: {:.3}", variance);
}
let welch: PowerSpectrum<f64> = PowerSpectrum::builder(&signal).build();
println!("{}", welch);
let now = Instant::now();
let ps = welch.periodogram();
println!(
"Power spectrum estimated in {}ms",
now.elapsed().as_millis()
);
{
let variance = 2. * ps.iter().sum::<f64>();
println!("Signal variance from power spectrum: {:.3}", variance);
}
}
Spectral density
use rand::prelude::*;
use rand_distr::StandardNormal;
use std::time::Instant;
use welch_sde::{SpectralDensity, Build};
fn main() {
let n = 1e5 as usize;
let fs = 10e3_f64;
let amp = 2. * 2f64.sqrt();
let freq = 1550f64;
let noise_power = 0.001 * fs / 2.;
let sigma = noise_power.sqrt();
let signal: Vec<f64> = (0..n)
.map(|i| i as f64 / fs)
.map(|t| {
amp * (2. * std::f64::consts::PI * freq * t).sin()
+ thread_rng().sample::<f64, StandardNormal>(StandardNormal) * sigma
})
.collect();
let welch: SpectralDensity<f64> =
SpectralDensity::<f64>::builder(&signal, fs).build();
println!("{}", welch);
let now = Instant::now();
let sd = welch.periodogram();
println!(
"Spectral density estimated in {}ms",
now.elapsed().as_millis()
);
let noise_floor = sd.iter().cloned().sum::<f64>() / sd.len() as f64;
println!("Noise floor: {:.3}", noise_floor);
let _: complot::LinLog = (
sd.frequency()
.into_iter()
.zip(&(*sd))
.map(|(x, &y)| (x, vec![y])),
complot::complot!(
"spectral_density.png",
xlabel = "Frequency [Hz]",
ylabel = "Spectral density [s^2/Hz]"
),
)
.into();
}
Structs
Generic builder
Hann window
One window
Signal periodogram
Power spectrum
Spectral density
Welch spectral density estimator
Traits
Interface to the power spectrum periodogram
The trait the signal type T
must implement
Interface to the spatial density periodogram
Signal windowing interface