LogBeta.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.numbers.gamma;

  18. /**
  19.  * Computes \( log_e B(p, q) \).
  20.  * <p>
  21.  * This class is immutable.
  22.  * </p>
  23.  */
  24. public final class LogBeta {
  25.     /** The threshold value of 10 where the series expansion of the \( \Delta \) function applies. */
  26.     private static final double TEN = 10;
  27.     /** The threshold value of 2 for algorithm switch. */
  28.     private static final double TWO = 2;
  29.     /** The threshold value of 1000 for algorithm switch. */
  30.     private static final double THOUSAND = 1000;

  31.     /** The constant value of ½log 2π. */
  32.     private static final double HALF_LOG_TWO_PI = 0.9189385332046727;

  33.     /**
  34.      * The coefficients of the series expansion of the \( \Delta \) function.
  35.      * This function is defined as follows:
  36.      * \[
  37.      *  \Delta(x) = \log \Gamma(x) - (x - \frac{1}{2}) \log a + a - \frac{1}{2} \log 2\pi,
  38.      * \]
  39.      * <p>
  40.      * See equation (23) in Didonato and Morris (1992). The series expansion,
  41.      * which applies for \( x \geq 10 \), reads
  42.      * </p>
  43.      * \[
  44.      *  \Delta(x) = \frac{1}{x} \sum_{n = 0}^{14} d_n (\frac{10}{x})^{2 n}
  45.      * \]
  46.      */
  47.     private static final double[] DELTA = {
  48.         .833333333333333333333333333333E-01,
  49.         -.277777777777777777777777752282E-04,
  50.         .793650793650793650791732130419E-07,
  51.         -.595238095238095232389839236182E-09,
  52.         .841750841750832853294451671990E-11,
  53.         -.191752691751854612334149171243E-12,
  54.         .641025640510325475730918472625E-14,
  55.         -.295506514125338232839867823991E-15,
  56.         .179643716359402238723287696452E-16,
  57.         -.139228964661627791231203060395E-17,
  58.         .133802855014020915603275339093E-18,
  59.         -.154246009867966094273710216533E-19,
  60.         .197701992980957427278370133333E-20,
  61.         -.234065664793997056856992426667E-21,
  62.         .171348014966398575409015466667E-22
  63.     };

  64.     /** Private constructor. */
  65.     private LogBeta() {
  66.         // intentionally empty.
  67.     }

  68.     /**
  69.      * Returns the value of \( \Delta(b) - \Delta(a + b) \),
  70.      * with \( 0 \leq a \leq b \) and \( b \geq 10 \).
  71.      * Based on equations (26), (27) and (28) in Didonato and Morris (1992).
  72.      *
  73.      * @param a First argument.
  74.      * @param b Second argument.
  75.      * @return the value of \( \Delta(b) - \Delta(a + b) \)
  76.      * @throws IllegalArgumentException if {@code a < 0} or {@code a > b}
  77.      * @throws IllegalArgumentException if {@code b < 10}
  78.      */
  79.     private static double deltaMinusDeltaSum(final double a,
  80.                                              final double b) {
  81.         // Assumptions:
  82.         // Never called with a < 0; a > b; or b < 10

  83.         final double h = a / b;
  84.         final double p = h / (1 + h);
  85.         final double q = 1 / (1 + h);
  86.         final double q2 = q * q;
  87.         /*
  88.          * s[i] = 1 + q + ... - q**(2 * i)
  89.          */
  90.         final double[] s = new double[DELTA.length];
  91.         s[0] = 1;
  92.         for (int i = 1; i < s.length; i++) {
  93.             s[i] = 1 + (q + q2 * s[i - 1]);
  94.         }
  95.         /*
  96.          * w = Delta(b) - Delta(a + b)
  97.          */
  98.         final double sqrtT = 10 / b;
  99.         final double t = sqrtT * sqrtT;
  100.         double w = DELTA[DELTA.length - 1] * s[s.length - 1];
  101.         for (int i = DELTA.length - 2; i >= 0; i--) {
  102.             w = t * w + DELTA[i] * s[i];
  103.         }
  104.         return w * p / b;
  105.     }

  106.     /**
  107.      * Returns the value of \( \Delta(p) + \Delta(q) - \Delta(p + q) \),
  108.      * with \( p, q \geq 10 \).
  109.      * Based on the <em>NSWC Library of Mathematics Subroutines</em> implementation,
  110.      * {@code DBCORR}.
  111.      *
  112.      * @param p First argument.
  113.      * @param q Second argument.
  114.      * @return the value of \( \Delta(p) + \Delta(q) - \Delta(p + q) \).
  115.      * @throws IllegalArgumentException if {@code p < 10} or {@code q < 10}.
  116.      */
  117.     private static double sumDeltaMinusDeltaSum(final double p,
  118.                                                 final double q) {

  119.         if (p < TEN) {
  120.             throw new GammaException(GammaException.OUT_OF_RANGE, p, TEN, Double.POSITIVE_INFINITY);
  121.         }
  122.         if (q < TEN) {
  123.             throw new GammaException(GammaException.OUT_OF_RANGE, q, TEN, Double.POSITIVE_INFINITY);
  124.         }

  125.         final double a = Math.min(p, q);
  126.         final double b = Math.max(p, q);
  127.         final double sqrtT = 10 / a;
  128.         final double t = sqrtT * sqrtT;
  129.         double z = DELTA[DELTA.length - 1];
  130.         for (int i = DELTA.length - 2; i >= 0; i--) {
  131.             z = t * z + DELTA[i];
  132.         }
  133.         return z / a + deltaMinusDeltaSum(a, b);
  134.     }

  135.     /**
  136.      * Returns the value of \( \log B(p, q) \) for \( 0 \leq x \leq 1 \) and \( p, q &gt; 0 \).
  137.      * Based on the <em>NSWC Library of Mathematics Subroutines</em> implementation,
  138.      * {@code DBETLN}.
  139.      *
  140.      * @param p First argument.
  141.      * @param q Second argument.
  142.      * @return the value of \( \log B(p, q) \), or {@code NaN} if
  143.      * {@code p <= 0} or {@code q <= 0}.
  144.      */
  145.     public static double value(double p,
  146.                                double q) {
  147.         if (Double.isNaN(p) ||
  148.             Double.isNaN(q) ||
  149.             p <= 0 ||
  150.             q <= 0) {
  151.             return Double.NaN;
  152.         }

  153.         final double a = Math.min(p, q);
  154.         final double b = Math.max(p, q);
  155.         if (a >= TEN) {
  156.             final double w = sumDeltaMinusDeltaSum(a, b);
  157.             final double h = a / b;
  158.             final double c = h / (1 + h);
  159.             final double u = -(a - 0.5) * Math.log(c);
  160.             final double v = b * Math.log1p(h);
  161.             if (u <= v) {
  162.                 return (((-0.5 * Math.log(b) + HALF_LOG_TWO_PI) + w) - u) - v;
  163.             }
  164.             return (((-0.5 * Math.log(b) + HALF_LOG_TWO_PI) + w) - v) - u;
  165.         } else if (a > TWO) {
  166.             if (b > THOUSAND) {
  167.                 final int n = (int) Math.floor(a - 1);
  168.                 double prod = 1;
  169.                 double ared = a;
  170.                 for (int i = 0; i < n; i++) {
  171.                     ared -= 1;
  172.                     prod *= ared / (1 + ared / b);
  173.                 }
  174.                 return (Math.log(prod) - n * Math.log(b)) +
  175.                         (LogGamma.value(ared) +
  176.                          logGammaMinusLogGammaSum(ared, b));
  177.             }
  178.             double prod1 = 1;
  179.             double ared = a;
  180.             while (ared > TWO) {
  181.                 ared -= 1;
  182.                 final double h = ared / b;
  183.                 prod1 *= h / (1 + h);
  184.             }
  185.             if (b < TEN) {
  186.                 double prod2 = 1;
  187.                 double bred = b;
  188.                 while (bred > TWO) {
  189.                     bred -= 1;
  190.                     prod2 *= bred / (ared + bred);
  191.                 }
  192.                 return Math.log(prod1) +
  193.                        Math.log(prod2) +
  194.                        (LogGamma.value(ared) +
  195.                        (LogGamma.value(bred) -
  196.                         LogGammaSum.value(ared, bred)));
  197.             }
  198.             return Math.log(prod1) +
  199.                    LogGamma.value(ared) +
  200.                    logGammaMinusLogGammaSum(ared, b);
  201.         } else if (a >= 1) {
  202.             if (b > TWO) {
  203.                 if (b < TEN) {
  204.                     double prod = 1;
  205.                     double bred = b;
  206.                     while (bred > TWO) {
  207.                         bred -= 1;
  208.                         prod *= bred / (a + bred);
  209.                     }
  210.                     return Math.log(prod) +
  211.                            (LogGamma.value(a) +
  212.                             (LogGamma.value(bred) -
  213.                              LogGammaSum.value(a, bred)));
  214.                 }
  215.                 return LogGamma.value(a) +
  216.                     logGammaMinusLogGammaSum(a, b);
  217.             }
  218.             return LogGamma.value(a) +
  219.                    LogGamma.value(b) -
  220.                    LogGammaSum.value(a, b);
  221.         } else {
  222.             if (b >= TEN) {
  223.                 return LogGamma.value(a) +
  224.                        logGammaMinusLogGammaSum(a, b);
  225.             }
  226.             // The original NSWC implementation was
  227.             //   LogGamma.value(a) + (LogGamma.value(b) - LogGamma.value(a + b));
  228.             // but the following command turned out to be more accurate.
  229.             // Note: Check for overflow that occurs if a and/or b are tiny.
  230.             final double beta = Gamma.value(a) * Gamma.value(b) / Gamma.value(a + b);
  231.             if (Double.isFinite(beta)) {
  232.                 return Math.log(beta);
  233.             }
  234.             return LogGamma.value(a) + (LogGamma.value(b) - LogGamma.value(a + b));
  235.         }
  236.     }

  237.     /**
  238.      * Returns the value of \( \log ( \Gamma(b) / \Gamma(a + b) ) \)
  239.      * for \( a \geq 0 \) and \( b \geq 10 \).
  240.      * Based on the <em>NSWC Library of Mathematics Subroutines</em> implementation,
  241.      * {@code DLGDIV}.
  242.      *
  243.      * <p>This method assumes \( a \leq b \).
  244.      *
  245.      * @param a First argument.
  246.      * @param b Second argument.
  247.      * @return the value of \( \log(\Gamma(b) / \Gamma(a + b) \).
  248.      * @throws IllegalArgumentException if {@code a < 0} or {@code b < 10}.
  249.      */
  250.     private static double logGammaMinusLogGammaSum(double a,
  251.                                                    double b) {
  252.         if (a < 0) {
  253.             throw new GammaException(GammaException.OUT_OF_RANGE, a, 0, Double.POSITIVE_INFINITY);
  254.         }
  255.         if (b < TEN) {
  256.             throw new GammaException(GammaException.OUT_OF_RANGE, b, TEN, Double.POSITIVE_INFINITY);
  257.         }

  258.         /*
  259.          * d = a + b - 0.5
  260.          */
  261.         // Assumption: always called with a <= b.
  262.         final double d = b + (a - 0.5);
  263.         final double w = deltaMinusDeltaSum(a, b);

  264.         final double u = d * Math.log1p(a / b);
  265.         final double v = a * (Math.log(b) - 1);

  266.         return u <= v ?
  267.             (w - u) - v :
  268.             (w - v) - u;
  269.     }
  270. }