1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.rng.core.source32;
18
19 import java.util.SplittableRandom;
20 import java.util.concurrent.ThreadLocalRandom;
21 import java.util.function.Function;
22 import java.util.function.IntSupplier;
23 import java.util.function.Supplier;
24 import java.util.function.UnaryOperator;
25 import java.util.stream.IntStream;
26 import java.util.stream.Stream;
27 import org.apache.commons.rng.LongJumpableUniformRandomProvider;
28 import org.apache.commons.rng.RestorableUniformRandomProvider;
29 import org.apache.commons.rng.core.RandomAssert;
30 import org.junit.jupiter.api.Assertions;
31 import org.junit.jupiter.api.RepeatedTest;
32 import org.junit.jupiter.api.Test;
33 import org.junit.jupiter.api.TestInstance;
34 import org.junit.jupiter.api.TestInstance.Lifecycle;
35 import org.junit.jupiter.params.ParameterizedTest;
36 import org.junit.jupiter.params.provider.Arguments;
37 import org.junit.jupiter.params.provider.MethodSource;
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98 @TestInstance(Lifecycle.PER_CLASS)
99 abstract class AbstractLXMTest {
100
101
102
103
104 interface Mix {
105
106
107
108
109
110
111
112 int apply(int a, int b);
113 }
114
115
116
117
118
119
120
121
122 interface SubGen {
123
124
125
126
127
128 int stateAndUpdate();
129
130
131
132
133
134
135 SubGen copyAndJump();
136
137
138
139
140
141
142 SubGen copyAndLongJump();
143 }
144
145
146
147
148
149
150
151
152 static int mixLea32(int a, int b) {
153 return LXMSupport.lea32(a + b);
154 }
155
156
157
158
159 static class LCG32 implements SubGen {
160
161 private static final int M = LXMSupport.M32;
162
163 private final int a;
164
165 private int s;
166
167 private final int jumpPower;
168
169 private final int longJumpPower;
170
171
172
173
174
175
176
177 LCG32(int a, int s) {
178 this(a, s, 0, 16);
179 }
180
181
182
183
184
185
186
187 LCG32(int a, int s, int jumpPower, int longJumpPower) {
188 this.a = a | 1;
189 this.s = s;
190 this.jumpPower = jumpPower;
191 this.longJumpPower = longJumpPower;
192 }
193
194 @Override
195 public int stateAndUpdate() {
196 final int s0 = s;
197 s = M * s + a;
198 return s0;
199 }
200
201 @Override
202 public SubGen copyAndJump() {
203 final SubGen copy = new LCG32(a, s, jumpPower, longJumpPower);
204 s = LXMSupportTest.lcgAdvancePow2(s, M, a, jumpPower);
205 return copy;
206 }
207
208 @Override
209 public SubGen copyAndLongJump() {
210 final SubGen copy = new LCG32(a, s, jumpPower, longJumpPower);
211 s = LXMSupportTest.lcgAdvancePow2(s, M, a, longJumpPower);
212 return copy;
213 }
214 }
215
216
217
218
219
220
221 static class XBGXoRoShiRo64 extends AbstractXoRoShiRo64 implements SubGen {
222
223
224
225
226
227 XBGXoRoShiRo64(int[] seed) {
228 super(seed);
229 }
230
231
232
233
234
235 XBGXoRoShiRo64(int seed0, int seed1) {
236 super(seed0, seed1);
237 }
238
239
240
241
242
243
244 XBGXoRoShiRo64(XBGXoRoShiRo64 source) {
245
246
247
248
249
250 this(source.state0, source.state1);
251 }
252
253 @Override
254 public int stateAndUpdate() {
255 final int s0 = state0;
256 next();
257 return s0;
258 }
259
260 @Override
261 public SubGen copyAndJump() {
262
263 return new XBGXoRoShiRo64(this);
264 }
265
266 @Override
267 public SubGen copyAndLongJump() {
268
269 return new XBGXoRoShiRo64(this);
270 }
271
272 @Override
273 protected int nextOutput() {
274
275 return 0;
276 }
277 }
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292 static class LXMGenerator extends IntProvider implements LongJumpableUniformRandomProvider {
293
294 private final Mix mix;
295
296 private final SubGen lcg;
297
298 private final SubGen xbg;
299
300
301
302
303
304
305
306
307
308
309
310 LXMGenerator(Mix mix, SubGen lcg, SubGen xbg) {
311 this.lcg = lcg;
312 this.xbg = xbg;
313 this.mix = mix;
314 }
315
316 @Override
317 public int next() {
318 return mix.apply(lcg.stateAndUpdate(), xbg.stateAndUpdate());
319 }
320
321 @Override
322 public LXMGenerator jump() {
323 return new LXMGenerator(mix, lcg.copyAndJump(), xbg.copyAndJump());
324 }
325
326 @Override
327 public LXMGenerator longJump() {
328 return new LXMGenerator(mix, lcg.copyAndLongJump(), xbg.copyAndLongJump());
329 }
330 }
331
332
333
334
335 interface LXMGeneratorFactory {
336
337
338
339
340
341 int lcgSeedSize();
342
343
344
345
346
347
348 int xbgSeedSize();
349
350
351
352
353
354
355
356 default int seedSize() {
357 return lcgSeedSize() + xbgSeedSize();
358 }
359
360
361
362
363
364
365
366
367
368
369 LXMGenerator create(int[] seed);
370
371
372
373
374
375
376
377 default Mix getMix() {
378 return AbstractLXMTest::mixLea32;
379 }
380 }
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399 static class LCGTest {
400
401 private static final int NO_JUMP = -1;
402
403 private static final int JUMP = 2;
404
405 private static final int LONG_JUMP = 4;
406
407 @RepeatedTest(value = 10)
408 void testLCG32DefaultJump() {
409 final SplittableRandom rng = new SplittableRandom();
410 final int state = rng.nextInt();
411 final int add = rng.nextInt();
412 final SubGen lcg1 = new LCG32(add, state);
413 final SubGen lcg2 = new LCG32(add, state, 0, 16);
414 for (int j = 0; j < 10; j++) {
415 Assertions.assertEquals(lcg1.stateAndUpdate(), lcg2.stateAndUpdate(),
416 () -> String.format("seed %d,%d", state, add));
417 }
418 lcg1.copyAndJump();
419 lcg2.copyAndJump();
420 for (int j = 0; j < 10; j++) {
421 Assertions.assertEquals(lcg1.stateAndUpdate(), lcg2.stateAndUpdate(),
422 () -> String.format("seed %d,%d", state, add));
423 }
424 lcg1.copyAndLongJump();
425 lcg2.copyAndLongJump();
426 for (int j = 0; j < 10; j++) {
427 Assertions.assertEquals(lcg1.stateAndUpdate(), lcg2.stateAndUpdate(),
428 () -> String.format("seed %d,%d", state, add));
429 }
430 }
431
432 @RepeatedTest(value = 10)
433 void testLCG32() {
434 final SplittableRandom rng = new SplittableRandom();
435 final int state = rng.nextInt();
436 final int add = rng.nextInt();
437 int s = state;
438 final int a = add | 1;
439 final SubGen lcg = new LCG32(add, state, NO_JUMP, NO_JUMP);
440 for (int j = 0; j < 10; j++) {
441 Assertions.assertEquals(s, lcg.stateAndUpdate(),
442 () -> String.format("seed %d,%d", state, add));
443 s = LXMSupport.M32 * s + a;
444 }
445 }
446
447 @RepeatedTest(value = 10)
448 void testLCG32Jump() {
449 final SplittableRandom rng = new SplittableRandom();
450 final int state = rng.nextInt();
451 final int add = rng.nextInt();
452 final Supplier<String> msg = () -> String.format("seed %d,%d", state, add);
453 int s = state;
454 final int a = add | 1;
455 final SubGen lcg = new LCG32(add, state, JUMP, LONG_JUMP);
456
457 final SubGen copy1 = lcg.copyAndJump();
458 for (int j = 1 << JUMP; j-- != 0;) {
459 Assertions.assertEquals(s, copy1.stateAndUpdate(), msg);
460 s = LXMSupport.M32 * s + a;
461 }
462 Assertions.assertEquals(s, lcg.stateAndUpdate(), msg);
463 s = LXMSupport.M32 * s + a;
464
465 final SubGen copy2 = lcg.copyAndLongJump();
466 for (int j = 1 << LONG_JUMP; j-- != 0;) {
467 Assertions.assertEquals(s, copy2.stateAndUpdate(), msg);
468 s = LXMSupport.M32 * s + a;
469 }
470 Assertions.assertEquals(s, lcg.stateAndUpdate(), msg);
471 }
472 }
473
474
475
476
477
478
479
480
481
482
483 static class XBGTest {
484
485 @RepeatedTest(value = 5)
486 void testXBGXoRoShiRo64NoJump() {
487 final SplittableRandom r = new SplittableRandom();
488 assertNoJump(new XBGXoRoShiRo64(r.nextInt(), r.nextInt()));
489 }
490
491 void assertNoJump(SubGen g) {
492 final SubGen g1 = g.copyAndJump();
493 Assertions.assertNotSame(g, g1);
494 for (int i = 0; i < 10; i++) {
495 Assertions.assertEquals(g.stateAndUpdate(), g1.stateAndUpdate());
496 }
497 final SubGen g2 = g.copyAndLongJump();
498 Assertions.assertNotSame(g, g2);
499 for (int i = 0; i < 10; i++) {
500 Assertions.assertEquals(g.stateAndUpdate(), g2.stateAndUpdate());
501 }
502 }
503
504
505
506
507
508 @RepeatedTest(value = 5)
509 void testXBGXoRoShiRo64Jump() {
510 assertJumpAndCycle(ThreadLocalRandom.current().nextLong(), 2, XBGXoRoShiRo64::new, SubGen::copyAndJump);
511 }
512
513 @RepeatedTest(value = 5)
514 void testXBGXoRoShiRo64LongJump() {
515 assertJumpAndCycle(ThreadLocalRandom.current().nextLong(), 2, XBGXoRoShiRo64::new, SubGen::copyAndLongJump);
516 }
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531 private static void assertJumpAndCycle(long testSeed, int seedSize,
532 Function<int[], SubGen> factory, UnaryOperator<SubGen> jump) {
533 final SplittableRandom r = new SplittableRandom(testSeed);
534 final int[] seed = r.ints(seedSize).toArray();
535 final int[] steps = createSteps(r, seedSize);
536 final int cycles = 2 * seedSize;
537
538 final SubGen rng1 = factory.apply(seed);
539 final SubGen rng2 = factory.apply(seed);
540
541
542 for (int i = 0; i < steps.length; i++) {
543 final int step = steps[i];
544 final int index = i;
545
546 final SubGen copy = jump.apply(rng1);
547
548 for (int j = 0; j < step; j++) {
549 Assertions.assertEquals(rng2.stateAndUpdate(), copy.stateAndUpdate(),
550 () -> String.format("[%d] Incorrect trailing copy, seed=%d", index, testSeed));
551
552 rng1.stateAndUpdate();
553 }
554
555 jump.apply(rng2);
556 for (int j = 0; j < cycles; j++) {
557 Assertions.assertEquals(rng1.stateAndUpdate(), rng2.stateAndUpdate(),
558 () -> String.format("[%d] Incorrect after catch up jump, seed=%d", index, testSeed));
559 }
560 }
561 }
562 }
563
564
565
566
567
568
569
570
571
572
573
574 abstract LXMGeneratorFactory getFactory();
575
576
577
578
579
580
581
582 abstract LongJumpableUniformRandomProvider create(int[] seed);
583
584
585
586
587
588
589
590
591
592
593
594
595 abstract Stream<Arguments> getReferenceData();
596
597
598
599
600
601
602 @ParameterizedTest
603 @MethodSource(value = "getReferenceData")
604 final void testReferenceData(int[] seed, int[] expected) {
605 RandomAssert.assertEquals(expected, create(seed));
606 }
607
608
609
610
611
612 @ParameterizedTest
613 @MethodSource(value = "getReferenceData")
614 final void testReferenceDataWithComposite(int[] seed, int[] expected) {
615 RandomAssert.assertEquals(expected, getFactory().create(seed));
616 }
617
618
619
620
621
622
623 @RepeatedTest(value = 5)
624 final void testInitialOutput() {
625 final int[] seed = createRandomSeed();
626
627 final int s = seed[getFactory().lcgSeedSize() / 2];
628
629 final int t = seed[getFactory().lcgSeedSize()];
630 Assertions.assertEquals(getFactory().getMix().apply(s, t), create(seed).nextInt());
631 }
632
633 @Test
634 final void testConstructorWithZeroSeedIsPartiallyFunctional() {
635
636
637
638
639 final int seedSize = getFactory().seedSize();
640 RandomAssert.assertNextLongNonZeroOutput(create(new int[seedSize]), 0, seedSize);
641 }
642
643 @ParameterizedTest
644 @MethodSource(value = "getReferenceData")
645 final void testConstructorWithoutFullLengthSeed(int[] seed) {
646
647 final int seedSize = getFactory().seedSize();
648 RandomAssert.assertNextIntNonZeroOutput(create(new int[] {seed[0]}),
649 seedSize, seedSize);
650 }
651
652 @RepeatedTest(value = 5)
653 final void testConstructorIgnoresFinalAddParameterSeedBit() {
654 final int[] seed1 = createRandomSeed();
655 final int[] seed2 = seed1.clone();
656 final int seedSize = getFactory().seedSize();
657
658 final int index = getFactory().lcgSeedSize() / 2 - 1;
659 seed1[index] &= -1 << 1;
660 seed2[index] |= 1;
661 Assertions.assertEquals(1, seed1[index] ^ seed2[index]);
662 final LongJumpableUniformRandomProvider rng1 = create(seed1);
663 final LongJumpableUniformRandomProvider rng2 = create(seed2);
664 RandomAssert.assertNextLongEquals(seedSize * 2, rng1, rng2);
665 }
666
667 @ParameterizedTest
668 @MethodSource(value = "getReferenceData")
669 final void testJump(int[] seed, int[] expected) {
670 final int[] expectedAfter = createExpectedSequence(seed, expected.length, false);
671 RandomAssert.assertJumpEquals(expected, expectedAfter, create(seed));
672 }
673
674 @ParameterizedTest
675 @MethodSource(value = "getReferenceData")
676 final void testLongJump(int[] seed, int[] expected) {
677 final int[] expectedAfter = createExpectedSequence(seed, expected.length, true);
678 RandomAssert.assertLongJumpEquals(expected, expectedAfter, create(seed));
679 }
680
681 @RepeatedTest(value = 5)
682 final void testJumpAndOutput() {
683 assertJumpAndOutput(false, ThreadLocalRandom.current().nextLong());
684 }
685
686 @RepeatedTest(value = 5)
687 final void testLongJumpAndOutput() {
688 assertJumpAndOutput(true, ThreadLocalRandom.current().nextLong());
689 }
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711 private void assertJumpAndOutput(boolean longJump, long testSeed) {
712 final SplittableRandom r = new SplittableRandom(testSeed);
713 final int[] seed = createRandomSeed(r::nextInt);
714 final int[] steps = createSteps(r);
715 final int cycles = getFactory().seedSize();
716
717 LongJumpableUniformRandomProvider rng1 = create(seed);
718 LongJumpableUniformRandomProvider rng2 = getFactory().create(seed);
719
720 final UnaryOperator<LongJumpableUniformRandomProvider> jump = longJump ?
721 x -> (LongJumpableUniformRandomProvider) x.longJump() :
722 x -> (LongJumpableUniformRandomProvider) x.jump();
723
724
725 for (int i = 0; i < steps.length; i++) {
726 final int step = steps[i];
727 final int index = i;
728
729 LongJumpableUniformRandomProvider copy = jump.apply(rng1);
730
731 for (int j = 0; j < step; j++) {
732 Assertions.assertEquals(rng2.nextInt(), copy.nextInt(),
733 () -> String.format("[%d] Incorrect trailing copy, seed=%d", index, testSeed));
734
735 rng1.nextInt();
736 }
737
738 jump.apply(rng2);
739 for (int j = 0; j < cycles; j++) {
740 Assertions.assertEquals(rng1.nextInt(), rng2.nextInt(),
741 () -> String.format("[%d] Incorrect after catch up jump, seed=%d", index, testSeed));
742 }
743
744
745 copy = rng1;
746 rng1 = rng2;
747 rng2 = copy;
748 }
749 }
750
751 @RepeatedTest(value = 5)
752 final void testSaveRestoreAfterJump() {
753 assertSaveRestoreAfterJump(false, ThreadLocalRandom.current().nextLong());
754 }
755
756 @RepeatedTest(value = 5)
757 final void testSaveRestoreAfterLongJump() {
758 assertSaveRestoreAfterJump(true, ThreadLocalRandom.current().nextLong());
759 }
760
761
762
763
764
765
766
767
768 private void assertSaveRestoreAfterJump(boolean longJump, long testSeed) {
769 final SplittableRandom r = new SplittableRandom(testSeed);
770 final int cycles = getFactory().seedSize();
771
772 final UnaryOperator<LongJumpableUniformRandomProvider> jump = longJump ?
773 x -> (LongJumpableUniformRandomProvider) x.longJump() :
774 x -> (LongJumpableUniformRandomProvider) x.jump();
775
776
777 final LongJumpableUniformRandomProvider rng1 = create(createRandomSeed(r::nextInt));
778 final LongJumpableUniformRandomProvider rng2 = create(createRandomSeed(r::nextInt));
779 jump.apply(rng1);
780 jump.apply(rng2);
781
782
783 RestorableUniformRandomProvider g1 = (RestorableUniformRandomProvider) rng1;
784 RestorableUniformRandomProvider g2 = (RestorableUniformRandomProvider) rng2;
785 g2.restoreState(g1.saveState());
786
787
788 RandomAssert.assertNextLongEquals(cycles, rng1, rng2);
789
790
791 jump.apply(rng1);
792 jump.apply(rng2);
793
794 RandomAssert.assertNextLongEquals(cycles, rng1, rng2);
795 }
796
797
798
799
800
801
802
803
804
805
806
807 private int[] createExpectedSequence(int[] seed, int length, boolean longJump) {
808 final LXMGenerator rng = getFactory().create(seed);
809 if (longJump) {
810 rng.longJump();
811 } else {
812 rng.jump();
813 }
814 return IntStream.generate(rng::nextInt).limit(length).toArray();
815 }
816
817
818
819
820
821
822 private int[] createRandomSeed() {
823 return createRandomSeed(new SplittableRandom()::nextInt);
824 }
825
826
827
828
829
830
831
832
833 private int[] createRandomSeed(IntSupplier gen) {
834 return IntStream.generate(gen).limit(getFactory().seedSize()).toArray();
835 }
836
837
838
839
840
841
842
843
844 private int[] createSteps(SplittableRandom rng) {
845 return createSteps(rng, getFactory().seedSize());
846 }
847
848
849
850
851
852
853
854
855 private static int[] createSteps(SplittableRandom rng, int seedSize) {
856 final int[] steps = IntStream.rangeClosed(1, seedSize).toArray();
857
858 for (int i = steps.length; i > 1; i--) {
859 final int j = rng.nextInt(i);
860 final int tmp = steps[i - 1];
861 steps[i - 1] = steps[j];
862 steps[j] = tmp;
863 }
864 return steps;
865 }
866 }