Coverage for /usr/lib/python3/dist-packages/scipy/stats/_rvs_sampling.py: 11%

27 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-14 15:55 +0200

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, `numpy.random.Generator`, 

26 `numpy.random.RandomState`}, optional 

27 

28 If `seed` is None (or `np.random`), the `numpy.random.RandomState` 

29 singleton is used. 

30 If `seed` is an int, a new ``RandomState`` instance is used, 

31 seeded with `seed`. 

32 If `seed` is already a ``Generator`` or ``RandomState`` instance then 

33 that instance is used. 

34 

35 Returns 

36 ------- 

37 rvs : ndarray 

38 The random variates distributed according to the probability 

39 distribution defined by the pdf. 

40 

41 Notes 

42 ----- 

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

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

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

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

47 

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

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

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

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

52 

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

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

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

56 

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

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

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

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

61 verified. 

62 

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

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

65 that is proportional to the probability density function by dropping 

66 unnecessary normalization factors. 

67 

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

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

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

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

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

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

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

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

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

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

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

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

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

81 

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

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

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

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

86 

87 References 

88 ---------- 

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

90 Springer-Verlag, 1986. 

91 

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

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

94 

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

96 Variables Using the Ratio of Uniform Deviates", 

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

98 

99 Examples 

100 -------- 

101 >>> import numpy as np 

102 >>> from scipy import stats 

103 >>> rng = np.random.default_rng() 

104 

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

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

107 normalization factor of the density. 

108 

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

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

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

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

113 ... random_state=rng) 

114 

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

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

117 

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

119 0.250634764150542 

120 

121 The exponential distribution provides another example where the bounding 

122 rectangle can be determined explicitly. 

123 

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

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

126 ... random_state=rng) 

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

128 0.21121052054580314 

129 

130 """ 

131 if vmin >= vmax: 

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

133 

134 if umax <= 0: 

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

136 

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

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

139 

140 # start sampling using ratio of uniforms method 

141 rng = check_random_state(random_state) 

142 x = np.zeros(N) 

143 simulated, i = 0, 1 

144 

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

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

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

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

149 # which is of order 10e-22 

150 while simulated < N: 

151 k = N - simulated 

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

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

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

155 # apply rejection method 

156 rvs = v1 / u1 + c 

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

158 num_accept = np.sum(accept) 

159 if num_accept > 0: 

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

161 simulated += num_accept 

162 

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

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

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

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

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

168 raise RuntimeError(msg) 

169 i += 1 

170 

171 return np.reshape(x, size1d)