use crate::DType;
use numr::error::Result;
use numr::runtime::Runtime;
use numr::tensor::Tensor;
pub trait SpectralAnalysisAlgorithms<R: Runtime<DType = DType>> {
fn welch(&self, x: &Tensor<R>, params: WelchParams<R>) -> Result<WelchResult<R>>;
fn periodogram(
&self,
x: &Tensor<R>,
params: PeriodogramParams<R>,
) -> Result<PeriodogramResult<R>>;
fn csd(&self, x: &Tensor<R>, y: &Tensor<R>, params: WelchParams<R>) -> Result<CsdResult<R>>;
fn coherence(
&self,
x: &Tensor<R>,
y: &Tensor<R>,
params: WelchParams<R>,
) -> Result<CoherenceResult<R>>;
fn lombscargle(
&self,
t: &Tensor<R>,
x: &Tensor<R>,
freqs: &Tensor<R>,
normalize: bool,
) -> Result<Tensor<R>>;
}
#[derive(Debug, Clone)]
pub enum SpectralWindow<R: Runtime<DType = DType>> {
Rectangular,
Hann,
Hamming,
Blackman,
Kaiser(f64),
Custom(Tensor<R>),
}
#[allow(clippy::derivable_impls)]
impl<R: Runtime<DType = DType>> Default for SpectralWindow<R> {
fn default() -> Self {
SpectralWindow::Hann
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum PsdScaling {
#[default]
Density,
Spectrum,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Detrend {
#[default]
None,
Constant,
Linear,
}
#[derive(Debug, Clone)]
pub struct WelchParams<R: Runtime<DType = DType>> {
pub fs: f64,
pub window: SpectralWindow<R>,
pub nperseg: Option<usize>,
pub noverlap: Option<usize>,
pub nfft: Option<usize>,
pub detrend: Detrend,
pub scaling: PsdScaling,
pub onesided: bool,
pub device: R::Device,
}
impl<R: Runtime<DType = DType>> WelchParams<R> {
pub fn new(device: R::Device) -> Self {
Self {
fs: 1.0,
window: SpectralWindow::default(),
nperseg: None,
noverlap: None,
nfft: None,
detrend: Detrend::default(),
scaling: PsdScaling::default(),
onesided: true,
device,
}
}
pub fn with_fs(mut self, fs: f64) -> Self {
self.fs = fs;
self
}
pub fn with_window(mut self, window: SpectralWindow<R>) -> Self {
self.window = window;
self
}
pub fn with_nperseg(mut self, nperseg: usize) -> Self {
self.nperseg = Some(nperseg);
self
}
pub fn with_noverlap(mut self, noverlap: usize) -> Self {
self.noverlap = Some(noverlap);
self
}
pub fn with_nfft(mut self, nfft: usize) -> Self {
self.nfft = Some(nfft);
self
}
}
#[derive(Debug, Clone)]
pub struct PeriodogramParams<R: Runtime<DType = DType>> {
pub fs: f64,
pub window: SpectralWindow<R>,
pub nfft: Option<usize>,
pub detrend: Detrend,
pub scaling: PsdScaling,
pub onesided: bool,
pub device: R::Device,
}
impl<R: Runtime<DType = DType>> PeriodogramParams<R> {
pub fn new(device: R::Device) -> Self {
Self {
fs: 1.0,
window: SpectralWindow::default(),
nfft: None,
detrend: Detrend::default(),
scaling: PsdScaling::default(),
onesided: true,
device,
}
}
pub fn with_fs(mut self, fs: f64) -> Self {
self.fs = fs;
self
}
pub fn with_window(mut self, window: SpectralWindow<R>) -> Self {
self.window = window;
self
}
}
#[derive(Debug, Clone)]
pub struct WelchResult<R: Runtime<DType = DType>> {
pub freqs: Tensor<R>,
pub psd: Tensor<R>,
}
#[derive(Debug, Clone)]
pub struct PeriodogramResult<R: Runtime<DType = DType>> {
pub freqs: Tensor<R>,
pub psd: Tensor<R>,
}
#[derive(Debug, Clone)]
pub struct CsdResult<R: Runtime<DType = DType>> {
pub freqs: Tensor<R>,
pub pxy_real: Tensor<R>,
pub pxy_imag: Tensor<R>,
}
impl<R: Runtime<DType = DType>> CsdResult<R> {
pub fn magnitude(&self) -> Result<Tensor<R>> {
let re: Vec<f64> = self.pxy_real.to_vec();
let im: Vec<f64> = self.pxy_imag.to_vec();
let n = re.len();
let mag: Vec<f64> = re
.iter()
.zip(im.iter())
.map(|(&r, &i)| (r * r + i * i).sqrt())
.collect();
let device = self.pxy_real.device();
Ok(Tensor::from_slice(&mag, &[n], device))
}
pub fn phase(&self) -> Result<Tensor<R>> {
let re: Vec<f64> = self.pxy_real.to_vec();
let im: Vec<f64> = self.pxy_imag.to_vec();
let n = re.len();
let phase: Vec<f64> = re
.iter()
.zip(im.iter())
.map(|(&r, &i)| i.atan2(r))
.collect();
let device = self.pxy_real.device();
Ok(Tensor::from_slice(&phase, &[n], device))
}
}
#[derive(Debug, Clone)]
pub struct CoherenceResult<R: Runtime<DType = DType>> {
pub freqs: Tensor<R>,
pub cxy: Tensor<R>,
}