DiscreteUniformSampler.java
- /*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package org.apache.commons.rng.sampling.distribution;
- import org.apache.commons.rng.UniformRandomProvider;
- /**
- * Discrete uniform distribution sampler.
- *
- * <p>Sampling uses {@link UniformRandomProvider#nextInt}.</p>
- *
- * <p>When the range is a power of two the number of calls is 1 per sample.
- * Otherwise a rejection algorithm is used to ensure uniformity. In the worst
- * case scenario where the range spans half the range of an {@code int}
- * (2<sup>31</sup> + 1) the expected number of calls is 2 per sample.</p>
- *
- * <p>This sampler can be used as a replacement for {@link UniformRandomProvider#nextInt}
- * with appropriate adjustment of the upper bound to be inclusive and will outperform that
- * method when the range is not a power of two. The advantage is gained by pre-computation
- * of the rejection threshold.</p>
- *
- * <p>The sampling algorithm is described in:</p>
- *
- * <blockquote>
- * Lemire, D (2019). <i>Fast Random Integer Generation in an Interval.</i>
- * <strong>ACM Transactions on Modeling and Computer Simulation</strong> 29 (1).
- * </blockquote>
- *
- * <p>The number of {@code int} values required per sample follows a geometric distribution with
- * a probability of success p of {@code 1 - ((2^32 % n) / 2^32)}. This requires on average 1/p random
- * {@code int} values per sample.</p>
- *
- * @see <a href="https://arxiv.org/abs/1805.10941">Fast Random Integer Generation in an Interval</a>
- *
- * @since 1.0
- */
- public class DiscreteUniformSampler
- extends SamplerBase
- implements SharedStateDiscreteSampler {
- /** The appropriate uniform sampler for the parameters. */
- private final SharedStateDiscreteSampler delegate;
- /**
- * Base class for a sampler from a discrete uniform distribution. This contains the
- * source of randomness.
- */
- private abstract static class AbstractDiscreteUniformSampler
- implements SharedStateDiscreteSampler {
- /** Underlying source of randomness. */
- protected final UniformRandomProvider rng;
- /**
- * @param rng Generator of uniformly distributed random numbers.
- */
- AbstractDiscreteUniformSampler(UniformRandomProvider rng) {
- this.rng = rng;
- }
- @Override
- public String toString() {
- return "Uniform deviate [" + rng.toString() + "]";
- }
- }
- /**
- * Discrete uniform distribution sampler when the sample value is fixed.
- */
- private static final class FixedDiscreteUniformSampler
- extends AbstractDiscreteUniformSampler {
- /** The value. */
- private final int value;
- /**
- * @param value The value.
- */
- FixedDiscreteUniformSampler(int value) {
- // No requirement for the RNG
- super(null);
- this.value = value;
- }
- @Override
- public int sample() {
- return value;
- }
- @Override
- public String toString() {
- // No RNG to include in the string
- return "Uniform deviate [X=" + value + "]";
- }
- @Override
- public SharedStateDiscreteSampler withUniformRandomProvider(UniformRandomProvider rng) {
- // No requirement for the RNG
- return this;
- }
- }
- /**
- * Discrete uniform distribution sampler when the range is a power of 2 and greater than 1.
- * This sampler assumes the lower bound of the range is 0.
- *
- * <p>Note: This cannot be used when the range is 1 (2^0) as the shift would be 32-bits
- * which is ignored by the shift operator.</p>
- */
- private static final class PowerOf2RangeDiscreteUniformSampler
- extends AbstractDiscreteUniformSampler {
- /** Bit shift to apply to the integer sample. */
- private final int shift;
- /**
- * @param rng Generator of uniformly distributed random numbers.
- * @param range Maximum range of the sample (exclusive).
- * Must be a power of 2 greater than 2^0.
- */
- PowerOf2RangeDiscreteUniformSampler(UniformRandomProvider rng,
- int range) {
- super(rng);
- this.shift = Integer.numberOfLeadingZeros(range) + 1;
- }
- /**
- * @param rng Generator of uniformly distributed random numbers.
- * @param source Source to copy.
- */
- PowerOf2RangeDiscreteUniformSampler(UniformRandomProvider rng,
- PowerOf2RangeDiscreteUniformSampler source) {
- super(rng);
- this.shift = source.shift;
- }
- @Override
- public int sample() {
- // Use a bit shift to favour the most significant bits.
- // Note: The result would be the same as the rejection method used in the
- // small range sampler when there is no rejection threshold.
- return rng.nextInt() >>> shift;
- }
- @Override
- public SharedStateDiscreteSampler withUniformRandomProvider(UniformRandomProvider rng) {
- return new PowerOf2RangeDiscreteUniformSampler(rng, this);
- }
- }
- /**
- * Discrete uniform distribution sampler when the range is small
- * enough to fit in a positive integer.
- * This sampler assumes the lower bound of the range is 0.
- *
- * <p>Implements the algorithm of Lemire (2019).</p>
- *
- * @see <a href="https://arxiv.org/abs/1805.10941">Fast Random Integer Generation in an Interval</a>
- */
- private static final class SmallRangeDiscreteUniformSampler
- extends AbstractDiscreteUniformSampler {
- /** Maximum range of the sample (exclusive). */
- private final long n;
- /**
- * The level below which samples are rejected based on the fraction remainder.
- *
- * <p>Any remainder below this denotes that there are still floor(2^32 / n) more
- * observations of this sample from the interval [0, 2^32), where n is the range.</p>
- */
- private final long threshold;
- /**
- * @param rng Generator of uniformly distributed random numbers.
- * @param range Maximum range of the sample (exclusive).
- */
- SmallRangeDiscreteUniformSampler(UniformRandomProvider rng,
- int range) {
- super(rng);
- // Handle range as an unsigned 32-bit integer
- this.n = range & 0xffffffffL;
- // Compute 2^32 % n
- threshold = (1L << 32) % n;
- }
- /**
- * @param rng Generator of uniformly distributed random numbers.
- * @param source Source to copy.
- */
- SmallRangeDiscreteUniformSampler(UniformRandomProvider rng,
- SmallRangeDiscreteUniformSampler source) {
- super(rng);
- this.n = source.n;
- this.threshold = source.threshold;
- }
- @Override
- public int sample() {
- // Rejection method using multiply by a fraction:
- // n * [0, 2^32 - 1)
- // -------------
- // 2^32
- // The result is mapped back to an integer and will be in the range [0, n).
- // Note this is comparable to range * rng.nextDouble() but with compensation for
- // non-uniformity due to round-off.
- long result;
- do {
- // Compute 64-bit unsigned product of n * [0, 2^32 - 1).
- // The upper 32-bits contains the sample value in the range [0, n), i.e. result / 2^32.
- // The lower 32-bits contains the remainder, i.e. result % 2^32.
- result = n * (rng.nextInt() & 0xffffffffL);
- // Test the sample uniformity.
- // Samples are observed on average (2^32 / n) times at a frequency of either
- // floor(2^32 / n) or ceil(2^32 / n).
- // To ensure all samples have a frequency of floor(2^32 / n) reject any results with
- // a remainder < (2^32 % n), i.e. the level below which denotes that there are still
- // floor(2^32 / n) more observations of this sample.
- } while ((result & 0xffffffffL) < threshold);
- // Divide by 2^32 to get the sample.
- return (int)(result >>> 32);
- }
- @Override
- public SharedStateDiscreteSampler withUniformRandomProvider(UniformRandomProvider rng) {
- return new SmallRangeDiscreteUniformSampler(rng, this);
- }
- }
- /**
- * Discrete uniform distribution sampler when the range between lower and upper is too large
- * to fit in a positive integer.
- */
- private static final class LargeRangeDiscreteUniformSampler
- extends AbstractDiscreteUniformSampler {
- /** Lower bound. */
- private final int lower;
- /** Upper bound. */
- private final int upper;
- /**
- * @param rng Generator of uniformly distributed random numbers.
- * @param lower Lower bound (inclusive) of the distribution.
- * @param upper Upper bound (inclusive) of the distribution.
- */
- LargeRangeDiscreteUniformSampler(UniformRandomProvider rng,
- int lower,
- int upper) {
- super(rng);
- this.lower = lower;
- this.upper = upper;
- }
- @Override
- public int sample() {
- // Use a simple rejection method.
- // This is used when (upper-lower) >= Integer.MAX_VALUE.
- // This will loop on average 2 times in the worst case scenario
- // when (upper-lower) == Integer.MAX_VALUE.
- while (true) {
- final int r = rng.nextInt();
- if (r >= lower &&
- r <= upper) {
- return r;
- }
- }
- }
- @Override
- public SharedStateDiscreteSampler withUniformRandomProvider(UniformRandomProvider rng) {
- return new LargeRangeDiscreteUniformSampler(rng, lower, upper);
- }
- }
- /**
- * Adds an offset to an underlying discrete sampler.
- */
- private static final class OffsetDiscreteUniformSampler
- extends AbstractDiscreteUniformSampler {
- /** The offset. */
- private final int offset;
- /** The discrete sampler. */
- private final SharedStateDiscreteSampler sampler;
- /**
- * @param offset The offset for the sample.
- * @param sampler The discrete sampler.
- */
- OffsetDiscreteUniformSampler(int offset,
- SharedStateDiscreteSampler sampler) {
- super(null);
- this.offset = offset;
- this.sampler = sampler;
- }
- @Override
- public int sample() {
- return offset + sampler.sample();
- }
- @Override
- public String toString() {
- return sampler.toString();
- }
- @Override
- public SharedStateDiscreteSampler withUniformRandomProvider(UniformRandomProvider rng) {
- return new OffsetDiscreteUniformSampler(offset, sampler.withUniformRandomProvider(rng));
- }
- }
- /**
- * This instance delegates sampling. Use the factory method
- * {@link #of(UniformRandomProvider, int, int)} to create an optimal sampler.
- *
- * @param rng Generator of uniformly distributed random numbers.
- * @param lower Lower bound (inclusive) of the distribution.
- * @param upper Upper bound (inclusive) of the distribution.
- * @throws IllegalArgumentException if {@code lower > upper}.
- */
- public DiscreteUniformSampler(UniformRandomProvider rng,
- int lower,
- int upper) {
- this(of(rng, lower, upper));
- }
- /**
- * Private constructor used by to prevent partially initialized object if the construction
- * of the delegate throws. In future versions the public constructor should be removed.
- *
- * @param delegate Delegate.
- */
- private DiscreteUniformSampler(SharedStateDiscreteSampler delegate) {
- super(null);
- this.delegate = delegate;
- }
- /** {@inheritDoc} */
- @Override
- public int sample() {
- return delegate.sample();
- }
- /** {@inheritDoc} */
- @Override
- public String toString() {
- return delegate.toString();
- }
- /**
- * {@inheritDoc}
- *
- * @since 1.3
- */
- @Override
- public SharedStateDiscreteSampler withUniformRandomProvider(UniformRandomProvider rng) {
- // Direct return of the optimised sampler
- return delegate.withUniformRandomProvider(rng);
- }
- /**
- * Creates a new discrete uniform distribution sampler.
- *
- * @param rng Generator of uniformly distributed random numbers.
- * @param lower Lower bound (inclusive) of the distribution.
- * @param upper Upper bound (inclusive) of the distribution.
- * @return the sampler
- * @throws IllegalArgumentException if {@code lower > upper}.
- * @since 1.3
- */
- public static SharedStateDiscreteSampler of(UniformRandomProvider rng,
- int lower,
- int upper) {
- if (lower > upper) {
- throw new IllegalArgumentException(lower + " > " + upper);
- }
- // Choose the algorithm depending on the range
- // Edge case for no range.
- // This must be done first as the methods to handle lower == 0
- // do not handle upper == 0.
- if (upper == lower) {
- return new FixedDiscreteUniformSampler(lower);
- }
- // Algorithms to ignore the lower bound if it is zero.
- if (lower == 0) {
- return createZeroBoundedSampler(rng, upper);
- }
- final int range = (upper - lower) + 1;
- // Check power of 2 first to handle range == 2^31.
- if (isPowerOf2(range)) {
- return new OffsetDiscreteUniformSampler(lower,
- new PowerOf2RangeDiscreteUniformSampler(rng, range));
- }
- if (range <= 0) {
- // The range is too wide to fit in a positive int (larger
- // than 2^31); use a simple rejection method.
- // Note: if range == 0 then the input is [Integer.MIN_VALUE, Integer.MAX_VALUE].
- // No specialisation exists for this and it is handled as a large range.
- return new LargeRangeDiscreteUniformSampler(rng, lower, upper);
- }
- // Use a sample from the range added to the lower bound.
- return new OffsetDiscreteUniformSampler(lower,
- new SmallRangeDiscreteUniformSampler(rng, range));
- }
- /**
- * Create a new sampler for the range {@code 0} inclusive to {@code upper} inclusive.
- *
- * <p>This can handle any positive {@code upper}.
- *
- * @param rng Generator of uniformly distributed random numbers.
- * @param upper Upper bound (inclusive) of the distribution. Must be positive.
- * @return the sampler
- */
- private static AbstractDiscreteUniformSampler createZeroBoundedSampler(UniformRandomProvider rng,
- int upper) {
- // Note: Handle any range up to 2^31 (which is negative as a signed
- // 32-bit integer but handled as a power of 2)
- final int range = upper + 1;
- return isPowerOf2(range) ?
- new PowerOf2RangeDiscreteUniformSampler(rng, range) :
- new SmallRangeDiscreteUniformSampler(rng, range);
- }
- /**
- * Checks if the value is a power of 2.
- *
- * <p>This returns {@code true} for the value {@code Integer.MIN_VALUE} which can be
- * handled as an unsigned integer of 2^31.</p>
- *
- * @param value Value.
- * @return {@code true} if a power of 2
- */
- private static boolean isPowerOf2(final int value) {
- return value != 0 && (value & (value - 1)) == 0;
- }
- }