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;
}
}