The OpenD Programming Language

1 /++
2 Authors: Ilya Yaroshenko, documentation is partially based on Phobos.
3 Copyright: Copyright, Ilya Yaroshenko 2016-.
4 License:  $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
5 
6 $(RED This module is available in the extended configuration.)
7 +/
8 module mir.random.algorithm;
9 
10 
11 static if (is(typeof({ import mir.ndslice.slice; })))
12 {
13 import mir.math.common;
14 import mir.primitives;
15 import mir.random;
16 import mir.random.ndvariable: isNdRandomVariable;
17 import mir.random.variable: isRandomVariable;
18 import std.range.primitives: isInputRange, isForwardRange, popFrontExactly, hasSlicing;
19 import std.traits;
20 public import mir.random.engine;
21 import mir.ndslice.slice: Slice;
22 
23 /++
24 Allocates ndslice (vector, matrix, or tensor) and fills it with random numbers.
25 If no variable is specified each element `e` is generated per `rand!(typeof(e))`.
26 
27 Params:
28     gen = random engine (optional, param or template param)
29     var = random variable (optional)
30     lengths = one or more lengths
31 +/
32 pragma(inline, false)
33 auto randomSlice(G, D, size_t N)(G gen, D var, size_t[N] lengths...)
34     if (N && isSaturatedRandomEngine!G && isRandomVariable!D &&
35         (is(G == class) || is(G == interface)))
36 {
37     import mir.ndslice.allocation: uninitSlice;
38     alias T = typeof(var(gen));
39     auto ret = lengths.uninitSlice!T();
40     foreach (ref e; ret.field)
41         e = var(gen);
42     return ret;
43 }
44 
45 /// ditto
46 pragma(inline, false)
47 auto randomSlice(G, D, size_t N)(scope ref G gen, D var, size_t[N] lengths...)
48     if (N && isSaturatedRandomEngine!G && isRandomVariable!D &&
49         is(G == struct))
50 {
51     import mir.ndslice.allocation: uninitSlice;
52     alias T = typeof(var(gen));
53     auto ret = lengths.uninitSlice!T();
54     foreach (ref e; ret.field)
55         e = var(gen);
56     return ret;
57 }
58 
59 /// ditto
60 auto randomSlice(G, D, size_t N)(scope G* gen, D var, size_t[N] lengths...)
61     if (N && isSaturatedRandomEngine!G && isRandomVariable!D &&
62         is(G == struct))
63 {
64     return randomSlice(*gen, var, lengths);
65 }
66 
67 /// ditto
68 auto randomSlice(D, size_t N)(D var, size_t[N] lengths...)
69     if (N && isRandomVariable!D)
70 {
71     return randomSlice(rne, var, lengths);
72 }
73 
74 /// ditto
75 pragma(inline, false)
76 auto randomSlice(G, D, size_t N)(G gen, D var, size_t[N] lengths...)
77     if (N > 1 && isSaturatedRandomEngine!G && isNdRandomVariable!D &&
78         (is(G == class) || is(G == interface)))
79 {
80     import mir.algorithm.iteration: each;
81     import mir.ndslice.allocation: uninitSlice;
82     import mir.ndslice.topology: pack;
83     alias T = D.Element;
84     auto ret = lengths.uninitSlice!T();
85     ret.pack!1.each!(a => var(gen, a));
86     return ret;
87 }
88 
89 /// ditto
90 pragma(inline, false)
91 auto randomSlice(G, D, size_t N)(scope ref G gen, D var, size_t[N] lengths...)
92     if (N > 1 && isSaturatedRandomEngine!G && isNdRandomVariable!D &&
93         is(G == struct))
94 {
95     import mir.algorithm.iteration: each;
96     import mir.ndslice.allocation: uninitSlice;
97     import mir.ndslice.topology: pack;
98     alias T = D.Element;
99     auto ret = lengths.uninitSlice!T();
100     ret.pack!1.each!(a => var(gen, a.field));
101     return ret;
102 }
103 
104 /// ditto
105 auto randomSlice(G, D, size_t N)(scope G* gen, D var, size_t[N] lengths...)
106     if (N > 1 && isSaturatedRandomEngine!G && isNdRandomVariable!D &&
107         is(G == struct))
108 {
109     return randomSlice(*gen, var, lengths);
110 }
111 
112 /// ditto
113 auto randomSlice(D, size_t N)(D var, size_t[N] lengths...)
114     if (N > 1 && isNdRandomVariable!D)
115 {
116     return randomSlice(rne, var, lengths);
117 }
118 
119 /// ditto
120 pragma(inline, false)
121 auto randomSlice(T, G, size_t N)(G gen, size_t[N] lengths...)
122     if (N && isSaturatedRandomEngine!G && (is(G == class) || is(G == interface)))
123 {
124     import mir.internal.utility: isComplex;
125     import mir.ndslice.allocation: uninitSlice;
126     auto ret = lengths.uninitSlice!T();
127     foreach (ref e; ret.field)
128         static if (isComplex!T)
129         {
130             alias R = typeof(T.init.re);
131             e = gen.rand!R + gen.rand!R * 1fi;
132         }
133         else
134             e = gen.rand!T;
135     return ret;
136 }
137 
138 /// ditto
139 pragma(inline, false)
140 auto randomSlice(T, G, size_t N)(scope ref G gen, size_t[N] lengths...)
141     if (N && isSaturatedRandomEngine!G && is(G == struct))
142 {
143     import mir.internal.utility: isComplex;
144     import mir.ndslice.allocation: uninitSlice;
145     auto ret = lengths.uninitSlice!T();
146     foreach (ref e; ret.field)
147         static if (isComplex!T)
148         {
149             alias R = typeof(T.init.re);
150             e = gen.rand!R + gen.rand!R * 1fi;
151         }
152         else
153             e = gen.rand!T;
154     return ret;
155 }
156 
157 /// ditto
158 auto randomSlice(T, G, size_t N)(scope G* gen, size_t[N] lengths...)
159     if (N && isSaturatedRandomEngine!G && is(G == struct))
160 {
161     return randomSlice!T(*gen, lengths);
162 }
163 
164 /// ditto
165 auto randomSlice(T, alias gen = rne, size_t N)(size_t[N] lengths...)
166     if (N && isSaturatedRandomEngine!(typeof(gen)))
167 {
168     return randomSlice!T(gen, lengths);
169 }
170 
171 /// Random sample from Normal distribution
172 nothrow @safe version(mir_random_test) unittest
173 {
174     // mir.ndslice package is required for 'randomSlice', it can be found in 'mir-algorithm'
175     static if (is(typeof({ import mir.ndslice.slice; })))
176     {
177         import mir.random.variable: normalVar;
178         // Using default RNE:
179         auto sample = normalVar.randomSlice(10);
180         assert(sample.shape == [10]);
181 
182         import mir.ndslice.slice: Slice;
183         assert(is(typeof(sample) == Slice!(double*)));
184 
185         // Using pointer to RNE:
186         sample = threadLocalPtr!Random.randomSlice(normalVar, 15);
187 
188         // Using local RNE:
189         auto rng = Random(12345);
190         sample = rng.randomSlice(normalVar, 15);
191     }
192 }
193 
194 /// Random sample from uniform distribution strictly in the interval `(-1, 1)`.
195 nothrow @safe version(mir_random_test) unittest
196 {
197     // mir.ndslice package is required for 'randomSlice', it can be found in 'mir-algorithm'
198     static if (is(typeof({ import mir.ndslice.slice; })))
199     {
200         import mir.algorithm.iteration: all;
201         import mir.math.common: fabs;
202         // Using default RNE:
203         auto sample = randomSlice!double(10);
204         assert(sample.shape == [10]);
205 
206         import mir.ndslice.slice: Slice;
207         assert(is(typeof(sample) == Slice!(double*)));
208         assert(sample.all!(a => a.fabs < 1));
209 
210         // Using pointer to RNE:
211         sample = threadLocalPtr!Random.randomSlice!double(15);
212 
213         // Using local RNE:
214         auto rng = Random(12345);
215         sample = rng.randomSlice!double(15);
216 
217         // For complex numbers:
218         auto csample = randomSlice!cdouble(10);
219     }
220 }
221 
222 /// Random sample from 3D-sphere distribution
223 nothrow @safe version(mir_random_test) unittest
224 {
225     // mir.ndslice package is required for 'randomSlice', it can be found in 'mir-algorithm'
226     static if (is(typeof({ import mir.ndslice.slice; })))
227     {
228         import mir.random.ndvariable: sphereVar;
229         // Using default RNE:
230         auto sample = sphereVar.randomSlice(10, 3);
231         assert(sample.shape == [10, 3]);
232         // 10 observations from R_3
233 
234         import mir.ndslice.slice: Slice;
235         assert(is(typeof(sample) == Slice!(double*, 2)));
236 
237         // Using pointer to RNE:
238         sample = threadLocalPtr!Random.randomSlice(sphereVar, 15, 3);
239 
240         // Using local RNE:
241         auto rng = Random(12345);
242         sample = rng.randomSlice(sphereVar, 15, 3);
243     }
244 }
245 
246 /// Random binary data
247 nothrow @safe version(mir_random_test) unittest
248 {
249     // mir.ndslice package is required for 'randomSlice', it can be found in 'mir-algorithm'
250     static if (is(typeof({ import mir.ndslice.slice; })))
251     {
252         // Using default RNE:
253         auto sample = randomSlice!ulong(15);
254         assert(sample.shape == [15]);
255 
256         import mir.ndslice.slice: Slice;
257         assert(is(typeof(sample) == Slice!(ulong*)));
258 
259         // Using pointer to RNE:
260         sample = randomSlice!ulong(threadLocalPtr!Random, 15);
261 
262         // Using local RNE:
263         auto rng = Random(12345);
264         sample = randomSlice!ulong(rng, 15);
265     }
266 }
267 
268 /++
269 Random sampling utility.
270 Complexity:
271     O(n)
272 References:
273     Jeffrey Scott Vitter, An efficient algorithm for sequential random sampling
274 +/
275 struct VitterStrides
276 {
277     @nogc:
278     nothrow:
279     pure:
280     @safe:
281 
282     private enum alphainv = 16;
283     private double vprime;
284     private size_t N;
285     private size_t n;
286     private bool hot;
287 
288     this(this)
289     {
290         hot = false;
291     }
292 
293     /++
294     Params:
295         N = range length
296         n = sample length
297     +/
298     this()(size_t N, size_t n)
299     {
300         assert(N >= n);
301         this.N = N;
302         this.n = n;
303     }
304 
305     /// Returns: `true` if sample length equals to 0.
306     bool empty()() const @property { return n == 0; }
307     /// Returns: `N` (remaining sample length)
308     size_t length()() const @property { return n; }
309     /// Returns: `n` (remaining range length)
310     size_t tail()() const @property { return N; }
311 
312     /++
313     Returns: random stride step (`S`).
314         After each call `N` decreases by `S + 1` and `n` decreases by `1`.
315     Params:
316         gen = random number engine to use
317     +/
318     sizediff_t opCall(G)(scope ref G gen)
319     {
320         pragma(inline, false);
321         import mir.math.constant: LN2;
322         import mir.random;
323         size_t S;
324         switch(n)
325         {
326         default:
327             double Nr = N;
328             if(alphainv * n > N)
329             {
330                 hot = false;
331                 double top = N - n;
332                 double v = gen.rand!double.fabs;
333                 double quot = top / Nr;
334                 while(quot > v)
335                 {
336                     top--;
337                     Nr--;
338                     S++;
339                     quot *= top / Nr;
340                 }
341                 goto R;
342             }
343             double nr = n;
344             if(hot)
345             {
346                 hot = false;
347                 goto L;
348             }
349         M:
350             vprime = exp2(-gen.randExponential2!double / nr);
351         L:
352             double X = Nr * (1 - vprime);
353             S = cast(size_t) X;
354             if (S + n > N)
355                 goto M;
356             size_t qu1 = N - n + 1;
357             double qu1r = qu1;
358             double y1 = exp2(gen.randExponential2!double / (1 - nr) + double(1 / LN2) / qu1r);
359             vprime = y1 * (1 - X / Nr) * (qu1r / (qu1r - S));
360             if (vprime <= 1)
361             {
362                 hot = true;
363                 goto R;
364             }
365             double y2 = 1;
366             double top = Nr - 1;
367             double bottom = void;
368             size_t limit = void;
369             if(n > S + 1)
370             {
371                 bottom = N - n;
372                 limit = N - S;
373             }
374             else
375             {
376                 bottom = N - (S + 1);
377                 limit = qu1;
378             }
379             foreach_reverse(size_t t; limit .. N)
380             {
381                 y2 *= top / bottom;
382                 top--;
383                 bottom--;
384             }
385             if(Nr / (Nr - X) >= y1 * exp2(log2(y2) / (nr - 1)))
386                 goto R;
387             goto M;
388         case 1:
389             S = gen.randIndex(N);
390         R:
391             N -= S + 1;
392             n--;
393             return S;
394         case 0:
395             S = -1;
396             goto R;
397         }
398     }
399 }
400 
401 ///
402 @nogc nothrow pure @safe version(mir_random_test) unittest
403 {
404     import mir.random.engine.xorshift;
405     auto gen = Xorshift(112);
406     auto strides = VitterStrides(20, 3);
407     size_t s;
408     foreach(_; 0..3)
409     {
410         s += strides(gen) + 1;
411         assert(s + strides.tail == 20);
412     }
413 }
414 
415 /++
416 Selects a random subsample out of `range`, containing exactly `n` elements.
417 The order of elements is the same as in the original range.
418 Returns: $(LREF RandomSample) over the `range`.
419 Params:
420     range = range to sample from
421     gen = random number engine to use
422     n = number of elements to include in the sample; must be less than or equal to the `range.length`
423 Complexity: O(n)
424 +/
425 auto sample(G, Range)(G gen, Range range, size_t n)
426     if(isInputRange!Range && hasLength!Range && (__traits(hasMember, Range, "popFrontExactly") || hasSlicing!Range) &&
427         isSaturatedRandomEngine!G &&
428         (is(G == class) || is(G == interface)))
429 {
430     return RandomSample!(G, Range)(range, gen, n);
431 }
432 
433 /// ditto
434 auto sample(G, Range)(G* gen, Range range, size_t n)
435     if(isInputRange!Range && hasLength!Range && (__traits(hasMember, Range, "popFrontExactly") || hasSlicing!Range) &&
436         isSaturatedRandomEngine!G &&
437         is(G == struct))
438 {
439     return RandomSample!(G, Range)(range, gen, n);
440 }
441 
442 /// ditto
443 auto sample(G, Range)(ref G gen, Range range, size_t n) @system
444     if(isInputRange!Range && hasLength!Range && (__traits(hasMember, Range, "popFrontExactly") || hasSlicing!Range) &&
445         isSaturatedRandomEngine!G &&
446         is(G == struct))
447 {
448     return RandomSample!(G, Range)(range, gen, n);
449 }
450 
451 /// ditto
452 auto sample(alias gen = rne, Range)(Range range, size_t n)
453     if(isInputRange!Range && hasLength!Range && (__traits(hasMember, Range, "popFrontExactly") || hasSlicing!Range) &&
454         __traits(compiles, { static assert(isSaturatedRandomEngine!(typeof(gen))); }))
455 {
456     return RandomSample!(Range, gen)(range, n);
457 }
458 
459 /// Default RNE
460 nothrow @safe version(mir_random_test) unittest
461 {
462     // mir.ndslice package is required for 'iota', it can be found in 'mir-algorithm'
463     static if (is(typeof({ import mir.ndslice.slice; })))
464     {
465         import mir.ndslice.topology: iota;
466 
467         auto sample = 100.iota.sample(7);
468         assert(sample.length == 7);
469     }
470 }
471 
472 ///
473 nothrow @safe version(mir_random_test) unittest
474 {
475     // mir.ndslice package is required for 'iota', it can be found in 'mir-algorithm'
476     static if (is(typeof({ import mir.ndslice.slice; })))
477     {
478         import mir.algorithm.iteration: equal;
479         import mir.ndslice.topology: iota;
480         import mir.random.engine.xorshift;
481 
482         // Using pointer to RNE:
483         setThreadLocalSeed!Xorshift(112); //Use a known seed instead of a random seed.
484         Xorshift* gen_ptr = threadLocalPtr!Xorshift;
485         auto sample1 = gen_ptr.sample(100.iota, 7);
486 
487         // Using alias of local RNE:
488         Xorshift gen = Xorshift(112);
489         auto sample2 = 100.iota.sample!gen(7);
490 
491         assert(sample1.equal(sample2));
492     }
493 }
494 
495 @nogc nothrow @safe version(mir_random_test) unittest
496 {
497     // mir.ndslice package is required for 'iota', it can be found in 'mir-algorithm'
498     static if (is(typeof({ import mir.ndslice.slice; })))
499     {
500         import mir.algorithm.iteration: equal;
501         import mir.ndslice.topology: iota;
502         import mir.random.engine.xorshift;
503         setThreadLocalSeed!Xorshift(232);//Use a known seed instead of a random seed.
504         Xorshift* gen = threadLocalPtr!Xorshift;
505 
506         assert(iota(0).equal(gen.sample(iota(0), 0)));
507         assert(iota(1).equal(gen.sample(iota(1), 1)));
508         assert(iota(2).equal(gen.sample(iota(2), 2)));
509         assert(iota(3).equal(gen.sample(iota(3), 3)));
510         assert(iota(8).equal(gen.sample(iota(8), 8)));
511         assert(iota(1000).equal(gen.sample(iota(1000), 1000)));
512     }
513 }
514 
515 @nogc nothrow version(mir_random_test) unittest
516 {
517 	__gshared size_t[] arr = [1, 2, 3];
518 	auto res = rne.sample(arr, 1);
519 }
520 
521 @nogc nothrow version(mir_random_test) unittest
522 {
523 	__gshared size_t[] arr = [1, 2, 3];
524     import mir.ndslice.topology: map;
525 	auto res = rne.sample(arr.map!(a => a + 1), 1);
526 }
527 
528 /++
529 Lazy input or forward range containing a random sample.
530 $(LREF VitterStrides) is used to skip elements.
531 Complexity: O(n)
532 Note: $(UL $(LI The structure holds a pointer to a generator.) $(LI The structure must not be copied (explicitly or implicitly) outside from a function.))
533 +/
534 struct RandomSample(G, Range)
535 {
536     private VitterStrides strides;
537     static if (is(G == struct))
538         private G* gen;
539     else
540         private G gen;
541     private Range range;
542 
543     ///
544     this()(Range range, G gen, size_t n)
545     if (is(G == class) || is(G == interface))
546     {
547         this.range = range;
548         this.gen = gen;
549         strides = VitterStrides(range.length, n);
550         auto s = strides(gen);
551         if(s > 0)
552             this.range.popFrontExactly(s);
553     }
554 
555     /// ditto
556     this()(Range range, G* gen, size_t n)
557     if (is(G == struct))
558     {
559         this.range = range;
560         this.gen = gen;
561         strides = VitterStrides(range.length, n);
562         auto s = strides(*this.gen);
563         if(s > 0)
564             this.range.popFrontExactly(s);
565     }
566 
567     /// ditto
568     this()(Range range, ref G gen, size_t n) @system
569     if (is(G == struct))
570     {
571         this(range, &gen, n);
572     }
573 
574     /// Range primitives
575     size_t length() const @property { return strides.length + 1; }
576     /// ditto
577     bool empty()() const @property { return length == 0; }
578     /// ditto
579     auto ref front()() @property { return range.front; }
580     /// ditto
581     void popFront()() { range.popFrontExactly(strides(gen) + 1); }
582     /// ditto
583     static if (isForwardRange!Range)
584     auto save()() @property { import std.range.primitives: save; return RandomSample(range.save, gen, length); }
585 }
586 
587 /// ditto
588 struct RandomSample(Range, alias gen)
589 {
590     private VitterStrides strides;
591     private Range range;
592     ///
593     this(Range range, size_t n)
594     {
595         this.range = range;
596         strides = VitterStrides(range.length, n);
597         auto s = strides(gen);
598         if(s > 0)
599             this.range.popFrontExactly(s);
600     }
601 
602     /// Range primitives
603     size_t length()() const @property { return strides.length + 1; }
604     /// ditto
605     bool empty()() const @property { return length == 0; }
606     /// ditto
607     auto ref front()() @property { return range.front; }
608     /// ditto
609     void popFront()() { range.popFrontExactly(strides(gen) + 1); }
610     /// ditto
611     static if (isForwardRange!Range)
612     auto save()() @property { import std.range.primitives: save; return RandomSample!(Range,gen)(range.save, length); }
613 }
614 
615 /++
616 Shuffles elements of `range`.
617 Params:
618     gen = random number engine to use
619     range = random-access range whose elements are to be shuffled
620 Complexity: O(range.length)
621 +/
622 pragma(inline, false)
623 void shuffle(G, Iterator)(scope ref G gen, Slice!Iterator range)
624     if (isSaturatedRandomEngine!G)
625 {
626     for (; !range.empty; range.popFront)
627     {
628         auto idx = gen.randIndex(range.length);
629         static if (is(typeof(&range[0])))
630         {
631             import mir.utility: swap;
632             swap(range.front, range[idx]);
633         }
634         else
635         {
636             auto t = range.front;
637             range.front = range[idx];
638             range[idx] = t;
639         }
640     }
641 }
642 
643 /// ditto
644 void shuffle(G, Iterator)(scope G* gen, Slice!Iterator range)
645     if (isSaturatedRandomEngine!G)
646 {
647     return .shuffle(*gen, range);
648 }
649 
650 /// ditto
651 void shuffle(Iterator)(Slice!Iterator range)
652 {
653     return .shuffle(rne, range);
654 }
655 
656 ///
657 nothrow @safe version(mir_random_test) unittest
658 {
659     // mir.ndslice package is required, it can be found in 'mir-algorithm'
660     static if (is(typeof({ import mir.ndslice.slice; })))
661     {
662         import mir.ndslice.allocation: slice;
663         import mir.ndslice.topology: iota;
664         import mir.ndslice.sorting;
665 
666         auto a = iota(10).slice;
667 
668         shuffle(a);
669 
670         sort(a);
671         assert(a == iota(10));
672     }
673 }
674 
675 ///
676 nothrow @safe version(mir_random_test) unittest
677 {
678     // mir.ndslice package is required, it can be found in 'mir-algorithm'
679     static if (is(typeof({ import mir.ndslice.slice; })))
680     {
681         import mir.ndslice.slice: sliced;
682         import mir.ndslice.sorting;
683 
684         auto a = [1, 2, 3, 4];
685         a.sliced.shuffle;
686 
687         sort(a);
688         assert(a == [1, 2, 3, 4]);
689     }
690 }
691 
692 /++
693 Partially shuffles the elements of `range` such that upon returning `range[0..n]`
694 is a random subset of `range` and is randomly ordered. 
695 `range[n..r.length]` will contain the elements not in `range[0..n]`.
696 These will be in an undefined order, but will not be random in the sense that their order after
697 `shuffle` returns will not be independent of their order before
698 `shuffle` was called.
699 Params:
700     gen = (optional) random number engine to use
701     range = random-access range with length whose elements are to be shuffled
702     n = number of elements of `r` to shuffle (counting from the beginning);
703         must be less than `r.length`
704 Complexity: O(n)
705 +/
706 pragma(inline, false)
707 void shuffle(G, Iterator)(scope ref G gen, Slice!Iterator range, size_t n)
708     if (isSaturatedRandomEngine!G)
709 {
710     assert(n <= range.length, "n must be <= range.length for shuffle.");
711     for (; n; n--, range.popFront)
712     {
713         auto idx = gen.randIndex(range.length);
714         static if (is(typeof(&range[0])))
715         {
716             import mir.utility: swap;
717             swap(range.front, range[idx]);
718         }
719         else
720         {
721             auto t = range.front;
722             range.front = range[idx];
723             range[idx] = t;
724         }
725     }
726 }
727 
728 /// ditto
729 void shuffle(G, Iterator)(scope G* gen, Slice!Iterator range, size_t n)
730     if (isSaturatedRandomEngine!G)
731 {
732     return .shuffle(*gen, range, n);
733 }
734 
735 /// ditto
736 void shuffle(Iterator)(Slice!Iterator range, size_t n)
737 {
738     return .shuffle(rne, range, n);
739 }
740 
741 ///
742 nothrow @safe version(mir_random_test) unittest
743 {
744     static if (is(typeof({ import mir.ndslice.slice; })))
745     {
746         import mir.ndslice.allocation: slice;
747         import mir.ndslice.topology: iota;
748         import mir.ndslice.sorting;
749 
750         auto a = iota(10).slice;
751 
752         shuffle(a, 4);
753 
754         sort(a);
755         assert(a == iota(10));
756     }
757 }
758 
759 // Ensure that the demo code in README.md stays up to date.
760 // If this unittest needs to be updated due to a change, update
761 // README.md too!
762 nothrow @safe version(mir_random_test) unittest
763 {
764     static if (is(typeof({ import mir.ndslice.slice; })))
765     {
766         import mir.random;
767         import mir.random.variable: normalVar;
768         import mir.random.algorithm: randomSlice;
769 
770         auto sample = normalVar.randomSlice(10);
771 
772         auto k = sample[$.randIndex];
773     }
774 }
775 
776 nothrow @safe version(mir_random_test) unittest
777 {
778     static if (is(typeof({ import mir.ndslice.slice; })))
779     {
780         import mir.random;
781         import mir.random.variable: normalVar;
782         import mir.random.algorithm: randomSlice;
783 
784         // Engines are allocated on stack or global
785         auto rng = Random(unpredictableSeed);
786         auto sample = rng.randomSlice(normalVar, 10);
787 
788         auto k = sample[rng.randIndex($)];
789     }
790 }
791 }
792 else
793 {
794     version(unittest) {} else static assert(0, "mir.ndslice is required for mir.random.algorithm, it can be found in 'mir-algorithm' repository.");
795 }