ChiSqDist.java

/*
 * Copyright (C) 2015 Alberto Irurueta Carro (alberto@irurueta.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.irurueta.statistics;

/**
 * Contains methods to work with Chi squared distributions.
 * Methods of this class use methods of the Gamma class.
 * This class is based in code of Numerical Recipes 3rd ed. section 6.14.8.
 */
public class ChiSqDist {

    /**
     * Nu parameter of chi square distribution.
     * Typically, this parameter is provided as an integer value indicating the
     * number of degrees of freedom.
     */
    private double nu;

    /**
     * Constant factor to multiply p.d.f of chi squared by. This factor depends
     * on nu parameter.
     */
    private double fac;

    /**
     * A gamma function instance to be reused for memory efficiency.
     */
    private final Gamma gamma;

    /**
     * Constructor.
     *
     * @param nu parameter of chi square distribution.
     * @throws IllegalArgumentException if provided nu parameter is negative or
     *                                  zero.
     */
    public ChiSqDist(final double nu) {
        gamma = new Gamma();
        setNu(nu);
    }

    /**
     * Returns nu parameter of chi square distribution.
     * Typically, this parameter is an integer value indicating the number of
     * degrees of freedom.
     *
     * @return nu parameter of chi square distribution.
     */
    public double getNu() {
        return nu;
    }

    /**
     * Sets nu parameter of chi square distribution.
     * Typically, this parameter is an integer value indicating the number of
     * degrees of freedom.
     *
     * @param nu nu parameter of chi square distribution.
     * @throws IllegalArgumentException if provided nu parameter is negative or
     *                                  zero.
     */
    public final void setNu(final double nu) {
        if (nu <= 0.0) {
            throw new IllegalArgumentException();
        }

        this.nu = nu;
        fac = fac(nu);
    }

    /**
     * Evaluates the probability density function (p.d.f.) of a Chi square
     * distribution.
     *
     * @param x2 chi square value where p.d.f. is evaluated. Must be greater
     *           than 0.0.
     * @param nu nu parameter of chi square distribution. Typically, this is an
     *           integer value indicating the number of degrees of freedom. Must be
     *           greater than 0.0.
     * @return evaluation of p.d.f.
     * @throws IllegalArgumentException if either x2 or nu are negative or zero.
     */
    public static double p(final double x2, final double nu) {
        if (nu <= 0.0) {
            throw new IllegalArgumentException();
        }

        return internalP(x2, nu, fac(nu));
    }

    /**
     * Evaluates the probability density function (p.d.f.) of this Chi square
     * distribution.
     *
     * @param x2 chi square value where p.d.f. is evaluated. Must be greater
     *           than 0.0.
     * @return evaluation of p.d.f.
     * @throws IllegalArgumentException if x2 is negative or zero.
     */
    public double p(final double x2) {
        return internalP(x2, nu, fac);
    }

    /**
     * Evaluates the cumulative distribution function (c.d.f.) of a Chi-squared
     * distribution having parameter nu.
     *
     * @param x2 chi square value where c.d.f. is evaluated. Must be positive or
     *           zero.
     * @param nu nu parameter of chi square distribution. Typically, this is an
     *           integer value indicating the number of degrees of freedom. Must be
     *           greater than 0.0.
     * @return evaluation of c.d.f.
     * @throws IllegalArgumentException       if provided chi square value is negative
     *                                        or if provided nu parameter is negative or zero.
     * @throws MaxIterationsExceededException if convergence of incomplete
     *                                        gamma function cannot be reached. This is rarely thrown and happens
     *                                        usually for numerically unstable input values.
     */
    public static double cdf(final double x2, final double nu)
            throws MaxIterationsExceededException {
        if (nu <= 0.0) {
            throw new IllegalArgumentException("nu must be greater than 0.0");
        }

        return internalCdf(x2, nu, new Gamma());
    }

    /**
     * Evaluates the cumulative distribution function (c.d.f.) of this Chi-square
     * distribution.
     *
     * @param x2 chi square value where c.d.f. is evaluated. Must be positive or
     *           zero.
     * @return evaluation of c.d.f.
     * @throws IllegalArgumentException       if provided chi square value is
     *                                        negative.
     * @throws MaxIterationsExceededException if convergence of incomplete gamma
     *                                        function cannot be reached. This is rarely thrown and happens usually for
     *                                        numerically unstable input values.
     */
    public double cdf(final double x2) throws MaxIterationsExceededException {
        return internalCdf(x2, nu, gamma);
    }

    /**
     * Evaluates the inverse cumulative distribution function of a Chi squared
     * distribution having parameter nu.
     * Because the c.d.f is a monotonically increasing function with values
     * between 0.0 and 1.0, its inverse is uniquely defined between such range
     * of values.
     *
     * @param p  value to evaluate the inverse c.d.f. at. This value is
     *           equivalent to a probability and must be between 0.0 and 1.0.
     * @param nu nu parameter of chi square distribution. Typically, this is an
     *           integer value indicating the number of degrees of freedom. Must be
     *           greater than 0.0.
     * @return the value x2 for which the c.d.f. has value p.
     * @throws IllegalArgumentException       if provided probability value is not
     *                                        between 0.0 and 1.0 of if provided nu parameter is negative or zero.
     * @throws MaxIterationsExceededException if convergence of inverse
     *                                        incomplete gamma function cannot be reached. This is rarely thrown and
     *                                        happens usually for numerically unstable values.
     */
    public static double invcdf(final double p, final double nu) throws MaxIterationsExceededException {
        if (nu <= 0.0) {
            throw new IllegalArgumentException("nu must be greater than 0.0");
        }

        return internalInvcdf(p, nu, new Gamma());
    }

    /**
     * Evaluates the inverse cumulative distribution function of this Chi squared
     * distribution.
     * Because the c.d.f is a monotonically increasing function with values
     * between 0.0 and 1.0, its inverse is uniquely defined between such range
     * of values.
     *
     * @param p value to evaluate the inverse c.d.f. at. This value is
     *          equivalent to a probability and must be between 0.0 and 1.0.
     * @return the value x2 for which the c.d.f. has value p.
     * @throws IllegalArgumentException       if provided probability value is not
     *                                        between 0.0 and 1.0.
     * @throws MaxIterationsExceededException if convergence of inverse
     *                                        incomplete gamma function cannot be reached. This is rarely thrown and
     *                                        happens usually for numerically unstable values.
     */
    public double invcdf(final double p) throws MaxIterationsExceededException {
        return internalInvcdf(p, nu, gamma);
    }

    /**
     * Evaluates the probability density function (p.d.f.) of a Chi square
     * distribution.
     * This method is used internally.
     *
     * @param x2  chi square value where p.d.f. is evaluated. Must be greater
     *            than 0.0.
     * @param nu  nu parameter of chi square distribution. Typically, this is an
     *            integer value indicating the number of degrees of freedom. Must be
     *            greater than 0.0.
     * @param fac actor to multiply p.d.f of chi squared by.
     * @return evaluation of p.d.f.
     * @throws IllegalArgumentException if x2 is negative or zero.
     */
    private static double internalP(final double x2, final double nu, final double fac) {
        if (x2 <= 0.0) {
            throw new IllegalArgumentException(
                    "chi square must be greater than zero");
        }

        return Math.exp(-0.5 * (x2 - (nu - 2.0) * Math.log(x2)) - fac);
    }

    /**
     * Evaluates the cumulative distribution function (c.d.f.) of a Chi-squared
     * distribution having parameter nu.
     * This method is used internally.
     *
     * @param x2    chi square value where c.d.f. is evaluated. Must be positive
     *              or zero.
     * @param nu    nu parameter of chi square distribution. Typically, this is an
     *              integer value indicating the number of degrees of freedom. Must be
     *              greater than 0.0.
     * @param gamma a gamma instance to evaluate the incomplete gamma function.
     * @return evaluation of c.d.f.
     * @throws IllegalArgumentException       if provided chi square value is
     *                                        negative.
     * @throws MaxIterationsExceededException if convergence of incomplete gamma
     *                                        function cannot be reached. This is rarely thrown and happens usually for
     *                                        numerically unstable values.
     */
    private static double internalCdf(final double x2, final double nu, final Gamma gamma)
            throws MaxIterationsExceededException {
        if (x2 < 0.0) {
            throw new IllegalArgumentException("chi square must be positive or zero");
        }

        return gamma.gammp(0.5 * nu, 0.5 * x2);
    }

    /**
     * Evaluates the inverse cumulative distribution function of a Chi squared
     * distribution having parameter nu.
     * Because the c.d.f is a monotonically increasing function with values
     * between 0.0 and 1.0, its inverse is uniquely defined between such range
     * of values.
     * This method is used internally.
     *
     * @param p     value to evaluate the inverse c.d.f. at. This value is
     *              equivalent to a probability and must be between 0.0 and 1.0.
     * @param nu    nu parameter of chi square distribution. Typically, this is an
     *              integer value indicating the number of degrees of freedom. Must be
     *              greater than 0.0.
     * @param gamma a gamma instance to evaluate the inverse incomplete gamma
     *              function.
     * @return the value x2 for which the c.d.f. has value p.
     * @throws IllegalArgumentException       if provided probability value is not
     *                                        between 0.0 and 1.0.
     * @throws MaxIterationsExceededException if convergence of inverse
     *                                        incomplete gamma function cannot be reached. This is rarely thrown and
     *                                        happens usually for numerically unstable values.
     */
    private static double internalInvcdf(final double p, final double nu, final Gamma gamma)
            throws MaxIterationsExceededException {

        if (p < 0.0 || p >= 1.0) {
            throw new IllegalArgumentException("probability value must be between 0.0 and 1.0");
        }

        return 2.0 * gamma.invgammp(p, 0.5 * nu);
    }

    /**
     * Computes constant factor to multiply p.d.f. of chi squared by.
     *
     * @param nu nu parameter of chi square distribution. Typically, this
     *           parameter is provided as an integer value indicating the number of
     *           degrees of freedom.
     * @return constant factor to multiply p.d.f. of chi squared.
     */
    private static double fac(final double nu) {
        return 0.693147180559945309 * (0.5 * nu) + Gamma.gammln(0.5 * nu);
    }
}