Beta.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.  *      https://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. /*
  18.  * This is not the original file distributed by the Apache Software Foundation
  19.  * It has been modified by the Hipparchus project
  20.  */
  21. package org.hipparchus.special;

  22. import org.hipparchus.exception.LocalizedCoreFormats;
  23. import org.hipparchus.exception.MathIllegalArgumentException;
  24. import org.hipparchus.util.ContinuedFraction;
  25. import org.hipparchus.util.FastMath;
  26. import org.hipparchus.util.MathUtils;

  27. /**
  28.  * <p>
  29.  * This is a utility class that provides computation methods related to the
  30.  * Beta family of functions.
  31.  * </p>
  32.  * <p>
  33.  * Implementation of {@link #logBeta(double, double)} is based on the
  34.  * algorithms described in
  35.  * </p>
  36.  * <ul>
  37.  * <li><a href="http://dx.doi.org/10.1145/22721.23109">Didonato and Morris
  38.  *     (1986)</a>, <em>Computation of the Incomplete Gamma Function Ratios
  39.  *     and their Inverse</em>, TOMS 12(4), 377-393,</li>
  40.  * <li><a href="http://dx.doi.org/10.1145/131766.131776">Didonato and Morris
  41.  *     (1992)</a>, <em>Algorithm 708: Significant Digit Computation of the
  42.  *     Incomplete Beta Function Ratios</em>, TOMS 18(3), 360-373,</li>
  43.  * </ul>
  44.  * <p>
  45.  * and implemented in the
  46.  * <a href="http://www.dtic.mil/docs/citations/ADA476840">NSWC Library of Mathematical Functions</a>,
  47.  * available
  48.  * <a href="http://www.ualberta.ca/CNS/RESEARCH/Software/NumericalNSWC/site.html">here</a>.
  49.  * This library is "approved for public release", and the
  50.  * <a href="http://www.dtic.mil/dtic/pdf/announcements/CopyrightGuidance.pdf">Copyright guidance</a>
  51.  * indicates that unless otherwise stated in the code, all FORTRAN functions in
  52.  * this library are license free. Since no such notice appears in the code these
  53.  * functions can safely be ported to Hipparchus.
  54.  * </p>
  55.  */
  56. public class Beta {
  57.     /** Maximum allowed numerical error. */
  58.     private static final double DEFAULT_EPSILON = 1E-14;

  59.     /** The constant value of ½log 2π. */
  60.     private static final double HALF_LOG_TWO_PI = .9189385332046727;

  61.     /**
  62.      * <p>
  63.      * The coefficients of the series expansion of the Δ function. This function
  64.      * is defined as follows
  65.      * </p>
  66.      * <center>Δ(x) = log Γ(x) - (x - 0.5) log a + a - 0.5 log 2π,</center>
  67.      * <p>
  68.      * see equation (23) in Didonato and Morris (1992). The series expansion,
  69.      * which applies for x ≥ 10, reads
  70.      * </p>
  71.      * <pre>
  72.      *                 14
  73.      *                ====
  74.      *             1  \                2 n
  75.      *     Δ(x) = ---  >    d  (10 / x)
  76.      *             x  /      n
  77.      *                ====
  78.      *                n = 0
  79.      * <pre>
  80.      */
  81.     private static final double[] DELTA = {
  82.         .833333333333333333333333333333E-01,
  83.         -.277777777777777777777777752282E-04,
  84.         .793650793650793650791732130419E-07,
  85.         -.595238095238095232389839236182E-09,
  86.         .841750841750832853294451671990E-11,
  87.         -.191752691751854612334149171243E-12,
  88.         .641025640510325475730918472625E-14,
  89.         -.295506514125338232839867823991E-15,
  90.         .179643716359402238723287696452E-16,
  91.         -.139228964661627791231203060395E-17,
  92.         .133802855014020915603275339093E-18,
  93.         -.154246009867966094273710216533E-19,
  94.         .197701992980957427278370133333E-20,
  95.         -.234065664793997056856992426667E-21,
  96.         .171348014966398575409015466667E-22
  97.     };

  98.     /**
  99.      * Default constructor.  Prohibit instantiation.
  100.      */
  101.     private Beta() {}

  102.     /**
  103.      * Returns the
  104.      * <a href="http://mathworld.wolfram.com/RegularizedBetaFunction.html">
  105.      * regularized beta function</a> I(x, a, b).
  106.      *
  107.      * @param x Value.
  108.      * @param a Parameter {@code a}.
  109.      * @param b Parameter {@code b}.
  110.      * @return the regularized beta function I(x, a, b).
  111.      * @throws org.hipparchus.exception.MathIllegalStateException
  112.      * if the algorithm fails to converge.
  113.      */
  114.     public static double regularizedBeta(double x, double a, double b) {
  115.         return regularizedBeta(x, a, b, DEFAULT_EPSILON, Integer.MAX_VALUE);
  116.     }

  117.     /**
  118.      * Returns the
  119.      * <a href="http://mathworld.wolfram.com/RegularizedBetaFunction.html">
  120.      * regularized beta function</a> I(x, a, b).
  121.      *
  122.      * @param x Value.
  123.      * @param a Parameter {@code a}.
  124.      * @param b Parameter {@code b}.
  125.      * @param epsilon When the absolute value of the nth item in the
  126.      * series is less than epsilon the approximation ceases to calculate
  127.      * further elements in the series.
  128.      * @return the regularized beta function I(x, a, b)
  129.      * @throws org.hipparchus.exception.MathIllegalStateException
  130.      * if the algorithm fails to converge.
  131.      */
  132.     public static double regularizedBeta(double x,
  133.                                          double a, double b,
  134.                                          double epsilon) {
  135.         return regularizedBeta(x, a, b, epsilon, Integer.MAX_VALUE);
  136.     }

  137.     /**
  138.      * Returns the regularized beta function I(x, a, b).
  139.      *
  140.      * @param x the value.
  141.      * @param a Parameter {@code a}.
  142.      * @param b Parameter {@code b}.
  143.      * @param maxIterations Maximum number of "iterations" to complete.
  144.      * @return the regularized beta function I(x, a, b)
  145.      * @throws org.hipparchus.exception.MathIllegalStateException
  146.      * if the algorithm fails to converge.
  147.      */
  148.     public static double regularizedBeta(double x,
  149.                                          double a, double b,
  150.                                          int maxIterations) {
  151.         return regularizedBeta(x, a, b, DEFAULT_EPSILON, maxIterations);
  152.     }

  153.     /**
  154.      * Returns the regularized beta function I(x, a, b).
  155.      *
  156.      * The implementation of this method is based on:
  157.      * <ul>
  158.      * <li>
  159.      * <a href="http://mathworld.wolfram.com/RegularizedBetaFunction.html">
  160.      * Regularized Beta Function</a>.</li>
  161.      * <li>
  162.      * <a href="http://functions.wolfram.com/06.21.10.0001.01">
  163.      * Regularized Beta Function</a>.</li>
  164.      * </ul>
  165.      *
  166.      * @param x the value.
  167.      * @param a Parameter {@code a}.
  168.      * @param b Parameter {@code b}.
  169.      * @param epsilon When the absolute value of the nth item in the
  170.      * series is less than epsilon the approximation ceases to calculate
  171.      * further elements in the series.
  172.      * @param maxIterations Maximum number of "iterations" to complete.
  173.      * @return the regularized beta function I(x, a, b)
  174.      * @throws org.hipparchus.exception.MathIllegalStateException
  175.      * if the algorithm fails to converge.
  176.      */
  177.     public static double regularizedBeta(double x,
  178.                                          final double a, final double b,
  179.                                          double epsilon, int maxIterations) {
  180.         double ret;

  181.         if (Double.isNaN(x) ||
  182.             Double.isNaN(a) ||
  183.             Double.isNaN(b) ||
  184.             x < 0 ||
  185.             x > 1 ||
  186.             a <= 0 ||
  187.             b <= 0) {
  188.             ret = Double.NaN;
  189.         } else if (x > (a + 1) / (2 + b + a) &&
  190.                    1 - x <= (b + 1) / (2 + b + a)) {
  191.             ret = 1 - regularizedBeta(1 - x, b, a, epsilon, maxIterations);
  192.         } else {
  193.             ContinuedFraction fraction = new ContinuedFraction() {

  194.                 /** {@inheritDoc} */
  195.                 @Override
  196.                 protected double getB(int n, double x) {
  197.                     double ret;
  198.                     double m;
  199.                     if (n % 2 == 0) { // even
  200.                         m = n / 2.0;
  201.                         ret = (m * (b - m) * x) /
  202.                             ((a + (2 * m) - 1) * (a + (2 * m)));
  203.                     } else {
  204.                         m = (n - 1.0) / 2.0;
  205.                         ret = -((a + m) * (a + b + m) * x) /
  206.                                 ((a + (2 * m)) * (a + (2 * m) + 1.0));
  207.                     }
  208.                     return ret;
  209.                 }

  210.                 /** {@inheritDoc} */
  211.                 @Override
  212.                 protected double getA(int n, double x) {
  213.                     return 1.0;
  214.                 }
  215.             };
  216.             ret = FastMath.exp((a * FastMath.log(x)) + (b * FastMath.log1p(-x)) -
  217.                 FastMath.log(a) - logBeta(a, b)) *
  218.                 1.0 / fraction.evaluate(x, epsilon, maxIterations);
  219.         }

  220.         return ret;
  221.     }

  222.     /**
  223.      * Returns the value of log Γ(a + b) for 1 ≤ a, b ≤ 2. Based on the
  224.      * <em>NSWC Library of Mathematics Subroutines</em> double precision
  225.      * implementation, {@code DGSMLN}. In {@code BetaTest.testLogGammaSum()},
  226.      * this private method is accessed through reflection.
  227.      *
  228.      * @param a First argument.
  229.      * @param b Second argument.
  230.      * @return the value of {@code log(Gamma(a + b))}.
  231.      * @throws MathIllegalArgumentException if {@code a} or {@code b} is lower than
  232.      * {@code 1.0} or greater than {@code 2.0}.
  233.      */
  234.     private static double logGammaSum(final double a, final double b)
  235.         throws MathIllegalArgumentException {

  236.         MathUtils.checkRangeInclusive(a, 1, 2);
  237.         MathUtils.checkRangeInclusive(b, 1, 2);

  238.         final double x = (a - 1.0) + (b - 1.0);
  239.         if (x <= 0.5) {
  240.             return Gamma.logGamma1p(1.0 + x);
  241.         } else if (x <= 1.5) {
  242.             return Gamma.logGamma1p(x) + FastMath.log1p(x);
  243.         } else {
  244.             return Gamma.logGamma1p(x - 1.0) + FastMath.log(x * (1.0 + x));
  245.         }
  246.     }

  247.     /**
  248.      * Returns the value of log[Γ(b) / Γ(a + b)] for a ≥ 0 and b ≥ 10. Based on
  249.      * the <em>NSWC Library of Mathematics Subroutines</em> double precision
  250.      * implementation, {@code DLGDIV}. In
  251.      * {@code BetaTest.testLogGammaMinusLogGammaSum()}, this private method is
  252.      * accessed through reflection.
  253.      *
  254.      * @param a First argument.
  255.      * @param b Second argument.
  256.      * @return the value of {@code log(Gamma(b) / Gamma(a + b))}.
  257.      * @throws MathIllegalArgumentException if {@code a < 0.0} or {@code b < 10.0}.
  258.      */
  259.     private static double logGammaMinusLogGammaSum(final double a,
  260.                                                    final double b)
  261.         throws MathIllegalArgumentException {

  262.         if (a < 0.0) {
  263.             throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_SMALL,
  264.                                                    a, 0.0);
  265.         }
  266.         if (b < 10.0) {
  267.             throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_SMALL,
  268.                                                    b, 10.0);
  269.         }

  270.         /*
  271.          * d = a + b - 0.5
  272.          */
  273.         final double d;
  274.         final double w;
  275.         if (a <= b) {
  276.             d = b + (a - 0.5);
  277.             w = deltaMinusDeltaSum(a, b);
  278.         } else {
  279.             d = a + (b - 0.5);
  280.             w = deltaMinusDeltaSum(b, a);
  281.         }

  282.         final double u = d * FastMath.log1p(a / b);
  283.         final double v = a * (FastMath.log(b) - 1.0);

  284.         return u <= v ? (w - u) - v : (w - v) - u;
  285.     }

  286.     /**
  287.      * Returns the value of Δ(b) - Δ(a + b), with 0 ≤ a ≤ b and b ≥ 10. Based
  288.      * on equations (26), (27) and (28) in Didonato and Morris (1992).
  289.      *
  290.      * @param a First argument.
  291.      * @param b Second argument.
  292.      * @return the value of {@code Delta(b) - Delta(a + b)}
  293.      * @throws MathIllegalArgumentException if {@code a < 0} or {@code a > b}
  294.      * @throws MathIllegalArgumentException if {@code b < 10}
  295.      */
  296.     private static double deltaMinusDeltaSum(final double a,
  297.                                              final double b)
  298.         throws MathIllegalArgumentException {

  299.         MathUtils.checkRangeInclusive(a, 0, b);
  300.         if (b < 10) {
  301.             throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_SMALL,
  302.                                                    b, 10);
  303.         }

  304.         final double h = a / b;
  305.         final double p = h / (1.0 + h);
  306.         final double q = 1.0 / (1.0 + h);
  307.         final double q2 = q * q;
  308.         /*
  309.          * s[i] = 1 + q + ... - q**(2 * i)
  310.          */
  311.         final double[] s = new double[DELTA.length];
  312.         s[0] = 1.0;
  313.         for (int i = 1; i < s.length; i++) {
  314.             s[i] = 1.0 + (q + q2 * s[i - 1]);
  315.         }
  316.         /*
  317.          * w = Delta(b) - Delta(a + b)
  318.          */
  319.         final double sqrtT = 10.0 / b;
  320.         final double t = sqrtT * sqrtT;
  321.         double w = DELTA[DELTA.length - 1] * s[s.length - 1];
  322.         for (int i = DELTA.length - 2; i >= 0; i--) {
  323.             w = t * w + DELTA[i] * s[i];
  324.         }
  325.         return w * p / b;
  326.     }

  327.     /**
  328.      * Returns the value of Δ(p) + Δ(q) - Δ(p + q), with p, q ≥ 10. Based on
  329.      * the <em>NSWC Library of Mathematics Subroutines</em> double precision
  330.      * implementation, {@code DBCORR}. In
  331.      * {@code BetaTest.testSumDeltaMinusDeltaSum()}, this private method is
  332.      * accessed through reflection.
  333.      *
  334.      * @param p First argument.
  335.      * @param q Second argument.
  336.      * @return the value of {@code Delta(p) + Delta(q) - Delta(p + q)}.
  337.      * @throws MathIllegalArgumentException if {@code p < 10.0} or {@code q < 10.0}.
  338.      */
  339.     private static double sumDeltaMinusDeltaSum(final double p,
  340.                                                 final double q) {

  341.         if (p < 10.0) {
  342.             throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_SMALL,
  343.                                                    p, 10.0);
  344.         }
  345.         if (q < 10.0) {
  346.             throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_SMALL,
  347.                                                    q, 10.0);
  348.         }

  349.         final double a = FastMath.min(p, q);
  350.         final double b = FastMath.max(p, q);
  351.         final double sqrtT = 10.0 / a;
  352.         final double t = sqrtT * sqrtT;
  353.         double z = DELTA[DELTA.length - 1];
  354.         for (int i = DELTA.length - 2; i >= 0; i--) {
  355.             z = t * z + DELTA[i];
  356.         }
  357.         return z / a + deltaMinusDeltaSum(a, b);
  358.     }

  359.     /**
  360.      * Returns the value of log B(p, q) for 0 ≤ x ≤ 1 and p, q &gt; 0. Based on the
  361.      * <em>NSWC Library of Mathematics Subroutines</em> implementation,
  362.      * {@code DBETLN}.
  363.      *
  364.      * @param p First argument.
  365.      * @param q Second argument.
  366.      * @return the value of {@code log(Beta(p, q))}, {@code NaN} if
  367.      * {@code p <= 0} or {@code q <= 0}.
  368.      */
  369.     public static double logBeta(final double p, final double q) {
  370.         if (Double.isNaN(p) || Double.isNaN(q) || (p <= 0.0) || (q <= 0.0)) {
  371.             return Double.NaN;
  372.         }

  373.         final double a = FastMath.min(p, q);
  374.         final double b = FastMath.max(p, q);
  375.         if (a >= 10.0) {
  376.             final double w = sumDeltaMinusDeltaSum(a, b);
  377.             final double h = a / b;
  378.             final double c = h / (1.0 + h);
  379.             final double u = -(a - 0.5) * FastMath.log(c);
  380.             final double v = b * FastMath.log1p(h);
  381.             if (u <= v) {
  382.                 return (((-0.5 * FastMath.log(b) + HALF_LOG_TWO_PI) + w) - u) - v;
  383.             } else {
  384.                 return (((-0.5 * FastMath.log(b) + HALF_LOG_TWO_PI) + w) - v) - u;
  385.             }
  386.         } else if (a > 2.0) {
  387.             if (b > 1000.0) {
  388.                 final int n = (int) FastMath.floor(a - 1.0);
  389.                 double prod = 1.0;
  390.                 double ared = a;
  391.                 for (int i = 0; i < n; i++) {
  392.                     ared -= 1.0;
  393.                     prod *= ared / (1.0 + ared / b);
  394.                 }
  395.                 return (FastMath.log(prod) - n * FastMath.log(b)) +
  396.                         (Gamma.logGamma(ared) +
  397.                          logGammaMinusLogGammaSum(ared, b));
  398.             } else {
  399.                 double prod1 = 1.0;
  400.                 double ared = a;
  401.                 while (ared > 2.0) {
  402.                     ared -= 1.0;
  403.                     final double h = ared / b;
  404.                     prod1 *= h / (1.0 + h);
  405.                 }
  406.                 if (b < 10.0) {
  407.                     double prod2 = 1.0;
  408.                     double bred = b;
  409.                     while (bred > 2.0) {
  410.                         bred -= 1.0;
  411.                         prod2 *= bred / (ared + bred);
  412.                     }
  413.                     return FastMath.log(prod1) +
  414.                            FastMath.log(prod2) +
  415.                            (Gamma.logGamma(ared) +
  416.                            (Gamma.logGamma(bred) -
  417.                             logGammaSum(ared, bred)));
  418.                 } else {
  419.                     return FastMath.log(prod1) +
  420.                            Gamma.logGamma(ared) +
  421.                            logGammaMinusLogGammaSum(ared, b);
  422.                 }
  423.             }
  424.         } else if (a >= 1.0) {
  425.             if (b > 2.0) {
  426.                 if (b < 10.0) {
  427.                     double prod = 1.0;
  428.                     double bred = b;
  429.                     while (bred > 2.0) {
  430.                         bred -= 1.0;
  431.                         prod *= bred / (a + bred);
  432.                     }
  433.                     return FastMath.log(prod) +
  434.                            (Gamma.logGamma(a) +
  435.                             (Gamma.logGamma(bred) -
  436.                              logGammaSum(a, bred)));
  437.                 } else {
  438.                     return Gamma.logGamma(a) +
  439.                            logGammaMinusLogGammaSum(a, b);
  440.                 }
  441.             } else {
  442.                 return Gamma.logGamma(a) +
  443.                        Gamma.logGamma(b) -
  444.                        logGammaSum(a, b);
  445.             }
  446.         } else {
  447.             if (b >= 10.0) {
  448.                 return Gamma.logGamma(a) +
  449.                        logGammaMinusLogGammaSum(a, b);
  450.             } else {
  451.                 // The following command is the original NSWC implementation.
  452.                 // return Gamma.logGamma(a) +
  453.                 // (Gamma.logGamma(b) - Gamma.logGamma(a + b));
  454.                 // The following command turns out to be more accurate.
  455.                 return FastMath.log(Gamma.gamma(a) * Gamma.gamma(b) /
  456.                                     Gamma.gamma(a + b));
  457.             }
  458.         }
  459.     }
  460. }