use std::f64::consts::PI;
use eqsolver::integrators::NewtonCotes;
use crate::Error;
struct ImhofParams<'a> {
q: f64,
lambda: &'a [f64],
}
fn theta(u: f64, params: &ImhofParams) -> f64 {
let mut sum_atan = 0.0;
for &lam in params.lambda {
sum_atan += (lam * u).atan();
}
0.5 * sum_atan - 0.5 * params.q * u
}
fn rho(u: f64, params: &ImhofParams) -> f64 {
let mut prod = 1.0;
for &lam in params.lambda {
prod *= (1.0 + lam.powi(2) * u.powi(2)).powf(0.25);
}
prod
}
fn imhof_integrand(u: f64, params: &ImhofParams) -> f64 {
if u.abs() < 1e-9 {
let sum_lam: f64 = params.lambda.iter().sum();
return 0.5 * (sum_lam - params.q);
}
let t = theta(u, params);
let r = rho(u, params);
t.sin() / (u * r)
}
fn transformed_integrand(t: f64, params: &ImhofParams) -> f64 {
if t >= 1.0 - 1e-9 {
return 0.0;
}
let one_minus_t = 1.0 - t;
let u = t / one_minus_t;
let du_dt = 1.0 / one_minus_t.powi(2);
imhof_integrand(u, params) * du_dt
}
pub(crate) fn significance_level(statistic: f64, eigenvalues: &[f64]) -> Result<f64, Error> {
let params = ImhofParams {
q: statistic,
lambda: eigenvalues,
};
let result = NewtonCotes::new(|t| transformed_integrand(t, ¶ms)).integrate(0.0, 1.0)?;
let p_val = 0.5 + result / PI;
Ok(p_val.clamp(0.0, 1.0))
}