This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits 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 8abfa517 RNG-196: Introduce default alpha to exclude unobservable
categories
8abfa517 is described below
commit 8abfa51735d6d4371e6c65f63a568cfa63f1b6d5
Author: Alex Herbert <[email protected]>
AuthorDate: Sat Aug 22 09:53:48 2026 +0100
RNG-196: Introduce default alpha to exclude unobservable categories
---
.../FastLoadedDiceRollerDiscreteSampler.java | 31 ++++++++++++++++++----
.../FastLoadedDiceRollerDiscreteSamplerTest.java | 23 ++++++++++++++--
src/changes/changes.xml | 7 +++++
3 files changed, 54 insertions(+), 7 deletions(-)
diff --git
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/FastLoadedDiceRollerDiscreteSampler.java
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/FastLoadedDiceRollerDiscreteSampler.java
index 474a307e..ffeca8ae 100644
---
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/FastLoadedDiceRollerDiscreteSampler.java
+++
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/FastLoadedDiceRollerDiscreteSampler.java
@@ -83,6 +83,16 @@ public abstract class FastLoadedDiceRollerDiscreteSampler
private static final int NO_LABEL = Integer.MAX_VALUE;
/** Name of the sampler. */
private static final String SAMPLER_NAME = "Fast Loaded Dice Roller";
+ /**
+ * Default relative magnitude cutoff (alpha) applied when creating a
sampler from
+ * {@code double} weights. Any weight approximately 2<sup>53</sup> smaller
than the
+ * largest weight is set to zero. This bounds the size of the discrete
distribution
+ * generating (DDG) tree by a function of the number of categories rather
than the
+ * dynamic range of the weights. Categories excluded by this cutoff have a
sampling
+ * probability below 2<sup>-53</sup> (approximately 1e-16) relative to the
most
+ * frequent category.
+ */
+ private static final int DEFAULT_ALPHA = 53;
/**
* Class to handle the edge case of observations in only one category.
@@ -299,9 +309,19 @@ public abstract class FastLoadedDiceRollerDiscreteSampler
* <p>Weights are converted to rational numbers {@code p / q} where {@code
q} is a power of 2.
* The numerators {@code p} are scaled to use a common denominator before
summing.
*
- * <p>All weights are used to create the sampler. Weights with a small
magnitude relative
- * to the largest weight can be excluded using the constructor method with
the
- * relative magnitude parameter {@code alpha} (see {@link
#of(UniformRandomProvider, double[], int)}).
+ * <p>Weights with a small magnitude relative to the largest weight are
excluded using
+ * a default relative magnitude parameter {@code alpha} of 53: any weight
+ * approximately 2<sup>53</sup> smaller than the largest weight is set to
zero.
+ * Categories excluded by this cutoff have a sampling probability below
+ * 2<sup>-53</sup> (approximately 1e-16) relative to the most frequent
category. The
+ * cutoff bounds the construction cost of the sampler by the number of
categories
+ * rather than the dynamic range of the weights. To create an
<em>exact</em> sampler
+ * from the full dynamic range of the weights use the constructor method
with a
+ * non-positive relative magnitude parameter {@code alpha} (see
+ * {@link #of(UniformRandomProvider, double[], int)}); note that the
construction cost
+ * is then a function of the ratio of the largest to the smallest non-zero
weight.
+ *
+ * <p>The default {@code alpha} of 53 was introduced in version 1.8.
*
* @param rng Generator of uniformly distributed random numbers.
* @param weights Weights of the discrete distribution.
@@ -313,7 +333,7 @@ public abstract class FastLoadedDiceRollerDiscreteSampler
*/
public static FastLoadedDiceRollerDiscreteSampler of(UniformRandomProvider
rng,
double[] weights) {
- return of(rng, weights, 0);
+ return of(rng, weights, DEFAULT_ALPHA);
}
/**
@@ -355,7 +375,8 @@ public abstract class FastLoadedDiceRollerDiscreteSampler
* <p><b>Implementation Note</b>
*
* <p>This method creates a sampler with <em>exact</em> samples from the
- * specified probability distribution. It is recommended to use this
method:
+ * specified probability distribution when {@code alpha} does not exclude
weights.
+ * It is recommended to use this method:
* <ul>
* <li>if the weights are computed, for example from a probability mass
function; or</li>
* <li>if the weights sum to an infinite value.</li>
diff --git
a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/FastLoadedDiceRollerDiscreteSamplerTest.java
b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/FastLoadedDiceRollerDiscreteSamplerTest.java
index 440dc30a..fd251388 100644
---
a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/FastLoadedDiceRollerDiscreteSamplerTest.java
+++
b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/FastLoadedDiceRollerDiscreteSamplerTest.java
@@ -46,7 +46,7 @@ class FastLoadedDiceRollerDiscreteSamplerTest {
}
/**
- * Creates the sampler.
+ * Creates the sampler using the default {@code alpha} parameter.
*
* @param weights Weights.
* @return the FLDR sampler
@@ -295,7 +295,9 @@ class FastLoadedDiceRollerDiscreteSamplerTest {
@ParameterizedTest
@MethodSource
void testSamplesWeights(double[] weights) {
- final SharedStateDiscreteSampler sampler = createSampler(weights);
+ // Must use alpha=0 to include all weights
+ final UniformRandomProvider rng = RandomAssert.createRNG();
+ final SharedStateDiscreteSampler sampler =
FastLoadedDiceRollerDiscreteSampler.of(rng, weights, 0);
final int numberOfSamples = 10000;
final long[] samples = new long[weights.length];
sampler.samples(numberOfSamples).forEach(x -> samples[x]++);
@@ -443,6 +445,23 @@ class FastLoadedDiceRollerDiscreteSamplerTest {
Assertions.assertFalse(Arrays.equals(s1, s3), "alpha+1 parameter
should not ignore the small weight");
}
+ /**
+ * Test the weights factory method applies a default relative magnitude
cutoff so
+ * that an extreme weight dynamic range does not amplify the construction
cost of
+ * the discrete distribution generating tree.
+ */
+ @Test
+ void testDefaultAlphaBoundsWeightDynamicRange() {
+ final double[] w1 = {1, 0.5, 0.5, 0};
+ final double[] w2 = {1, 0.5, 0.5, Double.MIN_VALUE};
+ final UniformRandomProvider[] rngs = RandomAssert.createRNG(2);
+ final UniformRandomProvider rng1 = rngs[0];
+ final UniformRandomProvider rng2 = rngs[1];
+ RandomAssert.assertProduceSameSequence(
+ FastLoadedDiceRollerDiscreteSampler.of(rng1, w1),
+ FastLoadedDiceRollerDiscreteSampler.of(rng2, w2));
+ }
+
static Stream<long[]> testSharedStateSampler() {
return Stream.of(
new long[] {42},
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index dc18e076..fb5ec0aa 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -56,6 +56,13 @@ If the output is not quite correct, check for invisible
trailing spaces!
<release version="1.8" date="TBD" description="
New features, updates and bug fixes (requires Java 8).
">
+ <action dev="aherbert" type="update" due-to="Security scan, Alex
Herbert" issue="RNG-196">
+ "FastLoadedDiceRollerDiscreteSampler": Introduce a defaut alpha to
exclude
+ practically unobservable categories with a probability of less than
2^-53
+ relative to the most frequent category. This is a behavioural change.
A full
+ discrete distribution generating (DDG) tree can be created using
alpha=0
+ in the overloaded factory constructor.
+ </action>
<action dev="aherbert" type="update" due-to="Security scan, Alex
Herbert">
"JDKRandom": Fail fast when restoring from a saved state if the
serialized bytes contains a proxy class descriptor.