1 /++ 2 $(SCRIPT inhibitQuickIndex = 1;) 3 4 $(BOOKTABLE $(H2 Utilities) 5 6 $(TR $(TH Name), $(TH Description)) 7 $(T2 isRandomVariable, Trait) 8 ) 9 10 $(BOOKTABLE $(H2 Random Variables), 11 12 $(TR $(TH Generator name) $(TH Description)) 13 $(RVAR Bernoulli, $(WIKI_D Bernoulli)) 14 $(RVAR Bernoulli2, Optimized $(WIKI_D Bernoulli) for `p = 1/2`) 15 $(RVAR Beta, $(WIKI_D Beta)) 16 $(RVAR Binomial, $(WIKI_D Binomial)) 17 $(RVAR Cauchy, $(WIKI_D Cauchy)) 18 $(RVAR ChiSquared, $(WIKI_D Chi-squared)) 19 $(RVAR Discrete, Discrete distribution) 20 $(RVAR Exponential, $(WIKI_D Exponential)) 21 $(RVAR ExtremeValue, $(WIKI_D2 Generalized_extreme_value, Extreme value)) 22 $(RVAR FisherF, $(WIKI_D F)) 23 $(RVAR Gamma, $(WIKI_D Gamma)) 24 $(RVAR Geometric, $(WIKI_D Geometric)) 25 $(RVAR LogNormal, $(WIKI_D Log-normal)) 26 $(RVAR NegativeBinomial, $(WIKI_D Negative_binomial)) 27 $(RVAR Normal, $(WIKI_D Normal)) 28 $(RVAR PiecewiseConstant, Piecewise constant distribution) 29 $(RVAR PiecewiseLinear, Piecewise linear distribution) 30 $(RVAR Poisson, $(WIKI_D Poisson)) 31 $(RVAR StudentT, $(WIKI_D Student's_t)) 32 $(RVAR Uniform, $(WIKI_D Discrete_uniform) 33 and $(HTTP en.wikipedia.org/wiki/Uniform_distribution_(continuous), 34 Uniform distribution (continuous))) 35 $(RVAR Weibull, $(WIKI_D Weibull)) 36 ) 37 38 Authors: Ilya Yaroshenko, Sebastian Wilzbach (DiscreteVariable) 39 Copyright: Copyright, Ilya Yaroshenko 2016-. 40 License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 41 42 Macros: 43 WIKI_D = $(HTTP en.wikipedia.org/wiki/$1_distribution, $1 random variable) 44 WIKI_D2 = $(HTTP en.wikipedia.org/wiki/$1_distribution, $2 random variable) 45 T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) 46 RVAR = $(TR $(TDNW $(LREF $1Variable)) $(TD $+)) 47 +/ 48 module mir.random.variable; 49 50 import mir.random; 51 import std.traits; 52 53 import mir.math; 54 55 private T sumSquares(T)(const T a, const T b) 56 { 57 return fmuladd(a, a, b * b); 58 } 59 60 @nogc nothrow pure @safe version(mir_random_test) unittest 61 { 62 assert(13.0 == sumSquares(2.0, 3.0)); 63 } 64 65 /// User Defined Attribute definition for Random Variable. 66 enum RandomVariable; 67 68 /++ 69 Test if T is a random variable. 70 +/ 71 template isRandomVariable(T) 72 { 73 static if (is(typeof(T.isRandomVariable) : bool)) 74 { 75 enum isRandomVariable = T.isRandomVariable && 76 is(typeof(((T rv, Random* gen) => rv(*gen))(T.init, null))) 77 && 78 is(typeof(((T rv, Random* gen) => rv(*gen))(T.init, null)) 79 == 80 typeof(((T rv, Random* gen) => rv.opCall!Random(*gen))(T.init, null))); 81 } 82 else enum isRandomVariable = false; 83 } 84 85 /++ 86 $(WIKI_D Discrete_uniform). 87 Returns: `X ~ U[a, b]` 88 +/ 89 struct UniformVariable(T) 90 if (isIntegral!T) 91 { 92 /// 93 enum isRandomVariable = true; 94 95 private alias U = Unsigned!T; 96 private U length; 97 private T location = 0; 98 99 /++ 100 Constraints: `a <= b`. 101 +/ 102 this(T a, T b) 103 { 104 assert(a <= b, "constraint: a <= b"); 105 length = cast(U) (b - a + 1); 106 location = a; 107 } 108 109 /// 110 T opCall(G)(scope ref G gen) 111 if (isSaturatedRandomEngine!G) 112 { 113 return length ? cast(T) (gen.randIndex!U(length) + location) : gen.rand!U; 114 } 115 /// ditto 116 T opCall(G)(scope G* gen) 117 if (isSaturatedRandomEngine!G) 118 { 119 return opCall!(G)(*gen); 120 } 121 122 /// 123 T min() @property { return location; } 124 /// 125 T max() @property { return cast(T) (length - 1 + location); } 126 } 127 128 /// ditto 129 UniformVariable!T uniformVar(T)(in T a, in T b) 130 if(isIntegral!T) 131 { 132 return typeof(return)(a, b); 133 } 134 135 version (D_Ddoc) 136 { 137 // For DDoc we pretend uniformVariable is two separate functions 138 // rather than a single alias because otherwise it couldn't appear 139 // in two separate places in the documentation. 140 141 /// ditto 142 UniformVariable!T uniformVariable(T = double)(in T a, in T b) 143 if(isIntegral!T) 144 { 145 return typeof(return)(a, b); 146 } 147 } 148 149 /// 150 @nogc nothrow @safe version(mir_random_test) unittest 151 { 152 import mir.random.engine; 153 auto gen = Random(unpredictableSeed); 154 auto rv = uniformVar(-10, 10); // [-10, 10] 155 static assert(isRandomVariable!(typeof(rv))); 156 auto x = rv(gen); // random variable 157 assert(rv.min == -10); 158 assert(rv.max == 10); 159 } 160 161 /// 162 @nogc nothrow @safe version(mir_random_test) unittest 163 { 164 import mir.random.engine; 165 Random* gen = threadLocalPtr!Random; 166 auto rv = UniformVariable!int(-10, 10); // [-10, 10] 167 auto x = rv(gen); // random variable 168 assert(rv.min == -10); 169 assert(rv.max == 10); 170 } 171 172 @nogc nothrow pure @safe version(mir_random_test) unittest 173 { 174 // Test alias. 175 assert(uniformVariable(-10, 10) == uniformVar(-10, 10)); 176 // Test that uniformVar works correctly with ubyte. 177 static assert(isRandomVariable!(typeof(uniformVar!ubyte(0, 255)))); 178 } 179 180 /++ 181 $(HTTP en.wikipedia.org/wiki/Uniform_distribution_(continuous), Uniform distribution (continuous)). 182 Returns: `X ~ U[a, b$(RPAREN)` 183 +/ 184 struct UniformVariable(T) 185 if (isFloatingPoint!T) 186 { 187 /// 188 enum isRandomVariable = true; 189 190 private T _a; 191 private T _b; 192 193 /++ 194 Constraints: `a < b`, `a` and `b` are finite numbers. 195 +/ 196 this(T a, T b) 197 { 198 assert(a < b, "constraint: a < b"); 199 assert(a.fabs < T.infinity); 200 assert(b.fabs < T.infinity); 201 _a = a; 202 _b = b; 203 } 204 205 /// 206 T opCall(G)(scope ref G gen) 207 if (isSaturatedRandomEngine!G) 208 { 209 auto ret = gen.rand!T.fabs.fmuladd(_b - _a, _a); 210 if(ret < _b) 211 return ret; 212 return max; 213 } 214 /// ditto 215 T opCall(G)(scope G* gen) 216 if (isSaturatedRandomEngine!G) 217 { 218 return opCall!(G)(*gen); 219 } 220 221 /// 222 T min() @property { return _a; } 223 /// 224 T max() @property { return _b.nextDown; } 225 } 226 227 /// ditto 228 UniformVariable!T uniformVar(T = double)(in T a, in T b) 229 if(isFloatingPoint!T) 230 { 231 return typeof(return)(a, b); 232 } 233 234 version (D_Ddoc) 235 { 236 // For DDoc we pretend uniformVariable is two separate functions 237 // rather than a single alias because otherwise it couldn't appear 238 // in two separate places in the documentation. 239 240 /// ditto 241 UniformVariable!T uniformVariable(T = double)(in T a, in T b) 242 if(isFloatingPoint!T) 243 { 244 return typeof(return)(a, b); 245 } 246 } 247 248 /// 249 @nogc nothrow @safe version(mir_random_test) unittest 250 { 251 import mir.random.engine; 252 import mir.math : nextDown; 253 auto gen = Random(unpredictableSeed); 254 auto rv = uniformVar(-8.0, 10); // [-8, 10) 255 static assert(isRandomVariable!(typeof(rv))); 256 auto x = rv(gen); // random variable 257 assert(rv.min == -8.0); 258 assert(rv.max == 10.0.nextDown); 259 } 260 261 /// 262 @nogc nothrow @safe version(mir_random_test) unittest 263 { 264 import mir.random.engine; 265 import mir.math : nextDown; 266 auto gen = Random(unpredictableSeed); 267 auto rv = UniformVariable!double(-8, 10); // [-8, 10) 268 foreach(_; 0..1000) 269 { 270 auto x = rv(gen); 271 assert(rv.min <= x && x <= rv.max); 272 } 273 } 274 275 /// 276 @nogc nothrow @safe version(mir_random_test) unittest 277 { 278 import mir.random.engine; 279 import mir.math : nextDown; 280 Random* gen = threadLocalPtr!Random; 281 auto rv = UniformVariable!double(-8, 10); // [-8, 10) 282 auto x = rv(gen); // random variable 283 assert(rv.min == -8.0); 284 assert(rv.max == 10.0.nextDown); 285 } 286 287 @nogc nothrow pure @safe version(mir_random_test) unittest 288 { 289 // Test alias. 290 assert(uniformVariable(-8.0, 10.0) == uniformVar(-8.0, 10.0)); 291 } 292 293 version (D_Ddoc) 294 { 295 // For DDoc we pretend uniformVariable is two separate functions 296 // rather than a single alias because otherwise it couldn't appear 297 // in two separate places in the documentation. 298 } 299 else 300 { 301 alias uniformVariable = uniformVar; 302 } 303 304 /++ 305 $(WIKI_D Exponential). 306 Returns: `X ~ Exp(β)` 307 +/ 308 struct ExponentialVariable(T) 309 if (isFloatingPoint!T) 310 { 311 /// 312 enum isRandomVariable = true; 313 314 private T scale = T(LN2); 315 316 /// 317 this(T scale) 318 { 319 this.scale = T(LN2) * scale; 320 } 321 322 /// 323 T opCall(G)(scope ref G gen) 324 if (isSaturatedRandomEngine!G) 325 { 326 return gen.randExponential2!T * scale; 327 } 328 /// ditto 329 T opCall(G)(scope G* gen) 330 if (isSaturatedRandomEngine!G) 331 { 332 return opCall!(G)(*gen); 333 } 334 335 /// 336 enum T min = 0; 337 /// 338 enum T max = T.infinity; 339 } 340 341 /// ditto 342 ExponentialVariable!T exponentialVar(T = double)(in T scale = 1) 343 if (isFloatingPoint!T) 344 { 345 return typeof(return)(scale); 346 } 347 348 /// ditto 349 alias exponentialVariable = exponentialVar; 350 351 /// 352 @nogc nothrow @safe version(mir_random_test) unittest 353 { 354 auto rv = exponentialVar; 355 static assert(isRandomVariable!(typeof(rv))); 356 import mir.random.engine; 357 auto x = rv(rne); 358 } 359 360 /// 361 @nogc nothrow @safe version(mir_random_test) unittest 362 { 363 import mir.random.engine; 364 Random* gen = threadLocalPtr!Random; 365 auto rv = ExponentialVariable!double(1); 366 auto x = rv(gen); 367 } 368 369 /++ 370 $(WIKI_D Weibull). 371 +/ 372 struct WeibullVariable(T) 373 if (isFloatingPoint!T) 374 { 375 /// 376 enum isRandomVariable = true; 377 378 private T _pow = 1; 379 private T scale = 1; 380 381 /// 382 this(T shape, T scale) 383 { 384 _pow = 1 / shape; 385 this.scale = scale; 386 } 387 388 /// 389 T opCall(G)(scope ref G gen) 390 if (isSaturatedRandomEngine!G) 391 { 392 return ExponentialVariable!T()(gen).pow(_pow) * scale; 393 } 394 /// ditto 395 T opCall(G)(scope G* gen) 396 if (isSaturatedRandomEngine!G) 397 { 398 return opCall!(G)(*gen); 399 } 400 401 /// 402 enum T min = 0; 403 /// 404 enum T max = T.infinity; 405 } 406 407 /// ditto 408 WeibullVariable!T weibullVar(T = double)(T shape = 1, T scale = 1) 409 if (isFloatingPoint!T) 410 { 411 return typeof(return)(shape, scale); 412 } 413 414 /// ditto 415 alias weibullVariable = weibullVar; 416 417 /// 418 @nogc nothrow @safe version(mir_random_test) unittest 419 { 420 import mir.random.engine; 421 auto gen = Random(unpredictableSeed); 422 auto rv = weibullVar; 423 static assert(isRandomVariable!(typeof(rv))); 424 auto x = rv(gen); 425 } 426 427 /// 428 @nogc nothrow @safe version(mir_random_test) unittest 429 { 430 import mir.random.engine; 431 Random* gen = threadLocalPtr!Random; 432 auto rv = WeibullVariable!double(3, 2); 433 auto x = rv(gen); 434 } 435 436 /++ 437 $(WIKI_D Gamma). 438 Returns: `X ~ Gamma(𝝰, 𝞫)` 439 Params: 440 T = floating point type 441 Exp = if true log-scaled values are produced. `ExpGamma(𝝰, 𝞫)`. 442 The flag is useful when shape parameter is small (`𝝰 << 1`). 443 +/ 444 struct GammaVariable(T, bool Exp = false) 445 if (isFloatingPoint!T) 446 { 447 /// 448 enum isRandomVariable = true; 449 450 private T shape = 1; 451 private T scale = 1; 452 453 /// 454 this(T shape, T scale) 455 { 456 this.shape = shape; 457 if(Exp) 458 this.scale = log(scale); 459 else 460 this.scale = scale; 461 } 462 463 /// 464 T opCall(G)(scope ref G gen) 465 if (isSaturatedRandomEngine!G) 466 { 467 T x = void; 468 if (shape > 1) 469 { 470 T b = shape - 1; 471 T c = fmuladd(3, shape, - 0.75f); 472 for(;;) 473 { 474 T u = gen.rand!T; 475 T v = gen.rand!T; 476 T w = (u + 1) * (1 - u); 477 T y = sqrt(c / w) * u; 478 x = b + y; 479 if (!(0 <= x && x < T.infinity)) 480 continue; 481 auto z = w * v; 482 if (z * z * w <= 1 - 2 * y * y / x) 483 break; 484 if (fmuladd(3, log(w), 2 * log(fabs(v))) <= 2 * (b * log(x / b) - y)) 485 break; 486 } 487 } 488 else 489 if (shape < 1) 490 { 491 T b = 1 - shape; 492 T c = 1 / shape; 493 for (;;) 494 { 495 T u = gen.rand!T.fabs; 496 T v = gen.randExponential2!T * T(LN2); 497 if (u > b) 498 { 499 T e = -log((1 - u) * c); 500 u = fmuladd(shape, e, b); 501 v += e; 502 } 503 static if (Exp) 504 { 505 x = log(u) * c; 506 if (x <= log(v)) 507 return x + scale; 508 } 509 else 510 { 511 x = pow(u, c); 512 if (x <= v) 513 break; 514 } 515 } 516 } 517 else 518 { 519 x = gen.randExponential2!T * T(LN2); 520 } 521 static if (Exp) 522 return log(x) + scale; 523 else 524 return x * scale; 525 } 526 /// ditto 527 T opCall(G)(scope G* gen) 528 if (isSaturatedRandomEngine!G) 529 { 530 return opCall!(G)(*gen); 531 } 532 533 /// 534 enum T min = Exp ? -T.infinity : 0; 535 /// 536 enum T max = T.infinity; 537 } 538 539 /// ditto 540 GammaVariable!T gammaVar(T = double)(in T shape = 1, in T scale = 1) 541 if (isFloatingPoint!T) 542 { 543 return typeof(return)(shape, scale); 544 } 545 546 /// ditto 547 alias gammaVariable = gammaVar; 548 549 /// 550 @nogc nothrow @safe version(mir_random_test) unittest 551 { 552 auto rv = gammaVar; 553 static assert(isRandomVariable!(typeof(rv))); 554 import mir.random.engine; 555 auto x = rv(rne); 556 } 557 558 /// 559 @nogc nothrow @safe version(mir_random_test) unittest 560 { 561 import mir.random.engine; 562 Random* gen = threadLocalPtr!Random; 563 auto rv = GammaVariable!double(1, 1); 564 auto x = rv(gen); 565 } 566 567 /++ 568 $(WIKI_D Beta). 569 Returns: `X ~ Beta(𝝰, 𝞫)` 570 +/ 571 struct BetaVariable(T) 572 if (isFloatingPoint!T) 573 { 574 /// 575 enum isRandomVariable = true; 576 577 private T a = 1; 578 private T b = 1; 579 580 /// 581 this(T a, T b) 582 { 583 this.a = a; 584 this.b = b; 585 } 586 587 /// 588 T opCall(G)(scope ref G gen) 589 if (isSaturatedRandomEngine!G) 590 { 591 if (a <= 1 && b <= 1) for (;;) 592 { 593 T u = gen.randExponential2!T; 594 T v = gen.randExponential2!T; 595 u = -u; 596 v = -v; 597 u /= a; 598 v /= b; 599 T x = exp2(u); 600 T y = exp2(v); 601 T z = x + y; 602 if (z <= 1) 603 { 604 if (z) 605 return x / z; 606 z = fmax(u, v); 607 u -= z; 608 v -= z; 609 return exp2(u - log2(exp2(u) + exp2(v))); 610 } 611 } 612 T x = GammaVariable!T(a, 1)(gen); 613 T y = GammaVariable!T(b, 1)(gen); 614 T z = x + y; 615 return x / z; 616 } 617 /// ditto 618 T opCall(G)(scope G* gen) 619 if (isSaturatedRandomEngine!G) 620 { 621 return opCall!(G)(*gen); 622 } 623 624 /// 625 enum T min = 0; 626 /// 627 enum T max = 1; 628 } 629 630 /// ditto 631 BetaVariable!T betaVar(T)(in T a, in T b) 632 if (isFloatingPoint!T) 633 { 634 return typeof(return)(a, b); 635 } 636 637 /// ditto 638 alias betaVariable = betaVar; 639 640 /// 641 @nogc nothrow @safe version(mir_random_test) unittest 642 { 643 auto rv = betaVariable(2.0, 5); 644 static assert(isRandomVariable!(typeof(rv))); 645 import mir.random.engine; 646 auto x = rv(rne); 647 } 648 649 @nogc nothrow @safe version(mir_random_test) unittest 650 { 651 import mir.random.engine; 652 Random* gen = threadLocalPtr!Random; 653 auto rv = BetaVariable!double(2, 5); 654 auto x = rv(gen); 655 } 656 657 /++ 658 $(WIKI_D Chi-squared). 659 +/ 660 struct ChiSquaredVariable(T) 661 if (isFloatingPoint!T) 662 { 663 /// 664 enum isRandomVariable = true; 665 666 private T _shape = 1; 667 668 /// 669 this(size_t k) 670 { 671 _shape = T(k) / 2; 672 } 673 674 /// 675 T opCall(G)(scope ref G gen) 676 if (isSaturatedRandomEngine!G) 677 { 678 return GammaVariable!T(_shape, 2)(gen); 679 } 680 /// ditto 681 T opCall(G)(scope G* gen) 682 if (isSaturatedRandomEngine!G) 683 { 684 return opCall!(G)(*gen); 685 } 686 687 /// 688 enum T min = 0; 689 /// 690 enum T max = T.infinity; 691 } 692 693 /// ditto 694 ChiSquaredVariable!T chiSquared(T = double)(size_t k) 695 if(isFloatingPoint!T) 696 { 697 return typeof(return)(k); 698 } 699 700 /// 701 @nogc nothrow @safe version(mir_random_test) unittest 702 { 703 auto rv = chiSquared(3); 704 static assert(isRandomVariable!(typeof(rv))); 705 import mir.random.engine; 706 auto x = rv(rne); 707 } 708 709 /// 710 @nogc nothrow @safe version(mir_random_test) unittest 711 { 712 import mir.random.engine; 713 Random* gen = threadLocalPtr!Random; 714 auto rv = ChiSquaredVariable!double(3); 715 auto x = rv(gen); 716 } 717 718 /++ 719 $(WIKI_D F). 720 +/ 721 struct FisherFVariable(T) 722 if (isFloatingPoint!T) 723 { 724 /// 725 enum isRandomVariable = true; 726 727 private T _d1 = 1, _d2 = 1; 728 729 /// 730 this(T d1, T d2) 731 { 732 _d1 = d1; 733 _d2 = d2; 734 } 735 736 /// 737 T opCall(G)(scope ref G gen) 738 if (isSaturatedRandomEngine!G) 739 { 740 auto xv = GammaVariable!T(_d1 * 0.5f, 1); 741 auto yv = GammaVariable!T(_d2 * 0.5f, 1); 742 auto x = xv(gen); 743 auto y = yv(gen); 744 x *= _d1; 745 y *= _d2; 746 return x / y; 747 } 748 /// ditto 749 T opCall(G)(scope G* gen) 750 if (isSaturatedRandomEngine!G) 751 { 752 return opCall!(G)(*gen); 753 } 754 755 /// 756 enum T min = 0; 757 /// 758 enum T max = T.infinity; 759 } 760 761 /// ditto 762 FisherFVariable!T fisherFVar(T)(in T d1, in T d2) 763 if (isFloatingPoint!T) 764 { 765 return typeof(return)(d1, d2); 766 } 767 768 /// ditto 769 alias fisherFVariable = fisherFVar; 770 771 /// 772 @nogc nothrow @safe version(mir_random_test) unittest 773 { 774 auto rv = fisherFVar(3.0, 4); 775 static assert(isRandomVariable!(typeof(rv))); 776 import mir.random.engine; 777 auto x = rv(rne); 778 } 779 780 @nogc nothrow @safe version(mir_random_test) unittest 781 { 782 import mir.random.engine; 783 Random* gen = threadLocalPtr!Random; 784 auto rv = FisherFVariable!double(3, 4); 785 auto x = rv(gen); 786 } 787 788 /++ 789 $(WIKI_D Student's_t). 790 +/ 791 struct StudentTVariable(T) 792 if (isFloatingPoint!T) 793 { 794 /// 795 enum isRandomVariable = true; 796 797 private NormalVariable!T _nv; 798 private T _nu = 1; 799 800 /// 801 this(T nu) 802 { 803 _nu = nu; 804 } 805 806 /// 807 T opCall(G)(scope ref G gen) 808 if (isSaturatedRandomEngine!G) 809 { 810 auto x = _nv(gen); 811 auto y = _nu / GammaVariable!T(_nu * 0.5f, 2)(gen); 812 if(y < T.infinity) 813 x *= y.sqrt; 814 return x; 815 } 816 /// ditto 817 T opCall(G)(scope G* gen) 818 if (isSaturatedRandomEngine!G) 819 { 820 return opCall!(G)(*gen); 821 } 822 823 /// 824 enum T min = -T.infinity; 825 /// 826 enum T max = T.infinity; 827 } 828 829 /// ditto 830 StudentTVariable!T studentTVar(T)(in T nu) 831 if(isFloatingPoint!T) 832 { 833 return typeof(return)(nu); 834 } 835 836 /// ditto 837 alias studentTVariable = studentTVar; 838 839 /// 840 @nogc nothrow @safe version(mir_random_test) unittest 841 { 842 auto rv = studentTVar(10.0); 843 static assert(isRandomVariable!(typeof(rv))); 844 import mir.random.engine; 845 auto x = rv(rne); 846 } 847 848 /// 849 @nogc nothrow @safe version(mir_random_test) unittest 850 { 851 import mir.random.engine; 852 Random* gen = threadLocalPtr!Random; 853 auto rv = StudentTVariable!double(10); 854 auto x = rv(gen); 855 } 856 857 private T hypot01(T)(const T x, const T y) 858 { 859 // Scale x and y to avoid underflow and overflow. 860 // If one is huge and the other tiny, return the larger. 861 // If both are huge, avoid overflow by scaling by 1/sqrt(real.max/2). 862 // If both are tiny, avoid underflow by scaling by sqrt(real.min_normal*real.epsilon). 863 864 enum T SQRTMIN = 0.5f * sqrt(T.min_normal); // This is a power of 2. 865 enum T SQRTMAX = 1 / SQRTMIN; // 2^^((max_exp)/2) = nextUp(sqrt(T.max)) 866 867 static assert(2*(SQRTMAX/2)*(SQRTMAX/2) <= T.max); 868 869 // Proves that sqrt(T.max) ~~ 0.5/sqrt(T.min_normal) 870 static assert(T.min_normal*T.max > 2 && T.min_normal*T.max <= 4); 871 872 T u = fabs(x); 873 T v = fabs(y); 874 if (u < v) 875 { 876 auto t = v; 877 v = u; 878 u = t; 879 } 880 881 if (u <= SQRTMIN) 882 { 883 // hypot (tiny, tiny) -- avoid underflow 884 // This is only necessary to avoid setting the underflow 885 // flag. 886 u *= SQRTMAX / T.epsilon; 887 v *= SQRTMAX / T.epsilon; 888 return sumSquares(u, v).sqrt * (SQRTMIN * T.epsilon); 889 } 890 891 if (u * T.epsilon > v) 892 { 893 // hypot (huge, tiny) = huge 894 return u; 895 } 896 897 // both are in the normal range 898 return sumSquares(u, v).sqrt; 899 } 900 901 /++ 902 $(WIKI_D Normal). 903 Returns: `X ~ N(μ, σ)` 904 +/ 905 struct NormalVariable(T) 906 if (isFloatingPoint!T) 907 { 908 /// 909 enum isRandomVariable = true; 910 911 private T location = 0; 912 private T scale = 1; 913 private T y = 0; 914 private bool hot; 915 916 /// 917 this(T location, T scale) 918 { 919 this.location = location; 920 this.scale = scale; 921 } 922 923 this(this) 924 { 925 hot = false; 926 } 927 928 /// 929 T opCall(G)(scope ref G gen) 930 if (isSaturatedRandomEngine!G) 931 { 932 T x = void; 933 if (hot) 934 { 935 hot = false; 936 x = y; 937 } 938 else 939 { 940 T u = void; 941 T v = void; 942 T s = void; 943 do 944 { 945 u = gen.rand!T; 946 v = gen.rand!T; 947 s = hypot01(u, v); 948 } 949 while (s > 1 || s == 0); 950 s = 2 * sqrt(-log(s)) / s; 951 y = v * s; 952 x = u * s; 953 hot = true; 954 } 955 return fmuladd(x, scale, location); 956 } 957 /// ditto 958 T opCall(G)(scope G* gen) 959 if (isSaturatedRandomEngine!G) 960 { 961 return opCall!(G)(*gen); 962 } 963 964 /// 965 enum T min = -T.infinity; 966 /// 967 enum T max = T.infinity; 968 } 969 970 971 /// ditto 972 NormalVariable!T normalVar(T = double)(in T location = 0.0, in T scale = 1) 973 if(isFloatingPoint!T) 974 { 975 return typeof(return)(location, scale); 976 } 977 978 /// ditto 979 alias normalVariable = normalVar; 980 981 /// 982 @nogc nothrow @safe version(mir_random_test) unittest 983 { 984 auto rv = normalVar; 985 static assert(isRandomVariable!(typeof(rv))); 986 import mir.random.engine; 987 auto x = rv(rne); 988 } 989 990 /// 991 @nogc nothrow @safe version(mir_random_test) unittest 992 { 993 import mir.random.engine; 994 Random* gen = threadLocalPtr!Random; 995 auto rv = NormalVariable!double(0, 1); 996 auto x = rv(gen); 997 } 998 999 /++ 1000 $(WIKI_D Log-normal). 1001 +/ 1002 struct LogNormalVariable(T) 1003 if (isFloatingPoint!T) 1004 { 1005 /// 1006 enum isRandomVariable = true; 1007 1008 private NormalVariable!T _nv; 1009 1010 /++ 1011 Params: 1012 normalLocation = location of associated normal 1013 normalScale = scale of associated normal 1014 +/ 1015 this(T normalLocation, T normalScale) 1016 { 1017 _nv = NormalVariable!T(normalLocation, normalScale); 1018 } 1019 1020 /// 1021 T opCall(G)(scope ref G gen) 1022 if (isSaturatedRandomEngine!G) 1023 { 1024 return exp(_nv(gen)); 1025 } 1026 /// ditto 1027 T opCall(G)(scope G* gen) 1028 if (isSaturatedRandomEngine!G) 1029 { 1030 return opCall!(G)(*gen); 1031 } 1032 1033 /// 1034 enum T min = 0; 1035 /// 1036 enum T max = T.infinity; 1037 } 1038 1039 /// ditto 1040 LogNormalVariable!T logNormalVar(T = double)(in T normalLocation = 0.0, in T normalScale = 1) 1041 if(isFloatingPoint!T) 1042 { 1043 return typeof(return)(normalLocation, normalScale); 1044 } 1045 1046 /// ditto 1047 alias logNormalVariable = logNormalVar; 1048 1049 /// 1050 @nogc nothrow @safe version(mir_random_test) unittest 1051 { 1052 auto rv = logNormalVar; 1053 static assert(isRandomVariable!(typeof(rv))); 1054 import mir.random.engine; 1055 auto x = rv(rne); 1056 } 1057 1058 /// 1059 @nogc nothrow @safe version(mir_random_test) unittest 1060 { 1061 import mir.random.engine; 1062 Random* gen = threadLocalPtr!Random; 1063 auto rv = LogNormalVariable!double(0, 1); 1064 auto x = rv(gen); 1065 } 1066 1067 /++ 1068 $(WIKI_D Cauchy). 1069 Returns: `X ~ Cauchy(x, γ)` 1070 +/ 1071 struct CauchyVariable(T) 1072 if (isFloatingPoint!T) 1073 { 1074 /// 1075 enum isRandomVariable = true; 1076 1077 private T location = 0; 1078 private T scale = 1; 1079 1080 /// 1081 this(T location, T scale) 1082 { 1083 this.location = location; 1084 this.scale = scale; 1085 } 1086 1087 /// 1088 T opCall(G)(scope ref G gen) 1089 if (isSaturatedRandomEngine!G) 1090 { 1091 T u = void; 1092 T v = void; 1093 T x = void; 1094 do 1095 { 1096 u = gen.rand!T; 1097 v = gen.rand!T; 1098 } 1099 while (sumSquares(u, v) > 1 || !(fabs(x = u / v) < T.infinity)); 1100 return fmuladd(x, scale, location); 1101 } 1102 /// ditto 1103 T opCall(G)(scope G* gen) 1104 if (isSaturatedRandomEngine!G) 1105 { 1106 return opCall!(G)(*gen); 1107 } 1108 1109 /// 1110 enum T min = -T.infinity; 1111 /// 1112 enum T max = T.infinity; 1113 } 1114 1115 1116 /// ditto 1117 CauchyVariable!T cauchyVar(T = double)(in T location = 0.0, in T scale = 1) 1118 if(isFloatingPoint!T) 1119 { 1120 return typeof(return)(location, scale); 1121 } 1122 1123 /// ditto 1124 alias cauchyVariable = cauchyVar; 1125 1126 /// 1127 @nogc nothrow @safe version(mir_random_test) unittest 1128 { 1129 auto rv = cauchyVar; 1130 static assert(isRandomVariable!(typeof(rv))); 1131 import mir.random.engine; 1132 auto x = rv(rne); 1133 } 1134 1135 /// 1136 @nogc nothrow @safe version(mir_random_test) unittest 1137 { 1138 import mir.random.engine; 1139 Random* gen = threadLocalPtr!Random; 1140 auto rv = CauchyVariable!double(0, 1); 1141 auto x = rv(gen); 1142 } 1143 1144 /++ 1145 $(WIKI_D2 Generalized_extreme_value, Extreme value). 1146 +/ 1147 struct ExtremeValueVariable(T) 1148 if (isFloatingPoint!T) 1149 { 1150 /// 1151 enum isRandomVariable = true; 1152 1153 private T location = 0; 1154 private T scale = 1; 1155 1156 /// 1157 this(T location, T scale) 1158 { 1159 this.location = location; 1160 this.scale = scale * -T(LN2); 1161 } 1162 1163 /// 1164 T opCall(G)(scope ref G gen) 1165 if (isSaturatedRandomEngine!G) 1166 { 1167 return fmuladd(log2(gen.randExponential2!T * T(LN2)), scale, location); 1168 } 1169 /// ditto 1170 T opCall(G)(scope G* gen) 1171 if (isSaturatedRandomEngine!G) 1172 { 1173 return opCall!(G)(*gen); 1174 } 1175 1176 /// 1177 enum T min = -T.infinity; 1178 /// 1179 enum T max = T.infinity; 1180 } 1181 1182 /// ditto 1183 ExtremeValueVariable!T extremeValueVar(T = double)(in T location = 0.0, in T scale = 1) 1184 if(isFloatingPoint!T) 1185 { 1186 return typeof(return)(location, scale); 1187 } 1188 1189 /// ditto 1190 alias extremeValueVariable = extremeValueVar; 1191 1192 /// 1193 @nogc nothrow @safe version(mir_random_test) unittest 1194 { 1195 auto rv = extremeValueVar; 1196 static assert(isRandomVariable!(typeof(rv))); 1197 import mir.random.engine; 1198 auto x = rv(rne); 1199 } 1200 1201 /// 1202 @nogc nothrow @safe version(mir_random_test) unittest 1203 { 1204 import mir.random.engine; 1205 Random* gen = threadLocalPtr!Random; 1206 auto rv = ExtremeValueVariable!double(0, 1); 1207 auto x = rv(gen); 1208 } 1209 1210 /++ 1211 $(WIKI_D Bernoulli). 1212 +/ 1213 struct BernoulliVariable(T) 1214 if (isFloatingPoint!T) 1215 { 1216 /// 1217 enum isRandomVariable = true; 1218 1219 private T p = 0; 1220 1221 /++ 1222 Params: 1223 p = `true` probability 1224 +/ 1225 this(T p) 1226 { 1227 assert(0 <= p && p <= 1); 1228 this.p = p; 1229 } 1230 1231 /// 1232 bool opCall(RNG)(scope ref RNG gen) 1233 if (isSaturatedRandomEngine!RNG) 1234 { 1235 return gen.rand!T.fabs < p; 1236 } 1237 /// ditto 1238 bool opCall(RNG)(scope RNG* gen) 1239 if (isSaturatedRandomEngine!RNG) 1240 { 1241 return opCall!(RNG)(*gen); 1242 } 1243 1244 /// 1245 enum bool min = 0; 1246 /// 1247 enum bool max = 1; 1248 } 1249 1250 /// ditto 1251 BernoulliVariable!T bernoulliVar(T)(in T p) 1252 if(isFloatingPoint!T) 1253 { 1254 return typeof(return)(p); 1255 } 1256 1257 /// ditto 1258 alias bernoulliVariable = bernoulliVar; 1259 1260 /// 1261 @nogc nothrow @safe version(mir_random_test) unittest 1262 { 1263 import mir.random.engine; 1264 auto rv = bernoulliVar(0.7); 1265 static assert(isRandomVariable!(typeof(rv))); 1266 int[2] hist; 1267 foreach(_; 0..1000) 1268 hist[rv(rne)]++; 1269 //import std.stdio; 1270 //writeln(hist); 1271 } 1272 1273 /// 1274 @nogc nothrow @safe version(mir_random_test) unittest 1275 { 1276 import mir.random.engine; 1277 Random* gen = threadLocalPtr!Random; 1278 auto rv = BernoulliVariable!double(0.7); 1279 int[2] hist; 1280 foreach(_; 0..10) 1281 hist[rv(gen)]++; 1282 } 1283 1284 /++ 1285 $(WIKI_D Bernoulli). A fast specialization for `p := 1/2`. 1286 +/ 1287 struct Bernoulli2Variable 1288 { 1289 /// 1290 enum isRandomVariable = true; 1291 private size_t payload; 1292 private size_t mask; 1293 1294 this(this) 1295 { 1296 payload = mask = 0; 1297 } 1298 1299 /// 1300 bool opCall(RNG)(scope ref RNG gen) 1301 if (isSaturatedRandomEngine!RNG) 1302 { 1303 if(mask == 0) 1304 { 1305 mask = sizediff_t.min; 1306 payload = gen.rand!size_t; 1307 } 1308 bool ret = (payload & mask) != 0; 1309 mask >>>= 1; 1310 return ret; 1311 } 1312 /// ditto 1313 bool opCall(RNG)(scope RNG* gen) 1314 if (isSaturatedRandomEngine!RNG) 1315 { 1316 return opCall!(RNG)(*gen); 1317 } 1318 1319 /// 1320 enum bool min = 0; 1321 /// 1322 enum bool max = 1; 1323 } 1324 1325 /// ditto 1326 Bernoulli2Variable bernoulli2Var()() 1327 { 1328 return typeof(return).init; 1329 } 1330 1331 /// ditto 1332 alias bernoulli2Variable = bernoulli2Var; 1333 1334 /// 1335 @nogc nothrow @safe version(mir_random_test) unittest 1336 { 1337 import mir.random.engine; 1338 auto rv = bernoulli2Var; 1339 static assert(isRandomVariable!(typeof(rv))); 1340 int[2] hist; 1341 foreach(_; 0..1000) 1342 hist[rv(rne)]++; 1343 } 1344 1345 /// 1346 @nogc nothrow @safe version(mir_random_test) unittest 1347 { 1348 import mir.random.engine; 1349 Random* gen = threadLocalPtr!Random; 1350 auto rv = Bernoulli2Variable.init; 1351 int[2] hist; 1352 foreach(_; 0..10) 1353 hist[rv(gen)]++; 1354 } 1355 1356 /++ 1357 $(WIKI_D Geometric). 1358 +/ 1359 struct GeometricVariable(T) 1360 if (isFloatingPoint!T) 1361 { 1362 /// 1363 enum isRandomVariable = true; 1364 1365 private T scale = 1; 1366 1367 /++ 1368 Params: 1369 p = probability 1370 success = p is success probability if `true` and failure probability otherwise. 1371 +/ 1372 this(T p, bool success) 1373 { 1374 assert(0 <= p && p <= 1); 1375 scale = -1 / log2(success ? 1 - p : p); 1376 } 1377 1378 /// 1379 ulong opCall(RNG)(scope ref RNG gen) 1380 if (isSaturatedRandomEngine!RNG) 1381 { 1382 auto ret = gen.randExponential2!T * scale; 1383 return ret < ulong.max ? cast(ulong)ret : ulong.max; 1384 } 1385 /// ditto 1386 ulong opCall(RNG)(scope RNG* gen) 1387 if (isSaturatedRandomEngine!RNG) 1388 { 1389 return opCall!(RNG)(*gen); 1390 } 1391 1392 /// 1393 enum ulong min = 0; 1394 /// 1395 enum ulong max = ulong.max; 1396 } 1397 1398 1399 /// ditto 1400 GeometricVariable!T geometricVar(T)(in T p, bool success = true) 1401 if(isFloatingPoint!T) 1402 { 1403 return typeof(return)(p, success); 1404 } 1405 1406 /// ditto 1407 alias geometricVariable = geometricVar; 1408 1409 /// 1410 nothrow @safe version(mir_random_test) unittest 1411 { 1412 import mir.random.engine; 1413 auto rv = geometricVar(0.1); 1414 static assert(isRandomVariable!(typeof(rv))); 1415 size_t[ulong] hist; 1416 foreach(_; 0..1000) 1417 hist[rv(rne)]++; 1418 //import std.stdio; 1419 //foreach(i; 0..100) 1420 // if(auto count = i in hist) 1421 // write(*count, ", "); 1422 // else 1423 // write("0, "); 1424 //writeln(); 1425 } 1426 1427 /// 1428 nothrow @safe version(mir_random_test) unittest 1429 { 1430 import mir.random.engine; 1431 Random* gen = threadLocalPtr!Random; 1432 auto rv = GeometricVariable!double(0.1, true); 1433 size_t[ulong] hist; 1434 foreach(_; 0..10) 1435 hist[rv(gen)]++; 1436 } 1437 1438 private T _mLogFactorial(T)(ulong k) 1439 { 1440 ulong r = 1; 1441 foreach(i; 2 .. k + 1) 1442 r *= k; 1443 return -log(T(k)); 1444 } 1445 1446 private enum mLogFactorial(T) = [ 1447 _mLogFactorial!T(0), 1448 _mLogFactorial!T(1), 1449 _mLogFactorial!T(2), 1450 _mLogFactorial!T(3), 1451 _mLogFactorial!T(4), 1452 _mLogFactorial!T(5), 1453 _mLogFactorial!T(6), 1454 _mLogFactorial!T(7), 1455 _mLogFactorial!T(8), 1456 _mLogFactorial!T(9), 1457 ]; 1458 1459 /++ 1460 $(WIKI_D Poisson). 1461 +/ 1462 struct PoissonVariable(T) 1463 if (isFloatingPoint!T) 1464 { 1465 /// 1466 enum isRandomVariable = true; 1467 1468 import mir.math.constant : E; 1469 private T rate = 1; 1470 private T temp1 = 1 / E; 1471 T a = void, b = void; 1472 1473 /++ 1474 Params: 1475 rate = rate 1476 +/ 1477 this(T rate) 1478 { 1479 this.rate = rate; 1480 if (rate >= 10) 1481 { 1482 temp1 = rate.log; 1483 b = fmuladd(sqrt(rate), T(2.53), T(0.931)); 1484 a = fmuladd(b, T(0.02483), T(-0.059)); 1485 } 1486 else 1487 temp1 = exp(-rate); 1488 } 1489 1490 /// 1491 ulong opCall(RNG)(scope ref RNG gen) 1492 if (isSaturatedRandomEngine!RNG) 1493 { 1494 import core.stdc.tgmath: lgamma; 1495 if (rate >= 10) for (;;) 1496 { 1497 T u = gen.rand!T(-1); 1498 T v = gen.rand!T.fabs; 1499 T us = 0.5f - fabs(u); 1500 T kr = fmuladd(2 * a / us + b, u, rate) + T(0.43); 1501 if (!(kr >= 0)) 1502 continue; 1503 long k = cast(long)kr; 1504 if (us >= T(0.07) && v <= T(0.9277) - T(3.6224) / (b - 2)) 1505 return k; 1506 if (k < 0 || us < T(0.013) && v > us) 1507 continue; 1508 if (log(v * (T(1.1239) + T(1.1328) / (b - T(3.4))) / (a / (us * us) + b)) 1509 <= k * temp1 - rate - lgamma(T(k + 1))) 1510 return k; 1511 } 1512 T prod = 1.0; 1513 for(size_t x = 0; ; x++) 1514 { 1515 prod *= gen.rand!T.fabs; 1516 if (prod <= temp1) 1517 return x; 1518 } 1519 } 1520 /// ditto 1521 ulong opCall(G)(scope G* gen) 1522 if (isSaturatedRandomEngine!G) 1523 { 1524 return opCall!(G)(*gen); 1525 } 1526 1527 /// 1528 enum ulong min = 0; 1529 /// 1530 enum ulong max = ulong.max; 1531 } 1532 1533 /// ditto 1534 PoissonVariable!T poissonVar(T = double)(in T rate = 1.0) 1535 if(isFloatingPoint!T) 1536 { 1537 return typeof(return)(rate); 1538 } 1539 1540 /// ditto 1541 alias poissonVariable = poissonVar; 1542 1543 /// 1544 nothrow @safe version(mir_random_test) unittest 1545 { 1546 import mir.random; 1547 auto rv = poissonVar; 1548 static assert(isRandomVariable!(typeof(rv))); 1549 size_t[ulong] hist; 1550 foreach(_; 0..1000) 1551 hist[rv(rne)]++; 1552 //import std.stdio; 1553 //foreach(i; 0..100) 1554 // if(auto count = i in hist) 1555 // write(*count, ", "); 1556 // else 1557 // write("0, "); 1558 //writeln(); 1559 } 1560 1561 /// 1562 nothrow @safe version(mir_random_test) unittest 1563 { 1564 import mir.random.engine; 1565 Random* gen = threadLocalPtr!Random; 1566 auto rv = PoissonVariable!double(10); 1567 size_t[ulong] hist; 1568 foreach(_; 0..10) 1569 hist[rv(gen)]++; 1570 } 1571 1572 /++ 1573 $(WIKI_D Negative_binomial). 1574 +/ 1575 struct NegativeBinomialVariable(T) 1576 if (isFloatingPoint!T) 1577 { 1578 /// 1579 enum isRandomVariable = true; 1580 1581 size_t r; 1582 T p; 1583 1584 /++ 1585 Params: 1586 r = r > 0; number of failures until the experiment is stopped 1587 p = p ∈ (0,1); success probability in each experiment 1588 +/ 1589 this(size_t r, T p) 1590 { 1591 this.r = r; 1592 this.p = p; 1593 } 1594 1595 /// 1596 ulong opCall(RNG)(scope ref RNG gen) 1597 if (isSaturatedRandomEngine!RNG) 1598 { 1599 if (r <= 21 * p) 1600 { 1601 auto bv = BernoulliVariable!T(p); 1602 size_t s, f; 1603 do (bv(gen) ? s : f)++; 1604 while (s < r); 1605 return f; 1606 } 1607 return PoissonVariable!T(GammaVariable!T(r, (1 - p) / p)(gen))(gen); 1608 } 1609 /// ditto 1610 ulong opCall(RNG)(scope RNG* gen) 1611 if (isSaturatedRandomEngine!RNG) 1612 { 1613 return opCall!(RNG)(*gen); 1614 } 1615 1616 /// 1617 enum ulong min = 0; 1618 /// 1619 enum ulong max = ulong.max; 1620 } 1621 1622 /// ditto 1623 NegativeBinomialVariable!T negativeBinomialVar(T)(size_t r, in T p) 1624 if(isFloatingPoint!T) 1625 { 1626 return typeof(return)(r, p); 1627 } 1628 1629 /// ditto 1630 alias negativeBinomialVariable = negativeBinomialVar; 1631 1632 /// 1633 nothrow @safe version(mir_random_test) unittest 1634 { 1635 import mir.random; 1636 auto rv = negativeBinomialVar(30, 0.3); 1637 static assert(isRandomVariable!(typeof(rv))); 1638 size_t[ulong] hist; 1639 foreach(_; 0..1000) 1640 hist[rv(rne)]++; 1641 //import std.stdio; 1642 //foreach(i; 0..100) 1643 // if(auto count = i in hist) 1644 // write(*count, ", "); 1645 // else 1646 // write("0, "); 1647 //writeln(); 1648 } 1649 1650 /// 1651 nothrow @safe version(mir_random_test) unittest 1652 { 1653 import mir.random.engine; 1654 Random* gen = threadLocalPtr!Random; 1655 auto rv = NegativeBinomialVariable!double(30, 0.3); 1656 size_t[ulong] hist; 1657 foreach(_; 0..10) 1658 hist[rv(gen)]++; 1659 } 1660 1661 /++ 1662 $(WIKI_D Binomial). 1663 +/ 1664 struct BinomialVariable(T) 1665 if (isFloatingPoint!T) 1666 { 1667 /// 1668 enum isRandomVariable = true; 1669 1670 import core.stdc.tgmath: lgamma; 1671 size_t n = void; 1672 T np = void; 1673 T q = void; 1674 T qn = void; 1675 T r = void; 1676 T g = void; 1677 T b = void; 1678 T a = void; 1679 T c = void; 1680 T vr = void; 1681 T alpha = void; 1682 T lpq = void; 1683 T fm = void; 1684 T h = void; 1685 bool swap = void; 1686 @disable this(); 1687 1688 /++ 1689 Params: 1690 n = n > 0; number of trials 1691 p = p ∈ [0,1]; success probability in each trial 1692 +/ 1693 this(size_t n, T p) 1694 { 1695 this.n = n; 1696 if(p <= 0.5f) 1697 { 1698 this.q = 1 - p; 1699 swap = false; 1700 } 1701 else 1702 { 1703 this.q = p; 1704 swap = true; 1705 p = 1 - p; 1706 } 1707 np = p * n; 1708 if (np >= 10) 1709 { 1710 auto spq = sqrt(np * q); 1711 1712 b = fmuladd(spq, 2.53f, 1.15f); 1713 a = fmuladd(p, 0.01f, fmuladd(b, 0.0248f, -0.0873f)); 1714 c = fmuladd(p, n, 0.5f); 1715 vr = 0.92f - 4.2f/ b; 1716 alpha = (2.83f+5.1f / b) * spq; 1717 lpq = log(p / q); 1718 fm = floor((n + 1) * p); 1719 h = lgamma(fm + 1) + lgamma(n - fm - 1); 1720 } 1721 else 1722 { 1723 qn = pow (q, n); 1724 r = p / q; 1725 g = r * (n + 1); 1726 } 1727 } 1728 1729 /// 1730 size_t opCall(RNG)(scope ref RNG gen) 1731 if (isSaturatedRandomEngine!RNG) 1732 { 1733 T kr = void; 1734 if (np >= 10) for (;;) 1735 { 1736 T u = gen.rand!T(-1); 1737 T us = 0.5 - u.fabs; 1738 kr = floor (fmuladd(2 * a / us + b, u, c)); 1739 if (kr < 0) 1740 continue; 1741 if (kr > n) 1742 continue; 1743 T v = gen.rand!T(); 1744 if (us >= 0.07f && v <= vr) 1745 break; 1746 v = log (v * alpha / (a / (us * us) + b)); 1747 if (v <= (h - lgamma(kr + 1) - lgamma(n - kr + 1) + (kr - fm) * lpq)) 1748 break; 1749 } 1750 else 1751 { 1752 enum max_k = 110; 1753 1754 T f = qn; 1755 T u = gen.rand!T.fabs; 1756 T kmax = n > max_k ? max_k : n; 1757 kr = 0; 1758 do 1759 { 1760 if (u < f) 1761 break; 1762 u -= f; 1763 kr++; 1764 f *= (g / kr - r); 1765 } 1766 while (kr <= kmax); 1767 } 1768 auto ret = cast(typeof(return)) kr; 1769 return swap ? n - ret : ret; 1770 } 1771 /// 1772 size_t opCall(RNG)(scope RNG* gen) 1773 if (isSaturatedRandomEngine!RNG) 1774 { 1775 return opCall!(RNG)(*gen); 1776 } 1777 1778 /// 1779 enum size_t min = 0; 1780 /// 1781 size_t max() @property { return n; }; 1782 } 1783 1784 /// ditto 1785 BinomialVariable!T binomialVar(T)(size_t r, in T p) 1786 if(isFloatingPoint!T) 1787 { 1788 return typeof(return)(r, p); 1789 } 1790 1791 /// ditto 1792 alias binomialVariable = binomialVar; 1793 1794 /// 1795 nothrow @safe version(mir_random_test) unittest 1796 { 1797 import mir.random; 1798 auto rv = binomialVar(20, 0.5); 1799 static assert(isRandomVariable!(typeof(rv))); 1800 int[] hist = new int[rv.max + 1]; 1801 auto cnt = 1000; 1802 foreach(_; 0..cnt) 1803 hist[rv(rne)]++; 1804 //import std.stdio; 1805 //foreach(n, e; hist) 1806 // writefln("p(x = %s) = %s", n, double(e) / cnt); 1807 } 1808 1809 /// 1810 nothrow @safe version(mir_random_test) unittest 1811 { 1812 import mir.random.engine; 1813 Random* gen = threadLocalPtr!Random; 1814 auto rv = BinomialVariable!double(20, 0.5); 1815 int[] hist = new int[rv.max + 1]; 1816 auto cnt = 10; 1817 foreach(_; 0..cnt) 1818 hist[rv(gen)]++; 1819 } 1820 1821 /++ 1822 _Discrete distribution sampler that draws random values from a _discrete 1823 distribution given an array of the respective probability density points (weights). 1824 +/ 1825 struct DiscreteVariable(T) 1826 if (isNumeric!T) 1827 { 1828 /// 1829 enum isRandomVariable = true; 1830 1831 private T[] cdf; 1832 1833 /++ 1834 `DiscreteVariable` constructor computes cumulative density points 1835 in place without memory allocation. 1836 1837 Params: 1838 weights = density points 1839 cumulative = optional flag indiciates if `weights` are already cumulative 1840 +/ 1841 this(T[] weights, bool cumulative) 1842 { 1843 if(!cumulative) 1844 { 1845 static if (isFloatingPoint!T && is(typeof({import mir.math.sum; }))) 1846 { 1847 import mir.math.sum; 1848 Summator!(T, Summation.kb2) s = 0; 1849 foreach(ref e; weights) 1850 { 1851 s += e; 1852 e = s.sum; 1853 } 1854 } 1855 else 1856 { 1857 T s = 0; 1858 foreach(ref e; weights) 1859 { 1860 s += e; 1861 e = s; 1862 } 1863 } 1864 } 1865 cdf = weights; 1866 } 1867 1868 /++ 1869 Samples a value from the discrete distribution using a custom random generator. 1870 Complexity: 1871 `O(log n)` where `n` is the number of `weights`. 1872 +/ 1873 size_t opCall(RNG)(scope ref RNG gen) 1874 if (isSaturatedRandomEngine!RNG) 1875 { 1876 import std.range : assumeSorted; 1877 static if (isFloatingPoint!T) 1878 T v = gen.rand!T.fabs * cdf[$-1]; 1879 else 1880 T v = gen.randIndex!(Unsigned!T)(cdf[$-1]); 1881 return cdf.length - cdf.assumeSorted!"a < b".upperBound(v).length; 1882 } 1883 /// ditto 1884 size_t opCall(RNG)(scope RNG* gen) 1885 if (isSaturatedRandomEngine!RNG) 1886 { 1887 return opCall!(RNG)(*gen); 1888 } 1889 1890 /// 1891 enum size_t min = 0; 1892 /// 1893 size_t max() @property { return cdf.length - 1; } 1894 } 1895 1896 /// ditto 1897 DiscreteVariable!T discreteVar(T)(T[] weights, bool cumulative = false) 1898 if (isNumeric!T) 1899 { 1900 return typeof(return)(weights, cumulative); 1901 } 1902 1903 /// ditto 1904 alias discreteVariable = discreteVar; 1905 1906 /// 1907 nothrow @safe version(mir_random_test) unittest 1908 { 1909 import mir.random.engine; 1910 auto gen = Random(unpredictableSeed); 1911 // 10%, 20%, 20%, 40%, 10% 1912 auto weights = [10.0, 20, 20, 40, 10]; 1913 auto ds = discreteVar(weights); 1914 static assert(isRandomVariable!(typeof(ds))); 1915 1916 // weight is changed to cumulative sums 1917 assert(weights == [10, 30, 50, 90, 100]); 1918 1919 // sample from the discrete distribution 1920 auto obs = new uint[weights.length]; 1921 1922 foreach (i; 0..1000) 1923 obs[ds(gen)]++; 1924 1925 //import std.stdio; 1926 //writeln(obs); 1927 } 1928 1929 /// Cumulative 1930 nothrow @safe version(mir_random_test) unittest 1931 { 1932 import mir.random.engine; 1933 1934 auto gen = Random(unpredictableSeed); 1935 1936 auto cumulative = [10.0, 30, 40, 90, 120]; 1937 auto ds = discreteVar(cumulative, true); 1938 1939 assert(cumulative == [10.0, 30, 40, 90, 120]); 1940 1941 // sample from the discrete distribution 1942 auto obs = new uint[cumulative.length]; 1943 foreach (i; 0..1000) 1944 obs[ds(gen)]++; 1945 } 1946 1947 /// 1948 nothrow @safe version(mir_random_test) unittest 1949 { 1950 import mir.random.engine; 1951 auto gen = Random(unpredictableSeed); 1952 // 10%, 20%, 20%, 40%, 10% 1953 auto weights = [10.0, 20, 20, 40, 10]; 1954 auto ds = discreteVar(weights); 1955 1956 // weight is changed to cumulative sums 1957 assert(weights == [10, 30, 50, 90, 100]); 1958 1959 // sample from the discrete distribution 1960 auto obs = new uint[weights.length]; 1961 foreach (i; 0..1000) 1962 obs[ds(gen)]++; 1963 1964 //import std.stdio; 1965 //writeln(obs); 1966 //[999, 1956, 2063, 3960, 1022] 1967 } 1968 1969 // test with cumulative probs 1970 nothrow @safe version(mir_random_test) unittest 1971 { 1972 auto gen = Random(unpredictableSeed); 1973 1974 // 10%, 20%, 20%, 40%, 10% 1975 auto weights = [0.1, 0.3, 0.5, 0.9, 1]; 1976 auto ds = DiscreteVariable!double(weights, true); 1977 1978 auto obs = new uint[weights.length]; 1979 foreach (i; 0..1000) 1980 obs[ds(gen)]++; 1981 1982 1983 //assert(obs == [1030, 1964, 1968, 4087, 951]); 1984 } 1985 1986 // test with cumulative count 1987 nothrow @safe version(mir_random_test) unittest 1988 { 1989 auto gen = Random(unpredictableSeed); 1990 1991 // 1, 2, 1 1992 auto weights = [1, 2, 1]; 1993 auto ds = discreteVar(weights); 1994 1995 auto obs = new uint[weights.length]; 1996 foreach (i; 0..1000) 1997 obs[ds(gen)]++; 1998 1999 //assert(obs == [2536, 4963, 2501]); 2000 } 2001 2002 // test with zero probabilities 2003 nothrow @safe version(mir_random_test) unittest 2004 { 2005 auto gen = Random(unpredictableSeed); 2006 2007 // 0, 1, 2, 0, 1 2008 auto weights = [0, 1, 3, 3, 4]; 2009 auto ds = DiscreteVariable!int(weights, true); 2010 2011 auto obs = new uint[weights.length]; 2012 foreach (i; 0..1000) 2013 obs[ds(gen)]++; 2014 2015 assert(obs[3] == 0); 2016 } 2017 2018 nothrow @safe version(mir_random_test) unittest 2019 { 2020 import mir.random.engine; 2021 Random* gen = threadLocalPtr!Random; 2022 auto cumulative = [10.0, 30, 40, 90, 120]; 2023 auto ds = DiscreteVariable!double(cumulative, true); 2024 2025 assert(cumulative == [10.0, 30, 40, 90, 120]); 2026 2027 // sample from the discrete distribution 2028 auto obs = new uint[cumulative.length]; 2029 foreach (i; 0..1000) 2030 obs[ds(gen)]++; 2031 } 2032 2033 /++ 2034 Piecewise constant variable. 2035 +/ 2036 struct PiecewiseConstantVariable(T, W = T) 2037 if (isNumeric!T && isNumeric!W) 2038 { 2039 /// 2040 enum isRandomVariable = true; 2041 2042 private DiscreteVariable!W dv; 2043 private T[] intervals; 2044 2045 /++ 2046 `PiecewiseConstantVariable` constructor computes cumulative density points 2047 in place without memory allocation. 2048 Params: 2049 intervals = strictly increasing sequence of interval bounds. 2050 weights = density points 2051 cumulative = optional flag indicates if `weights` are already cumulative 2052 +/ 2053 this(T[] intervals, W[] weights, bool cumulative) 2054 { 2055 assert(weights.length); 2056 assert(intervals.length == weights.length + 1); 2057 dv = DiscreteVariable!W(weights, cumulative); 2058 this.intervals = intervals; 2059 } 2060 2061 /++ 2062 Complexity: 2063 `O(log n)` where `n` is the number of `weights`. 2064 +/ 2065 T opCall(RNG)(scope ref RNG gen) 2066 if (isSaturatedRandomEngine!RNG) 2067 { 2068 size_t index = dv(gen); 2069 return UniformVariable!T(intervals[index], intervals[index + 1])(gen); 2070 } 2071 /// ditto 2072 T opCall(RNG)(scope RNG* gen) 2073 if (isSaturatedRandomEngine!RNG) 2074 { 2075 return opCall!(RNG)(*gen); 2076 } 2077 2078 /// 2079 T min() @property { return intervals[0]; } 2080 /// 2081 T max() @property { return intervals[$-1].nextDown; } 2082 } 2083 2084 /// ditto 2085 PiecewiseConstantVariable!(T, W) piecewiseConstantVar(T, W)(T[] intervals, W[] weights, bool cumulative = false) 2086 if (isNumeric!T && isNumeric!W) 2087 { 2088 return typeof(return)(intervals, weights, cumulative); 2089 } 2090 2091 /// ditto 2092 alias piecewiseConstantVariable = piecewiseConstantVar; 2093 2094 /// 2095 nothrow @safe version(mir_random_test) unittest 2096 { 2097 import mir.random.engine; 2098 // 50% of the time, generate a random number between 0 and 1 2099 // 50% of the time, generate a random number between 10 and 15 2100 double[] i = [0, 1, 10, 15]; 2101 double[] w = [1, 0, 1]; 2102 auto pcv = piecewiseConstantVar(i, w); 2103 static assert(isRandomVariable!(typeof(pcv))); 2104 assert(w == [1, 1, 2]); 2105 2106 int[int] hist; 2107 foreach(_; 0 .. 10000) 2108 ++hist[cast(int)pcv(rne)]; 2109 2110 //import std.stdio; 2111 //import mir.ndslice.topology: repeat; 2112 //foreach(j; 0..cast(int)i[$-1]) 2113 // if(auto count = j in hist) 2114 // writefln("%2s %s", j, '*'.repeat(*count / 100)); 2115 2116 //////// output example ///////// 2117 /+ 2118 0 ************************************************** 2119 10 ********* 2120 11 ********* 2121 12 ********** 2122 13 ********* 2123 14 ********** 2124 +/ 2125 } 2126 2127 /// 2128 nothrow @safe version(mir_random_test) unittest 2129 { 2130 import mir.random.engine; 2131 Random* gen = threadLocalPtr!Random; 2132 // 50% of the time, generate a random number between 0 and 1 2133 // 50% of the time, generate a random number between 10 and 15 2134 double[] i = [0, 1, 10, 15]; 2135 double[] w = [1, 0, 1]; 2136 auto pcv = piecewiseConstantVar(i, w); 2137 assert(w == [1, 1, 2]); 2138 2139 int[int] hist; 2140 foreach(_; 0 .. 10) 2141 ++hist[cast(int)pcv(gen)]; 2142 } 2143 2144 /++ 2145 Piecewise constant variable. 2146 +/ 2147 struct PiecewiseLinearVariable(T) 2148 if (isFloatingPoint!T) 2149 { 2150 /// 2151 enum isRandomVariable = true; 2152 2153 private DiscreteVariable!T dv; 2154 private T[] points; 2155 private T[] weights; 2156 2157 /++ 2158 Params: 2159 points = strictly increasing sequence of interval bounds. 2160 weights = density points 2161 areas = user allocated uninitialized array 2162 Constrains: 2163 `points.length == weights.length` $(BR) 2164 `areas.length > 0` $(BR) 2165 `areas.length + 1 == weights.length` 2166 +/ 2167 this(T[] points, T[] weights, T[] areas) 2168 in { 2169 assert(points.length == weights.length); 2170 assert(areas.length); 2171 assert(areas.length + 1 == weights.length); 2172 } 2173 do { 2174 foreach(size_t i; 0 .. areas.length) 2175 areas[i] = (weights[i + 1] + weights[i]) * (points[i + 1] - points[i]); 2176 dv = discreteVar(areas); 2177 this.points = points; 2178 this.weights = weights; 2179 } 2180 2181 /++ 2182 Complexity: 2183 `O(log n)` where `n` is the number of `weights`. 2184 +/ 2185 T opCall(RNG)(scope ref RNG gen) 2186 if (isSaturatedRandomEngine!RNG) 2187 { 2188 size_t index = dv(gen); 2189 T w0 = weights[index + 0]; 2190 T w1 = weights[index + 1]; 2191 T b0 = points [index + 0]; 2192 T b1 = points [index + 1]; 2193 T ret = gen.rand!T.fabs; 2194 T z = fmin(w0, w1) / fmax(w0, w1); 2195 if(!(z > gen.rand!T(-1).fabs * (1 + z))) 2196 ret = ret.sqrt; 2197 ret *= b1 - b0; 2198 if(w0 > w1) 2199 ret = b1 - ret; 2200 else 2201 ret = ret + b0; 2202 if(!(ret < b1)) 2203 ret = b1.nextDown; 2204 return ret; 2205 } 2206 /// ditto 2207 T opCall(RNG)(scope RNG* gen) 2208 if (isSaturatedRandomEngine!RNG) 2209 { 2210 return opCall!(RNG)(*gen); 2211 } 2212 2213 /// 2214 T min() @property { return points[0]; } 2215 /// 2216 T max() @property { return points[$-1].nextDown; } 2217 } 2218 2219 /// ditto 2220 PiecewiseLinearVariable!T piecewiseLinearVar(T)(T[] points, T[] weights, T[] areas) 2221 if (isFloatingPoint!T) 2222 { 2223 return typeof(return)(points, weights, areas); 2224 } 2225 2226 /// ditto 2227 alias piecewiseLinearVariable = piecewiseLinearVar; 2228 2229 /// 2230 nothrow @safe version(mir_random_test) unittest 2231 { 2232 import mir.random.engine; 2233 auto gen = Random(unpredictableSeed); 2234 // increase the probability from 0 to 5 2235 // remain flat from 5 to 10 2236 // decrease from 10 to 15 at the same rate 2237 double[] i = [0, 5, 10, 15]; 2238 double[] w = [0, 1, 1, 0]; 2239 auto pcv = piecewiseLinearVar(i, w, new double[w.length - 1]); 2240 static assert(isRandomVariable!(typeof(pcv))); 2241 2242 int[int] hist; 2243 foreach(_; 0 .. 10000) 2244 ++hist[cast(int)pcv(gen)]; 2245 2246 //import std.stdio; 2247 //import mir.ndslice.topology: repeat; 2248 //foreach(j; 0..cast(int)i[$-1]+1) 2249 // if(auto count = j in hist) 2250 // writefln("%2s %s", j, '*'.repeat(*count / 100)); 2251 2252 //////// output example ///////// 2253 /+ 2254 0 * 2255 1 ** 2256 2 ***** 2257 3 ******* 2258 4 ******** 2259 5 ********** 2260 6 ********* 2261 7 ********* 2262 8 ********** 2263 9 ********* 2264 10 ********* 2265 11 ******* 2266 12 **** 2267 13 ** 2268 14 * 2269 +/ 2270 } 2271 2272 /// 2273 nothrow @safe version(mir_random_test) unittest 2274 { 2275 import mir.random.engine; 2276 Random* gen = threadLocalPtr!Random; 2277 // increase the probability from 0 to 5 2278 // remain flat from 5 to 10 2279 // decrease from 10 to 15 at the same rate 2280 double[] i = [0, 5, 10, 15]; 2281 double[] w = [0, 1, 1, 0]; 2282 auto pcv = PiecewiseLinearVariable!double(i, w, new double[w.length - 1]); 2283 2284 int[int] hist; 2285 foreach(_; 0 .. 10) 2286 ++hist[cast(int)pcv(gen)]; 2287 }