view rndutils.c @ 3:974d7be8eec4 tip

Update to pack-based dcg utilities
author samer
date Tue, 03 Oct 2017 11:52:23 +0100
parents b31415b4a196
children
line wrap: on
line source
/*
 * Pseudorandom generators for SWI Prolog
 * Samer Abdallah (2009)
 * Some samplers adapted from code in Tom Minka's lightspeed Matlab toolbox.
 * Other bits of code culled from SWI Prolog distribution random.c
 *
 * To do:
 *    work out lower bound for skew stable distributions
 * 	stream splitting with jumps
 * 	reduce blob copying
 * 	fast discrete distributions
 * 	ziggurat for normals
 */

#define _USE_MATH_DEFINES 1

#include <math.h>
#include <float.h>
#include "rndutils.h"

#if HAVE_GSL
#include <gsl/gsl_sf_zeta.h>
#else
double gsl_sf_zeta(const double s) { return NAN; }
double gsl_sf_hzeta(const double s, const double k) { return NAN; }
#endif

#ifndef M_PI
#define M_PI       3.14159265358979323846
#endif


// -----------------------------------------------------

// Returns a sample from Normal(0,1)
double Normal(RndState *S)
{
	double x=S->prev_normal;

	if(!isnan(x)) {
		S->prev_normal=NAN;
	} else {
		double y,radius;

		/* Generate a random point inside the unit circle */
		do {
			x = 2*Uniform(S)-1;
			y = 2*Uniform(S)-1;
			radius = (x*x)+(y*y);
		} while((radius >= 1.0) || (radius == 0.0));
		/* Box-Muller formula */
		radius = sqrt(-2*log(radius)/radius);
		x *= radius;
		y *= radius;
		S->prev_normal=y;
	}
	return x;
}

/* Returns a sample from Gamma(a, 1).
 * For Gamma(a,b), scale the result by b.
 */
double Gamma(RndState *S, double a)
{
  /* Algorithm:
   * G. Marsaglia and W.W. Tsang, A simple method for generating gamma
   * variables, ACM Transactions on Mathematical Software, Vol. 26, No. 3,
   * Pages 363-372, September, 2000.
   * http://portal.acm.org/citation.cfm?id=358414
   */
  double boost, d, c, v;
  if(a < 1) {
    /* boost using Marsaglia's (1961) method: gam(a) = gam(a+1)*U^(1/a) */
    boost = exp(log(Uniform(S))/a);
    a++;
  } 
  else boost = 1;
  d = a-1.0/3; c = 1.0/sqrt(9*d);
  while(1) {
    double x,u;
    do {
      x = Normal(S);
      v = 1+c*x;
    } while(v <= 0);
    v = v*v*v;
    x = x*x;
    u = Uniform(S);
    if((u < 1-.0331*x*x) || 
       (log(u) < 0.5*x + d*(1-v+log(v)))) break;
  }
  return( boost*d*v );
}

/* Returns a sample from Dirichlet(n,a) where n is dimensionality */
int Dirichlet(RndState *S, long n, double *a, double *x)
{
	long i;
	double tot=0, z;
	for (i=0; i<n; i++) { z=Gamma(S,a[i]); tot+=z; x[i]=z; }
	for (i=0; i<n; i++) { x[i]/=tot; }
	return 1;
}

/* Returns a sample from Beta(a,b) */
double Beta(RndState *S, double a, double b)
{
  double g = Gamma(S,a);
  return g/(g + Gamma(S,b));
}

double Hyperbolic(RndState *S, double a)
{
	return floor(pow(1-Uniform(S),-1/a));
}

/* Sample from zeta distribution
 *
 * This is an inverse CDF method. It works by using  an 
 * approximation of the Hurwitz Zeta function and then
 * walking left or right until we get to the correct point.
 * The approximation is good for large values so usually we
 * only have to walk left or right a few steps for small 
 * values.
 */
double Zeta(RndState *S, double s)
{
	double v=(1-Uniform(S))*gsl_sf_zeta(s);
	double k0, k=round(pow(v*(s-1),1/(1-s))); // approx inverse of hzeta
	double zk0, zk=gsl_sf_hzeta(s,k);

	if (zk>=v) {
		do {
			k0=k; zk0=zk;
			k=k0+1; zk=zk0-pow(k0,-s);
		} while (zk>=v && zk!=zk0);
		return k0;
	} else {
		do {
			k0=k; zk0=zk;
			k=k0-1; zk=zk0+pow(k,-s);
		} while (zk<v && zk!=zk0);
		return k;
	}
}

/* Very fast binomial sampler. 
 * Returns the number of successes out of n trials, with success probability p.
 */
int Binomial(RndState *S, double p, int n)
{
  int r = 0;
  if(isnan(p)) return 0;
  if(p < DBL_EPSILON) return 0;
  if(p >= 1-DBL_EPSILON) return n;
  if((p > 0.5) && (n < 15)) {
    /* Coin flip method. This takes O(n) time. */
    int i;
    for(i=0;i<n;i++) {
      if(Uniform(S) < p) r++;
    }
    return r;
  }
  if(n*p < 10) {
    /* Waiting time method.  This takes O(np) time. */
    double q = -log(1-p), e = -log(Uniform(S)), s;
    r = n;
    for(s = e/r; s <= q; s += e/r) {
      r--;
      if(r == 0) break;
      e = -log(Uniform(S));
    }
    r = n-r;
    return r;
  }
  if (1) {
    /* Recursive method.  This makes O(log(log(n))) recursive calls. */
    int i = (int)floor(p*(n+1));
    double b = Beta(S,i, n+1-i);
    if(b <= p) r = i + Binomial(S,(p-b)/(1-b), n-i);
    else r = i - 1 - Binomial(S,(b-p)/b, i-1);
    return r;
  }
}

double Exponential(RndState *S) { return -log(1-Uniform(S)); }

long Poisson(RndState *S, double lambda)
{
	long r;
	if (lambda>=15) {
		double m=floor(lambda*7/8);
		double x=Gamma(S,m);

		if (x>lambda) r=Binomial(S,lambda/x,m-1);
		else          r=m+Poisson(S,lambda-x);
	} else {
		double p;
		for (p=0, r=-1; p<lambda; p+=Exponential(S)) r++; 
	}
	return r;
}

/* Returns a sample from stable distribution */
double Stable(RndState *S, int param, double alpha, double beta)
{
	double X, theta=M_PI*(Uniform(S)-0.5);

	// These methods come from John Nolan's book about
	// stable distributions. They return standardised stable random 
	// numbers in the S(alpha,beta;0) parameterisation

	if (beta==0) {
		if (alpha==1) {
			X=tan(theta);
		} else {
			double W=Exponential(S);
			X=sin(alpha*theta)/cos(theta)*pow(cos((alpha-1)*theta)/(W*cos(theta)),1/alpha-1);
		}
	} else {
		double W=Exponential(S);
		double zeta=beta*tan(alpha*M_PI/2);
		if (alpha==1) {
			double b=2*beta/M_PI;
			double bt=1+b*theta;
			X=bt*tan(theta) - b*log(W*cos(theta)/bt);
		} else {
			double ath = alpha*theta; 
			double a1th = theta-ath; 
			X = ((sin(ath) + zeta*cos(ath))/cos(theta))
				 * pow((cos(a1th) + zeta*sin(a1th))/(W*cos(theta)),1/alpha-1);
			if (param==0) X=X-zeta;
		}
	}
	return X;
}

/* Returns a sample from discrete distribution */
long Discrete(RndState *S, long n, double *p, double tot)
{
	double u;
	long i;

	for (i=0, u=tot*Uniform(S)-p[0]; u>=0; u-=p[++i]); 
	return i;
}

double Uniform(RndState *S0)
{
	return RngStream_Double(&S0->g,&S0->g);
}

double Raw(RndState *S0)
{
	return RngStream_Raw(&S0->g,&S0->g);
}

void InitRndState(RndState *S)
{
	RngStream_InitState(&S->g);
	S->prev_normal=NAN;
}


int spawn_gen(RndState *S0, RndState *S1)
{
	unsigned seeds[6];
	int i;

	for (i=0; i<6; i++) seeds[i]=(unsigned)RngStream_Raw(&S0->g,&S0->g);
	RngStream_SetState(&S1->g,seeds);
	for (i=0; i<3; i++) RngStream_Raw(&S1->g,&S1->g);
	S1->prev_normal=NAN;
	return 1;
}

int randomise_from(FILE *p, RndState *S)
{
	unsigned seeds[6];
	size_t r=fread((void *)seeds,sizeof(unsigned),6,p);

	if (r==6) {
		int i;
		if (RngStream_SetState(&S->g,seeds)<0) return 0;
		for (i=0; i<3; i++) RngStream_Raw(&S->g,&S->g);
		S->prev_normal=NAN;
		return 1;
	} else return 0;
}