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

#include <gsl/gsl_integration.h>
#include <gsl/gsl_spline.h>
#include <gsl/gsl_errno.h>

#include "ccl.h"
#include "ccl_params.h"
#include "ccl_redshifts.h"

// ---- LSST redshift distributions & current specs -----
// ---- Consider spline for input dN/dz - pending

/*------ ROUTINE: ccl_create_photoz_info ------
INPUT: void * params, (double *) pz_func (double, double, void *)
TASK: create a structure amalgamating the user-input information on the photo-z model.
The structure holds a pointer to the function which returns the probability of getting a certain z_ph (first double)
given a z_spec (second double), and a pointer to the parameters which get passed to that function (other than z_ph and z_sp); */
pz_info* ccl_create_photoz_info(void * params,
					   double (*pz_func)(double, double,void*, int*))
{
  pz_info * this_info = malloc(sizeof(pz_info));
  this_info ->your_pz_params = params;
  this_info -> your_pz_func = pz_func;

  return this_info;
}

/*------ ROUTINE: ccl_photoz -----
INPUT: double z_ph, void *params
TASK:  Returns the value of p(z_photo, z). Change this function to
       change the way true-z and photo-z's are related.
       This has to be in a form that gsl can integrate.
*/
// struct of parameters to pass to photo_z
struct pz_params{
  double z_true; // Gives the true redshift at which to evaluate
  pz_info * pz_information; //Calls the photo-z scatter model
  int *status;
};

static double ccl_photoz(double z_ph, void * params)
{
  struct pz_params * p = (struct pz_params *) params;
  pz_info * user_stuff = (pz_info*) p->pz_information;
  double z_s = p->z_true;

  return (user_stuff->your_pz_func)(z_ph, z_s, user_stuff->your_pz_params,p->status);
}

// Gaussian photo-z function
double gaussian_pz(double z_ph, double z_s, void* params, int *status){
    double sigma_z0 = *((double*) params);

    double sigma_z = sigma_z0 * (1. + z_s);
    return exp(- (z_ph - z_s)*(z_ph - z_s) / (2.*sigma_z*sigma_z)) \
         / (sqrt(2.*M_PI) *sigma_z);
}

/*------ ROUTINE: ccl_specs_create_gaussian_photoz_info ------
INPUT: void * user_pz_params, (double *) user_pz_func (double, double, void *)
TASK: Convenience function for creating a Gaussian photo-z model with error
sigma(z) = sigma_z0 (1 + z). */

pz_info* ccl_create_gaussian_photoz_info(double sigma_z0){

    // Allocate memory so that this value persists
    double* sigma_z0_copy = malloc(sizeof(double));
    *sigma_z0_copy = sigma_z0;

    // Construct pz_info struct
    pz_info * this_info = malloc(sizeof(pz_info));
    this_info->your_pz_params = sigma_z0_copy;
    this_info->your_pz_func = &gaussian_pz;
    return this_info;
}

/* ------ ROUTINE: ccl_free_photoz_info -------
INPUT: pz_info my_photoz_info
TASK: free memory holding the structure containing user-input photoz information */

void ccl_free_photoz_info(pz_info *my_photoz_info)
{
  free(my_photoz_info);
}

/*------ ROUTINE: ccl_create_dNdz_info ------
INPUT: void * params, (double *) dNdz_func (double, void *, int*)
TASK: create a structure amalgamating the information on an analytic true dNdz model.
The structure holds a pointer to the function which returns the analytic dNdz
* and a pointer to the parameters which get passed to that function (other than z); */
dNdz_info* ccl_create_dNdz_info(void * params, double(*dNdz_func)(double,void*,int*))
{
  dNdz_info * this_info = malloc(sizeof(dNdz_info));
  this_info ->your_dN_params = params;
  this_info -> your_dN_func = dNdz_func;

  return this_info;
}

/*------ ROUTINE: dNdz_smail ----------
 * INPUT: z, params (containing: alpha, beta, z0), status
 * OUTPUT: Analytic Smail et al. type dNdz (NOT normalized) */

double dNdz_smail(double z, void* params, int *status){
    double alpha = ((smail_params*) params)->alpha;
    double beta = ((smail_params*) params)->beta;
    double z0 = ((smail_params*) params)->z0;

    return pow(z, alpha) * exp(- pow(z/z0, beta) );
}

/*------ ROUTINE: ccl_create_smail_dNdz_info ------
INPUT: alpha, beta, z0
TASK: Convenience function for creating an analytic dNdz wrt true z
* of the 'smail' form: dNdz ~ z^alpha exp(- (z/z0)^beta) */

dNdz_info* ccl_create_Smail_dNdz_info(double alpha, double beta, double z0){

    // Allocate a smail type parmaeter structure
    smail_params * smail_par = malloc(sizeof(smail_params));
    smail_par->alpha = alpha;
    smail_par->beta = beta;
    smail_par->z0 = z0;

    // Construct dNdz_info struct
    dNdz_info * this_info = malloc(sizeof(dNdz_info));
    this_info->your_dN_params = smail_par;
    this_info->your_dN_func = &dNdz_smail;
    return this_info;
}

/*------ ROUTINE: ccl_dNdz -----
INPUT: double z, void *params
TASK:  Returns the value of dNdz(z). Change this function to
       change the way true-z and photo-z's are related.
       This has to be in a form that gsl can integrate.
*/

static double ccl_dNdz(double z, dNdz_info*  params, int* status)
{

  return (params->your_dN_func)(z, params->your_dN_params, status);
}

/* ------ ROUTINE: ccl_free_dNdz_info -------
INPUT: dNdz_info my_dNdz_info
TASK: free memory holding the structure containing dNdz information */

void ccl_free_dNdz_info(dNdz_info *my_dNdz_info)
{
  free(my_dNdz_info);
}

/*------ ROUTINE: ccl_specs_norm_integrand -----
INPUT: double z_ph, void *params
TASK:  Returns the integrand which is integrated to get the normalization of
       dNdz in a given photometric redshift bin (the denominator from dNdz_sources_tomog).
       This has to be an separate function that gsl can integrate.
*/

// struct of parameters to pass to norm_integrand
struct norm_params {
  double bin_zmin_;
  double bin_zmax_;
  pz_info * pz_information;
  dNdz_info * dN_information;
  int *status;
};

static double ccl_norm_integrand(double z, void* params)
{

  // This is a struct that contains a true redshift and a pointer to the information about the photo_z model
  struct pz_params pz_val_p; // parameters for the photoz pdf wrt true-z

  double pz_int=0; // pointer to the value of the integral over the photoz model
  struct norm_params *p = (struct norm_params *) params; // parameters of the current function (because of form required for gsl integration)

  double z_min = p->bin_zmin_;
  double z_max = p->bin_zmax_;

  // Check whether ccl_splines and ccl_gsl exist; exit gracefully if they
  // can't be loaded
  if(ccl_splines==NULL || ccl_gsl==NULL) ccl_cosmology_read_config();
  if(ccl_splines==NULL || ccl_gsl==NULL) {
    ccl_raise_exception(CCL_ERROR_MISSING_CONFIG_FILE,
                        "ccl_redshift.c: Failed to read config file.");
    return NAN;
  }

  // Set up parameters for the pz part of the intermediary integral.
  pz_val_p.z_true = z;
  pz_val_p.status = p->status;
  pz_val_p.pz_information = p-> pz_information;

  // Do the intermediary integral over the model relating  photo-z to true-z
  gsl_integration_cquad_workspace * workspace = gsl_integration_cquad_workspace_alloc(ccl_gsl->N_ITERATION);
  gsl_function F;
  F.function = ccl_photoz;
  F.params = &pz_val_p;
  int gslstatus = gsl_integration_cquad(&F, z_min, z_max, 0.0,ccl_gsl->INTEGRATION_DNDZ_EPSREL,workspace,&pz_int, NULL, NULL);
  if(gslstatus != GSL_SUCCESS) {
    ccl_raise_gsl_warning(gslstatus, "ccl_redshifts.c: ccl_specs_norm_integrand():");
    *p->status|= gslstatus;
  }
  gsl_integration_cquad_workspace_free(workspace);

  return ccl_dNdz(z, p->dN_information, p->status) * pz_int ;
}

/*------ ROUTINE: ccl_specs_dNdz_tomog -----
INPUT: double z, , double bin_zmin, double bin_zmax, dNdz function pointer, sigma_z function pointer
       tomographic boundaries are [bin_zmin,bin_zmax]
TASK:  dNdz in a particular tomographic bin,
       convolved with a photo-z model (defined by the user), and normalized.
       returns a status integer 0 if called with an allowable type of dNdz, non-zero otherwise
       (this is different from the regular status handling procedure because we don't pass a cosmology to this function)
*/
void ccl_dNdz_tomog(double z, double bin_zmin, double bin_zmax,
              pz_info * photo_info,  dNdz_info * dN_info, double *tomoout, int *status)
{
  // This uses equation 33 of Joachimi & Schneider 2009, arxiv:0905.0393
  double numerator_integrand=0, denom_integrand=0, dNdz_t;
  // This struct contains a spec redshift and a pointer to a photoz information struct.
  struct pz_params pz_p_val; //parameters for the integral over the photoz's
  struct norm_params norm_p_val;

  // Check whether ccl_splines and ccl_gsl exist; exit gracefully if they
  // can't be loaded
  if(ccl_splines==NULL || ccl_gsl==NULL) ccl_cosmology_read_config();
  if(ccl_splines==NULL || ccl_gsl==NULL) {
    ccl_raise_exception(CCL_ERROR_MISSING_CONFIG_FILE,
                        "ccl_redshifts.c: Failed to read config file.");
    *status = CCL_ERROR_MISSING_CONFIG_FILE;
    return;
  }

  // Set up the parameters to pass to the normalising integral (of type struct norm_params
  norm_p_val.bin_zmin_=bin_zmin;
  norm_p_val.bin_zmax_=bin_zmax;
  norm_p_val.pz_information = photo_info;
  norm_p_val.status = status;
  norm_p_val.dN_information = dN_info;

  dNdz_t = ccl_dNdz(z, dN_info, status);

  // Set up the parameters for the integral over the photo z function in the numerator (of type struct pz_params)
  pz_p_val.z_true = z;
  pz_p_val.status = status;
  pz_p_val.pz_information = photo_info; // pointer to user information

  // Integrate over the assumed pdf of photo-z wrt true-z in this bin (this goes in the numerator of the result):
  gsl_integration_cquad_workspace * workspace = gsl_integration_cquad_workspace_alloc(ccl_gsl->N_ITERATION);
  gsl_function F;
  F.function = ccl_photoz;
  F.params = &pz_p_val;
  int gslstatus = gsl_integration_cquad(&F, bin_zmin, bin_zmax, 0.0,ccl_gsl->INTEGRATION_DNDZ_EPSREL,workspace,&numerator_integrand, NULL, NULL);
  if(gslstatus != GSL_SUCCESS) {
    ccl_raise_gsl_warning(gslstatus, "ccl_redshifts.c: ccl_norm_integrand():");
    *status |= gslstatus;
  }
  gsl_integration_cquad_workspace_free(workspace);

  // Now get the denominator, which normalizes dNdz over the photometric bin
  workspace = gsl_integration_cquad_workspace_alloc(ccl_gsl->N_ITERATION);
  F.function = ccl_norm_integrand;
  F.params = &norm_p_val;
  gslstatus = gsl_integration_cquad(&F, Z_MIN_SOURCES, Z_MAX_SOURCES, 0.0,ccl_gsl->INTEGRATION_DNDZ_EPSREL,workspace,&denom_integrand, NULL, NULL);
  if(gslstatus != GSL_SUCCESS) {
    ccl_raise_gsl_warning(gslstatus, "ccl_redshifts.c: ccl_norm_integrand():");
    *status |= gslstatus;
  }

  gsl_integration_cquad_workspace_free(workspace);
  if (*status) {
    *status = CCL_ERROR_INTEG;
    return;
  }
  *tomoout = dNdz_t * numerator_integrand / denom_integrand;

}


/* multifit/gsl_multifit.h
 *
 * Copyright (C) 2000, 2007, 2010 Brian Gough
 * Copyright (C) 2013, Patrick Alken
 *
 * 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.
 */

#ifndef __GSL_MULTIFIT_H__
#define __GSL_MULTIFIT_H__

#include <stdlib.h>
#include <gsl/gsl_math.h>
#include <gsl/gsl_vector.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_types.h>

#undef __BEGIN_DECLS
#undef __END_DECLS
#ifdef __cplusplus
# define __BEGIN_DECLS extern "C" {
# define __END_DECLS }
#else
# define __BEGIN_DECLS /* empty */
# define __END_DECLS /* empty */
#endif

__BEGIN_DECLS

typedef struct
{
  size_t nmax;         /* maximum number of observations */
  size_t pmax;         /* maximum number of parameters */
  size_t n;            /* number of observations in current SVD decomposition */
  size_t p;            /* number of parameters in current SVD decomposition */
  gsl_matrix * A;      /* least squares matrix for SVD, n-by-p */
  gsl_matrix * Q;
  gsl_matrix * QSI;
  gsl_vector * S;
  gsl_vector * t;
  gsl_vector * xt;
  gsl_vector * D;
  double rcond;        /* reciprocal condition number */
}
gsl_multifit_linear_workspace;

gsl_multifit_linear_workspace *
gsl_multifit_linear_alloc (const size_t n, const size_t p);

void
gsl_multifit_linear_free (gsl_multifit_linear_workspace * w);

int
gsl_multifit_linear (const gsl_matrix * X,
                     const gsl_vector * y,
                     gsl_vector * c,
                     gsl_matrix * cov,
                     double * chisq,
                     gsl_multifit_linear_workspace * work);

int
gsl_multifit_linear_tsvd (const gsl_matrix * X,
                          const gsl_vector * y,
                          const double tol,
                          gsl_vector * c,
                          gsl_matrix * cov,
                          double * chisq,
                          size_t * rank,
                          gsl_multifit_linear_workspace * work);

int
gsl_multifit_linear_svd (const gsl_matrix * X,
                         gsl_multifit_linear_workspace * work);

int
gsl_multifit_linear_bsvd (const gsl_matrix * X,
                          gsl_multifit_linear_workspace * work);

size_t
gsl_multifit_linear_rank(const double tol, const gsl_multifit_linear_workspace * work);

int
gsl_multifit_linear_solve (const double lambda,
                           const gsl_matrix * X,
                           const gsl_vector * y,
                           gsl_vector * c,
                           double *rnorm,
                           double *snorm,
                           gsl_multifit_linear_workspace * work);

int
gsl_multifit_linear_applyW(const gsl_matrix * X,
                           const gsl_vector * w,
                           const gsl_vector * y,
                           gsl_matrix * WX,
                           gsl_vector * Wy);

int
gsl_multifit_linear_stdform1 (const gsl_vector * L,
                              const gsl_matrix * X,
                              const gsl_vector * y,
                              gsl_matrix * Xs,
                              gsl_vector * ys,
                              gsl_multifit_linear_workspace * work);

int
gsl_multifit_linear_wstdform1 (const gsl_vector * L,
                               const gsl_matrix * X,
                               const gsl_vector * w,
                               const gsl_vector * y,
                               gsl_matrix * Xs,
                               gsl_vector * ys,
                               gsl_multifit_linear_workspace * work);

int
gsl_multifit_linear_L_decomp (gsl_matrix * L, gsl_vector * tau);

int
gsl_multifit_linear_stdform2 (const gsl_matrix * LQR,
                              const gsl_vector * Ltau,
                              const gsl_matrix * X,
                              const gsl_vector * y,
                              gsl_matrix * Xs,
                              gsl_vector * ys,
                              gsl_matrix * M,
                              gsl_multifit_linear_workspace * work);

int
gsl_multifit_linear_wstdform2 (const gsl_matrix * LQR,
                               const gsl_vector * Ltau,
                               const gsl_matrix * X,
                               const gsl_vector * w,
                               const gsl_vector * y,
                               gsl_matrix * Xs,
                               gsl_vector * ys,
                               gsl_matrix * M,
                               gsl_multifit_linear_workspace * work);

int
gsl_multifit_linear_genform1 (const gsl_vector * L,
                              const gsl_vector * cs,
                              gsl_vector * c,
                              gsl_multifit_linear_workspace * work);

int
gsl_multifit_linear_genform2 (const gsl_matrix * LQR,
                              const gsl_vector * Ltau,
                              const gsl_matrix * X,
                              const gsl_vector * y,
                              const gsl_vector * cs,
                              const gsl_matrix * M,
                              gsl_vector * c,
                              gsl_multifit_linear_workspace * work);

int
gsl_multifit_linear_wgenform2 (const gsl_matrix * LQR,
                               const gsl_vector * Ltau,
                               const gsl_matrix * X,
                               const gsl_vector * w,
                               const gsl_vector * y,
                               const gsl_vector * cs,
                               const gsl_matrix * M,
                               gsl_vector * c,
                               gsl_multifit_linear_workspace * work);

int
gsl_multifit_linear_lreg (const double smin, const double smax,
                          gsl_vector * reg_param);

int
gsl_multifit_linear_lcurve (const gsl_vector * y,
                            gsl_vector * reg_param,
                            gsl_vector * rho, gsl_vector * eta,
                            gsl_multifit_linear_workspace * work);

int
gsl_multifit_linear_lcorner(const gsl_vector *rho,
                            const gsl_vector *eta,
                            size_t *idx);

int
gsl_multifit_linear_lcorner2(const gsl_vector *reg_param,
                             const gsl_vector *eta,
                             size_t *idx);

int
gsl_multifit_linear_Lk(const size_t p, const size_t k, gsl_matrix *L);

int
gsl_multifit_linear_Lsobolev(const size_t p, const size_t kmax,
                             const gsl_vector *alpha, gsl_matrix *L,
                             gsl_multifit_linear_workspace *work);

int
gsl_multifit_wlinear (const gsl_matrix * X,
                      const gsl_vector * w,
                      const gsl_vector * y,
                      gsl_vector * c,
                      gsl_matrix * cov,
                      double * chisq,
                      gsl_multifit_linear_workspace * work);

int
gsl_multifit_wlinear_tsvd (const gsl_matrix * X,
                           const gsl_vector * w,
                           const gsl_vector * y,
                           const double tol,
                           gsl_vector * c,
                           gsl_matrix * cov,
                           double * chisq,
                           size_t * rank,
                           gsl_multifit_linear_workspace * work);

int
gsl_multifit_wlinear_svd (const gsl_matrix * X,
                          const gsl_vector * w,
                          const gsl_vector * y,
                          double tol,
                          size_t * rank,
                          gsl_vector * c,
                          gsl_matrix * cov,
                          double *chisq,
                          gsl_multifit_linear_workspace * work);

int
gsl_multifit_wlinear_usvd (const gsl_matrix * X,
                           const gsl_vector * w,
                           const gsl_vector * y,
                           double tol,
                           size_t * rank,
                           gsl_vector * c,
                           gsl_matrix * cov,
                           double *chisq,
                           gsl_multifit_linear_workspace * work);

int
gsl_multifit_linear_est (const gsl_vector * x,
                         const gsl_vector * c,
                         const gsl_matrix * cov, double *y, double *y_err);

double
gsl_multifit_linear_rcond (const gsl_multifit_linear_workspace * w);

int
gsl_multifit_linear_residuals (const gsl_matrix *X, const gsl_vector *y,
                               const gsl_vector *c, gsl_vector *r);

/* gcv.c */
int
gsl_multifit_linear_gcv_init(const gsl_vector * y,
                             gsl_vector * reg_param,
                             gsl_vector * UTy,
                             double * delta0,
                             gsl_multifit_linear_workspace * work);

int
gsl_multifit_linear_gcv_curve(const gsl_vector * reg_param,
                              const gsl_vector * UTy,
                              const double delta0,
                              gsl_vector * G,
                              gsl_multifit_linear_workspace * work);

int
gsl_multifit_linear_gcv_min(const gsl_vector * reg_param,
                            const gsl_vector * UTy,
                            const gsl_vector * G,
                            const double delta0,
                            double * lambda,
                            gsl_multifit_linear_workspace * work);

double
gsl_multifit_linear_gcv_calc(const double lambda,
                             const gsl_vector * UTy,
                             const double delta0,
                             gsl_multifit_linear_workspace * work);

int
gsl_multifit_linear_gcv(const gsl_vector * y,
                        gsl_vector * reg_param,
                        gsl_vector * G,
                        double * lambda,
                        double * G_lambda,
                        gsl_multifit_linear_workspace * work);

typedef struct
{
  const char * name;     /* method name */
  int (*wfun)(const gsl_vector *r, gsl_vector *w);
  int (*psi_deriv)(const gsl_vector *r, gsl_vector *dpsi);
  double tuning_default; /* default tuning constant */
} gsl_multifit_robust_type;

typedef struct
{
  double sigma_ols;    /* OLS estimate of sigma */
  double sigma_mad;    /* MAD estimate of sigma */
  double sigma_rob;    /* robust estimate of sigma */
  double sigma;        /* final estimate of sigma */
  double Rsq;          /* R^2 coefficient of determination */
  double adj_Rsq;      /* degree of freedom adjusted R^2 */
  double rmse;         /* root mean squared error */
  double sse;          /* residual sum of squares */
  size_t dof;          /* degrees of freedom */
  size_t numit;        /* number of iterations */
  gsl_vector *weights; /* final weights */
  gsl_vector *r;       /* final residuals y - X c */
} gsl_multifit_robust_stats;

typedef struct
{
  size_t n;            /* number of observations */
  size_t p;            /* number of parameters */
  size_t numit;        /* number of iterations */
  size_t maxiter;      /* maximum iterations */
  const gsl_multifit_robust_type *type;
  double tune;         /* tuning parameter */

  gsl_vector *r;       /* residuals at current iteration */
  gsl_vector *weights; /* weights at current iteration */
  gsl_vector *c_prev;  /* coefficients from previous iteration */
  gsl_vector *resfac;  /* multiplicative factors for residuals */

  gsl_vector *psi;     /* psi(r) */
  gsl_vector *dpsi;    /* psi'(r) */

  gsl_matrix *QSI;     /* Q S^{-1} of original matrix X */
  gsl_vector *D;       /* balancing parameters of original matrix X */

  gsl_vector *workn;   /* workspace of length n */

  gsl_multifit_robust_stats stats; /* various statistics */

  gsl_multifit_linear_workspace *multifit_p;
} gsl_multifit_robust_workspace;

/* available types */
GSL_VAR const gsl_multifit_robust_type * gsl_multifit_robust_default;
GSL_VAR const gsl_multifit_robust_type * gsl_multifit_robust_bisquare;
GSL_VAR const gsl_multifit_robust_type * gsl_multifit_robust_cauchy;
GSL_VAR const gsl_multifit_robust_type * gsl_multifit_robust_fair;
GSL_VAR const gsl_multifit_robust_type * gsl_multifit_robust_huber;
GSL_VAR const gsl_multifit_robust_type * gsl_multifit_robust_ols;
GSL_VAR const gsl_multifit_robust_type * gsl_multifit_robust_welsch;

gsl_multifit_robust_workspace *gsl_multifit_robust_alloc(const gsl_multifit_robust_type *T,
                                                         const size_t n, const size_t p);
void gsl_multifit_robust_free(gsl_multifit_robust_workspace *w);
int gsl_multifit_robust_tune(const double tune,
                             gsl_multifit_robust_workspace *w);
int gsl_multifit_robust_maxiter(const size_t maxiter,
                                gsl_multifit_robust_workspace *w);
const char *gsl_multifit_robust_name(const gsl_multifit_robust_workspace *w);
gsl_multifit_robust_stats gsl_multifit_robust_statistics(const gsl_multifit_robust_workspace *w);
int gsl_multifit_robust_weights(const gsl_vector *r, gsl_vector *wts,
                                gsl_multifit_robust_workspace *w);
int gsl_multifit_robust(const gsl_matrix * X, const gsl_vector * y,
                        gsl_vector * c, gsl_matrix *cov,
                        gsl_multifit_robust_workspace *w);
int gsl_multifit_robust_est(const gsl_vector * x, const gsl_vector * c,
                            const gsl_matrix * cov, double *y, double *y_err);
int gsl_multifit_robust_residuals(const gsl_matrix * X,
                                  const gsl_vector * y,
                                  const gsl_vector * c, gsl_vector * r,
                                  gsl_multifit_robust_workspace * w);

__END_DECLS

#endif /* __GSL_MULTIFIT_H__ */


//Roots of polynomial using compan matrix, as in Matlab/Octave.
//This does roots for each vector X.
//The roots are from an eig using LAPACKE,
//and are tested to match Octave, except sometimes for sorting order.

//For the complex case with non-contiguous vecs in X and Y,
//the answers are correct except for a mysterious sign change.

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

#ifdef __cplusplus
namespace codee {
extern "C" {
#endif

int poly2roots_s (float *Y, const float *X, const size_t R, const size_t C, const size_t S, const size_t H, const int iscolmajor, const size_t dim);
int poly2roots_d (double *Y, const double *X, const size_t R, const size_t C, const size_t S, const size_t H, const int iscolmajor, const size_t dim);
int poly2roots_c (float *Y, const float *X, const size_t R, const size_t C, const size_t S, const size_t H, const int iscolmajor, const size_t dim);
int poly2roots_z (double *Y, const double *X, const size_t R, const size_t C, const size_t S, const size_t H, const int iscolmajor, const size_t dim);

int poly2roots_s (float *Y, const float *X, const size_t R, const size_t C, const size_t S, const size_t H, const int iscolmajor, const size_t dim)
{
    if (dim>3u) { fprintf(stderr,"error in poly2roots_s: dim must be in [0 3]\n"); return 1; }

    const size_t N = R*C*S*H;
    const size_t Lx = (dim==0u) ? R : (dim==1u) ? C : (dim==2u) ? S : H;

    if (N==0u || Lx<2u) {}
    else if (Lx==2u)
    {
        if (Lx==N) { *Y = -*(X+1); *++Y = 0.0f; }
        else
        {
            const size_t K = (iscolmajor) ? ((dim==0u) ? 1u : (dim==1u) ? R : (dim==2u) ? R*C : R*C*S) : ((dim==0u) ? C*S*H : (dim==1u) ? S*H : (dim==2u) ? H : 1u);
            const size_t B = (iscolmajor && dim==0u) ? C*S*H : K;
            const size_t V = N/Lx, G = V/B;

            if (K==1u && (G==1u || B==1u))
            {
                for (size_t v=V; v>0u; --v, ++X, ++Y) { ++X; *Y = -*X; *++Y = 0.0f; }
            }
            else
            {
                for (size_t g=G; g>0u; --g, X+=B)
                {
                    for (size_t b=B; b>0u; --b, ++X, ++Y) { *Y = -*(X+K); *++Y = 0.0f; }
                }
            }
        }
    }
    else
    {
        const size_t Ly = Lx - 1u;
        float x0;
        const int job = 'E', compz = 'N';  //eigenvalues only
        const lapack_int ldh = (int)Ly, n = (int)Ly, ldz = 1;
        const lapack_int ilo = 1, ihi = n;  //avoids balancing
        lapack_int info;
        float *compan, *wr, *wi, zz[1];
        if (!(wr=(float *)malloc((size_t)(n)*sizeof(float)))) { fprintf(stderr,"error in poly2roots_s: problem with malloc. "); perror("malloc"); return 1; }
        if (!(wi=(float *)malloc((size_t)(n)*sizeof(float)))) { fprintf(stderr,"error in poly2roots_s: problem with malloc. "); perror("malloc"); return 1; }
        if (!(compan=(float *)calloc((size_t)(n*n),sizeof(float)))) { fprintf(stderr,"error in poly2roots_s: problem with calloc. "); perror("calloc"); return 1; }

        if (Lx==N)
        {
            ++compan;
            for (size_t l=Ly; l>0u; --l, compan+=Lx) { *compan = 1.0f; }
            compan -= Lx*Ly + 1u;
            x0 = -*X++;
            for (size_t l=Ly; l>0u; --l, ++X, compan+=Ly) { *compan = *X / x0; }
            compan -= Ly*Ly;
            info = LAPACKE_shseqr(LAPACK_COL_MAJOR,job,compz,n,ilo,ihi,compan,ldh,wr,wi,zz,ldz);  //eig
            if (info) { fprintf(stderr,"error in poly2roots_s: lapacke decomposition failed\n"); return 1; }
            for (size_t l=Ly; l>0u; --l) { *Y++ = *wr++; *Y++ = *wi++; }
            wr -= Ly; wi -= Ly;
        }
        else
        {
            const size_t K = (iscolmajor) ? ((dim==0u) ? 1u : (dim==1u) ? R : (dim==2u) ? R*C : R*C*S) : ((dim==0u) ? C*S*H : (dim==1u) ? S*H : (dim==2u) ? H : 1u);
            const size_t B = (iscolmajor && dim==0u) ? C*S*H : K;
            const size_t V = N/Lx, G = V/B;

            if (K==1u && (G==1u || B==1u))
            {
                for (size_t v=V; v>0u; --v)
                {
                    for (size_t l=Ly*Ly; l>0u; --l, ++compan) { *compan = 0.0f; }
                    compan -= Lx;
                    for (size_t l=Ly; l>2u; --l, compan-=Lx) { *compan = 1.0f; }
                    *compan-- = 1.0f;
                    x0 = -*X++;
                    for (size_t l=Ly; l>0u; --l, ++X, compan+=Ly) { *compan = *X / x0; }
                    compan -= Ly*Ly;
                    info = LAPACKE_shseqr(LAPACK_COL_MAJOR,job,compz,n,ilo,ihi,compan,ldh,wr,wi,zz,ldz);  //eig
                    if (info) { fprintf(stderr,"error in poly2roots_s: lapacke decomposition failed\n"); return 1; }
                    for (size_t l=Ly; l>0u; --l) { *Y++ = *wr++; *Y++ = *wi++; }
                    wr -= Ly; wi -= Ly;
                }
            }
            else
            {
                for (size_t g=G; g>0u; --g, X+=B*(Lx-1u), Y+=2u*B*(Ly-1u))
                {
                    for (size_t b=B; b>0u; --b, X-=K*Lx-1u, Y-=2u*K*Ly-2u)
                    {
                        for (size_t l=Ly*Ly; l>0u; --l, ++compan) { *compan = 0.0f; }
                        compan -= Lx;
                        for (size_t l=Ly; l>2u; --l, compan-=Lx) { *compan = 1.0f; }
                        *compan-- = 1.0f;
                        x0 = -*X; X += K;
                        for (size_t l=Ly; l>0u; --l, X+=K, compan+=Ly) { *compan = *X / x0; }
                        compan -= Ly*Ly;
                        info = LAPACKE_shseqr(LAPACK_COL_MAJOR,job,compz,n,ilo,ihi,compan,ldh,wr,wi,zz,ldz);  //eig
                        if (info) { fprintf(stderr,"error in poly2roots_s: lapacke decomposition failed\n"); return 1; }
                        for (size_t l=Ly; l>0u; --l, Y+=2u*K-1u) { *Y = *wr++; *++Y = *wi++; }
                        wr -= Ly; wi -= Ly;
                    }
                }
            }
        }

        free(compan); free(wr); free(wi);
    }

    return 0;
}

int poly2roots_d (double *Y, const double *X, const size_t R, const size_t C, const size_t S, const size_t H, const int iscolmajor, const size_t dim)
{
    if (dim>3u) { fprintf(stderr,"error in poly2roots_d: dim must be in [0 3]\n"); return 1; }

    const size_t N = R*C*S*H;
    const size_t Lx = (dim==0u) ? R : (dim==1u) ? C : (dim==2u) ? S : H;

    if (N==0u || Lx<2u) {}
    else if (Lx==2u)
    {
        if (Lx==N) { *Y = -*(X+1); *++Y = 0.0; }
        else
        {
            const size_t K = (iscolmajor) ? ((dim==0u) ? 1u : (dim==1u) ? R : (dim==2u) ? R*C : R*C*S) : ((dim==0u) ? C*S*H : (dim==1u) ? S*H : (dim==2u) ? H : 1u);
            const size_t B = (iscolmajor && dim==0u) ? C*S*H : K;
            const size_t V = N/Lx, G = V/B;

            if (K==1u && (G==1u || B==1u))
            {
                for (size_t v=V; v>0u; --v, ++X, ++Y) { ++X; *Y = -*X; *++Y = 0.0; }
            }
            else
            {
                for (size_t g=G; g>0u; --g, X+=B)
                {
                    for (size_t b=B; b>0u; --b, ++X, ++Y) { *Y = -*(X+K); *++Y = 0.0; }
                }
            }
        }
    }
    else
    {
        const size_t Ly = Lx - 1u;
        double x0;

        const int job = 'E', compz = 'N';  //eigenvalues only
        const lapack_int ldh = (int)Ly, n = (int)Ly, ldz = 1;
        const lapack_int ilo = 1, ihi = n;  //avoids balancing
        lapack_int info;
        double *compan, *wr, *wi, zz[1];
        if (!(wr=(double *)malloc((size_t)(n)*sizeof(double)))) { fprintf(stderr,"error in poly2roots_d: problem with malloc. "); perror("malloc"); return 1; }
        if (!(wi=(double *)malloc((size_t)(n)*sizeof(double)))) { fprintf(stderr,"error in poly2roots_d: problem with malloc. "); perror("malloc"); return 1; }
        if (!(compan=(double *)calloc((size_t)(n*n),sizeof(double)))) { fprintf(stderr,"error in poly2roots_d: problem with calloc. "); perror("calloc"); return 1; }

        if (Lx==N)
        {
            ++compan;
            for (size_t l=Ly; l>0u; --l, compan+=Lx) { *compan = 1.0; }
            compan -= Lx*Ly + 1u;
            x0 = -*X++;
            for (size_t l=Ly; l>0u; --l, ++X, compan+=Ly) { *compan = *X / x0; }
            compan -= Ly*Ly;
            info = LAPACKE_dhseqr(LAPACK_COL_MAJOR,job,compz,n,ilo,ihi,compan,ldh,wr,wi,zz,ldz);  //eig
            if (info) { fprintf(stderr,"error in poly2roots_d: lapacke decomposition failed\n"); return 1; }
            for (size_t l=Ly; l>0u; --l) { *Y++ = *wr++; *Y++ = *wi++; }
            wr -= Ly; wi -= Ly;
        }
        else
        {
            const size_t K = (iscolmajor) ? ((dim==0u) ? 1u : (dim==1u) ? R : (dim==2u) ? R*C : R*C*S) : ((dim==0u) ? C*S*H : (dim==1u) ? S*H : (dim==2u) ? H : 1u);
            const size_t B = (iscolmajor && dim==0u) ? C*S*H : K;
            const size_t V = N/Lx, G = V/B;

            if (K==1u && (G==1u || B==1u))
            {
                for (size_t v=V; v>0u; --v)
                {
                    for (size_t l=Ly*Ly; l>0u; --l, ++compan) { *compan = 0.0; }
                    compan -= Lx;
                    for (size_t l=Ly; l>2u; --l, compan-=Lx) { *compan = 1.0; }
                    *compan-- = 1.0;
                    x0 = -*X++;
                    for (size_t l=Ly; l>0u; --l, ++X, compan+=Ly) { *compan = *X / x0; }
                    compan -= Ly*Ly;
                    info = LAPACKE_dhseqr(LAPACK_COL_MAJOR,job,compz,n,ilo,ihi,compan,ldh,wr,wi,zz,ldz);  //eig
                    if (info) { fprintf(stderr,"error in poly2roots_d: lapacke decomposition failed\n"); return 1; }
                    for (size_t l=Ly; l>0u; --l) { *Y++ = *wr++; *Y++ = *wi++; }
                    wr -= Ly; wi -= Ly;
                }
            }
            else
            {
                for (size_t g=G; g>0u; --g, X+=B*(Lx-1u), Y+=2u*B*(Ly-1u))
                {
                    for (size_t b=B; b>0u; --b, X-=K*Lx-1u, Y-=2u*K*Ly-2u)
                    {
                        for (size_t l=Ly*Ly; l>0u; --l, ++compan) { *compan = 0.0; }
                        compan -= Lx;
                        for (size_t l=Ly; l>2u; --l, compan-=Lx) { *compan = 1.0; }
                        *compan-- = 1.0;
                        x0 = -*X; X += K;
                        for (size_t l=Ly; l>0u; --l, X+=K, compan+=Ly) { *compan = *X / x0; }
                        compan -= Ly*Ly;
                        info = LAPACKE_dhseqr(LAPACK_COL_MAJOR,job,compz,n,ilo,ihi,compan,ldh,wr,wi,zz,ldz);  //eig
                        if (info) { fprintf(stderr,"error in poly2roots_d: lapacke decomposition failed\n"); return 1; }
                        for (size_t l=Ly; l>0u; --l, Y+=2u*K-1u) { *Y = *wr++; *++Y = *wi++; }
                        wr -= Ly; wi -= Ly;
                    }
                }
            }
        }

        free(compan); free(wr); free(wi);
    }

    return 0;
}

int poly2roots_c (float *Y, const float *X, const size_t R, const size_t C, const size_t S, const size_t H, const int iscolmajor, const size_t dim)
{
    if (dim>3u) { fprintf(stderr,"error in poly2roots_c: dim must be in [0 3]\n"); return 1; }

    const size_t N = R*C*S*H;
    const size_t Lx = (dim==0u) ? R : (dim==1u) ? C : (dim==2u) ? S : H;

    if (N==0u || Lx<2u) {}
    else
    {
        const size_t Ly = Lx - 1u;
        const int job = 'E', compz = 'N';  //eigenvalues only
        const lapack_int ldh = (int)Ly, n = (int)Ly, ldz = 1;
        const lapack_int ilo = 1, ihi = n;  //avoids balancing
        lapack_int info;
        float *compan, zz[2], scr, sci;
        if (!(compan=(float *)calloc((size_t)(4*n*n),sizeof(float)))) { fprintf(stderr,"error in poly2roots_c: problem with calloc. "); perror("calloc"); return 1; }

        if (Lx==N)
        {
            compan += 2;
            for (size_t l=Ly; l>0u; --l, compan+=2u*Lx) { *compan = 1.0f; }
            compan -= 2u*Lx*Ly + 2u;
            scr = 1.0f/(*X**X+*(X+1)**(X+1)); sci = *(X+1)*scr; scr *= -*X;
            X += 2;
            for (size_t l=Ly; l>0u; --l, X+=2, compan+=2u*Ly-1u)
            {
                *compan = *X*scr - *(X+1)*sci;
                *++compan = *X*sci + *(X+1)*scr;
            }
            compan -= 2u*Ly*Ly;
            info = LAPACKE_chseqr(LAPACK_COL_MAJOR,job,compz,n,ilo,ihi,(lapack_complex_float *)compan,ldh,(lapack_complex_float *)Y,(lapack_complex_float *)zz,ldz);
            if (info) { fprintf(stderr,"error in poly2roots_c: lapacke decomposition failed\n"); return 1; }
        }
        else
        {
            const size_t K = (iscolmajor) ? ((dim==0u) ? 1u : (dim==1u) ? R : (dim==2u) ? R*C : R*C*S) : ((dim==0u) ? C*S*H : (dim==1u) ? S*H : (dim==2u) ? H : 1u);
            const size_t B = (iscolmajor && dim==0u) ? C*S*H : K;
            const size_t V = N/Lx, G = V/B;

            if (K==1u && (G==1u || B==1u))
            {
                for (size_t v=V; v>0u; --v, Y+=2u*Ly)
                {
                    if (Ly>1u)
                    {
                        for (size_t l=0u; l<2u*Ly*Ly; ++l, ++compan) { *compan = 0.0f; }
                        compan -= 2u*Lx;
                        for (size_t l=Ly; l>2u; --l, compan-=2u*Lx) { *compan = 1.0f; }
                        *compan = 1.0f; compan -= 2;
                    }
                    scr = 1.0f/(*X**X+*(X+1)**(X+1)); sci = *(X+1)*scr; scr *= -*X;
                    X += 2;
                    for (size_t l=Ly; l>0u; --l, X+=2, compan+=2u*Ly-1u)
                    {
                        *compan = *X*scr - *(X+1)*sci;
                        *++compan = *X*sci + *(X+1)*scr;
                    }
                    compan -= 2u*Ly*Ly;
                    info = LAPACKE_chseqr(LAPACK_COL_MAJOR,job,compz,n,ilo,ihi,(lapack_complex_float *)compan,ldh,(lapack_complex_float *)Y,(lapack_complex_float *)zz,ldz);
                    if (info) { fprintf(stderr,"error in poly2roots_c: lapacke decomposition failed\n"); return 1; }
                }
            }
            else
            {
                float *roots;
                if (!(roots=(float *)malloc((size_t)(2*n)*sizeof(float)))) { fprintf(stderr,"error in poly2roots_c: problem with malloc. "); perror("malloc"); return 1; }

                for (size_t g=G; g>0u; --g, X+=2u*B*(Lx-1u), Y+=2u*B*(Ly-1u))
                {
                    for (size_t b=B; b>0u; --b, X-=2u*K*Lx-2u, Y-=2u*K*Ly-2u)
                    {
                        if (Ly>1u)
                        {
                            for (size_t l=0u; l<2u*Ly*Ly; ++l, ++compan) { *compan = 0.0f; }
                            compan -= 2u*Lx;
                            for (size_t l=Ly; l>2u; --l, compan-=2u*Lx) { *compan = 1.0f; }
                            *compan = 1.0f; compan -= 2;
                        }
                        scr = 1.0f/(*X**X+*(X+1)**(X+1)); sci = *(X+1)*scr; scr *= -*X;
                        X += 2u*K;
                        for (size_t l=Ly; l>0u; --l, X+=2u*K, compan+=2u*Ly-1u)
                        {
                            *compan = *X*scr - *(X+1)*sci;
                            *++compan = *X*sci + *(X+1)*scr;
                        }
                        compan -= 2u*Ly*Ly;
                        info = LAPACKE_chseqr(LAPACK_COL_MAJOR,job,compz,n,ilo,ihi,(lapack_complex_float *)compan,ldh,(lapack_complex_float *)roots,(lapack_complex_float *)zz,ldz);
                        if (info) { fprintf(stderr,"error in poly2roots_c: lapacke decomposition failed\n"); return 1; }
                        for (size_t l=Ly; l>0u; --l, ++roots, Y+=2u*K-1u) { *Y++ = *roots++; *Y = *roots; }
                        roots -= 2u*Ly;
                    }
                }
                free(roots);
            }
        }
        free(compan);
    }

    return 0;
}

int poly2roots_z (double *Y, const double *X, const size_t R, const size_t C, const size_t S, const size_t H, const int iscolmajor, const size_t dim)
{
    if (dim>3u) { fprintf(stderr,"error in poly2roots_z: dim must be in [0 3]\n"); return 1; }

    const size_t N = R*C*S*H;
    const size_t Lx = (dim==0u) ? R : (dim==1u) ? C : (dim==2u) ? S : H;

    if (N==0u || Lx<2u) {}
    else
    {
        const size_t Ly = Lx - 1u;
        const int job = 'E', compz = 'N';  //eigenvalues only
        const lapack_int ldh = (int)Ly, n = (int)Ly, ldz = 1;
        const lapack_int ilo = 1, ihi = n;  //avoids balancing
        lapack_int info;
        double *compan, zz[2], scr, sci;
        if (!(compan=(double *)calloc((size_t)(4*n*n),sizeof(double)))) { fprintf(stderr,"error in poly2roots_z: problem with calloc. "); perror("calloc"); return 1; }

        if (Lx==N)
        {
            compan += 2;
            for (size_t l=Ly; l>0u; --l, compan+=2u*Lx) { *compan = 1.0; }
            compan -= 2u*Lx*Ly + 2u;
            scr = 1.0/(*X**X+*(X+1)**(X+1)); sci = *(X+1)*scr; scr *= -*X;
            X += 2;
            for (size_t l=Ly; l>0u; --l, X+=2, compan+=2u*Ly-1u)
            {
                *compan = *X*scr - *(X+1)*sci;
                *++compan = *X*sci + *(X+1)*scr;
            }
            compan -= 2u*Ly*Ly;
            info = LAPACKE_zhseqr(LAPACK_COL_MAJOR,job,compz,n,ilo,ihi,(lapack_complex_double *)compan,ldh,(lapack_complex_double *)Y,(lapack_complex_double *)zz,ldz);
            if (info) { fprintf(stderr,"error in poly2roots_z: lapacke decomposition failed\n"); return 1; }
        }
        else
        {
            const size_t K = (iscolmajor) ? ((dim==0u) ? 1u : (dim==1u) ? R : (dim==2u) ? R*C : R*C*S) : ((dim==0u) ? C*S*H : (dim==1u) ? S*H : (dim==2u) ? H : 1u);
            const size_t B = (iscolmajor && dim==0u) ? C*S*H : K;
            const size_t V = N/Lx, G = V/B;

            if (K==1u && (G==1u || B==1u))
            {
                for (size_t v=V; v>0u; --v, Y+=2u*Ly)
                {
                    if (Ly>1u)
                    {
                        for (size_t l=0u; l<2u*Ly*Ly; ++l, ++compan) { *compan = 0.0; }
                        compan -= 2u*Lx;
                        for (size_t l=Ly; l>2u; --l, compan-=2u*Lx) { *compan = 1.0; }
                        *compan = 1.0; compan -= 2;
                    }
                    scr = 1.0/(*X**X+*(X+1)**(X+1)); sci = *(X+1)*scr; scr *= -*X;
                    X += 2;
                    for (size_t l=Ly; l>0u; --l, X+=2, compan+=2u*Ly-1u)
                    {
                        *compan = *X*scr - *(X+1)*sci;
                        *++compan = *X*sci + *(X+1)*scr;
                    }
                    compan -= 2u*Ly*Ly;
                    info = LAPACKE_zhseqr(LAPACK_COL_MAJOR,job,compz,n,ilo,ihi,(lapack_complex_double *)compan,ldh,(lapack_complex_double *)Y,(lapack_complex_double *)zz,ldz);
                    if (info) { fprintf(stderr,"error in poly2roots_z: lapacke decomposition failed\n"); return 1; }
                }
            }
            else
            {
                double *roots;
                if (!(roots=(double *)malloc((size_t)(2*n)*sizeof(double)))) { fprintf(stderr,"error in poly2roots_z: problem with malloc. "); perror("malloc"); return 1; }

                for (size_t g=G; g>0u; --g, X+=2u*B*(Lx-1u), Y+=2u*B*(Ly-1u))
                {
                    for (size_t b=B; b>0u; --b, X-=2u*K*Lx-2u, Y-=2u*K*Ly-2u)
                    {
                        if (Ly>1u)
                        {
                            for (size_t l=0u; l<2u*Ly*Ly; ++l, ++compan) { *compan = 0.0; }
                            compan -= 2u*Lx;
                            for (size_t l=Ly; l>2u; --l, compan-=2u*Lx) { *compan = 1.0; }
                            *compan = 1.0; compan -= 2;
                        }
                        scr = 1.0/(*X**X+*(X+1)**(X+1)); sci = *(X+1)*scr; scr *= -*X;
                        X += 2u*K;
                        for (size_t l=Ly; l>0u; --l, X+=2u*K, compan+=2u*Ly-1u)
                        {
                            *compan = *X*scr - *(X+1)*sci;
                            *++compan = *X*sci + *(X+1)*scr;
                        }
                        compan -= 2u*Ly*Ly;
                        info = LAPACKE_zhseqr(LAPACK_COL_MAJOR,job,compz,n,ilo,ihi,(lapack_complex_double *)compan,ldh,(lapack_complex_double *)roots,(lapack_complex_double *)zz,ldz);
                        if (info) { fprintf(stderr,"error in poly2roots_z: lapacke decomposition failed\n"); return 1; }
                        for (size_t l=Ly; l>0u; --l, ++roots, Y+=2u*K-1u) { *Y++ = *roots++; *Y = *roots; }
                        roots -= 2u*Ly;
                    }
                }
                free(roots);
            }
        }
        free(compan);
    }

    return 0;
}

#ifdef __cplusplus
}
}
#endif


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <math.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_blas.h>
#include <gsl/gsl_rng.h>
#include <gsl/gsl_randist.h>

void read_matrix(int** index, int** matrix, double scaling, int N_kw, char* input_fileName)
{
    FILE *fp = fopen(input_fileName, "r");
    fscanf(fp, "%*[^\n]\n");

    for (int ii = 0; ii < N_kw; ii++)
        fscanf(fp, "%d,", &((*index)[ii]));
    fscanf(fp, "%*[^\n]\n");

    int tmp;
    for (int ii = 0; ii < N_kw; ii++)
    {
        for (int jj = 0; jj < N_kw; jj++)
        {
            fscanf(fp, "%d,", &tmp);
            (*matrix)[ii*N_kw + jj] = (int) (tmp * scaling);
        }
        fscanf(fp, "%*[^\n]\n");
    }
    fclose(fp);
}

void pad_matrix(int** matrix_padded, int** matrix, double lambda, int N_kw, int N_doc)
{
    // Initialising RNG
    const gsl_rng_type * T;
    gsl_rng *r1, *r2;
    gsl_rng_env_setup();
    r1 = gsl_rng_alloc(gsl_rng_default);
    r2 = gsl_rng_alloc (gsl_rng_taus);

    // perform padding
    int ii, jj;

    int max_resp_len = 0;
    for (ii = 0; ii < N_kw; ii++)
        if ((*matrix)[ii*N_kw + ii] > max_resp_len)
            max_resp_len = (*matrix)[ii*N_kw + ii];

    #pragma omp parallel for private(ii)
    for (ii = 0; ii < N_kw; ii++)
        (*matrix_padded)[ii*N_kw + ii] = (int) (lambda * (max_resp_len)) + gsl_rng_uniform_int(r2, max_resp_len+1);

    #pragma omp parallel for private(ii, jj)
    for (ii = 0; ii < N_kw; ii++)
    {
        for (jj = 0; jj < N_kw; jj++)
        {
            if (ii > jj)
            {
                int n1, n2, n3;
                n3 = 0;
                if ((*matrix_padded)[ii*N_kw + ii] > (*matrix)[ii*N_kw + ii])
                {
                    n1 = (*matrix)[ii*N_kw + jj];
                    n3 += gsl_ran_hypergeometric(r1, (*matrix_padded)[ii*N_kw + ii] - (*matrix)[ii*N_kw + ii], N_doc, (*matrix_padded)[jj*N_kw + jj]);
                }
                else
                    n1 = gsl_ran_hypergeometric(r1, (*matrix)[ii*N_kw + jj], (*matrix)[ii*N_kw + ii] - (*matrix)[ii*N_kw + jj], (*matrix_padded)[ii*N_kw + ii] - (*matrix)[ii*N_kw + jj]);

                if ((*matrix_padded)[jj*N_kw + jj] > (*matrix)[jj*N_kw + jj])
                {
                    n2 = n1;
                    n3 += gsl_ran_hypergeometric(r1, (*matrix_padded)[jj*N_kw + jj] - (*matrix)[jj*N_kw + jj], N_doc, (*matrix_padded)[ii*N_kw + ii] - (*matrix)[ii*N_kw + jj]);
                }
                else
                    n2 = gsl_ran_hypergeometric(r1, n1, (*matrix)[jj*N_kw + jj] - n1, (*matrix_padded)[jj*N_kw + jj]);

                (*matrix_padded)[ii*N_kw + jj] = n2 + n3;
                (*matrix_padded)[jj*N_kw + ii] = n2 + n3;
            }
        }
    }
    gsl_rng_free(r1);
    gsl_rng_free(r2);
}

void observe_matrix(gsl_matrix* matrix_obs, int** matrix_padded, int N_kw)
{
    // perform observed count generation
    for (int ii = 0; ii < N_kw; ii++)
        for (int jj = 0; jj < N_kw; jj++)
            gsl_matrix_set(matrix_obs, ii, jj, (double) ((*matrix_padded)[ii*N_kw + jj]));
}

void permutation_generation(int* idx1, int* idx2, int** permutation_tmp, int** permutation, int** permutation_inv, int N_kw, int N_obs)
{
    int count = 0;
    *idx1 = rand() % N_obs;
    *idx2 = -1;
    int idx_old = (*permutation)[*idx1];
    int idx_new = rand() % N_kw;

    (*permutation_tmp)[*idx1] = idx_new;
    if ((*permutation_inv)[idx_new] >= 0)
    {
        *idx2 = (*permutation_inv)[idx_new];
        (*permutation_tmp)[*idx2] = idx_old;
    }
}


#ifndef SPECTRALFUNCTIONS
#define SPECTRALFUNCTIONS

#include <string>
#include <iostream>
#include <vector>
#include <gsl/gsl_complex.h>
#include <tuple>

#include "Constants.h"
#include "NuclearUtilities.h"

namespace bsg {

/**
 * Namespace containing all correction factors to the allowed beta spectrum shape
 * as described by Hayen et al., Rev. Mod. Phys. 90 (2018) 015008
 */
namespace SpectralFunctions {
/// type of beta decay, negative or positive
enum BetaType { BETA_PLUS = -1, BETA_MINUS = 1 };
enum DecayType { FERMI, GAMOW_TELLER, MIXED };

// the different corrections
double PhaseSpace(double W, double W0, int motherSpinParity,
                  int daughterSpinParity);
/**
 * @brief Fermi function
 * @param W total energy in units of electron mass
 * @param Z the proton number of the daughter
 * @param R the nuclear radius of the daughter
 * @param betaType the BetaType of the transition
 * @return the value
 *
 * The point charge Fermi function @f$ F_0(Z, W) @f$
 * \f[ F_0(Z,W) = 2(\gamma+1) \Gamma(2\gamma+1)^{-2} (2pR)^{2(\gamma-1)}
 *e^{\pi\alpha Z W /p} |\Gamma(\gamma + i \alpha Z W /p) |^2\f]
 * the last factor \f$ |\Gamma(\gamma + i \alpha Z W /p) |^2 \f$ is calculated
 *using GSL. The GSL function returns the magnitude and phase, and here, as we
 *are interested in the absolute value of the result we just use the
 *magnitude.
 * The calculation of the 2nd factor \f$ \Gamma(2\gamma+1)^{-2} \f$ and the
 *last factor \f$ |\Gamma(\gamma + i \alpha Z W /p) |^2 \f$ is combined, i.e.
 * \f[ \ln\frac{a^2}{b^2} = 2\ln\frac{a}{b} = 2(\ln a - \ln b) \f]
 *
 */
double FermiFunction(double W, int Z, double R, int betaType);

/**
 * @brief C correction
 * @param W total energy in units of electron mass
 * @param W0 the total endpoint energy in units of the electron rest mass
 * @param Z proton number
 * @param A mass number
 * @param R nuclear radius in natural units
 * @param betaType the BetaType of the transition
 * @param decayType the DecayType of the transition
 * @param gA the axial vector coupling constant
 * @param gP the induced pseudoscalar coupling constant
 * @param fc1 the c1 form factor as per Holstein (@f$ g_A M_{GT} @f$)
 * @param fb the b form factor as per Holstein
 * @param fd the d form factor as per Holstein
 * @param ratioM121 the ratio of the @f$^AM_{121}@f$ and @f$ ^AM_{101}@f$ matrix elements in the Behrens-Buehring formalism
 * @param addCI boolean to decide whether or not to include the isovector correction to the shape part of C
 * @param NSShape string, says which charge distribution to use in the C correction
 * @param hoFit the fitted A value for the Modified Gaussian distribution
 * @param spsi NuclearStructure::SingleParticleState object denoting the initial nucleon state
 * @param spsf NuclearStructure::SingleParticleState object denoting the final nucleon state
 *
 * The C correction describing effects of finite nuclear size and induced currents when the connection
   to the NME library has been made and actual single-particle wave functions will be used in C_I
 */
double CCorrection(double W, double W0, int Z, int A, double R, int betaType,
                   int decayType, double gA, double gP, double fc1, double fb,
                   double fd, double ratioM121, bool addCI, std::string NSShape,
                   double hoFit, nme::NuclearStructure::SingleParticleState& spsi, nme::NuclearStructure::SingleParticleState& spsf);

/**
 * @brief C correction
 * @param W total energy in units of electron mass
 * @param W0 the total endpoint energy in units of the electron rest mass
 * @param Z proton number
 * @param A mass number
 * @param R nuclear radius in natural units
 * @param betaType the BetaType of the transition
 * @param decayType the DecayType of the transition
 * @param gA the axial vector coupling constant
 * @param gP the induced pseudoscalar coupling constant
 * @param fc1 the c1 form factor as per Holstein (@f$ g_A M_{GT} @f$)
 * @param fb the b form factor as per Holstein
 * @param fd the d form factor as per Holstein
 * @param ratioM121 the ratio of the @f$^AM_{121}@f$ and @f$ ^AM_{101}@f$ matrix elements in the Behrens-Buehring formalism
 * @param addCI boolean to decide whether or not to include the isovector correction to the shape part of C
 * @param NSShape string, says which charge distribution to use in the C correction
 * @param hoFit the fitted A value for the Modified Gaussian distribution
 *
 * The C correction describing effects of finite nuclear size and induced currents.
 */
double CCorrection(double W, double W0, int Z, int A, double R, int betaType,
                   int decayType, double gA, double gP, double fc1, double fb,
                   double fd, double ratioM121, bool addCI, std::string NSShape,
                   double hoFit);
/**
 * @brief C correction
 * @param W total energy in units of electron mass
 * @param W0 the total endpoint energy in units of the electron rest mass
 * @param Z proton number
 * @param A mass number
 * @param R nuclear radius in natural units
 * @param betaType the BetaType of the transition
 * @param hoFit the fitted A value for the Modified Gaussian distribution
 * @param decayType the DecayType of the transition
 * @param gA the axial vector coupling constant
 * @param gP the induced pseudoscalar coupling constant
 * @param fc1 the c1 form factor as per Holstein (@f$ g_A M_{GT} @f$)
 * @param fb the b form factor as per Holstein
 * @param fd the d form factor as per Holstein
 * @param ratioM121 the ratio of the @f$^AM_{121}@f$ and @f$ ^AM_{101}@f$ matrix elements in the Behrens-Buehring formalism
 * @param NSShape string, says which charge distribution to use in the C correction
 * @param hoFit the fitted A value for the Modified Gaussian distribution
 * @return the value
 *
 * The C correction describing effects of finite nuclear size and induced currents.
   Return the shape and nuclear-sensitive parts separately in a tuple.
 */
std::tuple<double, double> CCorrectionComponents(double W, double W0, int Z, int A, double R, int betaType,
                   int decayType, double gA, double gP, double fc1, double fb, double fd, double ratioM121,
                   std::string NSShape, double hoFit);

/**
 * Isovector correction to the charge density-calculated C correction
 *
 * @param W electron total energy in units of its rest mass
 * @param W0 total endpoint energy in units of the electron rest mass
 * @param Z proton number
 * @param A mass number
 * @param R nuclear radius in natural units
 * @param betaType BetaType of the transition
 * @param decayType DecayType of the transition
 */
double CICorrection(double W, double W0, int Z, int A, double R, int betaType);

/**
 * Isovector correction to the charge density-calculated C correction
 * Gets called when connection with NME is turned on, thereby using actual
 * single particle wave functions from a (deformed) Woods-Saxon potential
 *
 * @param W electron total energy in units of its rest mass
 * @param W0 total endpoint energy i units of the electron rest mass
 * @param Z proton number
 * @param R nuclear radius in natural units
 * @betaType beta type of the transition
 * @param spsi NuclearStructure::SingleParticleState object denoting the initial state
 * @param spsf NuclearStructure::SingleParticleState object denoting the final state
 */
double CICorrection(double W, double W0, double Z, double R, int betaType,
    nme::NuclearStructure::SingleParticleState& spsi, nme::NuclearStructure::SingleParticleState& spsf);

/**
 * Relativistic matrix element correction to the vector pahe "black hole girl" right now, and how she's being given too much credit for her role in the historic first image of a black hole. Because this is too important, I want to set the record straight.

Once Katie Bouman became the "face" of the black hole photo, and articles began to call her "the woman behind the black hole photo", an assortment of people that I'm strongly inclined to callrt of the C correction
 * as per Wilkinson.
 *
 * @param W electron total energy in units of its rest mass
 * @param W0 total endpoint energy in units of the electron rest mass
 * @param Z proton number
 * @param A mass number
 * @param R nuclear radius in natural units
 * @param betaType BetaType of the transition
 * @param decayType DecayType of the transition
 */
double RelativisticCorrection(double W, double W0, int Z, int A, double R,
                              int betaType, int decayType);

/**
 * Deformation correction to L0
 *
 * @param W electron total energy in units of its rest mass
 * @param W0 total endpoint energy in units of the electron rest mass
 * @param Z proton number
 * @param A mass number
 * @param R nuclear radius in natural units
 * @param beta2 quadrupole deformation
 * @param betaType BetaType of the transition
 * @param aPos array of fitted constants for beta+ decay
 * @param aNeg array of fitted constants for beta- decay
 */
double DeformationCorrection(double W, double W0, int Z, double R, double beta2,
                             int betaType, double aPos[], double aNeg[]);

/**
 * Electrostatic finite size correction to the point charge Fermi function
 * written as @f$L_0(Z, W) @f$
 *
 * @param W electron total energy in units of its rest mass
 * @param W0 total endpoint energy in units of the electron rest mass
 * @param Z proton number
 * @param r radius in natural units
 * @param betaType BetaType of the transition
 * @param aPos array of fitted constants for beta+ decay
 * @param aNeg array of fitted constants for beta- decay
 */
double L0Correction(double W, int Z, double r, int betaType, double aPos[],
                    double aNeg[]);

/**
 * Correction to L0 by moving from a uniformly charged sphere to a more elaborate nuclear shape
 *
 * @param W electron total energy in units of its rest mass
 * @param Z proton number
 * @param R nuclear radius in natural units
 * @param betaType BetaType of the transition
 * @param ESShape string denoting the name of the base shape. Currently only Fermi is implemented
 * @param v vector representing the first 3 terms in an even-r power expansion of the base shape
 * @param vp vector representing the first 3 terms in an even-r power expansion of the new shape
 */
double UCorrection(double W, int Z, double R, int betaType, std::string ESShape, std::vector<double>& v, std::vector<double>& vp);

/**
 * Correction to L0 by calculating @f$ \frac{L_0'}{L_0} @f$ using a power expansion of the potentials
 *
 * @param W electron total energy in units of its rest mass
 * @param Z proton number
 * @param R nuclear radius in natural units
 * @param betaType BetaType of the transition
 * @param v vector representing the first 3 terms in an even-r power expansion of the base shape
 * @param vp vector representing the first 3 terms in an even-r power expansion of the new shape
 * @see UCorrection
 */
double UCorrection(double W, int Z, double R, int betaType, std::vector<double>& v, std::vector<double>& vp);

/**
 * Electromagnetic correction to the Fermi function due to the change in the
 * electromagnetic field of the recoiling nucleus compared to it standing still
 *
 * @param W electron total energy in units of its rest mass
 * @param W0 total endpoint energy in units of the electron rest mass
 * @param Z proton number
 * @param A mass number
 * @param betaType BetaType of the transition
 * @param decayType decsay type of the transition
 * @double mixingRatio mixing ratio of Fermi versus Gamow-Teller
 */
double QCorrection(double W, double W0, int Z, int A, int betaType,
                   int decayType, double mixingRatio);

/**
 * The radiative correction up to order @f$ \alpha^3Z^2 @f$ as per Sirlin
 *
 * @param W total electron energy in units of its rest mass
 * @param W0 total endpoint energy in units of the electron rest mass
 * @param Z proton number
 * @param R nuclear radius in natural units
 * @param betaType BetaType of the transition
 * @param gA axial vector coupling constant @f$ g_A @f$
 * @param gM nucleon isovector moment @f$ g_M = 4.706 @f$
 *
 * The first order correction is \f$ \delta_1=\frac{\alpha}{2\pi}g(W_0,W) \f$,
 *where \f$ g \f$ is defined by Sirlin (1967).
 * \f{eqnarray*} g(W_0,W) = & 3 \ln{\frac{m_p}{m_e}} - \frac{3}{4} +
 *\frac{4}{\beta}\rm{Spence}{\frac{2\beta}{1+\beta}} + 4 \left(
 *\frac{\tanh^{-1}{\beta}}{\beta}-1 \right) \\
 * & \times \left[ \frac{W_0-W}{3W} - \frac{3}{2} + \ln{2(W_0-W)} \right]  \\
 * & + \frac{\tanh^{-1}{\beta}}{\beta} \left[ 2(1+\beta^2) +
 *\frac{(W_0-W)^2}{6W^2} -4\tanh^{-1}{\beta}\right]
 * \f}
 *
 * where \f$ \tanh^{-1} \f$ is the inverse hyperbolic tangent function, \f$
 *\beta = \sqrt{W^2-1} \f$ and the Spence function is defined elsewhere.
 * @see Spence()
 */
double RadiativeCorrection(double W, double W0, int Z, double R, int betaType,
                           double gA, double gM);

/**
 * Radiative correction to the neutrino spectrum by Sirlin and Marciano
 *
 * @param Wv neutrino total energy in units of the electron rest mass
 */
double NeutrinoRadiativeCorrection(double Wv);

/**
 * Kinematic recoil correction to the beta spectrum due to the enlargement of the phase space
 *
 * @param W electron total energy in units of its rest mass
 * @param W0 total endpoint energy in units of the electron rest mass
 * @param A mass number
 * @param decayType decsay type of the transition
 * @double mixingRatio mixing ratio of Fermi versus Gamow-Teller
 */
double RecoilCorrection(double W, double W0, int A, int decayType,
                        double mixingRatio);

/**
 * Correction due to atomic screening calculated using the Salvat potential
 *
 * @param W electron total energy in units of its rest mass
 * @param Z proton number
 * @param betaType the BetaType of the transition
 */
double AtomicScreeningCorrection(double W, int Z, int betaType);

/**
 * The atomic exchange correction where an electron decays into a bound state of the daughter atom
 * and its corresponding interference with the direct process
 *
 * @param W electron total energy in units of its rest mass
 * @param exPars array containing the 9 fit parameters required for the analytical parametrisation
 */
double AtomicExchangeCorrection(double W, double exPars[9]);

/**
 * Correction due to the mismatch between initial and final atomic states, causing the
 * endpoint of the transition to get smaller
 *
 * @param W electron total energy in units of its rest mass
 * @param W0 total endpoint energy in units of the electron rest mass
 * @param Z proton number
 * @param A mass number
 * @param betaType BetaType of the transition
 */
double AtomicMismatchCorrection(double W, double W0, int Z, int A,
                                int betaType);

/**
 * @brief Spence function
 * @param x
 * @return -gs_sf_dilog(x)
 *
 * The Spence function is included here because the Wilkinson article refers
 *to it. It's value is actually equal (with a sign change) to the DiLogarithm,
 *which is used from GSL.
 * \f[ \rm{Spence} = \int_0^x\frac{\ln{(1-t)}}{t}\mathrm{d}t \equiv -
 *\sum_{k=1}^{k=\infty}\frac{x^k}{k^2} \equiv -\mathrm{Li}_2(x) \f]
 */
double Spence(double x);
}
}

#endif  // SPECTRALFUNCTIONS


/* specfunc/bessel_Kn.c
 *
 * Copyright (C) 1996, 1997, 1998, 1999, 2000 Gerard Jungman
 *
 * 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.
 */

/* Author:  G. Jungman */

#include <config.h>
#include <gsl/gsl_math.h>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_sf_gamma.h>
#include <gsl/gsl_sf_psi.h>
#include <gsl/gsl_sf_bessel.h>

#include "error.h"

#include "bessel.h"

/*-*-*-*-*-*-*-*-*-*-*-* Private Section *-*-*-*-*-*-*-*-*-*-*-*/

/* [Abramowitz+Stegun, 9.6.11]
 * assumes n >= 1
 */
static
int
bessel_Kn_scaled_small_x(const int n, const double x, gsl_sf_result * result)
{
  int k;
  double y = 0.25 * x * x;
  double ln_x_2 = log(0.5*x);
  double ex = exp(x);
  gsl_sf_result ln_nm1_fact;
  double k_term;
  double term1, sum1, ln_pre1;
  double term2, sum2, pre2;

  gsl_sf_lnfact_e((unsigned int)(n-1), &ln_nm1_fact);

  ln_pre1 = -n*ln_x_2 + ln_nm1_fact.val;
  if(ln_pre1 > GSL_LOG_DBL_MAX - 3.0) GSL_ERROR ("error", GSL_EOVRFLW);

  sum1 = 1.0;
  k_term = 1.0;
  for(k=1; k<=n-1; k++) {
    k_term *= -y/(k * (n-k));
    sum1 += k_term;
  }
  term1 = 0.5 * exp(ln_pre1) * sum1;

  pre2 = 0.5 * exp(n*ln_x_2);
  if(pre2 > 0.0) {
    const int KMAX = 20;
    gsl_sf_result psi_n;
    gsl_sf_result npk_fact;
    double yk = 1.0;
    double k_fact  = 1.0;
    double psi_kp1 = -M_EULER;
    double psi_npkp1;
    gsl_sf_psi_int_e(n, &psi_n);
    gsl_sf_fact_e((unsigned int)n, &npk_fact);
    psi_npkp1 = psi_n.val + 1.0/n;
    sum2 = (psi_kp1 + psi_npkp1 - 2.0*ln_x_2)/npk_fact.val;
    for(k=1; k<KMAX; k++) {
      psi_kp1   += 1.0/k;
      psi_npkp1 += 1.0/(n+k);
      k_fact    *= k;
      npk_fact.val *= n+k;
      yk *= y;
      k_term = yk*(psi_kp1 + psi_npkp1 - 2.0*ln_x_2)/(k_fact*npk_fact.val);
      sum2 += k_term;
    }
    term2 = ( GSL_IS_ODD(n) ? -1.0 : 1.0 ) * pre2 * sum2;
  }
  else {
    term2 = 0.0;
  }

  result->val  = ex * (term1 + term2);
  result->err  = ex * GSL_DBL_EPSILON * (fabs(ln_pre1)*fabs(term1) + fabs(term2));
  result->err += 2.0 * GSL_DBL_EPSILON * fabs(result->val);

  return GSL_SUCCESS;
}

/*-*-*-*-*-*-*-*-*-*-*-* Functions with Error Codes *-*-*-*-*-*-*-*-*-*-*-*/

int gsl_sf_bessel_Kn_scaled_e(int n, const double x, gsl_sf_result * result)
{
  n = abs(n); /* K(-n, z) = K(n, z) */

  /* CHECK_POINTER(result) */

  if(x <= 0.0) {
    DOMAIN_ERROR(result);
  }
  else if(n == 0) {
    return gsl_sf_bessel_K0_scaled_e(x, result);
  }
  else if(n == 1) {
    return gsl_sf_bessel_K1_scaled_e(x, result);
  }
  else if(x <= 5.0) {
    return bessel_Kn_scaled_small_x(n, x, result);
  }
  else if(GSL_ROOT3_DBL_EPSILON * x > 0.25 * (n*n + 1)) {
    return gsl_sf_bessel_Knu_scaled_asympx_e((double)n, x, result);
  }
  else if(GSL_MIN(0.29/(n*n), 0.5/(n*n + x*x)) < GSL_ROOT3_DBL_EPSILON) {
    return gsl_sf_bessel_Knu_scaled_asymp_unif_e((double)n, x, result);
  }
  else {
    /* Upward recurrence. [Gradshteyn + Ryzhik, 8.471.1] */
    double two_over_x = 2.0/x;
    gsl_sf_result r_b_jm1;
    gsl_sf_result r_b_j;
    int stat_0 = gsl_sf_bessel_K0_scaled_e(x, &r_b_jm1);
    int stat_1 = gsl_sf_bessel_K1_scaled_e(x, &r_b_j);
    double b_jm1 = r_b_jm1.val;
    double b_j   = r_b_j.val;
    double b_jp1;
    int j;

    for(j=1; j<n; j++) {
      b_jp1 = b_jm1 + j * two_over_x * b_j;
      b_jm1 = b_j;
      b_j   = b_jp1;
    }

    result->val  = b_j;
    result->err  = n * (fabs(b_j) * (fabs(r_b_jm1.err/r_b_jm1.val) + fabs(r_b_j.err/r_b_j.val)));
    result->err += 2.0 * GSL_DBL_EPSILON * fabs(result->val);

    return GSL_ERROR_SELECT_2(stat_0, stat_1);
  }
}

int gsl_sf_bessel_Kn_e(const int n, const double x, gsl_sf_result * result)
{
  const int status = gsl_sf_bessel_Kn_scaled_e(n, x, result);
  const double ex = exp(-x);
  result->val *= ex;
  result->err *= ex;
  result->err += x * GSL_DBL_EPSILON * fabs(result->val);
  return status;
}

int gsl_sf_bessel_Kn_scaled_array(const int nmin, const int nmax, const double x, double * result_array)
{
  /* CHECK_POINTER(result_array) */

  if(nmin < 0 || nmax < nmin || x <= 0.0) {
    int j;
    for(j=0; j<=nmax-nmin; j++) result_array[j] = 0.0;
    GSL_ERROR ("domain error", GSL_EDOM);
  }
  else if(nmax == 0) {
    gsl_sf_result b;
    int stat = gsl_sf_bessel_K0_scaled_e(x, &b);
    result_array[0] = b.val;
    return stat;
  }
  else {
    double two_over_x = 2.0/x;
    gsl_sf_result r_Knm1;
    gsl_sf_result r_Kn;
    int stat_0 = gsl_sf_bessel_Kn_scaled_e(nmin,   x, &r_Knm1);
    int stat_1 = gsl_sf_bessel_Kn_scaled_e(nmin+1, x, &r_Kn);
    int stat = GSL_ERROR_SELECT_2(stat_0, stat_1);
    double Knp1;
    double Kn   = r_Kn.val;
    double Knm1 = r_Knm1.val;
    int n;

    for(n=nmin+1; n<=nmax+1; n++) {
      if(Knm1 < GSL_DBL_MAX) {
        result_array[n-1-nmin] = Knm1;
        Knp1 = Knm1 + n * two_over_x * Kn;
        Knm1 = Kn;
        Kn   = Knp1;
      }
      else {
        /* Overflow. Set the rest of the elements to
         * zero and bug out.
         * FIXME: Note: this relies on the convention
         * that the test x < DBL_MIN fails for x not
         * a number. This may be only an IEEE convention,
         * so the portability is unclear.
         */
        int j;
        for(j=n; j<=nmax+1; j++) result_array[j-1-nmin] = 0.0;
        GSL_ERROR ("overflow", GSL_EOVRFLW);
      }
    }

    return stat;
  }
}

int
gsl_sf_bessel_Kn_array(const int nmin, const int nmax, const double x, double * result_array)
{
  int status = gsl_sf_bessel_Kn_scaled_array(nmin, nmax, x, result_array);
  double ex = exp(-x);
  int i;
  for(i=0; i<=nmax-nmin; i++) result_array[i] *= ex;
  return status;
}

/*-*-*-*-*-*-*-*-*-* Functions w/ Natural Prototypes *-*-*-*-*-*-*-*-*-*-*/

#include "eval.h"

double gsl_sf_bessel_Kn_scaled(const int n, const double x)
{
  EVAL_RESULT(gsl_sf_bessel_Kn_scaled_e(n, x, &result));
}

double gsl_sf_bessel_Kn(const int n, const double x)
{
  EVAL_RESULT(gsl_sf_bessel_Kn_e(n, x, &result));
}


/**
 * @file iftCommon.h
 * @brief Common definitions and functions to remaining modules.
 */

#ifndef IFT_COMMON_H
#define IFT_COMMON_H

#ifdef __cplusplus
extern "C" {
#endif

#include <stdarg.h>
#include <assert.h>
#include <cblas.h>
#include <ctype.h>
#include <dirent.h>
#include <float.h>
#include <libgen.h>
#include <limits.h>
#include <math.h>
#include <mm_malloc.h>
#include <regex.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>

#ifdef __linux
#include <dirent.h>
#include <omp.h>
#endif

#if !defined(__APPLE__)
    #include <malloc.h>
#endif
#if defined(__linux)
#endif

#include "iftBasicDataTypes.h"
#include "iftMemory.h"
#include "iftDialog.h"
#include "iftString.h"

/**
 * Common definitions
 */

#ifndef PI
#define PI IFT_PI
#endif

typedef enum ift_axis_order {
  XYZ, XZY, YXZ, YZX, ZXY, ZYX
} iftAxisOrder;

#define IFT_RANDOM_SEED (unsigned int) 213344
#define IFT_MAXWEIGHT     4095.0
#define IFT_AXIS_X 0
#define IFT_AXIS_Y 1
#define IFT_AXIS_Z 2
#define IFT_PI      3.14159265358979323846
#define IFT_INTERIOR    0
#define IFT_EXTERIOR    1
#define IFT_BOTH        2
#define IFT_WHITE       0
#define IFT_GRAY        1
#define IFT_BLACK       2
#define IFT_NIL        -1
#define IFT_INCREASING  1
#define IFT_DECREASING  0
#define IFT_EPSILON     1E-07

#ifndef FALSE
#define FALSE 0
#endif
#ifndef TRUE
#define TRUE 1
#endif

/**
 * Common operations
 */

#ifndef iftMax
#define iftMax(x,y) (((x) > (y))?(x):(y))
#endif

#ifndef iftMin
#define iftMin(x,y) (((x) < (y))?(x):(y))
#endif

#define iftRound(x) ((x < 0)?(int)(x-0.5):(int)(x+0.5))
#define iftSign(x) ((x >= 0)?1:-1)

/** @brief Euclidean Distance between voxels or points */
#define iftPointDistance(u,v) (sqrtf((u.x-v.x)*(u.x-v.x)+(u.y-v.y)*(u.y-v.y)+(u.z-v.z)*(u.z-v.z)))

/** @brief Euclidean Distance between voxels or porints */
#define iftVoxelDistance(u,v) iftPointDistance(u,v)

/** @brief Computes the Squared Distance between two voxels or points */
#define iftSquaredVoxelDistance(u,v) ((u.x-v.x)*(u.x-v.x)+(u.y-v.y)*(u.y-v.y)+(u.z-v.z)*(u.z-v.z))

/* @brief Computes the Smooth Euclidean Distances from a pre-computed Squared Euclidean distance among 26-neighbors
 * @param squared_dist The pre-computed Squared Euclidean Distance among 26-neighbors. The details are in N. Kiryati and G. Sze kely, "Estimating shortest paths and minimal distances on digitized three-dimensional surfaces," Pattern Recognition, vol. 26, pp. 1623-1637, 1993.
 * @return The resulting Smooth Euclidean Distance
 */
#define iftSmoothEuclideanDistance(squared_dist) ((fabs(squared_dist-1)<1.0e-6)? 0.9016 : (fabs(squared_dist-2)<1.0e-6)? 1.289 : 1.615 )

/**
 * @brief Converts a Point to a Voxel
 * @author Samuel
 * @date May 24, 2016
 */
#define iftPointToVoxel(point) ((iftVoxel) {(int) (point).x, (int) (point).y, (int) (point).z})

/**
 * @brief Converts a Voxel to a Point
 * @author Samuel
 * @date May 24, 2016
 */
#define iftVoxelToPoint(v) ((iftPoint) {(v).x, (v).y, (v).z})

#define iftImageCenter(img) ((iftVoxel){((img)->xsize - 1)/2, ((img)->ysize - 1)/2, ((img)->zsize - 1)/2})

/** @brief Constant used to align memory */
#define IFT_MEMORY_ALIGNMENT 16

/**
 * @brief Init the GPU devices.
 * @warning If not called, the GPU devices will be started in the first call for a GPU function.
 * @author Peixinho
 */
void iftStartGPU();

/**
 * @brief Stop the GPU devices.
 * @author Peixinho
 */
void iftStopGPU();

/**
 * @brief Checks if the long positive number is prime or not.
 * @author Samuel Martins
 * @date Jan 17th, 2016
 * @note Based on https://en.wikipedia.org/wiki/Primality_test
 */
bool iftIsPrime(long n);

/**
 * @brief Computes a fast power for small integer exponents.
 * @param base
 * @param b
 * @return Returns base^b
 * @author Peixinho
 * @date May, 2016
 * @warning The funcion is faster upto b=10, after that the regular function pow is called.
 */
double iftFastPow(double base, unsigned b);

/**
 * @brief Prints a Float array on stdout of lentgh <n>.
 */
void      iftPrintFloatArray(float* v, int n);

/**
 * @brief Prints a Double array on stdout of lentgh <n>.
 */
void      iftPrintDoubleArray(double* v, int n);

/**
 * @brief Prints an Integer array on stdout of lentgh <n>.
 */
void      iftPrintIntArray(int* v, int n);

/**
 * @brief Returns a random integer number between low and high.
 */
int iftRandomInteger (int low, int high);

/**
 * @brief Randomly selects nelems of the set [low, high]
 */
int *iftRandomIntegers (int low, int high, int nelems);

/**
 * @brief Randomly selects a normal distributed (N(0,1)) float number
 */
float iftRandomNormalFloat(void);

/**
 * @brief Returns initial time
 */
timer *iftTic(void);

/**
 * @brief Returns final time
 */
timer *iftToc(void);

/**
 * @brief Prints the computational time from tstart to tend, as obtained by iftTic() and iftToc() functions.
 */
void iftPrintCompTime(timer *tstart, timer *tend, const char *msg, ...);

/**
 * @brief Computes the difference in ms from the initial time to the final time.
 *
 * @param tic The initial time.
 * @param toc The final time.
 *
 * @return The computed time difference in milliseconds.
 *
 * @warning The memory allocated for <tic> and <toc> is freed inside this function.
 */
float iftCompTime(timer *tic, timer *toc);

/**
 * @brief Returns a string with the formatted time: days hours mins secs ms.
 *
 * @author Samuel
 *
 * Given a time in ms, this function returns a formatted time: days hours mins secs ms.
 * The runtime in MILISECONDS can be obtained with the function iftCompTime(timer *tic, timer *toc).
 *
 * @param runtime Time in ms.
 * @return The formatted time.
 */
char *iftFormattedTime(float runtime);

/**
 * @brief Prints the runtime in the following format: days hours mins secs ms.
 *
 * @author Samuel Martins
 * @date October 23, 2015
 * @sa iftFormattedTime()
 *
 * Given a time in ms, this function prints the runtime following the format: days hours mins secs ms.
 * The runtime in MILISECONDS can be obtained with the function iftCompTime(timer *tic, timer *toc).
 *
 * @param runtime Time in ms.
 */
void iftPrintFormattedTime(float runtime);

/**
 * @brief Writes a timer to a given file, using the following format %s: %f, where %s is the
 * given information corresponding to the time and %f is the current time in milliseconds.
 *
 * @param tic is freed by the function.
 */
void iftWriteTimerToFile(const char *filename, const char *information, timer *tic);

/**
 * @brief Generates seed for rand(), used in iftRandomInteger.
 */
void iftRandomSeed(unsigned int);

/**
 * @brief Returns the factorial of a number or NIL in case of overflow
 */
long double iftFactorial(int n);

/**
 * @brief Returns the limit to avoid overflow in factorial computation
 */
int iftFactorialLimit(void);

void iftUnitNorm(float *feats, int nelems);
void iftNormalizeFeatures(float *feats, int nelems);

float iftSquaredFeatDistance(float *A, float *B, int n);
float iftFeatDistance(float *A, float *B, int n);

/**
 * @brief Computes the Manhattan Distance between two arrays of float considering the sinal, ie.,
 * wihout the module between the feature difference: dist = (b[0]-a[0]) + (b[1]-a[1]) + ...
 */
float iftSignedManhattanDistance(float *A, float *B, int n);

/**
 * @brief Computes the log(val) in the specified base.
 * @author Peixinho
 */
double iftLog(double val, double base);

/**
 * @brief Returns the Central Voxel from the Bounding Box
 * @author Samuel Martins
 * @date Mar 15, 2016
 * @ingroup Geometry
 */
iftVoxel iftCenterFromBoundingBox(iftBoundingBox bb);

/**
 * @brief Vector operations defined as macros so that iftVoxels, iftVectors, and iftPoints may be used interchangeably.
 */
/**
 * @brief Subtracts point vec1 from vec2 (it works for iftVectors/iftVoxels/iftPoints).
 *
 * @author Thiago Vallin Spina
 * @date Mar 04, 2016
 * @ingroup Geometry
 *
 * @warning The result must be cast as an iftVoxel, iftVector, or iftPoint.
 */
#define iftVectorSub(vec1, vec2) {(vec1).x - (vec2).x, (vec1).y - (vec2).y, (vec1).z - (vec2).z}
/**
 * @brief Adds points vec1 and vec2 (it works for iftVectors/iftVoxels/iftPoints).
 *
 * @author Thiago Vallin Spina
 * @date Mar 04, 2016
 * @ingroup Geometry
 *
 * @warning The result must be cast as an iftVoxel, iftVector, or iftPoint.
 */
#define iftVectorSum(vec1, vec2) {(vec1).x + (vec2).x, (vec1).y + (vec2).y, (vec1).z + (vec2).z}
/**
 * @brief Computes the cross product between two voxels, points, or vectors.
 *
 * @author Thiago Vallin Spina (changed the macro function's name)
 * @date Mar 04, 2016
 *
 * @warning The result must be cast as an iftVoxel, iftVector, or iftPoint.
 */
#define iftVectorCrossProd(a, b) {(a).y*(b).z - (a).z*(b).y, (a).z*(b).x - (a).x*(b).z, (a).x*(b).y - (a).y*(b).x}
/**
 * @brief Computes the inner product between two voxels, points, or vectors.
 *
 * @author Thiago Vallin Spina (changed the macro function's name)
 * @date Mar 04, 2016
 *
 * @warning The result must be cast as an iftVoxel, iftVector, or iftPoint.
 */
#define iftVectorInnerProduct(a, b) (((a).x*(b).x + (a).y*(b).y + (a).z*(b).z))

/**
 * @brief Computes the Vector Magnitude from a voxel or point
 *
 * @author Thiago Vallin Spina
 * @date Mar 04, 2016
 * @ingroup Geometry
 *
 */
#define iftVectorMagnitude(v) (sqrtf(iftVectorInnerProduct((v),(v))))

/**
 * @brief Returns the XY angle of a 2D vector
 * @author Peixinho
 * @date April, 2016
 * @ingroup Geometry
 */
#define iftVectorXYAngle(v) ( (atan2(v.y, v.x)+IFT_PI) * (180.0/IFT_PI) )

/**
 * @brief Rounds a vector to integer coordinates.
 *
 * @author Thiago Vallin Spina
 * @date Mar 04, 2016
 * @ingroup Geometry
 *
 * @warning The result must be cast as an iftVoxel, iftVector, or iftPoint.
 */
#define iftVectorRound(vec1) {iftRound((vec1).x), iftRound((vec1).y), iftRound((vec1).z)}
/**
 * @brief Multiplies a vector by an scalar.
 *
 * @author Thiago Vallin Spina
 * @date Mar 04, 2016
 * @ingroup Geometry
 *
 * @warning The result must be cast as an iftVoxel, iftVector, or iftPoint.
 */
#define iftVectorScalarProd(vec, s) {(vec).x*(s), (vec).y*(s), (vec).z*(s)}
/**
 * @brief Adds an scalar to a vector.
 *
 * @author Thiago Vallin Spina
 * @date Mar 04, 2016
 * @ingroup Geometry
 *
 * @warning The result must be cast as an iftVoxel, iftVector, or iftPoint.
 */
#define iftVectorScalarSum(vec, s) {(vec).x+(s), (vec).y+(s), (vec).z+(s)}
/**
 * @brief Divides a vector by an scalar.
 *
 * @author Thiago Vallin Spina
 * @date Mar 04, 2016
 * @ingroup Geometry
 *
 * @return The vector divided by the scalar, or the vector itself if the scalar is close to 0.0
 *
 * @warning The result must be cast as an iftVoxel, iftVector, or iftPoint.
 */
iftVector iftVectorScalarDiv(iftVector vec, double s);
/**
 * @brief Verifies if a vector has coordinates (0, 0, 0).
 *
 * @author Thiago Vallin Spina
 * @date Mar 04, 2016
 * @ingroup Geometry
 */
#define iftIsNullVector(vec) (iftAlmostZero((vec).x) && iftAlmostZero((vec).y) && iftAlmostZero((vec).z))
/**
 * @brief Verifies if three points, voxels, or vectors are collinear.
 *
 * This is achieved by testing if the cross product between the vectors P1->P2 and P1->P3 is Null (0,0,0).
 *
 * @author Thiago Vallin Spina
 * @date Mar 04, 2016
 * @ingroup Geometry
 */
#define iftCollinearPoints(P1, P2, P3) (iftIsNullVector((iftVector)iftVectorCrossProd((iftVector)iftVectorSub((P2), (P1)), (iftVector)iftVectorSub((P3), (P1)))))

/**
 * @brief Tests if two iftVoxels are equal
 *
 * @author Thiago Vallin Spina
 * @date Apr 19, 2016
 *
 * @param u1 The first voxel
 * @param u2 The second voxel
 * @return True if their coordinates are the same
 */
bool iftVoxelsAreEqual(iftVoxel u1, iftVoxel u2);

/**
 * @brief Computes the mean voxel
 *
 * @author Thiago Vallin Spina
 * @date May 1, 2016
 *
 * @param u1 The first voxel
 * @param u2 The second voxel
 * @return The mean voxel (u1 + u2) / 2
 */
iftVoxel iftMeanVoxel(iftVoxel u1, iftVoxel u2);

/**
 * @brief Tests if two iftPoints/iftVectors are equal
 *
 * @author Thiago Vallin Spina
 * @date Apr 19, 2016
 *
 * @param u1 The first point/vector
 * @param u2 The second point/vector
 * @return True if their coordinates are (almost) the same
 */
#define iftVectorsAreEqual(u1, u2) (iftAlmostZero((u1).x-(u2).x) && iftAlmostZero((u1).y-(u2).y) && \
                                      iftAlmostZero((u1).z-(u2).z))

/**
 * @brief Normalizes a vector if its norm is greater than 0.0, otherwise returns the vector itself.
 *
 * @author Thiago Vallin Spina
 * @date Mar 04, 2016
 * @ingroup Geometry
 */
iftVector iftNormalizeVector(iftVector v);

/**
 * @brief Projects vector U onto vector V.
 *
 * @author Thiago Vallin Spina
 * @date Mar 04, 2016
 * @ingroup Geometry
 *
 * @warning If vector V has norm close to 0.0 the function issues and iftError.
 */
iftVector iftProjectVector(iftVector U, iftVector V);

/**
 * @brief Returns the distance between P0 and the line from P1 to P2, whose size is P1P2
 *
 * @author Thiago Vallin Spina
 * @date Mar 04, 2016
 * @ingroup Geometry
 */
double iftVoxelLineDist2D(iftVoxel P0, iftVoxel P1, iftVoxel P2, double P1P2);

/**
 * @brief Given the vector from P1 to P0, returns the voxel projected on the line between P1 and P2.
 *
 * @author Thiago Vallin Spina
 * @date Mar 04, 2016
 * @ingroup Geometry
 */
iftVoxel iftClosestVoxelOnLine2D(iftVoxel P0, iftVoxel P1, iftVoxel P2);

/**
 * @brief Returns the position of P0 with respect to the line from P1 to P2.
 *
 * Negative values indicate left side, 0 indicates on the line,
 * and positive values indicate right side.
 *
 * @author Thiago Vallin Spina
 * @date Mar 04, 2016
 * @ingroup Geometry
 */
int iftVoxelLinePosition2D(iftVoxel P0, iftVoxel P1, iftVoxel P2);

/**
 * @brief Computes the circle's area given a radius.
 *
 * @author Thiago Vallin Spina
 * @date Mar 04, 2016
 *
 * @ingroup Geometry
 */
static inline double iftCircleArea(double r) {
    return (r*r)*IFT_PI;
}

/* @brief Gets rid of the carriage return and the line feed characteres introduced by DOS systems when reading
 * strings from ASCII files.
 *
 * @author Alexandre Xavier Falcao
 *
 * @param line The string in which all carriage return characters are removed.
 */
void iftRemoveCarriageReturn(char *line);

/**
 * @brief Evaluates the sigmoid function, with x = value.
 *
 * @param alfa Controls the decay of the function.
 * @author Renzo Phellan
 */
float iftSigmoidalValue(float value, float alfa);

void iftVerifyToken(FILE *fp, char *token, char *function);
void iftReadIntValue(FILE *fp, int *value, char *token, char *function);
void iftReadIntValues(FILE *fp, int **value, int nvalues, char *token, char *function);
void iftWriteIntValue(FILE *fp, int value, char *token);
void iftWriteIntValues(FILE *fp, int *value, int nvalues, char *token);
void iftReadFloatValue(FILE *fp, float *value, char *token, char *function);
void iftReadFloatValues(FILE *fp, float **value, int nvalues, char *token, char *function);
void iftWriteFloatValue(FILE *fp, float value, char *token);
void iftWriteFloatValues(FILE *fp, float *value, int nvalues, char *token);
void iftReadDoubleValue(FILE *fp, double *value, char *token, char *function);
void iftReadDoubleValues(FILE *fp, double **value, int nvalues, char *token, char *function);
void iftWriteDoubleValue(FILE *fp, double value, char *token);
void iftWriteDoubleValues(FILE *fp, double *value, int nvalues, char *token);
void iftSkipComments(FILE *fp);

/* These functions are currently used to communicate with numpy */
void iftWriteRawIntArray(char *filename, int *array, int n);
int* iftReadRawIntArray(char *filename, int n);

/**
 * @brief Computes the mean value of a float array.
 */
float iftMean(float *x, int n);
/**
 * @brief Computes the variance of a float array.
 */
float iftVar(float *x, int n);
/**
 * @brief Computes the covariance of float arrays.
 */
float iftCov(float *x, float *y, int n);

/**
 * @brief Tests if a real (float/double) number is zero. It should be used for comparisons if two floats are equal.
 *
 * @author Thiago Vallin Spina
 */
int iftAlmostZero(double x);
/**
 * @brief Computes the mod operation of <a> by <n>. If the mod value is negative, it is placed within [0,n-1].
 */
int iftSafeMod(int a, int n);

/**
 * @brief Gets an Integer Array with the Unique Elements from an Input Array.
 *
 * Ex: Given the array = [1, 2, 3, 4, 4, 3, 2, 1], with size n = 8, the resulting array of unique elements will be:
 * [1, 2, 3, 4]
 *
 * @param  array Input Int Array to be scanned.
 * @param  n     Number of Elements from the Input Int Array.
 * @return       An Integer Array with the Unique Elements from <b>array</b>.
 */
iftIntArray *iftIntArrayUnique(const int *array, int n);

/**
 * @brief Counts the Number of Unique Elements from an array.
 *
 * @param array Integer Array whose Number of Unique Elements will be counted.
 * @param n     Array Size.
 * @return      The number of Unique Elements.
 */
int iftCountUniqueIntElems(const int *array, int n);

/**
 * @brief This function finds the most suitable normalization value to be used during image color/feature conversion/normalization.
 *
 * It is afunction of the most common number of bits in imaging sensors. It assumes that these values are in {1, 8, 10,
 * 12, 16, 24, 32} bits per voxel.
 *
 * @author Alexandre Xavier Falcao.
 *
 * @param maxval The maximum value of an image, for instance.
 */
int iftNormalizationValue(int maxval);

/**
 * @brief Converts a number in radians to degrees.
 *
 * @author Thiago Vallin Spina
 */
static inline double iftDegrees(double rad) {
  return rad * 180.0 / IFT_PI;
}

/**
 * @brief Converts a number in degrees to radians.
 *
 * @author Thiago Vallin Spina
 */
static inline double iftRadians(double deg) {
  return deg * IFT_PI / 180.0;
}

/**
 * @brief Converts a negative radian number to positive.
 *
 * @author Thiago Vallin Spina
 */
static inline double iftNegativeToPositiveRadians(double rad) {
  return (rad < 0) ? 2 * IFT_PI + rad : rad;
}

/**
 * @brief Converts a negative degree number to positive.
 *
 * @author Thiago Vallin Spina
 */
static inline double iftNegativeToPositiveDegrees(double deg) {
  return (deg < 0) ? 360.0+deg : deg;
}

/**
 * @brief Compute the index of the element in vector x with minimum value
 *
 * @param x Float vector.
 * @param n Size of the vectors.
 * @return index.
*/
int iftArgmin(const int *x, int n);

/**
 * @brief Compute the index of the element in vector x with maximum value
 *
 * @param x Float vector.
 * @param n Size of the vectors.
 * @return index.
*/
int iftArgmax(const int *x, int n);

/**
 * @brief Compute the index of the element in vector x with minimum value
 *
 * @param x Float vector.
 * @param n Size of the vectors.
 * @return index.
*/
int iftFArgmin(const float *x, int n);

/**
 * @brief Compute the index of the element in vector x with maximum value
 *
 * @param x Float vector.
 * @param n Size of the vectors.
 * @return index.
*/
int iftFArgmax(const float *x, int n);

/**
 * @brief Compute the index of the element in vector x with minimum value
 *
 * @param x Double vector.
 * @param n Size of the vectors.
 * @return index.
*/
int iftDArgmin(const double *x, int n);

/**
 * @brief Compute the index of the element in vector x with maximum value
 *
 * @param x Double vector.
 * @param n Size of the vectors.
 * @return index.
*/
int iftDArgmax(const double *x, int n);

/**
 * @brief Finds the index of <elem> in array <x> and returns it.
 *
 * @author Peixinho
 *
 * @param x Unsorted array of elements.
 * @param n The size of the array.
 * @param elem The elem to be found.
 *
 * @return The element's index in <x> or NIL if not found
 */
int iftFindIntArrayElementIndex(int *x, int n, int elem);

/**
 * @brief Compute the Gaussian Probability Density Function for a set of values.
 * @author Samuel Martins
 * @date May 12, 2016
 *
 * @param  vals   Array of Values to compute the Gaussian PDF.
 * @param  n_vals Number of values.
 * @param  mean   Mean used to compute the Gaussian PDF.
 * @param  stdev  Standard Deviation used to compute the Gaussian PDF.
 * @return        Array of probabilities, one for each input value.
 */
double *iftGaussPDF(double *vals, size_t n_vals, double mean, double stdev);

/**
 * @brief Compute the Cumulative Density of the (Gaussian) PDF (Normal Distribution) in a Random Variable with value x.
 * @author Samuel Martins
 * @date May 12, 2016
 *
 * It gives the Area under the Probability Density Function (Normal Distribution) from minus infinity to x. \n
 * Then, it also describes the probability that a random variable X takes on a value less than or
 * equal to a number x. \n
 *
 * @param  x      Value from the Random Variable to compute the CDF.
 * @param  mean   Mean used to compute the Gaussian PDF.
 * @param  stdev  Standard Deviation used to compute the Gaussian PDF.
 * @return        The area under the PDF from minus to x.
 */
double iftCDF(double x, double mean, double stdev);

#ifdef __cplusplus
}
#endif

#endif


/*
Copyright 2010-2011, D. E. Shaw Research.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

* Redistributions of source code must retain the above copyright
  notice, this list of conditions, and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright
  notice, this list of conditions, and the following disclaimer in the
  documentation and/or other materials provided with the distribution.

* Neither the name of D. E. Shaw Research nor the names of its
  contributors may be used to endorse or promote products derived from
  this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <gsl/gsl_randist.h>
#include <stdio.h>
#include "random123/philox.h"
#include "random123/threefry.h"
#include "random123/gsl_microrng.h"

/* Compute pi, using the gsl_ran_flat distribution with
   an underlying threefry4x64 counter-based rng (cbrng).
   We can call cbrng 8 times between calls to cbrng_reset */

GSL_MICRORNG(cbrng, threefry4x64); /* creates gsl_rng_cbrng */

#include "pi_check.h"

int main(int argc, char **argv){
    unsigned long hits = 0, tries = 0;
    gsl_rng *r;
    (void)argc; (void)argv; /* unused */

    threefry4x64_ctr_t c = {{0}};
    threefry4x64_key_t k = {{0}};
    r = gsl_rng_alloc(gsl_rng_cbrng);
    printf("%lu uniforms from %s\n", NTRIES, gsl_rng_name(r));
    while (tries < NTRIES) {
            double x, y;
            c.v[0]++; /* increment the counter */
            cbrng_reset(r, c, k); /* reset the rng to the new counter */
            x = gsl_ran_flat (r, -1.0, 1.0);
            y = gsl_ran_flat (r, -1.0, 1.0);
            if( x*x + y*y < 1.0 )
                hits++;
	    tries++;
    }
    gsl_rng_free (r);
    return pi_check(hits, tries);
}