AhrensDieterExponentialSampler.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 an <a href="http://mathworld.wolfram.com/ExponentialDistribution.html">exponential distribution</a>.
  21.  *
  22.  * <p>Sampling uses:</p>
  23.  *
  24.  * <ul>
  25.  *   <li>{@link UniformRandomProvider#nextLong()}
  26.  *   <li>{@link UniformRandomProvider#nextDouble()}
  27.  * </ul>
  28.  *
  29.  * @since 1.0
  30.  */
  31. public class AhrensDieterExponentialSampler
  32.     extends SamplerBase
  33.     implements SharedStateContinuousSampler {
  34.     /**
  35.      * Table containing the constants
  36.      * \( q_i = sum_{j=1}^i (\ln 2)^j / j! = \ln 2 + (\ln 2)^2 / 2 + ... + (\ln 2)^i / i! \)
  37.      * until the largest representable fraction below 1 is exceeded.
  38.      *
  39.      * Note that
  40.      * \( 1 = 2 - 1 = \exp(\ln 2) - 1 = sum_{n=1}^\infinity (\ln 2)^n / n! \)
  41.      * thus \( q_i \rightarrow 1 as i \rightarrow +\infinity \),
  42.      * so the higher \( i \), the closer we get to 1 (the series is not alternating).
  43.      *
  44.      * By trying, n = 16 in Java is enough to reach 1.
  45.      */
  46.     private static final double[] EXPONENTIAL_SA_QI = new double[16];
  47.     /** The mean of this distribution. */
  48.     private final double mean;
  49.     /** Underlying source of randomness. */
  50.     private final UniformRandomProvider rng;

  51.     //
  52.     // Initialize tables.
  53.     //
  54.     static {
  55.         //
  56.         // Filling EXPONENTIAL_SA_QI table.
  57.         // Note that we don't want qi = 0 in the table.
  58.         //
  59.         final double ln2 = Math.log(2);
  60.         double qi = 0;

  61.         // Start with 0!
  62.         // This will not overflow a long as the length < 21
  63.         long factorial = 1;
  64.         for (int i = 0; i < EXPONENTIAL_SA_QI.length; i++) {
  65.             factorial *= i + 1;
  66.             qi += Math.pow(ln2, i + 1.0) / factorial;
  67.             EXPONENTIAL_SA_QI[i] = qi;
  68.         }
  69.     }

  70.     /**
  71.      * Create an instance.
  72.      *
  73.      * @param rng Generator of uniformly distributed random numbers.
  74.      * @param mean Mean of this distribution.
  75.      * @throws IllegalArgumentException if {@code mean <= 0}
  76.      */
  77.     public AhrensDieterExponentialSampler(UniformRandomProvider rng,
  78.                                           double mean) {
  79.         // Validation before java.lang.Object constructor exits prevents partially initialized object
  80.         this(InternalUtils.requireStrictlyPositive(mean, "mean"), rng);
  81.     }

  82.     /**
  83.      * @param mean Mean.
  84.      * @param rng Generator of uniformly distributed random numbers.
  85.      */
  86.     private AhrensDieterExponentialSampler(double mean,
  87.                                            UniformRandomProvider rng) {
  88.         super(null);
  89.         this.rng = rng;
  90.         this.mean = mean;
  91.     }

  92.     /** {@inheritDoc} */
  93.     @Override
  94.     public double sample() {
  95.         // Step 1:
  96.         double a = 0;
  97.         // Avoid u=0 which creates an infinite loop
  98.         double u = InternalUtils.makeNonZeroDouble(rng.nextLong());

  99.         // Step 2 and 3:
  100.         while (u < 0.5) {
  101.             a += EXPONENTIAL_SA_QI[0];
  102.             u *= 2;
  103.         }

  104.         // Step 4 (now u >= 0.5):
  105.         u += u - 1;

  106.         // Step 5:
  107.         if (u <= EXPONENTIAL_SA_QI[0]) {
  108.             return mean * (a + u);
  109.         }

  110.         // Step 6:
  111.         int i = 0; // Should be 1, be we iterate before it in while using 0.
  112.         double u2 = rng.nextDouble();
  113.         double umin = u2;

  114.         // Step 7 and 8:
  115.         do {
  116.             ++i;
  117.             u2 = rng.nextDouble();

  118.             if (u2 < umin) {
  119.                 umin = u2;
  120.             }

  121.             // Step 8:
  122.         } while (u > EXPONENTIAL_SA_QI[i]); // Ensured to exit since EXPONENTIAL_SA_QI[MAX] = 1.

  123.         return mean * (a + umin * EXPONENTIAL_SA_QI[0]);
  124.     }

  125.     /** {@inheritDoc} */
  126.     @Override
  127.     public String toString() {
  128.         return "Ahrens-Dieter Exponential deviate [" + rng.toString() + "]";
  129.     }

  130.     /**
  131.      * {@inheritDoc}
  132.      *
  133.      * @since 1.3
  134.      */
  135.     @Override
  136.     public SharedStateContinuousSampler withUniformRandomProvider(UniformRandomProvider rng) {
  137.         // Use private constructor without validation
  138.         return new AhrensDieterExponentialSampler(mean, rng);
  139.     }

  140.     /**
  141.      * Create a new exponential distribution sampler.
  142.      *
  143.      * @param rng Generator of uniformly distributed random numbers.
  144.      * @param mean Mean of the distribution.
  145.      * @return the sampler
  146.      * @throws IllegalArgumentException if {@code mean <= 0}
  147.      * @since 1.3
  148.      */
  149.     public static SharedStateContinuousSampler of(UniformRandomProvider rng,
  150.                                                   double mean) {
  151.         return new AhrensDieterExponentialSampler(rng, mean);
  152.     }
  153. }