View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.rng.sampling;
18  
19  import org.junit.Assert;
20  import org.junit.Test;
21  import org.apache.commons.rng.simple.RandomSource;
22  import org.apache.commons.rng.UniformRandomProvider;
23  import org.apache.commons.rng.core.source64.SplitMix64;
24  
25  /**
26   * Test for {@link UnitSphereSampler}.
27   */
28  public class UnitSphereSamplerTest {
29      @Test(expected = IllegalArgumentException.class)
30      public void testPrecondition() {
31          new UnitSphereSampler(0, null);
32      }
33  
34      /**
35       * Test the distribution of points in two dimensions.
36       */
37      @Test
38      public void testDistribution2D() {
39          UniformRandomProvider rng = RandomSource.create(RandomSource.XOR_SHIFT_1024_S, 17399225432L);
40          UnitSphereSampler generator = new UnitSphereSampler(2, rng);
41  
42          // In 2D, angles with a given vector should be uniformly distributed.
43          final int[] angleBuckets = new int[100];
44          final int steps = 1000000;
45          for (int i = 0; i < steps; ++i) {
46              final double[] v = generator.nextVector();
47              Assert.assertEquals(2, v.length);
48              Assert.assertEquals(1, length(v), 1e-10);
49              // Compute angle formed with vector (1, 0)?
50              // Cosine of angle is their dot product, because both are unit length.
51              // Dot product here is just the first element of the vector by construction.
52              final double angle = Math.acos(v[0]);
53              final int bucket = (int) (angle / Math.PI * angleBuckets.length);
54              ++angleBuckets[bucket];
55          }
56  
57          // Simplistic test for roughly even distribution.
58          final int expectedBucketSize = steps / angleBuckets.length;
59          for (int bucket : angleBuckets) {
60              Assert.assertTrue("Bucket count " + bucket + " vs expected " + expectedBucketSize,
61                                Math.abs(expectedBucketSize - bucket) < 350);
62          }
63      }
64  
65      /** Cf. RNG-55. */
66      @Test(expected = StackOverflowError.class)
67      public void testBadProvider1() {
68          final UniformRandomProvider bad = new UniformRandomProvider() {
69                  // CHECKSTYLE: stop all
70                  public long nextLong(long n) { return 0; }
71                  public long nextLong() { return 0; }
72                  public int nextInt(int n) { return 0; }
73                  public int nextInt() { return 0; }
74                  public float nextFloat() { return 0; }
75                  public double nextDouble() { return 0;}
76                  public void nextBytes(byte[] bytes, int start, int len) {}
77                  public void nextBytes(byte[] bytes) {}
78                  public boolean nextBoolean() { return false; }
79                  // CHECKSTYLE: resume all
80              };
81  
82          new UnitSphereSampler(1, bad).nextVector();
83      }
84  
85      /** Cf. RNG-55. */
86      @Test
87      public void testBadProvider1ThenGoodProvider() {
88          // Create a provider that will create a bad first sample but then recover.
89          // This checks recursion will return a good value.
90          final UniformRandomProvider bad = new SplitMix64(0L) {
91                  private int count;
92                  // CHECKSTYLE: stop all
93                  public long nextLong() { return (count++ == 0) ? 0 : super.nextLong(); }
94                  public double nextDouble() { return (count++ == 0) ? 0 : super.nextDouble(); }
95                  // CHECKSTYLE: resume all
96              };
97  
98          final double[] vector = new UnitSphereSampler(1, bad).nextVector();
99          Assert.assertEquals(1, vector.length);
100     }
101 
102     /**
103      * Test to demonstrate that using floating-point equality of the norm squared with
104      * zero is valid. Any norm squared after zero should produce a valid scaling factor.
105      */
106     @Test
107     public void testNextNormSquaredAfterZeroIsValid() {
108         // The sampler explicitly handles length == 0 using recursion.
109         // Anything above zero should be valid.
110         final double normSq = Math.nextAfter(0, 1);
111         // Map to the scaling factor
112         final double f = 1 / Math.sqrt(normSq);
113         // As long as this is finite positive then the sampler is valid
114         Assert.assertTrue(f > 0 && f <= Double.MAX_VALUE);
115     }
116 
117     /**
118      * Test the SharedStateSampler implementation.
119      */
120     @Test
121     public void testSharedStateSampler() {
122         final UniformRandomProvider rng1 = RandomSource.create(RandomSource.SPLIT_MIX_64, 0L);
123         final UniformRandomProvider rng2 = RandomSource.create(RandomSource.SPLIT_MIX_64, 0L);
124         final int n = 3;
125         final UnitSphereSampler sampler1 =
126             new UnitSphereSampler(n, rng1);
127         final UnitSphereSampler sampler2 = sampler1.withUniformRandomProvider(rng2);
128         RandomAssert.assertProduceSameSequence(
129             new RandomAssert.Sampler<double[]>() {
130                 @Override
131                 public double[] sample() {
132                     return sampler1.nextVector();
133                 }
134             },
135             new RandomAssert.Sampler<double[]>() {
136                 @Override
137                 public double[] sample() {
138                     return sampler2.nextVector();
139                 }
140             });
141     }
142 
143     /**
144      * @return the length (L2-norm) of given vector.
145      */
146     private static double length(double[] vector) {
147         double total = 0;
148         for (double d : vector) {
149             total += d * d;
150         }
151         return Math.sqrt(total);
152     }
153 }