use solow_distributions::special::lgamma;
use solow_distributions::{norm_cdf, norm_pdf};
const N_INNER: usize = 100;
const N_OUTER: usize = 80;
fn gauss_legendre(n: usize) -> (Vec<f64>, Vec<f64>) {
let mut x = vec![0.0; n];
let mut w = vec![0.0; n];
let m = n.div_ceil(2);
let nf = n as f64;
for i in 0..m {
let mut z = (std::f64::consts::PI * (i as f64 + 0.75) / (nf + 0.5)).cos();
let mut pp = 0.0;
for _ in 0..100 {
let mut p1 = 1.0;
let mut p2 = 0.0;
for j in 0..n {
let p3 = p2;
p2 = p1;
let jf = j as f64;
p1 = ((2.0 * jf + 1.0) * z * p2 - jf * p3) / (jf + 1.0);
}
pp = nf * (z * p1 - p2) / (z * z - 1.0);
let z1 = z;
z = z1 - p1 / pp;
if (z - z1).abs() <= 1e-15 {
break;
}
}
x[i] = -z;
x[n - 1 - i] = z;
let wt = 2.0 / ((1.0 - z * z) * pp * pp);
w[i] = wt;
w[n - 1 - i] = wt;
}
(x, w)
}
#[inline]
fn phi_cdf(z: f64) -> f64 {
norm_cdf(z)
}
fn range_cdf(w: f64, k: f64, nodes: &(Vec<f64>, Vec<f64>)) -> f64 {
if w <= 0.0 {
return 0.0;
}
let a = -8.0;
let b = 8.0 + w;
let half = 0.5 * (b - a);
let mid = 0.5 * (b + a);
let (x, wt) = nodes;
let mut acc = 0.0;
for i in 0..x.len() {
let z = half * x[i] + mid;
let inner = phi_cdf(z) - phi_cdf(z - w);
acc += wt[i] * norm_pdf(z) * inner.powf(k - 1.0);
}
k * half * acc
}
pub fn srange_cdf(q: f64, k: f64, df: f64) -> f64 {
if q <= 0.0 {
return 0.0;
}
let inner = gauss_legendre(N_INNER);
if !df.is_finite() {
return range_cdf(q, k, &inner);
}
let outer = gauss_legendre(N_OUTER);
let logc = (df / 2.0) * df.ln() - (df / 2.0 - 1.0) * std::f64::consts::LN_2 - lgamma(df / 2.0);
let a = 1e-9;
let b = 1.0 + 10.0 / df.sqrt();
let half = 0.5 * (b - a);
let mid = 0.5 * (b + a);
let (x, wt) = &outer;
let mut acc = 0.0;
for i in 0..x.len() {
let u = half * x[i] + mid;
let f_u = (logc + (df - 1.0) * u.ln() - df * u * u / 2.0).exp();
acc += wt[i] * f_u * range_cdf(q * u, k, &inner);
}
let v = half * acc;
v.clamp(0.0, 1.0)
}
pub fn srange_sf(q: f64, k: f64, df: f64) -> f64 {
1.0 - srange_cdf(q, k, df)
}
pub fn srange_ppf(p: f64, k: f64, df: f64) -> f64 {
if p <= 0.0 {
return 0.0;
}
if p >= 1.0 {
return f64::INFINITY;
}
let mut lo = 0.0;
let mut hi = 100.0;
for _ in 0..200 {
let mid = 0.5 * (lo + hi);
if srange_cdf(mid, k, df) < p {
lo = mid;
} else {
hi = mid;
}
if hi - lo <= 1e-12 * (1.0 + hi) {
break;
}
}
0.5 * (lo + hi)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn gauss_legendre_integrates_polynomial() {
let (x, w) = gauss_legendre(2);
let val: f64 = x.iter().zip(&w).map(|(&xi, &wi)| wi * xi * xi).sum();
assert!((val - 2.0 / 3.0).abs() < 1e-12);
}
#[test]
fn cdf_is_monotone_and_bounded() {
let a = srange_cdf(2.0, 3.0, 20.0);
let b = srange_cdf(4.0, 3.0, 20.0);
assert!(a > 0.0 && a < b && b < 1.0);
}
#[test]
fn ppf_inverts_cdf() {
let q = srange_ppf(0.95, 4.0, 30.0);
let p = srange_cdf(q, 4.0, 30.0);
assert!((p - 0.95).abs() < 1e-8);
}
}