Subversion Repositories public iLand

Rev

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