use num_traits::Float;
use strafe_type::{FloatConstraint, Positive64, Probability64, Real64};
use crate::{
distribution::{binom::dbinom_raw, gamma::lgamma1p, pois::dpois_raw},
traits::DPQ,
};
pub fn dnbinom<R: Into<Real64>, PO: Into<Positive64>, PR: Into<Probability64>>(
x: R,
size: PO,
prob: PR,
log: bool,
) -> Real64 {
let mut x = x.into().unwrap();
let mut size = size.into().unwrap();
let prob = prob.into().unwrap();
let mut ans = 0.0;
if prob <= 0.0 {
return f64::nan().into();
}
if x.is_non_integer() {
warn!("non-integer x = {}", x);
return f64::d_0(log).into();
}
if x < 0.0 || !x.is_finite() {
return f64::d_0(log).into();
}
if x == 0.0 {
return if size == 0.0 {
f64::d_1(log).into()
} else if log {
(size * prob.ln()).into()
} else {
prob.powf(size).into()
};
}
x = x.round();
if !size.is_finite() {
size = f64::MAX
}
if x < 1e-10 * size {
(size * prob.ln() + x * (size.ln() + (-prob).ln_1p()) - lgamma1p(x)
+ (x * (x - 1.0) / (2.0 * size)).ln_1p())
.d_exp(log)
.into()
} else {
let p = if log {
if x < size {
(-x / (size + x)).ln_1p()
} else {
(size / (size + x)).ln()
}
} else {
size / (size + x)
};
ans = dbinom_raw(size, x + size, prob, 1.0 - prob, log);
if log { p + ans } else { p * ans }.into()
}
}
pub fn dnbinom_mu<R: Into<Real64>, PO1: Into<Positive64>, PO2: Into<Positive64>>(
x: R,
size: PO1,
mu: PO2,
log: bool,
) -> Real64 {
let mut x = x.into().unwrap();
let size = size.into().unwrap();
let mu = mu.into().unwrap();
if x.is_non_integer() {
warn!("non-integer x = {}", x);
return f64::d_0(log).into();
}
if x < 0.0 || !x.is_finite() {
return f64::d_0(log).into();
}
if x == 0.0 && size == 0.0 {
return f64::d_1(log).into();
}
x = x.round();
if !size.is_finite() {
return dpois_raw(x, mu, log).into();
}
if x == 0.0 {
(size
* if size < mu {
(size / (size + mu)).ln()
} else {
(-mu / (size + mu)).ln_1p()
})
.d_exp(log);
}
if x < 1e-10 * size {
let p = if size < mu {
(size / (1.0 + size / mu)).ln()
} else {
(mu / (1.0 + mu / size)).ln()
};
((x * p - mu - lgamma1p(x)) + (x * (x - 1.0) / (2.0 * size)).ln_1p()).d_exp(log)
} else {
let p_0 = if log {
if x < size {
(-x / (size + x)).ln_1p()
} else {
(size / (size + x)).ln()
}
} else {
size / (size + x)
};
let ans = dbinom_raw(size, x + size, size / (size + mu), mu / (size + mu), log);
if log {
p_0 + ans
} else {
p_0 * ans
}
}
.into()
}