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 }