r2rs-nmath 0.1.1

Statistics programming for Rust based on R's nmath package
Documentation
// Translation of nmath's qf
//
// 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_qbeta, qbeta},
        chisq::{log_qchisq, qchisq},
    },
    traits::DPQ,
};

/// The quantile function of the F distribution.
pub fn qf<P: Into<Probability64>, R1: Into<Rational64>, R2: Into<Rational64>>(
    p: P,
    df1: R1,
    df2: R2,
    lower_tail: bool,
) -> Real64 {
    let p = p.into().unwrap();
    qf_inner(p, df1, df2, lower_tail, false)
}

/// The quantile function of the F distribution.
pub fn log_qf<LP: Into<LogProbability64>, R1: Into<Rational64>, R2: Into<Rational64>>(
    p: LP,
    df1: R1,
    df2: R2,
    lower_tail: bool,
) -> Real64 {
    let p = p.into().unwrap();
    qf_inner(p, df1, df2, lower_tail, true)
}

fn qf_inner<R1: Into<Rational64>, R2: Into<Rational64>>(
    mut p: f64,
    df1: R1,
    df2: R2,
    lower_tail: bool,
    log: bool,
) -> Real64 {
    let df1 = df1.into().unwrap();
    let df2 = df2.into().unwrap();

    if let Some(ret) = p.q_p01_boundaries(0.0, f64::infinity(), lower_tail, log) {
        return ret.into();
    }

    /* fudge the extreme DF cases -- qbeta doesn't do this well.
      But we still need to fudge the infinite ones.
    */

    if df1 <= df2 && df2 > 4e5 {
        if !df1.is_finite() {
            /* df1 == df2 == Inf : */
            return 1.0.into();
        }
        /* else value for df2 == Inf : */
        return if log {
            (log_qchisq(p, df1, lower_tail).unwrap() / df1).into()
        } else {
            (qchisq(p, df1, lower_tail).unwrap() / df1).into()
        };
    }
    if df1 > 4e5 {
        /* and so  df2 < df1 -- return value for df1 == Inf */
        return if log {
            (df2 / log_qchisq(p, df2, !lower_tail).unwrap()).into()
        } else {
            (df2 / qchisq(p, df2, !lower_tail).unwrap()).into()
        };
    }

    // FIXME: (1/qb - 1) = (1 - qb)/qb; if we know qb ~= 1, should use other tail
    p =
        (1.0 / if log {
            log_qbeta(p, df2 / 2.0, df1 / 2.0, !lower_tail).unwrap()
        } else {
            qbeta(p, df2 / 2.0, df1 / 2.0, !lower_tail).unwrap()
        } - 1.0)
            * (df2 / df1);
    if !p.is_nan() { p } else { f64::nan() }.into()
}