/*
 * BRAINS
 * (B)LR (R)everberation-mapping (A)nalysis (I)n AGNs with (N)ested (S)ampling
 * Yan-Rong Li, liyanrong@ihep.ac.cn
 * Thu, Aug 4, 2016
 */

/*! \file dnest_sa1d.c
 *  \brief run dnest sampling for sa and 1d rm analysis.
 */

#ifdef SA

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stddef.h>
#include <math.h>
#include <float.h>
#include <string.h>
#include <gsl/gsl_rng.h>
#include <gsl/gsl_randist.h>
#include <gsl/gsl_interp.h>
#include <mpi.h>

#include "brains.h"

DNestFptrSet *fptrset_sa1d;

/*
 * run denst for SA and 1D RM analysis.
 */
int dnest_sa1d(int argc, char **argv)
{
  int i;
  double logz_sa1d;

  if(parset.flag_sa_par_mutual == 0) /* SA and RM have the same BLR */
  {
    set_blr_model2d();
    num_params_sa_blr_model = 0;
  }
  else /* SA and RM have different BLRs but share the same inc. */
  {
    set_blr_model1d();
    set_sa_blr_model();
  }
  /* RM */
  num_params_blr = num_params_blr_model + 1; /* include line sys err */
  num_params_rm = parset.n_con_recon + num_params_var + num_params_blr;

  /* SA */
  num_params_sa_blr = num_params_sa_blr_model + num_params_sa_extpar;
  num_params_sa = num_params_sa_blr;

  /* total */
  num_params_blr_tot = num_params_blr + num_params_sa_blr;
  num_params = num_params_sa + num_params_rm;
  idx_resp = num_params_blr_tot + num_params_drw + num_params_trend;
  idx_difftrend = idx_resp + num_params_resp;

  par_fix = (int *) malloc(num_params * sizeof(int));
  par_fix_val = (double *) malloc(num_params * sizeof(double));
  par_range_model = malloc( num_params * sizeof(double *));
  par_prior_gaussian = malloc( num_params * sizeof(double *));
  for(i=0; i<num_params; i++)
  {
    par_range_model[i] = malloc(2*sizeof(double));
    par_prior_gaussian[i] = malloc(2*sizeof(double));
  }
  par_prior_model = malloc( num_params * sizeof(int));

  fptrset_sa1d = dnest_malloc_fptrset();
  /* setup functions used for dnest*/
  fptrset_sa1d->from_prior = from_prior_sa1d;
  fptrset_sa1d->print_particle = print_particle_sa1d;
  fptrset_sa1d->restart_action = restart_action_sa;
  fptrset_sa1d->accept_action = accept_action_sa1d;
  fptrset_sa1d->kill_action = kill_action_sa1d;
  fptrset_sa1d->perturb = perturb_sa1d;
  fptrset_sa1d->read_particle = read_particle_sa1d;

  fptrset_sa1d->log_likelihoods_cal_initial = log_likelihoods_cal_initial_sa1d;
  fptrset_sa1d->log_likelihoods_cal_restart = log_likelihoods_cal_restart_sa1d;
  fptrset_sa1d->log_likelihoods_cal = log_likelihoods_cal_sa1d;

  set_par_range_sa1d();
  set_par_fix_blrmodel();
  set_par_fix_sa_blrmodel();

  /* the rest parameters */
  for(i=num_params_blr_model; i<num_params_blr; i++)
  {
    par_fix[i] = 0;
    par_fix_val[i] = -DBL_MAX;
  }
  for(i=num_params_blr + num_params_sa_blr_model; i<num_params; i++)
  {
    par_fix[i] = 0;
    par_fix_val[i] = -DBL_MAX;
  }

  /* fix non-linear response */
  if(parset.flag_nonlinear !=1)
  {
    par_fix[idx_resp+1] = 1;
    par_fix_val[idx_resp+1] = 0.0;
  }

  /* fix systematic error of line */
  if(parset.flag_line_sys_err != 1)
  {
    par_fix[num_params_blr-1] = 1;
    par_fix_val[num_params_blr-1] = log(1.0);
  }

  /* fix inc of SA BLR if SA and RM have different BLR
   * in this case, DA must be fixed.
   */
  if(parset.flag_sa_par_mutual != 0)
  {
    /* get the index for mbh and inclination parameters */
    set_idx_par_mutual();

    /* inc */
    par_fix[num_params_blr + idx_sa_par_mutual[1]] = 1;
    par_fix_val[num_params_blr + idx_sa_par_mutual[1]] = 0.0;

    /* fix DA */
    par_fix[num_params_blr + num_params_sa_blr_model] = 1;
    par_fix_val[num_params_blr + num_params_sa_blr_model] = log(550.0);
  }

  /* fix FA */
  par_fix[num_params_blr + num_params_sa_blr_model+2] = 1;
  par_fix_val[num_params_blr + num_params_sa_blr_model+2] = log(1.0);

  /* fix systematic error of continuum */
  if(parset.flag_con_sys_err != 1)
  {
    par_fix[num_params_blr_tot] = 1;
    par_fix_val[num_params_blr_tot] = log(1.0);
  }

  /* fix continuum variation parameter sigma and tau if flag_fixvar is true */
  if(parset.flag_fixvar == 1)
  {
    par_fix[num_params_blr_tot + 1] = 1;
    par_fix_val[num_params_blr_tot + 1] = var_param[1];
    par_fix[num_params_blr_tot + 2] = 1;
    par_fix_val[num_params_blr_tot + 2] = var_param[2];
  }

  print_par_names_sa1d();

  force_update = parset.flag_force_update;
  if(parset.flag_para_name != 1)
    logz_sa1d = dnest(argc, argv, fptrset_sa1d, num_params, dnest_options_file);

  dnest_free_fptrset(fptrset_sa1d);

  return 0;
}

void set_par_range_sa1d()
{
  int i, j, i1, i2;

  /* RM BLR parameters first */
  for(i=0; i<num_params_blr_model; i++)
  {
    par_range_model[i][0] = blr_range_model[i][0];
    par_range_model[i][1] = blr_range_model[i][1];

    par_prior_model[i] = UNIFORM;
    par_prior_gaussian[i][0] = 0.0;
    par_prior_gaussian[i][1] = 0.0;
  }

  /* systematic line error */
  i = num_params_blr -1;
  par_range_model[i][0] = sys_err_line_range[0];
  par_range_model[i][1] = sys_err_line_range[1];

  par_prior_model[i] = UNIFORM;
  par_prior_gaussian[i][0] = 0.0;
  par_prior_gaussian[i][1] = 0.0;

  /* SA BLR parameters */
  for(i=num_params_blr; i<num_params_blr + num_params_sa_blr_model; i++)
  {
    par_range_model[i][0] = sa_blr_range_model[i-num_params_blr][0];
    par_range_model[i][1] = sa_blr_range_model[i-num_params_blr][1];

    par_prior_model[i] = UNIFORM;
    par_prior_gaussian[i][0] = 0.0;
    par_prior_gaussian[i][1] = 0.0;
  }
  /* SA extra parameters */
  for(i=num_params_blr + num_params_sa_blr_model; i<num_params_blr_tot; i++)
  {
    par_range_model[i][0] = sa_extpar_range[i-(num_params_blr + num_params_sa_blr_model)][0];
    par_range_model[i][1] = sa_extpar_range[i-(num_params_blr + num_params_sa_blr_model)][1];

    par_prior_model[i] = UNIFORM;
    par_prior_gaussian[i][0] = 0.0;
    par_prior_gaussian[i][1] = 0.0;
  }

  /* variability parameters */
  /* first systematic error */
  i = num_params_blr_tot;
  par_range_model[i][0] = var_range_model[0][0];
  par_range_model[i][1] = var_range_model[0][1];

  par_prior_model[i] = UNIFORM;
  par_prior_gaussian[i][0] = 0.0;
  par_prior_gaussian[i][1] = 0.0;

  /* drw parameters */
  for(i=num_params_blr_tot+1; i<num_params_drw + num_params_blr_tot; i++)
  {
    if(var_param_std[i-num_params_blr_tot] > 0.0)
    {
      par_range_model[i][0] = var_param[i-num_params_blr_tot] - 5.0 * var_param_std[i-num_params_blr_tot];
      par_range_model[i][1] = var_param[i-num_params_blr_tot] + 5.0 * var_param_std[i-num_params_blr_tot];

      /* make sure that the range lies within the initial range */
      par_range_model[i][0] = fmax(par_range_model[i][0], var_range_model[i-num_params_blr_tot][0]);
      par_range_model[i][1] = fmin(par_range_model[i][1], var_range_model[i-num_params_blr_tot][1]);

      par_prior_model[i] = GAUSSIAN;
      par_prior_gaussian[i][0] = var_param[i-num_params_blr_tot];
      par_prior_gaussian[i][1] = var_param_std[i-num_params_blr_tot];
    }
    else
    {
      par_range_model[i][0] = var_range_model[i-num_params_blr_tot][0];
      par_range_model[i][1] = var_range_model[i-num_params_blr_tot][1];

      par_prior_model[i] = UNIFORM;
      par_prior_gaussian[i][0] = 0.0;
      par_prior_gaussian[i][1] = 0.0;
    }
  }
  /* long-term trend */
  for(i=num_params_drw + num_params_blr_tot; i< num_params_drw + num_params_trend + num_params_blr_tot; i++)
  {
    par_range_model[i][0] = var_range_model[3][0];
    par_range_model[i][1] = var_range_model[3][1];

    par_prior_model[i] = GAUSSIAN;
    par_prior_gaussian[i][0] = 0.0;
    par_prior_gaussian[i][1] = 1.0;
  }

  /* response A and Ag */
  j = 0;
  i1 = idx_resp;
  i2 = idx_resp + num_params_resp;
  for(i=i1; i<i2; i++)
  {
    par_range_model[i][0] = resp_range[j][0];
    par_range_model[i][1] = resp_range[j][1];

    par_prior_model[i] = UNIFORM;
    par_prior_gaussian[i][0] = 0.0;
    par_prior_gaussian[i][1] = 0.0;

    j++;
  }

  /* different trend */
  j = 0;
  i1 = idx_difftrend;
  i2 = idx_difftrend + num_params_difftrend;
  for(i=i1; i< i2; i++)
  {
    par_range_model[i][0] = var_range_model[4 + j][0];
    par_range_model[i][1] = var_range_model[4 + j][1];

    par_prior_model[i] = UNIFORM;
    par_prior_gaussian[i][0] = 0.0;
    par_prior_gaussian[i][1] = 0.0;

    j++;
  }

  /* continuum light curve parameters */
  for(i=num_params_blr_tot+num_params_var; i<num_params; i++)
  {
    par_range_model[i][0] = var_range_model[4+num_params_difftrend][0];
    par_range_model[i][1] = var_range_model[4+num_params_difftrend][1];

    par_prior_model[i] = GAUSSIAN;
    par_prior_gaussian[i][0] = 0.0;
    par_prior_gaussian[i][1] = 1.0;
  }
  return;
}

/*!
 *  print names and prior ranges for parameters
 *
 */
void print_par_names_sa1d()
{
  if(thistask!= roottask)
    return;

  int i, j;
  FILE *fp;
  char fname[BRAINS_MAX_STR_LENGTH], str_fmt[BRAINS_MAX_STR_LENGTH];

  sprintf(fname, "%s/%s", parset.file_dir, "data/para_names_sa1d.txt");
  fp = fopen(fname, "w");
  if(fp == NULL)
  {
    fprintf(stderr, "# Error: Cannot open file %s.\n", fname);
    exit(0);
  }

  strcpy(str_fmt, "%4d %-15s %10.6f %10.6f %4d %4d %15.6e\n");

  printf("# Print parameter name in %s\n", fname);

  fprintf(fp, "#*************************************************\n");
  fprint_version(fp);
  fprintf(fp, "#*************************************************\n");

  fprintf(fp, "%4s %-15s %10s %10s %4s %4s %15s\n", "#", "Par", "Min", "Max", "Prior", "Fix", "Val");

  i=-1;
  for(j=0; j<num_params_blr_model; j++)
  {
    i++;
    fprintf(fp, str_fmt, i, "BLR model", par_range_model[i][0], par_range_model[i][1], par_prior_model[i],
                            par_fix[i], par_fix_val[i]);
  }

  i++;
  fprintf(fp, str_fmt, i, "sys_err_line", par_range_model[i][0], par_range_model[i][1], par_prior_model[i],
                            par_fix[i], par_fix_val[i]);

  for(j=0; j<num_params_sa_blr_model; j++)
  {
    i++;
    fprintf(fp, str_fmt, i, "SA BLR model", par_range_model[i][0], par_range_model[i][1], par_prior_model[i],
                            par_fix[i], par_fix_val[i]);
  }

  for(j=0; j<num_params_sa_extpar; j++)
  {
    i++;
    fprintf(fp, str_fmt, i, "SA Extra Par", par_range_model[i][0], par_range_model[i][1], par_prior_model[i],
                            par_fix[i], par_fix_val[i]);
  }

  i++;
  fprintf(fp, str_fmt, i, "sys_err_con", par_range_model[i][0], par_range_model[i][1], par_prior_model[i],
                            par_fix[i], par_fix_val[i]);
  i++;
  fprintf(fp, str_fmt, i, "sigmad", par_range_model[i][0], par_range_model[i][1], par_prior_model[i],
                            par_fix[i], par_fix_val[i]);
  i++;
  fprintf(fp, str_fmt, i, "taud", par_range_model[i][0], par_range_model[i][1], par_prior_model[i],
                            par_fix[i], par_fix_val[i]);

  for(j=0; j<num_params_trend; j++)
  {
    i++;
    fprintf(fp, str_fmt, i, "trend", par_range_model[i][0], par_range_model[i][1], par_prior_model[i],
                            par_fix[i], par_fix_val[i]);
  }

  i++;
  fprintf(fp, str_fmt, i, "A", par_range_model[i][0], par_range_model[i][1], par_prior_model[i],
                            par_fix[i], par_fix_val[i]);

  i++;
  fprintf(fp, str_fmt, i, "Ag", par_range_model[i][0], par_range_model[i][1], par_prior_model[i],
                            par_fix[i], par_fix_val[i]);

  for(j=0; j<num_params_difftrend; j++)
  {
    i++;
    fprintf(fp, str_fmt, i, "diff trend", par_range_model[i][0], par_range_model[i][1], par_prior_model[i],
                            par_fix[i], par_fix_val[i]);
  }

  for(j=0; j<parset.n_con_recon; j++)
  {
    i++;
    fprintf(fp, str_fmt, i, "time series", par_range_model[i][0], par_range_model[i][1], par_prior_model[i],
                            par_fix[i], par_fix_val[i]);
  }

  fclose(fp);
}

/*!
 * this function generates a sample from prior.
 */
void from_prior_sa1d(void *model)
{
  int i;
  double *pm = (double *)model;

  for(i=0; i<num_params; i++)
  {
    if(par_prior_model[i] == GAUSSIAN)
    {
      pm[i] = dnest_randn()*par_prior_gaussian[i][1] + par_prior_gaussian[i][0];
      dnest_wrap(&pm[i], par_range_model[i][0], par_range_model[i][1]);
    }
    else
    {
      pm[i] = par_range_model[i][0] + dnest_rand() * (par_range_model[i][1] - par_range_model[i][0]);
    }
  }

  /* cope with fixed parameters */
  for(i=0; i<num_params; i++)
  {
    if(par_fix[i] == 1)
      pm[i] = par_fix_val[i];
  }

  which_parameter_update = -1;

  return;
}

/*!
 * this function calculates likelihood at initial step.
 */
double log_likelihoods_cal_initial_sa1d(const void *model)
{
  double logL;
  logL = prob_initial_sa1d(model);
  return logL;
}

/*!
 * this function calculates likelihood at initial step.
 */
double log_likelihoods_cal_restart_sa1d(const void *model)
{
  double logL;
  logL = prob_initial_sa1d(model);
  return logL;
}

/*!
 * this function calculates likelihood.
 */
double log_likelihoods_cal_sa1d(const void *model)
{
  double logL;
  logL = prob_sa1d(model);
  return logL;
}

/*!
 * this function prints out parameters.
 */
void print_particle_sa1d(FILE *fp, const void *model)
{
  int i;
  double *pm = (double *)model;

  for(i=0; i<num_params; i++)
  {
    fprintf(fp, "%e ", pm[i] );
  }
  fprintf(fp, "\n");
  return;
}

/*!
 * This function read the particle from the file.
 */
void read_particle_sa1d(FILE *fp, void *model)
{
  int j;
  double *psample = (double *)model;

  for(j=0; j < dnest_num_params; j++)
  {
    if(fscanf(fp, "%lf", psample+j) < 1)
    {
      printf("%f\n", *psample);
      fprintf(stderr, "#Error: Cannot read file %s.\n", options.sample_file);
      exit(0);
    }
  }
  return;
}

/*!
 * this function perturbs parameters.
 */
double perturb_sa1d(void *model)
{
  double *pm = (double *)model;
  double logH = 0.0, limit1, limit2, width, rnd;
  int which, which_level;

  /*
   * fixed parameters need not to update
   * perturb important parameters more frequently
   */
  do
  {
    rnd = dnest_rand();
    if(rnd < fmax(0.2, 1.0*(num_params_blr_tot+num_params_var)/num_params))
      which = dnest_rand_int(num_params_blr_tot + num_params_var);
    else
      which = dnest_rand_int(parset.n_con_recon) + num_params_blr_tot + num_params_var;

  }while(par_fix[which] == 1);

  which_parameter_update = which;

  /* level-dependent width */
  count_saves = dnest_get_count_saves();
  which_level_update = dnest_get_which_level_update();
  which_level = which_level_update > (size_levels-10)?(size_levels-10):which_level_update;

  if( which_level > 0)
  {
    limit1 = limits[(which_level-1) * num_params *2 + which *2];
    limit2 = limits[(which_level-1) * num_params *2 + which *2 + 1];
    width = (limit2 - limit1);
  }
  else
  {
    limit1 = par_range_model[which][0];
    limit2 = par_range_model[which][1];
    width = (par_range_model[which][1] - par_range_model[which][0]);
  }
  width /= (2.35);

  if(par_prior_model[which] == GAUSSIAN)
  {
    logH -= (-0.5*pow((pm[which] - par_prior_gaussian[which][0])/par_prior_gaussian[which][1], 2.0) );
    pm[which] += dnest_randh() * width;
    dnest_wrap(&pm[which], par_range_model[which][0], par_range_model[which][1]);
    //dnest_wrap(&pm[which], limit1, limit2);
    logH += (-0.5*pow((pm[which] - par_prior_gaussian[which][0])/par_prior_gaussian[which][1], 2.0) );
  }
  else
  {
    pm[which] += dnest_randh() * width;
    dnest_wrap(&(pm[which]), par_range_model[which][0], par_range_model[which][1]);
    //dnest_wrap(&pm[which], limit1, limit2);
  }

  return logH;
}

/*!
 * this function calculates likelihood.
 */
double log_likelihoods_cal_sa1d_exam(const void *model)
{
  return 0.0;
}

void accept_action_sa1d()
{
  int param;
  double *ptemp;

  // the parameter previously updated
  param = which_parameter_update;

  /* continuum parameter is updated */
  if( param >= num_params_blr_tot )
  {
    /*
     *note that (response) Fline is also changed as long as Fcon is changed.
     *num_params_blr-the parameter is the systematic error of continuum.
     *the change of this parameter also changes continuum reconstruction.
     */

    ptemp = Fcon_rm_particles[which_particle_update];
    Fcon_rm_particles[which_particle_update] = Fcon_rm_particles_perturb[which_particle_update];
    Fcon_rm_particles_perturb[which_particle_update] = ptemp;

    if(force_update != 1)
    {
      ptemp = Fline_at_data_particles[which_particle_update];
      Fline_at_data_particles[which_particle_update] = Fline_at_data_particles_perturb[which_particle_update];
      Fline_at_data_particles_perturb[which_particle_update] = ptemp;
    }
  }
  else if( param != num_params_blr-1 && force_update != 1)
  {
    /* RM and SA BLR parameter is updated
     * Note a) that the (num_par_blr-1)-th parameter is systematic error of line.
     * when this parameter is updated, Trans1D and Fline, phase_sa, and Fline_sa are unchanged.
     *      b) Fline is always changed, except param = num_params_blr-1.
     */

    {
      ptemp = TransTau_particles[which_particle_update];
      TransTau_particles[which_particle_update] = TransTau_particles_perturb[which_particle_update];
      TransTau_particles_perturb[which_particle_update] = ptemp;

      ptemp = Trans1D_particles[which_particle_update];
      Trans1D_particles[which_particle_update] = Trans1D_particles_perturb[which_particle_update];
      Trans1D_particles_perturb[which_particle_update] = ptemp;

      ptemp = Fline_at_data_particles[which_particle_update];
      Fline_at_data_particles[which_particle_update] = Fline_at_data_particles_perturb[which_particle_update];
      Fline_at_data_particles_perturb[which_particle_update] = ptemp;

      prob_sa_particles[which_particle_update] = prob_sa_particles_perturb[which_particle_update];
    }
  }

  return;
}

/*
 * action when particle i is killed in cdnest sampling.
 * particle i_copy's properties is copyed to particle i.
 */
void kill_action_sa1d(int i, int i_copy)
{
  memcpy(Fcon_rm_particles[i], Fcon_rm_particles[i_copy], parset.n_con_recon * sizeof(double));
  memcpy(Fline_at_data_particles[i], Fline_at_data_particles[i_copy], n_line_data * sizeof(double));
  memcpy(TransTau_particles[i], TransTau_particles[i_copy], parset.n_tau*sizeof(double));
  memcpy(Trans1D_particles[i], Trans1D_particles[i_copy], parset.n_tau * sizeof(double));

  prob_sa_particles[i] = prob_sa_particles[i_copy];

  return;
}

#endif


/**
 * \author Sylvain Marsat, University of Maryland - NASA GSFC
 *
 * \brief C header for EOBNRv2HM reduced order model (non-spinning version).
 * See CQG 31 195010, 2014, arXiv:1402.4146 for details on the reduced order method.
 * See arXiv:1106.1021 for the EOBNRv2HM model.
 *
 * Borrows from the SEOBNR ROM LAL code written by Michael Puerrer and John Veitch.
 *
 * Put the untared data in the directory designated by the environment variable ROM_DATA_PATH.
 *
 * Parameter range:
 *   q = 1-12 (almost)
 *   No spin
 *   Mtot >= 10Msun for fstart=8Hz
 *
 */

#ifndef _EOBNRV2HMROM_H
#define _EOBNRV2HMROM_H

#define _XOPEN_SOURCE 500

#ifdef __GNUC__
#define UNUSED __attribute__ ((unused))
#else
#define UNUSED
#endif

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <complex.h>
#include <time.h>
#include <unistd.h>
#include <getopt.h>
#include <stdbool.h>
#include <string.h>

#include <gsl/gsl_errno.h>
#include <gsl/gsl_bspline.h>
#include <gsl/gsl_blas.h>
#include <gsl/gsl_min.h>
#include <gsl/gsl_spline.h>
#include <gsl/gsl_complex.h>

#include "constants.h"
#include "struct.h"
#include "EOBNRv2HMROMstruct.h"

#if defined(__cplusplus)
extern "C" {
#elif 0
} /* so that editors will match preceding brace */
#endif

   /********General definitions********/

#define nk_amp 10  /* number of SVD-modes == number of basis functions for amplitude */
#define nk_phi 20  /* number of SVD-modes == number of basis functions for phase */

/********External array for the list of modes********/
#define nbmodemax 5
extern const int listmode[nbmodemax][2];

/**************************************************/
/**************** Prototypes **********************/

/* Functions to load, initalize and cleanup data */
int EOBNRv2HMROM_Init_DATA(void);
int EOBNRv2HMROM_Init(const char dir[]);

void EOBNRHMROMdata_Init(EOBNRHMROMdata **data);
void EOBNRHMROMdata_interp_Init(EOBNRHMROMdata_interp **data_interp);
void EOBNRHMROMdata_coeff_Init(EOBNRHMROMdata_coeff **data_coeff);

void EOBNRHMROMdata_Cleanup(EOBNRHMROMdata *data);
void EOBNRHMROMdata_interp_Cleanup(EOBNRHMROMdata_interp *data_interp);
void EOBNRHMROMdata_coeff_Cleanup(EOBNRHMROMdata_coeff *data_coeff);

/* Function to read data */
int Read_Data_Mode(const char dir[], const int mode[2], EOBNRHMROMdata *data);

/* Functions to interpolate the data and to evaluate the interpolated data for a given q */

int Evaluate_Spline_Data(
  const double q,                           /* Input: q-value for which projection coefficients should be evaluated */
  const EOBNRHMROMdata_interp* data_interp,  /* Input: data in interpolated form */
  EOBNRHMROMdata_coeff* data_coeff           /* Output: vectors of projection coefficients and shifts in time and phase */
);

int Interpolate_Spline_Data(
  const EOBNRHMROMdata *data,           /* Input: data in vector/matrix form to interpolate */
  EOBNRHMROMdata_interp *data_interp    /* Output: interpolated data */
);

/* Functions for waveform reconstruction */

int EOBNRv2HMROMCore(
  struct tagListmodesCAmpPhaseFrequencySeries **listhlm,
  int nbmode,
  double tRef,
  double phiRef,
  double fRef,
  double Mtot_sec,
  double q,
  double distance,
  int setphiRefattRef);

int SimEOBNRv2HMROM(
  ListmodesCAmpPhaseFrequencySeries **listhlm,  /* Output: list of modes in Frequency-domain amplitude and phase form */
  int nbmode,                                    /* Number of modes to generate (starting with the 22) */
  double tRef,                                   /* Time shift with respect to the 22-fit removed waveform (s) */
  double phiRef,                                 /* Phase at reference frequency */
  double fRef,                                   /* Reference frequency (Hz); 0 defaults to fLow */
  double m1SI,                                   /* Mass of companion 1 (kg) */
  double m2SI,                                   /* Mass of companion 2 (kg) */
  double distance,                               /* Distance of source (m) */
  int setphiRefattRef);                          /* Flag for adjusting the FD phase at phiRef at the given fRef, which depends also on tRef - if false, treat phiRef simply as an orbital phase shift (minus an observer phase shift) */

int SimEOBNRv2HMROMExtTF2(
  ListmodesCAmpPhaseFrequencySeries **listhlm,   /* Output: list of modes in Frequency-domain amplitude and phase form */
  int nbmode,                                    /* Number of modes to generate (starting with the 22) */
  double Mf_match,                               /* Minimum frequency using EOBNRv2HMROM in inverse total mass units*/
  double minf,                                   /* Minimum frequency required */
  int tagexthm,                                  /* Tag to decide whether or not to extend the higher modes as well */
  double deltatRef,                              /* Time shift so that the peak of the 22 mode occurs at deltatRef */
  double phiRef,                                 /* Phase at reference frequency */
  double fRef,                                   /* Reference frequency (Hz); 0 defaults to fLow */
  double m1SI,                                   /* Mass of companion 1 (kg) */
  double m2SI,                                   /* Mass of companion 2 (kg) */
  double distance,                               /* Distance of source (m) */
  int setphiRefattRef);                          /* Flag for adjusting the FD phase at phiRef at the given fRef, which depends also on tRef - if false, treat phiRef simply as an orbital phase shift (minus an observer phase shift) */

#if 0
{ /* so that editors will match succeeding brace */
#elif defined(__cplusplus)
}
#endif

#endif /* _EOBNRV2HMROM_H */


/* -*- Mode: C; c-basic-offset:4 ; indent-tabs-mode:nil ; -*- */
/** \file
 * jitconv "doRef" calculation - a simplified gemm impl.
 *
 * This should be correct, and may even be somewhat fast "in general".
 * Do not expect "fastest" performance.
 *
 * libvednn has a more sophisticated GEMM convolution.
 */
#include "vednn_helper.h"
#include "convolution_gemm.h"
#include <string.h>
#include <stdlib.h>
#include <cblas.h>

#define LOCAL_FTRACE 1
#if LOCAL_FTRACE
#include "conv_test_param.h" // just for FTRACE macros
#define LFTRACE_BEGIN(...) FTRACE_BEGIN(__VA_ARGS__)
#define LFTRACE_END(...) FTRACE_END(__VA_ARGS__)
#define LFTRACE_IF(...) FTRACE_IF(__VA_ARGS__)
#else
#define LFTRACE_BEGIN(...) do{}while(0)
#define LFTRACE_END(...) do{}while(0)
#define LFTRACE_IF(...) do{}while(0)
#endif // LOCAL_FTRACE

// Scratchpad: none -- hardwired to malloc/free im2col buffers
//                     slower, but safer.

#define GEMM_PARA_THRESH 32768

// Around Jan. 2022, ncc-3.4.20, SGEMM began to segfault for M==1,
// so a workaround writes out the trivial matrix multiply.
/// 0 : SGEMM works great
/// 1 : safe macros
/// 2: dev code (generic)
#define SGEMM_M1_SEGFAULTS 1
/// 0 : old code, bias via sgemm (w/ problems)
/// 1 : just use cblas_saxpy alternate (simpler and avoids sgemm bug!)
#define BIAS_SAXPY 0

#if SGEMM_M1_SEGFAULTS==0 // issues ncc-3.4.20 and M==1 ???
#define SGEMM sgemm_
#define SGEMM_A1B0 sgemm_
#define SGEMM_A1B1K1 sgemm_
#define SGEMM_A1B0t sgemm_
#define SGEMM_A1tB1 sgemm_
#else
#define SGEMM sgemm_ // dangerous now - require bug workaround for M=1
#define SGEMM_A1B0   SGEMM_SAFE_A1B0
#define SGEMM_A1B1K1 SGEMM_SAFE_A1B1K1
#define SGEMM_A1B0t SGEMM_SAFE_A1B0t
#define SGEMM_A1tB1 SGEMM_SAFE_A1tB1
#endif

/// A workaround for SGEMM M==1 segfaults. (circa ncc-3.4.20, Jan 2022)
/// For alpha=1, beta=0 (main gemm calculation)
#define SGEMM_SAFE_A1B0(TRANSA,TRANSB, N,M,K, ALPHA,A,LDA, B,LDB, BETA,C,LDC) \
  do { \
    if(*(M) > 1){ \
      sgemm_(TRANSA,TRANSB, N,M,K, ALPHA,A,LDA, B,LDB, BETA,C,LDC); \
    }else{ \
      int const NN = *(N); \
      /*int const MM = *(M);*/ \
      int const KK = *(K); \
      if(*(M) == 1 && *(K) > 1){ /* using just M==1 */ \
        _Pragma("omp parallel if(NN * KK > 32768)") /* C99 */ \
        for (int n=0; n < (NN); ++n) { \
          float acc = 0.0f; \
          for (int k=0; k < (KK); ++k) { \
            acc += (A)[k * (NN) + n] * (B)[k]; \
          } \
          (C)[n] = acc; /* M==1 && beta==0.0 : no accumulation into C */ \
        } \
      }else{ /* M=1, K=1 */ \
        _Pragma("omp parallel if((NN) > 32768)") \
        for (int n=0; n < *(N); ++n) { \
          (C)[n] = (A)[n] * (B)[0]; \
        } \
      } \
    } \
  }while(0)
/// Backward Data also has alpha=1, beta=0, but B[] is transposed
// XXX test with jitconv -T BackwardData
#define SGEMM_SAFE_A1B0t(TRANSA,TRANSB, N,M,K, ALPHA,A,LDA, B,LDB, BETA,C,LDC) \
  do { \
    if(*(M) > 1){ \
      sgemm_(TRANSA,TRANSB, N,M,K, ALPHA,A,LDA, B,LDB, BETA,C,LDC); \
    }else{ \
      /* for M=1, B is K x 1, so vector ignoring the transpose is OK */ \
      int const NN = *(N); \
      /*int const MM = *(M);*/ \
      int const KK = *(K); \
      if(*(M) == 1 && *(K) > 1){ /* using just M==1 */ \
        _Pragma("omp parallel if(NN * KK > 32768)") /* C99 */ \
        for (int n=0; n < (NN); ++n) { \
          float acc = 0.0f; \
          for (int k=0; k < (KK); ++k) { \
            acc += (A)[k * (NN) + n] * (B)[k]; \
          } \
          (C)[n] = acc; /* M==1 && beta==0.0 : no accumulation into C */ \
        } \
      }else{ /* M=1, K=1 */ \
        _Pragma("omp parallel if((NN) > 32768)") \
        for (int n=0; n < *(N); ++n) { \
          (C)[n] = (A)[n] * (B)[0]; \
        } \
      } \
    } \
  }while(0)
/// for BackwardFilter, alpha=1, beta=1 and A is transposed
// XXX test with jitconv -T BackwardFilter
// try only a single omp || ?
// needs testing of ALL impls (update jitconv testBackwardFilter!!!)
#define SGEMM_SAFE_A1tB1_0(TRANSA,TRANSB, N,M,K, ALPHA,A,LDA, B,LDB, BETA,C,LDC) \
  do { \
    /*printf(" A1tB1 N=%d M=%d K=%d\n",*(N),*(M),*(K)); fflush(stdout);*/ \
    if(*(M) > 1){ \
      sgemm_(TRANSA,TRANSB, N,M,K, ALPHA,A,LDA, B,LDB, BETA,C,LDC); \
    }else{ \
      /* for M=1, A is N x K, so need A transpose wrt. Forward impl */ \
      int const NN = *(N); \
      int const KK = *(K); \
      int const NNKK = NN*KK; \
      /* M==1, any K */ \
      _Pragma("omp parallel if(NNKK > 32768)") /* C99 */ \
      for (int n=0; n < (NN); ++n) { \
        float acc = 0.0f; \
        for (int k=0; k < (KK); ++k) { \
          acc += (A)[n * (KK) + k] * (B)[k]; \
        } \
        (C)[n] += acc; /* beta=1 accumulation into C */ \
      } \
    } \
  }while(0)
#define SGEMM_SAFE_A1tB1(TRANSA,TRANSB, N,M,K, ALPHA,A,LDA, B,LDB, BETA,C,LDC) \
  do { \
    /*printf(" A1tB1 N=%d M=%d K=%d\n",*(N),*(M),*(K)); fflush(stdout);*/ \
    if(*(M) > 1){ \
      sgemm_(TRANSA,TRANSB, N,M,K, ALPHA,A,LDA, B,LDB, BETA,C,LDC); \
    }else{ \
      /* for M=1, A is N x K, so need A transpose wrt. Forward impl */ \
      int const NN = *(N); \
      int const KK = *(K); \
      if(*(M) == 1 && *(K) > 1){ /* using just M==1 */ \
        /*_Pragma("omp parallel if(NN * KK > 32768)")*/ /* C99 */ \
        for (int n=0; n < (NN); ++n) { \
          float acc = 0.0f; \
          for (int k=0; k < (KK); ++k) { \
            acc += (A)[n * (KK) + k] * (B)[k]; \
          } \
          (C)[n] += acc; /* beta=1 accumulation into C */ \
        } \
      }else{ /* M=1, K=1 */ \
        /*_Pragma("omp parallel if((NN) > 32768)")*/ \
        for (int n=0; n < (NN); ++n) { \
          (C)[n] += (A)[n] * (B)[0]; /* beta=1 accum */ \
        } \
      } \
    } \
  }while(0)
// using M==1 and K==1
// here A[N] is 1.0
/// workaround for M=1 bias segfault.
/// Here K=1, alpha=1, beta=1, \b and A[] is all-1.0  (bias accumulation)
#define SGEMM_SAFE_A1B1K1(TRANSA,TRANSB, N,M,K, ALPHA,A,LDA, B,LDB, BETA,C,LDC) \
  do { \
    if(1 && *(M) > 1) /* always elide? */ \
    { \
      sgemm_(TRANSA,TRANSB, N,M,K, ALPHA,A,LDA, B,LDB, BETA,C,LDC); \
    }else{ \
      int const MN = *(M) * *(N); \
      float const B_0 = *(B);/* (B)[0] */ \
      /* wrong output if try to parallelize? */ \
      /* _Pragma("omp parallel if(MN > 32768)") */ \
      for (int mn=0; mn < MN; ++mn) { \
        (C)[mn] += B_0; \
      } \
    } \
  }while(0)

#if 0
#define DBG(...) do{printf(__VA_ARGS__);fflush(stdout);}while(0)
#else
#define DBG(...)
#endif

#if 1
void sgemm_(char *TRANSA, char *TRANSB, int *M, int *N, int *K,
    float *ALPHA, float *A,  int *LDA, float *B, int *LDB,
    float *BETA, float *C, int *LDC ) ;
//void cblas_saxpy(const int N, const float alpha, const float *X,
//                 const int incX, float *Y, const int incY);
#endif

static char  TRANS   = 'T';
static char  NOTRANS = 'N';
static float FONE    = 1.0f;
static float FZERO   = 0.0f;
static int   IONE    = 1;

/* ----------------------------------------------------------------------- */
static inline int is_a_ge_zero_and_a_lt_b(int a, int b) {
  //return (unsigned)a < (unsigned)b;
  return a>=0  && a<b; // for ncc auto vectorization, this is better
}

  static void
#if 0
im2col_cpu(const float * restrict data_im, const int channels,
    const int height, const int width, const int kernel_h, const int kernel_w,
    const int output_h, const int output_w,
    const int pad_h, const int pad_w,
    const int stride_h, const int stride_w,
    const int dilation_h, const int dilation_w,
    float * restrict data_col)
#else
im2col_cpu(const float * data_im, const int channels,
    const int height, const int width, const int kernel_h, const int kernel_w,
    const int output_h, const int output_w,
    const int pad_h, const int pad_w,
    const int stride_h, const int stride_w,
    const int dilation_h, const int dilation_w,
    float * data_col)
#endif
{
  LFTRACE_BEGIN("im2col_cpu");
#if 0
  const int output_h = (height + 2 * pad_h - (dilation_h * (kernel_h - 1) + 1)) / stride_h + 1;
  const int output_w = (width + 2 * pad_w -  (dilation_w * (kernel_w - 1) + 1)) / stride_w + 1;
#endif
  const int channel_size = height * width;

  int channel;
  if (0){
    // Note: alloc is for ic * ih * kw, but probably only need (ic/g) * kh * kw here?
    DBG("im2col_cpu _alloc needs %llu floats\n",(long long)channels*channel_size);
    for(size_t i=0; i<(size_t)channels*(size_t)channel_size; ++i){
      data_col[i] = 0.0f;
    }
    DBG("data_col / pColBuf accessible");
  }

//#pragma omp parallel for if(channels>=3)
  for (channel = 0 ; channel < channels; channel++) {       // inChannel
    //printf(" i2c c%d/%d",channel,channels); fflush(stdout);
    int kernel_row, kernel_col, output_rows, output_cols, output_col;

    int inOffset = channel * channel_size;
    int outOffset = channel * output_h * output_w * kernel_h * kernel_w;

    for (kernel_row = 0; kernel_row < kernel_h; kernel_row++) {   // kernHeight
      for (kernel_col = 0; kernel_col < kernel_w; kernel_col++) {   // kernWidth
        int input_row = -pad_h + kernel_row * dilation_h;
        for (output_rows = output_h; output_rows; output_rows--) {  // outHeight
          if (!is_a_ge_zero_and_a_lt_b(input_row, height)) {
            for (output_cols = output_w; output_cols; output_cols--) {
              // *(data_col++) = 0;
              data_col[outOffset++] = 0.f;
            }
          } else {
            int input_col = -pad_w + kernel_col * dilation_w;
            // following still bombed
            //#pragma _NEC novector
            for (output_col = output_w; output_col; output_col--) { // outWidth
#if 1 // newer
              data_col[outOffset++] //*(data_col++)
                = (is_a_ge_zero_and_a_lt_b(input_col, width)
                    ? data_im[inOffset + input_row * width + input_col]
                    : 0.f);
#else // older
              if (outOffset < 0 || outOffset >= channels*kernel_h*kernel_w*output_h*output_w){
                printf("ERROR: outOffset"); fflush(stdout); exit(-1);
              }
              if (is_a_ge_zero_and_a_lt_b(input_col, width)) {
                // *(data_col++) = data_im[input_row * width + input_col];
                data_col[outOffset] = data_im[inOffset + input_row * width + input_col];
              } else {
                // *(data_col++) = 0;
                data_col[outOffset] = 0;
              }
              ++outOffset;
#endif
              input_col += stride_w;
            }
          }
          input_row += stride_h;
        }
      }
    }
  }
  LFTRACE_END("im2col_cpu");
}

static void
col2im_cpu(
    const float* data_col, const int channels,
    const int height, const int width, const int kernel_h, const int kernel_w,
    const int output_h, const int output_w,
    const int pad_h, const int pad_w,
    const int stride_h, const int stride_w,
    const int dilation_h, const int dilation_w,
    float* data_im) {

  LFTRACE_BEGIN("col2im_cpu");
  memset(data_im, 0, sizeof(float)*height*width*channels) ;

#if 0
  const int output_h = (height + 2 * pad_h - (dilation_h * (kernel_h - 1) + 1)) / stride_h + 1;
  const int output_w = (width + 2 * pad_w -  (dilation_w * (kernel_w - 1) + 1)) / stride_w + 1;
#endif

  const int channel_size = height * width;

  int channel;

#pragma omp parallel for if(channels>=3)
  for (channel = 0 ; channel < channels; channel++) {       // inChannel
    int kernel_row, kernel_col, output_rows, output_cols, output_col;

    int inOffset = channel * channel_size;
    int outOffset = channel * output_h * output_w * kernel_h * kernel_w;

    for (kernel_row = 0; kernel_row < kernel_h; kernel_row++) {   // kernHeight
      for (kernel_col = 0; kernel_col < kernel_w; kernel_col++) {   // kernWidth
        int input_row = -pad_h + kernel_row * dilation_h;
        for (output_rows = output_h; output_rows; output_rows--) {  // outHeight
          if (!is_a_ge_zero_and_a_lt_b(input_row, height)) {
            for (output_cols = output_w; output_cols; output_cols--) {
              data_col[outOffset++] ;
            }
          } else {
            int input_col = -pad_w + kernel_col * dilation_w;
            for (output_col = output_w; output_col; output_col--) { // outWidth
              if (is_a_ge_zero_and_a_lt_b(input_col, width)) {
                data_im[inOffset + input_row * width + input_col] += data_col[outOffset++] ;
              } else {
                outOffset++ ;
              }
              input_col += stride_w;
            }
          }
          input_row += stride_h;
        }
      }
    }
  }
  LFTRACE_END("col2im_cpu");
}

// pOne is oh*ow of 1.0f
// pColBuff is scratch of ic*kw*kh * ow*oh * iw*ih (huge, in this version) see conv_test_param.c
  vednnError_t
convolution_forward_gemm(
    const vednnTensorParam_t * restrict pParamIn, const void * restrict pDataIn,
    const vednnFilterParam_t * restrict pParamKernel, const void * restrict pDataKernel,
    const vednnBiasParam_t * restrict pParamBias, const void * restrict pDataBias,
    const vednnTensorParam_t * restrict pParamOut, void * restrict pDataOut,
    //const float * restrict pOne,  float * restrict pColBuff,
    const float * pOne,  float * pColBuff,
    const vednnConvolutionParam_t * restrict pParamConv )
{
  LFTRACE_BEGIN("convolution_forward_gemm");

  int batch   = pParamIn->batch;
  int inChannel = pParamIn->channel;
  int inWidth   = pParamIn->width;
  int inHeight  = pParamIn->height;
  int outChannel  = pParamOut->channel;
  int outWidth  = pParamOut->width;
  int outHeight = pParamOut->height;
  int kernWidth = pParamKernel->width;
  int kernHeight  = pParamKernel->height;

  int group   = pParamConv->group;
  int strideWidth = pParamConv->strideWidth;;
  int strideHeight  = pParamConv->strideHeight;
  int padWidth  = pParamConv->padWidth;
  int padHeight = pParamConv->padHeight;
  int dilationWidth = pParamConv->dilationWidth;
  int dilationHeight  = pParamConv->dilationHeight;

  int inChannelGroup  = inChannel  / group; // pParamKernel->inChannel
  int outChannelGroup = outChannel / group; // pParamKernel->outChannel

  int no_im2col = (kernWidth == 1 && kernHeight == 1 && strideWidth == 1 && strideHeight == 1 && padWidth == 0 && padHeight == 0);
  if (0){
    printf("mb=%d g=%d ic=%d ic/g=%d noi2c=%d", batch,group,inChannel,inChannelGroup, no_im2col);
    fflush(stdout);
    float const* pk = (float const*)(pDataKernel); // assume float (debug) XXX
    int const ksz = group * outChannelGroup * inChannelGroup * kernHeight * kernWidth;
    size_t const ksz2 = getKernelSize(pParamKernel) * pParamConv->group;
    if (ksz != ksz2) {
      printf("ksz %d != ksz2 %d\n");
    }
    for(int i=0; i<ksz; ++i){
      if (isnan(pk[i])) {
        printf("generateRandomData --> nans!\n");
        printf("ksz=%d i=%d\n");
        exit(-1);
      }
      if (pk[i] < -5.0 || pk[i] > +5.0){
        printf("generateRandomData --> outside [-5.0,5.0]\n");
        printf("ksz=%d i=%d\n");
        exit(-1);
      }
    }
    printf("input pDataKernel[0..%d - 1] looks good\n",ksz);
    printf("input pDataKernel[0..%lu - 1] looks good\n",(long unsigned)ksz2);
  }

  float * transformed_filter = NULL ;
  if( pParamKernel->layout == VEDNN_FILTER_LAYOUT_HWCN ) { // only support group=1
    if (group!=1){
      printf("Unsupported ref calc: HWCN wants group==1");
      exit(-1);
      //return VEDNN_ERROR_INVALID_PARAM;
    }

    const int N = outChannel ;
    const int C = inChannel ;
    const int H = kernHeight ;
    const int W = kernWidth ;

    float * filter = (float *) pDataKernel ;
    transformed_filter = (float *) malloc(sizeof(float)*N*C*H*W) ;
#pragma omp parallel for
    for(int n=0; n<N ; n++) {
      for(int c=0; c<C ; c++) {
        for(int hw=0; hw<H*W ; hw++) {
          transformed_filter[((n*C+c)*H)*W+hw] = filter[((hw)*C+c)*N+n] ;
        }
      }
    }
  }

  const float * restrict pIn     = pDataIn;
  const float * restrict pBias   = pDataBias;
  const float * restrict pKernel = transformed_filter == NULL ? pDataKernel : transformed_filter ;
  float * restrict pOut    = pDataOut;

  for (int n = 0; n < batch; n++) { // this->num_
    int inBatchOffset  = n * inChannel  * inWidth  * inHeight;
    int outBatchOffset = n * outChannel * outWidth * outHeight;

    for (int g = 0; g < group; g++) {
      int inGroupOffset   = g * inChannelGroup                   * inHeight   * inWidth;
      int outGroupOffset  = g * outChannelGroup                  * outHeight  * outWidth;
      int kernGroupOffset = g * outChannelGroup * inChannelGroup * kernHeight * kernWidth;
      int biasGroupOffset = g * outChannelGroup;

      int inOffset  = inBatchOffset  + inGroupOffset;
      int outOffset = outBatchOffset + outGroupOffset;

      if (no_im2col) {
        int M = outChannelGroup;
        int N = outWidth * outHeight;
        int K = inChannelGroup;
        int LDA = inWidth * inHeight;
        //printf(" M=%d N=%d K=%d ",M,N,K);

#if SGEMM_M1_SEGFAULTS==0 // issues ncc-3.4.20 and M==1 ???
        SGEMM(&NOTRANS, &NOTRANS, &N, &M, &K,
            &FONE, (float *) &pIn[inOffset], &LDA,
            (float *) &pKernel[kernGroupOffset], &K,
            &FZERO, &pOut[outOffset], &N);
#elif SGEMM_M1_SEGFAULTS==1 // issues ncc-3.4.20 and M==1 ???
        SGEMM_A1B0(&NOTRANS, &NOTRANS, &N, &M, &K,
            &FONE, (float *) &pIn[inOffset], &LDA,
            (float *) &pKernel[kernGroupOffset], &K,
            &FZERO, &pOut[outOffset], &N);
#else // ncc-3.4.20 fixup...  Here I show general equivalent for the SGEMM...
        if (M>1) { // || K>1) {
          SGEMM(&NOTRANS, &NOTRANS, &N, &M, &K,
              &FONE, (float *) &pIn[inOffset], &LDA, // LDA=N for no_im2col
              (float *) &pKernel[kernGroupOffset], &K, // LDB=K for no_im2col
              &FZERO, &pOut[outOffset], &N);
        }else{
          // M==1 has some BLAS segv !!! just write it out for now...
          // At -O3 and -O4, ncc should have -fassociative-math -fmatrix-multiply
          // and should emit matrix-multiply code for these
          // consulting sgemm docs
          //  3rd dim is the summation index
          float const* A = (float const*)&pIn[inOffset];            // size N x K
          float const* B = (float const*)&pKernel[kernGroupOffset]; // size K x M
          float * C = &pOut[outOffset];                             // size N x M
#if 0 // generic, long-hand matrix multiply
          if (M>1) { // actually this is NOT quite right yet :(
            for (int n=0; n<N; n++) {
              for (int m=0; m<M; m++) {
                C[m*N + n] = 0.0f;
              }
            }
            for (int n=0; n<N; n++) {
              for (int m=0; m<M; m++) {
                float acc = 0.0f;
                for (int k=0; k<K; k++) {
                  acc += A[k*N + n] * B[m*K + k];
                }
                C[m*N + n] += acc; // beta=0
              }
            }
          }else
#endif
#if 1
            if(M==1 && K>1){ // using just M==1
#pragma omp parallel if(N*K>GEMM_PARA_THRESH)
              for (int n=0; n<N; n++) {
                float acc = 0.0f;
                for (int k=0; k<K; k++) {
                  acc += A[k*N+n] * B[k];
                }
                C[n] = acc; // beta=0, no accumulation into C
              }
            }else
#endif
          { // using M==1 and K==1
            float b0 = B[0];
#pragma omp parallel if(N*K>GEMM_PARA_THRESH)
            for (int n=0; n<N; n++) {
              C[n] = A[n] * b0;
            }
          }
        }
#endif

        if (pBias) {
          //printf("noi2c pBias M=%d N=%d K=%d ", M,N,K);
#if SGEMM_M1_SEGFAULTS==0 // issues ncc-3.4.20 and M==1 ???
          SGEMM(&NOTRANS, &NOTRANS, &N, &M, &IONE,
              &FONE, (float *) pOne, &N,
              (float *) &pBias[biasGroupOffset], &IONE,
              &FONE, &pOut[outOffset], &N);
#elif SGEMM_M1_SEGFAULTS==1 // issues ncc-3.4.20 and M==1 ???
#if BIAS_SAXPY==0
          SGEMM_A1B1K1(&NOTRANS, &NOTRANS, &N, &M, &IONE,
              &FONE, (float *) pOne, &N,                // N x 1
              (float *) &pBias[biasGroupOffset], &IONE, // 1 x M
              &FONE, &pOut[outOffset], &N);             // N x M
#else
          // note that this might be formulated as a
          // SAXPY( N=MN,
          //        ALPHA=1.0,
          //        X=&pBias[biasGroupOffset],
          //        INCX=0, /* <-- "add constant" */
          //        Y=&pOut[outOffset], /* add to  this */
          //        INCY=1
          //      )
          // Unfortunately, this only handles for M=1
          if (M>1) {
            SGEMM(&NOTRANS, &NOTRANS, &N, &M, &IONE,
                &FONE, (float *) pOne, &N,
                (float *) &pBias[biasGroupOffset], &IONE,
                &FONE, &pOut[outOffset], &N);
          }else{
            //printf(" saxpy M=%d N=%d\n", M,N);
            cblas_saxpy( N, 1.0, &pBias[biasGroupOffset], 0,
                &pOut[outOffset], 1);
          }
#endif
#elif 0 // debug
          {
            // M==1 has some BLAS segv !!! just write it out for now...
            // K=1 summation index is a huge simplification
            //   We might want to never fully call the SGEMM!
            //float const* A = (float const*)pOne;            // size N x K
            float const* B = (float const*)&pBias[biasGroupOffset]; // size K x M
            float * C = &pOut[outOffset];                   // size N x M
            if (1) { // further simplificcation only elides 1 scalar multiply
              //for (int n=0; n<N; n++) for (int m=0; m<M; m++) C[m*N + n] = 0.0f;
#if 0 // version 0, all loops before simplification
              printf("x7");
              int const K = 1;
              for (int n=0; n<N; n++) {
                for (int m=0; m<M; m++) {
                  float acc = 0.0f;
                  for (int k=0; k<K; k++) {
                    acc += 1.0/* A[k*N + n] */ * B[m*K + k];
                  }
                  C[m*N + n] += acc; // beta=0
                }
              }
#elif 0 // K=1 is a drastic simplification
              printf("x8");
              for (int m=0; m<M; ++m) {
                for (int n=0; n<N; ++n) {
                  C[m*N + n] += B[0]; // beta=0
                }
              }
#else // not working with omp parllel
              DBG("x9");
              int const MN = M * N;
//#pragma omp parallel if(MN > GEMM_PARA_THRESH) /* this cause wrong output */
              for (int mn=0; mn < MN; ++mn) {
                C[mn] += B[0]; // beta=0
              }
            }
          }
#endif

#else // dev code, summarized
          // maybe it's faster to always elide the SGEMM?
          if (M>1) {
            SGEMM(&NOTRANS, &NOTRANS, &N, &M, &IONE,
                &FONE, (float *) pOne, &N,
                (float *) &pBias[biasGroupOffset], &IONE,
                &FONE, &pOut[outOffset], &N);
          }else{
            // workaround for M=1 segfault circa ncc 3.4.20
            // using M==1 and K==1
            // here A[N] is 1.0
            float b0 = pBias[biasGroupOffset+0]; // B[0]
            int const MN = M * N;
            /* _Pragma("omp parallel if(MN > 32768)") //wrong output? */
            for (int mn=0; mn < MN; ++mn) {
              pOut[outOffset+mn] += b0; // C[n]
            }
          }
#endif
        }// if pBias

      } else {

        int M = outChannelGroup;
        int N = outWidth * outHeight;
        int K = inChannelGroup * kernWidth * kernHeight;

        im2col_cpu(&pIn[inOffset],
            inChannelGroup, inHeight, inWidth, kernHeight, kernWidth, outHeight, outWidth,
            padHeight, padWidth, strideHeight, strideWidth, dilationHeight, dilationWidth,
            pColBuff);

#if SGEMM_M1_SEGFAULTS==0 // issues ncc-3.4.20 and M==1 ???
        SGEMM(&NOTRANS, &NOTRANS, &N, &M, &K,
            &FONE, pColBuff, &N,
            (float *)&pKernel[kernGroupOffset], &K,
            &FZERO, &pOut[outOffset], &N);
        // segfault if M==1, at least w/ ncc 3.4.20 etc.
#elif SGEMM_M1_SEGFAULTS==1 // issues ncc-3.4.20 and M==1 ???
        SGEMM_A1B0(&NOTRANS, &NOTRANS, &N, &M, &K,
            &FONE, pColBuff, &N,
            (float *)&pKernel[kernGroupOffset], &K,
            &FZERO, &pOut[outOffset], &N);
#else
        if (M>1) {
          SGEMM(&NOTRANS, &NOTRANS, &N, &M, &K,
              &FONE, pColBuff, &N,                    // N x K
              (float *)&pKernel[kernGroupOffset], &K, // K x M
              &FZERO, &pOut[outOffset], &N);          // N x M
        }else{
          // M==1 has some BLAS segv !!! just write it out for now...
          // At -O3 and -O4, ncc should have -fassociative-math -fmatrix-multiply
          // and should emit matrix-multiply code for these
          float const* A = pColBuff;                                  // osz x icg*ksz (?)
          float const* B = (float const*)&pKernel[kernGroupOffset];   // icg*ksz x ocg
          float      * C = &pOut[outOffset];                          // osz x ocg (?)
#if 0
          if (M>1) {
            for (int n=0; n<N; n++) {
              for (int m=0; m<M; m++) {
                C[m*N + n] = 0.0f;
              }
            }
            for (int n=0; n<N; n++) {
              for (int m=0; m<M; m++) {
                float acc = 0.0f;
                for (int k=0; k<K; k++) {
                  acc += A[k*N + n] * B[m*K + k];
                }
                C[m*N + n] += acc; // beta=0, no accumulation into C
              }
            }
          }else
#endif
          { // M==1
            for (int n=0; n<N; n++) {
              float acc = 0.0f;
              for (int k=0; k<K; k++) {
                acc += A[k*N+n] * B[k];
              }
              C[n] = acc;
            }
          }
        }
#endif
        //printf(" back from SGEMM..."); fflush(stdout);

        if (pBias) {
          //printf("i2c bias...\n"); fflush(stdout);
#if SGEMM_M1_SEGFAULTS==0 // issues ncc-3.4.20 and M==1 ???
          SGEMM(&NOTRANS, &NOTRANS, &N, &M, &IONE,
              &FONE, (float *)pOne, &N,
              (float *) &pBias[biasGroupOffset], &IONE,
              &FONE, &pOut[outOffset], &N);
#elif SGEMM_M1_SEGFAULTS==1 // issues ncc-3.4.20 and M==1 ???
#if 1 //BIAS_SAXPY==0
          SGEMM_A1B1K1(&NOTRANS, &NOTRANS, &N, &M, &IONE,
              &FONE, (float *)pOne, &N,                 // size N x 1
              (float *) &pBias[biasGroupOffset], &IONE, // size 1 x M
              &FONE, &pOut[outOffset], &N);             // size N x M
#else
          cblas_saxpy( M*N, 1.0, &pBias[biasGroupOffset], 0,
              &pOut[outOffset], 1);
#endif
#else
          if (M>1) {
            SGEMM(&NOTRANS, &NOTRANS, &N, &M, &IONE,
                &FONE, (float *) pOne, &N,
                (float *) &pBias[biasGroupOffset], &IONE,
                &FONE, &pOut[outOffset], &N);
          }else{
            // workaround for M=1 segfault circa ncc 3.4.20
            // using M==1 and K==1
            // here A[N] is 1.0
            float b0 = pBias[biasGroupOffset+0]; // B[0]
            for (int n=0; n<N; n++) {
              pOut[outOffset+n] += b0; // C[n]
            }
          }
#endif
        }
      } // no_im2col?
    } // group
  } // batch

  if( transformed_filter != NULL ) free(transformed_filter) ;

  LFTRACE_END("convolution_forward_gemm");
  return VEDNN_SUCCESS;
}

  vednnError_t
convolution_backward_data_gemm(
    const vednnTensorParam_t * restrict pParamGradOut, const void * restrict pDataGradOut,
    const vednnFilterParam_t * restrict pParamKernel, const void * restrict pDataKernel,
    const vednnTensorParam_t * restrict pParamGradIn, void * restrict pDataGradIn,
    float * restrict pColBuff,
    const vednnConvolutionParam_t * restrict pParamConv )
{
  LFTRACE_BEGIN("convolution_backward_data_gemm");
  int n, g;

  int batch   = pParamGradOut->batch;
  int gOutChannel = pParamGradOut->channel;
  int gOutWidth = pParamGradOut->width;
  int gOutHeight  = pParamGradOut->height;
  int gInChannel  = pParamGradIn->channel;
  int gInWidth  = pParamGradIn->width;
  int gInHeight = pParamGradIn->height;
  int kernWidth = pParamKernel->width;
  int kernHeight  = pParamKernel->height;

  int group   = pParamConv->group;
  int strideWidth = pParamConv->strideWidth;;
  int strideHeight  = pParamConv->strideHeight;
  int padWidth  = pParamConv->padWidth;
  int padHeight = pParamConv->padHeight;
  int dilationWidth = pParamConv->dilationWidth;
  int dilationHeight  = pParamConv->dilationHeight;

  int gOutChannelGroup = gOutChannel  / group;
  int gInChannelGroup  = gInChannel / group;

  int no_im2col = (kernWidth == 1 && kernHeight == 1 && strideWidth == 1 && strideHeight == 1 && padWidth == 0 && padHeight == 0);

  float * transformed_filter = NULL ;
  if( pParamKernel->layout == VEDNN_FILTER_LAYOUT_HWCN ) { // only support group=1

    const int N = gOutChannel ;
    const int C = gInChannel ;
    const int H = kernHeight ;
    const int W = kernWidth ;

    float * filter = (float *) pDataKernel ;
    transformed_filter = (float *) malloc(sizeof(float)*N*C*H*W) ;
#pragma omp parallel for
    for(int n=0; n<N ; n++) {
      for(int c=0; c<C ; c++) {
        for(int hw=0; hw<H*W ; hw++) {
          transformed_filter[((n*C+c)*H)*W+hw] = filter[((hw)*C+c)*N+n] ;
        }
      }
    }
  }

  const float * restrict pGradOut = pDataGradOut;
  const float * restrict pKernel  = transformed_filter == NULL ? pDataKernel : transformed_filter ;
  float * restrict pGradIn  = pDataGradIn;

  for (n = 0; n < batch; n++) { // this->num_
    int gOutBatchOffset  = n * gOutChannel  * gOutWidth  * gOutHeight;
    int gInBatchOffset = n * gInChannel * gInWidth * gInHeight;

    for (g = 0; g < group; g++) {
      int gOutGroupOffset = g *                   gOutChannelGroup * gOutHeight * gOutWidth;
      int gInGroupOffset  = g * gInChannelGroup                    * gInHeight  * gInWidth;
      int kernGroupOffset = g * gInChannelGroup * gOutChannelGroup * kernHeight * kernWidth;

      int gOutOffset = gOutBatchOffset + gOutGroupOffset;
      int gInOffset  = gInBatchOffset  + gInGroupOffset;

      int M = gInChannelGroup * kernWidth * kernHeight;
      int N = gOutWidth * gOutHeight;
      int K = gOutChannelGroup;

      if( no_im2col ) {
        SGEMM_A1B0t(&NOTRANS, &TRANS, &N, &M, &K,
            &FONE, (float *) &pGradOut[gOutOffset], &N, // N x K
            (float *) &pKernel[kernGroupOffset], &M,    // M x K (trans!)
            &FZERO, &pGradIn[gInOffset], &N);           // N x M
      }
      else {
        SGEMM_A1B0t(&NOTRANS, &TRANS, &N, &M, &K,
            &FONE, (float *) &pGradOut[gOutOffset], &N,
            (float *) &pKernel[kernGroupOffset], &M,
            &FZERO, pColBuff, &N);

        col2im_cpu(pColBuff,
            gInChannelGroup, gInHeight, gInWidth, kernHeight, kernWidth, gOutHeight, gOutWidth,
            padHeight, padWidth, strideHeight, strideWidth, dilationHeight, dilationWidth,
            &pGradIn[gInOffset]);
      }
    } // group
  } // batch

  if( transformed_filter != NULL ) free(transformed_filter) ;

  LFTRACE_END("convolution_backward_data_gemm");
  return VEDNN_SUCCESS;
}

  vednnError_t
convolution_backward_filter_gemm(
    const vednnTensorParam_t * restrict pParamIn, const void * restrict pDataIn,
    const vednnTensorParam_t * restrict pParamGradOut, const void * restrict pDataGradOut,
    const vednnFilterParam_t * restrict pParamGradKernel, void * restrict pDataGradKernel,
    float * restrict pColBuff,
    const vednnConvolutionParam_t * restrict pParamConv )
{
  LFTRACE_BEGIN("convolution_backward_filter_gemm");
  int n, g;

  int batch   = pParamIn->batch;
  int inChannel = pParamIn->channel;
  int inWidth   = pParamIn->width;
  int inHeight  = pParamIn->height;
  int outChannel  = pParamGradOut->channel;
  int outWidth  = pParamGradOut->width;
  int outHeight = pParamGradOut->height;
  int kernWidth = pParamGradKernel->width;
  int kernHeight  = pParamGradKernel->height;

  int group   = pParamConv->group;
  int strideWidth = pParamConv->strideWidth;;
  int strideHeight  = pParamConv->strideHeight;
  int padWidth  = pParamConv->padWidth;
  int padHeight = pParamConv->padHeight;
  int dilationWidth = pParamConv->dilationWidth;
  int dilationHeight  = pParamConv->dilationHeight;

  int inChannelGroup  = inChannel  / group; // pParamKernel->inChannel
  int outChannelGroup = outChannel / group; // pParamKernel->outChannel

  int no_im2col = (kernWidth == 1 && kernHeight == 1 && strideWidth == 1 && strideHeight == 1 && padWidth == 0 && padHeight == 0);

  float * transformed_filter = NULL ;
  if( pParamGradKernel->layout == VEDNN_FILTER_LAYOUT_HWCN ) { // only support group=1
    const int N = outChannel ;
    const int C = inChannel ;
    const int H = kernHeight ;
    const int W = kernWidth ;

    transformed_filter = (float *) malloc(sizeof(float)*N*C*H*W) ;
#pragma omp parallel for
    for(int i=0; i<N*C*H*W; i++) transformed_filter[i] = 0.f ;
  }

  const float * restrict pIn     = pDataIn;
  const float * restrict pOut    = pDataGradOut;
  float * restrict pKernel = transformed_filter == NULL ? pDataGradKernel : transformed_filter ;

  for (n = 0; n < batch; n++) { // this->num_
    int inBatchOffset  = n * inChannel  * inWidth  * inHeight;
    int outBatchOffset = n * outChannel * outWidth * outHeight;

    for (g = 0; g < group; g++) {
      int inGroupOffset   = g * inChannelGroup                   * inHeight   * inWidth;
      int outGroupOffset  = g * outChannelGroup                  * outHeight  * outWidth;
      int kernGroupOffset = g * outChannelGroup * inChannelGroup * kernHeight * kernWidth;

      int inOffset  = inBatchOffset  + inGroupOffset;
      int outOffset = outBatchOffset + outGroupOffset;

      if( no_im2col ) {
        int M = outChannelGroup;
        int N = inChannelGroup * kernWidth * kernHeight;
        int K = outWidth * outHeight;

        SGEMM_A1tB1(&TRANS, &NOTRANS, &N, &M, &K,
            &FONE,  (float*)&pIn[inOffset], &K,
            (float*)&pOut[outOffset], &K,
            &FONE, &pKernel[kernGroupOffset], &N);
      }
      else {
        im2col_cpu(&pIn[inOffset],
            inChannelGroup, inHeight, inWidth, kernHeight, kernWidth, outHeight, outWidth,
            padHeight, padWidth, strideHeight, strideWidth, dilationHeight, dilationWidth,
            pColBuff);

        int M = outChannelGroup;
        int N = inChannelGroup * kernWidth * kernHeight;
        int K = outWidth * outHeight;

        SGEMM_A1tB1(&TRANS, &NOTRANS, &N, &M, &K,
            &FONE,  pColBuff, &K,
            (float*)&pOut[outOffset], &K,
            &FONE, &pKernel[kernGroupOffset], &N);
      }
    } // group
  } // batch

  if( transformed_filter != NULL ) {
    const int N = outChannel ;
    const int C = inChannel ;
    const int H = kernHeight ;
    const int W = kernWidth ;

    float * filter = (float *) pDataGradKernel ;
#pragma omp parallel for
    for(int n=0; n<N ; n++) {
      for(int c=0; c<C ; c++) {
        for(int hw=0; hw<H*W ; hw++) {
          filter[((hw)*C+c)*N+n] += transformed_filter[((n*C+c)*H)*W+hw] ;
        }
      }
    }

    free(transformed_filter) ;
  }

  LFTRACE_END("convolution_backward_filter_gemm");
  return VEDNN_SUCCESS;
}
// vim: et ts=2 sw=2 cindent cino=^0,=0,l0,\:0,N-s syntax=cpp.doxygen


/*
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <gsl/gsl_randist.h>
#include <gsl/gsl_rng.h>
//#include "gsl-sprng.h"
#include <mpi.h>
#include <time.h>
#include <unistd.h>

const int SAMPLENUM = 1000;
int obsnum;
gsl_rng * r;
int procnum;

typedef struct {
	int prey;
	int predator0;
	int predator1;
} lvstate;

typedef struct {
	double time;
	double rawdata;
} obsdata;

void simPrior(lvstate *simData, MPI_Datatype datatype, MPI_Comm comm);
void rowsample(int *rows, double *w);
void stepLV(lvstate *state, double *t0p, double *dtp, double *lvParam, MPI_Datatype datatype, MPI_Comm comm);
void peturb(double *lvParam, MPI_Datatype datatype, MPI_Comm comm);

double obsLik(lvstate *mystate, double obs)
{
		const double SIGMA = 20.0;
		return log(gsl_ran_gaussian_pdf((obs - mystate->prey), SIGMA));
}

void mpiStepLV(lvstate *simData, double *t0p, double *dtp, double *lvParam, MPI_Datatype datatype, MPI_Comm comm)
{
	//printf("...callStepLV...");
	int j, offset, destNode, sourceNode, rank;
	int procnum, avgsize, samplemod;
	double t0, dt;
	MPI_Status status;

	MPI_Comm_rank(MPI_COMM_WORLD,&rank);
	MPI_Comm_size(MPI_COMM_WORLD, &procnum);
	//const unsigned int MAX_MPI_BUFFER = 524300000;
	//printf("rank in mpiStepLV %i \n", rank);
	//for (j = 0; j < SAMPLENUM; j++) {
		//MPI_Bcast(&simData[j].predator, 1, MPI_INT, 0, comm);
		//MPI_Bcast(&simData[j].prey, 1, MPI_INT, 0, comm);
	//}
	MPI_Bcast(lvParam, 5, MPI_DOUBLE, 0, comm);
	MPI_Bcast(t0p, 1, MPI_DOUBLE, 0, comm);
	MPI_Bcast(dtp, 1, MPI_DOUBLE, 0, comm);

	avgsize = SAMPLENUM/(procnum-1);
	samplemod = SAMPLENUM%(procnum-1);

	t0 = *t0p;
	dt = *dtp;

	if (rank==0)
	{
		//send tasks to other nodes

		offset = 0;

		for (destNode=1; destNode<procnum; destNode++){
			//MPI_Send(&lvParam, 3, MPI_DOUBLE, destNode, 0, MPI_COMM_WORLD);
			MPI_Send(&offset, 1, MPI_INT, destNode, 0, MPI_COMM_WORLD);
			for (j=0;j<avgsize;j++)
			{
				MPI_Send(&simData[offset+j].prey, 1, MPI_INT, destNode, 0, MPI_COMM_WORLD);
				MPI_Send(&simData[offset+j].predator0, 1, MPI_INT, destNode, 0, MPI_COMM_WORLD);
				MPI_Send(&simData[offset+j].predator1, 1, MPI_INT, destNode, 0, MPI_COMM_WORLD);
			}
			offset = offset + avgsize;
		}

		for (j=0;j<samplemod;j++){
			stepLV(simData+offset+j, &t0, &dt, lvParam, MPI_FLOAT, MPI_COMM_WORLD);
		}

		// Wait to receive results from each node
		for (sourceNode=1; sourceNode<procnum; sourceNode++){

			MPI_Recv(&offset, 1, MPI_INT, sourceNode, 0, MPI_COMM_WORLD, &status);
			for (j=0;j<avgsize;j++)
			{
				MPI_Recv(&simData[offset+j].prey, 1, MPI_INT, sourceNode, 0, MPI_COMM_WORLD, &status);
				MPI_Recv(&simData[offset+j].predator0, 1, MPI_INT, sourceNode, 0, MPI_COMM_WORLD, &status);
				MPI_Recv(&simData[offset+j].predator1, 1, MPI_INT, sourceNode, 0, MPI_COMM_WORLD, &status);
			}
		}
	}
	else if(rank>0 && rank<procnum)
	{
		//Receive data distributed from master node
		MPI_Recv(&offset, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &status);
		for (j=0;j<avgsize;j++)
		{
			MPI_Recv(&simData[offset+j].prey, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &status);
			MPI_Recv(&simData[offset+j].predator0, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &status);
			MPI_Recv(&simData[offset+j].predator1, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &status);
		}

		//doing stepLV for every slave processor/node
		for (j=0;j<avgsize;j++)
		{
			stepLV(simData+offset+j, &t0, &dt, lvParam, MPI_FLOAT, MPI_COMM_WORLD);
		}
		//printf("---5---");
		MPI_Send(&offset, 1, MPI_INT, 0, 0, MPI_COMM_WORLD);
		for (j=0;j<avgsize;j++)
		{
			MPI_Send(&simData[offset+j].prey, 1, MPI_INT, 0, 0, MPI_COMM_WORLD);
			MPI_Send(&simData[offset+j].predator0, 1, MPI_INT, 0, 0, MPI_COMM_WORLD);
			MPI_Send(&simData[offset+j].predator1, 1, MPI_INT, 0, 0, MPI_COMM_WORLD);
		}

	}
	//MPI_Buffer_detach( &buff, &b_size );
	return;
}

//particle filter: output new estimated lvstate and its likehood
void pfPropPar(lvstate ppstate[16], obsdata *myobsdata, double *lvParam, double *ll, MPI_Datatype datatype, MPI_Comm comm)
{
	int rank, procnum;
	MPI_Comm_rank(MPI_COMM_WORLD,&rank);
	MPI_Comm_size(MPI_COMM_WORLD, &procnum);

	typedef struct {
		lvstate *vpptuple;
	} stateVec;

	int i, j, k, l;

	double timeUnit = 2.0;
	int deltaTimeUnit = 1;
	double delTime = timeUnit * deltaTimeUnit;
	double curTime = 0.0;

	double lw[SAMPLENUM];
	double w[SAMPLENUM];
	double wSum;
	double maxLw;
	double likehood = 0.0;
	unsigned int rows[SAMPLENUM];

	lvstate *simData = malloc(SAMPLENUM * sizeof(lvstate));
	if (simData == NULL) {
		printf("Fail to initiate simData...EXIT!");
		goto freeMem;
	}
	else {
		memset(simData, 0, SAMPLENUM * sizeof(lvstate));
	}
	//initiate state: get simulated prey-predator tuple
		stateVec *myStateVec = malloc(obsnum * sizeof(stateVec));
		if (myStateVec == NULL) {
			printf("Fail to initiate myStateVec...Exit");
			goto freeMem;
		}
		memset(myStateVec, 0, obsnum * sizeof(stateVec));

		stateVec *tempStateVec = malloc(obsnum * sizeof(stateVec));
		if (tempStateVec == NULL) {
			printf("Fail to initiate tempStateVec...Exit");
			goto freeMem;
		}
		memset(tempStateVec, 0, obsnum * sizeof(stateVec));

	if (rank==0)
	{
		simPrior(simData, MPI_FLOAT, MPI_COMM_WORLD);
		*ll = 0.0;

		if ((tempStateVec + 0)->vpptuple == NULL) {
			(tempStateVec + 0)->vpptuple = (lvstate*) malloc(
					SAMPLENUM * sizeof(lvstate));
			if ((tempStateVec + 0)->vpptuple == NULL) {
				printf("Fail to initiate myStateVec->vpptuple...Exit");
				goto freeMem;
			}
		}

		memset(tempStateVec[0].vpptuple, 0, SAMPLENUM * sizeof(lvstate));
		for (j = 0; j < SAMPLENUM; j++) {
			((tempStateVec + 0)->vpptuple + j)->predator0 = (simData + j)->predator0;
			((tempStateVec + 0)->vpptuple + j)->predator1 = (simData + j)->predator1;
			((tempStateVec + 0)->vpptuple + j)->prey = (simData + j)->prey;
		}
	}

	for (i = 1; i < obsnum; i++)
	{
		curTime = myobsdata[i-1].time;
		wSum = 0.0;

		if (rank==0)
		{
			if ((tempStateVec + i)->vpptuple == NULL) {
				(tempStateVec + i)->vpptuple = (lvstate*) malloc(
						SAMPLENUM * sizeof(lvstate));
				if ((tempStateVec + i)->vpptuple == NULL) {
					printf("Fail to initiate myStateVec->vpptuple...Exit");
					goto freeMem;
				}
				memset((tempStateVec + i)->vpptuple, 0,
						SAMPLENUM * sizeof(lvstate));
			}
		}

		mpiStepLV(simData, &curTime, &delTime, lvParam, MPI_FLOAT, MPI_COMM_WORLD);

		if (rank==0)
		{
			for (j = 0; j < SAMPLENUM; j++)
			{
				((tempStateVec + i)->vpptuple + j)->predator0 = (simData + j)->predator0;
				((tempStateVec + i)->vpptuple + j)->predator1 = (simData + j)->predator1;
				((tempStateVec + i)->vpptuple + j)->prey = (simData + j)->prey;
				lw[j] = obsLik(((tempStateVec + i)->vpptuple + j), myobsdata[i].rawdata);
			}

			//get the max line weight
			for (j = 0; j < SAMPLENUM; j++) {
				if (j == 0) {
					maxLw = lw[0];
				} else {
					if (lw[j] > maxLw) {
						maxLw = lw[j];
					}
				}
			}
			//normalize the line wight and output list w[]
			for (l = 0; l < SAMPLENUM; l++) {
				w[l] = exp(lw[l] - maxLw);
				wSum = wSum + w[l];
			}
			rowsample(rows, w);

			//reorganize the vector with only keeping the rows in 'rows'
			for (k = 0; k <= i; k++)
			{
				if ((myStateVec + k)->vpptuple == NULL) {
					//printf("init mystatevec  \n");
					(myStateVec + k)->vpptuple = (lvstate*) malloc(
							SAMPLENUM * sizeof(lvstate));
					if ((myStateVec + k)->vpptuple == NULL) {
						printf("Fail to initiate myStateVec->vpptuple...Exit");
						goto freeMem;
					}
				}

				for (j = 0; j < SAMPLENUM; j++) {
					((myStateVec + k)->vpptuple + j)->predator0 =
							((tempStateVec + k)->vpptuple + rows[j])->predator0;
					((myStateVec + k)->vpptuple + j)->predator1 =
							((tempStateVec + k)->vpptuple + rows[j])->predator1;
					((myStateVec + k)->vpptuple + j)->prey =
							((tempStateVec + k)->vpptuple + rows[j])->prey;
				}
				for (j = 0; j < SAMPLENUM; j++) {
					((tempStateVec + k)->vpptuple + j)->predator0 =
							((myStateVec + k)->vpptuple + j)->predator0;
					((tempStateVec + k)->vpptuple + j)->predator1 =
							((myStateVec + k)->vpptuple + j)->predator1;
					((tempStateVec + k)->vpptuple + j)->prey =
							((myStateVec + k)->vpptuple + j)->prey;
				}
			}
			for (j = 0; j < SAMPLENUM; j++) {
				simData[j].predator0 = ((myStateVec + i)->vpptuple + j)->predator0;
				simData[j].predator1 = ((myStateVec + i)->vpptuple + j)->predator1;
				simData[j].prey = ((myStateVec + i)->vpptuple + j)->prey;
			}
			likehood = likehood + maxLw + log(wSum/SAMPLENUM);
			//printf("likehood: %f, maxLW: %f, mean: %f, wSum: %f\n", likehood, maxLw, log(wSum/SAMPLENUM), wSum);
			*ll = likehood;
			rows[SAMPLENUM] = 0;
		}

	}
	if (rank==0)
	{
		for (i = 0; i < obsnum; i++) {
			ppstate[i].prey = ((myStateVec + i)->vpptuple + 0)->prey;
			ppstate[i].predator0 = ((myStateVec + i)->vpptuple + 0)->predator0;
			ppstate[i].predator1 = ((myStateVec + i)->vpptuple + 0)->predator1;
		}
	}

freeMem:

	if (myStateVec != NULL) {
		for (i = 0; ((myStateVec + i)->vpptuple != NULL) && i < obsnum; i++) {
			free((myStateVec + i)->vpptuple);
			(myStateVec + i)->vpptuple = NULL;
		}
		free(myStateVec);
		myStateVec = NULL;
	}
	if (tempStateVec != NULL) {
		for (i = 0; NULL != (tempStateVec + i)->vpptuple && i < obsnum; i++) {
			free((tempStateVec + i)->vpptuple);
			(tempStateVec + i)->vpptuple = NULL;
		}
		free(tempStateVec);
		tempStateVec = NULL;
	}
	if (simData != NULL) {
		free(simData);
		simData = NULL;
	}
}

void stepLV(lvstate *state, double *t0p, double *dtp, double *lvParam, MPI_Datatype datatype, MPI_Comm comm) {
	int rank;
	MPI_Comm_rank(MPI_COMM_WORLD,&rank);
	MPI_Comm_size(MPI_COMM_WORLD, &procnum);

	register double t0 = *t0p, dt = *dtp, t;
	register double h0, h1, h2, h3, h4, h5, u, l0, l1, l2, l3, l4;
	register int prey, predator0, predator1;
	prey = state->prey;
	predator0 = state->predator0;
	predator1 = state->predator1;
	l0 = lvParam[0];
	l1 = lvParam[1];
	l2 = lvParam[2];
	l3 = lvParam[3];
	l4 = lvParam[4];

	while (dt > 0) {
		h1 = l0 * prey;
		h2 = l1 * prey * predator0;
		h3 = l2 * prey * predator1;
		h4 = l3 * predator0;
		h5 = l4 * predator1;
		h0 = h1 + h2 + h3 + h4 + h5;

		if ((h0 < (1e-10)) || (prey >= 1000000))
			t = 1e99;
		else {
			t = gsl_ran_exponential(r, 1/h0);
		}
		if (t > dt) {
			state->prey = prey;
			state->predator0 = predator0;
			state->predator1 = predator1;
			return;
		} else {
			u = gsl_rng_uniform(r);
			if (u < (h1 / h0)) {
				prey = prey + 1;
			} else if (u < ((h1 + h2) / h0)) {
				prey = prey - 1;
				predator0 = predator0 + 1;
			} else if (u < ((h1 + h2 + h3) / h0)) {
				prey = prey - 1;
				predator1 = predator1 + 1;
			} else if (u < ((h1 + h2 + h3 + h4) / h0)) {
				predator0 = predator0 - 1;
			} else {
				predator1 = predator1 - 1;
			}
			dt = dt - t;

		}
	}
	return;
}

//mcmc process
void runPmmhPath(int its, double *lvParam, double *obslik, lvstate ppstate[16],
		obsdata *myobsdata, MPI_Datatype datatype, MPI_Comm comm)
{
	int rank;
	MPI_Comm_rank(MPI_COMM_WORLD,&rank);
	MPI_Comm_size(MPI_COMM_WORLD, &procnum);

	int i, j;
	double propParam[5];
	double curParam[5];
	double ll;
	double propMll, curMll = -1e99;
	lvstate curPath[obsnum];
	lvstate propPath[obsnum];
	FILE *fp = fopen("mcmc-out.txt", "wab+");
	if (fp == NULL) {
		printf("file is null.\n");
		return;
	}

	if (rank==0)
	{

		fprintf(fp, "th1,th2,th3,th4,th5,");
		for (i = 0; i <= 30; i++) {
			if ((i % 2) == 0) {
				if (i == 30) {
					fprintf(fp, "x%i,y0_%i,y1_%i", i, i, i);
				} else {
					fprintf(fp, "x%i,y0_%i,y1_%i,", i, i, i);
				}
			}
		}
		fprintf(fp, "\n");
		memcpy(curPath, ppstate, sizeof(lvstate) * obsnum);
		memcpy(curParam, lvParam, sizeof(double) * 5);
	}

	for (i = its; i > 0; i--) {
		memcpy(propParam, curParam, sizeof(double) * 5);
		peturb(propParam, MPI_FLOAT, MPI_COMM_WORLD);

		pfPropPar(propPath, myobsdata, propParam, &ll, MPI_FLOAT, MPI_COMM_WORLD);

		if (rank==0)
		{
			//printf("likelihood %f, parameter %f, %f, %f, %f, %f:\n", ll, propParam[0], propParam[1], propParam[2], propParam[3], propParam[4]);
			propMll = ll;
			if (log(gsl_ran_flat(r, 0.0, 1.0)) < (propMll - curMll)) {
				curMll = propMll;
				memcpy(curParam, propParam, sizeof(double) * 5);
				memcpy(curPath, propPath, sizeof(lvstate) * obsnum);
			}
		}

		if (rank==0)
		{
			printf("run for the %ith iteration.\n", i);
			//write current parameters and state sequence into output
			fprintf(fp, "%f,%f,%f,%f,%f,", curParam[0], curParam[1], curParam[2], curParam[3], curParam[4]);

			for (j = 0; j < obsnum; j++) {
				if (j == (obsnum - 1)) {
					fprintf(fp, "%i,%i,%i", curPath[j].prey, curPath[j].predator0, curPath[j].predator1);
				} else {
					fprintf(fp, "%i,%i,%i,", curPath[j].prey, curPath[j].predator0, curPath[j].predator1);
				}
			}
			fprintf(fp, "\n");
			printf("end for the %ith iteration.\n", i);
		}

	}
	fclose(fp);

}

void rowsample(int *rows, double *w)
{
	gsl_ran_discrete_t * grdp;
	int row;
	int i;
	grdp = gsl_ran_discrete_preproc(SAMPLENUM, w);

	for (i = 0; i < SAMPLENUM; i++) {
		row = (int) gsl_ran_discrete(r, grdp);
		rows[i] = row;
	}

	gsl_ran_discrete_free(grdp);
	return;

}

void peturb(double *lvParam, MPI_Datatype datatype, MPI_Comm comm) {
	int rank;
	MPI_Comm_rank(MPI_COMM_WORLD,&rank);
	if (rank==0)
	{
		const double SIGMA = 0.035;
		lvParam[0] = lvParam[0] * exp(gsl_ran_gaussian(r, SIGMA));
		lvParam[1] = lvParam[1] * exp(gsl_ran_gaussian(r, SIGMA));
		lvParam[2] = lvParam[2] * exp(gsl_ran_gaussian(r, SIGMA));
		lvParam[3] = lvParam[3] * exp(gsl_ran_gaussian(r, SIGMA));
		lvParam[4] = lvParam[4] * exp(gsl_ran_gaussian(r, SIGMA));
	}
	return;
}

//to get the prior simulate value of prey and predator
void simPrior(lvstate *simData, MPI_Datatype datatype, MPI_Comm comm)
{
	int rank;
	MPI_Comm_rank(MPI_COMM_WORLD,&rank);

	int i;

	const double PREY_MEAN = 800.0;
	const double PRED0_MEAN = 500.0;
	const double PRED1_MEAN = 600.0;

	for (i = 0; i < SAMPLENUM; i++) {
		simData[i].prey = gsl_ran_poisson(r, PREY_MEAN);
		simData[i].predator0 = gsl_ran_poisson(r, PRED0_MEAN);
		simData[i].predator1 = gsl_ran_poisson(r, PRED1_MEAN);
	}
	return;
}

void runModel(int its, MPI_Datatype datatype, MPI_Comm comm) {
	int rank;
	MPI_Comm_rank(MPI_COMM_WORLD,&rank);

	obsnum = 16;
	obsdata myobsdata[obsnum];
	lvstate mylvstate[obsnum];
	double ll = 0.0;
	double lvParam[5] = { 10.0, 0.005, 0.0025, 6.0, 3.0 };

	//produce time list(with even number in)
	if (rank==0) {
		int i;
		int j = 0;

		for (i = 0; i <= 30; i++) {
			if ((i % 2) == 0) {
				myobsdata[j].time = (double) i;
				j++;
			}
		}

		//read external txt file
		FILE *file = fopen("LV12.txt", "r");
		char line[1024];
		if (file == NULL) {
			printf("file is null.\n");
			exit(EXIT_FAILURE);
		}
		if (file != NULL) {
			i = 0;
			while (fgets(line, 1024, file)) {
				myobsdata[i].rawdata = atof(line);
				i++;
			}
			fclose(file);
		}
	}

	runPmmhPath(its, lvParam, &ll, mylvstate, myobsdata, MPI_FLOAT, MPI_COMM_WORLD);
	return;
}

int main(int argc,char *argv[])
{
	int its, i, rank;
	double x, can, a, alpha, Nsum, mytime, maxtime, mintime;
	clock_t begin, end;
	double time_spent;

	begin = clock();
	const gsl_rng_type * T;
	long seed;
	gsl_rng_env_setup();
	T = gsl_rng_default;
	r = gsl_rng_alloc(T);
	seed = time(NULL) * getpid();    // set seed
	gsl_rng_set(r, seed);

	MPI_Init(NULL, NULL);
	MPI_Comm_rank(MPI_COMM_WORLD,&rank);
	MPI_Comm_size(MPI_COMM_WORLD, &procnum);

	if (argc == 1) {
		its = 50;
	}
	else {
		its = atoi(argv[1]);
	}

	runModel(its, MPI_FLOAT, MPI_COMM_WORLD);

	if (rank==0) {
		printf("Starting lv-pmcmc ...\n");
		printf("Running for %i", its);
		printf(" iterations, Done.\n");
	}

	gsl_rng_free (r);

	// here, do your time-consuming job
	end = clock();
	time_spent = (double)(end - begin) / CLOCKS_PER_SEC;

	if (rank==0){
		printf("Time consuming in total is %f\n", time_spent);
	}

	MPI_Finalize();
    return(EXIT_SUCCESS);
}
*/


#include <stdlib.h>
#include <stdio.h>
#include <gsl/gsl_matrix.h>
#include "bstrlib/bstrlib.h"
#include "DosValues.h"

int write_dos_vals(char *outPath, int num_dos, double *Es, double *dos_vals);

int main(int argc, char *argv[]) {
    if (argc < 15) {
        printf("Usage: DosValues.out 'out_path' 'n' 'num_dos' 'Tae' 'Tce' 'Tbe' 'Tao' 'Tco' 'Tbo' 'EpsilonR' 'EpsilonM' 'M' 'W' 'Mu'\n");
        printf("Example: DosValues.out 'dos_vals' '8' '500' '0.2' '1.0' '0.4' '0.08' '0.4' '0.16' '0.05' '0.05' '1.0' '1.0' '-2.0'\n");
        return 2;
    }
    // Parse arguments.
    char *out_path = argv[1];
    int n = atoi(argv[2]);
    int num_dos = atoi(argv[3]);
    double Tae = atof(argv[4]);
    double Tce = atof(argv[5]);
    double Tbe = atof(argv[6]);
    double Tao = atof(argv[7]);
    double Tco = atof(argv[8]);
    double Tbo = atof(argv[9]);
    double EpsilonR = atof(argv[10]);
    double EpsilonM = atof(argv[11]);
    double M = atof(argv[12]);
    double W = atof(argv[13]);
    double Mu = atof(argv[14]);

    // Set up data.
    double *Es = malloc(num_dos * sizeof(double));
    Environment *env = malloc(sizeof(Environment));
    env->Tae = Tae;
    env->Tce = Tce;
    env->Tbe = Tbe;
    env->Tao = Tao;
    env->Tco = Tco;
    env->Tbo = Tbo;
    env->EpsilonM = EpsilonM;
    env->EpsilonR = EpsilonR;
    env->M = M;
    env->W = W;
    env->Mu = Mu;

    // Calculate DOS.
    double *dos_vals = DosValues(env, n, Es, num_dos);

    // Write out DOS.
    int write_err = write_dos_vals(out_path, num_dos, Es, dos_vals);
    if (write_err != 0) {
        printf("Error writing DOS values\n");
    }

    // Cleanup.
    free(dos_vals);
    free(Es);
    return 0;
}

int write_dos_vals(char *outPath, int num_dos, double *Es, double *dos_vals) {
    FILE *fp = fopen(outPath, "w");
    if (fp == NULL) {
        return 1;
    }

    // Write header.
    fprintf(fp, "E\tDOS\n");
    // Write DOS data.
    int i;
    for (i = 0; i < num_dos; i++) {
        fprintf(fp, "%.10f\t%.10f\n", Es[i], dos_vals[i]);
    }

    fclose(fp);
    return 0;
}


#include "utils.h"
#include <assert.h>
#include "stdio.h"
#include "string.h"
#include "cosmosis/datablock/c_datablock.h"
#include <gsl/gsl_math.h>
#include <gsl/gsl_interp2d.h>
#include <gsl/gsl_spline2d.h>

gsl_spline * spline_from_arrays(int n, double * x, double *y)
{
	gsl_spline * output = gsl_spline_alloc(gsl_interp_akima, n);
	assert (output!=NULL);
	gsl_spline_init(output,x,y,n);
	return output;
}

DATABLOCK_STATUS save_spline(c_datablock * block, const char * section,
	const char * n_name, const char * x_name, const char * y_name,
	gsl_spline * s)
{

	DATABLOCK_STATUS status = 0;
	if (strlen(n_name)>0){
		status |= c_datablock_put_int(block, section, n_name, s->size);
	}
	if (strlen(x_name)>0){
		status |= c_datablock_put_double_array_1d(block, section, x_name,
			s->x, s->size);
	}
	status |= c_datablock_put_double_array_1d(block, section, y_name,
		s->y, s->size);
	return status;
}

void reverse(double * x, int n)
{
	for (int i=0; i<n/2; i++){
		double tmp = x[i];
		x[i] = x[n-1-i];
		x[n-1-i] = tmp;
	}
}

static
double identity_function(double x, double y, double z, void * a)
{
  return z;
}

Interpolator2D *
load_interpolator_chi_function(c_datablock * block, gsl_spline * chi_of_z_spline,
	const char * section,
	const char * k_name, const char * z_name, const char * P_name,
	interp2d_modifier_function function, void * args
	)
{
	int nk, nz, nP;
	double *k=NULL, *z=NULL;
	double **P=NULL;
	int status = 0;

	status = c_datablock_get_double_grid(block, section,
		k_name, &nk, &k,
		z_name, &nz, &z,
		P_name, &P);

	if (status){
		fprintf(stderr, "Could not load interpolator for P(k).  Error %d\n",status);
		return NULL;
	}

        //read only P(k,z) and not P(k,0) since the growth factor will be inside the kernels
	for (int j=0; j<nk; j++){
		for (int i=0; i<nz; i++){
			P[j][i] = function(k[j], z[0], P[j][0], args);
		}
	}

	// What we have now is P(k, z).
	// We can optionally convert to P(k, chi)
	// If so we loop, lookup, and replace
	if (chi_of_z_spline){
		for (int i=0; i<nz; i++){
			double zi = z[i];
			double chi_i = gsl_spline_eval(chi_of_z_spline, zi, NULL);
			z[i] = chi_i;
		}
	}

	if (status) return NULL;
	Interpolator2D * interp = init_interp_2d_akima_grid(k, z, P, nk, nz);
	deallocate_2d_double(&P, nk);
	free(k);
	free(z);
	return interp;
}

Interpolator2D *
load_interpolator_chi(c_datablock * block, gsl_spline * chi_of_z_spline,
	const char * section,
	const char * k_name, const char * z_name, const char * P_name)
{

	return load_interpolator_chi_function(block, chi_of_z_spline, section, k_name, z_name, P_name, identity_function, NULL);
}

Interpolator2D *
load_interpolator_function(c_datablock * block,
	const char * section,
	const char * k_name, const char * z_name, const char * P_name,
	interp2d_modifier_function function, void * args
	)
{
	return load_interpolator_chi_function(block, NULL, section, k_name, z_name, P_name, function, args);

}

Interpolator2D *
load_interpolator(c_datablock * block,
	const char * section,
	const char * k_name, const char * z_name, const char * P_name)
{
	return load_interpolator_chi_function(block, NULL, section, k_name, z_name, P_name, identity_function, NULL);
}

// this is for the scale dependent growth rate from here----------------

Interpolator2D *
load_interpolator_chi_function2(c_datablock * block, gsl_spline * chi_of_z_spline,  gsl_spline * z_of_chi_spline,
	const char * section,
	const char * k_name, const char * z_name, const char * P_name,
	interp2d_modifier_function function, void * args
	)
{
	int nk, nz, nT;
	double *k=NULL, *z=NULL;
	double **P=NULL;
	int status = 0;

	status = c_datablock_get_double_grid(block, section,
		k_name, &nk, &k,
		z_name, &nz, &z,
		P_name, &P);

	double a[nk][nz],*pp[nk],**dTdz=NULL;
        for(int j=0;j<nk;j++){
	pp[j]=&a[j][0];}//for the derivative
	dTdz=pp;

	double b[nk][nz],*ppp[nk],**f=NULL;
        for(int j=0;j<nk;j++){
	ppp[j]=&b[j][0];}//for f(k,z)
	f=ppp;

	double c[nk][nz],*pppp[nk],**T=NULL;
        for(int j=0;j<nk;j++){
	pppp[j]=&c[j][0];}//for the T(k,z)
	T=pppp;

	if (status){
		fprintf(stderr, "Could not load interpolator for T(k).  Error %d\n",status);
		return NULL;
	}

	for (int j=0; j<nk; j++){
		for (int i=0; i<nz; i++){
			P[j][i] = function(k[j], z[i], P[j][i], args);
		}
	}

	//this is to take T(k,z)
	for (int j=0; j<nk; j++){
		for (int i=0; i<nz; i++){
			T[j][i] = sqrt(P[j][i]/P[j][0]);
		}
	}

        //derivative dT/dz

	for (int j=0; j<nk; j++){
		for (int i=0; i<nz; i++){

   	        //forward differences

   		if(i==0){
   			dTdz[j][i] = (T[j][i+1]-T[j][i])/(z[i+1]-z[i]);
			}

		//central differences

		if(0<i && i<(nz-1)){
   			dTdz[j][i] = (T[j][i+1]-T[j][i-1])/(2.*(z[i+1]-z[i]));
			}
		//backward differences

		if(i==(nz-1)){
   			dTdz[j][i] = (T[j][i]-T[j][i-1])/(z[i]-z[i-1]);
			}

		}
	}

	//printf("ok with derivative\n");

	//this is to take f(k,z)
	for (int j=0; j<nk; j++){
		for (int i=0; i<nz; i++){
			f[j][i] = -((z[i]+1.)/T[j][i])*dTdz[j][i];
		}
	}

	// What we have now is f(k, z).
	// We can optionally convert to f(k, z(chi))
	// If so we loop, lookup, and replace
	if (chi_of_z_spline){
		for (int i=0; i<nz; i++){
                        //int j=0; //my modification
			double zi = z[i]; //// j instead of i
			double chi_i = gsl_spline_eval(chi_of_z_spline, zi, NULL);
			//double z_i = gsl_spline_eval(z_of_chi_spline, chi_i, NULL);
			z[i] = chi_i;
		}
	}

	Interpolator2D * interp2 = init_interp_2d_akima_grid(k, z, f, nk, nz);
	return interp2;
}

Interpolator2D *
load_interpolator_chi2(c_datablock * block, gsl_spline * chi_of_z_spline, gsl_spline * z_of_chi_spline,
	const char * section,
	const char * k_name, const char * z_name, const char * P_name)
{

	return load_interpolator_chi_function2(block, chi_of_z_spline, z_of_chi_spline, section, k_name, z_name, P_name, identity_function, NULL);
}

Interpolator2D *
load_interpolator_function2(c_datablock * block,
	const char * section,
	const char * k_name, const char * z_name, const char * P_name,
	interp2d_modifier_function function, void * args
	)
{
	return load_interpolator_chi_function2(block, NULL, NULL, section, k_name, z_name, P_name, function, args);

}

Interpolator2D *
load_interpolator2(c_datablock * block,
	const char * section,
	const char * k_name, const char * z_name, const char * P_name)
{
	return load_interpolator_chi_function2(block, NULL, NULL, section, k_name, z_name, P_name, identity_function, NULL);
}

// to here---------------------------------------

// here write the scale dependent growth factor

Interpolator2D *
load_interpolator_chi_function3(c_datablock * block, gsl_spline * chi_of_z_spline,  gsl_spline * z_of_chi_spline,
	const char * section,
	const char * k_name, const char * z_name, const char * P_name,
	interp2d_modifier_function function, void * args
	)
{
	int nk, nz, nT;
	double *k=NULL, *z=NULL;
	double **P=NULL;
	int status = 0;

	status = c_datablock_get_double_grid(block, section,
		k_name, &nk, &k,
		z_name, &nz, &z,
		P_name, &P);

	double b[nk][nz],*ppp[nk],**D=NULL;
        for(int j=0;j<nk;j++){
	ppp[j]=&b[j][0];}//for D(k,z)
	D=ppp;

	if (status){
		fprintf(stderr, "Could not load interpolator for P_nl(k).  Error %d\n",status);
		return NULL;
	}

	for (int j=0; j<nk; j++){
		for (int i=0; i<nz; i++){
			P[j][i] = function(k[j], z[i], P[j][i], args);
		}
	}

	//printf("ok with derivative\n");

	//this is to take D(k,z)
	for (int j=0; j<nk; j++){
		for (int i=0; i<nz; i++){
			D[j][i] = sqrt(P[j][i]/P[j][0]);
		}
	}

	// What we have now is D(k, z).
	// We can optionally convert to D(k, z(chi))
	// If so we loop, lookup, and replace
	if (chi_of_z_spline){
		for (int i=0; i<nz; i++){
                        //int j=0; //my modification
			double zi = z[i]; //// j instead of i
			double chi_i = gsl_spline_eval(chi_of_z_spline, zi, NULL);
			//double z_i = gsl_spline_eval(z_of_chi_spline, chi_i, NULL);
			z[i] = chi_i;
		}
	}

	//printf("ok with z_of_chi\n");
	Interpolator2D * interp3 = init_interp_2d_akima_grid(k, z, D, nk, nz);
	return interp3;
}

Interpolator2D *
load_interpolator_chi3(c_datablock * block, gsl_spline * chi_of_z_spline, gsl_spline * z_of_chi_spline,
	const char * section,
	const char * k_name, const char * z_name, const char * P_name)
{

	return load_interpolator_chi_function3(block, chi_of_z_spline, z_of_chi_spline, section, k_name, z_name, P_name, identity_function, NULL);
}

Interpolator2D *
load_interpolator_function3(c_datablock * block,
	const char * section,
	const char * k_name, const char * z_name, const char * P_name,
	interp2d_modifier_function function, void * args
	)
{
	return load_interpolator_chi_function3(block, NULL, NULL, section, k_name, z_name, P_name, function, args);

}

Interpolator2D *
load_interpolator3(c_datablock * block,
	const char * section,
	const char * k_name, const char * z_name, const char * P_name)
{
	return load_interpolator_chi_function3(block, NULL, NULL, section, k_name, z_name, P_name, identity_function, NULL);
}

//-------------------to here

//-----------------here is the scale dependent galaxy bias accounting for neutrinos

Interpolator2D *
load_interpolator_chi_function4(c_datablock * block, gsl_spline * chi_of_z_spline,  gsl_spline * z_of_chi_spline,
	const char * section,
	const char * k_name, const char * z_name, const char * P_name,
	interp2d_modifier_function function, void * args
	)
{
	int nk, nz, nT;
	double *k=NULL, *z=NULL;
	double **P=NULL;
	int status = 0;

	status = c_datablock_get_double_grid(block, section,
		k_name, &nk, &k,
		z_name, &nz, &z,
		P_name, &P);

	double bb[nk][nz],*pppp[nk],**Bias=NULL;
        for(int j=0;j<nk;j++){
	pppp[j]=&bb[j][0];}//for b(k,z)
	Bias=pppp;

	double bbL[nk][nz],*ppppL[nk],**bL=NULL;
        for(int j=0;j<nk;j++){
	ppppL[j]=&bbL[j][0];}//for bL(k,z)
	bL=ppppL;

	double bbS[nk][nz],*ppppS[nk],**bS=NULL;
        for(int j=0;j<nk;j++){
	ppppS[j]=&bbS[j][0];}//for bS(k,z)
	bS=ppppS;

	if (status){
		fprintf(stderr, "Could not load interpolator for P_nl(k).  Error %d\n",status);
		return NULL;
	}

	for (int j=0; j<nk; j++){
		for (int i=0; i<nz; i++){
			P[j][i] = function(k[j], z[i], P[j][i], args);
		}
	}

  	int status7=0;
	double omega_m;
	status7 = c_datablock_get_double(block, COSMOLOGICAL_PARAMETERS_SECTION, "omega_m", &omega_m);
	double Om = omega_m;

  	int status8=0;
	double omega_vh2;
	status8 = c_datablock_get_double(block, COSMOLOGICAL_PARAMETERS_SECTION, "omnuh2", &omega_vh2);
	double Ovh2 = omega_vh2;

  	int status9=0;
	double hs;
	status9 = c_datablock_get_double(block, COSMOLOGICAL_PARAMETERS_SECTION, "h0", &hs);
	double hubble = hs;

        double fv = (Ovh2/(hubble * hubble))/Om;

        double mv = Ovh2 * 93.14; //this is Sum(mv)/1eV in h/Mpc but for one massive neutrino only Sum(mv)=mv

        // this the neutrino free streaming scale in h/Mpc
	//for (int i=0; i<nz; i++){
	//	kfs[0][i] = 0.82 * sqrt(1.0 - Om + Om * (1.0 + z[i]) * (1.0 + z[i]) * (1.0 + z[i]))/((1.0 + z[i]) * (1.0 + z[i])) * mv;
	//}
        double knr;
        knr = 0.018 * sqrt(Om) * sqrt(mv);

        double constant=1.0;
	//this is to take b(k,z)
	for (int j=0; j<nk; j++){
		for (int i=0; i<nz; i++){

                bL[j][i] =  constant/sqrt(P[0][i]/P[0][0]);

                bS[j][i] = constant/sqrt(P[0][i]/P[0][0]) - constant/sqrt(P[0][i]/P[0][0]) * fv;

                Bias[j][i] = bL[j][i] + (bS[j][i] - bL[j][i]) * 0.5 * (tanh(log(pow(k[j]/(knr),5.0)))+1.0) ;

		}
	}

	// What we have now is b(k, z).
	// We can optionally convert to b(k, z(chi))
	// If so we loop, lookup, and replace
	if (chi_of_z_spline){
		for (int i=0; i<nz; i++){
                        //int j=0; //my modification
			double zi = z[i]; //// j instead of i
			double chi_i = gsl_spline_eval(chi_of_z_spline, zi, NULL);
			//double z_i = gsl_spline_eval(z_of_chi_spline, chi_i, NULL);
			z[i] = chi_i;
		}
	}

	//printf("ok with z_of_chi\n");
	Interpolator2D * interp4 = init_interp_2d_akima_grid(k, z, Bias, nk, nz);
	return interp4;
}

Interpolator2D *
load_interpolator_chi4(c_datablock * block, gsl_spline * chi_of_z_spline, gsl_spline * z_of_chi_spline,
	const char * section,
	const char * k_name, const char * z_name, const char * P_name)
{

	return load_interpolator_chi_function4(block, chi_of_z_spline, z_of_chi_spline, section, k_name, z_name, P_name, identity_function, NULL);
}

Interpolator2D *
load_interpolator_function4(c_datablock * block,
	const char * section,
	const char * k_name, const char * z_name, const char * P_name,
	interp2d_modifier_function function, void * args
	)
{
	return load_interpolator_chi_function4(block, NULL, NULL, section, k_name, z_name, P_name, function, args);

}

Interpolator2D *
load_interpolator4(c_datablock * block,
	const char * section,
	const char * k_name, const char * z_name, const char * P_name)
{
	return load_interpolator_chi_function4(block, NULL, NULL, section, k_name, z_name, P_name, identity_function, NULL);
}

//------------------to here

gsl_spline * load_spline(c_datablock * block, const char * section,
	const char * x_name, const char * y_name)
{
	int status = 0 ;
	int nx, ny;
	double *x, *y;
	status |= c_datablock_get_double_array_1d(block, section, x_name, &x, &nx);
	if (status) return NULL;
	status |= c_datablock_get_double_array_1d(block, section, y_name, &y, &ny);
	if (status){
		free(x);
		return NULL;
	}

	gsl_spline * output = NULL;

	if (x[1]<x[0]) {reverse(x, nx); reverse(y, ny);}

	if (nx==ny) output=spline_from_arrays(nx, x, y);
	else {fprintf(stderr, "Non-matching array sizes %s=%d, %s=%d\n", x_name, nx, y_name, ny);}
	free(x);
	free(y);
	return output;
}

/*
gsl_spline2d *
load_gsl_interpolator_chi(c_datablock * block, gsl_spline * chi_of_z_spline,
	const char * section,
	const char * k_name, const char * z_name, const char * P_name,
	)
{
	int nk, nz, nP;
	double *k=NULL, *z=NULL;
	double **P=NULL;
	int status = 0;

	status = c_datablock_get_double_grid(block, section,
		k_name, &nk, &k,
		z_name, &nz, &z,
		P_name, &P);

	if (status){
		fprintf(stderr, "Could not load interpolator for P(k).  Error %d\n",status);
		return NULL;
	}

	// What we have now is P(k, z).
	// We can optionally convert to P(k, chi)
	// If so we loop, lookup, and replace
	if (chi_of_z_spline){
		for (int i=0; i<nz; i++){
			double zi = z[i];
			double chi_i = gsl_spline_eval(chi_of_z_spline, zi, NULL);
			z[i] = chi_i;
		}
	}

	if (status) return NULL;
        const gsl_interp2d_type *T = gsl_interp2d_bicubic;
	gsl_spline2d *spline = gsl_spline2d_alloc(T, nz, nk);
	gsl_interp_accel *xacc = gsl_interp_accel_alloc();
	gsl_interp_accel *yacc = gsl_interp_accel_alloc();
	gsl_spline_init

	Interpolator2D * interp = init_interp_2d_akima_grid(k, z, P, nk, nz);
	deallocate_2d_double(&P, nk);
	free(k);
	free(z);
	return interp;
}
*/