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    *      https://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  
18  /*
19   * This is not the original file distributed by the Apache Software Foundation
20   * It has been modified by the Hipparchus project
21   */
22  package org.hipparchus.random;
23  
24  import static org.junit.Assert.assertTrue;
25  import static org.junit.Assert.fail;
26  
27  import java.util.Arrays;
28  
29  import org.hipparchus.UnitTestUtils;
30  import org.hipparchus.exception.MathIllegalArgumentException;
31  import org.hipparchus.util.FastMath;
32  import org.junit.Assert;
33  import org.junit.Before;
34  import org.junit.Test;
35  
36  /**
37   * Base class for RandomGenerator tests.
38   * <p>
39   * Tests RandomGenerator methods directly and also executes RandomDataTest
40   * test cases against a RandomDataImpl created using the provided generator.
41   * <p>
42   * RandomGenerator test classes should extend this class, implementing
43   * makeGenerator() to provide a concrete generator to test. The generator
44   * returned by makeGenerator should be seeded with a fixed seed.
45   */
46  public abstract class RandomGeneratorAbstractTest extends RandomDataGeneratorTest {
47  
48      /** RandomGenerator under test */
49      protected RandomGenerator generator;
50  
51      /**
52       * Override this method in subclasses to provide a concrete generator to test.
53       * Return a generator seeded with a fixed seed.
54       */
55      protected abstract RandomGenerator makeGenerator();
56  
57      /**
58       * Initialize generator and randomData instance in superclass.
59       */
60      public RandomGeneratorAbstractTest() {
61          generator = makeGenerator();
62          randomData = RandomDataGenerator.of(generator);
63      }
64  
65      /**
66       * Set a fixed seed for the tests
67       */
68      @Before
69      public void setUp() {
70          generator = makeGenerator();
71      }
72  
73      /**
74       * Tests uniformity of nextInt(int) distribution by generating 1000
75       * samples for each of 10 test values and for each sample performing
76       * a chi-square test of homogeneity of the observed distribution with
77       * the expected uniform distribution.  Tests are performed at the .01
78       * level and an average failure rate higher than 2% (i.e. more than 20
79       * null hypothesis rejections) causes the test case to fail.
80       *
81       * All random values are generated using the generator instance used by
82       * other tests and the generator is not reseeded, so this is a fixed seed
83       * test.
84       */
85      @Test
86      public void testNextIntDirect() {
87          // Set up test values - end of the array filled randomly
88          int[] testValues = new int[] {4, 10, 12, 32, 100, 10000, 0, 0, 0, 0};
89          for (int i = 6; i < 10; i++) {
90              final int val = generator.nextInt();
91              testValues[i] = val < 0 ? -val : val + 1;
92          }
93  
94          final int numTests = 1000;
95          for (int i = 0; i < testValues.length; i++) {
96              final int n = testValues[i];
97              // Set up bins
98              int[] binUpperBounds;
99              if (n < 32) {
100                 binUpperBounds = new int[n];
101                 for (int k = 0; k < n; k++) {
102                     binUpperBounds[k] = k;
103                 }
104             } else {
105                 binUpperBounds = new int[10];
106                 final int step = n / 10;
107                 for (int k = 0; k < 9; k++) {
108                     binUpperBounds[k] = (k + 1) * step;
109                 }
110                 binUpperBounds[9] = n - 1;
111             }
112             // Run the tests
113             int numFailures = 0;
114             final int binCount = binUpperBounds.length;
115             final long[] observed = new long[binCount];
116             final double[] expected = new double[binCount];
117             expected[0] = binUpperBounds[0] == 0 ? (double) smallSampleSize / (double) n :
118                 (double) ((binUpperBounds[0] + 1) * smallSampleSize) / (double) n;
119             for (int k = 1; k < binCount; k++) {
120                 expected[k] = (double) smallSampleSize *
121                 (double) (binUpperBounds[k] - binUpperBounds[k - 1]) / n;
122             }
123             for (int j = 0; j < numTests; j++) {
124                 Arrays.fill(observed, 0);
125                 for (int k = 0; k < smallSampleSize; k++) {
126                     final int value = generator.nextInt(n);
127                     assertTrue("nextInt range",(value >= 0) && (value < n));
128                     for (int l = 0; l < binCount; l++) {
129                         if (binUpperBounds[l] >= value) {
130                             observed[l]++;
131                             break;
132                         }
133                     }
134                 }
135                 if (UnitTestUtils.chiSquareTest(expected, observed) < 0.01) {
136                     numFailures++;
137                 }
138             }
139             if ((double) numFailures / (double) numTests > 0.02) {
140                 fail("Too many failures for n = " + n +
141                      " " + numFailures + " out of " + numTests + " tests failed.");
142             }
143         }
144     }
145 
146     @Test
147     public void testNextLongDirect() {
148         long q1 = Long.MAX_VALUE/4;
149         long q2 = 2 *  q1;
150         long q3 = 3 * q1;
151 
152         long[] observed = new long[4];
153         long val = 0;
154         for (int i = 0; i < smallSampleSize; i++) {
155             val = generator.nextLong();
156             val = val < 0 ? -val : val;
157             if (val < q1) {
158                observed[0] = ++observed[0];
159             } else if (val < q2) {
160                observed[1] = ++observed[1];
161             } else if (val < q3) {
162                observed[2] = ++observed[2];
163             } else {
164                observed[3] = ++observed[3];
165             }
166         }
167 
168         /* Use ChiSquare dist with df = 4-1 = 3, alpha = .001
169          * Change to 11.34 for alpha = .01
170          */
171         assertTrue("chi-square test -- will fail about 1 in 1000 times",
172                    UnitTestUtils.chiSquare(expected,observed) < 16.27);
173     }
174 
175     @Test
176     public void testNextBooleanDirect() {
177         long halfSampleSize = smallSampleSize / 2;
178         double[] expected = {halfSampleSize, halfSampleSize};
179         long[] observed = new long[2];
180         for (int i=0; i<smallSampleSize; i++) {
181             if (generator.nextBoolean()) {
182                 observed[0]++;
183             } else {
184                 observed[1]++;
185             }
186         }
187         /* Use ChiSquare dist with df = 2-1 = 1, alpha = .001
188          * Change to 6.635 for alpha = .01
189          */
190         assertTrue("chi-square test -- will fail about 1 in 1000 times",
191                    UnitTestUtils.chiSquare(expected,observed) < 10.828);
192     }
193 
194     @Test
195     public void testNextFloatDirect() {
196         long[] observed = new long[4];
197         for (int i=0; i<smallSampleSize; i++) {
198             float val = generator.nextFloat();
199             if (val < 0.25) {
200                 observed[0] = ++observed[0];
201             } else if (val < 0.5) {
202                 observed[1] = ++observed[1];
203             } else if (val < 0.75) {
204                 observed[2] = ++observed[2];
205             } else {
206                 observed[3] = ++observed[3];
207             }
208         }
209 
210         /* Use ChiSquare dist with df = 4-1 = 3, alpha = .001
211          * Change to 11.34 for alpha = .01
212          */
213         assertTrue("chi-square test -- will fail about 1 in 1000 times",
214                    UnitTestUtils.chiSquare(expected,observed) < 16.27);
215     }
216 
217     @Test
218     public void testNextDouble() {
219         final double[] sample = new double[10000];
220         final double[] expected = new double[100];
221         final long[] observed = new long[100];
222         Arrays.fill(expected, 100d);
223         for (int i = 0; i < sample.length; i++) {
224             sample[i] = generator.nextDouble();
225             int j = 0;
226             while (sample[i] < j / 100) {
227                 j++;
228             }
229             observed[j] = observed[j]++;
230         }
231         UnitTestUtils.assertChiSquareAccept(expected, observed, 0.01);
232     }
233 
234 
235     @Test(expected=MathIllegalArgumentException.class)
236     public void testNextIntPrecondition1() {
237         generator.nextInt(-1);
238     }
239 
240     @Test(expected=MathIllegalArgumentException.class)
241     public void testNextIntPrecondition2() {
242         generator.nextInt(0);
243     }
244 
245     @Test
246     public void testNextInt2() {
247         int walk = 0;
248         final int N = 10000;
249         for (int k = 0; k < N; ++k) {
250            if (generator.nextInt() >= 0) {
251                ++walk;
252            } else {
253                --walk;
254            }
255         }
256         assertTrue("Walked too far astray: " + walk + "\nNote: This " +
257                    "test will fail randomly about 1 in 100 times.",
258                    FastMath.abs(walk) < FastMath.sqrt(N) * 2.576);
259     }
260 
261     @Test
262     public void testNextLong2() {
263         int walk = 0;
264         final int N = 1000;
265         for (int k = 0; k < N; ++k) {
266            if (generator.nextLong() >= 0) {
267                ++walk;
268            } else {
269                --walk;
270            }
271         }
272         assertTrue("Walked too far astray: " + walk + "\nNote: This " +
273                    "test will fail randomly about 1 in 100 times.",
274                    FastMath.abs(walk) < FastMath.sqrt(N) * 2.576);
275     }
276 
277     @Test
278     public void testNextBoolean2() {
279         int walk = 0;
280         final int N = 10000;
281         for (int k = 0; k < N; ++k) {
282            if (generator.nextBoolean()) {
283                ++walk;
284            } else {
285                --walk;
286            }
287         }
288         assertTrue("Walked too far astray: " + walk + "\nNote: This " +
289                    "test will fail randomly about 1 in 100 times.",
290                    FastMath.abs(walk) < FastMath.sqrt(N) * 2.576);
291     }
292 
293     @Test
294     public void testNextBytes() {
295         long[] count = new long[256];
296         byte[] bytes = new byte[10];
297         double[] expected = new double[256];
298         final int sampleSize = 100000;
299 
300         for (int i = 0; i < 256; i++) {
301             expected[i] = (double) sampleSize / 265f;
302         }
303 
304         for (int k = 0; k < sampleSize; ++k) {
305            generator.nextBytes(bytes);
306            for (byte b : bytes) {
307                ++count[b + 128];
308            }
309         }
310 
311         UnitTestUtils.assertChiSquareAccept(expected, count, 0.001);
312     }
313 
314     // MATH-1300
315     @Test
316     public void testNextBytesChunks() {
317         final int[] chunkSizes = { 4, 8, 12, 16 };
318         final int[] chunks = { 1, 2, 3, 4, 5 };
319         for (int chunkSize : chunkSizes) {
320             for (int numChunks : chunks) {
321                 checkNextBytesChunks(chunkSize, numChunks);
322             }
323         }
324     }
325 
326     @Test
327     public void testSeeding() {
328         // makeGenerator initializes with fixed seed
329         RandomGenerator gen = makeGenerator();
330         RandomGenerator gen1 = makeGenerator();
331         checkSameSequence(gen, gen1);
332         // reseed, but recreate the second one
333         // verifies MATH-723
334         gen.setSeed(100);
335         gen1 = makeGenerator();
336         gen1.setSeed(100);
337         checkSameSequence(gen, gen1);
338     }
339 
340     private void checkSameSequence(RandomGenerator gen1, RandomGenerator gen2) {
341         final int len = 11;  // Needs to be an odd number to check MATH-723
342         final double[][] values = new double[2][len];
343         for (int i = 0; i < len; i++) {
344             values[0][i] = gen1.nextDouble();
345         }
346         for (int i = 0; i < len; i++) {
347             values[1][i] = gen2.nextDouble();
348         }
349         assertTrue(Arrays.equals(values[0], values[1]));
350         for (int i = 0; i < len; i++) {
351             values[0][i] = gen1.nextFloat();
352         }
353         for (int i = 0; i < len; i++) {
354             values[1][i] = gen2.nextFloat();
355         }
356         assertTrue(Arrays.equals(values[0], values[1]));
357         for (int i = 0; i < len; i++) {
358             values[0][i] = gen1.nextInt();
359         }
360         for (int i = 0; i < len; i++) {
361             values[1][i] = gen2.nextInt();
362         }
363         assertTrue(Arrays.equals(values[0], values[1]));
364         for (int i = 0; i < len; i++) {
365             values[0][i] = gen1.nextLong();
366         }
367         for (int i = 0; i < len; i++) {
368             values[1][i] = gen2.nextLong();
369         }
370         assertTrue(Arrays.equals(values[0], values[1]));
371         for (int i = 0; i < len; i++) {
372             values[0][i] = gen1.nextInt(len);
373         }
374         for (int i = 0; i < len; i++) {
375             values[1][i] = gen2.nextInt(len);
376         }
377         assertTrue(Arrays.equals(values[0], values[1]));
378         for (int i = 0; i < len; i++) {
379             values[0][i] = gen1.nextBoolean() ? 1 : 0;
380         }
381         for (int i = 0; i < len; i++) {
382             values[1][i] = gen2.nextBoolean() ? 1 : 0;
383         }
384         assertTrue(Arrays.equals(values[0], values[1]));
385         for (int i = 0; i < len; i++) {
386             values[0][i] = gen1.nextGaussian();
387         }
388         for (int i = 0; i < len; i++) {
389             values[1][i] = gen2.nextGaussian();
390         }
391         assertTrue(Arrays.equals(values[0], values[1]));
392     }
393 
394     // MATH-1300
395     private void checkNextBytesChunks(int chunkSize,
396                                       int numChunks) {
397         final RandomGenerator rg = makeGenerator();
398         final long seed = 1234567L;
399 
400         final byte[] b1 = new byte[chunkSize * numChunks];
401         final byte[] b2 = new byte[chunkSize];
402 
403         // Generate the chunks in a single call.
404         rg.setSeed(seed);
405         rg.nextBytes(b1);
406 
407         // Reset.
408         rg.setSeed(seed);
409         // Generate the chunks in consecutive calls.
410         for (int i = 0; i < numChunks; i++) {
411             rg.nextBytes(b2);
412         }
413 
414         // Store last 128 bytes chunk of b1 into b3.
415         final byte[] b3 = new byte[chunkSize];
416         System.arraycopy(b1, b1.length - b3.length, b3, 0, b3.length);
417 
418         // Sequence of calls must be the same.
419         Assert.assertArrayEquals("chunkSize=" + chunkSize + " numChunks=" + numChunks,
420                                  b2, b3);
421     }
422 
423     @Override
424     public void testNextZipf() {
425         // Skip this test for the individual generators
426     }
427 }