/*
** Implementation of LISA algorithm
** for statistical inference of fMRI images
**
** 2nd level inference for grouped samples, two-sample ttest, paired t-test
**
** G.Lohmann, MPI-KYB, 2018
** Zalan Rajna, University of Oulu, 2020
*/
#include <viaio/Vlib.h>
#include <viaio/file.h>
#include <viaio/mu.h>
#include <viaio/option.h>
#include <viaio/os.h>
#include <viaio/VImage.h>
#include <via/via.h>

#include <gsl/gsl_cdf.h>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_math.h>
#include <gsl/gsl_rng.h>
#include <gsl/gsl_randist.h>
#include <gsl/gsl_histogram.h>
#include <gsl/gsl_histogram2d.h>

#include <math.h>
#include <stdio.h>
#include <string.h>

#ifdef _OPENMP
#include <omp.h>
#endif /*_OPENMP*/

#define ABS(x) ((x) > 0 ? (x) : -(x))
#define MIN(x,y) ((x) < (y) ? (x) : (y))
#define MAX(x,y) ((x) > (y) ? (x) : (y))

extern double ttest2(double *data1,double *data2,int n1,int n2);
extern double xtest2(double *data1,double *data2,int n);
extern double paired_ttest(double *data1,double *data2,int n);
extern double welchtest(double *data1,double *data2,int n1,int n2);
extern void   VIsolatedVoxels(VImage src,float threshold);
extern void   VHistogram(gsl_histogram *histogram,VString filename);
extern void   VCheckImage(VImage src);
extern void   FDR(VImage src,VImage dest,gsl_histogram *nullhist,gsl_histogram *realhist,double);
extern double ttest1(double *data1,int n);
extern void   ImageStats(VImage src,double *,double *,double *hmin,double *hmax);
extern void   VBilateralFilter(VImage src,VImage dest,int radius,double var1,double var2,int);
extern double VImageVar(VImage src);
extern void   VGetHistRange(VImage src,double *hmin,double *hmax);
extern void   VZScale(VImage src,float mode,float stddev);
extern float  VGetMode(VImage src);
extern void   HistoUpdate(VImage,gsl_histogram *);

/* generate permutation table */
int **genperm(long seed,int n1,int n2,int numperm,int testtype)
{
  int i,j,n=n1+n2;
  int **table = (int **) VCalloc(numperm,sizeof(int *));
  int *base = (int *) VCalloc(n,sizeof(int));
  for (j=0; j<n; j++) base[j] = j;

  gsl_rng_env_setup();
  const gsl_rng_type *T = gsl_rng_default;
  gsl_rng *rx = gsl_rng_alloc(T);
  gsl_rng_set(rx,(unsigned long int)seed);

  /* for each permutation:  */
  for (i = 0; i < numperm; i++) {

    /* unpaired test */
    if (testtype == 0 || testtype == 2) {
      table[i] = (int *) VCalloc(n,sizeof(int));
      gsl_ran_shuffle (rx,base,n,sizeof(int));
      for (j=0; j<n; j++) {
	table[i][j] = base[j];
      }
    }

    /* paired test */
    if (testtype == 1) {
      table[i] = (int *) VCalloc(n1,sizeof(int));
      for (j=0; j<n1; j++) {
	table[i][j] = 0;
	if (gsl_ran_bernoulli (rx,(double)0.5) == 1) table[i][j] = 1;
      }
    }
  }

  gsl_rng_free(rx);
  VFree(base);
  return table;
}

void TTest(VImage *src1,VImage *src2,int *permtable,VImage dest,int n1,int n2,int testtype)
{
  int i,b,r,c,nslices,nrows,ncols;
  double u=0,v=0,z=0;
  int k1=0,k2=0,k=0;
  int n = n1+n2;
  int min1 = n1-2;
  int min2 = n2-2;

  gsl_set_error_handler_off();

  nslices = VImageNBands(src1[0]);
  nrows   = VImageNRows(src1[0]);
  ncols   = VImageNColumns(src1[0]);
  VFillImage(dest,VAllBands,0);

  /* alloc */
  double *data1 = (double *) VCalloc(n1,sizeof(double));
  double *data2 = (double *) VCalloc(n2,sizeof(double));
  double *data = (double *) VCalloc(n,sizeof(double));

  /* for every voxel */
  for (b=0; b<nslices; b++) {
    for (r=0; r<nrows; r++) {
      for (c=0; c<ncols; c++) {

	/* unpaired test */
	if (testtype == 0 || testtype == 2) {

	  k = 0;
	  for (i=0; i<n1; i++) {
	    data[k++] = VPixel(src1[i],b,r,c,VFloat);
	  }
	  for (i=0; i<n2; i++) {
	    data[k++] = VPixel(src2[i],b,r,c,VFloat);
	  }

	  k1 = 0;
	  for (i=0; i<n1; i++) {
	    u = data[permtable[i]];
	    if (fabs(u) > 0) {
	      data1[k1++] = u;
	    }
	  }
	  if (k1 < min1) continue;

	  k2 = 0;
	  for (i=0; i<n2; i++) {
	    u = data[permtable[i+n1]];
	    if (fabs(u) > 0) {
	      data2[k2++] = u;
	    }
	  }
	  if (k2 < min2) continue;
	}

	/* paired test */
	if (testtype == 1) {
	  k=0;
	  for (i=0; i<n1; i++) {
	    if (permtable[i] == 0) {  /* no flip */
	      u = VPixel(src1[i],b,r,c,VFloat);
	      v = VPixel(src2[i],b,r,c,VFloat);
	    }
	    else {   /* flip */
	      u = VPixel(src2[i],b,r,c,VFloat);
	      v = VPixel(src1[i],b,r,c,VFloat);
	    }
	    if (fabs(u) > 0 && fabs(v) > 0) {
	      data1[k] = u;
	      data2[k] = v;
	      k++;
	    }
	  }
	  if (k < min1) continue;
	}

	/* t-test */
	z = 0;
	switch(testtype) {
	case 0 :
	  z = ttest2(data1,data2,k1,k2);     /* pooled variance */
	  break;
	case 1 :
	  z = paired_ttest(data1,data2,k);   /* paired */
	  break;
	case 2 :
	  z = welchtest(data1,data2,k1,k2);  /* unequal variance */
	  break;
	default:
	  VError(" unknown testtype");
	}
	VPixel(dest,b,r,c,VFloat) = z;
      }
    }
  }
  VFree(data);
  VFree(data1);
  VFree(data2);
}

VDictEntry TSTDict[] = {
  { "pooled", 0, 0,0,0,0 },
  { "paired", 1, 0,0,0,0 },
  { "welch", 2, 0,0,0,0  },
  { NULL, 0,0,0,0,0 }
};

int main (int argc, char *argv[])
{
  static VArgVector in_files1;
  static VArgVector in_files2;
  static VString  out_filename="";
  static VString  mask_filename="";
  static VFloat   alpha = 0.05;
  static VShort   testtype = 2;  /* default is welch */
  static VShort   radius = 2;
  static VFloat   rvar = 2.0;
  static VFloat   svar = 2.0;
  static VShort   numiter = 2;
  static VShort   numperm = 5000;
  static VLong    seed = 99402622;
  static VBoolean centering = FALSE;
  static VBoolean cleanup = TRUE;
  static VShort   nproc = 0;
  static VOptionDescRec options[] = {
    {"in1", VStringRepn, 0, & in_files1, VRequiredOpt, NULL,"Input files 1 (single file per group)" },
    {"in2", VStringRepn, 0, & in_files2, VRequiredOpt, NULL,"Input files 2 (single file per group)" },
    {"out", VStringRepn, 1, & out_filename, VRequiredOpt, NULL,"Output file" },
    {"alpha",VFloatRepn,1,(VPointer) &alpha,VOptionalOpt,NULL,"FDR significance level"},
    {"perm",VShortRepn,1,(VPointer) &numperm,VOptionalOpt,NULL,"Number of permutations"},
    {"mask", VStringRepn, 1, (VPointer) &mask_filename, VRequiredOpt, NULL, "Mask"},
    {"test",VShortRepn,1,(VPointer) &testtype,VOptionalOpt,TSTDict,"type of test"},
    {"seed",VLongRepn,1,(VPointer) &seed,VOptionalOpt,NULL,"Seed for random number generation"},
    {"radius",VShortRepn,1,(VPointer) &radius,VOptionalOpt,NULL,"Bilateral parameter (radius in voxels)"},
    {"rvar",VFloatRepn,1,(VPointer) &rvar,VOptionalOpt,NULL,"Bilateral parameter (radiometric)"},
    {"svar",VFloatRepn,1,(VPointer) &svar,VOptionalOpt,NULL,"Bilateral parameter (spatial)"},
    {"filteriterations",VShortRepn,1,(VPointer) &numiter,VOptionalOpt,NULL,"Bilateral parameter (number of iterations)"},
    {"cleanup",VBooleanRepn,1,(VPointer) &cleanup,VOptionalOpt,NULL,"Whether to apply cleanup"},
    {"j",VShortRepn,1,(VPointer) &nproc,VOptionalOpt,NULL,"Number of processors to use, '0' to use all"},
  };

  FILE *fp=NULL;
  VString in_filename,str1,str2;
  VAttrList list1=NULL,list2=NULL,out_list=NULL,geolist=NULL;
  int i,ngroups1=0,ngroups2=0,npix=0;
  char *prg_name=GetLipsiaName("vlisa_grouped_twosample");
  fprintf (stderr, "%s\n", prg_name);

  /* parse command line */
  if (! VParseCommand (VNumber (options), options, & argc, argv)) {
    VReportUsage (argv[0], VNumber (options), options, NULL);
    exit (EXIT_FAILURE);
  }
  if (argc > 1) {
    VReportBadArgs (argc, argv);
    exit (EXIT_FAILURE);
  }

  /* omp-stuff */
#ifdef _OPENMP
  int num_procs=omp_get_num_procs();
  if (nproc > 0 && nproc < num_procs) num_procs = nproc;
  fprintf(stderr," using %d cores\n",(int)num_procs);
  omp_set_num_threads(num_procs);
#endif /* _OPENMP */

  /* read mask */
  VImage mask = VReadImageFile(mask_filename);
  if (mask==NULL) VError("Error reading mask file %s",mask_filename);

  /* number of inputs */
  ngroups1 = in_files1.number;
  ngroups2 = in_files2.number;
  fprintf(stderr," ngroups= %d  %d\n",ngroups1,ngroups2);

  /* images 1  */
  VImage *src1 = (VImage *) VCalloc(ngroups1,sizeof(VImage));
  for (i = 0; i < ngroups1; i++) {
    in_filename = ((VString *) in_files1.vector)[i];
    list1   = VReadAttrList(in_filename,0L,TRUE,FALSE);
    VMaskMinval(list1,mask,0.0);
    src1[i] = VReadImage(list1);
    if (src1[i] == NULL) VError(" no input image found");
    if (VPixelRepn(src1[i]) != VFloatRepn) VError(" input pixel repn must be float");
    if (i == 0) npix = VImageNPixels(src1[i]);
    else if (npix != VImageNPixels(src1[i])) VError(" inconsistent image dimensions");

    /* use geometry info from 1st file */
    if (geolist == NULL) geolist = VGetGeoInfo(list1);
  }

  /* images 2  */
  VImage *src2 = (VImage *) VCalloc(ngroups2,sizeof(VImage));
  for (i = 0; i < ngroups2; i++) {
    in_filename = ((VString *) in_files2.vector)[i];
    list2   = VReadAttrList(in_filename,0L,TRUE,FALSE);
    VMaskMinval(list2,mask,0.0);
    src2[i] = VReadImage(list2);
    if (src2[i] == NULL) VError(" no input image found");
    if (VPixelRepn(src2[i]) != VFloatRepn) VError(" input pixel repn must be float");
    if (i == 0) npix = VImageNPixels(src2[i]);
    else if (npix != VImageNPixels(src2[i])) VError(" inconsistent image dimensions");
  }

  if (testtype == 1) {  /* print filenames to terminal */
    for (i = 0; i < ngroups1; i++) {
      str1 = ((VString *) in_files1.vector)[i];
      str2 = ((VString *) in_files2.vector)[i];
      fprintf(stderr," %3d:  %s   %s\n",i,str1,str2);
    }
  }
  else {
    fprintf(stderr," Group 1:\n");
    for (i = 0; i < ngroups1; i++) {
      str1 = ((VString *) in_files1.vector)[i];
      fprintf(stderr," %3d:  %s\n",i,str1);
    }
    fprintf(stderr,"\n Group 2:\n");
    for (i = 0; i < ngroups2; i++) {
      str2 = ((VString *) in_files2.vector)[i];
      fprintf(stderr," %3d:  %s\n",i,str2);
    }
  }

  /* random permutations */
  size_t n = ngroups1+ngroups2;
  int nperm=0;
  int **permtable = genperm((long)seed,(int)ngroups1,(int)ngroups2,(int)numperm,(int)testtype);
  int *nopermtable = (int *) VCalloc(n,sizeof(int));
  for (i=0; i<n; i++) nopermtable[i]=i;
  if (testtype == 1) for (i=0; i<n; i++) nopermtable[i]=0;

  /* estimate null variance based on first 30 permutations */
  double hmin=0,hmax=0;
  float stddev=1.0;
  if (numperm > 0) {
    int tstperm = 30;
    if (tstperm > numperm) tstperm = numperm;
    double varsum=0,nx=0;

#pragma omp parallel for shared(src1,permtable) schedule(dynamic)
    for (nperm = 0; nperm < tstperm; nperm++) {
      VImage zmap = VCreateImageLike(src1[0]);
      TTest(src1,src2,permtable[nperm],zmap,ngroups1,ngroups2,(int)testtype);
#pragma omp critical
      {
	varsum += VImageVar(zmap);
	nx++;
      }
      VDestroyImage(zmap);
    }
    double meanvar = varsum/nx;
    stddev = sqrt(meanvar);
  }

  /* no permutation */
  VImage dst1  = VCreateImageLike (src1[0]);
  VImage zmap1 = VCreateImageLike(src1[0]);
  VFillImage(zmap1,VAllBands,0);
  TTest(src1,src2,nopermtable,zmap1,ngroups1,ngroups2,(int)testtype);

  if (numperm == 0) {
    double z = VImageVar(zmap1);
    stddev = (float)(sqrt(z)); /* update stddev */
  }
  float mode=0;
  if (centering) mode = VGetMode(zmap1);
  if (numperm > 0) VZScale(zmap1,mode,stddev);
  VBilateralFilter(zmap1,dst1,(int)radius,(double)rvar,(double)svar,(int)numiter);

  /* ini histograms */
  VGetHistRange(dst1,&hmin,&hmax);
  size_t nbins = 20000;
  gsl_histogram *hist0 = gsl_histogram_alloc (nbins);
  gsl_histogram_set_ranges_uniform (hist0,hmin,hmax);
  gsl_histogram *histz = gsl_histogram_alloc (nbins);
  gsl_histogram_set_ranges_uniform (histz,hmin,hmax);
  HistoUpdate(dst1,histz);

#pragma omp parallel for shared(src1,src2,permtable) schedule(dynamic)
  for (nperm = 0; nperm < numperm; nperm++) {
    if (nperm%20 == 0) fprintf(stderr," perm  %4d  of  %d\r",nperm,(int)numperm);

    VImage zmap = VCreateImageLike(src1[0]);
    VImage dst  = VCreateImageLike (zmap);
    TTest(src1,src2,permtable[nperm],zmap,ngroups1,ngroups2,(int)testtype);

    float mode=0;
    if (centering) mode = VGetMode(zmap);
    VZScale(zmap,mode,stddev);
    VBilateralFilter(zmap,dst,(int)radius,(double)rvar,(double)svar,(int)numiter);

#pragma omp critical
    {
      HistoUpdate(dst,hist0);
    }
    VDestroyImage(dst);
    VDestroyImage(zmap);
  }

  /* apply fdr */
  VImage fdrimage = VCopyImage (dst1,NULL,VAllBands);
  if (numperm > 0) {
    FDR(dst1,fdrimage,hist0,histz,(double)alpha);
    if (cleanup && alpha < 1.0) {
      VIsolatedVoxels(fdrimage,(float)(1.0-alpha));
    }
  }

  /* write output to disk */
  out_list = VCreateAttrList ();
  VHistory(VNumber(options),options,prg_name,&list1,&out_list);
  VSetGeoInfo(geolist,out_list);
  VAppendAttr (out_list,"image",NULL,VImageRepn,fdrimage);
  fp = VOpenOutputFile (out_filename, TRUE);
  if (! VWriteFile (fp, out_list)) exit (1);
  fclose(fp);
  fprintf (stderr, "\n");
  fprintf (stderr, "%s: done.\n", argv[0]);
  exit(0);
}


/**
 * @file dereverberation.h
 * @brief Single- and multi-channel dereverberation base on linear prediction in the subband domain.
 * @author John McDonough
 */

#ifndef DEREVERBERATION_H
#define DEREVERBERATION_H

#include <stdio.h>
#include <assert.h>

#include <gsl/gsl_block.h>
#include <gsl/gsl_vector.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_complex.h>
#include <gsl/gsl_complex_math.h>
#include <gsl/gsl_linalg.h>
#include "common/jexception.h"

#include "stream/stream.h"
#include "feature/feature.h"

/*
#include <Eigen/Cholesky>
#include <iostream>
#include <Eigen/Dense>
*/

// ----- definition for class `SingleChannelWPEDereverberationFeature' -----
//
class SingleChannelWPEDereverberationFeature : public VectorComplexFeatureStream {
  typedef vector<gsl_vector_complex*>			Samples_;
  typedef Samples_::iterator				SamplesIterator_;

 public:
  SingleChannelWPEDereverberationFeature(VectorComplexFeatureStreamPtr& samples, unsigned lowerN, unsigned upperN, unsigned iterationsN = 2, double loadDb = -20.0, double bandWidth = 0.0,  double sampleRate = 16000.0, const String& nm = "SingleChannelWPEDereverberationFeature");

  ~SingleChannelWPEDereverberationFeature();

  virtual const gsl_vector_complex* next(int frame_no = -5);
  virtual void reset();
  unsigned estimate_filter(int start_frame_no = 0, int frame_num = -1);
  void reset_filter();
  void next_speaker();
  void print_objective_func(int subbandX){ printing_subbandX_ = subbandX;}

#ifdef ENABLE_LEGACY_BTK_API
  void nextSpeaker(){ next_speaker(); }
#endif

private:
  static const double					subband_floor_;

  void fill_buffer_(int start_frame_no, int frame_num);
  void estimate_Gn_();
  void calc_Rr_(unsigned subbandX);
  void calc_Thetan_();
  void load_R_();
  unsigned set_band_width_(double bandWidth, double sampleRate);

  const gsl_vector_complex* get_lags_(unsigned subbandX, unsigned sampleX);
  VectorComplexFeatureStreamPtr				samples_;

  const unsigned					lowerN_;
  const unsigned					upperN_;
  const unsigned					predictionN_;
  const unsigned					iterationsN_;
  bool							estimated_;
  unsigned						framesN_; // no frames used for filter estimation
  const double						load_factor_;
  const unsigned					lower_bandWidthN_;
  const unsigned					upper_bandWidthN_;

  Samples_						yn_; // buffer to keep observations
  gsl_matrix*						thetan_;
  gsl_vector_complex**					gn_;
  gsl_matrix_complex*					R_;
  gsl_vector_complex*					r_;
  gsl_vector_complex*					lag_samples_;
  int                                                   printing_subbandX_;
};

typedef Inherit<SingleChannelWPEDereverberationFeature, VectorComplexFeatureStreamPtr> SingleChannelWPEDereverberationFeaturePtr;

// ----- definition for class `MultiChannelWPEDereverberation' -----
//
class MultiChannelWPEDereverberation : public Countable {
  typedef vector<VectorComplexFeatureStreamPtr>		SourceList_;
  typedef SourceList_::iterator				SourceListIterator_;

  typedef vector<gsl_vector_complex*>			FrameBrace_;
  typedef FrameBrace_::iterator				FrameBraceIterator_;

  typedef vector<FrameBrace_>				FrameBraceList_;
  typedef FrameBraceList_::iterator			FrameBraceListIterator_;

 public:
  MultiChannelWPEDereverberation(unsigned subbandsN, unsigned channelsN, unsigned lowerN, unsigned upperN, unsigned iterationsN = 2, double loadDb = -20.0, double bandWidth = 0.0, double diagonal_bias = 0.0, double sampleRate = 16000.0);

  ~MultiChannelWPEDereverberation();

  void reset();
  unsigned size() const { return subbandsN_; }
  void set_input(VectorComplexFeatureStreamPtr& samples);
  const gsl_vector_complex* get_output(unsigned channelX);
  gsl_vector_complex** calc_every_channel_output(int frame_no = -5);
  unsigned estimate_filter(int start_frame_no = 0, int frame_num = -1);
  void reset_filter();
  void next_speaker();
  int  frame_no() const { return frame_no_; }
  void print_objective_func(int subbandX){ printing_subbandX_ = subbandX;}

#ifdef ENABLE_LEGACY_BTK_API
  void setInput(VectorComplexFeatureStreamPtr& samples){ set_input(samples); }
  const gsl_vector_complex* getOutput(unsigned channelX, int frame_no = -5){ return get_output(channelX); }
  void nextSpeaker(){ next_speaker(); }
#endif

private:
  static const double					subband_floor_;

  void fill_buffer_(int start_frame_no, int frame_num);
  void estimate_Gn_();
  void calc_Rr_(unsigned subbandX);
  void calc_Thetan_();
  void load_R_();
  unsigned set_band_width_(double bandWidth, double sampleRate);

  void increment_() { frame_no_++; }
  const gsl_vector_complex* get_lags_(unsigned subbandX, unsigned sampleX);

  SourceList_						sources_;
  const unsigned					subbandsN_;
  const unsigned					channelsN_;
  const unsigned					lowerN_;
  const unsigned					upperN_;
  const unsigned					predictionN_;
  const unsigned					iterationsN_;
  const unsigned					totalPredictionN_;

  bool							estimated_;
  unsigned						framesN_;
  const double						load_factor_;
  const unsigned					lower_bandWidthN_;
  const unsigned					upper_bandWidthN_;

  FrameBraceList_					frames_;
  gsl_matrix**						thetan_;
  gsl_vector_complex***					Gn_;
  gsl_matrix_complex**					R_;
  gsl_vector_complex**					r_;
  gsl_vector_complex*					lag_samples_;
  gsl_vector_complex**					output_;

  const int						initial_frame_no_;
  int							frame_no_;

  const double                                          diagonal_bias_;
  int                                                   printing_subbandX_;
};

typedef refcountable_ptr<MultiChannelWPEDereverberation> MultiChannelWPEDereverberationPtr;

// ----- definition for class `MultiChannelWPEDereverberationFeature' -----
//
class MultiChannelWPEDereverberationFeature : public VectorComplexFeatureStream {
public:
  MultiChannelWPEDereverberationFeature(MultiChannelWPEDereverberationPtr& source, unsigned channelX, unsigned primaryChannelX = 0, const String& nm = "MultiChannelWPEDereverberationFeature");

  ~MultiChannelWPEDereverberationFeature();

  virtual const gsl_vector_complex* next(int frame_no = -5);
  virtual void reset();

private:
  MultiChannelWPEDereverberationPtr			source_;
  const unsigned					channelX_;
  const unsigned					primaryChannelX_; // compute the dereverbed output only when channelX_ == primaryChannelX_. Otherwise copy the precomputed output
};

typedef Inherit<MultiChannelWPEDereverberationFeature, VectorComplexFeatureStreamPtr> MultiChannelWPEDereverberationFeaturePtr;

#endif // DEREVERBERATION_H


/**
 *
 * @file timing.c
 *
 *  PLASMA auxiliary routines
 *  PLASMA is a software package provided by Univ. of Tennessee,
 *  Univ. of California Berkeley and Univ. of Colorado Denver
 *
 * @version 2.6.0
 * @author Mathieu Faverge
 * @author Dulceneia Becker
 * @date 2010-11-15
 *
 **/

 #define _FMULS FMULS_GEQRF( M, N )
#define _FADDS FADDS_GEQRF( M, N )

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef _OPENMP
#include <omp.h>
#endif

#ifdef PLASMA_EZTRACE
#include <eztrace.h>
#endif

#include <unistd.h>
#include <sys/time.h>
#include <sys/resource.h>

#ifdef USE_MKL
#include <mkl_cblas.h>
#include <mkl_lapacke.h>
#else
#include <cblas.h>
#include <lapacke.h>
#endif
#include "plasma.h"
#include "core_blas.h"
#include "flops.h"
#include "timing.h"
#include "auxiliary.h"
#include "main.h"
#include "workspace.h"
#include "Utils.h"
#define EPSILON 1.0E-9

static double RunTest(double *t_, struct user_parameters*);

static double
RunTest_dpotrf(real_Double_t *t_, struct user_parameters* params)
{
    double  t;
    int64_t N     = params->matrix_size;
    int64_t NB    = params->blocksize;
    int check     = params->check;
    int uplo = PlasmaUpper;
    double check_res = 0;

    /* Allocate Data */
    PLASMA_desc *descA = NULL;
    double* ptr = malloc(N * N * sizeof(double));
    PLASMA_Desc_Create(&descA, ptr, PlasmaRealDouble, NB, NB, NB*NB, N, N, 0, 0, N, N);

#pragma omp parallel
#pragma omp master
    plasma_pdplgsy_quark( (double)N, *descA, 51 );

    /* Save A for check */
    double *A = NULL;
    if(check) {
        A = (double*)malloc(N * N * sizeof(double));
        plasma_pdtile_to_lapack_quark(*descA, (void*)A, N);
    }

    double t_start, t_end;

    //START_TIMING();
    t_start = rtclock();
#pragma omp parallel
#pragma omp master
    plasma_pdpotrf_quark(uplo, *descA);
    //STOP_TIMING();
    t_end = rtclock();

    *t_ = t_end - t_start;

    /* Check the solution */
    if ( check )
    {
        PLASMA_desc *descB = NULL;
        double* ptr = (double*)malloc(N * sizeof(double));
        PLASMA_Desc_Create(&descB, ptr, PlasmaRealDouble, NB, NB, NB*NB, N, 1, 0, 0, N, 1);

        plasma_pdpltmg_seq(* descB, 7672 );
        double* B = (double*)malloc(N * sizeof(double));
        plasma_pdtile_to_lapack_quark(*descB, (void*)B, N);

        PLASMA_dpotrs_Tile( uplo, descA, descB );

        double* X = (double*)malloc(N * sizeof(double));
        plasma_pdtile_to_lapack_quark(*descB, (void*)X, N);

        check_res = d_check_solution(N, N, 1, A, N, B, X, N);

        PASTE_CODE_FREE_MATRIX( descB );
        free( A );
        free( B );
        free( X );
    }

    PASTE_CODE_FREE_MATRIX( descA );

    return check_res;
}

static double
RunTest_dgetrf(real_Double_t *t_, struct user_parameters* params)
{
    double  t;
    int64_t N     = params->matrix_size;
    int64_t NB    = params->blocksize;
    int check     = params->check;
    double check_res = 0;

    /* Allocate Data */
    PLASMA_desc *descA = NULL;
    double* ptr = malloc(N * N * sizeof(double));
    PLASMA_Desc_Create(&descA, ptr, PlasmaRealDouble, NB, NB, NB*NB, N, N, 0, 0, N, N);

    int* piv = (int*)malloc(N * sizeof(double));

#pragma omp parallel
#pragma omp master
    plasma_pdpltmg_quark(*descA, 3456);

    /* Save AT in lapack layout for check */
    double *A = NULL;
    if(check) {
        A = (double*)malloc(N * N * sizeof(double));
        plasma_pdtile_to_lapack_quark(*descA, (void*)A, N);
    }

    double t_start, t_end;

    //START_TIMING();
    t_start = rtclock();
#pragma omp parallel
#pragma omp master
    plasma_pdgetrf_rectil_quark(*descA, piv);
    //STOP_TIMING();
    t_end = rtclock();

    *t_ = t_end - t_start;

    /* Check the solution */
    if ( check )
    {
        PLASMA_desc *descB = NULL;
        double* ptr = (double*)malloc(N * sizeof(double));
        PLASMA_Desc_Create(&descB, ptr, PlasmaRealDouble, NB, NB, NB*NB, N, 1, 0, 0, N, 1);

        plasma_pdpltmg_seq(*descB, 7732 );
        double* b = (double*)malloc(N * sizeof(double));
        plasma_pdtile_to_lapack_quark(*descB, (void*)b, N);

        PLASMA_dgetrs_Tile( PlasmaNoTrans, descA, piv, descB );

        double* x = (double*)malloc(N * sizeof(double));
        plasma_pdtile_to_lapack_quark(*descB, (void*)x, N);

        check_res = d_check_solution(N, N, 1, A, N, b, x, N);

        PASTE_CODE_FREE_MATRIX( descB );
        free(A); free(b); free(x);
    }

    PASTE_CODE_FREE_MATRIX( descA );
    free( piv );

    return check_res;
}

static double
RunTest_dgeqrf(real_Double_t *t_, struct user_parameters* params)
{
    double t;
    PLASMA_desc *descT;
    int64_t N     = params->matrix_size;
    int64_t IB    = params->iblocksize;
    int64_t NB    = params->blocksize;
    int check     = params->check;
    double check_res = 0;

    /* Allocate Data */
    PLASMA_desc *descA = NULL;
    double *ptr = (double*)malloc(N * N * sizeof(double));
    PLASMA_Desc_Create(&descA, ptr, PlasmaRealDouble, NB, NB, NB*NB, N, N, 0, 0, N, N);

#pragma omp parallel
#pragma omp master
    plasma_pdpltmg_quark(*descA, 5373 );

    /* Save A for check */
    double *A = NULL;
    if ( check ) {
        A = (double*)malloc(N * N * sizeof(double));
        plasma_pdtile_to_lapack_quark(*descA, (void*)A, N);
    }

    /* Allocate Workspace */
    plasma_alloc_ibnb_tile(N, N, PlasmaRealDouble, &descT, IB, NB);

    double t_start, t_end;

    //START_TIMING();
    t_start = rtclock();
#pragma omp parallel
#pragma omp master
    plasma_pdgeqrf_quark( *descA, *descT , IB);
    //STOP_TIMING();
    t_end = rtclock();

    *t_ = t_end - t_start;

    /* Check the solution */
    if ( check )
    {
        /* Allocate B for check */
        PLASMA_desc *descB = NULL;
        double* ptr = (double*)malloc(N * sizeof(double));
        PLASMA_Desc_Create(&descB, ptr, PlasmaRealDouble, NB, NB, NB*NB, N, 1, 0, 0, N, 1);

        /* Initialize and save B */
        plasma_pdpltmg_seq(*descB, 2264 );
        double *B = (double*)malloc(N * sizeof(double));
        plasma_pdtile_to_lapack_quark(*descB, (void*)B, N);

        /* Compute the solution */
        PLASMA_dgeqrs_Tile( descA, descT, descB , IB);

        /* Copy solution to X */
        double *X = (double*)malloc(N * sizeof(double));
        plasma_pdtile_to_lapack_quark(*descB, (void*)X, N);

        check_res = d_check_solution(N, N, 1, A, N, B, X, N);

        /* Free checking structures */
        PASTE_CODE_FREE_MATRIX( descB );
        free( A );
        free( B );
        free( X );
    }

    /* Free data */
    PLASMA_Dealloc_Handle_Tile(&descT);
    PASTE_CODE_FREE_MATRIX( descA );

    return check_res;
}

int ISEED[4] = {0,0,0,1};   /* initial seed for zlarnv() */

double run(struct user_parameters* params)
{
    double   t;
    double   fmuls, fadds;
    double   flops;

    params->succeed = 1;
    int type = params->type;
    if (params->matrix_size <= 0) {
        params->matrix_size = 2048;
    }

    if (params->blocksize <= 0) {
        params->blocksize = 128;
    }
//ifdef
    if (params->iblocksize <= 0) {
        params->iblocksize = params->blocksize;
    }

    if (params->type <= 0) {
        params->type = 1;
	type = params->type;
    }

    int64_t N    = params->matrix_size;
    int64_t M    = params->matrix_size;
    fadds = (double)(_FADDS);
    fmuls = (double)(_FMULS);
    flops = 1e-9 * (fmuls + fadds);
    //mod aqui

    if (type == 1) {
      if (RunTest_dgeqrf(&t, params) > EPSILON && params->check)
          params->succeed = 0;
    } else if (type == 2) {
      if (RunTest_dgetrf(&t, params) > EPSILON && params->check)
          params->succeed = 0;
    } else if (type == 3) {
      if (RunTest_dpotrf(&t, params) > EPSILON && params->check)
          params->succeed = 0;
    }

    // return gflops
    return flops / t;
}


/*****************************************************************\
           __
          / /
		 / /                     __  __
		/ /______    _______    / / / / ________   __       __
	   / ______  \  /_____  \  / / / / / _____  | / /      / /
	  / /      | / _______| / / / / / / /____/ / / /      / /
	 / /      / / / _____  / / / / / / _______/ / /      / /
	/ /      / / / /____/ / / / / / / |______  / |______/ /
   /_/      /_/ |________/ / / / /  \_______/  \_______  /
                          /_/ /_/                     / /
			                                         / /
		       High Level Game Framework            /_/

  ---------------------------------------------------------------

  Copyright (c) 2007-2011 - Rodrigo Braz Monteiro.
  This file is subject to the terms of halley_license.txt.

\*****************************************************************/

#pragma once

#include <random>
#include <halley/utils/utils.h>
#include <gsl/span>

namespace Halley {
	typedef std::mt19937 RandomGenType;

	class Random {
	public:
		static Random& getGlobal();

		Random();
		Random(long seed);
		Random(char* bytes, size_t nBytes);

		template <typename T> T get(T max) { return get(T(0), max); }
		template <typename T> T get(T min, T max);

		template <typename T>
		T getInt(T min, T max)
		{
			if (min > max) {
				std::swap(min, max);
			}
			std::uniform_int_distribution<T> dist(min, max);
			return dist(generator);
		}

		template <typename T>
		T getFloat(T min, T max)
		{
			if (min > max) std::swap(min, max);
			std::uniform_real_distribution<T> dist(min, max);
			return dist(generator);
		}

		void getBytes(gsl::span<gsl::byte> dst);
		void setSeed(long seed);
		void setSeed(char* bytes, size_t nBytes);

	private:
		RandomGenType generator;
	};

	template <typename T>
	struct _getHack {
		static T get(Random* rng, T min, T max) { return rng->getInt(min, max); }
	};

	template <>
	struct _getHack<float> {
		static float get(Random *rng, float min, float max) { return rng->getFloat(min, max); }
	};

	template <>
	struct _getHack<double> {
		static double get(Random *rng, double min, double max) { return rng->getFloat(min, max); }
	};

	template <typename T>
	T Random::get(T min, T max)
	{
		return _getHack<T>::get(this, min, max);
	}

}


#ifndef GRAPH_H_
#define GRAPH_H_

#include "common.h"
#include <gsl/gsl_rng.h>
#include <gsl/gsl_randist.h>

/** A graph represented by an adjacency list (made of dynamic arrays). */
typedef struct
{
    int num_v; /** Number of vertices. */

    int *num_e; /** Number of edges per vertex. */

    int *capacity; /** Space allocated to each vertex. */

    int **adj_list; /** Adjacency list. */

    double **w_list; /** Weights. */
}
graph;

/** Initialize a graph with a fixed number of vertices. */
void graph_init(graph *g, int vertices);

/** Number of edges in the entire graph. */
int graph_edges(const graph *g);

/** Number of proper edges in the entire graph. */
int graph_proper_edges(const graph *g);

/** Number of loops in the entire graph. */
int graph_loops(const graph *g);

/** Number of outgoing edges for vertex \f$v\f$. \f$O(1)\f$.*/
int graph_outdegree(const graph *g, int u);

/** Number of ingoing edges for vertex \f$v\f$. \f$O(|V||E|)\f$.*/
int graph_indegree(const graph *g, int u);

/** Return true if the graph is balanced (i.e.: for each vertex \f$v\f$, indegree(v) == outdegree(v)). \f$O(|V||E|)\f$. */
int graph_is_balanced(const graph *g);

/** Add an edge between vertices 'u' and 'v' with weight. O(1), worst-case O(|N|). */
void graph_add_edge(graph *g, int u, int v, double weight);

/** Add an edge between vertices 'u' and 'v' and vice-versa. */
void graph_add_sym_edges(graph *g, int u, int v, double weight);

/** Remove the edge between vertices 'u' and 'v'. */
int graph_rmv_edge(graph *g, int u, int v);

/** Remove the edges between 'u' and 'v' and vice-versa. Return false if only 1 or 0 edges were removed. */
int graph_rmv_sym_edges(graph *g, int u, int v);

/** Return true if the graph has an edge between 'u' and 'v'. O(|E|). */
int graph_has_edge(graph *g, int u, int v);

/** Return true if the graph is strongly connected. */
int graph_strongly_connected(const graph *g);

/** Print as a svg file (assumes all points are in the [0, 1) range).*/
void graph_svg(const graph *g, double *x, double *y, double size, double offset, FILE *out);

/**
 * Print as a svg file with stronger colors for higher abundances. abun must point to an array
 * with one double in the [0, 1) range for each vertex. color = 0 (red), 1 (green), 2 (blue).
 */
void graph_svg_abun(const graph *g, double *x, double *y, double size, double offset, double *abun, int color, FILE *out);

/** Print the graph in GraphML format. */
void graph_graphml(const graph *g, FILE *out, unsigned int id);

/** Print the adjacency list. */
void graph_print(const graph *g, FILE *out);

/** Free the memory of the struct. */
void graph_free(graph *g);

/**
 * Get a random geometric graph in [0,1]^2 with radius 'r'.
 *
 * 'x' and 'y' must be initialized and the correct amount of space must
 * be allocated. The weight of the realized edges is 'r - d', where 'd'
 * is the geometric distance between the two vertices (in short the
 * weight is always between 0 and 1.0).
 */
void graph_get_rgg(graph *g, int vertices, double r, double *x, double *y, gsl_rng *rng);

/**
 * Get a connected random geometric graph in [0,1]^2 with radius 'r'.
 *
 * 'x' and 'y' must be initialized and the correct amount of space must
 * be allocated. The weight of the realized edges is 'r - d', where 'd'
 * is the geometric distance between the two vertices (in short the
 * weight is always between 0 and 1.0).
 */
void graph_get_crgg(graph *g, int vertices, double r, double *x, double *y, gsl_rng *rng);

/**
 * Get a random geometric graph in a rectangle with an area of 1 and
 * radius 'r'.
 *
 * 'x' and 'y' must be initialized and the correct amount of space must
 * be allocated. The weight of the realized edges is 'r - d', where 'd'
 * is the geometric distance between the two vertices (in short the
 * weight is always between 0 and 1.0).
 */
void graph_get_rec_rgg(graph *g, int vertices, double width, double r, double *x, double *y, gsl_rng *rng);

/**
 * Get a connected random geometric graph in a rectangle with an area of
 * 1 and radius 'r'.
 *
 * 'x' and 'y' must be initialized and the correct amount of space must
 * be allocated. The weight of the realized edges is 'r - d', where 'd'
 * is the geometric distance between the two vertices (in short the
 * weight is always between 0 and 1.0).
 */
void graph_get_rec_crgg(graph *g, int vertices, double width, double r, double *x, double *y, gsl_rng *rng);

/**
 * Return a complete graph (all vertices linked to each other). Realized
 * edges have a default weight of 1.0.
 */
void graph_get_complete(graph *g, int vertices);

/**
 * Return a circle. Realized edges have a default weight of 1.0.
 */
void graph_get_circle(graph *g, int vertices);

/**
 * Return a star. Realized edges have a default weight of 1.0.
 */
void graph_get_star(graph *g, int vertices);

///////////////////////////////////////////////////////////////
// 'Private' functions. You shouldn't need those.

/** Recursive function used to test connectivity. */
void graph_test_cc(const graph *g, int *group, int u);

/** Increse the storage of the lists for vertex 'u'. O(|E|). */
void graph_grow_lists(graph *g, int u);

#endif


/*!
 \file fasttransforms.h
 \brief fasttransforms.h is the main header file for FastTransforms.
*/
#ifndef FASTTRANSFORMS_H
#define FASTTRANSFORMS_H

#include <cblas.h>
#include <fftw3.h>

typedef double ft_complex[2];

#ifdef _OPENMP
    #include <omp.h>
    #define FT_GET_THREAD_NUM() omp_get_thread_num()
    #define FT_GET_NUM_THREADS() omp_get_num_threads()
    #define FT_GET_MAX_THREADS() omp_get_max_threads()
    #define FT_SET_NUM_THREADS(x) omp_set_num_threads(x)
#else
    #define FT_GET_THREAD_NUM() 0
    #define FT_GET_NUM_THREADS() 1
    #define FT_GET_MAX_THREADS() 1
    #define FT_SET_NUM_THREADS(x)
#endif

#define FT_CONCAT(prefix, name, suffix) prefix ## name ## suffix

void ft_horner(const int n, const double * c, const int incc, const int m, double * x, double * f);
void ft_hornerf(const int n, const float * c, const int incc, const int m, float * x, float * f);

void ft_clenshaw(const int n, const double * c, const int incc, const int m, double * x, double * f);
void ft_clenshawf(const int n, const float * c, const int incc, const int m, float * x, float * f);

void ft_orthogonal_polynomial_clenshaw(const int n, const double * c, const int incc, const double * A, const double * B, const double * C, const int m, double * x, double * phi0, double * f);
void ft_orthogonal_polynomial_clenshawf(const int n, const float * c, const int incc, const float * A, const float * B, const float * C, const int m, float * x, float * phi0, float * f);

void ft_eigen_eval(const int n, const double * c, const int incc, const double * A, const double * B, const double * C, const int m, double * x, const int sign, double * f);
void ft_eigen_evalf(const int n, const float * c, const int incc, const float * A, const float * B, const float * C, const int m, float * x, const int sign, float * f);
void ft_eigen_evall(const int n, const long double * c, const int incc, const long double * A, const long double * B, const long double * C, const int m, long double * x, const int sign, long double * f);
#if defined(FT_QUADMATH)
    #include <quadmath.h>
    typedef __float128 quadruple;
    void ft_eigen_evalq(const int n, const quadruple * c, const int incc, const quadruple * A, const quadruple * B, const quadruple * C, const int m, quadruple * x, const int sign, quadruple * f);
#endif

#define FT_SN (1U << 0)
#define FT_CN (1U << 1)
#define FT_DN (1U << 2)

#include "tdc.h"

/*!
  \brief Pre-compute a factorization of the connection coefficients between Legendre and Chebyshev polynomials in double precision so that ft_bfmv converts between expansions:
  \f[
  \sum_{\ell=0}^{n-1} c_\ell^{\mathrm{Legendre}} P_\ell(x) = \sum_{\ell=0}^{n-1} c_\ell^{\mathrm{Chebyshev}} T_\ell(x).
  \f]
  `normleg` and `normcheb` govern the normalizations, either standard ( == 0) or orthonormalized ( == 1).\n
  See also \ref ft_plan_legendre_to_chebyshevf, \ref ft_plan_legendre_to_chebyshevl, and \ref ft_mpfr_plan_legendre_to_chebyshev.
*/
ft_tb_eigen_FMM * ft_plan_legendre_to_chebyshev(const int normleg, const int normcheb, const int n);
/*!
  \brief Pre-compute a factorization of the connection coefficients between Chebyshev and Legendre polynomials in double precision so that ft_bfmv converts between expansions:
  \f[
  \sum_{\ell=0}^{n-1} c_\ell^{\mathrm{Chebyshev}} T_\ell(x) = \sum_{\ell=0}^{n-1} c_\ell^{\mathrm{Legendre}} P_\ell(x).
  \f]
  `normcheb` and `normleg` govern the normalizations, either standard ( == 0) or orthonormalized ( == 1).\n
  See also \ref ft_plan_chebyshev_to_legendref, \ref ft_plan_chebyshev_to_legendrel, and \ref ft_mpfr_plan_chebyshev_to_legendre.
*/
ft_tb_eigen_FMM * ft_plan_chebyshev_to_legendre(const int normcheb, const int normleg, const int n);
/*!
  \brief Pre-compute a factorization of the connection coefficients between ultraspherical polynomials in double precision so that ft_bfmv converts between expansions:
  \f[
  \sum_{\ell=0}^{n-1} c_\ell^{(1)} C_\ell^{(\lambda)}(x) = \sum_{\ell=0}^{n-1} c_\ell^{(2)} C_\ell^{(\mu)}(x).
  \f]
  `norm1` and `norm2` govern the normalizations, either standard ( == 0) or orthonormalized ( == 1).\n
  See also \ref ft_plan_ultraspherical_to_ultrasphericalf, \ref ft_plan_ultraspherical_to_ultrasphericall, and \ref ft_mpfr_plan_ultraspherical_to_ultraspherical.
*/
ft_tb_eigen_FMM * ft_plan_ultraspherical_to_ultraspherical(const int norm1, const int norm2, const int n, const double lambda, const double mu);
/*!
  \brief Pre-compute a factorization of the connection coefficients between Jacobi polynomials in double precision so that ft_bfmv converts between expansions:
  \f[
  \sum_{\ell=0}^{n-1} c_\ell^{(1)} P_\ell^{(\alpha,\beta)}(x) = \sum_{\ell=0}^{n-1} c_\ell^{(2)} P_\ell^{(\gamma,\delta)}(x).
  \f]
  `norm1` and `norm2` govern the normalizations, either standard ( == 0) or orthonormalized ( == 1).\n
  See also \ref ft_plan_jacobi_to_jacobif, \ref ft_plan_jacobi_to_jacobil, and \ref ft_mpfr_plan_jacobi_to_jacobi.
*/
ft_tb_eigen_FMM * ft_plan_jacobi_to_jacobi(const int norm1, const int norm2, const int n, const double alpha, const double beta, const double gamma, const double delta);
/*!
  \brief Pre-compute a factorization of the connection coefficients between Laguerre polynomials in double precision so that ft_bfmv converts between expansions:
  \f[
  \sum_{\ell=0}^{n-1} c_\ell^{(1)} L_\ell^{(\alpha)}(x) = \sum_{\ell=0}^{n-1} c_\ell^{(2)} L_\ell^{(\beta)}(x).
  \f]
  `norm1` and `norm2` govern the normalizations, either standard ( == 0) or orthonormalized ( == 1).\n
  See also \ref ft_plan_laguerre_to_laguerref, \ref ft_plan_laguerre_to_laguerrel, and \ref ft_mpfr_plan_laguerre_to_laguerre.
*/
ft_tb_eigen_FMM * ft_plan_laguerre_to_laguerre(const int norm1, const int norm2, const int n, const double alpha, const double beta);
/*!
  \brief Pre-compute a factorization of the connection coefficients between Jacobi and ultraspherical polynomials in double precision so that ft_bfmv converts between expansions:
  \f[
  \sum_{\ell=0}^{n-1} c_\ell^{\mathrm{Jacobi}} P_\ell^{(\alpha,\beta)}(x) = \sum_{\ell=0}^{n-1} c_\ell^{\mathrm{ultraspherical}} C_\ell^{(\lambda)}(x).
  \f]
  `normjac` and `normultra` govern the normalizations, either standard ( == 0) or orthonormalized ( == 1).\n
  See also \ref ft_plan_jacobi_to_ultrasphericalf, \ref ft_plan_jacobi_to_ultrasphericall, and \ref ft_mpfr_plan_jacobi_to_ultraspherical.
*/
ft_tb_eigen_FMM * ft_plan_jacobi_to_ultraspherical(const int normjac, const int normultra, const int n, const double alpha, const double beta, const double lambda);
/*!
  \brief Pre-compute a factorization of the connection coefficients between ultraspherical and Jacobi polynomials in double precision so that ft_bfmv converts between expansions:
  \f[
  \sum_{\ell=0}^{n-1} c_\ell^{\mathrm{ultraspherical}} C_\ell^{(\lambda)}(x) = \sum_{\ell=0}^{n-1} c_\ell^{\mathrm{Jacobi}} P_\ell^{(\alpha,\beta)}(x).
  \f]
  `normultra` and `normjac` govern the normalizations, either standard ( == 0) or orthonormalized ( == 1).\n
  See also \ref ft_plan_ultraspherical_to_jacobif, \ref ft_plan_ultraspherical_to_jacobil, and \ref ft_mpfr_plan_ultraspherical_to_jacobi.
*/
ft_tb_eigen_FMM * ft_plan_ultraspherical_to_jacobi(const int normultra, const int normjac, const int n, const double lambda, const double alpha, const double beta);
/*!
  \brief Pre-compute a factorization of the connection coefficients between Jacobi and Chebyshev polynomials in double precision so that ft_bfmv converts between expansions:
  \f[
  \sum_{\ell=0}^{n-1} c_\ell^{\mathrm{Jacobi}} P_\ell^{(\alpha,\beta)}(x) = \sum_{\ell=0}^{n-1} c_\ell^{\mathrm{Chebyshev}} T_\ell(x).
  \f]
  `normjac` and `normcheb` govern the normalizations, either standard ( == 0) or orthonormalized ( == 1).\n
  See also \ref ft_plan_jacobi_to_chebyshevf, \ref ft_plan_jacobi_to_chebyshevl, and \ref ft_mpfr_plan_jacobi_to_chebyshev.
*/
ft_tb_eigen_FMM * ft_plan_jacobi_to_chebyshev(const int normjac, const int normcheb, const int n, const double alpha, const double beta);
/*!
  \brief Pre-compute a factorization of the connection coefficients between Chebyshev and Jacobi polynomials in double precision so that ft_bfmv converts between expansions:
  \f[
  \sum_{\ell=0}^{n-1} c_\ell^{\mathrm{Chebyshev}} T_\ell(x) = \sum_{\ell=0}^{n-1} c_\ell^{\mathrm{Jacobi}} P_\ell^{(\alpha,\beta)}(x).
  \f]
  `normcheb` and `normjac` govern the normalizations, either standard ( == 0) or orthonormalized ( == 1).\n
  See also \ref ft_plan_chebyshev_to_jacobif, \ref ft_plan_chebyshev_to_jacobil, and \ref ft_mpfr_plan_chebyshev_to_jacobi.
*/
ft_tb_eigen_FMM * ft_plan_chebyshev_to_jacobi(const int normcheb, const int normjac, const int n, const double alpha, const double beta);
/*!
  \brief Pre-compute a factorization of the connection coefficients between ultraspherical and Chebyshev polynomials in double precision so that ft_bfmv converts between expansions:
  \f[
  \sum_{\ell=0}^{n-1} c_\ell^{\mathrm{ultraspherical}} C_\ell^{(\lambda)}(x) = \sum_{\ell=0}^{n-1} c_\ell^{\mathrm{Chebyshev}} T_\ell(x).
  \f]
  `normultra` and `normcheb` govern the normalizations, either standard ( == 0) or orthonormalized ( == 1).\n
  See also \ref ft_plan_ultraspherical_to_chebyshevf, \ref ft_plan_ultraspherical_to_chebyshevl, and \ref ft_mpfr_plan_ultraspherical_to_chebyshev.
*/
ft_tb_eigen_FMM * ft_plan_ultraspherical_to_chebyshev(const int normultra, const int normcheb, const int n, const double lambda);
/*!
  \brief Pre-compute a factorization of the connection coefficients between Chebyshev and ultraspherical polynomials in double precision so that ft_bfmv converts between expansions:
  \f[
  \sum_{\ell=0}^{n-1} c_\ell^{\mathrm{Chebyshev}} T_\ell(x) = \sum_{\ell=0}^{n-1} c_\ell^{\mathrm{ultraspherical}} C_\ell^{(\lambda)}(x).
  \f]
  `normcheb` and `normultra` govern the normalizations, either standard ( == 0) or orthonormalized ( == 1).\n
  See also \ref ft_plan_chebyshev_to_ultrasphericalf, \ref ft_plan_chebyshev_to_ultrasphericall, and \ref ft_mpfr_plan_chebyshev_to_ultraspherical.
*/
ft_tb_eigen_FMM * ft_plan_chebyshev_to_ultraspherical(const int normcheb, const int normultra, const int n, const double lambda);

ft_btb_eigen_FMM * ft_plan_associated_jacobi_to_jacobi(const int norm1, const int norm2, const int n, const int c, const double alpha, const double beta, const double gamma, const double delta);
ft_btb_eigen_FMM * ft_plan_associated_laguerre_to_laguerre(const int norm1, const int norm2, const int n, const int c, const double alpha, const double beta);
ft_btb_eigen_FMM * ft_plan_associated_hermite_to_hermite(const int norm1, const int norm2, const int n, const int c);

/// A single precision version of \ref ft_plan_legendre_to_chebyshev.
ft_tb_eigen_FMMf * ft_plan_legendre_to_chebyshevf(const int normleg, const int normcheb, const int n);
/// A single precision version of \ref ft_plan_chebyshev_to_legendre.
ft_tb_eigen_FMMf * ft_plan_chebyshev_to_legendref(const int normcheb, const int normleg, const int n);
/// A single precision version of \ref ft_plan_ultraspherical_to_ultraspherical.
ft_tb_eigen_FMMf * ft_plan_ultraspherical_to_ultrasphericalf(const int norm1, const int norm2, const int n, const float lambda, const float mu);
/// A single precision version of \ref ft_plan_jacobi_to_jacobi.
ft_tb_eigen_FMMf * ft_plan_jacobi_to_jacobif(const int norm1, const int norm2, const int n, const float alpha, const float beta, const float gamma, const float delta);
/// A single precision version of \ref ft_plan_laguerre_to_laguerre.
ft_tb_eigen_FMMf * ft_plan_laguerre_to_laguerref(const int norm1, const int norm2, const int n, const float alpha, const float beta);
/// A single precision version of \ref ft_plan_jacobi_to_ultraspherical.
ft_tb_eigen_FMMf * ft_plan_jacobi_to_ultrasphericalf(const int normjac, const int normultra, const int n, const float alpha, const float beta, const float lambda);
/// A single precision version of \ref ft_plan_ultraspherical_to_jacobi.
ft_tb_eigen_FMMf * ft_plan_ultraspherical_to_jacobif(const int normultra, const int normjac, const int n, const float lambda, const float alpha, const float beta);
/// A single precision version of \ref ft_plan_jacobi_to_chebyshev.
ft_tb_eigen_FMMf * ft_plan_jacobi_to_chebyshevf(const int normjac, const int normcheb, const int n, const float alpha, const float beta);
/// A single precision version of \ref ft_plan_chebyshev_to_jacobi.
ft_tb_eigen_FMMf * ft_plan_chebyshev_to_jacobif(const int normcheb, const int normjac, const int n, const float alpha, const float beta);
/// A single precision version of \ref ft_plan_ultraspherical_to_chebyshev.
ft_tb_eigen_FMMf * ft_plan_ultraspherical_to_chebyshevf(const int normultra, const int normcheb, const int n, const float lambda);
/// A single precision version of \ref ft_plan_chebyshev_to_ultraspherical.
ft_tb_eigen_FMMf * ft_plan_chebyshev_to_ultrasphericalf(const int normcheb, const int normultra, const int n, const float lambda);

ft_btb_eigen_FMMf * ft_plan_associated_jacobi_to_jacobif(const int norm1, const int norm2, const int n, const int c, const float alpha, const float beta, const float gamma, const float delta);
ft_btb_eigen_FMMf * ft_plan_associated_laguerre_to_laguerref(const int norm1, const int norm2, const int n, const int c, const float alpha, const float beta);
ft_btb_eigen_FMMf * ft_plan_associated_hermite_to_hermitef(const int norm1, const int norm2, const int n, const int c);

/// A long double precision version of \ref ft_plan_legendre_to_chebyshev.
ft_tb_eigen_FMMl * ft_plan_legendre_to_chebyshevl(const int normleg, const int normcheb, const int n);
/// A long double precision version of \ref ft_plan_chebyshev_to_legendre.
ft_tb_eigen_FMMl * ft_plan_chebyshev_to_legendrel(const int normcheb, const int normleg, const int n);
/// A long double precision version of \ref ft_plan_ultraspherical_to_ultraspherical.
ft_tb_eigen_FMMl * ft_plan_ultraspherical_to_ultrasphericall(const int norm1, const int norm2, const int n, const long double lambda, const long double mu);
/// A long double precision version of \ref ft_plan_jacobi_to_jacobi.
ft_tb_eigen_FMMl * ft_plan_jacobi_to_jacobil(const int norm1, const int norm2, const int n, const long double alpha, const long double beta, const long double gamma, const long double delta);
/// A long double precision version of \ref ft_plan_laguerre_to_laguerre.
ft_tb_eigen_FMMl * ft_plan_laguerre_to_laguerrel(const int norm1, const int norm2, const int n, const long double alpha, const long double beta);
/// A long double precision version of \ref ft_plan_jacobi_to_ultraspherical.
ft_tb_eigen_FMMl * ft_plan_jacobi_to_ultrasphericall(const int normjac, const int normultra, const int n, const long double alpha, const long double beta, const long double lambda);
/// A long double precision version of \ref ft_plan_ultraspherical_to_jacobi.
ft_tb_eigen_FMMl * ft_plan_ultraspherical_to_jacobil(const int normultra, const int normjac, const int n, const long double lambda, const long double alpha, const long double beta);
/// A long double precision version of \ref ft_plan_jacobi_to_chebyshev.
ft_tb_eigen_FMMl * ft_plan_jacobi_to_chebyshevl(const int normjac, const int normcheb, const int n, const long double alpha, const long double beta);
/// A long double precision version of \ref ft_plan_chebyshev_to_jacobi.
ft_tb_eigen_FMMl * ft_plan_chebyshev_to_jacobil(const int normcheb, const int normjac, const int n, const long double alpha, const long double beta);
/// A long double precision version of \ref ft_plan_ultraspherical_to_chebyshev.
ft_tb_eigen_FMMl * ft_plan_ultraspherical_to_chebyshevl(const int normultra, const int normcheb, const int n, const long double lambda);
/// A long double precision version of \ref ft_plan_chebyshev_to_ultraspherical.
ft_tb_eigen_FMMl * ft_plan_chebyshev_to_ultrasphericall(const int normcheb, const int normultra, const int n, const long double lambda);

ft_btb_eigen_FMMl * ft_plan_associated_jacobi_to_jacobil(const int norm1, const int norm2, const int n, const int c, const long double alpha, const long double beta, const long double gamma, const long double delta);
ft_btb_eigen_FMMl * ft_plan_associated_laguerre_to_laguerrel(const int norm1, const int norm2, const int n, const int c, const long double alpha, const long double beta);
ft_btb_eigen_FMMl * ft_plan_associated_hermite_to_hermitel(const int norm1, const int norm2, const int n, const int c);

#include <mpfr.h>

typedef struct {
    mpfr_t * data;
    int n;
    int b;
} ft_mpfr_triangular_banded;

void ft_mpfr_destroy_plan(mpfr_t * A, int n);
void ft_mpfr_trmv(char TRANS, int n, mpfr_t * A, int LDA, mpfr_t * x, mpfr_rnd_t rnd);
void ft_mpfr_trsv(char TRANS, int n, mpfr_t * A, int LDA, mpfr_t * x, mpfr_rnd_t rnd);
void ft_mpfr_trmm(char TRANS, int n, mpfr_t * A, int LDA, mpfr_t * B, int LDB, int N, mpfr_rnd_t rnd);
void ft_mpfr_trsm(char TRANS, int n, mpfr_t * A, int LDA, mpfr_t * B, int LDB, int N, mpfr_rnd_t rnd);
// C -- Julia interoperability. Julia `BigFloat` does not have the same size as `mpfr_t`.
// So we give all Julia-owned data its own address, and dereference to retrieve the number.
void ft_mpfr_trmv_ptr(char TRANS, int n, mpfr_t * A, int LDA, mpfr_t ** x, mpfr_rnd_t rnd);
void ft_mpfr_trsv_ptr(char TRANS, int n, mpfr_t * A, int LDA, mpfr_t ** x, mpfr_rnd_t rnd);
void ft_mpfr_trmm_ptr(char TRANS, int n, mpfr_t * A, int LDA, mpfr_t ** B, int LDB, int N, mpfr_rnd_t rnd);
void ft_mpfr_trsm_ptr(char TRANS, int n, mpfr_t * A, int LDA, mpfr_t ** B, int LDB, int N, mpfr_rnd_t rnd);

/// A multi-precision version of \ref ft_plan_legendre_to_chebyshev that returns a dense array of connection coefficients.
mpfr_t * ft_mpfr_plan_legendre_to_chebyshev(const int normleg, const int normcheb, const int n, mpfr_prec_t prec, mpfr_rnd_t rnd);
/// A multi-precision version of \ref ft_plan_chebyshev_to_legendre that returns a dense array of connection coefficients.
mpfr_t * ft_mpfr_plan_chebyshev_to_legendre(const int normcheb, const int normleg, const int n, mpfr_prec_t prec, mpfr_rnd_t rnd);
/// A multi-precision version of \ref ft_plan_ultraspherical_to_ultraspherical that returns a dense array of connection coefficients.
mpfr_t * ft_mpfr_plan_ultraspherical_to_ultraspherical(const int norm1, const int norm2, const int n, mpfr_srcptr lambda, mpfr_srcptr mu, mpfr_prec_t prec, mpfr_rnd_t rnd);
/// A multi-precision version of \ref ft_plan_jacobi_to_jacobi that returns a dense array of connection coefficients.
mpfr_t * ft_mpfr_plan_jacobi_to_jacobi(const int norm1, const int norm2, const int n, mpfr_srcptr alpha, mpfr_srcptr beta, mpfr_srcptr gamma, mpfr_srcptr delta, mpfr_prec_t prec, mpfr_rnd_t rnd);
/// A multi-precision version of \ref ft_plan_laguerre_to_laguerre that returns a dense array of connection coefficients.
mpfr_t * ft_mpfr_plan_laguerre_to_laguerre(const int norm1, const int norm2, const int n, mpfr_srcptr alpha, mpfr_srcptr beta, mpfr_prec_t prec, mpfr_rnd_t rnd);
/// A multi-precision version of \ref ft_plan_jacobi_to_ultraspherical that returns a dense array of connection coefficients.
mpfr_t * ft_mpfr_plan_jacobi_to_ultraspherical(const int normjac, const int normultra, const int n, mpfr_srcptr alpha, mpfr_srcptr beta, mpfr_srcptr lambda, mpfr_prec_t prec, mpfr_rnd_t rnd);
/// A multi-precision version of \ref ft_plan_ultraspherical_to_jacobi that returns a dense array of connection coefficients.
mpfr_t * ft_mpfr_plan_ultraspherical_to_jacobi(const int normultra, const int normjac, const int n, mpfr_srcptr lambda, mpfr_srcptr alpha, mpfr_srcptr beta, mpfr_prec_t prec, mpfr_rnd_t rnd);
/// A multi-precision version of \ref ft_plan_jacobi_to_chebyshev that returns a dense array of connection coefficients.
mpfr_t * ft_mpfr_plan_jacobi_to_chebyshev(const int normjac, const int normcheb, const int n, mpfr_srcptr alpha, mpfr_srcptr beta, mpfr_prec_t prec, mpfr_rnd_t rnd);
/// A multi-precision version of \ref ft_plan_chebyshev_to_jacobi that returns a dense array of connection coefficients.
mpfr_t * ft_mpfr_plan_chebyshev_to_jacobi(const int normcheb, const int normjac, const int n, mpfr_srcptr alpha, mpfr_srcptr beta, mpfr_prec_t prec, mpfr_rnd_t rnd);
/// A multi-precision version of \ref ft_plan_ultraspherical_to_chebyshev that returns a dense array of connection coefficients.
mpfr_t * ft_mpfr_plan_ultraspherical_to_chebyshev(const int normultra, const int normcheb, const int n, mpfr_srcptr lambda, mpfr_prec_t prec, mpfr_rnd_t rnd);
/// A multi-precision version of \ref ft_plan_chebyshev_to_ultraspherical that returns a dense array of connection coefficients.
mpfr_t * ft_mpfr_plan_chebyshev_to_ultraspherical(const int normcheb, const int normultra, const int n, mpfr_srcptr lambda, mpfr_prec_t prec, mpfr_rnd_t rnd);

/// Set the number of OpenMP threads.
void ft_set_num_threads(const int n);

/// Data structure to store sines and cosines of Givens rotations.
typedef struct ft_rotation_plan_s ft_rotation_plan;

/// Destroy a \ref ft_rotation_plan.
void ft_destroy_rotation_plan(ft_rotation_plan * RP);

ft_rotation_plan * ft_plan_rotsphere(const int n);

/// Convert a single vector of spherical harmonic coefficients in A with stride S from order m2 down to order m1.
void ft_kernel_sph_hi2lo(const ft_rotation_plan * RP, const int m1, const int m2, double * A, const int S);
/// Convert a single vector of spherical harmonic coefficients in A with stride S from order m1 up to order m2.
void ft_kernel_sph_lo2hi(const ft_rotation_plan * RP, const int m1, const int m2, double * A, const int S);

ft_rotation_plan * ft_plan_rottriangle(const int n, const double alpha, const double beta, const double gamma);

/// Convert a single vector of triangular harmonic coefficients in A with stride S from order m2 down to order m1.
void ft_kernel_tri_hi2lo(const ft_rotation_plan * RP, const int m1, const int m2, double * A, const int S);
/// Convert a single vector of triangular harmonic coefficients in A with stride S from order m1 up to order m2.
void ft_kernel_tri_lo2hi(const ft_rotation_plan * RP, const int m1, const int m2, double * A, const int S);

ft_rotation_plan * ft_plan_rotdisk(const int n, const double alpha, const double beta);

/// Convert a single vector of disk harmonic coefficients in A with stride S from order m2 down to order m1.
void ft_kernel_disk_hi2lo(const ft_rotation_plan * RP, const int m1, const int m2, double * A, const int S);
/// Convert a single vector of disk harmonic coefficients in A with stride S from order m1 up to order m2.
void ft_kernel_disk_lo2hi(const ft_rotation_plan * RP, const int m1, const int m2, double * A, const int S);

ft_rotation_plan * ft_plan_rotrectdisk(const int n, const double beta);

/// Convert a single vector of rectangularized disk harmonic coefficients in A with stride S from order m2 down to order m1.
void ft_kernel_rectdisk_hi2lo(const ft_rotation_plan * RP, const int m1, const int m2, double * A, const int S);
/// Convert a single vector of rectangularized disk harmonic coefficients in A with stride S from order m1 up to order m2.
void ft_kernel_rectdisk_lo2hi(const ft_rotation_plan * RP, const int m1, const int m2, double * A, const int S);

void ft_kernel_tet_hi2lo(const ft_rotation_plan * RP, const int L, const int m, double * A);
void ft_kernel_tet_lo2hi(const ft_rotation_plan * RP, const int L, const int m, double * A);

/// Data structure to store sines and cosines of Givens rotations for spin-weighted harmonics.
typedef struct ft_spin_rotation_plan_s ft_spin_rotation_plan;

/// Destroy a \ref ft_spin_rotation_plan.
void ft_destroy_spin_rotation_plan(ft_spin_rotation_plan * SRP);

ft_spin_rotation_plan * ft_plan_rotspinsphere(const int n, const int s);

/// Convert a single vector of spin-weighted spherical harmonic coefficients in A with stride S from order m down to order m%2.
void ft_kernel_spinsph_hi2lo(const ft_spin_rotation_plan * SRP, const int m, ft_complex * A, const int S);
/// Convert a single vector of spin-weighted spherical harmonic coefficients in A with stride S from order m%2 up to order m.
void ft_kernel_spinsph_lo2hi(const ft_spin_rotation_plan * SRP, const int m, ft_complex * A, const int S);

void ft_execute_sph_hi2lo(const ft_rotation_plan * RP, double * A, double * B, const int M);
void ft_execute_sph_lo2hi(const ft_rotation_plan * RP, double * A, double * B, const int M);

void ft_execute_sphv_hi2lo(const ft_rotation_plan * RP, double * A, double * B, const int M);
void ft_execute_sphv_lo2hi(const ft_rotation_plan * RP, double * A, double * B, const int M);

void ft_execute_tri_hi2lo(const ft_rotation_plan * RP, double * A, double * B, const int M);
void ft_execute_tri_lo2hi(const ft_rotation_plan * RP, double * A, double * B, const int M);

void ft_execute_disk_hi2lo(const ft_rotation_plan * RP, double * A, double * B, const int M);
void ft_execute_disk_lo2hi(const ft_rotation_plan * RP, double * A, double * B, const int M);

void ft_execute_rectdisk_hi2lo(const ft_rotation_plan * RP, double * A, double * B, const int M);
void ft_execute_rectdisk_lo2hi(const ft_rotation_plan * RP, double * A, double * B, const int M);

void ft_execute_tet_hi2lo(const ft_rotation_plan * RP1, const ft_rotation_plan * RP2, double * A, const int L, const int M);
void ft_execute_tet_lo2hi(const ft_rotation_plan * RP1, const ft_rotation_plan * RP2, double * A, const int L, const int M);

void ft_execute_spinsph_hi2lo(const ft_spin_rotation_plan * SRP, ft_complex * A, ft_complex * B, const int M);
void ft_execute_spinsph_lo2hi(const ft_spin_rotation_plan * SRP, ft_complex * A, ft_complex * B, const int M);

/// Data structure to store \ref ft_rotation_plan "ft_rotation_plan"s, arrays to represent 1D orthogonal polynomial transforms and their inverses, and Greek parameters.
typedef struct {
    ft_rotation_plan ** RP;
    double * B;
    double ** P;
    double ** Pinv;
    double alpha;
    double beta;
    double gamma;
    double delta;
    int NRP;
    int NP;
} ft_harmonic_plan;

/// Destroy a \ref ft_harmonic_plan.
void ft_destroy_harmonic_plan(ft_harmonic_plan * P);

/// Plan a spherical harmonic transform.
ft_harmonic_plan * ft_plan_sph2fourier(const int n);

/// Transform a spherical harmonic expansion to a bivariate Fourier series.
void ft_execute_sph2fourier(const char TRANS, const ft_harmonic_plan * P, double * A, const int N, const int M);
/// Transform a bivariate Fourier series to a spherical harmonic expansion.
void ft_execute_fourier2sph(const char TRANS, const ft_harmonic_plan * P, double * A, const int N, const int M);

void ft_execute_sphv2fourier(const char TRANS, const ft_harmonic_plan * P, double * A, const int N, const int M);
void ft_execute_fourier2sphv(const char TRANS, const ft_harmonic_plan * P, double * A, const int N, const int M);

/// Plan a triangular harmonic transform.
ft_harmonic_plan * ft_plan_tri2cheb(const int n, const double alpha, const double beta, const double gamma);

/// Transform a triangular harmonic expansion to a bivariate Chebyshev series.
void ft_execute_tri2cheb(const char TRANS, const ft_harmonic_plan * P, double * A, const int N, const int M);
/// Transform a bivariate Chebyshev series to a triangular harmonic expansion.
void ft_execute_cheb2tri(const char TRANS, const ft_harmonic_plan * P, double * A, const int N, const int M);

/// Plan a disk harmonic transform.
ft_harmonic_plan * ft_plan_disk2cxf(const int n, const double alpha, const double beta);

/// Transform a disk harmonic expansion to a Chebyshev--Fourier series.
void ft_execute_disk2cxf(const char TRANS, const ft_harmonic_plan * P, double * A, const int N, const int M);
/// Transform a Chebyshev--Fourier series to a disk harmonic expansion.
void ft_execute_cxf2disk(const char TRANS, const ft_harmonic_plan * P, double * A, const int N, const int M);

/// Plan a rectangularized disk harmonic transform.
ft_harmonic_plan * ft_plan_rectdisk2cheb(const int n, const double beta);

/// Transform a rectangularized disk harmonic expansion to a Chebyshev series.
void ft_execute_rectdisk2cheb(const char TRANS, const ft_harmonic_plan * P, double * A, const int N, const int M);
/// Transform a Chebyshev series to a rectangularized disk harmonic expansion.
void ft_execute_cheb2rectdisk(const char TRANS, const ft_harmonic_plan * P, double * A, const int N, const int M);

/// Plan a tetrahedral harmonic transform.
ft_harmonic_plan * ft_plan_tet2cheb(const int n, const double alpha, const double beta, const double gamma, const double delta);

/// Transform a tetrahedral harmonic expansion to a trivariate Chebyshev series.
void ft_execute_tet2cheb(const char TRANS, const ft_harmonic_plan * P, double * A, const int N, const int L, const int M);
/// Transform a trivariate Chebyshev series to a tetrahedral harmonic expansion.
void ft_execute_cheb2tet(const char TRANS, const ft_harmonic_plan * P, double * A, const int N, const int L, const int M);

/// Data structure to store a \ref ft_spin_rotation_plan, and various arrays to represent 1D orthogonal polynomial transforms.
typedef struct {
    ft_spin_rotation_plan * SRP;
    ft_complex * B;
    ft_complex * P1;
    ft_complex * P2;
    ft_complex * P1inv;
    ft_complex * P2inv;
    int s;
} ft_spin_harmonic_plan;

/// Destroy a \ref ft_spin_harmonic_plan.
void ft_destroy_spin_harmonic_plan(ft_spin_harmonic_plan * P);

/// Plan a spin-weighted spherical harmonic transform.
ft_spin_harmonic_plan * ft_plan_spinsph2fourier(const int n, const int s);

/// Transform a spin-weighted spherical harmonic expansion to a bivariate Fourier series.
void ft_execute_spinsph2fourier(const char TRANS, const ft_spin_harmonic_plan * P, ft_complex * A, const int N, const int M);
/// Transform a bivariate Fourier series to a spin-weighted spherical harmonic expansion.
void ft_execute_fourier2spinsph(const char TRANS, const ft_spin_harmonic_plan * P, ft_complex * A, const int N, const int M);

int ft_fftw_init_threads(void);
void ft_fftw_plan_with_nthreads(const int n);

typedef struct {
    fftw_plan plantheta1;
    fftw_plan plantheta2;
    fftw_plan plantheta3;
    fftw_plan plantheta4;
    fftw_plan planphi;
    double * Y;
} ft_sphere_fftw_plan;

/// Destroy a \ref ft_sphere_fftw_plan.
void ft_destroy_sphere_fftw_plan(ft_sphere_fftw_plan * P);

ft_sphere_fftw_plan * ft_plan_sph_with_kind(const int N, const int M, const fftw_r2r_kind kind[3][1]);
/// Plan FFTW synthesis on the sphere.
ft_sphere_fftw_plan * ft_plan_sph_synthesis(const int N, const int M);
/// Plan FFTW analysis on the sphere.
ft_sphere_fftw_plan * ft_plan_sph_analysis(const int N, const int M);
ft_sphere_fftw_plan * ft_plan_sphv_synthesis(const int N, const int M);
ft_sphere_fftw_plan * ft_plan_sphv_analysis(const int N, const int M);

/// Execute FFTW synthesis on the sphere.
void ft_execute_sph_synthesis(const char TRANS, const ft_sphere_fftw_plan * P, double * X, const int N, const int M);
/// Execute FFTW analysis on the sphere.
void ft_execute_sph_analysis(const char TRANS, const ft_sphere_fftw_plan * P, double * X, const int N, const int M);

void ft_execute_sphv_synthesis(const char TRANS, const ft_sphere_fftw_plan * P, double * X, const int N, const int M);
void ft_execute_sphv_analysis(const char TRANS, const ft_sphere_fftw_plan * P, double * X, const int N, const int M);

typedef struct {
    fftw_plan planxy;
} ft_triangle_fftw_plan;

/// Destroy a \ref ft_triangle_fftw_plan.
void ft_destroy_triangle_fftw_plan(ft_triangle_fftw_plan * P);

ft_triangle_fftw_plan * ft_plan_tri_with_kind(const int N, const int M, const fftw_r2r_kind kind0, const fftw_r2r_kind kind1);
/// Plan FFTW synthesis on the triangle.
ft_triangle_fftw_plan * ft_plan_tri_synthesis(const int N, const int M);
/// Plan FFTW analysis on the triangle.
ft_triangle_fftw_plan * ft_plan_tri_analysis(const int N, const int M);

/// Execute FFTW synthesis on the triangle.
void ft_execute_tri_synthesis(const char TRANS, const ft_triangle_fftw_plan * P, double * X, const int N, const int M);
/// Execute FFTW analysis on the triangle.
void ft_execute_tri_analysis(const char TRANS, const ft_triangle_fftw_plan * P, double * X, const int N, const int M);

typedef struct {
    fftw_plan planxyz;
} ft_tetrahedron_fftw_plan;

void ft_destroy_tetrahedron_fftw_plan(ft_tetrahedron_fftw_plan * P);

ft_tetrahedron_fftw_plan * ft_plan_tet_with_kind(const int N, const int L, const int M, const fftw_r2r_kind kind0, const fftw_r2r_kind kind1, const fftw_r2r_kind kind2);
ft_tetrahedron_fftw_plan * ft_plan_tet_synthesis(const int N, const int L, const int M);
ft_tetrahedron_fftw_plan * ft_plan_tet_analysis(const int N, const int L, const int M);

void ft_execute_tet_synthesis(const char TRANS, const ft_tetrahedron_fftw_plan * P, double * X, const int N, const int L, const int M);
void ft_execute_tet_analysis(const char TRANS, const ft_tetrahedron_fftw_plan * P, double * X, const int N, const int L, const int M);

typedef struct {
    fftw_plan planr1;
    fftw_plan planr2;
    fftw_plan planr3;
    fftw_plan planr4;
    fftw_plan plantheta;
    double * Y;
} ft_disk_fftw_plan;

/// Destroy a \ref ft_disk_fftw_plan.
void ft_destroy_disk_fftw_plan(ft_disk_fftw_plan * P);

ft_disk_fftw_plan * ft_plan_disk_with_kind(const int N, const int M, const fftw_r2r_kind kind[3][1]);
/// Plan FFTW synthesis on the disk.
ft_disk_fftw_plan * ft_plan_disk_synthesis(const int N, const int M);
/// Plan FFTW analysis on the disk.
ft_disk_fftw_plan * ft_plan_disk_analysis(const int N, const int M);

/// Execute FFTW synthesis on the disk.
void ft_execute_disk_synthesis(const char TRANS, const ft_disk_fftw_plan * P, double * X, const int N, const int M);
/// Execute FFTW analysis on the disk.
void ft_execute_disk_analysis(const char TRANS, const ft_disk_fftw_plan * P, double * X, const int N, const int M);

typedef struct {
    fftw_plan planx1;
    fftw_plan planx2;
    fftw_plan plany;
} ft_rectdisk_fftw_plan;

/// Destroy a \ref ft_rectdisk_fftw_plan.
void ft_destroy_rectdisk_fftw_plan(ft_rectdisk_fftw_plan * P);

ft_rectdisk_fftw_plan * ft_plan_rectdisk_with_kind(const int N, const int M, const fftw_r2r_kind kind[3][1]);
/// Plan FFTW synthesis on the rectangularized disk.
ft_rectdisk_fftw_plan * ft_plan_rectdisk_synthesis(const int N, const int M);
/// Plan FFTW analysis on the rectangularized disk.
ft_rectdisk_fftw_plan * ft_plan_rectdisk_analysis(const int N, const int M);

/// Execute FFTW synthesis on the rectangularized disk.
void ft_execute_rectdisk_synthesis(const char TRANS, const ft_rectdisk_fftw_plan * P, double * X, const int N, const int M);
/// Execute FFTW analysis on the rectangularized disk.
void ft_execute_rectdisk_analysis(const char TRANS, const ft_rectdisk_fftw_plan * P, double * X, const int N, const int M);

typedef struct {
    fftw_plan plantheta1;
    fftw_plan plantheta2;
    fftw_plan plantheta3;
    fftw_plan plantheta4;
    fftw_plan planphi;
    double * Y;
    int S;
} ft_spinsphere_fftw_plan;

/// Destroy a \ref ft_spinsphere_fftw_plan.
void ft_destroy_spinsphere_fftw_plan(ft_spinsphere_fftw_plan * P);

int ft_get_spin_spinsphere_fftw_plan(const ft_spinsphere_fftw_plan * P);

ft_spinsphere_fftw_plan * ft_plan_spinsph_with_kind(const int N, const int M, const int S, const fftw_r2r_kind kind[2][1], const int sign);
/// Plan FFTW synthesis on the sphere with spin.
ft_spinsphere_fftw_plan * ft_plan_spinsph_synthesis(const int N, const int M, const int S);
/// Plan FFTW analysis on the sphere with spin.
ft_spinsphere_fftw_plan * ft_plan_spinsph_analysis(const int N, const int M, const int S);

/// Execute FFTW synthesis on the sphere with spin.
void ft_execute_spinsph_synthesis(const char TRANS, const ft_spinsphere_fftw_plan * P, ft_complex * X, const int N, const int M);
/// Execute FFTW analysis on the sphere with spin.
void ft_execute_spinsph_analysis(const char TRANS, const ft_spinsphere_fftw_plan * P, ft_complex * X, const int N, const int M);

typedef struct {
    ft_banded ** B;
    ft_triangular_banded ** T;
    int n;
} ft_gradient_plan;

void ft_destroy_gradient_plan(ft_gradient_plan * P);

ft_gradient_plan * ft_plan_sph_gradient(const int n);

void ft_execute_sph_gradient(ft_gradient_plan * P, double * U, double * Ut, double * Up, const int N, const int M);
void ft_execute_sph_curl(ft_gradient_plan * P, double * U, double * Ut, double * Up, const int N, const int M);

typedef struct {
    ft_triangular_banded ** T;
    ft_banded_qr ** F;
    double * X;
    int n;
} ft_helmholtzhodge_plan;

void ft_destroy_helmholtzhodge_plan(ft_helmholtzhodge_plan * P);

ft_helmholtzhodge_plan * ft_plan_sph_helmholtzhodge(const int n);

void ft_execute_sph_helmholtzhodge(ft_helmholtzhodge_plan * P, double * U1, double * U2, double * V1, double * V2, const int N, const int M);

/*!
  \brief A static struct to store an orthogonal matrix \f$Q \in \mathbb{R}^{3\times3}\f$, such that \f$Q^\top Q = I\f$.
  \f$Q\f$ has column-major storage.
*/
typedef struct {
    double Q[9];
} ft_orthogonal_transformation;

/*!
  \brief Every orthogonal matrix \f$Q \in \mathbb{R}^{3\times3}\f$ can be decomposed as a product of \f$ZYZ\f$ Euler angles and, if necessary, a reflection \f$R\f$ about the \f$xy\f$-plane.
  \f[
  Q = ZYZR,
  \f]
  where the \f$z\f$-axis rotations are:
  \f[
  Z = \begin{pmatrix} c & -s & 0\\ s & c & 0\\ 0 & 0 & 1\end{pmatrix},
  \f]
  the \f$y\f$-axis rotation is:
  \f[
  Y = \begin{pmatrix} c & 0 & -s\\ 0 & 1 & 0\\ s & 0 & c\end{pmatrix},
  \f]
  and the potential reflection is:
  \f[
  R = \begin{pmatrix} 1 & 0 & 0\\ 0 & 1 & 0\\ 0 & 0 & \pm 1\end{pmatrix}.
  \f]
  The reflection is stored as an integer `sign` corresponding to the bottom right entry.
*/
typedef struct {
    double s[3];
    double c[3];
    int sign;
} ft_ZYZR;

/*!
  \brief A static struct to store a reflection about the plane \f$w\cdot x = 0\f$ in \f$\mathbb{R}^3\f$.
*/
typedef struct {
    double w[3];
} ft_reflection;

ft_ZYZR ft_create_ZYZR(ft_orthogonal_transformation Q);

void ft_apply_ZYZR(ft_ZYZR Q, ft_orthogonal_transformation * U);

void ft_apply_reflection(ft_reflection Q, ft_orthogonal_transformation * U);

void ft_execute_sph_polar_rotation(double * A, const int N, const int M, double s, double c);

void ft_execute_sph_polar_reflection(double * A, const int N, const int M);

typedef struct {
    ft_symmetric_tridiagonal_symmetric_eigen * F11;
    ft_symmetric_tridiagonal_symmetric_eigen * F21;
    ft_symmetric_tridiagonal_symmetric_eigen * F12;
    ft_symmetric_tridiagonal_symmetric_eigen * F22;
    int l;
} ft_partial_sph_isometry_plan;

typedef struct {
    ft_partial_sph_isometry_plan ** F;
    int n;
} ft_sph_isometry_plan;

void ft_destroy_partial_sph_isometry_plan(ft_partial_sph_isometry_plan * F);

void ft_destroy_sph_isometry_plan(ft_sph_isometry_plan * F);

ft_partial_sph_isometry_plan * ft_plan_partial_sph_isometry(const int l);

ft_sph_isometry_plan * ft_plan_sph_isometry(const int n);

void ft_execute_sph_yz_axis_exchange(ft_sph_isometry_plan * J, double * A, const int N, const int M);

void ft_execute_sph_isometry(ft_sph_isometry_plan * J, ft_ZYZR Q, double * A, const int N, const int M);

void ft_execute_sph_rotation(ft_sph_isometry_plan * J, const double alpha, const double beta, const double gamma, double * A, const int N, const int M);

void ft_execute_sph_reflection(ft_sph_isometry_plan * J, ft_reflection W, double * A, const int N, const int M);

void ft_execute_sph_orthogonal_transformation(ft_sph_isometry_plan * J, ft_orthogonal_transformation Q, double * A, const int N, const int M);

#endif // FASTTRANSFORMS_H


#pragma once
#include <stdio.h>
#include <gsl/gsl_math.h>
#include <gsl/gsl_integration.h>

double integrator_qag(gsl_function *, double, double, double, double, size_t, double *,int *);
double integrator_cquad(gsl_function *,double,double,double,double,size_t,double *,int *);
double integrator_qags(gsl_function *,double,double,double,double,size_t,double *,int *);


#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <gbpLib.h>
#include <gbpMath.h>
#include <gbpCosmo_core.h>
#include <gbpCosmo_NFW_etc.h>
#include <gsl/gsl_sf_expint.h>

double R_vir_NFW(double M_vir, double z, int mode, cosmo_info **cosmo) {
    double c_vir;
    double R_vir;
    set_NFW_params(M_vir, z, mode, cosmo, &c_vir, &R_vir);
    return (R_vir);
}


/*
 Copyright (C) 2002 M. Marques, A. Castro, A. Rubio, G. Bertsch

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2, or (at your option)
 any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 02110-1301, USA.

*/

#include <gsl/gsl_complex_math.h>
#include <gsl/gsl_sf_erf.h>
#include <math.h>
#include <stdlib.h>

/* ------------------------------------------------------ */
gsl_complex gsl_complex_step_real (gsl_complex a)
{
  gsl_complex z;

  if (GSL_REAL(a) < 0)
    {
      GSL_SET_COMPLEX (&z, 0, 0);
    }
  else
    {
      GSL_SET_COMPLEX (&z, 1, 0);
    }

  return z;
}

/* ------------------------------------------------------ */
gsl_complex gsl_complex_min_real (gsl_complex a, gsl_complex b)
{
  gsl_complex z;
  double min;

  /* just consider real parts */
  min = GSL_REAL(a) < GSL_REAL(b) ? GSL_REAL(a) : GSL_REAL(b);
  GSL_SET_COMPLEX (&z, min, 0);

  return z;
}

/* ------------------------------------------------------ */
gsl_complex gsl_complex_max_real (gsl_complex a, gsl_complex b)
{
  gsl_complex z;
  double max;

  /* just consider real parts */
  max = GSL_REAL(a) > GSL_REAL(b) ? GSL_REAL(a) : GSL_REAL(b);
  GSL_SET_COMPLEX (&z, max, 0);

  return z;
}

/* ------------------------------------------------------ */
gsl_complex gsl_complex_carg (gsl_complex a)
{
  gsl_complex z;

  GSL_SET_COMPLEX (&z, gsl_complex_arg(a), 0);

  return z;
}

/* ------------------------------------------------------ */
gsl_complex gsl_complex_cabs (gsl_complex a)
{
  gsl_complex z;

  GSL_SET_COMPLEX (&z, gsl_complex_abs(a), 0);

  return z;
}

/* ------------------------------------------------------ */
gsl_complex gsl_complex_cabs2 (gsl_complex a)
{
  gsl_complex z;

  GSL_SET_COMPLEX (&z, gsl_complex_abs2(a), 0);

  return z;
}

/* ------------------------------------------------------ */
gsl_complex gsl_complex_clogabs (gsl_complex a)
{
  gsl_complex z;

  GSL_SET_COMPLEX (&z, gsl_complex_logabs(a), 0);

  return z;
}

/* ------------------------------------------------------ */
gsl_complex gsl_complex_erf (gsl_complex a)
{
  gsl_complex z;

  GSL_SET_COMPLEX (&z, gsl_sf_erf(GSL_REAL(a)), 0);

  return z;
}

/* ------------------------------------------------------ */
gsl_complex gsl_complex_arctan2 (gsl_complex a, gsl_complex b)
{
  gsl_complex z, p;

  if(GSL_REAL(b) != 0.0)
    {
      z = gsl_complex_arctan(gsl_complex_div(a, b));
      if(GSL_REAL(b) < 0.0){
	GSL_SET_COMPLEX (&p, M_PI, 0);
	if(GSL_REAL(a) >= 0.0)
	  z = gsl_complex_add(z, p);
	else
	  z = gsl_complex_sub(z, p);
      }
    }
  else
    {
      if(GSL_REAL(a) >= 0.0)
	{
	  GSL_SET_COMPLEX (&z, M_PI/2.0, 0.0);
	}
      else
	{
	  GSL_SET_COMPLEX (&z, -M_PI/2.0, 0.0);
	}
    }

  return z;
}

/* ------------------------------------------------------ */
gsl_complex gsl_complex_realpart (gsl_complex a)
{
  gsl_complex z;

  GSL_SET_COMPLEX (&z, GSL_REAL(a), 0);

  return z;
}

/* ------------------------------------------------------ */
gsl_complex gsl_complex_imagpart (gsl_complex a)
{
  gsl_complex z;

  GSL_SET_COMPLEX (&z, GSL_IMAG(a), 0);

  return z;
}

/* ------------------------------------------------------ */
gsl_complex gsl_complex_round (gsl_complex a)
{
  gsl_complex z;

  GSL_SET_COMPLEX (&z, trunc(GSL_REAL(a)), 0);

  return z;
}

/* ------------------------------------------------------ */
gsl_complex gsl_complex_ceiling (gsl_complex a)
{
  gsl_complex z;

  GSL_SET_COMPLEX (&z, ceil(GSL_REAL(a)), 0);

  return z;
}

/* ------------------------------------------------------ */
gsl_complex gsl_complex_floor (gsl_complex a)
{
  gsl_complex z;

  GSL_SET_COMPLEX (&z, floor(GSL_REAL(a)), 0);

  return z;
}

/* ------------------------------------------------------ */
void gsl_complex_rand_seed(long a)
{
  srandom(a);
}

/* ------------------------------------------------------ */
gsl_complex gsl_complex_rand (gsl_complex a)
{

  double r = random()/((double) RAND_MAX);
  return gsl_complex_rect(r, 0.0);
}


#include <stdio.h>
#include "sweeny_dc.h"
#include <gsl/gsl_rng.h>
#include <math.h>
#include "../src/dyncon.h"
#include <ctype.h>

#define  MIN(a,b) a<=b ? a: b
#define MAX(a,b) a>=b? a:b
static char verbose = 0;
static char equilibration=1;
static char setup=0;

static __u32 DX;
static __u32 seed;
static __u32 cutoff;
static __u32 steps;
static double beta;
static double coupling;
static double q;
static double rnd_num;

static double v;
static __u32 j=0;
static __s8 dB = 0,dN=0;
static gsl_rng *r;
static Edge *cEdge;
static double rcWeights[4]; // array with precalculated mc weights
static double p_min_del,p_max_del, p_min_ins,p_max_ins;

static __u32 *num_bonds, *num_cluster, *size_giant;
static __u64 *sec_cs_moment,*four_cs_moment;

/******************************************************************************
 *****************************************************************************/
static void extract_observables(__u32 i) {
    static __u32 clust_cnt=0;
    static __u64 sum=0,sum_2=0;
    static __u32 maxc=0;
    static s_tree *c;
    num_bonds[i] = nte + te;
    c = levels[0]->head;
    while(c) {
        clust_cnt++;
        sum += pow(c->root->n,2);//*c->root->n;
        sum_2 += pow(c->root->n,4);
        if(c->root->n > maxc)
            maxc = c->root->n;
        c = c->next;
    }
    num_cluster[i] = clust_cnt;
    size_giant[i] = maxc;
    sec_cs_moment[i] = sum;
    four_cs_moment[i] = sum_2;
    sum=sum_2=0;
    maxc = 0;
    clust_cnt=0;
}

/******************************************************************************
 *****************************************************************************/

static inline void mcStep(void) {
    // get a randomly selected edge
    cEdge = &adjMatrix[gsl_rng_uniform_int(r,2*DX*DX)];

	rnd_num = gsl_rng_uniform(r);
 	if(ACTIVE_EDGE(cEdge)) { //edge is active hence delete it
	  dB = -1;
	  if(rnd_num < p_min_del) {
	    delete_dc(cEdge);
	  }
	  else {
	      if(rnd_num < p_max_del) {
		dN = delete_dc(cEdge);
		if(rnd_num >= rcWeights[dB == -1? dN : 2-dN])
		  insert_dc(cEdge);
	      }

	  }

	}
      else {
        // Insert cedge. If adjacent vertices are already connected, the new
        // edge will be a non-tree edge, i.e. dN = 0
        dB = 1;
        if(rnd_num < p_min_ins) {
	  insert_dc(cEdge);
	}
	else {
	  if(rnd_num < p_max_ins) {
	    dN = connected_dc_e(cEdge,1) ? 0: -1;
	    dB = 1;
	    if(rnd_num < rcWeights[dB == -1? dN : 2-dN])
		insert_dc(cEdge);
	  }

	}
      }

}
/******************************************************************************
 *****************************************************************************/
static inline void sweep() {
    for(j=0;j<2*DX*DX;j++)mcStep();
}
/******************************************************************************
 *****************************************************************************/
char init_sweeny_dc(double _q,unsigned int _l,double _beta,double _coupl,
        unsigned int _cutoff,unsigned _tslength,unsigned int rng_seed,
        void *ts_0,void *ts_1,void * ts_2,void *ts_3, void *ts_4) {

    q = _q;
    DX = _l;
    beta = _beta;
    coupling = _coupl;
    cutoff = _cutoff;
    steps = _tslength;
    seed = rng_seed;
    num_bonds = (__u32 *)ts_0;
    num_cluster = (__u32 *)ts_1;
    size_giant = (__u32 *)ts_2;
    sec_cs_moment = (__u64 *)ts_3;
    four_cs_moment = (__u64 *)ts_4;

    r = gsl_rng_alloc(gsl_rng_mt19937);
    gsl_rng_set(r,seed);
    v = exp(coupling*beta) - 1.; // >0
    rcWeights[0] = pow(v,-1); //db==-1,dN==0
    rcWeights[1] = rcWeights[0]*q; //db==-1,dN=1
    rcWeights[2] = v; //db==1,dN==0
    rcWeights[3] = v*pow(q,-1); //db==1,dN==-1
    p_min_del = MIN(rcWeights[0],rcWeights[1]);
    p_max_del = MAX(rcWeights[0],rcWeights[1]);
    p_min_ins = MIN(rcWeights[2],rcWeights[3]);
    p_max_ins = MAX(rcWeights[2],rcWeights[3]);
    return setup=(r && init_dc(DX));

}

/******************************************************************************
 *****************************************************************************/
void destroy_sweeny_dc(void) {
    if(setup) {
        gsl_rng_free(r);
        destroy_dc();
    }
    setup=0;
}
/******************************************************************************
 *****************************************************************************/
char simulate_sweeny_dc(void) {
    if(!setup) return 0;
    __u32 i;
    for(i=0;i<cutoff;i++)sweep();
    if(verbose)
        printf("Equilibration done!\n");
    equilibration=0;
    for(i=0;i<steps;i++){
        sweep();
        extract_observables(i);
    }
    return 1;
}
/******************************************************************************
 *****************************************************************************/


#pragma once
#include <gsl/gsl_multifit_nlinear.h>
#include <gsl/gsl_blas.h>
#include <gsl/gsl_cdf.h>

#include <vector>
#include <functional>
#include <algorithm>
#include <tuple>

// this "fdf_Helper" helper class is used for create the polynomial to be fit in the runtime.
// Since GSL would like a static function, have to do some trick to make it happy
// see https://stackoverflow.com/questions/13074756/how-to-avoid-static-member-function-when-using-gsl-with-c?noredirect=1&lq=1
// https://stackoverflow.com/questions/6610046/stdfunction-and-stdbind-what-are-they-and-when-should-they-be-used
// https://stackoverflow.com/questions/7582546/using-generic-stdfunction-objects-with-member-functions-in-one-class

class PolynomialFit
{
public:
    struct data
    {
        double* x;
        double* y;
        size_t n;
        std::function<int(const gsl_vector* p, void* datafit, gsl_vector* f)> f = NULL;
        std::function<int(const gsl_vector* p, void* datafit, gsl_matrix* J)> df = NULL;
        std::function<int(const gsl_vector* p, const gsl_vector* v, void* datafit, gsl_vector* fvv)> fvv = NULL;
    };
    class fdf_Helper
    {
    public:
        static int invoke_f(const gsl_vector* p, void* datafit, gsl_vector* f) {
            PolynomialFit::data* dataf = static_cast<PolynomialFit::data*>(datafit);
            return dataf->f(p, datafit, f);
        }
        static int invoke_df(const gsl_vector* p, void* datafit, gsl_matrix* J) {
            PolynomialFit::data* dataf = static_cast<PolynomialFit::data*>(datafit);
            return dataf->df(p, datafit, J);
        }
        static int invoke_fvv(const gsl_vector* p, const gsl_vector* v, void* datafit, gsl_vector* fvv) {
            PolynomialFit::data* dataf = static_cast<PolynomialFit::data*>(datafit);
            return dataf->fvv(p, v, datafit, fvv);
        }
    };

private:

    gsl_vector* p0;     /* initial fitting parameters: a0,...,an, (b0,b1)*/
    gsl_vector* p;      /* fitting parameters */
    gsl_vector* f;      /* residual yi-y(xi) */
    gsl_matrix* covar;  /*covariance matrix, not yet multiplied by sigma, so is not variance-covariance matrix*/

    gsl_multifit_nlinear_fdf         fdf;
    gsl_multifit_nlinear_parameters  fdf_params;
    gsl_multifit_nlinear_workspace*  work;

    data                             fit_data;

    size_t                           max_iter;
    double                           ptol; /* tolerance on fitting parameter p */
    double                           gtol; /* tolerance on gradient */
    double                           ftol;

    int                              info; /* fiting stop reason, for debug*/
    double                           rss;  /* mean of sum of residual square, dof corrected*/
    gsl_vector*                      confid95;

public:

    /* model function: a0 + a1*x + a2*x^2 +...+ b0*sqrt(x-b1) */

    PolynomialFit() {};
    PolynomialFit(size_t n, double* datax, double* datay, unsigned order, bool useSqrt,
        std::vector<double> init_para,
        size_t max_iter = 200, double ptol = 1.0e-8, double gtol = 1.0e-8, double ftol = 1.0e-8);
    double polynomial(std::vector<double> para, double t);
    ~PolynomialFit();
    void solve_system();
    void set_data(double* datay);
    void set_data(size_t n, double* datax, double* datay, bool free = true);
    void set_initialP(std::vector<double> para);

    std::vector<double> calcFittedGaussian();
    std::vector<double> fittedPara() const;
    std::vector<double> confidence95Interval() const;

private:

    bool useSqrt;
    unsigned order;
    unsigned numOfPara;

    void init_dataFunc();

    //static int funcf_Wrapper/*next line is the argument of this "funcf_Wrapper"*/
    //(const gsl_vector* p, void* datafit, gsl_vector* f);

    //static int (*funcf_Wrapper/*next line is the argument of this "funcf_Wrapper"*/
    //(std::function<int(const gsl_vector* p, void* datafit, gsl_vector* f)> func_f))
    //    (const gsl_vector* p, void* datafit, gsl_vector* f);

    int func_f(const gsl_vector* p, void* datafit, gsl_vector* f);
    int func_df(const gsl_vector* p, void* datafit, gsl_matrix* J);
    int func_fvv(const gsl_vector* p, const gsl_vector* v, void* datafit, gsl_vector* fvv);

};


/* multimin/steepest_descent.c
 *
 * Copyright (C) 1996, 1997, 1998, 1999, 2000 Fabrice Rossi
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

/* steepest_descent.c -- the steepest descent algorithm */

#include <config.h>
#include <gsl/gsl_multimin.h>
#include <gsl/gsl_blas_types.h>
#include <gsl/gsl_blas.h>

typedef struct
{
  double step;
  double max_step;
  gsl_vector *x1;
  gsl_vector *g1;
}
steepest_descent_state_t;

static int
steepest_descent_alloc (void *vstate, size_t n)
{
  steepest_descent_state_t *state = (steepest_descent_state_t *) vstate;

  state->x1 = gsl_vector_alloc (n);

  if (state->x1 == NULL)
    {
      GSL_ERROR ("failed to allocate space for x1", GSL_ENOMEM);
    }

  state->g1 = gsl_vector_alloc (n);

  if (state->g1 == NULL)
    {
      gsl_vector_free (state->x1);
      GSL_ERROR ("failed to allocate space for g1", GSL_ENOMEM);
    }

  return GSL_SUCCESS;
}

static int
steepest_descent_set (void *vstate, gsl_multimin_function_fdf * fdf,
                      const gsl_vector * x, double *f,
                      gsl_vector * gradient, double step_size)
{
  steepest_descent_state_t *state = (steepest_descent_state_t *) vstate;

  GSL_MULTIMIN_FN_EVAL_F_DF (fdf, x, f, gradient);

  state->step = step_size;
  state->max_step = step_size;

  return GSL_SUCCESS;
}

static void
steepest_descent_free (void *vstate)
{
  steepest_descent_state_t *state = (steepest_descent_state_t *) vstate;

  gsl_vector_free (state->x1);
  gsl_vector_free (state->g1);
}

static int
steepest_descent_restart (void *vstate)
{
  steepest_descent_state_t *state = (steepest_descent_state_t *) vstate;

  state->step = state->max_step;

  return GSL_SUCCESS;
}

static int
steepest_descent_iterate (void *vstate, gsl_multimin_function_fdf * fdf,
                          gsl_vector * x, double *f,
                          gsl_vector * gradient, gsl_vector * dx)
{
  steepest_descent_state_t *state = (steepest_descent_state_t *) vstate;

  gsl_vector *x1 = state->x1;
  gsl_vector *g1 = state->g1;

  double f0 = *f;
  double f1, fm, f_trial;
  double step0 = 0.0, step1, stepm, step_trial, step = state->step;
  size_t iter = 0;

  /* compute new trial point at x1= x - step * dir, where dir is the
     normalized gradient */

  double gnorm = gsl_blas_dnrm2 (gradient);

  gsl_vector_set_zero (dx);
  gsl_blas_daxpy (-step / gnorm, gradient, dx);

  gsl_vector_memcpy (x1, x);
  gsl_blas_daxpy (1.0, dx, x1);

  /* evaluate function and gradient at new point x1 */

  GSL_MULTIMIN_FN_EVAL_F_DF (fdf, x1, &f1, g1);

  if (f1 < f0)
    {
      state->step = 2.0 * step;

      gsl_vector_memcpy (x, x1);
      gsl_vector_memcpy (gradient, g1);

      *f = f1;

      return GSL_SUCCESS;
    }

  step1 = step;

trial:

  stepm = 0.5 * step1;

  gsl_vector_set_zero (dx);
  gsl_blas_daxpy (-stepm / gnorm, gradient, dx);

  gsl_vector_memcpy (x1, x);
  gsl_blas_daxpy (1.0, dx, x1);

  fm = GSL_MULTIMIN_FN_EVAL_F (fdf, x1);
  printf("trying stepm = %.18e fm=%g\n", stepm, fm);
  if (fm >= f0)
    {
      /* downhill step failed, reduce step-size and try again */
      f1 = fm;
      step1 = stepm;
      goto trial;
    }

  /* We now have a triplet (0,f0) (stepm, fm) (step1, f1) */

minimize:
  iter++;

  if ((stepm - step0) > (step1 - stepm))
    {
      step_trial = stepm - 0.38 * (stepm - step0);
    }
  else
    {
      step_trial = stepm + 0.38 * (step1 - stepm);
    }

  gsl_vector_set_zero (dx);
  gsl_blas_daxpy (-step_trial / gnorm, gradient, dx);

  gsl_vector_memcpy (x1, x);
  gsl_blas_daxpy (1.0, dx, x1);

  f_trial = GSL_MULTIMIN_FN_EVAL_F (fdf, x1);

  if (f_trial > fm)
    {
      if (step_trial < stepm)
        {
          step0 = step_trial;
          f0 = f_trial;
        }
      else
        {
          step1 = step_trial;
          f1 = f_trial;
        }
    }
  else
    {
      if (step_trial < stepm)
        {
          step1 = stepm;
          f1 = fm;
        }
      else
        {
          step0 = stepm;
          f0 = fm;
        }

      stepm = step_trial;
      fm = f_trial;
    }

  printf("f = %.18e\n", fm);

  if (iter > 100)
    {
      gsl_vector_set_zero (dx);
      gsl_blas_daxpy (-stepm / gnorm, gradient, dx);
      gsl_blas_daxpy (1.0, dx, x);

      GSL_MULTIMIN_FN_EVAL_DF (fdf, x, gradient);
      *f = fm;
      state->step = stepm;

      return GSL_SUCCESS;
    }

  goto minimize;
}

static const gsl_multimin_fdfminimizer_type steepest_descent_type =
  { "steepest_descent",         /* name */
  sizeof (steepest_descent_state_t),
  &steepest_descent_alloc,
  &steepest_descent_set,
  &steepest_descent_iterate,
  &steepest_descent_restart,
  &steepest_descent_free
};

const gsl_multimin_fdfminimizer_type
  *gsl_multimin_fdfminimizer_steepest_descent = &steepest_descent_type;


/*
 * C version of Diffusive Nested Sampling (DNest4) by Brendon J. Brewer
 *
 * Yan-Rong Li, liyanrong@mail.ihep.ac.cn
 * Jun 30, 2016
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <math.h>
#include <string.h>
#include <mpi.h>
#include <gsl/gsl_rng.h>

#include "dnest.h"
#include "model2.h"

int which_level_update;
int num_data_points;
int num_params;
DNestFptrSet *fptrset_thismodel2;

DataType *data;
void *best_model_thismodel, *best_model_std_thismodel;

void model2()
{
  int i, argc=0, narg=6;
  char **argv;

  argv = malloc(narg*sizeof(char *));
  for(i=0; i<narg; i++)
  {
    argv[i] = malloc(200*sizeof(char));
  }
  strcpy(argv[argc++], "dnest");
  strcpy(argv[argc++], "-s");
  strcpy(argv[argc++], "restart_dnest2.txt");
  strcpy(argv[argc++], "-l"); //level-dependnet sampling
  strcpy(argv[argc++], "-g"); //tag
  strcpy(argv[argc++], "2");

  /* setup szie of modeltype, which is used for dnest */
  num_params = 3;

  /* setup number of data points and allocate memory */
  num_data_points = 30;
  data = (DataType *)malloc(num_data_points * sizeof(DataType));
  fptrset_thismodel2 = dnest_malloc_fptrset();

  /* setup functions used for dnest*/
  fptrset_thismodel2->from_prior = from_prior_thismodel2;
  fptrset_thismodel2->log_likelihoods_cal = log_likelihoods_cal_thismodel2;
  fptrset_thismodel2->log_likelihoods_cal_initial = log_likelihoods_cal_thismodel2;
  fptrset_thismodel2->log_likelihoods_cal_restart = log_likelihoods_cal_thismodel2;
  fptrset_thismodel2->perturb = perturb_thismodel2;
  fptrset_thismodel2->print_particle = print_particle_thismodel2;
  fptrset_thismodel2->restart_action = restart_action_model2;

  /* load data */
  if(thistask == 0)
  {
    data_load_thismodel2();
  }
  MPI_Bcast(data, num_data_points*sizeof(DataType), MPI_BYTE, 0, MPI_COMM_WORLD);

  /* run dnest */
  dnest(argc, argv, fptrset_thismodel2, num_params, NULL, NULL, NULL, "./", "OPTIONS2", NULL, NULL);

  /* free memory */
  free(data);
  dnest_free_fptrset(fptrset_thismodel2);

  for(i=0; i<narg; i++)
    free(argv[i]);
  free(argv);
}

/*====================================================*/
/* users responsible for following struct definitions */

void from_prior_thismodel2(void *model)
{
  double *params = (double *)model;

  params[0] = 1E3*dnest_randn();
  params[1] = 1E3*dnest_randn();

	// Log-uniform prior
  params[2] = exp(-10. + 20.*dnest_rand());

}

double log_likelihoods_cal_thismodel2(const void *model)
{
  double *params = (double *)model;
  double logL = 0.0;
  double var = params[2] * params[2];

  int i;
  double mu;

	// Conventional gaussian sampling distribution
  for(i=0; i<num_data_points; i++)
  {
    mu = params[0] * data[i].x + params[1];
    logL += -0.5*log(2*M_PI*var) - 0.5*pow(data[i].y - mu, 2.0)/var;
  }

  return logL;
}

double perturb_thismodel2(void *model)
{
  double *params = (double *)model;
  double logH = 0.0, width, limit1, limit2;
  int which = dnest_rand_int(num_params), which_level;
  int size_levels;

  which_level_update = dnest_get_which_level_update();
  size_levels = dnest_get_size_levels();

  which_level = which_level_update > (size_levels - 20)?(size_levels-20):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);
  }

	if(which == 0)
	{
    if(which_level_update == 0)
    {
      width = 1E3;
    }
		logH -= -0.5*pow(params[0]/1E3, 2);
		params[0] += width*dnest_randh();
		logH += -0.5*pow(params[0]/1E3, 2);
	}
	else if(which == 1)
	{
    if(which_level_update == 0)
    {
      width = 1E3;
    }

		logH -= -0.5*pow(params[1]/1E3, 2);
		params[1] += width*dnest_randh();
		logH += -0.5*pow(params[1]/1E3, 2);
	}
	else
	{
    if(which_level_update == 0)
    {
      limit1 = -10.0;
      limit2 = 10.0;
      width = 20.0;
    }
    else
    {
      limit1 = log(limit1);
      limit2 = log(limit2);
      width = limit2 - limit1;
    }

		// Usual log-uniform prior trick
		params[2] = log(params[2]);
		params[2] += width*dnest_randh();
		dnest_wrap(&(params[2]), -10.0, 10.0);
		params[2] = exp(params[2]);
	}

  return logH;
}
/*=======================================================*/

void print_particle_thismodel2(FILE *fp, const void *model)
{
  int i;
  double *params = (double *)model;
  for(i=0; i<num_params; i++)
  {
    fprintf(fp, "%f ", params[i]);
  }
  fprintf(fp, "\n");
}

void data_load_thismodel2()
{
  FILE *fp;
  int i;

  fp = fopen("road.txt", "r");
  if(fp == NULL)
  {
    fprintf(stderr, "ERROR: Cannot open file road.txt.\n");
    exit(0);
  }

  for(i=0; i<num_data_points; i++)
  {
    fscanf(fp, "%lf %lf\n", &data[i].x, &data[i].y);
    //printf("%f %f\n", data[i].x, data[i].y);
  }
  fclose(fp);
}

/*========================================================*/

void restart_action_model2(int iflag)
{
  return;
}