use numrs2::array::Array;
use numrs2::math;
use scirs2_core::parallel_ops::ThreadPoolBuilder;
const N: usize = 50_000;
fn data_f64() -> Vec<f64> {
(0..N).map(|i| (i as f64) * 0.5 - 12345.0).collect()
}
fn data_f32() -> Vec<f32> {
(0..N).map(|i| (i as f32) * 0.5 - 12345.0).collect()
}
fn run_with_threads<R>(n: usize, f: impl FnOnce() -> R + Send) -> R
where
R: Send,
{
let pool = ThreadPoolBuilder::new()
.num_threads(n)
.build()
.unwrap_or_else(|e| panic!("failed to build a {n}-thread pool: {e}"));
pool.install(f)
}
#[test]
fn var_f64_bit_identical_under_1_vs_8_actual_threads() {
let arr = Array::from_vec(data_f64());
let got_1 = run_with_threads(1, || {
math::var(&arr, None, 0, false)
.expect("var should succeed")
.to_vec()[0]
});
let got_8 = run_with_threads(8, || {
math::var(&arr, None, 0, false)
.expect("var should succeed")
.to_vec()[0]
});
assert_eq!(
got_1.to_bits(),
got_8.to_bits(),
"var_f64 must be bit-for-bit identical under 1 vs 8 real rayon threads (the fixed-chunk \
kernel's whole determinism guarantee) -- got {got_1} ({:#x}) under 1 thread, {got_8} \
({:#x}) under 8 threads",
got_1.to_bits(),
got_8.to_bits()
);
}
#[test]
fn var_f32_bit_identical_under_1_vs_8_actual_threads() {
let arr = Array::from_vec(data_f32());
let got_1 = run_with_threads(1, || {
math::var(&arr, None, 0, false)
.expect("var should succeed")
.to_vec()[0]
});
let got_8 = run_with_threads(8, || {
math::var(&arr, None, 0, false)
.expect("var should succeed")
.to_vec()[0]
});
assert_eq!(
got_1.to_bits(),
got_8.to_bits(),
"var_f32 must be bit-for-bit identical under 1 vs 8 real rayon threads -- got {got_1} \
({:#x}) under 1 thread, {got_8} ({:#x}) under 8 threads",
got_1.to_bits(),
got_8.to_bits()
);
}
#[test]
fn sum_f64_is_deterministic_across_thread_counts() {
let arr = Array::from_vec(data_f64());
let got = math::sum(&arr, None, false)
.expect("sum should succeed")
.to_vec()[0];
assert_eq!(
got.to_bits(),
4710066088337997824u64,
"sum_f64 bit pattern changed -- got {got} ({:#x}); re-run under both \
RAYON_NUM_THREADS=1 and RAYON_NUM_THREADS=8 before updating this pin, to confirm \
a genuine algorithm change rather than a thread-count-dependent regression",
got.to_bits()
);
}
#[test]
fn mean_f64_is_deterministic_across_thread_counts() {
let arr = Array::from_vec(data_f64());
let got = math::mean(&arr, None, false)
.expect("mean should succeed")
.to_vec()[0];
assert_eq!(
got.to_bits(),
4639648798144987136u64,
"mean_f64 bit pattern changed -- got {got} ({:#x})",
got.to_bits()
);
}
#[test]
fn var_f64_ddof0_is_deterministic_across_thread_counts() {
let arr = Array::from_vec(data_f64());
let got = math::var(&arr, None, 0, false)
.expect("var should succeed")
.to_vec()[0];
assert_eq!(
got.to_bits(),
4722259316520779776u64,
"var_f64 (ddof=0) bit pattern changed -- got {got} ({:#x})",
got.to_bits()
);
}
#[test]
fn std_f64_ddof0_is_deterministic_across_thread_counts() {
let arr = Array::from_vec(data_f64());
let got = math::std(&arr, None, 0, false)
.expect("std should succeed")
.to_vec()[0];
assert_eq!(
got.to_bits(),
4664657056377925821u64,
"std_f64 (ddof=0) bit pattern changed -- got {got} ({:#x})",
got.to_bits()
);
}
#[test]
fn sum_f32_is_deterministic_across_thread_counts() {
let arr = Array::from_vec(data_f32());
let got = math::sum(&arr, None, false)
.expect("sum should succeed")
.to_vec()[0];
assert_eq!(
got.to_bits(),
1256988984u32,
"sum_f32 bit pattern changed -- got {got} ({:#x})",
got.to_bits()
);
}
#[test]
fn pinned_values_match_naive_computation() {
let data = data_f64();
let naive_sum: f64 = data.iter().sum();
let naive_mean = naive_sum / data.len() as f64;
let naive_ssd: f64 = data
.iter()
.map(|&x| (x - naive_mean) * (x - naive_mean))
.sum();
let naive_var = naive_ssd / data.len() as f64;
let naive_std = naive_var.sqrt();
let arr = Array::from_vec(data);
let got_sum = math::sum(&arr, None, false).expect("sum").to_vec()[0];
let got_mean = math::mean(&arr, None, false).expect("mean").to_vec()[0];
let got_var = math::var(&arr, None, 0, false).expect("var").to_vec()[0];
let got_std = math::std(&arr, None, 0, false).expect("std").to_vec()[0];
assert!((got_sum - naive_sum).abs() / naive_sum.abs() < 1e-9);
assert!((got_mean - naive_mean).abs() / naive_mean.abs() < 1e-9);
assert!((got_var - naive_var).abs() / naive_var.abs() < 1e-9);
assert!((got_std - naive_std).abs() / naive_std.abs() < 1e-9);
}