Rev 1221 | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
656 | werner | 1 | #include <cmath> |
2 | #include <stdexcept> |
||
3 | #include <sstream> |
||
4 | #include <iostream> |
||
5 | #include "SimpleRNG.h" |
||
6 | |||
7 | #define PI 3.1415926535897932384626433832795 |
||
8 | |||
9 | SimpleRNG::SimpleRNG() |
||
10 | { |
||
11 | // These values are not magical, just the default values Marsaglia used. |
||
12 | // Any unit should work. |
||
13 | m_u = 521288629; |
||
14 | m_v = 362436069; |
||
15 | } |
||
16 | |||
17 | void SimpleRNG::SetState(unsigned int u, unsigned int v) |
||
18 | { |
||
19 | m_u = u; |
||
20 | m_v = v; |
||
21 | } |
||
22 | |||
23 | void SimpleRNG::GetState(unsigned int& u, unsigned int& v) |
||
24 | { |
||
25 | u = m_u; |
||
26 | v = m_v; |
||
27 | } |
||
28 | |||
29 | double SimpleRNG::GetUniform(unsigned int& u, unsigned int& v) |
||
30 | { |
||
31 | // 0 <= u <= 2^32 |
||
32 | unsigned int z = GetUint(u, v); |
||
33 | // The magic number is 1/(2^32 + 1) and so result is positive and less than 1. |
||
34 | return z*2.328306435996595e-10; |
||
35 | } |
||
36 | |||
37 | unsigned int SimpleRNG::GetUint(unsigned int& u, unsigned int& v) |
||
38 | { |
||
39 | v = 36969*(v & 65535) + (v >> 16); |
||
40 | u = 18000*(u & 65535) + (u >> 16); |
||
41 | return (v << 16) + u; |
||
42 | } |
||
43 | |||
44 | double SimpleRNG::GetUniform() |
||
45 | { |
||
46 | return GetUniform(m_u, m_v); |
||
47 | } |
||
48 | |||
49 | unsigned int SimpleRNG::GetUint() |
||
50 | { |
||
51 | return GetUint(m_u, m_v); |
||
52 | } |
||
53 | |||
54 | // Get normal (Gaussian) random sample with specified mean and standard deviation |
||
55 | double SimpleRNG::GetNormal(double mean = 0.0, double standardDeviation = 1.0) |
||
56 | { |
||
57 | if (standardDeviation <= 0.0) |
||
58 | { |
||
59 | std::stringstream os; |
||
60 | os << "Standard deviation must be positive." << "\n" |
||
61 | << "Received standard deviation " << standardDeviation; |
||
62 | throw std::invalid_argument( os.str() ); |
||
63 | } |
||
64 | // Use Box-Muller algorithm |
||
65 | double u1 = GetUniform(); |
||
66 | double u2 = GetUniform(); |
||
67 | double r = sqrt( -2.0*log(u1) ); |
||
68 | double theta = 2.0*PI*u2; |
||
69 | return mean + standardDeviation*r*sin(theta); |
||
70 | } |
||
71 | |||
72 | // Get exponential random sample with specified mean |
||
73 | double SimpleRNG::GetExponential(double mean = 1.0) |
||
74 | { |
||
75 | if (mean <= 0.0) |
||
76 | { |
||
77 | std::stringstream os; |
||
78 | os << "Exponential mean must be positive." << "\n" |
||
79 | << "Received standard deviation " << mean; |
||
80 | throw std::invalid_argument( os.str() ); |
||
81 | } |
||
82 | return -mean*log( GetUniform() ); |
||
83 | } |
||
84 | |||
85 | double SimpleRNG::GetGamma(double shape, double scale) |
||
86 | { |
||
87 | // Implementation based on "A Simple Method for Generating Gamma Variables" |
||
88 | // by George Marsaglia and Wai Wan Tsang. ACM Transactions on Mathematical Software |
||
89 | // Vol 26, No 3, September 2000, pages 363-372. |
||
90 | |||
91 | double d, c, x, xsquared, v, u; |
||
92 | |||
93 | if (shape >= 1.0) |
||
94 | { |
||
95 | d = shape - 1.0/3.0; |
||
96 | c = 1.0/sqrt(9.0*d); |
||
97 | for (;;) |
||
98 | { |
||
99 | do |
||
100 | { |
||
101 | x = GetNormal(); |
||
102 | v = 1.0 + c*x; |
||
103 | } |
||
104 | while (v <= 0.0); |
||
105 | v = v*v*v; |
||
106 | u = GetUniform(); |
||
107 | xsquared = x*x; |
||
108 | if (u < 1.0 -.0331*xsquared*xsquared || log(u) < 0.5*xsquared + d*(1.0 - v + log(v))) |
||
109 | return scale*d*v; |
||
110 | } |
||
111 | } |
||
112 | else if (shape <= 0.0) |
||
113 | { |
||
114 | std::stringstream os; |
||
115 | os << "Shape parameter must be positive." << "\n" |
||
116 | << "Received shape parameter " << shape; |
||
117 | throw std::invalid_argument( os.str() ); |
||
118 | } |
||
119 | else |
||
120 | { |
||
121 | double g = GetGamma(shape+1.0, 1.0); |
||
122 | double w = GetUniform(); |
||
123 | return scale*g*pow(w, 1.0/shape); |
||
124 | } |
||
125 | } |
||
126 | |||
127 | double SimpleRNG::GetChiSquare(double degreesOfFreedom) |
||
128 | { |
||
129 | // A chi squared distribution with n degrees of freedom |
||
130 | // is a gamma distribution with shape n/2 and scale 2. |
||
131 | return GetGamma(0.5 * degreesOfFreedom, 2.0); |
||
132 | } |
||
133 | |||
134 | double SimpleRNG::GetInverseGamma(double shape, double scale) |
||
135 | { |
||
136 | // If X is gamma(shape, scale) then |
||
137 | // 1/Y is inverse gamma(shape, 1/scale) |
||
138 | return 1.0 / GetGamma(shape, 1.0 / scale); |
||
139 | } |
||
140 | |||
141 | double SimpleRNG::GetWeibull(double shape, double scale) |
||
142 | { |
||
143 | if (shape <= 0.0 || scale <= 0.0) |
||
144 | { |
||
145 | std::stringstream os; |
||
146 | os << "Shape and scale parameters must be positive." << "\n" |
||
147 | << "Received shape " << shape << " and scale " << scale; |
||
148 | throw std::invalid_argument( os.str() ); |
||
149 | } |
||
150 | return scale * pow(-log(GetUniform()), 1.0 / shape); |
||
151 | } |
||
152 | |||
153 | double SimpleRNG::GetCauchy(double median, double scale) |
||
154 | { |
||
155 | if (scale <= 0) |
||
156 | { |
||
157 | std::stringstream os; |
||
158 | os << "Shape parameter must be positive." << "\n" |
||
159 | << "Received shape parameter " << scale; |
||
160 | throw std::invalid_argument( os.str() ); |
||
161 | } |
||
162 | |||
163 | double p = GetUniform(); |
||
164 | |||
165 | // Apply inverse of the Cauchy distribution function to a uniform |
||
166 | return median + scale*tan(PI*(p - 0.5)); |
||
167 | } |
||
168 | |||
169 | double SimpleRNG::GetStudentT(double degreesOfFreedom) |
||
170 | { |
||
171 | if (degreesOfFreedom <= 0) |
||
172 | { |
||
173 | std::stringstream os; |
||
174 | os << "Degrees of freedom must be positive." << "\n" |
||
175 | << "Received shape parameter " << degreesOfFreedom; |
||
176 | throw std::invalid_argument( os.str() ); |
||
177 | } |
||
178 | |||
179 | // See Seminumerical Algorithms by Knuth |
||
180 | double y1 = GetNormal(); |
||
181 | double y2 = GetChiSquare(degreesOfFreedom); |
||
182 | return y1 / sqrt(y2 / degreesOfFreedom); |
||
183 | } |
||
184 | |||
185 | // The Laplace distribution is also known as the double exponential distribution. |
||
186 | double SimpleRNG::GetLaplace(double mean, double scale) |
||
187 | { |
||
188 | double u = GetUniform(); |
||
189 | return (u < 0.5) ? |
||
190 | mean + scale*log(2.0*u) : |
||
191 | mean - scale*log(2*(1-u)); |
||
192 | } |
||
193 | |||
194 | double SimpleRNG::GetLogNormal(double mu, double sigma) |
||
195 | { |
||
196 | return exp(GetNormal(mu, sigma)); |
||
197 | } |
||
198 | |||
199 | double SimpleRNG::GetBeta(double a, double b) |
||
200 | { |
||
201 | if (a <= 0.0 || b <= 0.0) |
||
202 | { |
||
203 | std::stringstream os; |
||
204 | os << "Beta parameters must be positive." << "\n" |
||
205 | << "Received parameters " << a << " and " << b; |
||
206 | throw std::invalid_argument( os.str() ); |
||
207 | } |
||
208 | |||
209 | // There are more efficient methods for generating beta samples. |
||
210 | // However such methods are a little more efficient and much more complicated. |
||
211 | // For an explanation of why the following method works, see |
||
212 | // http://www.johndcook.com/distribution_chart.html#gamma_beta |
||
213 | |||
214 | double u = GetGamma(a, 1.0); |
||
215 | double v = GetGamma(b, 1.0); |
||
216 | return u / (u + v); |
||
217 | } |
||
218 | |||
219 | int SimpleRNG::GetPoisson(double lambda) |
||
220 | { |
||
221 | return (lambda < 30.0) ? PoissonSmall(lambda) : PoissonLarge(lambda); |
||
222 | } |
||
223 | |||
224 | int SimpleRNG::PoissonSmall(double lambda) |
||
225 | { |
||
226 | // Algorithm due to Donald Knuth, 1969. |
||
227 | double p = 1.0, L = exp(-lambda); |
||
228 | int k = 0; |
||
229 | do |
||
230 | { |
||
231 | k++; |
||
232 | p *= GetUniform(); |
||
233 | } |
||
234 | while (p > L); |
||
235 | return k - 1; |
||
236 | } |
||
237 | |||
238 | int SimpleRNG::PoissonLarge(double lambda) |
||
239 | { |
||
240 | // "Rejection method PA" from "The Computer Generation of Poisson Random Variables" by A. C. Atkinson |
||
241 | // Journal of the Royal Statistical Society Series C (Applied Statistics) Vol. 28, No. 1. (1979) |
||
242 | // The article is on pages 29-35. The algorithm given here is on page 32. |
||
243 | |||
244 | double c = 0.767 - 3.36/lambda; |
||
245 | double beta = PI/sqrt(3.0*lambda); |
||
246 | double alpha = beta*lambda; |
||
247 | double k = log(c) - lambda - log(beta); |
||
248 | |||
249 | for(;;) |
||
250 | { |
||
251 | double u = GetUniform(); |
||
252 | double x = (alpha - log((1.0 - u)/u))/beta; |
||
253 | int n = (int) floor(x + 0.5); |
||
254 | if (n < 0) |
||
255 | continue; |
||
256 | double v = GetUniform(); |
||
257 | double y = alpha - beta*x; |
||
258 | double temp = 1.0 + exp(y); |
||
259 | double lhs = y + log(v/(temp*temp)); |
||
260 | double rhs = k + n*log(lambda) - LogFactorial(n); |
||
261 | if (lhs <= rhs) |
||
262 | return n; |
||
263 | } |
||
264 | } |
||
265 | |||
266 | double SimpleRNG::LogFactorial(int n) |
||
267 | { |
||
268 | |||
269 | if (n < 0) |
||
270 | { |
||
271 | std::stringstream os; |
||
272 | os << "Invalid input argument (" << n |
||
273 | << "); may not be negative" |
||
274 |