|
| 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; |
| 18 | + |
| 19 | +import java.util.HashSet; |
| 20 | +import java.util.Set; |
| 21 | +import java.util.stream.Stream; |
| 22 | +import org.junit.jupiter.api.Assertions; |
| 23 | +import org.junit.jupiter.params.ParameterizedTest; |
| 24 | +import org.junit.jupiter.params.provider.Arguments; |
| 25 | +import org.junit.jupiter.params.provider.MethodSource; |
| 26 | +import org.junit.jupiter.params.provider.ValueSource; |
| 27 | + |
| 28 | +/** |
| 29 | + * Tests for default method implementations in |
| 30 | + * {@link ArbitrarilyJumpableUniformRandomProvider}. |
| 31 | + * |
| 32 | + * <p>Streams methods are asserted to call the corresponding jump method in the |
| 33 | + * interface. |
| 34 | + */ |
| 35 | +class ArbitrarilyJumpableUniformRandomProviderTest { |
| 36 | + /** |
| 37 | + * Class for checking the behavior of the ArbitrarilyJumpableUniformRandomProvider. |
| 38 | + * This generator returns a fixed value. The value is incremented by jumping. |
| 39 | + */ |
| 40 | + private static class JumpableGenerator implements ArbitrarilyJumpableUniformRandomProvider { |
| 41 | + /** The value for nextLong(). */ |
| 42 | + private long value; |
| 43 | + |
| 44 | + JumpableGenerator(long seed) { |
| 45 | + this.value = seed; |
| 46 | + } |
| 47 | + |
| 48 | + @Override |
| 49 | + public long nextLong() { |
| 50 | + return value; |
| 51 | + } |
| 52 | + |
| 53 | + @Override |
| 54 | + public ArbitrarilyJumpableUniformRandomProvider jump(double distance) { |
| 55 | + final JumpableGenerator copy = new JumpableGenerator(value); |
| 56 | + // Support small distances as a long |
| 57 | + value += (long) distance; |
| 58 | + return copy; |
| 59 | + } |
| 60 | + |
| 61 | + @Override |
| 62 | + public ArbitrarilyJumpableUniformRandomProvider jumpPowerOfTwo(int logDistance) { |
| 63 | + throw new IllegalStateException("Not required by default methods"); |
| 64 | + } |
| 65 | + } |
| 66 | + |
| 67 | + interface JumpsFunction { |
| 68 | + /** |
| 69 | + * Create a stream of generators separated by the jump distance. |
| 70 | + * |
| 71 | + * @param rng Jumpable generator. |
| 72 | + * @param streamSize Number of objects to generate. |
| 73 | + * @param distance Distance to jump forward with the state cycle. |
| 74 | + * @return the stream |
| 75 | + */ |
| 76 | + Stream<ArbitrarilyJumpableUniformRandomProvider> apply(ArbitrarilyJumpableUniformRandomProvider rng, long size, double distance); |
| 77 | + } |
| 78 | + |
| 79 | + /** |
| 80 | + * Return a stream of jump arguments, each of the arguments consisting of the size of |
| 81 | + * the stream, the seed value for the jumpable generator, and the jump distance. |
| 82 | + * |
| 83 | + * @return the stream of arguments |
| 84 | + */ |
| 85 | + static Stream<Arguments> jumpArguments() { |
| 86 | + return Stream.of( |
| 87 | + // size, seed |
| 88 | + Arguments.of(0, 0, 0.0), |
| 89 | + Arguments.of(1, 62317845757L, 2.0), |
| 90 | + Arguments.of(5, -12683127894356L, 42.0) |
| 91 | + ); |
| 92 | + } |
| 93 | + |
| 94 | + @ParameterizedTest |
| 95 | + @ValueSource(longs = {-1, -2, Long.MIN_VALUE}) |
| 96 | + void testInvalidStreamSizeThrows(long size) { |
| 97 | + final ArbitrarilyJumpableUniformRandomProvider rng = new JumpableGenerator(0); |
| 98 | + final double distance = 10; |
| 99 | + Assertions.assertThrows(IllegalArgumentException.class, () -> rng.jumps(size, distance)); |
| 100 | + } |
| 101 | + |
| 102 | + @ParameterizedTest |
| 103 | + @ValueSource(doubles = {-1, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.NaN}) |
| 104 | + void testInvalidDistanceThrows(double distance) { |
| 105 | + final ArbitrarilyJumpableUniformRandomProvider rng = new JumpableGenerator(0); |
| 106 | + final long size = 1; |
| 107 | + Assertions.assertThrows(IllegalArgumentException.class, () -> rng.jumps(size, distance)); |
| 108 | + } |
| 109 | + |
| 110 | + @ParameterizedTest |
| 111 | + @MethodSource(value = {"jumpArguments"}) |
| 112 | + void testJumps(int size, long seed, double distance) { |
| 113 | + assertJumps(size, seed, distance, |
| 114 | + (rng, n, d) -> rng.jumps(d).limit(n)); |
| 115 | + } |
| 116 | + |
| 117 | + @ParameterizedTest |
| 118 | + @MethodSource(value = {"jumpArguments"}) |
| 119 | + void testJumpsWithSize(int size, long seed, double distance) { |
| 120 | + assertJumps(size, seed, distance, |
| 121 | + ArbitrarilyJumpableUniformRandomProvider::jumps); |
| 122 | + } |
| 123 | + |
| 124 | + /** |
| 125 | + * Assert that successive calls to the generator jump function will create a series of |
| 126 | + * generators that matches the stream function. |
| 127 | + * |
| 128 | + * @param size Number of jumps. |
| 129 | + * @param seed Seed for the generator. |
| 130 | + * @param distance Jump distance. |
| 131 | + * @param streamFunction Stream function to create a series of generators spaced |
| 132 | + * using the jump function. |
| 133 | + */ |
| 134 | + private static void assertJumps(int size, long seed, double distance, |
| 135 | + JumpsFunction streamFunction) { |
| 136 | + // Manually jump |
| 137 | + final JumpableGenerator jumpingRNG = new JumpableGenerator(seed); |
| 138 | + final long[] expected = new long[size]; |
| 139 | + for (int i = 0; i < size; i++) { |
| 140 | + final UniformRandomProvider copy = jumpingRNG.jump(distance); |
| 141 | + Assertions.assertNotSame(jumpingRNG, copy, "Jump function should return a copy"); |
| 142 | + expected[i] = copy.nextLong(); |
| 143 | + } |
| 144 | + |
| 145 | + // Stream (must be sequential) |
| 146 | + final JumpableGenerator streamingRNG = new JumpableGenerator(seed); |
| 147 | + final Stream<? extends UniformRandomProvider> stream = |
| 148 | + streamFunction.apply(streamingRNG, size, distance); |
| 149 | + Assertions.assertFalse(stream.isParallel(), "Jumping requires a non-parallel stream"); |
| 150 | + |
| 151 | + // Stream should create unique generators |
| 152 | + final Set<UniformRandomProvider> set = new HashSet<>(); |
| 153 | + final long[] actual = stream.map(x -> addAndReturn(set, x)) |
| 154 | + .mapToLong(UniformRandomProvider::nextLong) |
| 155 | + .toArray(); |
| 156 | + Assertions.assertEquals(size, set.size(), "Stream should have unique generators"); |
| 157 | + Assertions.assertFalse(set.contains(streamingRNG), "Stream contains the source of the stream as a generator"); |
| 158 | + |
| 159 | + Assertions.assertArrayEquals(expected, actual, "Stream function did not match the jump function"); |
| 160 | + } |
| 161 | + |
| 162 | + /** |
| 163 | + * Add the generator to the set and return the generator. This is a convenience |
| 164 | + * method used to check generator objects in a stream are unique and not the same as |
| 165 | + * the generator that created the stream. |
| 166 | + * |
| 167 | + * @param set Set |
| 168 | + * @param x Generator |
| 169 | + * @return the generator |
| 170 | + */ |
| 171 | + private static UniformRandomProvider addAndReturn(Set<UniformRandomProvider> set, UniformRandomProvider x) { |
| 172 | + set.add(x); |
| 173 | + return x; |
| 174 | + } |
| 175 | +} |
0 commit comments