use num_traits::Float;
use strafe_type::{FloatConstraint, LogProbability64, Positive64, Probability64, Real64};
use super::{log_pnbinom, pnbinom};
use crate::{distribution::func::discrete_body, traits::DPQ};
pub fn qnbinom<PR1: Into<Probability64>, PO: Into<Positive64>, PR2: Into<Probability64>>(
p: PR1,
size: PO,
prob: PR2,
lower_tail: bool,
) -> Real64 {
let p = p.into().unwrap();
qnbinom_inner(p, size, prob, lower_tail, false)
}
pub fn log_qnbinom<LP: Into<LogProbability64>, PO: Into<Positive64>, PR: Into<Probability64>>(
p: LP,
size: PO,
prob: PR,
lower_tail: bool,
) -> Real64 {
let p = p.into().unwrap();
qnbinom_inner(p, size, prob, lower_tail, true)
}
fn qnbinom_inner<PO: Into<Positive64>, PR: Into<Probability64>>(
p: f64,
size: PO,
prob: PR,
lower_tail: bool,
log: bool,
) -> Real64 {
let size = size.into().unwrap();
let prob = prob.into().unwrap();
if prob == 0.0 && size == 0.0 {
return 0.0.into();
}
if prob <= 0.0 {
return f64::nan().into();
}
if prob == 1.0 || size == 0.0 {
return 0.0.into();
}
if let Some(ret) = p.q_p01_boundaries(0.0, f64::infinity(), lower_tail, log) {
return ret.into();
}
let Q = 1.0 / prob;
let P = (1.0 - prob) * Q; let mu = size * P;
let sigma = (size * P * Q).sqrt();
let gamma = (Q + P) / sigma;
let y = discrete_body(
mu,
sigma,
gamma,
p,
None,
lower_tail,
log,
&|p| pnbinom(p, size, prob, lower_tail).unwrap(),
&|p| log_pnbinom(p, size, prob, lower_tail).unwrap(),
);
y.into()
}