1 /** 2 * Implementation of the xorshift1024* uniform random number generator. 3 * 4 * Authors: 5 * $(LINK2 http://braingam.es/, Joseph Rushton Wakeling) 6 * 7 * Copyright: 8 * Written in 2016 by Joseph Rushton Wakeling. 9 * 10 * License: 11 * $(LINK2 https://creativecommons.org/publicdomain/zero/1.0/legalcode, Creative Commons CC0) 12 * (public domain) 13 */ 14 module dxorshift.xorshift1024star; 15 16 /** 17 * Xorshift* generators extend the basic xorshift generation 18 * mechanism by scrambling its output through multiplication 19 * by a constant factor, without touching the underlying state. 20 * The result is an extremely fast family of generators with 21 * very high-quality statistical properties. 22 * 23 * The xorshift1024* generator offers a long (2 ^^ 1024 - 1) 24 * period, making it suitable for many massively parallel 25 * applications, while its speed and quality makes it useful 26 * as a good general purpose random number generator. 27 * 28 * If 1024 bits of state are too much, then it is suggested 29 * to use the Xoroshiro128+ generator instead. 30 * 31 * Credits: This code is ported from the public-domain 32 * reference implementation by Sebastiano Vigna, 33 * available online at 34 * $(LINK http://xorshift.di.unimi.it/xorshift1024star.c) 35 * 36 * See also the research paper introducing the 37 * xorshift* family of generators: 38 * $(LINK http://vigna.di.unimi.it/ftp/papers/xorshift.pdf) 39 */ 40 public struct Xorshift1024star 41 { 42 public: 43 /// Marks this range as a uniform random number generator 44 enum bool isUniformRandom = true; 45 46 /// Smallest generated value (0) 47 enum ulong min = ulong.min; 48 49 /// Largest generated value 50 enum ulong max = ulong.max; 51 52 /* Copy-by-value is disabled to avoid unintended 53 * duplication of random sequences; use the `dup` 54 * property if you really wish to copy the state 55 * of the RNG. 56 */ 57 @disable this(this); 58 59 // RNG can only be initialized with a seed 60 @disable this(); 61 62 /** 63 * Constructor (RNG instances can only be initialized 64 * with a specified seed). 65 */ 66 this(ulong s) @nogc @safe nothrow pure 67 { 68 this.seed(s); 69 } 70 71 /// ditto 72 this(ulong[16] s) @nogc @safe nothrow pure 73 { 74 this.seed(s); 75 } 76 77 /// ditto 78 this(R)(R range) 79 if (isInputRange!R && is(Unqual!(ElementType!R) == ulong)) 80 { 81 this.seed(range); 82 } 83 84 /// Range primitives 85 enum bool empty = false; 86 87 /// ditto 88 ulong front() @nogc @property @safe const nothrow pure 89 { 90 return this.state[this.p] * 1181783497276652981uL; 91 } 92 93 /// ditto 94 void popFront() @nogc @safe nothrow pure 95 in 96 { 97 assert(this.state != typeof(this.state).init); 98 } 99 body 100 { 101 immutable ulong s0 = this.state[this.p]; 102 ulong s1 = this.state[this.p = (this.p + 1) & 15]; 103 s1 ^= s1 << 31; // a 104 this.state[this.p] = s1 ^ s0 ^ (s1 >> 11) ^ (s0 >> 30); // b, c 105 } 106 107 /** 108 * Jump function, equivalent to 2 ^^ 512 calls to 109 * `popFront()`; can be used to generate 2 ^^ 512 110 * non-overlapping subsequences for parallel 111 * computation. 112 */ 113 void jump() @nogc @safe nothrow pure 114 { 115 enum ulong[16] jump_ = 116 [0x84242f96eca9c41d, 0xa3c65b8776f96855, 0x5b34a39f070b5837, 0x4489affce4f31a1e, 117 0x2ffeeb0a48316f40, 0xdc2d9891fe68c022, 0x3659132bb12fea70, 0xaac17d8efa43cab8, 118 0xc4cb815590989b13, 0x5ee975283d71c93b, 0x691548c86c1bd540, 0x7910c41d10a1e6a5, 119 0x0b5fc64563b3e2a8, 0x047f7684e9fc949d, 0xb99181f2d8f685ca, 0x284600e3f30e38c3]; 120 enum jumpSize = jump_.sizeof / (*(jump_.ptr)).sizeof; 121 122 ulong[16] t; 123 124 foreach (immutable size_t i; 0 .. jumpSize) 125 { 126 foreach (immutable int b; 0 .. 64) 127 { 128 if (jump_[i] & 1uL << b) 129 { 130 foreach (immutable int j; 0 .. 16) 131 { 132 t[j] ^= this.state[(j + this.p) & 15]; 133 } 134 } 135 this.popFront(); 136 } 137 } 138 139 foreach (immutable int j; 0 .. 16) 140 { 141 this.state[(j + this.p) & 15] = t[j]; 142 } 143 144 this.popFront(); 145 } 146 147 /** 148 * Provides a copy of this RNG instance with identical 149 * internal state. 150 * 151 * This property is provided in preference to `save` 152 * so as to allow the user to duplicate RNG state 153 * when explicitly desired, but without risking 154 * unintended copies by functions that save forward 155 * ranges provided as input. 156 */ 157 typeof(this) dup() @nogc @property @safe nothrow pure 158 { 159 return typeof(this)(this); 160 } 161 162 /// (Re)seeds the generator. 163 void seed(ulong s) @nogc @safe nothrow pure 164 { 165 import dxorshift.splitmix64 : SplitMix64; 166 this.seed(SplitMix64(s)); 167 } 168 169 /// ditto 170 void seed(ulong[16] s) @nogc @safe nothrow pure 171 in 172 { 173 assert(s != typeof(s).init); 174 } 175 body 176 { 177 this.state[] = s[]; 178 this.p = 0; 179 this.popFront(); 180 } 181 182 /// ditto 183 void seed(R)(R range) 184 if (isInputRange!R && is(Unqual!(ElementType!R) == ulong)) 185 { 186 foreach (ref s; this.state) 187 { 188 import std.range.primitives : empty, front, popFront; 189 assert(!range.empty, "Insufficient elements to populate RNG state!"); 190 s = range.front; 191 range.popFront(); 192 } 193 assert(this.state != typeof(this.state).init, "Seed elements cannot all be zero!"); 194 this.p = 0; 195 this.popFront(); 196 } 197 198 private: 199 // 1024 bits of state 200 ulong[16] state; 201 202 // state index used to determine `front` variate 203 int p; 204 205 // Helper constructor used to implement `dup` 206 this(ref typeof(this) that) @nogc @safe nothrow pure 207 { 208 this.state[] = that.state[]; 209 this.p = that.p; 210 } 211 212 import std.range.primitives : ElementType, isInputRange; 213 import std.traits : Unqual; 214 } 215 216 /// 217 unittest 218 { 219 import std.array : array; 220 import std.random : isUniformRNG, randomCover, uniform; 221 import std.range : iota, take; 222 import dxorshift.xorshift1024star; 223 224 // xorshift1024* generators must be initialized 225 // with a specified seed 226 auto gen = Xorshift1024star(123456); 227 228 // verify it is indeed a uniform RNG as defined 229 // in the standard library, whether accessed 230 // directly or via a pointer 231 static assert(isUniformRNG!(typeof(gen))); 232 static assert(isUniformRNG!(typeof(&gen))); 233 234 // since the postblit is disabled, we must 235 // pass a pointer to any functionality that 236 // would otherwise copy the RNG by value 237 assert((&gen).take(2).array == [1060672336872339994uL, 238 1269657541839679748uL]); 239 240 // this means, of course, that we must guarantee 241 // the lifetime of the pointer is valid for the 242 // lifetime of any functionality that uses it 243 auto sample = iota(100).randomCover(&gen).array; 244 245 // however, we can pass the RNG as-is to any 246 // functionality that takes it by ref and does 247 // not try to copy it by value 248 auto val = uniform!"[]"(0.0, 1.0, gen); 249 250 // in circumstances where we really want to 251 // copy the RNG state, we can use `dup` 252 auto gen2 = gen.dup; 253 assert((&gen).take(6).array == (&gen2).take(6).array); 254 } 255 256 unittest 257 { 258 import std.array : array; 259 import std.random : isUniformRNG, isSeedable; 260 import std.range : take; 261 import dxorshift.splitmix64 : SplitMix64; 262 263 static assert(isUniformRNG!Xorshift1024star); 264 static assert(isSeedable!Xorshift1024star); 265 static assert(isSeedable!(Xorshift1024star, ulong[16])); 266 static assert(isSeedable!(Xorshift1024star, SplitMix64)); 267 268 // output comparisons to reference implementation, 269 // using constructor, seeding, and duplication 270 { 271 ulong[16] s = [1uL, 2, 3, 4, 5, 6, 7, 8, 9, 272 10, 11, 12, 13, 14, 15, 16]; 273 auto gen = Xorshift1024star(s); 274 assert((&gen).take(10).array == [13859315694294268191uL, 660744553483990740uL, 275 478363890149751658uL, 15363185464596488753uL, 276 7048025930017007303uL, 14380354638086930432uL, 277 12113818199582042386uL, 1643575379993549061uL, 278 9691004143952970263uL, 660744553483990740uL]); 279 280 gen.seed(s); 281 auto gen2 = gen.dup; 282 assert((&gen).take(10).array == [13859315694294268191uL, 660744553483990740uL, 283 478363890149751658uL, 15363185464596488753uL, 284 7048025930017007303uL, 14380354638086930432uL, 285 12113818199582042386uL, 1643575379993549061uL, 286 9691004143952970263uL, 660744553483990740uL]); 287 288 assert((&gen2).take(10).array == [13859315694294268191uL, 660744553483990740uL, 289 478363890149751658uL, 15363185464596488753uL, 290 7048025930017007303uL, 14380354638086930432uL, 291 12113818199582042386uL, 1643575379993549061uL, 292 9691004143952970263uL, 660744553483990740uL]); 293 } 294 295 { 296 auto s = 123456; 297 auto gen = Xorshift1024star(s); 298 assert((&gen).take(10).array == [1060672336872339994uL, 1269657541839679748uL, 299 16774050821694422223uL, 12851806877936958554uL, 300 5358864585960698830uL, 15545527846258458164uL, 301 13619620665948728563uL, 8885411006495285088uL, 302 6807271905609969851uL, 5743177587051234395uL]); 303 304 gen.seed(s); 305 auto gen2 = gen.dup; 306 assert((&gen).take(10).array == [1060672336872339994uL, 1269657541839679748uL, 307 16774050821694422223uL, 12851806877936958554uL, 308 5358864585960698830uL, 15545527846258458164uL, 309 13619620665948728563uL, 8885411006495285088uL, 310 6807271905609969851uL, 5743177587051234395uL]); 311 312 assert((&gen2).take(10).array == [1060672336872339994uL, 1269657541839679748uL, 313 16774050821694422223uL, 12851806877936958554uL, 314 5358864585960698830uL, 15545527846258458164uL, 315 13619620665948728563uL, 8885411006495285088uL, 316 6807271905609969851uL, 5743177587051234395uL]); 317 } 318 319 { 320 auto s = SplitMix64(123456); 321 auto gen = Xorshift1024star(&s); 322 assert((&gen).take(10).array == [1060672336872339994uL, 1269657541839679748uL, 323 16774050821694422223uL, 12851806877936958554uL, 324 5358864585960698830uL, 15545527846258458164uL, 325 13619620665948728563uL, 8885411006495285088uL, 326 6807271905609969851uL, 5743177587051234395uL]); 327 328 s.seed(123456); 329 gen.seed(&s); 330 auto gen2 = gen.dup; 331 assert((&gen).take(10).array == [1060672336872339994uL, 1269657541839679748uL, 332 16774050821694422223uL, 12851806877936958554uL, 333 5358864585960698830uL, 15545527846258458164uL, 334 13619620665948728563uL, 8885411006495285088uL, 335 6807271905609969851uL, 5743177587051234395uL]); 336 337 assert((&gen2).take(10).array == [1060672336872339994uL, 1269657541839679748uL, 338 16774050821694422223uL, 12851806877936958554uL, 339 5358864585960698830uL, 15545527846258458164uL, 340 13619620665948728563uL, 8885411006495285088uL, 341 6807271905609969851uL, 5743177587051234395uL]); 342 } 343 344 // compare jump to reference implementation, 345 // using constructor, seeding, and duplication 346 { 347 ulong[16] s = [1uL, 2, 3, 4, 5, 6, 7, 8, 9, 348 10, 11, 12, 13, 14, 15, 16]; 349 auto gen = Xorshift1024star(s); 350 gen.jump(); 351 assert((&gen).take(10).array == [8155847354254234864uL, 6748997114909436352uL, 352 6977164193652481126uL, 894342858529849071uL, 353 7913408723420027619uL, 4104992605899338783uL, 354 15682203554882817936uL, 2242557222781099960uL, 355 248325190090758889uL, 3505479351942936508uL]); 356 357 gen.seed(s); 358 auto gen2 = gen.dup; 359 gen.jump(); 360 assert((&gen).take(10).array == [8155847354254234864uL, 6748997114909436352uL, 361 6977164193652481126uL, 894342858529849071uL, 362 7913408723420027619uL, 4104992605899338783uL, 363 15682203554882817936uL, 2242557222781099960uL, 364 248325190090758889uL, 3505479351942936508uL]); 365 366 gen2.jump(); 367 assert((&gen2).take(10).array == [8155847354254234864uL, 6748997114909436352uL, 368 6977164193652481126uL, 894342858529849071uL, 369 7913408723420027619uL, 4104992605899338783uL, 370 15682203554882817936uL, 2242557222781099960uL, 371 248325190090758889uL, 3505479351942936508uL]); 372 } 373 }