use nonstdfloat::f128;
use num_traits::{Float, ToPrimitive, Zero};
use strafe_type::{FloatConstraint, Positive64, Rational64, Real64};
use crate::{
distribution::{beta::dbeta, pois::dpois_raw},
traits::DPQ,
};
pub fn dnbeta<
RE: Into<Real64>,
RA1: Into<Rational64>,
RA2: Into<Rational64>,
P: Into<Positive64>,
>(
x: RE,
a: RA1,
b: RA2,
ncp: P,
log: bool,
) -> Real64 {
let x = x.into().unwrap();
let a = a.into().unwrap();
let b = b.into().unwrap();
let ncp = ncp.into().unwrap();
static eps: f64 = 1.0e-15;
let mut kMax = 0.0;
let mut k = 0.0;
let mut ncp2 = 0.0;
let mut dx2 = 0.0;
let mut d = 0.0;
let mut D = 0.0;
let mut sum = f128::zero();
let mut term = f128::zero();
let mut p_k = f128::zero();
let mut q = f128::zero();
if !a.is_finite() || !b.is_finite() || !ncp.is_finite() {
return f64::nan().into();
}
if !(0.0..=1.0).contains(&x) {
return f64::d_0(log).into();
}
if ncp == 0.0 {
return dbeta(x, a, b, log);
}
ncp2 = 0.5 * ncp;
dx2 = ncp2 * x;
d = (dx2 - a - 1.0) / 2.0;
D = d * d + dx2 * (a + b) - a;
if D <= 0.0 {
kMax = 0.0
} else {
D = (d + D.sqrt()).ceil();
kMax = if D > 0.0 { D.trunc() } else { 0.0 }
}
term = f128::new(dbeta(x, a + kMax, b, true));
p_k = f128::new(dpois_raw(kMax, ncp2, true));
if x == 0.0 || !term.is_finite() || !p_k.to_f64().unwrap().is_finite() {
return (p_k + term).to_f64().unwrap().d_exp(log).into();
}
p_k += term;
term = f128::new(1.0);
sum = term;
k = kMax;
while k > 0.0 && term > sum * f128::new(eps) {
k -= 1.0;
q = f128::new((k + 1.0) * (k + a) / (k + a + b) / dx2);
term *= q;
sum += term
}
term = f128::new(1.0);
k = kMax;
loop {
q = f128::new(dx2 * (k + a + b) / (k + a) / (k + 1.0));
k += 1.0;
term *= q;
sum += term;
if !(term > sum * f128::new(eps)) {
break;
}
}
(p_k + sum.ln()).to_f64().unwrap().d_exp(log).into()
}