001: /*
002: * Copyright 2003-2004 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.apache.commons.math.special;
017:
018: import java.io.Serializable;
019:
020: import org.apache.commons.math.ConvergenceException;
021: import org.apache.commons.math.MathException;
022: import org.apache.commons.math.util.ContinuedFraction;
023:
024: /**
025: * This is a utility class that provides computation methods related to the
026: * Gamma family of functions.
027: *
028: * @version $Revision: 233121 $ $Date: 2005-08-16 21:41:02 -0700 (Tue, 16 Aug 2005) $
029: */
030: public class Gamma implements Serializable {
031:
032: /** Maximum allowed numerical error. */
033: private static final double DEFAULT_EPSILON = 10e-9;
034:
035: /** Lanczos coefficients */
036: private static double[] lanczos = { 0.99999999999999709182,
037: 57.156235665862923517, -59.597960355475491248,
038: 14.136097974741747174, -0.49191381609762019978,
039: .33994649984811888699e-4, .46523628927048575665e-4,
040: -.98374475304879564677e-4, .15808870322491248884e-3,
041: -.21026444172410488319e-3, .21743961811521264320e-3,
042: -.16431810653676389022e-3, .84418223983852743293e-4,
043: -.26190838401581408670e-4, .36899182659531622704e-5, };
044:
045: /** Avoid repeated computation of log of 2 PI in logGamma */
046: private static final double HALF_LOG_2_PI = 0.5 * Math
047: .log(2.0 * Math.PI);
048:
049: /**
050: * Default constructor. Prohibit instantiation.
051: */
052: private Gamma() {
053: super ();
054: }
055:
056: /**
057: * Returns the natural logarithm of the gamma function Γ(x).
058: *
059: * The implementation of this method is based on:
060: * <ul>
061: * <li><a href="http://mathworld.wolfram.com/GammaFunction.html">
062: * Gamma Function</a>, equation (28).</li>
063: * <li><a href="http://mathworld.wolfram.com/LanczosApproximation.html">
064: * Lanczos Approximation</a>, equations (1) through (5).</li>
065: * <li><a href="http://my.fit.edu/~gabdo/gamma.txt">Paul Godfrey, A note on
066: * the computation of the convergent Lanczos complex Gamma approximation
067: * </a></li>
068: * </ul>
069: *
070: * @param x the value.
071: * @return log(Γ(x))
072: */
073: public static double logGamma(double x) {
074: double ret;
075:
076: if (Double.isNaN(x) || (x <= 0.0)) {
077: ret = Double.NaN;
078: } else {
079: double g = 607.0 / 128.0;
080:
081: double sum = 0.0;
082: for (int i = lanczos.length - 1; i > 0; --i) {
083: sum = sum + (lanczos[i] / (x + i));
084: }
085: sum = sum + lanczos[0];
086:
087: double tmp = x + g + .5;
088: ret = ((x + .5) * Math.log(tmp)) - tmp + HALF_LOG_2_PI
089: + Math.log(sum / x);
090: }
091:
092: return ret;
093: }
094:
095: /**
096: * Returns the regularized gamma function P(a, x).
097: *
098: * @param a the a parameter.
099: * @param x the value.
100: * @return the regularized gamma function P(a, x)
101: * @throws MathException if the algorithm fails to converge.
102: */
103: public static double regularizedGammaP(double a, double x)
104: throws MathException {
105: return regularizedGammaP(a, x, DEFAULT_EPSILON,
106: Integer.MAX_VALUE);
107: }
108:
109: /**
110: * Returns the regularized gamma function P(a, x).
111: *
112: * The implementation of this method is based on:
113: * <ul>
114: * <li>
115: * <a href="http://mathworld.wolfram.com/RegularizedGammaFunction.html">
116: * Regularized Gamma Function</a>, equation (1).</li>
117: * <li>
118: * <a href="http://mathworld.wolfram.com/IncompleteGammaFunction.html">
119: * Incomplete Gamma Function</a>, equation (4).</li>
120: * <li>
121: * <a href="http://mathworld.wolfram.com/ConfluentHypergeometricFunctionoftheFirstKind.html">
122: * Confluent Hypergeometric Function of the First Kind</a>, equation (1).
123: * </li>
124: * </ul>
125: *
126: * @param a the a parameter.
127: * @param x the value.
128: * @param epsilon When the absolute value of the nth item in the
129: * series is less than epsilon the approximation ceases
130: * to calculate further elements in the series.
131: * @param maxIterations Maximum number of "iterations" to complete.
132: * @return the regularized gamma function P(a, x)
133: * @throws MathException if the algorithm fails to converge.
134: */
135: public static double regularizedGammaP(double a, double x,
136: double epsilon, int maxIterations) throws MathException {
137: double ret;
138:
139: if (Double.isNaN(a) || Double.isNaN(x) || (a <= 0.0)
140: || (x < 0.0)) {
141: ret = Double.NaN;
142: } else if (x == 0.0) {
143: ret = 0.0;
144: } else if (a >= 1.0 && x > a) {
145: // use regularizedGammaQ because it should converge faster in this
146: // case.
147: ret = 1.0 - regularizedGammaQ(a, x, epsilon, maxIterations);
148: } else {
149: // calculate series
150: double n = 0.0; // current element index
151: double an = 1.0 / a; // n-th element in the series
152: double sum = an; // partial sum
153: while (Math.abs(an) > epsilon && n < maxIterations) {
154: // compute next element in the series
155: n = n + 1.0;
156: an = an * (x / (a + n));
157:
158: // update partial sum
159: sum = sum + an;
160: }
161: if (n >= maxIterations) {
162: throw new ConvergenceException(
163: "maximum number of iterations reached");
164: } else {
165: ret = Math.exp(-x + (a * Math.log(x)) - logGamma(a))
166: * sum;
167: }
168: }
169:
170: return ret;
171: }
172:
173: /**
174: * Returns the regularized gamma function Q(a, x) = 1 - P(a, x).
175: *
176: * @param a the a parameter.
177: * @param x the value.
178: * @return the regularized gamma function Q(a, x)
179: * @throws MathException if the algorithm fails to converge.
180: */
181: public static double regularizedGammaQ(double a, double x)
182: throws MathException {
183: return regularizedGammaQ(a, x, DEFAULT_EPSILON,
184: Integer.MAX_VALUE);
185: }
186:
187: /**
188: * Returns the regularized gamma function Q(a, x) = 1 - P(a, x).
189: *
190: * The implementation of this method is based on:
191: * <ul>
192: * <li>
193: * <a href="http://mathworld.wolfram.com/RegularizedGammaFunction.html">
194: * Regularized Gamma Function</a>, equation (1).</li>
195: * <li>
196: * <a href=" http://functions.wolfram.com/GammaBetaErf/GammaRegularized/10/0003/">
197: * Regularized incomplete gamma function: Continued fraction representations (formula 06.08.10.0003)</a></li>
198: * </ul>
199: *
200: * @param a the a parameter.
201: * @param x the value.
202: * @param epsilon When the absolute value of the nth item in the
203: * series is less than epsilon the approximation ceases
204: * to calculate further elements in the series.
205: * @param maxIterations Maximum number of "iterations" to complete.
206: * @return the regularized gamma function P(a, x)
207: * @throws MathException if the algorithm fails to converge.
208: */
209: public static double regularizedGammaQ(final double a, double x,
210: double epsilon, int maxIterations) throws MathException {
211: double ret;
212:
213: if (Double.isNaN(a) || Double.isNaN(x) || (a <= 0.0)
214: || (x < 0.0)) {
215: ret = Double.NaN;
216: } else if (x == 0.0) {
217: ret = 1.0;
218: } else if (x < a || a < 1.0) {
219: // use regularizedGammaP because it should converge faster in this
220: // case.
221: ret = 1.0 - regularizedGammaP(a, x, epsilon, maxIterations);
222: } else {
223: // create continued fraction
224: ContinuedFraction cf = new ContinuedFraction() {
225: protected double getA(int n, double x) {
226: return ((2.0 * n) + 1.0) - a + x;
227: }
228:
229: protected double getB(int n, double x) {
230: return n * (a - n);
231: }
232: };
233:
234: ret = 1.0 / cf.evaluate(x, epsilon, maxIterations);
235: ret = Math.exp(-x + (a * Math.log(x)) - logGamma(a)) * ret;
236: }
237:
238: return ret;
239: }
240: }
|