| /* |
| * 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.core; |
| |
| import org.junit.Assert; |
| |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.InvocationTargetException; |
| |
| import org.apache.commons.rng.JumpableUniformRandomProvider; |
| import org.apache.commons.rng.LongJumpableUniformRandomProvider; |
| import org.apache.commons.rng.UniformRandomProvider; |
| |
| /** |
| * Utility class for testing random generators. |
| */ |
| public final class RandomAssert { |
| /** |
| * Class contains only static methods. |
| */ |
| private RandomAssert() {} |
| |
| /** |
| * Assert that the random generator produces the expected output using |
| * {@link UniformRandomProvider#nextInt()}. |
| * |
| * @param expected Expected output. |
| * @param rng Random generator. |
| */ |
| public static void assertEquals(int[] expected, UniformRandomProvider rng) { |
| assertEquals("Value at position ", expected, rng); |
| } |
| |
| /** |
| * Assert that the random generator produces the expected output using |
| * {@link UniformRandomProvider#nextLong()}. |
| * |
| * @param expected Expected output. |
| * @param rng Random generator. |
| */ |
| public static void assertEquals(long[] expected, UniformRandomProvider rng) { |
| assertEquals("Value at position ", expected, rng); |
| } |
| |
| /** |
| * Assert that the random generator produces the expected output using |
| * {@link UniformRandomProvider#nextInt()}. |
| * The message prefix is prepended to the array index for the assertion message. |
| * |
| * @param messagePrefix Message prefix. |
| * @param expected Expected output. |
| * @param rng Random generator. |
| */ |
| private static void assertEquals(String messagePrefix, int[] expected, UniformRandomProvider rng) { |
| for (int i = 0; i < expected.length; i++) { |
| Assert.assertEquals(messagePrefix + i, expected[i], rng.nextInt()); |
| } |
| } |
| |
| /** |
| * Assert that the random generator produces the expected output using |
| * {@link UniformRandomProvider#nextLong()}. |
| * The message prefix is prepended to the array index for the assertion message. |
| * |
| * @param messagePrefix Message prefix. |
| * @param expected Expected output. |
| * @param rng Random generator. |
| */ |
| private static void assertEquals(String messagePrefix, long[] expected, UniformRandomProvider rng) { |
| for (int i = 0; i < expected.length; i++) { |
| Assert.assertEquals(messagePrefix + i, expected[i], rng.nextLong()); |
| } |
| } |
| |
| /** |
| * Assert that the random generator produces a <strong>different</strong> output using |
| * {@link UniformRandomProvider#nextLong()} to the expected output. |
| * |
| * @param expected Expected output. |
| * @param rng Random generator. |
| */ |
| public static void assertNotEquals(long[] expected, UniformRandomProvider rng) { |
| for (int i = 0; i < expected.length; i++) { |
| if (expected[i] != rng.nextLong()) { |
| return; |
| } |
| } |
| Assert.fail("Expected a different nextLong output"); |
| } |
| |
| /** |
| * Assert that the random generator satisfies the contract of the |
| * {@link JumpableUniformRandomProvider#jump()} function. |
| * |
| * <ul> |
| * <li>The jump returns a copy instance. This is asserted to be a different object |
| * of the same class type as the input. |
| * <li>The copy instance outputs the expected sequence for the current state of the input generator. |
| * This is asserted using the {@code expectedBefore} sequence. |
| * <li>The input instance outputs the expected sequence for an advanced state. |
| * This is asserted using the {@code expectedAfter} sequence. |
| * <ul> |
| * |
| * @param expectedBefore Expected output before the jump. |
| * @param expectedAfter Expected output after the jump. |
| * @param rng Random generator. |
| */ |
| public static void assertJumpEquals(int[] expectedBefore, |
| int[] expectedAfter, |
| JumpableUniformRandomProvider rng) { |
| final UniformRandomProvider copy = rng.jump(); |
| Assert.assertNotSame("The copy instance should be a different object", rng, copy); |
| Assert.assertEquals("The copy instance should be the same class", rng.getClass(), copy.getClass()); |
| assertEquals("Pre-jump value at position ", expectedBefore, copy); |
| assertEquals("Post-jump value at position ", expectedAfter, rng); |
| } |
| |
| /** |
| * Assert that the random generator satisfies the contract of the |
| * {@link JumpableUniformRandomProvider#jump()} function. |
| * |
| * <ul> |
| * <li>The jump returns a copy instance. This is asserted to be a different object |
| * of the same class type as the input. |
| * <li>The copy instance outputs the expected sequence for the current state of the input generator. |
| * This is asserted using the {@code expectedBefore} sequence. |
| * <li>The input instance outputs the expected sequence for an advanced state. |
| * This is asserted using the {@code expectedAfter} sequence. |
| * <ul> |
| * |
| * @param expectedBefore Expected output before the jump. |
| * @param expectedAfter Expected output after the jump. |
| * @param rng Random generator. |
| */ |
| public static void assertJumpEquals(long[] expectedBefore, |
| long[] expectedAfter, |
| JumpableUniformRandomProvider rng) { |
| final UniformRandomProvider copy = rng.jump(); |
| Assert.assertNotSame("The copy instance should be a different object", rng, copy); |
| Assert.assertEquals("The copy instance should be the same class", rng.getClass(), copy.getClass()); |
| assertEquals("Pre-jump value at position ", expectedBefore, copy); |
| assertEquals("Post-jump value at position ", expectedAfter, rng); |
| } |
| |
| /** |
| * Assert that the random generator satisfies the contract of the |
| * {@link LongJumpableUniformRandomProvider#longJump()} function. |
| * |
| * <ul> |
| * <li>The long jump returns a copy instance. This is asserted to be a different object |
| * of the same class type as the input. |
| * <li>The copy instance outputs the expected sequence for the current state of the input generator. |
| * This is asserted using the {@code expectedBefore} sequence. |
| * <li>The input instance outputs the expected sequence for an advanced state. |
| * This is asserted using the {@code expectedAfter} sequence. |
| * <ul> |
| * |
| * @param expectedBefore Expected output before the long jump. |
| * @param expectedAfter Expected output after the long jump. |
| * @param rng Random generator. |
| */ |
| public static void assertLongJumpEquals(int[] expectedBefore, |
| int[] expectedAfter, |
| LongJumpableUniformRandomProvider rng) { |
| final UniformRandomProvider copy = rng.longJump(); |
| Assert.assertNotSame("The copy instance should be a different object", rng, copy); |
| Assert.assertEquals("The copy instance should be the same class", rng.getClass(), copy.getClass()); |
| assertEquals("Pre-jump value at position ", expectedBefore, copy); |
| assertEquals("Post-jump value at position ", expectedAfter, rng); |
| } |
| |
| /** |
| * Assert that the random generator satisfies the contract of the |
| * {@link LongJumpableUniformRandomProvider#longJump()} function. |
| * |
| * <ul> |
| * <li>The long jump returns a copy instance. This is asserted to be a different object |
| * of the same class type as the input. |
| * <li>The copy instance outputs the expected sequence for the current state of the input generator. |
| * This is asserted using the {@code expectedBefore} sequence. |
| * <li>The input instance outputs the expected sequence for an advanced state. |
| * This is asserted using the {@code expectedAfter} sequence. |
| * <ul> |
| * |
| * @param expectedBefore Expected output before the long jump. |
| * @param expectedAfter Expected output after the long jump. |
| * @param rng Random generator. |
| */ |
| public static void assertLongJumpEquals(long[] expectedBefore, |
| long[] expectedAfter, |
| LongJumpableUniformRandomProvider rng) { |
| final UniformRandomProvider copy = rng.longJump(); |
| Assert.assertNotSame("The copy instance should be a different object", rng, copy); |
| Assert.assertEquals("The copy instance should be the same class", rng.getClass(), copy.getClass()); |
| assertEquals("Pre-jump value at position ", expectedBefore, copy); |
| assertEquals("Post-jump value at position ", expectedAfter, rng); |
| } |
| |
| /** |
| * Assert that the two random generators produce the same output for |
| * {@link UniformRandomProvider#nextInt()} over the given number of cycles. |
| * |
| * @param cycles Number of cycles. |
| * @param rng1 Random generator 1. |
| * @param rng2 Random generator 2. |
| */ |
| public static void assertNextIntEquals(int cycles, UniformRandomProvider rng1, UniformRandomProvider rng2) { |
| for (int i = 0; i < cycles; i++) { |
| Assert.assertEquals("Value at position " + i, rng1.nextInt(), rng2.nextInt()); |
| } |
| } |
| |
| /** |
| * Assert that the two random generators produce the same output for |
| * {@link UniformRandomProvider#nextLong()} over the given number of cycles. |
| * |
| * @param cycles Number of cycles. |
| * @param rng1 Random generator 1. |
| * @param rng2 Random generator 2. |
| */ |
| public static void assertNextLongEquals(int cycles, UniformRandomProvider rng1, UniformRandomProvider rng2) { |
| for (int i = 0; i < cycles; i++) { |
| Assert.assertEquals("Value at position " + i, rng1.nextLong(), rng2.nextLong()); |
| } |
| } |
| |
| /** |
| * Assert that the random generator produces zero output for |
| * {@link UniformRandomProvider#nextInt()} over the given number of cycles. |
| * This is used to test a poorly seeded generator cannot generate random output. |
| * |
| * @param rng Random generator. |
| * @param cycles Number of cycles. |
| */ |
| public static void assertNextIntZeroOutput(UniformRandomProvider rng, int cycles) { |
| for (int i = 0; i < cycles; i++) { |
| Assert.assertEquals("Expected the generator to output zeros", 0, rng.nextInt()); |
| } |
| } |
| |
| /** |
| * Assert that the random generator produces zero output for |
| * {@link UniformRandomProvider#nextLong()} over the given number of cycles. |
| * This is used to test a poorly seeded generator cannot generate random output. |
| * |
| * @param rng Random generator. |
| * @param cycles Number of cycles. |
| */ |
| public static void assertNextLongZeroOutput(UniformRandomProvider rng, int cycles) { |
| for (int i = 0; i < cycles; i++) { |
| Assert.assertEquals("Expected the generator to output zeros", 0, rng.nextLong()); |
| } |
| } |
| |
| /** |
| * Assert that following a set number of warm-up cycles the random generator produces |
| * at least one non-zero output for {@link UniformRandomProvider#nextLong()} over the |
| * given number of test cycles. This is used to test a poorly seeded generator can recover |
| * internal state to generate "random" output. |
| * |
| * @param rng Random generator. |
| * @param warmupCycles Number of warm-up cycles. |
| * @param testCycles Number of test cycles. |
| */ |
| public static void assertNextLongNonZeroOutput(UniformRandomProvider rng, |
| int warmupCycles, int testCycles) { |
| for (int i = 0; i < warmupCycles; i++) { |
| rng.nextLong(); |
| } |
| for (int i = 0; i < testCycles; i++) { |
| if (rng.nextLong() != 0L) { |
| return; |
| } |
| } |
| Assert.fail("No non-zero output after " + (warmupCycles + testCycles) + " cycles"); |
| } |
| |
| /** |
| * Assert that following a set number of warm-up cycles the random generator produces |
| * at least one non-zero output for {@link UniformRandomProvider#nextLong()} over the |
| * given number of test cycles. |
| * |
| * <p>Helper function to add the seed element and bit that was non zero to the fail message. |
| * |
| * @param rng Random generator. |
| * @param warmupCycles Number of warm-up cycles. |
| * @param testCycles Number of test cycles. |
| * @param seedElement Seed element for the message. |
| * @param bit Seed bit for the message. |
| */ |
| private static void assertNextLongNonZeroOutputFromSingleBitSeed( |
| UniformRandomProvider rng, int warmupCycles, int testCycles, int seedElement, int bit) { |
| try { |
| assertNextLongNonZeroOutput(rng, warmupCycles, testCycles); |
| } catch (AssertionError ex) { |
| Assert.fail("No non-zero output after " + (warmupCycles + testCycles) + " cycles. " + |
| "Seed element [" + seedElement + "], bit=" + bit); |
| } |
| } |
| |
| /** |
| * Assert that the random generator created using an {@code int[]} seed with a |
| * single bit set is functional. This is tested using the |
| * {@link #assertNextLongNonZeroOutput(UniformRandomProvider, int, int)} using |
| * two times the seed size as the warm-up and test cycles. |
| * |
| * @param type Class of the generator. |
| * @param size Seed size. |
| */ |
| public static <T extends UniformRandomProvider> void |
| assertIntArrayConstructorWithSingleBitSeedIsFunctional(Class<T> type, int size) { |
| assertIntArrayConstructorWithSingleBitInPoolIsFunctional(type, 32 * size); |
| } |
| |
| /** |
| * Assert that the random generator created using an {@code int[]} seed with a |
| * single bit set is functional. This is tested using the |
| * {@link #assertNextLongNonZeroOutput(UniformRandomProvider, int, int)} using |
| * two times the seed size as the warm-up and test cycles. |
| * |
| * <p>The seed size is determined from the size of the bit pool. Bits are set for every position |
| * in the pool from most significant first. If the pool size is not a multiple of 32 then the |
| * remaining lower significant bits of the last seed position are not tested.</p> |
| * |
| * @param type Class of the generator. |
| * @param k Number of bits in the pool (not necessarily a multiple of 32). |
| */ |
| public static <T extends UniformRandomProvider> void |
| assertIntArrayConstructorWithSingleBitInPoolIsFunctional(Class<T> type, int k) { |
| try { |
| // Find the int[] constructor |
| final Constructor<T> constructor = type.getConstructor(int[].class); |
| final int size = (k + 31) / 32; |
| final int[] seed = new int[size]; |
| int remaining = k; |
| for (int i = 0; i < seed.length; i++) { |
| seed[i] = 0x80000000; |
| for (int j = 0; j < 32; j++) { |
| final UniformRandomProvider rng = constructor.newInstance(seed); |
| RandomAssert.assertNextLongNonZeroOutputFromSingleBitSeed(rng, 2 * size, 2 * size, i, 31 - j); |
| // Eventually reset to zero |
| seed[i] >>>= 1; |
| // Terminate when the entire bit pool has been tested |
| if (--remaining == 0) { |
| return; |
| } |
| } |
| Assert.assertEquals("Seed element was not reset", 0, seed[i]); |
| } |
| } catch (IllegalAccessException ex) { |
| Assert.fail(ex.getMessage()); |
| } catch (NoSuchMethodException ex) { |
| Assert.fail(ex.getMessage()); |
| } catch (InstantiationException ex) { |
| Assert.fail(ex.getMessage()); |
| } catch (InvocationTargetException ex) { |
| Assert.fail(ex.getMessage()); |
| } |
| } |
| |
| /** |
| * Assert that the random generator created using a {@code long[]} seed with a |
| * single bit set is functional. This is tested using the |
| * {@link #assertNextLongNonZeroOutput(UniformRandomProvider, int, int)} using two times the seed |
| * size as the warm-up and test cycles. |
| * |
| * @param type Class of the generator. |
| * @param size Seed size. |
| */ |
| public static <T extends UniformRandomProvider> void |
| assertLongArrayConstructorWithSingleBitSeedIsFunctional(Class<T> type, int size) { |
| try { |
| // Find the long[] constructor |
| final Constructor<T> constructor = type.getConstructor(long[].class); |
| final long[] seed = new long[size]; |
| for (int i = 0; i < size; i++) { |
| seed[i] = 0x8000000000000000L; |
| for (int j = 0; j < 64; j++) { |
| final UniformRandomProvider rng = constructor.newInstance(seed); |
| RandomAssert.assertNextLongNonZeroOutputFromSingleBitSeed(rng, 2 * size, 2 * size, i, 63 - j); |
| // Eventually reset to zero |
| seed[i] >>>= 1; |
| } |
| Assert.assertEquals("Seed element was not reset", 0L, seed[i]); |
| } |
| } catch (IllegalAccessException ex) { |
| Assert.fail(ex.getMessage()); |
| } catch (NoSuchMethodException ex) { |
| Assert.fail(ex.getMessage()); |
| } catch (InstantiationException ex) { |
| Assert.fail(ex.getMessage()); |
| } catch (InvocationTargetException ex) { |
| Assert.fail(ex.getMessage()); |
| } |
| } |
| } |