/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.commons.math.special;
import java.io.Serializable;
import org.apache.commons.math.MathException;
import org.apache.commons.math.MaxIterationsExceededException;
import org.apache.commons.math.util.ContinuedFraction;
/**
* This is a utility class that provides computation methods related to the
* Gamma family of functions.
*
* @version $Revision: 549278 $ $Date: 2007-06-20 15:24:04 -0700 (Wed, 20 Jun 2007) $
*/
public class Gamma implements Serializable {
/** Serializable version identifier */
private static final long serialVersionUID = -6587513359895466954L;
/** Maximum allowed numerical error. */
private static final double DEFAULT_EPSILON = 10e-15;
/** Lanczos coefficients */
private static double[] lanczos =
{
0.99999999999999709182,
57.156235665862923517,
-59.597960355475491248,
14.136097974741747174,
-0.49191381609762019978,
.33994649984811888699e-4,
.46523628927048575665e-4,
-.98374475304879564677e-4,
.15808870322491248884e-3,
-.21026444172410488319e-3,
.21743961811521264320e-3,
-.16431810653676389022e-3,
.84418223983852743293e-4,
-.26190838401581408670e-4,
.36899182659531622704e-5,
};
/** Avoid repeated computation of log of 2 PI in logGamma */
private static final double HALF_LOG_2_PI = 0.5 * Math.log(2.0 * Math.PI);
/**
* Default constructor. Prohibit instantiation.
*/
private Gamma() {
super();
}
/**
* Returns the natural logarithm of the gamma function Γ(x).
*
* The implementation of this method is based on:
*
*
* @param x the value.
* @return log(Γ(x))
*/
public static double logGamma(double x) {
double ret;
if (Double.isNaN(x) || (x <= 0.0)) {
ret = Double.NaN;
} else {
double g = 607.0 / 128.0;
double sum = 0.0;
for (int i = lanczos.length - 1; i > 0; --i) {
sum = sum + (lanczos[i] / (x + i));
}
sum = sum + lanczos[0];
double tmp = x + g + .5;
ret = ((x + .5) * Math.log(tmp)) - tmp +
HALF_LOG_2_PI + Math.log(sum / x);
}
return ret;
}
/**
* Returns the regularized gamma function P(a, x).
*
* @param a the a parameter.
* @param x the value.
* @return the regularized gamma function P(a, x)
* @throws MathException if the algorithm fails to converge.
*/
public static double regularizedGammaP(double a, double x)
throws MathException
{
return regularizedGammaP(a, x, DEFAULT_EPSILON, Integer.MAX_VALUE);
}
/**
* Returns the regularized gamma function P(a, x).
*
* The implementation of this method is based on:
*
*
* @param a the a parameter.
* @param x the value.
* @param epsilon When the absolute value of the nth item in the
* series is less than epsilon the approximation ceases
* to calculate further elements in the series.
* @param maxIterations Maximum number of "iterations" to complete.
* @return the regularized gamma function P(a, x)
* @throws MathException if the algorithm fails to converge.
*/
public static double regularizedGammaP(double a,
double x,
double epsilon,
int maxIterations)
throws MathException
{
double ret;
if (Double.isNaN(a) || Double.isNaN(x) || (a <= 0.0) || (x < 0.0)) {
ret = Double.NaN;
} else if (x == 0.0) {
ret = 0.0;
} else if (a >= 1.0 && x > a) {
// use regularizedGammaQ because it should converge faster in this
// case.
ret = 1.0 - regularizedGammaQ(a, x, epsilon, maxIterations);
} else {
// calculate series
double n = 0.0; // current element index
double an = 1.0 / a; // n-th element in the series
double sum = an; // partial sum
while (Math.abs(an) > epsilon && n < maxIterations) {
// compute next element in the series
n = n + 1.0;
an = an * (x / (a + n));
// update partial sum
sum = sum + an;
}
if (n >= maxIterations) {
throw new MaxIterationsExceededException(maxIterations);
} else {
ret = Math.exp(-x + (a * Math.log(x)) - logGamma(a)) * sum;
}
}
return ret;
}
/**
* Returns the regularized gamma function Q(a, x) = 1 - P(a, x).
*
* @param a the a parameter.
* @param x the value.
* @return the regularized gamma function Q(a, x)
* @throws MathException if the algorithm fails to converge.
*/
public static double regularizedGammaQ(double a, double x)
throws MathException
{
return regularizedGammaQ(a, x, DEFAULT_EPSILON, Integer.MAX_VALUE);
}
/**
* Returns the regularized gamma function Q(a, x) = 1 - P(a, x).
*
* The implementation of this method is based on:
*
*
* @param a the a parameter.
* @param x the value.
* @param epsilon When the absolute value of the nth item in the
* series is less than epsilon the approximation ceases
* to calculate further elements in the series.
* @param maxIterations Maximum number of "iterations" to complete.
* @return the regularized gamma function P(a, x)
* @throws MathException if the algorithm fails to converge.
*/
public static double regularizedGammaQ(final double a,
double x,
double epsilon,
int maxIterations)
throws MathException
{
double ret;
if (Double.isNaN(a) || Double.isNaN(x) || (a <= 0.0) || (x < 0.0)) {
ret = Double.NaN;
} else if (x == 0.0) {
ret = 1.0;
} else if (x < a || a < 1.0) {
// use regularizedGammaP because it should converge faster in this
// case.
ret = 1.0 - regularizedGammaP(a, x, epsilon, maxIterations);
} else {
// create continued fraction
ContinuedFraction cf = new ContinuedFraction() {
private static final long serialVersionUID = 5378525034886164398L;
protected double getA(int n, double x) {
return ((2.0 * n) + 1.0) - a + x;
}
protected double getB(int n, double x) {
return n * (a - n);
}
};
ret = 1.0 / cf.evaluate(x, epsilon, maxIterations);
ret = Math.exp(-x + (a * Math.log(x)) - logGamma(a)) * ret;
}
return ret;
}
}