The OpenD Programming Language

1 /++
2 $(SCRIPT inhibitQuickIndex = 1;)
3 
4 $(BOOKTABLE $(H2 Generators)
5 
6     $(TR $(TH Generator name) $(TH Description))
7     $(RROW Xoshiro256StarStar, `xoshiro256**`: all-purpose, rock-solid generator)
8     $(RROW Xoshiro128StarStar_32, `xoshiro128**` (32-bit): 32-bit-oriented parameterization of `xoshiro**`)
9     $(RROW Xoroshiro128Plus, $(HTTP en.wikipedia.org/wiki/Xoroshiro128%2B, xoroshiro128+): fast, small, and high-quality))
10 
11 $(BOOKTABLE $(H2 Generic Templates)
12 
13     $(TR $(TH Template name) $(TH Description))
14     $(RROW XoshiroEngine, `xoshiro**` generator.)
15 )
16 
17 Copyright: Copyright Andrei Alexandrescu 2008 - 2009, Masahiro Nakagawa, Ilya Yaroshenko 2016-, Sebastiano Vigna.
18 License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
19 Authors: Masahiro Nakagawa, Ilya Yaroshenko (rework), Nathan Sashihara
20 
21 Macros:
22     WIKI_D = $(HTTP en.wikipedia.org/wiki/$1_distribution, $1 random variable)
23     WIKI_D2 = $(HTTP en.wikipedia.org/wiki/$1_distribution, $2 random variable)
24     T2=$(TR $(TDNW $(LREF $1)) $(TD $+))
25     RROW = $(TR $(TDNW $(LREF $1)) $(TD $+))
26 +/
27 module mir.random.engine.xoshiro;
28 
29 import std.traits;
30 
31 /++
32 `xoshiro256**` (XOR/shift/rotate) as described in $(HTTP arxiv.org/abs/1805.01407,
33 Scrambled linear pseudorandom number generators) (Blackman and Vigna, 2018).
34 64 bit output. 256 bits of state. Period of `2^^256-1`. 4-dimensionally
35 equidistributed. It is 15% slower than `xoroshiro128+` but none of its
36 bits fail binary rank tests and it passes tests for Hamming-weight
37 dependencies introduced in the linked paper. From the authors:
38 
39 <blockquote>
40 This is xoshiro256** 1.0, our all-purpose, rock-solid generator. It has
41 excellent (sub-ns) speed, a state (256 bits) that is large enough for
42 any parallel application, and it passes all tests we are aware of.
43 </blockquote>
44 
45 A `jump()` function is included that skips ahead by `2^^128` calls,
46 to generate non-overlapping subsequences for parallel computations.
47 
48 Public domain reference implementation:
49 $(HTTP xoshiro.di.unimi.it/xoshiro256starstar.c).
50 +/
51 alias Xoshiro256StarStar = XoshiroEngine!(ulong,256,"**",17,45,1,7,5,9);
52 
53 ///
54 @nogc nothrow pure @safe version(mir_random_test) unittest
55 {
56     import mir.random /+: isSaturatedRandomEngine, rand+/;
57     import mir.random.engine.xoshiro : Xoshiro256StarStar;
58     import mir.math.common: fabs;
59 
60     static assert(isRandomEngine!Xoshiro256StarStar);
61     static assert(isSaturatedRandomEngine!Xoshiro256StarStar);
62     auto gen = Xoshiro256StarStar(1234u);//Seed with constant.
63     assert(gen.rand!double.fabs == 0x1.b45d9a0e3ae53p-2);//Generate number from 0 inclusive to 1 exclusive.
64     assert(gen.rand!ulong == 15548185570577040190UL);
65     //Xoshiro256StarStar has a jump function that is equivalent
66     //to 2 ^^ 128 invocations of opCall.
67     gen.jump();
68     assert(gen.rand!ulong == 10759542936515257968UL);
69 }
70 
71 version(mir_random_test) version(unittest)
72 private void testIsPhobosStyleRandom(RNG)()
73 {
74     //Test RNG can be used as a Phobos-style random.
75     alias UIntType = typeof(RNG.init());
76     import std.random: isSeedable, isPhobosUniformRNG = isUniformRNG;
77     import std.range: isForwardRange;
78     static assert(isPhobosUniformRNG!(RNG, UIntType));
79     static assert(isSeedable!(RNG, UIntType));
80     static assert(isForwardRange!RNG);
81     auto gen1 = RNG(1);
82     auto gen2 = RNG(2);
83     auto gen3 = gen1.save;
84     gen2.seed(1);
85     assert(gen1 == gen2);
86     immutable a = gen1.front;
87     gen1.popFront();
88     assert(a == gen2());
89     assert(gen1.front == gen2());
90     assert(a == gen3());
91     assert(gen1.front == gen3());
92 }
93 
94 @nogc nothrow pure @safe version(mir_random_test) unittest
95 {
96     testIsPhobosStyleRandom!Xoshiro256StarStar();
97 }
98 
99 /++
100 32-bit-oriented `xoshiro**` with 128 bits of state.
101 In general $(LREF Xoshiro256StarStar) is preferable except if you are
102 tight on space <em>and</em> know that the generator's output will be
103 consumed 32 bits at a time. (If you need a generator with 128 bits of
104 state that is geared towards producing 64 bits at a time,
105 $(LREF Xoroshiro128Plus) is an option.)
106 32 bit output. 128 bits of state. Period of `2^^128-1`. 4-dimensionally
107 equidistributed. None of its bits fail binary rank tests and it passes
108 tests for Hamming-weight dependencies introduced in the `xoshiro` paper.
109 From the authors:
110 
111 <blockquote>
112 This is xoshiro128** 1.0, our 32-bit all-purpose, rock-solid generator. It
113 has excellent (sub-ns) speed, a state size (128 bits) that is large
114 enough for mild parallelism, and it passes all tests we are aware of.
115 </blockquote>
116 
117 A `jump()` function is included that skips ahead by `2^^64` calls,
118 to generate non-overlapping subsequences for parallel computations.
119 
120 Public domain reference implementation:
121 $(HTTP xoshiro.di.unimi.it/xoshiro128starstar.c).
122 +/
123 alias Xoshiro128StarStar_32 = XoshiroEngine!(uint,128,"**",9,11,0,7,5,9);
124 
125 ///
126 @nogc nothrow pure @safe version(mir_random_test) unittest
127 {
128     import mir.random : isSaturatedRandomEngine, rand;
129     import mir.random.engine.xoshiro : Xoshiro128StarStar_32;
130 
131     static assert(isSaturatedRandomEngine!Xoshiro128StarStar_32);
132     auto gen = Xoshiro128StarStar_32(1234u);//Seed with constant.
133     assert(gen.rand!uint == 1751597702U);
134     //Xoshiro128StarStar_32 has a jump function that is equivalent
135     //to 2 ^^ 64 invocations of opCall.
136     gen.jump();
137     assert(gen.rand!uint == 1248004684U);
138 }
139 
140 @nogc nothrow pure @safe version(mir_random_test) unittest
141 {
142     testIsPhobosStyleRandom!Xoshiro128StarStar_32();
143 }
144 
145 /+
146 Mixin to initialize an array of ulongs `s` from a single ulong `x0`.
147 If s.length > 1 this will never initialize `s` to all zeroes. If
148 s.length == 1 it is up to the caller to check s[0].
149 +/
150 private enum init_s_from_x0_using_splitmix64 =
151 q{
152     static assert(is(typeof(s[0]) == ulong));
153     static assert(is(typeof(x0) == ulong));
154     //http://xoroshiro.di.unimi.it/splitmix64.c
155     foreach (ref e; s)
156     {
157         ulong z = (x0 += 0x9e3779b97f4a7c15uL);
158         z = (z ^ (z >>> 30)) * 0xbf58476d1ce4e5b9uL;
159         z = (z ^ (z >>> 27)) * 0x94d049bb133111ebuL;
160         e = z ^ (z >>> 31);
161     }
162 };
163 
164 /+
165 Mixin to initialize an array of uints `s` from a single uint `x0`.
166 Ensures no element of `s` is 0.
167 +/
168 private enum init_s_from_x0_using_mt32_nozero =
169 q{
170     static assert(is(typeof(s[0]) == uint));
171     static assert(is(typeof(x0) == uint));
172     // Initialization routine from MersenneTwisterEngine.
173     foreach (uint i, ref e; s)
174     {
175         e = (x0 = 1812433253U * (x0 ^ (x0 >> 30)) + i + 1);
176         if (e == 0)
177             e = (i + 1);
178     }
179 };
180 
181 /++
182 Template for the `xoshiro` family of generators.
183 See the $(HTTP vigna.di.unimi.it/papers.php#BlVSLPNG, paper)
184 introducing `xoshiro` and `xoroshiro`.
185 
186 $(LREF Xoshiro256StarStar) and $(LREF Xoshiro128StarStar_32)
187 are aliases for `XoshiroEngine` instantiated with recommended
188 parameters for 64-bit and 32-bit architectures, respectively.
189 
190 Params:
191     UIntType = uint or ulong
192     nbits = number of bits (128, 256, 512; must be 4x or 8x bit size of UIntType)
193     scrambler = "**" (in the future "+" may be added)
194     A = state xor-lshift
195     B = state rotate left
196     I = index of element used for output
197     R = output scramble rotate left
198     S = output scramble pre-rotate multiplier (must be odd)
199     T = output scramble post-rotate multiplier (must be odd)
200 +/
201 struct XoshiroEngine(UIntType, uint nbits, string scrambler,
202 uint A, uint B, uint I, uint R, UIntType S, UIntType T)
203 if ((is(UIntType == uint) || is(UIntType == ulong))
204     && "**" == scrambler
205     && (UIntType.sizeof * 8 * 4 == nbits
206         || UIntType.sizeof * 8 * 8 == nbits))
207 {
208     static assert(nbits > 0 && nbits % (UIntType.sizeof * 8) == 0,
209         "nbits must be a positive multiple of the size in bits of "
210         ~ UIntType.stringof);
211     static assert(S % 2 == 1 && S > 1 && T % 2 == 1 && T > 1,
212         "scrambler multipliers S and T must be odd numbers > 1");
213     static assert(A > 0 && A < UIntType.sizeof*8,
214         "left shift A must be non-zero and less than "
215         ~UIntType.stringof~".sizeof*8");
216     static assert(B > 0 && B < UIntType.sizeof*8
217         && R > 0 && R < UIntType.sizeof*8,
218         "left rotations B and R must be non-zero and less than "
219         ~UIntType.stringof~".sizeof*8");
220 
221     ///
222     enum isRandomEngine = true;
223     /// Largest generated value.
224     enum UIntType max = UIntType.max;
225 
226     enum bool preferHighBits = "**" != scrambler;
227 
228     @disable this();
229     @disable this(this);
230 
231     /++
232     State must not be entirely zero.
233     The constructor ensures this condition is met.
234     +/
235     UIntType[nbits / (UIntType.sizeof * 8)] s;
236 
237     /// Initializes the generator with a seed.
238     this()(UIntType x0) @nogc nothrow pure @safe
239     {
240         static if (is(UIntType == ulong))
241             mixin(init_s_from_x0_using_splitmix64);
242         else static if (is(UIntType == uint))
243             mixin(init_s_from_x0_using_mt32_nozero);
244         else
245             static assert(0, "mir error: no ctor for "
246                 ~ XoshiroEngine.stringof);
247     }
248 
249     /++
250     Advances the random sequence.
251 
252     Returns:
253         A uniformly-distributed integer in the closed range
254         `[0, UIntType.max]`.
255     +/
256     UIntType opCall()() @nogc nothrow pure @safe
257     {
258         import core.bitop : rol;
259         const result = rol!(R, UIntType)(s[I] * S) * T;
260 
261         const t = s[1] << A;
262 
263         static if (s.length == 4)
264         {
265             s[2] ^= s[0];
266             s[3] ^= s[1];
267             s[1] ^= s[2];
268             s[0] ^= s[3];
269         }
270         else static if (s.length == 8)
271         {
272             s[2] ^= s[0];
273             s[5] ^= s[1];
274             s[1] ^= s[2];
275             s[7] ^= s[3];
276             s[3] ^= s[4];
277             s[4] ^= s[5];
278             s[0] ^= s[6];
279             s[6] ^= s[7];
280         }
281         else
282         {
283             static assert(0, "mir error: no opCall for "
284                 ~ XoshiroEngine.stringof);
285         }
286 
287         s[$-2] ^= t;
288         s[$-1] = rol!(B, UIntType)(s[$-1]);
289 
290         return result;
291     }
292 
293     static if((is(UIntType == ulong) && nbits == 256 && A == 17 && B == 45))
294         private enum _hasJump = true;
295     else static if (is(UIntType == ulong) && nbits == 512 && A == 11 && B == 21)
296         private enum _hasJump = true;
297     else static if (is(UIntType == uint) && nbits == 128 && A == 9 && B == 11)
298         private enum _hasJump = true;
299     else
300         private enum _hasJump = false;
301 
302     /++
303     Jump functions are defined for certain `UIntType`, `A`, `B`
304     combinations:
305 
306     <table>
307     <tr><td>UIntType</td><td>nbits</td><td>A</td><td>B</td><td>Num. calls skipped</td></tr>
308     <tr><td>ulong</td><td>256</td><td>17</td><td>45</td><td>2^^128</td></tr>
309     <tr><td>ulong</td><td>512</td><td>11</td><td>21</td><td>2^^256</td></tr>
310     <tr><td>uint</td><td>128</td><td>9</td><td>11</td><td>2^^64</td></tr>
311     </table>
312 
313     These can be used to generate non-overlapping subsequences for parallel
314     computations.
315     +/
316     static if(_hasJump)
317     void jump()() @nogc nothrow pure @safe
318     {
319         static if(is(UIntType == ulong)
320                 && nbits == 256
321                 && A == 17 && B == 45)
322             static immutable ulong[4] JUMP = [
323                 0x180ec6d33cfd0aba, 0xd5a61266f0c9392c,
324                 0xa9582618e03fc9aa, 0x39abdc4529b1661c,
325                 ];
326         else
327         static if(is(UIntType == ulong)
328                 && nbits == 512
329                 && A == 11 && B == 21)
330             static immutable ulong[8] JUMP = [
331                 0x33ed89b6e7a353f9, 0x760083d7955323be,
332                 0x2837f2fbb5f22fae, 0x4b8c5674d309511c,
333                 0xb11ac47a7ba28c25, 0xf1be7667092bcc1c,
334                 0x53851efdb6df0aaf, 0x1ebbc8b23eaf25db
335                 ];
336         else
337         static if(is(UIntType == uint)
338                 && nbits == 128
339                 && A == 9 && B == 11)
340             static immutable uint[4] JUMP = [
341                 0x8764000b, 0xf542d2d3,
342                 0x6fa035c3, 0x77f2db5b
343                 ];
344         else
345             static assert(0, "mir error: no jump for "
346                 ~ XorshiftEngine.stringof);
347 
348         UIntType[s.length] sj = 0;
349         foreach (i; 0 .. JUMP.length)
350             foreach (b; 0 .. (UIntType.sizeof * 8))
351             {
352                 if (JUMP[i] & (UIntType(1) << b))
353                 {
354                     sj[] ^= s[];
355                 }
356                 opCall();
357             }
358         s[] = sj[];
359     }
360 
361     /++
362     Compatibility with $(LINK2 https://dlang.org/phobos/std_random.html#.isUniformRNG,
363     Phobos library methods). Presents this RNG as an InputRange.
364 
365     This struct disables its default copy constructor and so will only work with
366     Phobos functions that "do the right thing" and take RNGs by reference and
367     do not accidentally make implicit copies.
368     +/
369     enum bool isUniformRandom = true;
370     /// ditto
371     enum typeof(this.max) min = typeof(this.max).min;
372     /// ditto
373     enum bool empty = false;
374     /// ditto
375     @property UIntType front()() const
376     {
377         import core.bitop : rol;
378         return rol!(R, UIntType)(s[I] * S) * T;
379     }
380     /// ditto
381     void popFront()() { opCall(); }
382     /// ditto
383     void seed()(UIntType x0)
384     {
385         this.__ctor(x0);
386     }
387     /// ditto
388     @property typeof(this) save()() const
389     {
390         typeof(return) copy = void;
391         foreach (i, ref fld; this.tupleof)
392             copy.tupleof[i] = fld;
393         return copy;
394     }
395 }
396 
397 /++
398 $(HTTP xoroshiro.di.unimi.it, xoroshiro128+) (XOR/rotate/shift/rotate) generator.
399 64 bit output. 128 bits of state. Period of $(D (2 ^^ 128) - 1).
400 
401 Created in 2016 by David Blackman and Sebastiano Vigna as the successor
402 to Vigna's extremely popular $(HTTP vigna.di.unimi.it/ftp/papers/xorshiftplus.pdf,
403 xorshift128+) generator used in the JavaScript engines of
404 $(HTTP v8project.blogspot.com/2015/12/theres-mathrandom-and-then-theres.html,
405 Google Chrome), $(LINK2 https://bugzilla.mozilla.org/show_bug.cgi?id=322529#c99,
406 Mozilla Firefox), $(LINK2 https://bugs.webkit.org/show_bug.cgi?id=151641, Safari),
407 and $(LINK2 https://github.com/Microsoft/ChakraCore/commit/dbda0182dc0a983dfb37d90c05000e79b6fc75b0,
408 Microsoft Edge). From the authors:
409 
410 <blockquote>
411 This is the successor to xorshift128+. It is the fastest full-period
412 generator passing BigCrush without systematic failures, but due to the
413 relatively short period it is acceptable only for applications with a
414 mild amount of parallelism; otherwise, use a xorshift1024* generator.
415 
416 Beside passing BigCrush, this generator passes the PractRand test suite
417 up to (and included) 16TB, with the exception of binary rank tests, as
418 the lowest bit of this generator is an LFSR. The next bit is not an
419 LFSR, but in the long run it will fail binary rank tests, too. The
420 other bits have no LFSR artifacts.
421 
422 We suggest to use a sign test to extract a random Boolean value, and
423 right shifts to extract subsets of bits.
424 </blockquote>
425 
426 Public domain reference implementation:
427 $(HTTP xoroshiro.di.unimi.it/xoroshiro128plus.c).
428 +/
429 struct Xoroshiro128Plus
430 {
431     ///
432     enum isRandomEngine = true;
433     /// Largest generated value.
434     enum ulong max = ulong.max;
435 
436     /++
437     State must not be entirely zero.
438     The constructor ensures this condition is met.
439     +/
440     ulong[2] s;
441 
442     /++
443     The lowest bit of this generator is an
444     $(LINK2 https://en.wikipedia.org/wiki/Linear-feedback_shift_register,
445     LFSR). The next bit is not an LFSR, but in the long run it will fail
446     binary rank tests, too. The other bits have no LFSR artifacts.
447     To provide some context, $(I every) bit of a Mersenne Twister generator
448     (either the 32-bit or 64-bit variant) is an LFSR.
449 
450     The `rand!T` functions in `mir.random` automatically will discard
451     the low bits when generating output smaller than `ulong` due to
452     this generator having `preferHighBits` defined `true`.
453     +/
454     enum bool preferHighBits = true;
455 
456     @disable this();
457     @disable this(this);
458 
459     /// Constructs an $(D Xoroshiro128Plus) generator seeded with $(D_PARAM x0).
460     this()(ulong x0) @nogc nothrow pure @safe
461     {
462         //Seed using splitmix64 as recommended by Vigna.
463         mixin(init_s_from_x0_using_splitmix64);
464     }
465 
466     /// Advances the random sequence.
467     ulong opCall()()
468     {
469         //Public domain implementation:
470         //http://xoroshiro.di.unimi.it/xoroshiro128plus.c
471         import core.bitop : rol;
472         immutable s0 = s[0];
473         auto s1 = s[1];
474         immutable result = s0 + s1;
475 
476         s1 ^= s0;
477         s[0] = rol!(55,ulong)(s0) ^ s1 ^ (s1 << 14); // a, b
478         s[1] = rol!(36,ulong)(s1); // c
479 
480         return result;
481     }
482 
483     /++
484     This is the jump function for the generator. It is equivalent
485     to 2^^64 calls to $(D opCall()); it can be used to generate 2^^64
486     non-overlapping subsequences for parallel computations.
487     +/
488     void jump()() @nogc nothrow pure @safe
489     {
490         static immutable ulong[2] JUMP = [ 0xbeac0467eba5facbUL, 0xd86b048b86aa9922UL ];
491 
492         ulong s0 = 0;
493         ulong s1 = 0;
494         foreach (jump; JUMP)
495         {
496             foreach (b; 0 .. 64)
497             {
498                 if (jump & (1uL << b))
499                 {
500                     s0 ^= s[0];
501                     s1 ^= s[1];
502                 }
503                 opCall();
504             }
505         }
506         s[0] = s0;
507         s[1] = s1;
508     }
509 
510 
511     /++
512     Compatibility with $(LINK2 https://dlang.org/phobos/std_random.html#.isUniformRNG,
513     Phobos library methods). Presents this RNG as an InputRange.
514 
515     This struct disables its default copy constructor and so will only work with
516     Phobos functions that "do the right thing" and take RNGs by reference and
517     do not accidentally make implicit copies.
518     +/
519     enum bool isUniformRandom = true;
520     /// ditto
521     enum typeof(this.max) min = typeof(this.max).min;
522     /// ditto
523     enum bool empty = false;
524     /// ditto
525     @property ulong front()() const { return s[0] + s[1]; }
526     /// ditto
527     void popFront()() { opCall(); }
528     /// ditto
529     void seed()(ulong x0)
530     {
531         this.__ctor(x0);
532     }
533     /// ditto
534     @property typeof(this) save()() const
535     {
536         typeof(return) copy = void;
537         foreach (i, ref fld; this.tupleof)
538             copy.tupleof[i] = fld;
539         return copy;
540     }
541 }
542 
543 ///
544 @nogc nothrow pure @safe version(mir_random_test) unittest
545 {
546     import mir.random.engine : isSaturatedRandomEngine;
547     static assert(isSaturatedRandomEngine!Xoroshiro128Plus);
548     auto gen = Xoroshiro128Plus(1234u);//Seed with constant.
549     assert(gen() == 5968561782418604543);//Generate number.
550     foreach (i; 0 .. 8)
551         gen();
552     assert(gen() == 8335647863237943914uL);
553     //Xoroshiro128Plus has a jump function that is equivalent
554     //to 2 ^^ 64 invocations of opCall.
555     gen.jump();
556     auto n = gen();
557 }
558 
559 @nogc nothrow pure @safe version(mir_random_test) unittest
560 {
561     testIsPhobosStyleRandom!Xoroshiro128Plus();
562 }
563 
564 // Verify that code rewriting has not changed algorithm results.
565 @nogc nothrow pure @safe version(mir_random_test) unittest
566 {
567     import std.meta: AliasSeq;
568     alias PRNGTypes = AliasSeq!(
569         Xoroshiro128Plus, Xoshiro256StarStar, Xoshiro128StarStar_32,
570         // (test-only) xoshiro512**
571         XoshiroEngine!(ulong,512,"**",11,21,1,7,5,9));
572     // Each PRNG has a length 4 array.
573     // The first two items are the first two results after seeding with 123456789.
574     // If the PRNG has a jump function the next two items in the array are the
575     // results after the jump. Otherwise they are 0.
576     immutable ulong[4][PRNGTypes.length] expected = [
577         // xoroshiro128+
578         [11299058612650730663UL, 6338390222986562044UL, 12200862009693591285UL, 8351819689202842404UL],
579         // xoshiro256**
580         [15127205273500847298UL, 16265768176396019016UL, 3991360392352292703UL, 17616895517737714975UL],
581         // xoshiro128** (32-bit)
582         [3135079214UL, 1907411621UL, 1969117605UL, 3884474249UL],
583         // (test-only) xoshiro512**
584         [15127205273500847298UL, 16265768176396019016UL, 12965208988828202353UL, 9889122391782473270UL],
585     ];
586     foreach (i, PRNGType; PRNGTypes)
587     {
588         auto rnd = PRNGType(123456789);
589         assert(rnd() == expected[i][0]);
590         assert(rnd() == expected[i][1]);
591         // Test jump functions.
592         static if (is(typeof(rnd.jump())))
593         {
594             rnd.jump();
595             assert(rnd() == expected[i][2]);
596             assert(rnd() == expected[i][3]);
597         }
598         else
599         {
600             static assert(expected[i][2] == 0 && expected[i][3] == 0);
601         }
602     }
603 }