View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.rng.core.source64;
18  
19  import java.math.BigInteger;
20  import java.util.SplittableRandom;
21  import java.util.concurrent.ThreadLocalRandom;
22  import java.util.function.Function;
23  import java.util.function.LongSupplier;
24  import java.util.function.Supplier;
25  import java.util.function.UnaryOperator;
26  import java.util.stream.IntStream;
27  import java.util.stream.LongStream;
28  import java.util.stream.Stream;
29  import org.apache.commons.rng.LongJumpableUniformRandomProvider;
30  import org.apache.commons.rng.RestorableUniformRandomProvider;
31  import org.apache.commons.rng.core.RandomAssert;
32  import org.apache.commons.rng.core.util.NumberFactory;
33  import org.junit.jupiter.api.Assertions;
34  import org.junit.jupiter.api.RepeatedTest;
35  import org.junit.jupiter.api.Test;
36  import org.junit.jupiter.api.TestInstance;
37  import org.junit.jupiter.api.TestInstance.Lifecycle;
38  import org.junit.jupiter.params.ParameterizedTest;
39  import org.junit.jupiter.params.provider.Arguments;
40  import org.junit.jupiter.params.provider.MethodSource;
41  
42  /**
43   * Base test for LXM generators. These generators all use the same basic steps:
44   * <pre>
45   * s = state of LCG
46   * t = state of XBG
47   *
48   * generate():
49   *    z <- mix(combine(w upper bits of s, w upper bits of t))
50   *    s <- LCG state update
51   *    t <- XBG state update
52   * </pre>
53   *
54   * <p>This base class provides a composite generator of an LCG and XBG
55   * both using w = 64-bits. Tests for a RNG implementation must define
56   * a factory for constructing a reference composite LXM generator. Implementations
57   * for the 64-bit and 128-bit LCG used in the LXM family are provided.
58   * Implementations for the XBG are provided based on version in the Commons RNG core library.
59   *
60   * <p>It is assumed the XBG generator is a {@link LongJumpableUniformRandomProvider}.
61   * The composite generator requires the sub-generators to provide a jump and long jump
62   * equivalent operation. This is performed by advancing/rewinding the LCG and XBG the same number
63   * of cycles. In practice this is performed using:
64   * <ul>
65   * <li>A large jump of the XBG that wraps the state of the LCG.
66   * <li>Leaving the XBG state unchanged and advancing the LCG. This effectively
67   * rewinds the state of the LXM by the period of the XBG multiplied by the number of
68   * cycles advanced by the LCG.
69   * </ul>
70   *
71   * <p>The paper by Steele and Vigna suggest advancing the LCG to take advantage of the fast
72   * update step of the LCG. This is particularly true for the 64-bit LCG variants. If
73   * the LCG and XBG sub-generators support jump/longJump then the composite can then be
74   * used to test arbitrary combinations of calls to: generate the next long value; and jump
75   * operations. This is not possible using the reference implementations of the LXM family
76   * in JDK 17 which do not implement jumping (instead providing a split operation to create
77   * new RNGs).
78   *
79   * <p>The test assumes the following conditions:
80   * <ul>
81   * <li>The seed length equals the state size of the LCG and XBG generators.
82   * <li>The LXM generator seed is the seed for the LCG appended with the seed for the XBG.
83   * <li>The LCG seed in order is [additive parameter, initial state]. The additive parameter
84   * must be odd for a maximum period LCG and the test asserts the final bit of the add parameter
85   * from the seed is effectively ignored as it is forced to be odd in the constructor.
86   * <li>If the XBG seed is all zeros, the LXM generator is partially functional. It will output
87   * non-zero values but the sequence may be statistically weak and the period is that of the LCG.
88   * This cannot be practically tested but non-zero output is verified for an all zero seed.
89   * </ul>
90   *
91   * <p>Note: The seed order prioritises the additive parameter first. This parameter can be used to
92   * create independent streams of generators. It allows constructing a generator from a single long
93   * value, where the rest of the state is filled with a procedure generating non-zero values,
94   * which will be independent from a second generator constructed with a different single additive
95   * parameter. The difference must be in the high 63-bits of the seed value for 64-bit LCG variants.
96   * A suitable filling procedure from an initial seed value is provided in the Commons RNG Simple
97   * module.
98   *
99   * <p>The class uses a per-class lifecycle. This allows abstract methods to be overridden
100  * in implementing classes to control test behaviour.
101  */
102 @TestInstance(Lifecycle.PER_CLASS)
103 abstract class AbstractLXMTest {
104     /**
105      * A function to mix two long values.
106      * Implements the combine and mix functions of the LXM generator.
107      */
108     interface Mix {
109         /**
110          * Mix the values.
111          *
112          * @param a Value a
113          * @param b Value b
114          * @return the result
115          */
116         long apply(long a, long b);
117     }
118 
119     /**
120      * A jumpable sub-generator. This can be a linear congruential generator (LCG)
121      * or xor-based generator (XBG) when used in the LXM family.
122      *
123      * <p>For simplicity the steps of obtaining the upper bits of the state
124      * and updating the state are combined to a single operation.
125      */
126     interface SubGen {
127         /**
128          * Return the upper 64-bits of the current state and update the state.
129          *
130          * @return the upper 64-bits of the old state
131          */
132         long stateAndUpdate();
133 
134         /**
135          * Create a copy and then advance the state in a single jump; the copy is returned.
136          *
137          * @return the copy
138          */
139         SubGen copyAndJump();
140 
141         /**
142          * Create a copy and then advance the state in a single long jump; the copy is returned.
143          *
144          * @return the copy
145          */
146         SubGen copyAndLongJump();
147     }
148 
149     /**
150      * Mix the sum using the star star function.
151      *
152      * @param a Value a
153      * @param b Value b
154      * @return the result
155      */
156     static long mixStarStar(long a, long b) {
157         return Long.rotateLeft((a + b) * 5, 7) * 9;
158     }
159 
160     /**
161      * Mix the sum using the lea64 function.
162      *
163      * @param a Value a
164      * @param b Value b
165      * @return the result
166      */
167     static long mixLea64(long a, long b) {
168         return LXMSupport.lea64(a + b);
169     }
170 
171     /**
172      * A 64-bit linear congruential generator.
173      */
174     static class LCG64 implements SubGen {
175         /** Multiplier. */
176         private static final long M = LXMSupport.M64;
177         /** Additive parameter (must be odd). */
178         private final long a;
179         /** State. */
180         private long s;
181         /** Power of 2 for the jump. */
182         private final int jumpPower;
183         /** Power of 2 for the long jump. */
184         private final int longJumpPower;
185 
186         /**
187          * Create an instance with a jump of 1 cycle and long jump of 2^32 cycles.
188          *
189          * @param a Additive parameter (set to odd)
190          * @param s State
191          */
192         LCG64(long a, long s) {
193             this(a, s, 0, 32);
194         }
195 
196         /**
197          * @param a Additive parameter (set to odd)
198          * @param s State
199          * @param jumpPower Jump size as a power of 2
200          * @param longJumpPower Long jump size as a power of 2
201          */
202         LCG64(long a, long s, int jumpPower, int longJumpPower) {
203             this.a = a | 1;
204             this.s = s;
205             this.jumpPower = jumpPower;
206             this.longJumpPower = longJumpPower;
207         }
208 
209         @Override
210         public long stateAndUpdate() {
211             final long s0 = s;
212             s = M * s + a;
213             return s0;
214         }
215 
216         @Override
217         public SubGen copyAndJump() {
218             final SubGen copy = new LCG64(a, s, jumpPower, longJumpPower);
219             s = LXMSupportTest.lcgAdvancePow2(s, M, a, jumpPower);
220             return copy;
221         }
222 
223         @Override
224         public SubGen copyAndLongJump() {
225             final SubGen copy = new LCG64(a, s, jumpPower, longJumpPower);
226             s = LXMSupportTest.lcgAdvancePow2(s, M, a, longJumpPower);
227             return copy;
228         }
229     }
230 
231     /**
232      * A 128-bit linear congruential generator.
233      */
234     static class LCG128 implements SubGen {
235         /** Low half of 128-bit multiplier. The upper half is 1. */
236         private static final long ML = LXMSupport.M128L;
237         /** High bits of additive parameter. */
238         private final long ah;
239         /** Low bits of additive parameter (must be odd). */
240         private final long al;
241         /** High bits of state. */
242         private long sh;
243         /** Low bits of state. */
244         private long sl;
245         /** Power of 2 for the jump. */
246         private final int jumpPower;
247         /** Power of 2 for the long jump. */
248         private final int longJumpPower;
249 
250         /**
251          * Create an instance with a jump of 1 cycle and long jump of 2^64 cycles.
252          *
253          * @param ah High bits of additive parameter
254          * @param al Low bits of additive parameter (set to odd)
255          * @param sh High bits of the 128-bit state
256          * @param sl Low bits of the 128-bit state
257          */
258         LCG128(long ah, long al, long sh, long sl) {
259             this(ah, al, sh, sl, 0, 64);
260         }
261 
262         /**
263          * @param ah High bits of additive parameter
264          * @param al Low bits of additive parameter (set to odd)
265          * @param sh High bits of the 128-bit state
266          * @param sl Low bits of the 128-bit state
267          * @param jumpPower Jump size as a power of 2
268          * @param longJumpPower Long jump size as a power of 2
269          */
270         LCG128(long ah, long al, long sh, long sl, int jumpPower, int longJumpPower) {
271             this.ah = ah;
272             this.al = al | 1;
273             this.sh = sh;
274             this.sl = sl;
275             this.jumpPower = jumpPower;
276             this.longJumpPower = longJumpPower;
277         }
278 
279         @Override
280         public long stateAndUpdate() {
281             final long s0 = sh;
282             // The LCG is, in effect, "s = m * s + a" where m = ((1LL << 64) + ML)
283             final long u = ML * sl;
284             // High half
285             sh = (ML * sh) + LXMSupport.unsignedMultiplyHigh(ML, sl) + sl + ah;
286             // Low half
287             sl = u + al;
288             // Carry propagation
289             if (Long.compareUnsigned(sl, u) < 0) {
290                 ++sh;
291             }
292             return s0;
293         }
294 
295         @Override
296         public SubGen copyAndJump() {
297             final SubGen copy = new LCG128(ah, al, sh, sl, jumpPower, longJumpPower);
298             final long nsl = LXMSupportTest.lcgAdvancePow2(sl, ML, al, jumpPower);
299             sh = LXMSupportTest.lcgAdvancePow2High(sh, sl, 1, ML, ah, al, jumpPower);
300             sl = nsl;
301             return copy;
302         }
303 
304         @Override
305         public SubGen copyAndLongJump() {
306             final SubGen copy = new LCG128(ah, al, sh, sl, jumpPower, longJumpPower);
307             final long nsl = LXMSupportTest.lcgAdvancePow2(sl, ML, al, longJumpPower);
308             sh = LXMSupportTest.lcgAdvancePow2High(sh, sl, 1, ML, ah, al, longJumpPower);
309             sl = nsl;
310             return copy;
311         }
312     }
313 
314     /**
315      * A xor-based generator using XoRoShiRo128.
316      *
317      * <p>The generator can be configured to perform jumps or simply return a copy.
318      * The LXM generator would jump by advancing only the LCG state.
319      */
320     static class XBGXoRoShiRo128 extends AbstractXoRoShiRo128 implements SubGen {
321         /** Jump flag. */
322         private final boolean jump;
323 
324         /**
325          * Create an instance with jumping enabled.
326          *
327          * @param seed seed
328          */
329         XBGXoRoShiRo128(long[] seed) {
330             super(seed);
331             jump = true;
332         }
333 
334         /**
335          * @param seed0 seed element 0
336          * @param seed1 seed element 1
337          * @param jump Set to true to perform jumping, otherwise a jump returns a copy
338          */
339         XBGXoRoShiRo128(long seed0, long seed1, boolean jump) {
340             super(seed0, seed1);
341             this.jump = jump;
342         }
343 
344         /**
345          * Copy constructor.
346          *
347          * @param source the source to copy
348          */
349         XBGXoRoShiRo128(XBGXoRoShiRo128 source) {
350             // There is no super-class copy constructor so just construct
351             // from the RNG state.
352             // Warning:
353             // This will not copy the cached state from 'source'
354             // which matters if tests use nextInt or nextBoolean.
355             // Currently tests only target the long output.
356             this(source.state0, source.state1, source.jump);
357         }
358 
359         @Override
360         public long stateAndUpdate() {
361             final long s0 = state0;
362             next();
363             return s0;
364         }
365 
366         @Override
367         public SubGen copyAndJump() {
368             return (SubGen) (jump ? super.jump() : copy());
369         }
370 
371         @Override
372         public SubGen copyAndLongJump() {
373             return (SubGen) (jump ? super.longJump() : copy());
374         }
375 
376         @Override
377         protected long nextOutput() {
378             // Not used
379             return 0;
380         }
381 
382         @Override
383         protected XBGXoRoShiRo128 copy() {
384             return new XBGXoRoShiRo128(this);
385         }
386     }
387 
388     /**
389      * A xor-based generator using XoShiRo256.
390      *
391      * <p>The generator can be configured to perform jumps or simply return a copy.
392      * The LXM generator would jump by advancing only the LCG state.
393      */
394     static class XBGXoShiRo256 extends AbstractXoShiRo256 implements SubGen {
395         /** Jump flag. */
396         private final boolean jump;
397 
398         /**
399          * Create an instance with jumping enabled.
400          *
401          * @param seed seed
402          */
403         XBGXoShiRo256(long[] seed) {
404             super(seed);
405             jump = true;
406         }
407 
408         /**
409          * @param seed0 seed element 0
410          * @param seed1 seed element 1
411          * @param seed2 seed element 2
412          * @param seed3 seed element 3
413          * @param jump Set to true to perform jumping, otherwise a jump returns a copy
414          */
415         XBGXoShiRo256(long seed0, long seed1, long seed2, long seed3, boolean jump) {
416             super(seed0, seed1, seed2, seed3);
417             this.jump = jump;
418         }
419 
420         /**
421          * Copy constructor.
422          *
423          * @param source the source to copy
424          */
425         XBGXoShiRo256(XBGXoShiRo256 source) {
426             super(source);
427             jump = source.jump;
428         }
429 
430         @Override
431         public long stateAndUpdate() {
432             final long s0 = state0;
433             next();
434             return s0;
435         }
436 
437         @Override
438         public SubGen copyAndJump() {
439             return (SubGen) (jump ? super.jump() : copy());
440         }
441 
442         @Override
443         public SubGen copyAndLongJump() {
444             return (SubGen) (jump ? super.longJump() : copy());
445         }
446 
447         @Override
448         protected long nextOutput() {
449             // Not used
450             return 0;
451         }
452 
453         @Override
454         protected XBGXoShiRo256 copy() {
455             return new XBGXoShiRo256(this);
456         }
457     }
458 
459     /**
460      * A xor-based generator using XoRoShiRo1024.
461      *
462      * <p>The generator can be configured to perform jumps or simply return a copy.
463      * The LXM generator would jump by advancing only the LCG state.
464      *
465      * <p>Note: To avoid changes to the parent class this test class uses the save/restore
466      * function to initialise the index. The rng state update can be managed by the
467      * super-class and this class holds a reference to the most recent s0 value.
468      */
469     static class XBGXoRoShiRo1024 extends AbstractXoRoShiRo1024 implements SubGen {
470         /** Jump flag. */
471         private final boolean jump;
472         /** The most recent s0 variable passed to {@link #transform(long, long)}. */
473         private long state0;
474 
475         /**
476          * Create an instance with jumping enabled.
477          *
478          * @param seed 16-element seed
479          */
480         XBGXoRoShiRo1024(long[] seed) {
481             this(seed, true);
482         }
483 
484         /**
485          * @param seed 16-element seed
486          * @param jump Set to true to perform jumping, otherwise a jump returns a copy
487          */
488         XBGXoRoShiRo1024(long[] seed, boolean jump) {
489             super(seed);
490             this.jump = jump;
491             // Ensure the first state returned corresponds to state[0].
492             // This requires setting:
493             // index = state.length - 1
494             // To avoid changing the private visibility of super-class state variables
495             // this is done using the save/restore function. It avoids any issues
496             // with using reflection on the 'index' field but must be maintained
497             // inline with the save/restore logic of the super-class.
498             final byte[] s = super.getStateInternal();
499             final byte[][] c = splitStateInternal(s, 17 * Long.BYTES);
500             final long[] tmp = NumberFactory.makeLongArray(c[0]);
501             // Here: index = state.length - 1
502             tmp[16] = 15;
503             c[0] = NumberFactory.makeByteArray(tmp);
504             super.setStateInternal(composeStateInternal(c[0], c[1]));
505         }
506 
507         /**
508          * Copy constructor.
509          *
510          * @param source the source to copy
511          */
512         XBGXoRoShiRo1024(XBGXoRoShiRo1024 source) {
513             super(source);
514             jump = source.jump;
515         }
516 
517         @Override
518         public long stateAndUpdate() {
519             next();
520             return state0;
521         }
522 
523         @Override
524         public SubGen copyAndJump() {
525             return (SubGen) (jump ? super.jump() : copy());
526         }
527 
528         @Override
529         public SubGen copyAndLongJump() {
530             return (SubGen) (jump ? super.longJump() : copy());
531         }
532 
533         @Override
534         protected long transform(long s0, long s15) {
535             this.state0 = s0;
536             // No transformation required.
537             return 0;
538         }
539 
540         @Override
541         protected XBGXoRoShiRo1024 copy() {
542             return new XBGXoRoShiRo1024(this);
543         }
544     }
545 
546     /**
547      * A composite LXM generator. Implements:
548      * <pre>
549      * s = state of LCG
550      * t = state of XBG
551      *
552      * generate():
553      *    z <- mix(combine(w upper bits of s, w upper bits of t))
554      *    s <- LCG state update
555      *    t <- XBG state update
556      * </pre>
557      * <p>w is assumed to be 64-bits.
558      */
559     static class LXMGenerator extends LongProvider implements LongJumpableUniformRandomProvider {
560         /** Mix implementation. */
561         private final Mix mix;
562         /** LCG implementation. */
563         private final SubGen lcg;
564         /** XBG implementation. */
565         private final SubGen xbg;
566 
567         /**
568          * Create a new instance.
569          * The jump and long jump of the LCG are assumed to appropriately match those of the XBG.
570          * This can be achieved by an XBG jump that wraps the period of the LCG; or advancing
571          * the LCG and leaving the XBG state unchanged, effectively rewinding the LXM generator.
572          *
573          * @param mix Mix implementation
574          * @param lcg LCG implementation
575          * @param xbg XBG implementation
576          */
577         LXMGenerator(Mix mix, SubGen lcg, SubGen xbg) {
578             this.lcg = lcg;
579             this.xbg = xbg;
580             this.mix = mix;
581         }
582 
583         @Override
584         public long next() {
585             return mix.apply(lcg.stateAndUpdate(), xbg.stateAndUpdate());
586         }
587 
588         @Override
589         public LXMGenerator jump() {
590             return new LXMGenerator(mix, lcg.copyAndJump(), xbg.copyAndJump());
591         }
592 
593         @Override
594         public LXMGenerator longJump() {
595             return new LXMGenerator(mix, lcg.copyAndLongJump(), xbg.copyAndLongJump());
596         }
597     }
598 
599     /**
600      * A factory for creating LXMGenerator objects.
601      */
602     interface LXMGeneratorFactory {
603         /**
604          * Return the size of the LCG long seed array.
605          *
606          * @return the LCG seed size
607          */
608         int lcgSeedSize();
609 
610         /**
611          * Return the size of the XBG long seed array.
612          *
613          * @return the XBG seed size
614          */
615         int xbgSeedSize();
616 
617         /**
618          * Return the size of the long seed array.
619          * The default implementation adds the size of the LCG and XBG seed.
620          *
621          * @return the seed size
622          */
623         default int seedSize() {
624             return lcgSeedSize() + xbgSeedSize();
625         }
626 
627         /**
628          * Creates a new LXMGenerator.
629          *
630          * <p>Tests using the LXMGenerator assume the seed is a composite containing the
631          * LCG seed and then the XBG seed.
632          *
633          * @param seed the seed
634          * @return the generator
635          */
636         LXMGenerator create(long[] seed);
637 
638         /**
639          * Gets the mix implementation. This is used to test initial output of the generator.
640          * The default implementation is {@link AbstractLXMTest#mixLea64(long, long)}.
641          *
642          * @return the mix
643          */
644         default Mix getMix() {
645             return AbstractLXMTest::mixLea64;
646         }
647     }
648 
649     /**
650      * Test the LCG implementations. These tests should execute only once, and not
651      * for each instance of the abstract outer class (i.e. the test is not {@code @Nested}).
652      *
653      * <p>Note: The LCG implementations are not present in the main RNG core package and
654      * this test ensures an LCG update of:
655      * <pre>
656      * s = m * s + a
657      *
658      * s = state
659      * m = multiplier
660      * a = addition
661      * </pre>
662      *
663      * <p>A test is made to ensure the LCG can perform jump and long jump operations using
664      * small jumps that can be verified by an equal number of single state updates.
665      */
666     static class LCGTest {
667         /** 65-bit multiplier for the 128-bit LCG. */
668         private static final BigInteger M = BigInteger.ONE.shiftLeft(64).add(toUnsignedBigInteger(LXMSupport.M128L));
669         /** 2^128. Used as the modulus for the 128-bit LCG. */
670         private static final BigInteger MOD = BigInteger.ONE.shiftLeft(128);
671 
672         /** A count {@code k} where a jump of {@code 2^k} will wrap the LCG state. */
673         private static final int NO_JUMP = -1;
674         /** A count {@code k=2} for a jump of {@code 2^k}, or 4 cycles. */
675         private static final int JUMP = 2;
676         /** A count {@code k=4} for a long jump of {@code 2^k}, or 16 cycles. */
677         private static final int LONG_JUMP = 4;
678 
679         @RepeatedTest(value = 10)
680         void testLCG64DefaultJump() {
681             final SplittableRandom rng = new SplittableRandom();
682             final long state = rng.nextLong();
683             final long add = rng.nextLong();
684             final SubGen lcg1 = new LCG64(add, state);
685             final SubGen lcg2 = new LCG64(add, state, 0, 32);
686             for (int j = 0; j < 10; j++) {
687                 Assertions.assertEquals(lcg1.stateAndUpdate(), lcg2.stateAndUpdate(),
688                     () -> String.format("seed %d,%d", state, add));
689             }
690             lcg1.copyAndJump();
691             lcg2.copyAndJump();
692             for (int j = 0; j < 10; j++) {
693                 Assertions.assertEquals(lcg1.stateAndUpdate(), lcg2.stateAndUpdate(),
694                     () -> String.format("seed %d,%d", state, add));
695             }
696             lcg1.copyAndLongJump();
697             lcg2.copyAndLongJump();
698             for (int j = 0; j < 10; j++) {
699                 Assertions.assertEquals(lcg1.stateAndUpdate(), lcg2.stateAndUpdate(),
700                     () -> String.format("seed %d,%d", state, add));
701             }
702         }
703 
704         @RepeatedTest(value = 10)
705         void testLCG64() {
706             final SplittableRandom rng = new SplittableRandom();
707             final long state = rng.nextLong();
708             final long add = rng.nextLong();
709             long s = state;
710             final long a = add | 1;
711             final SubGen lcg = new LCG64(add, state, NO_JUMP, NO_JUMP);
712             for (int j = 0; j < 10; j++) {
713                 Assertions.assertEquals(s, lcg.stateAndUpdate(),
714                     () -> String.format("seed %d,%d", state, add));
715                 s = LXMSupport.M64 * s + a;
716             }
717         }
718 
719         @RepeatedTest(value = 10)
720         void testLCG64Jump() {
721             final SplittableRandom rng = new SplittableRandom();
722             final long state = rng.nextLong();
723             final long add = rng.nextLong();
724             final Supplier<String> msg = () -> String.format("seed %d,%d", state, add);
725             long s = state;
726             final long a = add | 1;
727             final SubGen lcg = new LCG64(add, state, JUMP, LONG_JUMP);
728 
729             final SubGen copy1 = lcg.copyAndJump();
730             for (int j = 1 << JUMP; j-- != 0;) {
731                 Assertions.assertEquals(s, copy1.stateAndUpdate(), msg);
732                 s = LXMSupport.M64 * s + a;
733             }
734             Assertions.assertEquals(s, lcg.stateAndUpdate(), msg);
735             s = LXMSupport.M64 * s + a;
736 
737             final SubGen copy2 = lcg.copyAndLongJump();
738             for (int j = 1 << LONG_JUMP; j-- != 0;) {
739                 Assertions.assertEquals(s, copy2.stateAndUpdate(), msg);
740                 s = LXMSupport.M64 * s + a;
741             }
742             Assertions.assertEquals(s, lcg.stateAndUpdate(), msg);
743         }
744 
745         @RepeatedTest(value = 10)
746         void testLCG128DefaultJump() {
747             final SplittableRandom rng = new SplittableRandom();
748             final long stateh = rng.nextLong();
749             final long statel = rng.nextLong();
750             final long addh = rng.nextLong();
751             final long addl = rng.nextLong();
752             final SubGen lcg1 = new LCG128(addh, addl, stateh, statel);
753             final SubGen lcg2 = new LCG128(addh, addl, stateh, statel, 0, 64);
754             for (int j = 0; j < 10; j++) {
755                 Assertions.assertEquals(lcg1.stateAndUpdate(), lcg2.stateAndUpdate(),
756                     () -> String.format("seed %d,%d,%d,%d", stateh, statel, addh, addl));
757             }
758             lcg1.copyAndJump();
759             lcg2.copyAndJump();
760             for (int j = 0; j < 10; j++) {
761                 Assertions.assertEquals(lcg1.stateAndUpdate(), lcg2.stateAndUpdate(),
762                     () -> String.format("seed %d,%d,%d,%d", stateh, statel, addh, addl));
763             }
764             lcg1.copyAndLongJump();
765             lcg2.copyAndLongJump();
766             for (int j = 0; j < 10; j++) {
767                 Assertions.assertEquals(lcg1.stateAndUpdate(), lcg2.stateAndUpdate(),
768                     () -> String.format("seed %d,%d,%d,%d", stateh, statel, addh, addl));
769             }
770         }
771 
772         @RepeatedTest(value = 10)
773         void testLCG128() {
774             final SplittableRandom rng = new SplittableRandom();
775             final long stateh = rng.nextLong();
776             final long statel = rng.nextLong();
777             final long addh = rng.nextLong();
778             final long addl = rng.nextLong();
779             BigInteger s = toUnsignedBigInteger(stateh).shiftLeft(64).add(toUnsignedBigInteger(statel));
780             final BigInteger a = toUnsignedBigInteger(addh).shiftLeft(64).add(toUnsignedBigInteger(addl | 1));
781             final SubGen lcg = new LCG128(addh, addl, stateh, statel, NO_JUMP, NO_JUMP);
782             for (int j = 0; j < 10; j++) {
783                 Assertions.assertEquals(s.shiftRight(64).longValue(), lcg.stateAndUpdate(),
784                         () -> String.format("seed %d,%d,%d,%d", stateh, statel, addh, addl));
785                 s = M.multiply(s).add(a).mod(MOD);
786             }
787         }
788 
789         @RepeatedTest(value = 10)
790         void testLCG128Jump() {
791             final SplittableRandom rng = new SplittableRandom();
792             final long stateh = rng.nextLong();
793             final long statel = rng.nextLong();
794             final long addh = rng.nextLong();
795             final long addl = rng.nextLong();
796             final Supplier<String> msg = () -> String.format("seed %d,%d,%d,%d", stateh, statel, addh, addl);
797             BigInteger s = toUnsignedBigInteger(stateh).shiftLeft(64).add(toUnsignedBigInteger(statel));
798             final BigInteger a = toUnsignedBigInteger(addh).shiftLeft(64).add(toUnsignedBigInteger(addl | 1));
799             final SubGen lcg = new LCG128(addh, addl, stateh, statel, JUMP, LONG_JUMP);
800 
801             final SubGen copy1 = lcg.copyAndJump();
802             for (int j = 1 << JUMP; j-- != 0;) {
803                 Assertions.assertEquals(s.shiftRight(64).longValue(), copy1.stateAndUpdate(), msg);
804                 s = M.multiply(s).add(a).mod(MOD);
805             }
806             Assertions.assertEquals(s.shiftRight(64).longValue(), lcg.stateAndUpdate(), msg);
807             s = M.multiply(s).add(a).mod(MOD);
808 
809             final SubGen copy2 = lcg.copyAndLongJump();
810             for (int j = 1 << LONG_JUMP; j-- != 0;) {
811                 Assertions.assertEquals(s.shiftRight(64).longValue(), copy2.stateAndUpdate(), msg);
812                 s = M.multiply(s).add(a).mod(MOD);
813             }
814             Assertions.assertEquals(s.shiftRight(64).longValue(), lcg.stateAndUpdate(), msg);
815         }
816 
817         /**
818          * Create a big integer treating the value as unsigned.
819          *
820          * @param v Value
821          * @return the big integer
822          */
823         private static BigInteger toUnsignedBigInteger(long v) {
824             // Delegate
825             return LXMSupportTest.toUnsignedBigInteger(v);
826         }
827     }
828 
829     /**
830      * Test the XBG implementations. These tests should execute only once, and not
831      * for each instance of the abstract outer class (i.e. the test is not {@code @Nested}).
832      *
833      * <p>Note: The XBG implementation are extensions of RNGs already verified against
834      * reference implementations. These tests ensure that the upper bits of the state is
835      * identical after {@code n} cycles then a jump; or a jump then {@code n} cycles.
836      * This is the functionality required for a jumpable XBG generator.
837      */
838     static class XBGTest {
839 
840         @RepeatedTest(value = 5)
841         void testXBGXoRoShiRo128NoJump() {
842             final SplittableRandom r = new SplittableRandom();
843             assertNoJump(new XBGXoRoShiRo128(r.nextLong(), r.nextLong(), false));
844         }
845 
846         @RepeatedTest(value = 5)
847         void testXBGXoShiRo256NoJump() {
848             final SplittableRandom r = new SplittableRandom();
849             assertNoJump(new XBGXoShiRo256(r.nextLong(), r.nextLong(), r.nextLong(), r.nextLong(), false));
850         }
851 
852         @RepeatedTest(value = 5)
853         void testXBGXoRoShiRo1024NoJump() {
854             final SplittableRandom r = new SplittableRandom();
855             assertNoJump(new XBGXoRoShiRo1024(r.longs(16).toArray(), false));
856         }
857 
858         void assertNoJump(SubGen g) {
859             final SubGen g1 = g.copyAndJump();
860             Assertions.assertNotSame(g, g1);
861             for (int i = 0; i < 10; i++) {
862                 Assertions.assertEquals(g.stateAndUpdate(), g1.stateAndUpdate());
863             }
864             final SubGen g2 = g.copyAndLongJump();
865             Assertions.assertNotSame(g, g2);
866             for (int i = 0; i < 10; i++) {
867                 Assertions.assertEquals(g.stateAndUpdate(), g2.stateAndUpdate());
868             }
869         }
870 
871         @RepeatedTest(value = 5)
872         void testXBGXoRoShiRo128Jump() {
873             assertJumpAndCycle(ThreadLocalRandom.current().nextLong(), 2, XBGXoRoShiRo128::new, SubGen::copyAndJump);
874         }
875 
876         @RepeatedTest(value = 5)
877         void testXBGXoRoShiRo128LongJump() {
878             assertJumpAndCycle(ThreadLocalRandom.current().nextLong(), 2, XBGXoRoShiRo128::new, SubGen::copyAndLongJump);
879         }
880 
881         @RepeatedTest(value = 5)
882         void testXBGXoShiRo256Jump() {
883             assertJumpAndCycle(ThreadLocalRandom.current().nextLong(), 4, XBGXoShiRo256::new, SubGen::copyAndJump);
884         }
885 
886         @RepeatedTest(value = 5)
887         void testXBGXShiRo256LongJump() {
888             assertJumpAndCycle(ThreadLocalRandom.current().nextLong(), 4, XBGXoShiRo256::new, SubGen::copyAndLongJump);
889         }
890 
891         @RepeatedTest(value = 5)
892         void testXBGXoRoShiRo1024Jump() {
893             assertJumpAndCycle(ThreadLocalRandom.current().nextLong(), 16, XBGXoRoShiRo1024::new, SubGen::copyAndJump);
894         }
895 
896         @RepeatedTest(value = 5)
897         void testXBGXoRoShiRo1024LongJump() {
898             assertJumpAndCycle(ThreadLocalRandom.current().nextLong(), 16, XBGXoRoShiRo1024::new, SubGen::copyAndLongJump);
899         }
900 
901         /**
902          * Assert alternating jump and cycle of the XBG.
903          *
904          * <p>This test verifies one generator can jump and advance {@code n} steps
905          * while the other generator advances {@code n} steps then jumps. The two should
906          * be in the same state. The copy left behind by the first jump should match the
907          * state of the other generator as it cycles.
908          *
909          * @param testSeed A seed for the test
910          * @param seedSize The seed size
911          * @param factory Factory to create the XBG
912          * @param jump XBG jump function
913          */
914         private static void assertJumpAndCycle(long testSeed, int seedSize,
915                 Function<long[], SubGen> factory, UnaryOperator<SubGen> jump) {
916             final SplittableRandom r = new SplittableRandom(testSeed);
917             final long[] seed = r.longs(seedSize).toArray();
918             final int[] steps = createSteps(r, seedSize);
919             final int cycles = 2 * seedSize;
920 
921             final SubGen rng1 = factory.apply(seed);
922             final SubGen rng2 = factory.apply(seed);
923 
924             // Try jumping and stepping with a number of steps up to the seed size
925             for (int i = 0; i < steps.length; i++) {
926                 final int step = steps[i];
927                 final int index = i;
928                 // Jump 1
929                 final SubGen copy = jump.apply(rng1);
930                 // Advance 2; it should match the copy
931                 for (int j = 0; j < step; j++) {
932                     Assertions.assertEquals(rng2.stateAndUpdate(), copy.stateAndUpdate(),
933                         () -> String.format("[%d] Incorrect trailing copy, seed=%d", index, testSeed));
934                     // Advance in parallel
935                     rng1.stateAndUpdate();
936                 }
937                 // Catch up 2; it should match 1
938                 jump.apply(rng2);
939                 for (int j = 0; j < cycles; j++) {
940                     Assertions.assertEquals(rng1.stateAndUpdate(), rng2.stateAndUpdate(),
941                         () -> String.format("[%d] Incorrect after catch up jump, seed=%d", index, testSeed));
942                 }
943             }
944         }
945     }
946 
947     /////////////////////////////////////
948     // Tests for the LXM implementation
949     /////////////////////////////////////
950 
951     /**
952      * Gets the factory to create a composite LXM generator.
953      * The generator is used to provide reference output for the generator under test.
954      *
955      * @return the factory
956      */
957     abstract LXMGeneratorFactory getFactory();
958 
959     /**
960      * Creates a new instance of the RNG under test.
961      *
962      * @param seed the seed
963      * @return the RNG
964      */
965     abstract LongJumpableUniformRandomProvider create(long[] seed);
966 
967     /**
968      * Gets a stream of reference data. Each Argument consist of the long array seed and
969      * the long array of the expected output from the LXM generator.
970      * <pre>
971      * long[] seed;
972      * long[] expected;
973      * return Stream.of(Arguments.of(seed, expected));
974      * </pre>
975      *
976      * @return the reference data
977      */
978     abstract Stream<Arguments> getReferenceData();
979 
980     /**
981      * Test the reference data with the LXM generator.
982      * The reference data should be created with a reference implementation of the
983      * LXM algorithm, for example the implementations in JDK 17.
984      */
985     @ParameterizedTest
986     @MethodSource(value = "getReferenceData")
987     final void testReferenceData(long[] seed, long[] expected) {
988         RandomAssert.assertEquals(expected, create(seed));
989     }
990 
991     /**
992      * Test the reference data with the composite LXM generator. This ensures the composite
993      * correctly implements the LXM algorithm.
994      */
995     @ParameterizedTest
996     @MethodSource(value = "getReferenceData")
997     final void testReferenceDataWithComposite(long[] seed, long[] expected) {
998         RandomAssert.assertEquals(expected, getFactory().create(seed));
999     }
1000 
1001     /**
1002      * Test initial output is the mix of the most significant bits of the LCG and XBG state.
1003      * This test ensures the LXM algorithm applies the mix function to the current state
1004      * before state update. Thus the initial output should be a direct mix of the seed state.
1005      */
1006     @RepeatedTest(value = 5)
1007     final void testInitialOutput() {
1008         final long[] seed = createRandomSeed();
1009         // Upper 64 bits of LCG state
1010         final long s = seed[getFactory().lcgSeedSize() / 2];
1011         // Upper 64 bits of XBG state
1012         final long t = seed[getFactory().lcgSeedSize()];
1013         Assertions.assertEquals(getFactory().getMix().apply(s, t), create(seed).nextLong());
1014     }
1015 
1016     @Test
1017     final void testConstructorWithZeroSeedIsPartiallyFunctional() {
1018         // Note: It is impractical to demonstrate the RNG is partially functional:
1019         // output quality may be statistically poor and the period is that of the LCG.
1020         // The test demonstrates the RNG is not totally non-functional with a zero seed
1021         // which is not the case for a standard XBG.
1022         final int seedSize = getFactory().seedSize();
1023         RandomAssert.assertNextLongNonZeroOutput(create(new long[seedSize]), 0, seedSize);
1024     }
1025 
1026     @ParameterizedTest
1027     @MethodSource(value = "getReferenceData")
1028     final void testConstructorWithoutFullLengthSeed(long[] seed) {
1029         // Hit the case when the input seed is self-seeded when not full length
1030         final int seedSize = getFactory().seedSize();
1031         RandomAssert.assertNextLongNonZeroOutput(create(new long[] {seed[0]}),
1032                 seedSize, seedSize);
1033     }
1034 
1035     @RepeatedTest(value = 5)
1036     final void testConstructorIgnoresFinalAddParameterSeedBit() {
1037         final long[] seed1 = createRandomSeed();
1038         final long[] seed2 = seed1.clone();
1039         final int seedSize = getFactory().seedSize();
1040         // seed1 unset final add parameter bit; seed2 set final bit
1041         final int index = getFactory().lcgSeedSize() / 2 - 1;
1042         seed1[index] &= -1L << 1;
1043         seed2[index] |= 1;
1044         Assertions.assertEquals(1, seed1[index] ^ seed2[index]);
1045         final LongJumpableUniformRandomProvider rng1 = create(seed1);
1046         final LongJumpableUniformRandomProvider rng2 = create(seed2);
1047         RandomAssert.assertNextLongEquals(seedSize * 2, rng1, rng2);
1048     }
1049 
1050     @ParameterizedTest
1051     @MethodSource(value = "getReferenceData")
1052     final void testJump(long[] seed, long[] expected) {
1053         final long[] expectedAfter = createExpectedSequence(seed, expected.length, false);
1054         RandomAssert.assertJumpEquals(expected, expectedAfter, create(seed));
1055     }
1056 
1057     @ParameterizedTest
1058     @MethodSource(value = "getReferenceData")
1059     final void testLongJump(long[] seed, long[] expected) {
1060         final long[] expectedAfter = createExpectedSequence(seed, expected.length, true);
1061         RandomAssert.assertLongJumpEquals(expected, expectedAfter, create(seed));
1062     }
1063 
1064     @RepeatedTest(value = 5)
1065     final void testJumpAndOutput() {
1066         assertJumpAndOutput(false, ThreadLocalRandom.current().nextLong());
1067     }
1068 
1069     @RepeatedTest(value = 5)
1070     final void testLongJumpAndOutput() {
1071         assertJumpAndOutput(true, ThreadLocalRandom.current().nextLong());
1072     }
1073 
1074     /**
1075      * Assert alternating jump and output from the LXM generator and the
1076      * reference composite LXM generator.
1077      *
1078      * <p>The composite generator uses an LCG that is tested to match
1079      * output after a jump (see {@link LCGTest} or a series of cycles.
1080      * The XBG generator is <em>assumed</em> to function similarly.
1081      * The {@link XBGTest} verifies that the generator is in the same
1082      * state when using {@code n} steps then a jump; or a jump then
1083      * {@code n} steps.
1084      *
1085      * <p>This test verifies one LXM generator can jump and advance
1086      * {@code n} steps while the other generator advances {@code n}
1087      * steps then jumps. The two should be in the same state. The copy
1088      * left behind by the first jump should match the output of the other
1089      * generator as it cycles.
1090      *
1091      * @param longJump If true use the long jump; otherwise the jump
1092      * @param testSeed A seed for the test
1093      */
1094     private void assertJumpAndOutput(boolean longJump, long testSeed) {
1095         final SplittableRandom r = new SplittableRandom(testSeed);
1096         final long[] seed = createRandomSeed(r::nextLong);
1097         final int[] steps = createSteps(r);
1098         final int cycles = getFactory().seedSize();
1099 
1100         LongJumpableUniformRandomProvider rng1 = create(seed);
1101         LongJumpableUniformRandomProvider rng2 = getFactory().create(seed);
1102 
1103         final UnaryOperator<LongJumpableUniformRandomProvider> jump = longJump ?
1104             x -> (LongJumpableUniformRandomProvider) x.longJump() :
1105             x -> (LongJumpableUniformRandomProvider) x.jump();
1106 
1107         // Alternate jumping and stepping then stepping and jumping.
1108         for (int i = 0; i < steps.length; i++) {
1109             final int step = steps[i];
1110             final int index = i;
1111             // Jump 1
1112             LongJumpableUniformRandomProvider copy = jump.apply(rng1);
1113             // Advance 2; it should match the copy
1114             for (int j = 0; j < step; j++) {
1115                 Assertions.assertEquals(rng2.nextLong(), copy.nextLong(),
1116                     () -> String.format("[%d] Incorrect trailing copy, seed=%d", index, testSeed));
1117                 // Advance 1 in parallel
1118                 rng1.nextLong();
1119             }
1120             // Catch up 2; it should match 1
1121             jump.apply(rng2);
1122             for (int j = 0; j < cycles; j++) {
1123                 Assertions.assertEquals(rng1.nextLong(), rng2.nextLong(),
1124                     () -> String.format("[%d] Incorrect after catch up jump, seed=%d", index, testSeed));
1125             }
1126 
1127             // Swap
1128             copy = rng1;
1129             rng1 = rng2;
1130             rng2 = copy;
1131         }
1132     }
1133 
1134     @RepeatedTest(value = 5)
1135     final void testSaveRestoreAfterJump() {
1136         assertSaveRestoreAfterJump(false, ThreadLocalRandom.current().nextLong());
1137     }
1138 
1139     @RepeatedTest(value = 5)
1140     final void testSaveRestoreAfterLongJump() {
1141         assertSaveRestoreAfterJump(true, ThreadLocalRandom.current().nextLong());
1142     }
1143 
1144     /**
1145      * Assert that the precomputation of the jump coefficients functions
1146      * with saved/restore.
1147      *
1148      * @param longJump If true use the long jump; otherwise the jump
1149      * @param testSeed A seed for the test
1150      */
1151     private void assertSaveRestoreAfterJump(boolean longJump, long testSeed) {
1152         final SplittableRandom r = new SplittableRandom(testSeed);
1153         final int cycles = getFactory().seedSize();
1154 
1155         final UnaryOperator<LongJumpableUniformRandomProvider> jump = longJump ?
1156             x -> (LongJumpableUniformRandomProvider) x.longJump() :
1157             x -> (LongJumpableUniformRandomProvider) x.jump();
1158 
1159         // Create 2 generators and jump them
1160         final LongJumpableUniformRandomProvider rng1 = create(createRandomSeed(r::nextLong));
1161         final LongJumpableUniformRandomProvider rng2 = create(createRandomSeed(r::nextLong));
1162         jump.apply(rng1);
1163         jump.apply(rng2);
1164 
1165         // Restore the state from one to the other
1166         RestorableUniformRandomProvider g1 = (RestorableUniformRandomProvider) rng1;
1167         RestorableUniformRandomProvider g2 = (RestorableUniformRandomProvider) rng2;
1168         g2.restoreState(g1.saveState());
1169 
1170         // They should have the same output
1171         RandomAssert.assertNextLongEquals(cycles, rng1, rng2);
1172 
1173         // They should have the same output after a jump too
1174         jump.apply(rng1);
1175         jump.apply(rng2);
1176 
1177         RandomAssert.assertNextLongEquals(cycles, rng1, rng2);
1178     }
1179 
1180     /**
1181      * Create the expected sequence after a jump by advancing the XBG and LCG
1182      * sub-generators. This is done by creating and jumping the composite LXM
1183      * generator.
1184      *
1185      * @param seed the seed
1186      * @param length the length of the expected sequence
1187      * @param longJump If true use the long jump; otherwise the jump
1188      * @return the expected sequence
1189      */
1190     private long[] createExpectedSequence(long[] seed, int length, boolean longJump) {
1191         final LXMGenerator rng = getFactory().create(seed);
1192         if (longJump) {
1193             rng.longJump();
1194         } else {
1195             rng.jump();
1196         }
1197         return LongStream.generate(rng::nextLong).limit(length).toArray();
1198     }
1199 
1200     /**
1201      * Creates a random seed of the correct length. Used when the seed can be anything.
1202      *
1203      * @return the seed
1204      */
1205     private long[] createRandomSeed() {
1206         return createRandomSeed(new SplittableRandom()::nextLong);
1207     }
1208 
1209     /**
1210      * Creates a random seed of the correct length using the provided generator.
1211      * Used when the seed can be anything.
1212      *
1213      * @param gen the generator
1214      * @return the seed
1215      */
1216     private long[] createRandomSeed(LongSupplier gen) {
1217         return LongStream.generate(gen).limit(getFactory().seedSize()).toArray();
1218     }
1219 
1220     /**
1221      * Creates a random permutation of steps in [1, seed size].
1222      * The seed size is obtained from the LXM generator factory.
1223      *
1224      * @param rng Source of randomness
1225      * @return the steps
1226      */
1227     private int[] createSteps(SplittableRandom rng) {
1228         return createSteps(rng, getFactory().seedSize());
1229     }
1230 
1231     /**
1232      * Creates a random permutation of steps in [1, seed size].
1233      *
1234      * @param rng Source of randomness
1235      * @param seedSize Seed size
1236      * @return the steps
1237      */
1238     private static int[] createSteps(SplittableRandom rng, int seedSize) {
1239         final int[] steps = IntStream.rangeClosed(1, seedSize).toArray();
1240         // Fisher-Yates shuffle
1241         for (int i = steps.length; i > 1; i--) {
1242             final int j = rng.nextInt(i);
1243             final int tmp = steps[i - 1];
1244             steps[i - 1] = steps[j];
1245             steps[j] = tmp;
1246         }
1247         return steps;
1248     }
1249 }