Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1import numpy as np 

2from scipy._lib._util import check_random_state 

3 

4 

5def rvs_ratio_uniforms(pdf, umax, vmin, vmax, size=1, c=0, random_state=None): 

6 """ 

7 Generate random samples from a probability density function using the 

8 ratio-of-uniforms method. 

9 

10 Parameters 

11 ---------- 

12 pdf : callable 

13 A function with signature `pdf(x)` that is proportional to the 

14 probability density function of the distribution. 

15 umax : float 

16 The upper bound of the bounding rectangle in the u-direction. 

17 vmin : float 

18 The lower bound of the bounding rectangle in the v-direction. 

19 vmax : float 

20 The upper bound of the bounding rectangle in the v-direction. 

21 size : int or tuple of ints, optional 

22 Defining number of random variates (default is 1). 

23 c : float, optional. 

24 Shift parameter of ratio-of-uniforms method, see Notes. Default is 0. 

25 random_state : {None, int, `~np.random.RandomState`, `~np.random.Generator`}, optional 

26 If `random_state` is `None` the `~np.random.RandomState` singleton is 

27 used. 

28 If `random_state` is an int, a new ``RandomState`` instance is used, 

29 seeded with random_state. 

30 If `random_state` is already a ``RandomState`` or ``Generator`` 

31 instance, then that object is used. 

32 Default is None. 

33 

34 Returns 

35 ------- 

36 rvs : ndarray 

37 The random variates distributed according to the probability 

38 distribution defined by the pdf. 

39 

40 Notes 

41 ----- 

42 Given a univariate probability density function `pdf` and a constant `c`, 

43 define the set ``A = {(u, v) : 0 < u <= sqrt(pdf(v/u + c))}``. 

44 If `(U, V)` is a random vector uniformly distributed over `A`, 

45 then `V/U + c` follows a distribution according to `pdf`. 

46 

47 The above result (see [1]_, [2]_) can be used to sample random variables 

48 using only the pdf, i.e. no inversion of the cdf is required. Typical 

49 choices of `c` are zero or the mode of `pdf`. The set `A` is a subset of 

50 the rectangle ``R = [0, umax] x [vmin, vmax]`` where 

51 

52 - ``umax = sup sqrt(pdf(x))`` 

53 - ``vmin = inf (x - c) sqrt(pdf(x))`` 

54 - ``vmax = sup (x - c) sqrt(pdf(x))`` 

55 

56 In particular, these values are finite if `pdf` is bounded and 

57 ``x**2 * pdf(x)`` is bounded (i.e. subquadratic tails). 

58 One can generate `(U, V)` uniformly on `R` and return 

59 `V/U + c` if `(U, V)` are also in `A` which can be directly 

60 verified. 

61 

62 The algorithm is not changed if one replaces `pdf` by k * `pdf` for any 

63 constant k > 0. Thus, it is often convenient to work with a function 

64 that is proportional to the probability density function by dropping 

65 unneccessary normalization factors. 

66 

67 Intuitively, the method works well if `A` fills up most of the 

68 enclosing rectangle such that the probability is high that `(U, V)` 

69 lies in `A` whenever it lies in `R` as the number of required 

70 iterations becomes too large otherwise. To be more precise, note that 

71 the expected number of iterations to draw `(U, V)` uniformly 

72 distributed on `R` such that `(U, V)` is also in `A` is given by 

73 the ratio ``area(R) / area(A) = 2 * umax * (vmax - vmin) / area(pdf)``, 

74 where `area(pdf)` is the integral of `pdf` (which is equal to one if the 

75 probability density function is used but can take on other values if a 

76 function proportional to the density is used). The equality holds since 

77 the area of `A` is equal to 0.5 * area(pdf) (Theorem 7.1 in [1]_). 

78 If the sampling fails to generate a single random variate after 50000 

79 iterations (i.e. not a single draw is in `A`), an exception is raised. 

80 

81 If the bounding rectangle is not correctly specified (i.e. if it does not 

82 contain `A`), the algorithm samples from a distribution different from 

83 the one given by `pdf`. It is therefore recommended to perform a 

84 test such as `~scipy.stats.kstest` as a check. 

85 

86 References 

87 ---------- 

88 .. [1] L. Devroye, "Non-Uniform Random Variate Generation", 

89 Springer-Verlag, 1986. 

90 

91 .. [2] W. Hoermann and J. Leydold, "Generating generalized inverse Gaussian 

92 random variates", Statistics and Computing, 24(4), p. 547--557, 2014. 

93 

94 .. [3] A.J. Kinderman and J.F. Monahan, "Computer Generation of Random 

95 Variables Using the Ratio of Uniform Deviates", 

96 ACM Transactions on Mathematical Software, 3(3), p. 257--260, 1977. 

97 

98 Examples 

99 -------- 

100 >>> from scipy import stats 

101 

102 Simulate normally distributed random variables. It is easy to compute the 

103 bounding rectangle explicitly in that case. For simplicity, we drop the 

104 normalization factor of the density. 

105 

106 >>> f = lambda x: np.exp(-x**2 / 2) 

107 >>> v_bound = np.sqrt(f(np.sqrt(2))) * np.sqrt(2) 

108 >>> umax, vmin, vmax = np.sqrt(f(0)), -v_bound, v_bound 

109 >>> np.random.seed(12345) 

110 >>> rvs = stats.rvs_ratio_uniforms(f, umax, vmin, vmax, size=2500) 

111 

112 The K-S test confirms that the random variates are indeed normally 

113 distributed (normality is not rejected at 5% significance level): 

114 

115 >>> stats.kstest(rvs, 'norm')[1] 

116 0.33783681428365553 

117 

118 The exponential distribution provides another example where the bounding 

119 rectangle can be determined explicitly. 

120 

121 >>> np.random.seed(12345) 

122 >>> rvs = stats.rvs_ratio_uniforms(lambda x: np.exp(-x), umax=1, 

123 ... vmin=0, vmax=2*np.exp(-1), size=1000) 

124 >>> stats.kstest(rvs, 'expon')[1] 

125 0.928454552559516 

126 

127 """ 

128 

129 if vmin >= vmax: 

130 raise ValueError("vmin must be smaller than vmax.") 

131 

132 if umax <= 0: 

133 raise ValueError("umax must be positive.") 

134 

135 size1d = tuple(np.atleast_1d(size)) 

136 N = np.prod(size1d) # number of rvs needed, reshape upon return 

137 

138 # start sampling using ratio of uniforms method 

139 rng = check_random_state(random_state) 

140 x = np.zeros(N) 

141 simulated, i = 0, 1 

142 

143 # loop until N rvs have been generated: expected runtime is finite. 

144 # to avoid infinite loop, raise exception if not a single rv has been 

145 # generated after 50000 tries. even if the expected numer of iterations 

146 # is 1000, the probability of this event is (1-1/1000)**50000 

147 # which is of order 10e-22 

148 while simulated < N: 

149 k = N - simulated 

150 # simulate uniform rvs on [0, umax] and [vmin, vmax] 

151 u1 = umax * rng.uniform(size=k) 

152 v1 = rng.uniform(vmin, vmax, size=k) 

153 # apply rejection method 

154 rvs = v1 / u1 + c 

155 accept = (u1**2 <= pdf(rvs)) 

156 num_accept = np.sum(accept) 

157 if num_accept > 0: 

158 x[simulated:(simulated + num_accept)] = rvs[accept] 

159 simulated += num_accept 

160 

161 if (simulated == 0) and (i*N >= 50000): 

162 msg = ("Not a single random variate could be generated in {} " 

163 "attempts. The ratio of uniforms method does not appear " 

164 "to work for the provided parameters. Please check the " 

165 "pdf and the bounds.".format(i*N)) 

166 raise RuntimeError(msg) 

167 i += 1 

168 

169 return np.reshape(x, size1d)