use nonstdfloat::f128;
use num_traits::{Float, ToPrimitive, Zero};
use strafe_type::{
FloatConstraint, LogProbability64, Positive64, Probability64, Rational64, Real64,
};
use crate::{
distribution::func::bratio,
func::{lbeta, lgamma},
traits::DPQ,
};
pub fn pnbeta_raw(x: f64, o_x: f64, a: f64, b: f64, ncp: f64) -> f128 {
static errmax: f64 = 1.0e-9;
let itrmax = 10000;
let mut a0 = 0.0;
let mut lBeta = 0.0;
let mut c = 0.0;
let mut errbd = 0.0;
let mut x0 = 0.0;
let mut ans = f128::zero();
let mut ax = f128::zero();
let mut gx = f128::zero();
let mut q = f128::zero();
let mut sumq = f128::zero();
if ncp < 0.0 || a <= 0.0 || b <= 0.0 {
return f128::new(f64::nan());
}
if x < 0.0 || o_x > 1.0 || (x == 0.0 && o_x == 1.0) {
return f128::new(0.0);
}
if x > 1.0 || o_x < 0.0 || (x == 1.0 && o_x == 0.0) {
return f128::new(1.0);
}
c = ncp / 2.0;
x0 = (c - 7.0 * c.sqrt()).max(0.0).floor();
a0 = a + x0;
lBeta = lbeta(a0, b).unwrap();
let mut temp = f64::nan();
let mut w1 = f64::nan();
let mut ierr = 0;
bratio(a0, b, x, o_x, &mut temp, &mut w1, &mut ierr, false);
if ierr != 0 {
panic!("Error in bratio {}", ierr);
}
gx = f128::new(
(a0 * x.ln() + b * (if x < 0.5 { (-x).ln_1p() } else { o_x.ln() }) - lBeta - a0.ln()).exp(),
);
if a0 > a {
q = f128::new((-c + x0 * c.ln() - lgamma(x0 + 1.0).unwrap()).exp())
} else {
q = f128::new((-c).exp())
}
sumq = f128::new(1.0) - q;
ax = q * f128::new(temp);
ans = ax;
let mut j = x0.floor(); loop {
j += 1.0;
temp -= gx.to_f64().unwrap();
gx *= f128::new(x * (a + b + j - 1.0) / (a + j));
q *= f128::new(c / j);
sumq -= q;
ax = f128::new(temp) * q;
ans += ax;
errbd = ((f128::new(temp) - gx) * sumq).to_f64().unwrap();
if !(errbd > errmax && j < itrmax as f64 + x0) {
break;
}
}
if errbd > errmax {
warn!("full precision may not have been achieved in pnbeta");
}
if j >= itrmax as f64 + x0 {
warn!("convergence failed in pnbeta");
}
if ans.is_sign_negative() && ans.to_f64().unwrap().abs() < 1e30 {
f128::zero()
} else {
ans
}
}
pub fn pnbeta2(x: f64, o_x: f64, a: f64, b: f64, ncp: f64, lower_tail: bool, log: bool) -> f64 {
let mut ans = pnbeta_raw(x, o_x, a, b, ncp);
if lower_tail {
if log { ans.ln() } else { ans }.to_f64().unwrap()
} else {
if ans > f128::new(1.0 - 1e-10) {
warn!("full precision may not have been achieved in pnbeta");
}
if ans > f128::new(1.0) {
ans = f128::new(1.0)
}
if log {
(-ans).ln_1p()
} else {
(f128::new(1.0)) - ans
}
.to_f64()
.unwrap()
}
}
pub fn pnbeta<
RE: Into<Real64>,
RA1: Into<Rational64>,
RA2: Into<Rational64>,
P: Into<Positive64>,
>(
x: RE,
a: RA1,
b: RA2,
ncp: P,
lower_tail: bool,
) -> Probability64 {
pnbeta_inner(x, a, b, ncp, lower_tail, false).into()
}
pub fn log_pnbeta<
RE: Into<Real64>,
RA1: Into<Rational64>,
RA2: Into<Rational64>,
P: Into<Positive64>,
>(
x: RE,
a: RA1,
b: RA2,
ncp: P,
lower_tail: bool,
) -> LogProbability64 {
pnbeta_inner(x, a, b, ncp, lower_tail, true).into()
}
fn pnbeta_inner<
RE: Into<Real64>,
RA1: Into<Rational64>,
RA2: Into<Rational64>,
P: Into<Positive64>,
>(
x: RE,
a: RA1,
b: RA2,
ncp: P,
lower_tail: bool,
log: bool,
) -> f64 {
let x = x.into().unwrap();
let a = a.into().unwrap();
let b = b.into().unwrap();
let ncp = ncp.into().unwrap();
if let Some(ret) = x.p_bounds_01(0.0, 1.0, lower_tail, log) {
return ret;
}
pnbeta2(x, 1.0 - x, a, b, ncp, lower_tail, log)
}