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         /** 2^63. */
668         private static final BigInteger TWO_POW_63 = BigInteger.ONE.shiftLeft(63);
669         /** 65-bit multiplier for the 128-bit LCG. */
670         private static final BigInteger M = BigInteger.ONE.shiftLeft(64).add(toUnsignedBigInteger(LXMSupport.M128L));
671         /** 2^128. Used as the modulus for the 128-bit LCG. */
672         private static final BigInteger MOD = BigInteger.ONE.shiftLeft(128);
673 
674         /** A count {@code k} where a jump of {@code 2^k} will wrap the LCG state. */
675         private static final int NO_JUMP = -1;
676         /** A count {@code k=2} for a jump of {@code 2^k}, or 4 cycles. */
677         private static final int JUMP = 2;
678         /** A count {@code k=4} for a long jump of {@code 2^k}, or 16 cycles. */
679         private static final int LONG_JUMP = 4;
680 
681         @RepeatedTest(value = 10)
682         void testLCG64DefaultJump() {
683             final SplittableRandom rng = new SplittableRandom();
684             final long state = rng.nextLong();
685             final long add = rng.nextLong();
686             final SubGen lcg1 = new LCG64(add, state);
687             final SubGen lcg2 = new LCG64(add, state, 0, 32);
688             for (int j = 0; j < 10; j++) {
689                 Assertions.assertEquals(lcg1.stateAndUpdate(), lcg2.stateAndUpdate(),
690                     () -> String.format("seed %d,%d", state, add));
691             }
692             lcg1.copyAndJump();
693             lcg2.copyAndJump();
694             for (int j = 0; j < 10; j++) {
695                 Assertions.assertEquals(lcg1.stateAndUpdate(), lcg2.stateAndUpdate(),
696                     () -> String.format("seed %d,%d", state, add));
697             }
698             lcg1.copyAndLongJump();
699             lcg2.copyAndLongJump();
700             for (int j = 0; j < 10; j++) {
701                 Assertions.assertEquals(lcg1.stateAndUpdate(), lcg2.stateAndUpdate(),
702                     () -> String.format("seed %d,%d", state, add));
703             }
704         }
705 
706         @RepeatedTest(value = 10)
707         void testLCG64() {
708             final SplittableRandom rng = new SplittableRandom();
709             final long state = rng.nextLong();
710             final long add = rng.nextLong();
711             long s = state;
712             final long a = add | 1;
713             final SubGen lcg = new LCG64(add, state, NO_JUMP, NO_JUMP);
714             for (int j = 0; j < 10; j++) {
715                 Assertions.assertEquals(s, lcg.stateAndUpdate(),
716                     () -> String.format("seed %d,%d", state, add));
717                 s = LXMSupport.M64 * s + a;
718             }
719         }
720 
721         @RepeatedTest(value = 10)
722         void testLCG64Jump() {
723             final SplittableRandom rng = new SplittableRandom();
724             final long state = rng.nextLong();
725             final long add = rng.nextLong();
726             final Supplier<String> msg = () -> String.format("seed %d,%d", state, add);
727             long s = state;
728             final long a = add | 1;
729             final SubGen lcg = new LCG64(add, state, JUMP, LONG_JUMP);
730 
731             final SubGen copy1 = lcg.copyAndJump();
732             for (int j = 1 << JUMP; j-- != 0;) {
733                 Assertions.assertEquals(s, copy1.stateAndUpdate(), msg);
734                 s = LXMSupport.M64 * s + a;
735             }
736             Assertions.assertEquals(s, lcg.stateAndUpdate(), msg);
737             s = LXMSupport.M64 * s + a;
738 
739             final SubGen copy2 = lcg.copyAndLongJump();
740             for (int j = 1 << LONG_JUMP; j-- != 0;) {
741                 Assertions.assertEquals(s, copy2.stateAndUpdate(), msg);
742                 s = LXMSupport.M64 * s + a;
743             }
744             Assertions.assertEquals(s, lcg.stateAndUpdate(), msg);
745         }
746 
747         @RepeatedTest(value = 10)
748         void testLCG128DefaultJump() {
749             final SplittableRandom rng = new SplittableRandom();
750             final long stateh = rng.nextLong();
751             final long statel = rng.nextLong();
752             final long addh = rng.nextLong();
753             final long addl = rng.nextLong();
754             final SubGen lcg1 = new LCG128(addh, addl, stateh, statel);
755             final SubGen lcg2 = new LCG128(addh, addl, stateh, statel, 0, 64);
756             for (int j = 0; j < 10; j++) {
757                 Assertions.assertEquals(lcg1.stateAndUpdate(), lcg2.stateAndUpdate(),
758                     () -> String.format("seed %d,%d,%d,%d", stateh, statel, addh, addl));
759             }
760             lcg1.copyAndJump();
761             lcg2.copyAndJump();
762             for (int j = 0; j < 10; j++) {
763                 Assertions.assertEquals(lcg1.stateAndUpdate(), lcg2.stateAndUpdate(),
764                     () -> String.format("seed %d,%d,%d,%d", stateh, statel, addh, addl));
765             }
766             lcg1.copyAndLongJump();
767             lcg2.copyAndLongJump();
768             for (int j = 0; j < 10; j++) {
769                 Assertions.assertEquals(lcg1.stateAndUpdate(), lcg2.stateAndUpdate(),
770                     () -> String.format("seed %d,%d,%d,%d", stateh, statel, addh, addl));
771             }
772         }
773 
774         @RepeatedTest(value = 10)
775         void testLCG128() {
776             final SplittableRandom rng = new SplittableRandom();
777             final long stateh = rng.nextLong();
778             final long statel = rng.nextLong();
779             final long addh = rng.nextLong();
780             final long addl = rng.nextLong();
781             BigInteger s = toUnsignedBigInteger(stateh).shiftLeft(64).add(toUnsignedBigInteger(statel));
782             final BigInteger a = toUnsignedBigInteger(addh).shiftLeft(64).add(toUnsignedBigInteger(addl | 1));
783             final SubGen lcg = new LCG128(addh, addl, stateh, statel, NO_JUMP, NO_JUMP);
784             for (int j = 0; j < 10; j++) {
785                 Assertions.assertEquals(s.shiftRight(64).longValue(), lcg.stateAndUpdate(),
786                         () -> String.format("seed %d,%d,%d,%d", stateh, statel, addh, addl));
787                 s = M.multiply(s).add(a).mod(MOD);
788             }
789         }
790 
791         @RepeatedTest(value = 10)
792         void testLCG128Jump() {
793             final SplittableRandom rng = new SplittableRandom();
794             final long stateh = rng.nextLong();
795             final long statel = rng.nextLong();
796             final long addh = rng.nextLong();
797             final long addl = rng.nextLong();
798             final Supplier<String> msg = () -> String.format("seed %d,%d,%d,%d", stateh, statel, addh, addl);
799             BigInteger s = toUnsignedBigInteger(stateh).shiftLeft(64).add(toUnsignedBigInteger(statel));
800             final BigInteger a = toUnsignedBigInteger(addh).shiftLeft(64).add(toUnsignedBigInteger(addl | 1));
801             final SubGen lcg = new LCG128(addh, addl, stateh, statel, JUMP, LONG_JUMP);
802 
803             final SubGen copy1 = lcg.copyAndJump();
804             for (int j = 1 << JUMP; j-- != 0;) {
805                 Assertions.assertEquals(s.shiftRight(64).longValue(), copy1.stateAndUpdate(), msg);
806                 s = M.multiply(s).add(a).mod(MOD);
807             }
808             Assertions.assertEquals(s.shiftRight(64).longValue(), lcg.stateAndUpdate(), msg);
809             s = M.multiply(s).add(a).mod(MOD);
810 
811             final SubGen copy2 = lcg.copyAndLongJump();
812             for (int j = 1 << LONG_JUMP; j-- != 0;) {
813                 Assertions.assertEquals(s.shiftRight(64).longValue(), copy2.stateAndUpdate(), msg);
814                 s = M.multiply(s).add(a).mod(MOD);
815             }
816             Assertions.assertEquals(s.shiftRight(64).longValue(), lcg.stateAndUpdate(), msg);
817         }
818 
819         /**
820          * Create a big integer treating the value as unsigned.
821          *
822          * @param v Value
823          * @return the big integer
824          */
825         private static BigInteger toUnsignedBigInteger(long v) {
826             return v < 0 ?
827                 TWO_POW_63.add(BigInteger.valueOf(v & Long.MAX_VALUE)) :
828                 BigInteger.valueOf(v);
829         }
830     }
831 
832     /**
833      * Test the XBG implementations. These tests should execute only once, and not
834      * for each instance of the abstract outer class (i.e. the test is not {@code @Nested}).
835      *
836      * <p>Note: The XBG implementation are extensions of RNGs already verified against
837      * reference implementations. These tests ensure that the upper bits of the state is
838      * identical after {@code n} cycles then a jump; or a jump then {@code n} cycles.
839      * This is the functionality required for a jumpable XBG generator.
840      */
841     static class XBGTest {
842 
843         @RepeatedTest(value = 5)
844         void testXBGXoRoShiRo128NoJump() {
845             final SplittableRandom r = new SplittableRandom();
846             assertNoJump(new XBGXoRoShiRo128(r.nextLong(), r.nextLong(), false));
847         }
848 
849         @RepeatedTest(value = 5)
850         void testXBGXoShiRo256NoJump() {
851             final SplittableRandom r = new SplittableRandom();
852             assertNoJump(new XBGXoShiRo256(r.nextLong(), r.nextLong(), r.nextLong(), r.nextLong(), false));
853         }
854 
855         @RepeatedTest(value = 5)
856         void testXBGXoRoShiRo1024NoJump() {
857             final SplittableRandom r = new SplittableRandom();
858             assertNoJump(new XBGXoRoShiRo1024(r.longs(16).toArray(), false));
859         }
860 
861         void assertNoJump(SubGen g) {
862             final SubGen g1 = g.copyAndJump();
863             Assertions.assertNotSame(g, g1);
864             for (int i = 0; i < 10; i++) {
865                 Assertions.assertEquals(g.stateAndUpdate(), g1.stateAndUpdate());
866             }
867             final SubGen g2 = g.copyAndLongJump();
868             Assertions.assertNotSame(g, g2);
869             for (int i = 0; i < 10; i++) {
870                 Assertions.assertEquals(g.stateAndUpdate(), g2.stateAndUpdate());
871             }
872         }
873 
874         @RepeatedTest(value = 5)
875         void testXBGXoRoShiRo128Jump() {
876             assertJumpAndCycle(ThreadLocalRandom.current().nextLong(), 2, XBGXoRoShiRo128::new, SubGen::copyAndJump);
877         }
878 
879         @RepeatedTest(value = 5)
880         void testXBGXoRoShiRo128LongJump() {
881             assertJumpAndCycle(ThreadLocalRandom.current().nextLong(), 2, XBGXoRoShiRo128::new, SubGen::copyAndLongJump);
882         }
883 
884         @RepeatedTest(value = 5)
885         void testXBGXoShiRo256Jump() {
886             assertJumpAndCycle(ThreadLocalRandom.current().nextLong(), 4, XBGXoShiRo256::new, SubGen::copyAndJump);
887         }
888 
889         @RepeatedTest(value = 5)
890         void testXBGXShiRo256LongJump() {
891             assertJumpAndCycle(ThreadLocalRandom.current().nextLong(), 4, XBGXoShiRo256::new, SubGen::copyAndLongJump);
892         }
893 
894         @RepeatedTest(value = 5)
895         void testXBGXoRoShiRo1024Jump() {
896             assertJumpAndCycle(ThreadLocalRandom.current().nextLong(), 16, XBGXoRoShiRo1024::new, SubGen::copyAndJump);
897         }
898 
899         @RepeatedTest(value = 5)
900         void testXBGXoRoShiRo1024LongJump() {
901             assertJumpAndCycle(ThreadLocalRandom.current().nextLong(), 16, XBGXoRoShiRo1024::new, SubGen::copyAndLongJump);
902         }
903 
904         /**
905          * Assert alternating jump and cycle of the XBG.
906          *
907          * <p>This test verifies one generator can jump and advance {@code n} steps
908          * while the other generator advances {@code n} steps then jumps. The two should
909          * be in the same state. The copy left behind by the first jump should match the
910          * state of the other generator as it cycles.
911          *
912          * @param testSeed A seed for the test
913          * @param seedSize The seed size
914          * @param factory Factory to create the XBG
915          * @param jump XBG jump function
916          */
917         private static void assertJumpAndCycle(long testSeed, int seedSize,
918                 Function<long[], SubGen> factory, UnaryOperator<SubGen> jump) {
919             final SplittableRandom r = new SplittableRandom(testSeed);
920             final long[] seed = r.longs(seedSize).toArray();
921             final int[] steps = createSteps(r, seedSize);
922             final int cycles = 2 * seedSize;
923 
924             final SubGen rng1 = factory.apply(seed);
925             final SubGen rng2 = factory.apply(seed);
926 
927             // Try jumping and stepping with a number of steps up to the seed size
928             for (int i = 0; i < steps.length; i++) {
929                 final int step = steps[i];
930                 final int index = i;
931                 // Jump 1
932                 final SubGen copy = jump.apply(rng1);
933                 // Advance 2; it should match the copy
934                 for (int j = 0; j < step; j++) {
935                     Assertions.assertEquals(rng2.stateAndUpdate(), copy.stateAndUpdate(),
936                         () -> String.format("[%d] Incorrect trailing copy, seed=%d", index, testSeed));
937                     // Advance in parallel
938                     rng1.stateAndUpdate();
939                 }
940                 // Catch up 2; it should match 1
941                 jump.apply(rng2);
942                 for (int j = 0; j < cycles; j++) {
943                     Assertions.assertEquals(rng1.stateAndUpdate(), rng2.stateAndUpdate(),
944                         () -> String.format("[%d] Incorrect after catch up jump, seed=%d", index, testSeed));
945                 }
946             }
947         }
948     }
949 
950     /////////////////////////////////////
951     // Tests for the LXM implementation
952     /////////////////////////////////////
953 
954     /**
955      * Gets the factory to create a composite LXM generator.
956      * The generator is used to provide reference output for the generator under test.
957      *
958      * @return the factory
959      */
960     abstract LXMGeneratorFactory getFactory();
961 
962     /**
963      * Creates a new instance of the RNG under test.
964      *
965      * @param seed the seed
966      * @return the RNG
967      */
968     abstract LongJumpableUniformRandomProvider create(long[] seed);
969 
970     /**
971      * Gets a stream of reference data. Each Argument consist of the long array seed and
972      * the long array of the expected output from the LXM generator.
973      * <pre>
974      * long[] seed;
975      * long[] expected;
976      * return Stream.of(Arguments.of(seed, expected));
977      * </pre>
978      *
979      * @return the reference data
980      */
981     abstract Stream<Arguments> getReferenceData();
982 
983     /**
984      * Test the reference data with the LXM generator.
985      * The reference data should be created with a reference implementation of the
986      * LXM algorithm, for example the implementations in JDK 17.
987      */
988     @ParameterizedTest
989     @MethodSource(value = "getReferenceData")
990     final void testReferenceData(long[] seed, long[] expected) {
991         RandomAssert.assertEquals(expected, create(seed));
992     }
993 
994     /**
995      * Test the reference data with the composite LXM generator. This ensures the composite
996      * correctly implements the LXM algorithm.
997      */
998     @ParameterizedTest
999     @MethodSource(value = "getReferenceData")
1000     final void testReferenceDataWithComposite(long[] seed, long[] expected) {
1001         RandomAssert.assertEquals(expected, getFactory().create(seed));
1002     }
1003 
1004     /**
1005      * Test initial output is the mix of the most significant bits of the LCG and XBG state.
1006      * This test ensures the LXM algorithm applies the mix function to the current state
1007      * before state update. Thus the initial output should be a direct mix of the seed state.
1008      */
1009     @RepeatedTest(value = 5)
1010     final void testInitialOutput() {
1011         final long[] seed = createRandomSeed();
1012         // Upper 64 bits of LCG state
1013         final long s = seed[getFactory().lcgSeedSize() / 2];
1014         // Upper 64 bits of XBG state
1015         final long t = seed[getFactory().lcgSeedSize()];
1016         Assertions.assertEquals(getFactory().getMix().apply(s, t), create(seed).nextLong());
1017     }
1018 
1019     @Test
1020     final void testConstructorWithZeroSeedIsPartiallyFunctional() {
1021         // Note: It is impractical to demonstrate the RNG is partially functional:
1022         // output quality may be statistically poor and the period is that of the LCG.
1023         // The test demonstrates the RNG is not totally non-functional with a zero seed
1024         // which is not the case for a standard XBG.
1025         final int seedSize = getFactory().seedSize();
1026         RandomAssert.assertNextLongNonZeroOutput(create(new long[seedSize]), 0, seedSize);
1027     }
1028 
1029     @ParameterizedTest
1030     @MethodSource(value = "getReferenceData")
1031     final void testConstructorWithoutFullLengthSeed(long[] seed) {
1032         // Hit the case when the input seed is self-seeded when not full length
1033         final int seedSize = getFactory().seedSize();
1034         RandomAssert.assertNextLongNonZeroOutput(create(new long[] {seed[0]}),
1035                 seedSize, seedSize);
1036     }
1037 
1038     @RepeatedTest(value = 5)
1039     final void testConstructorIgnoresFinalAddParameterSeedBit() {
1040         final long[] seed1 = createRandomSeed();
1041         final long[] seed2 = seed1.clone();
1042         final int seedSize = getFactory().seedSize();
1043         // seed1 unset final add parameter bit; seed2 set final bit
1044         final int index = getFactory().lcgSeedSize() / 2 - 1;
1045         seed1[index] &= -1L << 1;
1046         seed2[index] |= 1;
1047         Assertions.assertEquals(1, seed1[index] ^ seed2[index]);
1048         final LongJumpableUniformRandomProvider rng1 = create(seed1);
1049         final LongJumpableUniformRandomProvider rng2 = create(seed2);
1050         RandomAssert.assertNextLongEquals(seedSize * 2, rng1, rng2);
1051     }
1052 
1053     @ParameterizedTest
1054     @MethodSource(value = "getReferenceData")
1055     final void testJump(long[] seed, long[] expected) {
1056         final long[] expectedAfter = createExpectedSequence(seed, expected.length, false);
1057         RandomAssert.assertJumpEquals(expected, expectedAfter, create(seed));
1058     }
1059 
1060     @ParameterizedTest
1061     @MethodSource(value = "getReferenceData")
1062     final void testLongJump(long[] seed, long[] expected) {
1063         final long[] expectedAfter = createExpectedSequence(seed, expected.length, true);
1064         RandomAssert.assertLongJumpEquals(expected, expectedAfter, create(seed));
1065     }
1066 
1067     @RepeatedTest(value = 5)
1068     final void testJumpAndOutput() {
1069         assertJumpAndOutput(false, ThreadLocalRandom.current().nextLong());
1070     }
1071 
1072     @RepeatedTest(value = 5)
1073     final void testLongJumpAndOutput() {
1074         assertJumpAndOutput(true, ThreadLocalRandom.current().nextLong());
1075     }
1076 
1077     /**
1078      * Assert alternating jump and output from the LXM generator and the
1079      * reference composite LXM generator.
1080      *
1081      * <p>The composite generator uses an LCG that is tested to match
1082      * output after a jump (see {@link LCGTest} or a series of cycles.
1083      * The XBG generator is <em>assumed</em> to function similarly.
1084      * The {@link XBGTest} verifies that the generator is in the same
1085      * state when using {@code n} steps then a jump; or a jump then
1086      * {@code n} steps.
1087      *
1088      * <p>This test verifies one LXM generator can jump and advance
1089      * {@code n} steps while the other generator advances {@code n}
1090      * steps then jumps. The two should be in the same state. The copy
1091      * left behind by the first jump should match the output of the other
1092      * generator as it cycles.
1093      *
1094      * @param longJump If true use the long jump; otherwise the jump
1095      * @param testSeed A seed for the test
1096      */
1097     private void assertJumpAndOutput(boolean longJump, long testSeed) {
1098         final SplittableRandom r = new SplittableRandom(testSeed);
1099         final long[] seed = createRandomSeed(r::nextLong);
1100         final int[] steps = createSteps(r);
1101         final int cycles = getFactory().seedSize();
1102 
1103         LongJumpableUniformRandomProvider rng1 = create(seed);
1104         LongJumpableUniformRandomProvider rng2 = getFactory().create(seed);
1105 
1106         final UnaryOperator<LongJumpableUniformRandomProvider> jump = longJump ?
1107             x -> (LongJumpableUniformRandomProvider) x.longJump() :
1108             x -> (LongJumpableUniformRandomProvider) x.jump();
1109 
1110         // Alternate jumping and stepping then stepping and jumping.
1111         for (int i = 0; i < steps.length; i++) {
1112             final int step = steps[i];
1113             final int index = i;
1114             // Jump 1
1115             LongJumpableUniformRandomProvider copy = jump.apply(rng1);
1116             // Advance 2; it should match the copy
1117             for (int j = 0; j < step; j++) {
1118                 Assertions.assertEquals(rng2.nextLong(), copy.nextLong(),
1119                     () -> String.format("[%d] Incorrect trailing copy, seed=%d", index, testSeed));
1120                 // Advance 1 in parallel
1121                 rng1.nextLong();
1122             }
1123             // Catch up 2; it should match 1
1124             jump.apply(rng2);
1125             for (int j = 0; j < cycles; j++) {
1126                 Assertions.assertEquals(rng1.nextLong(), rng2.nextLong(),
1127                     () -> String.format("[%d] Incorrect after catch up jump, seed=%d", index, testSeed));
1128             }
1129 
1130             // Swap
1131             copy = rng1;
1132             rng1 = rng2;
1133             rng2 = copy;
1134         }
1135     }
1136 
1137     @RepeatedTest(value = 5)
1138     final void testSaveRestoreAfterJump() {
1139         assertSaveRestoreAfterJump(false, ThreadLocalRandom.current().nextLong());
1140     }
1141 
1142     @RepeatedTest(value = 5)
1143     final void testSaveRestoreAfterLongJump() {
1144         assertSaveRestoreAfterJump(true, ThreadLocalRandom.current().nextLong());
1145     }
1146 
1147     /**
1148      * Assert that the precomputation of the jump coefficients functions
1149      * with saved/restore.
1150      *
1151      * @param longJump If true use the long jump; otherwise the jump
1152      * @param testSeed A seed for the test
1153      */
1154     private void assertSaveRestoreAfterJump(boolean longJump, long testSeed) {
1155         final SplittableRandom r = new SplittableRandom(testSeed);
1156         final int cycles = getFactory().seedSize();
1157 
1158         final UnaryOperator<LongJumpableUniformRandomProvider> jump = longJump ?
1159             x -> (LongJumpableUniformRandomProvider) x.longJump() :
1160             x -> (LongJumpableUniformRandomProvider) x.jump();
1161 
1162         // Create 2 generators and jump them
1163         final LongJumpableUniformRandomProvider rng1 = create(createRandomSeed(r::nextLong));
1164         final LongJumpableUniformRandomProvider rng2 = create(createRandomSeed(r::nextLong));
1165         jump.apply(rng1);
1166         jump.apply(rng2);
1167 
1168         // Restore the state from one to the other
1169         RestorableUniformRandomProvider g1 = (RestorableUniformRandomProvider) rng1;
1170         RestorableUniformRandomProvider g2 = (RestorableUniformRandomProvider) rng2;
1171         g2.restoreState(g1.saveState());
1172 
1173         // They should have the same output
1174         RandomAssert.assertNextLongEquals(cycles, rng1, rng2);
1175 
1176         // They should have the same output after a jump too
1177         jump.apply(rng1);
1178         jump.apply(rng2);
1179 
1180         RandomAssert.assertNextLongEquals(cycles, rng1, rng2);
1181     }
1182 
1183     /**
1184      * Create the expected sequence after a jump by advancing the XBG and LCG
1185      * sub-generators. This is done by creating and jumping the composite LXM
1186      * generator.
1187      *
1188      * @param seed the seed
1189      * @param length the length of the expected sequence
1190      * @param longJump If true use the long jump; otherwise the jump
1191      * @return the expected sequence
1192      */
1193     private long[] createExpectedSequence(long[] seed, int length, boolean longJump) {
1194         final LXMGenerator rng = getFactory().create(seed);
1195         if (longJump) {
1196             rng.longJump();
1197         } else {
1198             rng.jump();
1199         }
1200         return LongStream.generate(rng::nextLong).limit(length).toArray();
1201     }
1202 
1203     /**
1204      * Creates a random seed of the correct length. Used when the seed can be anything.
1205      *
1206      * @return the seed
1207      */
1208     private long[] createRandomSeed() {
1209         return createRandomSeed(new SplittableRandom()::nextLong);
1210     }
1211 
1212     /**
1213      * Creates a random seed of the correct length using the provided generator.
1214      * Used when the seed can be anything.
1215      *
1216      * @param gen the generator
1217      * @return the seed
1218      */
1219     private long[] createRandomSeed(LongSupplier gen) {
1220         return LongStream.generate(gen).limit(getFactory().seedSize()).toArray();
1221     }
1222 
1223     /**
1224      * Creates a random permutation of steps in [1, seed size].
1225      * The seed size is obtained from the LXM generator factory.
1226      *
1227      * @param rng Source of randomness
1228      * @return the steps
1229      */
1230     private int[] createSteps(SplittableRandom rng) {
1231         return createSteps(rng, getFactory().seedSize());
1232     }
1233 
1234     /**
1235      * Creates a random permutation of steps in [1, seed size].
1236      *
1237      * @param rng Source of randomness
1238      * @param seedSize Seed size
1239      * @return the steps
1240      */
1241     private static int[] createSteps(SplittableRandom rng, int seedSize) {
1242         final int[] steps = IntStream.rangeClosed(1, seedSize).toArray();
1243         // Fisher-Yates shuffle
1244         for (int i = steps.length; i > 1; i--) {
1245             final int j = rng.nextInt(i);
1246             final int tmp = steps[i - 1];
1247             steps[i - 1] = steps[j];
1248             steps[j] = tmp;
1249         }
1250         return steps;
1251     }
1252 }