| /* |
| * 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.numbers.gamma; |
| |
| /** |
| * Computes \( log_e \Beta(p, q) \). |
| * <p> |
| * This class is immutable. |
| * </p> |
| */ |
| public final class LogBeta { |
| /** The threshold value of 10 where the series expansion of the Δ function applies. */ |
| private static final double TEN = 10; |
| /** The threshold value of 2 for algorithm switch. */ |
| private static final double TWO = 2; |
| /** The threshold value of 1000 for algorithm switch. */ |
| private static final double THOUSAND = 1000; |
| |
| /** The constant value of ½log 2π. */ |
| private static final double HALF_LOG_TWO_PI = 0.9189385332046727; |
| |
| /** |
| * The coefficients of the series expansion of the Δ function. This function |
| * is defined as follows: |
| * <pre> |
| * Δ(x) = log Γ(x) - (x - 0.5) log a + a - 0.5 log 2π, |
| * </pre> |
| * <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 |
| }; |
| |
| /** Private constructor. */ |
| private LogBeta() { |
| // intentional empty. |
| } |
| |
| /** |
| * 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 IllegalArgumentException if {@code a < 0} or {@code a > b} |
| * @throws IllegalArgumentException if {@code b < 10} |
| */ |
| private static double deltaMinusDeltaSum(final double a, |
| final double b) { |
| if (a < 0 || |
| a > b) { |
| throw new GammaException(GammaException.OUT_OF_RANGE, a, 0, b); |
| } |
| if (b < TEN) { |
| throw new GammaException(GammaException.OUT_OF_RANGE, b, TEN, Double.POSITIVE_INFINITY); |
| } |
| |
| final double h = a / b; |
| final double p = h / (1 + h); |
| final double q = 1 / (1 + h); |
| final double q2 = q * q; |
| /* |
| * s[i] = 1 + q + ... - q**(2 * i) |
| */ |
| final double[] s = new double[DELTA.length]; |
| s[0] = 1; |
| for (int i = 1; i < s.length; i++) { |
| s[i] = 1 + (q + q2 * s[i - 1]); |
| } |
| /* |
| * w = Delta(b) - Delta(a + b) |
| */ |
| final double sqrtT = 10 / 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> implementation, |
| * {@code DBCORR}. |
| * |
| * @param p First argument. |
| * @param q Second argument. |
| * @return the value of {@code Delta(p) + Delta(q) - Delta(p + q)}. |
| * @throws IllegalArgumentException if {@code p < 10} or {@code q < 10}. |
| */ |
| private static double sumDeltaMinusDeltaSum(final double p, |
| final double q) { |
| |
| if (p < TEN) { |
| throw new GammaException(GammaException.OUT_OF_RANGE, p, TEN, Double.POSITIVE_INFINITY); |
| } |
| if (q < TEN) { |
| throw new GammaException(GammaException.OUT_OF_RANGE, q, TEN, Double.POSITIVE_INFINITY); |
| } |
| |
| final double a = Math.min(p, q); |
| final double b = Math.max(p, q); |
| final double sqrtT = 10 / 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 {@code log B(p, q)} for {@code 0 ≤ x ≤ 1} and {@code 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 value(double p, |
| double q) { |
| if (Double.isNaN(p) || |
| Double.isNaN(q) || |
| p <= 0 || |
| q <= 0) { |
| return Double.NaN; |
| } |
| |
| final double a = Math.min(p, q); |
| final double b = Math.max(p, q); |
| if (a >= TEN) { |
| final double w = sumDeltaMinusDeltaSum(a, b); |
| final double h = a / b; |
| final double c = h / (1 + h); |
| final double u = -(a - 0.5) * Math.log(c); |
| final double v = b * Math.log1p(h); |
| if (u <= v) { |
| return (((-0.5 * Math.log(b) + HALF_LOG_TWO_PI) + w) - u) - v; |
| } else { |
| return (((-0.5 * Math.log(b) + HALF_LOG_TWO_PI) + w) - v) - u; |
| } |
| } else if (a > TWO) { |
| if (b > THOUSAND) { |
| final int n = (int) Math.floor(a - 1); |
| double prod = 1; |
| double ared = a; |
| for (int i = 0; i < n; i++) { |
| ared -= 1; |
| prod *= ared / (1 + ared / b); |
| } |
| return (Math.log(prod) - n * Math.log(b)) + |
| (LogGamma.value(ared) + |
| logGammaMinusLogGammaSum(ared, b)); |
| } else { |
| double prod1 = 1; |
| double ared = a; |
| while (ared > 2) { |
| ared -= 1; |
| final double h = ared / b; |
| prod1 *= h / (1 + h); |
| } |
| if (b < TEN) { |
| double prod2 = 1; |
| double bred = b; |
| while (bred > 2) { |
| bred -= 1; |
| prod2 *= bred / (ared + bred); |
| } |
| return Math.log(prod1) + |
| Math.log(prod2) + |
| (LogGamma.value(ared) + |
| (LogGamma.value(bred) - |
| LogGammaSum.value(ared, bred))); |
| } else { |
| return Math.log(prod1) + |
| LogGamma.value(ared) + |
| logGammaMinusLogGammaSum(ared, b); |
| } |
| } |
| } else if (a >= 1) { |
| if (b > TWO) { |
| if (b < TEN) { |
| double prod = 1; |
| double bred = b; |
| while (bred > 2) { |
| bred -= 1; |
| prod *= bred / (a + bred); |
| } |
| return Math.log(prod) + |
| (LogGamma.value(a) + |
| (LogGamma.value(bred) - |
| LogGammaSum.value(a, bred))); |
| } else { |
| return LogGamma.value(a) + |
| logGammaMinusLogGammaSum(a, b); |
| } |
| } else { |
| return LogGamma.value(a) + |
| LogGamma.value(b) - |
| LogGammaSum.value(a, b); |
| } |
| } else { |
| if (b >= TEN) { |
| return LogGamma.value(a) + |
| logGammaMinusLogGammaSum(a, b); |
| } else { |
| // The original NSWC implementation was |
| // LogGamma.value(a) + (LogGamma.value(b) - LogGamma.value(a + b)); |
| // but the following command turned out to be more accurate. |
| return Math.log(Gamma.value(a) * Gamma.value(b) / |
| Gamma.value(a + b)); |
| } |
| } |
| } |
| |
| /** |
| * Returns the value of log[Γ(b) / Γ(a + b)] for a ≥ 0 and b ≥ 10. |
| * Based on the <em>NSWC Library of Mathematics Subroutines</em> implementation, |
| * {@code DLGDIV}. |
| * |
| * @param a First argument. |
| * @param b Second argument. |
| * @return the value of {@code log(Gamma(b) / Gamma(a + b))}. |
| * @throws IllegalArgumentException if {@code a < 0} or {@code b < 10}. |
| */ |
| private static double logGammaMinusLogGammaSum(double a, |
| double b) { |
| if (a < 0) { |
| throw new GammaException(GammaException.OUT_OF_RANGE, a, 0, Double.POSITIVE_INFINITY); |
| } |
| if (b < TEN) { |
| throw new GammaException(GammaException.OUT_OF_RANGE, b, TEN, Double.POSITIVE_INFINITY); |
| } |
| |
| /* |
| * 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 * Math.log1p(a / b); |
| final double v = a * (Math.log(b) - 1); |
| |
| return u <= v ? |
| (w - u) - v : |
| (w - v) - u; |
| } |
| } |