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.shape;
18  
19  import java.util.Arrays;
20  import org.apache.commons.math3.stat.inference.ChiSquareTest;
21  import org.apache.commons.rng.UniformRandomProvider;
22  import org.apache.commons.rng.sampling.RandomAssert;
23  import org.apache.commons.rng.sampling.UnitSphereSampler;
24  import org.junit.jupiter.api.Assertions;
25  import org.junit.jupiter.api.Test;
26  
27  /**
28   * Test for {@link LineSampler}.
29   */
30  class LineSamplerTest {
31      /**
32       * Test an unsupported dimension.
33       */
34      @Test
35      void testInvalidDimensionThrows() {
36          final UniformRandomProvider rng = RandomAssert.seededRNG();
37          Assertions.assertThrows(IllegalArgumentException.class,
38              () -> LineSampler.of(rng, new double[0], new double[0]));
39      }
40  
41      /**
42       * Test a dimension mismatch between vertices.
43       */
44      @Test
45      void testDimensionMismatchThrows() {
46          final UniformRandomProvider rng = RandomAssert.seededRNG();
47          final double[] c2 = new double[2];
48          final double[] c3 = new double[3];
49          for (double[][] c : new double[][][] {
50              {c2, c3},
51              {c3, c2},
52          }) {
53              Assertions.assertThrows(IllegalArgumentException.class,
54                  () -> LineSampler.of(rng, c[0], c[1]),
55                  () -> String.format("Did not detect dimension mismatch: %d,%d",
56                      c[0].length, c[1].length));
57          }
58      }
59  
60      /**
61       * Test non-finite vertices.
62       */
63      @Test
64      void testNonFiniteVertexCoordinates() {
65          final UniformRandomProvider rng = RandomAssert.seededRNG();
66          // A valid line
67          final double[][] c = new double[][] {
68              {0, 1, 2}, {-1, 2, 3}
69          };
70          Assertions.assertNotNull(LineSampler.of(rng, c[0],  c[1]));
71          final double[] bad = {Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NaN};
72          for (int i = 0; i < c.length; i++) {
73              final int ii = i;
74              for (int j = 0; j < c[0].length; j++) {
75                  final int jj = j;
76                  for (final double d : bad) {
77                      final double value = c[i][j];
78                      c[i][j] = d;
79                      Assertions.assertThrows(IllegalArgumentException.class,
80                          () -> LineSampler.of(rng, c[0], c[1]),
81                          () -> String.format("Did not detect non-finite coordinate: %d,%d = %s",
82                              ii, jj, d));
83                      c[i][j] = value;
84                  }
85              }
86          }
87      }
88  
89      /**
90       * Test a line with coordinates that are separated by more than
91       * {@link Double#MAX_VALUE} in 1D.
92       */
93      @Test
94      void testExtremeValueCoordinates1D() {
95          testExtremeValueCoordinates(1);
96      }
97  
98      /**
99       * Test a line with coordinates that are separated by more than
100      * {@link Double#MAX_VALUE} in 2D.
101      */
102     @Test
103     void testExtremeValueCoordinates2D() {
104         testExtremeValueCoordinates(2);
105     }
106 
107     /**
108      * Test a line with coordinates that are separated by more than
109      * {@link Double#MAX_VALUE} in 3D.
110      */
111     @Test
112     void testExtremeValueCoordinates3D() {
113         testExtremeValueCoordinates(3);
114     }
115 
116     /**
117      * Test a line with coordinates that are separated by more than
118      * {@link Double#MAX_VALUE} in 4D.
119      */
120     @Test
121     void testExtremeValueCoordinates4D() {
122         testExtremeValueCoordinates(4);
123     }
124 
125     /**
126      * Test a line with coordinates that are separated by more than
127      * {@link Double#MAX_VALUE}.
128      *
129      * @param dimension the dimension
130      */
131     private static void testExtremeValueCoordinates(int dimension) {
132         final double[][] c1 = new double[2][dimension];
133         final double[][] c2 = new double[2][dimension];
134         // Create a valid line that can be scaled
135         Arrays.fill(c1[0], -1);
136         Arrays.fill(c1[1], 1);
137         // Extremely large value for scaling. Use a power of 2 for exact scaling.
138         final double scale = 0x1.0p1023;
139         for (int i = 0; i < c1.length; i++) {
140             // Scale the second line
141             for (int j = 0; j < dimension; j++) {
142                 c2[i][j] = c1[i][j] * scale;
143             }
144         }
145         // Show the line is too big to compute vectors between points.
146         Assertions.assertEquals(Double.POSITIVE_INFINITY, c2[1][0] - c2[0][0],
147             "Expect vector b - a to be infinite in the x dimension");
148 
149         final LineSampler sampler1 = LineSampler.of(
150             RandomAssert.seededRNG(), c1[0], c1[1]);
151         final LineSampler sampler2 = LineSampler.of(
152             RandomAssert.seededRNG(), c2[0], c2[1]);
153 
154         for (int n = 0; n < 10; n++) {
155             final double[] a = sampler1.sample();
156             final double[] b = sampler2.sample();
157             for (int i = 0; i < a.length; i++) {
158                 a[i] *= scale;
159             }
160             Assertions.assertArrayEquals(a, b);
161         }
162     }
163 
164     /**
165      * Test the distribution of points in 1D.
166      */
167     @Test
168     void testDistribution1D() {
169         testDistributionND(1);
170     }
171 
172     /**
173      * Test the distribution of points in 2D.
174      */
175     @Test
176     void testDistribution2D() {
177         testDistributionND(2);
178     }
179 
180     /**
181      * Test the distribution of points in 3D.
182      */
183     @Test
184     void testDistribution3D() {
185         testDistributionND(3);
186     }
187 
188     /**
189      * Test the distribution of points in 4D.
190      */
191     @Test
192     void testDistribution4D() {
193         testDistributionND(4);
194     }
195 
196     /**
197      * Test the distribution of points in N dimensions. The output coordinates
198      * should be uniform in the line.
199      *
200      * @param dimension the dimension
201      */
202     private static void testDistributionND(int dimension) {
203         final UniformRandomProvider rng = RandomAssert.createRNG();
204 
205         double[] a;
206         double[] b;
207         if (dimension == 1) {
208             a = new double[] {rng.nextDouble()};
209             b = new double[] {-rng.nextDouble()};
210         } else {
211             final UnitSphereSampler sphere = UnitSphereSampler.of(rng, dimension);
212             a = sphere.sample();
213             b = sphere.sample();
214         }
215 
216         // To test uniformity on the line all fractional lengths along each dimension
217         // should be the same constant:
218         // x - a
219         // ----- = C
220         // b - a
221         // This should be uniformly distributed in the range [0, 1].
222         // Pre-compute scaling:
223         final double[] scale = new double[dimension];
224         for (int i = 0; i < dimension; i++) {
225             scale[i] = 1.0 / (b[i] - a[i]);
226         }
227 
228         // Assign bins
229         final int bins = 100;
230         final int samplesPerBin = 20;
231 
232         // Expect a uniform distribution
233         final double[] expected = new double[bins];
234         Arrays.fill(expected, 1.0 / bins);
235 
236         // Increase the loops and use a null seed (i.e. randomly generated) to verify robustness
237         final LineSampler sampler = LineSampler.of(rng, a, b);
238         final int samples = expected.length * samplesPerBin;
239         for (int n = 0; n < 1; n++) {
240             // Assign each coordinate to a region inside the line
241             final long[] observed = new long[expected.length];
242             for (int i = 0; i < samples; i++) {
243                 final double[] x = sampler.sample();
244                 Assertions.assertEquals(dimension, x.length);
245                 final double c = (x[0] - a[0]) * scale[0];
246                 Assertions.assertTrue(c >= 0.0 && c <= 1.0, "Not uniformly distributed");
247                 for (int j = 1; j < dimension; j++) {
248                     Assertions.assertEquals(c, (x[j] - a[j]) * scale[j], 1e-14, "Not on the line");
249                 }
250                 // Assign the uniform deviate to a bin. Assumes c != 1.0.
251                 observed[(int) (c * bins)]++;
252             }
253             final double p = new ChiSquareTest().chiSquareTest(expected, observed);
254             Assertions.assertFalse(p < 0.001, () -> "p-value too small: " + p);
255         }
256     }
257 
258     /**
259      * Test the SharedStateSampler implementation for 1D.
260      */
261     @Test
262     void testSharedStateSampler1D() {
263         testSharedStateSampler(1);
264     }
265 
266     /**
267      * Test the SharedStateSampler implementation for 2D.
268      */
269     @Test
270     void testSharedStateSampler2D() {
271         testSharedStateSampler(2);
272     }
273 
274     /**
275      * Test the SharedStateSampler implementation for 3D.
276      */
277     @Test
278     void testSharedStateSampler3D() {
279         testSharedStateSampler(3);
280     }
281 
282     /**
283      * Test the SharedStateSampler implementation for 4D.
284      */
285     @Test
286     void testSharedStateSampler4D() {
287         testSharedStateSampler(4);
288     }
289 
290     /**
291      * Test the SharedStateSampler implementation for the given dimension.
292      */
293     private static void testSharedStateSampler(int dimension) {
294         final UniformRandomProvider rng1 = RandomAssert.seededRNG();
295         final UniformRandomProvider rng2 = RandomAssert.seededRNG();
296         final double[] c1 = createCoordinate(1, dimension);
297         final double[] c2 = createCoordinate(2, dimension);
298         final LineSampler sampler1 = LineSampler.of(rng1, c1, c2);
299         final LineSampler sampler2 = sampler1.withUniformRandomProvider(rng2);
300         RandomAssert.assertProduceSameSequence(sampler1, sampler2);
301     }
302 
303     /**
304      * Test the input vectors are copied and not used by reference for 1D.
305      */
306     @Test
307     void testChangedInputCoordinates1D() {
308         testChangedInputCoordinates(1);
309     }
310 
311     /**
312      * Test the input vectors are copied and not used by reference for 2D.
313      */
314     @Test
315     void testChangedInputCoordinates2D() {
316         testChangedInputCoordinates(2);
317     }
318 
319     /**
320      * Test the input vectors are copied and not used by reference for 3D.
321      */
322     @Test
323     void testChangedInputCoordinates3D() {
324         testChangedInputCoordinates(3);
325     }
326 
327     /**
328      * Test the input vectors are copied and not used by reference for 4D.
329      */
330     @Test
331     void testChangedInputCoordinates4D() {
332         testChangedInputCoordinates(4);
333     }
334 
335     /**
336      * Test the input vectors are copied and not used by reference for the given
337      * dimension.
338      *
339      * @param dimension the dimension
340      */
341     private static void testChangedInputCoordinates(int dimension) {
342         final UniformRandomProvider rng1 = RandomAssert.seededRNG();
343         final UniformRandomProvider rng2 = RandomAssert.seededRNG();
344         final double[] c1 = createCoordinate(1, dimension);
345         final double[] c2 = createCoordinate(2, dimension);
346         final LineSampler sampler1 = LineSampler.of(rng1, c1, c2);
347         // Check the input vectors are copied and not used by reference.
348         // Change them in place and create a new sampler. It should have different output
349         // translated by the offset.
350         final double offset = 10;
351         for (int i = 0; i < dimension; i++) {
352             c1[i] += offset;
353             c2[i] += offset;
354         }
355         final LineSampler sampler2 = LineSampler.of(rng2, c1, c2);
356         for (int n = 0; n < 3; n++) {
357             final double[] s1 = sampler1.sample();
358             final double[] s2 = sampler2.sample();
359             Assertions.assertEquals(s1.length, s2.length);
360             Assertions.assertFalse(Arrays.equals(s1, s2),
361                 "First sampler has used the vertices by reference");
362             for (int i = 0; i < dimension; i++) {
363                 Assertions.assertEquals(s1[i] + offset, s2[i], 1e-10);
364             }
365         }
366     }
367 
368     /**
369      * Creates the coordinate of length specified by the dimension filled with
370      * the given value and the dimension index: x + i.
371      *
372      * @param x the value for index 0
373      * @param dimension the dimension
374      * @return the coordinate
375      */
376     private static double[] createCoordinate(double x, int dimension) {
377         final double[] coord = new double[dimension];
378         for (int i = 0; i < dimension; i++) {
379             coord[0] = x + i;
380         }
381         return coord;
382     }
383 }