mirror of https://github.com/jlizier/jidt
543 lines
20 KiB
Java
543 lines
20 KiB
Java
/*
|
|
* Java Information Dynamics Toolkit (JIDT)
|
|
* Copyright (C) 2017, Joseph T. Lizier
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/*
|
|
* This class was originally distributed as part of the Apache Commons
|
|
* Math3 library (3.6.1), under the Apache License Version 2.0, which is
|
|
* copied below. This Apache 2 software is now included as a derivative
|
|
* work in the GPLv3 licensed JIDT project, as per:
|
|
* http://www.apache.org/licenses/GPL-compatibility.html
|
|
*
|
|
* The original Apache source code has been modified as follows:
|
|
* -- We have modified package names to sit inside the JIDT structure.
|
|
*/
|
|
|
|
/*
|
|
* 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 infodynamics.utils.commonsmath3.special;
|
|
|
|
import infodynamics.utils.commonsmath3.exception.NumberIsTooSmallException;
|
|
import infodynamics.utils.commonsmath3.exception.OutOfRangeException;
|
|
import infodynamics.utils.commonsmath3.util.ContinuedFraction;
|
|
import infodynamics.utils.commonsmath3.util.FastMath;
|
|
|
|
/**
|
|
* <p>
|
|
* This is a utility class that provides computation methods related to the
|
|
* Beta family of functions.
|
|
* </p>
|
|
* <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>
|
|
* <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,</li>
|
|
* </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.
|
|
* </p>
|
|
*
|
|
*
|
|
*/
|
|
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 infodynamics.utils.commonsmath3.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 infodynamics.utils.commonsmath3.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 infodynamics.utils.commonsmath3.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).
|
|
*
|
|
* The implementation of this method is based on:
|
|
* <ul>
|
|
* <li>
|
|
* <a href="http://mathworld.wolfram.com/RegularizedBetaFunction.html">
|
|
* Regularized Beta Function</a>.</li>
|
|
* <li>
|
|
* <a href="http://functions.wolfram.com/06.21.10.0001.01">
|
|
* Regularized Beta Function</a>.</li>
|
|
* </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 infodynamics.utils.commonsmath3.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).
|
|
*
|
|
* The implementation of this method is based on:
|
|
* <ul>
|
|
* <li><a href="http://mathworld.wolfram.com/BetaFunction.html">
|
|
* Beta Function</a>, equation (1).</li>
|
|
* </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));
|
|
}
|
|
}
|
|
}
|
|
}
|