r2rs-nmath 0.1.1

Statistics programming for Rust based on R's nmath package
Documentation
// Translation of nmath's dnf
//
// 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::{FloatConstraint, Positive64, Rational64, Real64};

use crate::{
    distribution::{beta::dnbeta, chisq::dnchisq, gamma::dgamma},
    traits::DPQ,
};

/// The density function of the non-central F distribution ---
/// obtained by differentiating the corresp. cumulative distribution function
/// using dnbeta.
/// For $ df1 < 2 $, since the F density has a singularity as $ x -> \infty $.
pub fn dnf<RE: Into<Real64>, RA1: Into<Rational64>, RA2: Into<Rational64>, P: Into<Positive64>>(
    x: RE,
    df1: RA1,
    df2: RA2,
    ncp: P,
    log: bool,
) -> Real64 {
    let x = x.into().unwrap();
    let df1 = df1.into().unwrap();
    let df2 = df2.into().unwrap();
    let ncp = ncp.into().unwrap();

    let mut y = 0.0;
    let mut z = 0.0;
    let mut f = 0.0;

    /* want to compare dnf(ncp=0) behavior with df() one, hence *NOT* :
     * if (ncp == 0)
     *   return df(x, df1, df2, give_log); */

    if x < 0.0 {
        return f64::d_0(log).into();
    }
    if !ncp.is_finite() {
        /* ncp = +Inf -- FIXME?: in some cases, limit exists */

        return f64::nan().into();
    }

    /* This is not correct for  df1 == 2, ncp > 0 - and seems unneeded:
     *  if (x == 0.0) return(df1 > 2 ? R_D__0 : (df1 == 2 ? R_D__1 : ML_POSINF));
     */
    if !df1.is_finite() && !df2.is_finite() {
        /* both +Inf */
        /* PR: not sure about this (taken from  ncp==0)  -- FIXME ? */
        return if x == 1.0 {
            f64::infinity()
        } else {
            f64::d_0(log)
        }
        .into();
    }
    if !df2.is_finite() {
        /* i.e.  = +Inf */
        return (df1 * dnchisq(x * df1, df1, ncp, log).unwrap()).into();
    }
    /*  ==  dngamma(x, df1/2, 2./df1, ncp, give_log)  -- but that does not exist */
    if df1 > 1e14 && ncp < 1e7 {
        /* includes df1 == +Inf: code below is inaccurate there */
        f = 1.0 + ncp / df1; /* assumes  ncp << df1 [ignores 2*ncp^(1/2)/df1*x term] */
        z = dgamma(1.0 / x / f, df2 / 2.0, 2.0 / df2, log).unwrap();
        return if log {
            (z - 2.0 * x.ln()) - f.ln()
        } else {
            (z / (x * x)) / f
        }
        .into();
    }

    y = df1 / df2 * x;
    z = dnbeta(y / (1.0 + y), df1 / 2.0, df2 / 2.0, ncp, log).unwrap();
    if log {
        (z + df1.ln() - df2.ln()) - 2.0 * y.ln_1p()
    } else {
        (z * (df1 / df2) / (1.0 + y)) / (1.0 + y)
    }
    .into()
}