/* -*- linux-c -*- */
/* sigma_binsingle.c

   Copyright (C) 2002-2004 John M. Fregeau

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

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

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

#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <getopt.h>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_rng.h>
#include "fewbody.h"
#include "sigma_binsingle.h"

/* print the usage */
void print_usage(FILE *stream)
{
	fprintf(stream, "USAGE:\n");
	fprintf(stream, "  sigma_binsingle [options...]\n");
	fprintf(stream, "\n");
	fprintf(stream, "OPTIONS:\n");
	fprintf(stream, "  -m --m0 <m0/MSUN>             : set mass of single star [%.6g]\n", FB_M0/FB_CONST_MSUN);
	fprintf(stream, "  -n --m10 <m10/MSUN>           : set mass of star 0 of binary [%.6g]\n", FB_M10/FB_CONST_MSUN);
	fprintf(stream, "  -o --m11 <m11/MSUN>           : set mass of star 1 of binary [%.6g]\n", FB_M11/FB_CONST_MSUN);
	fprintf(stream, "  -r --r0 <r0/RSUN>             : set radius of single star [%.6g]\n", FB_R0/FB_CONST_RSUN);
	fprintf(stream, "  -g --r10 <r10/RSUN>           : set radius of star 0 of binary [%.6g]\n", FB_R10/FB_CONST_RSUN);
	fprintf(stream, "  -i --r11 <r11/RSUN>           : set radius of star 1 of binary [%.6g]\n", FB_R11/FB_CONST_RSUN);
	fprintf(stream, "  -a --a1 <a1/AU>               : set semimajor axis of binary [%.6g]\n", FB_A1/FB_CONST_AU);
	fprintf(stream, "  -e --e1 <e1>                  : set eccentricity of binary 0 [%.6g]\n", FB_E1);
	fprintf(stream, "  -v --vinf <vinf/v_crit>       : set velocity at infinity [%.6g]\n", FB_VINF);
	fprintf(stream, "  -P --precision <dsigma/sigma> : set fractional precision on cross\n");
	fprintf(stream, "                                  section [%.6g]\n", FB_PRECISION);
	fprintf(stream, "  -t --tstop <tstop/t_dyn>      : set stopping time [%.6g]\n", FB_TSTOP);
	fprintf(stream, "  -D --dt <dt/t_dyn>            : set approximate output dt [%.6g]\n", FB_DT);
	fprintf(stream, "  -c --tcpustop <tcpustop/sec>  : set cpu stopping time [%.6g]\n", FB_TCPUSTOP);
	fprintf(stream, "  -A --absacc <absacc>          : set integrator's absolute accuracy [%.6g]\n", FB_ABSACC);
	fprintf(stream, "  -R --relacc <relacc>          : set integrator's relative accuracy [%.6g]\n", FB_RELACC);
	fprintf(stream, "  -N --ncount <ncount>          : set number of integration steps between calls\n");
	fprintf(stream, "                                  to fb_classify() [%d]\n", FB_NCOUNT);
	fprintf(stream, "  -z --tidaltol <tidaltol>      : set tidal tolerance [%.6g]\n", FB_TIDALTOL);
	fprintf(stream, "  -x --fexp <f_exp>             : set expansion factor of merger product [%.6g]\n", FB_FEXP);
	fprintf(stream, "  -k --ks                       : turn K-S regularization on or off [%d]\n", FB_KS);
	fprintf(stream, "  -s --seed                     : set random seed [%ld]\n", FB_SEED);
	fprintf(stream, "  -d --debug                    : turn on debugging\n");
	fprintf(stream, "  -V --version                  : print version info\n");
	fprintf(stream, "  -h --help                     : display this help text\n");
}

/* calculate the units used */
int calc_units(fb_obj_t *obj[2], fb_units_t *units)
{
	units->v = sqrt(FB_CONST_G*(obj[0]->m + obj[1]->m)/(obj[0]->m * obj[1]->m) * \
			(obj[1]->obj[0]->m * obj[1]->obj[1]->m / obj[1]->a));
	units->l = obj[1]->a;
	units->t = units->l / units->v;
	units->m = units->l * fb_sqr(units->v) / FB_CONST_G;
	units->E = units->m * fb_sqr(units->v);

	return(0);
}

/* the main attraction */
int main(int argc, char *argv[])
{
	int i, j, k, res, err, sid, bid, precisionmet=0;
	long l, nb;
	unsigned long int seed;
	double m0, m10, m11, r0, r10, r11, a1, e1, precision;
	double rtid, vinf, rperi, b, b0, bmin, bmax, db, bxlast, t, vc;
	fb_sigma_t sigmacurr[11], sigmaprev[11];
	char outcome[11][FB_MAX_STRING_LENGTH];
	/* char string1[FB_MAX_STRING_LENGTH], string2[FB_MAX_STRING_LENGTH]; */
	fb_hier_t hier;
	fb_input_t input;
	fb_ret_t retval;
	fb_units_t units;
	gsl_rng *rng;
	const gsl_rng_type *rng_type=gsl_rng_mt19937;
	const char *short_opts = "m:n:o:r:g:i:a:e:v:P:t:D:c:A:R:N:z:x:k:s:dVh";
	const struct option long_opts[] = {
		{"m0", required_argument, NULL, 'm'},
		{"m10", required_argument, NULL, 'n'},
		{"m11", required_argument, NULL, 'o'},
		{"r0", required_argument, NULL, 'r'},
		{"r10", required_argument, NULL, 'g'},
		{"r11", required_argument, NULL, 'i'},
		{"a1", required_argument, NULL, 'a'},
		{"e1", required_argument, NULL, 'e'},
		{"vinf", required_argument, NULL, 'v'},
		{"precision", required_argument, NULL, 'P'},
		{"tstop", required_argument, NULL, 't'},
		{"dt", required_argument, NULL, 'D'},
		{"tcpustop", required_argument, NULL, 'c'},
		{"absacc", required_argument, NULL, 'A'},
		{"relacc", required_argument, NULL, 'R'},
		{"ncount", required_argument, NULL, 'N'},
		{"tidaltol", required_argument, NULL, 'z'},
		{"fexp", required_argument, NULL, 'x'},
		{"ks", required_argument, NULL, 'k'},
		{"seed", required_argument, NULL, 's'},
		{"debug", no_argument, NULL, 'd'},
		{"version", no_argument, NULL, 'V'},
		{"help", no_argument, NULL, 'h'},
		{NULL, 0, NULL, 0}
	};

	/* set parameters to default values */
	m0 = FB_M0;
	m10 = FB_M10;
	m11 = FB_M11;
	r0 = FB_R0;
	r10 = FB_R10;
	r11 = FB_R11;
	a1 = FB_A1;
	e1 = FB_E1;
	vinf = FB_VINF;
	precision = 1.0e-2;
	input.ks = FB_KS;
	input.tstop = FB_TSTOP;
	input.Dflag = 0;
	input.dt = FB_DT;
	input.tcpustop = FB_TCPUSTOP;
	input.absacc = FB_ABSACC;
	input.relacc = FB_RELACC;
	input.ncount = FB_NCOUNT;
	input.tidaltol = FB_TIDALTOL;
	input.fexp = FB_FEXP;
	seed = FB_SEED;
	fb_debug = FB_DEBUG;

	while ((i = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) {
		switch (i) {
		case 'm':
			m0 = atof(optarg) * FB_CONST_MSUN;
			break;
		case 'n':
			m10 = atof(optarg) * FB_CONST_MSUN;
			break;
		case 'o':
			m11 = atof(optarg) * FB_CONST_MSUN;
			break;
		case 'r':
			r0 = atof(optarg) * FB_CONST_RSUN;
			break;
		case 'g':
			r10 = atof(optarg) * FB_CONST_RSUN;
			break;
		case 'i':
			r11 = atof(optarg) * FB_CONST_RSUN;
			break;
		case 'a':
			a1 = atof(optarg) * FB_CONST_AU;
			break;
		case 'e':
			e1 = atof(optarg);
			if (e1 >= 1.0) {
				fprintf(stderr, "e0 must be less than 1\n");
				return(1);
			}
			break;
		case 'v':
			vinf = atof(optarg);
			if (vinf < 0.0) {
				fprintf(stderr, "vinf must be non-negative\n");
				return(1);
			}
			break;
		case 'P':
			precision = atof(optarg);
			if (precision <= 0.0) {
				fprintf(stderr, "precision must be > 0\n");
				return(1);
			} else if (precision > 1.0) {
				fprintf(stderr, "precision must be < 1\n");
				return(1);
			}
			break;
		case 't':
			input.tstop = atof(optarg);
			break;
		case 'D':
			input.Dflag = 1;
			input.dt = atof(optarg);
			break;
		case 'c':
			input.tcpustop = atof(optarg);
			break;
		case 'A':
			input.absacc = atof(optarg);
			break;
		case 'R':
			input.relacc = atof(optarg);
			break;
		case 'N':
			input.ncount = atoi(optarg);
			break;
		case 'z':
			input.tidaltol = atof(optarg);
			break;
		case 'x':
			input.fexp = atof(optarg);
			break;
		case 'k':
			input.ks = atoi(optarg);
			break;
		case 's':
			seed = atol(optarg);
			break;
		case 'd':
			fb_debug = 1;
			break;
		case 'V':
			fb_print_version(stdout);
			return(0);
		case 'h':
			fb_print_version(stdout);
			fprintf(stdout, "\n");
			print_usage(stdout);
			return(0);
		default:
			break;
		}
	}

	/* check to make sure there was nothing crazy on the command line */
	if (optind < argc) {
		print_usage(stdout);
		return(1);
	}

	/* initialize a few things for integrator */
	hier.nstarinit = 3;
	fb_malloc_hier(&hier);

	/* initialize GSL rng */
	gsl_rng_env_setup();
	rng = gsl_rng_alloc(rng_type);
	gsl_rng_set(rng, seed);

	/* zero everything out before summing */
	for (i=0; i<11; i++) {
		sigmacurr[i].sigma_r = 0.0;
		sigmacurr[i].dsigma2_r_minus = 0.0;
		sigmacurr[i].dsigma2_r_plus = 0.0;
		sigmacurr[i].sigma_nr = 0.0;
		sigmacurr[i].dsigma2_nr_minus = 0.0;
		sigmacurr[i].dsigma2_nr_plus = 0.0;
		sigmaprev[i].sigma_r = 0.0;
		sigmaprev[i].dsigma2_r_minus = 0.0;
		sigmaprev[i].dsigma2_r_plus = 0.0;
		sigmaprev[i].sigma_nr = 0.0;
		sigmaprev[i].dsigma2_nr_minus = 0.0;
		sigmaprev[i].dsigma2_nr_plus = 0.0;
	}
	/* loop through and calcluate cross sections */
	nb = (long) (1.0/precision);
	while (!precisionmet) {
		/* select b0 that gives r_p=2a */
		vc = sqrt(FB_CONST_G * (m0+m10+m11) / (m0*(m10+m11)) * (m10*m11/a1));
		rperi = 2.0 * a1;
		/* b0 is in code units here (vinf is in units of vc) */
		b0 = sqrt(fb_sqr(rperi) + 2.0 * FB_CONST_G * (m0+m10+m11) * rperi / fb_sqr(vinf*vc)) / a1;
		db = b0/((double) nb);
		bxlast = GSL_POSINF;

		l = 0;
		bmin = ((double) l) * db;
		bmax = bmin + db;
		while (bmax <= b0 || bmax <= 2.0*bxlast) {
			bmin = ((double) l) * db;
			bmax = bmin + db;
			/* choose b uniformly in area from bmin to bmax */
			b = sqrt(gsl_rng_uniform(rng) * (fb_sqr(bmax)-fb_sqr(bmin)) + fb_sqr(bmin));

			/* do scattering experiment */
			/* initialize a few things for integrator */
			t = 0.0;
			hier.nstar = 3;
			fb_init_hier(&hier);

			/* create binary */
			hier.hier[hier.hi[2]+0].obj[0] = &(hier.hier[hier.hi[1]+1]);
			hier.hier[hier.hi[2]+0].obj[1] = &(hier.hier[hier.hi[1]+2]);
			hier.hier[hier.hi[2]+0].t = t;

			/* give the objects some properties */
			for (j=0; j<hier.nstar; j++) {
				hier.hier[hier.hi[1]+j].ncoll = 1;
				hier.hier[hier.hi[1]+j].id[0] = j;
				snprintf(hier.hier[hier.hi[1]+j].idstring, FB_MAX_STRING_LENGTH, "%d", j);
				hier.hier[hier.hi[1]+j].n = 1;
				hier.hier[hier.hi[1]+j].obj[0] = NULL;
				hier.hier[hier.hi[1]+j].obj[1] = NULL;
				hier.hier[hier.hi[1]+j].Eint = 0.0;
				hier.hier[hier.hi[1]+j].Lint[0] = 0.0;
				hier.hier[hier.hi[1]+j].Lint[1] = 0.0;
				hier.hier[hier.hi[1]+j].Lint[2] = 0.0;
			}

			hier.hier[hier.hi[1]+0].R = r0;
			hier.hier[hier.hi[1]+1].R = r10;
			hier.hier[hier.hi[1]+2].R = r11;

			hier.hier[hier.hi[1]+0].m = m0;
			hier.hier[hier.hi[1]+1].m = m10;
			hier.hier[hier.hi[1]+2].m = m11;

			hier.hier[hier.hi[2]+0].m = m10 + m11;

			hier.hier[hier.hi[2]+0].a = a1;
			hier.hier[hier.hi[2]+0].e = e1;

			hier.obj[0] = &(hier.hier[hier.hi[1]+0]);
			hier.obj[1] = &(hier.hier[hier.hi[2]+0]);
			hier.obj[2] = NULL;

			/* get the units and normalize */
			calc_units(hier.obj, &units);
			fb_normalize(&hier, units);

			/* move hierarchies analytically in from infinity along hyperbolic orbit */
			rtid = pow(2.0*(hier.obj[0]->m + hier.obj[1]->m) / (hier.obj[1]->m * input.tidaltol), 1.0/3.0) *
				hier.obj[1]->a * (1.0 + hier.obj[1]->e);
			fb_init_scattering(hier.obj[0], hier.obj[1], vinf, b, rtid);

			/* trickle down the binary properties, then back up */
			fb_randorient(&(hier.hier[hier.hi[2]+0]), rng);
			fb_downsync(&(hier.hier[hier.hi[2]+0]), t);
			/* fb_upsync(&(hier.hier[hier.hi[2]+0]), t); */

			/* call fewbody! */
			retval = fewbody(input, &hier, &t);

			k = -1;
			res = 0;
			err = 0;

			/* analyze outcome */
			if (retval.retval == 1 &&
			    FB_MIN(fabs(retval.DeltaEfrac), fabs(retval.DeltaE)) <= 1.0e-4 &&
			    FB_MIN(fabs(retval.DeltaLfrac), fabs(retval.DeltaL)) <= 1.0e-4) {
				/* check for resonance */
				if (retval.Nosc >= 1) {
					res = 1;
				} else {
					res = 0;
				}
				/* classify outcome */
				if (hier.nstar == 3) {
					if (hier.nobj == 1) {
						/* triples physically forbidden */
						err = 1;
					} else if (hier.nobj == 2) {
						if (fb_n_hier(hier.obj[0])==2) {
							bid = 0;
							sid = 1;
						} else {
							bid = 1;
							sid = 0;
						}
						if (hier.obj[sid]->id[0] == 0) {
							k = 0;
						} else if (hier.obj[sid]->id[0] == 1) {
							k = 1;
						} else {
							k = 2;
						}
					} else { /* hier.nobj == 3 */
						k = 3;
					}
                                } else if (hier.nstar == 2) {
					if (hier.nobj == 1) {
						if ((hier.obj[0]->obj[0]->ncoll==1 && hier.obj[0]->obj[0]->id[0]==0) ||
						    (hier.obj[0]->obj[1]->ncoll==1 && hier.obj[0]->obj[1]->id[0]==0)) {
							k = 4;
						} else if ((hier.obj[0]->obj[0]->ncoll==1 && hier.obj[0]->obj[0]->id[0]==1) ||
							   (hier.obj[0]->obj[1]->ncoll==1 && hier.obj[0]->obj[1]->id[0]==1)) {
							k = 5;
						} else {
							k = 6;
						}
					} else { /* hier.nobj == 2 */
						if ((hier.obj[0]->ncoll==1 && hier.obj[0]->id[0]==0) ||
						    (hier.obj[1]->ncoll==1 && hier.obj[1]->id[0]==0)) {
							k = 7;
						} else if ((hier.obj[0]->ncoll==1 && hier.obj[0]->id[0]==1) ||
							   (hier.obj[1]->ncoll==1 && hier.obj[1]->id[0]==1)) {
							k = 8;
						} else {
							k = 9;
						}
					}
				} else if (hier.nstar == 1) {
					k = 10;
				} else {
					fprintf(stderr, "ERROR: hier.nstar!=1,2,3!\n");
					exit(1);
				}
                        } else { /* bad outcome */
				err = 1;
			}

			/* tally up cross sections */
			if (!err) {
				for (i=0; i<11; i++) {
					if (i==k) {
						if (res) {
							sigmacurr[i].sigma_r += FB_CONST_PI * (fb_sqr(bmax)-fb_sqr(bmin));
							sigmacurr[i].dsigma2_r_minus += fb_sqr(0.1587 * FB_CONST_PI * (fb_sqr(bmax)-fb_sqr(bmin)));
							sigmacurr[i].dsigma2_nr_plus += fb_sqr(0.8413/21.0 * FB_CONST_PI * (fb_sqr(bmax)-fb_sqr(bmin)));
						} else {
							sigmacurr[i].sigma_nr += FB_CONST_PI * (fb_sqr(bmax)-fb_sqr(bmin));
							sigmacurr[i].dsigma2_nr_minus += fb_sqr(0.1587 * FB_CONST_PI * (fb_sqr(bmax)-fb_sqr(bmin)));
							sigmacurr[i].dsigma2_r_plus += fb_sqr(0.8413/21.0 * FB_CONST_PI * (fb_sqr(bmax)-fb_sqr(bmin)));
						}
					} else {
						sigmacurr[i].dsigma2_r_plus += fb_sqr(0.8413/21.0 * FB_CONST_PI * (fb_sqr(bmax)-fb_sqr(bmin)));
						sigmacurr[i].dsigma2_nr_plus += fb_sqr(0.8413/21.0 * FB_CONST_PI * (fb_sqr(bmax)-fb_sqr(bmin)));
					}
				}
			} else {
				for (i=0; i<11; i++) {
					sigmacurr[i].dsigma2_r_plus += fb_sqr(0.8413/22.0 * FB_CONST_PI * (fb_sqr(bmax)-fb_sqr(bmin)));
					sigmacurr[i].dsigma2_nr_plus += fb_sqr(0.8413/22.0 * FB_CONST_PI * (fb_sqr(bmax)-fb_sqr(bmin)));
				}
			}

			/* if the outcome was not a weak flyby, then record last impact parameter */
			if (!err && (k!=0 || (k==0 && res))) {
				bxlast = bmax;
			}

			/*
			fprintf(stderr, "outcome:  retval=%d  %s  (%s)\n", retval.retval,
				fb_sprint_hier(hier, string1), fb_sprint_hier_hr(hier, string2));
			fprintf(stderr, "  DeltaL/L0=%.6g  DeltaL=%.6g\n", retval.DeltaLfrac, retval.DeltaL);
			fprintf(stderr, "  DeltaE/E0=%.6g  DeltaE=%.6g\n", retval.DeltaEfrac, retval.DeltaE);
			fprintf(stderr, "  l=%ld b0=%g bmin=%g bmax=%g bxlast=%g k=%d res=%d err=%d\n", l, b0, bmin, bmax, bxlast, k, res, err);
			*/

			l++;
		}

		/* add current and previous sigmas, and transfer current to previous */

		/* increase resolution geometrically */
		nb *= 2;

		precisionmet = 1;
	}

	snprintf(outcome[0], FB_MAX_STRING_LENGTH, "[1 2] 0 (preservation)");
	snprintf(outcome[1], FB_MAX_STRING_LENGTH, "[0 2] 1 (exchange_1)");
	snprintf(outcome[2], FB_MAX_STRING_LENGTH, "[0 1] 2 (exchange_2)");
	snprintf(outcome[3], FB_MAX_STRING_LENGTH, "0 1 2 (ionization)");
	snprintf(outcome[4], FB_MAX_STRING_LENGTH, "[1:2 0] (merger_binary_12)");
	snprintf(outcome[5], FB_MAX_STRING_LENGTH, "[0:2 1] (merger_binary_02)");
	snprintf(outcome[6], FB_MAX_STRING_LENGTH, "[0:1 2] (merger_binary_01)");
	snprintf(outcome[7], FB_MAX_STRING_LENGTH, "1:2 0 (merger_12)");
	snprintf(outcome[8], FB_MAX_STRING_LENGTH, "0:2 1 (merger_02)");
	snprintf(outcome[9], FB_MAX_STRING_LENGTH, "0:1 2 (merger_01)");
	snprintf(outcome[10], FB_MAX_STRING_LENGTH, "0:1:2 (triple_merger)");

	/* precision has now been met, so print out results */
	fprintf(stdout, "outcome  sigma_r  dsigma_r_minus  dsigma_r_plus  sigma_nr  dsigma_nr_minus  dsigma_nr_plus\n");
	for (i=0; i<11; i++) {
		fprintf(stdout, "%27s  %6.6g  %6.6g  %6.6g  %6.6g  %6.6g  %6.6g\n", outcome[i],
			sigmacurr[i].sigma_r/FB_CONST_PI,
			sqrt(sigmacurr[i].dsigma2_r_minus)/FB_CONST_PI,
			sqrt(sigmacurr[i].dsigma2_r_plus)/FB_CONST_PI,
			sigmacurr[i].sigma_nr/FB_CONST_PI,
			sqrt(sigmacurr[i].dsigma2_nr_minus)/FB_CONST_PI,
			sqrt(sigmacurr[i].dsigma2_nr_plus)/FB_CONST_PI);
	}

	/* free GSL stuff */
	gsl_rng_free(rng);

	/* free our own stuff */
	fb_free_hier(hier);

	/* done! */
	return(0);
}


#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_grid.h"
#include "core_proto_grid.h"

int32_t init(void)
{
  int32_t i, status;

  sum_photons = 0.0;

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

  set_units(); // Change units to code units where necessary.
  srand((unsigned) time(NULL));

  read_snap_list(); // Read the snapshots required.

  for(i = 0; i < Snaplistlen; i++)
  {
    ZZ[i] = 1 / AA[i] - 1; // Redshift array.
    Age[i] = time_to_present(ZZ[i]); // Age array.
  }

  if (fescPrescription == 0)
  {
    printf("\n\nUsing a constant escape fraction of %.4f\n", fesc);
  }
  else if (fescPrescription == 1)
  {
    fprintf(stderr, "\n\nDeprecated! Use a new one!\n");
    return EXIT_FAILURE;
  }
  else if (fescPrescription == 2)
  {
    printf("\n\nUsing an fesc prescription that scales with halo mass.\n\n");
    determine_fesc_constants();
  }
  else if (fescPrescription == 3)
  {
    printf("\n\nUsing an fesc prescription that scales with the fraction of ejected mass in the galaxy.\n");
    printf("\nThis takes the form A*fej + B with A = %.4e and B = %.4e\n", alpha, beta);
  }
  else if (fescPrescription == 4)
  {
    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);
  }
  else if (fescPrescription == 5)
  {
    printf("\n\nUsing Anne's functional form for an escape fraction that decreases for increasing halo mass.\n");
    XASSERT(fesc_low > fesc_high, "Input file contain fesc_low = %.2f and fesc_high = %.2f. For this prescription (fescPrescription == 5), we require fesc_low > fesc_high\n", fesc_low, fesc_high);
  }
  else if (fescPrescription == 6)
  {
    printf("\n\nUsing Anne's functional form for an escape fraction that increases for increasing halo mass.\n");
    XASSERT(fesc_low < fesc_high, "Input file contain fesc_low = %.2f and fesc_high = %.2f. For this prescription (fescPrescription == 6), we require fesc_low < fesc_high\n", fesc_low, fesc_high);
  }
  else if (fescPrescription == 7)
  {
    printf("\n\nUsing an fesc prescription that scales as a power law with the fraction of ejected gas.\n");
    determine_fesc_constants();
  }
  else
  {
    printf("\n\nOnly escape fraction prescriptions 0 to 6 (exlucding 1) are permitted.\n");
    return EXIT_FAILURE;
  }

  Grid = malloc(sizeof(struct GRID_STRUCT));
  if (Grid == NULL)
  {
    fprintf(stderr, "Could not allocate memory for the high level grid structure\n");
    return EXIT_FAILURE;
  }

  status = init_grid(Grid);
  if (status == EXIT_FAILURE)
  {
    return EXIT_FAILURE;
  }

  return EXIT_SUCCESS;

  QuasarEventsAbovePartCut = 0;
  QuasarEventsBelowPartCut = 0;

}

int32_t init_grid(struct GRID_STRUCT *grid)
{

#define ALLOCATE_GRID_MEMORY(name, length) \
{                                          \
  name = calloc(length, sizeof(*(name)));  \
  if (name == NULL)                        \
  {                                        \
    fprintf(stderr, "Out of memory allocating %ld bytes, could not allocate"#name".\n", sizeof(*(name)* length)); \
    return EXIT_FAILURE;                   \
  }                                        \
}

  int32_t i;
  uint64_t cell_idx;

  if (grid == NULL)
  {
    fprintf(stderr, "init_grid was called with a GRID_STRUCT pointer that has not been initialized\n");
    return EXIT_FAILURE;
  }

  grid->GridSize = GridSize;
  grid->NumCellsTotal = CUBE(GridSize);

  grid->NumGrids = NGrid;

  grid->GridProperties = malloc(sizeof(struct GRID_PROPERTIES_STRUCT) * grid->NumGrids);
  if (grid->GridProperties == NULL)
  {
    fprintf(stderr, "Could not allocate memory for the grid properties struct\n");
    return EXIT_FAILURE;
  }

  for (i = 0; i < grid->NumGrids; ++i)
  {
#ifdef MPI
    printf("Allocating grid %d on Task %d.\n", i, ThisTask);
#endif

    /* For now the self-consistent model only using the number of ionizing photons. */
    /* Also use the number of galaxies for sanity check. */

    ALLOCATE_GRID_MEMORY(grid->GridProperties[i].Nion_HI, grid->NumCellsTotal);
    ALLOCATE_GRID_MEMORY(grid->GridProperties[i].GalCount, grid->NumCellsTotal);

    if (self_consistent == 0)
    {
      ALLOCATE_GRID_MEMORY(grid->GridProperties[i].SFR, grid->NumCellsTotal);
      ALLOCATE_GRID_MEMORY(grid->GridProperties[i].StellarMass, grid->NumCellsTotal);
      ALLOCATE_GRID_MEMORY(grid->GridProperties[i].Nion_HeI, grid->NumCellsTotal);
      ALLOCATE_GRID_MEMORY(grid->GridProperties[i].Nion_HeII, grid->NumCellsTotal);

    }

    // All the memory has been allocated for the inner grid.
    // Now let's initialize the values.

    for (cell_idx = 0; cell_idx < Grid->NumCellsTotal; ++cell_idx)
    {
      grid->GridProperties[i].Nion_HI[cell_idx] = 0.0;
      grid->GridProperties[i].GalCount[cell_idx] = 0;

      if (self_consistent == 0)
      {
        grid->GridProperties[i].SFR[cell_idx] = 0.0;
        grid->GridProperties[i].StellarMass[cell_idx] = 0.0;
        grid->GridProperties[i].Nion_HeI[cell_idx] = 0.0;
        grid->GridProperties[i].Nion_HeII[cell_idx] = 0.0;

      }
    }

  }

  printf("All grids have been initialized\n");
  return EXIT_SUCCESS;
}

int32_t free_grid(void)
{

  int32_t i;

  for (i = 0; i < Grid->NumGrids; ++i)
  {
    free(Grid->GridProperties[i].Nion_HI);
    free(Grid->GridProperties[i].GalCount);

    if (self_consistent == 0)
    {
      free(Grid->GridProperties[i].Nion_HeII);
      free(Grid->GridProperties[i].Nion_HeI);
      free(Grid->GridProperties[i].StellarMass);
      free(Grid->GridProperties[i].SFR);
    }
  }

  free(Grid->GridProperties);
  free(Grid);

  return EXIT_SUCCESS;

}

void set_units(void)
{

  UnitTime_in_s = UnitLength_in_cm / UnitVelocity_in_cm_per_s;
  UnitTime_in_Megayears = UnitTime_in_s / SEC_PER_MEGAYEAR;
  G = GRAVITY / pow(UnitLength_in_cm, 3) * UnitMass_in_g * pow(UnitTime_in_s, 2);
  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);

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

  // compute a few quantitites
  RhoCrit = 3 * Hubble * Hubble / (8 * M_PI * G);

}

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

  snprintf(fname, MAXLEN, "%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);

#ifdef MPI
  if(ThisTask == 0)
#endif
  printf("found %d defined times in snaplist\n", Snaplistlen);
}

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 / Hubble,
    1.0e-8, WORKSIZE, GSL_INTEG_GAUSS21, workspace, &result, &abserr);

  time = 1 / 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)
{
  return 1 / sqrt(Omega / a + (1 - Omega - OmegaLambda) + OmegaLambda * a * a);
}

void determine_fesc_constants(void)
{

  double A, B, log_A;

  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);

  alpha = A;
  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", alpha, beta);

}
/*
void estimate_grid_memory(void)
{

  if (Verbose == 1)
  {
    printf("The size of the Grid Struct (that contains all the pointers) is %lu bytes\nEach grid cell has a struct, in addition to data that the pointers point to.\n", sizeof(struct GRID));
    printf("The total memory taken up by the Grid will be the sizeof a grid cell (%lu bytes) times the number of grid cells (%d) times the number of output snapshots (%d) times 2 because we need memory for both the pointers for the grid and the actual numbers themselves.\n", sizeof(struct GRID), CUBE(GridSize), NGrid);
  }

  printf("Approximate total memory for the grid ===== %lu mb\n", sizeof(struct GRID)*CUBE(GridSize)*NGrid*2/1024/1024);

}

void estimate_gal_memory(int NtotGals)
{
  if (Verbose == 1)
  {
    printf("The size of the Galaxy struct is %lu bytes and the size of the GalaxyGrid struct (that contains all the pointers for the SnapShot history) is %lu\n", sizeof(struct GALAXY_INPUT), sizeof(struct GALAXY_GRID));
    printf("The total memory taken up by this file of galaxies will be the sizeof the galaxy input (%lu bytes) times the number of galaxies (%d) plus the sizeof the galaxygrid (%lu bytes) times the number of snapshots in the simulation (%d) times the number of galaxies (%d)\n", sizeof(struct GALAXY_INPUT), NtotGals, sizeof(struct GALAXY_GRID), LastSnapShotNr, NtotGals);
  }

  printf("Approximate memory occupied for this file of galaxies ===== %lu mb\n", (sizeof(struct GALAXY_INPUT)*NtotGals + sizeof(struct GALAXY_GRID)*NtotGals*LastSnapShotNr)/1024/1024);

}
*/


/**
 *
 * @file pcunmqr_blgtrd.c
 *
 *  PLASMA auxiliary routines
 *  PLASMA is a software package provided by Univ. of Tennessee,
 *  Univ. of California Berkeley and Univ. of Colorado Denver
 *
 * @version 2.6.0
 * @author Azzam Haidar
 * @date 2011-05-15
 * @generated c Tue Jan  7 11:45:13 2014
 *
 **/
#include <lapacke.h>
#include <cblas.h>
#include "common.h"
#undef REAL
#define COMPLEX

#define E(_m, _n) (E   + (_m) + LDE * (_n) )
#define V(_m)     (V   + (_m))
#define T(_m)     (T   + (_m))
#define TAU(_m)   (TAU + (_m))
/***************************************************************************
 *  Parallel apply Q2 from bulgechasing matrices - static scheduling
 *  Lower case is treated
 **/
    /*
     * side == PlasmaLeft:
     *     meaning apply E = Q*E = (q_1*q_2*.....*q_n) * E ==> so
     *     traverse Vs in reverse order (forward) from q_n to q_1 Also
     *     E is splitten by block of col over cores because each apply
     *     consist in a block of row (horizontal block)
     */
    /*
     * side == PlasmaRight:
     *     meaning apply E = E*Q = E * (q_1*q_2*.....*q_n) ==> so
     *     traverse Vs in normal order (forward) from q_1 to q_n Also
     *     E is splitten by block of row over core because each apply
     *     consist in a block of col (vertical block)
     */
/***************************************************************************/
void plasma_pcunmqr_blgtrd(plasma_context_t *plasma)
{
    int my_core_id = PLASMA_RANK;
    int cores_num  = plasma->world_size;

    /*===========================*/
    int N, NB, NE, LDE, Vblksiz, WANTZ;
    PLASMA_enum side;
    PLASMA_enum trans;
    PLASMA_Complex32_t *V;
    PLASMA_Complex32_t *T;
    PLASMA_Complex32_t *TAU;
    PLASMA_Complex32_t *E;
    PLASMA_sequence *sequence;
    PLASMA_request *request;

    /*===========================
     *   local variables
     *===========================*/
    PLASMA_Complex32_t *WORK;
    int LDT, LDV;
    int Vm, Vn, mt, nt;
    int myrow, mycol, blkj, blki;
    int firstrow, nbcolinvolvd;
    int blkid, vpos, taupos, tpos;
    int chunkid, nbchunk, colpercore, corest, corelen, len, col;
    int coreid, allcoresnb, maxrequiredcores;
    int lchunkid, rchunkid, halfchunk, nbportion, sw;
    int LWORK;
    int standalonework = 0 ;
    int versionL, versionR;

    plasma_unpack_args_14(side, trans, N, NB, NE, Vblksiz, WANTZ, V, T, TAU, E, LDE, sequence, request);
    if (sequence->status != PLASMA_SUCCESS)
        return;

    /* Quick return */
    if ( N == 0 ) {
        return;
    }
    if ( NB == 0 ) {
        return;
    }
    if ( NE == 0 ) {
        return;
    }
    /* ==========================================
     * some infor for developer
     * Initialisation and checking nb of cores
     * ==========================================*/
    /* we have to 2 algo for left (113 114) and 2 algo for right (91 92)
     * which correspond to versionL versionR.
     * They ae very similar (detail explained in tech report and matlab code)
     * however version 114 and 92 improve locality.
     * while version 113 is used in case WNATZ=1 (construct Q2) which allow
     * the construction to be done in an optimized way taking into
     * consideration that the matrix is Identity so making less flops.
     *
     */
    versionL = 113;
    versionR = 92;
    LDT      = Vblksiz;
    LDV      = NB+Vblksiz-1;
    /* use colpercore =  N/cores_num; :if i want to split E into
     * cores_num chunk so I will have large chunk for each core.
     * However I prefer to split E into chunk of small size where
     * I guarantee that blas3 occur and the size of chunk remain into
     * cache, I think it is better. than I will have different chunk of
     * small size per core and i will do each chunk till the end and
     * then move to the second one for data locality
     *
     * version v1: for each chunck it apply all the V's then move to
     *                    the other chunck. the locality here inside each
     *                    chunck meaning that thread "t" apply V_k then move
     *                    to V_k+1 which overlap with V_k meaning that the
     *                    E_k+1 overlap with E_k. so here is the
     *                    locality however thread t had to read V_k+1 and
     *                    T_k+1 at each apply. note that all thread if they
     *                    run at same speed they might reading the same V_k
     *                    and T_k at the same time.
     *
     * version v2: for each V_k and T_k thread t apply those V_k and
     *                    T_k to E_k for all its chunck, then move to V_k+1
     *                    T_k+1 and apply them to E_k+1 on all the chunck,
     *                    and so on. the difference is that, thread keep V_k
     *                    and T_K while move over the E_k.
     *                    both version look like similar in perf.
     *
     *  THIS IS v1 CODE
     * */

    if(WANTZ==1)
        colpercore =  plasma_ceildiv(NE,2*cores_num);
    else
        colpercore =  plasma_ceildiv(NE,cores_num);

    if(colpercore>1000)
        colpercore =  plasma_ceildiv(colpercore,10);
    else if(colpercore>800)
        colpercore =  plasma_ceildiv(colpercore,8);
    else if(colpercore>600)
        colpercore =  plasma_ceildiv(colpercore,6);
    else if(colpercore>400)
        colpercore =  plasma_ceildiv(colpercore,4);
    else if(colpercore>200)
        colpercore =  plasma_ceildiv(colpercore,2);
    if(colpercore>200)
        colpercore=120;
    if(colpercore<30)
        colpercore=32;
    /*colpercore = N make the code sequential running on thread=0;*/
    nbchunk          =  plasma_ceildiv(NE, colpercore);
    allcoresnb       = cores_num;
    maxrequiredcores = nbchunk;
    if( maxrequiredcores < 1 )
        maxrequiredcores = 1;
    if(allcoresnb > maxrequiredcores)
        allcoresnb = maxrequiredcores;

#if defined (ENABLE_DEBUG)
    if(my_core_id==0)
        printf("  APPLY Q_v1   parallel with threads %d   nbchunk %d  colpercore %d  N %d   NB %d   Vblksiz %d SIDE %c TRANS %c versionL %d versionR %d WANTZ %d \n",
               cores_num,nbchunk,colpercore,N,NB,Vblksiz,lapack_const(side),lapack_const(trans),versionL,versionR,WANTZ);
#endif
    /* =========================================
     * case NB = 1  special case.
     * just make the element real for complex
     * =========================================
     */
    if ( NB == 1 ) {
        /* NOTE in CASE USED STANDALONE FUNCTION
         * In COMPLEX matrix Z need to be scaled by the TAU
         * generated during the make off-diagonal elements real
         */
        /*
         * In case where the first stage has been done we are sure
         * that all element of E are real so no need to go through
         * NB=1.  However in case where this function need to be used
         * for only band matrices meaning only stage2 has been called
         * then it require to make all off-diagonal elements real and
         * so remove the return from here and from the bulgechasing
         * function
         */
        if(WANTZ==1){
            PLASMA_Complex32_t zone = 1.0;
            memset(E, 0, LDE*N*sizeof(PLASMA_Complex32_t));
            for(sw=0; sw<NE; sw++)
                E[sw+sw*LDE] = zone;
        }
        if(standalonework==0){
            return;
        }
        else{
#ifdef COMPLEX
            for (chunkid = 0; chunkid<nbchunk; chunkid++) {
                coreid  = chunkid%allcoresnb;
                corest  = chunkid*colpercore;
                corelen = min(colpercore, (NE-(chunkid*colpercore)));
                if( my_core_id==coreid ){
                    if( side==PlasmaLeft ){
                        for (mycol =1; mycol<NE; mycol++){
                            cblas_cscal(corelen, TAU(mycol), E(mycol, corest), LDE);
                        }
                    }else{
                        PLASMA_Complex32_t ztmp;
                        /*Upper case need to be checked*/
                        for (mycol = corest; mycol<corest+corelen; mycol++){
                            ztmp = conjf(*TAU(mycol));
                            cblas_cscal(N, &ztmp, E(0,mycol), 1);
                        }
                    }
                }
            }
#endif
            return;
        }
    }
    /* =========================================
     *       case NB >1  main code
     * =========================================
     */
    LWORK = 2*colpercore*max(Vblksiz,64);
    WORK  = (PLASMA_Complex32_t*) plasma_private_alloc(plasma, LWORK, PlasmaComplexFloat);
    /* WANTZ = 1 meaning E is IDENTITY so form Q using optimized update.
     *         So we use the reverse order from small q to large one,
     *         so from q_n to q_1 so Left update to Identity.
     *         Use version 113 because in 114 we need to update the whole matrix and not in icreasing order.
     * WANTZ = 2 meaning E is a full matrix and need to be updated from Left or Right so use normal update
     * */
    if ( WANTZ==1 ) {
        versionL = 113;
        halfchunk = plasma_ceildiv(nbchunk,2);
        for (lchunkid = 0; lchunkid<halfchunk; lchunkid++) {
            rchunkid  = (nbchunk-1) - lchunkid;
            nbportion = lchunkid == rchunkid ? 1 : 2;
            /* sw is the switch between left and right side chunk
             * it is used to have same load distribution of work */

            for (sw = 0; sw<nbportion; sw++) {
                chunkid = sw == 0 ? lchunkid : rchunkid;
                coreid  = lchunkid%allcoresnb;
                corest  = chunkid*colpercore;
                corelen = min(colpercore, (NE-(chunkid*colpercore)));
                if( my_core_id == coreid ) {
                    /*
                     * Version 113:
                     * loop over the block_col (nt) and for each find the
                     * number of tiles (mt) in this block_col. then loop over mt, find
                     * the size of the V's(Vm,Vn) and apply it to the corresponding
                     * portion of E.
                     */
                    nt  = plasma_ceildiv((N-1),Vblksiz);
                    for (blkj=nt-1; blkj>=0; blkj--) {
                        /* the index of the first row on the top of block (blkj) */
                        firstrow = blkj * Vblksiz + 1;
                        /*find the number of tile for this block */
                        if( blkj == nt-1 )
                            mt = plasma_ceildiv( N -  firstrow,    NB);
                        else
                            mt = plasma_ceildiv( N - (firstrow+1), NB);
                        /*loop over the tiles find the size of the Vs and apply it */
                        for (blki=mt; blki>0; blki--) {
                            /*calculate the size of each losange of Vs= (Vm,Vn)*/
                            myrow     = firstrow + (mt-blki)*NB;
                            mycol     = blkj*Vblksiz;
                            Vm = min( NB+Vblksiz-1, N-myrow);
                            if( ( blkj == nt-1 ) && ( blki == mt ) ){
                                Vn = min (Vblksiz, Vm);
                            } else {
                                Vn = min (Vblksiz, Vm-1);
                            }
                            /*calculate the pointer to the Vs and the Ts.
                             * Note that Vs and Ts have special storage done
                             * by the bulgechasing function*/
                            findVTpos(N,NB,Vblksiz,mycol,myrow, &vpos, &taupos, &tpos, &blkid);
                            if((Vm>0)&&(Vn>0)){
                                col = max(mycol,corest);
                                len = corelen - (col-corest);
                                if(side==PlasmaLeft){
                                    if( len > 0 )
                                        CORE_clarfb_gemm(
                                            side, trans,
                                            PlasmaForward, PlasmaColumnwise,
                                            Vm, len, Vn, V(vpos), LDV, T(tpos), LDT, E(myrow,col), LDE,  WORK, len);
                                }
                                else{
                                    if( len > 0 )
                                        CORE_clarfb_gemm(
                                            side, trans,
                                            PlasmaForward, PlasmaColumnwise,
                                            len, Vm, Vn, V(vpos), LDV, T(tpos), LDT, E(col, myrow), LDE,  WORK, len);
                                }
                            }
                        }
                    }
                } /* END my_core_id=coreid */
            } /* END of sw  */
        } /* END loop over the chunk */
    } /* END if WANTZ=1 */
    else{
        /*
         * WANTZ != 1
         */
        for (chunkid = 0; chunkid<nbchunk; chunkid++) {
            coreid  = chunkid%allcoresnb;
            corest  = chunkid*colpercore;
            corelen = min(colpercore, (NE-(chunkid*colpercore)));
            if(corelen < 0)
                corelen = 0;
            if( my_core_id == coreid ) {
                /*
                 * PlasmaLeft
                 */
                //if( side == PlasmaLeft ) {
                if( versionL == 113 ) {
                    /*
                     * Version 113:
                     * loop over the block_col (nt) and for each find the
                     * number of tiles (mt) in this block_col. then loop over mt, find
                     * the size of the V's(Vm,Vn) and apply it to the corresponding
                     * portion of E.
                     */
                    if( versionL == 113 ) {
                        nt  = plasma_ceildiv((N-1),Vblksiz);
                        for (blkj=nt-1; blkj>=0; blkj--) {
                            /* the index of the first row on the top of block (blkj) */
                            firstrow = blkj * Vblksiz + 1;
                            /*find the number of tile for this block */
                            if( blkj == nt-1 )
                                mt = plasma_ceildiv( N -  firstrow,    NB);
                            else
                                mt = plasma_ceildiv( N - (firstrow+1), NB);
                            /*loop over the tiles find the size of the Vs and apply it */
                            for (blki=mt; blki>0; blki--) {
                                /*calculate the size of each losange of Vs= (Vm,Vn)*/
                                myrow     = firstrow + (mt-blki)*NB;
                                mycol     = blkj*Vblksiz;
                                Vm = min( NB+Vblksiz-1, N-myrow);
                                if( ( blkj == nt-1 ) && ( blki == mt ) ){
                                    Vn = min (Vblksiz, Vm);
                                } else {
                                    Vn = min (Vblksiz, Vm-1);
                                }
                                /*calculate the pointer to the Vs and the Ts.
                                 * Note that Vs and Ts have special storage done
                                 * by the bulgechasing function*/
                                findVTpos(N,NB,Vblksiz,mycol,myrow, &vpos, &taupos, &tpos, &blkid);
                                if((Vm>0)&&(Vn>0)){
                                    if( side == PlasmaLeft ){
                                        CORE_clarfb_gemm(
                                            PlasmaLeft, trans,
                                            PlasmaForward, PlasmaColumnwise,
                                            Vm, corelen, Vn, V(vpos), LDV, T(tpos), LDT, E(myrow,corest), LDE,  WORK, corelen);

                                    }else{
                                        CORE_clarfb_gemm(
                                            PlasmaRight, trans,
                                            PlasmaForward, PlasmaColumnwise,
                                            corelen, Vm, Vn, V(vpos), LDV, T(tpos), LDT, E(corest, myrow), LDE,  WORK, corelen);
                                    }
                                }
                            }
                        }
                    }
                    /*
                     * Version 114:
                     * loop over the block_row (mt) and for each find diagonally the
                     * number of tiles (nt) in this block_row. then loop over nt, find
                     * the size of the V's(Vm,Vn) and apply it to the corresponding
                     * portion of E.
                     */
                    else {
                        mt    = plasma_ceildiv((N-1),NB);
                        for (blki = mt; blki>0; blki--) {
                            /* nbcolinvolvd = number of column corresponding to this block_row (blki) */
                            nbcolinvolvd = min(N-1, blki*NB);
                            /*find the number of tile for this block (diagonal row of tiles) */
                            nt = plasma_ceildiv(nbcolinvolvd,Vblksiz);
                            /*loop over the tiles find the size of the Vs and apply it */
                            for (blkj = nt-1; blkj>=0; blkj--) {
                                /* the index of the first row of the first col meaning
                                 * the block on the top left (blki) */
                                firstrow = (mt-blki)*NB+1;
                                /*calculate the size of each losange of Vs= (Vm,Vn)*/
                                myrow    = firstrow + blkj*Vblksiz;
                                mycol    = blkj*Vblksiz;
                                Vm = min( NB+Vblksiz-1, N-myrow);
                                if( ( blkj == nt-1 ) && ( blki == mt ) ){
                                    Vn = min (Vblksiz, Vm);
                                }else{
                                    Vn = min (Vblksiz, Vm-1);
                                }
                                /*calculate the pointer to the Vs and the Ts.
                                 * Note that Vs and Ts have special storage done
                                 * by the bulgechasing function*/
                                findVTpos(N,NB,Vblksiz,mycol,myrow, &vpos, &taupos, &tpos, &blkid);
                                if((Vm>0)&&(Vn>0)) {
                                    CORE_clarfb_gemm(
                                        PlasmaLeft, trans,
                                        PlasmaForward, PlasmaColumnwise,
                                        Vm, corelen, Vn, V(vpos), LDV, T(tpos), LDT, E(myrow,corest), LDE,  WORK, corelen);
                                }
                            }
                        }
                    }
                }
                /*
                 * PlasmaRight
                 */
                else {
                    /*
                     * Version 91:
                     */
                    if( versionR == 91 ) {
                        nt  = plasma_ceildiv((N-1),Vblksiz);
                        for (blkj=0; blkj<nt; blkj++) {
                            /* the index of the first myrow on the top of block (blkj) */
                            firstrow = blkj * Vblksiz + 1;
                            /*find the number of tile for this block */
                            if( blkj == nt-1 )
                                mt = plasma_ceildiv( N -  firstrow,    NB);
                            else
                                mt = plasma_ceildiv( N - (firstrow+1), NB);
                            /*loop over the tiles find the size of the Vs and apply it */
                            for (blki=1; blki<=mt; blki++) {
                                /*calculate the size of each losange of Vs= (Vm,Vn)*/
                                myrow  = firstrow + (mt-blki)*NB;
                                Vm = min( NB+Vblksiz-1, N-myrow);
                                if( ( blkj == nt-1 ) && ( blki == mt ) )
                                {
                                    Vn = min (Vblksiz, Vm);
                                }else{
                                    Vn = min (Vblksiz, Vm-1);
                                }
                                /*calculate the pointer to the Vs and the Ts.
                                 * Note that Vs and Ts have special storage done
                                 * by the bulgechasing function*/
                                mycol     = blkj*Vblksiz;
                                findVTpos(N,NB,Vblksiz,mycol,myrow, &vpos, &taupos, &tpos, &blkid);
                                if((Vm>0)&&(Vn>0)){
                                    CORE_clarfb_gemm(
                                        PlasmaRight,
                                        trans,
                                        PlasmaForward,
                                        PlasmaColumnwise,
                                        corelen, Vm, Vn, V(vpos), LDV, T(tpos), LDT, E(corest,myrow), LDE,  WORK, corelen);
                                }
                            }
                        }
                    }
                    /*trans
                     * Version 92:
                     */
                    else {
                        mt    = plasma_ceildiv((N-1),NB);
                        for (blki = 1; blki<=mt; blki++) {
                            /* nbcolinvolvd = number of column corresponding to this block_row (blki) */
                            nbcolinvolvd = min(N-1, blki*NB);
                            /*find the number of tile for this block (diagonal row of tiles) */
                            nt = plasma_ceildiv(nbcolinvolvd,Vblksiz);
                            /*loop over the tiles find the size of the Vs and apply it */
                            for (blkj = 0; blkj<nt; blkj++) {
                                /* the index of the first row of the first col meaning
                                 * the block on the top left (blki) */
                                firstrow = (mt-blki)*NB+1;
                                /*calculate the size of each losange of Vs= (Vm,Vn)*/
                                myrow    = firstrow + blkj*Vblksiz;
                                mycol    = blkj*Vblksiz;
                                Vm = min( NB+Vblksiz-1, N-myrow);
                                if( ( blkj == nt-1 ) && ( blki == mt ) ){
                                    Vn = min (Vblksiz, Vm);
                                }else{
                                    Vn = min (Vblksiz, Vm-1);
                                }
                                /*calculate the pointer to the Vs and the Ts.
                                 * Note that Vs and Ts have special storage done
                                 * by the bulgechasing function*/
                                findVTpos(N,NB,Vblksiz,mycol,myrow, &vpos, &taupos, &tpos, &blkid);
                                if((Vm>0)&&(Vn>0)) {
                                    CORE_clarfb_gemm(
                                        PlasmaRight, trans,
                                        PlasmaForward, PlasmaColumnwise,
                                        corelen, Vm, Vn, V(vpos), LDV, T(tpos), LDT, E(corest,myrow), LDE,  WORK, corelen);
                                }
                            }
                        }
                    }
                }
            } /* END my_core_id=coreid */
        } /* END loop over the chunk */
    } /* END ELSE of WANTZ == 1 */
    plasma_private_free(plasma, WORK);
}
#undef E
#undef V
#undef T
#undef TAU
/***************************************************************************/
#undef COMPLEX


///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
//
// 			     //////////////////////////////////////////////////////////
//  			     //							     //
// 			     //   	        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;

}