Rev 1221 |
Blame |
Compare with Previous |
Last modification |
View Log
| RSS feed
#include <cmath>
#include <stdexcept>
#include <sstream>
#include <iostream>
#include "SimpleRNG.h"
#define PI 3.1415926535897932384626433832795
SimpleRNG::SimpleRNG()
{
// These values are not magical, just the default values Marsaglia used.
// Any unit should work.
m_u = 521288629;
m_v = 362436069;
}
void SimpleRNG::SetState(unsigned int u, unsigned int v)
{
m_u = u;
m_v = v;
}
void SimpleRNG::GetState(unsigned int& u, unsigned int& v)
{
u = m_u;
v = m_v;
}
double SimpleRNG::GetUniform(unsigned int& u, unsigned int& v)
{
// 0 <= u <= 2^32
unsigned int z = GetUint(u, v);
// The magic number is 1/(2^32 + 1) and so result is positive and less than 1.
return z*2.328306435996595e-10;
}
unsigned int SimpleRNG::GetUint(unsigned int& u, unsigned int& v)
{
v = 36969*(v & 65535) + (v >> 16);
u = 18000*(u & 65535) + (u >> 16);
return (v << 16) + u;
}
double SimpleRNG::GetUniform()
{
return GetUniform(m_u, m_v);
}
unsigned int SimpleRNG::GetUint()
{
return GetUint(m_u, m_v);
}
// Get normal (Gaussian) random sample with specified mean and standard deviation
double SimpleRNG::GetNormal(double mean = 0.0, double standardDeviation = 1.0)
{
if (standardDeviation <= 0.0)
{
std::stringstream os;
os << "Standard deviation must be positive." << "\n"
<< "Received standard deviation " << standardDeviation;
throw std::invalid_argument( os.str() );
}
// Use Box-Muller algorithm
double u1 = GetUniform();
double u2 = GetUniform();
double r = sqrt( -2.0*log(u1) );
double theta = 2.0*PI*u2;
return mean + standardDeviation*r*sin(theta);
}
// Get exponential random sample with specified mean
double SimpleRNG::GetExponential(double mean = 1.0)
{
if (mean <= 0.0)
{
std::stringstream os;
os << "Exponential mean must be positive." << "\n"
<< "Received standard deviation " << mean;
throw std::invalid_argument( os.str() );
}
return -mean*log( GetUniform() );
}
double SimpleRNG::GetGamma(double shape, double scale)
{
// Implementation based on "A Simple Method for Generating Gamma Variables"
// by George Marsaglia and Wai Wan Tsang. ACM Transactions on Mathematical Software
// Vol 26, No 3, September 2000, pages 363-372.
double d, c, x, xsquared, v, u;
if (shape >= 1.0)
{
d = shape - 1.0/3.0;
c = 1.0/sqrt(9.0*d);
for (;;)
{
do
{
x = GetNormal();
v = 1.0 + c*x;
}
while (v <= 0.0);
v = v*v*v;
u = GetUniform();
xsquared = x*x;
if (u < 1.0 -.0331*xsquared*xsquared || log(u) < 0.5*xsquared + d*(1.0 - v + log(v)))
return scale*d*v;
}
}
else if (shape <= 0.0)
{
std::stringstream os;
os << "Shape parameter must be positive." << "\n"
<< "Received shape parameter " << shape;
throw std::invalid_argument( os.str() );
}
else
{
double g = GetGamma(shape+1.0, 1.0);
double w = GetUniform();
return scale*g*pow(w, 1.0/shape);
}
}
double SimpleRNG::GetChiSquare(double degreesOfFreedom)
{
// A chi squared distribution with n degrees of freedom
// is a gamma distribution with shape n/2 and scale 2.
return GetGamma(0.5 * degreesOfFreedom, 2.0);
}
double SimpleRNG::GetInverseGamma(double shape, double scale)
{
// If X is gamma(shape, scale) then
// 1/Y is inverse gamma(shape, 1/scale)
return 1.0 / GetGamma(shape, 1.0 / scale);
}
double SimpleRNG::GetWeibull(double shape, double scale)
{
if (shape <= 0.0 || scale <= 0.0)
{
std::stringstream os;
os << "Shape and scale parameters must be positive." << "\n"
<< "Received shape " << shape << " and scale " << scale;
throw std::invalid_argument( os.str() );
}
return scale * pow(-log(GetUniform()), 1.0 / shape);
}
double SimpleRNG::GetCauchy(double median, double scale)
{
if (scale <= 0)
{
std::stringstream os;
os << "Shape parameter must be positive." << "\n"
<< "Received shape parameter " << scale;
throw std::invalid_argument( os.str() );
}
double p = GetUniform();
// Apply inverse of the Cauchy distribution function to a uniform
return median + scale*tan(PI*(p - 0.5));
}
double SimpleRNG::GetStudentT(double degreesOfFreedom)
{
if (degreesOfFreedom <= 0)
{
std::stringstream os;
os << "Degrees of freedom must be positive." << "\n"
<< "Received shape parameter " << degreesOfFreedom;
throw std::invalid_argument( os.str() );
}
// See Seminumerical Algorithms by Knuth
double y1 = GetNormal();
double y2 = GetChiSquare(degreesOfFreedom);
return y1 / sqrt(y2 / degreesOfFreedom);
}
// The Laplace distribution is also known as the double exponential distribution.
double SimpleRNG::GetLaplace(double mean, double scale)
{
double u = GetUniform();
return (u < 0.5) ?
mean + scale*log(2.0*u) :
mean - scale*log(2*(1-u));
}
double SimpleRNG::GetLogNormal(double mu, double sigma)
{
return exp(GetNormal(mu, sigma));
}
double SimpleRNG::GetBeta(double a, double b)
{
if (a <= 0.0 || b <= 0.0)
{
std::stringstream os;
os << "Beta parameters must be positive." << "\n"
<< "Received parameters " << a << " and " << b;
throw std::invalid_argument( os.str() );
}
// There are more efficient methods for generating beta samples.
// However such methods are a little more efficient and much more complicated.
// For an explanation of why the following method works, see
// http://www.johndcook.com/distribution_chart.html#gamma_beta
double u = GetGamma(a, 1.0);
double v = GetGamma(b, 1.0);
return u / (u + v);
}
int SimpleRNG::GetPoisson(double lambda)
{
return (lambda < 30.0) ? PoissonSmall(lambda) : PoissonLarge(lambda);
}
int SimpleRNG::PoissonSmall(double lambda)
{
// Algorithm due to Donald Knuth, 1969.
double p = 1.0, L = exp(-lambda);
int k = 0;
do
{
k++;
p *= GetUniform();
}
while (p > L);
return k - 1;
}
int SimpleRNG::PoissonLarge(double lambda)
{
// "Rejection method PA" from "The Computer Generation of Poisson Random Variables" by A. C. Atkinson
// Journal of the Royal Statistical Society Series C (Applied Statistics) Vol. 28, No. 1. (1979)
// The article is on pages 29-35. The algorithm given here is on page 32.
double c = 0.767 - 3.36/lambda;
double beta = PI/sqrt(3.0*lambda);
double alpha = beta*lambda;
double k = log(c) - lambda - log(beta);
for(;;)
{
double u = GetUniform();
double x = (alpha - log((1.0 - u)/u))/beta;
int n = (int) floor(x + 0.5);
if (n < 0)
continue;
double v = GetUniform();
double y = alpha - beta*x;
double temp = 1.0 + exp(y);
double lhs = y + log(v/(temp*temp));
double rhs = k + n*log(lambda) - LogFactorial(n);
if (lhs <= rhs)
return n;
}
}
double SimpleRNG::LogFactorial(int n)
{
if (n < 0)
{
std::stringstream os;
os << "Invalid input argument (" << n
<< "); may not be negative"