| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.commons.rng.simple; |
| |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.ArrayList; |
| import java.util.concurrent.Callable; |
| import java.io.IOException; |
| import java.io.ObjectOutputStream; |
| import java.io.ObjectInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.ByteArrayInputStream; |
| |
| import org.junit.Assert; |
| import org.junit.Test; |
| import org.junit.Assume; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| import org.junit.runners.Parameterized.Parameters; |
| |
| import org.apache.commons.rng.UniformRandomProvider; |
| import org.apache.commons.rng.JumpableUniformRandomProvider; |
| import org.apache.commons.rng.LongJumpableUniformRandomProvider; |
| import org.apache.commons.rng.RandomProviderState; |
| import org.apache.commons.rng.RestorableUniformRandomProvider; |
| import org.apache.commons.rng.core.RandomProviderDefaultState; |
| import org.apache.commons.rng.core.source64.SplitMix64; |
| |
| /** |
| * Tests which all generators must pass. |
| */ |
| @RunWith(value = Parameterized.class) |
| public class ProvidersCommonParametricTest { |
| /** RNG under test. */ |
| private final UniformRandomProvider generator; |
| /** RNG specifier. */ |
| private final RandomSource originalSource; |
| /** Seed (constructor's first parameter). */ |
| private final Object originalSeed; |
| /** Constructor's additional parameters. */ |
| private final Object[] originalArgs; |
| |
| /** |
| * Initializes the test instance. |
| * |
| * @param data Random source (and seed arguments) to be tested. |
| */ |
| public ProvidersCommonParametricTest(ProvidersList.Data data) { |
| originalSource = data.getSource(); |
| originalSeed = data.getSeed(); |
| originalArgs = data.getArgs(); |
| generator = RandomSource.create(originalSource, originalSeed, originalArgs); |
| } |
| |
| @Parameters(name = "{index}: data={0}") |
| public static Iterable<ProvidersList.Data[]> getList() { |
| return ProvidersList.list(); |
| } |
| |
| // Seeding tests. |
| |
| @Test(expected = UnsupportedOperationException.class) |
| public void testUnsupportedSeedType() { |
| final byte seed = 123; |
| RandomSource.create(originalSource, seed, originalArgs); |
| } |
| |
| @Test |
| public void testAllSeedTypes() { |
| final Integer intSeed = -12131415; |
| final Long longSeed = -1213141516171819L; |
| final int[] intArraySeed = new int[] {0, 11, -22, 33, -44, 55, -66, 77, -88, 99}; |
| final long[] longArraySeed = new long[] {11111L, -222222L, 3333333L, -44444444L}; |
| final byte[] byteArraySeed = new byte[] {-128, -91, -45, -32, -1, 0, 11, 23, 54, 88, 127}; |
| |
| final Object[] seeds = new Object[] {null, |
| intSeed, |
| longSeed, |
| intArraySeed, |
| longArraySeed, |
| byteArraySeed}; |
| |
| int nonNativeSeedCount = 0; |
| int seedCount = 0; |
| for (Object s : seeds) { |
| ++seedCount; |
| if (originalSource.isNativeSeed(s)) { |
| Assert.assertNotNull("Identified native seed is null", s); |
| Assert.assertEquals("Incorrect identification of native seed type", |
| s.getClass(), originalSeed.getClass()); |
| } else { |
| ++nonNativeSeedCount; |
| } |
| |
| RandomSource.create(originalSource, s, originalArgs); |
| } |
| |
| Assert.assertEquals(6, seedCount); |
| Assert.assertEquals(5, nonNativeSeedCount); |
| } |
| |
| @Test |
| public void testNullSeed() { |
| // Note: This is the only test that explicitly calls create() with no other arguments. |
| final UniformRandomProvider rng = originalArgs == null ? |
| RandomSource.create(originalSource) : |
| RandomSource.create(originalSource, null, originalArgs); |
| checkNextIntegerInRange(rng, 10, 10000); |
| } |
| |
| @Test |
| public void testEmptyIntArraySeed() { |
| final int[] empty = new int[0]; |
| Assume.assumeTrue(originalSource.isNativeSeed(empty)); |
| |
| // Exercise the default seeding procedure. |
| final UniformRandomProvider rng = RandomSource.create(originalSource, empty, originalArgs); |
| checkNextIntegerInRange(rng, 10, 20000); |
| } |
| |
| @Test |
| public void testEmptyLongArraySeed() { |
| final long[] empty = new long[0]; |
| Assume.assumeTrue(originalSource.isNativeSeed(empty)); |
| // The Middle-Square Weyl Sequence generator cannot self-seed |
| Assume.assumeFalse(originalSource == RandomSource.MSWS); |
| |
| // Exercise the default seeding procedure. |
| final UniformRandomProvider rng = RandomSource.create(originalSource, empty, originalArgs); |
| checkNextIntegerInRange(rng, 10, 10000); |
| } |
| |
| @Test |
| public void testZeroIntArraySeed() { |
| // Exercise capacity to escape all "zero" state. |
| final int[] zero = new int[2000]; // Large enough to fill the entire state with zeroes. |
| final UniformRandomProvider rng = RandomSource.create(originalSource, zero, originalArgs); |
| Assume.assumeTrue("RNG is non-functional with an all zero seed: " + originalSource, |
| createsNonZeroLongOutput(rng, 2000)); |
| checkNextIntegerInRange(rng, 10, 10000); |
| } |
| |
| @Test |
| public void testZeroLongArraySeed() { |
| // Exercise capacity to escape all "zero" state. |
| final long[] zero = new long[2000]; // Large enough to fill the entire state with zeroes. |
| final UniformRandomProvider rng = RandomSource.create(originalSource, zero, originalArgs); |
| Assume.assumeTrue("RNG is non-functional with an all zero seed: " + originalSource, |
| createsNonZeroLongOutput(rng, 2000)); |
| checkNextIntegerInRange(rng, 10, 10000); |
| } |
| |
| @Test |
| public void testRandomSourceCreateSeed() { |
| final byte[] seed = originalSource.createSeed(); |
| final UniformRandomProvider rng = RandomSource.create(originalSource, seed, originalArgs); |
| checkNextIntegerInRange(rng, 10, 10000); |
| } |
| |
| @Test |
| public void testRandomSourceCreateSeedFromRNG() { |
| final byte[] seed = originalSource.createSeed(new SplitMix64(RandomSource.createLong())); |
| final UniformRandomProvider rng = RandomSource.create(originalSource, seed, originalArgs); |
| checkNextIntegerInRange(rng, 10, 10000); |
| } |
| |
| // State save and restore tests. |
| |
| @Test |
| public void testUnrestorable() { |
| // Create two generators of the same type as the one being tested. |
| final UniformRandomProvider rng1 = RandomSource.create(originalSource, originalSeed, originalArgs); |
| final UniformRandomProvider rng2 = RandomSource.unrestorable(RandomSource.create(originalSource, originalSeed, originalArgs)); |
| |
| // Ensure that they generate the same values. |
| RandomAssert.assertProduceSameSequence(rng1, rng2); |
| |
| // Cast must work. |
| final RestorableUniformRandomProvider restorable = (RestorableUniformRandomProvider) rng1; |
| // Cast must fail. |
| try { |
| final RestorableUniformRandomProvider dummy = (RestorableUniformRandomProvider) rng2; |
| Assert.fail("Cast should have failed"); |
| } catch (ClassCastException e) { |
| // Expected. |
| } |
| } |
| |
| @Test |
| public void testSerializingState() |
| throws IOException, |
| ClassNotFoundException { |
| // Large "n" is not necessary here as we only test the serialization. |
| final int n = 100; |
| |
| // Cast is OK: all instances created by this library inherit from "BaseProvider". |
| final RestorableUniformRandomProvider restorable = (RestorableUniformRandomProvider) generator; |
| |
| // Save. |
| final RandomProviderState stateOrig = restorable.saveState(); |
| // Serialize. |
| ByteArrayOutputStream bos = new ByteArrayOutputStream(); |
| ObjectOutputStream oos = new ObjectOutputStream(bos); |
| oos.writeObject(((RandomProviderDefaultState) stateOrig).getState()); |
| |
| // Store some values. |
| final List<Number> listOrig = makeList(n); |
| |
| // Discard a few more. |
| final List<Number> listDiscard = makeList(n); |
| Assert.assertTrue(listDiscard.size() != 0); |
| Assert.assertFalse(listOrig.equals(listDiscard)); |
| |
| // Retrieve from serialized stream. |
| ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); |
| ObjectInputStream ois = new ObjectInputStream(bis); |
| final RandomProviderState stateNew = new RandomProviderDefaultState((byte[]) ois.readObject()); |
| |
| Assert.assertTrue(stateOrig != stateNew); |
| |
| // Reset. |
| restorable.restoreState(stateNew); |
| |
| // Replay. |
| final List<Number> listReplay = makeList(n); |
| Assert.assertFalse(listOrig == listReplay); |
| |
| // Check that the serialized data recreated the orginal state. |
| Assert.assertTrue(listOrig.equals(listReplay)); |
| } |
| |
| @Test |
| public void testUnrestorableToString() { |
| Assert.assertEquals(generator.toString(), |
| RandomSource.unrestorable(generator).toString()); |
| } |
| |
| @Test |
| public void testSupportedInterfaces() { |
| final UniformRandomProvider rng = RandomSource.create(originalSource, null, originalArgs); |
| Assert.assertEquals("isJumpable", rng instanceof JumpableUniformRandomProvider, |
| originalSource.isJumpable()); |
| Assert.assertEquals("isLongJumpable", rng instanceof LongJumpableUniformRandomProvider, |
| originalSource.isLongJumpable()); |
| } |
| |
| ///// Support methods below. |
| |
| |
| // The methods |
| // * makeList |
| // * checkNextIntegerInRange |
| // * checkNextInRange |
| // have been copied from "src/test" in module "commons-rng-core". |
| // TODO: check whether it is possible to have a single implementation. |
| |
| /** |
| * Populates a list with random numbers. |
| * |
| * @param n Loop counter. |
| * @return a list containing {@code 11 * n} random numbers. |
| */ |
| private List<Number> makeList(int n) { |
| final List<Number> list = new ArrayList<Number>(); |
| |
| for (int i = 0; i < n; i++) { |
| // Append 11 values. |
| list.add(generator.nextInt()); |
| list.add(generator.nextInt(21)); |
| list.add(generator.nextInt(436)); |
| list.add(generator.nextLong()); |
| list.add(generator.nextLong(157894)); |
| list.add(generator.nextLong(5745833)); |
| list.add(generator.nextFloat()); |
| list.add(generator.nextFloat()); |
| list.add(generator.nextDouble()); |
| list.add(generator.nextDouble()); |
| list.add(generator.nextBoolean() ? 1 : 0); |
| } |
| |
| return list; |
| } |
| |
| /** |
| * Tests uniformity of the distribution produced by {@code nextInt(int)}. |
| * |
| * @param rng Generator. |
| * @param max Upper bound. |
| * @param sampleSize Number of random values generated. |
| */ |
| private void checkNextIntegerInRange(final UniformRandomProvider rng, |
| final int max, |
| int sampleSize) { |
| final Callable<Integer> nextMethod = new Callable<Integer>() { |
| @Override |
| public Integer call() throws Exception { |
| return rng.nextInt(max); |
| } |
| }; |
| |
| checkNextInRange(max, sampleSize, nextMethod); |
| } |
| |
| /** |
| * Tests uniformity of the distribution produced by the given |
| * {@code nextMethod}. |
| * It performs a chi-square test of homogeneity of the observed |
| * distribution with the expected uniform distribution. |
| * Repeat tests are performed at the 1% level and the total number of failed |
| * tests is tested at the 0.5% significance level. |
| * |
| * @param max Upper bound. |
| * @param nextMethod method to call. |
| * @param sampleSize Number of random values generated. |
| */ |
| private <T extends Number> void checkNextInRange(T max, |
| int sampleSize, |
| Callable<T> nextMethod) { |
| final int numTests = 500; |
| |
| // Do not change (statistical test assumes that dof = 9). |
| final int numBins = 10; // dof = numBins - 1 |
| |
| // Set up bins. |
| final long n = max.longValue(); |
| final long[] binUpperBounds = new long[numBins]; |
| final double step = n / (double) numBins; |
| for (int k = 0; k < numBins; k++) { |
| binUpperBounds[k] = (long) ((k + 1) * step); |
| } |
| // Rounding error occurs on the long value of 2305843009213693951L |
| binUpperBounds[numBins - 1] = n; |
| |
| // Run the tests. |
| int numFailures = 0; |
| |
| final double[] expected = new double[numBins]; |
| long previousUpperBound = 0; |
| for (int k = 0; k < numBins; k++) { |
| final long range = binUpperBounds[k] - previousUpperBound; |
| expected[k] = sampleSize * (range / (double) n); |
| previousUpperBound = binUpperBounds[k]; |
| } |
| |
| final int[] observed = new int[numBins]; |
| // Chi-square critical value with 9 degrees of freedom |
| // and 1% significance level. |
| final double chi2CriticalValue = 21.67; |
| |
| try { |
| for (int i = 0; i < numTests; i++) { |
| Arrays.fill(observed, 0); |
| for (int j = 0; j < sampleSize; j++) { |
| final long value = nextMethod.call().longValue(); |
| Assert.assertTrue("Range", (value >= 0) && (value < n)); |
| |
| for (int k = 0; k < numBins; k++) { |
| if (value < binUpperBounds[k]) { |
| ++observed[k]; |
| break; |
| } |
| } |
| } |
| |
| // Compute chi-square. |
| double chi2 = 0; |
| for (int k = 0; k < numBins; k++) { |
| final double diff = observed[k] - expected[k]; |
| chi2 += diff * diff / expected[k]; |
| } |
| |
| // Statistics check. |
| if (chi2 > chi2CriticalValue) { |
| ++numFailures; |
| } |
| } |
| } catch (Exception e) { |
| // Should never happen. |
| throw new RuntimeException("Unexpected", e); |
| } |
| |
| // The expected number of failed tests can be modelled as a Binomial distribution |
| // B(n, p) with n=500, p=0.01 (500 tests with a 1% significance level). |
| // The cumulative probability of the number of failed tests (X) is: |
| // x P(X>x) |
| // 10 0.0132 |
| // 11 0.00521 |
| // 12 0.00190 |
| |
| if (numFailures > 11) { // Test will fail with 0.5% probability |
| Assert.fail(generator + ": Too many failures for n = " + n + |
| " (" + numFailures + " out of " + numTests + " tests failed)"); |
| } |
| } |
| |
| /** |
| * Return true if the generator creates non-zero output from |
| * {@link UniformRandomProvider#nextLong()} within the given number of cycles. |
| * |
| * @param rng Random generator. |
| * @param cycles Number of cycles. |
| * @return true if non-zero output |
| */ |
| private static boolean createsNonZeroLongOutput(UniformRandomProvider rng, |
| int cycles) { |
| boolean nonZero = false; |
| for (int i = 0; i < cycles; i++) { |
| if (rng.nextLong() != 0) { |
| nonZero = true; |
| } |
| } |
| return nonZero; |
| } |
| } |