use super::*;
#[test]
fn a_perfect_prediction_correlates_at_one() {
let obs = [0.0f32, 0.0, 3.0, 1.0, 40.0, 0.0, 7.0];
let pred = obs;
assert!((spearman(&obs, &pred) - 1.0).abs() < 1e-5);
assert!((pearson_log1p(&obs, &pred) - 1.0).abs() < 1e-5);
}
#[test]
fn spearman_ignores_a_monotone_distortion_that_pearson_sees() {
let obs = [0.0f32, 1.0, 2.0, 3.0, 4.0, 5.0];
let pred = [0.0f32, 0.1, 0.2, 0.3, 0.4, 5000.0];
assert!(
(spearman(&obs, &pred) - 1.0).abs() < 1e-5,
"ranks are identical"
);
let r = pearson_log1p(&obs, &pred);
assert!(r < 0.7, "log-scale distortion should show: r = {r}");
}
#[test]
fn a_reversed_prediction_correlates_at_minus_one() {
let obs = [1.0f32, 2.0, 3.0, 4.0];
let pred = [4.0f32, 3.0, 2.0, 1.0];
assert!((spearman(&obs, &pred) + 1.0).abs() < 1e-5);
}
#[test]
fn tied_zeros_share_an_averaged_rank() {
let v = [0.0f32, 0.0, 0.0, 5.0];
let r = average_ranks(&v);
assert_eq!(r[0], r[1]);
assert_eq!(r[1], r[2]);
assert!(
(r[0] - 2.0).abs() < 1e-9,
"three tied firsts average to 2, got {}",
r[0]
);
assert!((r[3] - 4.0).abs() < 1e-9);
let shuffled = [5.0f32, 0.0, 0.0, 0.0];
let rs = average_ranks(&shuffled);
assert!((rs[0] - 4.0).abs() < 1e-9);
assert!((rs[1] - 2.0).abs() < 1e-9);
}
#[test]
fn a_constant_vector_gives_nan_not_zero() {
let obs = [0.0f32; 5];
let pred = [1.0f32, 2.0, 3.0, 4.0, 5.0];
assert!(spearman(&obs, &pred).is_nan());
assert!(pearson_log1p(&obs, &pred).is_nan());
}
#[test]
fn a_single_gene_cannot_be_correlated() {
assert!(spearman(&[1.0], &[1.0]).is_nan());
assert!(pearson_log1p(&[1.0], &[1.0]).is_nan());
}
#[test]
fn pearson_log1p_is_not_scale_invariant() {
let obs = [0.0f32, 0.0, 0.0, 1.0, 2.0, 0.0, 5.0, 40.0];
let total: f32 = obs.iter().sum();
let comp: Vec<f32> = obs.iter().map(|o| o / total).collect();
let on_counts: Vec<f32> = comp.iter().map(|c| c * total).collect();
assert!(
(pearson_log1p(&obs, &on_counts) - 1.0).abs() < 1e-4,
"a proportional prediction on the count scale must score 1"
);
let as_rate = pearson_log1p(&obs, &comp);
assert!(
as_rate < 0.95,
"the same prediction as a composition must score visibly lower, got {as_rate}"
);
assert!((spearman(&obs, &comp) - 1.0).abs() < 1e-6);
}
#[test]
fn agreement_from_rate_puts_a_proportional_prediction_at_one() {
let obs = [0.0f32, 0.0, 1.0, 2.0, 5.0, 40.0];
for scale in [1.0f32, 1e-4, 7.0, 1e5] {
let rate: Vec<f32> = obs.iter().map(|o| o * scale + 1e-9).collect();
let a = agreement_from_rate(&obs, &rate);
assert!(
(a.pearson_log1p - 1.0).abs() < 1e-3,
"scale {scale}: {}",
a.pearson_log1p
);
}
}
#[test]
fn the_log_rate_entry_point_agrees_with_the_linear_one() {
let obs = [0.0f32, 3.0, 1.0, 9.0];
let rate = [0.5f32, 4.0, 1.0, 12.0];
let log_rate: Vec<f32> = rate.iter().map(|r| r.ln()).collect();
let a = agreement_from_rate(&obs, &rate);
let b = agreement_from_log_rate(&obs, &log_rate);
assert!((a.pearson_log1p - b.pearson_log1p).abs() < 1e-4);
assert!((a.spearman - b.spearman).abs() < 1e-6);
}
#[test]
fn a_huge_log_rate_does_not_overflow() {
let obs = [1.0f32, 2.0, 3.0];
let a = agreement_from_log_rate(&obs, &[300.0, 301.0, 302.0]);
assert!(a.pearson_log1p.is_finite() && a.spearman.is_finite());
}
#[test]
fn the_probability_floor_and_the_log_floor_agree() {
assert!((f64::from(PROB_FLOOR).ln() - LOG_PROB_FLOOR).abs() < 1e-6);
}