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