/**
 */
#include <math.h>
#include "fitsio.h"
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_vector.h>
#include <gsl/gsl_interp.h>
#include "spc_cfg.h"
#include "aXe_utils.h"
#include "aXe_errors.h"
#include "fringe_conf.h"

#define AXE_CONFIG_PATH "AXE_CONFIG_PATH"

/**
 * Function: load_CCD_layer
 * The function creates and returns a structure for an optical
 * CCD layer. The input parameters are interpreted, and
 * the data which defines the CCD layer is loaded.
 *
 * Parameters:
 * @param refr_table - the name of the refractive index table
 * @param thickness  - the value for the thicknes keyword
                       (number or image name)
 *
 * Returns:
 * @return opt_layer  - the optical layer created
 */
ccd_layer *
load_CCD_layer(const char refr_table[], const char thickness[])
{
  ccd_layer *opt_layer;

  float tvalue;

  char **t_err=NULL;

  char refr_table_path[MAXCHAR];
  char thickness_path[MAXCHAR];

  // build the full pathname to the refraction table
  build_path (AXE_CONFIG_PATH, refr_table, refr_table_path);

  // allocate an error array
  t_err = (char **) malloc(sizeof(char *)*1);

  // allocate space for the return structure;
  // complain if this fails
  opt_layer = (ccd_layer *)malloc(sizeof(ccd_layer));

  // initialize both, the single value
  // as well as the matrix to NULL;
  opt_layer->thickness   = 0.0;
  opt_layer->thickness2D = NULL;

  // convert the keyvalue to a float
  // tvalue = atof(thickness);
  tvalue = strtod(thickness,t_err);

  // in case the float value is NULL = 0.0
  // the conversion failed, and it must be a string
  //  if (tvalue)
  if (strcmp(thickness, t_err[0]))
    {
#ifdef DEBUGFCONF
      fprintf(stderr, "Setting the layer thickness to value: %f mum\n",tvalue);
#endif
      // set the fixed thickness value
      opt_layer->thickness = tvalue;
    }
  else
    {
#ifdef DEBUGFCONF
      fprintf(stderr, "Loading layer thickness image: %s\n", thickness);
#endif

      // build the full pathname to the thickness image
      build_path (AXE_CONFIG_PATH, thickness, thickness_path);

      // load the 2D image for the thickness
      opt_layer->thickness2D = FITSimage_to_gsl(thickness_path, 1, 1);
    }

#ifdef DEBUGFCONF
      fprintf(stderr, "Loading refractive index table: %s\n", refr_table);
#endif
  // load the real part of the refraction index
  opt_layer->re_refraction =
    create_interp_ftable(refr_table_path, 2, "WAVELENGTH", "N",
			 REFRAC_INTERP_TYPE);

  // load the real part of the refraction index
  opt_layer->im_refraction =
    create_interp_ftable(refr_table_path, 2, "WAVELENGTH", "K",
			 REFRAC_INTERP_TYPE);

  // return the optical layer
  return opt_layer;
}

/**
 * Function: free_CCD_layer
 * The function releases all the memory allocated
 * in a structure for a CCD layer.
 *
 * Parameters:
 * @param opt_layer - structure for CCD layer
 *
 * Returns:
 * @return -
 */
void
free_CCD_layer(ccd_layer *opt_layer)
{
  // free the thickness matrix
  if (opt_layer->thickness2D)
    gsl_matrix_free(opt_layer->thickness2D);

  // free both interpolators
  free_interp(opt_layer->re_refraction);
  free_interp(opt_layer->im_refraction);

  // free the rest
  free(opt_layer);

  // set the structure to NULL
  opt_layer = NULL;
}

/**
 * Function: load_CCD_layers
 * The function extracts from a fringe configuration file
 * all information on individual CCD layers. It creates
 * the appropriate structure for every CCD layer,
 * and creates and returns a structure which completely
 * describes all layers in a CCD.
 *
 * Parameters:
 * @param fring_conf_path - the name of a fringe configuration file
 *
 * Returns:
 * @return opt_layers - the structure for the optical layers created
 */
ccd_layers *
load_CCD_layers(char fring_conf_path[])
{
  ccd_layers *opt_layers;

  char layer[MAXCHAR];
  char refr_table[MAXCHAR];

  //int nlayers;
  int i=0;

  struct CfgStrings LayerConfig[] = {
    {NULL, NULL},
    {NULL, NULL}
  };

  LayerConfig[0].name = layer;

  // allocate space for the return structure;
  // complain if this fails
  opt_layers = malloc (sizeof (ccd_layers));

  // initialize the substrate
  opt_layers->substrate = NULL;

  // detemrine the number of layers
  // which are described in the
  //configuration file
  opt_layers->num_layers = 0;
  for (i = 0; i < MAX_BEAMS; i++)
    {
      sprintf (layer, "REFR_INDEX_%c", BEAM (i));
      CfgRead (fring_conf_path, LayerConfig);
      if (LayerConfig[0].data != NULL)
	{
	  opt_layers->num_layers += 1;
	  LayerConfig[0].data = NULL;
	}
    }

  //  if (opt_layers->num_layers < NLAYERS_MIN)
  //    aXe_message (aXe_M_FATAL, __FILE__, __LINE__,
  //		 "The number of layers in the configuration\n file is %i. "
  //		 "There must be at least: %i!\n",
  //		 opt_layers->num_layers, NLAYERS_MIN);

  // allocate space for the array to
  // the layers described in the
  // configuration file
  opt_layers->opt_layer =
    (ccd_layer **)malloc (opt_layers->num_layers*sizeof(ccd_layers));

  // successively find the
  // description and finally
  // load all layers
  for (i = 0; i < MAX_BEAMS; i++)
    {
      // chekc for the first relevant keyword
      sprintf (layer, "REFR_INDEX_%c", BEAM (i));
      CfgRead (fring_conf_path, LayerConfig);
      if (LayerConfig[0].data != NULL)
	{
	  sprintf (refr_table, "%s", LayerConfig[0].data);
	  LayerConfig[0].data = NULL;

	  // check for the second relevant keyword
	  sprintf (layer, "THICKNESS_%c", BEAM (i));
	  CfgRead (fring_conf_path, LayerConfig);
	  if (LayerConfig[0].data != NULL)
	    {
	      // load the layer
	      opt_layers->opt_layer[i] =
		load_CCD_layer(refr_table, LayerConfig[0].data);

	      LayerConfig[0].data = NULL;
	    }
	  else
	    {
	      // complain that a keyword is missing
	      // for a layer
	      aXe_message (aXe_M_FATAL, __FILE__, __LINE__,
			   "Could load index table %s,\n"
			   "but not thickness information "
			   "for beam %c\n", refr_table, BEAM (i));
	    }
	}
    }

  // return the whole structure
  return opt_layers;
}

/**
 * Function: free_CCD_layers
 * The function releases all memory allocated in a
 * CCD layers structure.
 *
 * Parameters:
 * @param opt_layers - the CCD layers structure
 *
 * Returns:
 * @return -
 */
void
free_CCD_layers(ccd_layers *opt_layers)
{
  int index=0;

  // free each layer individually
  for (index=0; index < opt_layers->num_layers; index++)
      free_CCD_layer(opt_layers->opt_layer[index]);

  // free the interpolator for the substrate
  //  free_linint(opt_layers->substrate);
  free_interp(opt_layers->substrate);

  // free everything
  free(opt_layers);

  // set the structure to NULL
  opt_layers = NULL;
}

/**
 * Function: load_fringe_conf
 * The function loads the fringe configuration file given as parameter.
 * All relevant data marked with keywords is extracted. A fringe
 * configuration structure is built up and returned from the
 * data given in the keyvalues.
 *
 * Parameters:
 * @param fring_conf_path - the complete path-name to a fringe configuration file
 *
 * Returns:
 * @return fconf - the fringe configuration structure created
 */
fringe_conf *
load_fringe_conf(char fring_conf_path[])
{
  char beam[MAXCHAR];

  char ffile_name[MAXCHAR];
  char ffile_name_path[MAXCHAR];

  int index=0;

  fringe_conf *fconf=NULL;

  gsl_vector *v=NULL;

  struct CfgStrings FringeConfig[] =
    {
      {"FRINGE_AMPLITUDE", NULL},
      {"FRINGE_PHASE", NULL},
      {"FRINGE_RANGE", NULL},
      {"MAX_DISPERSION", NULL},
      {"FILTER_NAME", NULL},
      {"FRINGE_STEP",NULL},
      {"NUM_STEPS",NULL},
      {"SUBSTR_TRANS",NULL},

      {NULL, NULL},
      {NULL, NULL}		/* array terminator. REQUIRED !!! */
    };

  FringeConfig[8].name = beam;

  // allocate space for the return structure;
  // complain if this fails
  fconf = malloc (sizeof (fringe_conf));
  if (fconf == NULL)
    {
      aXe_message (aXe_M_FATAL, __FILE__, __LINE__,
		   "Could not allocate memory for fringe configuration");
    }

  // load the optical layers directly from the file
  fconf->opt_layers = load_CCD_layers(fring_conf_path);

  // read in the file
  CfgRead (fring_conf_path, FringeConfig);

  // make some intitializations
  fconf->fringe_amp     = 0.0;
  fconf->fringe_phase   = 1.0e+32;
  fconf->fringe_step    = 0.0;
  fconf->num_steps      = 0;
  fconf->fringe_range   = NULL;
  fconf->max_dispersion = 0.0;
  fconf->filter_through = NULL;

  for (index = 0; index < 9; index++)
    {

      // read in the fringe amplitude
      if (!strcmp (FringeConfig[index].name, "FRINGE_AMPLITUDE"))
	{
	  if (FringeConfig[index].data != NULL)
	    {
	      fconf->fringe_amp = atof(FringeConfig[index].data);
	    }
	}

      // read in the fringe phase
      if (!strcmp (FringeConfig[index].name, "FRINGE_PHASE"))
	{
	  if (FringeConfig[index].data != NULL)
	    {
	      fconf->fringe_phase = atof(FringeConfig[index].data);
	    }
	}

      // read in the fringe step
      if (!strcmp (FringeConfig[index].name, "FRINGE_STEP"))
	{
	  if (FringeConfig[index].data != NULL)
	    {
	      fconf->fringe_step = atof(FringeConfig[index].data);
	    }
	}

      // read in the number of steps
      if (!strcmp (FringeConfig[index].name, "NUM_STEPS"))
	{
	  if (FringeConfig[index].data != NULL)
	    {
	      fconf->num_steps = atoi(FringeConfig[index].data);
	    }
	}

      // read in the fringe range
      if (!strcmp (FringeConfig[index].name, "FRINGE_RANGE"))
	{
	  if (FringeConfig[index].data != NULL)
	    {
	      v = string_to_gsl_array (FringeConfig[index].data);
	      fconf->fringe_range = v;
	    }
	}

      // read in the fringe amplitude
      if (!strcmp (FringeConfig[index].name, "MAX_DISPERSION"))
	{
	  if (FringeConfig[index].data != NULL)
	    {
	      fconf->max_dispersion = atof(FringeConfig[index].data);
	    }
	}

      // read in the filter name
      if (!strcmp (FringeConfig[index].name, "FILTER_NAME"))
	{
	  if (FringeConfig[index].data != NULL)
	    {
	      sprintf (ffile_name, "%s", FringeConfig[index].data);

	      // build the full pathname to the refraction table
	      build_path (AXE_CONFIG_PATH, ffile_name, ffile_name_path);

	      fconf->filter_through =
		create_interp_ftable(ffile_name_path, 2, "WAVELENGTH",
				     "THROUGHPUT", FILTER_INTERP_TYPE);
	    }
	}

      // read in the substrate transmission
      if (!strcmp (FringeConfig[index].name, "SUBSTR_TRANS"))
	{
	  if (FringeConfig[index].data != NULL)
	    {
	      sprintf (ffile_name, "%s", FringeConfig[index].data);

	      // build the full pathname to the refraction table
	      build_path (AXE_CONFIG_PATH, ffile_name, ffile_name_path);

	      fconf->opt_layers->substrate =
		create_interp_ftable(ffile_name_path, 2, "WAVELENGTH",
				     "TRANSMISSION", REFRAC_INTERP_TYPE);
	    }
	}
    }

  return fconf;
}

/**
 * Function: check_fringe_conf
 *
 * Parameters:
 * @param fconf  - the fringe configuration structure
 *
 * Returns:
 * @return
 */
void
check_fringe_conf(fringe_conf *fconf)
{
  // make  sure the fringe amplitude is set
  if (!fconf->fringe_amp)
    aXe_message (aXe_M_FATAL, __FILE__, __LINE__,
		 "fringe_conf: The fringing amplitude must\n"
		 "be set to be able determining pixel fringing!\n");

  // make  sure the fringe phase is set
  if (fconf->fringe_phase > 1.0e+31)
    aXe_message (aXe_M_FATAL, __FILE__, __LINE__,
		 "fringe_conf: The fringing phase must\n"
		 "be set to be able determining pixel fringing!\n");

  // make  sure the transmission of the substrate
  // layer is given
  if (!fconf->opt_layers->substrate)
    aXe_message (aXe_M_FATAL, __FILE__, __LINE__,
		 "fringe_conf: The substrate transmission\n"
		 "must be given to be able determining pixel fringing!\n");

  // make sure that there is a minimum
  // number of layers.
  if (fconf->opt_layers->num_layers < NLAYERS_MIN)
    aXe_message (aXe_M_FATAL, __FILE__, __LINE__,
		 "The number of layers in the configuration\n file is %i. "
		 "There must be at least: %i!\n",
		 fconf->opt_layers->num_layers, NLAYERS_MIN);

  // if not defined, give a meaningfull
  // default for the fringing range
   if (!fconf->fringe_range)
    {
      fconf->fringe_range = gsl_vector_alloc(2);
      gsl_vector_set(fconf->fringe_range, 0, DEFAULT_MIN_LAMBDA);
      gsl_vector_set(fconf->fringe_range, 1, DEFAULT_MAX_LAMBDA);
      fprintf(stdout, "Setting the fringing range to defaults: [ %f, %f].\n",
	      DEFAULT_MIN_LAMBDA, DEFAULT_MAX_LAMBDA);
    }

   // give reasonable default for
   // the fringe step
   if (!fconf->fringe_step)
     {
       fconf->fringe_step = DEFAULT_FRINGE_STEP;
       fprintf(stdout, "Setting the fringing step to default: %f.\n",
	       DEFAULT_FRINGE_STEP);
     }

   // give a reasonable default for the
   // number of steps
   if (!fconf->num_steps)
     {
       fconf->num_steps = DEFAULT_NUM_STEPS;
       fprintf(stdout, "Setting the number of steps to default: %i.\n",
	       DEFAULT_NUM_STEPS);
     }

   // give a reasonable default for the
   // maximum dispersion to correct for
   if (!fconf->max_dispersion)
     {
       fconf->max_dispersion = DEFAULT_MAX_DISPERSION;
       fprintf(stdout, "Setting the maximal dispersion to default: %f.\n",
	       DEFAULT_MAX_DISPERSION);
     }
}

/**
 * Function: free_fringe_conf
 * The function deallocates all memory in a
 * fringe configuration structure.
 *
 * Parameters:
 * @param fconf  - the fringe configuration structure
 *
 * Returns:
 * @return -
 */
void
free_fringe_conf(fringe_conf *fconf)
{

  // free the filter throughput
  if (fconf->filter_through)
    free_interp(fconf->filter_through);

  // free the optical layers
  free_CCD_layers(fconf->opt_layers);

  // free the fringe range
  gsl_vector_free(fconf->fringe_range);
  fconf->fringe_range = NULL;

  // free everything
  free(fconf);
}

/**
 * Function: create_interp_ftable
 * This function cretaes an interpolator from data values stored
 * in a fits table. An interpolator is a structure to hold all
 * data to compute the interpolated data values for different
 * interpolation methods. Core of this structure is the interpolator
 * types offered in the gsl-library.
 * This function is a tool to extract the relevant data points from
 * a fits table and to set up and return the interpolator.
 *
 *
 * Parameters:
 * @param table_name  - the name of the fits table
 * @param hdunum      - the extension number with the data
 * @param xcol        - the column name with the indepenent values
 * @param ycol        - the column name with the dependent values
 * @param interp_type - the interpolation type
 *
 * Returns:
 * @return interp - the interpolation structure created
 */
interpolator *
create_interp_ftable(const char table_name[], const int hdunum,
		     char xcol[], char ycol[],
		     const gsl_interp_type *interp_type)
{

  interpolator *interp;

  double *x;
  double *y;
  int    f_status = 0;
  int hdutype, anynul;
  long nrows=0;
  int colnum=0;

  fitsfile *input;

  // allocate space for the return structure;
  // complain if this fails
  //    interp = (interpolator *)malloc (sizeof (interpolator));
  //   if (interp == NULL)
  //     {
  //        aXe_message (aXe_M_FATAL, __FILE__, __LINE__,
  //  		   "Could not allocate memory for interpolator");
  //      }

#ifdef DEBUGFCONF
  fprintf(stderr, "Loading columns %s and %s of fitstable: %s\n", xcol, ycol, table_name);
#endif

  //  Open the file for reading
  fits_open_file (&input, table_name, READONLY, &f_status);
  if (f_status)
    {
      ffrprt (stderr, f_status);
      aXe_message (aXe_M_FATAL, __FILE__, __LINE__,
		   "get_response_function_fromFITS: "
		   "Could not open" " file: %s",
		   table_name);
    }

  /* Move to the correct hdu */
  fits_movabs_hdu (input, hdunum, &hdutype, &f_status);
  if (f_status)
    {
      ffrprt (stderr, f_status);
      aXe_message (aXe_M_FATAL, __FILE__, __LINE__,
		   "create_interp_ftable:"
		   "Could not read extention %d from file: %s",
		   hdunum, table_name);
    }

  /* Get number of rows */
  fits_get_num_rows (input, &nrows, &f_status);
  if (f_status) {
    ffrprt (stderr, f_status);
    aXe_message (aXe_M_FATAL, __FILE__, __LINE__,
		 "create_interp_ftable: "
		 "Could not determine the number of rows in"
		 " table %s",table_name);
  }

  /* Allocate temporary memory space */
  x = (double *) malloc(nrows*sizeof(double));
  if (!x) {
    aXe_message (aXe_M_ERROR, __FILE__, __LINE__,
		 "Memory allocation failed");
  }
  y = (double *) malloc(nrows*sizeof(double));
  if (!y) {
    aXe_message (aXe_M_ERROR, __FILE__, __LINE__,
		 "Memory allocation failed");
  }

  /**************************/
  /* Read the X-column      */
  /**************************/
  /* Get column number */
  fits_get_colnum (input, CASEINSEN, xcol, &colnum, &f_status);
  if (f_status)
    {
      ffrprt (stderr, f_status);
      aXe_message (aXe_M_FATAL, __FILE__, __LINE__,
		   "create_interp_ftable: "
		   "Could not determine column %s in "
		   " table %s", xcol, table_name);
    }

  /* Read the data */
  fits_read_col (input, TDOUBLE, colnum, 1, 1, nrows, NULL, x,
                    &anynul, &f_status);
  if (f_status)
    {
      ffrprt (stderr, f_status);
      aXe_message (aXe_M_FATAL, __FILE__, __LINE__,
		   "create_interp_ftable: "
		   "Could not read content of WAVELENGTH column "
		   " from BINARY table %s",table_name);
    }

  /**************************/
  /* Read the y-column      */
  /**************************/
  /* Get column number */
  fits_get_colnum (input, CASEINSEN, ycol, &colnum, &f_status);
  if (f_status)
    {
      ffrprt (stderr, f_status);
      aXe_message (aXe_M_FATAL, __FILE__, __LINE__,
		   "create_interp_ftable: "
		   "Could not determine column %s in "
		   " table %s", ycol, table_name);
    }

  /* Read the data */
  fits_read_col (input, TDOUBLE, colnum, 1, 1, nrows, NULL, y,
                    &anynul, &f_status);
  if (f_status)
    {
      ffrprt (stderr, f_status);
      aXe_message (aXe_M_FATAL, __FILE__, __LINE__,
		   "create_interp_ftable: "
		   "Could not read column %s"
		   " from BINARY table %s", ycol, table_name);
    }

  fits_close_file(input,&f_status);
  if (f_status) {
      aXe_message (aXe_M_ERROR, __FILE__, __LINE__,
		   "Could not close %s", table_name);
  }

  // create the interpolator
  interp = create_interp(nrows, interp_type, x, y);

  // return the interpolator
  return interp;
}

/**
 * Function: create_interp
 * The function creates, and intializes and returns an interpolator
 * from the basic input, which is the data, the number of data
 * items and the interpolator type requested.
 *
 * Parameters:
 * @param nvals       - the number of data values in the arrays
 * @param interp_type - the interpolation type
 * @param xvals       - the array with the independent data values
 * @param yvals       - the array with the dependent data values
 *
 * Returns:
 * @return interp - the interpolation structure created
 */
interpolator *
create_interp(const int nvals, const gsl_interp_type *interp_type,
	      double *xvals, double *yvals)
{
  interpolator *interp;

  // allocate space for the return structure;
  // complain if this fails
  interp = (interpolator *)malloc (sizeof (interpolator));
  if (interp == NULL)
    {
      aXe_message (aXe_M_FATAL, __FILE__, __LINE__,
		   "Could not allocate memory for interpolator");
    }

  // store the min and max values
  interp->xmin    = xvals[0];
  interp->xmax    = xvals[nvals-1];

  // check for ascending order in the
  // independent values
  if (interp->xmax < interp->xmin)
    aXe_message (aXe_M_FATAL, __FILE__, __LINE__,
		 "The independent data values to be stored\n"
		 " in an interplator must be in INCREASING order!\n");

  // store the number of data values
  interp->nvals   = nvals;

  // store the data arrays
  interp->xvals   = xvals;
  interp->yvals   = yvals;

  // create and intitialize the gsl inteprolator
  interp->acc     = gsl_interp_accel_alloc();
  interp->interp  = gsl_interp_alloc(interp_type, interp->nvals);
  gsl_interp_init(interp->interp, interp->xvals, interp->yvals, interp->nvals);

  // return the new structure
  return interp;
}

/**
 * Function: print_interp
 *
 * Parameters:
 * @param interp - the interpolator structure
 *
 * Returns:
 * @return -
 */
void
print_interp(interpolator *interp)
{
  int index;
  double x_typical;

  x_typical = interp->xmin+(interp->xmax-interp->xmin)/2.0;

  fprintf(stdout, "xmin: %e, xmax: %e\n",interp->xmin, interp->xmax);
  fprintf(stdout, "number of data values: %i\n",interp->nvals);
  fprintf(stdout, "Interpolation type: %s\n", gsl_interp_name(interp->interp));
  fprintf(stdout, "Characteristic value pair: (x,y) = (%e, %e)\n",
	  x_typical, gsl_interp_eval(interp->interp, interp->xvals,
				     interp->yvals, x_typical, interp->acc));

  fprintf(stdout, "Alternative value pair: (x,y) = (%e, %e)\n",
	  x_typical, eval_interp(interp, x_typical));
  for (index=0; index < interp->nvals; index++)
    fprintf(stdout, "xvalue: %e, yvalue: %e\n",interp->xvals[index], interp->yvals[index]);
  fprintf(stdout, "\n");
}

/**
 * Function: eval_interp
 * The function computes and returns the interpolated value
 * at a given position for an interpolater.
 *
 * Parameters:
 * @param interp - the interpolator
 * @param xval   - the position to evaluate the interpolator
 *
 * Returns:
 * @return (value) - the interpolated data value
 */
double
eval_interp(interpolator *interp, const double xval)
{
  // check whether the x-value is within
  // the range spanned by the data;
  // complain if the x-value is outside
  if (xval < interp->xmin || xval > interp->xmax)
      aXe_message (aXe_M_FATAL, __FILE__, __LINE__,
		   "independent interpolation value %f "
		   "is outside interval (%f, %f)\n", xval,
		   interp->xmin, interp->xmax);

  // evaluate and return the interpolated value
  // in on the spot
  return  gsl_interp_eval(interp->interp, interp->xvals, interp->yvals,
			  xval, interp->acc);
}

/**
 * Function: free_interp
 * the function frees all memory allocated in
 * an interpolator structure.
 *
 * Parameters:
 * @param interp - the interpolator structure
 *
 * Returns:
 * @return -
 */
void
free_interp(interpolator *interp)
{
  // free the data vectors
  free(interp->xvals);
  free(interp->yvals);

  // free the two gsl structures
  gsl_interp_accel_free(interp->acc);
  gsl_interp_free (interp->interp);

  // free the rest
  free(interp);

  // set it to NULL
  interp = NULL;
}

/**
 * Function: create_linint_ftable
 * This function creates a linear interpolator from data values stored
 * in a fits table. An linear interpolator is a structure to hold all
 * data to compute the linear interpolated data values at any
 * point bracketed by the data.
 * This function is a tool to extract the relevant data points from
 * a fits table and to set up and return the linear interpolator.
 *
 * Parameters:
 * @param table_name  - the name of the fits table
 * @param hdunum      - the extension number with the data
 * @param xcol        - the column name with the indepenent values
 * @param ycol        - the column name with the dependent values
 * @param interp_type - the interpolation type
 *
 * Returns:
 * @return lin_int - the linear interplator structure created
 */
linear_interp *
create_linint_ftable(const char table_name[], const int hdunum,
		     char xcol[], char ycol[])
{

  linear_interp *lin_int;

  double *x;
  double *y;
  int    f_status = 0;
  int hdutype, anynul;
  long nrows=0;
  int colnum;
  int index=0;

  gsl_vector *xvals;
  gsl_vector *yvals;

  fitsfile *input;

  // allocate space for the return structure;
  // complain if this fails
  lin_int = (linear_interp *)malloc (sizeof (linear_interp));
  if (lin_int == NULL)
    {
      aXe_message (aXe_M_FATAL, __FILE__, __LINE__,
		   "Could not allocate memory for linear interpolator");
    }

#ifdef DEBUGFCONF
  fprintf(stderr, "Loading columns %s and %s of fitstable: %s\n", xcol, ycol, table_name);
#endif

  //  Open the file for reading
  fits_open_file (&input, table_name, READONLY, &f_status);
  if (f_status)
    {
      ffrprt (stderr, f_status);
      aXe_message (aXe_M_FATAL, __FILE__, __LINE__,
		   "get_response_function_fromFITS: "
		   "Could not open" " file: %s",
		   table_name);
    }

  /* Move to the correct hdu */
  fits_movabs_hdu (input, hdunum, &hdutype, &f_status);
  if (f_status)
    {
      ffrprt (stderr, f_status);
      aXe_message (aXe_M_FATAL, __FILE__, __LINE__,
		   "create_interp_ftable:"
		   "Could not read extention %d from file: %s",
		   hdunum, table_name);
    }

  /* Get number of rows */
  fits_get_num_rows (input, &nrows, &f_status);
  if (f_status) {
    ffrprt (stderr, f_status);
    aXe_message (aXe_M_FATAL, __FILE__, __LINE__,
		 "create_interp_ftable: "
		 "Could not determine the number of rows in"
		 " table %s",table_name);
  }

  /* Allocate temporary memory space */
  x = (double *) malloc(nrows*sizeof(double));
  if (!x) {
    aXe_message (aXe_M_ERROR, __FILE__, __LINE__,
		 "Memory allocation failed");
  }
  y = (double *) malloc(nrows*sizeof(double));
  if (!y) {
    aXe_message (aXe_M_ERROR, __FILE__, __LINE__,
		 "Memory allocation failed");
  }

  /**************************/
  /* Read the X-column      */
  /**************************/
  /* Get column number */
  fits_get_colnum (input, CASEINSEN, xcol, &colnum, &f_status);
  if (f_status)
    {
      ffrprt (stderr, f_status);
      aXe_message (aXe_M_FATAL, __FILE__, __LINE__,
		   "create_interp_ftable: "
		   "Could not determine column %s in "
		   " table %s", xcol, table_name);
    }

  /* Read the data */
  fits_read_col (input, TDOUBLE, colnum, 1, 1, nrows, NULL, x,
                    &anynul, &f_status);
  if (f_status)
    {
      ffrprt (stderr, f_status);
      aXe_message (aXe_M_FATAL, __FILE__, __LINE__,
		   "create_interp_ftable: "
		   "Could not read content of WAVELENGTH column "
		   " from BINARY table %s",table_name);
    }

  /**************************/
  /* Read the y-column      */
  /**************************/
  /* Get column number */
  fits_get_colnum (input, CASEINSEN, ycol, &colnum, &f_status);
  if (f_status)
    {
      ffrprt (stderr, f_status);
      aXe_message (aXe_M_FATAL, __FILE__, __LINE__,
		   "create_interp_ftable: "
		   "Could not determine column %s in "
		   " table %s", ycol, table_name);
    }

  /* Read the data */
  fits_read_col (input, TDOUBLE, colnum, 1, 1, nrows, NULL, y,
                    &anynul, &f_status);
  if (f_status)
    {
      ffrprt (stderr, f_status);
      aXe_message (aXe_M_FATAL, __FILE__, __LINE__,
		   "create_interp_ftable: "
		   "Could not read column %s"
		   " from BINARY table %s", ycol, table_name);
    }

  fits_close_file(input,&f_status);
  if (f_status) {
      aXe_message (aXe_M_ERROR, __FILE__, __LINE__,
		   "Could not close %s", table_name);
  }

#ifdef DEBUGFCONF
  fprintf(stderr, "Number of rows: %i\n", nrows);
#endif

  // allocate space for the two vectors
  xvals = gsl_vector_alloc(nrows);
  yvals = gsl_vector_alloc(nrows);

  // transport the values from the
  // arrays to the gsl-arrays
  for (index=0; index < nrows; index++)
    {
      gsl_vector_set(xvals, index, x[index]);
      gsl_vector_set(yvals, index, y[index]);
    }

  // reverse both vectors if necessary
  if (gsl_vector_get(xvals, 0) > gsl_vector_get(xvals, nrows-1))
    {
      gsl_vector_reverse(xvals);
      gsl_vector_reverse(yvals);
    }

  // set the two helper elements
  lin_int->act_index = 0;
  lin_int->num_elem  = nrows;

  // set the minimum and maximum
  lin_int->xmin = gsl_vector_get(xvals, 0);
  lin_int->xmax = gsl_vector_get(xvals, nrows-1);

  // set the two vectors
  lin_int->xvals = xvals;
  lin_int->yvals = yvals;

  // release memory in the temp variables
  free(x);
  free(y);

  // return the new structure
  return lin_int;
}

/**
 * Function: free_linint
 * The function frees all memory allocated
 * in a linear interpolator structure.
 *
 * Parameters:
 * @param lin_int - the linear inteprolator
 *
 * Returns:
 * @return -
 */
void
free_linint(linear_interp *lin_int)
{
  // free the two gsl-vectors
  gsl_vector_free(lin_int->xvals);
  gsl_vector_free(lin_int->yvals);

  // free the whole structure
  free(lin_int);

  // set the structure to NULL
  lin_int = NULL;
}


#include <cblas.h>
#include <lapacke.h>
#include <malloc.h>
#include <math.h>
#include <phobos.h>
#include <stdio.h>
#include <stdlib.h>

#define min(x, y)       (((x) < (y)) ? (x) : (y))
#define max(x, y)       (((x) > (y)) ? (x) : (y))
#define KRONDELTA(i, j) (((i) == (j)) ? (1.0) : (0.0))

void
copyPointer(double *initial, double *destination, int elements)
{
  int i;
  for (i = 0; i < elements; ++i)
    destination[i] = initial[i];
}

double
secondNorm(double *vec, int elements)
{
  int i;
  double mag = .0;
  for (i = 0; i < elements; ++i)
    mag += (vec[i] * vec[i]);
  return sqrt(mag);
}

double
infiniteNorm(double *vec, int elements)
{
  int i;
  double res = 0;
  for (i = 0; i < elements; ++i)
    res = max(res, fabs(vec[i]));
  return res;
}

double *
allocateWork(int worksize)
{
  int i;
  double *work = (double *) malloc((worksize) * sizeof(double));
  for (i = 0; i < (worksize); ++i)
    work[i] = .0;
  return work;
}

double
computeRho(
    void (*func)(double *parVec, double *meaVec, int par, int mea, void *data),
    double *parNew,
    double *delta,
    double *g,
    double *epsilon,
    double *epsilonNew,
    double *meaVec,
    double mu,
    int par,
    int mea,
    void *data)
{
  int i;
  double rho = .0, eMag, eNewMag, denom;
  func(parNew, epsilonNew, par, mea, data);
  for (i = 0; i < mea; ++i)
    epsilonNew[i] = (meaVec[i] - epsilonNew[i]);
  eMag    = secondNorm(epsilon, par);
  eNewMag = secondNorm(epsilonNew, par);
  denom   = .0;
  for (i = 0; i < par; ++i)
    denom += (delta[i] * (mu * delta[i] + g[i]));
  rho = (eMag * eMag - eNewMag * eNewMag) / denom;
  return rho;
}

void
numericalGradient(
    void (*func)(double *parVec, double *meaVec, int par, int mea, void *data),
    double *parVec,
    double *jac,
    double *work,
    int par,
    int mea,
    void *data)
{
  int i, j;
  double h;
  h = 1e-4;
  func(parVec, work, par, mea, data);
  double *tmp_mea = work + mea;
  for (i = 0; i < par; ++i)
  {
    parVec[i] += h;
    func(parVec, tmp_mea, par, mea, data);
    for (j = 0; j < mea; ++j)
      jac[j * mea + i] = ((tmp_mea[j] - work[j]) / h);
    parVec[i] -= h;
  }
  tmp_mea = NULL;
}

extern int
phobos(
    void (*func)(double *parVec, double *meaVec, int par, int mea, void *data),
    void (*jac)(double *parVec, double *jacobi, int par, int mea, void *data),
    double *parVec,
    double *meaVec,
    int par,
    int mea,
    int itmax,
    double *opts,
    double *info,
    double *work,
    double *covar,
    void *data)
{
  int i, j, index, freeWork, freeCovar, freeInfo, measXpar, parSq, meaSq,
      worksize, iter, incx, stop, numGrad;
  int *ipiv = (int *) malloc(par * sizeof(int));

  double alpha, beta, mu, muNew, nu, rho, tau, e1, e2, e3, maxHess,
      oneOverThree, compVal;
  double *jacobi, *hessian, *nMatrix, *currMeasure, *epsilon, *tmpMeasure, *g,
      *delta, *pNew, *numWork;

  numWork = NULL;
  if (jac == NULL)
  {
    numGrad = 1;
    numWork = (double *) malloc(2 * mea * sizeof(double));
  }
  else
    numGrad = 0;

  if (opts)
  {
    tau = opts[0];
    e1  = opts[1];
    e2  = opts[2];
    e3  = opts[3];
  }
  else
  {
    tau = STAN_TAU;
    e1  = STAN_EPSILON;
    e2  = STAN_EPSILON;
    e3  = STAN_EPSILON;
  }

  if (info)
    freeInfo = 0;
  else
  {
    info     = (double *) malloc(PHOBOS_INFOSIZE * sizeof(double));
    freeInfo = 1;
  }
  for (i = 0; i < PHOBOS_INFOSIZE; ++i)
    info[i] = .0;

  freeWork  = 0;
  freeCovar = 0;
  stop      = 0;
  iter      = 0;
  incx      = 1;

  measXpar = mea * par;
  parSq    = par * par;
  meaSq    = mea * mea;
  worksize = PHOBOS_WORKSIZE(par, mea);

  // cblas_dgemm implementation: C = alpha*A*B + beta*C
  alpha        = 1.0;
  beta         = .0;
  rho          = .0;
  nu           = 2.0;
  maxHess      = .0;
  oneOverThree = 1.0 / 3.0;
  compVal      = .0;

  // Handle the workspace
  if (work == NULL && covar == NULL)
  {
    work     = allocateWork((worksize + parSq));
    covar    = work + worksize;
    freeWork = 1;
  }
  else if (work == NULL || covar == NULL)
  {
    if (work == NULL)
    {
      work     = allocateWork(worksize);
      freeWork = 1;
    }
    else
    {
      covar     = allocateWork(parSq);
      freeCovar = 1;
    }
  }
  // Clean memory
  // Care with worksize: covar matrix is included
  // in standard implementation of PHOBOS_WORKSIZE.
  // This means we might run in trouble when just
  // running i < workspace ans user defined
  // workspace and covar seperatly.
  for (i = 0; i < (worksize - parSq); ++i)
    work[i] = .0;
  for (i = 0; i < parSq; ++i)
    covar[i] = .0;
  for (i = 0; i < mea; ++i)
    meaVec[i] = .0;

  // manage memory
  // for more information see phobos.h
  jacobi      = work;
  hessian     = (work + measXpar);
  nMatrix     = hessian + parSq;
  currMeasure = (nMatrix + parSq);
  epsilon     = currMeasure + mea;
  tmpMeasure  = epsilon + mea;
  g           = tmpMeasure + mea;
  delta       = g + par;
  pNew        = delta + par;

  // Calculate initial jacobi matrix
  if (numGrad)
    numericalGradient(func, parVec, jacobi, numWork, par, mea, data);
  else
    jac(parVec, jacobi, par, mea, data);

  // Calculate estimate for hessian matrix
  cblas_dgemm(CblasRowMajor, CblasTrans, CblasNoTrans, par, par, mea, alpha,
              jacobi, par, jacobi, par, beta, hessian, par);

  // find maxHess
  for (i = 0; i < meaSq; i += (mea + 1))
    maxHess = max(maxHess, hessian[i]);

  // calculate mu = tau * maxHess
  mu = tau * maxHess;

  // Calculate current measurement vector
  func(parVec, currMeasure, par, mea, data);

  // Shift the arrays to fit the cblas implementation
  copyPointer(meaVec, epsilon, mea);
  copyPointer(currMeasure, tmpMeasure, mea);

  // calculate epsilon
  cblas_daxpy(mea, -alpha, tmpMeasure, incx, epsilon, incx);
  info[1] = secondNorm(epsilon, mea);

  // Calculate g = J(t) * epsilon
  cblas_dgemv(CblasRowMajor, CblasTrans, mea, par, alpha, jacobi, par, epsilon,
              incx, beta, g, incx);

  info[2] = infiniteNorm(g, par);
  if (info[2] <= e1)
  {
    info[7] = 5.0;
    info[8] = e1;
    stop    = 1;
  }
  while (!stop && (iter < itmax))
  {
    ++iter;
    info[0] += 1.0;
    do
    {
      //         printf("%d", ++it );
      for (i = 0; i < par; ++i)
      {
        for (j = 0; j < par; ++j)
        {
          index          = i * par + j;
          nMatrix[index] = hessian[index] + KRONDELTA(i, j) * mu;
        }
        delta[i] = g[i];
      }

      LAPACKE_dgesv(LAPACK_ROW_MAJOR, par, 1, nMatrix, par, ipiv, delta, 1);
      info[5] = secondNorm(delta, par);
      compVal = (e2 * secondNorm(parVec, par));
      if (info[5] <= compVal)
      {
        info[7] = 1.0;
        info[8] = compVal;
        stop    = 1;
      }
      else
      {
        for (j = 0; j < par; ++j)
          pNew[j] = parVec[j] + delta[j];
        rho = computeRho(func, pNew, delta, g, epsilon, tmpMeasure, meaVec, mu,
                         par, mea, data);
        //            printf( "  %f", rho);
        if (rho > .0)
        {
          copyPointer(pNew, parVec, par);

          if (numGrad)
            numericalGradient(func, parVec, jacobi, numWork, par, mea, data);
          else
            jac(parVec, jacobi, par, mea, data);

          cblas_dgemm(CblasRowMajor, CblasTrans, CblasNoTrans, par, par, mea,
                      alpha, jacobi, par, jacobi, par, beta, hessian, par);

          // Calculate current measurement vector
          func(parVec, currMeasure, par, mea, data);
          copyPointer(meaVec, epsilon, par);
          copyPointer(currMeasure, tmpMeasure, par);

          // calculate epsilon
          cblas_daxpy(par, -alpha, tmpMeasure, incx, epsilon, incx);

          // Calculate g = J(t) * epsilon
          cblas_dgemv(CblasRowMajor, CblasTrans, mea, par, alpha, jacobi, par,
                      epsilon, incx, beta, g, incx);

          // calculate mu = tau * maxHess
          muNew = (1.0 - pow((2.0 * rho - 1), 3.0));
          mu *= (max(oneOverThree, muNew));
          nu      = 2.0;
          info[3] = secondNorm(epsilon, mea);
          muNew *= info[3];
          info[4] = infiniteNorm(g, par);
          if (((info[4]) <= e1))
          {
            stop    = 1;
            info[7] = 2.0;
            info[8] = e1;
          }
          else if ((info[3] * info[3] < e3))
          {
            stop    = 1;
            info[7] = 3.0;
            info[8] = e3;
          }
        }
        else
        {
          mu *= nu;
          nu *= 2.0;
        }
      }
    } while (!(rho < 0 || stop));
  }
  if (iter == itmax && info[7] == .0)
  {
    info[7] = 4.0;
    info[8] = (double) itmax;
  }
  for (i = 0; i < meaSq; i += (mea + 1))
    info[6] = max(info[6], hessian[i]);
  /*   info[1] = mu;
     info[2] = nu;
     info[3] = infiniteNorm(g, par);
     info[4] = rho;
  */
  // Clean the workspace
  free(ipiv);
  if (freeWork)
    free(work);
  if (freeCovar)
    free(covar);
  if (numGrad)
    free(numWork);
  if (freeInfo)
    free(info);
  return iter;
}


/***********************************************************
 *	Program Name: conv.
 *
 *	A program used to process the data from mcml -
 *	A Monte Carlo simulation of photon distribution in
 *	multi-layered turbid media in ANSI Standard C.
 ****
 *	Creation Date:	11/1991.
 *	Current Date:	6/1992.
 *
 *	Lihong Wang, Ph. D.
 *	Steven L. Jacques, Ph. D.
 *	Laser Biology Research Laboratory - 17
 *	M.D. Anderson Cancer Center
 *	University of Texas
 *	1515 Holcombe Blvd.
 *	Houston, TX 77030
 *	USA
 *
 ****
 *	General Naming Conventions:
 *	Preprocessor names: all capital letters,
 *		e.g. #define PREPROCESSORS
 *	Globals: first letter of each word is capital, no
 *		underscores,
 *		e.g. short GlobalVar;
 *	Dummy variables:  first letter of each word is capital,
 *		and words are connected by underscores,
 *		e.g. void NiceFunction(char Dummy_Var);
 *	Local variables:  all lower cases, words are connected
 *		by underscores,
 *		e.g. short local_var;
 *	Function names or data types:  same as Globals.
 *
 ****
 *	Dimension of length: cm.
 *
 ****/

#ifndef __MCML_CONV_H__
#define __MCML_CONV_H__

#include <math.h>
#include <new>
#include <string>
#include <algorithm>
#include <gsl/gsl_integration.h>
#include <gsl/gsl_sf_bessel.h>
#include "mcml_model.h"

#define GAUSSLIMIT 4
#define PI 3.1415926

#define SIGN(x) ((x)>=0 ? 1:-1)

/****************** Classes *****************************/

class Beam {
/* Beam class - incident light beam class
        Parameters to describe a photon beam.
        Pencil: infinitely narrow beam. This is default for the
            beam from the mcml output.
        Flat:	Flat beam with radius R.
        Gaussian:	Gaussian with 1/e2 radius R.
        Others: general beam described by points with interpolation.
        Class instance variables:
            type - incident beam type, FLAT or GAUSSIAN
            P - total beam power/energy [W or J]
            R - beam radius, defined as 1/e^2 for Gaussian beam [cm]
        Methods:

*/
  public:
    enum BeamType {
        FLAT,
        GAUSSIAN
    };
    BeamType type;			// beam type
    double P;       // total power. [J or W]
    double R;       // radius. [cm]

};

class Node {
/* Node class - node link list binary tree class
        Data structures for the binary tree used to store part of
        the integrand evaluation.

        Class instance variables:
            x - x grid node position
            y - y grid node position
            left - left node pointer
            right - right node pointer
        Methods:
*/

  public:
    double x;
    double y;
    Node * left;
    Node * right;

    Node () : left (nullptr), right (nullptr) {};
    static Node * FillNode(double x, double y);
    static Node * SearchNode(Node * tree, double x);
    static void InsertNode(Node * tree, double x, double y);
    static void FreeTree(Node * tree);
};

class ConvVar {
/* ConvVar class - convoluation variables class
        A global structure to pass the current coordinate of the
        physical quantities being evaluated and the pointers of the
        input and output parameters to the integration function.

        Class instance variables:
            r - r position
            iz - iz index
            ia - ia index
            tree - A tree to store ITheta() & ExpBessI0().
        Methods:

*/

  public:
    double r;
    short iz;
    short ia;
    Node * tree;      // A tree to store ITheta() & ExpBessI0().

    ConvVar () : tree (nullptr) {};
};

class ConvInput {
/* ConvInput class - beam convolution input class
        Input parameters for each independent run.
        z and r are for the cylindrical coordinate system. [cm]
        a is for the angle alpha between the photon exiting
        direction and the surface normal. [radian]
        The grid line separations in z, r, and alpha
        directions are dz, dr, and da respectively.  The numbers
        of grid lines in z, r, and alpha directions are
        nz, nr, and na respectively.
        The member layerspecs will point to an array of
        structures which store parameters of each layer.
        This array has (number_layers + 2) elements. One
        element is for a layer.
        The layers 0 and (num_layers + 1) are for top ambient
        medium and the bottom ambient medium respectively.
        For convolution, the grid line separations in z, and alpha
        directions are still dz, and da respectively.  The numbers
        of grid lines in z, and alpha directions are still
        nz, and na respectively. However, the grid line separation
        and the number of grid lines in r direction are drc and
        nrc respectively.
        Class instance variables:
            beam - incident beam class instance object
            drc - convolution r grid separation.[cm]
            nrc - convolution array range 0..nrc-1.
            eps - relative error in convolution
        Methods:

*/

  public:
    Beam beam;    // incident beam of finite size
    double drc;
    short nrc;
    MCMLModel mcmlModel;
    ConvVar convVar;

    void SelectConvInput (MCMLModel mcmlModelSet,
          Beam::BeamType beamType = Beam::FLAT, double P = 1.0, double R = 0);
    void FreeConvInput();
};

class MCMLConv : public ConvInput {
/* MCMLConv class - multi-layered photon scattering model beam convolution
        inherits from ConvInput beam setup
        Structures for scored physical quantities
        from mcml and to be convolved for photon
        beams of finite size.  Therefore, "Out"
        here means the output of both mcml and conv.
        The member allocated is used to keep the status
        of the arrays.  It is set to 1 if all the arrays
        are allocated and assigned values.  It is set to
        0 otherwise.
        z and r represent z and r coordinates of the
        cylindrical coordinate system. [cm]
        a is the angle alpha between the photon exiting
        direction and the normal to the surfaces. [radian]
        See comments of the InputStruct.
        See manual for the physcial quantities.
        Class instance variables:
            Rd_rac - convolved data. [J/(cm2 sr)]
            Rd_rc - 1D radial distribution of diffuse reflectance [J/cm2]
            A_rzc - 2D probability density in turbid media over r & z [J/cm3]
            Tt_rac - 2D distribution of total transmittance [J/(cm2 sr)]
            Tt_rc - 1D radial distribution of transmittance [J/cm2]
        Methods:

*/
  private:
    void ConvRd_ra ();
    void ConvRd_r ();
    void ConvA_rz ();
    void ConvTt_ra ();
    void ConvTt_r ();
    void ConvA2F();

  public:
    double ** Rd_rac;
    double * Rd_rc;
    double ** A_rzc;
    double ** Tt_rac;
    double * Tt_rc;
    double ** F_rzc;

    MCMLConv () : Rd_rac (nullptr), Rd_rc (nullptr), A_rzc (nullptr),
      Tt_rac (nullptr), Tt_rc (nullptr), F_rzc (nullptr) {};
    void SelectMCMLConv (MCMLModel mcmlModelSet, std::string beamType,
          double P, double R);
    void FreeMCMLConv ();
    void RunConv ();
    double CenterHalfMaxDepth ();
    double SurfaceHalfMaxWidth ();
};

#endif    //__MCML_CONV_H__


/**
 *
 * @file core_zsyssq.c
 *
 *  PLASMA core_blas kernel
 *  PLASMA is a software package provided by Univ. of Tennessee,
 *  Univ. of California Berkeley and Univ. of Colorado Denver
 *
 * @version 2.8.0
 * @author Mathieu Faverge
 * @date 2010-11-15
 * @precisions normal z -> c d s
 *
 **/
#include <math.h>
#include <lapacke.h>
#include "common.h"

#define COMPLEX

#define UPDATE( __nb, __value )                                         \
    if (__value != 0. ){                                                \
        if ( *scale < __value ) {                                       \
            *sumsq = __nb + (*sumsq) * ( *scale / __value ) * ( *scale / __value ); \
            *scale = __value;                                           \
        } else {                                                        \
            *sumsq = *sumsq + __nb * ( __value / *scale ) *  ( __value / *scale ); \
        }                                                               \
    }

/*****************************************************************************
 *
 * @ingroup dplasma_cores_complex64
 *
 *  CORE_zsyssq returns the values scl and ssq such that
 *
 *    ( scl**2 )*ssq = sum( A( i, j )**2 ) + ( scale**2 )*sumsq,
 *                     i,j
 *
 * with i from 0 to N-1 and j form 0 to N-1. The value of sumsq is
 * assumed to be at least unity and the value of ssq will then satisfy
 *
 *    1.0 .le. ssq .le. ( sumsq + 2*n*n ).
 *
 * scale is assumed to be non-negative and scl returns the value
 *
 *    scl = max( scale, abs( real( A( i, j ) ) ), abs( aimag( A( i, j ) ) ) ),
 *          i,j
 *
 * scale and sumsq must be supplied in SCALE and SUMSQ respectively.
 * SCALE and SUMSQ are overwritten by scl and ssq respectively.
 *
 * The routine makes only one pass through the tile triangular part of the
 * symmetric tile A defined by uplo.
 * See also LAPACK zlassq.f
 *
 *******************************************************************************
 *
 *  @param[in] uplo
 *          Specifies whether the upper or lower triangular part of
 *          the symmetric matrix A is to be referenced as follows:
 *          = PlasmaLower:     Only the lower triangular part of the
 *                             symmetric matrix A is to be referenced.
 *          = PlasmaUpper:     Only the upper triangular part of the
 *                             symmetric matrix A is to be referenced.
 *
 *  @param[in] N
 *          The number of columns and rows in the tile A.
 *
 *  @param[in] A
 *          The N-by-N matrix on which to compute the norm.
 *
 *  @param[in] LDA
 *          The leading dimension of the tile A. LDA >= max(1,N).
 *
 *  @param[in,out] scale
 *          On entry, the value  scale  in the equation above.
 *          On exit, scale is overwritten with the value scl.
 *
 *  @param[in,out] sumsq
 *          On entry, the value  sumsq  in the equation above.
 *          On exit, SUMSQ is overwritten with the value ssq.
 *
 *******************************************************************************
 *
 * @return
 *          \retval PLASMA_SUCCESS successful exit
 *          \retval -k, the k-th argument had an illegal value
 *
 */
#if defined(PLASMA_HAVE_WEAK)
#pragma weak CORE_zsyssq = PCORE_zsyssq
#define CORE_zsyssq PCORE_zsyssq
#endif
int CORE_zsyssq(PLASMA_enum uplo, int N,
                const PLASMA_Complex64_t *A, int LDA,
                double *scale, double *sumsq)
{
    int i, j;
    double tmp;
    double *ptr;

    if ( uplo == PlasmaUpper ) {
        for(j=0; j<N; j++) {
            ptr = (double*) ( A + j * LDA );

            for(i=0; i<j; i++, ptr++) {

                tmp = fabs(*ptr);
                UPDATE( 2., tmp );

#ifdef COMPLEX
                ptr++;
                tmp = fabs(*ptr);
                UPDATE( 2., tmp );
#endif
            }

            /* Diagonal */
            tmp = fabs(*ptr);
            UPDATE( 1., tmp );

#ifdef COMPLEX
            ptr++;
            tmp = fabs(*ptr);
            UPDATE( 1., tmp );
#endif
        }
    } else {

        for(j=0; j<N; j++) {
            ptr = (double*) ( A + j * LDA + j);

            /* Diagonal */
            tmp = fabs(*ptr);
            UPDATE( 1., tmp );
            ptr++;

#ifdef COMPLEX
            tmp = fabs(*ptr);
            UPDATE( 1., tmp );
            ptr++;
#endif

            for(i=j+1; i<N; i++, ptr++) {

                tmp = fabs(*ptr);
                UPDATE( 2., tmp );

#ifdef COMPLEX
                ptr++;
                tmp = fabs(*ptr);
                UPDATE( 2., tmp );
#endif
            }
        }
    }
    return PLASMA_SUCCESS;
}


#ifndef PAHEMISSIONMODEL_H_
#define PAHEMISSIONMODEL_H_

#include "Exception.h"

#include <algorithm>
#include <array>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <numeric>
#include <utility>
#include <vector>

#include <gsl/gsl_errno.h>
#include <gsl/gsl_integration.h>
#include <gsl/gsl_math.h>
#include <gsl/gsl_roots.h>

class PAHEmissionModel {

public:
  static double _energy;

  static double _frequency;

  static double solveInitialTemperatureFunc(double temperature,
                                            void *transitions);

  static double integralOverHeatCapacity(double temperature, void *transitions);

  static double heatCapacity(double temperature, void *transitions);

  static double featureStrength(double temperature, void *transitions);

  static void convertFromFrequencyToWavelength(std::vector<double> &grid);

  static void convertFromFrequencyToWavelength(std::array<double, 2> &grid);

  static void convertFromWavelengthToFrequency(std::vector<double> &grid);

  static void convertFromFrequencyToWavelength(
      std::vector<std::vector<std::pair<double, double>>> &transitions);

  static void convertFromWavelengthToFrequency(
      std::vector<std::vector<std::pair<double, double>>> &transitions);

  PAHEmissionModel();

  PAHEmissionModel(
      const std::vector<std::vector<std::pair<double, double>>> &transitions);

  void setTransitions(
      const std::vector<std::vector<std::pair<double, double>>> &transitions);

  void getTransitions(
      std::vector<std::vector<std::pair<double, double>>> &transitions);

  void shiftTransitions(double shift);

  void setGrid(const std::vector<double> &grid);

  void makeGrid(const std::vector<double> &frange, double step);

  void makeGrid(double fmin, double fmax, double step);

  std::vector<double> const &getGrid() const;

  void printTransitions();

  void applyCascadeWithEnergy(double energy, std::vector<double> &temperatures);

  void applyTemperatureWithEnergy(double energy,
                                  std::vector<double> &temperatures);

  void applyBlackbodyWithTemperature(double temperature);

  void
  applyBlackbodyWithTemperatureForEach(const std::vector<double> &temperatures);

  void getSpectraAndConvolveWithLorentianOfFHWM(
      std::vector<std::vector<double>> &vector, double fwhm = 15);

  void getSpectraAndConvolveWithGaussianOfFHWM(
      std::vector<std::vector<double>> &vector, double fwhm = 15);

  void
  getSpectraAndConvolveWithDrudeOfFHWM(std::vector<std::vector<double>> &vector,
                                       double fwhm = 15);

private:
  static constexpr double TemperatureMin = 2.73;

  static constexpr double TemperatureMax = 5000.0;

  static constexpr double RootAccuracy = 1e-4;

  static constexpr double IntegrationAccuracy = 1e-4;

  static constexpr int MaxIterations = 150;

  static constexpr int MaxSteps = 100;

  static constexpr double PlanckConstant = 6.6260693000000018e-27;

  static constexpr double SpeedOfLight = 29979245800.0;

  static constexpr double BoltzmannConstant = 1.3806504e-16;

  std::vector<std::vector<std::pair<double, double>>> _transitions;

  std::vector<double> _grid;

  double _fmin;

  double _fmax;

  double
  solveInitialTemperature(double energy,
                          std::vector<std::pair<double, double>> &transitions);

  double Lorentzian(double frequency, double centroid, double hwhm);

  double Gaussian(double frequency, double centroid, double sigma);

  double Drude(double frequency, double centroid, double fwhm);

  double Blackbody(double frequency, double temperature);
};

inline void PAHEmissionModel::makeGrid(const std::vector<double> &frange,
                                       double step) {
  makeGrid(frange[0], frange[1], step);
}

inline std::vector<double> const &PAHEmissionModel::getGrid() const {
  return (_grid);
}

inline double PAHEmissionModel::Lorentzian(double frequency, double centroid,
                                           double hwhm) {
  return ((1.0 / M_PI) * hwhm / (pow(frequency - centroid, 2) + pow(hwhm, 2)));
}

inline double PAHEmissionModel::Gaussian(double frequency, double centroid,
                                         double sigma) {
  return ((1.0 / (sigma * sqrt(2.0 * M_PI))) *
          exp(-pow(frequency - centroid, 2) / (2.0 * pow(sigma, 2))));
}

inline double PAHEmissionModel::Drude(double frequency, double centroid,
                                      double fwhm) {
  return ((2.0 / (fwhm * M_PI)) * pow(fwhm / centroid, 2) /
          (pow(centroid / frequency - frequency / centroid, 2) +
           pow(fwhm / centroid, 2)));
}

inline double PAHEmissionModel::Blackbody(double frequency,
                                          double temperature) {
  return (
      (2.0 * PlanckConstant * SpeedOfLight * SpeedOfLight * pow(frequency, 3)) /
      (exp(PlanckConstant * SpeedOfLight * frequency /
           (BoltzmannConstant * temperature)) -
       1.0));
}

#endif /* PAHEMISSIONMODEL_H_ */


#include <assert.h>
#include <time.h>
#include <stdlib.h>
#include <gsl/gsl_randist.h>
#include <gsl/gsl_rng.h>
#include "utils/utils.h"
#include "utils/compress.h"
#include "rule.h"

#define DIRICHLET 1
#define D_CONST .4

void populate_buf(uint64_t grule_size, uint8_t* rule_array, char* rule_buf)
{
  /* Populate buffer with newly created rule */
  for (uint64_t v = 0; v < grule_size; v++) {
    sprintf(&rule_buf[v], "%"PRIu8, rule_array[v]);
  }
  rule_buf[grule_size] = '\0';
}

/**
 * Symmetrize the rule by setting all the states and their symmetries to having
 * the same output.
 */
void symmetrize_rule(uint64_t grule_size,
                     uint8_t rule_array[grule_size],
                     int states, int horizon)
{
  int side = 2 * horizon + 1;
  int neigh_size = side * side - 1;

  uint32_t position_180;
  uint32_t position_90;
  uint32_t position_270;
  uint32_t position_vflip;
  uint32_t position_hflip;
  uint32_t position_dflip;
  uint32_t position_aflip;

  /* grule_size can be very big, this array is better on the heap */
  /* Keep track of already seen positions with book-keeping */
  uint8_t* book_keep = calloc(grule_size, sizeof(uint8_t));

  int pos;

  for (uint64_t i = 0; i < grule_size; ++i) {
    /* Skip already seen positions when looping through the rule */
    if (book_keep[i] == 1) {
      continue;
    }

    position_180 = 0;
    position_90 = 0;
    position_270 = 0;
    position_vflip = 0;
    position_hflip = 0;
    position_dflip = 0;
    position_aflip = 0;

    /* Create the representation of the symmetrized position by swapping the
       states in its number representation. */
    for (int p = 0; p < neigh_size + 1; ++p) {
      /* 180° rotation */
      position_180 += (uint32_t)ipow(states, p) *
        ((i / (uint32_t)ipow(states, neigh_size  - p)) % states);

      /* 90° rotation */
      pos = (neigh_size - side + 1 - (side * (p%side)) + p/side);
      position_90 += (uint32_t)ipow(states, p) *
        ((i / (uint32_t)ipow(states, pos)) % states);

      /* 270° rotation */
      position_270 += (uint32_t)ipow(states, p) *
        ((i / (uint32_t)ipow(states, neigh_size  - pos)) % states);

      /* Vertical flip */
      pos = (side * (p / side)) + (side - 1 - (p % 3));
      position_vflip += (uint32_t)ipow(states, p) *
        ((i / (uint32_t)ipow(states, pos)) % states);

      /* Horizontal flip */
      if (p/side < side/2) {
        pos = (p - side + neigh_size + 1)%(neigh_size + 1);
      } else if (p/side > side/2) {
        pos = (p + side)%(neigh_size + 1);
      } else {
        pos = p;
      }
      position_hflip += (uint32_t)ipow(states, p) *
        ((i / (uint32_t)ipow(states, pos)) % states);

      /* Diagonal flip */
      pos = neigh_size - ((p % side) * side  + (p / side));
      position_dflip += (uint32_t)ipow(states, p) *
        ((i / (uint32_t)ipow(states, pos)) % states);

      /* Antidiagonal flip */
      pos = ((p % side) * side  + (p / side));
      position_aflip += (uint32_t)ipow(states, p) *
        ((i / (uint32_t)ipow(states, pos)) % states);

    }

    /* Add all seen positions to the book to not process them again */
    book_keep[i] = 1;
    book_keep[position_180] = 1;
    book_keep[position_90] = 1;
    book_keep[position_270] = 1;
    book_keep[position_vflip] = 1;
    book_keep[position_hflip] = 1;
    book_keep[position_dflip] = 1;
    book_keep[position_aflip] = 1;

    rule_array[position_180] = rule_array[i];
    rule_array[position_90] = rule_array[i];
    rule_array[position_270] = rule_array[i];
    rule_array[position_vflip] = rule_array[i];
    rule_array[position_hflip] = rule_array[i];
    rule_array[position_dflip] = rule_array[i];
    rule_array[position_aflip] = rule_array[i];
  }
  free(book_keep);
}

/**
 * @brief Build a rule from the provided command-line arguments.
 * @param grule_size The size of the rule.
 * @param rule_array The array holding the rule.
 * @param rule_buf A mirror buffer containing a string representation of the
 *                 rule.
 * @param rule_arg The string from which the rule is read.
 * @param states Number of expected states in the rule.
 */
void build_rule_from_args(uint64_t grule_size,
                          uint8_t rule_array[grule_size],
                          char rule_buf[grule_size + 1],
                          char* rule_arg, int states)
{
  /* Rule is given in base-(states - 1) format */
  for (uint64_t s = 0 ; s < grule_size; ++s) {
    rule_array[s] = rule_arg[s] - '0';
    rule_buf[s] = rule_arg[s];
    assert(rule_array[s] < states);
  }
  rule_buf[grule_size] = '\0';
}

/**
 * Simple double comparison function used for sorting in the rule generation
 * function.
 */
int comp (const void * elem1, const void * elem2)
{
  float f = *((float*)elem1);
  float s = *((float*)elem2);
  return (f > s) - (f < s);
}

/**
 * Generate a random general rule. The rule is written to rule_array and a
 * string  representation is also saved in rule_buf.
 */
void generate_general_rule(uint64_t grule_size,
                           uint8_t rule_array[grule_size],
                           char rule_buf[grule_size + 1],
                           int states, int horizon)
{
  int inc;

  /* Choose lambda parameter at random as well as the proportion of
     transitions to other states */
  double alphas[states];
  double theta[states], lambda[states - 1], rand_num;

  /* This method samples the transition probability simplex according to a
     Dirichlet distribution with parameter D_CONST */
  if (DIRICHLET == 1) {
    const gsl_rng_type * T;

    gsl_rng * r;
    gsl_rng_env_setup();

    T = gsl_rng_default;
    r = gsl_rng_alloc(T);
    time_t t;
    gsl_rng_set(r, (unsigned long)time(&t));

    for (int i = 0; i < states; ++i) {
      alphas[i] = D_CONST;
    }
    gsl_ran_dirichlet(r, states, alphas, theta);
    lambda[0] = theta[0];
    for (int i = 1; i < states - 1; ++i) {
      lambda[i] = theta[i] + lambda[i - 1];
    }

    gsl_rng_free(r);
  }
  /* Second method that samples the simplex uniformly */
  else {
    for (int i = 0; i < states - 1; ++i) {
      lambda[i] = ((double)rand() / (double)((unsigned)RAND_MAX + 1));
    }
    qsort(lambda, sizeof(lambda)/sizeof(*lambda), sizeof(*lambda), comp);
  }

  for (uint64_t v = 0; v < grule_size; v++) {
    /* Assign the rule to the first state that passes the test */
    rand_num = (double)rand() / (double)((unsigned)RAND_MAX + 1);
    inc = 0;
    while (lambda[inc] < rand_num && inc < states - 1) {
      ++inc;
    }
    rule_array[v] = (uint8_t)inc;
  }

  symmetrize_rule(grule_size, rule_array, states, horizon);

  populate_buf(grule_size, rule_array, rule_buf);
}

void cross_breed(uint64_t grule_size, uint8_t* parent_rule_A,
                 uint8_t* parent_rule_B, uint8_t* child,
                 char rule_buf[grule_size], double rate,
                 int horizon, int states)
{
  for (uint64_t i = 0; i < grule_size; ++i) {
    double rand_num = (double)rand() / (double)((unsigned)RAND_MAX + 1);
    child[i] = (rate > rand_num) ? parent_rule_A[i]: parent_rule_B[i];
  }

  perturb_rule(grule_size, child, rule_buf, states, horizon, 0.05);
}

void perturb_rule(uint64_t grule_size,
                  uint8_t rule_array[grule_size],
                  char rule_buf[grule_size + 1],
                  int states, int horizon, double rate)
{
  for (uint64_t v = 0; v < grule_size; ++v) {
    /* Perturb transisition outcome with probability rate */
    double rand_num = (double)rand() / (double)((unsigned)RAND_MAX + 1);
    if (rand_num < rate) {
      rule_array[v] += (1 + rand() % (states - 1));
      rule_array[v] %= states;
    }
  }

  symmetrize_rule(grule_size, rule_array, states, horizon);
  populate_buf(grule_size, rule_array, rule_buf);
}

void make_map(struct Options2D* opts, char* rule_buf, int step)
{
  FILE* dic_file;
  char* fname;

  asprintf(&fname, "%s/map/%lu.map", opts->data_dir_name, hash(rule_buf));
  dic_file = fopen(fname, "w+");

  if (dic_file == NULL) {
    fprintf(stderr, "Error while opening map file %s.", fname);
    exit(1);
  }

  fprintf(dic_file, "%s", rule_buf);
  fclose(dic_file);
  free(fname);

  sprintf(rule_buf, "%lu", hash(rule_buf));
  printf("%i: Rule %s\n", step, rule_buf);
}

void generate_totalistic_rule(uint64_t rule_size, uint8_t rule_array[rule_size],
                              char rule_buf[rule_size + 1], int states)
{
  unsigned long rule_number = 0UL;

  for (uint64_t s = 0 ; s < rule_size; ++s) {
    rule_array[s] = rand() % states;
    rule_number += rule_array[s] * ipow(states, s);
    if (states >= 3) {
      rule_buf[s] = '0' + rule_array[s];
    }
  }

  if (states == 2) {
    sprintf(rule_buf, "%lu", rule_number);
  }
}


/***************************************************************************
 * data_cf.h is part of Math Graphic Library
 * Copyright (C) 2007-2016 Alexey Balakin <mathgl.abalakin@gmail.ru>       *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU Library General Public License as       *
 *   published by the Free Software Foundation; either version 3 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 Library General Public     *
 *   License along with this program; if not, write to the                 *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#ifndef _MGL_DATAC_CF_H_
#define _MGL_DATAC_CF_H_
//-----------------------------------------------------------------------------
#include "mgl2/abstract.h"
//-----------------------------------------------------------------------------
#if MGL_HAVE_GSL
#include <gsl/gsl_vector.h>
#include <gsl/gsl_matrix.h>
#else
struct gsl_vector;
struct gsl_matrix;
#endif
//-----------------------------------------------------------------------------
#ifdef __cplusplus
class mglDataC;
typedef mglDataC* HADT;
extern "C" {
#else
typedef void *HADT;
#endif
/// Get integer power of x
cmdual MGL_EXPORT_CONST mgl_ipowc(mdual x,int n);
cmdual MGL_EXPORT mgl_ipowc_(mdual *x,int *n);
/// Get complex number from string. Parse (%g,%g), {%g,%g} and [%g,%g] if adv!=0.
cmdual MGL_EXPORT mgl_atoc(const char *s, int adv);
/// Get exp(i*a)
cmdual MGL_EXPORT_CONST mgl_expi(mdual a);

/// Create HMDT object
HADT MGL_EXPORT mgl_create_datac();
uintptr_t MGL_EXPORT mgl_create_datac_();
/// Create HMDT object with specified sizes
HADT MGL_EXPORT mgl_create_datac_size(long nx, long ny, long nz);
uintptr_t MGL_EXPORT mgl_create_datac_size_(int *nx, int *ny, int *nz);
/// Create HMDT object with data from file
HADT MGL_EXPORT mgl_create_datac_file(const char *fname);
uintptr_t MGL_EXPORT mgl_create_datac_file_(const char *fname, int len);
/// Delete HMDT object
void MGL_EXPORT mgl_delete_datac(HADT dat);
void MGL_EXPORT mgl_delete_datac_(uintptr_t *dat);

/// Rearange data dimensions
void MGL_EXPORT mgl_datac_rearrange(HADT dat, long mx,long my,long mz);
void MGL_EXPORT mgl_datac_rearrange_(uintptr_t *dat, int *mx, int *my, int *mz);
/// Link external data array (don't delete it at exit)
void MGL_EXPORT mgl_datac_link(HADT dat, mdual *A,long mx,long my,long mz);
void MGL_EXPORT mgl_datac_link_(uintptr_t *d, mdual *A, int *nx,int *ny,int *nz);
/// Allocate memory and copy the data from the (float *) array
void MGL_EXPORT mgl_datac_set_float(HADT dat, const float *A,long mx,long my,long mz);
void MGL_EXPORT mgl_datac_set_float_(uintptr_t *dat, const float *A,int *NX,int *NY,int *NZ);
/// Allocate memory and copy the data from the (double *) array
void MGL_EXPORT mgl_datac_set_double(HADT dat, const double *A,long mx,long my,long mz);
void MGL_EXPORT mgl_datac_set_double_(uintptr_t *dat, const double *A,int *NX,int *NY,int *NZ);
/// Allocate memory and copy the data from the (dual *) array
void MGL_EXPORT mgl_datac_set_complex(HADT dat, const mdual *A,long mx,long my,long mz);
void MGL_EXPORT mgl_datac_set_complex_(uintptr_t *d, const mdual *A,int *NX,int *NY,int *NZ);
/// Import data from abstract type
void MGL_EXPORT mgl_datac_set(HADT dat, HCDT a);
void MGL_EXPORT mgl_datac_set_(uintptr_t *dat, uintptr_t *a);
/// Allocate memory and copy the data from the gsl_vector
void MGL_EXPORT mgl_datac_set_vector(HADT dat, gsl_vector *v);
/// Allocate memory and copy the data from the gsl_matrix
void MGL_EXPORT mgl_datac_set_matrix(HADT dat, gsl_matrix *m);
/// Set value of data element [i,j,k]
void MGL_EXPORT mgl_datac_set_value(HADT dat, mdual v, long i, long j, long k);
void MGL_EXPORT mgl_datac_set_value_(uintptr_t *d, mdual *v, int *i, int *j, int *k);
/// Get value of data element [i,j,k]
cmdual MGL_EXPORT mgl_datac_get_value(HCDT dat, long i, long j, long k);
cmdual MGL_EXPORT mgl_datac_get_value_(uintptr_t *d, int *i, int *j, int *k);
/// Allocate memory and scanf the data from the string
void MGL_EXPORT mgl_datac_set_values(HADT dat, const char *val, long nx, long ny, long nz);
void MGL_EXPORT mgl_datac_set_values_(uintptr_t *d, const char *val, int *nx, int *ny, int *nz, int l);

/// Get array as solution of tridiagonal matrix solution a[i]*x[i-1]+b[i]*x[i]+c[i]*x[i+1]=d[i]
/** String \a how may contain:
 * 'x', 'y', 'z' for solving along x-,y-,z-directions, or
 * 'h' for solving along hexagonal direction at x-y plain (need nx=ny),
 * 'c' for using periodical boundary conditions,
 * 'd' for diffraction/diffuse calculation.
 * NOTE: It work for flat data model only (i.e. for a[i,j]==a[i+nx*j]) */
HADT MGL_EXPORT mgl_datac_tridmat(HCDT A, HCDT B, HCDT C, HCDT D, const char *how);
uintptr_t MGL_EXPORT mgl_datac_tridmat_(uintptr_t *A, uintptr_t *B, uintptr_t *C, uintptr_t *D, const char *how, int);

/// Returns pointer to internal data array
MGL_EXPORT mdual *mgl_datac_data(HADT dat);
/// Returns pointer to data element [i,j,k]
MGL_EXPORT mdual *mgl_datac_value(HADT dat, long i,long j,long k);

/// Set the data from HCDT objects for real and imaginary parts
void MGL_EXPORT mgl_datac_set_ri(HADT dat, HCDT re, HCDT im);
void MGL_EXPORT mgl_datac_set_ri_(uintptr_t *dat, uintptr_t *re, uintptr_t *im);
/// Set the data from HCDT objects as amplitude and phase of complex data
void MGL_EXPORT mgl_datac_set_ap(HADT dat, HCDT abs, HCDT phi);
void MGL_EXPORT mgl_datac_set_ap_(uintptr_t *dat, uintptr_t *abs, uintptr_t *phi);

/// Read data from tab-separated text file with auto determining size
int MGL_EXPORT mgl_datac_read(HADT dat, const char *fname);
int MGL_EXPORT mgl_datac_read_(uintptr_t *d, const char *fname,int l);
/// Read data from text file with size specified at beginning of the file
int MGL_EXPORT mgl_datac_read_mat(HADT dat, const char *fname, long dim);
int MGL_EXPORT mgl_datac_read_mat_(uintptr_t *dat, const char *fname, int *dim, int);
/// Read data from text file with specifeid size
int MGL_EXPORT mgl_datac_read_dim(HADT dat, const char *fname,long mx,long my,long mz);
int MGL_EXPORT mgl_datac_read_dim_(uintptr_t *dat, const char *fname,int *mx,int *my,int *mz,int);
/// Read data from tab-separated text files with auto determining size which filenames are result of sprintf(fname,templ,t) where t=from:step:to
int MGL_EXPORT mgl_datac_read_range(HADT d, const char *templ, double from, double to, double step, int as_slice);
int MGL_EXPORT mgl_datac_read_range_(uintptr_t *d, const char *fname, mreal *from, mreal *to, mreal *step, int *as_slice,int l);
/// Read data from tab-separated text files with auto determining size which filenames are satisfied to template (like "t_*.dat")
int MGL_EXPORT mgl_datac_read_all(HADT dat, const char *templ, int as_slice);
int MGL_EXPORT mgl_datac_read_all_(uintptr_t *d, const char *fname, int *as_slice,int l);
/// Save whole data array (for ns=-1) or only ns-th slice to text file
void MGL_EXPORT mgl_datac_save(HCDT dat, const char *fname,long ns);
void MGL_EXPORT mgl_datac_save_(uintptr_t *dat, const char *fname,int *ns,int);

/// Read data array from HDF file (parse HDF4 and HDF5 files)
int MGL_EXPORT mgl_datac_read_hdf(HADT d,const char *fname,const char *data);
int MGL_EXPORT mgl_datac_read_hdf_(uintptr_t *d, const char *fname, const char *data,int l,int n);
/// Save data to HDF file
void MGL_EXPORT mgl_datac_save_hdf(HCDT d,const char *fname,const char *data,int rewrite);
void MGL_EXPORT mgl_datac_save_hdf_(uintptr_t *d, const char *fname, const char *data, int *rewrite,int l,int n);

/// Create or recreate the array with specified size and fill it by zero
void MGL_EXPORT mgl_datac_create(HADT dat, long nx,long ny,long nz);
void MGL_EXPORT mgl_datac_create_(uintptr_t *dat, int *nx,int *ny,int *nz);
/// Transpose dimensions of the data (generalization of Transpose)
void MGL_EXPORT mgl_datac_transpose(HADT dat, const char *dim);
void MGL_EXPORT mgl_datac_transpose_(uintptr_t *dat, const char *dim,int);

/// Get sub-array of the data with given fixed indexes
HADT MGL_EXPORT mgl_datac_subdata(HCDT dat, long xx,long yy,long zz);
uintptr_t MGL_EXPORT mgl_datac_subdata_(uintptr_t *dat, int *xx,int *yy,int *zz);
/// Get sub-array of the data with given fixed indexes (like indirect access)
HADT MGL_EXPORT mgl_datac_subdata_ext(HCDT dat, HCDT xx, HCDT yy, HCDT zz);
uintptr_t MGL_EXPORT mgl_datac_subdata_ext_(uintptr_t *dat, uintptr_t *xx,uintptr_t *yy,uintptr_t *zz);
/// Get column (or slice) of the data filled by formulas of named columns
HADT MGL_EXPORT mgl_datac_column(HCDT dat, const char *eq);
uintptr_t MGL_EXPORT mgl_datac_column_(uintptr_t *dat, const char *eq,int l);
/// Get trace of the data array
HADT MGL_EXPORT mgl_datac_trace(HCDT d);
uintptr_t MGL_EXPORT mgl_datac_trace_(uintptr_t *d);
/// Resize the data to new sizes
HADT MGL_EXPORT mgl_datac_resize(HCDT dat, long mx,long my,long mz);
uintptr_t MGL_EXPORT mgl_datac_resize_(uintptr_t *dat, int *mx,int *my,int *mz);
/// Resize the data to new sizes of box [x1,x2]*[y1,y2]*[z1,z2]
HADT MGL_EXPORT mgl_datac_resize_box(HCDT dat, long mx,long my,long mz,mreal x1,mreal x2,mreal y1,mreal y2,mreal z1,mreal z2);
uintptr_t MGL_EXPORT mgl_datac_resize_box_(uintptr_t *dat, int *mx,int *my,int *mz,mreal *x1,mreal *x2,mreal *y1,mreal *y2,mreal *z1,mreal *z2);
/// Get momentum (1D-array) of data along direction 'dir'. String looks like "x1" for median in x-direction, "x2" for width in x-dir and so on.
HADT MGL_EXPORT mgl_datac_momentum(HCDT dat, char dir, const char *how);
uintptr_t MGL_EXPORT mgl_datac_momentum_(uintptr_t *dat, char *dir, const char *how, int,int);
/// Get array which values is result of interpolation this for coordinates from other arrays
HADT MGL_EXPORT mgl_datac_evaluate(HCDT dat, HCDT idat, HCDT jdat, HCDT kdat, int norm);
uintptr_t MGL_EXPORT mgl_datac_evaluate_(uintptr_t *dat, uintptr_t *idat, uintptr_t *jdat, uintptr_t *kdat, int *norm);
/// Get array which is result of summation in given direction or directions
HADT MGL_EXPORT mgl_datac_sum(HCDT dat, const char *dir);
uintptr_t MGL_EXPORT mgl_datac_sum_(uintptr_t *dat, const char *dir,int);
/// Get the data which is direct multiplication (like, d[i,j] = this[i]*a[j] and so on)
HADT MGL_EXPORT mgl_datac_combine(HCDT dat1, HCDT dat2);
uintptr_t MGL_EXPORT mgl_datac_combine_(uintptr_t *dat1, uintptr_t *dat2);
/// Get data from sections ids, separated by value val along specified direction.
/** If section id is negative then reverse order is used (i.e. -1 give last section). */
HADT MGL_EXPORT mgl_datac_section(HCDT dat, HCDT ids, char dir, mreal val);
uintptr_t MGL_EXPORT mgl_datac_section_(uintptr_t *d, uintptr_t *ids, const char *dir, mreal *val,int);
/// Get data from section id, separated by value val along specified direction.
/** If section id is negative then reverse order is used (i.e. -1 give last section). */
HADT MGL_EXPORT mgl_datac_section_val(HCDT dat, long id, char dir, mreal val);
uintptr_t MGL_EXPORT mgl_datac_section_val_(uintptr_t *d, int *id, const char *dir, mreal *val,int);

/// Equidistantly fill the data to range [x1,x2] in direction dir
void MGL_EXPORT mgl_datac_fill(HADT dat, mdual x1,mdual x2,char dir);
void MGL_EXPORT mgl_datac_fill_(uintptr_t *dat, mdual *x1,mdual *x2,const char *dir,int);
/// Modify the data by specified formula assuming x,y,z in range [r1,r2]
void MGL_EXPORT mgl_datac_fill_eq(HMGL gr, HADT dat, const char *eq, HCDT vdat, HCDT wdat,const char *opt);
void MGL_EXPORT mgl_datac_fill_eq_(uintptr_t *gr, uintptr_t *dat, const char *eq, uintptr_t *vdat, uintptr_t *wdat,const char *opt, int, int);
/// Fill dat by interpolated values of vdat parametrically depended on xdat for x in range [x1,x2] using global spline
void MGL_EXPORT mgl_datac_refill_gs(HADT dat, HCDT xdat, HCDT vdat, mreal x1, mreal x2, long sl);
void MGL_EXPORT mgl_datac_refill_gs_(uintptr_t *dat, uintptr_t *xdat, uintptr_t *vdat, mreal *x1, mreal *x2, long *sl);
/// Fill dat by interpolated values of vdat parametrically depended on xdat for x in range [x1,x2]
void MGL_EXPORT mgl_datac_refill_x(HADT dat, HCDT xdat, HCDT vdat, mreal x1, mreal x2, long sl);
void MGL_EXPORT mgl_datac_refill_x_(uintptr_t *dat, uintptr_t *xdat, uintptr_t *vdat, mreal *x1, mreal *x2, long *sl);
/// Fill dat by interpolated values of vdat parametrically depended on xdat,ydat for x,y in range [x1,x2]*[y1,y2]
void MGL_EXPORT mgl_datac_refill_xy(HADT dat, HCDT xdat, HCDT ydat, HCDT vdat, mreal x1, mreal x2, mreal y1, mreal y2, long sl);
void MGL_EXPORT mgl_datac_refill_xy_(uintptr_t *dat, uintptr_t *xdat, uintptr_t *ydat, uintptr_t *vdat, mreal *x1, mreal *x2, mreal *y1, mreal *y2, long *sl);
/// Fill dat by interpolated values of vdat parametrically depended on xdat,ydat,zdat for x,y,z in range [x1,x2]*[y1,y2]*[z1,z2]
void MGL_EXPORT mgl_datac_refill_xyz(HADT dat, HCDT xdat, HCDT ydat, HCDT zdat, HCDT vdat, mreal x1, mreal x2, mreal y1, mreal y2, mreal z1, mreal z2);
void MGL_EXPORT mgl_datac_refill_xyz_(uintptr_t *dat, uintptr_t *xdat, uintptr_t *ydat, uintptr_t *zdat, uintptr_t *vdat, mreal *x1, mreal *x2, mreal *y1, mreal *y2, mreal *z1, mreal *z2);
/// Fill dat by interpolated values of vdat parametrically depended on xdat,ydat,zdat for x,y,z in axis range
void MGL_EXPORT mgl_datac_refill_gr(HMGL gr, HADT dat, HCDT xdat, HCDT ydat, HCDT zdat, HCDT vdat, long sl, const char *opt);
void MGL_EXPORT mgl_datac_refill_gr_(uintptr_t *gr, uintptr_t *dat, uintptr_t *xdat, uintptr_t *ydat, uintptr_t *zdat, uintptr_t *vdat, long *sl, const char *opt,int);

/// Modify the data by specified formula
void MGL_EXPORT mgl_datac_modify(HADT dat, const char *eq,long dim);
void MGL_EXPORT mgl_datac_modify_(uintptr_t *dat, const char *eq,int *dim,int);
/// Modify the data by specified formula
void MGL_EXPORT mgl_datac_modify_vw(HADT dat, const char *eq,HCDT vdat,HCDT wdat);
void MGL_EXPORT mgl_datac_modify_vw_(uintptr_t *dat, const char *eq, uintptr_t *vdat, uintptr_t *wdat,int);

/// Limit the data to be inside [-v,v], keeping the original sign
void MGL_EXPORT mgl_datac_limit(HADT dat, mreal v);
void MGL_EXPORT mgl_datac_limit_(uintptr_t *dat, mreal *v);

/// Put value to data element(s)
void MGL_EXPORT mgl_datac_put_val(HADT dat, mdual val, long i, long j, long k);
void MGL_EXPORT mgl_datac_put_val_(uintptr_t *dat, mdual *val, int *i, int *j, int *k);
/// Put array to data element(s)
void MGL_EXPORT mgl_datac_put_dat(HADT dat, HCDT val, long i, long j, long k);
void MGL_EXPORT mgl_datac_put_dat_(uintptr_t *dat, uintptr_t *val, int *i, int *j, int *k);

/// Reduce size of the data
void MGL_EXPORT mgl_datac_squeeze(HADT dat, long rx,long ry,long rz,long smooth);
void MGL_EXPORT mgl_datac_squeeze_(uintptr_t *dat, int *rx,int *ry,int *rz,int *smooth);
/// Extend data dimensions
void MGL_EXPORT mgl_datac_extend(HADT dat, long n1, long n2);
void MGL_EXPORT mgl_datac_extend_(uintptr_t *dat, int *n1, int *n2);
/// Insert data rows/columns/slices
void MGL_EXPORT mgl_datac_insert(HADT dat, char dir, long at, long num);
void MGL_EXPORT mgl_datac_insert_(uintptr_t *dat, const char *dir, int *at, int *num, int);
/// Delete data rows/columns/slices
void MGL_EXPORT mgl_datac_delete(HADT dat, char dir, long at, long num);
void MGL_EXPORT mgl_datac_delete_(uintptr_t *dat, const char *dir, int *at, int *num, int);
/// Joind another data array
void MGL_EXPORT mgl_datac_join(HADT dat, HCDT d);
void MGL_EXPORT mgl_datac_join_(uintptr_t *dat, uintptr_t *d);

/// Smooth the data on specified direction or directions
/** String \a dir may contain:
 *  ‘x’, ‘y’, ‘z’ for 1st, 2nd or 3d dimension;
 *  ‘dN’ for linear averaging over N points;
 *  ‘3’ for linear averaging over 3 points;
 *  ‘5’ for linear averaging over 5 points.
 *  By default quadratic averaging over 5 points is used. */
void MGL_EXPORT mgl_datac_smooth(HADT d, const char *dirs, mreal delta);
void MGL_EXPORT mgl_datac_smooth_(uintptr_t *dat, const char *dirs, mreal *delta,int);
/// Cumulative summation the data in given direction or directions
void MGL_EXPORT mgl_datac_cumsum(HADT dat, const char *dir);
void MGL_EXPORT mgl_datac_cumsum_(uintptr_t *dat, const char *dir,int);
/// Integrate (cumulative summation) the data in given direction or directions
void MGL_EXPORT mgl_datac_integral(HADT dat, const char *dir);
void MGL_EXPORT mgl_datac_integral_(uintptr_t *dat, const char *dir,int);
/// Differentiate the data in given direction or directions
void MGL_EXPORT mgl_datac_diff(HADT dat, const char *dir);
void MGL_EXPORT mgl_datac_diff_(uintptr_t *dat, const char *dir,int);
/// Differentiate the parametrically specified data along direction v1 with v2,v3=const (v3 can be NULL)
void MGL_EXPORT mgl_datac_diff_par(HADT dat, HCDT v1, HCDT v2, HCDT v3);
void MGL_EXPORT mgl_datac_diff_par_(uintptr_t *dat, uintptr_t *v1, uintptr_t *v2, uintptr_t *v3);
/// Double-differentiate (like Laplace operator) the data in given direction
void MGL_EXPORT mgl_datac_diff2(HADT dat, const char *dir);
void MGL_EXPORT mgl_datac_diff2_(uintptr_t *dat, const char *dir,int);
/// Swap left and right part of the data in given direction (useful for Fourier spectrum)
void MGL_EXPORT mgl_datac_swap(HADT dat, const char *dir);
void MGL_EXPORT mgl_datac_swap_(uintptr_t *dat, const char *dir,int);
/// Roll data along direction dir by num slices
void MGL_EXPORT mgl_datac_roll(HADT dat, char dir, long num);
void MGL_EXPORT mgl_datac_roll_(uintptr_t *dat, const char *dir, int *num, int);
/// Mirror the data in given direction (useful for Fourier spectrum)
void MGL_EXPORT mgl_datac_mirror(HADT dat, const char *dir);
void MGL_EXPORT mgl_datac_mirror_(uintptr_t *dat, const char *dir,int);
/// Crop the data
void MGL_EXPORT mgl_datac_crop(HADT dat, long n1, long n2, char dir);
void MGL_EXPORT mgl_datac_crop_(uintptr_t *dat, int *n1, int *n2, const char *dir,int);
/// Crop the data to be most optimal for FFT (i.e. to closest value of 2^n*3^m*5^l)
void MGL_EXPORT mgl_datac_crop_opt(HADT dat, const char *how);
void MGL_EXPORT mgl_datac_crop_opt_(uintptr_t *dat, const char *how,int);

/// Multiply the data by other one for each element
void MGL_EXPORT mgl_datac_mul_dat(HADT dat, HCDT d);
void MGL_EXPORT mgl_datac_mul_dat_(uintptr_t *dat, uintptr_t *d);
/// Divide the data by other one for each element
void MGL_EXPORT mgl_datac_div_dat(HADT dat, HCDT d);
void MGL_EXPORT mgl_datac_div_dat_(uintptr_t *dat, uintptr_t *d);
/// Add the other data
void MGL_EXPORT mgl_datac_add_dat(HADT dat, HCDT d);
void MGL_EXPORT mgl_datac_add_dat_(uintptr_t *dat, uintptr_t *d);
/// Subtract the other data
void MGL_EXPORT mgl_datac_sub_dat(HADT dat, HCDT d);
void MGL_EXPORT mgl_datac_sub_dat_(uintptr_t *dat, uintptr_t *d);
/// Multiply each element by the number
void MGL_EXPORT mgl_datac_mul_num(HADT dat, mdual d);
void MGL_EXPORT mgl_datac_mul_num_(uintptr_t *dat, mdual *d);
/// Divide each element by the number
void MGL_EXPORT mgl_datac_div_num(HADT dat, mdual d);
void MGL_EXPORT mgl_datac_div_num_(uintptr_t *dat, mdual *d);
/// Add the number
void MGL_EXPORT mgl_datac_add_num(HADT dat, mdual d);
void MGL_EXPORT mgl_datac_add_num_(uintptr_t *dat, mdual *d);
/// Subtract the number
void MGL_EXPORT mgl_datac_sub_num(HADT dat, mdual d);
void MGL_EXPORT mgl_datac_sub_num_(uintptr_t *dat, mdual *d);

/// Apply Hankel transform
void MGL_EXPORT mgl_datac_hankel(HADT dat, const char *dir);
void MGL_EXPORT mgl_datac_hankel_(uintptr_t *dat, const char *dir,int);
/// Apply Sin-Fourier transform
void MGL_EXPORT mgl_datac_sinfft(HADT dat, const char *dir);
void MGL_EXPORT mgl_datac_sinfft_(uintptr_t *dat, const char *dir,int);
/// Apply Cos-Fourier transform
void MGL_EXPORT mgl_datac_cosfft(HADT dat, const char *dir);
void MGL_EXPORT mgl_datac_cosfft_(uintptr_t *dat, const char *dir,int);
/// Apply Fourier transform
void MGL_EXPORT mgl_datac_fft(HADT dat, const char *dir);
void MGL_EXPORT mgl_datac_fft_(uintptr_t *dat, const char *dir,int);
/// Find correlation between 2 data arrays
HADT MGL_EXPORT mgl_datac_correl(HCDT dat1, HCDT dat2, const char *dir);
uintptr_t MGL_EXPORT mgl_datac_correl_(uintptr_t *dat1, uintptr_t *dat2, const char *dir,int);
/// Calculate one step of diffraction by finite-difference method with parameter q
void MGL_EXPORT mgl_datac_diffr(HADT dat, const char *how, mreal q);
void MGL_EXPORT mgl_datac_diffr_(uintptr_t *d, const char *how, double q,int l);
/// Apply wavelet transform
/** Parameter \a dir may contain:
 * ‘x‘,‘y‘,‘z‘ for directions,
 * ‘d‘ for daubechies, ‘D‘ for centered daubechies,
 * ‘h‘ for haar, ‘H‘ for centered haar,
 * ‘b‘ for bspline, ‘B‘ for centered bspline,
 * ‘i‘ for applying inverse transform. */
void MGL_EXPORT mgl_datac_wavelet(HADT dat, const char *how, int k);
void MGL_EXPORT mgl_datac_wavelet_(uintptr_t *d, const char *dir, int *k,int);
/// Set as the data envelop
void MGL_EXPORT mgl_datac_envelop(HADT dat, char dir);
void MGL_EXPORT mgl_datac_envelop_(uintptr_t *dat, const char *dir, int);

/// Get real part of data values
HMDT MGL_EXPORT mgl_datac_real(HCDT dat);
uintptr_t MGL_EXPORT mgl_datac_real_(uintptr_t *dat);
/// Get imaginary part of data values
HMDT MGL_EXPORT mgl_datac_imag(HCDT dat);
uintptr_t MGL_EXPORT mgl_datac_imag_(uintptr_t *dat);
/// Get absolute value of data values, i.e. |u|
HMDT MGL_EXPORT mgl_datac_abs(HCDT dat);
uintptr_t MGL_EXPORT mgl_datac_abs_(uintptr_t *dat);
/// Get argument of data values
HMDT MGL_EXPORT mgl_datac_arg(HCDT dat);
uintptr_t MGL_EXPORT mgl_datac_arg_(uintptr_t *dat);
/// Get square of absolute value of data values, i.e. |u|^2
HMDT MGL_EXPORT mgl_datac_norm(HCDT dat);
uintptr_t MGL_EXPORT mgl_datac_norm_(uintptr_t *dat);

/// Interpolate by linear function the data to given point x=[0...nx-1], y=[0...ny-1], z=[0...nz-1]
cmdual MGL_EXPORT mgl_datac_linear(HCDT d, mreal x,mreal y,mreal z);
cmdual MGL_EXPORT mgl_datac_linear_(uintptr_t *d, mreal *x,mreal *y,mreal *z);
/// Interpolate by linear function the data and return its derivatives at given point x=[0...nx-1], y=[0...ny-1], z=[0...nz-1]
cmdual MGL_EXPORT mgl_datac_linear_ext(HCDT d, mreal x,mreal y,mreal z, mdual *dx,mdual *dy,mdual *dz);
cmdual MGL_EXPORT mgl_datac_linear_ext_(uintptr_t *d, mreal *x,mreal *y,mreal *z, mdual *dx,mdual *dy,mdual *dz);
/// Interpolate by cubic spline the data to given point x=[0...nx-1], y=[0...ny-1], z=[0...nz-1]
cmdual MGL_EXPORT mgl_datac_spline(HCDT dat, mreal x,mreal y,mreal z);
cmdual MGL_EXPORT mgl_datac_spline_(uintptr_t *dat, mreal *x,mreal *y,mreal *z);
/// Interpolate by cubic spline the data and return its derivatives at given point x=[0...nx-1], y=[0...ny-1], z=[0...nz-1]
cmdual MGL_EXPORT mgl_datac_spline_ext(HCDT dat, mreal x,mreal y,mreal z, mdual *dx,mdual *dy,mdual *dz);
cmdual MGL_EXPORT mgl_datac_spline_ext_(uintptr_t *dat, mreal *x,mreal *y,mreal *z, mdual *dx,mdual *dy,mdual *dz);
/// Prepare coefficients for global spline interpolation
HADT MGL_EXPORT mgl_gsplinec_init(HCDT x, HCDT v);
uintptr_t MGL_EXPORT mgl_gspline_init_(uintptr_t *x, uintptr_t *v);
/// Evaluate global spline (and its derivatives d1, d2 if not NULL) using prepared coefficients \a coef
cmdual MGL_EXPORT mgl_gsplinec(HCDT coef, mreal dx, mdual *d1, mdual *d2);
cmdual MGL_EXPORT mgl_gsplinec_(uintptr_t *c, mreal *dx, mdual *d1, mdual *d2);

/// Find roots for set of nonlinear equations defined by textual formulas
HADT MGL_EXPORT mgl_find_roots_txt_c(const char *func, const char *vars, HCDT ini);
uintptr_t MGL_EXPORT mgl_find_roots_txt_c_(const char *func, const char *vars, uintptr_t *ini,int,int);

#ifdef __cplusplus
}
#endif
//-----------------------------------------------------------------------------
#endif


#ifndef _RICO_
#define _RICO_

#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <ctype.h>
#include <math.h>
#include <unistd.h>
#include "../Parameter_files/COSMOLOGY.H"
#include "../Parameter_files/INIT_PARAMS.H"
#include <gsl/gsl_interp.h>
#include <gsl/gsl_spline.h>
#include "cosmo_progs.c"
#include "misc.c"
#include "ps.c"

#define A_NPTS (int) (60) /*Warning: the calculation of the MHR model parameters is valid only from redshift 2 to A_NPTS+2*/
static double A_table[A_NPTS], A_params[A_NPTS];
static gsl_interp_accel *A_acc;
static gsl_spline *A_spline;

#define C_NPTS (int) (12)
static double C_table[C_NPTS], C_params[C_NPTS];
static gsl_interp_accel *C_acc;
static gsl_spline *C_spline;

#define beta_NPTS (int) (5)
static double beta_table[beta_NPTS], beta_params[beta_NPTS];
static gsl_interp_accel *beta_acc;
static gsl_spline *beta_spline;

#define RR_Z_NPTS (int) (300) // number of points in redshift axis;  we will only interpolate over gamma, and just index sample in redshift
#define RR_DEL_Z (float) (0.2)
#define RR_lnGamma_NPTS (int) (150) // number of samples of gamma for the interpolation tables
#define RR_lnGamma_min (double) (-10) // min ln gamma12 used
#define RR_DEL_lnGamma (float) (0.1)
static double RR_table[RR_Z_NPTS][RR_lnGamma_NPTS], lnGamma_values[RR_lnGamma_NPTS];
static gsl_interp_accel *RR_acc[RR_Z_NPTS];
static gsl_spline *RR_spline[RR_Z_NPTS];

/***  FUNCTION PROTOTYPES ***/
double splined_recombination_rate(double z_eff, double gamma12_bg); // assumes T=1e4 and case B

double recombination_rate(double z_eff, double gamma12_bg, double T4, int usecaseB);
void init_MHR(); /*initializes the lookup table for the PDF density integral in MHR00 model at redshift z*/
void free_MHR(); /* deallocates the gsl structures from init_MHR */
double Gamma_SS(double Gamma_bg, double Delta, double T_4, double z);//ionization rate w. self shielding
double MHR_rr (double D, void *params);
double A_MHR(double z); /*returns the A parameter in MHR00model*/
double C_MHR(double z); /*returns the C parameter in MHR00model*/
double beta_MHR(double z); /*returns the beta parameter in MHR00model*/
double splined_A_MHR(double z); /*returns the splined A parameter in MHR00model*/
double splined_C_MHR(double z); /*returns the splined C parameter in MHR00model*/
double splined_beta_MHR(double z);/*returns the splined beta parameter in MHR00*/
void free_A_MHR(); /* deallocates the gsl structures from init_A */
void free_C_MHR(); /* deallocates the gsl structures from init_C */
void free_beta_MHR(); /* deallocates the gsl structures from init_beta */
void init_A_MHR(); /*initializes the lookup table for the A paremeter in MHR00 model*/
void init_C_MHR(); /*initializes the lookup table for the C paremeter in MHR00 model*/
void init_beta_MHR(); /*initializes the lookup table for the beta paremeter in MHR00 model*/

double splined_recombination_rate(double z_eff, double gamma12_bg){
  int z_ct = (int) (z_eff / RR_DEL_Z + 0.5); // round to nearest int
  double lnGamma = log(gamma12_bg);

  // check out of bounds
  if ( z_ct < 0 ){ // out of array bounds
//    fprintf(stderr, "WARNING: splined_recombination_rate: effective redshift %g is outside of array bouds\n", z_eff);
    z_ct = 0;
  }
  else if (z_ct  >= RR_Z_NPTS){
//    fprintf(stderr, "WARNING: splined_recombination_rate: effective redshift %g is outside of array bouds\n", z_eff);
    z_ct = RR_Z_NPTS-1;
  }

  if (lnGamma < RR_lnGamma_min){
    return 0;
  }
  else if (lnGamma >= (RR_lnGamma_min + RR_DEL_lnGamma * RR_lnGamma_NPTS) ){
//    fprintf(stderr, "WARNING: splined_recombination_rate: Gamma12 of %g is outside of interpolation array\n", gamma12_bg);
    lnGamma =  RR_lnGamma_min + RR_DEL_lnGamma * RR_lnGamma_NPTS - FRACT_FLOAT_ERR;
  }

  return gsl_spline_eval(RR_spline[z_ct], lnGamma, RR_acc[z_ct]);
}

void init_MHR(){
  int z_ct, gamma_ct;
  float z, gamma;

  // first initialize the MHR parameter look up tables
  init_C_MHR(); /*initializes the lookup table for the C paremeter in MHR00 model*/
  init_beta_MHR(); /*initializes the lookup table for the beta paremeter in MHR00 model*/
  init_A_MHR(); /*initializes the lookup table for the A paremeter in MHR00 model*/

  // now the recombination rate look up tables
  for (z_ct=0; z_ct < RR_Z_NPTS; z_ct++){

    z = z_ct * RR_DEL_Z; // redshift corresponding to index z_ct of the array

    // Intialize the Gamma values
    for (gamma_ct=0; gamma_ct < RR_lnGamma_NPTS; gamma_ct++){
      lnGamma_values[gamma_ct] = RR_lnGamma_min  + gamma_ct*RR_DEL_lnGamma;  // ln of Gamma12
      gamma = exp(lnGamma_values[gamma_ct]);
      RR_table[z_ct][gamma_ct] = recombination_rate(z, gamma, 1, 1); // CHANGE THIS TO INCLUDE TEMPERATURE
    }

    // set up the spline in gamma
    RR_acc[z_ct] = gsl_interp_accel_alloc();
    RR_spline[z_ct] = gsl_spline_alloc (gsl_interp_cspline, RR_lnGamma_NPTS);
    gsl_spline_init(RR_spline[z_ct], lnGamma_values, RR_table[z_ct], RR_lnGamma_NPTS);

  } // go to next redshift

  return;
}

void free_MHR(){
  int z_ct;

  free_A_MHR();
  free_C_MHR();
  free_beta_MHR();

  // now the recombination rate look up tables
  for (z_ct=0; z_ct < RR_Z_NPTS; z_ct++){
    gsl_spline_free (RR_spline[z_ct]);
    gsl_interp_accel_free(RR_acc[z_ct]);
  }

  return;
}

//calculates the attenuated photoionization rate due to self-shielding (in units of 1e-12 s^-1)
// input parameters are the background ionization rate, overdensity, temperature (in 10^4k), redshift, respectively
//  Uses the fitting formula from Rahmati et al, assuming a UVB power law index of alpha=5
double Gamma_SS(double Gamma_bg, double D, double T_4, double z){
  double D_ss = 26.7*pow(T_4, 0.17) * pow( (1+z)/10.0, -3) * pow(Gamma_bg, 2.0/3.0);
  return Gamma_bg * (0.98 * pow( (1.0+pow(D/D_ss, 1.64)), -2.28) + 0.02*pow( 1.0+D/D_ss, -0.84));
}

typedef struct {double z, gamma12_bg, T4, A, C_0, beta, avenH; int usecaseB;} RR_par;

double MHR_rr (double lnD, void *params){
  double D=exp(lnD);
  double alpha;
  RR_par p = *(RR_par *) params;
  double z = p.z;
  double gamma = Gamma_SS(p.gamma12_bg, D, p.T4, z);
  double n_H = p.avenH*D;
  double x_e = 1.0 - neutral_fraction(n_H, p.T4, gamma, p.usecaseB);
  double PDelta;

  PDelta = p.A * exp( - 0.5*pow((pow(D,-2.0/3.0)- p.C_0 ) / ((2.0*7.61/(3.0*(1.0+z)))), 2)) * pow(D, p.beta);

  if (p.usecaseB)
    alpha = alpha_B(p.T4*1e4);
  else
    alpha = alpha_A(p.T4*1e4);

  //  fprintf(stderr, "%g\t%g\t%g\t%g\t%g\n", n_H, PDelta, alpha, x_e, D);

  return n_H * PDelta * alpha * x_e * x_e * D * D;//note extra D since we are integrating over lnD
}

// returns the recombination rate per baryon (1/s), integrated over the MHR density PDF,
// given an ionizing background of gamma12_bg
// temeperature T4 (in 1e4 K), and usecaseB rate coefficient
// Assumes self-shielding according to Rahmati+ 2013
double recombination_rate(double z, double gamma12_bg, double T4, int usecaseB){
  double result, error, lower_limit, upper_limit, A, C_0, beta, avenH;
  gsl_function F;
  double rel_tol  = 0.01; //<- relative tolerance
  gsl_integration_workspace * w = gsl_integration_workspace_alloc (1000);
  RR_par p = {z, gamma12_bg, T4, A_MHR(z), C_MHR(z), beta_MHR(z), No*pow( 1+z, 3), usecaseB};

  F.function = &MHR_rr;
  F.params=&p;
  lower_limit = log(0.01);
  upper_limit = log(200);

  gsl_integration_qag (&F, lower_limit, upper_limit, 0, rel_tol,
		       1000, GSL_INTEG_GAUSS61, w, &result, &error);
  gsl_integration_workspace_free (w);

  return result;
}

double aux_function(double D, void *params){
  double result;
  double z = *(double *) params;

  result = exp(-(pow(D,-2.0/3.0)-C_MHR(z))*(pow(D,-2.0/3.0)-C_MHR(z))/(2.0*(2.0*7.61/(3.0*(1.0+z)))*(2.0*7.61/(3.0*(1.0+z)))))*pow(D, beta_MHR(z));

  return result;
}

double A_aux_integral(double z){
  double result, error, lower_limit, upper_limit;
  gsl_function F;
  double rel_tol  = 0.001; //<- relative tolerance
  gsl_integration_workspace * w = gsl_integration_workspace_alloc (1000);

  F.function = &aux_function;
  F.params = &z;
  lower_limit = 1e-25;
  upper_limit = 1e25;

  gsl_integration_qag (&F, lower_limit, upper_limit, 0, rel_tol,
		       1000, GSL_INTEG_GAUSS61, w, &result, &error);
  gsl_integration_workspace_free (w);

  return result;
}

double A_MHR(double z){
  double result;
  if(z>=2.0+(float)A_NPTS)
    result = splined_A_MHR(2.0+(float)A_NPTS);
  else
    if(z<=2.0)
      result = splined_A_MHR(2.0);
    else
      result = splined_A_MHR(z);
  return result;
}

void init_A_MHR(){
/* initialize the lookup table for the parameter A in the MHR00 model */
   int i;

   for (i=0; i<A_NPTS; i++){
     A_params[i] = 2.0+(float)i;
     A_table[i] = 1.0/A_aux_integral(2.0+(float)i);
   }

  // Set up spline table
  A_acc   = gsl_interp_accel_alloc();
  A_spline  = gsl_spline_alloc (gsl_interp_cspline, A_NPTS);
  gsl_spline_init(A_spline, A_params, A_table, A_NPTS);

  return;
 }

double splined_A_MHR(double x){
  return gsl_spline_eval(A_spline, x, A_acc);
}

void free_A_MHR(){

  gsl_spline_free (A_spline);
  gsl_interp_accel_free(A_acc);

  return;
}

double C_MHR(double z){
  double result;
  if(z>=13.0)
    result = 1.0;
  else
    if(z<=2.0)
      result = 0.558;
    else
      result = splined_C_MHR(z);
  return result;
}

void init_C_MHR(){
/* initialize the lookup table for the parameter C in the MHR00 model */
   int i;

  for (i=0; i<C_NPTS; i++)
    C_params[i] = (float)i+2.0;

  C_table[0] = 0.558;
  C_table[1] = 0.599;
  C_table[2] = 0.611;
  C_table[3] = 0.769;
  C_table[4] = 0.868;
  C_table[5] = 0.930;
  C_table[6] = 0.964;
  C_table[7] = 0.983;
  C_table[8] = 0.993;
  C_table[9] = 0.998;
  C_table[10] = 0.999;
  C_table[11] = 1.00;

  // Set up spline table
  C_acc   = gsl_interp_accel_alloc ();
  C_spline  = gsl_spline_alloc (gsl_interp_cspline, C_NPTS);
  gsl_spline_init(C_spline, C_params, C_table, C_NPTS);

  return;
 }

double splined_C_MHR(double x){
  return gsl_spline_eval(C_spline, x, C_acc);
}

void free_C_MHR(){

  gsl_spline_free (C_spline);
  gsl_interp_accel_free(C_acc);

  return;
}

double beta_MHR(double z){
  double result;
  if(z>=6.0)
    result = -2.50;
  else
    if(z<=2.0)
      result = -2.23;
    else
      result = splined_beta_MHR(z);
  return result;
}

void init_beta_MHR(){
/* initialize the lookup table for the parameter C in the MHR00 model */
   int i;

  for (i=0; i<beta_NPTS; i++)
    beta_params[i] = (float)i+2.0;

  beta_table[0] = -2.23;
  beta_table[1] = -2.35;
  beta_table[2] = -2.48;
  beta_table[3] = -2.49;
  beta_table[4] = -2.50;

  // Set up spline table
  beta_acc   = gsl_interp_accel_alloc ();
  beta_spline  = gsl_spline_alloc (gsl_interp_cspline, beta_NPTS);
  gsl_spline_init(beta_spline, beta_params, beta_table, beta_NPTS);

  return;
 }

double splined_beta_MHR(double x){
  return gsl_spline_eval(beta_spline, x, beta_acc);
}

void free_beta_MHR(){

  gsl_spline_free(beta_spline);
  gsl_interp_accel_free(beta_acc);

  return;
}

#endif