package edu.northwestern.at.utils.math.distributions;

/*  Please see the license information at the end of this file. */

import edu.northwestern.at.utils.math.*;

/** Gamma distribution functions.
 */

public class Gamma
{
    /** Maximum value for which gamma which can be computed. */

    public static final double MAXGAM = 171.624376956302725D;

    /** Gamma function.
     *
     *  @param  x   Value for which Gamma is to be computed.
     *
     *  @return     Value of gamma function if computable.
     *                  The value Double.POSITIVE_INFINITY is returned for
     *                  bad values of x or when overflow would occur.
     *
     *  <p>
     *  The method used is presented in "An Overview of Software
     *  Development for Special Functions" by W. J. Cody in
     *  <strong>Lecture Notes in Mathematics</strong>,
     *  506, Numerical Analysis Dundee, 1975, G. A. Watson
     *  (ed.), Springer Verlag, Berlin, 1976.
     *  </p>
     *
     *  <p>
     *  Also see Hart et al, "Computer Approximations",
     *  Wiley(1968), p. 130F.
     *  </p>
     *
     *  <p>
     *  This java code is a fairly straightforward translation of
     *  the freely available Fortran function GAMMA by Cody and Stolz.
     *  </p>
     */

    public static double gamma( double x )
    {
        final double[] p =
        {
            -1.71618513886549492533811D,
            24.7656508055759199108314D,
            -379.804256470945635097577D,
            629.331155312818442661052D,
            866.966202790413211295064D,
            -31451.2729688483675254357D,
            -36144.4134186911729807069D,
            66456.1438202405440627855D
        };

        final double[] q =
        {
            -30.8402300119738975254353D,
            315.350626979604161529144D,
            -1015.15636749021914166146D,
            -3107.77167157231109440444D,
            22538.1184209801510330112D,
            4755.84627752788110767815D,
            -134659.959864969306392456D,
            -115132.259675553483497211D
        };

        final double[] c =
        {
            -0.001910444077728D,
            8.4171387781295e-4,
            -5.952379913043012e-4,
            7.93650793500350248e-4,
            -0.002777777777777681622553D,
            0.08333333333333333331554247D,
            0.0057083835261D
        };

        final double XBIG = 171.624D;
        final double XMININ = 2.23e-308;

                                //  If argument is not a number,
                                //  return that.

        if ( Double.isNaN( x ) )
        {
            return x;
        }

        boolean parity;

        int n;

        double fact;
        double xden;
        double xnum;
        double y;
        double z;
        double y1;
        double res;
        double sum;
        double ysq;

        parity  = false;
        fact    = 1.0D;
        n       = 0;
        y       = x;

        if ( y <= 0.0D )
        {
                                //  Argument is negative.
            y   = -x;
            y1  = (double)((int)y);
            res = y - y1;

            if ( res != 0.0D )
            {
                if ( y1 != (double)((int)( y1 * 0.5D ) ) * 2.0D )
                {
                    parity = true;
                }

                fact    = -Math.PI / Math.sin( Math.PI * res );
                y       += 1.0D;
            }
            else
            {
                return Double.POSITIVE_INFINITY;
//              return Double.MAX_VALUE;
            }
        }
                                //  Argument is positive.

        if ( y < Constants.MACHEPS )
        {
                                //  Argument is less than machine epsilon.

            if ( y >= XMININ )
            {
                res = 1.0D / y;
            }
            else
            {
                return Double.POSITIVE_INFINITY;
//              return Double.MAX_VALUE;
            }
        }
        else if ( y < 12.0D )
        {
            y1  = y;

            if ( y < 1.0D )
            {
                                //  0.0 < argument < 1.0
                z   = y;
                y   += 1.0D;
            }
            else
            {
                                //  1.0 < argument < 12.0.
                                //  Reduce argument if necessary

                n   = (int)( y - 1.0D );
                y   -= (double)n;
                z   = y - 1.0D;
            }

                                //  Evaluate approximation for
                                //  1.0 < argument < 2.0
            xnum    = 0.0D;
            xden    = 1.0D;

            for ( int i = 0; i < 8; i++ )
            {
                xnum    = ( xnum + p[ i ] ) * z;
                xden    = xden * z + q[ i ];
            }

            res = xnum / xden + 1.0D;

            if ( y1 < y )
            {
                                //  Adjust result for 0.0 < argument < 1.0
                res /= y1;
            }
            else if ( y1 > y )
            {
                                // Adjust result for  2.0 < argument < 12.0

                for ( int i = 1; i <= n; i++ )
                {
                    res *= y;
                    y   += 1.0D;
                }
            }
        }
        else
        {
                                // Evaluate for argument >= 12.0
            if ( y <= XBIG )
            {
                ysq = y * y;
                sum = c[ 6 ];

                for ( int i = 0; i < 6; i++ )
                {
                    sum = sum / ysq + c[ i ];
                }

                sum = sum / y - y + Constants.LNSQRT2PI;
                sum += ( y - 0.5D ) * Math.log( y );

                res = Math.exp( sum );
            }
            else
            {
                return Double.POSITIVE_INFINITY;
//              return Double.MAX_VALUE;
            }
        }
                                // Perform final adjustments.
        if ( parity )
        {
            res = -res;
        }

        if ( fact != 1.0D )
        {
            res = fact / res;
        }

        return res;
    }

    /** Calculate log of Gamma function.
     *
     *  @param  y       Gamma distribution parameter.
     *
     *  @return         Log gamma for specified parameter.
     *                  The value Double.POSITIVE_INFINITY is returned for
     *                  y <= 0.0 or when overflow would occur.
     *
     *  <p>
     *  Minimax polynomial approximations are used over the
     *  intervals [-inf,0], [0,.5], [.5,1.5], [1.5,4.0],
     *  [4.0,12.0], [12.0,+inf].
     *  </p>
     *
     *  <p>
     *  See Hart et al, "Computer Approximations",
     *  Wiley(1968), p. 130F, and also
     *  Cody and Hillstrom, "Chebyshev approximations for
     *  the natural logarithm of the Gamma function",
     *  Mathematics of Computation, 21, April, 1967, P. 198F.
     *  </p>
     *
     *  <p>
     *  The minimax coefficients for y > 12 come from Hart et al.
     *  The other coefficients come from unpublished work by
     *  W. J. Cody and L. Stoltz at Argonne National Laboratory.
     *  This java code is a fairly straightforward translation of
     *  the freely available Fortran function ALGAMA by Cody and Stolz.
     *  </p>
     */

    public static double logGamma( double y )
    {
        final double PNT68  = 0.6796875D;

        /*  Largest double value for which log(gamma(x)) can be represented.
         */

        final double XBIG   = 2.5E305;

        /*  Approximate fourth root of XBIG.
         */

        final double FRTBIG = 2.25E76;

        /*  Numerator and denominator coefficients for rational minimax
         *  approximation over (0.5,1.5).
         */

        final double d1 =   -5.772156649015328605195174E-1;

        final double[] p1 =
        {
            4.945235359296727046734888E0,
            2.018112620856775083915565E2,
            2.290838373831346393026739E3,
            1.131967205903380828685045E4,
            2.855724635671635335736389E4,
            3.848496228443793359990269E4,
            2.637748787624195437963534E4,
            7.225813979700288197698961E3
        };

        final double[] q1 =
        {
            6.748212550303777196073036E1,
            1.113332393857199323513008E3,
            7.738757056935398733233834E3,
            2.763987074403340708898585E4,
            5.499310206226157329794414E4,
            6.161122180066002127833352E4,
            3.635127591501940507276287E4,
            8.785536302431013170870835E3
        };

        /*  Numerator and denominator coefficients for rational minimax
         *  Approximation over (1.5,4.0).
         */

        final double d2 =   4.227843350984671393993777E-1;

        final double[] p2 =
        {
            4.974607845568932035012064E0,
            5.424138599891070494101986E2,
            1.550693864978364947665077E4,
            1.847932904445632425417223E5,
            1.088204769468828767498470E6,
            3.338152967987029735917223E6,
            5.106661678927352456275255E6,
            3.074109054850539556250927E6
        };

        final double[] q2 =
        {
            1.830328399370592604055942E2,
            7.765049321445005871323047E3,
            1.331903827966074194402448E5,
            1.136705821321969608938755E6,
            5.267964117437946917577538E6,
            1.346701454311101692290052E7,
            1.782736530353274213975932E7,
            9.533095591844353613395747E6
        };

        /*  Numerator and denominator coefficients for rational minimax
         *  Approximation over (4.0,12.0).
         */

        final double d4 = 1.791759469228055000094023D;

        final double[] p4 =
        {
            1.474502166059939948905062E4,
            2.426813369486704502836312E6,
            1.214755574045093227939592E8,
            2.663432449630976949898078E9,
            2.940378956634553899906876E10,
            1.702665737765398868392998E11,
            4.926125793377430887588120E11,
            5.606251856223951465078242E11
        };

        final double[] q4 =
        {
            2.690530175870899333379843E3,
            6.393885654300092398984238E5,
            4.135599930241388052042842E7,
            1.120872109616147941376570E9,
            1.488613728678813811542398E10,
            1.016803586272438228077304E11,
            3.417476345507377132798597E11,
            4.463158187419713286462081E11
        };

        /*  Coefficients for minimax approximation over (12, INF).
         */

        final double[] c =
        {
            -1.910444077728E-03,
            8.4171387781295E-04,
            -5.952379913043012E-04,
            7.93650793500350248E-04,
            -2.777777777777681622553E-03,
            8.333333333333333331554247E-02,
            5.7083835261E-03
        };

        double  res;
        double  corr;
        double  ysq;
        double  xden;
        double  xnum;
        double  xm1;
        double  xm2;
        double  xm4;
                                //  If argument is not a number,
                                //  return that.

        if ( Double.isNaN( y ) )
        {
            return y;
        }

        if ( ( y > 0.0 ) && ( y <= XBIG ) )
        {
            if ( y <= Constants.MACHEPS )
            {
                res = -Math.log( y );
            }
            else if ( y <= 1.5D )
            {
                                // MACHEPS < x <= 1.5

                if ( y < PNT68 )
                {
                    corr    = -Math.log( y );
                    xm1     = y;
                }
                else
                {
                    corr    = 0.0D;
                    xm1     = ( y - 0.5D ) - 0.5D;
                }

                if ( ( y <= 0.5D ) || ( y >= PNT68 ) )
                {
                    xden    = 1.0D;
                    xnum    = 0.0D;

                    for ( int i = 0; i < 8; i++ )
                    {
                        xnum    = xnum * xm1 + p1[ i ];
                        xden    = xden * xm1 + q1[ i ];
                    }

                    res = corr + ( xm1 * ( d1 + xm1 * ( xnum / xden ) ) );
                }
                else
                {
                    xm2     = ( y - 0.5D ) - 0.5D;
                    xden    = 1.0D;
                    xnum    = 0.0D;

                    for ( int i = 0; i < 8; i++ )
                    {
                        xnum    = xnum * xm2 + p2[ i ];
                        xden    = xden * xm2 + q2[ i ];
                    }

                    res = corr + xm1 * ( d2 + xm2 * ( xnum / xden ) );
                }
            }
            else if ( y <= 4.0D )
            {
                                // .5 .LT. X .LE. 4.0

                xm2     = y - 2.0D;
                xden    = 1.0D;
                xnum    = 0.0D;

                for ( int i = 0; i < 8; i++ )
                {
                    xnum    = xnum * xm2 + p2[ i ];
                    xden    = xden * xm2 + q2[ i ];
                }

                res = xm2 * ( d2 + xm2 * ( xnum / xden ) );
            }
            else if ( y <= 12.0D )
            {
                                //  4.0 < x < 12.0

                xm4     = y - 4.0D;
                xden    = -1.0D;
                xnum    = 0.0D;

                for ( int i = 0; i < 8; i++ )
                {
                    xnum    = xnum * xm4 + p4[ i ];
                    xden    = xden * xm4 + q4[ i ];
                }

                res = d4 + xm4 * ( xnum / xden );
            }
            else
            {
                                //  Evaluate for argument .GE. 12.0
                res = 0.0D;

                if ( y <= FRTBIG )
                {
                    res = c[ 6 ];

                    ysq = y * y;

                    for ( int i = 0; i < 6; i++ )
                    {
                        res = res / ysq + c[ i ];
                    }
                }

                res     = res / y;
                corr    = Math.log( y );

                res     = res + Constants.LNSQRT2PI - 0.5D * corr;
                res     = res + y * ( corr - 1.0D );
            }
        }
        else
        {
                                // Return largest possible positive value
                                // for bad arguments.

            res = Double.POSITIVE_INFINITY;
//          res = Double.MAX_VALUE;
        }

        return res;
    }

    /** Cumulative probability density function for the incomplete gamma function.
     *
     *  @param  x       Gamma distribution value
     *  @param  alpha   Shape parameter
     *  @param  dPrec   Digits of precision desired (1 < dPrec < Constants.MAXPREC)
     *  @param  maxIter Maximum number of iterations allowed
     *
     *  @return         Cumulative probability density function value
     *
     *  @throws         IllegalArgumentException
     *                      if x < 0 or alpha <= 0
     *
     *  <p>
     *  Either an infinite series summation or a continued fraction
     *  approximation is used, depending upon the argument range.
     *  See Bhattacharjee GP (1970) The incomplete gamma integral.
     *  Applied Statistics, 19: 285-287 (AS32) .  The result is
     *  accurate to about 14 decimal digits.
     *  </p>
     */

    public static double incompleteGamma
    (
        double x,
        double alpha,
        int dPrec,
        int maxIter
    )
        throws IllegalArgumentException
    {
        final double overflow   = 1.0E+37;
        final double minExp     = -87.0D;

        double[] pn = new double[ 6 ];
        double gin;

        if ( x == 0.0D )
        {
            return 0.0D;
        }

        if ( ( x < 0.0D ) || ( alpha <= 0.0D ) )
        {
            throw new IllegalArgumentException( "x<0 or alpha<=0" );
        }

        double factor = alpha * Math.log( x ) - x - logGamma( alpha );
/*
        if ( factor < minExp )
        {
            return 1.0D;
        }
*/
        factor  = Math.exp( factor );

                                // Set desired precision for results.

        if ( dPrec > Constants.MAXPREC )
        {
            dPrec   = Constants.MAXPREC;
        }
        else if ( dPrec <= 0 )
        {
            dPrec   = 1;
        }

        double epsz = Math.pow( 10.0D , -dPrec );

                                // Choose between infinite series or
                                // continued fraction approximations.

        if ( ( x > 1.0 ) && ( x >= alpha ) )
        {
                                // Continued fraction.

            double a        =   1.0D - alpha;
            double b        =   a + x + 1.0D;

            double term     = 0.0D;

            pn[ 0 ]         = 1.0D;
            pn[ 1 ]         = x;
            pn[ 2 ]         = x + 1.0D;
            pn[ 3 ]         = x * b;

            gin             = pn[ 2 ] / pn[ 3 ];
            int iter        = 0;

            do
            {
                iter++;
                term++;
                a++;

                b           += 2;

                double an   = a * term;

                pn[ 4 ]     = b * pn[ 2 ] - an * pn[ 0 ];
                pn[ 5 ]     = b * pn[ 3 ] - an * pn[ 1 ];

                if ( pn[ 5 ] != 0 )
                {
                    double rn   = pn[ 4 ] / pn[ 5 ];

                    double dif  = Math.abs( gin - rn );

                    if ( dif <= epsz )
                    {
                        if ( dif <= ( epsz * rn ) )
                        {
                            break;
                        }
                    }

                    gin = rn;
                }

                pn[ 0 ] = pn[ 2 ];
                pn[ 1 ] = pn[ 3 ];
                pn[ 2 ] = pn[ 4 ];
                pn[ 3 ] = pn[ 5 ];

                if ( Math.abs( pn[ 4 ] ) >= overflow )
                {
                    pn[ 0 ] /= overflow;
                    pn[ 1 ] /= overflow;
                    pn[ 2 ] /= overflow;
                    pn[ 3 ] /= overflow;
                }
            }
            while ( iter <= maxIter );

            gin = 1.0 - ( factor * gin );
        }
        else
        {
                                // Series expansion.

            int iter        = 0;

            gin             = 1.0D;
            double term     = 1.0D;
            double a        = alpha;

            do
            {
                a++;
                term    *= x / a;
                gin     += term;
            }
            while ( ( iter <= maxIter ) && ( ( term / gin ) > epsz ) );

            gin *= factor / alpha;
        }

        return gin;
    }

    /** Cumulative probability density function for the incomplete gamma function.
     *
     *  @param  x       Gamma distribution value
     *  @param  alpha   Shape parameter
     *
     *  @return         Cumulative probability density function value
     *
     *  @throws         IllegalArgumentException
     *                      if x < 0 or alpha <= 0
     *
     *  <p>
     *  Either an infinite series summation or a continued fraction
     *  approximation is used, depending upon the argument range.
     *  See Bhattacharjee GP (1970) The incomplete gamma integral.
     *  Applied Statistics, 19: 285-287 (AS32) .  The result is
     *  accurate to about 14 decimal digits.
     *  </p>
     */

    public static double incompleteGamma
    (
        double x,
        double alpha
    )
        throws IllegalArgumentException
    {
        return incompleteGamma( x , alpha , Constants.MAXPREC , 1000 );
    }

    /** Make class non-instantiable but inheritable.
     */

    protected Gamma()
    {
    }
}

/*
Copyright (c) 2008, 2013 by Northwestern University.
All rights reserved.

Developed by:
   Academic and Research Technologies
   Northwestern University
   http://www.it.northwestern.edu/about/departments/at/

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal with the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimers.

    * Redistributions in binary form must reproduce the above
      copyright notice, this list of conditions and the following
      disclaimers in the documentation and/or other materials provided
      with the distribution.

    * Neither the names of Academic and Research Technologies,
      Northwestern University, nor the names of its contributors may be
      used to endorse or promote products derived from this Software
      without specific prior written permission.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE.
*/


