#![cfg(feature = "std")]
#[must_use]
pub fn fisher_combine(p_values: &[f64]) -> f64 {
let mut t = 0.0_f64;
let mut k = 0_usize;
for &p in p_values {
if !p.is_finite() {
continue;
}
let clamped = p.clamp(f64::EPSILON, 1.0);
t += -2.0 * clamped.ln();
k += 1;
}
if k == 0 {
return 1.0;
}
chi_squared_survival_even(k, t)
}
#[must_use]
pub fn chi_squared_survival_even(k: usize, t: f64) -> f64 {
if t <= 0.0 {
return 1.0;
}
if k == 0 {
return 0.0;
}
let y = 0.5 * t;
let mut term = 1.0_f64; let mut sum = 1.0_f64;
let mut compensation = 0.0_f64;
for i in 1..k {
#[allow(clippy::cast_precision_loss)]
let divisor = i as f64;
term *= y / divisor;
let adjusted = term - compensation;
let temp = sum + adjusted;
compensation = (temp - sum) - adjusted;
sum = temp;
}
let q = (-y).exp() * sum;
q.clamp(0.0, 1.0)
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::panic,
clippy::float_cmp,
clippy::cast_precision_loss
)]
mod tests {
use super::*;
#[test]
fn empty_input_returns_one() {
assert_eq!(fisher_combine(&[]), 1.0);
}
#[test]
fn single_p_value_survives_roundtrip_at_boundaries() {
let p = 0.3_f64;
let out = fisher_combine(&[p]);
assert!((out - p).abs() < 1.0e-9, "single-p = {out} expected {p}");
}
#[test]
fn all_ones_yield_p_equal_to_one() {
let out = fisher_combine(&[1.0; 8]);
assert_eq!(out, 1.0);
}
#[test]
fn small_p_values_combine_to_smaller_joint() {
let out = fisher_combine(&[0.001, 0.001, 0.001]);
assert!(out < 0.001);
}
#[test]
fn combining_mixed_evidence_stays_bounded() {
let out = fisher_combine(&[0.0001, 1.0, 1.0, 1.0]);
assert!(out > 0.0);
assert!(out < 1.0);
}
#[test]
fn non_finite_p_clamps_to_unity() {
let out = fisher_combine(&[f64::NAN, 0.1, 0.1]);
let ref_out = fisher_combine(&[0.1, 0.1]);
assert!((out - ref_out).abs() < 1.0e-12);
}
#[test]
fn chi_squared_survival_zero_argument_is_one() {
assert_eq!(chi_squared_survival_even(1, 0.0), 1.0);
assert_eq!(chi_squared_survival_even(8, 0.0), 1.0);
}
#[test]
fn chi_squared_survival_one_dof_closed_form() {
let t: f64 = 3.0;
let expected = (-0.5 * t).exp();
let out = chi_squared_survival_even(1, t);
assert!((out - expected).abs() < 1.0e-12);
}
}