//-------------------------------------------------------------------//
//
// Syntax:	gamma ( A )
//		gammaln ( A )
//		gammp ( A , X )
//		gammq ( A , X )

// Description:

// gamma:	Computes the value of the gamma function. The input, A
//		must be a row, or column vector. Allowable values of A
//		are: A > 0.  

// gammln:	Computes the value of the log-gamma function.

// gammp:	Computes the value of the incomplete gamma function,
//		P(a,x). 
// 		Reference: Numerical Recipes in C, pp 172.

// gammq:	Computes the value of the incomplete gamma function,
//		Q(a,x) = 1 - P(a,x). 
// 		Reference: Numerical Recipes in C, pp 173.

//-------------------------------------------------------------------//


static (gser, gcf);

gamma = function( A )
{
  if (!all (all (A >= 0))) { error ("valid range for gamma() 0:inf"); }
  return exp( gammaln( A ) );
};

gammaln = function( A )
{
  local(a, cof, j, ser, tmp);

  if( !all (all (A >= 0)) )	// Test for correct input range
  { 
    return inf(); 
  }

  cof = [76.18009173, -86.50532033, 24.01409822, ...
         -1.231739516, 0.120858003e-2, -0.536382e-5];

  a = A - 1;
  tmp = a + 5.5;
  tmp = tmp - (a + 0.5) .* log(tmp);
  ser = 1;
  for( j in 1:6 )
  {
    a = a + 1;
    ser = ser + cof[j]./a;
  }
  return ( -tmp + log(2.50662827465*ser) );
};

//
// Computes the value of the incomplete gamma function, P(a,x).
// Currently the input, A, must be a row, or column matrix.
//

gammp = function( X, A )
{
  local(a, gammcf, gammser, gln, g, i);

  if( any (any (X < 0)) || any (any (A < 0))) {
    error ("invalid arguments to gammp()");
  }

  if (!all (size (X) == size (A)))
  {
    if (A.n != 1) 
    { 
      error ("size of 2nd arg must match 1st, or be 1-by-1");
    else
      a = A.*ones (size (X));
    }
  else
    a = A;
  }
  g = zeros (size (X));
  for (i in 1:X.n)
  {
    if(X[i] < (a[i] + 1))
    {
      g[i] =  gser (a[i], X[i], gln);
    else
      g[i] =  1 - gcf (a[i], X[i], gln);
    }
  }
  return g;
};

//
// Computes the value of the incomplete gamma function, 
// Q(a,x) = 1 - P(a,x). 
//

gammq = function( X , A )
{
  local(a, gammcf, gammser, gln, g, i);

  if(any (any (X < 0)) || any (any (A <= 0))) {
    error ("invalid arguments to gammq()");
  }

  if (!all (size (A) == size (X)))
  {
    if (A.n != 1) 
    { 
      error ("size of 2nd arg must match 1st, or be 1-by-1");
    else
      a = A.*ones (size (X));
    }
  else
    a = A;
  }
  g = zeros (size (X));
  for (i in 1:X.n)
  {
    if(X[i] < (a[i] + 1))
    {
      g[i] = 1 - gser ( a[i], X[i], gln );
    else
      g[i] = gcf ( a[i], X[i], gln );
    }
  }
  return g;
};

//-------------------------------------------------------------------//
//
// Syntax:	gser ( A, X, GLN )

// Description:

// Computes the value of the incomplete gamma function, evaluated by
// it's series representation.

// Reference: Numerical Recipes in C, pp 173.
//-------------------------------------------------------------------//

gser = function( A, X, GLN )
{
  local(ITMAX, ap, del, eps, n, sum);

  GLN = gammaln(A);
  if( any (any(X <= 0)))
  {
    if( any( X < 0 ) ) {
      error("X less than 0 in function gser()");
    }
    return 0;
  else
    ITMAX = 100; eps = epsilon();
    ap = A;
    del = sum = 1./A;
    for( n in 1:ITMAX )
    {
      ap = ap + 1;
      del = del .* X./ap;
      sum = sum + del;
      if( all (all(abs (del) < abs (sum).*eps)))
      {
        return sum.*exp(-X + A.*log(X) - GLN);
      }
    }
    error("A too large, ITMAX too small in function gser()");
    return 0;
  }
};

//-------------------------------------------------------------------//
//
// Syntax:	gcf ( A, X, GLN )

// Description:

// Computes the value of the incomplete gamma function, evaluated by
// it's continued fraction representation.

// Reference: Numerical Recipes in C, pp 174.
//-------------------------------------------------------------------//

gcf = function( A, X, GLN )
{
  local(ITMAX, a0, a1, an, ana, anf, b0, b1, eps, fac, g, gold, n);

  ITMAX = 100;
  a0 = 1; b0 = 0; b1 = 1; fac = 1; gold = 0;
  eps = epsilon();
  GLN = gammaln(A);
  a1 = X;
  for( n in 1:ITMAX )
  {
    an = n;
    ana = an - A;
    a0 = (a1 + a0.*ana).*fac;
    b0 = (b1 + b0.*ana).*fac;
    anf = an.*fac;
    a1 = X.*a0 + anf.*a1;
    b1 = X.*b0 + anf.*b1;
    if( all(a1) )
    {
      fac = 1./a1;
      g = b1.*fac;
      if( abs((g - gold)/g) < eps )
      {
        return exp(-X + A.*log(X) - GLN).*g;
      }
    gold = g;
    }
  }
  error("A too large, ITMAX too small in function gcf()");
};
