r2rs-nmath 0.1.1

Statistics programming for Rust based on R's nmath package
Documentation
// Translation of nmath's pf
//
// 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, LogProbability64, Probability64, Rational64, Real64};

use crate::{
    distribution::{
        beta::{log_pbeta, pbeta},
        chisq::{log_pchisq, pchisq},
    },
    traits::DPQ,
};

/// The distribution function of the F distribution.
pub fn pf<RE: Into<Real64>, RA1: Into<Rational64>, RA2: Into<Rational64>>(
    x: RE,
    df1: RA1,
    df2: RA2,
    lower_tail: bool,
) -> Probability64 {
    pf_inner(x, df1, df2, lower_tail, false).into()
}

/// The distribution function of the F distribution.
pub fn log_pf<RE: Into<Real64>, RA1: Into<Rational64>, RA2: Into<Rational64>>(
    x: RE,
    df1: RA1,
    df2: RA2,
    lower_tail: bool,
) -> LogProbability64 {
    pf_inner(x, df1, df2, lower_tail, true).into()
}

fn pf_inner<RE: Into<Real64>, RA1: Into<Rational64>, RA2: Into<Rational64>>(
    x: RE,
    df1: RA1,
    df2: RA2,
    lower_tail: bool,
    log: bool,
) -> f64 {
    let mut x = x.into().unwrap();
    let df1 = df1.into().unwrap();
    let df2 = df2.into().unwrap();

    if let Some(ret) = x.p_bounds_01(0.0, f64::infinity(), lower_tail, log) {
        return ret;
    }

    /* move to pchisq for very large values - was 'df1 > 4e5' in 2.0.x,
    now only needed for df1 = Inf or df2 = Inf {since pbeta(0,*)=0} : */
    if df2 == f64::infinity() {
        if df1 == f64::infinity() {
            if x < 1.0 {
                return f64::dt_0(lower_tail, log);
            }
            if x == 1.0 {
                return if log { -std::f64::consts::LN_2 } else { 0.5 };
            }
            if x > 1.0 {
                return f64::dt_1(lower_tail, log);
            }
        }
        return if log {
            log_pchisq(x * df1, df1, lower_tail).into()
        } else {
            pchisq(x * df1, df1, lower_tail).into()
        };
    }

    if df1 == f64::infinity() {
        /* was "fudge" 'df1 > 4e5' in 2.0.x */
        return if log {
            log_pchisq(df2 / x, df2, !lower_tail).into()
        } else {
            pchisq(df2 / x, df2, !lower_tail).into()
        };
    }

    /* Avoid squeezing pbeta's first parameter against 1 :  */
    if df1 * x > df2 {
        x = if log {
            log_pbeta(df2 / (df2 + df1 * x), df2 / 2.0, df1 / 2.0, !lower_tail).into()
        } else {
            pbeta(df2 / (df2 + df1 * x), df2 / 2.0, df1 / 2.0, !lower_tail).into()
        }
    } else {
        x = if log {
            log_pbeta(df1 * x / (df2 + df1 * x), df1 / 2.0, df2 / 2.0, lower_tail).into()
        } else {
            pbeta(df1 * x / (df2 + df1 * x), df1 / 2.0, df2 / 2.0, lower_tail).into()
        }
    }

    x
}