///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
//
// 			     //////////////////////////////////////////////////////////
//  			     //							     //
// 			     //   	        hybridMANTIS v1.0		     //
// 			     //   	       fastDETECT2 - C code  		     //
//			     //		   (optical photons transport)		     //
//			     //							     //
//			     //////////////////////////////////////////////////////////
//
//
//
//
// ****Disclaimer****
//  This software and documentation (the "Software") were developed at the Food and Drug Administration (FDA) by employees of the Federal Government in
//  the course of their official duties. Pursuant to Title 17, Section 105 of the United States Code, this work is not subject to copyright protection
//  and is in the public domain. Permission is hereby granted, free of charge, to any person obtaining a copy of the Software, to deal in the Software
//  without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, or sell copies of the
//  Software or derivatives, and to permit persons to whom the Software is furnished to do so. FDA assumes no responsibility whatsoever for use by other
//  parties of the Software, its source code, documentation or compiled executables, and makes no guarantees, expressed or implied, about its quality,
//  reliability, or any other characteristic. Further, use of this code in no way implies endorsement by the FDA or confers any advantage in regulatory
//  decisions. Although this software can be redistributed and/or modified freely, we ask that any derivative works bear some notice that they are
//  derived from it, and any modified versions bear some notice that they have been modified.
//
//
//	Associated publication: Sharma Diksha, Badal Andreu and Badano Aldo, "hybridMANTIS: a CPU-GPU Monte Carlo method for modeling indirect x-ray detectors with
//				columnar scintillators". Physics in Medicine and Biology, 57(8), pp. 2357–2372 (2012)
//
//
//	File:   	hybridMANTIS_c_ver1_0.c
//	Author: 	Diksha Sharma (US Food and Drug Administration)
//	Email: 		diksha.sharma@fda.hhs.gov
//	Last updated:  	Apr 13, 2012
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/////////////////////////////////////////
//
//      Header libraries
//
/////////////////////////////////////////

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

/////////////////////////////////////////
//
//      Global variables
//
/////////////////////////////////////////

	#define max_photon_per_EDE 900000	// maximum number of optical photons that can be generated per energy deposition event (EDE)

	#ifndef USING_CUDA
		#define mybufsize 2304000	// CPU buffer size: # of events sent to the CPU
	#endif

/////////////////////////////////////////
//
//      Include kernel program
//
/////////////////////////////////////////
	#include "kernel_cuda_c_ver1_0.cu"

////////////////////////////////////////////////////////////////////////////
//				MAIN PROGRAM			          //
////////////////////////////////////////////////////////////////////////////

#ifndef USING_CUDA

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// cpuoptical(): Performs optical transport using CPU
//	  	 Input arguments: gpusize
//
// 		 gpusize - buffer size already processed in the GPU. CPU runs optical transport for rest of the buffer.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

	void cpuoptical_(int *gpusize)
	{

		// command line arguments
		float xdetector, ydetector, radius, height, n_C, n_IC, top_absfrac, bulk_abscoeff, beta, d_min, lbound_x, lbound_y, ubound_x, ubound_y, d_max, yield, sensorRefl;
		int pixelsize, num_primary, min_optphotons, max_optphotons, num_bins;

		float dcos[3]={0}; 		// directional cosines
		float normal[3]={0}; 		// normal to surface in case of TIR
		float pos[3] = {0}; 		// intersecting point coordinates
		float old_pos[3] = {0};  	// source coordinates

		int nbytes = (optical_.myctropt - (*gpusize))*sizeof(struct start_info);
		struct start_info *structa;
		structa = (struct start_info*) malloc(nbytes);
		if( structa == NULL )
			printf("\n Struct start_info array CANNOT BE ALLOCATED - %d !!", (optical_.myctropt-(*gpusize)));

		// get cpu time
		clock_t start, end;
		float num_sec;

		// get current time stamp to initialize seed input for RNG
		time_t seconds;
		seconds = time (NULL);
		struct timeval tv;
		gettimeofday(&tv,NULL);

		float rr=0.0f, theta=0.0f;
		float r=0.0f;			// random number
		float norm=0.0f;
		int jj=0;
		int my_index=0;
		int penindex = 0;		// equal to *gpusize
		int result_algo = 0;
		unsigned long long int *num_rebound;

		// initialize random number generator (RANECU)
		int seed_input = 271828182 ; 		// ranecu seed input
		int seed[2];

		// GNU scientific library (gsl) variables
		const gsl_rng_type * Tgsl;
		gsl_rng * rgsl;
		double mu_gsl;

		// output image variables
		int xdim = 0;
		int ydim = 0;
		int indexi=0, indexj=0;

		// copy to local variables from PENELOPE buffers
		xdetector = inputargs_.detx;		// x dimension of detector (in um). x in (0,xdetector)
		ydetector = inputargs_.dety;		// y dimension of detector (in um). y in (0,ydetector)
		height = inputargs_.detheight;		// height of column and thickness of detector (in um). z in range (-H/2, H/2)
		radius = inputargs_.detradius;		// radius of column (in um).
		n_C = inputargs_.detnC;			// refractive index of columns
		n_IC = inputargs_.detnIC;		// refractive index of intercolumnar material
		top_absfrac = inputargs_.dettop;	// column's top surface absorption fraction (0.0, 0.5, 0.98)
		bulk_abscoeff = inputargs_.detbulk;	// column's bulk absorption coefficient (in um^-1) (0.001, 0.1 cm^-1)
		beta = inputargs_.detbeta;		// roughness coefficient of column walls
		d_min = inputargs_.detdmin;		// minimum distance a photon can travel when transmitted from a column
		d_max = inputargs_.detdmax;
		lbound_x = inputargs_.detlboundx;	// x lower bound of region of interest of output image (in um)
		lbound_y = inputargs_.detlboundy;	// y lower bound (in um)
		ubound_x = inputargs_.detuboundx;	// x upper bound (in um)
		ubound_y = inputargs_.detuboundy;	// y upper bound (in um)
		yield = inputargs_.detyield;		// yield (/eV)
		pixelsize = inputargs_.detpixel;	// 1 pixel = pixelsize microns (in um)
		sensorRefl = inputargs_.detsensorRefl;	// Non-Ideal sensor reflectivity (%)
		num_primary = inputargs_.mynumhist;	// total number of primaries to be simulated
		min_optphotons = inputargs_.minphotons;	// minimum number of optical photons detected to be included in PHS
		max_optphotons = inputargs_.maxphotons;	// maximum number of optical photons detected to be included in PHS
		num_bins = inputargs_.mynumbins;	// number of bins for genrating PHS

	      	// create a generator chosen by the environment variable GSL_RNG_TYPE
	       	gsl_rng_env_setup();
	       	Tgsl = gsl_rng_default;
	       	rgsl = gsl_rng_alloc (Tgsl);

	       	// dimensions of PRF image
		xdim = ceil((ubound_x - lbound_x)/pixelsize);
		ydim = ceil((ubound_y - lbound_y)/pixelsize);
		unsigned long long int myimage[xdim][ydim];

		//initialize the output image 2D array
		for(indexi = 0; indexi < xdim; indexi++)
		{
		 for(indexj = 0; indexj < ydim; indexj++)
		 {
		  myimage[indexi][indexj] = 0;
		 }
		}

		// memory for storing histogram of # photons detected/primary
		int *h_num_detected_prim = 0;
		h_num_detected_prim = (int*)malloc(sizeof(int)*num_primary);

		for(indexj=0; indexj < num_primary; indexj++)
		  h_num_detected_prim[indexj] = 0;

		// memory for storing histogram of # photons detected/primary
		int *h_histogram = 0;
		h_histogram = (int*)malloc(sizeof(int)*num_bins);

		for(indexj=0; indexj < num_bins; indexj++)
		  h_histogram[indexj] = 0;

	penindex = *gpusize;

	for(my_index = 0; my_index < (optical_.myctropt-(*gpusize)); my_index++)		// iterate over x-rays
	{

		// reset the global counters
		num_generated = 0;
		num_detect=0;
		num_abs_top=0;
		num_abs_bulk=0;
		num_lost=0;
		num_outofcol=0;
		num_theta1=0;
		photon_distance=0.0f;

		//re-initialize the output image 2D array
		for(indexi = 0; indexi < xdim; indexi++)
		 for(indexj = 0; indexj < ydim; indexj++)
		   myimage[indexi][indexj] = 0;

		// copying PENELOPE buffer into *structa

		// units in the penelope output file are in cm. Convert them to microns.
		structa[my_index].str_x = optical_.xbufopt[penindex+my_index] * 10000.0f;	// x-coordinate of interaction event
		structa[my_index].str_y = optical_.ybufopt[penindex+my_index] * 10000.0f;	// y-coordinate
		structa[my_index].str_z = optical_.zbufopt[penindex+my_index] * 10000.0f;	// z-coordinate
		structa[my_index].str_E = optical_.debufopt[penindex+my_index];			// energy deposited
		structa[my_index].str_histnum = optical_.nbufopt[penindex+my_index];		// x-ray history

		// sample # optical photons based on light yield and energy deposited for this interaction event (using Poisson distribution)
		mu_gsl = (double)structa[my_index].str_E * yield;
		structa[my_index].str_N = gsl_ran_poisson(rgsl,mu_gsl);

		if(structa[my_index].str_N > max_photon_per_EDE)
		{
			printf("\n\n str_n exceeds max photons. program is exiting - %d !! \n\n",structa[my_index].str_N);
			exit(0);
		}

		num_rebound = (unsigned long long int*) malloc(structa[my_index].str_N*sizeof(unsigned long long int));
		if(num_rebound == NULL)
		printf("\n Error allocating num_rebound memory !\n");

		// start the clock
		start = clock();

		// initialize the RANECU generator in a position far away from the previous history:
		seed_input = (int)(seconds/3600+tv.tv_usec);			// seed input=seconds passed since 1970+current time in micro secs
		init_PRNG(my_index, 50000, seed_input, seed);      		// intialize RNG

		for(jj=0; jj<structa[my_index].str_N; jj++)
			num_rebound[jj] = 0;

		// reset the directional cosine and normal vectors
		dcos[0]=0.0f; dcos[1]=0.0f; dcos[2]=0.0f;
		normal[0]=0.0f; normal[1]=0.0f; normal[2]=0.0f;

		// re-initialize myimage
		for(indexi = 0; indexi < xdim; indexi++)
		 for(indexj = 0; indexj < ydim; indexj++)
			  myimage[indexi][indexj] = 0;

		// set starting location of photon
		pos[0] = structa[my_index].str_x; pos[1] = structa[my_index].str_y; pos[2] = structa[my_index].str_z;
		old_pos[0] = structa[my_index].str_x; old_pos[1] = structa[my_index].str_y; old_pos[2] = structa[my_index].str_z;

		// initializing the direction cosines for the first particle in each core
		r = (ranecu(seed) * 2.0f) - 1.0f; 	// random number between (-1,1)

		while(fabs(r) <= 0.01f)
		 {
		   	r = (ranecu(seed) * 2.0f) - 1.0f;
		 }

		dcos[2] = r;		// random number between (-1,1)
		rr = sqrt(1.0f-r*r);
		theta=ranecu(seed)*twopipen;
		dcos[0]=rr*cos(theta);
		dcos[1]=rr*sin(theta);

		norm = sqrt(dcos[0]*dcos[0] + dcos[1]*dcos[1] + dcos[2]*dcos[2]);

		if ((norm < (1.0f - epsilon)) || (norm > (1.0f + epsilon)))	// normalize
		 {
			dcos[0] = dcos[0]/norm;
			dcos[1] = dcos[1]/norm;
			dcos[2] = dcos[2]/norm;
		 }

		local_counter=0;	// total number of photons terminated (either detected at sensor, absorbed at the top or in the bulk) [global variable]
		while(local_counter < structa[my_index].str_N)		// until all the optical photons are not transported
		 {

			absorbed = 0;
			detect = 0;
			bulk_abs = 0;

			// set starting location of photon
			pos[0] = structa[my_index].str_x; pos[1] = structa[my_index].str_y; pos[2] = structa[my_index].str_z;
			old_pos[0] = structa[my_index].str_x; old_pos[1] = structa[my_index].str_y; old_pos[2] = structa[my_index].str_z;
			num_generated++;
			result_algo = 0;

			while(result_algo == 0)
			 {
			  	result_algo = algo(normal, old_pos, pos, dcos, num_rebound, seed, structa[my_index], &myimage[0][0], xdetector, ydetector, radius, height, n_C, n_IC, top_absfrac, bulk_abscoeff, beta, d_min, pixelsize, lbound_x, lbound_y, ubound_x, ubound_y, sensorRefl, d_max, ydim, h_num_detected_prim);
			 }

		 }

		// end the clock
		end = clock();

		num_sec = (((float)end - start)/CLOCKS_PER_SEC);

		 // type cast unsigned long long int to double
		 double cast_num_generated;
		 double cast_num_detect;
		 double cast_num_abs_top;
		 double cast_num_abs_bulk;
		 double cast_num_lost;
		 double cast_num_outofcol;
		 double cast_num_theta1;
		 double cast_gputime;

		 cast_num_generated = (double)num_generated;
		 cast_num_detect    = (double)num_detect;
		 cast_num_abs_top   = (double)num_abs_top;
		 cast_num_abs_bulk  = (double)num_abs_bulk;
		 cast_num_lost      = (double)num_lost;
		 cast_num_outofcol  = (double)num_outofcol;
		 cast_num_theta1    = (double)num_theta1;
		 cast_gputime	    = (double)(num_sec*1000.0);		// convert in millisecond

		 // save to global counters
		 optstats_.glgen      = optstats_.glgen      + cast_num_generated;
		 optstats_.gldetect   = optstats_.gldetect   + cast_num_detect;
		 optstats_.glabstop   = optstats_.glabstop   + cast_num_abs_top;
		 optstats_.glabsbulk  = optstats_.glabsbulk  + cast_num_abs_bulk;
		 optstats_.gllost     = optstats_.gllost     + cast_num_lost;
		 optstats_.gloutofcol = optstats_.gloutofcol + cast_num_outofcol;
		 optstats_.gltheta1   = optstats_.gltheta1   + cast_num_theta1;
		 optstats_.glgputime  = optstats_.glgputime  + cast_gputime;

		// release resources
		free(num_rebound);

	}	// my_index loop ends

	// make histogram of number of detected photons/primary for num_bins
	int binsize=0, newbin=0;
	int bincorr=0;

	binsize = floor((max_optphotons-min_optphotons)/num_bins);	// calculate size of each bin. Assuming equally spaced bins.
	bincorr = floor(min_optphotons/binsize);			// correction in bin number if min_optphotons > 0.

	for(indexi = 0; indexi < num_primary; indexi++)
	 {
	 	newbin = floor(h_num_detected_prim[indexi]/binsize) - bincorr;	// find bin #

	 	if(h_num_detected_prim[indexi] > 0)	// store only non-zero bins
	 	{
		 	if(h_num_detected_prim[indexi] <= min_optphotons)	// # detected < minimum photons given by user, add to the first bin
		 		h_histogram[0]++;
		 	else if(h_num_detected_prim[indexi] >= max_optphotons)	// # detected > maximum photons given by user, then add to the last bin
		 		h_histogram[num_bins-1]++;
		 	else
		 		h_histogram[newbin]++;
		}
	 }

	// add num_detected_primary to gldetprimary array in PENELOPE
	for(indexi = 0; indexi < num_bins; indexi++)
		outputdetprim_.gldetprimary[indexi] = outputdetprim_.gldetprimary[indexi] + h_histogram[indexi];

	// release resources
	free(structa);
	free(h_num_detected_prim);
	free(h_histogram);

		return;
	}	// C main() ends

#endif


#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <unistd.h>
#include <gsl/gsl_rng.h>
#include <gsl/gsl_math.h>
#include <gsl/gsl_integration.h>

#include "core_allvars.h"
#include "core_proto.h"
#include "UVmag/UVmag.h"

// Local Proto-Types //

int32_t init_delayedSN(void);
int32_t init_nionlookup(void);
int32_t init_metalcooling(void);
void read_snap_list(void);
void set_units(void);
int32_t init_fesc(void);
int32_t determine_fescMH_constants(void);

// External Functions //

void sage_init(void)
{
  int32_t i, status;

  count_gal = 0;
  random_generator = gsl_rng_alloc(gsl_rng_ranlxd1);
  gsl_rng_set(random_generator, 42);	 // start-up seed

  set_units();
  srand((unsigned) time(NULL));

  read_snap_list();
  for(i = 0; i < MAXSNAPS; i++)
  {
    ZZ[i] = 1 / AA[i] - 1;
    Age[i] = time_to_present(ZZ[i]);
  }

  a0 = 1.0 / (1.0 + Reionization_z0);
  ar = 1.0 / (1.0 + Reionization_zr);

  read_cooling_functions();

  status = init_fesc();
  if (status != EXIT_SUCCESS)
  {
    ABORT(0);
  }

  count_onehalo = 0;

  zeromass_count = 0;
  suppression_count = 0;
  previous_tree = 0;

  smallest_mass = 100000;
  lowmass_halo = 0;

  outside_box = 0;
  inside_box = 0;

  count_Mvir = 0;
  count_Len = 0;

  if (IMF == 0)
  {
    // Salpeter IMF //
    IMF_norm =0.1706;
    IMF_slope = -2.35;
    Eta_SNII = 7.432e-3; //Number fraction of stars that will end their lives as type II supernovae.
    m_SNII = 0.144; // Mass fraction of stars that will end their lives as type II supernovae.

  } else if (IMF == 1)
  {
    // Chabrier IMF //
    IMF_norm = 0.23638;
    IMF_slope = -2.3;
    Eta_SNII = 1.1893e-2; //Number fraction of stars that will end their lives as type II supernovae.
    m_SNII = 0.23638; // Mass fraction of stars that will end their lives as type II supernovae.

  }

  // Tiamat Parameters //

  V_energy = 70.0;
  alpha_energy = 0.5;
  beta_energy = 10.0;

  V_mass = 70.0;
  alpha_mass = 6.0;
  beta_mass = 10.0;

  epsilon_mass_max = 30.0;

  if (IRA == 0)
  {
    status = init_delayedSN();
    if (status != EXIT_SUCCESS)
    {
      ABORT(EXIT_FAILURE);
    }
  }

  if (PhotonPrescription == 1)
  {
    status = init_nionlookup();
    if (status != EXIT_SUCCESS)
    {
      ABORT(EXIT_FAILURE);
    }
  }

  if (calcUVmag == 1)
  {
    status = init_UVlookup();
    if (status != EXIT_SUCCESS)
    {
      ABORT(EXIT_FAILURE);
    }
  }

  if ((PhotonPrescription == 1) || (calcUVmag == 1))
  {
    StellarTracking_Len = STELLAR_TRACKING_TIME / TimeResolutionStellar;
  }

  mergedgal_mallocs = 0;
  gal_mallocs = 0 ;

  mergedgal_frees = 0;
  gal_frees = 0;

  Ngamma_HI_Total = 0.0;

}

// Local Functions //

int32_t init_delayedSN(void)
{

  // We keep the past 50 Myrs of star formation stored for each galaxy.
  // Based on the TimeResolution specified need to find out how many elements this corresponds to.

  if(TimeResolutionSN > 50)
  {
    fprintf(stderr, "The selected time resolution for SN feedback (TimeResolutionSN) is set too high (%d Myr).  Using TimeResolutionSN > 50Myr is the same as using the instantaneous recycling approximation; set 'IRA' to 1 instead!\n", TimeResolutionSN);
    ABORT(EXIT_FAILURE);
  } else if(TimeResolutionSN > 35)
  {
    fprintf(stderr, "Your selected time resolution for SN feedback (TimeResolutionSN) is quite high (%d Myr).  Beyond 50Myr the instantaneous recycling approximation is valid hence with your value it would likely be correct to set 'IRA' to 1.\n", TimeResolutionSN);
  } else
  {
    Time_SFH = 0;
    SN_Array_Len = 0;
    while(Time_SFH < 50)
    {
      Time_SFH += TimeResolutionSN;
      ++SN_Array_Len;
    }

  }

  // For the delayed SN scheme the number of SN for each time step depends on the IMF.
  // As we will need to calculate this for every substep we create look-up tables for efficiency.

  // First we need to know given a timestep, what is the minimum mass star that will go supernova?

  double a = 0.7473; // Fits from Portinari et al. (1998).
  double b = -2.6979;
  double c = -4.7659;
  double d = 0.5934;

  coreburning_tbins_low = 2; // Any time below 2Myr will result in stars > 120Myr to explode. This is beyond the limits of the IMF.
  coreburning_tbins_high = 45; // Any time above 45Myr will result in stars > 8Myr to explode. Can just return 8.0 in this case.
  coreburning_tbins_delta = 0.00001;

  N_tbins = (coreburning_tbins_high - coreburning_tbins_low) / (coreburning_tbins_delta);
  int32_t bin_idx;

  coreburning_times = malloc(sizeof(*(coreburning_times)) * N_tbins);
  if (coreburning_times == NULL)
  {
    fprintf(stderr, "Could not allocate memory for the grid to contains the coreburning times.\n");
    return EXIT_FAILURE;
  }

  for (bin_idx = 0; bin_idx < N_tbins; ++bin_idx)
  {
    double t = coreburning_tbins_low + ((double)bin_idx * coreburning_tbins_delta);
    coreburning_times[bin_idx] = exp10(a/log10(t) + b * exp(c/log10(t)) + d);
  }

  // Now need to know given a mass range (defined by the timestep) what is the number and mass fraction of SN that go supernova.

  m_IMFbins_low = 8.0; // The IMF range is from 8.0 to 120.0 Msun.
  m_IMFbins_high = 120.0;
  m_IMFbins_delta = 0.00001;

  N_massbins = (m_IMFbins_high - m_IMFbins_low) / (m_IMFbins_delta);

  IMF_massgrid_eta = malloc(sizeof(*(IMF_massgrid_eta)) * N_massbins);
  if (IMF_massgrid_eta == NULL)
  {
    fprintf(stderr, "Could not allocate memory for the grid to contain the IMF eta masses for delayed supernova.\n");
    return EXIT_FAILURE;
  }

  IMF_massgrid_m = malloc(sizeof(*(IMF_massgrid_m)) * N_massbins);
  if (IMF_massgrid_m == NULL)
  {
    fprintf(stderr, "Could not allocate memory for the grid to contain the IMF m masses for delayed supernova.\n");
    return EXIT_FAILURE;
  }

  for (bin_idx = 0; bin_idx < N_massbins; ++bin_idx)
  {
    IMF_massgrid_eta[bin_idx] = pow(m_IMFbins_low + ((double)bin_idx * m_IMFbins_delta), IMF_slope + 1.0);
    IMF_massgrid_m[bin_idx] = pow(m_IMFbins_low + ((double)bin_idx * m_IMFbins_delta), IMF_slope + 2.0);
  }

  return EXIT_SUCCESS;

}

int32_t init_nionlookup(void)
{

  // For PhotonPrescription == 1 we wish to explicitly track the stellar ages of a galaxy.
  // Then we determine the number of ionizing photons using the age of the stellar population.

  // Using STARBURST99 it was determined that the number of ionizing photons emitted from an instantaneous starburst depends on the
  // mass of stars formed and the time since the starburst.  Furthermore, the number of ionizing photons scales linearly with the mass of stars formed.
  // That is, a starburst that forms 7.0e10Msun worth of stars will emit 10x as many photons as a starburst that forms 6.0e10Msun worth of stars.

  // So if we read in a table that contains the number of ionizing photons emitted from a starburst for a 6.0e10Msun episode, then we can scale our values to this lookup table
  // using log10 Ngamma(Msun, t) = (log10 M* - 6.0) + log10 Ngamma(6.0, t).

#define MAXBINS 10000

  char buf[MAX_STRING_LEN], fname[MAX_STRING_LEN];
  FILE *niontable;
  int32_t i = 0, num_lines = 0;
  float t, HI, HI_L, HeI, HeI_L, HeII, HeII_L, L;

  snprintf(fname, MAX_STRING_LEN - 1, ROOT_DIR "/extra/nion_table.txt");
  niontable = fopen(fname, "r");
  if (niontable == NULL)
  {
    fprintf(stderr, "Could not open file %s\n", fname);
    return EXIT_FAILURE;
  }

  stars_tbins = calloc(MAXBINS, sizeof(*(stars_tbins)));
  if (stars_tbins == NULL)
  {
    fprintf(stderr, "Could not allocate memory for the time bins for the tracking of stellar populations.\n");
    return EXIT_FAILURE;
  }

  stars_Ngamma = calloc(MAXBINS, sizeof(*(stars_Ngamma)));
  if (stars_Ngamma == NULL)
  {
    fprintf(stderr, "Could not allocate memory for the Ngamma HI bins for the tracking of stellar populations.\n");
    return EXIT_FAILURE;
  }

  // The first 8 lines are all header information. So move past them.
  while (i < 8)
  {
    fgets(buf, MAX_STRING_LEN, niontable);
    ++i;
  }

  while (fscanf(niontable, "%f %f %f %f %f %f %f %f", &t, &HI, &HI_L, &HeI, &HeI_L, &HeII, &HeII_L, &L) == 8)
  {

    stars_tbins[num_lines] = t;
    stars_Ngamma[num_lines] = HI;

    ++num_lines;
    if (num_lines == MAXBINS - 1)
    {
      fprintf(stderr, "Exceeding the maximum bins for the tracking of stellar populations.\n");
      return EXIT_FAILURE;
    }
  }
  fclose(niontable);

  // Check that the Nion lookup table had enough datapoints to cover the time we're tracking the ages for.
  if (stars_tbins[num_lines - 1] / 1.0e6 < STELLAR_TRACKING_TIME)
  {
    fprintf(stderr, "The final time specified in the Nion lookup table is %.4f Myr. However we specified to track stellar ages over %d Myr.\n", stars_tbins[num_lines - 1] / 1.0e6, STELLAR_TRACKING_TIME);
    fprintf(stderr, "Either update the Nion lookup table or reduce the value of STELLAR_TRACKING_TIME in `core_allvars.h`.\n");
    return EXIT_FAILURE;
  }

  return EXIT_SUCCESS;

#undef MAXBINS
}

void set_units(void)
{

  UnitTime_in_s = UnitLength_in_cm / UnitVelocity_in_cm_per_s;
  UnitTime_in_Megayears = UnitTime_in_s / SEC_PER_MEGAYEAR;
  UnitDensity_in_cgs = UnitMass_in_g / pow(UnitLength_in_cm, 3);
  UnitPressure_in_cgs = UnitMass_in_g / UnitLength_in_cm / pow(UnitTime_in_s, 2);
  UnitCoolingRate_in_cgs = UnitPressure_in_cgs / UnitTime_in_s;
  UnitEnergy_in_cgs = UnitMass_in_g * pow(UnitLength_in_cm, 2) / pow(UnitTime_in_s, 2);

  EnergySNcode = EnergySN / UnitEnergy_in_cgs * Hubble_h;

  sage_G = GRAVITY / pow(UnitLength_in_cm, 3) * UnitMass_in_g * pow(UnitTime_in_s, 2);

  // convert some physical input parameters to internal units
  sage_Hubble = HUBBLE * UnitTime_in_s;

  // compute a few quantitites
  RhoCrit = 3 * sage_Hubble * sage_Hubble / (8 * M_PI * sage_G);
}

void read_snap_list(void)
{
  FILE *fd;
  char fname[1000];

  sprintf(fname, "%s", FileWithSnapList);

  if(!(fd = fopen(fname, "r")))
  {
    printf("can't read output list in file '%s'\n", fname);
    ABORT(0);
  }

  Snaplistlen = 0;
  do
  {
    if(fscanf(fd, " %lg ", &AA[Snaplistlen]) == 1)
      Snaplistlen++;
    else
      break;
  }
  while(Snaplistlen < MAXSNAPS);

  fclose(fd);
}

double time_to_present(double z)
{
#define WORKSIZE 1000
  gsl_function F;
  gsl_integration_workspace *workspace;
  double time, result, abserr;

  workspace = gsl_integration_workspace_alloc(WORKSIZE);
  F.function = &integrand_time_to_present;

  gsl_integration_qag(&F, 1.0 / (z + 1), 1.0, 1.0 / sage_Hubble,
    1.0e-8, WORKSIZE, GSL_INTEG_GAUSS21, workspace, &result, &abserr);

  time = 1 / sage_Hubble * result;

  gsl_integration_workspace_free(workspace);

  // return time to present as a function of redshift
  return time;
}

double integrand_time_to_present(double a, void *param)
{
  (void)(param); // Avoids triggering unused-parameter warning.
  return 1 / sqrt(Omega / a + (1 - Omega - OmegaLambda) + OmegaLambda * a * a);
}

int32_t init_fesc(void)
{
  switch(fescPrescription)
  {

    case 0:
      if (ThisTask == 0)
      {
        printf("\n\nUsing a constant escape fraction of %.4f\n", beta);
      }
      break;

    case 1:
      if (ThisTask == 0)
      {
        printf("\n\nUsing an fesc prescription that scales with the fraction of ejected mass in the galaxy.\nThis takes the form A*fej + B with A = %.4e and B = %.4e\n", alpha, beta);
      }
      break;

    case 2:
      if (ThisTask == 0)
      {
        printf("\n\nUsing an fesc prescription that depends upon quasar activity.\n");
        printf("\nFor a galaxy that had a quasar event within %.2f dynamical times go the escape fraction will be %.2f.  Otherwise it will have a constant value of %.2f\n", N_dyntime, quasar_boosted, quasar_baseline);
      }
      break;

    case 3:
      if (ThisTask == 0)
      {
        printf("\n\nUsing Anne's functional form for an escape fraction that increases for descreasing halo mass.\n");
        printf("MH_low = %.4e\tMH_high = %.4e\tfesc_low = %.4f\tfesc_high =  %.4f.\n", MH_low, MH_high, fesc_low, fesc_high);
      }
      XASSERT(fesc_low > fesc_high, "Input file contain fesc_low = %.2f and fesc_high = %.2f. For this prescription (fescPrescription == 3), we require fesc_low > fesc_high\n", fesc_low, fesc_high);

      break;

    case 4:
      if (ThisTask == 0)
      {
        printf("\n\nUsing Anne's functional form for an escape fraction that increases for increasing halo mass.\n");
        printf("MH_low = %.4e\tMH_high = %.4e\tfesc_low = %.4f\tfesc_high =  %.4f.\n", MH_low, MH_high, fesc_low, fesc_high);
      }
      XASSERT(fesc_low < fesc_high, "Input file contain fesc_low = %.2f and fesc_high = %.2f. For this prescription (fescPrescription == 4), we require fesc_low < fesc_high\n", fesc_low, fesc_high);
      break;

    case 5:
      if (ThisTask == 0)
      {
        printf("\n\nUsing an fesc prescription that depends upon the SFR of the galaxy.\n");
        printf("This takes the function form of a logistic function, fesc = delta / (1.0 + exp(-alpha*(log10(SFR)-beta))), with range [0.0, delta].  `alpha` controls the steepness of the curve and beta defines the value of log10(SFR) gives fesc = delta/2.\n");
        printf("alpha = %.4f\tbeta = %.4f\tdelta = %.4f\n", alpha, beta, delta);
      }
      break;

    default:
      printf("\n\nOnly escape fraction prescriptions 0, 1, 2, 3, 4 or 5 are permitted.\n");
      return EXIT_FAILURE;
  }
  return EXIT_SUCCESS;
}

/*
For some escape fraction prescriptions, the functional form is a power law with the constants calculated using two fixed points.
This function determines these constants based on the fixed points specified.
Refer to the .ini file or `determine_fesc()` function for full details on each `fescPrescription`.

Parameters
----------
None.  All variables used/adjusted are global.

Returns
----------
None. All variables adjusted are global
.
Pointer Updates
----------
None.

Units
----------
The Halo Masses used to specify the fixed points (MH_low and MH_high) are in Msun.
*/

int32_t determine_fescMH_constants(void)
{

  double A, B, log_A;

  switch(fescPrescription)
  {
    case 3:
      log_A = (log10(fesc_high) - (log10(fesc_low)*log10(MH_high)/log10(MH_low))) * pow(1 - (log10(MH_high) / log10(MH_low)), -1);
      B = (log10(fesc_low) - log_A) / log10(MH_low);
      A = pow(10, log_A);

      fescMH_alpha = A;
      fescMH_beta = B;

      printf("Fixing the points (%.4e, %.2f) and (%.4e, %.2f)\n", MH_low, fesc_low, MH_high, fesc_high);
      printf("This gives a power law with constants A = %.4e, B = %.4e\n", fescMH_alpha, fescMH_beta);

      break;

  }

  return EXIT_SUCCESS;

}


/*

Excited States software: KGS
Contributors: See CONTRIBUTORS.txt
Contact: kgs-contact@simtk.org

Copyright (C) 2009-2017 Stanford University

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

This entire text, including the above copyright notice and this permission notice
shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.

*/

#ifndef KGS_LSNRELATIVEDIRECTION_H
#define KGS_LSNRELATIVEDIRECTION_H

#include <gsl/gsl_vector.h>

#include "Selection.h"
#include "Direction.h"
#include "core/Configuration.h"

class LSNrelativeDirection: public Direction  {
 public:
  LSNrelativeDirection(Selection& atomsMoving,
                       const std::vector< std::tuple<Atom*, Atom*, double> >& goal_distances);

  const std::vector< std::tuple<Atom*, Atom*, double> > getGoalDistances(){return m_goalDistances;}
  void setGoalDistances(std::vector< std::tuple<Atom*, Atom*, double> > goal_distances){m_goalDistances=goal_distances;}

 protected:
  void computeGradient(Configuration* conf, Configuration* conf2, gsl_vector* ret) override;
  void computeGradientExternalN(gsl_matrix* fullN, Configuration* conf, Configuration* conf2, gsl_vector* ret);

 private:

  void fillmatrices(Configuration* current_q, gsl_matrix* targetJacobian, gsl_matrix* targetDirection);
  // void clashFreeGradient(gsl_vector* gradient, gsl_vector* admissible_gradient, Molecule* protein);
  gsl_matrix* determineBestMove(gsl_matrix* N, gsl_matrix* targetJacobian, gsl_matrix* TargetPosition);
  Selection& m_atomsMovingSelection;
  std::vector< std::tuple<Atom*, Atom*, double> > m_goalDistances;

  friend class LSNclashAvoidingMove; //requires the computeGradientExternalN function
};

#endif //KGS_LSNRELATIVEDIRECTION_H


#ifndef ControlByRow_h
#define ControlByRow_h
/**
 * @file
 * $Revision: 1.2 $
 * $Date: 2007/01/30 22:12:21 $
 *
 *   Unless noted otherwise, the portions of Isis written by the USGS are
 *   public domain. See individual third-party library and package descriptions
 *   for intellectual property information, user agreements, and related
 *   information.
 *
 *   Although Isis has been used by the USGS, no warranty, expressed or
 *   implied, is made by the USGS as to the accuracy and functioning of such
 *   software and related material nor shall the fact of distribution
 *   constitute any such warranty, and no responsibility is assumed by the
 *   USGS in connection therewith.
 *
 *   For additional information, launch
 *   $ISISROOT/doc//documents/Disclaimers/Disclaimers.html
 *   in a browser or see the Privacy & Disclaimers page on the Isis website,
 *   http://isis.astrogeology.usgs.gov, and the USGS privacy and disclaimers on
 *   http://www.usgs.gov/privacy.html.
 */

#include <vector>
#include "ControlMeasure.h"
#include "ControlMeasureLogData.h"
#include "Statistics.h"
#include "CollectorMap.h"
#include "IString.h"
#include <gsl/gsl_math.h>

namespace Isis {

  /**
   * @brief Container for point collection
   */
  struct PointData {
    ControlMeasure refPoint;
    ControlMeasure chpPoint;
  };

  /**
   * @brief Less than test for Control point group
   *
   * This function tests the reference line numbers and returns true if the first
   * point reference line is less than the second.
   *
   * @param p1  First PointData set to compare
   * @param p2  Second PointData set to compare
   *
   * @return bool If first point reference line is less than the second
   */
  inline bool PointLess(const PointData &p1, const PointData &p2) {
    return (p1.refPoint.GetLine() < p2.refPoint.GetLine());
  }

  /**
   * @brief Equality test for Control point group
   *
   * This function tests the reference line numbers for equality and returns true
   * if the line references are equivalent, according to an approximation using an
   * epsilon of 1.0e-6.
   *
   * @param p1  First PointData set to compare
   * @param p2  Second PointData set to compare
   *
   * @return bool If the reference point lines are (approximately) equivalent
   */
  inline bool PointEqual(const PointData &p1, const PointData &p2) {
    return (gsl_fcmp(p1.refPoint.GetLine(), p2.refPoint.GetLine(), 1.0E-6) == 0);
  }

  /**
   * @brief Collector for Control points within the same row for analysis
   *
   * This class is designed to be used as a Functor object collecting control net
   * file and collapsing all column measures into on row.  This is primarily used
   * for analysis of coregistration results with one or more columns specified
   * in the search/pattern chip strategy.
   *
   * @author ????-??-?? Unknown
   *
   * @internal
   */
  class ControlByRow {
    public:
      /**
       * @brief Structure to return control point statistics for row
       *
       * This structure contains the row statistics of merged control points.
       * This will eventually be used to compute the spline interpolations for
       * line and sample offsets.
       *
       */
      struct RowPoint {
        RowPoint() : refLine(0.0), refSamp(0.0), chpLine(0.0), chpSamp(0.0),
          total(0), count(0) { }
        double refLine;    //!<  Reference line (row)
        double refSamp;    //!<  Reference sample
        double chpLine;    //!<  Registered line
        double chpSamp;    //!<  Registered sample
        int total;         //!<  Total points in row
        int count;         //!<  Valid points found

        Statistics rSStats;
        Statistics cLStats;
        Statistics cSStats;
        Statistics cLOffset;
        Statistics cSOffset;
        Statistics GOFStats;
      };

    public:
      /**
       * @brief Default constructor
       */
      ControlByRow()  {
        _minGOF = DBL_MIN;
        _maxGOF = DBL_MAX;
      }

      /**
       * @brief Constructor that sets the maximum goodness of fit tolerance
       * @param maxGOF Value that specifies the maximum goodness of fit which is
       *               typically never expected to exceed 1.0 for a good fit.
       */
      ControlByRow(double maxGOF)  {
        _minGOF = DBL_MIN;
        _maxGOF = maxGOF;
      }

      /**
       * @brief Constructor that sets the maximum goodness of fit tolerance
       * @param minGOF Value of minimum goodness of fit.  Allows user selectable
       *               adjustment to coregistration minimum tolerance
       * @param maxGOF Value that specifies the maximum goodness of fit which is
       *               typically never expected to exceed 1.0 for a good fit.
       */
      ControlByRow(double minGOF, double maxGOF)  {
        _minGOF = minGOF;
        _maxGOF = maxGOF;
      }

      /**
       * @brief Destructor
       */
      ~ControlByRow() { }

      /**
       * @brief Determines the number of points (rows) found valid
       *
       * The number returned is really the number of unique rows of coregistration
       * chips gleened from the control net.
       *
       * @return unsigned int Row/line count
       */
      inline unsigned int size() const {
        return (_rowList.size());
      }

      /**
       * @brief Set the minimum acceptable goodness of fit value
       *
       * This sets the minimum (absolute) value used to gleen valid points from
       * the control net data.
       *
       * @param minGOF Minimum goodness of fit tolerance
       */
      void setMinGOF(double minGOF) {
        _minGOF = minGOF;
      }

      /**
       * @brief Set the maximum acceptable goodness of fit value
       *
       * This sets the maximum (absolute) value used to gleen valid points from
       * the control net data.  This is intended to use to exclude wild points
       * that exceed the level of reasonable tolerance.  This is typically 1.0 for
       * most coregistration algorithms.
       *
       * @param maxGOF Maximum goodness of fit tolerance
       */
      void setMaxGOF(double maxGOF) {
        _maxGOF = maxGOF;
      }

      /**
       * @brief Operator used to add a point to the data set
       *
       * This method provides support for the STL for_each algorithm.  This allows
       * this class to be used as a functor object.
       *
       * @param p Point to tested for acceptance in the list
       */
      void operator()(const PointData &p) {
        addPoint(p);
        return;
      }

      /**
       * @brief Formal method of adding a control point to the data set
       *
       * This method will add the provided point to the collection of rows (or
       * lines of points).
       *
       * @param p  Point to add to the list
       */
      void addPoint(const PointData &p) {
        if(_rowList.exists(p.refPoint.GetLine())) {
          PointList &r = _rowList.get(p.refPoint.GetLine());
          r.push_back(p);
        }
        else {
          PointList pl;
          pl.push_back(p);
          _rowList.add(p.refPoint.GetLine(), pl);
        }
        return;
      }

      /**
       * @brief Returns a point at the ith location
       *
       * Traverses the list of points after computing the merge statistics for the
       * ith row.
       *
       * @param i Index of point to return
       *
       * @return RowPoint  Structure containing the (statistically) merged row
       */
      RowPoint operator[](int i) const {
        try {
          return (computeStats(_rowList.getNth(i)));
        }
        catch(IException &oor) {
          QString msg = "Requested value (" + toString(i) +  ") not found";
          throw IException(oor, IException::User, msg, _FILEINFO_);
        }
      }

    private:
      typedef std::vector<PointData> PointList;    //!< Composite list
      typedef CollectorMap<double, PointList, RobustFloatCompare> CNetRow;  /**
                                  * Nifty templated collector class works just
                                  * nicely for merging rows of correlations
                                                                         */
      double            _minGOF;  //!<  Minimum acceptable goodness of fit
      double            _maxGOF;  //!<  Maximum acceptable goodness of fit
      CNetRow           _rowList; //!<  Collection of merged rows/lines

      /**
       * @brief Convenience method for adding point to Statistics class
       *
       * The interface to the Isis Statistics class requires the value to be added
       * by address.  This method expedites the addition of values from say a
       * function or method.
       *
       * @param value  Value to add to Statistics class
       * @param stats  Statistics class to add the data point to
       */
      inline void addToStats(double value, Statistics &stats) const {
        stats.AddData(&value, 1);
        return;
      }

      /**
       * @brief  All important method that computes statistics for a row
       *
       * This method computes the statistics for a potentially merged row of
       * coregistration chips.  It applies the minimum and maximum goodness of fit
       * tolerance checks and add valid points to each statistical component of
       * the merge.
       *
       * @param cols  List of column chip registrations
       *
       * @return RowPoint Structure containing the statistics for row/line of
       *         merged registration columns
       */
      RowPoint computeStats(const std::vector<PointData> &cols) const {
        RowPoint rp;
        rp.refLine = cols[0].refPoint.GetLine();
        for(unsigned int i = 0; i < cols.size() ; i++) {
          double regGOF =
            cols[i].chpPoint.GetLogData(ControlMeasureLogData::GoodnessOfFit)
              .GetNumericalValue();
          if(fabs(regGOF) > _maxGOF) continue;
          if(fabs(regGOF) < _minGOF) continue;
          (rp.count)++;
          addToStats(cols[i].refPoint.GetSample(), rp.rSStats);
          addToStats(cols[i].chpPoint.GetLine(), rp.cLStats);
          addToStats(cols[i].chpPoint.GetSample(), rp.cSStats);
          addToStats(cols[i].chpPoint.GetLineResidual(), rp.cLOffset);
          addToStats(cols[i].chpPoint.GetSampleResidual(), rp.cSOffset);
          addToStats(regGOF, rp.GOFStats);
        }

        rp.total = cols.size();
        rp.refSamp = rp.rSStats.Average();
        rp.chpLine = rp.cLStats.Average();
        rp.chpSamp = rp.cSStats.Average();
        return (rp);
      }

  };

}  // namespace Isis

#endif


/*
 *  parameters.h
 *  Spike
 *
 *  Created by Ben Evans on 18/08/2008.
 *  Copyright 2008 University of Oxford. All rights reserved.
 *
 */

#ifndef _PARAMETERS_H
#define _PARAMETERS_H

#include <stdbool.h>
#include <gsl/gsl_rng.h>
#include <gsl/gsl_randist.h>
#include <gsl/gsl_version.h>

typedef int tstep; // Signed to allow spikeTimes[0] = -BIG //long long

typedef unsigned char uchar;

struct SIMULATION {
    bool Xgrid;
    tstep tally;
    tstep ptTS;
    tstep trainTS;
    tstep testTS;
    tstep totTS;
    float minTau;
    double start;
    double elapsed;
    double realSecPerSimSec;
} SIM;

typedef struct DIMENSIONS {
    int nRows;
    int nCols;
    int nFilt;
} DIM;

//extern SIMULATION SIM;

/*typedef enum {
    Off,
    //Uniform,
    Gaussian
} NOISE;*/

typedef enum {
    MinD,
    ConstD,
    UniformD,
    GaussD,
    SOMD
} DELAY;

typedef enum {
    //Zero,
    Constant, // Zero set using appropriate Dg modifier
    Uniform,
    Gaussian,
    SOM
} INITIALISATION;

typedef enum {
    None,
    MaintainLength,
    MaintainSum
} NORMALISATION;

/********************** Main Network Parmeters *************************/
// Update read_parameters and defaults.m when a new parameter is added
typedef struct {
    // Simulation
    float DT;
    bool initialised; // Internal
    int loops;
    bool pretrain;// = true;
    bool train;// = true;
    bool priorPhases;
    bool isolateEfE;
    bool trainPause;
    bool noise; // int
    float noiseScale;
    float SigmaE;
    float SigmaI;
    NORMALISATION normalise;
    int nRecordsPL;// = 0;
    int nRecords;
    int * vRecords;
    //float TotalTime;
    float MaxTime;
    float EpochTime;
    float TestTime;
    int EpochMS;
    int TestMS;
    int TotalMS;
    int RecordMS;
    //tstep TotalTS;
    int TSperMS;
    int spkBuffer;
    //int inpSpkBuff;
    bool printConnections;
    bool saveInputSpikes;
    bool probConnect;
    bool loadWeights;

    // Stimuli
    char * imgDir;
    char * imgList;
    bool randStimOrder;// = true;
    bool randTransOrder;
    bool randTransDirection;
    bool interleaveTrans;
    bool localRep;// = true;
    float current;// = 1.25e-9;
    float currentSpread;
    bool loadStimuli;
    bool stimGroups;
    int nBG; // # shared between groups
    int nWG; // # specific within each group
    int nGroups;
    int nStimuli;
    int nTransPS;
    bool newTestSet;
    int M; // Degree of training i.e. train with M of the NCK
    int K; // Number of stimuli presented simultaneously
    //int nTestGroups;
    int nTestStimuli;
    int nTestTransPS;
    float transP_Train;
    float transP_Test;
    int shift;
    int nFiringNeurons;
    float a; // Sparseness of stimuli
    bool useFilteredImages;
    bool gabor;
    int nScales;
    int * vScales;
    int nOrients;
    int * vOrients;
    int nPhases;
    int * vPhases;
    int sInputs;
    int nRows;
    int nCols;

    // Network
    int nLayers;// = 2;
    int nWLayers;
    bool inputInhib;
    int nExcit;// = 120;
    int * vExcit;
    int LvExcit;
    int nSynEfE;
    float * pCnxEfE;
    int LpEfE;
    int nSynElE;
    float * pCnxElE;
    int LpElE;
    int nSynIE;
    float * pCnxIE;
    int LpIE;
    int nInhib;// = 40;
    float rInhib;
    int * vInhib;
    int LvInhib;
    int nSynEI;
    float * pCnxEI;
    int LpEI;
    int nSynII;
    float * pCnxII;
    int LpII;
    INITIALISATION initEfE;
    float iEfE;
    bool axonDelay; //DELAY axonDelay;
    DELAY delayEfE;
    DELAY delayElE;
    DELAY delayEI;
    float d_const;
    float d_min;
    float d_max;
    float d_mean;
    float d_sd;
    float spatialScale;
    float condSpeed;
    float maxDelay;
    bool SOM;
    bool SOMinput;
    float SOMsigE;
    float SOMsigI;
    float SOMclip;
    //float SOMstrE;
    //float SOMstrI;
    bool trainEfE; // Internal var modified through isolateEfE
    bool trainElE;
    INITIALISATION initElE;
    float iElE;
    DIM * layDim;
    int * vSquare;

    // Cell bodies
    float capE;// = 2.0e-10;
    float capI;// = 10.0e-12;
    float gLeakE;// = 10.0e-9;
    float gLeakI;// = 5.0e-9;
    float VrestE;
    float VrestI;
    float VhyperE;
    float VhyperI;
    float ThreshE;
    float ThreshI;
    float VrevE;
    float VrevI;
    float refract;// = 0.002
    bool adaptation;
    float alphaCa;
    float tauCa;
    float gAHP;
    float VK;

    // Synapses (afferent axons)
    bool noSTDPdelay;
    float alphaC;// = 0.5;
    float tauC;// = 0.005;
    float alphaD;// = 0.5;
    float tauD;// = 0.009;
    float learnR;// = 0.1;
    float DgEfE; //modEf; // alter the strength of Excit f-f synapses
    //float tauEfE;// tauEE = 0.01;
    float DgElE;//Dg_ElE
    //float tauElE;
    float tauEE;
    float DgIE;//Dg_IE = 0.5;
    float tauIE;// = 0.001
    float DgEI;//Dg_EI = 0.5;
    float tauEI;// = 0.001;
    float DgII;//Dg_II = 0.5;
    float tauII;// = 0.001;
    float gMax;// = 48.0e-10;
} PARAMS;

//PARAMS * mp;
extern PARAMS * mp;
extern gsl_rng * mSeed;
extern gsl_rng ** states;

#endif


/*
Copyright (c) 2015, Patrick Weltevrede
All rights reserved.

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

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

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

3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

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

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <gsl/gsl_version.h>
#include <gsl/gsl_multifit_nlin.h>
#include <gsl/gsl_blas.h>
#include "psrsalsa.h"
typedef struct {
  size_t n;
  double *x;
  double *y;
  double *sigma;
  fitfunc_collection_type *fitfunction;
  size_t iter;
}levmar_internal_fitter_data_def;
static levmar_internal_fitter_data_def levmar_internal_fitter_data;
typedef struct {
  gsl_vector_view func_params;
  const gsl_multifit_fdfsolver_type *solver_type;
  gsl_multifit_function_fdf user_itteration_funcs;
  gsl_multifit_fdfsolver *solver;
  gsl_matrix *covar;
}levmar_internal_gsl_info;
typedef struct {
  double *covar;
  double *alpha;
  double *beta;
  double *alpha_tmp;
  double *beta_tmp;
  double *func_params_laststep;
  double *derivatives;
  double alambda;
  double chisq;
  long nrfitparams;
  levmar_internal_fitter_data_def *data;
}levmar_internal_psrsalsa_info;
void print_gsl_version_used(FILE *stream)
{
  int major, minor;
  major = GSL_VERSION_NUMBER/100.0;
  minor = GSL_VERSION_NUMBER-100*major;
  fprintf(stream, "%s (header) %d.%d (specified during compilation)", GSL_VERSION, major, minor);
}
double evaluate_fitfunc_collection(fitfunc_collection_type *function, double x, verbose_definition verbose)
{
  int i;
  double y;
  y = 0;
  for(i = 0; i < function->nrfuncs; i++) {
    if(function->func[i].type == FUNC_POLYNOMAL) {
      y += function->func[i].value[0]*pow(x, function->func[i].param[0]);
    }else {
      fflush(stdout);
      printerror(verbose.debug, "ERROR evaluate_fitfunction_collection: Unknown funtional type in specified function.");
      exit(0);
    }
  }
  return y;
}
double evaluate_fitfunc_collection_deriv_param(fitfunc_collection_type *function, int functionnr, int paramnr, double x, verbose_definition verbose)
{
  if(function->func[functionnr].type == FUNC_POLYNOMAL) {
    if(paramnr == 0) {
      return pow(x, function->func[functionnr].param[0]);
    }
  }else {
    fflush(stdout);
    printerror(verbose.debug, "ERROR evaluate_fitfunc_collection_deriv_param: Unknown funtional type in specified function.");
    exit(0);
  }
  fflush(stdout);
  printerror(verbose.debug, "ERROR evaluate_fitfunc_collection_deriv_param: Unknown paramer number specified for given funtional type.");
  exit(0);
}
int print_fitfunctions(fitfunc_collection_type *function, int novalue, int showerror, int index, verbose_definition verbose)
{
  int i, j, n;
  if(showerror)
    showerror = 1;
  for(n = 0; n <= showerror; n++) {
    if(n == 0)
      printf("y = ");
    j = 1;
    for(i = 0; i < function->nrfuncs; i++) {
      if(index == i || index < 0) {
 if(function->func[i].type == FUNC_POLYNOMAL) {
   if(novalue || showerror) {
     if(showerror && n == 1) {
       printf("    a%d = %13e +- %13e   (%lf +- %lf)\n", j, function->func[i].value[0], function->func[i].error[0], function->func[i].value[0], function->func[i].error[0]);
     }else {
       printf("a%d*x**%lf", j, function->func[i].param[0]);
     }
   }else {
     printf("%lf*x**%lf", function->func[i].value[0], function->func[i].param[0]);
   }
   j += 1;
 }else {
   fflush(stdout);
   printerror(verbose.debug, "ERROR print_fitfunctions: Unrecognized function type.");
   return 0;
 }
 if(i == function->nrfuncs - 1)
   printf("\n");
 else if(n == 0)
   printf(" + ");
      }
    }
  }
  return 1;
}
void countnrparameters_fitfunction(fitfunc_collection_type *function, int *nrfitparameters)
{
  int n, fnr, nrvalues;
  *nrfitparameters = 0;
  for(fnr = 0; fnr < function->nrfuncs; fnr++) {
    if(function->func[fnr].type == FUNC_POLYNOMAL) {
      nrvalues = 1;
    }else {
      fflush(stdout);
      printerror(0, "ERROR countnrparameters_fitfunction: Unknown funtional type.");
      exit(0);
    }
    for(n = 0; n < nrvalues; n++) {
      if(function->func[fnr].fit_flag[n]) {
 *nrfitparameters += 1;
      }
    }
  }
}
int set_fitted_parameters_fitfunc_collection(fitfunc_collection_type *function, double *fitparameters, verbose_definition verbose)
{
  int fnr, nrvalues, valnr, fitparamnr;
  fitparamnr = 0;
  for(fnr = 0; fnr < function->nrfuncs; fnr++) {
    if(function->func[fnr].type == FUNC_POLYNOMAL) {
      nrvalues = 1;
    }else {
      fflush(stdout);
      printerror(verbose.debug, "ERROR set_fitted_parameters_fitfunc_collection: Unknown funtional type.");
      return 0;
    }
    for(valnr = 0; valnr < nrvalues; valnr++) {
      if(function->func[fnr].fit_flag[valnr]) {
 function->func[fnr].value[valnr] = fitparameters[fitparamnr++];
      }
    }
  }
  return 1;
}
int levmar_itteration_calc_function_internal_gsl(const gsl_vector *fitparams, void *data, gsl_vector *f)
{
  int n;
  size_t npts = ((levmar_internal_fitter_data_def *)data)->n;
  double *xdata = ((levmar_internal_fitter_data_def *)data)->x;
  double *ydata = ((levmar_internal_fitter_data_def *)data)->y;
  double *sigma = ((levmar_internal_fitter_data_def *)data)->sigma;
  fitfunc_collection_type *fitfunction_internal = ((levmar_internal_fitter_data_def *)data)->fitfunction;
  double value;
  verbose_definition noverbose;
  cleanVerboseState(&noverbose);
  if(fitparams->stride != 1) {
    printerror(0, "ERROR levmar_itteration_calc_function_internal_gsl: It is assumed the gsl vectors have a stride of 1.");
    exit(0);
  }
  fitfunc_collection_type newfunction;
  memcpy(&newfunction, fitfunction_internal, sizeof(fitfunc_collection_type));
  if(set_fitted_parameters_fitfunc_collection(&newfunction, fitparams->data, noverbose) == 0) {
    printerror(0, "ERROR levmar_itteration_calc_function_internal_gsl: Copying new fit parameters failed.");
    exit(0);
  }
  for(n = 0; n < npts; n++) {
    value = evaluate_fitfunc_collection(&newfunction, xdata[n], noverbose);
    gsl_vector_set(f, n, (value - ydata[n])/sigma[n]);
  }
  return GSL_SUCCESS;
}
int levmar_itteration_calc_deriv_internal_gsl(const gsl_vector *fitparams, void *data, gsl_matrix *J)
{
  int fnr, j, n, nrvalues, paramnr;
  size_t npts = ((levmar_internal_fitter_data_def *)data)->n;
  double *xdata = ((levmar_internal_fitter_data_def *)data)->x;
  double *sigma = ((levmar_internal_fitter_data_def *)data)->sigma;
  fitfunc_collection_type *fitfunction_internal = ((levmar_internal_fitter_data_def *)data)->fitfunction;
  double deriv;
  verbose_definition noverbose;
  cleanVerboseState(&noverbose);
  if(fitparams->stride != 1) {
    printerror(0, "ERROR levmar_itteration_calc_function_internal_gsl: It is assumed the gsl vectors have a stride of 1.");
    exit(0);
  }
  fitfunc_collection_type newfunction;
  memcpy(&newfunction, fitfunction_internal, sizeof(fitfunc_collection_type));
  if(set_fitted_parameters_fitfunc_collection(&newfunction, fitparams->data, noverbose) == 0) {
    printerror(0, "ERROR levmar_itteration_calc_function_internal_gsl: Copying new fit parameters failed.");
    exit(0);
  }
  j = 0;
  for(fnr = 0; fnr < newfunction.nrfuncs; fnr++) {
    if(newfunction.func[fnr].type == FUNC_POLYNOMAL) {
      nrvalues = 1;
    }else {
      fflush(stdout);
      printerror(noverbose.debug, "ERROR levmar_itteration_calc_deriv_internal_gsl: Unknown funtional type.");
      return 0;
    }
    for(paramnr = 0; paramnr < nrvalues; paramnr++) {
      if(newfunction.func[fnr].fit_flag[paramnr]) {
 for(n = 0; n < npts; n++) {
   deriv = evaluate_fitfunc_collection_deriv_param(&newfunction, fnr, paramnr, xdata[n], noverbose);
   gsl_matrix_set(J, n, j, deriv/sigma[n]);
 }
 j++;
      }
    }
  }
  return GSL_SUCCESS;
}
int levmar_itteration_calc_func_and_deriv_internal_gsl(const gsl_vector *x, void *data, gsl_vector *f, gsl_matrix *J)
{
  levmar_itteration_calc_function_internal_gsl(x, data, f);
  levmar_itteration_calc_deriv_internal_gsl(x, data, J);
  return GSL_SUCCESS;
}
void levmar_itteration_calc_alpha_beta_chi2_internal_psrsalsa(double *params, void *info)
{
  long curDataPoint, nrDataPoints, curparam, curparam2, nrfitparams, fnr, nrvalues, paramnr;
  double *alpha, *beta, *derivatives, *x, *y, *sigma, chi2, ypred, tmp1, tmp2, tmp3, tmp4;
  verbose_definition noverbose;
  cleanVerboseState(&noverbose);
  nrDataPoints = ((levmar_internal_psrsalsa_info *)info)->data->n;
  x = ((levmar_internal_psrsalsa_info *)info)->data->x;
  y = ((levmar_internal_psrsalsa_info *)info)->data->y;
  sigma = ((levmar_internal_psrsalsa_info *)info)->data->sigma;
  alpha = ((levmar_internal_psrsalsa_info *)info)->alpha;
  beta = ((levmar_internal_psrsalsa_info *)info)->beta;
  derivatives = ((levmar_internal_psrsalsa_info *)info)->derivatives;
  nrfitparams = ((levmar_internal_psrsalsa_info *)info)->nrfitparams;
  fitfunc_collection_type newfunction;
  memcpy(&newfunction, levmar_internal_fitter_data.fitfunction, sizeof(fitfunc_collection_type));
  if(set_fitted_parameters_fitfunc_collection(&newfunction, params, noverbose) == 0) {
    printerror(0, "ERROR levmar_itteration_calc_func_and_deriv_internal_nr: Copying new fit parameters failed.");
    exit(0);
  }
  for(curparam = 0; curparam < nrfitparams; curparam++) {
    beta[curparam] = 0;
    for(curparam2 = curparam; curparam2 < nrfitparams; curparam2++) {
      alpha[curparam*nrfitparams+curparam2] = 0;
    }
  }
  chi2 = 0;
  for(curDataPoint = 0; curDataPoint < nrDataPoints; curDataPoint++) {
    ypred = evaluate_fitfunc_collection(&newfunction, x[curDataPoint], noverbose);
    curparam = 0;
    for(fnr = 0; fnr < newfunction.nrfuncs; fnr++) {
      if(newfunction.func[fnr].type == FUNC_POLYNOMAL) {
 nrvalues = 1;
      }else {
 fflush(stdout);
 printerror(noverbose.debug, "ERROR levmar_itteration_calc_alpha_beta_chi2_internal_psrsalsa: Unknown funtional type.");
 exit(0);
      }
      for(paramnr = 0; paramnr < nrvalues; paramnr++) {
 if(newfunction.func[fnr].fit_flag[paramnr]) {
   derivatives[curparam++] = evaluate_fitfunc_collection_deriv_param(&newfunction, fnr, paramnr, x[curDataPoint], noverbose);
 }
      }
    }
    tmp1 = 1.0/(sigma[curDataPoint]*sigma[curDataPoint]);
    tmp2 = (y[curDataPoint] - ypred);
    tmp3 = tmp1*tmp2;
    chi2 += tmp2*tmp3;
    for(curparam = 0; curparam < nrfitparams; curparam++) {
      tmp4 = derivatives[curparam]*tmp3;
      beta[curparam] += tmp4;
      for(curparam2 = curparam; curparam2 < nrfitparams; curparam2++) {
 alpha[curparam*nrfitparams+curparam2] += derivatives[curparam]*derivatives[curparam2]*tmp1;
      }
    }
  }
  for(curparam = 0; curparam < nrfitparams; curparam++) {
    for(curparam2 = curparam+1; curparam2 < nrfitparams; curparam2++) {
      alpha[curparam2*nrfitparams+curparam] = alpha[curparam*nrfitparams+curparam2];
    }
  }
  ((levmar_internal_psrsalsa_info *)info)->chisq = chi2;
}
int initialize_levmar_check_start_and_current_are_the_same(fitfunc_collection_type *function, verbose_definition verbose)
{
  int fnr, nrvalues, paramnr;
  for(fnr = 0; fnr < function->nrfuncs; fnr++) {
    if(function->func[fnr].type == FUNC_POLYNOMAL) {
      nrvalues = 1;
    }else {
      fflush(stdout);
      printerror(verbose.debug, "ERROR levmar_itteration_calc_func_and_deriv_internal_nr: Unknown funtional type.");
      return 0;
    }
    for(paramnr = 0; paramnr < nrvalues; paramnr++) {
      if(function->func[fnr].fit_flag[paramnr]) {
 if(function->func[fnr].value[paramnr] != function->func[fnr].start[paramnr]) {
   fflush(stdout);
   printerror(verbose.debug, "ERROR initialize_levmar_check_start_and_current_are_the_same: Start and current value appear to be different (%lf != %lf)", function->func[fnr].value[paramnr], function->func[fnr].start[paramnr]);
   return 0;
 }
      }
    }
  }
  return 1;
}
int levmar_initialize(fitfunc_collection_type *function, double **func_params, double **func_params_err, int *nrfitparameters, double *datax, double *datay, double *datasigma, long nrdatapoints, levmar_internal_gsl_info *gsl_params, levmar_internal_psrsalsa_info *psrsalsa_params,
#ifdef NRAVAIL
levmar_internal_nr_info *nr_params,
#endif
int algorithm, int mode, int showcovariance, verbose_definition verbose)
{
  int fnr, nrvalues, paramnr, n, n2;
  if(mode == 0) {
    countnrparameters_fitfunction(function, nrfitparameters);
    if(verbose.verbose) printf("  Initializing fitter with %d fit parameters\n", *nrfitparameters);
  }else if(mode == 1) {
    levmar_internal_fitter_data.n = nrdatapoints;
    levmar_internal_fitter_data.x = datax;
    levmar_internal_fitter_data.y = datay;
    levmar_internal_fitter_data.sigma = datasigma;
    levmar_internal_fitter_data.fitfunction = function;
    *func_params = malloc(*nrfitparameters*sizeof(double));
    *func_params_err = malloc(*nrfitparameters*sizeof(double));
    if(*func_params == NULL || *func_params_err == NULL) {
      fflush(stdout);
      printerror(verbose.debug, "ERROR levmar_initialize: Cannot allocate memory");
      return 0;
    }
    paramnr = 0;
    for(fnr = 0; fnr < function->nrfuncs; fnr++) {
      if(function->func[fnr].type == FUNC_POLYNOMAL) {
 nrvalues = 1;
      }else {
 fflush(stdout);
 printerror(verbose.debug, "ERROR levmar_initialize: Unknown funtional type.");
 return 0;
      }
      for(n = 0; n < nrvalues; n++) {
 if(function->func[fnr].fit_flag[n]) {
   (*func_params)[paramnr++] = function->func[fnr].value[n];
 }
 function->func[fnr].error[n] = sqrt(-1);
      }
    }
    if(algorithm == 1) {
      gsl_params->func_params = gsl_vector_view_array(*func_params, *nrfitparameters);
      gsl_params->covar = gsl_matrix_alloc(*nrfitparameters, *nrfitparameters);
      gsl_params->user_itteration_funcs.f = &levmar_itteration_calc_function_internal_gsl;
      gsl_params->user_itteration_funcs.df = &levmar_itteration_calc_deriv_internal_gsl;
      gsl_params->user_itteration_funcs.fdf = &levmar_itteration_calc_func_and_deriv_internal_gsl;
      gsl_params->user_itteration_funcs.n = nrdatapoints;
      gsl_params->user_itteration_funcs.p = *nrfitparameters;
      gsl_params->user_itteration_funcs.params = &levmar_internal_fitter_data;
      gsl_params->solver_type = gsl_multifit_fdfsolver_lmsder;
      gsl_params->solver = gsl_multifit_fdfsolver_alloc (gsl_params->solver_type, nrdatapoints, *nrfitparameters);
      gsl_multifit_fdfsolver_set(gsl_params->solver, &(gsl_params->user_itteration_funcs), &(gsl_params->func_params.vector));
    }else if(algorithm == 2) {
      psrsalsa_params->func_params_laststep = malloc(sizeof(double)*(*nrfitparameters));
      psrsalsa_params->beta = malloc(sizeof(double)*(*nrfitparameters));
      psrsalsa_params->derivatives = malloc(sizeof(double)*(*nrfitparameters));
      psrsalsa_params->covar = malloc(sizeof(double)*(*nrfitparameters)*(*nrfitparameters));
      psrsalsa_params->alpha = malloc(sizeof(double)*(*nrfitparameters)*(*nrfitparameters));
      psrsalsa_params->alpha_tmp = malloc(sizeof(double)*(*nrfitparameters)*(*nrfitparameters));
      psrsalsa_params->beta_tmp = malloc(sizeof(double)*(*nrfitparameters));
      if(psrsalsa_params->func_params_laststep == NULL || psrsalsa_params->beta == NULL || psrsalsa_params->derivatives == NULL || psrsalsa_params->alpha == NULL || psrsalsa_params->covar == NULL || psrsalsa_params->beta_tmp == NULL || psrsalsa_params->alpha_tmp == NULL) {
 printerror(verbose.debug, "ERROR levmar_initialize: Memory allocation error");
 return 0;
      }
      psrsalsa_params->nrfitparams = *nrfitparameters;
      psrsalsa_params->data = &levmar_internal_fitter_data;
      psrsalsa_params->alambda = 0.001;
      levmar_itteration_calc_alpha_beta_chi2_internal_psrsalsa(*func_params, psrsalsa_params);
    }else if(algorithm == 3) {
#ifdef NRAVAIL
      nr_params->ia = malloc(sizeof(int)*(*nrfitparameters));
      nr_params->func_params_laststep = malloc(sizeof(double)*(*nrfitparameters));
      if(nr_params->ia == NULL || nr_params->func_params_laststep == NULL) {
 printerror(verbose.debug, "ERROR levmar_initialize: Memory allocation error");
 return 0;
      }
      for(n = 0; n < *nrfitparameters; n++)
 nr_params->ia[n] = 1;
      double **matrix_nr_d(long nrl, long nrh, long ncl, long nch);
      nr_params->covar = matrix_nr_d(1, *nrfitparameters, 1, *nrfitparameters);
      nr_params->alpha = matrix_nr_d(1, *nrfitparameters, 1, *nrfitparameters);
      void mrqmin_nr_d(double x[], double y[], double sig[], int ndata, double a[], int ia[],
     int ma, double **covar, double **alpha, double *chisq,
     void (*funcs)(double, double [], double *, double [], int), double *alamda);
      nr_params->alambda = -1;
      mrqmin_nr_d(levmar_internal_fitter_data.x-1, levmar_internal_fitter_data.y-1, levmar_internal_fitter_data.sigma-1, nrdatapoints, (*func_params)-1, nr_params->ia-1, *nrfitparameters, nr_params->covar, nr_params->alpha, &nr_params->chisq, levmar_itteration_calc_func_and_deriv_internal_nr, &(nr_params->alambda));
#else
      printerror(verbose.debug, "ERROR levmar_initialize: Code is not compiled with NR support");
      return 0;
#endif
    }
  }else if(mode == 2) {
    paramnr = 0;
    for(fnr = 0; fnr < function->nrfuncs; fnr++) {
      if(function->func[fnr].type == FUNC_POLYNOMAL) {
 nrvalues = 1;
      }else {
 fflush(stdout);
 printerror(0, "ERROR levmar_initialize: Unknown funtional type.");
 return 0;
      }
      for(n = 0; n < nrvalues; n++) {
 if(function->func[fnr].fit_flag[n]) {
   function->func[fnr].value[n] = (*func_params)[paramnr];
   function->func[fnr].error[n] = (*func_params_err)[paramnr];
   paramnr++;
 }
      }
    }
    if(algorithm == 1) {
      if(showcovariance) {
 printf("Covariance matrix: \n");
 for(n = 0; n < *nrfitparameters; n++) {
   for(n2 = 0; n2 < *nrfitparameters; n2++) {
     printf("%lf ", gsl_matrix_get(gsl_params->covar,n,n2));
   }
   printf("\n");
 }
      }
      gsl_multifit_fdfsolver_free(gsl_params->solver);
      gsl_matrix_free(gsl_params->covar);
    }else if(algorithm == 2) {
      if(showcovariance) {
 printf("Covariance matrix: \n");
 for(n = 0; n < *nrfitparameters; n++) {
   for(n2 = 0; n2 < *nrfitparameters; n2++) {
     printf("%lf ", psrsalsa_params->covar[n*(*nrfitparameters)+n2]);
   }
   printf("\n");
 }
      }
      free(psrsalsa_params->beta);
      free(psrsalsa_params->derivatives);
      free(psrsalsa_params->alpha);
      free(psrsalsa_params->covar);
      free(psrsalsa_params->func_params_laststep);
      free(psrsalsa_params->alpha_tmp);
      free(psrsalsa_params->beta_tmp);
    }else if(algorithm == 3) {
#ifdef NRAVAIL
      if(showcovariance) {
 printf("Covariance matrix: \n");
 for(n = 0; n < *nrfitparameters; n++) {
   for(n2 = 0; n2 < *nrfitparameters; n2++) {
     printf("%lf ", nr_params->covar[n+1][n2+1]);
   }
   printf("\n");
 }
      }
      void free_matrix_nr_d(double **m, long nrl, long nrh, long ncl, long nch);
      free_matrix_nr_d(nr_params->covar, 1, *nrfitparameters, 1, *nrfitparameters);
      free_matrix_nr_d(nr_params->alpha, 1, *nrfitparameters, 1, *nrfitparameters);
      free(nr_params->ia);
      free(nr_params->func_params_laststep);
#else
      printerror(verbose.debug, "ERROR levmar_initialize: Code is not compiled with NR support");
      return 0;
#endif
    }
    free(*func_params);
    free(*func_params_err);
  }
  return 1;
}
int fit_levmar_internal(int algorithm, int nrdatapoints, int nrfitparameters, double *func_params, double *func_params_err, levmar_internal_gsl_info *gsl_params, levmar_internal_psrsalsa_info *psrsalsa_params,
#ifdef NRAVAIL
   levmar_internal_nr_info *nr_params,
#endif
   double epsabs, double epsrel, int maxiter, verbose_definition verbose)
{
  int status, i, converged;
  long iter;
  double chisq_last_nr;
#ifdef NRAVAIL
  void mrqmin_nr_d(double x[], double y[], double sig[], int ndata, double a[], int ia[],
     int ma, double **covar, double **alpha, double *chisq,
     void (*funcs)(double, double [], double *, double [], int), double *alamda);
#else
  if(algorithm == 3) {
    printerror(verbose.debug, "ERROR fit_levmar_internal: Code is not compiled with NR support");
    return 0;
  }
#endif
  if(algorithm == 2) {
    memcpy(psrsalsa_params->func_params_laststep, func_params, sizeof(double)*nrfitparameters);
    chisq_last_nr = psrsalsa_params->chisq;
#ifdef NRAVAIL
  }else if(algorithm == 3) {
    memcpy(nr_params->func_params_laststep, func_params, sizeof(double)*nrfitparameters);
    chisq_last_nr = nr_params->chisq;
#endif
  }
  iter = 0;
  do {
    iter++;
    levmar_internal_fitter_data.iter += 1;
    if(algorithm == 1) {
      status = gsl_multifit_fdfsolver_iterate(gsl_params->solver);
      if(status == GSL_ETOLF || status == GSL_ETOLX || status == GSL_ETOLG) {
 int status2;
 status2 = gsl_multifit_test_delta(gsl_params->solver->dx, gsl_params->solver->x, epsabs, epsrel);
 if(status2 == GSL_SUCCESS) {
   status = 0;
 }else {
   if(verbose.debug == 0) {
     printwarning(verbose.debug, "Machine precision reached, but required precision tolerance conditions are not met.");
   }else {
     printwarning(verbose.debug, "Machine precision reached, but required precision tolerance conditions are not met:");
     for(i = 0; i < nrfitparameters; i++) {
       printwarning(verbose.debug, "parameter %d: value=%e stepsize=%e", i+1, gsl_vector_get(gsl_params->solver->x, i), gsl_vector_get(gsl_params->solver->dx, i));
     }
   }
 }
      }
      if(verbose.debug) {
 printf("fit_levmar_internal: itteration %ld - status = %s\n", iter, gsl_strerror(status));
      }
      if(status) {
 break;
      }else {
 status = gsl_multifit_test_delta(gsl_params->solver->dx, gsl_params->solver->x, epsabs, epsrel);
      }
    }else if(algorithm == 2) {
      for(i = 0; i < nrfitparameters*nrfitparameters; i++)
 psrsalsa_params->alpha_tmp[i] = psrsalsa_params->alpha[i];
      for(i = 0; i < nrfitparameters; i++)
 psrsalsa_params->beta_tmp[i] = psrsalsa_params->beta[i];
      for (i = 0; i < nrfitparameters; i++) {
 psrsalsa_params->alpha[i*nrfitparameters+i] *= 1.0+psrsalsa_params->alambda;
      }
      int linalg_solve_matrix_eq_gauss_jordan(double *matrixa, double *matrixb, int n, int m, verbose_definition verbose);
      if(linalg_solve_matrix_eq_gauss_jordan(psrsalsa_params->alpha, psrsalsa_params->beta, nrfitparameters, 1, verbose) != 0) {
 printerror(verbose.debug, "ERROR fit_levmar_internal: Solving matrix equation failed.");
 return 9;
      }
      for (i = 0; i < nrfitparameters; i++)
 func_params[i] += psrsalsa_params->beta[i];
      levmar_itteration_calc_alpha_beta_chi2_internal_psrsalsa(func_params, psrsalsa_params);
      status = GSL_SUCCESS;
      if(chisq_last_nr < psrsalsa_params->chisq) {
 status = GSL_ETOLF;
 memcpy(func_params, psrsalsa_params->func_params_laststep, sizeof(double)*nrfitparameters);
 for(i = 0; i < nrfitparameters*nrfitparameters; i++)
   psrsalsa_params->alpha[i] = psrsalsa_params->alpha_tmp[i];
 for(i = 0; i < nrfitparameters; i++)
   psrsalsa_params->beta[i] = psrsalsa_params->beta_tmp[i];
 psrsalsa_params->alambda *= 10.1;
      }
      converged = 1;
      for(i = 0; i < nrfitparameters; i++) {
 if(fabs(func_params[i] - psrsalsa_params->func_params_laststep[i]) >= 1.2e-16+epsabs+epsrel*fabs(psrsalsa_params->func_params_laststep[i])) {
   converged = 0;
   break;
 }
      }
      if(converged && status == GSL_ETOLF)
 status = GSL_SUCCESS;
      if(status == GSL_ETOLF) {
       printwarning(verbose.debug, "Machine precision reached, but required precision tolerance conditions are not met.");
      }else if(converged == 0) {
 status = GSL_CONTINUE;
      }
      if(chisq_last_nr > psrsalsa_params->chisq) {
 memcpy(psrsalsa_params->func_params_laststep, func_params, sizeof(double)*nrfitparameters);
 chisq_last_nr = psrsalsa_params->chisq;
 psrsalsa_params->alambda *= 0.1;
      }
      if(verbose.debug) {
 printf("fit_levmar_internal: itteration %ld - status = %s\n", iter, gsl_strerror(status));
      }
#ifdef NRAVAIL
    }else if(algorithm == 3) {
      mrqmin_nr_d(levmar_internal_fitter_data.x-1, levmar_internal_fitter_data.y-1, levmar_internal_fitter_data.sigma-1, nrdatapoints, func_params-1, nr_params->ia-1, nrfitparameters, nr_params->covar, nr_params->alpha, &(nr_params->chisq), levmar_itteration_calc_func_and_deriv_internal_nr, &(nr_params->alambda));
      status = GSL_SUCCESS;
      for(i = 0; i < nrfitparameters; i++) {
 if(fabs(func_params[i] - nr_params->func_params_laststep[i]) >= epsabs+epsrel*fabs(nr_params->func_params_laststep[i])) {
   status = GSL_CONTINUE;
   break;
 }
      }
      if(verbose.debug) {
 printf("fit_levmar_internal: itteration %ld - status = %s\n", iter, gsl_strerror(status));
      }
      if(status == GSL_CONTINUE) {
 if(chisq_last_nr <= nr_params->chisq) {
   status = GSL_ETOLF;
   printwarning(verbose.debug, "Machine precision reached, but required precision tolerance conditions are not met.");
 }else {
   memcpy(nr_params->func_params_laststep, func_params, sizeof(double)*nrfitparameters);
   chisq_last_nr = nr_params->chisq;
 }
      }
#endif
    }
  }while(status == GSL_CONTINUE && iter < maxiter);
  if(algorithm == 1) {
#if GSL_VERSION_NUMBER >= 200
    if(verbose.debug) {
      printf("fit_levmar_internal: Entering gsl >= 2.0 specific part of the code\n");
    }
    gsl_matrix *J = gsl_matrix_alloc(nrdatapoints, nrfitparameters);
    gsl_multifit_fdfsolver_jac(gsl_params->solver, J);
    gsl_multifit_covar(J, 0.0, gsl_params->covar);
#else
    if(verbose.debug) {
      printf("fit_levmar_internal: Entering gsl < 2.0 specific part of the code\n");
    }
    gsl_multifit_covar(gsl_params->solver->J, 0.0, gsl_params->covar);
#endif
    levmar_internal_fitter_data.fitfunction->chi2 = gsl_blas_dnrm2(gsl_params->solver->f);
    levmar_internal_fitter_data.fitfunction->chi2_red = (levmar_internal_fitter_data.fitfunction->chi2)/sqrt(nrdatapoints - nrfitparameters);
    levmar_internal_fitter_data.fitfunction->chi2_red *= levmar_internal_fitter_data.fitfunction->chi2_red;
    levmar_internal_fitter_data.fitfunction->chi2 *= levmar_internal_fitter_data.fitfunction->chi2;
  }else if(algorithm == 2) {
    int linalg_solve_matrix_eq_gauss_jordan(double *matrixa, double *matrixb, int n, int m, verbose_definition verbose);
    if(linalg_solve_matrix_eq_gauss_jordan(psrsalsa_params->alpha, psrsalsa_params->beta, nrfitparameters, 1, verbose) != 0) {
      printerror(verbose.debug, "ERROR fit_levmar_internal: Solving matrix equation failed.");
      return 9;
    }
    for(i = 0; i < nrfitparameters*nrfitparameters; i++)
      psrsalsa_params->covar[i] = psrsalsa_params->alpha[i];
    levmar_internal_fitter_data.fitfunction->chi2 = psrsalsa_params->chisq;
    levmar_internal_fitter_data.fitfunction->chi2_red = psrsalsa_params->chisq/(double)(nrdatapoints - nrfitparameters);
#ifdef NRAVAIL
  }else if(algorithm == 3) {
    nr_params->alambda = 0;
    mrqmin_nr_d(levmar_internal_fitter_data.x-1, levmar_internal_fitter_data.y-1, levmar_internal_fitter_data.sigma-1, nrdatapoints, func_params-1, nr_params->ia-1, nrfitparameters, nr_params->covar, nr_params->alpha, &nr_params->chisq, levmar_itteration_calc_func_and_deriv_internal_nr, &nr_params->alambda);
    levmar_internal_fitter_data.fitfunction->chi2 = nr_params->chisq;
    levmar_internal_fitter_data.fitfunction->chi2_red = nr_params->chisq/(double)(nrdatapoints - nrfitparameters);
#endif
  }
  if(verbose.debug) {
    printf("chisq/dof = %g after %ld iterations\n", levmar_internal_fitter_data.fitfunction->chi2_red, iter);
  }
  if(algorithm == 1) {
    for(i = 0; i < nrfitparameters; i++) {
      func_params[i] = gsl_vector_get(gsl_params->solver->x, i);
      func_params_err[i] = sqrt(gsl_matrix_get(gsl_params->covar,i,i));
    }
  }else if(algorithm == 2) {
    for(i = 0; i < nrfitparameters; i++) {
      func_params_err[i] = sqrt(psrsalsa_params->covar[i*nrfitparameters+i]);
    }
#ifdef NRAVAIL
  }else if(algorithm == 3) {
    for(i = 0; i < nrfitparameters; i++) {
      func_params_err[i] = sqrt(nr_params->covar[i+1][i+1]);
    }
#endif
  }
  if(levmar_internal_fitter_data.iter == maxiter)
    return 2;
  if(algorithm == 1) {
    if(status == 0)
      return 0;
    if(status == GSL_ETOLF || status == GSL_ETOLX || status == GSL_ETOLG) {
      return 3;
    }
    if(status == GSL_ENOPROG) {
      fflush(stdout);
      printerror(verbose.debug, "fit_levmar_internal: Cannot determine suitable trial step. Continue itterating might help");
      return 4;
    }
  }else if(algorithm == 2 || algorithm == 3) {
    return 0;
  }
  fflush(stdout);
  printerror(verbose.debug, "fit_levmar_internal: UNKNOWN RETURN CODE");
  return 10;
}
int fit_levmar(int algorithm, fitfunc_collection_type *function, double *data_x, double *data_y, double *data_sigma, long ndata, int oneatatime, int force_chi2_1, double epsabs, double epsrel, int maxiter, int *status, int showresults, int showcovariance, verbose_definition verbose)
{
  int loopnr, improved, i, j, k, fnr, valuenr, nrfitparameters_one, nrfitparameters, free_data_sigma, nrvalues;
  double old_chi2, sig_scale, *func_params, *func_params_err;
  verbose_definition noverbose;
  levmar_internal_gsl_info gsl_params;
  levmar_internal_psrsalsa_info psrsalsa_params;
#ifndef NRAVAIL
  if(algorithm == 3) {
    printerror(verbose.debug, "ERROR fit_levmar: Code is not compiled with NR support");
    return 0;
  }
#else
  levmar_internal_nr_info nr_params;
#endif
  cleanVerboseState(&noverbose);
  noverbose.nocounters = 1;
  if(verbose.verbose) {
    printf("Levenberg-Marquardt algorithm:\n");
    if(verbose.debug) {
      printf("  Fit function: ");
      print_fitfunctions(function, 1, 0, -1, verbose);
    }
  }
  if(initialize_levmar_check_start_and_current_are_the_same(function, verbose) == 0) {
    fflush(stdout);
    printerror(verbose.debug, "ERROR fit_levmar: Start and current value appear to be different");
    return 1;
  }
  free_data_sigma = 0;
  if(data_sigma == NULL) {
    data_sigma = malloc(ndata*sizeof(double));
    for(i = 0; i < ndata; i++)
      data_sigma[i] = 1;
    free_data_sigma = 1;
    force_chi2_1 = 1;
  }
  if(levmar_initialize(function, &func_params, &func_params_err, &nrfitparameters, data_x, data_y, data_sigma, ndata, &gsl_params, &psrsalsa_params,
#ifdef NRAVAIL
         &nr_params,
#endif
         algorithm, 0, showcovariance, verbose) == 0) {
    return 1;
  }
  levmar_internal_fitter_data.iter = 0;
  if(oneatatime == 0) {
    if(levmar_initialize(function, &func_params, &func_params_err, &nrfitparameters, data_x, data_y, data_sigma, ndata, &gsl_params, &psrsalsa_params,
#ifdef NRAVAIL
    &nr_params,
#endif
    algorithm, 1, showcovariance, verbose) == 0) {
      return 1;
    }
    *status = fit_levmar_internal(algorithm, ndata, nrfitparameters, func_params, func_params_err, &gsl_params, &psrsalsa_params,
#ifdef NRAVAIL
      &nr_params,
#endif
      epsabs, epsrel, maxiter, verbose);
  }else {
    fitfunc_collection_type *original_function;
    original_function = malloc(sizeof(fitfunc_collection_type));
    if(original_function == NULL) {
      printerror(verbose.debug, "ERROR fit_levmar: Memory allocation error.");
      return 1;
    }
    memcpy(original_function, function, sizeof(fitfunc_collection_type));
    old_chi2 = -1;
    loopnr = 0;
    do {
      improved = 0;
      i = 0;
      for(fnr = 0; fnr < function->nrfuncs; fnr++) {
 if(function->func[fnr].type == FUNC_POLYNOMAL) {
   nrvalues = 1;
 }else {
   fflush(stdout);
   printerror(verbose.debug, "ERROR fit_levmar: Unknown funtional type.");
   return 1;
 }
 for(valuenr = 0; valuenr < nrvalues; valuenr++) {
   if(original_function->func[fnr].fit_flag[valuenr]) {
     for(j = 0; j < function->nrfuncs; j++) {
       for(k = 0; k < MaxNrFitParameters; k++)
  function->func[j].fit_flag[k] = 0;
     }
     function->func[fnr].fit_flag[valuenr] = 1;
     if(verbose.debug) {
       printf("  Loop %d: chi2 = %f", loopnr, old_chi2);
       printf("\n");
     }
     if(levmar_initialize(function, &func_params, &func_params_err, &nrfitparameters_one, data_x, data_y, data_sigma, ndata, &gsl_params, &psrsalsa_params,
#ifdef NRAVAIL
     &nr_params,
#endif
     algorithm, 0, showcovariance, noverbose) == 0) {
       free(original_function);
       return 1;
     }
     if(levmar_initialize(function, &func_params, &func_params_err, &nrfitparameters_one, data_x, data_y, data_sigma, ndata, &gsl_params, &psrsalsa_params,
#ifdef NRAVAIL
     &nr_params,
#endif
     algorithm, 1, showcovariance, verbose) == 0) {
       free(original_function);
       return 1;
     }
     *status = fit_levmar_internal(algorithm, ndata, nrfitparameters_one, func_params, func_params_err, &gsl_params, &psrsalsa_params,
#ifdef NRAVAIL
       &nr_params,
#endif
       epsabs, epsrel, maxiter, verbose);
     if(levmar_initialize(function, &func_params, &func_params_err, &nrfitparameters_one, data_x, data_y, data_sigma, ndata, &gsl_params, &psrsalsa_params,
#ifdef NRAVAIL
     &nr_params,
#endif
     algorithm, 2, showcovariance, verbose) == 0) {
       free(original_function);
       return 1;
     }
     if(function->chi2 < old_chi2 || old_chi2 < 0) {
       old_chi2 = function->chi2;
       improved = 1;
     }
     loopnr++;
     i++;
   }
 }
      }
    }while(improved);
    for(j = 0; j < function->nrfuncs; j++) {
      for(k = 0; k < MaxNrFitParameters; k++)
 function->func[j].fit_flag[k] = original_function->func[j].fit_flag[k];
    }
    if(levmar_initialize(function, &func_params, &func_params_err, &nrfitparameters, data_x, data_y, data_sigma, ndata, &gsl_params, &psrsalsa_params,
#ifdef NRAVAIL
    &nr_params,
#endif
    algorithm, 0, showcovariance, verbose) == 0) {
      free(original_function);
      return 1;
    }
    if(levmar_initialize(function, &func_params, &func_params_err, &nrfitparameters, data_x, data_y, data_sigma, ndata, &gsl_params, &psrsalsa_params,
#ifdef NRAVAIL
    &nr_params,
#endif
    algorithm, 1, showcovariance, verbose) == 0) {
      free(original_function);
      return 1;
    }
    *status = fit_levmar_internal(algorithm, ndata, nrfitparameters, func_params, func_params_err, &gsl_params, &psrsalsa_params,
#ifdef NRAVAIL
      &nr_params,
#endif
      epsabs, epsrel, maxiter, verbose);
    free(original_function);
  }
  if(verbose.verbose) {
    printf("  After %ld iterations found chisq/dof = %g\n", levmar_internal_fitter_data.iter, levmar_internal_fitter_data.fitfunction->chi2_red);
  }
  if(levmar_initialize(function, &func_params, &func_params_err, &nrfitparameters, data_x, data_y, data_sigma, ndata, &gsl_params, &psrsalsa_params,
#ifdef NRAVAIL
         &nr_params,
#endif
         algorithm, 2, showcovariance, verbose) == 0) {
    return 1;
  }
  if(force_chi2_1) {
    sig_scale = sqrt(function->chi2_red);
    for(i = 0; i < ndata; i++) {
      data_sigma[i] *= sig_scale;
    }
    if(levmar_initialize(function, &func_params, &func_params_err, &nrfitparameters, data_x, data_y, data_sigma, ndata, &gsl_params, &psrsalsa_params,
#ifdef NRAVAIL
    &nr_params,
#endif
    algorithm, 0, showcovariance, verbose) == 0) {
      return 1;
    }
    if(levmar_initialize(function, &func_params, &func_params_err, &nrfitparameters, data_x, data_y, data_sigma, ndata, &gsl_params, &psrsalsa_params,
#ifdef NRAVAIL
    &nr_params,
#endif
    algorithm, 1, showcovariance, verbose) == 0) {
      return 1;
    }
    *status = fit_levmar_internal(algorithm, ndata, nrfitparameters, func_params, func_params_err, &gsl_params, &psrsalsa_params,
#ifdef NRAVAIL
      &nr_params,
#endif
      epsabs, epsrel, maxiter, verbose);
    if(levmar_initialize(function, &func_params, &func_params_err, &nrfitparameters, data_x, data_y, data_sigma, ndata, &gsl_params, &psrsalsa_params,
#ifdef NRAVAIL
    &nr_params,
#endif
    algorithm, 2, showcovariance, verbose) == 0) {
      return 1;
    }
  }
  if(verbose.verbose || showresults) {
    printf("  Fit function: ");
    print_fitfunctions(function, 0, 1, -1, verbose);
    printf("  ");
    print_fitfunctions(function, 0, 0, -1, verbose);
    printf("\n  chi2 = %f\n  reduced chi2 = %f (%ld points - %d params)\n", function->chi2, function->chi2_red, ndata, nrfitparameters);
  }
  if(free_data_sigma) {
    free(data_sigma);
    data_sigma = NULL;
  }
  return *status;
}