KempSmallMeanPoissonSampler.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.  * Sampler for the <a href="http://mathworld.wolfram.com/PoissonDistribution.html">Poisson
  21.  * distribution</a>.
  22.  *
  23.  * <ul>
  24.  *   <li>
  25.  *     Kemp, A, W, (1981) Efficient Generation of Logarithmically Distributed
  26.  *     Pseudo-Random Variables. Journal of the Royal Statistical Society. Vol. 30, No. 3, pp.
  27.  *     249-253.
  28.  *   </li>
  29.  * </ul>
  30.  *
  31.  * <p>This sampler is suitable for {@code mean < 40}. For large means,
  32.  * {@link LargeMeanPoissonSampler} should be used instead.</p>
  33.  *
  34.  * <p>Note: The algorithm uses a recurrence relation to compute the Poisson probability
  35.  * and a rolling summation for the cumulative probability. When the mean is large the
  36.  * initial probability (Math.exp(-mean)) is zero and an exception is raised by the
  37.  * constructor.</p>
  38.  *
  39.  * <p>Sampling uses 1 call to {@link UniformRandomProvider#nextDouble()}. This method provides
  40.  * an alternative to the {@link SmallMeanPoissonSampler} for slow generators of {@code double}.</p>
  41.  *
  42.  * @see <a href="https://www.jstor.org/stable/2346348">Kemp, A.W. (1981) JRSS Vol. 30, pp.
  43.  * 249-253</a>
  44.  * @since 1.3
  45.  */
  46. public final class KempSmallMeanPoissonSampler
  47.     implements SharedStateDiscreteSampler {
  48.     /** Underlying source of randomness. */
  49.     private final UniformRandomProvider rng;
  50.     /**
  51.      * Pre-compute {@code Math.exp(-mean)}.
  52.      * Note: This is the probability of the Poisson sample {@code p(x=0)}.
  53.      */
  54.     private final double p0;
  55.     /**
  56.      * The mean of the Poisson sample.
  57.      */
  58.     private final double mean;

  59.     /**
  60.      * @param rng Generator of uniformly distributed random numbers.
  61.      * @param p0 Probability of the Poisson sample {@code p(x=0)}.
  62.      * @param mean Mean.
  63.      */
  64.     private KempSmallMeanPoissonSampler(UniformRandomProvider rng,
  65.                                         double p0,
  66.                                         double mean) {
  67.         this.rng = rng;
  68.         this.p0 = p0;
  69.         this.mean = mean;
  70.     }

  71.     /** {@inheritDoc} */
  72.     @Override
  73.     public int sample() {
  74.         // Note on the algorithm:
  75.         // - X is the unknown sample deviate (the output of the algorithm)
  76.         // - x is the current value from the distribution
  77.         // - p is the probability of the current value x, p(X=x)
  78.         // - u is effectively the cumulative probability that the sample X
  79.         //   is equal or above the current value x, p(X>=x)
  80.         // So if p(X>=x) > p(X=x) the sample must be above x, otherwise it is x
  81.         double u = rng.nextDouble();
  82.         int x = 0;
  83.         double p = p0;
  84.         while (u > p) {
  85.             u -= p;
  86.             // Compute the next probability using a recurrence relation.
  87.             // p(x+1) = p(x) * mean / (x+1)
  88.             p *= mean / ++x;
  89.             // The algorithm listed in Kemp (1981) does not check that the rolling probability
  90.             // is positive. This check is added to ensure no errors when the limit of the summation
  91.             // 1 - sum(p(x)) is above 0 due to cumulative error in floating point arithmetic.
  92.             if (p == 0) {
  93.                 return x;
  94.             }
  95.         }
  96.         return x;
  97.     }

  98.     /** {@inheritDoc} */
  99.     @Override
  100.     public String toString() {
  101.         return "Kemp Small Mean Poisson deviate [" + rng.toString() + "]";
  102.     }

  103.     /** {@inheritDoc} */
  104.     @Override
  105.     public SharedStateDiscreteSampler withUniformRandomProvider(UniformRandomProvider rng) {
  106.         return new KempSmallMeanPoissonSampler(rng, p0, mean);
  107.     }

  108.     /**
  109.      * Creates a new sampler for the Poisson distribution.
  110.      *
  111.      * @param rng Generator of uniformly distributed random numbers.
  112.      * @param mean Mean of the distribution.
  113.      * @return the sampler
  114.      * @throws IllegalArgumentException if {@code mean <= 0} or
  115.      * {@code Math.exp(-mean) == 0}.
  116.      */
  117.     public static SharedStateDiscreteSampler of(UniformRandomProvider rng,
  118.                                                 double mean) {
  119.         InternalUtils.requireStrictlyPositive(mean, "mean");

  120.         final double p0 = Math.exp(-mean);

  121.         // Probability must be positive. As mean increases then p(0) decreases.
  122.         if (p0 > 0) {
  123.             return new KempSmallMeanPoissonSampler(rng, p0, mean);
  124.         }

  125.         // This catches the edge case of a NaN mean
  126.         throw new IllegalArgumentException("No probability for mean: " + mean);
  127.     }
  128. }