GeometricSampler.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="https://en.wikipedia.org/wiki/Geometric_distribution">geometric
  21.  * distribution</a>.
  22.  *
  23.  * <p>This distribution samples the number of failures before the first success taking values in the
  24.  * set {@code [0, 1, 2, ...]}.</p>
  25.  *
  26.  * <p>The sample is computed using a related exponential distribution. If \( X \) is an
  27.  * exponentially distributed random variable with parameter \( \lambda \), then
  28.  * \( Y = \left \lfloor X \right \rfloor \) is a geometrically distributed random variable with
  29.  * parameter \( p = 1 − e^\lambda \), with \( p \) the probability of success.</p>
  30.  *
  31.  * <p>This sampler outperforms using the {@link InverseTransformDiscreteSampler} with an appropriate
  32.  * geometric inverse cumulative probability function.</p>
  33.  *
  34.  * <p>Usage note: As the probability of success (\( p \)) tends towards zero the mean of the
  35.  * distribution (\( \frac{1-p}{p} \)) tends towards infinity and due to the use of {@code int}
  36.  * for the sample this can result in truncation of the distribution.</p>
  37.  *
  38.  * <p>Sampling uses {@link UniformRandomProvider#nextDouble()}.</p>
  39.  *
  40.  * @see <a
  41.  * href="https://en.wikipedia.org/wiki/Geometric_distribution#Related_distributions">Geometric
  42.  * distribution - related distributions</a>
  43.  * @since 1.3
  44.  */
  45. public final class GeometricSampler {
  46.     /**
  47.      * Sample from the geometric distribution when the probability of success is 1.
  48.      */
  49.     private static final class GeometricP1Sampler
  50.         implements SharedStateDiscreteSampler {
  51.         /** The single instance. */
  52.         static final GeometricP1Sampler INSTANCE = new GeometricP1Sampler();

  53.         @Override
  54.         public int sample() {
  55.             // When probability of success is 1 the sample is always zero
  56.             return 0;
  57.         }

  58.         @Override
  59.         public String toString() {
  60.             return "Geometric(p=1) deviate";
  61.         }

  62.         @Override
  63.         public SharedStateDiscreteSampler withUniformRandomProvider(UniformRandomProvider rng) {
  64.             // No requirement for a new instance
  65.             return this;
  66.         }
  67.     }

  68.     /**
  69.      * Sample from the geometric distribution by using a related exponential distribution.
  70.      */
  71.     private static final class GeometricExponentialSampler
  72.         implements SharedStateDiscreteSampler {
  73.         /** Underlying source of randomness. Used only for the {@link #toString()} method. */
  74.         private final UniformRandomProvider rng;
  75.         /** The related exponential sampler for the geometric distribution. */
  76.         private final SharedStateContinuousSampler exponentialSampler;

  77.         /**
  78.          * @param rng Generator of uniformly distributed random numbers
  79.          * @param probabilityOfSuccess The probability of success (must be in the range
  80.          * {@code [0 < probabilityOfSuccess < 1]})
  81.          */
  82.         GeometricExponentialSampler(UniformRandomProvider rng, double probabilityOfSuccess) {
  83.             this.rng = rng;
  84.             // Use a related exponential distribution:
  85.             // λ = −ln(1 − probabilityOfSuccess)
  86.             // exponential mean = 1 / λ
  87.             // --
  88.             // Note on validation:
  89.             // If probabilityOfSuccess == Math.nextDown(1.0) the exponential mean is >0 (valid).
  90.             // If probabilityOfSuccess == Double.MIN_VALUE the exponential mean is +Infinity
  91.             // and the sample will always be Integer.MAX_VALUE (the distribution is truncated). It
  92.             // is noted in the class Javadoc that the use of a small p leads to truncation so
  93.             // no checks are made for this case.
  94.             final double exponentialMean = 1.0 / (-Math.log1p(-probabilityOfSuccess));
  95.             exponentialSampler = ZigguratSampler.Exponential.of(rng, exponentialMean);
  96.         }

  97.         /**
  98.          * @param rng Generator of uniformly distributed random numbers
  99.          * @param source Source to copy.
  100.          */
  101.         GeometricExponentialSampler(UniformRandomProvider rng, GeometricExponentialSampler source) {
  102.             this.rng = rng;
  103.             exponentialSampler = source.exponentialSampler.withUniformRandomProvider(rng);
  104.         }

  105.         @Override
  106.         public int sample() {
  107.             // Return the floor of the exponential sample
  108.             return (int) Math.floor(exponentialSampler.sample());
  109.         }

  110.         @Override
  111.         public String toString() {
  112.             return "Geometric deviate [" + rng.toString() + "]";
  113.         }

  114.         @Override
  115.         public SharedStateDiscreteSampler withUniformRandomProvider(UniformRandomProvider rng) {
  116.             return new GeometricExponentialSampler(rng, this);
  117.         }
  118.     }

  119.     /** Class contains only static methods. */
  120.     private GeometricSampler() {}

  121.     /**
  122.      * Creates a new geometric distribution sampler. The samples will be provided in the set
  123.      * {@code k=[0, 1, 2, ...]} where {@code k} indicates the number of failures before the first
  124.      * success.
  125.      *
  126.      * @param rng Generator of uniformly distributed random numbers.
  127.      * @param probabilityOfSuccess The probability of success.
  128.      * @return the sampler
  129.      * @throws IllegalArgumentException if {@code probabilityOfSuccess} is not in the range
  130.      * {@code [0 < probabilityOfSuccess <= 1]})
  131.      */
  132.     public static SharedStateDiscreteSampler of(UniformRandomProvider rng,
  133.                                                 double probabilityOfSuccess) {
  134.         if (probabilityOfSuccess <= 0 || probabilityOfSuccess > 1) {
  135.             throw new IllegalArgumentException(
  136.                 "Probability of success (p) must be in the range [0 < p <= 1]: " +
  137.                     probabilityOfSuccess);
  138.         }
  139.         return probabilityOfSuccess == 1 ?
  140.             GeometricP1Sampler.INSTANCE :
  141.             new GeometricExponentialSampler(rng, probabilityOfSuccess);
  142.     }
  143. }