// $Id: gaussQtables.src,v 1.3 2003/08/13 20:00:12 garren Exp $
// -*- C++ -*-
//
// gaussTables.src
//
// This program creates the data tables that RandGaussQ will use to 
// interpolate from the flat random R to the gaussian unit normal G.
// The tables are produced in files gaussQTables.cdat.  The data is 
// organized into values at 2 ranges of values of R:
//	by 2.0E-6  up to 5.0E-4
//	by 5.0E-4  up to .5
// This is substantially less data than is present in FlatToGaussian:
// Only 1250 grid points in two tables are used, instead of 2000 in five;
// single precision rather than double is used; and we do not store the 
// derivative separately.  The total footprint is 5K bytes.
//
//	Internally, the same tables are used as per FlatToGaussian, so that
//	the accuracy of the data is as good as possible.
//
// These are included from RandGaussQ.cc in the fire() routine, for example,
//	static const double gaussQTables[] = {
//    include "gaussQTables.cdat"
//      }
//
// At the end of gaussTables.cdat is a lengthy comment describing just what is 
// going on mathematically; this applies here as well.
//
// main         - Sets up TableInstructions then opens the output file and
//		  lets writeTabels do its thing.
// writeTables 	- Driver to create and write the gaussTables.cdat file by
//		  calling tabulateCDFvsX and then many calls to interpolate.
// tabulateCDFvsX - Integrate the pdf to create a table of cumulative density
//		    function (CDF) vs X with uniform spacing in X.
// interpolate  - Given the CDF values produced by tabulateCDFvsX, find X for
//		  a specified value of CDF (not necessarily at a node).
// pdf          - The gaussian distribution probability density function. 
// approxErrInt - Does an approximation of the error integral.
//
// This file need be compiled only once at one location; the gaussQTables.cdat
// generated is completely portable.  Therefore, one time only, gaussQTables.src
// has been renamed gaussQTables.cc, and compiled and run to create 
// gaussQTables.cdat.  The source gaussQTables.src is provided with CLHEP as a
// mode of concrete documentation of how those numers were computed.
//
// This file is stylistically not suitable for distribution as a reusable 
// module.
//
// 1/24/00   mf	Created.
// 1/  /00   mf	Having been used (from test area) to create gaussQTables.cdat, 
//		gaussQTables.cc was renamed .src and moved to Random area.
// --------------------------------------------------------------

#include "CLHEP/Random/defs.h"
#include "CLHEP/Units/PhysicalConstants.h"
#include <iostream>
#include <fstream>
#include <iomanip>

#define IOS_OK
#ifdef  IOS_OK
#include <ios.h>
#endif

using std::cout;

class TableInstructions {
public:
  double startingX;		// Value of first X in table of CDF vs X
				//
  double intervalX;		// (Uniform) spacing of X's in table of CDF 
				//
  int    NCDFvsX;		// Number of entries in table of CDF vs X
				//
  int	 NnextXmin;		// Number of X point matching first X point 
				//   in the next table of CDF vs X
  double startingCDF;		// Desired first CDF point to fill in table of
				//    X vs CDF value
  double intervalCDF;		// (Uniform) spacing in table of X vs CDF
				//
  int    NCDF;			// Number of entries in table of X vs CDF 
};

double pdf (double x);

void writeTables ( std::ostream & output, int nTables, 
			const TableInstructions tables[] );

void tabulateCDFvsX (double CDFbelow,
		     double startingX, 
		     double intervalX,
		     int N, 
		     double pdf(double x),
		     double* CDFTable,
		     int NnextXmin,
		     double & nextCDFbelow );

double interpolate ( double yStart, double Yspacing, int N, double* xTable,
			 double pdf(double xx), double x, int fi, int& newfi );

double approxErrInt (double v);


int main() {

  TableInstructions tables[6];

  // Set up for computing each table:
  //
  // For each table, we will have about 10000 integration steps
  // in the actual range, and more outside for safety.

  const double Xstep = .0002;
  const int    Xints =  500;	// number of steps in an interval of .1
				// 5000 points per unit X is found to give
				// the best accuracy.

  //	by 2.0E-13 up to 4.0E-11	200 R values

  tables[0].startingX   = -7.5;		// errInt (-7.5) ~ 3.0E-14
  tables[0].intervalX   = Xstep;	
  tables[0].NCDFvsX     = 12*Xints;	// errInt (-6.3) ~ 1.5E-10	
  tables[0].NnextXmin   =  9*Xints;	// to place it at -6.6
  tables[0].startingCDF = 2.0E-13;		
  tables[0].intervalCDF = 2.0E-13;		
  tables[0].NCDF        = 200;			

  //	by 4.0E-11 up to 1.0E-8		250 R values

  tables[1].startingX   = -6.6;		// errInt (-6.6) ~ 2.1E-11
  tables[1].intervalX   = Xstep;
  tables[1].NCDFvsX     = 12*Xints;	// errInt (-5.4) ~ 3.4E-8
  tables[1].NnextXmin   =  9*Xints;	// to place it at -5.7
  tables[1].startingCDF = 4.0E-11;	
  tables[1].intervalCDF = 4.0E-11;	
  tables[1].NCDF        = 250;		

  //	by 1.0E-8  up to 2.0E-6		200 R values

  tables[2].startingX   = -5.7;		// errInt (-5.7) ~ 6.2E-9
  tables[2].intervalX   = Xstep;
  tables[2].NCDFvsX     = 12*Xints;	// errInt (-4.5) ~ 4.0E-6
  tables[2].NnextXmin   =  9*Xints;	// to place it at -4.8
  tables[2].startingCDF = 1.0E-8;		
  tables[2].intervalCDF = 1.0E-8;		
  tables[2].NCDF        = 200;		

  //	by 2.0E-6  up to 5.0E-4		 250 R values

  tables[3].startingX   = -4.8;		// errInt (-4.8) ~ 8.3E-7
  tables[3].intervalX   = Xstep;
  tables[3].NCDFvsX     = 16*Xints;	// errInt (-3.2) ~ 7.4E-4
  tables[3].NnextXmin   = 12*Xints;	// to place it at -3.6
  tables[3].startingCDF = 2.0E-6;		
  tables[3].intervalCDF = 2.0E-6;	
  tables[3].NCDF        = 250;		

  //	by 5.0E-4  up to .5		1000 R values

  tables[4].startingX   = -3.6;		// errInt (-3.6) ~ 1.7E-4
  tables[4].intervalX   = Xstep;
  tables[4].NCDFvsX     = 37*Xints;	// errInt (+0.1) > .5
  tables[4].NnextXmin   = 0;		// arbitrary, since value won't be used
  tables[4].startingCDF = 5.0E-4;		
  tables[4].intervalCDF = 5.0E-4;	
  tables[4].NCDF        = 1000;			

  //    by 5.0E-4  up to .5 (reverse)	1000 R values

  tables[5].startingX   = 0.0;		
  tables[5].intervalX   = -Xstep;
  tables[5].NCDFvsX     = 36*Xints;	// errInt (-3.6) ~ 1.7E-4
  tables[5].NnextXmin   = 0;		// arbitrary, since value won't be used
  tables[5].startingCDF = 5.0E-4;		
  tables[5].intervalCDF = 5.0E-4;	
  tables[5].NCDF        = 1000;			

  // Open the output file

  std::ofstream output ( "gaussQTables.cdat", std::ios::out );

  // Write the tables

  writeTables ( output, 5, tables );

  cout << " Tables completed \n";

  return 0;
}

double pdf (double x) {
  return exp(-x*x/2.0)/sqrt(CLHEP::twopi);
}


void writeTables ( std::ostream & output, int nTables, 
			const TableInstructions tables[] ) {
  // Write nTables tables out to output, guided by the tables[] instructions.

  double nextCDFbelow = 0;

  for ( int nt = 0; nt < nTables; nt++ ) {

    TableInstructions insts = tables[nt];

    // **** First, tabulate the integrated cdf - which will be matched to r -
    //	    vs X (the limit of integration) which will be used as the deviate.

    double CDFbelow;
    if (nt == 0) {
      CDFbelow = approxErrInt ( insts.startingX );
    } else {
      CDFbelow = nextCDFbelow;
      cout << "approxErrInt(" << insts.startingX << ") = " 
	   << approxErrInt(insts.startingX) << "   CDFbelow = "
	   << CDFbelow << "\n";
    }

    int NCDFvsX = insts.NCDFvsX;
    double * CDFtable = new double [NCDFvsX];
    tabulateCDFvsX (CDFbelow, insts.startingX, insts.intervalX, 
		NCDFvsX, pdf, CDFtable, insts.NnextXmin, nextCDFbelow );

    // CDFtable now contains NCDFvsX points, each representing the CDF
    // at a point X in a uniformly spaced set of X's.  Also, nextCDFbelow
    // now contains the CDF at the desired point for the next interval.

    // **** From this table of CDF values, fill in the values of inverse CDF
    //      at each of the specified r points, by interpolation.  

	// Two things could go wrong here:  
	// 1- The r points might lie outside the range of f(r) given in the 
	//    instructions).  In that case, we will report it and punt.
	// 2- The estimated error of interpolation may be unacceptably large.
	//    In that case, we could go back and halve the spacing in the table.
 	//    However, we have computed in advance the necessary spacing, so
	//    we this error will always be acceptably small.

    if ( (insts.startingCDF + (insts.NCDF-1)*insts.intervalCDF) > 
						CDFtable [NCDFvsX-1] ) {
	cout << "Problem in table " << nt << ": r outside range tabulated\n";
	exit(1);
    }

    double * Xtable = new double [insts.NCDF];
    
    int fi = 0;
    int newfi;

    int i;
    double r;
    double f;
    for ( i = 0; i < insts.NCDF; i++ ) {
      r = insts.startingCDF + i * insts.intervalCDF;
      f = interpolate 
		( insts.startingX, insts.intervalX, insts.NCDFvsX, CDFtable, 
		  pdf, r, fi, newfi );
      Xtable[i] = f;
      fi = newfi;
    }


    // **** For just the last table, correct the endpoint by
    //	    tabulating CDF backward from 0, re-forming the Xtable,
    //      and takingthe appropriate linear combination of the two.
    if (nt == nTables-1) {
      insts = tables[nTables];
      NCDFvsX = insts.NCDFvsX;
      tabulateCDFvsX (.5, insts.startingX, insts.intervalX, 
		NCDFvsX, pdf, CDFtable, insts.NnextXmin, nextCDFbelow );
      // This table is not usable for the interpolation because it is backward:
      // reverse it!
      int n1; int n2;
      double temp;
      for ( n1 = 0, n2 = NCDFvsX -1; n1 < n2; n1++, n2-- ) {
	temp = CDFtable[n1];
	CDFtable[n1] = CDFtable[n2];
	CDFtable[n2] = temp;
      }
      // Form a second Xtable, just the way you did the first
      double * Xtable2 = new double [insts.NCDF];
      fi = 0;
      for ( i = 0; i < insts.NCDF; i++ ) {
        r = insts.startingCDF + i * insts.intervalCDF;
	// Note in interpolation that the MEANING of the dependent (X)
	// points in the CDFtable does not depend on startingX and
	// intervalX in the same way as before, since these values would
	// give the reversed table.
        f = interpolate 
		( insts.startingX+insts.intervalX*(insts.NCDFvsX-1), 
		  -insts.intervalX, insts.NCDFvsX, CDFtable, 
		  pdf, r, fi, newfi );
        Xtable2[i] = f;
        fi = newfi;
      }
      // Finally, correct the Xtable based on Xtable2 near .5
      double top    = insts.startingCDF + (insts.NCDF-1) * insts.intervalCDF;
      double bottom = insts.startingCDF;
      for ( i = 0; i < insts.NCDF; i++ ) {
        r = insts.startingCDF + i * insts.intervalCDF;
        Xtable[i] = ( Xtable2[i] * (r - bottom) +
		      Xtable[i]  * (top - r) ) / ( top - bottom );
      }
      delete Xtable2;
    } // End of correction for last table.

    // **** Write this table in the proper format
    //	
    //	    But only the last two tables are written out for RandGaussQ.
    //
    //	    The tables just have the values; the derivatives will be computed
    //	    implicitly when RandGaussQ interpolates between the grid points.

    if ( nt < 3 ) continue; // DON'T write the first 3 tables!

    output << "\n";
    for ( i = 0; i < insts.NCDF; i++ ) {
      r = insts.startingCDF + i * insts.intervalCDF;
      f = Xtable[i];
      output.setf(ios_base::fixed,ios_base::floatfield);
      output.precision(10);
      output << std::setw(25) << f ;
      output.setf(0,ios_base::floatfield);
      output.precision(6);
      output << ",      // " << r << "\n";
    } 

    output << "\n";

    delete Xtable;
    delete CDFtable;

  } // Now on to the next table.

} // writeTables 

// ======================
// Everything from here and below is the same as in gaussTables.cdat!
// ======================


void tabulateCDFvsX (double CDFbelow,
		     double startingX, 
		     double intervalX,
		     int N, 
		     double pdf(double x),
		     double* CDFtable,
		     int NnextXmin,
		     double & nextCDFbelow ) 
{
  // Create a table of integral from -infinity to f of pdf(x) dx, where we
  // assume the integral from -infinity to the startingF is given by rBelow.

  double f = startingX;
  double integral = CDFbelow;
  double halfStep = intervalX/2;
  const double h_6 = intervalX / 6.0;

  double* table = CDFtable;

  double d1;
  double d_midpt;
  double d2;

  d1 = pdf(f);
  *table = integral;
  table++;

  for ( int i = 1; i<N; i++ ) {
    d_midpt = pdf(f+halfStep);
    f += intervalX;
    d2 = pdf(f);
    integral += (d1 + 4*d_midpt + d2) * h_6;
    *table = integral;
    table++;
    d1 = d2;
    if ( i == NnextXmin ) {
      nextCDFbelow = integral;
    }
  } 

} // tabulateCDFvsX 



double interpolate ( double yStart, double Yspacing, int N, double* xTable,
			 double pdf(double xx), double x, int fi, int& newfi )
{
  // Given a table xTable of X values, for which the corresponding Y values 
  // are equal to yS, yS + Yspacing, ... yS + (N-1)*Yspacing; and a function
  // pdf which represents a derivative 

  // Find the highest point j such that x[j] does not exceed x (or
  // use point N-2 if j is larger than that x[N-2]).  Start from fi; and leave 
  // newfi as that end point.

  if (xTable[fi]  > x) {
    cout << "xTable[fi] too large?? \n";
    cout << "fi = " << fi << " xTable[fi] = " << xTable[fi] 
	 << " x = " << x << "\n";
    exit(1);
  }
  int j;
  for ( j = fi; j < N-1; j++ ) {
    if ( xTable[j] > x ) break;
  }
  j = j-1;
  newfi = j;

  // Perform the interpolation:

  double  y0 = yStart +   j  *Yspacing;
  double  y1 = yStart + (j+1)*Yspacing;
  double  d0 = 1.0/pdf(y0);
  double  d1 = 1.0/pdf(y1);

  double x0 = xTable[j];
  double x1 = xTable[j+1];

  double  h = x1 - x0;

  double  dx = (x - x0)/h;
  double  x2 = dx * dx;
  double  oneMinusX = 1 - dx;
  double  oneMinusX2 = oneMinusX * oneMinusX;

  double  f0 = (2. * dx + 1.) * oneMinusX2;
  double  f1 = (3. - 2. * dx) * x2;
  double  g0 =  h * dx * oneMinusX2;
  double  g1 =  - h * oneMinusX * x2;

  double answer;

  answer = f0 * y0 + f1 * y1 + g0 * d0 + g1 * d1 ;

  return answer;

} // interpolate 


double approxErrInt (double v) {

  // To use this routine, v MUST be negative and for accuracy should be < -4.

  // First approximation is e**(-v**2/2) / ( v* sqrt(2PI) )

  double errInt = exp(-v*v/2);
  errInt = - errInt / ( v*sqrt(CLHEP::twopi) );

  // correction factor of 
  // 	(1 - 1/v**2 + 3/v**4 - 3*5/v**6 + ... -3*5*7*9*11*13/v**14)

  double correction = 1;
  for ( int n = 12; n >= 0; n -= 2 ) {
    correction = 1.0 - correction*(n+1)/(v*v);
  }

  errInt *= correction;

  // This is accurate to 1 part in 2000 for v < -4, and one part in 
  // 50,000 for v < -5.  Since we are using it at v = -7.5, the accuracy is 
  // a part in 8E-8, but since this is multiplied by 3E-14, the error 
  // introduced into our returned deviates is about a 2E-21 or less.  This
  // of course is much less than the machine epsilon.

  //  cout << "errInt (" << v << ") = " << errInt << "\n";

  return errInt;
}

