use crate::tests_stat::Alternative;
#[must_use]
pub fn rank_sum_distribution(count: usize) -> Vec<f64> {
let max_w = count * (count + 1) / 2;
let mut counts = vec![0.0_f64; max_w + 1];
if let Some(first) = counts.first_mut() {
*first = 1.0;
}
for j in 1..=count {
for w in (j..=max_w).rev() {
let from = counts.get(w - j).copied().unwrap_or(0.0);
if let Some(slot) = counts.get_mut(w) {
*slot += from;
}
}
}
counts
}
#[must_use]
pub fn p_value(r_plus: f64, count: usize, alternative: Alternative) -> f64 {
let dist = rank_sum_distribution(count);
let total: f64 = dist.iter().sum();
let w_idx = r_plus.round();
let le: f64 = dist
.iter()
.enumerate()
.filter(|(w, _)| f64_from_index(*w) <= w_idx)
.map(|(_, &c)| c)
.sum();
let ge: f64 = dist
.iter()
.enumerate()
.filter(|(w, _)| f64_from_index(*w) >= w_idx)
.map(|(_, &c)| c)
.sum();
let p_less = le / total;
let p_greater = ge / total;
match alternative {
Alternative::Less => p_less.clamp(0.0, 1.0),
Alternative::Greater => p_greater.clamp(0.0, 1.0),
Alternative::TwoSided => (2.0 * p_less.min(p_greater)).clamp(0.0, 1.0),
}
}
fn f64_from_index(i: usize) -> f64 {
u32::try_from(i).map_or(f64::INFINITY, f64::from)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn distribution_sums_to_two_pow_n() {
let n = 6;
let dist = rank_sum_distribution(n);
let total: f64 = dist.iter().sum();
assert!((total - 64.0).abs() < 1e-9, "total was {total}");
let max_w = n * (n + 1) / 2; assert_eq!(dist.len(), max_w + 1, "len was {}", dist.len());
for w in 0..=(max_w / 2) {
let lo = dist.get(w).copied().unwrap_or(f64::NAN);
let hi = dist.get(max_w - w).copied().unwrap_or(f64::NAN);
assert!((lo - hi).abs() < 1e-9, "asymmetry at {w}: {lo} vs {hi}");
}
}
#[test]
fn tails_match_enumeration() {
let greater = p_value(8.0, 6, Alternative::Greater);
let less = p_value(8.0, 6, Alternative::Less);
let two = p_value(8.0, 6, Alternative::TwoSided);
assert!((greater - 0.71875).abs() < 1e-12, "greater was {greater}");
assert!((less - 0.34375).abs() < 1e-12, "less was {less}");
assert!((two - 0.6875).abs() < 1e-12, "two was {two}");
}
}