#pragma once

#include <memory_resource>
#include <vector>

#include <gsl-lite/gsl-lite.hpp>

#include <thrust/sort.h>
#include <cuda/define_specifiers.hpp>

#include <thrustshift/memory-resource.h>

namespace thrustshift {

//! Col indices must be ordered
template <typename DataType, typename IndexType>
class CSR {
   public:
	using value_type = DataType;
	using index_type = IndexType;

   private:
	bool cols_are_sorted() {
		for (size_t row_id = 0; row_id < this->num_rows(); ++row_id) {
			if (!std::is_sorted(col_indices_.begin() + row_ptrs_[row_id],
			                    col_indices_.begin() + row_ptrs_[row_id + 1])) {
				return false;
			}
		}
		return true;
	}

   public:
	CSR() : row_ptrs_(1, 0, &pmr::default_resource), num_cols_(0) {
	}

	template <class DataRange,
	          class ColIndRange,
	          class RowPtrsRange,
	          class MemoryResource>
	CSR(DataRange&& values,
	    ColIndRange&& col_indices,
	    RowPtrsRange&& row_ptrs,
	    size_t num_cols,
	    MemoryResource& memory_resource)
	    : values_(values.begin(), values.end(), &memory_resource),
	      col_indices_(col_indices.begin(),
	                   col_indices.end(),
	                   &memory_resource),
	      row_ptrs_(row_ptrs.begin(), row_ptrs.end(), &memory_resource),
	      num_cols_(num_cols) {
		gsl_Expects(values.size() == col_indices.size());
		gsl_Expects(row_ptrs.size() > 0);
		gsl_Expects(row_ptrs[0] == 0);
		gsl_ExpectsAudit(cols_are_sorted());
	}

	template <class DataRange, class ColIndRange, class RowPtrsRange>
	CSR(DataRange&& values,
	    ColIndRange&& col_indices,
	    RowPtrsRange&& row_ptrs,
	    size_t num_cols)
	    : CSR(std::forward<DataRange>(values),
	          std::forward<ColIndRange>(col_indices),
	          std::forward<RowPtrsRange>(row_ptrs),
	          num_cols,
	          pmr::default_resource) {
	}

	// The copy constructor is declared explicitly to ensure
	// managed memory is used per default.
	CSR(const CSR& other)
	    : CSR(other.values(),
	          other.col_indices(),
	          other.row_ptrs(),
	          other.num_cols()) {
	}

	CSR(CSR&& other) = default;

	CSR& operator=(const CSR& other) = default;

	gsl_lite::span<DataType> values() {
		return gsl_lite::make_span(values_);
	}

	gsl_lite::span<const DataType> values() const {
		return gsl_lite::make_span(values_);
	}

	gsl_lite::span<IndexType> col_indices() {
		return gsl_lite::make_span(col_indices_);
	}

	gsl_lite::span<const IndexType> col_indices() const {
		return gsl_lite::make_span(col_indices_);
	}

	gsl_lite::span<IndexType> row_ptrs() {
		return gsl_lite::make_span(row_ptrs_);
	}

	gsl_lite::span<const IndexType> row_ptrs() const {
		return gsl_lite::make_span(row_ptrs_);
	}

	size_t num_rows() const {
		return row_ptrs_.size() - 1;
	}

	size_t num_cols() const {
		return num_cols_;
	}

	void sort_column_indices() {
		for (size_t row_id = 0; row_id < num_rows(); ++row_id) {
			const auto nns_start = row_ptrs_[row_id];
			const auto nns_end = row_ptrs_[row_id + 1];
			thrust::sort_by_key(thrust::host,
			                    col_indices_.begin() + nns_start,
			                    col_indices_.begin() + nns_end,
			                    values_.begin() + nns_start);
		}
	}

	/*! \brief Add additional elements to each row of the matrix.
	 *
	 *  A row might already have the maximum number of element. Therefore
	 *  not every row might be extended by exactly `num_max_additional_elements_per_row`.
	 *
	 */
	void extend_rows(int num_max_additional_elements_per_row,
	                 DataType value = 0) {
		if (num_max_additional_elements_per_row == 0) {
			return;
		}
		gsl_Expects(num_max_additional_elements_per_row >= 0);
		std::vector<DataType> tmp_values(values_.begin(), values_.end());
		std::vector<IndexType> tmp_col_indices(col_indices_.begin(),
		                                       col_indices_.end());
		size_t num_additional_elements = 0;
		const int nc = gsl_lite::narrow<int>(num_cols());
		auto get_num_additional_elements_per_row = [&](size_t row_id) {
			const int num_elements_curr_row =
			    row_ptrs_[row_id + 1] - row_ptrs_[row_id];
			return std::max(std::min(num_max_additional_elements_per_row,
			                         nc - num_elements_curr_row),
			                0);
		};
		for (size_t row_id = 0; row_id < num_rows(); ++row_id) {
			num_additional_elements +=
			    get_num_additional_elements_per_row(row_id);
		}

		const size_t new_nnz = values_.size() + num_additional_elements;
		values_.resize(new_nnz);
		col_indices_.resize(new_nnz);
		int nns_offset = 0;
		for (size_t row_id = 0; row_id < num_rows(); ++row_id) {
			const int num_additional_elements_curr_row =
			    get_num_additional_elements_per_row(row_id);
			int nns_id = row_ptrs_[row_id];
			const int nns_end = row_ptrs_[row_id + 1];
			const int nnz_curr_row = nns_end - nns_id;
			int curr_col_id = 0;
			int num_added_elements = 0;
			for (int new_nns_id = nns_offset;
			     new_nns_id < nns_offset + num_additional_elements_curr_row +
			                      row_ptrs_[row_id + 1] - row_ptrs_[row_id];
			     ++new_nns_id) {
				const int other_col_id = nns_id == nns_end
				                             ? std::numeric_limits<int>::max()
				                             : tmp_col_indices[nns_id];
				if (curr_col_id < other_col_id &&
				    num_added_elements < num_additional_elements_curr_row) {
					values_[new_nns_id] = value;
					col_indices_[new_nns_id] = curr_col_id;
					++curr_col_id;
					++num_added_elements;
				}
				else {
					values_[new_nns_id] = tmp_values[nns_id];
					col_indices_[new_nns_id] = other_col_id;
					curr_col_id = other_col_id + 1;
					++nns_id;
				}
			}
			row_ptrs_[row_id] = nns_offset;
			nns_offset += nnz_curr_row + num_additional_elements_curr_row;
		}
		row_ptrs_.back() = values_.size();
		gsl_ExpectsAudit(std::is_sorted(row_ptrs_.begin(), row_ptrs_.end()));
		gsl_ExpectsAudit(cols_are_sorted());
	}

   private:
	std::pmr::vector<DataType> values_;
	std::pmr::vector<IndexType> col_indices_;
	std::pmr::vector<IndexType> row_ptrs_;
	size_t num_cols_;
};

template <typename DataType, typename IndexType>
bool operator==(const CSR<DataType, IndexType>& a,
                const CSR<DataType, IndexType>& b) {
	return equal(a.values(), b.values()) &&
	       equal(a.col_indices(), b.col_indices()) &&
	       equal(a.row_ptrs(), b.row_ptrs()) &&
	       a.num_rows() == b.num_rows() && a.num_cols() == b.num_cols();
}

template <typename DataType, typename IndexType>
class CSR_view {

   public:
	using value_type = DataType;
	using index_type = IndexType;

	template <typename OtherDataType, typename OtherIndexType>
	CSR_view(CSR<OtherDataType, OtherIndexType>& owner)
	    : values_(owner.values()),
	      col_indices_(owner.col_indices()),
	      row_ptrs_(owner.row_ptrs()),
	      num_cols_(owner.num_cols()) {
	}

	template <typename OtherDataType, typename OtherIndexType>
	CSR_view(const CSR<OtherDataType, OtherIndexType>& owner)
	    : values_(owner.values()),
	      col_indices_(owner.col_indices()),
	      row_ptrs_(owner.row_ptrs()),
	      num_cols_(owner.num_cols()) {
	}

	template <class DataRange,
	          class ColIndRange,
	          class RowPtrsRange>
	CSR_view(DataRange&& values,
	    ColIndRange&& col_indices,
	    RowPtrsRange&& row_ptrs,
	    size_t num_cols)
	    : values_(values),
	      col_indices_(col_indices),
	      row_ptrs_(row_ptrs),
	      num_cols_(num_cols) {
		gsl_Expects(values.size() == col_indices.size());
		gsl_Expects(row_ptrs.size() > 0);
		gsl_ExpectsAudit(row_ptrs[0] == 0);
		gsl_ExpectsAudit(row_ptrs.back() == values.size());
		gsl_ExpectsAudit(std::is_sorted(row_ptrs.begin(), row_ptrs.end()));
	}

	CSR_view(const CSR_view& other) = default;
	CSR_view(CSR_view&& other) = default;

	CUDA_FHD gsl_lite::span<DataType> values() {
		return values_;
	}

	CUDA_FHD gsl_lite::span<const DataType> values() const {
		return values_;
	}

	CUDA_FHD gsl_lite::span<IndexType> col_indices() {
		return col_indices_;
	}

	CUDA_FHD gsl_lite::span<const IndexType> col_indices() const {
		return col_indices_;
	}

	CUDA_FHD gsl_lite::span<IndexType> row_ptrs() {
		return row_ptrs_;
	}

	CUDA_FHD gsl_lite::span<const IndexType> row_ptrs() const {
		return row_ptrs_;
	}

	CUDA_FHD size_t num_rows() const {
		return row_ptrs_.size() - 1;
	}

	CUDA_FHD size_t num_cols() const {
		return num_cols_;
	}

	CUDA_FHD auto max_row_nnz() const {
		using I = typename std::remove_const<IndexType>::type;
		I mnnz = 0;
		for (size_t row_id = 1; row_id < row_ptrs_.size(); ++row_id) {
			mnnz = std::max(row_ptrs_[row_id] - row_ptrs_[row_id - 1], mnnz);
		}
		return mnnz;
	}

   private:
	gsl_lite::span<DataType> values_;
	gsl_lite::span<IndexType> col_indices_;
	gsl_lite::span<IndexType> row_ptrs_;
	size_t num_cols_;
};

} // namespace thrustshift


static char help[] = "This code implements the distribution federate of the T-D power flow use-case. A global convergence for the federation is implemented in this use-case. The The distribution federate obtains the upstream distribution substation voltage from transmission federate, solves power flow, and sends the updated injection into transmission. This code does not use HELICS iterative API\n\n";

#include <petsc.h>
#include <ValueFederate.h>
#include <opendss.h>
#include <complex.h>

typedef struct {
  helics_federate vfed;
  helics_publication pub;
  helics_subscription sub;
  helics_time_t currenttime;
  helics_iteration_status currenttimeiter;
  char       pub_topic[PETSC_MAX_PATH_LEN];
} UserData;

helics_status CreateDistributionFederate(helics_federate *distfed,char* pub_topic)
{
  helics_federate_info_t fedinfo;
  helics_status   status;
  const char*    fedinitstring="--federates=1";
  helics_federate vfed;
  char            fedname[PETSC_MAX_PATH_LEN];
  PetscErrorCode ierr;

  /* Create Federate Info object that describes the federate properties */
  fedinfo = helicsFederateInfoCreate();

  ierr = PetscStrcpy(fedname,"Distribution Federate ");CHKERRQ(ierr);
  ierr = PetscStrcat(fedname,pub_topic);CHKERRQ(ierr);
  /* Set Federate name */
  status = helicsFederateInfoSetFederateName(fedinfo,fedname);

  /* Set core type from string */
  status = helicsFederateInfoSetCoreTypeFromString(fedinfo,"zmq");

  /* Federate init string */
  status = helicsFederateInfoSetCoreInitString(fedinfo,fedinitstring);

  /* Set the message interval (timedelta) for federate. Note that
     HELICS minimum message time interval is 1 ns and by default
     it uses a time delta of 1 second. What is provided to the
     setTimedelta routine is a multiplier for the default timedelta.
  */
  /* Set one second message interval */
  status = helicsFederateInfoSetTimeDelta(fedinfo,1.0);

  status = helicsFederateInfoSetLoggingLevel(fedinfo,1);

  status = helicsFederateInfoSetMaxIterations(fedinfo,100);
  /* Create value federate */
  vfed = helicsCreateValueFederate(fedinfo);

  *distfed = vfed;

  return status;
}

int main(int argc,char **argv)
{
  PetscErrorCode ierr;
  const char*    helicsversion;
  UserData       user;
  helics_status   status;
  PetscBool      flg;
  char           netfile[PETSC_MAX_PATH_LEN],dcommand[PETSC_MAX_PATH_LEN];
  char           sub_topic[PETSC_MAX_PATH_LEN];
  double complex *Stotal;
  PetscInt      iter=0;
  PetscScalar   Vm,Va;
  PetscScalar   Pg,Qg,Pgprv,Qgprv;
  PetscInt      pflowT_conv=0,pflowD_conv=0,global_conv=0;
  char          pubstr[PETSC_MAX_PATH_LEN],substr[PETSC_MAX_PATH_LEN];
  PetscInt      strlen;
  PetscReal     tol=1E-6,mis;

  PetscInitialize(&argc,&argv,NULL,help);

  helicsversion = helicsGetVersion();

  printf("D FED: Helics version = %s\n",helicsversion);

  /* Get network data file from command line */
  ierr = PetscOptionsGetString(NULL,NULL,"-netfile",netfile,PETSC_MAX_PATH_LEN,&flg);CHKERRQ(ierr);

  ierr = PetscSNPrintf(dcommand,PETSC_MAX_PATH_LEN-1,"Redirect ");CHKERRQ(ierr);
  ierr = PetscStrcat(dcommand,netfile);CHKERRQ(ierr);

  /* Load the D file */
  ierr = OpenDSSRunCommand(dcommand);CHKERRQ(ierr);

  flg = PETSC_FALSE;
  /* Get the distribution topic for publication stream */
  ierr = PetscOptionsGetString(NULL,NULL,"-dtopic",user.pub_topic,PETSC_MAX_PATH_LEN,&flg);CHKERRQ(ierr);
  if(!flg) {
    SETERRQ(PETSC_COMM_SELF,0,"Need to specify the publication name, option -dtopic <topic_name>.\n This is same as the distribution feeder file name without the extension");
  }

    /* Create distribution federate */
  status = CreateDistributionFederate(&user.vfed,user.pub_topic);
  printf("Created D FEDERATE %s\n",user.pub_topic);

  /* Register the publication */
  user.pub = helicsFederateRegisterGlobalPublication(user.vfed,user.pub_topic,"string","");
  printf("D FEDERATE %s: Publication registered\n",user.pub_topic);

  /* Subscribe to transmission federate's publication */
  ierr = PetscStrcpy(sub_topic,"Trans/");
  ierr = PetscStrcat(sub_topic,user.pub_topic);
  user.sub = helicsFederateRegisterSubscription(user.vfed,sub_topic,"string","");
  printf("D FEDERATE %s: Subscription registered\n",user.pub_topic);

  status = helicsFederateEnterInitializationMode(user.vfed);
  printf("D FEDERATE %s: Entered initialization mode\n",user.pub_topic);

  user.currenttime = 0.0;
  user.currenttimeiter = iterating;

  PetscInt solve;
  /* Solve power flow */
  //  printf("D FEDERATE %s running power flow\n",user.pub_topic);
  ierr = OpenDSSSolutionGetSolve(&solve);CHKERRQ(ierr);

  /* Send power injection to transmission */
  /* Get the net injection at the boundary bus */
  ierr = OpenDSSCircuitGetTotalPower(&Stotal);CHKERRQ(ierr);
  Pgprv = -creal(Stotal[0])/1000.0; /* Conversion to MW */
  Qgprv = -cimag(Stotal[0])/1000.0;

  ierr = PetscSNPrintf(pubstr,PETSC_MAX_PATH_LEN-1,"%18.16f,%18.16f,%d",Pgprv,Qgprv,pflowD_conv);CHKERRQ(ierr);
  status = helicsPublicationPublishString(user.pub,pubstr);
  //  printf("D FEDERATE %s sent Pg = %4.3f, Qg = %4.3f, conv = %d from T FEDERATE\n",user.pub_topic,Pgprv,Qgprv,pflowD_conv);

  status = helicsFederateEnterExecutionMode(user.vfed);
  printf("D FEDERATE %s: Entered execution mode\n",user.pub_topic);

  while(user.currenttimeiter == iterating) {
    iter++;

    status = helicsSubscriptionGetString(user.sub,substr,PETSC_MAX_PATH_LEN-1,&strlen);
    sscanf(substr,"%lf,%lf,%d",&Vm,&Va,&pflowT_conv);
    //    printf("D FEDERATE %s received Vm = %4.3f, Va = %4.3f, conv = %d from T FEDERATE\n",user.pub_topic,Vm,Va,pflowT_conv);

    global_conv = pflowT_conv & pflowD_conv;

    if(global_conv) {
      helicsFederateRequestTimeIterative(user.vfed,user.currenttime,no_iteration,&user.currenttime,&user.currenttimeiter);
    } else {
      /* Set source bus voltage */
      ierr = OpenDSSVsourcesSetPU(Vm);CHKERRQ(ierr);
      ierr = OpenDSSVsourcesSetAngleDeg(Va);CHKERRQ(ierr);

      /* 2. Solve power flow */
      //      printf("D FEDERATE %s running power flow\n",user.pub_topic);
      ierr = OpenDSSSolutionGetSolve(&solve);CHKERRQ(ierr);

      /* Send power injection to transmission */
      /* Get the net injection at the boundary bus */
      ierr = OpenDSSCircuitGetTotalPower(&Stotal);CHKERRQ(ierr);
      Pg = -creal(Stotal[0])/1000.0; /* Conversion to MW */
      Qg = -cimag(Stotal[0])/1000.0;

      mis = PetscSqrtScalar((Pg-Pgprv)/100*(Pg-Pgprv)/100 + (Qg-Qgprv)/100*(Qg-Qgprv)/100); /* Divide by 100 for conversion to pu */
      if(mis < tol) {
	pflowD_conv = 1;
      } else {
	pflowD_conv = 0;
	Pgprv = Pg;
	Qgprv = Qg;
      }
      ierr = PetscSNPrintf(pubstr,PETSC_MAX_PATH_LEN-1,"%18.16f,%18.16f,%d",Pg,Qg,pflowD_conv);CHKERRQ(ierr);
      status = helicsPublicationPublishString(user.pub,pubstr);
      //      printf("D FEDERATE %s sent Pg = %4.3f, Qg = %4.3f, conv = %d to T FEDERATE\n",user.pub_topic,Pg,Qg,pflowD_conv);

      fflush(NULL);

      /*3. Publish Pg, Qg, and convergence status to transmission */
      status = helicsFederateRequestTimeIterative(user.vfed,user.currenttime,force_iteration,&user.currenttime,&user.currenttimeiter);

      printf("Iteration %d: D Federate %s mis = %g,converged = %d\n",iter,user.pub_topic,mis,pflowD_conv);

    }

  }

  status = helicsFederateEnterExecutionModeComplete(user.vfed);
  status = helicsFederateFinalize(user.vfed);

  PetscFinalize();
  return 0;
}


#include <pygsl/solver.h>
#include <gsl/gsl_min.h>

static const char min_f_type_name[] = "F-Minimizer";
static const char min_x_minimum_doc[]= "";
static const char min_x_lower_doc[]= "";
static const char min_x_upper_doc[]= "";
static const char min_f_minimum_doc[]= "";
static const char min_f_lower_doc[]= "";
static const char min_f_upper_doc[]= "";
static const char min_test_delta_doc[]= "";
static const char min_set_f_doc[]= "";

static const char min_init_brent_doc[]= "";
static const char min_init_goldensection_doc[]= "";

const char  * filename = __FILE__;
PyObject *module = NULL;

static PyObject*
PyGSL_min_f_minimum(PyGSL_solver *self, PyObject *args)
{
     return PyGSL_solver_ret_double(self, args, (double_m_t) gsl_min_fminimizer_f_minimum);
}

static PyObject*
PyGSL_min_f_lower(PyGSL_solver *self, PyObject *args)
{
     return PyGSL_solver_ret_double(self, args, (double_m_t) gsl_min_fminimizer_f_lower);
}

static PyObject*
PyGSL_min_f_upper(PyGSL_solver *self, PyObject *args)
{
     return PyGSL_solver_ret_double(self, args, (double_m_t) gsl_min_fminimizer_f_upper);
}

static PyObject*
PyGSL_min_x_minimum(PyGSL_solver *self, PyObject *args)
{
     return PyGSL_solver_ret_double(self, args, (double_m_t) gsl_min_fminimizer_x_minimum);
}

static PyObject*
PyGSL_min_x_lower(PyGSL_solver *self, PyObject *args)
{
     return PyGSL_solver_ret_double(self, args, (double_m_t) gsl_min_fminimizer_x_lower);
}

static PyObject*
PyGSL_min_x_upper(PyGSL_solver *self, PyObject *args)
{
     return PyGSL_solver_ret_double(self, args, (double_m_t) gsl_min_fminimizer_x_upper);
}

static PyObject*
PyGSL_min_solver_test_interval(PyGSL_solver * self, PyObject *args)
{
     double epsabs, epsrel;
     gsl_min_fminimizer *s = (gsl_min_fminimizer *) self->solver;
     if(!PyArg_ParseTuple(args, "dd", &epsabs, &epsrel))
	  return NULL;
     return PyInt_FromLong(gsl_min_test_interval(s->x_lower, s->x_upper, epsabs, epsrel));
}

static PyObject*
PyGSL_min_set_f(PyGSL_solver *self, PyObject *args, PyObject *kw)
{
     return PyGSL_solver_set_f(self, args, kw,  (void *)gsl_min_fminimizer_set, 0);
}

static PyMethodDef PyGSL_min_fmethods[] = {
     {"x_minimum", (PyCFunction)PyGSL_min_x_minimum,  METH_NOARGS, (char *)min_x_minimum_doc},
     {"x_lower",   (PyCFunction)PyGSL_min_x_lower,    METH_NOARGS, (char *)min_x_lower_doc},
     {"x_upper",   (PyCFunction)PyGSL_min_x_upper,    METH_NOARGS, (char *)min_x_upper_doc},
     {"f_minimum", (PyCFunction)PyGSL_min_f_minimum,  METH_NOARGS, (char *)min_f_minimum_doc},
     {"f_lower",   (PyCFunction)PyGSL_min_f_lower,    METH_NOARGS, (char *)min_f_lower_doc},
     {"f_upper",   (PyCFunction)PyGSL_min_f_upper,    METH_NOARGS, (char *)min_f_upper_doc},
     {"set",       (PyCFunction)PyGSL_min_set_f,    METH_VARARGS|METH_KEYWORDS, (char *)min_set_f_doc},
     {"test_interval",(PyCFunction)PyGSL_min_solver_test_interval, METH_VARARGS, NULL},
     {NULL, NULL, 0, NULL}           /* sentinel */
};

const struct _SolverStatic
min_solver_f = {{(void_m_t) gsl_min_fminimizer_free,
		 /* gsl_multimin_fminimizer_restart */  (void_m_t) NULL,
		 (name_m_t) gsl_min_fminimizer_name,
		 (int_m_t) gsl_min_fminimizer_iterate},
		1, PyGSL_min_fmethods, min_f_type_name};

static PyObject*
PyGSL_min_f_init(PyObject *self, PyObject *args,
		 const gsl_min_fminimizer_type * type)
{

     PyObject *tmp=NULL;
     solver_alloc_struct s = {type, (void_an_t) gsl_min_fminimizer_alloc,
			      &min_solver_f};
     FUNC_MESS_BEGIN();
     tmp = PyGSL_solver_dn_init(self, args, &s, 0);
     FUNC_MESS_END();
     return tmp;
}

#define AMIN_F(name)                                                  \
static PyObject* PyGSL_min_init_ ## name (PyObject *self, PyObject *args)\
{                                                                             \
     PyObject *tmp = NULL;                                                    \
     FUNC_MESS_BEGIN();                                                       \
     tmp = PyGSL_min_f_init(self, args,  gsl_min_fminimizer_ ## name); \
     if (tmp == NULL){                                                        \
	  PyGSL_add_traceback(module, __FILE__, __FUNCTION__, __LINE__); \
     }                                                                        \
     FUNC_MESS_END();                                                         \
     return tmp;                                                              \
}
AMIN_F(brent)
AMIN_F(goldensection)

static PyObject*
PyGSL_min_test_interval(PyObject * self, PyObject *args)
{
     double x_lower, x_upper, epsabs, epsrel;
     if(!PyArg_ParseTuple(args, "dddd", &x_lower, &x_upper, &epsabs, &epsrel))
	  return NULL;
     return PyInt_FromLong(gsl_min_test_interval(x_lower, x_upper, epsabs, epsrel));
}

static const char PyGSL_minimize_module_doc [] = "XXX Missing ";
static PyMethodDef mMethods[] = {
     /* solvers */
     {"brent",        PyGSL_min_init_brent, METH_NOARGS, (char *)min_init_brent_doc},
     {"goldensection",PyGSL_min_init_goldensection, METH_NOARGS, (char *)min_init_goldensection_doc},
     /* min methods */
     {"test_interval",   PyGSL_min_test_interval, METH_VARARGS, (char *)min_test_delta_doc},
     {NULL, NULL, 0, NULL}
};

void
initminimize(void)
{
     PyObject* m, *dict, *item;
     FUNC_MESS_BEGIN();

     m=Py_InitModule("minimize", mMethods);
     import_pygsl_solver();
     assert(PyGSL_API);

     module = m;
     assert(m);
     dict = PyModule_GetDict(m);
     if(!dict)
	  goto fail;

     if (!(item = PyString_FromString((char*)PyGSL_minimize_module_doc))){
	  PyErr_SetString(PyExc_ImportError,
			  "I could not generate module doc string!");
	  goto fail;
     }

     if (PyDict_SetItemString(dict, "__doc__", item) != 0){
	  PyErr_SetString(PyExc_ImportError,
			  "I could not init doc string!");
	  goto fail;
     }

     FUNC_MESS_END();

 fail:
     FUNC_MESS("FAIL");
     return;
}


/* Copyright 2016 by

  Laboratoire d'Informatique de Paris 6 - Équipe PEQUAN
  Sorbonne Universités
  UPMC Univ Paris 06
  UMR 7606, LIP6
  4, place Jussieu
  F-75252 Paris Cedex 05
  France

  Laboratoire d'Informatique de Paris 6, equipe PEQUAN,
  UPMC Universite Paris 06 - CNRS - UMR 7606 - LIP6, Paris, France

  Contributors:
  Anastasia Volkova anastasia.volkova@lip6.fr

  This software is a mathematical library whose purpose is to provide
  functions to compute the Worst-Case Peak Gain measure of Linear
  Time-Invariant Digital Filters.

  This software is governed by the CeCILL-C license under French law and
  abiding by the rules of distribution of free software.  You can  use,
  modify and/ or redistribute the software under the terms of the CeCILL-C
  license as circulated by CEA, CNRS and INRIA at the following URL
  "http://www.cecill.info".

  As a counterpart to the access to the source code and  rights to copy,
  modify and redistribute granted by the license, users are provided only
  with a limited warranty  and the software's author,  the holder of the
  economic rights,  and the successive licensors  have only  limited
  liability.

  In this respect, the user's attention is drawn to the risks associated
  with loading,  using,  modifying and/or developing or reproducing the
  software by the user in light of its specific status of free software,
  that may mean  that it is complicated to manipulate,  and  that  also
  therefore means  that it is reserved for developers  and  experienced
  professionals having in-depth computer knowledge. Users are therefore
  encouraged to load and test the software's suitability as regards their
  requirements in conditions enabling the security of their systems and/or
  data to be ensured and,  more generally, to use and operate it in the
  same conditions as regards security.

  The fact that you are presently reading this means that you have had
  knowledge of the CeCILL-C license and that you accept its terms.

  This program is distributed WITHOUT ANY WARRANTY; without even the
  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

 */

#ifndef SRC_LIB_CLAPACK_FUNCTIONS_CONFIG_H_
#define SRC_LIB_CLAPACK_FUNCTIONS_CONFIG_H_

#ifdef __cplusplus
extern "C" {
#endif

#include <stdlib.h>
#include "config.h"
#include "aux_funcs.h"

#include <f2c.h>

#ifdef CLAPACK_HEADER
	#include <clapack.h>
#endif

#ifdef LAPACK_HEADER
	#include <lapack.h>
#endif

#ifdef LAPACKE_HEADER
	#include <lapacke.h>
#endif

#ifdef BLAS_HEADER
	#include <blas.h>
#endif

#ifdef CBLAS_HEADER
	#include <cblas.h>
#endif

#ifdef BLASWRAP_HEADER
	#include <blaswrap.h>
#endif

#include <complex.h>
typedef struct complexdouble
{
	double r;
	double i;
}complexdouble;

#define doublereal double
#define lapack_int int
#define integer long int
#define lapacke_complex double _Complex

void my_zgetrf(int *m, int *n, complexdouble *A,
 	int *lda, int *ipiv, int *info);

void my_zgetri(int *n, complexdouble *A, int *lda,
		int *ipiv, complexdouble *work, int *lwork, int *info);
void my_dgeevx(int* n, double *A, int* lda, double *wr, double *wi, double *vl,
	       int* ldvl, double *vr, int* ldvr, int *ilo, int *ihi,
	       double *scale, double *abnrm, double *rconde, double *rcondv,
	       double *work, int *lwork, long int *iwork, int *info);

#ifdef __cplusplus
}
#endif

#endif /* SRC_LIB_CLAPACK_FUNCTIONS_CONFIG_H_ */


/**
 * @file lapacke.h
 * @brief Header file to automatically handle Intel MKL or LAPACKE includes.
 *
 * `npypacke` can be linked with a reference LAPACKE implementation, OpenBLAS,
 * or Intel MKL, but Intel MKL uses a different header file, `mkl.h`, while
 * OpenBLAS uses the LAPACKE header `lapacke.h`. This file includes the correct
 * header file given an appropriate preprocessor macro is defined and defines
 * some types appropriately so that the user can simply write in terms of the
 * Intel MKL interface, whether or not Intel MKL is actually linked.
 */

#ifndef NPY_LPK_LAPACKE_H
#define NPY_LPK_LAPACKE_H
// if linking with Netlib LAPACKE
#if defined(LAPACKE_INCLUDE)
#include <lapacke.h>
// define MKL_INT used in extension modules as in mkl.h
#ifndef MKL_INT
#define MKL_INT int
#endif /* MKL_INT */
// else if linking with OpenBLAS CBLAS
#elif defined(OPENBLAS_INCLUDE)
#include <complex.h>
#include <lapacke.h>
// OpenBLAS has blasint typedef, so define MKL_INT using blasint. for LAPACKE
// routines, should cast to lapack_int (same size as blasint in OpenBLAS)
#ifndef MKL_INT
#define MKL_INT blasint
#endif /* MKL_INT */
// else if linking with Intel MKL LAPACKE implementation
#elif defined(MKL_INCLUDE)
#include <mkl.h>
// else error, no LAPACKE includes specified
#else
// silence error squiggles in VS Code (__INTELLISENSE__ always defined)
#ifndef __INTELLISENSE__
#error "no LAPACKE includes specified. try -D(LAPACKE|OPENBLAS|MKL)_INCLUDE"
#endif /* __INTELLISENSE__ */
#endif

// silence error squiggles in VS Code. use mkl.h since it also define MKL types
#ifdef __INTELLISENSE__
#include <mkl.h>
#endif /* __INTELLISENSE__ */

#endif /* NPY_LPK_LAPACKE_H */


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

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

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

//
// Macros for replacing relative paths
#define EXPAND_STR(s) STRING(s)
#define STRING(s) #s

const ccl_configuration default_config = {ccl_boltzmann_class, ccl_halofit, ccl_nobaryons, ccl_tinker10, ccl_duffy2008, ccl_emu_strict};

const ccl_gsl_params default_gsl_params = {GSL_EPSREL,                          // EPSREL
                                           GSL_N_ITERATION,                     // N_ITERATION
                                           GSL_INTEGRATION_GAUSS_KRONROD_POINTS,// INTEGRATION_GAUSS_KRONROD_POINTS
                                           GSL_EPSREL,                          // INTEGRATION_EPSREL
                                           GSL_INTEGRATION_GAUSS_KRONROD_POINTS,// INTEGRATION_LIMBER_GAUSS_KRONROD_POINTS
                                           GSL_EPSREL,                          // INTEGRATION_LIMBER_EPSREL
                                           GSL_EPSREL_DIST,                     // INTEGRATION_DISTANCE_EPSREL
                                           GSL_EPSREL_DNDZ,                     // INTEGRATION_DNDZ_EPSREL
                                           GSL_EPSREL_SIGMAR,                   // INTEGRATION_SIGMAR_EPSREL
                                           GSL_EPSREL_NU,                       // INTEGRATION_NU_EPSREL
                                           GSL_EPSABS_NU,                       // INTEGRATION_NU_EPSABS
                                           GSL_EPSREL,                          // ROOT_EPSREL
                                           GSL_N_ITERATION,                     // ROOT_N_ITERATION
                                           GSL_EPSREL_GROWTH                    // ODE_GROWTH_EPSREL
                                          };

/* ------- ROUTINE: ccl_cosmology_read_config ------
   INPUTS: none, but will look for ini file in include/ dir
   TASK: fill out global variables of splines with user defined input.
   The variables are defined in ccl_params.h.

   The following are the relevant global variables:
*/

ccl_spline_params * ccl_splines=NULL; // Global variable
ccl_gsl_params * ccl_gsl=NULL; // Global variable

void ccl_cosmology_read_config(void)
{

  int CONFIG_LINE_BUFFER_SIZE=100;
  int MAX_CONFIG_VAR_LEN=100;
  FILE *fconfig;
  char buf[CONFIG_LINE_BUFFER_SIZE];
  char var_name[MAX_CONFIG_VAR_LEN];
  char* rtn;
  double var_dbl;

  // Get parameter .ini filename from environment variable or default location
  const char* param_file;
  const char* param_file_env = getenv("CCL_PARAM_FILE");
  if (param_file_env != NULL) {
    param_file = param_file_env;
  }
  else {
    // Use default ini file
    param_file = EXPAND_STR(__CCL_DATA_DIR__) "/ccl_params.ini";
  }
  if ((fconfig=fopen(param_file, "r")) == NULL) {
    ccl_raise_exception(CCL_ERROR_MISSING_CONFIG_FILE, "ccl_core.c: Failed to open config file: %s", param_file);
    return;
  }

  if(ccl_splines == NULL) {
    ccl_splines = malloc(sizeof(ccl_spline_params));
  }
  if(ccl_gsl == NULL) {
    ccl_gsl = malloc(sizeof(ccl_gsl_params));
    memcpy(ccl_gsl, &default_gsl_params, sizeof(ccl_gsl_params));
  }

  /* Exit gracefully if we couldn't allocate memory */
  if(ccl_splines==NULL || ccl_gsl==NULL) {
    ccl_raise_exception(CCL_ERROR_MEMORY, "ccl_core.c: Failed to allocate memory for config file data.");
    return;
  }

#define MATCH(s, action) if (0 == strcmp(var_name, s)) { action ; continue;} do{} while(0)

  int lineno = 0;
  while(! feof(fconfig)) {
    rtn = fgets(buf, CONFIG_LINE_BUFFER_SIZE, fconfig);
    lineno ++;

    if (buf[0]==';' || buf[0]=='[' || buf[0]=='\n') {
      continue;
    }
    else {
      sscanf(buf, "%99[^=]=%le\n",var_name, &var_dbl);

      // Spline parameters
      MATCH("A_SPLINE_NA", ccl_splines->A_SPLINE_NA=(int) var_dbl);
      MATCH("A_SPLINE_NLOG", ccl_splines->A_SPLINE_NLOG=(int) var_dbl);
      MATCH("A_SPLINE_MINLOG", ccl_splines->A_SPLINE_MINLOG=var_dbl);
      MATCH("A_SPLINE_MIN", ccl_splines->A_SPLINE_MIN=var_dbl);
      MATCH("A_SPLINE_MINLOG_PK", ccl_splines->A_SPLINE_MINLOG_PK=var_dbl);
      MATCH("A_SPLINE_MIN_PK", ccl_splines->A_SPLINE_MIN_PK=var_dbl);
      MATCH("A_SPLINE_MAX", ccl_splines->A_SPLINE_MAX=var_dbl);
      MATCH("LOGM_SPLINE_DELTA", ccl_splines->LOGM_SPLINE_DELTA=var_dbl);
      MATCH("LOGM_SPLINE_NM", ccl_splines->LOGM_SPLINE_NM=(int) var_dbl);
      MATCH("LOGM_SPLINE_MIN", ccl_splines->LOGM_SPLINE_MIN=var_dbl);
      MATCH("LOGM_SPLINE_MAX", ccl_splines->LOGM_SPLINE_MAX=var_dbl);
      MATCH("A_SPLINE_NA_PK", ccl_splines->A_SPLINE_NA_PK=(int) var_dbl);
      MATCH("A_SPLINE_NLOG_PK", ccl_splines->A_SPLINE_NLOG_PK=(int) var_dbl);
      MATCH("K_MAX_SPLINE", ccl_splines->K_MAX_SPLINE=var_dbl);
      MATCH("K_MAX", ccl_splines->K_MAX=var_dbl);
      MATCH("K_MIN", ccl_splines->K_MIN=var_dbl);
      MATCH("N_K", ccl_splines->N_K=(int) var_dbl);

      // 3dcorr parameters
      MATCH("N_K_3DCOR", ccl_splines->N_K_3DCOR=(int) var_dbl);

      // GSL parameters
      MATCH("GSL_EPSREL", ccl_gsl->EPSREL=var_dbl);
      MATCH("GSL_N_ITERATION", ccl_gsl->N_ITERATION=(size_t) var_dbl);
      MATCH("GSL_INTEGRATION_GAUSS_KRONROD_POINTS", ccl_gsl->INTEGRATION_GAUSS_KRONROD_POINTS=(int) var_dbl);
      MATCH("GSL_INTEGRATION_EPSREL", ccl_gsl->INTEGRATION_EPSREL=var_dbl);
      MATCH("GSL_INTEGRATION_DISTANCE_EPSREL", ccl_gsl->INTEGRATION_DISTANCE_EPSREL=var_dbl);
      MATCH("GSL_INTEGRATION_DNDZ_EPSREL", ccl_gsl->INTEGRATION_DNDZ_EPSREL=var_dbl);
      MATCH("GSL_INTEGRATION_SIGMAR_EPSREL", ccl_gsl->INTEGRATION_SIGMAR_EPSREL=var_dbl);
      MATCH("GSL_INTEGRATION_NU_EPSREL", ccl_gsl->INTEGRATION_NU_EPSREL=var_dbl);
      MATCH("GSL_INTEGRATION_NU_EPSABS", ccl_gsl->INTEGRATION_NU_EPSABS=var_dbl);
      MATCH("GSL_INTEGRATION_LIMBER_GAUSS_KRONROD_POINTS", ccl_gsl->INTEGRATION_LIMBER_GAUSS_KRONROD_POINTS=(int) var_dbl);
      MATCH("GSL_INTEGRATION_LIMBER_EPSREL", ccl_gsl->INTEGRATION_LIMBER_EPSREL=var_dbl);
      MATCH("GSL_ROOT_EPSREL", ccl_gsl->ROOT_EPSREL=var_dbl);
      MATCH("GSL_ROOT_N_ITERATION", ccl_gsl->ROOT_N_ITERATION=(int) var_dbl);
      MATCH("GSL_ODE_GROWTH_EPSREL", ccl_gsl->ODE_GROWTH_EPSREL=var_dbl);

      ccl_raise_exception(CCL_ERROR_MISSING_CONFIG_FILE, "ccl_core.c: Failed to parse config file at line %d: %s", lineno, buf);
    }
  }
#undef MATCH

  fclose(fconfig);
}

/* ------- ROUTINE: ccl_cosmology_create ------
INPUTS: ccl_parameters params
        ccl_configuration config
TASK: creates the ccl_cosmology struct and passes some values to it
DEFINITIONS:
chi: comoving distance [Mpc]
growth: growth function (density)
fgrowth: logarithmic derivative of the growth (density) (dlnD/da?)
E: E(a)=H(a)/H0
accelerator: interpolation accelerator for functions of a
accelerator_achi: interpolation accelerator for functions of chi
growth0: growth at z=0, defined to be 1
sigma: ?
p_lin: linear matter power spectrum at z=0?
p_lnl: nonlinear matter power spectrum at z=0?
computed_distances, computed_growth,
computed_power, computed_sigma: store status of the computations
*/
ccl_cosmology * ccl_cosmology_create(ccl_parameters params, ccl_configuration config)
{
  ccl_cosmology * cosmo = malloc(sizeof(ccl_cosmology));
  cosmo->params = params;
  cosmo->config = config;

  cosmo->data.chi = NULL;
  cosmo->data.growth = NULL;
  cosmo->data.fgrowth = NULL;
  cosmo->data.E = NULL;
  cosmo->data.accelerator=NULL;
  cosmo->data.accelerator_achi=NULL;
  cosmo->data.accelerator_m=NULL;
  cosmo->data.accelerator_d=NULL;
  cosmo->data.accelerator_k=NULL;
  cosmo->data.growth0 = 1.;
  cosmo->data.achi=NULL;

  cosmo->data.logsigma = NULL;
  cosmo->data.dlnsigma_dlogm = NULL;

  // hmf parameter for interpolation
  cosmo->data.alphahmf = NULL;
  cosmo->data.betahmf = NULL;
  cosmo->data.gammahmf = NULL;
  cosmo->data.phihmf = NULL;
  cosmo->data.etahmf = NULL;

  cosmo->data.p_lin = NULL;
  cosmo->data.p_nl = NULL;
  //cosmo->data.nu_pspace_int = NULL;
  cosmo->computed_distances = false;
  cosmo->computed_growth = false;
  cosmo->computed_power = false;
  cosmo->computed_sigma = false;
  cosmo->computed_hmfparams = false;
  cosmo->status = 0;
  ccl_cosmology_set_status_message(cosmo, "");

  return cosmo;
}

/* ------ ROUTINE: ccl_parameters_fill_initial -------
INPUT: ccl_parameters: params
TASK: fill parameters not set by ccl_parameters_create with some initial values
DEFINITIONS:
Omega_g = (Omega_g*h^2)/h^2 is the radiation parameter; "g" is for photons, as in CLASS
T_CMB: CMB temperature in Kelvin
Omega_l: Lambda
A_s: amplitude of the primordial PS, enforced here to initially set to NaN
sigma8: variance in 8 Mpc/h spheres for normalization of matter PS, enforced here to initially set to NaN
z_star: recombination redshift
 */
void ccl_parameters_fill_initial(ccl_parameters * params, int *status)
{
  // Fixed radiation parameters
  // Omega_g * h**2 is known from T_CMB
  params->T_CMB =  TCMB;
  // kg / m^3
  double rho_g = 4. * STBOLTZ / pow(CLIGHT, 3) * pow(params->T_CMB, 4);
  // kg / m^3
  double rho_crit = RHO_CRITICAL * SOLAR_MASS/pow(MPC_TO_METER, 3) * pow(params->h, 2);
  params->Omega_g = rho_g/rho_crit;

  // Get the N_nu_rel from Neff and N_nu_mass
  params->N_nu_rel = params->Neff - params->N_nu_mass * pow(TNCDM, 4) / pow(4./11.,4./3.);

  // Temperature of the relativistic neutrinos in K
  double T_nu= (params->T_CMB) * pow(4./11.,1./3.);
  // in kg / m^3
  double rho_nu_rel = params->N_nu_rel* 7.0/8.0 * 4. * STBOLTZ / pow(CLIGHT, 3) * pow(T_nu, 4);
  params-> Omega_n_rel = rho_nu_rel/rho_crit;

  // If non-relativistic neutrinos are present, calculate the phase_space integral.
  if((params->N_nu_mass)>0) {
    // Pass NULL for the accelerator here because we don't have our cosmology object defined yet.
    params->Omega_n_mass = ccl_Omeganuh2(1.0, params->N_nu_mass, params->mnu, params->T_CMB, NULL, status) / ((params->h)*(params->h));
    ccl_check_status_nocosmo(status);
  }
  else{
    params->Omega_n_mass = 0.;
  }

  params->Omega_m = params->Omega_b + params-> Omega_c;
  params->Omega_l = 1.0 - params->Omega_m - params->Omega_g - params->Omega_n_rel -params->Omega_n_mass- params->Omega_k;
  // Initially undetermined parameters - set to nan to trigger
  // problems if they are mistakenly used.
  if (isfinite(params->A_s)) {params->sigma8 = NAN;}
  if (isfinite(params->sigma8)) {params->A_s = NAN;}
  params->z_star = NAN;

  if(fabs(params->Omega_k)<1E-6)
    params->k_sign=0;
  else if(params->Omega_k>0)
    params->k_sign=-1;
  else
    params->k_sign=1;
  params->sqrtk=sqrt(fabs(params->Omega_k))*params->h/CLIGHT_HMPC;
}

/* ------ ROUTINE: ccl_parameters_create -------
INPUT: numbers for the basic cosmological parameters needed by CCL
TASK: fill params with some initial values provided by the user
DEFINITIONS:
Omega_c: cold dark matter
Omega_b: baryons
Omega_m: matter
Omega_k: curvature
little omega_x means Omega_x*h^2
Neff : Effective number of neutrino speces
mnu : Pointer to either sum of neutrino masses or list of three masses.
mnu_type : how the neutrino mass(es) should be treated
w0: Dark energy eq of state parameter
wa: Dark energy eq of state parameter, time variation
H0: Hubble's constant in km/s/Mpc.
h: Hubble's constant divided by (100 km/s/Mpc).
A_s: amplitude of the primordial PS
n_s: index of the primordial PS

 */
ccl_parameters ccl_parameters_create(
                     double Omega_c, double Omega_b, double Omega_k,
				     double Neff, double* mnu, ccl_mnu_convention mnu_type,
				     double w0, double wa, double h, double norm_pk,
				     double n_s, double bcm_log10Mc, double bcm_etab,
				     double bcm_ks, int nz_mgrowth, double *zarr_mgrowth,
				     double *dfarr_mgrowth, int *status)
{
  #ifndef USE_GSL_ERROR
    gsl_set_error_handler_off ();
  #endif

  ccl_parameters params;
  // Initialize params
  params.mnu = NULL;
  params.z_mgrowth=NULL;
  params.df_mgrowth=NULL;
  params.sigma8 = NAN;
  params.A_s = NAN;
  params.Omega_c = Omega_c;
  params.Omega_b = Omega_b;
  params.Omega_k = Omega_k;
  params.Neff = Neff;

  // Set the sum of neutrino masses
  params.sum_nu_masses = *mnu;
  double mnusum = *mnu;
  double *mnu_in = NULL;

  /* Check whether ccl_splines and ccl_gsl exist. If either is not set yet, load
     parameters from the config file. */
  if(ccl_splines==NULL || ccl_gsl==NULL) {
    ccl_cosmology_read_config();
  }

  // Decide how to split sum of neutrino masses between 3 neutrinos. We use
  // a Newton's rule numerical solution (thanks M. Jarvis).

  if (mnu_type==ccl_mnu_sum){
	  // Normal hierarchy

	  mnu_in = malloc(3*sizeof(double));

	  // Check if the sum is zero
	  if (*mnu<1e-15){
		  mnu_in[0] = 0.;
		  mnu_in[1] = 0.;
		  mnu_in[2] = 0.;
	  } else{

	      mnu_in[0] = 0.; // This is a starting guess.

	      double sum_check;
	      // Check that sum is consistent
	      mnu_in[1] = sqrt(DELTAM12_sq);
	      mnu_in[2] = sqrt(DELTAM13_sq_pos);
	      sum_check = mnu_in[0] + mnu_in[1] + mnu_in[2];
	      if (ccl_mnu_sum < sum_check){
		      *status = CCL_ERROR_MNU_UNPHYSICAL;
          }

          double dsdm1;
          // This is the Newton's method
          while (fabs(*mnu - sum_check) > 1e-15){

              dsdm1 = 1. + mnu_in[0] / mnu_in[1] + mnu_in[0] / mnu_in[2];
              mnu_in[0] = mnu_in[0] - (sum_check - *mnu) / dsdm1;
              mnu_in[1] = sqrt(mnu_in[0]*mnu_in[0] + DELTAM12_sq);
              mnu_in[2] = sqrt(mnu_in[0]*mnu_in[0] + DELTAM13_sq_pos);
              sum_check = mnu_in[0] + mnu_in[1] + mnu_in[2];
          }
	  }

  } else if (mnu_type==ccl_mnu_sum_inverted){
	  // Inverted hierarchy

	  mnu_in = malloc(3*sizeof(double));

	  	  // Check if the sum is zero
	  if (*mnu<1e-15){
		  mnu_in[0] = 0.;
		  mnu_in[1] = 0.;
		  mnu_in[2] = 0.;
	  } else{

	      mnu_in[0] = 0.; // This is a starting guess.

	      double sum_check;
	      // Check that sum is consistent
	      mnu_in[1] = sqrt(-1.* DELTAM13_sq_neg - DELTAM12_sq);
	      mnu_in[2] = sqrt(-1.* DELTAM13_sq_neg);
	      sum_check = mnu_in[0] + mnu_in[1] + mnu_in[2];
	      if (ccl_mnu_sum < sum_check){
		      *status = CCL_ERROR_MNU_UNPHYSICAL;
          }

          double dsdm1;
          // This is the Newton's method
          while (fabs(*mnu- sum_check) > 1e-15){
              dsdm1 = 1. + (mnu_in[0] / mnu_in[1]) + (mnu_in[0] / mnu_in[2]);
              mnu_in[0] = mnu_in[0] - (sum_check - *mnu) / dsdm1;
              mnu_in[1] = sqrt(mnu_in[0]*mnu_in[0] + DELTAM12_sq);
              mnu_in[2] = sqrt(mnu_in[0]*mnu_in[0] + DELTAM13_sq_neg);
              sum_check = mnu_in[0] + mnu_in[1] + mnu_in[2];
          }

      }

  } else if (mnu_type==ccl_mnu_sum_equal){
	    // Split the sum of masses equally
	    mnu_in = malloc(3*sizeof(double));
	    mnu_in[0] = params.sum_nu_masses / 3.;
	    mnu_in[1] = params.sum_nu_masses / 3.;
	    mnu_in[2] = params.sum_nu_masses / 3.;
  } else if (mnu_type == ccl_mnu_list){
      // A list of neutrino masses was already passed in
	  params.sum_nu_masses = mnu[0] + mnu[1] + mnu[2];
	  mnu_in = malloc(3*sizeof(double));
	  for(int i=0; i<3; i++) mnu_in[i] = mnu[i];
  } else {
	  *status = CCL_ERROR_NOT_IMPLEMENTED;
  }
  // Check for errors in the neutrino set up (e.g. unphysical mnu)
  ccl_check_status_nocosmo(status);

  // Check which of the neutrino species are non-relativistic today
  int N_nu_mass = 0;
  for(int i = 0; i<3; i=i+1){
  	if (mnu_in[i] > 0.00017){ // Limit taken from Lesgourges et al. 2012
  		N_nu_mass = N_nu_mass + 1;
  	}
  }
  params.N_nu_mass = N_nu_mass;

  // Fill the array of massive neutrinos
  if (N_nu_mass>0){
  	params.mnu = malloc(params.N_nu_mass*sizeof(double));
  	int relativistic[3] = {0, 0, 0};
	for (int i = 0; i < N_nu_mass; i = i + 1){
		for (int j = 0; j<3; j = j +1){
			if ((mnu_in[j]>0.00017) && (relativistic[j]==0)){
				relativistic[j]=1;
				params.mnu[i] = mnu_in[j];
				break;
			}
		} // end loop over neutrinos
	} // end loop over massive neutrinos
  } else{
	  params.mnu = malloc(sizeof(double));
	  params.mnu[0] = 0.;
  }
  // Free mnu_in
  if (mnu_in != NULL) free(mnu_in);

  // Dark Energy
  params.w0 = w0;
  params.wa = wa;

  // Hubble parameters
  params.h = h;
  params.H0 = h*100;

  // Primordial power spectra
  if(norm_pk<1E-5)
    params.A_s=norm_pk;
  else
    params.sigma8=norm_pk;
  params.n_s = n_s;

  //Baryonic params
  if(bcm_log10Mc<0)
    params.bcm_log10Mc=log10(1.2e14);
  else
    params.bcm_log10Mc=bcm_log10Mc;
  if(bcm_etab<0)
    params.bcm_etab=0.5;
  else
    params.bcm_etab=bcm_etab;
  if(bcm_ks<0)
    params.bcm_ks=55.0;
  else
    params.bcm_ks=bcm_ks;

  // Set remaining standard and easily derived parameters
  ccl_parameters_fill_initial(&params, status);

  //Trigger modified growth function if nz>0
  if(nz_mgrowth>0) {
    params.has_mgrowth=true;
    params.nz_mgrowth=nz_mgrowth;
    params.z_mgrowth=malloc(params.nz_mgrowth*sizeof(double));
    params.df_mgrowth=malloc(params.nz_mgrowth*sizeof(double));
    memcpy(params.z_mgrowth,zarr_mgrowth,params.nz_mgrowth*sizeof(double));
    memcpy(params.df_mgrowth,dfarr_mgrowth,params.nz_mgrowth*sizeof(double));
  }
  else {
    params.has_mgrowth=false;
    params.nz_mgrowth=0;
    params.z_mgrowth=NULL;
    params.df_mgrowth=NULL;
  }

  return params;
}

/* ------- ROUTINE: ccl_parameters_create_flat_lcdm --------
INPUT: some cosmological parameters needed to create a flat LCDM model
TASK: call ccl_parameters_create to produce an LCDM model
*/
ccl_parameters ccl_parameters_create_flat_lcdm(double Omega_c, double Omega_b, double h,
                                               double norm_pk, double n_s, int *status)
{
  double Omega_k = 0.0;
  double Neff = 3.046;
  double w0 = -1.0;
  double wa = 0.0;
  double *mnu;
  double mnuval = 0.;  // a pointer to the variable is not kept past the lifetime of this function
  mnu = &mnuval;
  ccl_mnu_convention mnu_type = ccl_mnu_sum;

  ccl_parameters params = ccl_parameters_create(Omega_c, Omega_b, Omega_k, Neff,
        mnu, mnu_type, w0, wa, h, norm_pk, n_s, -1, -1, -1, -1, NULL, NULL, status);

  return params;

}

/**
 * Write a cosmology parameters object to a file in yaml format.
 * @param cosmo Cosmological parameters
 * @param f FILE* pointer opened for reading
 * @return void
 */
void ccl_parameters_write_yaml(ccl_parameters * params, const char * filename, int *status)
{

  FILE * f = fopen(filename, "w");

  if (!f){
    *status = CCL_ERROR_FILE_WRITE;
    return;
  }

#define WRITE_DOUBLE(name) fprintf(f, #name ": %le\n",params->name)
#define WRITE_INT(name) fprintf(f, #name ": %d\n",params->name)

  // Densities: CDM, baryons, total matter, curvature
  WRITE_DOUBLE(Omega_c);
  WRITE_DOUBLE(Omega_b);
  WRITE_DOUBLE(Omega_m);
  WRITE_DOUBLE(Omega_k);
  WRITE_INT(k_sign);

  // Dark Energy
  WRITE_DOUBLE(w0);
  WRITE_DOUBLE(wa);

  // Hubble parameters
  WRITE_DOUBLE(H0);
  WRITE_DOUBLE(h);

  // Neutrino properties
  WRITE_DOUBLE(Neff);
  WRITE_INT(N_nu_mass);
  WRITE_DOUBLE(N_nu_rel);

  if (params->N_nu_mass>0){
    fprintf(f, "mnu: [");
    for (int i=0; i<params->N_nu_mass; i++){
      fprintf(f, "%le, ", params->mnu[i]);
    }
    fprintf(f, "]\n");
  }

  WRITE_DOUBLE(sum_nu_masses);
  WRITE_DOUBLE(Omega_n_mass);
  WRITE_DOUBLE(Omega_n_rel);

  // Primordial power spectra
  WRITE_DOUBLE(A_s);
  WRITE_DOUBLE(n_s);

  // Radiation parameters
  WRITE_DOUBLE(Omega_g);
  WRITE_DOUBLE(T_CMB);

  // BCM baryonic model parameters
  WRITE_DOUBLE(bcm_log10Mc);
  WRITE_DOUBLE(bcm_etab);
  WRITE_DOUBLE(bcm_ks);

  // Derived parameters
  WRITE_DOUBLE(sigma8);
  WRITE_DOUBLE(Omega_l);
  WRITE_DOUBLE(z_star);

  WRITE_INT(has_mgrowth);
  WRITE_INT(nz_mgrowth);

  if (params->has_mgrowth){
    fprintf(f, "z_mgrowth: [");
    for (int i=0; i<params->nz_mgrowth; i++){
      fprintf(f, "%le, ", params->z_mgrowth[i]);
    }
    fprintf(f, "]\n");

    fprintf(f, "df_mgrowth: [");
    for (int i=0; i<params->nz_mgrowth; i++){
      fprintf(f, "%le, ", params->df_mgrowth[i]);
    }
    fprintf(f, "]\n");
  }

#undef WRITE_DOUBLE
#undef WRITE_INT

  fclose(f);

}

/**
 * Write a cosmology parameters object to a file in yaml format.
 * @param cosmo Cosmological parameters
 * @param f FILE* pointer opened for reading
 * @return void
 */
ccl_parameters ccl_parameters_read_yaml(const char * filename, int *status)
{

  FILE * f = fopen(filename, "r");

  if (!f){
    *status = CCL_ERROR_FILE_READ;
    ccl_parameters bad_params;

    ccl_raise_exception(CCL_ERROR_FILE_READ, "ccl_core.c: Failed to read parameters from file.");

    return bad_params;
  }

#define READ_DOUBLE(name) double name; *status |= (0==fscanf(f, #name ": %le\n",&name));
#define READ_INT(name) int name; *status |= (0==fscanf(f, #name ": %d\n",&name))

  // Densities: CDM, baryons, total matter, curvature
  READ_DOUBLE(Omega_c);
  READ_DOUBLE(Omega_b);
  READ_DOUBLE(Omega_m);
  READ_DOUBLE(Omega_k);
  READ_INT(k_sign);

  // Dark Energy
  READ_DOUBLE(w0);
  READ_DOUBLE(wa);

  // Hubble parameters
  READ_DOUBLE(H0);
  READ_DOUBLE(h);

  // Neutrino properties
  READ_DOUBLE(Neff);
  READ_INT(N_nu_mass);
  READ_DOUBLE(N_nu_rel);

  double mnu[3] = {0.0, 0.0, 0.0};
  if (N_nu_mass>0){
    *status |= (0==fscanf(f, "mnu: ["));
    for (int i=0; i<N_nu_mass; i++){
      *status |= (0==fscanf(f, "%le, ", mnu+i));
    }
    *status |= (0==fscanf(f, "]\n"));
  }

  READ_DOUBLE(sum_nu_masses);
  READ_DOUBLE(Omega_n_mass);
  READ_DOUBLE(Omega_n_rel);

  // Primordial power spectra
  READ_DOUBLE(A_s);
  READ_DOUBLE(n_s);

  // Radiation parameters
  READ_DOUBLE(Omega_g);
  READ_DOUBLE(T_CMB);

  // BCM baryonic model parameters
  READ_DOUBLE(bcm_log10Mc);
  READ_DOUBLE(bcm_etab);
  READ_DOUBLE(bcm_ks);

  // Derived parameters
  READ_DOUBLE(sigma8);
  READ_DOUBLE(Omega_l);
  READ_DOUBLE(z_star);

  READ_INT(has_mgrowth);
  READ_INT(nz_mgrowth);

  double *z_mgrowth;
  double *df_mgrowth;

  if (has_mgrowth){
    z_mgrowth = malloc(nz_mgrowth*sizeof(double));
    df_mgrowth = malloc(nz_mgrowth*sizeof(double));
    *status |= (0==fscanf(f, "z_mgrowth: ["));
    for (int i=0; i<nz_mgrowth; i++){
      *status |= (0==fscanf(f, "%le, ", z_mgrowth+i));
    }
    *status |= (0==fscanf(f, "]\n"));

    *status |= (0==fscanf(f, "df_mgrowth: ["));
    for (int i=0; i<nz_mgrowth; i++){
      *status |= (0==fscanf(f, "%le, ", df_mgrowth+i));
    }
    *status |= (0==fscanf(f, "]\n"));
  }
  else{
    z_mgrowth = NULL;
    df_mgrowth = NULL;
  }

#undef READ_DOUBLE
#undef READ_INT

  fclose(f);

  if (status){
    char msg[256];
    snprintf(msg, 256, "ccl_core.c: Structure of YAML file incorrect: %s", filename);
    ccl_raise_exception(*status, msg);
  }

  double norm_pk;

  if (isnan(A_s)){
    norm_pk = sigma8;
  }
  else{
   norm_pk = A_s;
  }

  ccl_parameters params = ccl_parameters_create(
    Omega_c, Omega_b, Omega_k,
    Neff, mnu, ccl_mnu_list,
    w0, wa, h, norm_pk,
    n_s, bcm_log10Mc, bcm_etab,
    bcm_ks, nz_mgrowth, z_mgrowth,
    df_mgrowth, status);

  if(z_mgrowth) free(z_mgrowth);
  if (df_mgrowth) free(df_mgrowth);

  return params;

}

/* ------- ROUTINE: ccl_data_free --------
INPUT: ccl_data
TASK: free the input data
*/
void ccl_data_free(ccl_data * data)
{
  //We cannot assume that all of these have been allocated
  //TODO: it would actually make more sense to do this within ccl_cosmology_free,
  //where we could make use of the flags "computed_distances" etc. to figure out
  //what to free up
  gsl_spline_free(data->chi);
  gsl_spline_free(data->growth);
  gsl_spline_free(data->fgrowth);
  gsl_interp_accel_free(data->accelerator);
  gsl_interp_accel_free(data->accelerator_achi);
  gsl_spline_free(data->E);
  gsl_spline_free(data->achi);
  gsl_spline_free(data->logsigma);
  gsl_spline_free(data->dlnsigma_dlogm);
  gsl_spline2d_free(data->p_lin);
  gsl_spline2d_free(data->p_nl);
  gsl_spline_free(data->alphahmf);
  gsl_spline_free(data->betahmf);
  gsl_spline_free(data->gammahmf);
  gsl_spline_free(data->phihmf);
  gsl_spline_free(data->etahmf);
  gsl_interp_accel_free(data->accelerator_d);
  gsl_interp_accel_free(data->accelerator_m);
  gsl_interp_accel_free(data->accelerator_k);
}

/* ------- ROUTINE: ccl_cosmology_set_status_message --------
INPUT: ccl_cosmology struct, status_string
TASK: set the status message safely.
*/
void ccl_cosmology_set_status_message(ccl_cosmology * cosmo, const char * message, ...)
{
  const int trunc = 480; /* must be < 500 - 4 */
  va_list va;
  va_start(va, message);
  vsnprintf(cosmo->status_message, trunc, message, va);
  va_end(va);

  /* if truncation happens, message[trunc - 1] is not NULL, ... will show up. */
  strcpy(&cosmo->status_message[trunc], "...");
}

/* ------- ROUTINE: ccl_parameters_free --------
INPUT: ccl_parameters struct
TASK: free allocated quantities in the parameters struct
*/
void ccl_parameters_free(ccl_parameters * params)
{
  if (params->mnu != NULL){
    free(params->mnu);
    params->mnu = NULL;
  }
  if (params->z_mgrowth != NULL){
    free(params->z_mgrowth);
    params->z_mgrowth = NULL;
  }
  if (params->df_mgrowth != NULL){
    free(params->df_mgrowth);
    params->df_mgrowth = NULL;
  }
}

/* ------- ROUTINE: ccl_cosmology_free --------
INPUT: ccl_cosmology struct
TASK: free the input data and the cosmology struct
*/
void ccl_cosmology_free(ccl_cosmology * cosmo)
{
  ccl_data_free(&cosmo->data);
  free(cosmo);
}