/*
 * chi - chi^2 probabilities with degrees of freedom for null hypothesis
 *
 * Copyright (C) 2001,2021  Landon Curt Noll
 *
 * Calc is open software; you can redistribute it and/or modify it under
 * the terms of the version 2.1 of the GNU Lesser General Public License
 * as published by the Free Software Foundation.
 *
 * Calc 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 Lesser General
 * Public License for more details.
 *
 * A copy of version 2.1 of the GNU Lesser General Public License is
 * distributed with calc under the filename COPYING-LGPL.  You should have
 * received a copy with calc; if not, write to Free Software Foundation, Inc.
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * Under source code control:	2001/03/27 14:10:11
 * File existed as early as:	2001
 *
 * chongo <was here> /\oo/\	http://www.isthe.com/chongo/
 * Share and enjoy!  :-)	http://www.isthe.com/chongo/tech/comp/calc/
 */


/*
 * Z(x)
 *
 * From Handbook of Mathematical Functions
 *	10th printing, Dec 1972 with corrections
 *	National Bureau of Standards
 *
 * Section 26.2.1, p931.
 */
define Z(x, eps_term)
{
    local eps;		/* error term */

    /* obtain the error term */
    if (isnull(eps_term)) {
	eps = epsilon();
    } else {
	eps = eps_term;
    }

    /* compute Z(x) value */
    return exp(-x*x/2, eps) / sqrt(2*pi(eps), eps);
}


/*
 * P(x[, eps]) asymptotic P(x) expansion for x>0 to an given epsilon error term
 *
 * NOTE: If eps is omitted, the stored epsilon value is used.
 *
 * From Handbook of Mathematical Functions
 *	10th printing, Dec 1972 with corrections
 *	National Bureau of Standards
 *
 * 26.2.11, p932:
 *
 *	P(x) = 1/2 + Z(x) * sum(n=0; n < infinity){x^(2*n+1)/(1*3*5*...(2*n+1)};
 *
 * We continue the fraction until it is less than epsilon error term.
 *
 * Also note 26.2.5:
 *
 *	P(x) + Q(x) = 1
 */
define P(x, eps_term)
{
    local eps;		/* error term */
    local s;		/* sum */
    local x2;		/* x^2 */
    local x_term;	/* x^(2*r+1) */
    local odd_prod;	/* 1*3*5* ... */
    local odd_term;	/* next odd value to multiply into odd_prod */
    local term;		/* the recent term added to the sum */

    /* obtain the error term */
    if (isnull(eps_term)) {
	eps = epsilon();
    } else {
	eps = eps_term;
    }

    /* firewall */
    if (x <= 0) {
	if (x == 0) {
	    return 0;	/* hack */
	} else {
	    quit "Q(x[,eps]) 1st argument must be >= 0";
	}
    }
    if (eps <= 0) {
	quit "Q(x[,eps]) 2nd argument must be > 0";
    }

    /*
     * approximate sum(n=0; n < infinity){x^(2*n+1)/(1*3*5*...(2*n+1)}
     */
    x2 = x*x;
    x_term = x;
    s = x_term;	/* 1st term */
    odd_term = 1;
    odd_prod = 1;
    do {

	/* compute the term */
	odd_term += 2;
	odd_prod *= odd_term;
	x_term *= x2;
	term = x_term / odd_prod;
	s += term;

    } while (term >= eps);

    /* apply term and factor */
    return 0.5 + Z(x,eps)*s;
}


/*
 * chi_prob(chi_sq, v[, eps]) -  Prob of >= chi^2 with v degrees of freedom
 *
 *    Computes the Probability, given the Null Hypothesis, that a given
 *    Chi squared values >= chi_sq with v degrees of freedom.
 *
 *    The chi_prob() function does not work well with odd degrees of freedom.
 *    It is reasonable with even degrees of freedom, although one must give
 *    a sufficiently small error term as the degrees gets large (>100).
 *
 * NOTE: This function does not work well with odd degrees of freedom.
 *	 Can somebody help / find a bug / provide a better method of
 *	 this odd degrees of freedom case?
 *
 * NOTE: This function works well with even degrees of freedom.  However
 *	 when the even degrees gets large (say, as you approach 100), you
 *	 need to increase your error term.
 *
 * From Handbook of Mathematical Functions
 *	10th printing, Dec 1972 with corrections
 *	National Bureau of Standards
 *
 * Section 26.4.4, p941:
 *
 *   For odd v:
 *
 *	Q(chi_sq, v) = 2*Q(chi) + 2*Z(chi) * (
 *		     sum(r=1, r<=(r-1)/2) {(chi_sq^r/chi) / (1*3*5*...(2*r-1)});
 *
 *	chi = sqrt(chi_sq)
 *
 *	NOTE: Q(x) = 1-P(x)
 *
 * Section 26.4.5, p941.
 *
 *   For even v:
 *
 *	Q(chi_sq, v) = sqrt(2*pi()) * Z(chi) * ( 1 +
 *		       sum(r=1, r=((v-2)/2)) { chi_sq^r / (2*4*...*(2r)) } );
 *
 *	chi = sqrt(chi_sq)
 *
 * Observe that:
 *
 *	Z(x) = exp(-x*x/2) / sqrt(2*pi());	(Section 26.2.1, p931)
 *
 * and thus:
 *
 *	sqrt(2*pi()) * Z(chi) =
 *	sqrt(2*pi()) * Z(sqrt(chi_sq)) =
 *	sqrt(2*pi()) * exp(-sqrt(chi_sq)*sqrt(chi_sq)/2) / sqrt(2*pi()) =
 *	exp(-sqrt(chi_sq)*sqrt(chi_sq)/2) =
 *	exp(-sqrt(-chi_sq/2)
 *
 * So:
 *
 *	Q(chi_sq, v) = exp(-sqrt(-chi_sq/2) * ( 1 + sum(....){...} );
 */
define chi_prob(chi_sq, v, eps_term)
{
    local eps;		/* error term */
    local r;		/* index in finite sum */
    local r_lim;	/* limit value for r */
    local s;		/* sum */
    local d;		/* denominator (2*4*6*... or 1*3*5...) */
    local chi_term;	/* chi_sq^r */
    local ret;		/* return value */

    /* obtain the error term */
    if (isnull(eps_term)) {
	eps = epsilon();
    } else {
	eps = eps_term;
    }

    /*
     * odd degrees of freedom
     */
    if (isodd(v)) {

	local chi;		/* sqrt(chi_sq) */

	/* setup for sum */
	s = 1;
	d = 1;
	chi = sqrt(abs(chi_sq), eps);
	chi_term = chi;
	r_lim = (v-1)/2;

	/* compute sum(r=1, r=((v-1)/2)) {(chi_sq^r/chi) / (1*3*5...*(2r-1))} */
	for (r=2; r <= r_lim; ++r) {
	    chi_term *= chi_sq;
	    d *= (2*r)-1;
	    s += chi_term/d;
	}

	/* apply term and factor, Q(x) = 1-P(x) */
	ret = 2*(1-P(chi)) + 2*Z(chi)*s;

    /*
     * even degrees of freedom
     */
    } else {

	/* setup for sum */
	s =1;
	d = 1;
	chi_term = 1;
	r_lim = (v-2)/2;

	/* compute sum(r=1, r=((v-2)/2)) { chi_sq^r / (2*4*...*(2r)) } */
	for (r=1; r <= r_lim; ++r) {
	    chi_term *= chi_sq;
	    d *= r*2;
	    s += chi_term/d;
	}

	/* apply factor - see observation in the main comment above */
	ret = exp(-chi_sq/2, eps) * s;
    }

    return ret;
}
