welch_sde/lib.rs
1//! # Spectral density and power spectrum estimation with Welch method
2//!
3//! The Welch's method for the estimation of the periodogram of a *stationnary and
4//! zero-mean* signal involves splitting the signal in overlapping segments, multiplying
5//! each segment with a predeterming window and computing the discrete Fourier transform
6//! of each windowed segment.
7//! The periodogram is then given by the average of the squared magnitude
8//! of each Fourier transform.
9//! Only the halve of the power spectrum that corresponds to positive frequencies is returned,
10//! both halves beeing symmetric with respect to the zero frequency.
11//!
12//!
13//! From the periodogram, one can derive either the **spectral density** or the **power spectrum**.
14//! Both differs with respect to the scaling of the periodogram.
15//! For the **spectral density**, the periodogram is divided by the product of the sampling frequency with the sum of the squared window samples.
16//! For the **power spectrum**, the periodogram is divided by the square of the sum of the window samples.
17//!
18//! The Welch algorithm is implemented in the [Welch] structure.
19//! For convenience, 2 new types (that encapsulates [Welch]) are provided, [SpectralDensity] and [PowerSpectrum], to compute the **spectral density** and the **power spectrum**, respectively.
20//! [SpectralDensity] uses a [Hann] window whereas [PowerSpectrum] does not use any window.
21//!
22//! Custom windows can be used with [Welch] if they implement the [Window] trait.
23//! The signal is either a [single](f32) or [double](f64) floating point array.
24//!
25//! ## Examples
26//! ### Power spectrum
27//!```
28//!use rand::prelude::*;
29//!use rand_distr::StandardNormal;
30//!use std::time::Instant;
31//!use welch_sde::{Build, PowerSpectrum};
32//!
33//!fn main() {
34//! let n = 1e6 as usize;
35//! let signal: Vec<f64> = (0..n)
36//! .map(|_| 1.234_f64.sqrt() * thread_rng().sample::<f64, StandardNormal>(StandardNormal))
37//! .collect();
38//! {
39//! let mean = signal.iter().cloned().sum::<f64>() / n as f64;
40//! let variance = signal.iter().map(|s| *s - mean).map(|x| x * x).sum::<f64>() / n as f64;
41//! println!("Signal variance: {:.3}", variance);
42//! }
43//!
44//! let welch: PowerSpectrum<f64> = PowerSpectrum::builder(&signal).build();
45//! println!("{}", welch);
46//!
47//! let now = Instant::now();
48//! let ps = welch.periodogram();
49//! println!(
50//! "Power spectrum estimated in {}ms",
51//! now.elapsed().as_millis()
52//! );
53//! {
54//! let variance = 2. * ps.iter().sum::<f64>();
55//! println!("Signal variance from power spectrum: {:.3}", variance);
56//! }
57//!}
58//!```
59//! ### Spectral density
60//! ```
61//!use rand::prelude::*;
62//!use rand_distr::StandardNormal;
63//!use std::time::Instant;
64//!use welch_sde::{SpectralDensity, Build};
65//!
66//!fn main() {
67//! let n = 1e5 as usize;
68//! let fs = 10e3_f64;
69//! let amp = 2. * 2f64.sqrt();
70//! let freq = 1550f64;
71//! let noise_power = 0.001 * fs / 2.;
72//! let sigma = noise_power.sqrt();
73//! let signal: Vec<f64> = (0..n)
74//! .map(|i| i as f64 / fs)
75//! .map(|t| {
76//! amp * (2. * std::f64::consts::PI * freq * t).sin()
77//! + thread_rng().sample::<f64, StandardNormal>(StandardNormal) * sigma
78//! })
79//! .collect();
80//!
81//! let welch: SpectralDensity<f64> =
82//! SpectralDensity::<f64>::builder(&signal, fs).build();
83//! println!("{}", welch);
84//! let now = Instant::now();
85//! let sd = welch.periodogram();
86//! println!(
87//! "Spectral density estimated in {}ms",
88//! now.elapsed().as_millis()
89//! );
90//! let noise_floor = sd.iter().cloned().sum::<f64>() / sd.len() as f64;
91//! println!("Noise floor: {:.3}", noise_floor);
92//!
93//! let _: complot::LinLog = (
94//! sd.frequency()
95//! .into_iter()
96//! .zip(&(*sd))
97//! .map(|(x, &y)| (x, vec![y])),
98//! complot::complot!(
99//! "spectral_density.png",
100//! xlabel = "Frequency [Hz]",
101//! ylabel = "Spectral density [s^2/Hz]"
102//! ),
103//! )
104//! .into();
105//!}
106//!```
107
108mod builder;
109mod periodogram;
110mod power_spectrum;
111mod spectral_density;
112mod welch;
113mod window;
114pub use builder::Builder;
115use num_traits::Float;
116pub use periodogram::{Periodogram, PowerSpectrumPeriodogram, SpectralDensityPeriodogram};
117pub use power_spectrum::PowerSpectrum;
118use rustfft::FftNum;
119pub use spectral_density::SpectralDensity;
120pub use welch::Welch;
121pub use window::{Hann, One, Window};
122
123/// The trait the signal type `T` must implement
124pub trait Signal:
125 Float + FftNum + std::iter::Sum + std::ops::SubAssign + std::ops::AddAssign
126{
127}
128impl Signal for f64 {}
129impl Signal for f32 {}
130
131/// [Builder] trait
132pub trait Build<T: Signal, W: Window<T>, E> {
133 /// Returns a struct `E` initialized according to the [Builder] settings
134 fn build(&self) -> E;
135}