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 }