/*===========================================================================
  Copyright (C) 1995-2007 European Southern Observatory (ESO)
 
  This program is free software; you can redistribute it and/or 
  modify it under the terms of the GNU General Public License as 
  published by the Free Software Foundation; either version 2 of 
  the License, or (at your option) any later version.
 
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
 
  You should have received a copy of the GNU General Public 
  License along with this program; if not, write to the Free 
  Software Foundation, Inc., 675 Massachusetts Ave, Cambridge, 
  MA 02139, USA.
 
  Correspondence concerning ESO-MIDAS should be addressed as follows:
	Internet e-mail: midas@eso.org
	Postal address: European Southern Observatory
			Data Management Division 
			Karl-Schwarzschild-Strasse 2
			D 85748 Garching bei Muenchen 
			GERMANY
===========================================================================*/

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

.IDENT        genran.c
.LANGUAGE     C
.AUTHOR       K. Banse   ESO/IPG
.KEYWORDS     Bulk data frames, Images
.PURPOSE      create an image with data = random numbers generated
	      according to a given distribution
.VERSION      1.00  950921:   Creation 
 070705		last modif

---------------------------------------------------------------------*/

#include <midas_def.h>
#include <stdio.h>
#include <stdlib.h>

#include <math.h>

/*  M_PI not defined on some Linux installations,
    so we copied it from the math.h on Sun Solaris 8 */

# define M_PI           3.14159265358979323846  /* pi */
#define  R_EPS  10.e-30

static int  multflag, basicu = -1, ready = 0;
static int nnA;
static float  rrA, rrB, dista;
static double ddA;

/*

*/

static int stdseed;
static int stda = 16807;
static int stdm = 2147483647;         /* 2**31 - 1 */
static int stdq = 127773;
static int stdr = 2836;


#ifdef __STDC__
static void StdSeed(int newseed)
#else
static void StdSeed(newseed)
int newseed;
#endif

{
stdseed = newseed;
return;
}



/***********************************************************************
*
*   Minimal Standard uniform random generator
*   according to code from:    Stephen K. Park, Keith W. Miller
*
* for details (e.g. why this is is a "good" uniform random no. generator)
* see their article "Random Number Generators: Good Ones are hard to find"
* in: Commmunications of the ACM, Oct. 1988, Vol. 31, Number 10, page 1192
*
***********************************************************************/


#ifdef __STDC__
float StdNumber(void)
#else
float StdNumber()
#endif

{
int lo, hi;

float  uni;



hi =  stdseed / stdq;
lo = stdseed - (stdq * hi);

stdseed = (stda*lo) - (stdr*hi);
if (stdseed <= 0) stdseed += stdm;

uni = (float)stdseed / (float)stdm;
return (uni);
}
/*

*/

/***********************************************************************
*
*   Lagged Fibonnacci random number generator 
*
*   Paul Coddington, April 1993.
*
*
* General lagged Fibonnacci generator using subtraction, with lags 
* p and q, i.e. F(p,q,-) in Marsaglia's notation.
* 
* The random numbers X_{i} are obtained from the sequence:
* 
*    X_{i} = X_{i-q} - X_{i-p}   mod M
*  
* where M is 1 if the X's are taken to be floating point reals in [0,1),
* as they are here.
* 
* For good results, the biggest lag should be at least 1000, and probably
* on the order of 10000.
* 
* The following lags give the maximal period of the generator, which is 
* (2^{p} - 1)2^{n-1} on integers mod 2^n or reals with n bits in the 
* mantissa (see Knuth, or W. Zerler, Information and Control, 15 (1969) 67, 
* for a complete more list).
* 
*     P     Q
*   9689   471 
*   4423  1393
*   2281   715
*   1279   418
*    607   273
*    521   168
*    127    63
* 
* This program is based on the implementation of RANMAR in 
* F. James, "A Review of Pseudo-random Number Generators", 
* Comput. Phys. Comm. 60, 329 (1990).
* 
* For more details, see:
* 
* D.E. Knuth, The Art of Computer Programming Vol. 2: 
* Seminumerical Methods, (Addison-Wesley, Reading, Mass., 1981).
* 
* P. L'Ecuyer, Random numbers for simulation, Comm. ACM 33:10, 85 (1990).
* 
* G.A. Marsaglia, A current view of random number generators,
* in Computational Science and Statistics: The Interface,
* ed. L. Balliard (Elsevier, Amsterdam, 1985).
* 
***********************************************************************/
 


#define MAXSEED 900000000

#define SIGBITS 24   /* Number of significant bits */

/* Lags */
#define P 1279
#define Q  418

/* Variables for lagged Fibonacci */
double u[P+1];  /* seed table */

int pt0, pt1;   /* pointers into the seed table */



/***************************************************************************
	Initialize the random number generator.
        Taken from RMARIN in James's review -- initializes
	every significant bit using a combination linear 
	congruential and small lag Fibonacci generator.
***************************************************************************/

#ifdef __STDC__
static void FiboSeed(long int seed)
#else
static void FiboSeed(seed)
int  seed;
#endif

{
int ij, kl, i, j, k, l;
int ii, jj, m;

double t, s;


if (seed < 0) seed = - seed;
seed = seed % MAXSEED;

ij = seed / 30082;
kl = seed - 30082 * ij;
i = ((ij/177)% 177) + 2;
j =  ij%177 + 2;
k = ((kl/169)% 178) + 1;
l =  kl%169;
      
for (ii=1; ii<=P; ii++)
   {
   s = 0.0;
   t = 0.5;
   for (jj=1; jj<=SIGBITS; jj++)
      {
      m = (((i*j)% 179)*k)% 179;
      i = j;
      j = k;
      k = m;
      l = (53*l+1) % 169;
      if ( ((l*m)%64) >= 32) s = s + t;
      t = 0.5 * t;
      }
   u[ii] = s;
   }
pt0 = P;
pt1 = Q;

return;
}
  
/***************************************************************************
	Return a random double in [0.0, 1.0)
***************************************************************************/
#ifdef __STDC__
static double FiboNumber(void)
#else
static double FiboNumber()
#endif

{
double uni;
      


uni = u[pt0] - u[pt1];
if (uni < 0.0) uni = uni + 1.0;
u[pt0] = uni;

pt0 = pt0 - 1;
if (pt0 == 0) pt0 = P;

pt1 = pt1 - 1;
if (pt1 == 0) pt1 = P;
            
return(uni);
}
/*

*/

#ifdef __STDC__
static void RndSeed(int ifn, int iseed, float *fpt)
#else
static void RndSeed(ifn,iseed,fpt)
int  ifn, iseed;
float  *fpt;
#endif

{
float  rv1, rv2;

int  imeth;
long int  myseed;




basicu = ifn % 10;

if (basicu == 0) 
   {
   myseed = (long int) iseed;
   FiboSeed(myseed);		/* init  Fibonacci Number Generator */
   }
else 
   StdSeed(iseed);		/* init Standard Number Generator */


imeth = ifn / 10;
multflag = 0;
dista = 0.0;

rrA = *fpt++;
rrB = *fpt;

if (imeth == 3)
   {
   if (rrA > 0.0)
      {
      ddA = (double) -rrA;	/* save neg. mean for exponential p.f. now */
      return;
      }
   else
      {
      SCETER(23,"mean > 0.0 needed for exponential p.f. ");
      return;
      }
   }

else if (imeth == 4)
   return;

else if (imeth == 5)
   {	
   if (rrA < 0.0) 
      {
      SCETER(25,"Poisson r.n.g. for neg. mean not o.k. ...");
      return;
      }

   if (rrA > 33.0) 
      SCTPUT("Warning: Poisson r.n.g for large mean takes time...");

   ddA = (double) (-rrA);
   rrB = (float) exp(ddA);			/* get exp(-mean) once */
   return;
   }

else if (imeth == 6)
   {	
   nnA = CGN_NINT(rrA);			/* no. of trials */
   if (rrB > 0.5)			/* probability of an event */
      {
      rrB = 1.0 - rrB;
      dista = 99;			/* mark this change via `dista' */
      } 
   return;
   }


/* check, if we really need to adjust the [0,1) interval */

rv1 = rrA;
if (rv1 < 0.0) rv1 = -rv1;
rv2 = rrB - 1.0;
if (rv2 < 0.0) rv2 = -rv2;

if ((rv1 > R_EPS) || (rv2 > R_EPS))
   {
   multflag = 1;
   dista = rrB - rrA;		/* only useful for Uniform, but so what */
   }

}
/*

*/

#ifdef __STDC__
float UniNumber(void)
#else
float UniNumber()
#endif

{
float  randu;


if (basicu == 0)
   randu =  (float)FiboNumber();
else
   randu =  StdNumber();

return randu;
}





#ifdef __STDC__
static double GaussNumber(void)
#else
static double GaussNumber()
#endif

{
double p, q, phi, radius;
double new_gauss;
static double last_gauss;



/* we always deliver pairs */

if (ready == 1)
   {
   ready = 0;
   if (multflag == 1)			/* rrA = mean, rrB = sigma */
      last_gauss = rrA + (rrB * last_gauss);
   return last_gauss;
   }

do
   {
   p = (double) UniNumber();
   }
while (p <= 0.0);

q = (double) UniNumber();

radius = sqrt(-2 * log(p));
phi = 2 * q * M_PI;

last_gauss = cos(phi) * radius;
new_gauss = sin(phi) * radius;

if (multflag == 1)			/* rrA = mean, rrB = sigma */
   new_gauss = rrA + (rrB * new_gauss);

ready = 1;
return (new_gauss);
}





#ifdef __STDC__
static double ExpoNumber(void)
#else
static double ExpoNumber()
#endif

{
double p, q;


q = (double) UniNumber();
p = ddA * log(q);			/* ddA already = -mean */

return (p);
}




#ifdef __STDC__
static float PoisNumber(void)
#else
static float PoisNumber()
#endif

{
float r, pno;

int  mm;


mm = 1;
r = UniNumber();		/* init product of uniforms */

/* loop until product of uniform randoms is <= exp(-mean) (= rrB) */

while (r > rrB)
   {
   r *= UniNumber();
   mm ++;
   }
 
pno = (float) (mm - 1);

return pno;
}




#ifdef __STDC__
static float BinomNumber(void)
#else
static float BinomNumber()
#endif

/* nnA = no. of trials, rrB = probability of event */

{
float r, bno;

int   kno;
register int  jr;




if (nnA < 33)				/* for small no. of trials, */
   {					/* do it directly */
   kno = 0;
   for (jr=0; jr<nnA; jr++)
      {
      if ((r = UniNumber()) < rrB)  kno++;
      }
   }

else					/* else, use Poisson distr. */
   {
   float  bm;
   int  mm;


   bm = (float) exp((double)(rrA*rrB));		/* (n*prob) =  mean of distr */
   mm = 1;
   r = UniNumber();				/* init product of uniforms */

   /* loop until product of uniform randoms is <= exp(-mean) (= bm) */

   while (r > bm)
      {
      r *= UniNumber();
      mm ++;
      if (mm > nnA) break;		/* not more then nnA trials... */
      }
 
   kno = mm - 1;
   }

if (dista == 99)			/* we had switched the probability */
   kno = nnA - kno;

bno = (float) kno;
return bno;
}
/*

*/

#ifdef __STDC__
static void RndGen(int ifn, int size, float *rcuts, float *datpntr)
#else
static void RndGen(ifn,size,rcuts,datpntr)
int  ifn, size;
float  *rcuts, *datpntr;
#endif

{
register int  nr;

register float  rmin, rmax, value;



rmin = *rcuts;
rmax = *(rcuts+1);


/* Uniform pdf */

if (ifn < 12)
   {					/* UniNumber() is used by all */
   for (nr=0; nr<size; nr++)		/* other distributions, so we have */
      {					/* to adjust here... */
      value = UniNumber();
      if (multflag == 1)
         value = (value * dista) + rrA;		/* [0,1) => [a,b) */
      if (value < rmin)
         rmin = value;
      else if (value > rmax)
         rmax = value;
      *datpntr++ = value;
      }
   }

/* Gaussian pdf */

else if (ifn < 22)
   {
   for (nr=0; nr<size; nr++)
      {
      value = (float) GaussNumber();
      if (value < rmin)
         rmin = value;
      else if (value > rmax)
         rmax = value;
      *datpntr++ = value;
      }
   }

/* Exponential pdf */

else if (ifn < 32)
   {
   for (nr=0; nr<size; nr++)
      {
      value = (float) ExpoNumber();
      if (value < rmin)
         rmin = value;
      else if (value > rmax)
         rmax = value;
      *datpntr++ = value;
      }
   }

/* Cauchy pdf */

else if (ifn < 42)
   {
   double  g1;

   for (nr=0; nr<size; nr++)
      {
      g1 = (double)(UniNumber() - 0.5);
      g1 *= M_PI ;
      value = (float) tan(g1);

      if (value < rmin)
         rmin = value;
      else if (value > rmax)
         rmax = value;
      *datpntr++ = value;
      }
   }

/* Poisson pdf */

else if (ifn < 52)
   {
   for (nr=0; nr<size; nr++)
      {
      value = PoisNumber();
      if (value < rmin)
         rmin = value;
      else if (value > rmax)
         rmax = value;
      *datpntr++ = value;
      }
   }

/* Binomial pdf */

else if (ifn < 62)
   {
   for (nr=0; nr<size; nr++)
      {
      value = BinomNumber();
      if (value < rmin)
         rmin = value;
      else if (value > rmax)
         rmax = value;
      *datpntr++ = value;
      }
   }
}
/*

*/

int main()

{
char   *pntra;
char   ident[74], outfile[84], reffile[84], functype[24];

int  fmeth, in[4], nval, unit, null;
int  naxis, npix[3], imnob, size, mapsize, pixoff;
register int  nr;

float  *fpntr, rcoeff[2], rr[4];

double  start[3], step[3], dst[6];




(void) SCSPRO("genran");
(void) SCKGETC("IN_A",1,80,&nval,reffile);		/* get ref frame */
(void) SCKGETC("OUT_A",1,80,&nval,outfile);             /* get output frame */

(void) SCKGETC("P4",1,20,&nval,functype);		/* get distribution */

/* default to Uniform,Fibonacci */

in[3] = 0;
nval = CGN_INDEXC(functype,',');
if (nval > 0)
   {
   nval ++;
   if ((functype[nval] == 'S') || (functype[nval] == 's'))
      in[3] = 1;
   }

if ((functype[0] == 'U') || (functype[0] == 'u'))
   fmeth = 10 + in[3];					/* uniform */
else if ((functype[0] == 'G') || (functype[0] == 'g'))
   fmeth = 20 + in[3];					/* Gauss */
else if ((functype[0] == 'E') || (functype[0] == 'e'))
   fmeth = 30 + in[3];					/* exponential */
else if ((functype[0] == 'C') || (functype[0] == 'c'))
   fmeth = 40 + in[3];					/* Cauchy */
else if ((functype[0] == 'P') || (functype[0] == 'p'))
   fmeth = 50 + in[3];					/* Poisson */
else if ((functype[0] == 'B') || (functype[0] == 'b'))
   fmeth = 60 + in[3];					/* Binomial */
else
   {
   char  texto[84];

   fmeth = 10;
   (void) sprintf(texto,"pdf %s not implemented yet - default to Uniform,fibo",
                  functype);
   SCTPUT(texto);
   }

npix[1] = npix[2] = 1;

if (reffile[0] == '+')
   {
   (void) SCKRDI("ID",1,4,&nval,in,&unit,&null);
   (void) SCKRDD("RD",1,6,&nval,dst,&unit,&null);
   naxis = in[0];
   for (nr=0; nr<naxis; nr++)
      {
      npix[nr] = in[nr+1];
      start[nr] = dst[nr];
      step[nr] = dst[nr+naxis];
      }
   }
else
   {
   (void) SCFOPN(reffile,D_R4_FORMAT,0,F_IMA_TYPE,&imnob);
   (void) SCDRDI(imnob,"NAXIS",1,1,&nval,&naxis,&unit,&null);
   (void) SCDRDI(imnob,"NPIX",1,naxis,&nval,npix,&unit,&null);
   (void) SCDRDD(imnob,"START",1,naxis,&nval,start,&unit,&null);
   (void) SCDRDD(imnob,"STEP",1,naxis,&nval,step,&unit,&null);
   (void) SCFCLO(imnob);
   }


/* create frame */

size = npix[0] * npix[1] * npix[2];		/*  get total size of frame */
(void) SCFCRE(outfile,D_R4_FORMAT,F_O_MODE,F_IMA_TYPE,size,&imnob);
(void) SCDWRI(imnob,"NAXIS",&naxis,1,1,&unit);
(void) SCDWRI(imnob,"NPIX",npix,1,naxis,&unit);
(void) SCDWRD(imnob,"START",start,1,naxis,&unit);
(void) SCDWRD(imnob,"STEP",step,1,naxis,&unit);
if (naxis == 1)
   (void) strcpy(ident,"data          x-pix");
else if (naxis == 2)
   (void) strcpy(ident,"data          x-pix           y-pix ");
else
   (void) strcpy(ident,"data          x-pix           y-pix           z-pix ");

nval = (naxis + 1) * 16;
(void) SCDWRC(imnob,"CUNIT",1,ident,1,nval,&unit);
if (fmeth == 10)
   (void) strcpy(ident,"Random (uniform) image (Fibonacci method) ");
else if (fmeth == 11)
   (void) strcpy(ident,"Random (uniform) image (minimal Standard method) ");
(void) SCDWRC(imnob,"IDENT",1,ident,1,72,&unit);
rr[0] = rr[1] = rr[2] = rr[3] = 0.0;


/* initialize random generators */

(void) SCKRDR("RCOEFF",1,2,&nval,rcoeff,&unit,&null);
(void) SCKRDI("SEED",1,1,&nval,in,&unit,&null);

RndSeed(fmeth,in[0],rcoeff);


/* allocate virtual memory for buffer with right data types */


(void) SCKRDI("MONITPAR",20,1,&nval,&mapsize,&unit,&null);
mapsize *= mapsize;				/* square image */

if (mapsize < size)
   {
   nval = mapsize * sizeof(float);
   pntra = malloc((size_t)nval);
   if (pntra == (char *) 0) SCETER(33,"Could not allocate memory...");
   pixoff = 1;

loop:
   fpntr = (float *) pntra;
   RndGen(fmeth,mapsize,rr,fpntr);
   (void) SCFPUT(imnob,pixoff,mapsize,pntra);
   pixoff += mapsize;
   size -= mapsize;
   if (size >0)
      {
      if (size < mapsize) mapsize = size;
      goto loop;
      }
   }
else
   {
   (void) SCFMAP(imnob,F_O_MODE,1,size,&nval,&pntra);

   fpntr = (float *) pntra;
   RndGen(fmeth,size,rr,fpntr);
   }

(void) SCDWRR(imnob,"LHCUTS",rr,1,4,&unit);
(void) CGN_DSCUPD(imnob,imnob," ");		/* add descr. HISTORY */

(void) SCSEPI();

return 0;
}

