/*
 *
 * $Header: /usr/u/wjr/src/support/RCS/gaussian.c,v 1.2 1991/11/07 22:43:34 wjr Exp $
 *
 */
/*
 * Generate Gaussian distributions.
 */

static char rcsid[] = "@(#)$Header: /usr/u/wjr/src/support/RCS/gaussian.c,v 1.2 1991/11/07 22:43:34 wjr Exp $";

#include "misc.h"
#include <math.h>

/*
 * How many standard deviations out we should generate.
 * 8 is sufficient, since the values out there are about 10^{-15}.
 * Making this too large gives us denorms out in the tails, which can slow
 * processing immensely.
 */
#define SIGMA_SPREAD 8

static double r2p = 0;

/*
 * In many applications, this routine will be called many times with the same
 * value of sigma (or several such values). We want to avoid regenerating
 * the gaussian every time.
 */
#define	MAXOLD	32

static struct {
    double *gaussian;
    double sigma;
    int width;
    } old[MAXOLD];
static int nold = 0;
static int lastold = 0;

/*
 * This function generates one side of a Gaussian distribution with standard
 * deviation sigma. The Gaussian is normalised so that its integral is one.
 * The Gaussian returned will have width+1 entries (i.e. will be represented
 * from -width..width) (via symmetry).
 * In other words, it will generate v[0]..v[*width] such that
 * v[0] + 2 * v[1] + ... + 2 * v[*width] = 1
 * and v[*width],...,v[1],v[0],v[1],...,v[*width] is a Gaussian
 * distribution.
 * 
 * Note that the pointer returned *MUST NOT* be freed, and the values it
 * points at should not be modified.
 */
double *
makeGaussian(double sigma, int *width)
{
    double *gaussian;
    double accum;
    double sigma2;
    double r2psigma;
    int j;

    assert (sigma >= 0.);
    assert (width != (int *)NULL);

    /* Calculate sqrt(2*M_PI) once only. */
    if (r2p == 0.) {
	r2p = sqrt(2 * M_PI);
	}

    sigma2 = -2. * sigma * sigma;
    r2psigma = r2p * sigma;
	
    /* See if we happen to have this lying around */
    for (j = 0; j < nold; j++) {
	if (old[j].sigma == sigma) {
	    *width = old[j].width;
	    return(old[j].gaussian);
	    }
	}

    /*
     * Now find out how wide to make the actual gaussian, based on sigma.
     * SIGMA_SPREAD should be high enough to make the tails insignificant.
     * However, making it too large can slow down computation.
     */
    *width = SIGMA_SPREAD * sigma + 0.5;
    gaussian = (double *)malloc((*width + 1) * sizeof(*gaussian));
    if (gaussian == (double *)NULL) {
	return((double *)NULL);
	}

    /* Now fill in the gaussian array */
    if (sigma == 0.) {
	/* A delta function */
	gaussian[0] = 1.;
	}
    else {
	accum = 0.;
	for (j = 0; j <= *width; j++) {
	    gaussian[j] = 1 / r2psigma * exp(((double)j) * j / sigma2);
	    accum += (j == 0) ? gaussian[j] : (2 * gaussian[j]);
	    }
	/* Normalise the gaussian so that its total sum is 1. */
	for (j = 0; j <= *width; j++) {
	    gaussian[j] /= accum;
	    }
	}
    
    /* Now find a place to put it */
    if (nold < MAXOLD) {
	/* Still empty slots */
	old[nold].sigma = sigma;
	old[nold].width = *width;
	old[nold].gaussian = gaussian;
	nold++;
	}
    else {
	/* Clean out the oldest slot */
	free((void *)old[lastold].gaussian);
	old[lastold].sigma = sigma;
	old[lastold].width = *width;
	old[lastold].gaussian = gaussian;
	lastold++;
	if (lastold == MAXOLD) {
	    lastold = 0;
	    }
	}

    return(gaussian);
    }
