24
24
negative exponential
25
25
gamma
26
26
beta
27
+ binomial
27
28
pareto
28
29
Weibull
29
30
49
50
from math import log as _log , exp as _exp , pi as _pi , e as _e , ceil as _ceil
50
51
from math import sqrt as _sqrt , acos as _acos , cos as _cos , sin as _sin
51
52
from math import tau as TWOPI , floor as _floor , isfinite as _isfinite
53
+ from math import lgamma as _lgamma , fabs as _fabs
52
54
from os import urandom as _urandom
53
55
from _collections_abc import Sequence as _Sequence
54
56
from operator import index as _index
68
70
"Random" ,
69
71
"SystemRandom" ,
70
72
"betavariate" ,
73
+ "binomialvariate" ,
71
74
"choice" ,
72
75
"choices" ,
73
76
"expovariate" ,
@@ -725,6 +728,91 @@ def betavariate(self, alpha, beta):
725
728
return y / (y + self .gammavariate (beta , 1.0 ))
726
729
return 0.0
727
730
731
+
732
+ def binomialvariate (self , n = 1 , p = 0.5 ):
733
+ """Binomial random variable.
734
+
735
+ Gives the number of successes for *n* independent trials
736
+ with the probability of success in each trial being *p*:
737
+
738
+ sum(random() < p for i in range(n))
739
+
740
+ Returns an integer in the range: 0 <= X <= n
741
+
742
+ """
743
+ # Error check inputs and handle edge cases
744
+ if n < 0 :
745
+ raise ValueError ("n must be non-negative" )
746
+ if p <= 0.0 or p >= 1.0 :
747
+ if p == 0.0 :
748
+ return 0
749
+ if p == 1.0 :
750
+ return n
751
+ raise ValueError ("p must be in the range 0.0 <= p <= 1.0" )
752
+
753
+ random = self .random
754
+
755
+ # Fast path for a common case
756
+ if n == 1 :
757
+ return _index (random () < p )
758
+
759
+ # Exploit symmetry to establish: p <= 0.5
760
+ if p > 0.5 :
761
+ return n - self .binomialvariate (n , 1.0 - p )
762
+
763
+ if n * p < 10.0 :
764
+ # BG: Geometric method by Devroye with running time of O(np).
765
+ # https://dl.acm.org/doi/pdf/10.1145/42372.42381
766
+ x = y = 0
767
+ c = _log (1.0 - p )
768
+ if not c :
769
+ return x
770
+ while True :
771
+ y += _floor (_log (random ()) / c ) + 1
772
+ if y > n :
773
+ return x
774
+ x += 1
775
+
776
+ # BTRS: Transformed rejection with squeeze method by Wolfgang Hörmann
777
+ # https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.47.8407&rep=rep1&type=pdf
778
+ assert n * p >= 10.0 and p <= 0.5
779
+ setup_complete = False
780
+
781
+ spq = _sqrt (n * p * (1.0 - p )) # Standard deviation of the distribution
782
+ b = 1.15 + 2.53 * spq
783
+ a = - 0.0873 + 0.0248 * b + 0.01 * p
784
+ c = n * p + 0.5
785
+ vr = 0.92 - 4.2 / b
786
+
787
+ while True :
788
+
789
+ u = random ()
790
+ v = random ()
791
+ u -= 0.5
792
+ us = 0.5 - _fabs (u )
793
+ k = _floor ((2.0 * a / us + b ) * u + c )
794
+ if k < 0 or k > n :
795
+ continue
796
+
797
+ # The early-out "squeeze" test substantially reduces
798
+ # the number of acceptance condition evaluations.
799
+ if us >= 0.07 and v <= vr :
800
+ return k
801
+
802
+ # Acceptance-rejection test.
803
+ # Note, the original paper errorneously omits the call to log(v)
804
+ # when comparing to the log of the rescaled binomial distribution.
805
+ if not setup_complete :
806
+ alpha = (2.83 + 5.1 / b ) * spq
807
+ lpq = _log (p / (1.0 - p ))
808
+ m = _floor ((n + 1 ) * p ) # Mode of the distribution
809
+ h = _lgamma (m + 1 ) + _lgamma (n - m + 1 )
810
+ setup_complete = True # Only needs to be done once
811
+ v *= alpha / (a / (us * us ) + b )
812
+ if _log (v ) <= h - _lgamma (k + 1 ) - _lgamma (n - k + 1 ) + (k - m ) * lpq :
813
+ return k
814
+
815
+
728
816
def paretovariate (self , alpha ):
729
817
"""Pareto distribution. alpha is the shape parameter."""
730
818
# Jain, pg. 495
@@ -810,6 +898,7 @@ def _notimplemented(self, *args, **kwds):
810
898
gammavariate = _inst .gammavariate
811
899
gauss = _inst .gauss
812
900
betavariate = _inst .betavariate
901
+ binomialvariate = _inst .binomialvariate
813
902
paretovariate = _inst .paretovariate
814
903
weibullvariate = _inst .weibullvariate
815
904
getstate = _inst .getstate
@@ -834,15 +923,17 @@ def _test_generator(n, func, args):
834
923
low = min (data )
835
924
high = max (data )
836
925
837
- print (f'{ t1 - t0 :.3f} sec, { n } times { func .__name__ } ' )
926
+ print (f'{ t1 - t0 :.3f} sec, { n } times { func .__name__ } { args !r } ' )
838
927
print ('avg %g, stddev %g, min %g, max %g\n ' % (xbar , sigma , low , high ))
839
928
840
929
841
- def _test (N = 2000 ):
930
+ def _test (N = 10_000 ):
842
931
_test_generator (N , random , ())
843
932
_test_generator (N , normalvariate , (0.0 , 1.0 ))
844
933
_test_generator (N , lognormvariate , (0.0 , 1.0 ))
845
934
_test_generator (N , vonmisesvariate , (0.0 , 1.0 ))
935
+ _test_generator (N , binomialvariate , (15 , 0.60 ))
936
+ _test_generator (N , binomialvariate , (100 , 0.75 ))
846
937
_test_generator (N , gammavariate , (0.01 , 1.0 ))
847
938
_test_generator (N , gammavariate , (0.1 , 1.0 ))
848
939
_test_generator (N , gammavariate , (0.1 , 2.0 ))
0 commit comments