The OpenD Programming Language

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 }