summaryrefslogtreecommitdiff
path: root/src/main/java/org/apache/commons/math3/special/Beta.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/apache/commons/math3/special/Beta.java')
-rw-r--r--src/main/java/org/apache/commons/math3/special/Beta.java478
1 files changed, 478 insertions, 0 deletions
diff --git a/src/main/java/org/apache/commons/math3/special/Beta.java b/src/main/java/org/apache/commons/math3/special/Beta.java
new file mode 100644
index 0000000..2f6b6da
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/special/Beta.java
@@ -0,0 +1,478 @@
+/*
+ * 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.math3.special;
+
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.util.ContinuedFraction;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * This is a utility class that provides computation methods related to the Beta family of
+ * functions.
+ *
+ * <p>Implementation of {@link #logBeta(double, double)} is based on the algorithms described in
+ *
+ * <ul>
+ * <li><a href="http://dx.doi.org/10.1145/22721.23109">Didonato and Morris (1986)</a>,
+ * <em>Computation of the Incomplete Gamma Function Ratios and their Inverse</em>, TOMS 12(4),
+ * 377-393,
+ * <li><a href="http://dx.doi.org/10.1145/131766.131776">Didonato and Morris (1992)</a>,
+ * <em>Algorithm 708: Significant Digit Computation of the Incomplete Beta Function
+ * Ratios</em>, TOMS 18(3), 360-373,
+ * </ul>
+ *
+ * and implemented in the <a href="http://www.dtic.mil/docs/citations/ADA476840">NSWC Library of
+ * Mathematical Functions</a>, available <a
+ * href="http://www.ualberta.ca/CNS/RESEARCH/Software/NumericalNSWC/site.html">here</a>. This
+ * library is "approved for public release", and the <a
+ * href="http://www.dtic.mil/dtic/pdf/announcements/CopyrightGuidance.pdf">Copyright guidance</a>
+ * indicates that unless otherwise stated in the code, all FORTRAN functions in this library are
+ * license free. Since no such notice appears in the code these functions can safely be ported to
+ * Commons-Math.
+ */
+public class Beta {
+ /** Maximum allowed numerical error. */
+ private static final double DEFAULT_EPSILON = 1E-14;
+
+ /** The constant value of ½log 2π. */
+ private static final double HALF_LOG_TWO_PI = .9189385332046727;
+
+ /**
+ * <p>
+ * The coefficients of the series expansion of the Δ function. This function
+ * is defined as follows
+ * </p>
+ * <center>Δ(x) = log Γ(x) - (x - 0.5) log a + a - 0.5 log 2π,</center>
+ * <p>
+ * see equation (23) in Didonato and Morris (1992). The series expansion,
+ * which applies for x ≥ 10, reads
+ * </p>
+ * <pre>
+ * 14
+ * ====
+ * 1 \ 2 n
+ * Δ(x) = --- > d (10 / x)
+ * x / n
+ * ====
+ * n = 0
+ * <pre>
+ */
+ private static final double[] DELTA = {
+ .833333333333333333333333333333E-01,
+ -.277777777777777777777777752282E-04,
+ .793650793650793650791732130419E-07,
+ -.595238095238095232389839236182E-09,
+ .841750841750832853294451671990E-11,
+ -.191752691751854612334149171243E-12,
+ .641025640510325475730918472625E-14,
+ -.295506514125338232839867823991E-15,
+ .179643716359402238723287696452E-16,
+ -.139228964661627791231203060395E-17,
+ .133802855014020915603275339093E-18,
+ -.154246009867966094273710216533E-19,
+ .197701992980957427278370133333E-20,
+ -.234065664793997056856992426667E-21,
+ .171348014966398575409015466667E-22
+ };
+
+ /** Default constructor. Prohibit instantiation. */
+ private Beta() {}
+
+ /**
+ * Returns the <a href="http://mathworld.wolfram.com/RegularizedBetaFunction.html">regularized
+ * beta function</a> I(x, a, b).
+ *
+ * @param x Value.
+ * @param a Parameter {@code a}.
+ * @param b Parameter {@code b}.
+ * @return the regularized beta function I(x, a, b).
+ * @throws org.apache.commons.math3.exception.MaxCountExceededException if the algorithm fails
+ * to converge.
+ */
+ public static double regularizedBeta(double x, double a, double b) {
+ return regularizedBeta(x, a, b, DEFAULT_EPSILON, Integer.MAX_VALUE);
+ }
+
+ /**
+ * Returns the <a href="http://mathworld.wolfram.com/RegularizedBetaFunction.html">regularized
+ * beta function</a> I(x, a, b).
+ *
+ * @param x Value.
+ * @param a Parameter {@code a}.
+ * @param b Parameter {@code b}.
+ * @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.
+ * @return the regularized beta function I(x, a, b)
+ * @throws org.apache.commons.math3.exception.MaxCountExceededException if the algorithm fails
+ * to converge.
+ */
+ public static double regularizedBeta(double x, double a, double b, double epsilon) {
+ return regularizedBeta(x, a, b, epsilon, Integer.MAX_VALUE);
+ }
+
+ /**
+ * Returns the regularized beta function I(x, a, b).
+ *
+ * @param x the value.
+ * @param a Parameter {@code a}.
+ * @param b Parameter {@code b}.
+ * @param maxIterations Maximum number of "iterations" to complete.
+ * @return the regularized beta function I(x, a, b)
+ * @throws org.apache.commons.math3.exception.MaxCountExceededException if the algorithm fails
+ * to converge.
+ */
+ public static double regularizedBeta(double x, double a, double b, int maxIterations) {
+ return regularizedBeta(x, a, b, DEFAULT_EPSILON, maxIterations);
+ }
+
+ /**
+ * Returns the regularized beta function I(x, a, b).
+ *
+ * <p>The implementation of this method is based on:
+ *
+ * <ul>
+ * <li><a href="http://mathworld.wolfram.com/RegularizedBetaFunction.html">Regularized Beta
+ * Function</a>.
+ * <li><a href="http://functions.wolfram.com/06.21.10.0001.01">Regularized Beta Function</a>.
+ * </ul>
+ *
+ * @param x the value.
+ * @param a Parameter {@code a}.
+ * @param b Parameter {@code b}.
+ * @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 beta function I(x, a, b)
+ * @throws org.apache.commons.math3.exception.MaxCountExceededException if the algorithm fails
+ * to converge.
+ */
+ public static double regularizedBeta(
+ double x, final double a, final double b, double epsilon, int maxIterations) {
+ double ret;
+
+ if (Double.isNaN(x)
+ || Double.isNaN(a)
+ || Double.isNaN(b)
+ || x < 0
+ || x > 1
+ || a <= 0
+ || b <= 0) {
+ ret = Double.NaN;
+ } else if (x > (a + 1) / (2 + b + a) && 1 - x <= (b + 1) / (2 + b + a)) {
+ ret = 1 - regularizedBeta(1 - x, b, a, epsilon, maxIterations);
+ } else {
+ ContinuedFraction fraction =
+ new ContinuedFraction() {
+
+ /** {@inheritDoc} */
+ @Override
+ protected double getB(int n, double x) {
+ double ret;
+ double m;
+ if (n % 2 == 0) { // even
+ m = n / 2.0;
+ ret = (m * (b - m) * x) / ((a + (2 * m) - 1) * (a + (2 * m)));
+ } else {
+ m = (n - 1.0) / 2.0;
+ ret =
+ -((a + m) * (a + b + m) * x)
+ / ((a + (2 * m)) * (a + (2 * m) + 1.0));
+ }
+ return ret;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected double getA(int n, double x) {
+ return 1.0;
+ }
+ };
+ ret =
+ FastMath.exp(
+ (a * FastMath.log(x))
+ + (b * FastMath.log1p(-x))
+ - FastMath.log(a)
+ - logBeta(a, b))
+ * 1.0
+ / fraction.evaluate(x, epsilon, maxIterations);
+ }
+
+ return ret;
+ }
+
+ /**
+ * Returns the natural logarithm of the beta function B(a, b).
+ *
+ * <p>The implementation of this method is based on:
+ *
+ * <ul>
+ * <li><a href="http://mathworld.wolfram.com/BetaFunction.html">Beta Function</a>, equation
+ * (1).
+ * </ul>
+ *
+ * @param a Parameter {@code a}.
+ * @param b Parameter {@code b}.
+ * @param epsilon This parameter is ignored.
+ * @param maxIterations This parameter is ignored.
+ * @return log(B(a, b)).
+ * @deprecated as of version 3.1, this method is deprecated as the computation of the beta
+ * function is no longer iterative; it will be removed in version 4.0. Current
+ * implementation of this method internally calls {@link #logBeta(double, double)}.
+ */
+ @Deprecated
+ public static double logBeta(double a, double b, double epsilon, int maxIterations) {
+
+ return logBeta(a, b);
+ }
+
+ /**
+ * Returns the value of log Γ(a + b) for 1 ≤ a, b ≤ 2. Based on the <em>NSWC Library of
+ * Mathematics Subroutines</em> double precision implementation, {@code DGSMLN}. In {@code
+ * BetaTest.testLogGammaSum()}, this private method is accessed through reflection.
+ *
+ * @param a First argument.
+ * @param b Second argument.
+ * @return the value of {@code log(Gamma(a + b))}.
+ * @throws OutOfRangeException if {@code a} or {@code b} is lower than {@code 1.0} or greater
+ * than {@code 2.0}.
+ */
+ private static double logGammaSum(final double a, final double b) throws OutOfRangeException {
+
+ if ((a < 1.0) || (a > 2.0)) {
+ throw new OutOfRangeException(a, 1.0, 2.0);
+ }
+ if ((b < 1.0) || (b > 2.0)) {
+ throw new OutOfRangeException(b, 1.0, 2.0);
+ }
+
+ final double x = (a - 1.0) + (b - 1.0);
+ if (x <= 0.5) {
+ return Gamma.logGamma1p(1.0 + x);
+ } else if (x <= 1.5) {
+ return Gamma.logGamma1p(x) + FastMath.log1p(x);
+ } else {
+ return Gamma.logGamma1p(x - 1.0) + FastMath.log(x * (1.0 + x));
+ }
+ }
+
+ /**
+ * Returns the value of log[Γ(b) / Γ(a + b)] for a ≥ 0 and b ≥ 10. Based on the <em>NSWC Library
+ * of Mathematics Subroutines</em> double precision implementation, {@code DLGDIV}. In {@code
+ * BetaTest.testLogGammaMinusLogGammaSum()}, this private method is accessed through reflection.
+ *
+ * @param a First argument.
+ * @param b Second argument.
+ * @return the value of {@code log(Gamma(b) / Gamma(a + b))}.
+ * @throws NumberIsTooSmallException if {@code a < 0.0} or {@code b < 10.0}.
+ */
+ private static double logGammaMinusLogGammaSum(final double a, final double b)
+ throws NumberIsTooSmallException {
+
+ if (a < 0.0) {
+ throw new NumberIsTooSmallException(a, 0.0, true);
+ }
+ if (b < 10.0) {
+ throw new NumberIsTooSmallException(b, 10.0, true);
+ }
+
+ /*
+ * d = a + b - 0.5
+ */
+ final double d;
+ final double w;
+ if (a <= b) {
+ d = b + (a - 0.5);
+ w = deltaMinusDeltaSum(a, b);
+ } else {
+ d = a + (b - 0.5);
+ w = deltaMinusDeltaSum(b, a);
+ }
+
+ final double u = d * FastMath.log1p(a / b);
+ final double v = a * (FastMath.log(b) - 1.0);
+
+ return u <= v ? (w - u) - v : (w - v) - u;
+ }
+
+ /**
+ * Returns the value of Δ(b) - Δ(a + b), with 0 ≤ a ≤ b and b ≥ 10. Based on equations (26),
+ * (27) and (28) in Didonato and Morris (1992).
+ *
+ * @param a First argument.
+ * @param b Second argument.
+ * @return the value of {@code Delta(b) - Delta(a + b)}
+ * @throws OutOfRangeException if {@code a < 0} or {@code a > b}
+ * @throws NumberIsTooSmallException if {@code b < 10}
+ */
+ private static double deltaMinusDeltaSum(final double a, final double b)
+ throws OutOfRangeException, NumberIsTooSmallException {
+
+ if ((a < 0) || (a > b)) {
+ throw new OutOfRangeException(a, 0, b);
+ }
+ if (b < 10) {
+ throw new NumberIsTooSmallException(b, 10, true);
+ }
+
+ final double h = a / b;
+ final double p = h / (1.0 + h);
+ final double q = 1.0 / (1.0 + h);
+ final double q2 = q * q;
+ /*
+ * s[i] = 1 + q + ... - q**(2 * i)
+ */
+ final double[] s = new double[DELTA.length];
+ s[0] = 1.0;
+ for (int i = 1; i < s.length; i++) {
+ s[i] = 1.0 + (q + q2 * s[i - 1]);
+ }
+ /*
+ * w = Delta(b) - Delta(a + b)
+ */
+ final double sqrtT = 10.0 / b;
+ final double t = sqrtT * sqrtT;
+ double w = DELTA[DELTA.length - 1] * s[s.length - 1];
+ for (int i = DELTA.length - 2; i >= 0; i--) {
+ w = t * w + DELTA[i] * s[i];
+ }
+ return w * p / b;
+ }
+
+ /**
+ * Returns the value of Δ(p) + Δ(q) - Δ(p + q), with p, q ≥ 10. Based on the <em>NSWC Library of
+ * Mathematics Subroutines</em> double precision implementation, {@code DBCORR}. In {@code
+ * BetaTest.testSumDeltaMinusDeltaSum()}, this private method is accessed through reflection.
+ *
+ * @param p First argument.
+ * @param q Second argument.
+ * @return the value of {@code Delta(p) + Delta(q) - Delta(p + q)}.
+ * @throws NumberIsTooSmallException if {@code p < 10.0} or {@code q < 10.0}.
+ */
+ private static double sumDeltaMinusDeltaSum(final double p, final double q) {
+
+ if (p < 10.0) {
+ throw new NumberIsTooSmallException(p, 10.0, true);
+ }
+ if (q < 10.0) {
+ throw new NumberIsTooSmallException(q, 10.0, true);
+ }
+
+ final double a = FastMath.min(p, q);
+ final double b = FastMath.max(p, q);
+ final double sqrtT = 10.0 / a;
+ final double t = sqrtT * sqrtT;
+ double z = DELTA[DELTA.length - 1];
+ for (int i = DELTA.length - 2; i >= 0; i--) {
+ z = t * z + DELTA[i];
+ }
+ return z / a + deltaMinusDeltaSum(a, b);
+ }
+
+ /**
+ * Returns the value of log B(p, q) for 0 ≤ x ≤ 1 and p, q > 0. Based on the <em>NSWC Library of
+ * Mathematics Subroutines</em> implementation, {@code DBETLN}.
+ *
+ * @param p First argument.
+ * @param q Second argument.
+ * @return the value of {@code log(Beta(p, q))}, {@code NaN} if {@code p <= 0} or {@code q <=
+ * 0}.
+ */
+ public static double logBeta(final double p, final double q) {
+ if (Double.isNaN(p) || Double.isNaN(q) || (p <= 0.0) || (q <= 0.0)) {
+ return Double.NaN;
+ }
+
+ final double a = FastMath.min(p, q);
+ final double b = FastMath.max(p, q);
+ if (a >= 10.0) {
+ final double w = sumDeltaMinusDeltaSum(a, b);
+ final double h = a / b;
+ final double c = h / (1.0 + h);
+ final double u = -(a - 0.5) * FastMath.log(c);
+ final double v = b * FastMath.log1p(h);
+ if (u <= v) {
+ return (((-0.5 * FastMath.log(b) + HALF_LOG_TWO_PI) + w) - u) - v;
+ } else {
+ return (((-0.5 * FastMath.log(b) + HALF_LOG_TWO_PI) + w) - v) - u;
+ }
+ } else if (a > 2.0) {
+ if (b > 1000.0) {
+ final int n = (int) FastMath.floor(a - 1.0);
+ double prod = 1.0;
+ double ared = a;
+ for (int i = 0; i < n; i++) {
+ ared -= 1.0;
+ prod *= ared / (1.0 + ared / b);
+ }
+ return (FastMath.log(prod) - n * FastMath.log(b))
+ + (Gamma.logGamma(ared) + logGammaMinusLogGammaSum(ared, b));
+ } else {
+ double prod1 = 1.0;
+ double ared = a;
+ while (ared > 2.0) {
+ ared -= 1.0;
+ final double h = ared / b;
+ prod1 *= h / (1.0 + h);
+ }
+ if (b < 10.0) {
+ double prod2 = 1.0;
+ double bred = b;
+ while (bred > 2.0) {
+ bred -= 1.0;
+ prod2 *= bred / (ared + bred);
+ }
+ return FastMath.log(prod1)
+ + FastMath.log(prod2)
+ + (Gamma.logGamma(ared)
+ + (Gamma.logGamma(bred) - logGammaSum(ared, bred)));
+ } else {
+ return FastMath.log(prod1)
+ + Gamma.logGamma(ared)
+ + logGammaMinusLogGammaSum(ared, b);
+ }
+ }
+ } else if (a >= 1.0) {
+ if (b > 2.0) {
+ if (b < 10.0) {
+ double prod = 1.0;
+ double bred = b;
+ while (bred > 2.0) {
+ bred -= 1.0;
+ prod *= bred / (a + bred);
+ }
+ return FastMath.log(prod)
+ + (Gamma.logGamma(a) + (Gamma.logGamma(bred) - logGammaSum(a, bred)));
+ } else {
+ return Gamma.logGamma(a) + logGammaMinusLogGammaSum(a, b);
+ }
+ } else {
+ return Gamma.logGamma(a) + Gamma.logGamma(b) - logGammaSum(a, b);
+ }
+ } else {
+ if (b >= 10.0) {
+ return Gamma.logGamma(a) + logGammaMinusLogGammaSum(a, b);
+ } else {
+ // The following command is the original NSWC implementation.
+ // return Gamma.logGamma(a) +
+ // (Gamma.logGamma(b) - Gamma.logGamma(a + b));
+ // The following command turns out to be more accurate.
+ return FastMath.log(Gamma.gamma(a) * Gamma.gamma(b) / Gamma.gamma(a + b));
+ }
+ }
+ }
+}