This is an automated email from the ASF dual-hosted git repository. aherbert pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-rng.git
commit 6299f15f9ecb1235089b15193321f23250b7f44e Author: Alex Herbert <[email protected]> AuthorDate: Wed Feb 18 16:30:39 2026 +0000 RNG-189: Add arbitrary jump examples to the user guide --- .../ArbitrarilyJumpableUniformRandomProvider.java | 2 +- .../commons/rng/core/source64/Philox4x64Test.java | 40 ++++++++++++ src/site/apt/userguide/rng.apt | 72 +++++++++++++++++++++- 3 files changed, 110 insertions(+), 4 deletions(-) diff --git a/commons-rng-client-api/src/main/java/org/apache/commons/rng/ArbitrarilyJumpableUniformRandomProvider.java b/commons-rng-client-api/src/main/java/org/apache/commons/rng/ArbitrarilyJumpableUniformRandomProvider.java index 2470a4ce..4414bff3 100644 --- a/commons-rng-client-api/src/main/java/org/apache/commons/rng/ArbitrarilyJumpableUniformRandomProvider.java +++ b/commons-rng-client-api/src/main/java/org/apache/commons/rng/ArbitrarilyJumpableUniformRandomProvider.java @@ -36,7 +36,7 @@ import java.util.stream.Stream; * {@link ArbitrarilyJumpableUniformRandomProvider} and {@link #jump(double) jump} the * generator forward while passing each copy generator to a worker thread. The jump * {@code distance} should be sufficient to cover all expected output by each worker. - * Since each copy generator is also a {@link ArbitrarilyJumpableUniformRandomProvider} + * Since each copy generator is also an {@link ArbitrarilyJumpableUniformRandomProvider} * with care it is possible to further distribute generators within the original jump * {@code distance} and use the entire state cycle in different ways.</p> * diff --git a/commons-rng-core/src/test/java/org/apache/commons/rng/core/source64/Philox4x64Test.java b/commons-rng-core/src/test/java/org/apache/commons/rng/core/source64/Philox4x64Test.java index b8aac746..b160b92f 100644 --- a/commons-rng-core/src/test/java/org/apache/commons/rng/core/source64/Philox4x64Test.java +++ b/commons-rng-core/src/test/java/org/apache/commons/rng/core/source64/Philox4x64Test.java @@ -22,6 +22,8 @@ import java.util.Arrays; import java.util.SplittableRandom; import java.util.stream.Stream; import java.util.stream.Stream.Builder; +import org.apache.commons.rng.ArbitrarilyJumpableUniformRandomProvider; +import org.apache.commons.rng.UniformRandomProvider; import org.apache.commons.rng.core.RandomAssert; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -614,4 +616,42 @@ public class Philox4x64Test { // Return result with the same counter size return Arrays.copyOf(value, counter.length); } + + @Test + void userGuideExample1() { + ArbitrarilyJumpableUniformRandomProvider jumpable = new Philox4x64(SEEDS[3]); + + double distance = 42; + for (int i = 0; i < 5; i++) { + // Copy the state and then jump ahead + UniformRandomProvider copy = jumpable.jump(distance); + + // Catch up the jump using the native 64-bit output + for (int j = 0; j < distance; j++) { + copy.nextLong(); + } + + // The copy matches the jumped generator + Assertions.assertEquals(copy.nextLong(), jumpable.nextLong()); + } + } + + @Test + void userGuideExample2() { + ArbitrarilyJumpableUniformRandomProvider jumpable = new Philox4x64(SEEDS[3]); + + int logDistance = 123; + ArbitrarilyJumpableUniformRandomProvider copy = jumpable.jumpPowerOfTwo(logDistance); + + // Catch up the jump using: 4 * 2^119 + 2^121 + 2^122 + copy.jumpPowerOfTwo(logDistance - 4); + copy.jumpPowerOfTwo(logDistance - 4); + copy.jumpPowerOfTwo(logDistance - 2); + copy.jumpPowerOfTwo(logDistance - 4); + copy.jumpPowerOfTwo(logDistance - 1); + copy.jumpPowerOfTwo(logDistance - 4); + + // The copy matches the jumped generator + Assertions.assertEquals(copy.nextLong(), jumpable.nextLong()); + } } diff --git a/src/site/apt/userguide/rng.apt b/src/site/apt/userguide/rng.apt index 32346ad0..86fb3391 100644 --- a/src/site/apt/userguide/rng.apt +++ b/src/site/apt/userguide/rng.apt @@ -397,9 +397,56 @@ jumpable.jumps(streamSize).forEach(rng -> { Note that here the stream of RNGs is sequential; each RNG is used within a potentially long-running task that can run concurrently with other tasks using an executor service. - In the above example, the source is known to implement the <<<JumpableUniformRandomProvider>>> interface. + * The <<<ArbitrarilyJumpableUniformRandomProvider>>> interface allows creation of a copy of the generator and + advances the state of the current generator an <arbitrary> number of steps in a single jump. + Jump distances are supported using a <<<double>>> or using a power-of-2. Streams of jumpable + generators can be created using a <<<double>>> distance. + Since each copy generator is also an <<<ArbitrarilyJumpableUniformRandomProvider>>> + with care it is possible to further distribute generators within the original jump + distance and use the entire state cycle in different ways. + ++--------------------------+ +import org.apache.commons.rng.UniformRandomProvider; +import org.apache.commons.rng.ArbitrarilyJumpableUniformRandomProvider; +import org.apache.commons.rng.simple.RandomSource; + +RandomSource source = RandomSource.PHILOX_4X64; // Known to be arbitrarily jumpable. + +ArbitrarilyJumpableUniformRandomProvider jumpable = (ArbitrarilyJumpableUniformRandomProvider) source.create(); + +double distance = 42; +for (int i = 0; i < 5; i++) { + // Copy the state and then jump ahead + UniformRandomProvider copy = jumpable.jump(distance); + + // Catch up the jump using the native 64-bit output + for (int j = 0; j < distance; j++) { + copy.nextLong(); + } + + // The copy matches the jumped generator + assert copy.nextLong() == jumpable.nextLong(); +} + +int logDistance = 123; +ArbitrarilyJumpableUniformRandomProvider copy = jumpable.jumpPowerOfTwo(logDistance); + +// Catch up the jump using: 4 * 2^119 + 2^121 + 2^122 +copy.jumpPowerOfTwo(logDistance - 4); +copy.jumpPowerOfTwo(logDistance - 4); +copy.jumpPowerOfTwo(logDistance - 2); +copy.jumpPowerOfTwo(logDistance - 4); +copy.jumpPowerOfTwo(logDistance - 1); +copy.jumpPowerOfTwo(logDistance - 4); + +// The copy matches the jumped generator +assert copy.nextLong() == jumpable.nextLong(); ++--------------------------+ + + In the above examples, the source is known to implement the appropriate jumpable interface. Not all generators support this functionality. You can determine if a <<<RandomSource>>> is - jumpable without creating one using the instance methods <<<isJumpable()>>> and <<<isLongJumpable()>>>. + jumpable without creating one using the instance methods <<<isJumpable()>>>, + <<<isLongJumpable()>>> and <<<isArbitrarilyJumpable>>>. +--------------------------+ import org.apache.commons.rng.simple.RandomSource; @@ -412,6 +459,24 @@ public void initialise(RandomSource source) { } +--------------------------+ + Jumping can be used to create a series of non-overlapping generators + for use in multithreaded applications. + Note that there is not a one-to-one relationship between the number of output random + values from a provider and the number of steps from the underlying state cycle. This + is due to: + + * Possible use of rejection algorithms to output a random value using multiple + values from the state cycle. + + * The number of bits required to generate a random value differing from the + number of bits generated by the underlying source of randomness. For example + generation of a 64-bit <<<long>>> value using a 32-bit source of randomness. + + Users are advised to use jumping generators with care to avoid + overlapping output of multiple generators in parallel computations. + A cautious approach is to use a jump distance far larger than the expected + output length used by each generator. + * The <<<SplittableUniformRandomProvider>>> interface allows splitting a generator into two objects (the original and a new instance) each of which implements the same interface (and can be recursively split indefinitely). This can be used for parallel computations where the @@ -650,7 +715,8 @@ double[] coordinate = sampler.sample(); * Interfaces <<<RestorableUniformRandomProvider>>> and <<<RandomProviderState>>> provide the "save/restore" API. - * Interfaces <<<JumpableUniformRandomProvider>>> and <<<LongJumpableUniformRandomProvider>>> + * Interfaces <<<JumpableUniformRandomProvider>>>, <<<LongJumpableUniformRandomProvider>>> + and <<<ArbitrarilyJumpableUniformRandomProvider>>> provide the "copy and jump" API for parallel computations. These are suitable for tasks where the number of instances to use in parallel is known.
