RejectionInversionZipfSampler.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.  * Implementation of the <a href="https://en.wikipedia.org/wiki/Zipf's_law">Zipf distribution</a>.
  21.  *
  22.  * <p>Sampling uses {@link UniformRandomProvider#nextDouble()}.</p>
  23.  *
  24.  * @since 1.0
  25.  */
  26. public class RejectionInversionZipfSampler
  27.     extends SamplerBase
  28.     implements SharedStateDiscreteSampler {

  29.     /** The implementation of the sample method. */
  30.     private final SharedStateDiscreteSampler delegate;

  31.     /**
  32.      * Implements the rejection-inversion method for the Zipf distribution.
  33.      */
  34.     private static final class RejectionInversionZipfSamplerImpl implements SharedStateDiscreteSampler {
  35.         /** Threshold below which Taylor series will be used. */
  36.         private static final double TAYLOR_THRESHOLD = 1e-8;
  37.         /** 1/2. */
  38.         private static final double F_1_2 = 0.5;
  39.         /** 1/3. */
  40.         private static final double F_1_3 = 1d / 3;
  41.         /** 1/4. */
  42.         private static final double F_1_4 = 0.25;
  43.         /** Number of elements. */
  44.         private final int numberOfElements;
  45.         /** Exponent parameter of the distribution. */
  46.         private final double exponent;
  47.         /** {@code hIntegral(1.5) - 1}. */
  48.         private final double hIntegralX1;
  49.         /** {@code hIntegral(numberOfElements + 0.5)}. */
  50.         private final double hIntegralNumberOfElements;
  51.         /** {@code hIntegralX1 - hIntegralNumberOfElements}. */
  52.         private final double r;
  53.         /** {@code 2 - hIntegralInverse(hIntegral(2.5) - h(2)}. */
  54.         private final double s;
  55.         /** Underlying source of randomness. */
  56.         private final UniformRandomProvider rng;

  57.         /**
  58.          * @param rng Generator of uniformly distributed random numbers.
  59.          * @param numberOfElements Number of elements (must be {@code > 0}).
  60.          * @param exponent Exponent (must be {@code > 0}).
  61.          */
  62.         RejectionInversionZipfSamplerImpl(UniformRandomProvider rng,
  63.                                           int numberOfElements,
  64.                                           double exponent) {
  65.             this.rng = rng;
  66.             this.numberOfElements = numberOfElements;
  67.             this.exponent = exponent;
  68.             this.hIntegralX1 = hIntegral(1.5) - 1;
  69.             this.hIntegralNumberOfElements = hIntegral(numberOfElements + F_1_2);
  70.             this.r = hIntegralX1 - hIntegralNumberOfElements;
  71.             this.s = 2 - hIntegralInverse(hIntegral(2.5) - h(2));
  72.         }

  73.         /**
  74.          * @param rng Generator of uniformly distributed random numbers.
  75.          * @param source Source to copy.
  76.          */
  77.         private RejectionInversionZipfSamplerImpl(UniformRandomProvider rng,
  78.                                                   RejectionInversionZipfSamplerImpl source) {
  79.             this.rng = rng;
  80.             this.numberOfElements = source.numberOfElements;
  81.             this.exponent = source.exponent;
  82.             this.hIntegralX1 = source.hIntegralX1;
  83.             this.hIntegralNumberOfElements = source.hIntegralNumberOfElements;
  84.             this.r = source.r;
  85.             this.s = source.s;
  86.         }

  87.         @Override
  88.         public int sample() {
  89.             // The paper describes an algorithm for exponents larger than 1
  90.             // (Algorithm ZRI).
  91.             // The original method uses
  92.             //   H(x) = (v + x)^(1 - q) / (1 - q)
  93.             // as the integral of the hat function.
  94.             // This function is undefined for q = 1, which is the reason for
  95.             // the limitation of the exponent.
  96.             // If instead the integral function
  97.             //   H(x) = ((v + x)^(1 - q) - 1) / (1 - q)
  98.             // is used,
  99.             // for which a meaningful limit exists for q = 1, the method works
  100.             // for all positive exponents.
  101.             // The following implementation uses v = 0 and generates integral
  102.             // number in the range [1, numberOfElements].
  103.             // This is different to the original method where v is defined to
  104.             // be positive and numbers are taken from [0, i_max].
  105.             // This explains why the implementation looks slightly different.

  106.             while (true) {
  107.                 final double u = hIntegralNumberOfElements + rng.nextDouble() * r;
  108.                 // u is uniformly distributed in (hIntegralX1, hIntegralNumberOfElements]

  109.                 final double x = hIntegralInverse(u);
  110.                 int k = (int) (x + F_1_2);

  111.                 // Limit k to the range [1, numberOfElements] if it would be outside
  112.                 // due to numerical inaccuracies.
  113.                 if (k < 1) {
  114.                     k = 1;
  115.                 } else if (k > numberOfElements) {
  116.                     k = numberOfElements;
  117.                 }

  118.                 // Here, the distribution of k is given by:
  119.                 //
  120.                 //   P(k = 1) = C * (hIntegral(1.5) - hIntegralX1) = C
  121.                 //   P(k = m) = C * (hIntegral(m + 1/2) - hIntegral(m - 1/2)) for m >= 2
  122.                 //
  123.                 //   where C = 1 / (hIntegralNumberOfElements - hIntegralX1)

  124.                 if (k - x <= s || u >= hIntegral(k + F_1_2) - h(k)) {

  125.                     // Case k = 1:
  126.                     //
  127.                     //   The right inequality is always true, because replacing k by 1 gives
  128.                     //   u >= hIntegral(1.5) - h(1) = hIntegralX1 and u is taken from
  129.                     //   (hIntegralX1, hIntegralNumberOfElements].
  130.                     //
  131.                     //   Therefore, the acceptance rate for k = 1 is P(accepted | k = 1) = 1
  132.                     //   and the probability that 1 is returned as random value is
  133.                     //   P(k = 1 and accepted) = P(accepted | k = 1) * P(k = 1) = C = C / 1^exponent
  134.                     //
  135.                     // Case k >= 2:
  136.                     //
  137.                     //   The left inequality (k - x <= s) is just a short cut
  138.                     //   to avoid the more expensive evaluation of the right inequality
  139.                     //   (u >= hIntegral(k + 0.5) - h(k)) in many cases.
  140.                     //
  141.                     //   If the left inequality is true, the right inequality is also true:
  142.                     //     Theorem 2 in the paper is valid for all positive exponents, because
  143.                     //     the requirements h'(x) = -exponent/x^(exponent + 1) < 0 and
  144.                     //     (-1/hInverse'(x))'' = (1+1/exponent) * x^(1/exponent-1) >= 0
  145.                     //     are both fulfilled.
  146.                     //     Therefore, f(x) = x - hIntegralInverse(hIntegral(x + 0.5) - h(x))
  147.                     //     is a non-decreasing function. If k - x <= s holds,
  148.                     //     k - x <= s + f(k) - f(2) is obviously also true which is equivalent to
  149.                     //     -x <= -hIntegralInverse(hIntegral(k + 0.5) - h(k)),
  150.                     //     -hIntegralInverse(u) <= -hIntegralInverse(hIntegral(k + 0.5) - h(k)),
  151.                     //     and finally u >= hIntegral(k + 0.5) - h(k).
  152.                     //
  153.                     //   Hence, the right inequality determines the acceptance rate:
  154.                     //   P(accepted | k = m) = h(m) / (hIntegrated(m+1/2) - hIntegrated(m-1/2))
  155.                     //   The probability that m is returned is given by
  156.                     //   P(k = m and accepted) = P(accepted | k = m) * P(k = m) = C * h(m) = C / m^exponent.
  157.                     //
  158.                     // In both cases the probabilities are proportional to the probability mass function
  159.                     // of the Zipf distribution.

  160.                     return k;
  161.                 }
  162.             }
  163.         }

  164.         /** {@inheritDoc} */
  165.         @Override
  166.         public String toString() {
  167.             return "Rejection inversion Zipf deviate [" + rng.toString() + "]";
  168.         }

  169.         @Override
  170.         public SharedStateDiscreteSampler withUniformRandomProvider(UniformRandomProvider rng) {
  171.             return new RejectionInversionZipfSamplerImpl(rng, this);
  172.         }

  173.         /**
  174.          * {@code H(x)} is defined as
  175.          * <ul>
  176.          *  <li>{@code (x^(1 - exponent) - 1) / (1 - exponent)}, if {@code exponent != 1}</li>
  177.          *  <li>{@code log(x)}, if {@code exponent == 1}</li>
  178.          * </ul>
  179.          * H(x) is an integral function of h(x), the derivative of H(x) is h(x).
  180.          *
  181.          * @param x Free parameter.
  182.          * @return {@code H(x)}.
  183.          */
  184.         private double hIntegral(final double x) {
  185.             final double logX = Math.log(x);
  186.             return helper2((1 - exponent) * logX) * logX;
  187.         }

  188.         /**
  189.          * {@code h(x) = 1 / x^exponent}.
  190.          *
  191.          * @param x Free parameter.
  192.          * @return {@code h(x)}.
  193.          */
  194.         private double h(final double x) {
  195.             return Math.exp(-exponent * Math.log(x));
  196.         }

  197.         /**
  198.          * The inverse function of {@code H(x)}.
  199.          *
  200.          * @param x Free parameter
  201.          * @return y for which {@code H(y) = x}.
  202.          */
  203.         private double hIntegralInverse(final double x) {
  204.             double t = x * (1 - exponent);
  205.             if (t < -1) {
  206.                 // Limit value to the range [-1, +inf).
  207.                 // t could be smaller than -1 in some rare cases due to numerical errors.
  208.                 t = -1;
  209.             }
  210.             return Math.exp(helper1(t) * x);
  211.         }

  212.         /**
  213.          * Helper function that calculates {@code log(1 + x) / x}.
  214.          * <p>
  215.          * A Taylor series expansion is used, if x is close to 0.
  216.          * </p>
  217.          *
  218.          * @param x A value larger than or equal to -1.
  219.          * @return {@code log(1 + x) / x}.
  220.          */
  221.         private static double helper1(final double x) {
  222.             if (Math.abs(x) > TAYLOR_THRESHOLD) {
  223.                 return Math.log1p(x) / x;
  224.             }
  225.             return 1 - x * (F_1_2 - x * (F_1_3 - F_1_4 * x));
  226.         }

  227.         /**
  228.          * Helper function to calculate {@code (exp(x) - 1) / x}.
  229.          * <p>
  230.          * A Taylor series expansion is used, if x is close to 0.
  231.          * </p>
  232.          *
  233.          * @param x Free parameter.
  234.          * @return {@code (exp(x) - 1) / x} if x is non-zero, or 1 if x = 0.
  235.          */
  236.         private static double helper2(final double x) {
  237.             if (Math.abs(x) > TAYLOR_THRESHOLD) {
  238.                 return Math.expm1(x) / x;
  239.             }
  240.             return 1 + x * F_1_2 * (1 + x * F_1_3 * (1 + F_1_4 * x));
  241.         }
  242.     }

  243.     /**
  244.      * This instance delegates sampling. Use the factory method
  245.      * {@link #of(UniformRandomProvider, int, double)} to create an optimal sampler.
  246.      *
  247.      * @param rng Generator of uniformly distributed random numbers.
  248.      * @param numberOfElements Number of elements.
  249.      * @param exponent Exponent.
  250.      * @throws IllegalArgumentException if {@code numberOfElements <= 0}
  251.      * or {@code exponent < 0}.
  252.      */
  253.     public RejectionInversionZipfSampler(UniformRandomProvider rng,
  254.                                          int numberOfElements,
  255.                                          double exponent) {
  256.         this(of(rng, numberOfElements, exponent));
  257.     }

  258.     /**
  259.      * Private constructor used by to prevent partially initialized object if the construction
  260.      * of the delegate throws. In future versions the public constructor should be removed.
  261.      *
  262.      * @param delegate Delegate.
  263.      */
  264.     private RejectionInversionZipfSampler(SharedStateDiscreteSampler delegate) {
  265.         super(null);
  266.         this.delegate = delegate;
  267.     }

  268.     /**
  269.      * Rejection inversion sampling method for a discrete, bounded Zipf
  270.      * distribution that is based on the method described in
  271.      * <blockquote>
  272.      *   Wolfgang Hörmann and Gerhard Derflinger.
  273.      *   <i>"Rejection-inversion to generate variates from monotone discrete
  274.      *    distributions",</i><br>
  275.      *   <strong>ACM Transactions on Modeling and Computer Simulation</strong> (TOMACS) 6.3 (1996): 169-184.
  276.      * </blockquote>
  277.      */
  278.     @Override
  279.     public int sample() {
  280.         return delegate.sample();
  281.     }

  282.     /** {@inheritDoc} */
  283.     @Override
  284.     public String toString() {
  285.         return delegate.toString();
  286.     }

  287.     /**
  288.      * {@inheritDoc}
  289.      *
  290.      * @since 1.3
  291.      */
  292.     @Override
  293.     public SharedStateDiscreteSampler withUniformRandomProvider(UniformRandomProvider rng) {
  294.         return delegate.withUniformRandomProvider(rng);
  295.     }

  296.     /**
  297.      * Creates a new Zipf distribution sampler.
  298.      *
  299.      * <p>Note when {@code exponent = 0} the Zipf distribution reduces to a
  300.      * discrete uniform distribution over the interval {@code [1, n]} with {@code n}
  301.      * the number of elements.
  302.      *
  303.      * @param rng Generator of uniformly distributed random numbers.
  304.      * @param numberOfElements Number of elements.
  305.      * @param exponent Exponent.
  306.      * @return the sampler
  307.      * @throws IllegalArgumentException if {@code numberOfElements <= 0} or
  308.      * {@code exponent < 0}.
  309.      * @since 1.3
  310.      */
  311.     public static SharedStateDiscreteSampler of(UniformRandomProvider rng,
  312.                                                 int numberOfElements,
  313.                                                 double exponent) {
  314.         if (numberOfElements <= 0) {
  315.             throw new IllegalArgumentException("number of elements is not strictly positive: " + numberOfElements);
  316.         }
  317.         InternalUtils.requirePositive(exponent, "exponent");

  318.         // When the exponent is at the limit of 0 the distribution PMF reduces to 1 / n
  319.         // and sampling can use a discrete uniform sampler.
  320.         return exponent > 0 ?
  321.             new RejectionInversionZipfSamplerImpl(rng, numberOfElements, exponent) :
  322.             DiscreteUniformSampler.of(rng, 1, numberOfElements);
  323.     }
  324. }