You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by ah...@apache.org on 2021/06/14 15:53:31 UTC
[commons-rng] branch master updated: RNG-145:
ContinuousUniformSampler to support an open bound
This is an automated email from the ASF dual-hosted git repository.
aherbert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-rng.git
The following commit(s) were added to refs/heads/master by this push:
new 81b07fa RNG-145: ContinuousUniformSampler to support an open bound
81b07fa is described below
commit 81b07fadbfba6d6afe7bb74ec3e8a5a43a40c343
Author: aherbert <ah...@apache.org>
AuthorDate: Mon Jun 14 16:53:27 2021 +0100
RNG-145: ContinuousUniformSampler to support an open bound
---
.../distribution/ContinuousUniformSampler.java | 83 +++++++++++++++++++++-
.../rng/sampling/distribution/InternalUtils.java | 26 +++++++
.../distribution/ContinuousUniformSamplerTest.java | 68 +++++++++++++++++-
3 files changed, 175 insertions(+), 2 deletions(-)
diff --git a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/ContinuousUniformSampler.java b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/ContinuousUniformSampler.java
index feaddc2..6a8a9a9 100644
--- a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/ContinuousUniformSampler.java
+++ b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/ContinuousUniformSampler.java
@@ -36,6 +36,30 @@ public class ContinuousUniformSampler
private final UniformRandomProvider rng;
/**
+ * Specialization to sample from an open interval {@code (lo, hi)}.
+ */
+ private static class OpenIntervalContinuousUniformSampler extends ContinuousUniformSampler {
+ /**
+ * @param rng Generator of uniformly distributed random numbers.
+ * @param lo Lower bound.
+ * @param hi Higher bound.
+ */
+ OpenIntervalContinuousUniformSampler(UniformRandomProvider rng, double lo, double hi) {
+ super(rng, lo, hi);
+ }
+
+ @Override
+ double getU() {
+ return InternalUtils.nextDouble01(getRng());
+ }
+
+ @Override
+ public SharedStateContinuousSampler withUniformRandomProvider(UniformRandomProvider rng) {
+ return new OpenIntervalContinuousUniformSampler(rng, getLo(), getHi());
+ }
+ }
+
+ /**
* @param rng Generator of uniformly distributed random numbers.
* @param lo Lower bound.
* @param hi Higher bound.
@@ -52,10 +76,47 @@ public class ContinuousUniformSampler
/** {@inheritDoc} */
@Override
public double sample() {
- final double u = rng.nextDouble();
+ final double u = getU();
return u * hi + (1 - u) * lo;
}
+ /**
+ * Gets the uniform deviate {@code u} the interval 0 to 1.
+ * The interval may be open or closed depending on the implementation.
+ *
+ * @return u
+ */
+ double getU() {
+ return rng.nextDouble();
+ }
+
+ /**
+ * Gets the lower bound. This is deliberately scoped as package private.
+ *
+ * @return the lower bound
+ */
+ double getLo() {
+ return lo;
+ }
+
+ /**
+ * Gets the higher bound. This is deliberately scoped as package private.
+ *
+ * @return the higher bound
+ */
+ double getHi() {
+ return hi;
+ }
+
+ /**
+ * Gets the RNG. This is deliberately scoped as package private.
+ *
+ * @return the rng
+ */
+ UniformRandomProvider getRng() {
+ return rng;
+ }
+
/** {@inheritDoc} */
@Override
public String toString() {
@@ -86,4 +147,24 @@ public class ContinuousUniformSampler
double hi) {
return new ContinuousUniformSampler(rng, lo, hi);
}
+
+ /**
+ * Creates a new continuous uniform distribution sampler.
+ * The bounds can be optionally excluded.
+ *
+ * @param rng Generator of uniformly distributed random numbers.
+ * @param lo Lower bound.
+ * @param hi Higher bound.
+ * @param excludeBounds Set to {@code true} to use the open interval {@code (lower, upper)}.
+ * @return the sampler
+ * @since 1.4
+ */
+ public static SharedStateContinuousSampler of(UniformRandomProvider rng,
+ double lo,
+ double hi,
+ boolean excludeBounds) {
+ return excludeBounds ?
+ new OpenIntervalContinuousUniformSampler(rng, lo, hi) :
+ new ContinuousUniformSampler(rng, lo, hi);
+ }
}
diff --git a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/InternalUtils.java b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/InternalUtils.java
index af4c049..02eed2f 100644
--- a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/InternalUtils.java
+++ b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/InternalUtils.java
@@ -38,6 +38,13 @@ final class InternalUtils { // Class is package-private on purpose; do not make
/** The first array index with a non-zero log factorial. */
private static final int BEGIN_LOG_FACTORIALS = 2;
+ /**
+ * The multiplier to convert the least significant 53-bits of a {@code long} to a {@code double}.
+ * See {@link #makeDouble(long)} and {@link #makeDouble(int, int)}.
+ *
+ * <p>This is equivalent to 1.0 / (1L << 53).
+ */
+ private static final double DOUBLE_MULTIPLIER = 0x1.0p-53d;
/** Utility class. */
private InternalUtils() {}
@@ -119,6 +126,25 @@ final class InternalUtils { // Class is package-private on purpose; do not make
}
/**
+ * Gets a uniform random variable in the open interval {@code (0, 1)}.
+ *
+ * @param rng Generator of uniformly distributed random numbers.
+ * @return u
+ */
+ static double nextDouble01(UniformRandomProvider rng) {
+ // See o.a.c.rng.core.util.NumberFactory.createDouble(long)
+ // Require the least significant 53-bits so shift the higher bits across
+ final long x = rng.nextLong() >>> 11;
+ if (x == 0) {
+ // Recursive call will create a stack overflow if the random generator
+ // is broken (always returns 0-bits). The alternative would be an infinite loop.
+ return nextDouble01(rng);
+ }
+ // Generate a double in the 2^53-1 dyadic rationals in (0, 1).
+ return x * DOUBLE_MULTIPLIER;
+ }
+
+ /**
* Class for computing the natural logarithm of the factorial of {@code n}.
* It allows to allocate a cache of precomputed values.
* In case of cache miss, computation is performed by a call to
diff --git a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/ContinuousUniformSamplerTest.java b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/ContinuousUniformSamplerTest.java
index 59f12c2..f1cf6f9 100644
--- a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/ContinuousUniformSamplerTest.java
+++ b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/ContinuousUniformSamplerTest.java
@@ -17,6 +17,7 @@
package org.apache.commons.rng.sampling.distribution;
import org.apache.commons.rng.UniformRandomProvider;
+import org.apache.commons.rng.core.source64.SplitMix64;
import org.apache.commons.rng.sampling.RandomAssert;
import org.apache.commons.rng.simple.RandomSource;
import org.junit.Assert;
@@ -50,17 +51,82 @@ public class ContinuousUniformSamplerTest {
}
/**
+ * Test the sampler excludes the bounds when the underlying generator returns long values
+ * that produce the limit of the uniform double output.
+ */
+ @Test
+ public void testExcludeBounds() {
+ // A broken RNG that will return in an alternating sequence from 0 up or -1 down.
+ // This is either zero bits or all the bits
+ final UniformRandomProvider rng = new SplitMix64(0L) {
+ private long l1;
+ private long l2;
+ @Override
+ public long nextLong() {
+ if (l1 > l2) {
+ l2++;
+ // Descending sequence: -1, -2, -3, ...
+ return -l2;
+ }
+ // Ascending sequence: 0, 1, 2, ...
+ l1++;
+ // Shift by 11 bits to reverse the shift performed when computing the next
+ // double from a long.
+ return l1 << 11;
+ }
+ };
+ final double low = 3.18;
+ final double high = 5.23;
+ final SharedStateContinuousSampler sampler =
+ ContinuousUniformSampler.of(rng, low, high, true);
+ // Test the sampler excludes the end points
+ for (int i = 0; i < 10; i++) {
+ final double value = sampler.sample();
+ Assert.assertTrue("Value not in range", value >= low && value <= high);
+ }
+ }
+
+ /**
* Test the SharedStateSampler implementation.
*/
@Test
public void testSharedStateSampler() {
+ testSharedStateSampler(false);
+ testSharedStateSampler(true);
+ }
+
+ /**
+ * Test the SharedStateSampler implementation.
+ *
+ * @param excludedBounds Set to true to exclude the bounds.
+ */
+ private static void testSharedStateSampler(boolean excludedBounds) {
final UniformRandomProvider rng1 = RandomSource.SPLIT_MIX_64.create(0L);
final UniformRandomProvider rng2 = RandomSource.SPLIT_MIX_64.create(0L);
final double low = 1.23;
final double high = 4.56;
final SharedStateContinuousSampler sampler1 =
- ContinuousUniformSampler.of(rng1, low, high);
+ ContinuousUniformSampler.of(rng1, low, high, excludedBounds);
final SharedStateContinuousSampler sampler2 = sampler1.withUniformRandomProvider(rng2);
RandomAssert.assertProduceSameSequence(sampler1, sampler2);
}
+
+ /**
+ * Test the sampler implementation with bounds excluded matches that with bounds included
+ * when the generator does not produce the limit of the uniform double output.
+ */
+ @Test
+ public void testSamplerWithBoundsExcluded() {
+ // SplitMix64 only returns zero once in the output. Seeded with zero it outputs zero
+ // at the end of the period.
+ final UniformRandomProvider rng1 = RandomSource.SPLIT_MIX_64.create(0L);
+ final UniformRandomProvider rng2 = RandomSource.SPLIT_MIX_64.create(0L);
+ final double low = 1.23;
+ final double high = 4.56;
+ final SharedStateContinuousSampler sampler1 =
+ ContinuousUniformSampler.of(rng1, low, high, false);
+ final SharedStateContinuousSampler sampler2 =
+ ContinuousUniformSampler.of(rng2, low, high, true);
+ RandomAssert.assertProduceSameSequence(sampler1, sampler2);
+ }
}