aherbert commented on code in PR #191: URL: https://github.com/apache/commons-rng/pull/191#discussion_r2779757050
########## commons-rng-core/src/main/java/org/apache/commons/rng/core/source32/Philox4x32.java: ########## @@ -0,0 +1,431 @@ +/* + * 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.source32; + +import org.apache.commons.rng.JumpableUniformRandomProvider; +import org.apache.commons.rng.LongJumpableUniformRandomProvider; +import org.apache.commons.rng.UniformRandomProvider; +import org.apache.commons.rng.core.util.NumberFactory; + +/** + * This class implements the Philox4x32 128-bit counter-based generator with 10 rounds. + * Jumping in the sequence is essentially instantaneous. This generator provides subsequences for easy parallelization. + * + * @see <a href="https://www.thesalmons.org/john/random123/papers/random123sc11.pdf">Parallel Random Numbers: As Easy as 1,2,3</a> + * for details regarding the engine. + * @since 1.7 + */ +public final class Philox4x32 extends IntProvider implements LongJumpableUniformRandomProvider { + /** + * Philox 32-bit mixing constant for counter 0. + */ + private static final int K_PHILOX_10_A = 0x9E3779B9; + /** + * Philox 32-bit mixing constant for counter 1. + */ + private static final int K_PHILOX_10_B = 0xBB67AE85; + /** + * Philox 32-bit constant for key 0. + */ + private static final int K_PHILOX_SA = 0xD2511F53; + /** + * Philox 32-bit constant for key 1. + */ + private static final int K_PHILOX_SB = 0xCD9E8D57; + /** + * Internal buffer size. + */ + private static final int PHILOX_BUFFER_SIZE = 4; + /** + * number of int variables. + */ + private static final int STATE_SIZE = 7; + + /** + * Counter 0. + */ + private int counter0; + /** + * Counter 1. + */ + private int counter1; + /** + * Counter 2. + */ + private int counter2; + /** + * Counter 3. + */ + private int counter3; + /** + * Output point. + */ + private int[] buffer = new int[PHILOX_BUFFER_SIZE]; // UINT4 + /** + * Key low bits. + */ + private int key0; + /** + * Key high bits. + */ + private int key1; + /** + * State index: which output word is next (0..3). + */ + private int bufferPosition; + + + /** + * Copy constructor. + * + * @param source Source to copy. + */ + private Philox4x32(Philox4x32 source) { + super(source); + counter0 = source.counter0; + counter1 = source.counter1; + counter2 = source.counter2; + counter3 = source.counter3; + key0 = source.key0; + key1 = source.key1; + bufferPosition = source.bufferPosition; + buffer = source.buffer.clone(); + } + + /** + * Creates a new instance with default seed. Subsequence and offset are set to zero. + */ + public Philox4x32() { + this(67280421310721L, 0L, 0L); + } + + /** + * Creates a new instance with given seed. Subsequence and offset are set to zero. + * + * @param seed Initial seed. + */ + public Philox4x32(long seed) { + this(seed, 0L, 0L); + } + + /** + * Creates a new instance. Offset and subsequence determine the internal counter of Philox. + * + * @param seed Initial seed. + * @param subsequence a subsequence index + * @param offset an offset, zero for the first number. + */ + public Philox4x32(long seed, long subsequence, long offset) { + resetState(seed, subsequence); + incrementCounter(offset); + } + + /** + * Creates a new instance based on an array of int containing, seed, subsequence and offset. + * + * @param seed key0,key1,counter0,counter1,counter2,counter3. + */ + @SuppressWarnings("PMD.AvoidLiteralsInIfCondition") + public Philox4x32(int... seed) { + key0 = seed[0]; + if (seed.length > 1) { + key1 = seed[1]; + } else { + key1 = 0; + } + if (seed.length > 2) { + counter0 = seed[2]; + } else { + counter0 = 0; + } + if (seed.length > 3) { + counter1 = seed[3]; + } else { + counter1 = 0; + } + if (seed.length > 4) { + counter2 = seed[4]; + } else { + counter2 = 0; + } + if (seed.length > 5) { + counter3 = seed[5]; + } else { + counter3 = 0; + } + bufferPosition = PHILOX_BUFFER_SIZE; + } + + /** + * Resets the key, counter and state index. + * + * @param seed key for Philox + * @param subsequence counter third and fourth ints. + */ + public void resetState(long seed, long subsequence) { Review Comment: The `resetState`, `setOffset` and `getOffset` method are unique to this class and do not fit within the current API of the RNGs in the library. I am not sure if this support is required other than to demonstrate the arbitrary jumping ability of the RNG. In the case of the `jump(long n)` method it may require some thought for a new addition to the `JumpableUniformRandomProvider` interface for configurable jump size. ########## commons-rng-core/src/main/java/org/apache/commons/rng/core/source32/Philox4x32.java: ########## @@ -0,0 +1,431 @@ +/* + * 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.source32; + +import org.apache.commons.rng.JumpableUniformRandomProvider; +import org.apache.commons.rng.LongJumpableUniformRandomProvider; +import org.apache.commons.rng.UniformRandomProvider; +import org.apache.commons.rng.core.util.NumberFactory; + +/** + * This class implements the Philox4x32 128-bit counter-based generator with 10 rounds. + * Jumping in the sequence is essentially instantaneous. This generator provides subsequences for easy parallelization. + * + * @see <a href="https://www.thesalmons.org/john/random123/papers/random123sc11.pdf">Parallel Random Numbers: As Easy as 1,2,3</a> + * for details regarding the engine. + * @since 1.7 + */ +public final class Philox4x32 extends IntProvider implements LongJumpableUniformRandomProvider { + /** + * Philox 32-bit mixing constant for counter 0. + */ + private static final int K_PHILOX_10_A = 0x9E3779B9; + /** + * Philox 32-bit mixing constant for counter 1. + */ + private static final int K_PHILOX_10_B = 0xBB67AE85; + /** + * Philox 32-bit constant for key 0. + */ + private static final int K_PHILOX_SA = 0xD2511F53; + /** + * Philox 32-bit constant for key 1. + */ + private static final int K_PHILOX_SB = 0xCD9E8D57; + /** + * Internal buffer size. + */ + private static final int PHILOX_BUFFER_SIZE = 4; + /** + * number of int variables. + */ + private static final int STATE_SIZE = 7; + + /** + * Counter 0. + */ + private int counter0; + /** + * Counter 1. + */ + private int counter1; + /** + * Counter 2. + */ + private int counter2; + /** + * Counter 3. + */ + private int counter3; + /** + * Output point. + */ + private int[] buffer = new int[PHILOX_BUFFER_SIZE]; // UINT4 + /** + * Key low bits. + */ + private int key0; + /** + * Key high bits. + */ + private int key1; + /** + * State index: which output word is next (0..3). + */ + private int bufferPosition; + + + /** + * Copy constructor. + * + * @param source Source to copy. + */ + private Philox4x32(Philox4x32 source) { + super(source); + counter0 = source.counter0; + counter1 = source.counter1; + counter2 = source.counter2; + counter3 = source.counter3; + key0 = source.key0; + key1 = source.key1; + bufferPosition = source.bufferPosition; + buffer = source.buffer.clone(); + } + + /** + * Creates a new instance with default seed. Subsequence and offset are set to zero. + */ + public Philox4x32() { + this(67280421310721L, 0L, 0L); + } + + /** + * Creates a new instance with given seed. Subsequence and offset are set to zero. + * + * @param seed Initial seed. + */ + public Philox4x32(long seed) { + this(seed, 0L, 0L); + } + + /** + * Creates a new instance. Offset and subsequence determine the internal counter of Philox. + * + * @param seed Initial seed. + * @param subsequence a subsequence index + * @param offset an offset, zero for the first number. + */ + public Philox4x32(long seed, long subsequence, long offset) { + resetState(seed, subsequence); + incrementCounter(offset); + } + + /** + * Creates a new instance based on an array of int containing, seed, subsequence and offset. + * + * @param seed key0,key1,counter0,counter1,counter2,counter3. + */ + @SuppressWarnings("PMD.AvoidLiteralsInIfCondition") + public Philox4x32(int... seed) { + key0 = seed[0]; + if (seed.length > 1) { + key1 = seed[1]; + } else { + key1 = 0; + } + if (seed.length > 2) { + counter0 = seed[2]; + } else { + counter0 = 0; + } + if (seed.length > 3) { + counter1 = seed[3]; + } else { + counter1 = 0; + } + if (seed.length > 4) { + counter2 = seed[4]; + } else { + counter2 = 0; + } + if (seed.length > 5) { + counter3 = seed[5]; + } else { + counter3 = 0; + } + bufferPosition = PHILOX_BUFFER_SIZE; + } + + /** + * Resets the key, counter and state index. + * + * @param seed key for Philox + * @param subsequence counter third and fourth ints. + */ + public void resetState(long seed, long subsequence) { + key0 = (int) seed; + key1 = (int) (seed >>> 32); + + counter0 = 0; + counter1 = 0; + counter2 = (int) subsequence; + counter3 = (int) (subsequence >>> 32); + + bufferPosition = PHILOX_BUFFER_SIZE; + } + + /** + * Set the first two ints of the internal counter of Philox. + * + * @param offset an offet. An offet of 1 corresponds to the 4th number of the sequence. + */ + public void setOffset(long offset) { + counter0 = (int) offset; + counter1 = (int) (offset >>> 32); + bufferPosition = PHILOX_BUFFER_SIZE; + } + + /** + * Returns the offset, that is the first two ints of the internal counter as a long. + * + * @return the first two ints of the internal counter of Philox as long. + */ + public long getOffset() { + final long lo = counter0 & 0xFFFFFFFFL; + final long hi = (counter1 & 0xFFFFFFFFL) << 32; + return lo | hi; + } + + /** + * Fetch next integer from the buffer, or regenerate the buffer using 10 rounds. + * + * @return random integer + */ + @Override + public int next() { + if (bufferPosition < PHILOX_BUFFER_SIZE) { + return buffer[bufferPosition++]; + } + incrementCounter(); + rand10(); + bufferPosition = 1; + return buffer[0]; + } + + /** + * Counter increment. + * + * @param n how many steps to increment + */ + private void incrementCounter(long n) { + final int nlo = (int) n; + int nhi = (int) (n >>> 32); + + int old = counter0; + counter0 += nlo; + if (Integer.compareUnsigned(counter0, old) < 0) { + nhi++; + } + + old = counter1; + counter1 += nhi; + if (Integer.compareUnsigned(counter1, old) < 0 && ++counter2 == 0) { + counter3++; + } + } + + /** + * Increment by one. + */ + private void incrementCounter() { + counter0++; + if (counter0 != 0) { + return; + } + + counter1++; + if (counter1 != 0) { + return; + } + + counter2++; + if (counter2 != 0) { + return; + } + + counter3++; + } + + /** + * Performs a single round of philox. + * + * @param ctr local counter, which will be updated after each call. + * @param key0 key low bits + * @param key1 key high bits + */ + private static void singleRound(int[] ctr, int key0, int key1) { + long product = (K_PHILOX_SA & 0xFFFFFFFFL) * (ctr[0] & 0xFFFFFFFFL); + final int hi0 = (int) (product >>> 32); + final int lo0 = (int) product; + product = (K_PHILOX_SB & 0xFFFFFFFFL) * (ctr[2] & 0xFFFFFFFFL); + final int hi1 = (int) (product >>> 32); + final int lo1 = (int) product; + + ctr[0] = hi1 ^ ctr[1] ^ key0; + ctr[1] = lo1; + ctr[2] = hi0 ^ ctr[3] ^ key1; + ctr[3] = lo0; + } + + /** + * Perform 10 rounds, using counter0, counter1, counter2, counter3 as starting point. + * + */ + private void rand10() { + buffer[0] = counter0; + buffer[1] = counter1; + buffer[2] = counter2; + buffer[3] = counter3; + + int k0 = key0; + int k1 = key1; + + //unrolled loop for performance + singleRound(buffer, k0, k1); + k0 += K_PHILOX_10_A; + k1 += K_PHILOX_10_B; + singleRound(buffer, k0, k1); + k0 += K_PHILOX_10_A; + k1 += K_PHILOX_10_B; + singleRound(buffer, k0, k1); + k0 += K_PHILOX_10_A; + k1 += K_PHILOX_10_B; + singleRound(buffer, k0, k1); + k0 += K_PHILOX_10_A; + k1 += K_PHILOX_10_B; + singleRound(buffer, k0, k1); + k0 += K_PHILOX_10_A; + k1 += K_PHILOX_10_B; + singleRound(buffer, k0, k1); + k0 += K_PHILOX_10_A; + k1 += K_PHILOX_10_B; + singleRound(buffer, k0, k1); + k0 += K_PHILOX_10_A; + k1 += K_PHILOX_10_B; + singleRound(buffer, k0, k1); + k0 += K_PHILOX_10_A; + k1 += K_PHILOX_10_B; + singleRound(buffer, k0, k1); + k0 += K_PHILOX_10_A; + k1 += K_PHILOX_10_B; + singleRound(buffer, k0, k1); + } + + + /** + * {@inheritDoc} + * + * <p>Increments the subsequence by 1.</p> + * <p>The jump size is the equivalent of 4*2<sup>64</sup> calls to + * {@link UniformRandomProvider#nextInt() nextInt()}. + */ + @Override + public JumpableUniformRandomProvider longJump() { + final Philox4x32 copy = copy(); + if (++counter2 == 0) { + counter3++; + } + rand10(); + return copy; + } + + /** + * {@inheritDoc} + * + * <p>The jump size is the equivalent of 4*2<sup>32</sup> + * calls to {@link UniformRandomProvider#nextInt() nextInt()}. + */ + @Override + public UniformRandomProvider jump() { + final Philox4x32 copy = copy(); + incrementCounter(1L << 32); + rand10(); + resetCachedState(); + return copy; + } + + /** + * jump by n numbers in the sequence. This is equivalent to + * calling nextInt() n times. + * + * @param n the length to jump. + * @return a copy of the original random number generator. + */ + public UniformRandomProvider jump(long n) { + final Philox4x32 copy = copy(); + final long ndiv4 = (n + bufferPosition) / 4; Review Comment: I am not sure this will work if `n` is negative. This is not an unsigned long but the code is working as if it is. Also if `n` is close to overflowing 2^64 and does so by adding `bufferPosition` then the code should still call `incrementCounter` with 2^16. ########## commons-rng-core/src/main/java/org/apache/commons/rng/core/source64/Philox4x64.java: ########## @@ -0,0 +1,446 @@ +/* + * 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.source64; + +import org.apache.commons.rng.JumpableUniformRandomProvider; +import org.apache.commons.rng.LongJumpableUniformRandomProvider; +import org.apache.commons.rng.UniformRandomProvider; +import org.apache.commons.rng.core.util.NumberFactory; + +/** + * This class implements the Philox4x64 128-bit counter-based generator with 10 rounds. + * Jumping in the sequence is essentially instantaneous. This generator provides subsequences for easy parallelization. + * + * @see <a href="https://www.thesalmons.org/john/random123/papers/random123sc11.pdf">Parallel Random Numbers: As Easy as 1,2,3</a> + * for details regarding the engine. + * @since 1.7 + */ +public final class Philox4x64 extends LongProvider implements LongJumpableUniformRandomProvider { + /** + * Philox 32-bit mixing constant for counter 0. + */ + private static final long PHILOX_M0 = 0xD2E7470EE14C6C93L; + /** + * Philox 32-bit mixing constant for counter 1. + */ + private static final long PHILOX_M1 = 0xCA5A826395121157L; + /** + * Philox 32-bit constant for key 0. + */ + private static final long PHILOX_W0 = 0x9E3779B97F4A7C15L; + /** + * Philox 32-bit constant for key 1. + */ + private static final long PHILOX_W1 = 0xBB67AE8584CAA73BL; + /** + * Internal buffer size. + */ + private static final int PHILOX_BUFFER_SIZE = 4; + /** + * number of long variables. + */ + private static final int STATE_SIZE = 7; + + /** + * Counter 0. + */ + private long counter0; + /** + * Counter 1. + */ + private long counter1; + /** + * Counter 2. + */ + private long counter2; + /** + * Counter 3. + */ + private long counter3; + + /** + * Output point. + */ + private long[] buffer = new long[PHILOX_BUFFER_SIZE]; // UINT4 + /** + * Key low bits. + */ + private long key0; + /** + * Key high bits. + */ + private long key1; + /** + * State index: which output word is next (0..3). + */ + private int bufferPosition; + + /** + * Copy constructor. + * + * @param source Source to copy. + */ + private Philox4x64(Philox4x64 source) { + super(source); + counter0 = source.counter0; + counter1 = source.counter1; + counter2 = source.counter2; + counter3 = source.counter3; + key0 = source.key0; + key1 = source.key1; + bufferPosition = source.bufferPosition; + buffer = source.buffer.clone(); + } + + /** + * Creates a new instance with default seed. Subsequence and offset are set to zero. + */ + public Philox4x64() { + this(67280421310721L, 0x9E3779B97F4A7C15L, 0L, 0L, 0L, 0L); + } + + /** + * Creates a new instance. Offset and subsequence determine the internal counter of Philox. + * + * @param seed Initial seed. + * @param subsequence a subsequence index + * @param offset an offset, zero for the first number. + */ + public Philox4x64(long[] seed, long[] subsequence, long[] offset) { + resetState(seed, subsequence); + incrementCounter(offset); + } + + /** + * Creates a new instance given 6 long numbers. + * + * @param keyAndCounter the first two number are the key and the next 4 number are the counter. + */ + @SuppressWarnings("PMD.AvoidLiteralsInIfCondition") + public Philox4x64(long... keyAndCounter) { + key0 = keyAndCounter[0]; + if (keyAndCounter.length > 1) { + key1 = keyAndCounter[1]; + } else { + key1 = 0; + } + if (keyAndCounter.length > 2) { + counter0 = keyAndCounter[2]; + } else { + counter0 = 0; + } + if (keyAndCounter.length > 3) { + counter1 = keyAndCounter[3]; + } else { + counter1 = 0; + } + if (keyAndCounter.length > 4) { + counter2 = keyAndCounter[4]; + } else { + counter2 = 0; + } + if (keyAndCounter.length > 5) { + counter3 = keyAndCounter[5]; + } else { + counter3 = 0; + } + bufferPosition = PHILOX_BUFFER_SIZE; + } + + /** + * Resets the key, counter and state index. + * + * @param seed key for Philox + * @param subsequence counter third and fourth long. + */ + public void resetState(long[] seed, long[] subsequence) { + key0 = seed[0]; + key1 = seed[1]; + + counter0 = 0; + counter1 = 0; + counter2 = subsequence[0]; + counter3 = subsequence[1]; + + bufferPosition = PHILOX_BUFFER_SIZE; + } + + /** + * Set the first two ints of the internal counter of Philox. + * + * @param offset an offset as an array of size 2. An offet of 1 corresponds to the 4th number of the sequence. + */ + public void setOffset(long... offset) { + counter0 = offset[0]; + if (offset.length > 1) { + counter1 = offset[1]; + } else { + counter1 = 0; + } + bufferPosition = PHILOX_BUFFER_SIZE; + } + + /** + * Returns the offset, that is the first two ints of the internal counter as a long. + * + * @return the first two ints of the internal counter of Philox as long. + */ + public long[] getOffset() { + return new long[]{counter0, counter1}; + } + + /** + * Fetch next long from the buffer, or regenerate the buffer using 10 rounds. + * + * @return random 64-bit integer + */ + private long next64() { + if (bufferPosition < PHILOX_BUFFER_SIZE) { + return buffer[bufferPosition++]; + } + incrementCounter(); + rand10(); + bufferPosition = 1; + return buffer[0]; + } + + /** + * Counter increment. + * + * @param step a long array of size 4 representing how many steps to increment + */ + private void incrementCounter(long... step) { + int carry = 0; + + long orig = counter0; + counter0 += step[0]; + if (Long.compareUnsigned(counter0, orig) < 0) { + carry = 1; + } + + if (carry == 1) { + counter1++; + carry = (counter1 == 0) ? 1 : 0; + } + orig = counter1; + if (step.length > 1) { + counter1 += step[1]; + } + if (Long.compareUnsigned(counter1, orig) < 0) { + carry = 1; + } + + if (carry == 1) { + counter2++; + carry = (counter2 == 0) ? 1 : 0; + } + + if (carry == 1) { + counter3++; + } + } + + /** + * Increment by one. + */ + private void incrementCounter() { + counter0++; + if (counter0 != 0) { + return; + } + + counter1++; + if (counter1 != 0) { + return; + } + + counter2++; + if (counter2 != 0) { + return; + } + + counter3++; + } + + /** + * Performs a single round of philox. + * + * @param counter local counter, which will be updated after each call. + * @param key0 key low bits + * @param key1 key high bits + */ + private static void singleRound(long[] counter, long key0, long key1) { + final long lo0 = PHILOX_M0 * counter[0]; + final long hi0 = LXMSupport.unsignedMultiplyHigh(PHILOX_M0, counter[0]); + final long lo1 = PHILOX_M1 * counter[2]; + final long hi1 = LXMSupport.unsignedMultiplyHigh(PHILOX_M1, counter[2]); + + counter[0] = hi1 ^ counter[1] ^ key0; + counter[1] = lo1; + counter[2] = hi0 ^ counter[3] ^ key1; + counter[3] = lo0; + } + + /** + * Perform 10 rounds, using counter0, counter1, counter2, counter3 as starting point. + * + */ + private void rand10() { + buffer[0] = counter0; + buffer[1] = counter1; + buffer[2] = counter2; + buffer[3] = counter3; + + long k0 = key0; + long k1 = key1; + + //unrolled loop for performance + singleRound(buffer, k0, k1); + k0 += PHILOX_W0; + k1 += PHILOX_W1; + singleRound(buffer, k0, k1); + k0 += PHILOX_W0; + k1 += PHILOX_W1; + singleRound(buffer, k0, k1); + k0 += PHILOX_W0; + k1 += PHILOX_W1; + singleRound(buffer, k0, k1); + k0 += PHILOX_W0; + k1 += PHILOX_W1; + singleRound(buffer, k0, k1); + k0 += PHILOX_W0; + k1 += PHILOX_W1; + singleRound(buffer, k0, k1); + k0 += PHILOX_W0; + k1 += PHILOX_W1; + singleRound(buffer, k0, k1); + k0 += PHILOX_W0; + k1 += PHILOX_W1; + singleRound(buffer, k0, k1); + k0 += PHILOX_W0; + k1 += PHILOX_W1; + singleRound(buffer, k0, k1); + k0 += PHILOX_W0; + k1 += PHILOX_W1; + singleRound(buffer, k0, k1); + } + Review Comment: Double line break ########## commons-rng-core/src/main/java/org/apache/commons/rng/core/source32/Philox4x32.java: ########## @@ -0,0 +1,431 @@ +/* + * 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.source32; + +import org.apache.commons.rng.JumpableUniformRandomProvider; +import org.apache.commons.rng.LongJumpableUniformRandomProvider; +import org.apache.commons.rng.UniformRandomProvider; +import org.apache.commons.rng.core.util.NumberFactory; + +/** + * This class implements the Philox4x32 128-bit counter-based generator with 10 rounds. + * Jumping in the sequence is essentially instantaneous. This generator provides subsequences for easy parallelization. + * + * @see <a href="https://www.thesalmons.org/john/random123/papers/random123sc11.pdf">Parallel Random Numbers: As Easy as 1,2,3</a> + * for details regarding the engine. + * @since 1.7 + */ +public final class Philox4x32 extends IntProvider implements LongJumpableUniformRandomProvider { + /** + * Philox 32-bit mixing constant for counter 0. + */ + private static final int K_PHILOX_10_A = 0x9E3779B9; + /** + * Philox 32-bit mixing constant for counter 1. + */ + private static final int K_PHILOX_10_B = 0xBB67AE85; + /** + * Philox 32-bit constant for key 0. + */ + private static final int K_PHILOX_SA = 0xD2511F53; + /** + * Philox 32-bit constant for key 1. + */ + private static final int K_PHILOX_SB = 0xCD9E8D57; + /** + * Internal buffer size. + */ + private static final int PHILOX_BUFFER_SIZE = 4; + /** + * number of int variables. + */ + private static final int STATE_SIZE = 7; + + /** + * Counter 0. + */ + private int counter0; + /** + * Counter 1. + */ + private int counter1; + /** + * Counter 2. + */ + private int counter2; + /** + * Counter 3. + */ + private int counter3; + /** + * Output point. + */ + private int[] buffer = new int[PHILOX_BUFFER_SIZE]; // UINT4 + /** + * Key low bits. + */ + private int key0; + /** + * Key high bits. + */ + private int key1; + /** + * State index: which output word is next (0..3). + */ + private int bufferPosition; + + + /** + * Copy constructor. + * + * @param source Source to copy. + */ + private Philox4x32(Philox4x32 source) { + super(source); + counter0 = source.counter0; + counter1 = source.counter1; + counter2 = source.counter2; + counter3 = source.counter3; + key0 = source.key0; + key1 = source.key1; + bufferPosition = source.bufferPosition; + buffer = source.buffer.clone(); + } + + /** + * Creates a new instance with default seed. Subsequence and offset are set to zero. + */ + public Philox4x32() { + this(67280421310721L, 0L, 0L); + } + + /** + * Creates a new instance with given seed. Subsequence and offset are set to zero. + * + * @param seed Initial seed. + */ + public Philox4x32(long seed) { + this(seed, 0L, 0L); + } + + /** + * Creates a new instance. Offset and subsequence determine the internal counter of Philox. + * + * @param seed Initial seed. + * @param subsequence a subsequence index + * @param offset an offset, zero for the first number. + */ + public Philox4x32(long seed, long subsequence, long offset) { + resetState(seed, subsequence); + incrementCounter(offset); + } + + /** + * Creates a new instance based on an array of int containing, seed, subsequence and offset. + * + * @param seed key0,key1,counter0,counter1,counter2,counter3. + */ + @SuppressWarnings("PMD.AvoidLiteralsInIfCondition") + public Philox4x32(int... seed) { + key0 = seed[0]; + if (seed.length > 1) { + key1 = seed[1]; + } else { + key1 = 0; + } + if (seed.length > 2) { + counter0 = seed[2]; + } else { + counter0 = 0; + } + if (seed.length > 3) { + counter1 = seed[3]; + } else { + counter1 = 0; + } + if (seed.length > 4) { + counter2 = seed[4]; + } else { + counter2 = 0; + } + if (seed.length > 5) { + counter3 = seed[5]; + } else { + counter3 = 0; + } + bufferPosition = PHILOX_BUFFER_SIZE; + } + + /** + * Resets the key, counter and state index. + * + * @param seed key for Philox + * @param subsequence counter third and fourth ints. + */ + public void resetState(long seed, long subsequence) { + key0 = (int) seed; + key1 = (int) (seed >>> 32); + + counter0 = 0; + counter1 = 0; + counter2 = (int) subsequence; + counter3 = (int) (subsequence >>> 32); + + bufferPosition = PHILOX_BUFFER_SIZE; + } + + /** + * Set the first two ints of the internal counter of Philox. + * + * @param offset an offet. An offet of 1 corresponds to the 4th number of the sequence. + */ + public void setOffset(long offset) { + counter0 = (int) offset; + counter1 = (int) (offset >>> 32); + bufferPosition = PHILOX_BUFFER_SIZE; + } + + /** + * Returns the offset, that is the first two ints of the internal counter as a long. + * + * @return the first two ints of the internal counter of Philox as long. + */ + public long getOffset() { + final long lo = counter0 & 0xFFFFFFFFL; + final long hi = (counter1 & 0xFFFFFFFFL) << 32; + return lo | hi; + } + + /** + * Fetch next integer from the buffer, or regenerate the buffer using 10 rounds. + * + * @return random integer + */ + @Override + public int next() { + if (bufferPosition < PHILOX_BUFFER_SIZE) { Review Comment: There are some strange performance issues on JVMs with the use of post increments. This is something to be avoided when working with arrays if the same thing can be achieved with a pre-increment counter (offset by 1 for the range checks) or use of a temp counter. I wonder if this would be more performant: ```java final int p = bufferPosition; if (p < PHILOX_BUFFER_SIZE) { bufferPosition = p + 1; return buffer[p]; } ``` ########## commons-rng-core/src/main/java/org/apache/commons/rng/core/util/NumberFactory.java: ########## @@ -88,6 +98,18 @@ public static double makeDouble(long v) { return (v >>> 11) * DOUBLE_MULTIPLIER; } + /** + * Creates a {@code double} in the open interval {@code (0, 1)} from a {@code long} value. + * + * @param v Number. + * @return a {@code double} value in the open interval {@code (0, 1)}. + * @since 1.7 + */ + public static double makeDoubleOpen(long v) { + // Require the least significant 53-bits so shift the higher bits across + return ((v >>> 12) + 0.5) * DOUBLE_OPEN_MULTIPLIER; Review Comment: The comment refers to 53-bits but you shift 12. Could this be changed with the same effect to: ```java return ((v >>> 11) | 1L) * DOUBLE_MULTIPLIER; ``` Essentially calling nextDouble() but removing 1 bit of randomness and avoiding creating a zero. There is code within some of the sampling module that uses a semi-open interval of `(0, 1]` with: ``` return ((v >>> 11) + 1L) * DOUBLE_MULTIPLIER ``` I think addition of these methods could be discussed as a separate PR, either to add into the `UniformRandomProvider` interface or to add to a sampler in the sampling module. For now this can be removed from this PR and the two changed are different functionality. ########## commons-rng-examples/examples-jmh/src/main/java/org/apache/commons/rng/examples/jmh/simple/ConstructionPerformance.java: ########## @@ -398,6 +405,7 @@ private static int findNativeSeedElementByteSize(RandomSource randomSource) { case XO_RO_SHI_RO_64_SS: case XO_SHI_RO_128_PLUS: case XO_SHI_RO_128_SS: + case PHILOX_4X32: Review Comment: IIUC this should be 6 for the native seed size. ########## commons-rng-core/src/main/java/org/apache/commons/rng/core/source32/Philox4x32.java: ########## @@ -0,0 +1,431 @@ +/* + * 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.source32; + +import org.apache.commons.rng.JumpableUniformRandomProvider; +import org.apache.commons.rng.LongJumpableUniformRandomProvider; +import org.apache.commons.rng.UniformRandomProvider; +import org.apache.commons.rng.core.util.NumberFactory; + +/** + * This class implements the Philox4x32 128-bit counter-based generator with 10 rounds. + * Jumping in the sequence is essentially instantaneous. This generator provides subsequences for easy parallelization. + * + * @see <a href="https://www.thesalmons.org/john/random123/papers/random123sc11.pdf">Parallel Random Numbers: As Easy as 1,2,3</a> + * for details regarding the engine. + * @since 1.7 + */ +public final class Philox4x32 extends IntProvider implements LongJumpableUniformRandomProvider { + /** + * Philox 32-bit mixing constant for counter 0. + */ + private static final int K_PHILOX_10_A = 0x9E3779B9; + /** + * Philox 32-bit mixing constant for counter 1. + */ + private static final int K_PHILOX_10_B = 0xBB67AE85; + /** + * Philox 32-bit constant for key 0. + */ + private static final int K_PHILOX_SA = 0xD2511F53; + /** + * Philox 32-bit constant for key 1. + */ + private static final int K_PHILOX_SB = 0xCD9E8D57; + /** + * Internal buffer size. + */ + private static final int PHILOX_BUFFER_SIZE = 4; + /** + * number of int variables. + */ + private static final int STATE_SIZE = 7; + + /** + * Counter 0. + */ + private int counter0; + /** + * Counter 1. + */ + private int counter1; + /** + * Counter 2. + */ + private int counter2; + /** + * Counter 3. + */ + private int counter3; + /** + * Output point. + */ + private int[] buffer = new int[PHILOX_BUFFER_SIZE]; // UINT4 + /** + * Key low bits. + */ + private int key0; + /** + * Key high bits. + */ + private int key1; + /** + * State index: which output word is next (0..3). + */ + private int bufferPosition; + + + /** + * Copy constructor. + * + * @param source Source to copy. + */ + private Philox4x32(Philox4x32 source) { + super(source); + counter0 = source.counter0; + counter1 = source.counter1; + counter2 = source.counter2; + counter3 = source.counter3; + key0 = source.key0; + key1 = source.key1; + bufferPosition = source.bufferPosition; + buffer = source.buffer.clone(); + } + + /** + * Creates a new instance with default seed. Subsequence and offset are set to zero. + */ + public Philox4x32() { + this(67280421310721L, 0L, 0L); + } + + /** + * Creates a new instance with given seed. Subsequence and offset are set to zero. + * + * @param seed Initial seed. + */ + public Philox4x32(long seed) { + this(seed, 0L, 0L); + } + + /** + * Creates a new instance. Offset and subsequence determine the internal counter of Philox. + * + * @param seed Initial seed. + * @param subsequence a subsequence index + * @param offset an offset, zero for the first number. + */ + public Philox4x32(long seed, long subsequence, long offset) { + resetState(seed, subsequence); + incrementCounter(offset); + } + + /** + * Creates a new instance based on an array of int containing, seed, subsequence and offset. + * + * @param seed key0,key1,counter0,counter1,counter2,counter3. + */ + @SuppressWarnings("PMD.AvoidLiteralsInIfCondition") + public Philox4x32(int... seed) { + key0 = seed[0]; + if (seed.length > 1) { + key1 = seed[1]; + } else { + key1 = 0; + } + if (seed.length > 2) { + counter0 = seed[2]; + } else { + counter0 = 0; + } + if (seed.length > 3) { + counter1 = seed[3]; + } else { + counter1 = 0; + } + if (seed.length > 4) { + counter2 = seed[4]; + } else { + counter2 = 0; + } + if (seed.length > 5) { + counter3 = seed[5]; + } else { + counter3 = 0; + } + bufferPosition = PHILOX_BUFFER_SIZE; + } + + /** + * Resets the key, counter and state index. + * + * @param seed key for Philox + * @param subsequence counter third and fourth ints. + */ + public void resetState(long seed, long subsequence) { + key0 = (int) seed; + key1 = (int) (seed >>> 32); + + counter0 = 0; + counter1 = 0; + counter2 = (int) subsequence; + counter3 = (int) (subsequence >>> 32); + + bufferPosition = PHILOX_BUFFER_SIZE; + } + + /** + * Set the first two ints of the internal counter of Philox. + * + * @param offset an offet. An offet of 1 corresponds to the 4th number of the sequence. + */ + public void setOffset(long offset) { + counter0 = (int) offset; + counter1 = (int) (offset >>> 32); + bufferPosition = PHILOX_BUFFER_SIZE; + } + + /** + * Returns the offset, that is the first two ints of the internal counter as a long. + * + * @return the first two ints of the internal counter of Philox as long. + */ + public long getOffset() { + final long lo = counter0 & 0xFFFFFFFFL; + final long hi = (counter1 & 0xFFFFFFFFL) << 32; + return lo | hi; + } + + /** + * Fetch next integer from the buffer, or regenerate the buffer using 10 rounds. + * + * @return random integer + */ + @Override + public int next() { + if (bufferPosition < PHILOX_BUFFER_SIZE) { + return buffer[bufferPosition++]; + } + incrementCounter(); + rand10(); + bufferPosition = 1; + return buffer[0]; + } + + /** + * Counter increment. + * + * @param n how many steps to increment + */ + private void incrementCounter(long n) { + final int nlo = (int) n; + int nhi = (int) (n >>> 32); + + int old = counter0; + counter0 += nlo; + if (Integer.compareUnsigned(counter0, old) < 0) { + nhi++; Review Comment: If `nhi` is `-1` (all bits set) then this increment will not be carried to `counter2`. ########## commons-rng-core/src/main/java/org/apache/commons/rng/core/source32/Philox4x32.java: ########## @@ -0,0 +1,431 @@ +/* + * 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.source32; + +import org.apache.commons.rng.JumpableUniformRandomProvider; +import org.apache.commons.rng.LongJumpableUniformRandomProvider; +import org.apache.commons.rng.UniformRandomProvider; +import org.apache.commons.rng.core.util.NumberFactory; + +/** + * This class implements the Philox4x32 128-bit counter-based generator with 10 rounds. + * Jumping in the sequence is essentially instantaneous. This generator provides subsequences for easy parallelization. + * + * @see <a href="https://www.thesalmons.org/john/random123/papers/random123sc11.pdf">Parallel Random Numbers: As Easy as 1,2,3</a> + * for details regarding the engine. + * @since 1.7 + */ +public final class Philox4x32 extends IntProvider implements LongJumpableUniformRandomProvider { + /** + * Philox 32-bit mixing constant for counter 0. + */ + private static final int K_PHILOX_10_A = 0x9E3779B9; + /** + * Philox 32-bit mixing constant for counter 1. + */ + private static final int K_PHILOX_10_B = 0xBB67AE85; + /** + * Philox 32-bit constant for key 0. + */ + private static final int K_PHILOX_SA = 0xD2511F53; + /** + * Philox 32-bit constant for key 1. + */ + private static final int K_PHILOX_SB = 0xCD9E8D57; + /** + * Internal buffer size. + */ + private static final int PHILOX_BUFFER_SIZE = 4; + /** + * number of int variables. + */ + private static final int STATE_SIZE = 7; + + /** + * Counter 0. + */ + private int counter0; + /** + * Counter 1. + */ + private int counter1; + /** + * Counter 2. + */ + private int counter2; + /** + * Counter 3. + */ + private int counter3; + /** + * Output point. + */ + private int[] buffer = new int[PHILOX_BUFFER_SIZE]; // UINT4 + /** + * Key low bits. + */ + private int key0; + /** + * Key high bits. + */ + private int key1; + /** + * State index: which output word is next (0..3). + */ + private int bufferPosition; + + + /** + * Copy constructor. + * + * @param source Source to copy. + */ + private Philox4x32(Philox4x32 source) { + super(source); + counter0 = source.counter0; + counter1 = source.counter1; + counter2 = source.counter2; + counter3 = source.counter3; + key0 = source.key0; + key1 = source.key1; + bufferPosition = source.bufferPosition; + buffer = source.buffer.clone(); + } + + /** + * Creates a new instance with default seed. Subsequence and offset are set to zero. + */ + public Philox4x32() { + this(67280421310721L, 0L, 0L); + } + + /** + * Creates a new instance with given seed. Subsequence and offset are set to zero. + * + * @param seed Initial seed. + */ + public Philox4x32(long seed) { + this(seed, 0L, 0L); + } + + /** + * Creates a new instance. Offset and subsequence determine the internal counter of Philox. + * + * @param seed Initial seed. + * @param subsequence a subsequence index + * @param offset an offset, zero for the first number. + */ + public Philox4x32(long seed, long subsequence, long offset) { + resetState(seed, subsequence); + incrementCounter(offset); + } + + /** + * Creates a new instance based on an array of int containing, seed, subsequence and offset. + * + * @param seed key0,key1,counter0,counter1,counter2,counter3. + */ + @SuppressWarnings("PMD.AvoidLiteralsInIfCondition") + public Philox4x32(int... seed) { + key0 = seed[0]; + if (seed.length > 1) { + key1 = seed[1]; + } else { + key1 = 0; + } + if (seed.length > 2) { + counter0 = seed[2]; + } else { + counter0 = 0; + } + if (seed.length > 3) { + counter1 = seed[3]; + } else { + counter1 = 0; + } + if (seed.length > 4) { + counter2 = seed[4]; + } else { + counter2 = 0; + } + if (seed.length > 5) { + counter3 = seed[5]; + } else { + counter3 = 0; + } + bufferPosition = PHILOX_BUFFER_SIZE; + } + + /** + * Resets the key, counter and state index. + * + * @param seed key for Philox + * @param subsequence counter third and fourth ints. + */ + public void resetState(long seed, long subsequence) { + key0 = (int) seed; + key1 = (int) (seed >>> 32); + + counter0 = 0; + counter1 = 0; + counter2 = (int) subsequence; + counter3 = (int) (subsequence >>> 32); + + bufferPosition = PHILOX_BUFFER_SIZE; + } + + /** + * Set the first two ints of the internal counter of Philox. + * + * @param offset an offet. An offet of 1 corresponds to the 4th number of the sequence. + */ + public void setOffset(long offset) { + counter0 = (int) offset; + counter1 = (int) (offset >>> 32); + bufferPosition = PHILOX_BUFFER_SIZE; + } + + /** + * Returns the offset, that is the first two ints of the internal counter as a long. + * + * @return the first two ints of the internal counter of Philox as long. + */ + public long getOffset() { + final long lo = counter0 & 0xFFFFFFFFL; + final long hi = (counter1 & 0xFFFFFFFFL) << 32; + return lo | hi; + } + + /** + * Fetch next integer from the buffer, or regenerate the buffer using 10 rounds. + * + * @return random integer + */ + @Override + public int next() { + if (bufferPosition < PHILOX_BUFFER_SIZE) { + return buffer[bufferPosition++]; + } + incrementCounter(); + rand10(); + bufferPosition = 1; + return buffer[0]; + } + + /** + * Counter increment. + * + * @param n how many steps to increment + */ + private void incrementCounter(long n) { + final int nlo = (int) n; + int nhi = (int) (n >>> 32); + + int old = counter0; + counter0 += nlo; + if (Integer.compareUnsigned(counter0, old) < 0) { + nhi++; + } + + old = counter1; + counter1 += nhi; + if (Integer.compareUnsigned(counter1, old) < 0 && ++counter2 == 0) { + counter3++; + } + } + + /** + * Increment by one. + */ + private void incrementCounter() { + counter0++; + if (counter0 != 0) { + return; + } + + counter1++; + if (counter1 != 0) { + return; + } + + counter2++; + if (counter2 != 0) { + return; + } + + counter3++; + } + + /** + * Performs a single round of philox. + * + * @param ctr local counter, which will be updated after each call. + * @param key0 key low bits + * @param key1 key high bits + */ + private static void singleRound(int[] ctr, int key0, int key1) { + long product = (K_PHILOX_SA & 0xFFFFFFFFL) * (ctr[0] & 0xFFFFFFFFL); + final int hi0 = (int) (product >>> 32); + final int lo0 = (int) product; + product = (K_PHILOX_SB & 0xFFFFFFFFL) * (ctr[2] & 0xFFFFFFFFL); + final int hi1 = (int) (product >>> 32); + final int lo1 = (int) product; + + ctr[0] = hi1 ^ ctr[1] ^ key0; + ctr[1] = lo1; + ctr[2] = hi0 ^ ctr[3] ^ key1; + ctr[3] = lo0; + } + + /** + * Perform 10 rounds, using counter0, counter1, counter2, counter3 as starting point. + * + */ + private void rand10() { + buffer[0] = counter0; + buffer[1] = counter1; + buffer[2] = counter2; + buffer[3] = counter3; + + int k0 = key0; + int k1 = key1; + + //unrolled loop for performance + singleRound(buffer, k0, k1); + k0 += K_PHILOX_10_A; + k1 += K_PHILOX_10_B; + singleRound(buffer, k0, k1); + k0 += K_PHILOX_10_A; + k1 += K_PHILOX_10_B; + singleRound(buffer, k0, k1); + k0 += K_PHILOX_10_A; + k1 += K_PHILOX_10_B; + singleRound(buffer, k0, k1); + k0 += K_PHILOX_10_A; + k1 += K_PHILOX_10_B; + singleRound(buffer, k0, k1); + k0 += K_PHILOX_10_A; + k1 += K_PHILOX_10_B; + singleRound(buffer, k0, k1); + k0 += K_PHILOX_10_A; + k1 += K_PHILOX_10_B; + singleRound(buffer, k0, k1); + k0 += K_PHILOX_10_A; + k1 += K_PHILOX_10_B; + singleRound(buffer, k0, k1); + k0 += K_PHILOX_10_A; + k1 += K_PHILOX_10_B; + singleRound(buffer, k0, k1); + k0 += K_PHILOX_10_A; + k1 += K_PHILOX_10_B; + singleRound(buffer, k0, k1); + } + + + /** + * {@inheritDoc} + * + * <p>Increments the subsequence by 1.</p> + * <p>The jump size is the equivalent of 4*2<sup>64</sup> calls to + * {@link UniformRandomProvider#nextInt() nextInt()}. + */ + @Override + public JumpableUniformRandomProvider longJump() { + final Philox4x32 copy = copy(); + if (++counter2 == 0) { + counter3++; + } + rand10(); + return copy; + } + + /** + * {@inheritDoc} + * + * <p>The jump size is the equivalent of 4*2<sup>32</sup> + * calls to {@link UniformRandomProvider#nextInt() nextInt()}. + */ + @Override + public UniformRandomProvider jump() { + final Philox4x32 copy = copy(); + incrementCounter(1L << 32); + rand10(); + resetCachedState(); + return copy; + } + + /** + * jump by n numbers in the sequence. This is equivalent to + * calling nextInt() n times. + * + * @param n the length to jump. + * @return a copy of the original random number generator. + */ + public UniformRandomProvider jump(long n) { + final Philox4x32 copy = copy(); + final long ndiv4 = (n + bufferPosition) / 4; + if (ndiv4 > 0) { + incrementCounter(ndiv4); + rand10(); + bufferPosition = (int) ((bufferPosition + n) & 3); + } else { + for (int i = 0; i < n; i++) { + nextInt(); + } + } + resetCachedState(); + return copy; + } + + /** + * {@inheritDoc} + */ + @Override + protected byte[] getStateInternal() { + return composeStateInternal(NumberFactory.makeByteArray( + new int[]{key0, key1, counter0, counter1, counter2, counter3, bufferPosition}), + super.getStateInternal()); + } + + /** + * {@inheritDoc} + */ + @Override + protected void setStateInternal(byte[] s) { + final byte[][] c = splitStateInternal(s, STATE_SIZE * 4); + final int[] state = NumberFactory.makeIntArray(c[0]); + key0 = state[0]; + key1 = state[1]; + counter0 = state[2]; + counter1 = state[3]; + counter2 = state[4]; + counter3 = state[5]; + bufferPosition = state[6]; + super.setStateInternal(c[1]); + rand10(); Review Comment: Calling `rand10` would change the state and prevent restoring to exactly the same state saved by `getStateInternal`. ########## commons-rng-core/src/main/java/org/apache/commons/rng/core/source32/Philox4x32.java: ########## @@ -0,0 +1,431 @@ +/* + * 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.source32; + +import org.apache.commons.rng.JumpableUniformRandomProvider; +import org.apache.commons.rng.LongJumpableUniformRandomProvider; +import org.apache.commons.rng.UniformRandomProvider; +import org.apache.commons.rng.core.util.NumberFactory; + +/** + * This class implements the Philox4x32 128-bit counter-based generator with 10 rounds. + * Jumping in the sequence is essentially instantaneous. This generator provides subsequences for easy parallelization. + * + * @see <a href="https://www.thesalmons.org/john/random123/papers/random123sc11.pdf">Parallel Random Numbers: As Easy as 1,2,3</a> + * for details regarding the engine. + * @since 1.7 + */ +public final class Philox4x32 extends IntProvider implements LongJumpableUniformRandomProvider { + /** + * Philox 32-bit mixing constant for counter 0. + */ + private static final int K_PHILOX_10_A = 0x9E3779B9; + /** + * Philox 32-bit mixing constant for counter 1. + */ + private static final int K_PHILOX_10_B = 0xBB67AE85; + /** + * Philox 32-bit constant for key 0. + */ + private static final int K_PHILOX_SA = 0xD2511F53; + /** + * Philox 32-bit constant for key 1. + */ + private static final int K_PHILOX_SB = 0xCD9E8D57; + /** + * Internal buffer size. + */ + private static final int PHILOX_BUFFER_SIZE = 4; + /** + * number of int variables. + */ + private static final int STATE_SIZE = 7; + + /** + * Counter 0. + */ + private int counter0; + /** + * Counter 1. + */ + private int counter1; + /** + * Counter 2. + */ + private int counter2; + /** + * Counter 3. + */ + private int counter3; + /** + * Output point. + */ + private int[] buffer = new int[PHILOX_BUFFER_SIZE]; // UINT4 + /** + * Key low bits. + */ + private int key0; + /** + * Key high bits. + */ + private int key1; + /** + * State index: which output word is next (0..3). + */ + private int bufferPosition; + + + /** + * Copy constructor. + * + * @param source Source to copy. + */ + private Philox4x32(Philox4x32 source) { + super(source); + counter0 = source.counter0; + counter1 = source.counter1; + counter2 = source.counter2; + counter3 = source.counter3; + key0 = source.key0; + key1 = source.key1; + bufferPosition = source.bufferPosition; + buffer = source.buffer.clone(); + } + + /** + * Creates a new instance with default seed. Subsequence and offset are set to zero. + */ + public Philox4x32() { + this(67280421310721L, 0L, 0L); + } + + /** + * Creates a new instance with given seed. Subsequence and offset are set to zero. + * + * @param seed Initial seed. + */ + public Philox4x32(long seed) { + this(seed, 0L, 0L); + } + + /** + * Creates a new instance. Offset and subsequence determine the internal counter of Philox. + * + * @param seed Initial seed. + * @param subsequence a subsequence index + * @param offset an offset, zero for the first number. + */ + public Philox4x32(long seed, long subsequence, long offset) { + resetState(seed, subsequence); + incrementCounter(offset); + } + + /** + * Creates a new instance based on an array of int containing, seed, subsequence and offset. + * + * @param seed key0,key1,counter0,counter1,counter2,counter3. + */ + @SuppressWarnings("PMD.AvoidLiteralsInIfCondition") + public Philox4x32(int... seed) { + key0 = seed[0]; + if (seed.length > 1) { + key1 = seed[1]; + } else { + key1 = 0; + } + if (seed.length > 2) { + counter0 = seed[2]; + } else { + counter0 = 0; + } + if (seed.length > 3) { + counter1 = seed[3]; + } else { + counter1 = 0; + } + if (seed.length > 4) { + counter2 = seed[4]; + } else { + counter2 = 0; + } + if (seed.length > 5) { + counter3 = seed[5]; + } else { + counter3 = 0; + } + bufferPosition = PHILOX_BUFFER_SIZE; + } + + /** + * Resets the key, counter and state index. + * + * @param seed key for Philox + * @param subsequence counter third and fourth ints. + */ + public void resetState(long seed, long subsequence) { + key0 = (int) seed; + key1 = (int) (seed >>> 32); + + counter0 = 0; + counter1 = 0; + counter2 = (int) subsequence; + counter3 = (int) (subsequence >>> 32); + + bufferPosition = PHILOX_BUFFER_SIZE; + } + + /** + * Set the first two ints of the internal counter of Philox. + * + * @param offset an offet. An offet of 1 corresponds to the 4th number of the sequence. + */ + public void setOffset(long offset) { + counter0 = (int) offset; + counter1 = (int) (offset >>> 32); + bufferPosition = PHILOX_BUFFER_SIZE; + } + + /** + * Returns the offset, that is the first two ints of the internal counter as a long. + * + * @return the first two ints of the internal counter of Philox as long. + */ + public long getOffset() { + final long lo = counter0 & 0xFFFFFFFFL; + final long hi = (counter1 & 0xFFFFFFFFL) << 32; + return lo | hi; + } + + /** + * Fetch next integer from the buffer, or regenerate the buffer using 10 rounds. + * + * @return random integer + */ + @Override + public int next() { + if (bufferPosition < PHILOX_BUFFER_SIZE) { + return buffer[bufferPosition++]; + } + incrementCounter(); + rand10(); + bufferPosition = 1; + return buffer[0]; + } + + /** + * Counter increment. + * + * @param n how many steps to increment + */ + private void incrementCounter(long n) { + final int nlo = (int) n; + int nhi = (int) (n >>> 32); + + int old = counter0; + counter0 += nlo; + if (Integer.compareUnsigned(counter0, old) < 0) { + nhi++; + } + + old = counter1; + counter1 += nhi; + if (Integer.compareUnsigned(counter1, old) < 0 && ++counter2 == 0) { + counter3++; + } + } + + /** + * Increment by one. + */ + private void incrementCounter() { + counter0++; + if (counter0 != 0) { + return; + } + + counter1++; + if (counter1 != 0) { + return; + } + + counter2++; + if (counter2 != 0) { + return; + } + + counter3++; + } + + /** + * Performs a single round of philox. + * + * @param ctr local counter, which will be updated after each call. + * @param key0 key low bits + * @param key1 key high bits + */ + private static void singleRound(int[] ctr, int key0, int key1) { + long product = (K_PHILOX_SA & 0xFFFFFFFFL) * (ctr[0] & 0xFFFFFFFFL); + final int hi0 = (int) (product >>> 32); + final int lo0 = (int) product; + product = (K_PHILOX_SB & 0xFFFFFFFFL) * (ctr[2] & 0xFFFFFFFFL); + final int hi1 = (int) (product >>> 32); + final int lo1 = (int) product; + + ctr[0] = hi1 ^ ctr[1] ^ key0; + ctr[1] = lo1; + ctr[2] = hi0 ^ ctr[3] ^ key1; + ctr[3] = lo0; + } + + /** + * Perform 10 rounds, using counter0, counter1, counter2, counter3 as starting point. + * + */ + private void rand10() { + buffer[0] = counter0; + buffer[1] = counter1; + buffer[2] = counter2; + buffer[3] = counter3; + + int k0 = key0; + int k1 = key1; + + //unrolled loop for performance + singleRound(buffer, k0, k1); + k0 += K_PHILOX_10_A; + k1 += K_PHILOX_10_B; + singleRound(buffer, k0, k1); + k0 += K_PHILOX_10_A; + k1 += K_PHILOX_10_B; + singleRound(buffer, k0, k1); + k0 += K_PHILOX_10_A; + k1 += K_PHILOX_10_B; + singleRound(buffer, k0, k1); + k0 += K_PHILOX_10_A; + k1 += K_PHILOX_10_B; + singleRound(buffer, k0, k1); + k0 += K_PHILOX_10_A; + k1 += K_PHILOX_10_B; + singleRound(buffer, k0, k1); + k0 += K_PHILOX_10_A; + k1 += K_PHILOX_10_B; + singleRound(buffer, k0, k1); + k0 += K_PHILOX_10_A; + k1 += K_PHILOX_10_B; + singleRound(buffer, k0, k1); + k0 += K_PHILOX_10_A; + k1 += K_PHILOX_10_B; + singleRound(buffer, k0, k1); + k0 += K_PHILOX_10_A; + k1 += K_PHILOX_10_B; + singleRound(buffer, k0, k1); + } + + + /** + * {@inheritDoc} + * + * <p>Increments the subsequence by 1.</p> + * <p>The jump size is the equivalent of 4*2<sup>64</sup> calls to + * {@link UniformRandomProvider#nextInt() nextInt()}. + */ + @Override + public JumpableUniformRandomProvider longJump() { + final Philox4x32 copy = copy(); + if (++counter2 == 0) { + counter3++; + } + rand10(); + return copy; + } + + /** + * {@inheritDoc} + * + * <p>The jump size is the equivalent of 4*2<sup>32</sup> + * calls to {@link UniformRandomProvider#nextInt() nextInt()}. + */ + @Override + public UniformRandomProvider jump() { + final Philox4x32 copy = copy(); + incrementCounter(1L << 32); + rand10(); + resetCachedState(); Review Comment: Why call `resetCachedState` here but not in the `longJump`? ########## commons-rng-core/src/main/java/org/apache/commons/rng/core/source32/Philox4x32.java: ########## @@ -0,0 +1,431 @@ +/* + * 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.source32; + +import org.apache.commons.rng.JumpableUniformRandomProvider; +import org.apache.commons.rng.LongJumpableUniformRandomProvider; +import org.apache.commons.rng.UniformRandomProvider; +import org.apache.commons.rng.core.util.NumberFactory; + +/** + * This class implements the Philox4x32 128-bit counter-based generator with 10 rounds. + * Jumping in the sequence is essentially instantaneous. This generator provides subsequences for easy parallelization. + * + * @see <a href="https://www.thesalmons.org/john/random123/papers/random123sc11.pdf">Parallel Random Numbers: As Easy as 1,2,3</a> + * for details regarding the engine. + * @since 1.7 + */ +public final class Philox4x32 extends IntProvider implements LongJumpableUniformRandomProvider { + /** + * Philox 32-bit mixing constant for counter 0. + */ + private static final int K_PHILOX_10_A = 0x9E3779B9; + /** + * Philox 32-bit mixing constant for counter 1. + */ + private static final int K_PHILOX_10_B = 0xBB67AE85; + /** + * Philox 32-bit constant for key 0. + */ + private static final int K_PHILOX_SA = 0xD2511F53; + /** + * Philox 32-bit constant for key 1. + */ + private static final int K_PHILOX_SB = 0xCD9E8D57; + /** + * Internal buffer size. + */ + private static final int PHILOX_BUFFER_SIZE = 4; + /** + * number of int variables. + */ + private static final int STATE_SIZE = 7; + + /** + * Counter 0. + */ + private int counter0; + /** + * Counter 1. + */ + private int counter1; + /** + * Counter 2. + */ + private int counter2; + /** + * Counter 3. + */ + private int counter3; + /** + * Output point. + */ + private int[] buffer = new int[PHILOX_BUFFER_SIZE]; // UINT4 + /** + * Key low bits. + */ + private int key0; + /** + * Key high bits. + */ + private int key1; + /** + * State index: which output word is next (0..3). + */ + private int bufferPosition; + + + /** + * Copy constructor. + * + * @param source Source to copy. + */ + private Philox4x32(Philox4x32 source) { + super(source); + counter0 = source.counter0; + counter1 = source.counter1; + counter2 = source.counter2; + counter3 = source.counter3; + key0 = source.key0; + key1 = source.key1; + bufferPosition = source.bufferPosition; + buffer = source.buffer.clone(); + } + + /** + * Creates a new instance with default seed. Subsequence and offset are set to zero. + */ + public Philox4x32() { + this(67280421310721L, 0L, 0L); + } + + /** + * Creates a new instance with given seed. Subsequence and offset are set to zero. + * + * @param seed Initial seed. + */ + public Philox4x32(long seed) { + this(seed, 0L, 0L); + } + + /** + * Creates a new instance. Offset and subsequence determine the internal counter of Philox. + * + * @param seed Initial seed. + * @param subsequence a subsequence index + * @param offset an offset, zero for the first number. + */ + public Philox4x32(long seed, long subsequence, long offset) { + resetState(seed, subsequence); + incrementCounter(offset); + } + + /** + * Creates a new instance based on an array of int containing, seed, subsequence and offset. + * + * @param seed key0,key1,counter0,counter1,counter2,counter3. + */ + @SuppressWarnings("PMD.AvoidLiteralsInIfCondition") + public Philox4x32(int... seed) { Review Comment: All these constructors are not required. Just a single constructor accepting `int[]` would be fine. This can be simplified to: ```java input = seed.length < 6 ? Arrays.copyOf(seed, 6) : seed; key0 = input[0] ... counter3 = input[5]; bufferPosition = PHILOX_BUFFER_SIZE; ``` ########## commons-rng-simple/src/main/java/org/apache/commons/rng/simple/internal/ProviderBuilder.java: ########## @@ -16,45 +16,51 @@ */ package org.apache.commons.rng.simple.internal; -import java.lang.reflect.Array; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; - -import org.apache.commons.rng.UniformRandomProvider; import org.apache.commons.rng.RestorableUniformRandomProvider; +import org.apache.commons.rng.UniformRandomProvider; Review Comment: The imports here have been sorted. I am trying to understand when the order in master currently represents. It seems to be partly in historical order of when providers were added. I don't think this is needed but it would be best to remove the reordering here to make the PR addition more clear and I can fix the order in master separately. ########## commons-rng-simple/src/main/java/org/apache/commons/rng/simple/RandomSource.java: ########## @@ -661,7 +661,26 @@ public enum RandomSource { * </ul> * @since 1.5 */ - L32_X64_MIX(ProviderBuilder.RandomSourceInternal.L32_X64_MIX); + L32_X64_MIX(ProviderBuilder.RandomSourceInternal.L32_X64_MIX), + /** + * Source of randomness is {@link org.apache.commons.rng.core.source32.Philox4x32}. + * <ul> + * <li>Native seed type: {@code int[]}.</li> + * <li>Native seed size: 6.</li> + * </ul> + * @since 1.7 + */ + PHILOX_4X32(ProviderBuilder.RandomSourceInternal.PHILOX_4X32), + /** + * Source of randomness is {@link org.apache.commons.rng.core.source32.Philox4x32}. Review Comment: `source64` ########## commons-rng-core/src/main/java/org/apache/commons/rng/core/source32/Philox4x32.java: ########## @@ -0,0 +1,431 @@ +/* + * 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.source32; + +import org.apache.commons.rng.JumpableUniformRandomProvider; +import org.apache.commons.rng.LongJumpableUniformRandomProvider; +import org.apache.commons.rng.UniformRandomProvider; +import org.apache.commons.rng.core.util.NumberFactory; + +/** + * This class implements the Philox4x32 128-bit counter-based generator with 10 rounds. + * Jumping in the sequence is essentially instantaneous. This generator provides subsequences for easy parallelization. + * + * @see <a href="https://www.thesalmons.org/john/random123/papers/random123sc11.pdf">Parallel Random Numbers: As Easy as 1,2,3</a> + * for details regarding the engine. + * @since 1.7 + */ +public final class Philox4x32 extends IntProvider implements LongJumpableUniformRandomProvider { + /** + * Philox 32-bit mixing constant for counter 0. + */ + private static final int K_PHILOX_10_A = 0x9E3779B9; + /** + * Philox 32-bit mixing constant for counter 1. + */ + private static final int K_PHILOX_10_B = 0xBB67AE85; + /** + * Philox 32-bit constant for key 0. + */ + private static final int K_PHILOX_SA = 0xD2511F53; + /** + * Philox 32-bit constant for key 1. + */ + private static final int K_PHILOX_SB = 0xCD9E8D57; + /** + * Internal buffer size. + */ + private static final int PHILOX_BUFFER_SIZE = 4; + /** + * number of int variables. + */ + private static final int STATE_SIZE = 7; + + /** + * Counter 0. + */ + private int counter0; + /** + * Counter 1. + */ + private int counter1; + /** + * Counter 2. + */ + private int counter2; + /** + * Counter 3. + */ + private int counter3; + /** + * Output point. + */ + private int[] buffer = new int[PHILOX_BUFFER_SIZE]; // UINT4 + /** + * Key low bits. + */ + private int key0; + /** + * Key high bits. + */ + private int key1; + /** + * State index: which output word is next (0..3). + */ + private int bufferPosition; + + + /** + * Copy constructor. + * + * @param source Source to copy. + */ + private Philox4x32(Philox4x32 source) { + super(source); + counter0 = source.counter0; + counter1 = source.counter1; + counter2 = source.counter2; + counter3 = source.counter3; + key0 = source.key0; + key1 = source.key1; + bufferPosition = source.bufferPosition; + buffer = source.buffer.clone(); + } + + /** + * Creates a new instance with default seed. Subsequence and offset are set to zero. + */ + public Philox4x32() { + this(67280421310721L, 0L, 0L); + } + + /** + * Creates a new instance with given seed. Subsequence and offset are set to zero. + * + * @param seed Initial seed. + */ + public Philox4x32(long seed) { + this(seed, 0L, 0L); + } + + /** + * Creates a new instance. Offset and subsequence determine the internal counter of Philox. + * + * @param seed Initial seed. + * @param subsequence a subsequence index + * @param offset an offset, zero for the first number. + */ + public Philox4x32(long seed, long subsequence, long offset) { + resetState(seed, subsequence); + incrementCounter(offset); + } + + /** + * Creates a new instance based on an array of int containing, seed, subsequence and offset. + * + * @param seed key0,key1,counter0,counter1,counter2,counter3. + */ + @SuppressWarnings("PMD.AvoidLiteralsInIfCondition") + public Philox4x32(int... seed) { + key0 = seed[0]; + if (seed.length > 1) { + key1 = seed[1]; + } else { + key1 = 0; + } + if (seed.length > 2) { + counter0 = seed[2]; + } else { + counter0 = 0; + } + if (seed.length > 3) { + counter1 = seed[3]; + } else { + counter1 = 0; + } + if (seed.length > 4) { + counter2 = seed[4]; + } else { + counter2 = 0; + } + if (seed.length > 5) { + counter3 = seed[5]; + } else { + counter3 = 0; + } + bufferPosition = PHILOX_BUFFER_SIZE; + } + + /** + * Resets the key, counter and state index. + * + * @param seed key for Philox + * @param subsequence counter third and fourth ints. + */ + public void resetState(long seed, long subsequence) { + key0 = (int) seed; + key1 = (int) (seed >>> 32); + + counter0 = 0; + counter1 = 0; + counter2 = (int) subsequence; + counter3 = (int) (subsequence >>> 32); + + bufferPosition = PHILOX_BUFFER_SIZE; + } + + /** + * Set the first two ints of the internal counter of Philox. + * + * @param offset an offet. An offet of 1 corresponds to the 4th number of the sequence. + */ + public void setOffset(long offset) { + counter0 = (int) offset; + counter1 = (int) (offset >>> 32); + bufferPosition = PHILOX_BUFFER_SIZE; + } + + /** + * Returns the offset, that is the first two ints of the internal counter as a long. + * + * @return the first two ints of the internal counter of Philox as long. + */ + public long getOffset() { + final long lo = counter0 & 0xFFFFFFFFL; + final long hi = (counter1 & 0xFFFFFFFFL) << 32; + return lo | hi; + } + + /** + * Fetch next integer from the buffer, or regenerate the buffer using 10 rounds. + * + * @return random integer + */ + @Override + public int next() { + if (bufferPosition < PHILOX_BUFFER_SIZE) { + return buffer[bufferPosition++]; + } + incrementCounter(); + rand10(); + bufferPosition = 1; + return buffer[0]; + } + + /** + * Counter increment. + * + * @param n how many steps to increment + */ + private void incrementCounter(long n) { + final int nlo = (int) n; + int nhi = (int) (n >>> 32); + + int old = counter0; + counter0 += nlo; + if (Integer.compareUnsigned(counter0, old) < 0) { + nhi++; + } + + old = counter1; + counter1 += nhi; + if (Integer.compareUnsigned(counter1, old) < 0 && ++counter2 == 0) { + counter3++; + } + } + + /** + * Increment by one. + */ + private void incrementCounter() { + counter0++; + if (counter0 != 0) { + return; + } + + counter1++; + if (counter1 != 0) { + return; + } + + counter2++; + if (counter2 != 0) { + return; + } + + counter3++; + } + + /** + * Performs a single round of philox. + * + * @param ctr local counter, which will be updated after each call. + * @param key0 key low bits + * @param key1 key high bits + */ + private static void singleRound(int[] ctr, int key0, int key1) { + long product = (K_PHILOX_SA & 0xFFFFFFFFL) * (ctr[0] & 0xFFFFFFFFL); + final int hi0 = (int) (product >>> 32); + final int lo0 = (int) product; + product = (K_PHILOX_SB & 0xFFFFFFFFL) * (ctr[2] & 0xFFFFFFFFL); + final int hi1 = (int) (product >>> 32); + final int lo1 = (int) product; + + ctr[0] = hi1 ^ ctr[1] ^ key0; + ctr[1] = lo1; + ctr[2] = hi0 ^ ctr[3] ^ key1; + ctr[3] = lo0; + } + + /** + * Perform 10 rounds, using counter0, counter1, counter2, counter3 as starting point. + * + */ + private void rand10() { + buffer[0] = counter0; + buffer[1] = counter1; + buffer[2] = counter2; + buffer[3] = counter3; + + int k0 = key0; + int k1 = key1; + + //unrolled loop for performance + singleRound(buffer, k0, k1); + k0 += K_PHILOX_10_A; + k1 += K_PHILOX_10_B; + singleRound(buffer, k0, k1); + k0 += K_PHILOX_10_A; + k1 += K_PHILOX_10_B; + singleRound(buffer, k0, k1); + k0 += K_PHILOX_10_A; + k1 += K_PHILOX_10_B; + singleRound(buffer, k0, k1); + k0 += K_PHILOX_10_A; + k1 += K_PHILOX_10_B; + singleRound(buffer, k0, k1); + k0 += K_PHILOX_10_A; + k1 += K_PHILOX_10_B; + singleRound(buffer, k0, k1); + k0 += K_PHILOX_10_A; + k1 += K_PHILOX_10_B; + singleRound(buffer, k0, k1); + k0 += K_PHILOX_10_A; + k1 += K_PHILOX_10_B; + singleRound(buffer, k0, k1); + k0 += K_PHILOX_10_A; + k1 += K_PHILOX_10_B; + singleRound(buffer, k0, k1); + k0 += K_PHILOX_10_A; + k1 += K_PHILOX_10_B; + singleRound(buffer, k0, k1); + } + + + /** + * {@inheritDoc} + * + * <p>Increments the subsequence by 1.</p> + * <p>The jump size is the equivalent of 4*2<sup>64</sup> calls to + * {@link UniformRandomProvider#nextInt() nextInt()}. + */ + @Override + public JumpableUniformRandomProvider longJump() { + final Philox4x32 copy = copy(); + if (++counter2 == 0) { + counter3++; + } + rand10(); + return copy; + } + + /** + * {@inheritDoc} + * + * <p>The jump size is the equivalent of 4*2<sup>32</sup> + * calls to {@link UniformRandomProvider#nextInt() nextInt()}. + */ + @Override + public UniformRandomProvider jump() { + final Philox4x32 copy = copy(); + incrementCounter(1L << 32); + rand10(); + resetCachedState(); + return copy; + } + + /** + * jump by n numbers in the sequence. This is equivalent to + * calling nextInt() n times. + * + * @param n the length to jump. + * @return a copy of the original random number generator. + */ + public UniformRandomProvider jump(long n) { + final Philox4x32 copy = copy(); + final long ndiv4 = (n + bufferPosition) / 4; + if (ndiv4 > 0) { + incrementCounter(ndiv4); + rand10(); + bufferPosition = (int) ((bufferPosition + n) & 3); + } else { + for (int i = 0; i < n; i++) { Review Comment: This loop should only execute when `n` is in `[0, 3]`. But will be invoked for any negative `n`. ########## commons-rng-core/src/test/java/org/apache/commons/rng/core/source32/Philox4x32Test.java: ########## @@ -0,0 +1,242 @@ +/* + * 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.source32; + +import org.apache.commons.rng.UniformRandomProvider; +import org.apache.commons.rng.core.RandomAssert; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class Philox4x32Test { + + /* + * Data from python randomgen.philox.Philox(key=1234,number=4,width=32) random_raw() + * https://bashtage.github.io/randomgen/bit_generators/philox.html + */ + + private static final int[] EXPECTED_SEQUENCE_1234 = { + -1628512715, 482218876, -98078573, 343858512, 1070188760, + -66651592, -870905049, -1994573039, -1238984130, 599211371, + 1926069095, -394512546, 346514135, -352142790, 196394741, + -107436867, -903274039, 860026475, -1309487194, -1778049224, + -49503714, -1441076994, -866074276, -1339523817, -1290919251, + 1857369626, -1839251177, -2041498882, -1956330288, 905306810, + -2114083635, 200746399, 20291031, 214040874, -1628891823, + -1958807646, 9198301, -1607720479, -1349496224, 1418271217 + }; + + private static final int[] EXPECTED_SEQUENCE_DEFAULT = { + 623720234, -686991347, 358698524, 234508473, 1303720625, + 1235930736, -75297729, 110380616, 829652807, -1101240720, + -1443748750, -1366075136, -1702811520, 232450464, 350957237, + 1425642103, 256542391, 1837662153, -448554748, 637025846, + -902021712, 1085962074, -1391041963, 201580325, 1416828610, + 599210676, -628463662, -576572235, 457140358, -1026551805, + -917125498, 529387774, 1254882949, 1278069784, 724938314, + -4044975, -1211844829, -198846304, 286548119, 2085574084 + }; + + private static final int[] EXPECTED_SEQUENCE_AFTER_JUMP = { + -851136091, 1309410510, -1085986073, -2011015294, 1141542412, + 1418494107, -1747451871, -1055627323, -146194734, 1282520890, + -1352853386, 1006181297, -1439198278, 1236883457, 492325190, + 1314792982, 532544947, 1080385192, -2075089806, 1956500098, + -1226283606, 1100040044, -1227122850, -565515005, -851592399, + -2140061922, 138067050, 1387279196, 1163016478, -26858470, + -1462800132, 484042867, -1872158237, -300782320, -1425836673, + 830088801, 1808637392, 1353273018, 660570244, -1956528645 + }; + + private static final int[] EXPECTED_SEQUENCE_AFTER_LONG_JUMP = { + -1941342745, 535234737, -1560986946, 1333403881, -467630828, + -1212243215, 1924495835, 1889500660, 118588722, -444471278, + -984974572, 2134204567, 620921081, -929199568, -44345645, + -346841340, -557091335, 1023562906, -1544843001, 2014718360, + -186712859, -874952234, -1016908504, 953606755, -1406346322, + -1297454974, 1426742334, 1461035068, 206733349, 1606578263, + -1354963004, -604654637, 782017623, 1501746828, 853947605, + -1380277812, 1855551741, -1023933348, -635058958, 1752530776 + }; + + @Test + void testReferenceCode() { + RandomAssert.assertEquals(EXPECTED_SEQUENCE_1234, new Philox4x32(1234L, 0, 0)); + } + + @Test + void testReferenceCodeDefaultSeed() { + RandomAssert.assertEquals(EXPECTED_SEQUENCE_DEFAULT, new Philox4x32(67280421310721L, 0, 0)); + } + + @Test + void testConstructors() { + Philox4x32[] rngs = new Philox4x32[]{ + new Philox4x32(), + new Philox4x32(67280421310721L), + new Philox4x32(67280421310721L, 0, 0), + new Philox4x32(new int[]{(int) 67280421310721L, (int) (67280421310721L >>> 32), 0, 0, 0, 0}) + }; + int refValue = rngs[0].next(); + for (int i = 1; i < rngs.length; i++) { + int value = rngs[i].next(); + assertEquals(refValue, value, "Philox4x32 initialization for i=" + i); + } + rngs = new Philox4x32[]{ + new Philox4x32(1234L, 1, 1), + new Philox4x32(1234, 0, 1, 0, 1), + new Philox4x32(1234, 0, 1, 0, 1, 0), + }; + refValue = rngs[0].next(); + for (int i = 1; i < rngs.length; i++) { + int value = rngs[i].next(); + assertEquals(refValue, value, "Philox4x32 initialization for i=" + i); + } + rngs = new Philox4x32[]{ + new Philox4x32(1234L), + new Philox4x32(1234L, 0, 0), + new Philox4x32(1234), + new Philox4x32(new int[]{1234}), + new Philox4x32(1234, 0), + new Philox4x32(1234, 0, 0), + new Philox4x32(1234, 0, 0, 0), + new Philox4x32(1234, 0, 0, 0, 0), + new Philox4x32(1234, 0, 0, 0, 0, 0), + new Philox4x32(1234, 0, 0, 0, 0, 0, 0), + }; + refValue = rngs[0].next(); + for (int i = 1; i < rngs.length; i++) { + int value = rngs[i].next(); + assertEquals(refValue, value, "Philox4x32 initialization for i=" + i); + } + } + + @Test + void testJump() { + RandomAssert.assertJumpEquals(EXPECTED_SEQUENCE_DEFAULT, EXPECTED_SEQUENCE_AFTER_JUMP, new Philox4x32(67280421310721L, 0, 0)); + } + + @Test + void testLongJump() { + RandomAssert.assertLongJumpEquals(EXPECTED_SEQUENCE_DEFAULT, EXPECTED_SEQUENCE_AFTER_LONG_JUMP, new Philox4x32(67280421310721L, 0, 0)); + } + + @Test + void testOffset() { + Philox4x32 rng = new Philox4x32(67280421310721L, 0, 1); + assertEquals(1, rng.getOffset()); + assertEquals(EXPECTED_SEQUENCE_DEFAULT[4], rng.nextInt()); + rng = new Philox4x32(67280421310721L, 0, 1L << 32); + assertEquals(1L << 32, rng.getOffset()); + rng.setOffset(0); + assertEquals(EXPECTED_SEQUENCE_DEFAULT[0], rng.nextInt()); + } + + @Test + void testDouble() { + Philox4x32 rng = new Philox4x32(); + double valueOpen = rng.nextDoubleOpen(); + rng.setOffset(0); + double value = rng.nextDouble(); + assertEquals(value, valueOpen, Math.pow(2, -20)); //will differ after 20bits + } + + @Test + void testReset() { + Philox4x32 rng = new Philox4x32(67280421310721L, 0, 1); + assertEquals(EXPECTED_SEQUENCE_DEFAULT[4], rng.nextInt()); + rng.resetState(1234L, 0); + assertEquals(EXPECTED_SEQUENCE_1234[0], rng.nextInt()); + } + + @Test + void testInternalCounter() { + //test of incrementCounter + Philox4x32 rng = new Philox4x32(67280421310721L, 0, (1L << 32) - 1); + for (int i = 0; i < 4; i++) { + rng.next(); + } + long value = rng.next(); + Philox4x32 rng2 = new Philox4x32(67280421310721L, 0, 1L << 32); + long value2 = rng2.next(); + assertEquals(value, value2); + + rng = new Philox4x32(67280421310721L, 0, 0xffffffffffffffffL); + for (int i = 0; i < 4; i++) { + rng.next(); + } + value = rng.next(); + rng2 = new Philox4x32(67280421310721L, 1, 0); + value2 = rng2.next(); + assertEquals(value, value2); + + rng = new Philox4x32(67280421310721L, (1L << 32) - 1, 0xffffffffffffffffL); + for (int i = 0; i < 4; i++) { + rng.next(); + } + value = rng.next(); + rng2 = new Philox4x32(67280421310721L, 1L << 32, 0); + value2 = rng2.next(); + assertEquals(value, value2); + } + + @Test + void testJumpCounter() { + Philox4x32[] rngs = new Philox4x32[]{ + new Philox4x32(67280421310721L, 0, (1L << 32) - 1), + new Philox4x32(67280421310721L, 0, 0xffffffffffffffffL), + new Philox4x32(67280421310721L, (1L << 32) - 1, 0xffffffffffffffffL) + }; + for (Philox4x32 rng : rngs) { + UniformRandomProvider rngOrig = rng.jump(10); + long value = rng.nextInt(); + for (int i = 0; i < 10; i++) { + rngOrig.nextInt(); + } + long value2 = rngOrig.nextInt(); + assertEquals(value2, value); + } + } + + @Test + void testLongJumpCounter() { + Philox4x32 rng = new Philox4x32(1234L, 0xffffffffL, 0xffffffffffffffffL); + UniformRandomProvider rngOrig = rng.longJump(); + long value = rng.nextInt(); + Philox4x32 rng2 = new Philox4x32(1234L, 1L << 32, 0xffffffffffffffffL); + long value2 = rng2.nextInt(); + assertEquals(value2, value); + } + + @Test + void testArbitraryJumps() { + XoShiRo128PlusPlus rngForSkip = new XoShiRo128PlusPlus(10, 20, 30, 40); + Philox4x32 rng = new Philox4x32(); + for (int i = 0; i < 5120; i++) { + int n = rngForSkip.nextInt() >>> 23; + UniformRandomProvider rngOrig = rng.jump(n); + int jumpValue = rng.next(); + for (int k = 0; k < n; k++) { + rngOrig.nextInt(); + } + int origValue = rngOrig.nextInt(); + assertEquals(origValue, jumpValue); + } + Review Comment: Remove extra line breaks -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
