SeedFactory.java
- /*
- * 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.internal;
- import java.security.SecureRandom;
- import java.util.concurrent.locks.ReentrantLock;
- import org.apache.commons.rng.core.util.NumberFactory;
- import org.apache.commons.rng.UniformRandomProvider;
- import org.apache.commons.rng.core.source64.RandomLongSource;
- import org.apache.commons.rng.core.source64.SplitMix64;
- import org.apache.commons.rng.core.source64.XoRoShiRo1024PlusPlus;
- /**
- * Utilities related to seeding.
- *
- * <p>
- * This class provides methods to generate random seeds (single values
- * or arrays of values, of {@code int} or {@code long} types) that can
- * be passed to the {@link org.apache.commons.rng.simple.RandomSource
- * methods that create a generator instance}.
- * <br>
- * Although the seed-generating methods defined in this class will likely
- * return different values for all calls, there is no guarantee that the
- * produced seed will result always in a "good" sequence of numbers (even
- * if the generator initialized with that seed is good).
- * <br>
- * There is <i>no guarantee</i> that sequences will not overlap.
- * </p>
- *
- * @since 1.0
- */
- public final class SeedFactory {
- /** Size of the state array of "XoRoShiRo1024PlusPlus". */
- private static final int XO_RO_SHI_RO_1024_STATE_SIZE = 16;
- /** Size of block to fill in an {@code int[]} seed per synchronized operation. */
- private static final int INT_ARRAY_BLOCK_SIZE = 8;
- /** Size of block to fill in a {@code long[]} seed per synchronized operation. */
- private static final int LONG_ARRAY_BLOCK_SIZE = 4;
- /**
- * The lock to own when using the seed generator. This lock is unfair and there is no
- * particular access order for waiting threads.
- *
- * <p>This is used as an alternative to {@code synchronized} statements to guard access
- * to the seed generator.</p>
- */
- private static final ReentrantLock LOCK = new ReentrantLock(false);
- /** Generator with a long period. */
- private static final UniformRandomProvider SEED_GENERATOR;
- static {
- // Use a secure RNG so that different instances (e.g. in multiple JVM
- // instances started in rapid succession) will have different seeds.
- final SecureRandom seedGen = new SecureRandom();
- final byte[] bytes = new byte[8 * XO_RO_SHI_RO_1024_STATE_SIZE];
- seedGen.nextBytes(bytes);
- final long[] seed = NumberFactory.makeLongArray(bytes);
- // The XoRoShiRo1024PlusPlus generator cannot recover from an all zero seed and
- // will produce low quality initial output if initialized with some zeros.
- // Ensure it is non zero at all array positions using a SplitMix64
- // generator (this is insensitive to a zero seed so can use the first seed value).
- final SplitMix64 rng = new SplitMix64(seed[0]);
- for (int i = 0; i < seed.length; i++) {
- seed[i] = ensureNonZero(rng, seed[i]);
- }
- SEED_GENERATOR = new XoRoShiRo1024PlusPlus(seed);
- }
- /**
- * Class contains only static methods.
- */
- private SeedFactory() {}
- /**
- * Creates an {@code int} number for use as a seed.
- *
- * @return a random number.
- */
- public static int createInt() {
- LOCK.lock();
- try {
- return SEED_GENERATOR.nextInt();
- } finally {
- LOCK.unlock();
- }
- }
- /**
- * Creates a {@code long} number for use as a seed.
- *
- * @return a random number.
- */
- public static long createLong() {
- LOCK.lock();
- try {
- return SEED_GENERATOR.nextLong();
- } finally {
- LOCK.unlock();
- }
- }
- /**
- * Creates an array of {@code int} numbers for use as a seed.
- *
- * @param n Size of the array to create.
- * @return an array of {@code n} random numbers.
- */
- public static int[] createIntArray(int n) {
- // Behaviour from v1.3 is to ensure the first position is non-zero
- return createIntArray(n, 0, Math.min(n, 1));
- }
- /**
- * Creates an array of {@code int} numbers for use as a seed.
- * Optionally ensure a sub-range of the array is not all-zero.
- *
- * <p>This method is package-private for use by {@link NativeSeedType}.
- *
- * @param n Size of the array to create.
- * @param from The start of the not all-zero sub-range (inclusive).
- * @param to The end of the not all-zero sub-range (exclusive).
- * @return an array of {@code n} random numbers.
- * @throws IndexOutOfBoundsException if the sub-range is invalid
- * @since 1.5
- */
- static int[] createIntArray(int n, int from, int to) {
- final int[] seed = new int[n];
- // Compute the size that can be filled with complete blocks
- final int blockSize = INT_ARRAY_BLOCK_SIZE * (n / INT_ARRAY_BLOCK_SIZE);
- int i = 0;
- while (i < blockSize) {
- final int end = i + INT_ARRAY_BLOCK_SIZE;
- fillIntArray(seed, i, end);
- i = end;
- }
- // Final fill only if required
- if (i != n) {
- fillIntArray(seed, i, n);
- }
- ensureNonZero(seed, from, to);
- return seed;
- }
- /**
- * Creates an array of {@code long} numbers for use as a seed.
- *
- * @param n Size of the array to create.
- * @return an array of {@code n} random numbers.
- */
- public static long[] createLongArray(int n) {
- // Behaviour from v1.3 is to ensure the first position is non-zero
- return createLongArray(n, 0, Math.min(n, 1));
- }
- /**
- * Creates an array of {@code long} numbers for use as a seed.
- * Optionally ensure a sub-range of the array is not all-zero.
- *
- * <p>This method is package-private for use by {@link NativeSeedType}.
- *
- * @param n Size of the array to create.
- * @param from The start of the not all-zero sub-range (inclusive).
- * @param to The end of the not all-zero sub-range (exclusive).
- * @return an array of {@code n} random numbers.
- * @throws IndexOutOfBoundsException if the sub-range is invalid
- * @since 1.5
- */
- static long[] createLongArray(int n, int from, int to) {
- final long[] seed = new long[n];
- // Compute the size that can be filled with complete blocks
- final int blockSize = LONG_ARRAY_BLOCK_SIZE * (n / LONG_ARRAY_BLOCK_SIZE);
- int i = 0;
- while (i < blockSize) {
- final int end = i + LONG_ARRAY_BLOCK_SIZE;
- fillLongArray(seed, i, end);
- i = end;
- }
- // Final fill only if required
- if (i != n) {
- fillLongArray(seed, i, n);
- }
- ensureNonZero(seed, from, to);
- return seed;
- }
- /**
- * Fill the array between {@code start} inclusive and {@code end} exclusive from the
- * seed generator. The lock is used to guard access to the generator.
- *
- * @param array Array data.
- * @param start Start (inclusive).
- * @param end End (exclusive).
- */
- private static void fillIntArray(int[] array, int start, int end) {
- LOCK.lock();
- try {
- for (int i = start; i < end; i++) {
- array[i] = SEED_GENERATOR.nextInt();
- }
- } finally {
- LOCK.unlock();
- }
- }
- /**
- * Fill the array between {@code start} inclusive and {@code end} exclusive from the
- * seed generator. The lock is used to guard access to the generator.
- *
- * @param array Array data.
- * @param start Start (inclusive).
- * @param end End (exclusive).
- */
- private static void fillLongArray(long[] array, int start, int end) {
- LOCK.lock();
- try {
- for (int i = start; i < end; i++) {
- array[i] = SEED_GENERATOR.nextLong();
- }
- } finally {
- LOCK.unlock();
- }
- }
- /**
- * Creates an array of {@code byte} numbers for use as a seed using the supplied source of
- * randomness. A sub-range can be specified that must not contain all zeros.
- *
- * @param source Source of randomness.
- * @param n Size of the array to create.
- * @param from The start of the not all-zero sub-range (inclusive).
- * @param to The end of the not all-zero sub-range (exclusive).
- * @return an array of {@code n} random numbers.
- */
- static byte[] createByteArray(UniformRandomProvider source,
- int n,
- int from,
- int to) {
- final byte[] seed = new byte[n];
- source.nextBytes(seed);
- ensureNonZero(seed, from, to, source);
- return seed;
- }
- /**
- * Ensure the seed is not all-zero within the specified sub-range.
- *
- * <p>This method will check the sub-range and if all are zero it will fill the range
- * with a random sequence seeded from the default source of random int values. The
- * fill ensures position {@code from} has a non-zero value; and the entire sub-range
- * has a maximum of one zero. The method ensures any length sub-range contains
- * non-zero bits. The output seed is suitable for generators that cannot be seeded
- * with all zeros in the specified sub-range.</p>
- *
- * @param seed Seed array (modified in place).
- * @param from The start of the not all-zero sub-range (inclusive).
- * @param to The end of the not all-zero sub-range (exclusive).
- * @throws IndexOutOfBoundsException if the sub-range is invalid
- * @see #createInt()
- */
- static void ensureNonZero(int[] seed, int from, int to) {
- if (from >= to) {
- return;
- }
- // No check on the range so an IndexOutOfBoundsException will occur if invalid
- for (int i = from; i < to; i++) {
- if (seed[i] != 0) {
- return;
- }
- }
- // Fill with non-zero values using a SplitMix-style PRNG.
- // The range is at least 1.
- // To ensure the first value is not zero requires the input to the mix function
- // to be non-zero. This is ensured if the start is even since the increment is odd.
- int x = createInt() << 1;
- for (int i = from; i < to; i++) {
- x += MixFunctions.GOLDEN_RATIO_32;
- seed[i] = MixFunctions.murmur3(x);
- }
- }
- /**
- * Ensure the seed is not all-zero within the specified sub-range.
- *
- * <p>This method will check the sub-range and if all are zero it will fill the range
- * with a random sequence seeded from the default source of random long values. The
- * fill ensures position {@code from} has a non-zero value; and the entire sub-range
- * has a maximum of one zero. The method ensures any length sub-range contains
- * non-zero bits. The output seed is suitable for generators that cannot be seeded
- * with all zeros in the specified sub-range.</p>
- *
- * @param seed Seed array (modified in place).
- * @param from The start of the not all-zero sub-range (inclusive).
- * @param to The end of the not all-zero sub-range (exclusive).
- * @throws IndexOutOfBoundsException if the sub-range is invalid
- * @see #createLong()
- */
- static void ensureNonZero(long[] seed, int from, int to) {
- if (from >= to) {
- return;
- }
- // No check on the range so an IndexOutOfBoundsException will occur if invalid
- for (int i = from; i < to; i++) {
- if (seed[i] != 0) {
- return;
- }
- }
- // Fill with non-zero values using a SplitMix-style PRNG.
- // The range is at least 1.
- // To ensure the first value is not zero requires the input to the mix function
- // to be non-zero. This is ensured if the start is even since the increment is odd.
- long x = createLong() << 1;
- for (int i = from; i < to; i++) {
- x += MixFunctions.GOLDEN_RATIO_64;
- seed[i] = MixFunctions.stafford13(x);
- }
- }
- /**
- * Ensure the seed is not all-zero within the specified sub-range.
- *
- * <p>This method will check the sub-range and if all are zero it will fill the range
- * with a random sequence seeded from the provided source of randomness. If the range
- * length is above 8 then the first 8 bytes in the range are ensured to not all be
- * zero. If the range length is below 8 then the first byte in the range is ensured to
- * be non-zero. The method ensures any length sub-range contains non-zero bits. The
- * output seed is suitable for generators that cannot be seeded with all zeros in the
- * specified sub-range.</p>
- *
- * @param seed Seed array (modified in place).
- * @param from The start of the not all-zero sub-range (inclusive).
- * @param to The end of the not all-zero sub-range (exclusive).
- * @param source Source of randomness.
- * @throws IndexOutOfBoundsException if the sub-range is invalid
- */
- static void ensureNonZero(byte[] seed, int from, int to, UniformRandomProvider source) {
- if (from >= to) {
- return;
- }
- // No check on the range so an IndexOutOfBoundsException will occur if invalid
- for (int i = from; i < to; i++) {
- if (seed[i] != 0) {
- return;
- }
- }
- // Defend against a faulty source of randomness (which supplied all zero bytes)
- // by filling with non-zero values using a SplitMix-style PRNG seeded from the source.
- // The range is at least 1.
- // To ensure the first value is not zero requires the input to the mix function
- // to be non-zero. This is ensured if the start is even since the increment is odd.
- long x = source.nextLong() << 1;
- // Process in blocks of 8.
- // Get the length without the final 3 bits set for a multiple of 8.
- final int len = (to - from) & ~0x7;
- final int end = from + len;
- int i = from;
- while (i < end) {
- long v = MixFunctions.stafford13(x += MixFunctions.GOLDEN_RATIO_64);
- for (int j = 0; j < 8; j++) {
- seed[i++] = (byte) v;
- v >>>= 8;
- }
- }
- if (i < to) {
- // The final bytes.
- long v = MixFunctions.stafford13(x + MixFunctions.GOLDEN_RATIO_64);
- // Note the special case where no blocks have been processed requires these
- // bytes to be non-zero, i.e. (to - from) < 8. In this case the value 'v' will
- // be non-zero due to the initialisation of 'x' as even.
- // Rotate the value so the least significant byte is non-zero. The rotation
- // in bits is rounded down to the nearest 8-bit block to ensure a byte rotation.
- if (len == 0) {
- v = Long.rotateRight(v, Long.numberOfTrailingZeros(v) & ~0x7);
- }
- while (i < to) {
- seed[i++] = (byte) v;
- v >>>= 8;
- }
- }
- }
- /**
- * Ensure the value is non-zero.
- *
- * <p>This method will replace a zero with a non-zero random number from the random source.</p>
- *
- * @param source Source of randomness.
- * @param value Value.
- * @return {@code value} if non-zero; else a new random number
- */
- static long ensureNonZero(RandomLongSource source,
- long value) {
- long result = value;
- while (result == 0) {
- result = source.next();
- }
- return result;
- }
- }