/*                                                     igam.c
 *
 *     Incomplete Gamma integral
 *
 *
 *
 * SYNOPSIS:
 *
 * double a, x, y, igam();
 *
 * y = igam( a, x );
 *
 * DESCRIPTION:
 *
 * The function is defined by
 *
 *                           x
 *                            -
 *                   1       | |  -t  a-1
 *  igam(a,x)  =   -----     |   e   t   dt.
 *                  -      | |
 *                 | (a)    -
 *                           0
 *
 *
 * In this implementation both arguments must be positive.
 * The integral is evaluated by either a power series or
 * continued fraction expansion, depending on the relative
 * values of a and x.
 *
 * ACCURACY:
 *
 *                      Relative error:
 * arithmetic   domain     # trials      peak         rms
 *    IEEE      0,30       200000       3.6e-14     2.9e-15
 *    IEEE      0,100      300000       9.9e-14     1.5e-14
 */
/*							igamc()
 *
 *	Complemented incomplete Gamma integral
 *
 *
 *
 * SYNOPSIS:
 *
 * double a, x, y, igamc();
 *
 * y = igamc( a, x );
 *
 * DESCRIPTION:
 *
 * The function is defined by
 *
 *
 *  igamc(a,x)   =   1 - igam(a,x)
 *
 *                            inf.
 *                              -
 *                     1       | |  -t  a-1
 *               =   -----     |   e   t   dt.
 *                    -      | |
 *                   | (a)    -
 *                             x
 *
 *
 * In this implementation both arguments must be positive.
 * The integral is evaluated by either a power series or
 * continued fraction expansion, depending on the relative
 * values of a and x.
 *
 * ACCURACY:
 *
 * Tested at random a, x.
 *                a         x                      Relative error:
 * arithmetic   domain   domain     # trials      peak         rms
 *    IEEE     0.5,100   0,100      200000       1.9e-14     1.7e-15
 *    IEEE     0.01,0.5  0,100      200000       1.4e-13     1.6e-15
 */

/*
 * Cephes Math Library Release 2.0:  April, 1987
 * Copyright 1985, 1987 by Stephen L. Moshier
 * Direct inquiries to 30 Frost Street, Cambridge, MA 02140
 */

/* Sources
 * [1] "The Digital Library of Mathematical Functions", dlmf.nist.gov
 * [2] Maddock et. al., "Incomplete Gamma Functions",
 *     https://www.boost.org/doc/libs/1_61_0/libs/math/doc/html/math_toolkit/sf_gamma/igamma.html
 */

/* Scipy changes:
 * - 05-01-2016: added asymptotic expansion for igam to improve the
 *   a ~ x regime.
 * - 06-19-2016: additional series expansion added for igamc to
 *   improve accuracy at small arguments.
 * - 06-24-2016: better choice of domain for the asymptotic series;
 *   improvements in accuracy for the asymptotic series when a and x
 *   are very close.
 */

#include "mconf.h"
#include "lanczos.h"
#include "igam.h"

#ifdef MAXITER
#undef MAXITER
#endif

#define MAXITER 2000
#define IGAM 1
#define IGAMC 0
#define SMALL 20
#define LARGE 200
#define SMALLRATIO 0.3
#define LARGERATIO 4.5

extern double MACHEP, MAXLOG;
static double big = 4.503599627370496e15;
static double biginv = 2.22044604925031308085e-16;

static double igamc_continued_fraction(double, double);
static double igam_series(double, double);
static double igamc_series(double, double);
static double asymptotic_series(double, double, int);


double igam(double a, double x)
{
    double absxma_a;

    if (x < 0 || a < 0) {
        sf_error("gammainc", SF_ERROR_DOMAIN, NULL);
        return NPY_NAN;
    } else if (a == 0) {
        if (x > 0) {
            return 1;
        } else {
            return NPY_NAN;
        }
    } else if (x == 0) {
        /* Zero integration limit */
        return 0;
    } else if (npy_isinf(a)) {
        if (npy_isinf(x)) {
            return NPY_NAN;
        }
        return 0;
    } else if (npy_isinf(x)) {
        return 1;
    }

    /* Asymptotic regime where a ~ x; see [2]. */
    absxma_a = fabs(x - a) / a;
    if ((a > SMALL) && (a < LARGE) && (absxma_a < SMALLRATIO)) {
	return asymptotic_series(a, x, IGAM);
    } else if ((a > LARGE) && (absxma_a < LARGERATIO / sqrt(a))) {
	return asymptotic_series(a, x, IGAM);
    }

    if ((x > 1.0) && (x > a)) {
	return (1.0 - igamc(a, x));
    }

    return igam_series(a, x);
}


double igamc(double a, double x)
{
    double absxma_a;

    if (x < 0 || a < 0) {
	sf_error("gammaincc", SF_ERROR_DOMAIN, NULL);
	return NPY_NAN;
    } else if (a == 0) {
        if (x > 0) {
	    return 0;
	} else {
	    return NPY_NAN;
	}
    } else if (x == 0) {
	return 1;
    } else if (npy_isinf(a)) {
	if (npy_isinf(x)) {
	    return NPY_NAN;
	}
	return 1;
    } else if (npy_isinf(x)) {
	return 0;
    }

    /* Asymptotic regime where a ~ x; see [2]. */
    absxma_a = fabs(x - a) / a;
    if ((a > SMALL) && (a < LARGE) && (absxma_a < SMALLRATIO)) {
	return asymptotic_series(a, x, IGAMC);
    } else if ((a > LARGE) && (absxma_a < LARGERATIO / sqrt(a))) {
	return asymptotic_series(a, x, IGAMC);
    }

    /* Everywhere else; see [2]. */
    if (x > 1.1) {
	if (x < a) {
	    return 1.0 - igam_series(a, x);
	} else {
	    return igamc_continued_fraction(a, x);
	}
    } else if (x <= 0.5) {
	if (-0.4 / log(x) < a) {
	    return 1.0 - igam_series(a, x);
	} else {
	    return igamc_series(a, x);
	}
    } else {
	if (x * 1.1 < a) {
	    return 1.0 - igam_series(a, x);
	} else {
	    return igamc_series(a, x);
	}
    }
}


/* Compute
 *
 * x^a * exp(-x) / gamma(a)
 *
 * corrected from (15) and (16) in [2] by replacing exp(x - a) with
 * exp(a - x).
 */
double igam_fac(double a, double x)
{
    double ax, fac, res, num;

    if (fabs(a - x) > 0.4 * fabs(a)) {
	ax = a * log(x) - x - lgam(a);
	if (ax < -MAXLOG) {
	    sf_error("igam", SF_ERROR_UNDERFLOW, NULL);
	    return 0.0;
	}
	return exp(ax);
    }

    fac = a + lanczos_g - 0.5;
    res = sqrt(fac / exp(1)) / lanczos_sum_expg_scaled(a);

    if ((a < 200) && (x < 200)) {
	res *= exp(a - x) * pow(x / fac, a);
    } else {
	num = x - a - lanczos_g + 0.5;
	res *= exp(a * log1pmx(num / fac) + x * (0.5 - lanczos_g) / fac);
    }

    return res;
}


/* Compute igamc using DLMF 8.9.2. */
static double igamc_continued_fraction(double a, double x)
{
    int i;
    double ans, ax, c, yc, r, t, y, z;
    double pk, pkm1, pkm2, qk, qkm1, qkm2;

    ax = igam_fac(a, x);
    if (ax == 0.0) {
	return 0.0;
    }

    /* continued fraction */
    y = 1.0 - a;
    z = x + y + 1.0;
    c = 0.0;
    pkm2 = 1.0;
    qkm2 = x;
    pkm1 = x + 1.0;
    qkm1 = z * x;
    ans = pkm1 / qkm1;

    for (i = 0; i < MAXITER; i++) {
	c += 1.0;
	y += 1.0;
	z += 2.0;
	yc = y * c;
	pk = pkm1 * z - pkm2 * yc;
	qk = qkm1 * z - qkm2 * yc;
	if (qk != 0) {
	    r = pk / qk;
	    t = fabs((ans - r) / r);
	    ans = r;
	}
	else
	    t = 1.0;
	pkm2 = pkm1;
	pkm1 = pk;
	qkm2 = qkm1;
	qkm1 = qk;
	if (fabs(pk) > big) {
	    pkm2 *= biginv;
	    pkm1 *= biginv;
	    qkm2 *= biginv;
	    qkm1 *= biginv;
	}
	if (t <= MACHEP) {
	    break;
	}
    }

    return (ans * ax);
}


/* Compute igam using DLMF 8.11.4. */
static double igam_series(double a, double x)
{
    int i;
    double ans, ax, c, r;

    ax = igam_fac(a, x);
    if (ax == 0.0) {
	return 0.0;
    }

    /* power series */
    r = a;
    c = 1.0;
    ans = 1.0;

    for (i = 0; i < MAXITER; i++) {
	r += 1.0;
	c *= x / r;
	ans += c;
	if (c <= MACHEP * ans) {
	    break;
	}
    }

    return (ans * ax / a);
}


/* Compute igamc using DLMF 8.7.3. This is related to the series in
 * igam_series but extra care is taken to avoid cancellation.
 */
static double igamc_series(double a, double x)
{
    int n;
    double fac = 1;
    double sum = 0;
    double term, logx;

    for (n = 1; n < MAXITER; n++) {
	fac *= -x / n;
	term = fac / (a + n);
	sum += term;
	if (fabs(term) <= MACHEP * fabs(sum)) {
	    break;
	}
    }

    logx = log(x);
    term = -expm1(a * logx - lgam1p(a));
    return term - exp(a * logx - lgam(a)) * sum;
}


/* Compute igam/igamc using DLMF 8.12.3/8.12.4. */
static double asymptotic_series(double a, double x, int func)
{
    int k, n, sgn;
    int maxpow = 0;
    double lambda = x / a;
    double sigma = (x - a) / a;
    double eta, res, ck, ckterm, term, absterm;
    double absoldterm = NPY_INFINITY;
    double etapow[N] = {1};
    double sum = 0;
    double afac = 1;

    if (func == IGAM) {
	sgn = -1;
    } else {
	sgn = 1;
    }

    if (lambda > 1) {
 	eta = sqrt(-2 * log1pmx(sigma));
    } else if (lambda < 1) {
	eta = -sqrt(-2 * log1pmx(sigma));
    } else {
	eta = 0;
    }
    res = 0.5 * erfc(sgn * eta * sqrt(a / 2));

    for (k = 0; k < K; k++) {
	ck = d[k][0];
	for (n = 1; n < N; n++) {
	    if (n > maxpow) {
		etapow[n] = eta * etapow[n-1];
		maxpow += 1;
	    }
	    ckterm = d[k][n]*etapow[n];
	    ck += ckterm;
	    if (fabs(ckterm) < MACHEP * fabs(ck)) {
		break;
	    }
	}
	term = ck * afac;
	absterm = fabs(term);
	if (absterm > absoldterm) {
	    break;
	}
	sum += term;
	if (absterm < MACHEP * fabs(sum)) {
	    break;
	}
	absoldterm = absterm;
	afac /= a;
    }
    res += sgn * exp(-0.5 * a * eta * eta) * sum / sqrt(2 * NPY_PI * a);

    return res;
}
