use crate::config::*;
use ndarray::parallel::prelude::*;
use num::pow::Pow;
use reinterpret::reinterpret_slice;
use std::sync::Arc;
use super::fft::FFT;
use super::window::*;
use std::mem::MaybeUninit;
use realfft::{RealFftPlanner, RealToComplex};
pub type CPSResult = Array3<Cflt>;
pub trait CrossPowerSpecra {
fn ap(&self, ch: usize) -> Array1<Flt>;
fn tf(&self, chi: usize, chj: usize, chRef: Option<usize>) -> Array1<Cflt>;
}
impl CrossPowerSpecra for CPSResult {
fn ap(&self, ch: usize) -> Array1<Flt> {
self.slice(s![.., ch, ch]).mapv(|f| f.re)
}
fn tf(&self, chi: usize, chj: usize, chRef: Option<usize>) -> Array1<Cflt> {
match chRef {
None => {
let cij = self.slice(s![.., chi, chj]);
let cii = self.slice(s![.., chi, chi]);
let cjj = self.slice(s![.., chj, chj]);
Zip::from(cij)
.and(cii)
.and(cjj)
.par_map_collect(|cij, cii, cjj| 0.5 * (cij.conj() / cii + cjj / cij))
}
Some(chr) => {
let cir = self.slice(s![.., chi, chr]);
let cjr = self.slice(s![.., chj, chr]);
Zip::from(cir)
.and(cjr)
.par_map_collect(|cir, cjr| cjr / cir)
}
}
}
}
#[derive(Debug)]
pub struct PowerSpectra {
pub window_normalized: Window,
ffts: Vec<FFT>,
timedata: Array2<Flt>,
freqdata: Array2<Cflt>,
}
impl PowerSpectra {
pub fn nfft(&self) -> usize {
self.window_normalized.win.len()
}
pub fn newFromWindow(mut window: Window) -> PowerSpectra {
let nfft = window.win.len();
let win_pwr = window.win.mapv(|w| w.powi(2)).sum() / (nfft as Flt);
let sqrt_win_pwr = Flt::sqrt(win_pwr);
window.win.mapv_inplace(|v| v / sqrt_win_pwr);
assert!(nfft > 0);
assert!(nfft % 2 == 0);
let mut planner = RealFftPlanner::<Flt>::new();
let fft = planner.plan_fft_forward(nfft);
let Fft = FFT::new(fft);
PowerSpectra {
window_normalized: window,
ffts: vec![Fft],
timedata: Array2::zeros((nfft, 1)),
freqdata: Array2::zeros((nfft / 2 + 1, 1)),
}
}
fn compute_ffts<'a>(&'a mut self, timedata: ArrayView2<Flt>) -> ArrayView2<'a, Cflt> {
assert!(timedata.nrows() > 0);
let (n, nch) = timedata.dim();
let nfft = self.nfft();
assert!(n == nfft);
while nch > self.ffts.len() {
self.ffts
.push(self.ffts.last().expect("FFT's should not be empty").clone());
self.freqdata
.push_column(Ccol::from_vec(vec![Cflt::new(0., 0.); nfft / 2 + 1]).view())
.unwrap();
self.timedata.push_column(Dcol::zeros(nfft).view()).unwrap();
}
assert!(n == self.nfft());
assert!(n == self.window_normalized.win.len());
Zip::from(timedata.axis_iter(Axis(1)))
.and(self.timedata.axis_iter_mut(Axis(1)))
.and(&mut self.ffts)
.and(self.freqdata.axis_iter_mut(Axis(1)))
.par_for_each(|time_in, mut time_tmp_storage, fft, mut freq| {
let DC = time_in.mean().unwrap();
azip!((t in &mut time_tmp_storage, &tin in time_in, &win in &self.window_normalized.win) {
*t=(tin-DC)*win});
fft.process(&time_tmp_storage, &mut freq);
freq[0] = DC + 0. * I;
});
self.freqdata.view()
}
pub fn compute<'a, T>(&mut self, tdata: T) -> CPSResult
where
T: AsArray<'a, Flt, Ix2>,
{
let tdata = tdata.into();
let nfft = self.nfft();
let clen = nfft / 2 + 1;
if tdata.nrows() != nfft {
panic!("Invalid timedata length! Should be equal to nfft={nfft}");
}
let nchannels = tdata.ncols();
let fd = self.compute_ffts(tdata);
let fdconj = fd.mapv(|c| c.conj());
let result = Array3::uninit((clen, nchannels, nchannels));
let mut result: Array3<Cflt> = unsafe { result.assume_init() };
Zip::from(result.axis_iter_mut(Axis(1)))
.and(fd.axis_iter(Axis(1)))
.par_for_each(|mut out, chi| {
Zip::from(out.axis_iter_mut(Axis(1)))
.and(fdconj.axis_iter(Axis(1)))
.for_each(|mut out, chj| {
Zip::from(&mut out)
.and(chi)
.and(chj)
.for_each(|out, chi, chjc| {
*out = 0.5 * chi * chjc;
});
out[0] *= 2.;
out[clen - 1] *= 2.;
});
});
result
}
}
#[cfg(test)]
mod test {
use approx::{abs_diff_eq, assert_relative_eq, assert_ulps_eq, ulps_eq};
use num::complex::ComplexFloat;
fn generate_sinewave(nfft: usize, order: usize) -> Dcol {
Dcol::from_iter(
(0..nfft).map(|i| Flt::sin(i as Flt / (nfft) as Flt * order as Flt * 2. * pi)),
)
}
fn generate_cosinewave(nfft: usize, order: usize) -> Dcol {
Dcol::from_iter(
(0..nfft).map(|i| Flt::cos(i as Flt / (nfft) as Flt * order as Flt * 2. * pi)),
)
}
use crate::math::randNormal;
use super::*;
#[test]
fn test_fft_DC() {
const nfft: usize = 10;
let rect = Window::new(WindowType::Rect, nfft);
let mut ps = PowerSpectra::newFromWindow(rect);
let td = Dmat::ones((nfft, 1));
let fd = ps.compute_ffts(td.view());
assert_relative_eq!(fd[(0, 0)].re, 1.);
assert_relative_eq!(fd[(0, 0)].im, 0.);
let abs_fneq0 = fd.slice(s![1.., 0]).sum();
assert_relative_eq!(abs_fneq0.re, 0.);
assert_relative_eq!(abs_fneq0.im, 0.);
}
#[test]
fn test_fft_AC() {
const nfft: usize = 256;
let rect = Window::new(WindowType::Rect, nfft);
let mut ps = PowerSpectra::newFromWindow(rect);
let mut t: Dmat = Dmat::default((nfft, 0));
t.push_column(generate_sinewave(nfft, 1).view()).unwrap();
let fd = ps.compute_ffts(t.view());
assert_relative_eq!(fd[(0, 0)].re, 0., epsilon = Flt::EPSILON * nfft as Flt);
assert_relative_eq!(fd[(0, 0)].im, 0., epsilon = Flt::EPSILON * nfft as Flt);
assert_relative_eq!(fd[(1, 0)].re, 0., epsilon = Flt::EPSILON * nfft as Flt);
assert_ulps_eq!(fd[(1, 0)].im, -1., epsilon = Flt::EPSILON * nfft as Flt);
let sum_higher_freqs_abs = Cflt::abs(fd.slice(s![2.., 0]).sum());
assert_ulps_eq!(
sum_higher_freqs_abs,
0.,
epsilon = Flt::EPSILON * nfft as Flt
);
}
#[test]
fn test_ps_scale() {
const nfft: usize = 124;
let rect = Window::new(WindowType::Rect, nfft);
let mut ps = PowerSpectra::newFromWindow(rect);
let mut t: Dmat = Dmat::default((nfft, 0));
t.push_column(generate_cosinewave(nfft, 1).view()).unwrap();
let dc_component = 0.25;
let dc_power = dc_component.pow(2);
t.mapv_inplace(|t| t + dc_component);
let power = ps.compute(t.view());
assert_relative_eq!(
power[(0, 0, 0)].re,
dc_power,
epsilon = Flt::EPSILON * nfft as Flt
);
assert_relative_eq!(
power[(1, 0, 0)].re,
0.5,
epsilon = Flt::EPSILON * nfft as Flt
);
assert_relative_eq!(
power[(1, 0, 0)].im,
0.0,
epsilon = Flt::EPSILON * nfft as Flt
);
}
#[test]
fn test_parseval() {
const nfft: usize = 512;
let rect = Window::new(WindowType::Rect, nfft);
let mut ps = PowerSpectra::newFromWindow(rect);
let t: Dmat = randNormal((nfft, 1));
let tavg = t.sum() / (nfft as Flt);
let t_dc_power = tavg.powi(2);
let signal_pwr = t.mapv(|t| t.powi(2)).sum() / (nfft as Flt);
let power = ps.compute(t.view());
let fpower = power.sum().abs();
assert_ulps_eq!(
t_dc_power,
power[(0, 0, 0)].abs(),
epsilon = Flt::EPSILON * (nfft as Flt).powi(2)
);
assert_ulps_eq!(
signal_pwr,
fpower,
epsilon = Flt::EPSILON * (nfft as Flt).powi(2)
);
}
#[test]
fn test_parseval_with_window() {
const nfft: usize = 2usize.pow(20);
let window = Window::new(WindowType::Hann, nfft);
let mut ps = PowerSpectra::newFromWindow(window);
let t: Dmat = randNormal((nfft, 1));
let tavg = t.sum() / (nfft as Flt);
let t_dc_power = tavg.powi(2);
let signal_pwr = t.mapv(|t| t.powi(2)).sum() / (nfft as Flt);
let power = ps.compute(t.view());
let fpower = power.sum().abs();
assert_ulps_eq!(
t_dc_power,
power[(0, 0, 0)].abs(),
epsilon = Flt::EPSILON * (nfft as Flt).powi(2)
);
assert_ulps_eq!(signal_pwr, fpower, epsilon = 2e-2);
}
}