use crate::C64;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Moments {
pub m20: C64,
pub m21: f64,
pub m40: C64,
pub m41: C64,
pub m42: f64,
}
impl Moments {
pub fn sample(x: &[C64]) -> Moments {
let n = x.len() as f64;
let s = Moments::sums(x.iter().map(|&v| (v, 1.0)));
Moments { m20: s.m20 / n, m21: s.m21 / n, m40: s.m40 / n, m41: s.m41 / n, m42: s.m42 / n }
}
pub fn of_distribution(x: &[C64], p: &[f64]) -> Moments {
Moments::sums(x.iter().copied().zip(p.iter().copied()))
}
fn sums(rw: impl Iterator<Item = (C64, f64)>) -> Moments {
let zero = C64::new(0.0, 0.0);
let mut m = Moments { m20: zero, m21: 0.0, m40: zero, m41: zero, m42: 0.0 };
for (x, w) in rw {
let a = x.norm_sqr();
let x2 = x * x;
m.m20 += x2 * w;
m.m21 += a * w;
m.m40 += x2 * x2 * w;
m.m41 += x2 * a * w;
m.m42 += a * a * w;
}
m
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Correlation {
pub cross: C64,
pub energy_a: f64,
pub energy_b: f64,
}
impl Correlation {
pub fn between(a: &[C64], b: &[C64]) -> Correlation {
let mut c = Correlation { cross: C64::new(0.0, 0.0), energy_a: 0.0, energy_b: 0.0 };
for (u, v) in a.iter().zip(b) {
c.cross += u * v.conj();
c.energy_a += u.norm_sqr();
c.energy_b += v.norm_sqr();
}
c
}
pub fn rho(&self) -> f64 {
if self.energy_a > 0.0 && self.energy_b > 0.0 {
self.cross.norm() / (self.energy_a * self.energy_b).sqrt()
} else {
0.0
}
}
pub fn rho_squared(&self) -> f64 {
if self.energy_a > 0.0 && self.energy_b > 0.0 {
self.cross.norm_sqr() / (self.energy_a * self.energy_b)
} else {
0.0
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_moments_of_a_sample_are_those_of_its_uniform_distribution() {
let x: Vec<C64> = (0..7).map(|k| C64::new(f64::from(k) - 3.0, 0.5 * f64::from(k))).collect();
let s = Moments::sample(&x);
let d = Moments::of_distribution(&x, &[1.0 / 7.0; 7]);
assert!((s.m21 - d.m21).abs() < 1e-14 && (s.m42 - d.m42).abs() < 1e-12);
assert!((s.m40 - d.m40).norm() < 1e-12 && (s.m41 - d.m41).norm() < 1e-12 && (s.m20 - d.m20).norm() < 1e-14);
let e = |k: i32| x.iter().map(|v| v.norm_sqr().powi(k)).sum::<f64>() / 7.0;
assert_eq!((s.m21, s.m42), (e(1), e(2)));
}
#[test]
fn the_correlation_is_one_for_proportional_sequences_and_zero_without_energy() {
let a = [C64::new(1.0, 2.0), C64::new(-0.5, 0.1), C64::new(0.0, -1.0)];
let b: Vec<C64> = a.iter().map(|v| v * C64::new(-1.5, 0.7)).collect();
let c = Correlation::between(&a, &b);
assert!((c.rho() - 1.0).abs() < 1e-15 && (c.rho_squared() - 1.0).abs() < 1e-15);
let z = Correlation::between(&a, &[C64::new(0.0, 0.0); 3]);
assert_eq!((z.rho(), z.rho_squared()), (0.0, 0.0));
}
}