r2rs-nmath 0.1.1

Statistics programming for Rust based on R's nmath package
Documentation
// Translation of nmath's dmultinom
//
// Copyright (C) 2020 neek-sss
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

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() })
}