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.simple;
18  
19  import java.util.Arrays;
20  import java.util.List;
21  import java.util.ArrayList;
22  import java.util.concurrent.Callable;
23  import java.io.IOException;
24  import java.io.ObjectOutputStream;
25  import java.io.ObjectInputStream;
26  import java.io.ByteArrayOutputStream;
27  import java.io.ByteArrayInputStream;
28  
29  import org.junit.Assert;
30  import org.junit.Test;
31  import org.junit.Assume;
32  import org.junit.Ignore;
33  import org.junit.runner.RunWith;
34  import org.junit.runners.Parameterized;
35  import org.junit.runners.Parameterized.Parameters;
36  
37  import org.apache.commons.rng.UniformRandomProvider;
38  import org.apache.commons.rng.RandomProviderState;
39  import org.apache.commons.rng.RestorableUniformRandomProvider;
40  import org.apache.commons.rng.core.RandomProviderDefaultState;
41  
42  /**
43   * Tests which all generators must pass.
44   */
45  @RunWith(value=Parameterized.class)
46  public class ProvidersCommonParametricTest {
47      /** RNG under test. */
48      private final UniformRandomProvider generator;
49      /** RNG specifier. */
50      private final RandomSource originalSource;
51      /** Seed (constructor's first parameter). */
52      private final Object originalSeed;
53      /** Constructor's additional parameters. */
54      private final Object[] originalArgs;
55  
56      /**
57       * Initializes generator instance.
58       *
59       * @param rng RNG to be tested.
60       */
61      public ProvidersCommonParametricTest(ProvidersList.Data data) {
62          originalSource = data.getSource();
63          originalSeed = data.getSeed();
64          originalArgs = data.getArgs();
65          generator = RandomSource.create(originalSource, originalSeed, originalArgs);
66      }
67  
68      @Parameters(name = "{index}: data={0}")
69      public static Iterable<ProvidersList.Data[]> getList() {
70          return ProvidersList.list();
71      }
72  
73      // Seeding tests.
74  
75      @Test(expected=UnsupportedOperationException.class)
76      public void testUnsupportedSeedType() {
77          final byte seed = 123;
78          RandomSource.create(originalSource, seed, originalArgs);
79      }
80  
81      @Test
82      public void testAllSeedTypes() {
83          final Integer intSeed = -12131415;
84          final Long longSeed = -1213141516171819L;
85          final int[] intArraySeed = new int[] { 0, 11, -22, 33, -44, 55, -66, 77, -88, 99 };
86          final long[] longArraySeed = new long[] { 11111L, -222222L, 3333333L, -44444444L };
87          final byte[] byteArraySeed = new byte[] { -128, -91, -45, -32, -1, 0, 11, 23, 54, 88, 127 };
88  
89          final Object[] seeds = new Object[] { null,
90                                                intSeed,
91                                                longSeed,
92                                                intArraySeed,
93                                                longArraySeed,
94                                                byteArraySeed };
95  
96          int nonNativeSeedCount = 0;
97          int seedCount = 0;
98          for (Object s : seeds) {
99              ++seedCount;
100             if (!(originalSource.isNativeSeed(s))) {
101                 ++nonNativeSeedCount;
102             }
103 
104             Assert.assertNotEquals(intSeed, originalSeed);
105             RandomSource.create(originalSource, s, originalArgs);
106         }
107 
108         Assert.assertEquals(6, seedCount);
109         Assert.assertEquals(5, nonNativeSeedCount);
110     }
111 
112     @Test
113     public void testEmptyIntArraySeed() {
114         final int[] empty = new int[0];
115         Assume.assumeTrue(originalSource.isNativeSeed(empty));
116 
117         // Exercise the default seeding procedure.
118         final UniformRandomProvider rng = RandomSource.create(originalSource, empty, originalArgs);
119         checkNextIntegerInRange(rng, 10, 20000);
120     }
121 
122     @Test
123     public void testEmptyLongArraySeed() {
124         final long[] empty = new long[0];
125         Assume.assumeTrue(originalSource.isNativeSeed(empty));
126 
127         // Exercise the default seeding procedure.
128         final UniformRandomProvider rng = RandomSource.create(originalSource, empty, originalArgs);
129         checkNextIntegerInRange(rng, 10, 10000);
130     }
131 
132     @Ignore@Test
133     public void testZeroIntArraySeed() {
134         // Exercise capacity to escape all "zero" state.
135         final int[] zero = new int[2000]; // Large enough to fill the entire state with zeroes.
136         final UniformRandomProvider rng = RandomSource.create(originalSource, zero, originalArgs);
137         checkNextIntegerInRange(rng, 10, 10000);
138     }
139 
140     @Ignore@Test
141     public void testZeroLongArraySeed() {
142         // Exercise capacity to escape all "zero" state.
143         final long[] zero = new long[2000]; // Large enough to fill the entire state with zeroes.
144         final UniformRandomProvider rng = RandomSource.create(originalSource, zero, originalArgs);
145         checkNextIntegerInRange(rng, 10, 10000);
146     }
147 
148     // State save and restore tests.
149 
150     @Test
151     public void testUnrestorable() {
152         // Create two generators of the same type as the one being tested.
153         final UniformRandomProvider rng1 = RandomSource.create(originalSource, originalSeed, originalArgs);
154         final UniformRandomProvider rng2 = RandomSource.unrestorable(RandomSource.create(originalSource, originalSeed, originalArgs));
155 
156         // Ensure that they generate the same values.
157         RandomAssert.assertProduceSameSequence(rng1, rng2);
158 
159         // Cast must work.
160         final RestorableUniformRandomProvider restorable = (RestorableUniformRandomProvider) rng1;
161         // Cast must fail.
162         try {
163             final RestorableUniformRandomProvider dummy = (RestorableUniformRandomProvider) rng2;
164             Assert.fail("Cast should have failed");
165         } catch (ClassCastException e) {
166             // Expected.
167         }
168     }
169 
170     @Test
171     public void testSerializingState()
172         throws IOException,
173                ClassNotFoundException {
174         // Large "n" is not necessary here as we only test the serialization.
175         final int n = 100;
176 
177         // Cast is OK: all instances created by this library inherit from "BaseProvider".
178         final RestorableUniformRandomProvider restorable = (RestorableUniformRandomProvider) generator;
179 
180         // Save.
181         final RandomProviderState stateOrig = restorable.saveState();
182         // Serialize.
183         ByteArrayOutputStream bos = new ByteArrayOutputStream();
184         ObjectOutputStream oos = new ObjectOutputStream(bos);
185         oos.writeObject(((RandomProviderDefaultState) stateOrig).getState());
186 
187         // Store some values.
188         final List<Number> listOrig = makeList(n);
189 
190         // Discard a few more.
191         final List<Number> listDiscard = makeList(n);
192         Assert.assertTrue(listDiscard.size() != 0);
193         Assert.assertFalse(listOrig.equals(listDiscard));
194 
195         // Retrieve from serialized stream.
196         ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
197         ObjectInputStream ois = new ObjectInputStream(bis);
198         final RandomProviderState stateNew = new RandomProviderDefaultState((byte[]) ois.readObject());
199 
200         Assert.assertTrue(stateOrig != stateNew);
201 
202         // Reset.
203         restorable.restoreState(stateNew);
204 
205         // Replay.
206         final List<Number> listReplay = makeList(n);
207         Assert.assertFalse(listOrig == listReplay);
208 
209         // Check that the serialized data recreated the orginal state.
210         Assert.assertTrue(listOrig.equals(listReplay));
211     }
212 
213     @Test
214     public void testUnrestorableToString() {
215         Assert.assertEquals(generator.toString(),
216                             RandomSource.unrestorable(generator).toString());
217     }
218 
219     ///// Support methods below.
220 
221 
222     // The methods
223     //   * makeList
224     //   * checkNextIntegerInRange
225     //   * checkNextInRange
226     // have been copied from "src/test" in module "commons-rng-core".
227     // TODO: check whether it is possible to have a single implementation.
228 
229     /**
230      * Populates a list with random numbers.
231      *
232      * @param n Loop counter.
233      * @return a list containing {@code 11 * n} random numbers.
234      */
235     private List<Number> makeList(int n) {
236         final List<Number> list = new ArrayList<Number>();
237 
238         for (int i = 0; i < n; i++) {
239             // Append 11 values.
240             list.add(generator.nextInt());
241             list.add(generator.nextInt(21));
242             list.add(generator.nextInt(436));
243             list.add(generator.nextLong());
244             list.add(generator.nextLong(157894));
245             list.add(generator.nextLong(5745833));
246             list.add(generator.nextFloat());
247             list.add(generator.nextFloat());
248             list.add(generator.nextDouble());
249             list.add(generator.nextDouble());
250             list.add(generator.nextDouble());
251         }
252 
253         return list;
254     }
255 
256     /**
257      * Tests uniformity of the distribution produced by {@code nextInt(int)}.
258      *
259      * @param rng Generator.
260      * @param max Upper bound.
261      * @param sampleSize Number of random values generated.
262      */
263     private void checkNextIntegerInRange(final UniformRandomProvider rng,
264                                          final int max,
265                                          int sampleSize) {
266         final Callable<Integer> nextMethod = new Callable<Integer>() {
267             @Override
268             public Integer call() throws Exception {
269                 return rng.nextInt(max);
270             }
271         };
272 
273         checkNextInRange(max, sampleSize, nextMethod);
274     }
275 
276     /**
277      * Tests uniformity of the distribution produced by the given
278      * {@code nextMethod}.
279      * It performs a chi-square test of homogeneity of the observed
280      * distribution with the expected uniform distribution.
281      * Tests are performed at the 1% level and an average failure rate
282      * higher than 2% causes the test case to fail.
283      *
284      * @param max Upper bound.
285      * @param nextMethod method to call.
286      * @param sampleSize Number of random values generated.
287      */
288     private <T extends Number> void checkNextInRange(T max,
289                                                      int sampleSize,
290                                                      Callable<T> nextMethod) {
291         final int numTests = 500;
292 
293         // Do not change (statistical test assumes that dof = 9).
294         final int numBins = 10; // dof = numBins - 1
295 
296         // Set up bins.
297         final long n = max.longValue();
298         final long[] binUpperBounds = new long[numBins];
299         final double step = n / (double) numBins;
300         for (int k = 0; k < numBins; k++) {
301             binUpperBounds[k] = (long) ((k + 1) * step);
302         }
303 
304         // Run the tests.
305         int numFailures = 0;
306 
307         final double[] expected = new double[numBins];
308         long previousUpperBound = 0;
309         for (int k = 0; k < numBins; k++) {
310             final long range = binUpperBounds[k] - previousUpperBound;
311             expected[k] = sampleSize * (range / (double) n);
312             previousUpperBound = binUpperBounds[k];
313         }
314 
315         final int[] observed = new int[numBins];
316         // Chi-square critical value with 9 degrees of freedom
317         // and 1% significance level.
318         final double chi2CriticalValue = 21.67;
319 
320         try {
321             for (int i = 0; i < numTests; i++) {
322                 Arrays.fill(observed, 0);
323                 for (int j = 0; j < sampleSize; j++) {
324                     final long value = nextMethod.call().longValue();
325                     Assert.assertTrue("Range", (value >= 0) && (value < n));
326 
327                     for (int k = 0; k < numBins; k++) {
328                         if (value < binUpperBounds[k]) {
329                             ++observed[k];
330                             break;
331                         }
332                     }
333                 }
334 
335                 // Compute chi-square.
336                 double chi2 = 0;
337                 for (int k = 0; k < numBins; k++) {
338                     final double diff = observed[k] - expected[k];
339                     chi2 += diff * diff / expected[k];
340                 }
341 
342                 // Statistics check.
343                 if (chi2 > chi2CriticalValue) {
344                     ++numFailures;
345                 }
346             }
347         } catch (Exception e) {
348             // Should never happen.
349             throw new RuntimeException("Unexpected", e);
350         }
351 
352         if ((double) numFailures / (double) numTests > 0.02) {
353             Assert.fail(generator + ": Too many failures for n = " + n +
354                         " (" + numFailures + " out of " + numTests + " tests failed)");
355         }
356     }
357 }