use num_traits::Float;
use strafe_type::{r64, tof64, Checked, Positive64, Real64};
use crate::func::betagam::lgamma;
pub fn dmultinom(
x: &[Checked<Real64>],
prob: &[Checked<Positive64>],
log: bool,
) -> Checked<Real64> {
let mut x = x.iter().map(|x_i| tof64!(x_i)).collect::<Vec<_>>();
let mut prob = prob.iter().map(|p_i| tof64!(p_i)).collect::<Vec<_>>();
let K = prob.len();
if x.len() != K {
error!("x[] and prob[] must be equal length vectors.");
return r64!(f64::nan());
}
let s = prob.iter().copied().sum::<f64>();
if prob.iter().any(|p| !p.is_finite()) || prob.iter().any(|p| *p < 0.0) || s == 0.0 {
error!("probabilities must be finite, non-negative and not all 0");
return r64!(f64::nan());
}
prob.iter_mut().for_each(|p| *p /= s);
x.iter_mut().for_each(|x_i| *x_i = (*x_i + 0.5).trunc());
if x.iter().any(|x_i| (*x_i < 0.0)) {
error!("'x' must be non-negative");
return r64!(f64::nan());
}
let N = x.iter().copied().sum::<f64>();
let size = N;
let i0 = prob.iter().map(|p| *p == 0.0).collect::<Vec<_>>();
if i0.iter().any(|i| *i) {
if x.iter()
.zip(i0.iter())
.filter_map(|(x_i, i)| if *i { Some(*x_i) } else { None })
.any(|x_i| x_i != 0.0)
{
return r64!(if log { f64::NEG_INFINITY } else { 0.0 });
}
if i0.iter().all(|i| *i) {
return r64!(if log { 0.0 } else { 1.0 });
}
x = x
.into_iter()
.zip(i0.iter())
.filter_map(|(x_i, i)| if !*i { Some(x_i) } else { None })
.collect::<Vec<_>>();
prob = prob
.into_iter()
.zip(i0.iter())
.filter_map(|(p, i)| if !*i { Some(p) } else { None })
.collect::<Vec<_>>();
}
let r = tof64!(lgamma(r64!(size + 1.0)))
+ x.into_iter()
.zip(prob.into_iter())
.map(|(x_i, p)| x_i * p.ln() - tof64!(lgamma(r64!(x_i + 1.0))))
.sum::<f64>();
r64!(if log { r } else { r.exp() })
}