ChengBetaSampler.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.rng.sampling.distribution;

  18. import org.apache.commons.rng.UniformRandomProvider;

  19. /**
  20.  * Sampling from a <a href="http://en.wikipedia.org/wiki/Beta_distribution">beta distribution</a>.
  21.  *
  22.  * <p>Uses Cheng's algorithms for beta distribution sampling:</p>
  23.  *
  24.  * <blockquote>
  25.  * <pre>
  26.  * R. C. H. Cheng,
  27.  * "Generating beta variates with nonintegral shape parameters",
  28.  * Communications of the ACM, 21, 317-322, 1978.
  29.  * </pre>
  30.  * </blockquote>
  31.  *
  32.  * <p>Sampling uses {@link UniformRandomProvider#nextDouble()}.</p>
  33.  *
  34.  * @since 1.0
  35.  */
  36. public class ChengBetaSampler
  37.     extends SamplerBase
  38.     implements SharedStateContinuousSampler {
  39.     /** Natural logarithm of 4. */
  40.     private static final double LN_4 = Math.log(4.0);

  41.     /** The appropriate beta sampler for the parameters. */
  42.     private final SharedStateContinuousSampler delegate;

  43.     /**
  44.      * Base class to implement Cheng's algorithms for the beta distribution.
  45.      */
  46.     private abstract static class BaseChengBetaSampler
  47.             implements SharedStateContinuousSampler {
  48.         /**
  49.          * Flag set to true if {@code a} is the beta distribution alpha shape parameter.
  50.          * Otherwise {@code a} is the beta distribution beta shape parameter.
  51.          *
  52.          * <p>From the original Cheng paper this is equal to the result of: {@code a == a0}.</p>
  53.          */
  54.         protected final boolean aIsAlphaShape;
  55.         /**
  56.          * First shape parameter.
  57.          * The meaning of this is dependent on the {@code aIsAlphaShape} flag.
  58.          */
  59.         protected final double a;
  60.         /**
  61.          * Second shape parameter.
  62.          * The meaning of this is dependent on the {@code aIsAlphaShape} flag.
  63.          */
  64.         protected final double b;
  65.         /** Underlying source of randomness. */
  66.         protected final UniformRandomProvider rng;
  67.         /**
  68.          * The algorithm alpha factor. This is not the beta distribution alpha shape parameter.
  69.          * It is the sum of the two shape parameters ({@code a + b}.
  70.          */
  71.         protected final double alpha;
  72.         /** The logarithm of the alpha factor. */
  73.         protected final double logAlpha;

  74.         /**
  75.          * @param rng Generator of uniformly distributed random numbers.
  76.          * @param aIsAlphaShape true if {@code a} is the beta distribution alpha shape parameter.
  77.          * @param a Distribution first shape parameter.
  78.          * @param b Distribution second shape parameter.
  79.          */
  80.         BaseChengBetaSampler(UniformRandomProvider rng, boolean aIsAlphaShape, double a, double b) {
  81.             this.rng = rng;
  82.             this.aIsAlphaShape = aIsAlphaShape;
  83.             this.a = a;
  84.             this.b = b;
  85.             alpha = a + b;
  86.             logAlpha = Math.log(alpha);
  87.         }

  88.         /**
  89.          * @param rng Generator of uniformly distributed random numbers.
  90.          * @param source Source to copy.
  91.          */
  92.         BaseChengBetaSampler(UniformRandomProvider rng,
  93.                              BaseChengBetaSampler source) {
  94.             this.rng = rng;
  95.             aIsAlphaShape = source.aIsAlphaShape;
  96.             a = source.a;
  97.             b = source.b;
  98.             // Compute algorithm factors.
  99.             alpha = source.alpha;
  100.             logAlpha = source.logAlpha;
  101.         }

  102.         /** {@inheritDoc} */
  103.         @Override
  104.         public String toString() {
  105.             return "Cheng Beta deviate [" + rng.toString() + "]";
  106.         }

  107.         /**
  108.          * Compute the sample result X.
  109.          *
  110.          * <blockquote>
  111.          * If a == a0 deliver X = W/(b + W); otherwise deliver X = b/(b + W).
  112.          * </blockquote>
  113.          *
  114.          * <p>The finalisation step is shared between the BB and BC algorithm (as step 5 of the
  115.          * BB algorithm and step 6 of the BC algorithm).</p>
  116.          *
  117.          * @param w Algorithm value W.
  118.          * @return the sample value
  119.          */
  120.         protected double computeX(double w) {
  121.             // Avoid (infinity / infinity) producing NaN
  122.             final double tmp = Math.min(w, Double.MAX_VALUE);
  123.             return aIsAlphaShape ? tmp / (b + tmp) : b / (b + tmp);
  124.         }
  125.     }

  126.     /**
  127.      * Computes one sample using Cheng's BB algorithm, when beta distribution {@code alpha} and
  128.      * {@code beta} shape parameters are both larger than 1.
  129.      */
  130.     private static final class ChengBBBetaSampler extends BaseChengBetaSampler {
  131.         /** 1 + natural logarithm of 5. */
  132.         private static final double LN_5_P1 = 1 + Math.log(5.0);

  133.         /** The algorithm beta factor. This is not the beta distribution beta shape parameter. */
  134.         private final double beta;
  135.         /** The algorithm gamma factor. */
  136.         private final double gamma;

  137.         /**
  138.          * @param rng Generator of uniformly distributed random numbers.
  139.          * @param aIsAlphaShape true if {@code a} is the beta distribution alpha shape parameter.
  140.          * @param a min(alpha, beta) shape parameter.
  141.          * @param b max(alpha, beta) shape parameter.
  142.          */
  143.         ChengBBBetaSampler(UniformRandomProvider rng, boolean aIsAlphaShape, double a, double b) {
  144.             super(rng, aIsAlphaShape, a, b);
  145.             beta = Math.sqrt((alpha - 2) / (2 * a * b - alpha));
  146.             gamma = a + 1 / beta;
  147.         }

  148.         /**
  149.          * @param rng Generator of uniformly distributed random numbers.
  150.          * @param source Source to copy.
  151.          */
  152.         private ChengBBBetaSampler(UniformRandomProvider rng,
  153.                                    ChengBBBetaSampler source) {
  154.             super(rng, source);
  155.             // Compute algorithm factors.
  156.             beta = source.beta;
  157.             gamma = source.gamma;
  158.         }

  159.         @Override
  160.         public double sample() {
  161.             double r;
  162.             double w;
  163.             double t;
  164.             do {
  165.                 // Step 1:
  166.                 final double u1 = rng.nextDouble();
  167.                 final double u2 = rng.nextDouble();
  168.                 final double v = beta * (Math.log(u1) - Math.log1p(-u1));
  169.                 w = a * Math.exp(v);
  170.                 final double z = u1 * u1 * u2;
  171.                 r = gamma * v - LN_4;
  172.                 final double s = a + r - w;
  173.                 // Step 2:
  174.                 if (s + LN_5_P1 >= 5 * z) {
  175.                     break;
  176.                 }

  177.                 // Step 3:
  178.                 t = Math.log(z);
  179.                 if (s >= t) {
  180.                     break;
  181.                 }
  182.                 // Step 4:
  183.             } while (r + alpha * (logAlpha - Math.log(b + w)) < t);

  184.             // Step 5:
  185.             return computeX(w);
  186.         }

  187.         @Override
  188.         public SharedStateContinuousSampler withUniformRandomProvider(UniformRandomProvider rng) {
  189.             return new ChengBBBetaSampler(rng, this);
  190.         }
  191.     }

  192.     /**
  193.      * Computes one sample using Cheng's BC algorithm, when at least one of beta distribution
  194.      * {@code alpha} or {@code beta} shape parameters is smaller than 1.
  195.      */
  196.     private static final class ChengBCBetaSampler extends BaseChengBetaSampler {
  197.         /** 1/2. */
  198.         private static final double ONE_HALF = 1d / 2;
  199.         /** 1/4. */
  200.         private static final double ONE_QUARTER = 1d / 4;

  201.         /** The algorithm beta factor. This is not the beta distribution beta shape parameter. */
  202.         private final double beta;
  203.         /** The algorithm delta factor. */
  204.         private final double delta;
  205.         /** The algorithm k1 factor. */
  206.         private final double k1;
  207.         /** The algorithm k2 factor. */
  208.         private final double k2;

  209.         /**
  210.          * @param rng Generator of uniformly distributed random numbers.
  211.          * @param aIsAlphaShape true if {@code a} is the beta distribution alpha shape parameter.
  212.          * @param a max(alpha, beta) shape parameter.
  213.          * @param b min(alpha, beta) shape parameter.
  214.          */
  215.         ChengBCBetaSampler(UniformRandomProvider rng, boolean aIsAlphaShape, double a, double b) {
  216.             super(rng, aIsAlphaShape, a, b);
  217.             // Compute algorithm factors.
  218.             beta = 1 / b;
  219.             delta = 1 + a - b;
  220.             // The following factors are divided by 4:
  221.             // k1 = delta * (1 + 3b) / (18a/b - 14)
  222.             // k2 = 1 + (2 + 1/delta) * b
  223.             k1 = delta * (1.0 / 72.0 + 3.0 / 72.0 * b) / (a * beta - 7.0 / 9.0);
  224.             k2 = 0.25 + (0.5 + 0.25 / delta) * b;
  225.         }

  226.         /**
  227.          * @param rng Generator of uniformly distributed random numbers.
  228.          * @param source Source to copy.
  229.          */
  230.         private ChengBCBetaSampler(UniformRandomProvider rng,
  231.                                    ChengBCBetaSampler source) {
  232.             super(rng, source);
  233.             beta = source.beta;
  234.             delta = source.delta;
  235.             k1 = source.k1;
  236.             k2 = source.k2;
  237.         }

  238.         @Override
  239.         public double sample() {
  240.             double w;
  241.             while (true) {
  242.                 // Step 1:
  243.                 final double u1 = rng.nextDouble();
  244.                 final double u2 = rng.nextDouble();
  245.                 // Compute Y and Z
  246.                 final double y = u1 * u2;
  247.                 final double z = u1 * y;
  248.                 if (u1 < ONE_HALF) {
  249.                     // Step 2:
  250.                     if (ONE_QUARTER * u2 + z - y >= k1) {
  251.                         continue;
  252.                     }
  253.                 } else {
  254.                     // Step 3:
  255.                     if (z <= ONE_QUARTER) {
  256.                         final double v = beta * (Math.log(u1) - Math.log1p(-u1));
  257.                         w = a * Math.exp(v);
  258.                         break;
  259.                     }

  260.                     // Step 4:
  261.                     if (z >= k2) {
  262.                         continue;
  263.                     }
  264.                 }

  265.                 // Step 5:
  266.                 final double v = beta * (Math.log(u1) - Math.log1p(-u1));
  267.                 w = a * Math.exp(v);
  268.                 if (alpha * (logAlpha - Math.log(b + w) + v) - LN_4 >= Math.log(z)) {
  269.                     break;
  270.                 }
  271.             }

  272.             // Step 6:
  273.             return computeX(w);
  274.         }

  275.         @Override
  276.         public SharedStateContinuousSampler withUniformRandomProvider(UniformRandomProvider rng) {
  277.             return new ChengBCBetaSampler(rng, this);
  278.         }
  279.     }

  280.     /**
  281.      * Creates a sampler instance.
  282.      *
  283.      * @param rng Generator of uniformly distributed random numbers.
  284.      * @param alpha Distribution first shape parameter.
  285.      * @param beta Distribution second shape parameter.
  286.      * @throws IllegalArgumentException if {@code alpha <= 0} or {@code beta <= 0}
  287.      */
  288.     public ChengBetaSampler(UniformRandomProvider rng,
  289.                             double alpha,
  290.                             double beta) {
  291.         this(of(rng, alpha, beta));
  292.     }

  293.     /**
  294.      * @param delegate Beta sampler.
  295.      */
  296.     private ChengBetaSampler(SharedStateContinuousSampler delegate) {
  297.         super(null);
  298.         this.delegate = delegate;
  299.     }

  300.     /** {@inheritDoc} */
  301.     @Override
  302.     public double sample() {
  303.         return delegate.sample();
  304.     }

  305.     /** {@inheritDoc} */
  306.     @Override
  307.     public String toString() {
  308.         return delegate.toString();
  309.     }

  310.     /**
  311.      * {@inheritDoc}
  312.      *
  313.      * @since 1.3
  314.      */
  315.     @Override
  316.     public SharedStateContinuousSampler withUniformRandomProvider(UniformRandomProvider rng) {
  317.         return delegate.withUniformRandomProvider(rng);
  318.     }

  319.     /**
  320.      * Creates a new beta distribution sampler.
  321.      *
  322.      * @param rng Generator of uniformly distributed random numbers.
  323.      * @param alpha Distribution first shape parameter.
  324.      * @param beta Distribution second shape parameter.
  325.      * @return the sampler
  326.      * @throws IllegalArgumentException if {@code alpha <= 0} or {@code beta <= 0}
  327.      * @since 1.3
  328.      */
  329.     public static SharedStateContinuousSampler of(UniformRandomProvider rng,
  330.                                                   double alpha,
  331.                                                   double beta) {
  332.         InternalUtils.requireStrictlyPositive(alpha, "alpha");
  333.         InternalUtils.requireStrictlyPositive(beta, "beta");

  334.         // Choose the algorithm.
  335.         final double a = Math.min(alpha, beta);
  336.         final double b = Math.max(alpha, beta);
  337.         final boolean aIsAlphaShape = a == alpha;

  338.         return a > 1 ?
  339.             // BB algorithm
  340.             new ChengBBBetaSampler(rng, aIsAlphaShape, a, b) :
  341.             // The BC algorithm is deliberately invoked with reversed parameters
  342.             // as the argument order is: max(alpha,beta), min(alpha,beta).
  343.             // Also invert the 'a is alpha' flag.
  344.             new ChengBCBetaSampler(rng, !aIsAlphaShape, b, a);
  345.     }
  346. }