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 }