/*
 * author: Achim Gaedke
 * created: January 2002
 * file: pygsl/src/multiminmodule.c
 * $Id: multiminmodule.c,v 1.3 2003/07/25 17:15:16 schnizer Exp $
 */

#include <Python.h>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_multimin.h>
#include <pygsl/error_helpers.h>

typedef struct {
  PyObject_HEAD
  size_t n;
  gsl_multimin_fminimizer* min;
  gsl_multimin_function* func;
  PyObject* py_function;
  PyObject* trailing_params;
} multimin_multiminObject;

double pygsl_multimin_function(const gsl_vector* x, void* params) {
  PyObject* trailing_params;
  PyObject* function;
  PyObject* result_py;
  PyObject* result_float;
  PyObject* args;
  PyObject* args_tuple;
  double result;
  int i;
  int length;

  /* construct argument list for python function */
  args=PyList_New(0);
  /* the optimization parameters are the first arguments */
  for (i=0;i<x->size;i++) {
    PyObject* arg=PyFloat_FromDouble(gsl_vector_get(x,i));
    if (arg==NULL) {
      Py_DECREF(args);
      return GSL_NAN;
    }
    PyList_Append(args,arg);
    Py_DECREF(arg);
  }

  /* the other parameters are following */
  trailing_params=((multimin_multiminObject*)params)->trailing_params;
  if (trailing_params!=NULL) {
    if (PyTuple_Check(trailing_params)==1 || PyList_Check(trailing_params)==1) {
      length=PySequence_Length(trailing_params);
      for (i=0;i<length;i++) {
	PyList_Append(args,PySequence_GetItem(trailing_params,i));
      }
    }
    else {
      PyList_Append(args,trailing_params);
    }
  }
  args_tuple=PyList_AsTuple(args);
  Py_DECREF(args);

  /* extract the function */
  function=((multimin_multiminObject*)params)->py_function;
  if (function==NULL) {
    Py_DECREF(args_tuple);
    return GSL_NAN;
  }
  Py_INCREF(function);
  result_py=PyObject_CallObject(function,args_tuple);
  Py_DECREF(function);
  Py_DECREF(args_tuple);
  if (result_py==NULL) {
    return GSL_NAN;
  }

  result_float=PyNumber_Float(result_py);
  Py_DECREF(result_py);
  if (result_float==NULL) return GSL_NAN;
  result=PyFloat_AsDouble(result_float);
  Py_DECREF(result_float);
  return result;
}

static PyObject* multimin_multimin_set(PyObject *self,
				       PyObject *args
				       ) {
  PyObject* func;
  PyObject* params;
  PyObject* x;
  PyObject* steps;
  int i;
  int n;
  gsl_vector* gsl_x;
  gsl_vector* gsl_steps;

  /* arguments PyFunction, Parameters, start Vector, step Vector */
  if (0==PyArg_ParseTuple(args,"O!OOO",
			  &PyFunction_Type,&func,&params,&x,&steps))
      return NULL;
  if (((multimin_multiminObject*)self)->min==NULL) {
    PyErr_SetString(PyExc_RuntimeError, "got a min NULL pointer!");
    return NULL;
  }
  n=((multimin_multiminObject*)self)->n;

  /* look out for suitable x, steps */
  if (!PySequence_Check(x) || !PySequence_Check(steps)){
    PyErr_SetString(PyExc_RuntimeError, "start or steps are not a sequence");
    return NULL;
  }

  if (PySequence_Size(x)!=n || PySequence_Size(steps)!=n) {
    PyErr_SetString(PyExc_RuntimeError, "start or steps are not of required size");
    return NULL;
  }

  if (((multimin_multiminObject*)self)->func!=NULL) {
    /* free the previous function and params */
    if (((multimin_multiminObject*)self)->trailing_params!=NULL) {
      Py_DECREF(((multimin_multiminObject*)self)->trailing_params);
    }
    if (((multimin_multiminObject*)self)->py_function!=NULL) {
      Py_DECREF(((multimin_multiminObject*)self)->py_function);
    }
  }
  else {
    /* allocate function space */
    ((multimin_multiminObject*)self)->func=malloc(sizeof(gsl_multimin_function));
    if (((multimin_multiminObject*)self)->func==NULL) {
      PyErr_SetString(PyExc_RuntimeError, "can't allocate function space");
      return NULL;
    }
  }
  /* add new function  and parameters */
  ((multimin_multiminObject*)self)->trailing_params=params;
  Py_INCREF(params);
  ((multimin_multiminObject*)self)->py_function=func;
  Py_INCREF(func);

  /* initialize the function struct */
  ((multimin_multiminObject*)self)->func->n=n;
  ((multimin_multiminObject*)self)->func->f=&pygsl_multimin_function;
  ((multimin_multiminObject*)self)->func->params=(void*)self;

  /* build gsl_vectors for x and steps */
  gsl_x=gsl_vector_alloc(n);
  gsl_steps=gsl_vector_alloc(n);
  for (i=0; i<PySequence_Size(x);i++) {
    PyObject* pyxi=PySequence_GetItem(x,i);
    PyObject* pystepi=PySequence_GetItem(steps,i);
    PyObject* floatxi=PyNumber_Float(pyxi);
    PyObject* floatstepi=PyNumber_Float(pystepi);
    if (floatxi==NULL || floatstepi==NULL) {
      PyErr_SetString(PyExc_RuntimeError, "start or steps elements are not numbers");
      if (floatxi!=NULL) { Py_DECREF(floatxi); }
      if (floatstepi!=NULL) { Py_DECREF(floatstepi); }
      return NULL;
    }
    gsl_vector_set(gsl_x,i,PyFloat_AsDouble(pyxi));
    gsl_vector_set(gsl_steps,i,PyFloat_AsDouble(pystepi));
    Py_DECREF(floatxi);
    Py_DECREF(floatstepi);
  }

  gsl_multimin_fminimizer_set(((multimin_multiminObject*)self)->min,((multimin_multiminObject*)self)->func,gsl_x,gsl_steps);

  /* gsl_vector_free ? */
  Py_INCREF(Py_None);
  return Py_None;
}

static PyObject* multimin_multimin_iterate(PyObject *self,
					   PyObject *args
					   ) {

  if ( ((multimin_multiminObject*)self)->min==NULL || ((multimin_multiminObject*)self)->func==NULL) {
    PyErr_SetString(PyExc_RuntimeError,"no function specified!");
    return NULL;
  }
  return PyInt_FromLong(gsl_multimin_fminimizer_iterate(((multimin_multiminObject*)self)->min));
}

static PyObject* multimin_multimin_x(PyObject *self,
				     PyObject *args
				     ) {
  gsl_vector* result;
  PyObject* py_result;
  size_t i;
  if ( ((multimin_multiminObject*)self)->min==NULL || ((multimin_multiminObject*)self)->func==NULL) {
    PyErr_SetString(PyExc_RuntimeError,"no function specified!");
    return NULL;
  }
  result=gsl_multimin_fminimizer_x(((multimin_multiminObject*)self)->min);
  if (result==NULL) {
    return NULL;
  }
  py_result=PyTuple_New(result->size);
  if (py_result==NULL) {
    return NULL;
  }
  for (i=0; i<result->size; i++)
    PyTuple_SetItem(py_result,i,PyFloat_FromDouble(gsl_vector_get(result,i)));
  return py_result;
}

static PyObject* multimin_multimin_minimum(PyObject *self,
					   PyObject *args
					   ) {

  if ( ((multimin_multiminObject*)self)->min==NULL || ((multimin_multiminObject*)self)->func==NULL) {
    PyErr_SetString(PyExc_RuntimeError,"no function specified!");
    return NULL;
  }
  return PyFloat_FromDouble(gsl_multimin_fminimizer_minimum(((multimin_multiminObject*)self)->min));
}
static PyObject* multimin_multimin_size(PyObject *self,
					PyObject *args
					) {

  if ( ((multimin_multiminObject*)self)->min==NULL || ((multimin_multiminObject*)self)->func==NULL) {
    PyErr_SetString(PyExc_RuntimeError,"no function specified!");
    return NULL;
  }
  return PyFloat_FromDouble(gsl_multimin_fminimizer_size(((multimin_multiminObject*)self)->min));
}

static PyMethodDef multimin_multimin_methods[] = {
  {"set",(PyCFunction)multimin_multimin_set,METH_VARARGS,"sets a function and start values"},
  {"x",(PyCFunction)multimin_multimin_x,METH_VARARGS,"estimated position of the minimum"},
  {"minimum",(PyCFunction)multimin_multimin_minimum,METH_VARARGS,"estimated value of the minimum"},
  {"size",(PyCFunction)multimin_multimin_size,METH_VARARGS,"size of the minimizer"},
  {"iterate",(PyCFunction)multimin_multimin_iterate,METH_VARARGS,"makes next step"},
  /*  {"alloc",(PyCFunction)multimin_multimin_alloc,METH_VARARGS,"allocate necessary space"}, */
  {NULL, NULL, 0, NULL}           /* sentinel */
};

static PyObject* multimin_multimin_init(PyObject *self,
					PyObject *args,
					PyObject *kwds
					) {
  gsl_multimin_fminimizer* min;
  gsl_multimin_fminimizer_type* type;
  PyObject* type_py;
  size_t n;

  if (0==PyArg_ParseTuple(args,"O!l",
			  &PyCObject_Type,&type_py,&n))
    return NULL;
  if (n<=0) {
    PyErr_SetString(PyExc_RuntimeError, "dimension must be >0");
    return NULL;
  }

  type=PyCObject_AsVoidPtr(type_py);
  min=gsl_multimin_fminimizer_alloc(type,n);
  if (min==NULL) {
    return NULL;
  }
  ((multimin_multiminObject*)self)->n=n;
  ((multimin_multiminObject*)self)->min=min;
  ((multimin_multiminObject*)self)->func=NULL;
  ((multimin_multiminObject*)self)->py_function=NULL;
  ((multimin_multiminObject*)self)->trailing_params=NULL;
  Py_INCREF(Py_None);
  return Py_None;
}

static void
multimin_multimin_dealloc(PyObject* self)
{
  gsl_multimin_fminimizer* min;
  gsl_multimin_function* func;
  min=(gsl_multimin_fminimizer*)((multimin_multiminObject*)self)->min;
  if (min!=NULL) gsl_multimin_fminimizer_free(min);
  func=(gsl_multimin_function*)(((multimin_multiminObject*)self)->func);
  if (func!=NULL) { free(func); }
  if (((multimin_multiminObject*)self)->trailing_params!=NULL) {
    Py_DECREF(((multimin_multiminObject*)self)->trailing_params);
  }
  if (((multimin_multiminObject*)self)->py_function!=NULL) {
    Py_DECREF(((multimin_multiminObject*)self)->py_function);
  }
  self->ob_type->tp_free(self);
}

static PyObject*
multimin_multimin_getattr(PyObject* obj, char *name)
{
  return Py_FindMethod(multimin_multimin_methods, obj, name);
}

static
PyTypeObject multimin_multiminType = {
	PyObject_HEAD_INIT(NULL)
	0,
	"pygsl.multimin.multimin",
	sizeof(multimin_multiminObject),
	0,
	(destructor)multimin_multimin_dealloc,  /* tp_dealloc */
	0,                                      /* tp_print */
	multimin_multimin_getattr,              /* tp_getattr */
	0,					/* tp_setattr */
	0,			                /* tp_compare */
	0,                  			/* tp_repr */
	0,					/* tp_as_number */
	0,	                		/* tp_as_sequence */
	0,                                      /* tp_as_mapping */
	0,				        /* tp_hash */
	0,					/* tp_call */
	0,					/* tp_str */
	0,		                        /* tp_getattro */
	0,					/* tp_setattro */
	0,					/* tp_as_buffer */
	Py_TPFLAGS_DEFAULT,		        /* tp_flags */
        0,				        /* tp_doc */
	0, 		                        /* tp_traverse */
	0,			                /* tp_clear */
	0,              			/* tp_richcompare */
	0,					/* tp_weaklistoffset */
	0,			                /* tp_iter */
	0,					/* tp_iternext */
	0,				        /* tp_methods */
	0,					/* tp_members */
	0,					/* tp_getset */
	0,					/* tp_base */
	0,					/* tp_dict */
	0,					/* tp_descr_get */
	0,					/* tp_descr_set */
	0,					/* tp_dictoffset */
	(initproc)multimin_multimin_init,	/* tp_init */
	NULL,              			/* tp_alloc */
	NULL,                			/* tp_new */
	NULL         			        /* tp_free */
};

static PyMethodDef multiminMethods[] = {
  {NULL, NULL, 0, NULL}        /* Sentinel */
};

void registerMinTypes(PyObject* m) {
  PyObject* dict=PyModule_GetDict(m);
  PyDict_SetItemString(dict, "nmsimplex" , PyCObject_FromVoidPtr((void*)gsl_multimin_fminimizer_nmsimplex,NULL));
  PyDict_SetItemString(dict, "steepest_descent" , PyCObject_FromVoidPtr((void*)gsl_multimin_fdfminimizer_steepest_descent,NULL));
  PyDict_SetItemString(dict, "vector_bfgs" , PyCObject_FromVoidPtr((void*)gsl_multimin_fdfminimizer_vector_bfgs,NULL));
  PyDict_SetItemString(dict, "conjugate_pr" , PyCObject_FromVoidPtr((void*)gsl_multimin_fdfminimizer_conjugate_pr,NULL));
  PyDict_SetItemString(dict, "conjugate_fr" , PyCObject_FromVoidPtr((void*)gsl_multimin_fdfminimizer_conjugate_fr,NULL));
}

void
initmultimin(void)
{
  PyObject* m;
  m=Py_InitModule("multimin", multiminMethods);
  init_pygsl();
  /* init multimin type */
  multimin_multiminType.ob_type  = &PyType_Type;
  multimin_multiminType.tp_alloc = PyType_GenericAlloc;
  multimin_multiminType.tp_new   = PyType_GenericNew;
  multimin_multiminType.tp_free  = _PyObject_Del;

  /* install histogram type */
  /* important! must increment histogram type reference counter */
  Py_INCREF((PyObject*)&multimin_multiminType);
  PyModule_AddObject(m,"multimin", (PyObject*)&multimin_multiminType);
  registerMinTypes(m);
}


#include <gsl/gsl_math.h>
#include "gsl_cblas.h"
#include "cblas.h"

void
cblas_strmm (const enum CBLAS_ORDER Order, const enum CBLAS_SIDE Side,
	     const enum CBLAS_UPLO Uplo, const enum CBLAS_TRANSPOSE TransA,
	     const enum CBLAS_DIAG Diag, const int M, const int N,
	     const float alpha, const float *A, const int lda, float *B,
	     const int ldb)
{
#define BASE float
#include "source_trmm_r.h"
#undef BASE
}


#ifndef _amici_TPL_MODELNAME_h
#define _amici_TPL_MODELNAME_h
#include <cmath>
#include <memory>
#include <gsl/gsl-lite.hpp>

#include "amici/model_ode.h"
#include "amici/solver_cvodes.h"

#include "sundials/sundials_types.h"

namespace amici {

class Solver;

namespace model_TPL_MODELNAME {

extern std::array<const char*, TPL_NP> parameterNames;
extern std::array<const char*, TPL_NK> fixedParameterNames;
extern std::array<const char*, TPL_NX_RDATA> stateNames;
extern std::array<const char*, TPL_NY> observableNames;
extern std::array<const char*, TPL_NW> expressionNames;
extern std::array<const char*, TPL_NP> parameterIds;
extern std::array<const char*, TPL_NK> fixedParameterIds;
extern std::array<const char*, TPL_NX_RDATA> stateIds;
extern std::array<const char*, TPL_NY> observableIds;
extern std::array<const char*, TPL_NW> expressionIds;

TPL_JY_DEF
TPL_DJYDSIGMA_DEF
TPL_DJYDY_DEF
TPL_DJYDY_COLPTRS_DEF
TPL_DJYDY_ROWVALS_DEF
TPL_ROOT_DEF
TPL_DWDP_DEF
TPL_DWDP_COLPTRS_DEF
TPL_DWDP_ROWVALS_DEF
TPL_DWDX_DEF
TPL_DWDX_COLPTRS_DEF
TPL_DWDX_ROWVALS_DEF
TPL_DWDW_DEF
TPL_DWDW_COLPTRS_DEF
TPL_DWDW_ROWVALS_DEF
TPL_DXDOTDW_DEF
TPL_DXDOTDW_COLPTRS_DEF
TPL_DXDOTDW_ROWVALS_DEF
TPL_DXDOTDP_EXPLICIT_DEF
TPL_DXDOTDP_EXPLICIT_COLPTRS_DEF
TPL_DXDOTDP_EXPLICIT_ROWVALS_DEF
TPL_DXDOTDX_EXPLICIT_DEF
TPL_DXDOTDX_EXPLICIT_COLPTRS_DEF
TPL_DXDOTDX_EXPLICIT_ROWVALS_DEF
TPL_DYDX_DEF
TPL_DYDP_DEF
TPL_SIGMAY_DEF
TPL_DSIGMAYDP_DEF
TPL_W_DEF
TPL_X0_DEF
TPL_X0_FIXEDPARAMETERS_DEF
TPL_SX0_DEF
TPL_SX0_FIXEDPARAMETERS_DEF
TPL_XDOT_DEF
TPL_Y_DEF
TPL_STAU_DEF
TPL_DELTAX_DEF
TPL_DELTASX_DEF
TPL_X_RDATA_DEF
TPL_X_SOLVER_DEF
TPL_TOTAL_CL_DEF

/**
 * @brief AMICI-generated model subclass.
 */
class Model_TPL_MODELNAME : public amici::Model_ODE {
  public:
    /**
     * @brief Default constructor.
     */
    Model_TPL_MODELNAME()
        : amici::Model_ODE(
              amici::ModelDimensions(
                  TPL_NX_RDATA,                            // nx_rdata
                  TPL_NXTRUE_RDATA,                        // nxtrue_rdata
                  TPL_NX_SOLVER,                           // nx_solver
                  TPL_NXTRUE_SOLVER,                       // nxtrue_solver
                  TPL_NX_SOLVER_REINIT,                    // nx_solver_reinit
                  TPL_NP,                                  // np
                  TPL_NK,                                  // nk
                  TPL_NY,                                  // ny
                  TPL_NYTRUE,                              // nytrue
                  TPL_NZ,                                  // nz
                  TPL_NZTRUE,                              // nztrue
                  TPL_NEVENT,                              // nevent
                  TPL_NOBJECTIVE,                          // nobjective
                  TPL_NW,                                  // nw
                  TPL_NDWDX,                               // ndwdx
                  TPL_NDWDP,                               // ndwdp
                  TPL_NDWDW,                               // ndwdw
                  TPL_NDXDOTDW,                            // ndxdotdw
                  TPL_NDJYDY,                              // ndjydy
                  0,                                       // nnz
                  TPL_UBW,                                 // ubw
                  TPL_LBW                                  // lbw
              ),
              amici::SimulationParameters(
                  std::vector<realtype>{TPL_FIXED_PARAMETERS}, // fixedParameters
                  std::vector<realtype>{TPL_PARAMETERS}        // dynamic parameters
              ),
              TPL_O2MODE,                                  // o2mode
              std::vector<realtype>(TPL_NX_SOLVER, 0.0),   // idlist
              std::vector<int>{},                          // z2event
              true,                                        // pythonGenerated
              TPL_NDXDOTDP_EXPLICIT,                       // ndxdotdp_explicit
              TPL_NDXDOTDX_EXPLICIT,                       // ndxdotdx_explicit
              TPL_W_RECURSION_DEPTH                        // w_recursion_depth
          ) {}

    /**
     * @brief Clone this model instance.
     * @return A deep copy of this instance.
     */
    virtual amici::Model *clone() const override {
        return new Model_TPL_MODELNAME(*this);
    }

    /** model specific implementation of fJrz
     * @param nllh regularization for event measurements z
     * @param iz event output index
     * @param p parameter vector
     * @param k constant vector
     * @param z model event output at timepoint
     * @param sigmaz event measurement standard deviation at timepoint
     **/
    virtual void fJrz(realtype *nllh, const int iz, const realtype *p,
                      const realtype *k, const realtype *rz,
                      const realtype *sigmaz) override {}

    TPL_JY_IMPL

    /** model specific implementation of fJz
     * @param nllh negative log-likelihood for event measurements z
     * @param iz event output index
     * @param p parameter vector
     * @param k constant vector
     * @param z model event output at timepoint
     * @param sigmaz event measurement standard deviation at timepoint
     * @param mz event measurements at timepoint
     **/
    virtual void fJz(realtype *nllh, const int iz, const realtype *p,
                     const realtype *k, const realtype *z,
                     const realtype *sigmaz, const realtype *mz) override {}

    /** model specific implementation of fdJrzdsigma
     * @param dJrzdsigma Sensitivity of event penalization Jrz w.r.t.
     * standard deviation sigmaz
     * @param iz event output index
     * @param p parameter vector
     * @param k constant vector
     * @param rz model root output at timepoint
     * @param sigmaz event measurement standard deviation at timepoint
     **/
    virtual void fdJrzdsigma(realtype *dJrzdsigma, const int iz,
                             const realtype *p, const realtype *k,
                             const realtype *rz,
                             const realtype *sigmaz) override {}

    /** model specific implementation of fdJrzdz
     * @param dJrzdz partial derivative of event penalization Jrz
     * @param iz event output index
     * @param p parameter vector
     * @param k constant vector
     * @param rz model root output at timepoint
     * @param sigmaz event measurement standard deviation at timepoint
     **/
    virtual void fdJrzdz(realtype *dJrzdz, const int iz, const realtype *p,
                         const realtype *k, const realtype *rz,
                         const realtype *sigmaz) override {}

    TPL_DJYDSIGMA_IMPL

    /** model specific implementation of fdJzdsigma
     * @param dJzdsigma Sensitivity of event measurement
     * negative log-likelihood Jz w.r.t. standard deviation sigmaz
     * @param iz event output index
     * @param p parameter vector
     * @param k constant vector
     * @param z model event output at timepoint
     * @param sigmaz event measurement standard deviation at timepoint
     * @param mz event measurement at timepoint
     **/
    virtual void fdJzdsigma(realtype *dJzdsigma, const int iz,
                            const realtype *p, const realtype *k,
                            const realtype *z, const realtype *sigmaz,
                            const realtype *mz) override {}

    /** model specific implementation of fdJzdz
     * @param dJzdz partial derivative of event measurement negative
     *log-likelihood Jz
     * @param iz event output index
     * @param p parameter vector
     * @param k constant vector
     * @param z model event output at timepoint
     * @param sigmaz event measurement standard deviation at timepoint
     * @param mz event measurement at timepoint
     **/
    virtual void fdJzdz(realtype *dJzdz, const int iz, const realtype *p,
                        const realtype *k, const realtype *z,
                        const realtype *sigmaz, const realtype *mz) override {}

    /** model specific implementation of fdeltasx
     * @param deltaqB sensitivity update
     * @param t current time
     * @param x current state
     * @param p parameter vector
     * @param k constant vector
     * @param h heaviside vector
     * @param ip sensitivity index
     * @param ie event index
     * @param xdot new model right hand side
     * @param xdot_old previous model right hand side
     * @param xB adjoint state
     **/
    virtual void fdeltaqB(realtype *deltaqB, const realtype t,
                          const realtype *x, const realtype *p,
                          const realtype *k, const realtype *h, const int ip,
                          const int ie, const realtype *xdot,
                          const realtype *xdot_old,
                          const realtype *xB) override {}

    TPL_DELTASX_IMPL

    TPL_DELTAX_IMPL

    /** model specific implementation of fdeltaxB
     * @param deltaxB adjoint state update
     * @param t current time
     * @param x current state
     * @param p parameter vector
     * @param k constant vector
     * @param h heaviside vector
     * @param ie event index
     * @param xdot new model right hand side
     * @param xdot_old previous model right hand side
     * @param xB current adjoint state
     **/
    virtual void fdeltaxB(realtype *deltaxB, const realtype t,
                          const realtype *x, const realtype *p,
                          const realtype *k, const realtype *h, const int ie,
                          const realtype *xdot, const realtype *xdot_old,
                          const realtype *xB) override {}

    /** model specific implementation of fdrzdp
     * @param drzdp partial derivative of root output rz w.r.t. model parameters
     *p
     * @param ie event index
     * @param t current time
     * @param x current state
     * @param p parameter vector
     * @param k constant vector
     * @param h heaviside vector
     * @param ip parameter index w.r.t. which the derivative is requested
     **/
    virtual void fdrzdp(realtype *drzdp, const int ie, const realtype t,
                        const realtype *x, const realtype *p, const realtype *k,
                        const realtype *h, const int ip) override {}

    /** model specific implementation of fdrzdx
     * @param drzdx partial derivative of root output rz w.r.t. model states x
     * @param ie event index
     * @param t current time
     * @param x current state
     * @param p parameter vector
     * @param k constant vector
     * @param h heaviside vector
     **/
    virtual void fdrzdx(realtype *drzdx, const int ie, const realtype t,
                        const realtype *x, const realtype *p, const realtype *k,
                        const realtype *h) override {}

    TPL_DSIGMAYDP_IMPL

    /** model specific implementation of fsigmaz
     * @param dsigmazdp partial derivative of standard deviation of event
     *measurements
     * @param t current time
     * @param p parameter vector
     * @param k constant vector
     * @param ip sensitivity index
     **/
    virtual void fdsigmazdp(realtype *dsigmazdp, const realtype t,
                            const realtype *p, const realtype *k,
                            const int ip) override {}

    TPL_DJYDY_IMPL
    TPL_DJYDY_COLPTRS_IMPL
    TPL_DJYDY_ROWVALS_IMPL

    TPL_DWDP_IMPL
    TPL_DWDP_COLPTRS_IMPL
    TPL_DWDP_ROWVALS_IMPL

    TPL_DWDX_IMPL
    TPL_DWDX_COLPTRS_IMPL
    TPL_DWDX_ROWVALS_IMPL

    TPL_DWDW_IMPL
    TPL_DWDW_COLPTRS_IMPL
    TPL_DWDW_ROWVALS_IMPL

    TPL_DXDOTDW_IMPL
    TPL_DXDOTDW_COLPTRS_IMPL
    TPL_DXDOTDW_ROWVALS_IMPL

    TPL_DXDOTDP_EXPLICIT_IMPL
    TPL_DXDOTDP_EXPLICIT_COLPTRS_IMPL
    TPL_DXDOTDP_EXPLICIT_ROWVALS_IMPL

    TPL_DXDOTDX_EXPLICIT_IMPL
    TPL_DXDOTDX_EXPLICIT_COLPTRS_IMPL
    TPL_DXDOTDX_EXPLICIT_ROWVALS_IMPL

    TPL_DYDX_IMPL

    TPL_DYDP_IMPL

    /** model specific implementation of fdzdp
     * @param dzdp partial derivative of event-resolved output z w.r.t. model
     *parameters p
     * @param ie event index
     * @param t current time
     * @param x current state
     * @param p parameter vector
     * @param k constant vector
     * @param h heaviside vector
     * @param ip parameter index w.r.t. which the derivative is requested
     **/
    virtual void fdzdp(realtype *dzdp, const int ie, const realtype t,
                       const realtype *x, const realtype *p, const realtype *k,
                       const realtype *h, const int ip) override {}

    /** model specific implementation of fdzdx
     * @param dzdx partial derivative of event-resolved output z w.r.t. model
     *states x
     * @param ie event index
     * @param t current time
     * @param x current state
     * @param p parameter vector
     * @param k constant vector
     * @param h heaviside vector
     **/
    virtual void fdzdx(realtype *dzdx, const int ie, const realtype t,
                       const realtype *x, const realtype *p, const realtype *k,
                       const realtype *h) override {}

    TPL_ROOT_IMPL

    /** model specific implementation of frz
     * @param rz value of root function at current timepoint (non-output events
     *not included)
     * @param ie event index
     * @param t current time
     * @param x current state
     * @param p parameter vector
     * @param k constant vector
     * @param h heaviside vector
     **/
    virtual void frz(realtype *rz, const int ie, const realtype t,
                     const realtype *x, const realtype *p, const realtype *k,
                     const realtype *h) override {}

    TPL_SIGMAY_IMPL

    /** model specific implementation of fsigmaz
     * @param sigmaz standard deviation of event measurements
     * @param t current time
     * @param p parameter vector
     * @param k constant vector
     **/
    virtual void fsigmaz(realtype *sigmaz, const realtype t, const realtype *p,
                         const realtype *k) override {}

    /** model specific implementation of fsrz
     * @param srz Sensitivity of rz, total derivative
     * @param ie event index
     * @param t current time
     * @param x current state
     * @param p parameter vector
     * @param k constant vector
     * @param sx current state sensitivity
     * @param h heaviside vector
     * @param ip sensitivity index
     **/
    virtual void fsrz(realtype *srz, const int ie, const realtype t,
                      const realtype *x, const realtype *p, const realtype *k,
                      const realtype *h, const realtype *sx,
                      const int ip) override {}

    TPL_STAU_IMPL
    TPL_SX0_IMPL
    TPL_SX0_FIXEDPARAMETERS_IMPL

    /** model specific implementation of fsz
     * @param sz Sensitivity of rz, total derivative
     * @param ie event index
     * @param t current time
     * @param x current state
     * @param p parameter vector
     * @param k constant vector
     * @param h heaviside vector
     * @param sx current state sensitivity
     * @param ip sensitivity index
     **/
    virtual void fsz(realtype *sz, const int ie, const realtype t,
                     const realtype *x, const realtype *p, const realtype *k,
                     const realtype *h, const realtype *sx,
                     const int ip) override {}

    TPL_W_IMPL

    TPL_X0_IMPL

    TPL_X0_FIXEDPARAMETERS_IMPL

    TPL_XDOT_IMPL

    TPL_Y_IMPL

    /** model specific implementation of fz
     * @param z value of event output
     * @param ie event index
     * @param t current time
     * @param x current state
     * @param p parameter vector
     * @param k constant vector
     * @param h heaviside vector
     **/
    virtual void fz(realtype *z, const int ie, const realtype t,
                    const realtype *x, const realtype *p, const realtype *k,
                    const realtype *h) override {}

    TPL_X_RDATA_IMPL

    TPL_X_SOLVER_IMPL

    TPL_TOTAL_CL_IMPL

    std::string getName() const override {
        return "TPL_MODELNAME";
    }

    /**
     * @brief Get names of the model parameters
     * @return the names
     */
    virtual std::vector<std::string> getParameterNames() const override {
        return std::vector<std::string>(parameterNames.begin(),
                                        parameterNames.end());
    }

    /**
     * @brief Get names of the model states
     * @return the names
     */
    virtual std::vector<std::string> getStateNames() const override {
        return std::vector<std::string>(stateNames.begin(), stateNames.end());
    }

    /**
     * @brief Get names of the fixed model parameters
     * @return the names
     */
    virtual std::vector<std::string> getFixedParameterNames() const override {
        return std::vector<std::string>(fixedParameterNames.begin(),
                                        fixedParameterNames.end());
    }

    /**
     * @brief Get names of the observables
     * @return the names
     */
    virtual std::vector<std::string> getObservableNames() const override {
        return std::vector<std::string>(observableNames.begin(),
                                        observableNames.end());
    }

    /**
     * @brief Get names of model expressions
     * @return Expression names
     */
    virtual std::vector<std::string> getExpressionNames() const override {
        return std::vector<std::string>(expressionNames.begin(),
                                        expressionNames.end());
    }

    /**
     * @brief Get ids of the model parameters
     * @return the ids
     */
    virtual std::vector<std::string> getParameterIds() const override {
        return std::vector<std::string>(parameterIds.begin(),
                                        parameterIds.end());
    }

    /**
     * @brief Get ids of the model states
     * @return the ids
     */
    virtual std::vector<std::string> getStateIds() const override {
        return std::vector<std::string>(stateIds.begin(), stateIds.end());
    }

    /**
     * @brief Get ids of the fixed model parameters
     * @return the ids
     */
    virtual std::vector<std::string> getFixedParameterIds() const override {
        return std::vector<std::string>(fixedParameterIds.begin(),
                                        fixedParameterIds.end());
    }

    /**
     * @brief Get ids of the observables
     * @return the ids
     */
    virtual std::vector<std::string> getObservableIds() const override {
        return std::vector<std::string>(observableIds.begin(),
                                        observableIds.end());
    }

    /**
     * @brief Get IDs of model expressions
     * @return Expression IDs
     */
    virtual std::vector<std::string> getExpressionIds() const override {
        return std::vector<std::string>(expressionIds.begin(),
                                        expressionIds.end());
    }

    /**
     * @brief function indicating whether reinitialization of states depending on
     fixed parameters is permissible
     * @return flag indicating whether reinitialization of states depending on
     fixed parameters is permissible
     */
    virtual bool isFixedParameterStateReinitializationAllowed() const override {
        return TPL_REINIT_FIXPAR_INITCOND;
    }

    /**
     * @brief returns the AMICI version that was used to generate the model
     * @return AMICI version string
     */
    virtual std::string getAmiciVersion() const override {
        return "TPL_AMICI_VERSION_STRING";
    }

    /**
     * @brief returns the amici version that was used to generate the model
     * @return AMICI git commit hash
     */
    virtual std::string getAmiciCommit() const override {
        return "TPL_AMICI_COMMIT_STRING";
    }

    virtual bool hasQuadraticLLH() const override {
        return TPL_QUADRATIC_LLH;
    }
};

} // namespace model_TPL_MODELNAME

} // namespace amici

#endif /* _amici_TPL_MODELNAME_h */


#ifdef __cplusplus
extern "C"
{
#endif // __cplusplus
#ifdef USE_BLAS
#include <cblas.h>

int _use_blas()
{
    return 1;
}
#else // USE_BLAS
#include <xmmintrin.h>

#if defined(_MSC_VER)
#define ALIGNAS(byte_alignment) __declspec(align(byte_alignment))
#elif defined(__GNUC__)
#define ALIGNAS(byte_alignment) __attribute__((aligned(byte_alignment)))
#endif

float cblas_snrm2(const int N, const float *m1, const int incX)
{
    if (N % 4 != 0) {
        fprintf(stderr, "cblas_snrm2() expects N to be a multiple of 4.\n");
        exit(EXIT_FAILURE);
    }

    float norm = 0;
    ALIGNAS(16) float z[4];
    __m128 X;
    __m128 Z = _mm_setzero_ps();

    for (int i=0; i<N; i+=4) {
        X = _mm_load_ps(&m1[i]);
        X = _mm_mul_ps(X, X);
        Z = _mm_add_ps(X, Z);
    }

    _mm_store_ps(z, Z);
    norm += z[0] + z[1] + z[2] + z[3];
    return sqrtf(norm);
}

float cblas_sdot(const int N, const float *m1, const int incX,
                 const float *m2, const int incY)
{
    if (N % 4 != 0) {
        fprintf(stderr, "cblas_sdot() expects N to be a multiple of 4.\n");
        exit(EXIT_FAILURE);
    }

    float dot = 0;
    ALIGNAS(16) float z[4];
    __m128 X, Y;
    __m128 Z = _mm_setzero_ps();

    for (int i=0; i<N; i+=4) {
        X = _mm_load_ps(&m1[i]);
        Y = _mm_load_ps(&m2[i]);
        X = _mm_mul_ps(X, Y);
        Z = _mm_add_ps(X, Z);
    }

    _mm_store_ps(z, Z);
    dot += z[0] + z[1] + z[2] + z[3];
    return dot;
}

int _use_blas()
{
    return 0;
}
#endif // USE_BLAS
#ifdef __cplusplus
}
#endif // __cplusplus


#include <gsl/gsl_rng.h>
#include <gsl/gsl_randist.h>
#include <math.h>
#include <string.h>
#include "cconfigspace_internal.h"
#include "distribution_internal.h"

struct _ccs_distribution_mixture_data_s {
	_ccs_distribution_common_data_t  common_data;
	size_t                           num_distributions;
	ccs_distribution_t              *distributions;
	ccs_interval_t                  *bounds;
        ccs_float_t                     *weights;
};
typedef struct _ccs_distribution_mixture_data_s _ccs_distribution_mixture_data_t;

static ccs_result_t
_ccs_distribution_mixture_del(ccs_object_t o) {
	struct _ccs_distribution_mixture_data_s *data =
		(struct _ccs_distribution_mixture_data_s *)(((ccs_distribution_t)o)->data);
	for (size_t i = 0; i < data->num_distributions; i++) {
		ccs_release_object(data->distributions[i]);
	}
	return CCS_SUCCESS;
}

static ccs_result_t
_ccs_distribution_mixture_get_bounds(_ccs_distribution_data_t *data,
                                     ccs_interval_t           *interval_ret);

static ccs_result_t
_ccs_distribution_mixture_samples(_ccs_distribution_data_t *data,
                                  ccs_rng_t                 rng,
                                  size_t                    num_values,
                                  ccs_numeric_t            *values);

static ccs_result_t
_ccs_distribution_mixture_strided_samples(_ccs_distribution_data_t *data,
                                          ccs_rng_t                 rng,
                                          size_t                    num_values,
                                          size_t                    stride,
                                          ccs_numeric_t            *values);

static ccs_result_t
_ccs_distribution_mixture_soa_samples(_ccs_distribution_data_t  *data,
                                      ccs_rng_t                  rng,
                                      size_t                     num_values,
                                      ccs_numeric_t            **values);

static _ccs_distribution_ops_t _ccs_distribution_mixture_ops = {
	{ &_ccs_distribution_mixture_del },
	&_ccs_distribution_mixture_samples,
	&_ccs_distribution_mixture_get_bounds,
	&_ccs_distribution_mixture_strided_samples,
	&_ccs_distribution_mixture_soa_samples
};

ccs_result_t
ccs_create_mixture_distribution(size_t              num_distributions,
                                ccs_distribution_t *distributions,
                                ccs_float_t        *weights,
                                ccs_distribution_t *distribution_ret) {
	CCS_CHECK_ARY(num_distributions, distributions);
	CCS_CHECK_ARY(num_distributions, weights);
	CCS_CHECK_PTR(distribution_ret);
	if (!num_distributions || num_distributions > INT64_MAX)
		return -CCS_INVALID_VALUE;
	ccs_float_t sum = 0.0;

	size_t             dimension, i, j;
	ccs_result_t       err;
	CCS_VALIDATE(ccs_distribution_get_dimension(distributions[0], &dimension));
	for(i = 1; i < num_distributions; i++) {
		size_t             dimension_tmp;
		CCS_VALIDATE(ccs_distribution_get_dimension(distributions[i], &dimension_tmp));
		if (dimension != dimension_tmp)
			return -CCS_INVALID_DISTRIBUTION;
	}

	for(i = 0; i < num_distributions; i++) {
		if (weights[i] < 0.0)
			return -CCS_INVALID_VALUE;
		sum += weights[i];
	}
	if (sum == 0.0)
		return -CCS_INVALID_VALUE;
	ccs_float_t inv_sum = 1.0/sum;
	if (isnan(inv_sum) || !isfinite(inv_sum))
		return -CCS_INVALID_VALUE;

	uintptr_t mem;
	uintptr_t cur_mem;
	uintptr_t tmp_mem;
	ccs_interval_t     *bounds_tmp;
	ccs_numeric_type_t *data_types_tmp;
	ccs_distribution_t distrib;
	_ccs_distribution_mixture_data_t *distrib_data;
	mem = (uintptr_t)calloc(1,
		sizeof(struct _ccs_distribution_s) +
		sizeof(_ccs_distribution_mixture_data_t) +
		sizeof(ccs_distribution_t)*num_distributions +
		sizeof(ccs_interval_t)*dimension +
		sizeof(ccs_float_t)*(num_distributions + 1) +
		sizeof(ccs_numeric_type_t)*dimension);

	if (!mem)
		return -CCS_OUT_OF_MEMORY;
        cur_mem = mem;

	tmp_mem = (uintptr_t)calloc(1,
		sizeof(ccs_interval_t)*num_distributions +
		sizeof(ccs_numeric_type_t)*dimension);
	if (!tmp_mem) {
		err = -CCS_OUT_OF_MEMORY;
		goto memory;
	}
	bounds_tmp = (ccs_interval_t *)tmp_mem;
	data_types_tmp = (ccs_numeric_type_t *)(tmp_mem + sizeof(ccs_interval_t)*num_distributions);

	distrib = (ccs_distribution_t)cur_mem;
	cur_mem += sizeof(struct _ccs_distribution_s);
	_ccs_object_init(&(distrib->obj), CCS_DISTRIBUTION, (_ccs_object_ops_t *)&_ccs_distribution_mixture_ops);
	distrib_data = (_ccs_distribution_mixture_data_t *)(cur_mem);
	cur_mem += sizeof(_ccs_distribution_mixture_data_t);
	distrib_data->common_data.type        = CCS_MIXTURE;
	distrib_data->common_data.dimension   = dimension;
	distrib_data->num_distributions       = num_distributions;
	distrib_data->distributions           = (ccs_distribution_t *)(cur_mem);
	cur_mem += sizeof(ccs_distribution_t)*num_distributions;
	distrib_data->bounds                  = (ccs_interval_t *)(cur_mem);
	cur_mem += sizeof(ccs_interval_t)*dimension;
	distrib_data->weights                 = (ccs_float_t *)(cur_mem);
	cur_mem += sizeof(ccs_float_t)*(num_distributions + 1);
	distrib_data->common_data.data_types  = (ccs_numeric_type_t *)(cur_mem);
	cur_mem += sizeof(ccs_numeric_type_t)*dimension;

	CCS_VALIDATE_ERR_GOTO(err,
	  ccs_distribution_get_data_types(distributions[0], distrib_data->common_data.data_types),
	  tmpmemory);
	for (i = 1; i < num_distributions; i++) {
		CCS_VALIDATE_ERR_GOTO(err,
		  ccs_distribution_get_data_types(distributions[i], data_types_tmp),
		  tmpmemory);
		for (j = 0; j < dimension; j++)
			if(distrib_data->common_data.data_types[j] != data_types_tmp[j]) {
				err = -CCS_INVALID_DISTRIBUTION;
				goto tmpmemory;
			}
	}

	CCS_VALIDATE_ERR_GOTO(err,
	  ccs_distribution_get_bounds(distributions[0], distrib_data->bounds),
	  tmpmemory);
	for (i = 1; i < num_distributions; i++) {
		CCS_VALIDATE_ERR_GOTO(err,
		  ccs_distribution_get_bounds(distributions[i], bounds_tmp),
		  tmpmemory);
		for (j = 0; j < dimension; j++) {
			CCS_VALIDATE_ERR_GOTO(err,
			  ccs_interval_union(distrib_data->bounds + j, bounds_tmp + j,
			                         distrib_data->bounds + j), tmpmemory);
		}
	}

	distrib_data->weights[0] = 0.0;
	for (i = 1; i <= num_distributions; i++) {
		distrib_data->weights[i] = distrib_data->weights[i-1] + weights[i-1] * inv_sum;
	}
	distrib_data->weights[num_distributions] = 1.0;
	if (1.0 < distrib_data->weights[num_distributions-1]) {
		free((void *)mem);
		return -CCS_INVALID_VALUE;
	}
	for (i = 0; i < num_distributions; i++) {
		CCS_VALIDATE_ERR_GOTO(err, ccs_retain_object(distributions[i]), distrib);
		distrib_data->distributions[i] = distributions[i];
	}
	distrib->data = (_ccs_distribution_data_t *)distrib_data;
	*distribution_ret = distrib;
	free((void *)tmp_mem);
	return CCS_SUCCESS;
distrib:
	for (i = 0; i < num_distributions; i++) {
		if (distrib_data->distributions[i])
			ccs_release_object(distributions[i]);
	}
tmpmemory:
	free((void *)tmp_mem);
memory:
	free((void *)mem);
	return err;
}

static ccs_result_t
_ccs_distribution_mixture_get_bounds(_ccs_distribution_data_t *data,
                                     ccs_interval_t           *interval_ret) {
	_ccs_distribution_mixture_data_t *d = (_ccs_distribution_mixture_data_t *)data;
	memcpy(interval_ret, d->bounds, d->common_data.dimension*sizeof(ccs_interval_t));
	return CCS_SUCCESS;
}

static inline _ccs_distribution_ops_t *
ccs_distribution_get_ops(ccs_distribution_t distribution) {
	return (_ccs_distribution_ops_t *)distribution->obj.ops;
}

static ccs_result_t
_ccs_distribution_mixture_samples(_ccs_distribution_data_t *data,
                                  ccs_rng_t                 rng,
                                  size_t                    num_values,
                                  ccs_numeric_t            *values) {
	_ccs_distribution_mixture_data_t *d = (_ccs_distribution_mixture_data_t *)data;
	size_t dim = d->common_data.dimension;

	gsl_rng *grng;
	CCS_VALIDATE(ccs_rng_get_gsl_rng(rng, &grng));

	for (size_t i = 0; i < num_values; i++) {
		ccs_float_t rnd = gsl_rng_uniform(grng);
		ccs_int_t index = ccs_dichotomic_search(d->num_distributions, d->weights, rnd);
		CCS_VALIDATE(ccs_distribution_get_ops(d->distributions[index])->samples(d->distributions[index]->data, rng, 1, values + i*dim));
	}
	return CCS_SUCCESS;
}

static ccs_result_t
_ccs_distribution_mixture_strided_samples(_ccs_distribution_data_t *data,
                                          ccs_rng_t                 rng,
                                          size_t                    num_values,
                                          size_t                    stride,
                                          ccs_numeric_t            *values) {
	_ccs_distribution_mixture_data_t *d = (_ccs_distribution_mixture_data_t *)data;

	gsl_rng *grng;
	CCS_VALIDATE(ccs_rng_get_gsl_rng(rng, &grng));

	for (size_t i = 0; i < num_values; i++) {
		ccs_float_t rnd = gsl_rng_uniform(grng);
		ccs_int_t index = ccs_dichotomic_search(d->num_distributions, d->weights, rnd);
		CCS_VALIDATE(ccs_distribution_get_ops(d->distributions[index])->samples(d->distributions[index]->data, rng, 1, values + i*stride));
	}
	return CCS_SUCCESS;
}

static ccs_result_t
_ccs_distribution_mixture_soa_samples(_ccs_distribution_data_t  *data,
                                      ccs_rng_t                  rng,
                                      size_t                     num_values,
                                      ccs_numeric_t            **values) {
	_ccs_distribution_mixture_data_t *d = (_ccs_distribution_mixture_data_t *)data;
	size_t dim = d->common_data.dimension;
	int needed = 0;

	ccs_numeric_t **p_values = (ccs_numeric_t**)alloca(dim*sizeof(ccs_numeric_t*));
	for (size_t i = 0; i < dim; i++)
		if (values[i]) {
			needed = 1;
			break;
		}
	if (!needed)
		return CCS_SUCCESS;

	gsl_rng *grng;
	CCS_VALIDATE(ccs_rng_get_gsl_rng(rng, &grng));

	for (size_t i = 0; i < num_values; i++) {
		ccs_float_t rnd = gsl_rng_uniform(grng);
		ccs_int_t index = ccs_dichotomic_search(d->num_distributions, d->weights, rnd);
		for (size_t j = 0; j < dim; j++)
			if (values[j])
				p_values[j] = values[j] + i;
		CCS_VALIDATE(ccs_distribution_get_ops(d->distributions[index])->soa_samples(d->distributions[index]->data, rng, 1, p_values));
	}
	return CCS_SUCCESS;
}

ccs_result_t
ccs_mixture_distribution_get_num_distributions(ccs_distribution_t  distribution,
                                               size_t             *num_distributions_ret) {
	CCS_CHECK_DISTRIBUTION(distribution, CCS_MIXTURE);
	CCS_CHECK_PTR(num_distributions_ret);
	_ccs_distribution_mixture_data_t * data = (_ccs_distribution_mixture_data_t *)distribution->data;
	*num_distributions_ret = data->num_distributions;
	return CCS_SUCCESS;
}

ccs_result_t
ccs_mixture_distribution_get_distributions(ccs_distribution_t  distribution,
                                           size_t              num_distributions,
                                           ccs_distribution_t *distributions,
                                           size_t             *num_distributions_ret) {
	CCS_CHECK_DISTRIBUTION(distribution, CCS_MIXTURE);
	CCS_CHECK_ARY(num_distributions, distributions);
	if (!distributions && !num_distributions_ret)
		return -CCS_INVALID_VALUE;
	_ccs_distribution_mixture_data_t * data = (_ccs_distribution_mixture_data_t *)distribution->data;
	if (distributions) {
		if (num_distributions < data->num_distributions)
			return -CCS_INVALID_VALUE;
		for (size_t i = 0; i < data->num_distributions; i++)
			distributions[i] = data->distributions[i];
		for (size_t i = data->num_distributions; i < num_distributions; i++)
			distributions[i] = NULL;
	}
	if (num_distributions_ret)
		*num_distributions_ret = data->num_distributions;
	return CCS_SUCCESS;
}

ccs_result_t
ccs_mixture_distribution_get_weights(ccs_distribution_t  distribution,
                                     size_t              num_weights,
                                     ccs_float_t        *weights,
                                     size_t             *num_weights_ret) {
	CCS_CHECK_DISTRIBUTION(distribution, CCS_MIXTURE);
	CCS_CHECK_ARY(num_weights, weights);
	if (!weights && !num_weights_ret)
		return -CCS_INVALID_VALUE;
	_ccs_distribution_mixture_data_t * data = (_ccs_distribution_mixture_data_t *)distribution->data;
	if (weights) {
		if (num_weights < data->num_distributions)
			return -CCS_INVALID_VALUE;
		for (size_t i = 0; i < data->num_distributions; i++)
			weights[i] = data->weights[i+1] - data->weights[i];
		for (size_t i = data->num_distributions; i < num_weights; i++)
			weights[i] = 0.0;
	}
	if (num_weights_ret)
		*num_weights_ret = data->num_distributions;
	return CCS_SUCCESS;
}


#include <iostream>
#define ARMA_64BIT_WORD
#include <armadillo>
#include <gsl/gsl_rng.h>
#include <gsl/gsl_sf_psi.h>
#include <list>

#include "utils.h"
#include "eval.h"

using namespace std;
using namespace arma;
#include "data.h"

struct model_settings {
    bool verbose;

    string outdir;
    string datadir;

    double a_theta;
    double b_theta;
    double a_beta;
    double b_beta;
    double a_tau;
    double b_tau;
    double a_delta;
    double b_delta;

    bool social_only;
    bool factor_only;
    bool fix_influence;
    bool item_bias;
    bool binary;
    bool directed;

    long   seed;
    int    save_freq;
    int    eval_freq;
    int    conv_freq;
    int    max_iter;
    int    min_iter;
    double likelihood_delta;

    bool svi;
    bool final_pass;
    bool final_pass_test;
    int    sample_size;
    double delay;
    double forget;

    int k;

    void set(bool print, string out, string data, bool use_svi,
             double athe, double bthe, double abet, double bbet,
             double atau, double btau, double adelta, double bdelta,
             bool social, bool factor, bool bias, bool bin, bool dir,
             long rand, int savef, int evalf, int convf,
             int iter_max, int iter_min, double delta,
             bool finalpass, bool finalpasstest,
             int sample, double svi_delay, double svi_forget,
             bool fix, int num_factors) {
        verbose = print;

        outdir = out;
        datadir = data;

        svi = use_svi;

        a_theta = athe;
        b_theta = bthe;
        a_beta  = abet;
        b_beta  = bbet;
        a_tau   = atau;
        b_tau   = btau;
        a_delta = adelta;
        b_delta = bdelta;

        social_only = social;
        factor_only = factor;
        fix_influence = fix;
        item_bias = bias;
        binary = bin;
        directed = dir;

        seed = rand;
        save_freq = savef;
        eval_freq = evalf;
        conv_freq = convf;
        max_iter = iter_max;
        min_iter = iter_min;
        likelihood_delta = delta;

        final_pass = finalpass;
        final_pass_test = finalpasstest;
        sample_size = sample;
        delay = svi_delay;
        forget = svi_forget;

        k = num_factors;
    }

    void set_stochastic_inference(bool setting) {
        svi = setting;
    }

    void set_sample_size(int setting) {
        sample_size = setting;
    }

    void save(string filename) {
        FILE* file = fopen(filename.c_str(), "w");

        fprintf(file, "data directory: %s\n", datadir.c_str());

        fprintf(file, "\nmodel specification:\n");
        if (social_only) {
            fprintf(file, "\tsocial factorization (SF)   [ social factors only ]\n");
        } else if (factor_only) {
            fprintf(file, "\tPoisson factorization (PF)   [ general preference factors only ]\n");
        } else {
            fprintf(file, "\tsocial Poisson factorization (SPF)\n");
        }

        if (fix_influence) {
            fprintf(file,"\tsocial influence parameters fixed to 1\n");
        }

        if (!social_only) {
            fprintf(file, "\tK = %d   (number of latent factors for general preferences)\n", k);
        }

        fprintf(file, "\nshape and rate hyperparameters:\n");
        if (!social_only) {
            fprintf(file, "\ttheta (%f, %f)\n", a_theta, b_theta);
            fprintf(file, "\tbeta  (%f, %f)\n", a_beta, b_beta);
        }
        if (!factor_only) {
            fprintf(file, "\ttau   (%f, %f)\n", a_tau, b_tau);
        }
        if (item_bias) {
            fprintf(file, "\tdelta (%.2f, %.2f)\n", a_delta, b_delta);
        }

        fprintf(file, "\ndata attributes:\n");

        if (binary) {
            fprintf(file, "\tbinary ratings\n");
        } else {
            fprintf(file, "\tinteger ratings\n");
        }

        if (!factor_only) {
            if (directed) {
                fprintf(file, "\tdirected network\n");
            } else {
                fprintf(file, "\tundirected network\n");
            }
        }

        fprintf(file, "\ninference parameters:\n");
        fprintf(file, "\tseed:                                     %d\n", (int)seed);
        fprintf(file, "\tsave frequency:                           %d\n", save_freq);
        fprintf(file, "\tevaluation frequency:                     %d\n", eval_freq);
        fprintf(file, "\tconvergence check frequency:              %d\n", conv_freq);
        fprintf(file, "\tmaximum number of iterations:             %d\n", max_iter);
        fprintf(file, "\tminimum number of iterations:             %d\n", min_iter);
        fprintf(file, "\tchange in log likelihood for convergence: %f\n", likelihood_delta);
        fprintf(file, "\tfinal pass after convergence:             %s\n", final_pass ? "all users" :
            (final_pass_test ? "test users only" : "none"));

        if (svi) {
            fprintf(file, "\nStochastic variational inference parameters\n");
            fprintf(file, "\tsample size:                              %d\n", sample_size);
            fprintf(file, "\tSVI delay (tau):                          %f\n", delay);
            fprintf(file, "\tSVI forgetting rate (kappa):              %f\n", forget);
        } else {
            fprintf(file, "\nusing batch variational inference\n");
        }

        fclose(file);
    }
};

class SPF: protected Model {
    private:
        model_settings* settings;
        Data* data;

        // model parameters
        sp_fmat tau; // user influence
        sp_fmat logtau; // fake "log" user influence
                       // it's really exp(E[log(tau)]) which != E[tau]
        fmat theta;  // user preferences
        fmat beta;   // item attributes
        fmat logtheta;  // log variant of above
        fmat logbeta;   // ditto
        fvec delta;

        // helper parameters
        sp_fmat a_tau;
        sp_fmat b_tau;
        fmat a_theta;
        fmat b_theta;
        fmat a_beta;
        fmat a_beta_user;
        fmat a_beta_old;
        fmat b_beta;
        fvec a_delta;
        float b_delta;
        fvec a_delta_user;
        fvec a_delta_old;

        // random number generator
        gsl_rng* rand_gen;

        void initialize_parameters();
        void reset_helper_params();
        void save_parameters(string label);

        // parameter updates
        void update_shape(int user, int item, int rating);
        double update_tau(int user);
        double update_theta(int user);
        void update_beta(int item);
        void update_delta(int item);

        double get_ave_log_likelihood();
        void log_convergence(int iteration, double ave_ll, double delta_ll);
        void log_time(int iteration, double duration);
        void log_params(int iteration, double tau_change, double theta_change);
        void log_user(FILE* file, int user, int heldout, double rmse,
            double mae, double rank, int first, double crr, double ncrr,
            double ndcg);

        // define how to scale updates (training / sample size) (for SVI)
        double scale;

        // counts of number of times an item has been seen in a sample (for SVI)
        map<int,int> iter_count;

        void evaluate(string label);
        void evaluate(string label, bool write_rankings);

    public:
        SPF(model_settings* model_set, Data* dataset);
        void learn();
        double predict(int user, int item);
        void evaluate();

};


/**
 * @file      LoadFromBST.h
 *
 * @brief     Definition of function to load voxel volume from BST files
 *
 * @author    Stefan Reinhold
 * @copyright Copyright (C) 2018 Stefan Reinhold  -- All Rights Reserved.
 *            You may use, distribute and modify this code under the terms of
 *            the AFL 3.0 license; see LICENSE for full license details.
 */

#pragma once

#include "VolumeSize.h"
#include "VoxelSize.h"

#include <Eigen/Core>
#include <ImageStack/ImageStack.h>
#include <ImageStack/ImageStackLoaderBST.h>
#include <ImageStack/ResolutionDecorator.h>
#include <gsl/gsl>

namespace CortidQCT {

namespace IO {

/**
 * @brief Loads a BST file and calles the given functional with the loaded
 * data.
 *
 * The functional is called with a pointer to the raw data as the first
 * argument, the 3d size of the loaded data as the second argument a with the
 * 3d resolution (i.e. voxel size) as the third argument.
 *
 * @tparam F functional to be called by the function
 * @param filename Path to the file to be loaded
 * @param f functional that is beeing called
 * @throw std::runtime_error if the loading failed
 */
template <class F> void loadFromBST(std::string const &filename, F &&f) {
  using Decorator = ImageStack::ResolutionDecorator;
  using Img = ImageStack::ImageStack<float, ImageStack::HostStorage, Decorator>;
  using Loader = ImageStack::ImageStackLoaderBST<Img>;

  auto const img = Img(Loader(filename));

  auto const mapping = img.map();
  auto const size = img.size().template cast<std::size_t>().eval();
  auto const resol = img.resolution.template cast<float>().eval();
  VoxelSize voxelSize{resol.x(), resol.y(), resol.z()};
  VolumeSize volumeSize{size.x(), size.y(), size.z()};

  f(mapping.data(), volumeSize, voxelSize);
}

} // namespace IO

} // namespace CortidQCT


#include <stdio.h>
#include <stdlib.h>
#include <gsl/gsl_randist.h>
#include <errno.h>
#include <string.h>
#include <time.h>

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

typedef struct {
	int prey;
	int predator;
} lvstate;

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

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

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

void callStepLV(lvstate *simData, double *t0p, double *dtp, double *lvParam) {
	int j;
	for (j=0;j<SAMPLENUM;j++) {
		stepLV(simData + j, t0p, dtp, lvParam);
	}

}

//particle filter: output new estimated lvstate and its likehood
void pfPropPar(lvstate ppstate[16], obsdata *myobsdata, double *lvParam,
		double *ll) {
	//printf("Starting pfPropPar...\n");
	typedef struct {
		lvstate *vpptuple;
	} stateVec;

	int i, j, k, l;

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

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

	//printf("Starting pfPropPar 1 ...\n");
	stateVec *myStateVec = malloc(obsnum * sizeof(stateVec));
	if (myStateVec == NULL) {
		printf("Fail to initiate myStateVec...Exit");
		goto freeMem;
	}
	memset(myStateVec, 0, obsnum * sizeof(stateVec));

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

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

	*ll = 0.0;

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

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

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

		wSum = 0.0;

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

		//printf("tempvec 1 %i\n", ((tempStateVec+0)->vpptuple+1)->prey);
		callStepLV(simData, &curTime, &delTime, lvParam);

		for (j = 0; j < SAMPLENUM; j++) {
			//stepLV(simData + j, &curTime, &delTime, lvParam);
			((tempStateVec + i)->vpptuple + j)->predator =
					(simData + j)->predator;
			((tempStateVec + i)->vpptuple + j)->prey = (simData + j)->prey;
			//printf("tempState %i vpptuple %i , %p\n", i,j,(tempStateVec+i)->vpptuple+j);
			lw[j] = obsLik(((tempStateVec + i)->vpptuple + j),
					myobsdata[i].rawdata);

			if (j == 0) {
				maxLw = lw[0];
			} else {
				if (lw[j] > maxLw) {
					maxLw = lw[j];
				}
			}
		}

		for (l = 0; l < SAMPLENUM; l++) {
			w[l] = exp(lw[l] - maxLw);
			wSum = wSum + w[l];
		}
		rowsample(rows, w);

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

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

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

	freeMem:
	//printf("free mem...");
	if (myStateVec != NULL) {
		//printf("free mem 2...");
		for (i = 0; ((myStateVec + i)->vpptuple != NULL) && i < obsnum; i++) {
			//printf("free myStateVec...i %i\n", i);
			free((myStateVec + i)->vpptuple);
			(myStateVec + i)->vpptuple = NULL;
		}
		free(myStateVec);
		myStateVec = NULL;
	}
	if (tempStateVec != NULL) {
		for (i = 0; NULL != (tempStateVec + i)->vpptuple && i < obsnum; i++) {
			//printf("free tempStateVec...i %i\n", i);
			free((tempStateVec + i)->vpptuple);
			(tempStateVec + i)->vpptuple = NULL;
		}
		free(tempStateVec);
		tempStateVec = NULL;
	}
	if (simData != NULL) {
		free(simData);
		simData = NULL;
	}

}

void stepLV(lvstate *state, double *t0p, double *dtp, double *lvParam) {
	//printf("Starting stepLV...\n");
	register double t0 = *t0p, dt = *dtp, t;
	register double h0, h1, h2, h3, u, l0, l1, l2;
	register int prey, predator;
	prey = state->prey;
	predator = state->predator;
	l0 = lvParam[0];
	l1 = lvParam[1];
	l2 = lvParam[2];

	while (dt > 0) {
		h1 = l0 * prey;
		h2 = l1 * prey * predator;
		h3 = l2 * predator;
		h0 = h1 + h2 + h3;

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

	return;
}

//mcmc process
void runPmmhPath(int its, double *lvParam, double *obslik, lvstate ppstate[16],
		obsdata *myobsdata) {
	//printf("Starting runPmmhPath...\n");
	int i, j;
	double propParam[3];
	double curParam[3];
	double ll;
	double propMll, curMll = -1e99;
	lvstate curPath[obsnum];
	lvstate propPath[obsnum];

	FILE *fp = fopen("mcmc-out.txt", "wab+");
	if (fp == NULL) {
		printf("file is null.\n");
		return;
	}
	fprintf(fp, "th1,th2,th3,");
	for (i = 0; i <= 30; i++) {
		if ((i % 2) == 0) {
			if (i == 30) {
				fprintf(fp, "x%i,y%i", i, i);
			} else {
				fprintf(fp, "x%i,y%i,", i, i);
			}
		}
	}
	fprintf(fp, "\n");

	memcpy(curPath, ppstate, sizeof(lvstate) * obsnum);
	memcpy(curParam, lvParam, sizeof(double) * 3);
	for (i = its; i > 0; i--) {
		//calculate measurements and decide moving to next state or not
		memcpy(propParam, curParam, sizeof(double) * 3);
		peturb(propParam);

		pfPropPar(propPath, myobsdata, propParam, &ll);
		propMll = ll;
		if (log(gsl_ran_flat(r, 0.0, 1.0)) < (propMll - curMll)) {
			curMll = propMll;
			memcpy(curParam, propParam, sizeof(double) * 3);
			memcpy(curPath, propPath, sizeof(lvstate) * obsnum);
		}
		//write current parameters and state sequence into output
		fprintf(fp, "%f,%f,%f,", curParam[0], curParam[1], curParam[2]);

		for (j = 0; j < obsnum; j++) {
			if (j == (obsnum - 1)) {
				fprintf(fp, "%i,%i", (int)curPath[j].prey, (int)curPath[j].predator);
			} else {
				fprintf(fp, "%i,%i,", (int)curPath[j].prey, (int)curPath[j].predator);
			}
		}
		fprintf(fp, "\n");
	}
	fclose(fp);
}

void rowsample(int *rows, double *w) {
	//printf("Starting rowsample...\n");
	gsl_ran_discrete_t * grdp;
	int row;
	int i;
	grdp = gsl_ran_discrete_preproc(SAMPLENUM, w);
	for (i = 0; i < SAMPLENUM; i++) {

		row = (int) gsl_ran_discrete(r, grdp);
		rows[i] = row;

	}
	gsl_ran_discrete_free(grdp);
	return;
}

void peturb(double *lvParam) {
	//printf("Starting peturb...\n");

	const double SIGMA = 0.01;
	//printf("lvParam_before %f, %f, %f\n", lvParam[0], lvParam[1], lvParam[2]);
	lvParam[0] = lvParam[0] * exp(gsl_ran_gaussian(r, SIGMA));
	lvParam[1] = lvParam[1] * exp(gsl_ran_gaussian(r, SIGMA));
	lvParam[2] = lvParam[2] * exp(gsl_ran_gaussian(r, SIGMA));
	//printf("lvParam_after  %f, %f, %f\n", lvParam[0], lvParam[1], lvParam[2]);
}

//to get the prior simulate value of prey and predator
void simPrior(lvstate *simData) {
	//printf("Starting simPrior...\n");
	int i;

	const double PREY_MEAN = 50.0;
	const double PRED_MEAN = 100.0;

	for (i = 0; i < SAMPLENUM; i++) {
		simData[i].prey = gsl_ran_poisson(r, PREY_MEAN);
		simData[i].predator = gsl_ran_poisson(r, PRED_MEAN);
		//printf("simPrior %i\n", simData[i].prey);
	}
	//printf("End simPrior...\n");

}

void runModel(int its) {
	obsnum = 16;
	obsdata myobsdata[obsnum];
	lvstate mylvstate[obsnum];
	double ll = 0.0;

	double lvParam[3] = { 1.0, 0.005, 0.6 };

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

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

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

int main(int argc, char *argv[]) {
	const gsl_rng_type * T;
	long seed;
	gsl_rng_env_setup();
	T = gsl_rng_default;
	r = gsl_rng_alloc(T);
	seed = time(NULL) * getpid();    // set seed
	gsl_rng_set(r, seed);
	clock_t begin, end;
	double time_spent;

	begin = clock();
	int its;
	printf("Starting main...\n");

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

	gsl_rng_free(r);

	end = clock();
	time_spent = (double)(end - begin) / CLOCKS_PER_SEC;
	printf("Time consuming in total is %f\n", time_spent);

	printf("Running for %i", its);
	printf(" iterations, Done.");
	return 0;
}


#ifndef _PC_PATCH_H
#define _PC_PATCH_H
#include <petsc.h>
PETSC_EXTERN PetscErrorCode PCPatchInitializePackage(void);
PETSC_EXTERN PetscErrorCode PCCreate_PATCH(PC);
PETSC_EXTERN PetscErrorCode PCPatchSetCellNumbering(PC, PetscSection);
PETSC_EXTERN PetscErrorCode PCPatchSetDiscretisationInfo(PC, PetscInt, DM *, PetscInt *, PetscInt *, const PetscInt **, const PetscInt *, PetscInt, const PetscInt *, PetscInt, const PetscInt *);
PETSC_EXTERN PetscErrorCode PCPatchSetComputeOperator(PC, PetscErrorCode (*)(PC,Mat,PetscInt,const PetscInt *,PetscInt,const PetscInt *,void *),
                                                      void *);

typedef enum {PC_PATCH_STAR, PC_PATCH_VANKA, PC_PATCH_USER, PC_PATCH_PYTHON} PCPatchConstructType;

PETSC_EXTERN const char *const PCPatchConstructTypes[];
#endif


/*
 * Author : Pierre Schnizer
 * Date   : February 2005
 *
 */
#include <pygsl/error_helpers.h>
#include <pygsl/block_helpers.h>
#include <pygsl/pygsl_features.h>
#include <string.h>

#include <gsl/gsl_fft.h>
#include <gsl/gsl_fft_complex.h>
#include <gsl/gsl_fft_real.h>
#include <gsl/gsl_fft_halfcomplex.h>
#include <gsl/gsl_fft_complex_float.h>
#include <gsl/gsl_fft_real_float.h>
#include <gsl/gsl_fft_halfcomplex_float.h>
#include <gsl/gsl_blas.h>
#ifdef _PYGSL_GSL_HAS_WAVELET
#define forward forward_wavelet
#define backward backward_wavelet
#include <gsl/gsl_wavelet.h>
#include <gsl/gsl_wavelet2d.h>
#undef forward
#undef backward
#else
/*
#error Need GSL version 1.6 or higher with wavelet implementation! If you need it for a lower version
#error add the different conditional compilation!
*/
#endif /* gsl version > 1.5*/
static PyObject *module = NULL;
static const char filename[] = __FILE__;

/* The types */
#include "transformtypes.h"
/* The documentation strings */
#include "doc.h"
/* The python type which allows to allocate and pass the work space */
#include "space.c"
/* Function to copy the different arrays. */
#include "arraycopy.c"
/*
 * Macros and functions which set the information up and call the
 * PyGSL_transform functions
 */
#include "fft.c"
/*
 * The wavelet type and the methods to call the different transform functions.
 */
#ifdef _PYGSL_GSL_HAS_WAVELET
#include "wavelet.c"
#define PyGSL_WAVELET_TRANSFORM_TYPE(name) \
       {#name,             PyGSL_wavelet_init_ ## name,              METH_VARARGS, NULL}, \
       {#name "_centered", PyGSL_wavelet_init_ ## name ## _centered, METH_VARARGS, NULL},
#else
#define PyGSL_WAVELET_TRANSFORM_TYPE(name)
#endif
/*
 * The real workers.
 */
#include "core.c"

static PyMethodDef transformMethods[] = {
	/* workspace init functions  */
	{"complex_workspace",           PyGSL_transform_space_init_COMPLEX_WORKSPACE,            METH_VARARGS, (char*)cws_doc},
	{"complex_wavetable",           PyGSL_transform_space_init_COMPLEX_WAVETABLE,            METH_VARARGS, (char*)cwt_doc},
	{"real_workspace",              PyGSL_transform_space_init_REAL_WORKSPACE,               METH_VARARGS, (char*)rws_doc},
	{"real_wavetable",              PyGSL_transform_space_init_REAL_WAVETABLE,               METH_VARARGS, (char*)rwt_doc},
	{"halfcomplex_wavetable",       PyGSL_transform_space_init_HALFCOMPLEX_WAVETABLE,        METH_VARARGS, (char*)hcwt_doc},
	{"complex_workspace_float",     PyGSL_transform_space_init_COMPLEX_WORKSPACE_FLOAT,      METH_VARARGS, (char*)cws_doc},
	{"complex_wavetable_float",     PyGSL_transform_space_init_COMPLEX_WAVETABLE_FLOAT,      METH_VARARGS, (char*)cwt_doc},
	{"real_workspace_float",        PyGSL_transform_space_init_REAL_WORKSPACE_FLOAT,         METH_VARARGS, (char*)rws_doc},
	{"real_wavetable_float",        PyGSL_transform_space_init_REAL_WAVETABLE_FLOAT,         METH_VARARGS, (char*)rwt_doc},
	{"halfcomplex_wavetable_float", PyGSL_transform_space_init_HALFCOMPLEX_WAVETABLE_FLOAT,  METH_VARARGS, (char*)hcwt_doc},
#ifdef _PYGSL_GSL_HAS_WAVELET
	{"wavelet_workspace",           PyGSL_transform_space_init_WAVELET_WORKSPACE,            METH_VARARGS, (char*)ww_doc},
#endif
	/* transform functions */
	PyGSL_TRANSFORM_FD_FUNCTION("complex_forward",             fft_complex_forward,              cf_doc)
	PyGSL_TRANSFORM_FD_FUNCTION("complex_backward",            fft_complex_backward,             cb_doc)
	PyGSL_TRANSFORM_FD_FUNCTION("complex_inverse",             fft_complex_inverse,              ci_doc)
	PyGSL_TRANSFORM_FD_FUNCTION("complex_radix2_forward",      fft_complex_radix2_forward,       cf_doc_r2)
	PyGSL_TRANSFORM_FD_FUNCTION("complex_radix2_backward",     fft_complex_radix2_backward,      cb_doc_r2)
	PyGSL_TRANSFORM_FD_FUNCTION("complex_radix2_inverse",      fft_complex_radix2_inverse,       ci_doc_r2)
	PyGSL_TRANSFORM_FD_FUNCTION("complex_radix2_dif_forward",  fft_complex_radix2_dif_forward,   cf_doc_r2_dif)
	PyGSL_TRANSFORM_FD_FUNCTION("complex_radix2_dif_backward", fft_complex_radix2_dif_backward,  cb_doc_r2_dif)
	PyGSL_TRANSFORM_FD_FUNCTION("complex_radix2_dif_inverse",  fft_complex_radix2_dif_inverse,   ci_doc_r2_dif)
	PyGSL_TRANSFORM_FD_FUNCTION("real_transform",              fft_real_transform,               rt_doc)
	PyGSL_TRANSFORM_FD_FUNCTION("halfcomplex_transform",       fft_halfcomplex_transform,        hc_doc)
	PyGSL_TRANSFORM_FD_FUNCTION("halfcomplex_inverse",         fft_halfcomplex_inverse,          hi_doc)
	PyGSL_TRANSFORM_FD_FUNCTION("real_radix2_transform",       fft_real_radix2_transform,        rt_doc_r2)
	PyGSL_TRANSFORM_FD_FUNCTION("halfcomplex_radix2_transform",fft_halfcomplex_radix2_transform, hc_doc_r2)
	PyGSL_TRANSFORM_FD_FUNCTION("halfcomplex_radix2_inverse",  fft_halfcomplex_radix2_inverse,   hi_doc_r2)
	/* helper functions */
	{"halfcomplex_radix2_unpack",         PyGSL_fft_halfcomplex_radix2_unpack,       METH_VARARGS, (char*)un_doc_r2},
	{"halfcomplex_radix2_unpack_float",   PyGSL_fft_halfcomplex_radix2_unpack_float, METH_VARARGS, (char*)float_doc},
	/* wavelet inits */
#ifdef _PYGSL_GSL_HAS_WAVELET
	PyGSL_WAVELET_TRANSFORM_TYPE(daubechies)
	PyGSL_WAVELET_TRANSFORM_TYPE(haar)
	PyGSL_WAVELET_TRANSFORM_TYPE(bspline)
#endif
	{NULL, NULL, 0, NULL} /* Sentinel */
};

/*
 * Set the various function pointers for the different transforms. See the
 * structure _pygsl_transform_func_s for the functions. Some architectures do
 * not allow to initalise function pointers in static structures on the heap.
 * (some solaris versions? )
 */
#define PYGSL_INIT_FUNCS(helpers, space, table, spacet, tablet) \
	helpers.space_alloc = (pygsl_transform_helpn_t *) gsl_fft_ ## space ## _alloc; \
	helpers.space_free  = (pygsl_transform_help_t *)  gsl_fft_ ## space ## _free;  \
	helpers.table_alloc = (pygsl_transform_helpn_t *) gsl_fft_ ## table ## _alloc; \
	helpers.table_free  = (pygsl_transform_help_t *)  gsl_fft_ ## table ## _free; \
	helpers.space_type  = spacet; \
	helpers.table_type  = tablet; \

#define PYGSL_INIT_FUNCS_DF(helpers, space, table, spacet, tablet) \
       PYGSL_INIT_FUNCS(helpers ## _funcs, space, table, spacet, tablet) \
       PYGSL_INIT_FUNCS(helpers ## _float ## _funcs, space ## _float, table ## _float, spacet ## _FLOAT, tablet ## _FLOAT)

static void
init_helpers(void)
{
	FUNC_MESS_BEGIN();
	PYGSL_INIT_FUNCS_DF(complex,     complex_workspace, complex_wavetable, COMPLEX_WORKSPACE, COMPLEX_WAVETABLE)
	PYGSL_INIT_FUNCS_DF(real,        real_workspace,    real_wavetable, REAL_WORKSPACE, REAL_WAVETABLE)
	PYGSL_INIT_FUNCS_DF(halfcomplex, real_workspace,    halfcomplex_wavetable, REAL_WORKSPACE, HALFCOMPLEX_WAVETABLE)

        DEBUG_MESS(3, "PyArray_FLOAT   = %d ", PyArray_FLOAT  );
	DEBUG_MESS(3, "PyArray_DOUBLE  = %d ", PyArray_DOUBLE );
	DEBUG_MESS(3, "PyArray_CFLOAT  = %d ", PyArray_CFLOAT );
	DEBUG_MESS(3, "PyArray_CDOUBLE = %d ", PyArray_CDOUBLE);

#ifdef _PYGSL_GSL_HAS_WAVELET
	DEBUG_MESS(4, "%s @ %p", "daubechies", gsl_wavelet_daubechies);
	DEBUG_MESS(4, "%s @ %p", "daubechies_centered", gsl_wavelet_daubechies_centered);
	DEBUG_MESS(4, "%s @ %p", "haar", gsl_wavelet_haar);
	DEBUG_MESS(4, "%s @ %p", "haar_centered", gsl_wavelet_haar_centered);
	DEBUG_MESS(4, "%s @ %p", "bspline", gsl_wavelet_bspline);
	DEBUG_MESS(4, "%s @ %p", "bspline_centered", gsl_wavelet_bspline_centered);
#endif
	FUNC_MESS_END();

}

DL_EXPORT(void) init_transform(void)
{
     	PyObject *m = NULL, *dict = NULL, *item = NULL;

	FUNC_MESS_BEGIN();
	PyGSL_transform_space_pytype.ob_type = &PyType_Type;
#ifdef _PYGSL_GSL_HAS_WAVELET
	PyGSL_wavelet_pytype.ob_type = &PyType_Type;
#endif
	m = Py_InitModule("_transform", transformMethods);
	module = m;
	init_pygsl();
	init_helpers();
	if (m == NULL)
		return;

	dict = PyModule_GetDict(m);
	if (dict == NULL)
		return;

	if (!(item = PyString_FromString(transform_module_doc))){
		PyErr_SetString(PyExc_ImportError,
				"I could not generate module doc string!");
		return;
	}
	if (PyDict_SetItemString(dict, "__doc__", item) != 0){
		PyErr_SetString(PyExc_ImportError,
				"I could not init doc string!");
		return;
	}
	FUNC_MESS_END();
	return;
}

/*
 * Local Variables:
 * mode: C
 * c-file-style: "python"
 * End:
 */


#ifndef tabulate_H_INCLUDED
#define tabulate_H_INCLUDED

//#define DEBUG

#include <mpi.h>
#include <stdio.h>
#include <stdlib.h>
#include "wf.h"
#include "glasma.h"
#include "jet.h"
#include "bfkl.h"
#include <gsl/gsl_errno.h>

#define END_MSG 0
#define PIPE_MSG 1
#define RET_MSG 2

int myid;
//number of processors
int np;
//each point in phi is done on an individual slave node
//the code is setup such that the number of points in phi is the number of processors - 1

MPI_Status Status;

int master_main(int, char**) ;
int slave_main(int, char**) ;

char tag[256];

//*
//*
// This section of code initializes and reads in the
// following global variables from file
//*
//*
#define X_FIELDS \
   X(int, wfTAG, "%d") \
   X(int, A1, "%d") \
   X(int, A2, "%d") \
   X(double, rts, "%lf") \

#include "ReadParameters.cxx"
//

#endif


#include <config.h>
#include <stddef.h>
#include <stdlib.h>
#include <math.h>

#include <gsl/gsl_errno.h>
#include <gsl/gsl_complex.h>

#include <gsl/gsl_dft_complex.h>
#include <gsl/gsl_dft_complex_float.h>

#include "complex_internal.h"

#define BASE_DOUBLE
#include "templates_on.h"
#include "dft_source.c"
#include "templates_off.h"
#undef  BASE_DOUBLE

#define BASE_FLOAT
#include "templates_on.h"
#include "dft_source.c"
#include "templates_off.h"
#undef  BASE_FLOAT


/*! \file allvars.c
 *  \brief provides instances of all global variables.
 */

#include <stdio.h>
#include <gsl/gsl_rng.h>
#include "tags.h"
#include "allvars.h"

int ThisTask;		/*!< the rank of the local processor */
int NTask;               /*!< number of processors */
int PTask;	        /*!< smallest integer such that NTask <= 2^PTask */

int NumPart;		/*!< number of particles on the LOCAL processor */
int N_gas;		/*!< number of gas particles on the LOCAL processor  */
long long Ntype[6];      /*!< total number of particles of each type */
int NtypeLocal[6];       /*!< local number of particles of each type */

int NumForceUpdate;      /*!< number of active particles on local processor in current timestep  */
int NumSphUpdate;        /*!< number of active SPH particles on local processor in current timestep  */

double CPUThisRun;	/*!< Sums the CPU time for the process (current submission only) */

int RestartFlag;         /*!< taken from command line used to start code. 0 is normal start-up from
                                     initial conditions, 1 is resuming a run from a set of restart files, while 2
                                     marks a restart from a snapshot file. */

char *Exportflag;        /*!< Buffer used for flagging whether a particle needs to be exported to another process */

int  *Ngblist;           /*!< Buffer to hold indices of neighbours retrieved by the neighbour search routines */

int TreeReconstructFlag; /*!< Signals that a new tree needs to be constructed */

int Flag_FullStep;       /*!< This flag signals that the current step involves all particles */

int ZeroTimestepEncountered;  /*!< Flag used by AMUSE. When a particle is assigned a timestep of zero, an
                                     exception is raised instead of forcing the application to exit. */

gsl_rng *random_generator; /*!< the employed random number generator of the GSL library */

double RndTable[RNDTABLE]; /*!< Hold a table with random numbers, refreshed every timestep */

double DomainCorner[3];    /*!< gives the lower left corner of simulation volume */
double DomainCenter[3];    /*!< gives the center of simulation volume */
double DomainLen;          /*!< gives the (maximum) side-length of simulation volume */
double DomainFac;          /*!< factor used for converting particle coordinates to a Peano-Hilbert mesh covering the simulation volume */
int    DomainMyStart;      /*!< first domain mesh cell that resides on the local processor */
int    DomainMyLast;       /*!< last domain mesh cell that resides on the local processor */
int    *DomainStartList;   /*!< a table that lists the first domain mesh cell for all processors */
int    *DomainEndList;     /*!< a table that lists the last domain mesh cell for all processors */
double *DomainWork;        /*!< a table that gives the total "work" due to the particles stored by each processor */
int    *DomainCount;       /*!< a table that gives the total number of particles held by each processor */
int    *DomainCountSph;    /*!< a table that gives the total number of SPH particles held by each processor */

int    *DomainTask;        /*!< this table gives for each leaf of the top-level tree the processor it was assigned to */
int    *DomainNodeIndex;   /*!< this table gives for each leaf of the top-level tree the corresponding node of the gravitational tree */
FLOAT  *DomainTreeNodeLen; /*!< this table gives for each leaf of the top-level tree the side-length of the corresponding node of the gravitational tree */
FLOAT  *DomainHmax;        /*!< this table gives for each leaf of the top-level tree the maximum SPH smoothing length among the particles of the corresponding node of the gravitational tree */

struct DomainNODE
 *DomainMoment;                    /*!< this table stores for each node of the top-level tree corresponding node data from the gravitational tree */

peanokey *DomainKeyBuf;    /*!< this points to a buffer used during the exchange of particle data */

peanokey *Key;             /*!< a table used for storing Peano-Hilbert keys for particles */
peanokey *KeySorted;       /*!< holds a sorted table of Peano-Hilbert keys for all particles, used to construct top-level tree */

int NTopnodes;             /*!< total number of nodes in top-level tree */
int NTopleaves;            /*!< number of leaves in top-level tree. Each leaf can be assigned to a different processor */

struct topnode_data
 *TopNodes;                      /*!< points to the root node of the top-level tree */

double TimeOfLastTreeConstruction; /*!< holds what it says, only used in connection with FORCETEST */

/* variables for input/output, usually only used on process 0 */

char ParameterFile[MAXLEN_FILENAME];  /*!< file name of parameterfile used for starting the simulation */

FILE *FdInfo;       /*!< file handle for info.txt log-file. */
FILE *FdEnergy;     /*!< file handle for energy.txt log-file. */
FILE *FdTimings;    /*!< file handle for timings.txt log-file. */
FILE *FdCPU;        /*!< file handle for cpu.txt log-file. */

#ifdef FORCETEST
FILE *FdForceTest;  /*!< file handle for forcetest.txt log-file. */
#endif

double DriftTable[DRIFT_TABLE_LENGTH];      /*!< table for the cosmological drift factors */
double GravKickTable[DRIFT_TABLE_LENGTH];   /*!< table for the cosmological kick factor for gravitational forces */
double HydroKickTable[DRIFT_TABLE_LENGTH];  /*!< table for the cosmological kick factor for hydrodynmical forces */

void *CommBuffer;   /*!< points to communication buffer, which is used in the domain decomposition, the
                                parallel tree-force computation, the SPH routines, etc. */

/*! This structure contains data which is the SAME for all tasks (mostly code parameters read from the
 * parameter file).  Holding this data in a structure is convenient for writing/reading the restart file, and
 * it allows the introduction of new global variables in a simple way. The only thing to do is to introduce
 * them into this structure.
 */
struct global_data_all_processes
 All;

/*! This structure holds all the information that is
 * stored for each particle of the simulation.
 */
struct particle_data
 *P,              /*!< holds particle data on local processor */
 *DomainPartBuf;  /*!< buffer for particle data used in domain decomposition */

/* the following struture holds data that is stored for each SPH particle in addition to the collisionless
 * variables.
 */
struct sph_particle_data
 *SphP,                        	/*!< holds SPH particle data on local processor */
 *DomainSphBuf;                 /*!< buffer for SPH particle data in domain decomposition */

/*  Variables for Tree
 */

int MaxNodes;		/*!< maximum allowed number of internal nodes */
int Numnodestree;	/*!< number of (internal) nodes in each tree */

struct NODE
 *Nodes_base,                   /*!< points to the actual memory allocted for the nodes */
 *Nodes;                        /*!< this is a pointer used to access the nodes which is shifted such that Nodes[All.MaxPart]
 				     gives the first allocated node */

int *Nextnode;	        /*!< gives next node in tree walk */
int *Father;	        /*!< gives parent node in tree    */

struct extNODE           /*!< this structure holds additional tree-node information which is not needed in the actual gravity computation */
 *Extnodes_base,                /*!< points to the actual memory allocted for the extended node information */
 *Extnodes;                     /*!< provides shifted access to extended node information, parallel to Nodes/Nodes_base */

/*! Header for the standard file format.
 */
struct io_header
 header;  /*!< holds header for snapshot files */

char Tab_IO_Labels[IO_NBLOCKS][4];   /*<! This table holds four-byte character tags used for fileformat 2 */

/* global state of system, used for global statistics
 */
struct state_of_system
 SysState;      /*<! Structure for storing some global statistics about the simulation. */

/* Various structures for communication
 */
struct gravdata_in
 *GravDataIn,                   /*!< holds particle data to be exported to other processors */
 *GravDataGet,                  /*!< holds particle data imported from other processors */
 *GravDataResult,               /*!< holds the partial results computed for imported particles. Note: We use GravDataResult = GravDataGet, such that the result replaces the imported data */
 *GravDataOut;                  /*!< holds partial results received from other processors. This will overwrite the GravDataIn array */

struct gravdata_index
 *GravDataIndexTable;           /*!< the particles to be exported are grouped by task-number. This table allows the results to be disentangled again and to be assigned to the correct particle */

struct densdata_in
 *DensDataIn,                   /*!< holds particle data for SPH density computation to be exported to other processors */
 *DensDataGet;                  /*!< holds imported particle data for SPH density computation */

struct densdata_out
 *DensDataResult,               /*!< stores the locally computed SPH density results for imported particles */
 *DensDataPartialResult;        /*!< imported partial SPH density results from other processors */

struct hydrodata_in
 *HydroDataIn,                  /*!< holds particle data for SPH hydro-force computation to be exported to other processors */
 *HydroDataGet;                 /*!< holds imported particle data for SPH hydro-force computation */

struct hydrodata_out
 *HydroDataResult,              /*!< stores the locally computed SPH hydro results for imported particles */
 *HydroDataPartialResult;       /*!< imported partial SPH hydro-force results from other processors */

#ifdef TIMESTEP_LIMITER
struct timedata_in *TimeDataIn, *TimeDataGet;
#endif

#ifndef NOMPI
#include <mpi.h>
MPI_Comm GADGET_WORLD;
#endif