AhrensDieterMarsagliaTsangGammaSampler.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 the <a href="http://mathworld.wolfram.com/GammaDistribution.html">gamma distribution</a>.
  21.  * <ul>
  22.  *  <li>
  23.  *   For {@code 0 < alpha < 1}:
  24.  *   <blockquote>
  25.  *    Ahrens, J. H. and Dieter, U.,
  26.  *    <i>Computer methods for sampling from gamma, beta, Poisson and binomial distributions,</i>
  27.  *    Computing, 12, 223-246, 1974.
  28.  *   </blockquote>
  29.  *  </li>
  30.  *  <li>
  31.  *  For {@code alpha >= 1}:
  32.  *   <blockquote>
  33.  *   Marsaglia and Tsang, <i>A Simple Method for Generating
  34.  *   Gamma Variables.</i> ACM Transactions on Mathematical Software,
  35.  *   Volume 26 Issue 3, September, 2000.
  36.  *   </blockquote>
  37.  *  </li>
  38.  * </ul>
  39.  *
  40.  * <p>Sampling uses:</p>
  41.  *
  42.  * <ul>
  43.  *   <li>{@link UniformRandomProvider#nextDouble()} (both algorithms)
  44.  *   <li>{@link UniformRandomProvider#nextLong()} (only for {@code alpha >= 1})
  45.  * </ul>
  46.  *
  47.  * @see <a href="http://mathworld.wolfram.com/GammaDistribution.html">MathWorld Gamma distribution</a>
  48.  * @see <a href="https://en.wikipedia.org/wiki/Gamma_distribution">Wikipedia Gamma distribution</a>
  49.  * @since 1.0
  50.  */
  51. public class AhrensDieterMarsagliaTsangGammaSampler
  52.     extends SamplerBase
  53.     implements SharedStateContinuousSampler {
  54.     /** The appropriate gamma sampler for the parameters. */
  55.     private final SharedStateContinuousSampler delegate;

  56.     /**
  57.      * Base class for a sampler from the Gamma distribution.
  58.      */
  59.     private abstract static class BaseGammaSampler
  60.         implements SharedStateContinuousSampler {

  61.         /** Underlying source of randomness. */
  62.         protected final UniformRandomProvider rng;
  63.         /** The alpha parameter. This is a shape parameter. */
  64.         protected final double alpha;
  65.         /** The theta parameter. This is a scale parameter. */
  66.         protected final double theta;

  67.         /**
  68.          * @param rng Generator of uniformly distributed random numbers.
  69.          * @param alpha Alpha parameter of the distribution.
  70.          * @param theta Theta parameter of the distribution.
  71.          * @throws IllegalArgumentException if {@code alpha <= 0} or {@code theta <= 0}
  72.          */
  73.         BaseGammaSampler(UniformRandomProvider rng,
  74.                          double alpha,
  75.                          double theta) {
  76.             // Validation before java.lang.Object constructor exits prevents partially initialized object
  77.             this(InternalUtils.requireStrictlyPositive(alpha, "alpha"),
  78.                  InternalUtils.requireStrictlyPositive(theta, "theta"),
  79.                  rng);
  80.         }

  81.         /**
  82.          * @param alpha Alpha parameter of the distribution.
  83.          * @param theta Theta parameter of the distribution.
  84.          * @param rng Generator of uniformly distributed random numbers.
  85.          */
  86.         private BaseGammaSampler(double alpha,
  87.                                  double theta,
  88.                                  UniformRandomProvider rng) {
  89.             this.rng = rng;
  90.             this.alpha = alpha;
  91.             this.theta = theta;
  92.         }

  93.         /**
  94.          * @param rng Generator of uniformly distributed random numbers.
  95.          * @param source Source to copy.
  96.          */
  97.         BaseGammaSampler(UniformRandomProvider rng,
  98.                          BaseGammaSampler source) {
  99.             this.rng = rng;
  100.             this.alpha = source.alpha;
  101.             this.theta = source.theta;
  102.         }

  103.         /** {@inheritDoc} */
  104.         @Override
  105.         public String toString() {
  106.             return "Ahrens-Dieter-Marsaglia-Tsang Gamma deviate [" + rng.toString() + "]";
  107.         }
  108.     }

  109.     /**
  110.      * Class to sample from the Gamma distribution when {@code 0 < alpha < 1}.
  111.      *
  112.      * <blockquote>
  113.      *  Ahrens, J. H. and Dieter, U.,
  114.      *  <i>Computer methods for sampling from gamma, beta, Poisson and binomial distributions,</i>
  115.      *  Computing, 12, 223-246, 1974.
  116.      * </blockquote>
  117.      */
  118.     private static final class AhrensDieterGammaSampler
  119.         extends BaseGammaSampler {

  120.         /** Inverse of "alpha". */
  121.         private final double oneOverAlpha;
  122.         /** Optimization (see code). */
  123.         private final double bGSOptim;

  124.         /**
  125.          * @param rng Generator of uniformly distributed random numbers.
  126.          * @param alpha Alpha parameter of the distribution.
  127.          * @param theta Theta parameter of the distribution.
  128.          * @throws IllegalArgumentException if {@code alpha <= 0} or {@code theta <= 0}
  129.          */
  130.         AhrensDieterGammaSampler(UniformRandomProvider rng,
  131.                                  double alpha,
  132.                                  double theta) {
  133.             super(rng, alpha, theta);
  134.             oneOverAlpha = 1 / alpha;
  135.             bGSOptim = 1 + alpha / Math.E;
  136.         }

  137.         /**
  138.          * @param rng Generator of uniformly distributed random numbers.
  139.          * @param source Source to copy.
  140.          */
  141.         AhrensDieterGammaSampler(UniformRandomProvider rng,
  142.                                  AhrensDieterGammaSampler source) {
  143.             super(rng, source);
  144.             oneOverAlpha = source.oneOverAlpha;
  145.             bGSOptim = source.bGSOptim;
  146.         }

  147.         @Override
  148.         public double sample() {
  149.             // [1]: p. 228, Algorithm GS.

  150.             while (true) {
  151.                 // Step 1:
  152.                 final double u = rng.nextDouble();
  153.                 final double p = bGSOptim * u;

  154.                 if (p <= 1) {
  155.                     // Step 2:
  156.                     final double x = Math.pow(p, oneOverAlpha);
  157.                     final double u2 = rng.nextDouble();

  158.                     if (u2 > Math.exp(-x)) {
  159.                         // Reject.
  160.                         continue;
  161.                     }
  162.                     return theta * x;
  163.                 }

  164.                 // Step 3:
  165.                 final double x = -Math.log((bGSOptim - p) * oneOverAlpha);
  166.                 final double u2 = rng.nextDouble();

  167.                 if (u2 <= Math.pow(x, alpha - 1)) {
  168.                     return theta * x;
  169.                 }
  170.                 // Reject and continue.
  171.             }
  172.         }

  173.         @Override
  174.         public SharedStateContinuousSampler withUniformRandomProvider(UniformRandomProvider rng) {
  175.             return new AhrensDieterGammaSampler(rng, this);
  176.         }
  177.     }

  178.     /**
  179.      * Class to sample from the Gamma distribution when the {@code alpha >= 1}.
  180.      *
  181.      * <blockquote>
  182.      *  Marsaglia and Tsang, <i>A Simple Method for Generating
  183.      *  Gamma Variables.</i> ACM Transactions on Mathematical Software,
  184.      *  Volume 26 Issue 3, September, 2000.
  185.      * </blockquote>
  186.      */
  187.     private static final class MarsagliaTsangGammaSampler
  188.         extends BaseGammaSampler {

  189.         /** 1/3. */
  190.         private static final double ONE_THIRD = 1d / 3;

  191.         /** Optimization (see code). */
  192.         private final double dOptim;
  193.         /** Optimization (see code). */
  194.         private final double cOptim;
  195.         /** Gaussian sampling. */
  196.         private final NormalizedGaussianSampler gaussian;

  197.         /**
  198.          * @param rng Generator of uniformly distributed random numbers.
  199.          * @param alpha Alpha parameter of the distribution.
  200.          * @param theta Theta parameter of the distribution.
  201.          * @throws IllegalArgumentException if {@code alpha <= 0} or {@code theta <= 0}
  202.          */
  203.         MarsagliaTsangGammaSampler(UniformRandomProvider rng,
  204.                                    double alpha,
  205.                                    double theta) {
  206.             super(rng, alpha, theta);
  207.             gaussian = ZigguratSampler.NormalizedGaussian.of(rng);
  208.             dOptim = alpha - ONE_THIRD;
  209.             cOptim = ONE_THIRD / Math.sqrt(dOptim);
  210.         }

  211.         /**
  212.          * @param rng Generator of uniformly distributed random numbers.
  213.          * @param source Source to copy.
  214.          */
  215.         MarsagliaTsangGammaSampler(UniformRandomProvider rng,
  216.                                    MarsagliaTsangGammaSampler source) {
  217.             super(rng, source);
  218.             gaussian = ZigguratSampler.NormalizedGaussian.of(rng);
  219.             dOptim = source.dOptim;
  220.             cOptim = source.cOptim;
  221.         }

  222.         @Override
  223.         public double sample() {
  224.             while (true) {
  225.                 final double x = gaussian.sample();
  226.                 final double oPcTx = 1 + cOptim * x;
  227.                 final double v = oPcTx * oPcTx * oPcTx;

  228.                 if (v <= 0) {
  229.                     continue;
  230.                 }

  231.                 final double x2 = x * x;
  232.                 final double u = rng.nextDouble();

  233.                 // Squeeze.
  234.                 if (u < 1 - 0.0331 * x2 * x2) {
  235.                     return theta * dOptim * v;
  236.                 }

  237.                 if (Math.log(u) < 0.5 * x2 + dOptim * (1 - v + Math.log(v))) {
  238.                     return theta * dOptim * v;
  239.                 }
  240.             }
  241.         }

  242.         @Override
  243.         public SharedStateContinuousSampler withUniformRandomProvider(UniformRandomProvider rng) {
  244.             return new MarsagliaTsangGammaSampler(rng, this);
  245.         }
  246.     }

  247.     /**
  248.      * This instance delegates sampling. Use the factory method
  249.      * {@link #of(UniformRandomProvider, double, double)} to create an optimal sampler.
  250.      *
  251.      * @param rng Generator of uniformly distributed random numbers.
  252.      * @param alpha Alpha parameter of the distribution (this is a shape parameter).
  253.      * @param theta Theta parameter of the distribution (this is a scale parameter).
  254.      * @throws IllegalArgumentException if {@code alpha <= 0} or {@code theta <= 0}
  255.      */
  256.     public AhrensDieterMarsagliaTsangGammaSampler(UniformRandomProvider rng,
  257.                                                   double alpha,
  258.                                                   double theta) {
  259.         super(null);
  260.         delegate = of(rng, alpha, theta);
  261.     }

  262.     /** {@inheritDoc} */
  263.     @Override
  264.     public double sample() {
  265.         return delegate.sample();
  266.     }

  267.     /** {@inheritDoc} */
  268.     @Override
  269.     public String toString() {
  270.         return delegate.toString();
  271.     }

  272.     /**
  273.      * {@inheritDoc}
  274.      *
  275.      * @since 1.3
  276.      */
  277.     @Override
  278.     public SharedStateContinuousSampler withUniformRandomProvider(UniformRandomProvider rng) {
  279.         // Direct return of the optimised sampler
  280.         return delegate.withUniformRandomProvider(rng);
  281.     }

  282.     /**
  283.      * Creates a new gamma distribution sampler.
  284.      *
  285.      * @param rng Generator of uniformly distributed random numbers.
  286.      * @param alpha Alpha parameter of the distribution (this is a shape parameter).
  287.      * @param theta Theta parameter of the distribution (this is a scale parameter).
  288.      * @return the sampler
  289.      * @throws IllegalArgumentException if {@code alpha <= 0} or {@code theta <= 0}
  290.      * @since 1.3
  291.      */
  292.     public static SharedStateContinuousSampler of(UniformRandomProvider rng,
  293.                                                   double alpha,
  294.                                                   double theta) {
  295.         // Each sampler should check the input arguments.
  296.         return alpha < 1 ?
  297.                 new AhrensDieterGammaSampler(rng, alpha, theta) :
  298.                 new MarsagliaTsangGammaSampler(rng, alpha, theta);
  299.     }
  300. }