// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2008 Gael Guennebaud <gael.guennebaud@inria.fr>
//
// Eigen is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 3 of the License, or (at your option) any later version.
//
// Alternatively, you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of
// the License, or (at your option) any later version.
//
// Eigen 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 Lesser General Public License or the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License and a copy of the GNU General Public License along with
// Eigen. If not, see <http://www.gnu.org/licenses/>.

#ifndef EIGEN_GSL_HELPER
#define EIGEN_GSL_HELPER

#include <Eigen/Core>

#include <gsl/gsl_blas.h>
#include <gsl/gsl_multifit.h>
#include <gsl/gsl_eigen.h>
#include <gsl/gsl_linalg.h>
#include <gsl/gsl_complex.h>
#include <gsl/gsl_complex_math.h>
#include <gsl/gsl_poly.h>

namespace Eigen {

template<typename Scalar, bool IsComplex = NumTraits<Scalar>::IsComplex> struct GslTraits
{
  typedef gsl_matrix* Matrix;
  typedef gsl_vector* Vector;
  static Matrix createMatrix(int rows, int cols) { return gsl_matrix_alloc(rows,cols); }
  static Vector createVector(int size) { return gsl_vector_alloc(size); }
  static void free(Matrix& m) { gsl_matrix_free(m); m=0; }
  static void free(Vector& m) { gsl_vector_free(m); m=0; }
  static void prod(const Matrix& m, const Vector& v, Vector& x) { gsl_blas_dgemv(CblasNoTrans,1,m,v,0,x); }
  static void cholesky(Matrix& m) { gsl_linalg_cholesky_decomp(m); }
  static void cholesky_solve(const Matrix& m, const Vector& b, Vector& x) { gsl_linalg_cholesky_solve(m,b,x); }
  static void eigen_symm(const Matrix& m, Vector& eval, Matrix& evec)
  {
    gsl_eigen_symmv_workspace * w = gsl_eigen_symmv_alloc(m->size1);
    Matrix a = createMatrix(m->size1, m->size2);
    gsl_matrix_memcpy(a, m);
    gsl_eigen_symmv(a,eval,evec,w);
    gsl_eigen_symmv_sort(eval, evec, GSL_EIGEN_SORT_VAL_ASC);
    gsl_eigen_symmv_free(w);
    free(a);
  }
  static void eigen_symm_gen(const Matrix& m, const Matrix& _b, Vector& eval, Matrix& evec)
  {
    gsl_eigen_gensymmv_workspace * w = gsl_eigen_gensymmv_alloc(m->size1);
    Matrix a = createMatrix(m->size1, m->size2);
    Matrix b = createMatrix(_b->size1, _b->size2);
    gsl_matrix_memcpy(a, m);
    gsl_matrix_memcpy(b, _b);
    gsl_eigen_gensymmv(a,b,eval,evec,w);
    gsl_eigen_symmv_sort(eval, evec, GSL_EIGEN_SORT_VAL_ASC);
    gsl_eigen_gensymmv_free(w);
    free(a);
  }

  template<class EIGEN_VECTOR, class EIGEN_ROOTS>
  static void eigen_poly_solve(const EIGEN_VECTOR& poly, EIGEN_ROOTS& roots )
  {
    const int deg = poly.size()-1;
    double *z = new double[2*deg];
    double *a = new double[poly.size()];
    for( int i=0; i<poly.size(); ++i ){ a[i] = poly[i]; }

    gsl_poly_complex_workspace * w = gsl_poly_complex_workspace_alloc (poly.size());
    gsl_poly_complex_solve(a, poly.size(), w, z);
    gsl_poly_complex_workspace_free (w);
    for( int i=0; i<deg; ++i )
    {
      roots[i].real() = z[2*i];
      roots[i].imag() = z[2*i+1];
    }

    delete[] a;
    delete[] z;
  }
};

template<typename Scalar> struct GslTraits<Scalar,true>
{
  typedef gsl_matrix_complex* Matrix;
  typedef gsl_vector_complex* Vector;
  static Matrix createMatrix(int rows, int cols) { return gsl_matrix_complex_alloc(rows,cols); }
  static Vector createVector(int size) { return gsl_vector_complex_alloc(size); }
  static void free(Matrix& m) { gsl_matrix_complex_free(m); m=0; }
  static void free(Vector& m) { gsl_vector_complex_free(m); m=0; }
  static void cholesky(Matrix& m) { gsl_linalg_complex_cholesky_decomp(m); }
  static void cholesky_solve(const Matrix& m, const Vector& b, Vector& x) { gsl_linalg_complex_cholesky_solve(m,b,x); }
  static void prod(const Matrix& m, const Vector& v, Vector& x)
  { gsl_blas_zgemv(CblasNoTrans,gsl_complex_rect(1,0),m,v,gsl_complex_rect(0,0),x); }
  static void eigen_symm(const Matrix& m, gsl_vector* &eval, Matrix& evec)
  {
    gsl_eigen_hermv_workspace * w = gsl_eigen_hermv_alloc(m->size1);
    Matrix a = createMatrix(m->size1, m->size2);
    gsl_matrix_complex_memcpy(a, m);
    gsl_eigen_hermv(a,eval,evec,w);
    gsl_eigen_hermv_sort(eval, evec, GSL_EIGEN_SORT_VAL_ASC);
    gsl_eigen_hermv_free(w);
    free(a);
  }
  static void eigen_symm_gen(const Matrix& m, const Matrix& _b, gsl_vector* &eval, Matrix& evec)
  {
    gsl_eigen_genhermv_workspace * w = gsl_eigen_genhermv_alloc(m->size1);
    Matrix a = createMatrix(m->size1, m->size2);
    Matrix b = createMatrix(_b->size1, _b->size2);
    gsl_matrix_complex_memcpy(a, m);
    gsl_matrix_complex_memcpy(b, _b);
    gsl_eigen_genhermv(a,b,eval,evec,w);
    gsl_eigen_hermv_sort(eval, evec, GSL_EIGEN_SORT_VAL_ASC);
    gsl_eigen_genhermv_free(w);
    free(a);
  }
};

template<typename MatrixType>
void convert(const MatrixType& m, gsl_matrix* &res)
{
//   if (res)
//     gsl_matrix_free(res);
  res = gsl_matrix_alloc(m.rows(), m.cols());
  for (int i=0 ; i<m.rows() ; ++i)
    for (int j=0 ; j<m.cols(); ++j)
      gsl_matrix_set(res, i, j, m(i,j));
}

template<typename MatrixType>
void convert(const gsl_matrix* m, MatrixType& res)
{
  res.resize(int(m->size1), int(m->size2));
  for (int i=0 ; i<res.rows() ; ++i)
    for (int j=0 ; j<res.cols(); ++j)
      res(i,j) = gsl_matrix_get(m,i,j);
}

template<typename VectorType>
void convert(const VectorType& m, gsl_vector* &res)
{
  if (res) gsl_vector_free(res);
  res = gsl_vector_alloc(m.size());
  for (int i=0 ; i<m.size() ; ++i)
      gsl_vector_set(res, i, m[i]);
}

template<typename VectorType>
void convert(const gsl_vector* m, VectorType& res)
{
  res.resize (m->size);
  for (int i=0 ; i<res.rows() ; ++i)
    res[i] = gsl_vector_get(m, i);
}

template<typename MatrixType>
void convert(const MatrixType& m, gsl_matrix_complex* &res)
{
  res = gsl_matrix_complex_alloc(m.rows(), m.cols());
  for (int i=0 ; i<m.rows() ; ++i)
    for (int j=0 ; j<m.cols(); ++j)
    {
      gsl_matrix_complex_set(res, i, j,
        gsl_complex_rect(m(i,j).real(), m(i,j).imag()));
    }
}

template<typename MatrixType>
void convert(const gsl_matrix_complex* m, MatrixType& res)
{
  res.resize(int(m->size1), int(m->size2));
  for (int i=0 ; i<res.rows() ; ++i)
    for (int j=0 ; j<res.cols(); ++j)
      res(i,j) = typename MatrixType::Scalar(
        GSL_REAL(gsl_matrix_complex_get(m,i,j)),
        GSL_IMAG(gsl_matrix_complex_get(m,i,j)));
}

template<typename VectorType>
void convert(const VectorType& m, gsl_vector_complex* &res)
{
  res = gsl_vector_complex_alloc(m.size());
  for (int i=0 ; i<m.size() ; ++i)
      gsl_vector_complex_set(res, i, gsl_complex_rect(m[i].real(), m[i].imag()));
}

template<typename VectorType>
void convert(const gsl_vector_complex* m, VectorType& res)
{
  res.resize(m->size);
  for (int i=0 ; i<res.rows() ; ++i)
    res[i] = typename VectorType::Scalar(
        GSL_REAL(gsl_vector_complex_get(m, i)),
        GSL_IMAG(gsl_vector_complex_get(m, i)));
}

}

#endif // EIGEN_GSL_HELPER


#ifndef GMM_MODEL_H
#define GMM_MODEL_H

#include "DoubleVector.h"
#include "GMM/GmmAggregateNewComp.h"
#include "Handle.h"
#include "LambdaCreationFunctions.h"
#include "Object.h"
#include "PDBVector.h"

#include <gsl/gsl_blas.h>
#include <gsl/gsl_linalg.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_permutation.h>
#include <gsl/gsl_vector.h>
#include <math.h>
#include <vector>

// By Tania, October 2017

using namespace pdb;

/* This class contains our current GMM model, that is:
 * - num of dimensions;
 * - weights: set of (prior) weights for each component
 * - means: mean for each component
 * - covars: covariance for each component
 * - inv_covars: inverse covariance for each component
 * - dets: determinant of each covariance matrix
 */
class GmmModel : public Object {

private:
  int ndim = 0;
  int k = 0;
  Handle<DoubleVector> weights;
  Handle<Vector<Handle<DoubleVector>>> means;      // k*ndim
  Handle<Vector<Handle<DoubleVector>>> covars;     // k*ndim*ndim
  Handle<Vector<Handle<DoubleVector>>> inv_covars; // k*ndim*ndim
  Handle<DoubleVector> dets;

public:
  ENABLE_DEEP_COPY

  GmmModel() {}

  GmmModel(int k, int ndim) {
    std::cout << "K= " << k << std::endl;
    std::cout << "ndim= " << ndim << std::endl;

    this->ndim = ndim;
    this->k = k;

    this->weights = pdb::makeObject<DoubleVector>(k);
    this->dets = pdb::makeObject<DoubleVector>(k);

    this->means = pdb::makeObject<Vector<Handle<DoubleVector>>>();
    this->covars = pdb::makeObject<Vector<Handle<DoubleVector>>>();
    this->inv_covars = pdb::makeObject<Vector<Handle<DoubleVector>>>();

    for (int i = 0; i < k; i++) {
      this->weights->setDouble(i, 1.0 / k);
      this->means->push_back(makeObject<DoubleVector>(ndim));
      this->covars->push_back(makeObject<DoubleVector>(ndim * ndim));
      this->inv_covars->push_back(makeObject<DoubleVector>(ndim * ndim));
    }

    std::cout << "Model created!" << std::endl;
  }

  // this prints the first 10 elements of a DoubleVector
  void print_vector(Handle<DoubleVector> v) {
    int MAX = 10;
    if (v->size < MAX)
      MAX = v->size;
    for (int i = 0; i < MAX; i++) {
      std::cout << i << ": " << v->getDouble(i) << "; ";
    }
    std::cout << std::endl;
  }

  // Print current model
  void print() {
    // Print mean
    std::cout << "**Current model**" << std::endl;
    std::cout << "Weights: ";
    print_vector(this->weights);
    for (int i = 0; i < k; i++) {
      std::cout << "**Component " << i << std::endl;
      std::cout << "Mean: ";
      print_vector((*this->means)[i]);
      std::cout << "Covar: ";
      print_vector((*this->covars)[i]);
      std::cout << "Inv covars: ";
      print_vector((*this->inv_covars)[i]);
    }
  }

  int getNumK() { return k; }

  int getNDim() { return ndim; }

  double getWeight(int i) { return this->weights->getDouble(i); }

  void updateWeights(std::vector<double> w) {
    for (int i = 0; i < k; i++) {
      this->weights->setDouble(i, w[i]);
    }
    std::cout << "updated weights";
    print_vector(this->weights);
  }

  void updateMeans(std::vector<std::vector<double>> &m) {
    for (int i = 0; i < k; i++) {
      std::cout << "updated mean for comp " << i << std::endl;

      double *mean = (*this->means)[i]->getRawData();
      for (int j = 0; j < ndim; j++) {
        mean[j] = m[i][j];
      }
      print_vector((*this->means)[i]);
    }
  }

  void updateCovars(std::vector<std::vector<double>> &c) {
    for (int i = 0; i < k; i++) {
      std::cout << "updated covar for comp " << i << std::endl;

      double *covar = (*this->covars)[i]->getRawData();

      for (int j = 0; j < (ndim * ndim); j++) {
        covar[j] = c[i][j];
      }
      print_vector((*this->covars)[i]);
    }

    // calcInvCovars();
  }

  // It calculates determinants and inverse covariances based on Cholesky
  // decomposition
  void calcInvCovars() {

    for (int i = 0; i < this->getNumK(); i++) {

      gsl_matrix_view covar =
          gsl_matrix_view_array((*covars)[i]->data->c_ptr(), ndim, ndim);
      gsl_matrix_view inv_covar =
          gsl_matrix_view_array((*inv_covars)[i]->data->c_ptr(), ndim, ndim);

      gsl_matrix_memcpy(&inv_covar.matrix, &covar.matrix);

      // gsl_permutation *p = gsl_permutation_alloc(ndim);

      gsl_linalg_cholesky_decomp(&inv_covar.matrix);

      // Calculate determinant -> logdet
      double ax = 0.0;
      for (int i = 0; i < ndim; i++) {
        ax += log(gsl_matrix_get(&inv_covar.matrix, i, i));
      }

      // Check for
      gsl_linalg_cholesky_invert(&inv_covar.matrix);

      dets->setDouble(i, ax);
    }
  }

  // It returns the rvalue, responsability, posterior probability of the
  // datapoint
  // for GMM component i
  double log_normpdf(int i, Handle<DoubleVector> inputData) {

    gsl_vector_view data =
        gsl_vector_view_array(inputData->data->c_ptr(), ndim);
    gsl_vector_view mean =
        gsl_vector_view_array((*means)[i]->data->c_ptr(), ndim);
    gsl_matrix_view covar =
        gsl_matrix_view_array((*covars)[i]->data->c_ptr(), ndim, ndim);
    gsl_matrix_view inv_covar =
        gsl_matrix_view_array((*inv_covars)[i]->data->c_ptr(), ndim, ndim);

    double ax, ay;
    int s;
    gsl_vector *ym;
    gsl_vector *datan = gsl_vector_alloc(ndim);

    ax = dets->getDouble(i);

    gsl_vector_memcpy(datan, &data.vector);
    gsl_vector_sub(datan, &mean.vector);

    ym = gsl_vector_alloc(ndim);
    gsl_blas_dsymv(CblasUpper, 1.0, &inv_covar.matrix, datan, 0.0, ym);
    gsl_blas_ddot(datan, ym, &ay);

    gsl_vector_free(ym);
    gsl_vector_free(datan);

    ay = -ax - 0.5 * ay;

    return ay;
  }

  // It calculates the sum of elements in vector v in log space
  // To do this, we extract the maximum factor (maxVal) from
  // all the values
  double logSumExp(double *v, size_t size) {

    double maxVal = v[0];
    double sum = 0;

    for (int i = 1; i < size; i++) {
      if (v[i] > maxVal) {
        maxVal = v[i];
      }
    }

    for (int i = 0; i < size; i++) {
      sum += exp(v[i] - maxVal);
    }

    return log(sum) + maxVal;
  }

  double logSumExp(DoubleVector &vector) {
    return logSumExp(vector.getRawData(), vector.size);
  }

  double logSumExp(Vector<double> &vector) {
    return logSumExp(vector.c_ptr(), vector.size());
  }

  // It uses the partial results from the Aggregate to update the model

  double updateModel(GmmAggregateNewComp update) {

    const double MIN_COVAR = 1e-6;

    double *sumWeights = update.getSumWeights().c_ptr();
    double totalSumR = 0.0;

    for (int i = 0; i < k; i++) {
      totalSumR += sumWeights[i];
    }

    // Update weights -> weights = sumR/totalSumR
    gsl_vector_view gSumWeights = gsl_vector_view_array(sumWeights, k);
    gsl_vector_view gweights =
        gsl_vector_view_array((*weights).data->c_ptr(), k);
    gsl_vector_memcpy(&gweights.vector, &gSumWeights.vector);
    gsl_vector_scale(&gweights.vector, 1.0 / totalSumR);

    // Update Mean and Covar for each component
    for (int i = 0; i < k; i++) {

      // Update means -> means = 1/sumR * weightedX
      gsl_vector_view mean =
          gsl_vector_view_array((*means)[i]->data->c_ptr(), ndim);
      gsl_vector_view gsumMean =
          gsl_vector_view_array(update.getSumMean(i).c_ptr(), ndim);
      gsl_vector_memcpy(&mean.vector, &gsumMean.vector);
      gsl_vector_scale(&mean.vector, 1.0 / sumWeights[i]);

      // Update covars -> gsumCovarv / sumR - mean*mean^T

      gsl_vector_view gsumCovarv =
          gsl_vector_view_array(update.getSumCovar(i).c_ptr(), ndim * ndim);
      gsl_vector_scale(&gsumCovarv.vector, 1.0 / sumWeights[i]);
      gsl_matrix_view gsumCovarm =
          gsl_matrix_view_array(update.getSumCovar(i).c_ptr(), ndim, ndim);
      gsl_blas_dsyr(CblasUpper, -1.0, &mean.vector, &gsumCovarm.matrix);

      // Add constant to diagonal and copy lower triangular
      double d;
      for (int row = 0; row < ndim; row++) {
        d = gsl_matrix_get(&gsumCovarm.matrix, row, row);
        gsl_matrix_set(&gsumCovarm.matrix, row, row, d + MIN_COVAR);

        for (int col = row + 1; col < ndim; col++) {
          // matrix[j][i] = matrix[i][j]
          d = gsl_matrix_get(&gsumCovarm.matrix, row, col);
          gsl_matrix_set(&gsumCovarm.matrix, col, row, d);
        }
      }

      gsl_vector_view covar =
          gsl_vector_view_array((*covars)[i]->data->c_ptr(), ndim * ndim);
      gsl_vector_memcpy(&covar.vector, &gsumCovarv.vector);
    }

    // Finally, calculate inverse covariances and determinants to be used in the
    // next iteration
    calcInvCovars();

    return update.getLogLikelihood();
  }

  ~GmmModel() {}
};

#endif


/**
 *
 * @file example_strsmpl.c
 *
 *  PLASMA testing routines
 *  PLASMA is a software package provided by Univ. of Tennessee,
 *  Univ. of California Berkeley and Univ. of Colorado Denver
 *
 * @brief Example for solving a system of linear equations using
 *        LU factorization and PLASMA_strsmpl routine
 *
 * @version 2.6.0
 * @author Bilel Hadri
 * @date 2010-11-15
 * @generated s Tue Jan  7 11:45:21 2014
 *
 **/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>

#include <plasma.h>
#include <cblas.h>
#include <lapacke.h>
#include <core_blas.h>

int check_solution(int, int , float *, int, float *, float *, int);

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

int main ()
{

    int cores = 2;
    int N     = 10;
    int LDA   = 10;
    int NRHS  = 5;
    int LDB   = 10;
    int info;
    int info_solution;
    int i,j;
    int LDAxN = LDA*N;
    int LDBxNRHS = LDB*NRHS;

    float *A1 = (float *)malloc(LDA*N*(sizeof*A1));
    float *A2 = (float *)malloc(LDA*N*(sizeof*A2));
    float *B1 = (float *)malloc(LDB*NRHS*(sizeof*B1));
    float *B2 = (float *)malloc(LDB*NRHS*(sizeof*B2));
    PLASMA_desc *L;
    int *IPIV;

    /* Check if unable to allocate memory */
    if ((!A1)||(!A2)||(!B1)||(!B2)){
        printf("Out of Memory \n ");
        return EXIT_SUCCESS;
    }

    /*Plasma Initialize*/
    PLASMA_Init(cores);
    printf("-- PLASMA is initialized to run on %d cores. \n",cores);

    /* Initialize A1 and A2 Matrix */
    LAPACKE_slarnv_work(IONE, ISEED, LDAxN, A1);
    for ( i = 0; i < N; i++)
        for (  j = 0; j < N; j++)
            A2[LDA*j+i] = A1[LDA*j+i];

    /* Initialize B1 and B2 */
    LAPACKE_slarnv_work(IONE, ISEED, LDBxNRHS, B1);
    for ( i = 0; i < N; i++)
        for ( j = 0; j < NRHS; j++)
            B2[LDB*j+i] = B1[LDB*j+i];

    /* Allocate L and IPIV */
    info = PLASMA_Alloc_Workspace_sgetrf_incpiv(N, N, &L, &IPIV);

    /* LU factorization of the matrix A */
    info = PLASMA_sgetrf_incpiv(N, N, A2, LDA, L, IPIV);

    /* Solve the problem */
    info = PLASMA_strsmpl(N, NRHS, A2, LDA, L, IPIV, B2, LDB);
    info = PLASMA_strsm(PlasmaLeft, PlasmaUpper, PlasmaNoTrans, PlasmaNonUnit,
                        N, NRHS, (float)1.0, A2, LDA, B2, LDB);

    /* Check the solution */
    info_solution = check_solution(N, NRHS, A1, LDA, B1, B2, LDB);

    if ((info_solution != 0)|(info != 0))
       printf("-- Error in SGETRS example ! \n");
    else
       printf("-- Run of SGETRS example successful ! \n");

    free(A1); free(A2); free(B1); free(B2); free(IPIV); free(L);

    PLASMA_Finalize();

    return EXIT_SUCCESS;
}

/*------------------------------------------------------------------------
 *  Check the accuracy of the solution of the linear system
 */

int check_solution(int N, int NRHS, float *A1, int LDA, float *B1, float *B2, int LDB)
{
    int info_solution;
    float Rnorm, Anorm, Xnorm, Bnorm;
    float alpha, beta;
    float *work = (float *)malloc(N*sizeof(float));
    float eps;

    eps = LAPACKE_slamch_work('e');

    alpha = 1.0;
    beta  = -1.0;

    Xnorm = LAPACKE_slange_work(LAPACK_COL_MAJOR, lapack_const(PlasmaInfNorm), N, NRHS, B2, LDB, work);
    Anorm = LAPACKE_slange_work(LAPACK_COL_MAJOR, lapack_const(PlasmaInfNorm), N, N, A1, LDA, work);
    Bnorm = LAPACKE_slange_work(LAPACK_COL_MAJOR, lapack_const(PlasmaInfNorm), N, NRHS, B1, LDB, work);

    cblas_sgemm(CblasColMajor, CblasNoTrans, CblasNoTrans, N, NRHS, N, (alpha), A1, LDA, B2, LDB, (beta), B1, LDB);
    Rnorm = LAPACKE_slange_work(LAPACK_COL_MAJOR, lapack_const(PlasmaInfNorm), N, NRHS, B1, LDB, work);

    printf("============\n");
    printf("Checking the Residual of the solution \n");
    printf("-- ||Ax-B||_oo/((||A||_oo||x||_oo+||B||_oo).N.eps) = %e \n",Rnorm/((Anorm*Xnorm+Bnorm)*N*eps));

    if ( isnan(Rnorm/((Anorm*Xnorm+Bnorm)*N*eps)) || (Rnorm/((Anorm*Xnorm+Bnorm)*N*eps) > 10.0) ){
        printf("-- The solution is suspicious ! \n");
        info_solution = 1;
    }
    else{
        printf("-- The solution is CORRECT ! \n");
        info_solution = 0;
    }

    free(work);

    return info_solution;
}


#include <math.h>
#include <cblas.h>

#ifdef _MSC_VER
# define inline __inline
#endif

/*
 * General Cholesky Delete.
 * Remove an element from the cholesky factorization
 * m = columns
 * n = rows
 *
 * TODO: put transpose as an option
 */
static inline void cholesky_delete_dbl(int m, int n, double *L, int go_out)
{
    double c, s;

    /* delete row go_out */
    double *L1 = L + (go_out * m);
    int i;
    for (i = go_out; i < n - 1; ++i) {
        cblas_dcopy (i + 2, L1 + m , 1, L1,  1);
        L1 += m;
    }

    L1 = L + (go_out * m);
    for (i=go_out; i < n - 1; ++i) {

        cblas_drotg(L1 + i, L1 + i + 1, &c, &s);
        if (L1[i] < 0) {
            /* Diagonals cannot be negative */
            L1[i] = fabs(L1[i]);
            c = -c;
            s = -s;
        }
        L1[i+1] = 0.; /* just for cleanup */
        L1 += m;

        cblas_drot(n - (i + 2), L1 + i, m, L1 + i + 1,
                   m, c, s);
    }
}

static inline void cholesky_delete_flt(int m, int n, float *L, int go_out)
{
    float c, s;

    /* delete row go_out */
    float *L1 = L + (go_out * m);
    int i;
    for (i = go_out; i < n - 1; ++i) {
        cblas_scopy (i + 2, L1 + m , 1, L1,  1);
        L1 += m;
    }

    L1 = L + (go_out * m);
    for (i=go_out; i < n - 1; ++i) {

        cblas_srotg(L1 + i, L1 + i + 1, &c, &s);
        if (L1[i] < 0) {
            /* Diagonals cannot be negative */
            L1[i] = fabsf(L1[i]);
            c = -c;
            s = -s;
        }
        L1[i+1] = 0.; /* just for cleanup */
        L1 += m;

        cblas_srot(n - (i + 2), L1 + i, m, L1 + i + 1,
                   m, c, s);
    }
}


#include "stdio.h"
#include "gsl/gsl_integration.h"
#include "gsl/gsl_errno.h"
//include "limber.h"
#include <gsl/gsl_interp2d.h>
#include <gsl/gsl_spline2d.h>
#include <gsl/gsl_spline.h>
#include <gsl/gsl_monte_vegas.h>
#include <gsl/gsl_math.h>
#include <gsl/gsl_monte.h>
#include <gsl/gsl_sf_bessel.h>

// The integrated C_ell are in general allowed to be zero or negative if
// they describe cross-correlations. We use these statuses to describe
// errors where a log was requested also.
// LIMBER_STATUS_NEGATIVE is probably always an error
// but LIMBER_STATUS_ZERO is not necessarily
#define LIMBER_STATUS_OK 0
#define LIMBER_STATUS_ZERO 1
#define LIMBER_STATUS_NEGATIVE 2
#define LIMBER_STATUS_ERROR 3

// These are the options you can set for
// the Limber integrator.
typedef struct cl_config{
	int n_ell;  // Number of ell values you want in the spline
	int * ell;  // The chosen ell values you want
	double prefactor; //Scaling prefactor
    int status; // did everything go okay?
    double delta_range_factor;
    double log_nu_range;
    double absolute_tolerance;
    double relative_tolerance;
} cl_config;

int cl_integral(cl_config * config, gsl_spline * WX_red,
			    gsl_spline * WY_red, gsl_spline2d * P, gsl_spline * D_chi,
			    double * cl_out, double * cl_err_out);


/* ode-initval/rkf45.c
 *
 * Copyright (C) 2001 Brian Gough
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/* Runge-Kutta-Fehlberg 4(5)*/

#include <config.h>
#include <stdlib.h>
#include <string.h>
#include <gsl/gsl_errno.h>
#include "gsl_odeiv.h"

#include "odeiv_util.h"

/* Runge-Kutta-Fehlberg constants */
static const double ah[] = { 1.0/4.0, 3.0/8.0, 12.0/13.0, 1.0, 1.0/2.0 };

static const double b3[] = { 3.0/32.0, 9.0/32.0 };

static const double b4[] = { 1932.0/2197.0, -7200.0/2197.0, 7296.0/2197.0};

static const double b5[] = { 8341.0/4104.0, -32832.0/4104.0, 29440.0/4104.0, -845.0/4104.0};

static const double b6[] = { -6080.0/20520.0, 41040.0/20520.0, -28352.0/20520.0, 9295.0/20520.0, -5643.0/20520.0};

static const double c1 = 902880.0/7618050.0;
static const double c3 = 3953664.0/7618050.0;
static const double c4 = 3855735.0/7618050.0;
static const double c5 = -1371249.0/7618050.0;
static const double c6 = 277020.0/7618050.0;

static const double ec[] = { 0.0,
  1.0 / 360.0,
  0.0,
  -128.0 / 4275.0,
  -2197.0 / 75240.0,
  1.0 / 50.0,
  2.0 / 55.0
};

typedef struct
{
  double *k1;
  double *k2;
  double *k3;
  double *k4;
  double *k5;
  double *k6;
  double *y0;
  double *ytmp;
}
rkf45_state_t;

static void *
rkf45_alloc (size_t dim)
{
  rkf45_state_t *state = (rkf45_state_t *) malloc (sizeof (rkf45_state_t));

  if (state == 0)
    {
      GSL_ERROR_NULL ("failed to allocate space for rkf45_state", GSL_ENOMEM);
    }

  state->k1 = (double *) malloc (dim * sizeof (double));

  if (state->k1 == 0)
    {
      free (state);
      GSL_ERROR_NULL ("failed to allocate space for k1", GSL_ENOMEM);
    }

  state->k2 = (double *) malloc (dim * sizeof (double));

  if (state->k2 == 0)
    {
      free (state->k1);
      free (state);
      GSL_ERROR_NULL ("failed to allocate space for k2", GSL_ENOMEM);
    }

  state->k3 = (double *) malloc (dim * sizeof (double));

  if (state->k3 == 0)
    {
      free (state->k2);
      free (state->k1);
      free (state);
      GSL_ERROR_NULL ("failed to allocate space for k3", GSL_ENOMEM);
    }

  state->k4 = (double *) malloc (dim * sizeof (double));

  if (state->k4 == 0)
    {
      free (state->k3);
      free (state->k2);
      free (state->k1);
      free (state);
      GSL_ERROR_NULL ("failed to allocate space for k4", GSL_ENOMEM);
    }

  state->k5 = (double *) malloc (dim * sizeof (double));

  if (state->k5 == 0)
    {
      free (state->k4);
      free (state->k3);
      free (state->k2);
      free (state->k1);
      free (state);
      GSL_ERROR_NULL ("failed to allocate space for k5", GSL_ENOMEM);
    }

  state->k6 = (double *) malloc (dim * sizeof (double));

  if (state->k6 == 0)
    {
      free (state->k5);
      free (state->k4);
      free (state->k3);
      free (state->k2);
      free (state->k1);
      free (state);
      GSL_ERROR_NULL ("failed to allocate space for k6", GSL_ENOMEM);
    }

  state->y0 = (double *) malloc (dim * sizeof (double));

  if (state->y0 == 0)
    {
      free (state->k6);
      free (state->k5);
      free (state->k4);
      free (state->k3);
      free (state->k2);
      free (state->k1);
      free (state);
      GSL_ERROR_NULL ("failed to allocate space for y0", GSL_ENOMEM);
    }

  state->ytmp = (double *) malloc (dim * sizeof (double));

  if (state->ytmp == 0)
    {
      free (state->y0);
      free (state->k6);
      free (state->k5);
      free (state->k4);
      free (state->k3);
      free (state->k2);
      free (state->k1);
      free (state);
      GSL_ERROR_NULL ("failed to allocate space for ytmp", GSL_ENOMEM);
    }

  return state;
}

static int
rkf45_apply (void *vstate,
	    size_t dim,
	    double t,
	    double h,
	    double y[],
	    double yerr[],
	    const double dydt_in[],
	    double dydt_out[], const gsl_odeiv_system * sys)
{
  rkf45_state_t *state = (rkf45_state_t *) vstate;

  size_t i;
  int status = 0;

  double *const k1 = state->k1;
  double *const k2 = state->k2;
  double *const k3 = state->k3;
  double *const k4 = state->k4;
  double *const k5 = state->k5;
  double *const k6 = state->k6;
  double *const ytmp = state->ytmp;

  /* k1 step */
  if (dydt_in != NULL)
    {
      DBL_MEMCPY (k1, dydt_in, dim);
    }
  else
    {
      int s = GSL_ODEIV_FN_EVAL (sys, t, y, k1);
      GSL_STATUS_UPDATE (&status, s);
    }

  for (i = 0; i < dim; i++)
    ytmp[i] = y[i] +  ah[0] * h * k1[i];

  /* k2 step */
  {
    int s = GSL_ODEIV_FN_EVAL (sys, t + ah[0] * h, ytmp, k2);
    GSL_STATUS_UPDATE (&status, s);
  }
  for (i = 0; i < dim; i++)
    ytmp[i] = y[i] + h * (b3[0] * k1[i] + b3[1] * k2[i]);

  /* k3 step */
  {
    int s = GSL_ODEIV_FN_EVAL (sys, t + ah[1] * h, ytmp, k3);
    GSL_STATUS_UPDATE (&status, s);
  }
  for (i = 0; i < dim; i++)
    ytmp[i] = y[i] + h * (b4[0] * k1[i] + b4[1] * k2[i] + b4[2] * k3[i]);

  /* k4 step */
  {
    int s = GSL_ODEIV_FN_EVAL (sys, t + ah[2] * h, ytmp, k4);
    GSL_STATUS_UPDATE (&status, s);
  }
  for (i = 0; i < dim; i++)
    ytmp[i] =
      y[i] + h * (b5[0] * k1[i] + b5[1] * k2[i] + b5[2] * k3[i] +
		  b5[3] * k4[i]);

  /* k5 step */
  {
    int s = GSL_ODEIV_FN_EVAL (sys, t + ah[3] * h, ytmp, k5);
    GSL_STATUS_UPDATE (&status, s);
  }
  for (i = 0; i < dim; i++)
    ytmp[i] =
      y[i] + h * (b6[0] * k1[i] + b6[1] * k2[i] + b6[2] * k3[i] +
		  b6[3] * k4[i] + b6[4] * k5[i]);

  /* k6 step and final sum */
  {
    int s = GSL_ODEIV_FN_EVAL (sys, t + ah[4] * h, ytmp, k6);
    GSL_STATUS_UPDATE (&status, s);
  }

  for (i = 0; i < dim; i++)
    {
      const double d_i = c1 * k1[i] + c3 * k3[i] + c4 * k4[i] + c5 * k5[i] + c6 * k6[i];
      y[i] += h * d_i;
      if (dydt_out != NULL)
	dydt_out[i] = d_i;
    }

  /* difference between 4th and 5th order */
  for (i = 0; i < dim; i++)
    yerr[i] =
      h * (ec[1] * k1[i] + ec[3] * k3[i] + ec[4] * k4[i] + ec[5] * k5[i] +
	   ec[6] * k6[i]);

  return status;
}

static int
rkf45_reset (void *vstate, size_t dim)
{
  rkf45_state_t *state = (rkf45_state_t *) vstate;

  DBL_ZERO_MEMSET (state->k1, dim);
  DBL_ZERO_MEMSET (state->k2, dim);
  DBL_ZERO_MEMSET (state->k3, dim);
  DBL_ZERO_MEMSET (state->k4, dim);
  DBL_ZERO_MEMSET (state->k5, dim);
  DBL_ZERO_MEMSET (state->k6, dim);
  DBL_ZERO_MEMSET (state->ytmp, dim);

  return GSL_SUCCESS;
}

static unsigned int
rkf45_order (void *vstate)
{
  rkf45_state_t *state = (rkf45_state_t *) vstate;
  state = 0; /* prevent warnings about unused parameters */
  return 5;
}

static void
rkf45_free (void *vstate)
{
  rkf45_state_t *state = (rkf45_state_t *) vstate;

  free (state->ytmp);
  free (state->y0);
  free (state->k6);
  free (state->k5);
  free (state->k4);
  free (state->k3);
  free (state->k2);
  free (state->k1);
  free (state);
}

static const gsl_odeiv_step_type rkf45_type = { "rkf45",	/* name */
  1,				/* can use dydt_in */
  0,				/* gives exact dydt_out */
  &rkf45_alloc,
  &rkf45_apply,
  &rkf45_reset,
  &rkf45_order,
  &rkf45_free
};

const gsl_odeiv_step_type *gsl_odeiv_step_rkf45 = &rkf45_type;


/* Copyright (c) 2014, Giuseppe Argentieri <giuseppe.argentieri@ts.infn.it>

 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. 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.
 *
 * 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 HOLDER 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.
 *
 */

/*
 * =====================================================================================
 *
 *       Filename:  Reg_sc.c
 *
 *    Description:
 *
 *        Version:  1.0
 *        Created:  21/04/2014 12:27:24
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  Giuseppe Argentieri (ga), giuseppe.argentieri@ts.infn.it
 *   Organization:  Università degli Studi di Trieste
 *
 * =====================================================================================
 */

/*
   Re g_sc is made of 4 integrands: 2 with principal values and 2 without.

   The common factor function is k_func = alpha*k*exp(-k/omega_c)/tanh(b*k/2)

   The poles are in (omega1-O) and (omega1+O)

   The sign of the four fractions

   1/(k+(omega1+O)) , 1/(k+(omega1-O)) , 1/(k-(omega1-O)) , 1/(k-(omega1+O))

   are

   + , - , + , -
*/

#include <stdio.h>

#include "funcs.h"
#include <gsl/gsl_integration.h>

/*
 * ===  FUNCTION  ======================================================================
 *         Name:  k_func
 *  Description:  Common integrand factor
 * =====================================================================================
 */
double k_func ( double k, void* params )
{

	/* Copying parameters */
	struct f_params* pars = (struct f_params*) params ;
	double o_c, b, O, o_1, alpha ;
	assign_p( pars, &o_c, &b, &O, &o_1 ) ;
	alpha = pars->alpha ;

	double temp = alpha*k*exp(-k/o_c) ;
	double val = temp/tanh(b*k/2) ;

	return val;
}		/* -----  end of function k_func  ----- */

/*
 * ===  FUNCTION  ======================================================================
 *         Name:  k_func_1
 *  Description:  First integrand: k_func/(k+(omega1+O))
 * =====================================================================================
 */
double k_func_1 ( double k, void* params )
{
	/* Copying parameters */
	struct f_params* pars = (struct f_params*) params ;
	double o_c, b, O, o_1 ;
	assign_p( pars, &o_c, &b, &O, &o_1 ) ;

	/* Invoking k_func and returning value */
	double v , v1 ;
	v = k_func( k, pars ) ;
	v1 = v/(k+(o_1+O)) ;

	return v1 ;
}		/* -----  end of function k_func_1  ----- */

/*
 * ===  FUNCTION  ======================================================================
 *         Name:  first_int
 *  Description:  Integrating k_func/(k+(omega1+O))
 * =====================================================================================
 */
int first_int ( double* val, double* error, void* params )
{
	/* Copying parameters */
	struct f_params* pars = (struct f_params*) params ;
	double o_c, b, O, o_1 ;
	assign_p( pars, &o_c, &b, &O, &o_1 ) ;

	/* Allocating workspace for integration */
	gsl_integration_workspace* first_int_ws =
		gsl_integration_workspace_alloc(WS_SZ) ;

	gsl_function F ;
	F.function = &k_func_1 ;
	F.params = pars ;

	/* Integration over (0,+Infinity) */
	double res, err ;
	int status = gsl_integration_qagiu( &F, 0, 1e-9, 1e-3, WS_SZ, first_int_ws,
			&res, &err ) ;

	*val = res ; *error = err ;

	gsl_integration_workspace_free(first_int_ws) ;
	return status;
}		/* -----  end of function first_int  ----- */

/*
 *      FUNCTION
 *         Name:  k_func_2
 *  Description:  Second integrand: k_func/(k+omega1-O)
 *
 */
double k_func_2 ( double k , void* params )
{
	/* Copying parameters */
	struct f_params* pars = (struct f_params*) params ;
	double o_c, b, O, o_1 ;
	assign_p( pars, &o_c, &b, &O, &o_1 ) ;

	/* Invoking k_func and returning value */
	double v, v2 ;
	v = k_func( k, pars ) ;
	v2 = v/(k+(o_1-O)) ;

	return v2;
}		/* -----  end of function k_func_2  ----- */

/*
 *      FUNCTION
 *         Name:  second_int
 *  Description:  Integration k_func/(k+(omega1-O))
 *
 */
int second_int ( double* val, double* error, void* params )
{
	/* Copying parameters */
	struct f_params* pars = (struct f_params*) params ;
	double o_c, b, O, o_1 ;
	assign_p( pars, &o_c, &b, &O, &o_1 ) ;

	/* Allocating workspace for integration */
	gsl_integration_workspace* second_int_ws =
		gsl_integration_workspace_alloc(WS_SZ) ;

	gsl_function F;
	F.function = &k_func_2 ;
	F.params = pars ;

	double res, err ;
	int status = gsl_integration_qagiu( &F, 0, 1e-9, 1e-3, WS_SZ, second_int_ws,
			&res, &err ) ;
	*val = res ; *error = err ;

	gsl_integration_workspace_free(second_int_ws) ;

	return status;
}		/* -----  end of function second_int  ----- */

/*
 *      FUNCTION
 *         Name:  k_func_3
 *  Description:  k_func/(k-(omega1-O)) to be integrated on the tail +inf
 *
 */
double k_func_3 ( double k, void* params )
{
	/* Copying parameters */
	struct f_params* pars = (struct f_params*) params ;
	double o_c, b, O, o_1 ;
	assign_p( pars, &o_c, &b, &O, &o_1 ) ;

	double temp , val ;
	temp = k_func( k, pars ) ;
	val = temp/(k-(o_1-O)) ;

	return val;
}		/* -----  end of function k_func_3  ----- */

/*
 *      FUNCTION
 *         Name:  third_int
 *  Description:  Integration of the p.v. k_func/(k-(omega1-O))
 *
 */
int third_int ( double* val, double* err, void* params )
{
	/* Copying parameters */
	struct f_params* pars = (struct f_params*) params ;
	double o_c, b, O, o_1 ;
	assign_p( pars, &o_c, &b, &O, &o_1 ) ;

	/* Integration of the p.v. */
	gsl_function F ;
	F.function = &k_func ;
	F.params = pars ;

	gsl_integration_workspace* third_int_ws =
		gsl_integration_workspace_alloc(WS_SZ) ;

	double v1 , err1 ;
	int status1 = gsl_integration_qawc( &F, (o_1-O)/2, 3*(o_1-O)/2, o_1-O,
			1e-9, 1e-3, WS_SZ, third_int_ws, &v1, &err1 ) ;

	gsl_integration_workspace_free(third_int_ws) ;

	/* Integration to +infinity */
	gsl_function G ;
	G.function = &k_func_3 ;
	G.params = pars ;

	gsl_integration_workspace* third_int_ws_2 =
		gsl_integration_workspace_alloc(WS_SZ) ;

	double v2, err2 ;
	int status2 = gsl_integration_qagiu( &G, 3*(o_1-O)/2, 1e-9, 1e-3, WS_SZ,
			third_int_ws_2, &v2, &err2 ) ;

	gsl_integration_workspace_free(third_int_ws_2) ;

	/* Integration on ( 0, (o_1-O)/2 ) */
	gsl_integration_workspace* third_int_ws_3 =
		gsl_integration_workspace_alloc(WS_SZ) ;

	gsl_function H ;
	H.function = &k_func_3 ;
	H.params = pars ;

	double v3, err3 ;
	int status3 = gsl_integration_qag( &H, 0, (o_1-O)/2, 1e-9, 1e-3, WS_SZ, 6,
			third_int_ws_3, &v3, &err3 ) ;

	gsl_integration_workspace_free(third_int_ws_3) ;

	*val = v1 + v2 + v3 ; *err = err1 + err2 + err3 ;

	return status1 + status2 + status3 ;
}		/* -----  end of function third_int  ----- */

/*
 *      FUNCTION
 *         Name:  k_func_4
 *  Description:  k_func/(k-(omega1+O))
 *
 */
double k_func_4 ( double k, void* params )
{
	/* Copying parameters */
	struct f_params* pars = (struct f_params*) params ;
	double o_c, b, O, o_1 ;
	assign_p( pars, &o_c, &b, &O, &o_1 ) ;

	double temp , val ;
	temp = k_func( k, pars ) ;
	val = temp/(k-(o_1+O)) ;

	return val;
}		/* -----  end of function k_func_4  ----- */

/*
 *      FUNCTION
 *         Name:  fourth_integ
 *  Description:  Integration of the p.v. kfunc/(k-(omega1+O))
 *
 */
int fourth_int ( double* val, double* err, void* params )
{
	/* Copying parameters */
	struct f_params* pars = (struct f_params*) params ;
	double o_c, b, O, o_1 ;
	assign_p( pars, &o_c, &b, &O, &o_1 ) ;

	/* Integration of the p.v. */
	gsl_function F ;
	F.function = &k_func ;
	F.params = pars ;

	gsl_integration_workspace* fourth_int_ws =
		gsl_integration_workspace_alloc(WS_SZ) ;

	double v1 , err1 ;
	int status1 = gsl_integration_qawc( &F, (o_1+O)/2, 3*(o_1+O)/2, o_1+O,
			1e-9, 1e-3, WS_SZ, fourth_int_ws, &v1, &err1 ) ;

	gsl_integration_workspace_free(fourth_int_ws) ;

	/* Integration to +infinity */
	gsl_function G ;
	G.function = &k_func_4 ;
	G.params = pars ;

	gsl_integration_workspace* fourth_int_ws_2 =
		gsl_integration_workspace_alloc(WS_SZ) ;

	double v2, err2 ;
	int status2 = gsl_integration_qagiu( &G, 3*(o_1+O)/2, 1e-9, 1e-3, WS_SZ,
			fourth_int_ws_2, &v2, &err2 ) ;

	gsl_integration_workspace_free(fourth_int_ws_2) ;

	/* Integration on ( 0 , (O+o_1)/2 ) */
	gsl_integration_workspace* fourth_int_ws_3 =
		gsl_integration_workspace_alloc(WS_SZ) ;

	gsl_function K ;
	K.function = &k_func_4 ;
	K.params = pars ;
	double v3, err3 ;

	int status3 = gsl_integration_qag( &K, 0, (O+o_1)/2, 1e-9, 1e-3, WS_SZ,
			GSL_INTEG_GAUSS61, fourth_int_ws_3, &v3, &err3 ) ;

	gsl_integration_workspace_free(fourth_int_ws_3) ;

	*val = v1 + v2 + v3 ; *err = err1 + err2 + err3 ;

	return status1 + status2 + status3;
}		/* -----  end of function fourth_integ  ----- */

/*
 *      FUNCTION
 *         Name:  re_gsc
 *  Description:
 *
 */
int re_gsc ( double* result, double* error, void* params )
{
	/* Copying parameters */
	struct f_params* pars = (struct f_params*) params ;
	double o_c, b, O, o_1 ;
	assign_p( pars, &o_c, &b, &O, &o_1 ) ;

	double v1, v2, v3, v4, err1, err2, err3, err4 ;
	int status = first_int( &v1, &err1, pars ) + second_int( &v2, &err2, pars )
		+ third_int( &v3, &err3, pars ) + fourth_int( &v4, &err4, pars ) ;
	*result = -(v1 - v2 + v3 - v4)/4.0 ;
	*error = (err1 + err2 + err3 + err4)/4.0 ;

	return status;
}		/* -----  end of function re_gsc  ----- */

/*
 *      FUNCTION
 *         Name:  re_gcs
 *  Description:  Re g_cs is the same of Re g_sc but with O and o_1 exchanged.
 *  		  Therefore the signs are
 *
 *  		  + , + , - , -
 *
 */
int re_gcs ( double* result, double* error, void* params )
{
	/* Copying parameters */
	struct f_params* pars = (struct f_params*) params ;
	double o_c, b, O, o_1 ;
	assign_p( pars, &o_c, &b, &O, &o_1 ) ;

	double v1, v2, v3, v4, err1, err2, err3, err4 ;
	int status =  first_int( &v1, &err1, pars ) + second_int( &v2, &err2, pars )
		+ third_int( &v3, &err3, pars ) + fourth_int( &v4, &err4, pars ) ;

	*result = -(v1 + v2 - v3 - v4)/4 ; *error = (err1 + err2 + err3 + err4)/4 ;

	return status;
}		/* -----  end of function re_gcs  ----- */


/**
 * Various binning and weighting routines for aperture pixel tables.
 *
*/

#include <math.h>

#include <gsl/gsl_vector.h>
#include <gsl/gsl_matrix.h>

#include "spc_trace_functions.h"
#include "aXe_utils.h"
#include "aXe_grism.h"
#include "spc_spc.h"
#include "spc_driz.h"
#include "aper_conf.h"
#include "spce_output.h"
#include "spc_optimum.h"

#define SQR(x) ((x)*(x))
#define MIN(x,y) (((x)<(y))?(x):(y))
#define MAX(x,y) (((x)>(y))?(x):(y))

/*
gsl_matrix *
create_weightimage(ap_pixel *ap_p, const beam actbeam,
		   const aperture_conf *conf, const double exptime,
		   const double sky_cps)
{
  drzstamp_dim  dimension;
  drzstamp     *modvar;
  gsl_matrix   *weights;

  dimension = get_resample_dims(ap_p, actbeam);

  if (!dimension.resolution)
    return get_default_weight();

  compute_model_ivar(ap_p, conf->rdnoise, exptime, sky_cps);

  modvar = compute_modvar(ap_p, actbeam, dimension);

  weights = comp_allweight(modvar);

  return weights;
}
*/

/**
 * Function: compute_modvar
 *
 *
 * Parameters:
 * @param ap_p      - the PET table
 * @param actbeam   - beam to compute the modvar structure for
 * @param dimension - dimenasion of the images
 * @param modvar    - the structure to fill
 */
drzstamp *
compute_modvar(ap_pixel *ap_p, const beam actbeam,
	       const drzstamp_dim dimension)
{
  ap_pixel *cur_p;
  ap_pixel *tmp_p;

  quadrangle quad;

  double jacob;

  int jcen, icen;
  int jupp, iupp;
  int jlow, ilow;

  int ii, jj;
  int stpi, stpj;

  double maxarr, arr;
  //int iim, jjm;
  double value, allweig, weig;
  double stpc;
  double totweight;
  drzstamp *modvar;

  // allocate memory for the result structure
  modvar = alloc_drzstamp(dimension);

  // allocate memory
  tmp_p = (ap_pixel *) malloc(sizeof(ap_pixel));

  // go over each pixel
  for (cur_p = ap_p; cur_p->p_x != -1; cur_p++)
    {
      // Skip this pixel if it was not actually used
      if (fabs(cur_p->dist)>actbeam.width+0.5)
        continue;

      // transfer values to the temporary pixel
      tmp_p->lambda  = cur_p->xi;
      tmp_p->dist    = cur_p->dist;
      tmp_p->dxs     = cur_p->dxs;
      tmp_p->dlambda = 1.0;

      // create the quadrangle for the current pixel
      quad = get_quad_from_pixel(tmp_p, actbeam.orient, dimension);

      // get the jacobian (well, easy here)
      // the term "cos(cur_p->dxs)" must be there
      // to correct the enlargement necessary
      // to cover the whole lambda-crossdispersion area!
      // NOT COMPLETELY understood
      jacob = cos(cur_p->dxs);

      // get the central pixel (icen, jcen) of the current PET-pixel
      icen = (int) floor(cur_p->xi   - dimension.xstart+.5);
      jcen = (int) floor(cur_p->dist - dimension.ystart+.5);

      // get the uper and lower extend of the quadrangle in x
      iupp = (int)floor(quad.xmax - (double)icen + 0.5)+1;
      ilow = (int)floor(quad.xmin - (double)icen + 0.5);

      // get the uper and lower extend of the quadrangle in x
      jupp = (int)floor(quad.ymax - (double)jcen + 0.5)+1;
      jlow = (int)floor(quad.ymin - (double)jcen + 0.5);

      maxarr=0.0;
      totweight = 0.0;
      // go over the extend in x
      for (ii=ilow;ii<iupp;ii++)
	{
	  // go over the extend in x
	  for (jj=jlow;jj<jupp;jj++)
	    {

	      // get the coordinates of the current output pixel
	      stpi = icen+ii;
	      stpj = jcen+jj;

	      // check whether the current output pixel is within
	      // the stamp image; continue if not
	      if ((stpi>=dimension.xsize)||(stpi<0)
		  ||(stpj>=dimension.ysize)||(stpj<0))
		continue;

	      // get the area which falls onto the current output pixel
	      arr = boxer(stpi,stpj,quad.x,quad.y);

	      if (arr > 0.0)
		{
		  // get the already existing counts and weights
		  stpc = gsl_matrix_get(modvar->counts,stpi,stpj);
		  weig = gsl_matrix_get(modvar->weight,stpi,stpj);

		  // compute the new, total weight of the current output pixel
		  allweig = weig + arr*cur_p->weight;

		  // do a weighted sum of the count value at the current
		  // output pixel
		  value = (stpc*weig + arr*cur_p->model*cur_p->weight*jacob)
		    / (allweig);

		  // store the new count value and the new weight
		  gsl_matrix_set(modvar->counts,stpi,stpj,value);
		  gsl_matrix_set(modvar->weight,stpi,stpj,allweig);
		}
	    }
	}
    }

  // freep the temporary PET pixel
  free(tmp_p);

  // return the result
  return modvar;
}

void
shift_tracelength(ap_pixel *ap_p, const double xi_shift)
{
  ap_pixel *cur_p;

  // go over each PET pixel
  for (cur_p = ap_p; cur_p->p_x != -1; cur_p++)
    {
      // apply a shift in trace distance
      cur_p->xi += xi_shift;
    }
}

/**
 * Function: prepare_inv_variance
 * The function prepares the inverse model variances
 * for a data set containing an object and a background PET.
 * This is done in subroutines which compute the
 * theoretical inverse pixel variance following
 * different methods for the object and the background PET.
 *
 * Parameters:
 * @param ap_p     - the object PET table
 * @param bg_p     - the background PET table
 * @param dobck    - integer indicating background subtraction
 * @param conf     - the configuration structure
 * @param exptime  - the exposure time
 * @param sky_cps  - the sky background
 */
void
prepare_inv_variance(ap_pixel *ap_p, ap_pixel *bg_p, const int dobck,
		     const aperture_conf *conf, const double exptime,
		     const double sky_cps, const double xi_shift)
{

  if (dobck)
    {
      // shift both, the object and the background PET
      shift_tracelength(ap_p, xi_shift);
      shift_tracelength(bg_p, xi_shift);

      // compute the inverse variance for the object + background PET
      compute_total_ivar(bg_p, bg_p, conf->rdnoise, exptime, sky_cps);
    }
  else
    {
      // shift both, the object PET
      shift_tracelength(ap_p, xi_shift);

      // compute the inverse variance for the object PET
      compute_object_ivar(ap_p, conf->rdnoise, exptime, sky_cps);
    }
}

/**
 * Function: compute_model_ivar
 * The function computes for each PET pixel the
 * associated inverse variance value. The input
 * for variance and inverse variance are the
 * model value, the contamination value, the
 * constant sky background value and the readnoise.
 * The computed inverse variance value is stored
 * in the weight entry of the PET.
 * The function also offers the possibility to
 * shift the trace distance by a fixed amount.
 *
 * Parameters:
 * @param ap_p     - the PET table
 * @param rdnoise  - the readnoise value
 * @param exptime  - the exposure time
 * @param sky_cps  - the sky background
 * @param xi_shift - shift in trace distance
 */
void
compute_object_ivar(ap_pixel *ap_p, const double rdnoise,
		    const double exptime, const double sky_cps)
{
  double variance;
  double sqr_rdnoise;
  double sqr_exptime;
  ap_pixel *cur_p;

  // square the readnoise
  sqr_rdnoise = rdnoise*rdnoise;

  // square the exposure time
  sqr_exptime = exptime*exptime;

  // go over each PET pixel
  for (cur_p = ap_p; cur_p->p_x != -1; cur_p++)
    {
	// compute the variance
	variance = ((cur_p->model + cur_p->contam + sky_cps) * exptime
		    + sqr_rdnoise) / sqr_exptime;

      // store the inverse variance
      cur_p->weight = 1.0/variance;
    }
}

/**
 * Function: compute_value_ivar
 * The function computes for each PET pixel the
 * associated inverse variance value. The input
 * to derive the variance is the count value and
 * the readnoise.
 * The computed inverse variance value is stored
 * in the weight entry of the PET.
 * The function also offers the possibility to
 * shift the trace distance by a fixed amount.
 *
 * Parameters:
 * @param ap_p    - the PET table
 * @param rdnoise  - the readnoise value
 * @param exptime - the exposure time
 * @param sky_cps - the sky background
 * @param xi_shift - shift in trace distance
 */
void
compute_total_ivar(ap_pixel *ap_p, const ap_pixel *bg_p, const double rdnoise,
		   const double exptime, const double sky_cps)
{
  double variance;
  double sqr_rdnoise;
  double sqr_exptime;

  ap_pixel *cur_p;
  const ap_pixel *bac_p;

  // square the readnoise
  sqr_rdnoise = rdnoise*rdnoise;

  // square the exposure time
  sqr_exptime = exptime*exptime;

  // go over each PET pixel
  bac_p = bg_p;
  for (cur_p = ap_p; cur_p->p_x != -1; cur_p++)
    {
      // check that the foreground and background
      // PET element describe the same pixel
      if (bac_p->x != cur_p->x || bac_p->y != cur_p->y)
	aXe_message (aXe_M_FATAL, __FILE__, __LINE__,
		     "aXe_PET2SPC: Background PET and Object PET "
		     "have different pixel orders in PET's.\n");
      else
      // compute the variance
      variance = ((cur_p->count + bac_p->count + sky_cps) * exptime + sqr_rdnoise) / sqr_exptime;

      // store the inverse variance
      cur_p->weight = 1.0/variance;

      // increase the background pointer
      bac_p++;
    }
}

/**
 * Function: alloc_drzstamp
 * Allocates memory for a drizzle stamp structure
 * according the the dimension specified in the input.
 *
 * Parameters:
 * @param dimension - the dimesions to allocate
 *
 * Returns:
 * @return ret  - the drizzle stamp image allocated
 */
drzstamp *
alloc_drzstamp(const drzstamp_dim dimension)
{
  drzstamp   *ret;
  gsl_matrix *counts;
  gsl_matrix *weight;

  ret = (drzstamp *) malloc(sizeof(drzstamp));

  /* Allocate the stamp matrix*/
  /* Fill stamp with NaN values */
  counts = gsl_matrix_alloc(dimension.xsize,dimension.ysize);
  //  gsl_matrix_set_all(counts, GSL_NAN);
  gsl_matrix_set_all(counts, 0.0);

  /* Allocate the weight matrix*/
  /* Fill weight with 0.0 values */
  weight = gsl_matrix_alloc(dimension.xsize,dimension.ysize);
  gsl_matrix_set_all(weight, 0.0);

  // fill the output structure
  ret->counts = counts;
  ret->weight = weight;

  // return the allocated structure
  return ret;
}

/**
 * Function: get_all_dims
 * The function derives the dimensional information
 * on the foreground and, if available, on the
 * background PET. The dimensions are compared, and
 * in case of inequality an error is thrown.
 * If the dimensions are equal, one of them is
 * returned.
 *
 * Parameters:
 * @param ap_p    - the table of aperture pixels
 * @param bg_p    - the table of background pixels
 * @param actbeam - the beam to compute the dimensions for
 * @param dobck   - integer indicating background subtraction
 *
 * Returns:
 * @return ret  - the dimension structure
 */
drzstamp_dim
get_all_dims(const ap_pixel *ap_p, const ap_pixel *bg_p, const beam actbeam,
	     const int dobck)
{
  drzstamp_dim ret;
  drzstamp_dim bck;

  // get the aperture PET diemnsion
  ret = get_resample_dims(ap_p, actbeam);

  if (dobck)
    {
      // get the background PET dimension
      bck = get_resample_dims(bg_p, actbeam);

      // check whether the two dimensions
      // are equal, throw an error if not
      if (ret.resolution != bck.resolution
	  || ret.xstart  != bck.xstart
	  || ret.ystart  != bck.ystart
	  || ret.xsize   != bck.xsize
	  || ret.ysize   != bck.ysize)
	aXe_message (aXe_M_FATAL, __FILE__, __LINE__,
		     "aXe_PET2SPC: Background PET and Object PET "
		     "do not have the same dimension.\n");
    }

  // return one of the dimensional infos
  return ret;
}

/**
 * Function: get_resample_dims
 * The function parses through a PET table and determines
 * the dimension of the image in the coordinates
 * trace distance - cross-dispersion direction. Those
 * dimensions are stored in a special structure and
 * returned to the calling routine.
 *
 * Parameters:
 * @param ap_p    - the table of aperture pixels
 * @param actbeam - the beam to compute the dimensions for
 *
 * Returns:
 * @return ret  - the dimension structure
 */
drzstamp_dim
get_resample_dims(const ap_pixel *ap_p, const beam actbeam)
{
  drzstamp_dim ret;

  const ap_pixel *cur_p;

  int i_xint, i_dist;
  int imin_xint=0, imin_dist=0;
  int imax_xint=0, imax_dist=0;

  int npixel = 0;

  // immediately return empty PET's
  if (ap_p==NULL)
    return get_default_dim();

  // set the tmp pixel to the table start
  cur_p = ap_p;

  // initialize some relevant numbers
  // based on the first pixel
  i_xint = floor(cur_p->xi   + 0.5);
  i_dist = floor(cur_p->dist + 0.5);
  imin_xint = i_xint;
  imax_xint = i_xint;
  imin_dist = i_dist;
  imax_dist = i_dist;

  // go over all pixels, check
  // for new MIN's ans MAX's
  for (cur_p = ap_p; cur_p->p_x != -1; cur_p++)
    {
      // Skip the pixel if it was not actually used
      if (fabs(cur_p->dist)>actbeam.width+.5)
	continue;

      // compute the rounded value in (x,y)
      i_xint = floor(cur_p->xi   + 0.5);
      i_dist = floor(cur_p->dist + 0.5);

      // check for extrema in x
      imin_xint = MIN(imin_xint, i_xint);
      imax_xint = MAX(imax_xint, i_xint);

      // check for extrema in y
      imin_dist = MIN(imin_dist, i_dist);
      imax_dist = MAX(imax_dist, i_dist);

      // enhance the pixel counter
      npixel++;
    }

  // in case there were no valid pixels,
  // return a dummy structure
  if (!npixel)
    return get_default_dim();

  // fill the structure with the correct values
  ret.resolution = 1.0;
  ret.xstart     = imin_xint - NSPARE_PIX;
  ret.ystart     = imin_dist - NSPARE_PIX;
  ret.xsize      = imax_xint - imin_xint + 2 * NSPARE_PIX;
  ret.ysize      = imax_dist - imin_dist + 2 * NSPARE_PIX;

  // return the structure
  return ret;
}

/**
 * Function: get_default_dim
 * The function creates and returns the default
 * dimension structure. The dewfault dimension
 * structure contains a 0.0 in all fields.
 *
 * Returns:
 * @return res  - the dimension structure
 */
drzstamp_dim
get_default_dim()
{
  drzstamp_dim ret;

  // just set everything to zero
  ret.resolution=0.0;
  ret.xstart = 0;
  ret.ystart = 0;
  ret.xsize  = 0;
  ret.ysize  = 0;

  // return the result
  return ret;
}

/**
 * Function: get_default_modvar
 * The function computes the default drizzle stamp
 * structure, which in this case is used for
 * tha calculation of the resampled variance and
 * profile image.
 *
 * Returns:
 * @return res  - the resulting drzstamp structure
 */
drzstamp *
get_default_modvar()
{
  gsl_matrix *counts;
  gsl_matrix *weight;
  drzstamp *res;

  res = (drzstamp *) malloc(sizeof(drzstamp));

  /* Create a dummy stamp image */
  counts = gsl_matrix_alloc(10,10);
  weight = gsl_matrix_alloc(10,10);
  /* Fill stamp with 0.0 values */
  gsl_matrix_set_all(counts, 0.0);
  gsl_matrix_set_all(weight, 0.0);

  // fill the output
  res->counts = counts;
  res->weight = weight;

  // return the output
  return res;
}

gsl_matrix *
get_default_weight()
{
  gsl_matrix *res;

  res = gsl_matrix_alloc(10,10);
  gsl_matrix_set_all(res, 0.0);

  // return the output
  return res;
}

/*
 * Function: comp_opt_weight
 * The function uses the model map and and the variance
 * map and computes optimale weigths according to the
 * Hoorne method.
 * Pixels outside of the extraction
 * area are set to 1000.0
 *
 * Parameters:
 * @param mod_map - the matrix with the model values
 * @param var_map - the matrix with the variance values
 * @param ob      - the object structure
 *
 * Returns:
 * @return weight - the matrix with the exposure time weights
 */
gsl_matrix *
comp_allweight(drzstamp *modvar)
{
  gsl_matrix *weight;

  double mod_sum, weight_sum;
  double contr, norm, allweight;
  double mod_val;
  double act_weight=0;

  //int beamInt = 0;
  int i, j;

  // allocate the weight matrix and set the default
  weight = gsl_matrix_alloc(modvar->counts->size1, modvar->counts->size2);
  gsl_matrix_set_all(weight, 1000.0);

  //* go over all columns
  for (i=0; i < (int)modvar->counts->size1; i++)
    {
      mod_sum = 0.0;
      contr = 0.0;
      allweight = 0.0;
      weight_sum=0.0;

      // determine for each column the total model counts
      // and the number of pixels with non-zero model_counts
      for (j=0; j < (int)modvar->counts->size2; j++)
	{
	  mod_sum = mod_sum + gsl_matrix_get(modvar->counts, i, j);
	  contr = contr + 1.0;
	}

      // check whether the column has model values.
      // normalize the model values and compute
      // optimal weights if yes.
      if (mod_sum > 0.0)
	{
	  //* determine the mean model value
	  //	  norm = mod_sum / contr;
	  norm = mod_sum;

	  // go over each row
	  for (j=0; j < (int)modvar->counts->size2; j++)
	    {
	      // normalize the model counts
	      mod_val = gsl_matrix_get(modvar->counts, i, j)/norm;

	      // store the normalized model counts
	      gsl_matrix_set(modvar->counts, i, j, mod_val);

	      // add up the normalization value for the optimal weights
	      weight_sum = weight_sum
		+ mod_val*mod_val*gsl_matrix_get(modvar->weight, i, j);
	    }

	  // finally compute and write the weights:
	  // go over each pixel
	  for (j=0; j < (int)modvar->counts->size2; j++)
	    {
	      if (gsl_matrix_get(modvar->counts, i, j) > 0.0 )
		{
		  // compute and set the individual pixel weight
		  act_weight = gsl_matrix_get(modvar->counts,i,j)
		    *gsl_matrix_get(modvar->weight,i,j)/weight_sum;
		  //		  gsl_matrix_set(weight,i,j,gsl_matrix_get(modvar->counts,i,j)
		  //		 *gsl_matrix_get(modvar->weight,i,j)/weight_sum);
		  gsl_matrix_set(weight, i, j, act_weight);
		  allweight = allweight + gsl_matrix_get(weight, i, j);
		}
	      else
		{
		  // set the default value if no model value is zero
		  gsl_matrix_set(weight, i, j,0.0);
		}
	    }
	}
      // if the column does not have model values at all:
      else
	{
	  // go over each row
	  for (j=0; j < (int)modvar->counts->size2; j++)
	    {
	      // set the inside default value
	      gsl_matrix_set(weight, i,j,1.0);
	      allweight = allweight + gsl_matrix_get(modvar->counts, i, j);

	    }
	}
    }

  // return the weight matrix
  return weight;
}


#ifndef __LISAUTILS_H__
#define __LISAUTILS_H__ 1

#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <gsl/gsl_cdf.h>

#include "constants.h"
#include "struct.h"
#include "EOBNRv2HMROMstruct.h"
#include "EOBNRv2HMROM.h"
#include "waveform.h"
#include "wip.h"
#include "splinecoeffs.h"
#include "fresnel.h"
#include "likelihood.h"
#include "LISAFDresponse.h"
#include "LISAnoise.h"

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

/***************** Enumerator to choose what masses/time set to sample for *****************/

typedef enum SampleMassParamstag {
  m1m2,
  Mchirpeta
} SampleMassParamstag;
/* Superseded by sampleLframe */
// typedef enum SampleTimeParamtag {
//   tSSB,
//   tL
// } SampleTimeParamtag;

/* Function to convert string input SampleMassParams to tag */
SampleMassParamstag ParseSampleMassParamstag(char* string);
/* Function to convert string input SampleTimeParams to tag */
//SampleTimeParamtag ParseSampleTimeParamtag(char* string);

/***************** Structure definitions *****************/

/* Parameters for the generation of a LISA waveform (in the form of a list of modes) */
typedef struct tagLISAParams {
  double tRef;               /* reference time (s) - GPS time at the frequency representing coalescence */
  double phiRef;             /* reference phase (rad) - phase at the frequency representing coalescence (or at fRef if specified) */
  double m1;                 /* mass of companion 1 (solar masses, default 2e6) */
  double m2;                 /* mass of companion 2 (solar masses, default 1e6) */
  double distance;           /* distance of source (Mpc, default 1e3) */
  double lambda;             /* first angle for the position in the sky (rad, default 0) */
  double beta;               /* second angle for the position in the sky (rad, default 0) */
  double inclination;        /* inclination of L relative to line of sight (rad, default PI/3) */
  double polarization;       /* polarization angle (rad, default 0) */
  int nbmode;                /* number of modes to generate (starting with 22) - defaults to 5 (all modes) */
} LISAParams;

/* Global parameters for the waveform generation and overlap computation */
typedef struct tagLISAGlobalParams {
  double fRef;               /* reference frequency (Hz, default 0 which is interpreted as Mf=0.14) */
  double deltatobs;          /* max duration of observation (years, default 2) - the start of the signals might be cut in time instead of cut in frequency */
  double minf;               /* Minimal frequency (Hz, default=0) - when set to 0, use the lowest frequency where the detector noise model is trusted __LISASimFD_Noise_fLow (set somewhat arbitrarily)*/
  double maxf;               /* Maximal frequency (Hz, default=0) - when set to 0, use the highest frequency where the detector noise model is trusted __LISASimFD_Noise_fHigh (set somewhat arbitrarily)*/
  int tagextpn;              /* Tag to allow PN extension of the waveform at low frequencies */
  int tagtRefatLISA;         /* Tag to signal time to be referenced to arrival at LISA rather than at SSB */
  double Mfmatch;            /* When PN extension allowed, geometric matching frequency: will use ROM above this value. If <=0, use ROM down to the lowest covered frequency */
  int setphiRefatfRef;       /* Flag for adjusting the FD phase at phiRef at the given fRef, which depends also on tRef - if false, treat phiRef simply as an orbital phase shift (minus an observer phase shift) (default=1) */
  int nbmodeinj;             /* number of modes to include in the injection (starting with 22) - defaults to 5 (all modes) */
  int nbmodetemp;            /* number of modes to include in the templates (starting with 22) - defaults to 5 (all modes) */
  int tagint;                /* Tag choosing the integrator: 0 for wip (default), 1 for linear integration */
  TDItag tagtdi;             /* Tag choosing the TDI variables to use */
  int nbptsoverlap;          /* Number of points to use in loglinear overlaps (default 32768) */
  LISAconstellation *variant;  /* A structure defining the LISA constellation features */
  int zerolikelihood;        /* Tag to zero out the likelihood, to sample from the prior for testing purposes (default 0) */
  int frozenLISA;            /* Freeze the orbital configuration to the time of peak of the injection (default 0) */
  ResponseApproxtag responseapprox;    /* Approximation in the GAB and orb response - choices are full (full response, default), lowfL (keep orbital delay frequency-dependence but simplify constellation response) and lowf (simplify constellation and orbital response) - WARNING : at the moment noises are not consistent, and TDI combinations from the GAB are unchanged */
  int tagsimplelikelihood22; /* Tag to use simplified, frozen-LISA and lowf likelihood where mode overlaps are precomputed - 22-mode only - can only be used when the masses and time (tL) are pinned to injection values (Note: when using --snr, distance adjustment done using responseapprox, not the simple response) */
  int tagsimplelikelihoodHM; /* Tag to use simplified, frozen-LISA and lowf likelihood where mode overlaps are precomputed - set of modes - can only be used when the masses and time (tL) are pinned to injection values (Note: when using --snr, distance adjustment done using responseapprox, not the simple response) */
} LISAGlobalParams;

typedef struct tagLISASignalCAmpPhase
{
  struct tagListmodesCAmpPhaseFrequencySeries* TDI1Signal;   /* Signal in the TDI channel 1, in the form of a list of the contribution of each mode */
  struct tagListmodesCAmpPhaseFrequencySeries* TDI2Signal;   /* Signal in the TDI channel 2, in the form of a list of the contribution of each mode */
  struct tagListmodesCAmpPhaseFrequencySeries* TDI3Signal;   /* Signal in the TDI channel 3, in the form of a list of the contribution of each mode */
  double TDI123hh;                                           /* Combined Inner product (h|h) for TDI channels 123 */
} LISASignalCAmpPhase;

typedef struct tagLISAInjectionCAmpPhase
{
  struct tagListmodesCAmpPhaseSpline* TDI1Splines;   /* Signal in the TDI channel 1, in the form of a list of splines for the contribution of each mode */
  struct tagListmodesCAmpPhaseSpline* TDI2Splines;   /* Signal in the TDI channel 2, in the form of a list of splines for the contribution of each mode */
  struct tagListmodesCAmpPhaseSpline* TDI3Splines;   /* Signal in the TDI channel 3, in the form of a list of splines for the contribution of each mode */
  double TDI123ss;                                   /* Combined Inner product (s|s) for TDI channels 123 */
} LISAInjectionCAmpPhase;

typedef struct tagLISASignalReIm /* We don't store the SNRs here, as we will use -1/2(h-s|h-s) for the likelihood */
{
  struct tagReImFrequencySeries* TDI1Signal;   /* Signal in the TDI channel 1, in the form of a Re/Im frequency series where the modes have been summed */
  struct tagReImFrequencySeries* TDI2Signal;   /* Signal in the TDI channel 2, in the form of a Re/Im frequency series where the modes have been summed */
  struct tagReImFrequencySeries* TDI3Signal;   /* Signal in the TDI channel 3, in the form of a Re/Im frequency series where the modes have been summed */
} LISASignalReIm;

typedef struct tagLISAInjectionReIm /* Storing the vectors of frequencies and noise values - We don't store the SNRs here, as we will use -1/2(h-s|h-s) for the likelihood */
{
  struct tagReImFrequencySeries* TDI1Signal;   /* Signal in the TDI channel 1, in the form of a Re/Im frequency series where the modes have been summed */
  struct tagReImFrequencySeries* TDI2Signal;   /* Signal in the TDI channel 2, in the form of a Re/Im frequency series where the modes have been summed */
  struct tagReImFrequencySeries* TDI3Signal;   /* Signal in the TDI channel 3, in the form of a Re/Im frequency series where the modes have been summed */
  gsl_vector* freq;                            /* Vector of frequencies of the injection (assumed to be the same for A,E,T) */
  gsl_vector* noisevalues1;                    /* Vector of noise values on freq for TDI channel 1 */
  gsl_vector* noisevalues2;                    /* Vector of noise values on freq for TDI channel 2 */
  gsl_vector* noisevalues3;                    /* Vector of noise values on freq for TDI channel 3 */
} LISAInjectionReIm;

typedef struct tagLISAPrior {
  SampleMassParamstag samplemassparams;   /* Choose the set of mass params to sample from - options are m1m2 and Mchirpeta (default m1m2) */
  //SampleTimeParamtag sampletimeparam;     /* Choose the time param to sample from - options are tSSB and tL (default tSSB) */
  int sampleLframe;          /* flag to sample L-frame params tL, lambdaL, betaL, psiL instead of SSB-frame params -- priors are interpreted for those L-frame params - no phase transformation */
  double deltaT;             /* width of time prior centered on injected value (s) (default 1e5) */
  double comp_min;           /* minimum component mass (solar masses) (default 1e4) */
  double comp_max;           /* maximum component mass (solar masses) (default 1e8) */
  double mtot_min;           /* minimum total mass (solar masses) (default 5*1e4) */
  double mtot_max;           /* maximum total mass (solar masses) (default 1e8) */
  double qmax;               /* maximum asymmetric mass ratio (>=1) (default 11.98) */
  double Mchirp_min;         /* Minimum chirp mass in Solar masses - when sampling Mchirpeta (default=2e4) */
  double Mchirp_max;         /* Maximum chirp mass in Solar masses - when sampling Mchirpeta (default=4e7) */
  double eta_min;            /* Minimum symmetric mass ratio eta - when sampling Mchirpeta (default=0.072) */
  double eta_max;            /* Maximum symmetric mass ratio eta - when sampling Mchirpeta (default=0.25) */
  double dist_min;           /* minimum distance of source (Mpc) (default 100) */
  double dist_max;           /* maximum distance of source (Mpc) (default 40*1e3) */
  double lambda_min;         /* minimum lambda (rad, default 0) - for testing */
  double lambda_max;         /* maximum lambda (rad, default 2pi) - for testing */
  double beta_min;           /* minimum beta (rad, default 0) - for testing */
  double beta_max;           /* maximum beta (rad, default pi) - for testing */
  double phase_min;          /* minimum phase (rad, default 0) - for testing */
  double phase_max;          /* maximum phase (rad, default 2pi) - for testing */
  double pol_min;            /* minimum polarization (rad, default 0) - for testing */
  double pol_max;            /* maximum polarization (rad, default 2pi) - for testing */
  double inc_min;            /* minimum inclination (rad, default 0) - for testing */
  double inc_max;            /* maximum inclination (rad, default pi) - for testing */
  double fix_m1;
  double fix_m2;
  double fix_Mchirp;
  double fix_eta;
  double fix_time;
  double fix_lambda;
  double fix_beta;
  double fix_phase;
  double fix_pol;
  double fix_dist;
  double fix_inc;
  int pin_m1;
  int pin_m2;
  int pin_Mchirp;
  int pin_eta;
  int pin_time;
  int pin_lambda;
  int pin_beta;
  int pin_phase;
  int pin_pol;
  int pin_dist;
  int pin_inc;
  double snr_target;
  int rescale_distprior;
  int flat_distprior;
  int logflat_massprior;
} LISAPrior;

typedef struct tagLISARunParams {
  double eff;                /* target efficiency (default 0.1) */
  double tol;                /* logZ tolerance (default 0.5) */
  int    consteff;           /* constant efficiency mode (default 0) */
  int    nlive;              /* number of live points (default 1000) */
  int    writeparams;        /* Write params - if 1, write run parameters to file (default 1) */
  char   outroot[200];       /* output root (default "chains/LISAinference_") */
  int    bambi;              /* run BAMBI? (default 0) */
  int    resume;             /* resume form previous run? (default 0) */
  int    maxiter;            /* max number of iterations (default 0 - ignore) */
  char   netfile[200];       /* NN settings file (default "LISAinference.inp") */
  int    mmodal;             /* use multimodal decomposition ? */
  int    maxcls;             /* max number of modes in multimodal decomposition */
  int    nclspar;            /* number of parameters to use for multimodal decomposition - in the order of the cube */
  double ztol;               /* in multimodal decomposition, modes with lnZ lower than Ztol are ignored */
  int    seed;               /* seed the inference by setting one of the live points to the injection ? */
} LISARunParams;

/* Parameters for the generation of a LISA waveform (in the form of a list of modes) */
typedef struct tagLISAAddParams {
  double tRef;               /* reference time (s) - GPS time at the frequency representing coalescence */
  double phiRef;             /* reference phase (rad) - phase at the frequency representing coalescence (or at fRef if specified) */
  double m1;                 /* mass of companion 1 (solar masses, default 2e6) */
  double m2;                 /* mass of companion 2 (solar masses, default 1e6) */
  double distance;           /* distance of source (Mpc, default 1e3) */
  double lambda;             /* first angle for the position in the sky (rad, default 0) */
  double beta;               /* second angle for the position in the sky (rad, default 0) */
  double inclination;        /* inclination of L relative to line of sight (rad, default PI/3) */
  double polarization;       /* polarization angle (rad, default 0) */
  int loadparamsfile;        /* Option to load physical parameters from file for LISAlikelihood and to output resulting likelihoods to file (default 0) */
  int nlinesparams;          /* Number of lines in params file for LISAlikelihood */
  char indir[256];           /* Input directory for LISAlikelihood */
  char infile[256];          /* Input file for LISAlikelihood */
  char outdir[256];          /* Output directory for LISAlikelihood */
  char outfile[256];         /* Output file for LISAlikelihood */
} LISAAddParams;

/* Saved precomputed values for the injection , when using the simplified frozenLISA lowf likelihood - here for now assumes 22 mode only, old convention */
/* NOTE: difference in convention, new convention has a factor 3 in the integrand defining <lm|l'm'> */
typedef struct tagSimpleLikelihoodPrecomputedValues22 {
  double normalization;
  double complex sa;
  double complex se;
} SimpleLikelihoodPrecomputedValues22;
/* Saved precomputed values for the injection , when using the simplified frozenLISA lowf likelihood - here for set of modes, new convention */
/* NOTE: difference in convention, new convention has a factor 3 in the integrand defining <lm|l'm'> */
typedef struct tagSimpleLikelihoodPrecomputedValuesHM {
  gsl_matrix* Lambda_lm_lpmp; /* Matrix <lm|l'm'> */
  gsl_vector* sa_lm_real;         /* Vector s_a^{lm}, real part*/
  gsl_vector* sa_lm_imag;         /* Vector s_a^{lm}, imaginary part */
  gsl_vector* se_lm_real;         /* Vector s_e^{lm}, real part*/
  gsl_vector* se_lm_imag;         /* Vector s_e^{lm}, imaginary part */
} SimpleLikelihoodPrecomputedValuesHM;

/************ Functions for LISA parameters, injection, likelihood, prior ************/

/* Parse command line to initialize LISAParams, LISAGlobalParams, LISAPrior, and LISARunParams objects */
/* Masses are input in solar masses and distances in Mpc - converted in SI for the internals */
void parse_args_LISA(ssize_t argc, char **argv,
  LISAParams* params,
  LISAGlobalParams* globalparams,
  LISAPrior* prior,
  LISARunParams* run,
  LISAAddParams* addparams);

/* Functions to print the parameters of the run in files for reference */
int print_parameters_to_file_LISA(
  LISAParams* params,
  LISAGlobalParams* globalparams,
  LISAPrior* prior,
  LISARunParams* run);
int print_rescaleddist_to_file_LISA(
  LISAParams* params,
  LISAGlobalParams* globalparams,
  LISAPrior* prior,
  LISARunParams* run);
int print_snrlogZ_to_file_LISA(LISARunParams* run, double SNR, double logZ);
/* Function printing injection/signal parameters to stdout */
void report_LISAParams(LISAParams* params);

/* Initialization and clean-up for LISASignal structures */
void LISASignalCAmpPhase_Cleanup(LISASignalCAmpPhase* signal);
void LISASignalCAmpPhase_Init(LISASignalCAmpPhase** signal);
void LISAInjectionCAmpPhase_Cleanup(LISAInjectionCAmpPhase* signal);
void LISAInjectionCAmpPhase_Init(LISAInjectionCAmpPhase** signal);
void LISASignalReIm_Cleanup(LISASignalReIm* signal);
void LISASignalReIm_Init(LISASignalReIm** signal);
void LISAInjectionReIm_Cleanup(LISAInjectionReIm* signal);
void LISAInjectionReIm_Init(LISAInjectionReIm** signal);

//Function to restrict range of the signal/injection to within desired limits.
int listmodesCAmpPhaseTrim(ListmodesCAmpPhaseFrequencySeries* listSeries);

/* Function generating a LISA signal as a list of modes in CAmp/Phase form, from LISA parameters */
int LISAGenerateSignalCAmpPhase(
  struct tagLISAParams* params,                 /* Input: set of LISA parameters of the signal */
  struct tagLISASignalCAmpPhase* signal);  /* Output: structure for the generated signal */
/* Function generating a LISA injection as a list of modes, given as preinterpolated splines, from LISA parameters */
int LISAGenerateInjectionCAmpPhase(
  struct tagLISAParams* injectedparams,    /* Input: set of LISA parameters of the signal */
  struct tagLISAInjectionCAmpPhase* signal);  /* Output: structure for the generated signal */
/* Function generating a LISA signal as a frequency series in Re/Im form where the modes have been summed, from LISA parameters - takes as argument the frequencies on which to evaluate */
int LISAGenerateSignalReIm(
  struct tagLISAParams* params,       /* Input: set of LISA parameters of the template */
  gsl_vector* freq,                   /* Input: frequencies on which evaluating the waveform (from the injection) */
  struct tagLISASignalReIm* signal);  /* Output: structure for the generated signal */
/* Function generating a LISA injection as a frequency series in Re/Im form where the modes have been summed, from LISA parameters - frequencies on which to evaluate are to be determined internally */
int LISAGenerateInjectionReIm(
  struct tagLISAParams* injectedparams,       /* Input: set of LISA parameters of the injection */
  double fLow,                                /* Input: starting frequency */
  int nbpts,                                  /* Input: number of frequency samples */
  int tagsampling,                            /* Input: tag for using linear (0) or logarithmic (1) sampling */
  struct tagLISAInjectionReIm* signal);       /* Output: structure for the generated signal */

/*Wrapper for waveform generation with possibly a combination of EOBNRv2HMROM and TaylorF2*/
/* Note: GenerateWaveform accepts masses and distances in SI units, whereas LISA params is in solar masses and Mpc */
int GenerateWaveform(
  struct tagListmodesCAmpPhaseFrequencySeries **listhlm,  /* Output: list of modes in Frequency-domain amplitude and phase form */
  int nbmode,                                    /* Number of modes to generate (starting with the 22) */
  double f_match,                                /* Minimum frequency using EOBNRv2HMROM */
  double f_min,                                  /* Minimum frequency required */
  double deltatRef,                              /* Time shift so that the peak of the 22 mode occurs at deltatRef */
  double phiRef,                                 /* Phase at reference frequency */
  double fRef,                                   /* Reference frequency (Hz); 0 defaults to fLow */
  double m1SI,                                   /* Mass of companion 1 (kg) */
  double m2SI,                                   /* Mass of companion 2 (kg) */
  double distance                               /* Distance of source (m) */
		     );

/* Non-spinning merger TaylorF2 waveform, copied and condensed from LAL */
void TaylorF2nonspin(
		double *amp,                            /**< FD waveform amplitude (modulus)*/
		double *phase,                          /**< FD waveform phase */
		const double *freqs,                    /**< frequency points at which to evaluate the waveform (Hz) */
		const int size,                         /** number of freq samples */
		const double m1_SI,                     /**< mass of companion 1 (kg) */
		const double m2_SI,                     /**< mass of companion 2 (kg) */
		const double distance,                  /** distance (m) */
		const double imatch                     /**< index at which to match phase;
							   assumes arrays are preloaded at imatch and imatch+1
							   with the required result */
		     );

/* checks prior boundaires */
int PriorBoundaryCheckm1m2(LISAPrior *prior, double *Cube);
int PriorBoundaryCheckMchirpeta(LISAPrior *prior, double *Cube);

/* Prior functions from Cube to physical parameters
   x1 is min, x2 is max when specified
   r is Cube value */
double CubeToFlatPrior(double r, double x1, double x2);
double CubeToLogFlatPrior(double r, double x1, double x2);
double CubeToPowerPrior(double p, double r, double x1, double x2);
double CubeToGaussianPrior(double r, double mean, double sigma);
double CubeToSinPrior(double r, double x1, double x2);
double CubeToCosPrior(double r, double x1, double x2);
/* Prior functions from physical parameters to Cube
   x1 is min, x2 is max when specified
   y is physical value */
double FlatPriorToCube(double y, double x1, double x2);
double LogFlatPriorToCube(double y, double x1, double x2);
double PowerPriorToCube(double p, double y, double x1, double x2);
double SinPriorToCube(double y, double x1, double x2);
double CosPriorToCube(double y, double x1, double x2);

/* log-Likelihood functions */
double CalculateLogLCAmpPhase(LISAParams *params, LISAInjectionCAmpPhase* injection);
double CalculateLogLReIm(LISAParams *params, LISAInjectionReIm* injection);

/* Functions for simplified likelihood using precomputing relevant values */
int LISAComputeSimpleLikelihoodPrecomputedValues22(SimpleLikelihoodPrecomputedValues22* simplelikelihoodvals22, LISAParams* params);
int LISAComputeSimpleLikelihoodPrecomputedValuesHM(SimpleLikelihoodPrecomputedValuesHM* simplelikelihoodvalsHM, LISAParams* params);
double CalculateLogLSimpleLikelihood22(SimpleLikelihoodPrecomputedValues22* simplelikelihoodvals22, LISAParams* params);
double CalculateLogLSimpleLikelihoodHM(SimpleLikelihoodPrecomputedValuesHM* simplelikelihoodvalsHM, LISAParams* params);

/************ Global Parameters ************/

extern LISAParams* injectedparams;
extern LISAGlobalParams* globalparams;
extern LISAPrior* priorParams;
extern LISAAddParams* addparams;
extern double logZdata; /* TODO: not used */
extern SimpleLikelihoodPrecomputedValues22* simplelikelihoodinjvals22;
extern SimpleLikelihoodPrecomputedValuesHM* simplelikelihoodinjvalsHM;

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

#endif


/*
 Copyright (C) 2004-2007 M. Oliveira, F. Nogueira

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

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

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 02111-1307, USA.
*/

#include <config.h>
#include <string_f.h>

#include <gsl/gsl_errno.h>
#include <gsl/gsl_math.h>
#include <gsl/gsl_sf.h>
#include <gsl/gsl_vector.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_permutation.h>
#include <gsl/gsl_linalg.h>
#include <gsl/gsl_odeiv.h>
#include <gsl/gsl_spline.h>
#include <gsl/gsl_multiroots.h>

/* This file contains interface routines that allow Fortran 90/95
routines to call GSL routines. The names of these routines are
compiler dependent: Fortran 90/95 compilers have different
name-mangling schemes.  For a description of the routines please refer
to the GSL documentation. */

          /* Error Handling */

void FC_FUNC_(gsl_strerror, GSL_STRERROR)
     (int *err, STR_F_TYPE res STR_ARG1)
{
  char *c;

  c = gsl_strerror(*err);
  /*TO_F_STR1(c, res);!!not too sure what this does*/
}

          /* Mathematical Functions */

double FC_FUNC_(gsl_asinh, GSL_ASINH)
     (const double *x)
{
  return gsl_asinh(*x);
}

          /* Special Functions */

double FC_FUNC_(gsl_sf_bessel_knu_scaled, GSL_SF_BESSEL_KNU_SCALED)
     (double *nu, double *x)
{
  return gsl_sf_bessel_Knu_scaled(*nu,*x);
}

          /* Vectors and Matrices */

void FC_FUNC_(gsl_vector_alloc, GSL_VECTOR_ALLOC)
     (int *n, void **v)
{
  *v = (void*) gsl_vector_alloc (*n);
}

void FC_FUNC_(gsl_vector_free, GSL_VECTOR_FREE)
     (void **v)
{
  gsl_vector_free ((gsl_vector *)(*v));
}

double FC_FUNC_(gsl_vector_get, GSL_VECTOR_GET) (void **v, int *i)
{
  return gsl_vector_get ((gsl_vector *)(*v), *i);
}

void FC_FUNC_(gsl_vector_set, GSL_VECTOR_SET)
     (void **v, int *i, double *x)
{
  gsl_vector_set ((gsl_vector *)(*v), *i, *x);
}

void FC_FUNC_(gsl_matrix_alloc, GSL_MATRIX_ALLOC)
     (int *n1, int *n2, void **m)
{
  *m = (void*) gsl_matrix_alloc (*n1, *n2);
}

void FC_FUNC_(gsl_matrix_calloc, GSL_MATRIX_CALLOC)
     (int *n1, int *n2, void **m)
{
  *m = (void*) gsl_matrix_calloc (*n1, *n2);
}

void FC_FUNC_(gsl_matrix_free, GSL_MATRIX_FREE)
     (void **m)
{
  gsl_matrix_free ((gsl_matrix *)(*m));
}

double FC_FUNC_(gsl_matrix_get, GSL_MATRIX_GET)
     (void **m, int *i, int *j)
{
  return gsl_matrix_get ((gsl_matrix *)(*m), *i, *j);
}

void FC_FUNC_(gsl_matrix_set, GSL_MATRIX_SET)
     (void **m, int *i, int *j, double *x)
{
  gsl_matrix_set ((gsl_matrix *)(*m), *i, *j, *x);
}

          /* Permutations */

void FC_FUNC_(gsl_permutation_alloc, GSL_PERMUTATION_ALLOC)
     (int *n, void **p)
{
  *p = (void*) gsl_permutation_alloc (*n);
}

void FC_FUNC_(gsl_permutation_free, GSL_PERMUTATION_FREE)
     (void **p)
{
  gsl_permutation_free ((gsl_permutation *)(*p));
}

          /* Linear Algebra */

int FC_FUNC_(gsl_linalg_lu_decomp, GSL_LINALG_LU_DECOMP)
     (void **m, void **p, int *signum)
{
  int s,ierr;
  ierr = gsl_linalg_LU_decomp ((gsl_matrix *)(*m) , (gsl_permutation *)(*p), &s);
  *signum = s;
  return (ierr);
}

int FC_FUNC_(gsl_linalg_lu_invert, GSL_LINALG_LU_INVERT)
     (void **LU, void **p, void **inverse)
{

  return gsl_linalg_LU_invert ((gsl_matrix *)(*LU),(gsl_permutation *)(*p), (gsl_matrix *)(*inverse));
}

int FC_FUNC_(gsl_linalg_lu_solve, GSL_LINALG_LU_SOLVE)
     (void **LU, void **p, void **b, void **x)
{
  return gsl_linalg_LU_solve ((gsl_matrix *)(*LU), (gsl_permutation *)(*p), (gsl_vector *)(*b), (gsl_vector *)(*x));
}

          /* Ordinary Differential Equations */

void FC_FUNC_(gsl_odeiv_step_alloc, GSL_ODEIV_STEP_ALLOC)
     (const int *stepping_func, const int *dim, void **stp)
{
  /* WARNING: This routine has a small modification: the user needs to
   pass an integer in order to choose the stepping function instead of
   a variable of type gsl_odeiv_step_type. */

  switch(*stepping_func) {
  case 1 : // Embedded 2nd order Runge-Kutta with 3rd order error estimate
    *stp = (void *)gsl_odeiv_step_alloc(gsl_odeiv_step_rk2, *dim);
    break;
  case 2 : // 4th order (classical) Runge-Kutta
    *stp = (void *)gsl_odeiv_step_alloc(gsl_odeiv_step_rk4, *dim);
    break;
  case 3 : // Embedded 4th order Runge-Kutta-Fehlberg method with 5th order error estimate. This method is a good general-purpose integrator
    *stp = (void *)gsl_odeiv_step_alloc(gsl_odeiv_step_rkf45, *dim);
    break;
  case 4 : // Embedded 4th order Runge-Kutta Cash-Karp method with 5th order error estimate
    *stp = (void *)gsl_odeiv_step_alloc(gsl_odeiv_step_rkck, *dim);
    break;
  case 5 : // Embedded 8th order Runge-Kutta Prince-Dormand method with 9th order error estimate
    *stp = (void *)gsl_odeiv_step_alloc(gsl_odeiv_step_rk8pd, *dim);
    break;
  case 6 : // Implicit 2nd order Runge-Kutta at Gaussian points
    *stp = (void *)gsl_odeiv_step_alloc(gsl_odeiv_step_rk2imp, *dim);
    break;
  case 7 : // Implicit 4th order Runge-Kutta at Gaussian points
    *stp = (void *)gsl_odeiv_step_alloc(gsl_odeiv_step_rk4imp, *dim);
    break;
  case 8 : // Implicit Bulirsch-Stoer method of Bader and Deuflhard. This algorithm requires the Jacobian
    *stp = (void *)gsl_odeiv_step_alloc(gsl_odeiv_step_bsimp, *dim);
    break;
  case 9 : // M=1 implicit Gear method
    *stp = (void *)gsl_odeiv_step_alloc(gsl_odeiv_step_gear1, *dim);
    break;
  case 10 : // M=2 implicit Gear method
    *stp = (void *)gsl_odeiv_step_alloc(gsl_odeiv_step_gear2, *dim);
    break;
  }
}

void FC_FUNC_(gsl_odeiv_step_free, GSL_ODEIV_STEP_FREE)
     (void **stp)
{
  gsl_odeiv_step_free((gsl_odeiv_step *)(*stp));
}

void FC_FUNC_(gsl_odeiv_evolve_alloc, GSL_ODEIV_EVOLVE_ALLOC)
     (const int *dim, void **evl)
{
  *evl = (void *)gsl_odeiv_evolve_alloc (*dim);
}

int FC_FUNC_(gsl_odeiv_evolve_reset, GSL_ODEIV_EVOLVE_RESET)
     (void **evl)
{
  return gsl_odeiv_evolve_reset ((gsl_odeiv_evolve *)(*evl));
}

void FC_FUNC_(gsl_odeiv_evolve_free, GSL_ODEIV_EVOLVE_FREE)
     (void **evl)
{
  gsl_odeiv_evolve_free((gsl_odeiv_evolve *)(*evl));
}

void FC_FUNC_(gsl_odeiv_control_standart_new, GSL_ODEIV_CONTROL_STANDART_NEW)
     (void **ctrl, double *epsabs, double *epsrel, double *ay, double *adydt)
{
  *ctrl = (void*) gsl_odeiv_control_standard_new (*epsabs, *epsrel, *ay, *adydt);
}

void FC_FUNC_(gsl_odeiv_control_free, GSL_ODEIV_CONTROL_FREE)
     (void **ctrl)
{
  gsl_odeiv_control_free((gsl_odeiv_control *)(*ctrl));
}

          /* Interpolation */

void FC_FUNC_(gsl_interp_accel_alloc, GSL_INTERP_ACCEL_ALLOC)
     (void **acc)
{
     *acc = (void *)gsl_interp_accel_alloc();
}

void FC_FUNC_(gsl_spline_alloc, GSL_SPLINE_ALLOC)
     (void **spl, int *n, short int *opt)
{
  /* WARNING: This routine has a small modification: the user needs to
     pass an integer in order to choose the interpolation type instead
     of a variable of type gsl_interp_type. */

  switch (*opt) {
  case 1 : // linear interpolation
    *spl = (void *)gsl_spline_alloc(gsl_interp_linear, *n);
    break;
  case 2 : // polynomial interpolation
    *spl = (void *)gsl_spline_alloc(gsl_interp_polynomial, *n);
    break;
  case 3 : // cubic spline with natural boundary conditions
    *spl = (void *)gsl_spline_alloc(gsl_interp_cspline, *n);
    break;
  case 4 : // cubic spline with periodic boundary conditions
    *spl = (void *)gsl_spline_alloc(gsl_interp_cspline_periodic, *n);
    break;
  case 5 : // Akima spline with natural boundary conditions
    *spl = (void *)gsl_spline_alloc(gsl_interp_akima, *n);
    break;
  case 6 : // Akima spline with periodic boundary conditions
    *spl = (void *)gsl_spline_alloc(gsl_interp_akima_periodic, *n);
    break;
  }
}

void FC_FUNC_(gsl_spline_init, GSL_SPLINE_INIT)
     (void **spl, int *n, double *x, double *f)
{
  gsl_spline_init((gsl_spline *)(*spl), x, f, *n);
}

double FC_FUNC_(gsl_spline_eval, GSL_SPLINE_EVAL)
     (double *x, void **spl, void **acc)
{
  return gsl_spline_eval((gsl_spline *)(*spl), *x, (gsl_interp_accel *)(*acc));
}

double FC_FUNC_(gsl_spline_eval_deriv, GSL_SPLINE_EVAL_DERIV)
     (double *x, void **spl, void **acc)
{
  return gsl_spline_eval_deriv((gsl_spline *)(*spl), *x, (gsl_interp_accel *)(*acc));
}

double FC_FUNC_(gsl_spline_eval_deriv2, GSL_SPLINE_EVAL_DERIV2)
     (double *x, void **spl, void **acc)
{
  return gsl_spline_eval_deriv2((gsl_spline *)(*spl), *x, (gsl_interp_accel *)(*acc));
}

void FC_FUNC_(gsl_spline_free, GSL_SPLINE_FREE)
     (void **spl)
{
  gsl_spline_free((gsl_spline *)(*spl));

}

void FC_FUNC_(gsl_interp_accel_free, GSL_INTERP_ACCEL_FREE)
     (void **acc)
{
  gsl_interp_accel_free((gsl_interp_accel *)(*acc));
}

double FC_FUNC_(gsl_spline_eval_integ, GSL_SPLINE_EVAL_INTEG)
     (double *a, double *b, void **spl, void **acc)
{
  return gsl_spline_eval_integ((gsl_spline *)(*spl), *a, *b, (gsl_interp_accel *)(*acc));
}

          /* Multidimensional Root-Finding */

 void FC_FUNC_(gsl_multiroot_fsolver_alloc, GSL_MULTIROOT_FSOLVER_ALLOC)
     (void **s, const int *solver_type, const int *n)
{
  switch(*solver_type) {
  case 1 : // hybrid algorithm with internal scaling.
    *s = (void *)gsl_multiroot_fsolver_alloc(gsl_multiroot_fsolver_hybrids, *n);
    break;
  case 2 : // hybrid algorithm without internal scaling.
    *s = (void *)gsl_multiroot_fsolver_alloc(gsl_multiroot_fsolver_hybrid, *n);
    break;
  case 3 : // discrete Newton algorithm
    *s = (void *)gsl_multiroot_fsolver_alloc(gsl_multiroot_fsolver_dnewton, *n);
    break;
  case 4 : // Broyden algorithm
    *s = (void *)gsl_multiroot_fsolver_alloc(gsl_multiroot_fsolver_broyden, *n);
    break;
  }
}

void FC_FUNC_(gsl_multiroot_fsolver_free, GSL_MULTIROOT_FSOLVER_FREE)
     (void **s)
{
  gsl_multiroot_fsolver_free((gsl_multiroot_fsolver *)(*s));
}