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 f4023d5 RNG-148: Detect and discard infinite length vectors
f4023d5 is described below
commit f4023d55bc9336036e5faf10e69ba391257a64d4
Author: Alex Herbert <[email protected]>
AuthorDate: Wed Jun 30 09:50:18 2021 +0100
RNG-148: Detect and discard infinite length vectors
---
.../commons/rng/sampling/UnitSphereSampler.java | 33 +++++++--
.../rng/sampling/UnitSphereSamplerTest.java | 78 ++++++++++++++--------
src/changes/changes.xml | 3 +
3 files changed, 82 insertions(+), 32 deletions(-)
diff --git
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/UnitSphereSampler.java
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/UnitSphereSampler.java
index 76db7b0..e9b2a2a 100644
---
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/UnitSphereSampler.java
+++
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/UnitSphereSampler.java
@@ -104,8 +104,8 @@ public class UnitSphereSampler implements
SharedStateObjectSampler<double[]> {
final double y = sampler.sample();
final double sum = x * x + y * y;
- if (sum == 0) {
- // Zero-norm vector is discarded.
+ if (isInvalidSumForNormalization(sum)) {
+ // Invalid vector is discarded.
return sample();
}
@@ -141,8 +141,8 @@ public class UnitSphereSampler implements
SharedStateObjectSampler<double[]> {
final double z = sampler.sample();
final double sum = x * x + y * y + z * z;
- if (sum == 0) {
- // Zero-norm vector is discarded.
+ if (isInvalidSumForNormalization(sum)) {
+ // Invalid vector is discarded.
return sample();
}
@@ -187,8 +187,8 @@ public class UnitSphereSampler implements
SharedStateObjectSampler<double[]> {
sum += x * x;
}
- if (sum == 0) {
- // Zero-norm vector is discarded.
+ if (isInvalidSumForNormalization(sum)) {
+ // Invalid vector is discarded.
// Using recursion as it is highly unlikely to generate more
// than a few such vectors. It also protects against infinite
// loop (in case a buggy generator is used), by eventually
@@ -284,4 +284,25 @@ public class UnitSphereSampler implements
SharedStateObjectSampler<double[]> {
}
return new UnitSphereSamplerND(dimension, rng);
}
+
+ /**
+ * Returns true if the sum of squared components of a vector is invalid for
+ * normalization.
+ *
+ * <p>This is true for any sum where the factor {@code f = 1.0 / sqrt(sum)}
+ * cannot be used to create a unit length vector by multiplication. The sum
+ * is invalid if:
+ *
+ * <ul>
+ * <li>{@code sum = 0} then {@code f = infinity}
+ * <li>{@code sum = infinity} then {@code f = 0}
+ * </ul>
+ *
+ * @param sum Sum of squared components of a vector
+ * @return true if invalid for normalisation
+ */
+ private static boolean isInvalidSumForNormalization(double sum) {
+ // Note: Deliberate floating-point comparison with zero
+ return sum == 0 || sum == Double.POSITIVE_INFINITY;
+ }
}
diff --git
a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/UnitSphereSamplerTest.java
b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/UnitSphereSamplerTest.java
index 005d548..9733858 100644
---
a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/UnitSphereSamplerTest.java
+++
b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/UnitSphereSamplerTest.java
@@ -388,46 +388,72 @@ public class UnitSphereSamplerTest {
}
/**
- * Test the edge case where the normalisation sum to divide by is zero for
2D.
- */
- @Test
- public void testInvalidInverseNormalisation2D() {
- testInvalidInverseNormalisationND(2);
- }
-
- /**
- * Test the edge case where the normalisation sum to divide by is zero for
3D.
+ * Test the edge case where the normalisation sum to divide by is zero.
*/
@Test
- public void testInvalidInverseNormalisation3D() {
- testInvalidInverseNormalisationND(3);
+ public void testInvalidInverseNormalisationWithZeroLength() {
+ for (int dim = 2; dim <= 4; dim++) {
+ testInvalidInverseNormalisationND(dim, true);
+ }
}
/**
- * Test the edge case where the normalisation sum to divide by is zero for
4D.
+ * Test the edge case where the normalisation sum to divide by is infinite.
*/
@Test
- public void testInvalidInverseNormalisation4D() {
- testInvalidInverseNormalisationND(4);
+ public void testInvalidInverseNormalisationWithInfiniteLength() {
+ for (int dim = 2; dim <= 4; dim++) {
+ testInvalidInverseNormalisationND(dim, false);
+ }
}
/**
- * Test the edge case where the normalisation sum to divide by is zero.
- * This test requires generation of Gaussian samples with the value 0.
- * See RNG-55.
+ * Test the edge case where the normalisation sum to divide by is zero or
infinite. This
+ * test requires generation of Gaussian samples with the value 0 or
infinity. See RNG-55.
+ *
+ * @param dimension the dimension
+ * @param zeroSum true if the sum for the first vector should be zero
*/
- private static void testInvalidInverseNormalisationND(final int dimension)
{
+ private static void testInvalidInverseNormalisationND(final int dimension,
boolean zeroSum) {
// Create a provider that will create a bad first sample but then
recover.
// This checks recursion will return a good value.
- final UniformRandomProvider bad = new SplitMix64(0x1a2b3cL) {
- private int count = -2 * dimension;
- @Override
- public long nextLong() {
- // Return enough zeros to create Gaussian samples of zero for
all coordinates.
- return count++ < 0 ? 0 : super.nextLong();
- }
- };
+ // This sampler will createbvalues that manipulate the underlying
Gaussian sampler.
+ UniformRandomProvider bad;
+ if (zeroSum) {
+ // Create Gaussian samples of zero
+ bad = new SplitMix64(0x1a2b3cL) {
+ private int count = -2 * dimension;
+
+ @Override
+ public long nextLong() {
+ // Return enough zeros to create Gaussian samples of zero
for all coordinates.
+ return count++ < 0 ? 0 : super.nextLong();
+ }
+ };
+ } else {
+ // Create a Gaussian sample of infinity.
+ // This only requires 1 infinite value to create an infinite
length vector.
+ // Assumes the ZigguratNormalizedGaussianSampler.
+ // To create infinity requires a very large long value with the
lowest 7 bits as 0,
+ // then two doubles of zero.
+ bad = new SplitMix64(0x1a2b3cL) {
+ private int lcount = -1;
+ private int dcount = -2;
+
+ @Override
+ public long nextLong() {
+ return lcount++ < 0 ?
+ (-1L << 7) & Long.MAX_VALUE :
+ super.nextLong();
+ }
+
+ @Override
+ public double nextDouble() {
+ return dcount++ < 0 ? 0 : super.nextDouble();
+ }
+ };
+ }
final double[] vector = UnitSphereSampler.of(dimension, bad).sample();
Assert.assertEquals(dimension, vector.length);
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index f74e736..096844b 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -77,6 +77,9 @@ re-run tests that fail, and pass the build if they succeed
within the allotted number of reruns (the test will be marked
as 'flaky' in the report).
">
+ <action dev="aherbert" type="fix" issue="148">
+ "UnitSphereSampler": Detect and discard infinite length vectors.
+ </action>
<action dev="aherbert" type="add" issue="147">
New "LevySampler" to sample from a Levy distribution.
</action>