use minarrow::Vec64;
use simd_kernels::kernels::aggregate::{
pair_stats_f64, stat_moments_f64, sum_f64_raw, sum_squares,
};
#[test]
fn test_compensated_sum_large_small_interleave() {
let n = 1000;
let mut data = Vec64::with_capacity(3 * n);
for _ in 0..n {
data.push(1e15);
}
for _ in 0..n {
data.push(-1e15);
}
for _ in 0..n {
data.push(1.0);
}
let (result, count) = sum_f64_raw(&data, None, None);
assert_eq!(count, 3 * n);
let expected = n as f64;
assert!(
(result - expected).abs() < 1e-6,
"Large-small interleave: expected {expected}, got {result} (error = {})",
(result - expected).abs()
);
}
#[test]
fn test_compensated_sum_alternating_sign() {
let mut data = Vec64::with_capacity(20000);
for _ in 0..10000 {
data.push(1e15);
data.push(-1e15 + 1.0);
}
let (result, count) = sum_f64_raw(&data, None, None);
assert_eq!(count, 20000);
assert!(
(result - 10000.0).abs() < 1e-6,
"Alternating sign: expected 10000.0, got {result} (error = {})",
(result - 10000.0).abs()
);
}
#[test]
fn test_compensated_sum_harmonic() {
let data: Vec64<f64> = (1..=1_000_000u64).map(|i| 1.0 / i as f64).collect();
let (result, count) = sum_f64_raw(&data, None, None);
assert_eq!(count, 1_000_000);
let reference = 14.392726722864989;
assert!(
(result - reference).abs() < 1e-10,
"Harmonic series: expected ~{reference}, got {result} (error = {})",
(result - reference).abs()
);
}
#[test]
fn test_compensated_stat_moments() {
let data: Vec64<f64> = (1..=100u64).map(|k| 1e8 + k as f64).collect();
let (sum, sum2, count) = stat_moments_f64(&data, None, None);
assert_eq!(count, 100);
let expected_sum = 10_000_005_050.0;
assert!(
(sum - expected_sum).abs() < 1e-4,
"stat_moments sum: expected {expected_sum}, got {sum} (error = {})",
(sum - expected_sum).abs()
);
let expected_sum2: f64 = (1..=100u64)
.map(|k| {
let val = 1e8 + k as f64;
val * val
})
.fold((0.0f64, 0.0f64), |(s, c), x| {
let t = s + x;
let new_c = if s.abs() >= x.abs() {
c + (s - t) + x
} else {
c + (x - t) + s
};
(t, new_c)
})
.let_it(|(s, c)| s + c);
let rel_err = ((sum2 - expected_sum2) / expected_sum2).abs();
assert!(
rel_err < 1e-10,
"stat_moments sum2: expected {expected_sum2}, got {sum2} (relative error = {rel_err})"
);
}
trait LetIt {
fn let_it<R>(self, f: impl FnOnce(Self) -> R) -> R
where
Self: Sized;
}
impl<T> LetIt for T {
fn let_it<R>(self, f: impl FnOnce(Self) -> R) -> R {
f(self)
}
}
#[test]
fn test_compensated_pair_stats() {
let xs: Vec64<f64> = (1..=100u64).map(|k| 1e8 + k as f64).collect();
let ys: Vec64<f64> = (1..=100u64).map(|k| 2e8 + k as f64).collect();
let ps = pair_stats_f64(&xs, &ys, None, None);
assert_eq!(ps.n, 100);
let expected_sx = 10_000_005_050.0;
assert!(
(ps.sx - expected_sx).abs() < 1e-4,
"pair_stats sx: expected {expected_sx}, got {} (error = {})",
ps.sx,
(ps.sx - expected_sx).abs()
);
let expected_sy = 20_000_005_050.0;
assert!(
(ps.sy - expected_sy).abs() < 1e-4,
"pair_stats sy: expected {expected_sy}, got {} (error = {})",
ps.sy,
(ps.sy - expected_sy).abs()
);
let expected_sxy = neumaier_fold((1..=100u64).map(|k| {
let x = 1e8 + k as f64;
let y = 2e8 + k as f64;
x * y
}));
let rel_err_sxy = ((ps.sxy - expected_sxy) / expected_sxy).abs();
assert!(
rel_err_sxy < 1e-10,
"pair_stats sxy: expected {expected_sxy}, got {} (relative error = {rel_err_sxy})",
ps.sxy
);
let expected_sx2 = neumaier_fold((1..=100u64).map(|k| {
let x = 1e8 + k as f64;
x * x
}));
let rel_err_sx2 = ((ps.sx2 - expected_sx2) / expected_sx2).abs();
assert!(
rel_err_sx2 < 1e-10,
"pair_stats sx2: expected {expected_sx2}, got {} (relative error = {rel_err_sx2})",
ps.sx2
);
let expected_sy2 = neumaier_fold((1..=100u64).map(|k| {
let y = 2e8 + k as f64;
y * y
}));
let rel_err_sy2 = ((ps.sy2 - expected_sy2) / expected_sy2).abs();
assert!(
rel_err_sy2 < 1e-10,
"pair_stats sy2: expected {expected_sy2}, got {} (relative error = {rel_err_sy2})",
ps.sy2
);
}
fn neumaier_fold(iter: impl Iterator<Item = f64>) -> f64 {
let mut sum = 0.0f64;
let mut comp = 0.0f64;
for x in iter {
let t = sum + x;
if t.is_finite() {
if sum.abs() >= x.abs() {
comp += (sum - t) + x;
} else {
comp += (x - t) + sum;
}
}
sum = t;
}
sum + comp
}
#[test]
fn test_compensated_sum_squares() {
let data: Vec64<f64> = (1..=1000u64).map(|k| 1e8 + k as f64).collect();
let result = sum_squares(&data, None, None);
let expected = neumaier_fold((1..=1000u64).map(|k| {
let v = 1e8 + k as f64;
v * v
}));
let rel_err = ((result - expected) / expected).abs();
assert!(
rel_err < 1e-10,
"sum_squares: expected {expected}, got {result} (relative error = {rel_err})"
);
}