#include <mpi.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <gsl/gsl_math.h>
#include "allvars.h"
#include "proto.h"
#ifdef COSMIC_RAYS
#include "cosmic_rays.h"
#endif

void reconstruct_timebins(void)
{
  int i, n, prev, bin;
  long long glob_sum1, glob_sum2;

  for(bin = 0; bin < TIMEBINS; bin++)
    {
      TimeBinCount[bin] = 0;
      TimeBinCountSph[bin] = 0;
      FirstInTimeBin[bin] = -1;
      LastInTimeBin[bin] = -1;
#ifdef SFR
      TimeBinSfr[bin] = 0;
#endif
#ifdef BLACK_HOLES
      TimeBin_BH_mass[bin] = 0;
      TimeBin_BH_dynamicalmass[bin] = 0;
      TimeBin_BH_Mdot[bin] = 0;
      TimeBin_BH_Medd[bin] = 0;
#endif
    }

  for(i = 0; i < NumPart; i++)
    {
      bin = P[i].TimeBin;

      if(TimeBinCount[bin] > 0)
	{
	  PrevInTimeBin[i] = LastInTimeBin[bin];
	  NextInTimeBin[i] = -1;
	  NextInTimeBin[LastInTimeBin[bin]] = i;
	  LastInTimeBin[bin] = i;
	}
      else
	{
	  FirstInTimeBin[bin] = LastInTimeBin[bin] = i;
	  PrevInTimeBin[i] = NextInTimeBin[i] = -1;
	}
      TimeBinCount[bin]++;
      if(P[i].Type == 0)
	TimeBinCountSph[bin]++;

#ifdef SFR
      if(P[i].Type == 0)
	TimeBinSfr[bin] += SphP[i].Sfr;
#endif
#if BLACK_HOLES
      if(P[i].Type == 5)
	{
	  TimeBin_BH_mass[bin] += P[i].BH_Mass;
	  TimeBin_BH_dynamicalmass[bin] += P[i].Mass;
	  TimeBin_BH_Mdot[bin] += P[i].BH_Mdot;
	  TimeBin_BH_Medd[bin] += P[i].BH_Mdot / P[i].BH_Mass;
	}
#endif
    }

  FirstActiveParticle = -1;

  for(n = 0, prev = -1; n < TIMEBINS; n++)
    {
      if(TimeBinActive[n])
	for(i = FirstInTimeBin[n]; i >= 0; i = NextInTimeBin[i])
	  {
	    if(prev == -1)
	      FirstActiveParticle = i;

	    if(prev >= 0)
	      NextActiveParticle[prev] = i;

	    prev = i;
	  }
    }

  if(prev >= 0)
    NextActiveParticle[prev] = -1;

  sumup_large_ints(1, &NumForceUpdate, &glob_sum1);

  for(i = FirstActiveParticle, NumForceUpdate = 0; i >= 0; i = NextActiveParticle[i])
    {
      NumForceUpdate++;
      if(i >= NumPart)
	{
	  printf("Bummer i=%d\n", i);
	  endrun(12);
	}

    }

  sumup_large_ints(1, &NumForceUpdate, &glob_sum2);

  if(ThisTask == 0)
    {
      printf("sum1=%d%9d sum2=%d%9d\n",
	     (int) (glob_sum1 / 1000000000), (int) (glob_sum1 % 1000000000),
	     (int) (glob_sum2 / 1000000000), (int) (glob_sum2 % 1000000000));
    }

  if(glob_sum1 != glob_sum2 && All.NumCurrentTiStep > 0)
    endrun(121);
}

void drift_particle(int i, int time1)
{
  int j, time0, dt_step;
  double dt_drift, dt_gravkick, dt_hydrokick, dt_entr;

#ifdef DISTORTIONTENSORPS
  int j1, j2;
  /*
   Setup all numbers that are going to be written to caustic log file. Some of them will not be set, depending
   on Makefile options. Therefore we assign zero to each value in the beginning.
  */
  MyDouble s_1=0.0, s_2=0.0, s_3=0.0;
  MyDouble smear_x=0.0, smear_y=0.0, smear_z=0.0;
  MyDouble CurrentTurnaroundRadius=0.0;
  MyDouble caustic_counter = 0.0;
  MyDouble summed_annihilation = 0.0;
  MyDouble D_xP = 0.0;
  MyDouble init_density = 0.0;
  MyDouble rho_normed_cutoff = 0.0;
  MyDouble second_deriv_fac = 0.0;
  MyDouble analytic_add_to_annihilation = 0.0;

  /* things needed for caustics detection */
  MyDouble determinant =  1.0;
  MyDouble product_matrix[3][3];
  MyDouble current_normed_stream_density;

  MyDouble last_normed_stream_density;

#ifdef ANNIHILATION_RADIATION
  /*1: if annihilation radiation was integrated analytically through the caustic, 0: otherwise */
  int caustic_annihilation_flag = 0;
  MyDouble rho_0, kappa_caustic;
#endif

#if SIM_ADAPTIVE_SOFT
  CurrentTurnaroundRadius = All.CurrentTurnaroundRadius;
#endif
#endif /* DISTORTIONTENSORPS */

#ifdef COSMIC_RAYS
  int CRpop;
#endif

#ifdef SOFTEREQS
  double a3inv;

  if(All.ComovingIntegrationOn)
    a3inv = 1 / (All.Time * All.Time * All.Time);
  else
    a3inv = 1;
#endif

  time0 = P[i].Ti_current;

  if(time1 < time0)
    {
      printf("i=%d time0=%d time1=%d\n", i, time0, time1);
      endrun(12);
    }

  if(time1 == time0)
    return;

  if(All.ComovingIntegrationOn)
    {
      dt_drift = get_drift_factor(time0, time1);
      dt_gravkick = get_gravkick_factor(time0, time1);
      dt_hydrokick = get_hydrokick_factor(time0, time1);
    }
  else
    {
      dt_drift = dt_gravkick = dt_hydrokick = (time1 - time0) * All.Timebase_interval;
    }

  for(j = 0; j < 3; j++)
    P[i].Pos[j] += P[i].Vel[j] * dt_drift;

/* START PHASE-SPACE ANALYSIS ------------------------------------------------------ */
#ifdef DISTORTIONTENSORPS

  /* do the DRIFT for distortion (this updates D_xq and D_xp; D_vq and D_vp are updated during kick) */
  for(j1 = 0; j1 < 3; j1++)
    for(j2 = 0; j2 < 6; j2++)
      P[i].distortion_tensorps[j1][j2] += P[i].distortion_tensorps[j1 + 3][j2] * dt_drift;

#ifdef REINIT_AT_TURNAROUND
  /* do we have to reinit distortion tensor? Only particles that are initially outside the turnaround radius are analyzed */
  if ((P[i].Pos[0]*P[i].Vel[0]+P[i].Pos[1]*P[i].Vel[1]+P[i].Pos[2]*P[i].Vel[2] < 0.0) && (P[i].turnaround_flag == 0))
    {
      /* yes, particle turned around */
      /* re-init distortion tensor */
      for(j1 = 0; j1 < 6; j1++)
	for(j2 = 0; j2 < 6; j2++)
	  {
	    if((j1 == j2))
	      {
		P[i].distortion_tensorps[j1][j2] = 1.0;
	      }
	    else
	      {
		P[i].distortion_tensorps[j1][j2] = 0.0;
	      }
	  }
      /* mark particle so that we know that it already turned around */
      P[i].turnaround_flag = 1;

      /* set new initial sheet orientation since we start distortion from this point (analytic solution) */
      for (j1=0; j1<=2; j1++)
        for (j2=0; j2<=2; j2++)
          P[i].V_matrix[j1][j2] = P[i].Pos[j1]*P[i].Pos[j2] / (P[i].Pos[0]*P[i].Pos[0]+P[i].Pos[1]*P[i].Pos[1]+P[i].Pos[2]*P[i].Pos[2]) *
                                  (3.0/4.0*M_PI)*(3.0/4.0*M_PI) / (3.0 + 1.0/All.SIM_epsilon) * 1.0 / All.Time;
      /*
       The correct stream init density at turn around is given by rho_crit * 9.0 * M_PI * M_PI / (16.0*(3.0*epsilon+1.0)).
       This is exactly the density at the turnaround radius for a given density perturbation index epsilon.
       (see for example, Sikivie et al 1997)
      */
      P[i].init_density = 9.0 * M_PI * M_PI / (16.0*(3.0*All.SIM_epsilon+1.0)) * 1.0 / (6.0 * M_PI * All.G * All.Time * All.Time);

      /* reset last_stream_determinant (needed for caustic identification) */
      P[i].last_stream_determinant = 1.0;
#ifdef ANNIHILATION_RADIATION
      /* set annihilation radiation to zero */
      P[i].annihilation = 0.0;
#endif
    }
#endif

  /* NOW CAUSTIC AND ANNIHILATION HANDLING */
#ifdef REINIT_AT_TURNAROUND
 /* only analyse particle initially outside the turnaround radius, those initially inside have been set to P[i].turnaround_flag=-1 */
 if (P[i].turnaround_flag >= 0)
 {
#endif
  /* CAUSTIC IDENTIFICATION PART */
  /* projection phase-space distrtion matrix (see Vogelsberger et al, 2008 Eq. (22)) -> needed to check for caustic in drift */
  product_matrix[0][0] = P[i].distortion_tensorps[0][0] +
                         P[i].distortion_tensorps[0][3] * P[i].V_matrix[0][0] +
                         P[i].distortion_tensorps[0][4] * P[i].V_matrix[1][0] +
                         P[i].distortion_tensorps[0][5] * P[i].V_matrix[2][0];
  product_matrix[0][1] = P[i].distortion_tensorps[0][1] +
                         P[i].distortion_tensorps[0][3] * P[i].V_matrix[0][1] +
                         P[i].distortion_tensorps[0][4] * P[i].V_matrix[1][1] +
                         P[i].distortion_tensorps[0][5] * P[i].V_matrix[2][1];
  product_matrix[0][2] = P[i].distortion_tensorps[0][2] +
                         P[i].distortion_tensorps[0][3] * P[i].V_matrix[0][2] +
                         P[i].distortion_tensorps[0][4] * P[i].V_matrix[1][2] +
                         P[i].distortion_tensorps[0][5] * P[i].V_matrix[2][2];
  product_matrix[1][0] = P[i].distortion_tensorps[1][0] +
                         P[i].distortion_tensorps[1][3] * P[i].V_matrix[0][0] +
                         P[i].distortion_tensorps[1][4] * P[i].V_matrix[1][0] +
                         P[i].distortion_tensorps[1][5] * P[i].V_matrix[2][0];
  product_matrix[1][1] = P[i].distortion_tensorps[1][1] +
                         P[i].distortion_tensorps[1][3] * P[i].V_matrix[0][1] +
                         P[i].distortion_tensorps[1][4] * P[i].V_matrix[1][1] +
                         P[i].distortion_tensorps[1][5] * P[i].V_matrix[2][1];
  product_matrix[1][2] = P[i].distortion_tensorps[1][2] +
                         P[i].distortion_tensorps[1][3] * P[i].V_matrix[0][2] +
                         P[i].distortion_tensorps[1][4] * P[i].V_matrix[1][2] +
                         P[i].distortion_tensorps[1][5] * P[i].V_matrix[2][2];
  product_matrix[2][0] = P[i].distortion_tensorps[2][0] +
                         P[i].distortion_tensorps[2][3] * P[i].V_matrix[0][0] +
                         P[i].distortion_tensorps[2][4] * P[i].V_matrix[1][0] +
                         P[i].distortion_tensorps[2][5] * P[i].V_matrix[2][0];
  product_matrix[2][1] = P[i].distortion_tensorps[2][1] +
                         P[i].distortion_tensorps[2][3] * P[i].V_matrix[0][1] +
                         P[i].distortion_tensorps[2][4] * P[i].V_matrix[1][1] +
                         P[i].distortion_tensorps[2][5] * P[i].V_matrix[2][1];
  product_matrix[2][2] = P[i].distortion_tensorps[2][2] +
                         P[i].distortion_tensorps[2][3] * P[i].V_matrix[0][2] +
                         P[i].distortion_tensorps[2][4] * P[i].V_matrix[1][2] +
                         P[i].distortion_tensorps[2][5] * P[i].V_matrix[2][2];

  /* this determinant will change sign when we pass through a caustic -> criterion for caustics */
  determinant = ((product_matrix[0][0]) * (product_matrix[1][1]) * (product_matrix[2][2]) +
		 (product_matrix[0][1]) * (product_matrix[1][2]) * (product_matrix[2][0]) +
		 (product_matrix[0][2]) * (product_matrix[1][0]) * (product_matrix[2][1]) -
		 (product_matrix[0][2]) * (product_matrix[1][1]) * (product_matrix[2][0]) -
		 (product_matrix[0][0]) * (product_matrix[1][2]) * (product_matrix[2][1]) -
		 (product_matrix[0][1]) * (product_matrix[1][0]) * (product_matrix[2][2]));

  /*
    Current and last NORMED stream density, linear order result of the last and current timestep.
  */
  current_normed_stream_density = 1.0/fabs(determinant);
  last_normed_stream_density    = 1.0/fabs(P[i].last_stream_determinant);

#ifdef ANNIHILATION_RADIATION
  /* avarage stream density */
  rho_0 = (current_normed_stream_density + last_normed_stream_density) / 2;

  /* extract phase-space information for annihilation radiation calculation -> cutoff density */
  P[i].rho_normed_cutoff_last    = P[i].rho_normed_cutoff_current;
  P[i].rho_normed_cutoff_current = analyse_phase_space(i, &s_1, &s_2, &s_3, &smear_x, &smear_y, &smear_z, &D_xP, &second_deriv_fac);
#endif

  /* CAUSTIC FOUND? -> was there a caustics between the last and the current timestep and does the particle type fit? */
  if((determinant * P[i].last_stream_determinant < 0.0) && (((1 << P[i].Type) & (CAUSTIC_FINDER))))
    {

#ifdef ANNIHILATION_RADIATION
      /* get normed_cutoff density */
      rho_normed_cutoff = P[i].rho_normed_cutoff_current;

      /*
       If rho_0 = (current_normed_stream_density + last_normed_stream_density) / 2 (so the average of current and last
       normed stream density) is below current normed cutoff density, we integrate analytically through the caustic
       for that time step.

       rho
       ^
       |         *  <--- rho_normed_cutoff
       |        ***
       |       *****
       |      *******   <-- area under curve ~ diverges logarithmicly
       |     *********
       |    ***********
       |   *************  <--- rho_0
       |---|-----|-----|----> t
         t_last t_c   t_current

       Note that the stream density is assumed to be symmetric around the caustic point: t_caustic = (t_last + t_current) / 2

       So we can center everything around t=0:

       rho
       ^
       |         *  <--- rho_normed_cutoff
       |        ***
       |       *****
       |      *******   <-- area under curve ~ diverges logarithmicly
       |     *********
       |    ***********
       |   *************  <--- rho_0
       |---|-----|-----|----> t
    -dt_drift/2  0   +dt_drift/2

       t_caustic = 0  and  dt_drift = t_current - t_last

       The fact that the numerical normed density near the caustic is below the cutoff, means that we cannot
       numerically resolve the very high caustic density within the time step.

       The analytic integration exploits the logarithmic divergence:

       Ansatz:
       rho(t) = kappa / |t|   --->   rho_0 = kappa / |dt_drift/2|   --->  kappa = dt_drift/2 * rho_0

       Integrated annihilation rate:

       2 \int_{-dt_drift/2}^{+dt_drift/2} dt rho(t) =
       2 - 2 kappa \int_{-dt_drift/2}^{0} dt 1/t  =  [ substitute drho / dt = + kappa/t^2 for t <0]
       2 2 kappa \int_{rho_0}^{rho_c} drho 1/rho =
       2 2 kappa ln(rho_c/rho_0)

       Therefore we need to add 2 kappa ln(\rho_c/\rho_0) when passing through an unresolved caustic,
       where rho_c is the calculated maximum density in the caustic.

       Note:
       We assume that the maximum density for the current and last time step do not differ much, so
       that we can compare rho_0 always to the current maximum density.

      */
#ifdef REINIT_AT_TURNAROUND
      /*
       We only integrate annihilation for particles that turned around already (otherwise wrong initial density)
       This is a security check, since each particle going through a caustic in general already turned around before.
      */
      if (P[i].turnaround_flag == 1)
      {
#endif
      /* if this condition is true we have to integrate analyitcally through the caustic */
      if (rho_0 < P[i].rho_normed_cutoff_current)
       {
        /* we do have an analytic integration through the caustic -> increase the counter and mark the flag */
        P[i].analytic_caustics          += 1.0;
        caustic_annihilation_flag        = 1;
        /* calculate analytic contribution */
        kappa_caustic                    = dt_drift / 2.0 * rho_0;
        analytic_add_to_annihilation     = 2.0 * kappa_caustic * log(fabs(P[i].rho_normed_cutoff_current/rho_0));

        /* add contribution to annihilation radiation */
        P[i].annihilation += analytic_add_to_annihilation;
       }
      summed_annihilation  = P[i].annihilation;
#ifdef REINIT_AT_TURNAROUND
      } /* end if (P[i].turnaround_flag == 1) */
#endif

#endif /* ANNIHILATION_RADIATION */

      /* increase caustic counter by one */
      P[i].caustic_counter += 1.0;
      caustic_counter       = P[i].caustic_counter;

#ifdef OUTPUT_LAST_CAUSTIC
      P[i].lc_Time    = All.Time;
      P[i].lc_Pos[0]  = P[i].Pos[0];
      P[i].lc_Pos[1]  = P[i].Pos[1];
      P[i].lc_Pos[2]  = P[i].Pos[2];
      P[i].lc_Vel[0]  = P[i].Vel[0];
      P[i].lc_Vel[1]  = P[i].Vel[1];
      P[i].lc_Vel[2]  = P[i].Vel[2];
#ifdef ANNIHILATION_RADIATION
      P[i].lc_rho_normed_cutoff = P[i].rho_normed_cutoff_current;
#else
      P[i].lc_rho_normed_cutoff = 0.0;
#endif
#endif

      init_density = P[i].init_density;

      /* write data to caustic log file -> allows caustics to be tracked on time-stepping frequency */
      fprintf(FdCaustics, "%g %g %d %g %g %g %g %g %g %g %g %g %g %g %g %g %g %g %g %g %g %g %g\n",
	      (MyOutputFloat) All.Time,
              (MyOutputFloat) determinant,
               P[i].ID,
	      (MyOutputFloat) P[i].Pos[0], (MyOutputFloat) P[i].Pos[1], (MyOutputFloat) P[i].Pos[2],
	      (MyOutputFloat) P[i].Vel[0], (MyOutputFloat) P[i].Vel[1], (MyOutputFloat) P[i].Vel[2],
              caustic_counter,
              s_1, s_2, s_3, smear_x, smear_y, smear_z,
              summed_annihilation,
              CurrentTurnaroundRadius,
              D_xP,
	      init_density / (1.0/(6.0*M_PI*All.G*All.Time*All.Time)),
              rho_normed_cutoff,
              second_deriv_fac,
              analytic_add_to_annihilation);
      fflush(FdCaustics);
    }

#ifdef ANNIHILATION_RADIATION
  /* is the numerical stream density above the normed_cutoff -> cut it */
  if (rho_0 > P[i].rho_normed_cutoff_current)
   {
    rho_0 = P[i].rho_normed_cutoff_current;
   }

  /* save cutted averaged stream density in physical units -> multiply with initial stream density */
  P[i].stream_density = P[i].init_density * rho_0;
#ifdef REINIT_AT_TURNAROUND
  /*
    We only integrate annihilation for particles that turned around already (otherwise wrong initial density)
    This is a security check, since each particle going through a caustic in general already turned around before.
  */
  if (P[i].turnaround_flag == 1)
   {
#endif
    /* integrate normed annihilation radiation -> multiplication with initial stream density is done in io.c to reduce round-off */
    if (caustic_annihilation_flag == 0)
      P[i].annihilation += rho_0 * dt_drift;
#ifdef REINIT_AT_TURNAROUND
   } /* end if (P[i].turnaround_flag == 1) */
#endif

#ifdef NO_CENTER_ANNIHILATION
  /* ignore the center for annihilation */
  if (sqrt(P[i].Pos[0]*P[i].Pos[0] + P[i].Pos[1]*P[i].Pos[1] + P[i].Pos[2]*P[i].Pos[2]) < All.SofteningTable[P[i].Type])
   {
    /* set everything to zero in center */
    P[i].annihilation   = 0.0;
    P[i].stream_density = 0.0;
   }
#endif
#endif /*ANNIHILATION_RADIATION */

#ifdef REINIT_AT_TURNAROUND
 } /* end if (P[i].turnaround_flag >= 0) */
#endif

 /* update determinant, so we can identify the next caustic along the orbit */
 P[i].last_stream_determinant = determinant;

#endif /* DISTORTIONTENSORPS */
/* END PHASE-SPACE ANALYSIS ------------------------------------------------------ */

#ifndef HPM
  if(P[i].Type == 0)
    {
#ifdef PMGRID
      for(j = 0; j < 3; j++)
	SphP[i].VelPred[j] +=
	  (P[i].g.GravAccel[j] + P[i].GravPM[j]) * dt_gravkick + SphP[i].a.HydroAccel[j] * dt_hydrokick;
#else
      for(j = 0; j < 3; j++)
	SphP[i].VelPred[j] += P[i].g.GravAccel[j] * dt_gravkick + SphP[i].a.HydroAccel[j] * dt_hydrokick;
#endif

      SphP[i].d.Density *= exp(-SphP[i].v.DivVel * dt_drift);
      PPP[i].Hsml *= exp(0.333333333333 * SphP[i].v.DivVel * dt_drift);

      if(PPP[i].Hsml < All.MinGasHsml)
	PPP[i].Hsml = All.MinGasHsml;

#ifndef WAKEUP
      dt_step = (P[i].TimeBin ? (1 << P[i].TimeBin) : 0);
#else
      dt_step = P[i].dt_step;
#endif
      dt_entr = (time1 - (P[i].Ti_begstep + dt_step / 2)) * All.Timebase_interval;

#ifndef EOS_DEGENERATE
#ifndef MHM
#ifndef SOFTEREQS
#ifndef VORONOI_MESHRELAX

      SphP[i].Pressure = (SphP[i].Entropy + SphP[i].e.DtEntropy * dt_entr) * pow(SphP[i].d.Density, GAMMA);

#endif
#else
      if(SphP[i].d.Density * a3inv >= All.PhysDensThresh)
	SphP[i].Pressure =
	  All.FactorForSofterEQS * (SphP[i].Entropy +
				    SphP[i].e.DtEntropy * dt_entr) * pow(SphP[i].d.Density,
									 GAMMA) + (1 -
										   All.
										   FactorForSofterEQS) *
	  GAMMA_MINUS1 * SphP[i].d.Density * All.InitGasU;
      else
	SphP[i].Pressure = (SphP[i].Entropy + SphP[i].e.DtEntropy * dt_entr) * pow(SphP[i].d.Density, GAMMA);
#endif
#else
      /* Here we use an isothermal equation of state */
      SphP[i].Pressure = GAMMA_MINUS1 * SphP[i].d.Density * All.InitGasU;
      SphP[i].Entropy = SphP[i].Pressure / pow(SphP[i].d.Density, GAMMA);
#endif
#else
      /* call tabulated eos with physical units */
#ifdef EOS_ENERGY
      eos_calc_egiven2(SphP[i].d.Density * All.UnitDensity_in_cgs, SphP[i].xnuc,
		       SphP[i].Entropy + SphP[i].e.DtEntropy * dt_entr * All.UnitTime_in_s, &SphP[i].temp,
		       &SphP[i].Pressure);
      SphP[i].Pressure /= All.UnitPressure_in_cgs;
#else
      eos_calc_sgiven(SphP[i].d.Density * All.UnitDensity_in_cgs, SphP[i].xnuc,
		      SphP[i].Entropy + SphP[i].e.DtEntropy * dt_entr * All.UnitTime_in_s, &SphP[i].temp,
		      &SphP[i].Pressure, &SphP[i].u);
      SphP[i].Pressure /= All.UnitPressure_in_cgs;
#endif
#endif

#ifdef COSMIC_RAYS
#if defined( CR_UPDATE_PARANOIA )
      for(CRpop = 0; CRpop < NUMCRPOP; CRpop++)
	CR_Particle_Update(SphP + i, CRpop);
#endif
#ifndef CR_NOPRESSURE
      for(CRpop = 0; CRpop < NUMCRPOP; CRpop++)
	SphP[i].Pressure += CR_Comoving_Pressure(SphP + i, CRpop);
#endif
#endif

#if defined(MAGNETIC) && !defined(EULERPOTENTIALS)
      for(j = 0; j < 3; j++)
	SphP[i].BPred[j] += SphP[i].DtB[j] * dt_entr;
#ifdef DIVBCLEANING_DEDNER
      SphP[i].PhiPred += SphP[i].DtPhi * dt_entr;
#endif
#endif

    }
#endif /* end of HPM */

  P[i].Ti_current = time1;
}

void move_particles(int time1)
{
  int i;

  if(ThisTask == 0)
    printf("MOVE\n");

  for(i = 0; i < NumPart; i++)
    drift_particle(i, time1);
}

/*! This function makes sure that all particle coordinates (Pos) are
 *  periodically mapped onto the interval [0, BoxSize].  After this function
 *  has been called, a new domain decomposition should be done, which will
 *  also force a new tree construction.
 */
#ifdef PERIODIC
void do_box_wrapping(void)
{
  int i, j;
  double boxsize[3];

  for(j = 0; j < 3; j++)
    boxsize[j] = All.BoxSize;

#ifdef LONG_X
  boxsize[0] *= LONG_X;
#endif
#ifdef LONG_Y
  boxsize[1] *= LONG_Y;
#endif
#ifdef LONG_Z
  boxsize[2] *= LONG_Z;
#endif

  for(i = 0; i < NumPart; i++)
    for(j = 0; j < 3; j++)
      {
	while(P[i].Pos[j] < 0)
	  P[i].Pos[j] += boxsize[j];

	while(P[i].Pos[j] >= boxsize[j])
	  P[i].Pos[j] -= boxsize[j];
      }
}
#endif

/*

#ifdef XXLINFO
#ifdef MAGNETIC
  double MeanB_part = 0, MeanB_sum;

#ifdef TRACEDIVB
  double MaxDivB_part = 0, MaxDivB_all;
  double dmax1, dmax2;
#endif
#endif
#ifdef TIME_DEP_ART_VISC
  double MeanAlpha_part = 0, MeanAlpha_sum;
#endif
#endif

#ifdef XXLINFO
      if(Flag_FullStep == 1)
        {
#ifdef MAGNETIC
          MeanB_part += sqrt(SphP[i].BPred[0] * SphP[i].BPred[0] +
                             SphP[i].BPred[1] * SphP[i].BPred[1] + SphP[i].BPred[2] * SphP[i].BPred[2]);
#ifdef TRACEDIVB
          MaxDivB_part = DMAX(MaxDivB, fabs(SphP[i].divB));
#endif
#endif
#ifdef TIME_DEP_ART_VISC
          MeanAlpha_part += SphP[i].alpha;
#endif
        }
#endif

#ifdef XXLINFO
  if(Flag_FullStep == 1)
    {
#ifdef MAGNETIC
      MPI_Reduce(&MeanB_part, &MeanB_sum, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);
      if(ThisTask == 0)
	MeanB = MeanB_sum / All.TotN_gas;
#ifdef TRACEDIVB
      MPI_Reduce(&MaxDivB_part, &MaxDivB_all, 1, MPI_DOUBLE, MPI_MAX, 0, MPI_COMM_WORLD);
      if(ThisTask == 0)
	MaxDivB = MaxDivB_all;
#endif
#endif
#ifdef TIME_DEP_ART_VISC
      MPI_Reduce(&MeanAlpha_part, &MeanAlpha_sum, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);
      if(ThisTask == 0)
	MeanAlpha = MeanAlpha_sum / All.TotN_gas;
#endif
    }
#endif
*/


#ifndef ALLVARSGRID_H
#define ALLVARSGRID_H

#include <stdio.h>
#include <gsl/gsl_rng.h>
#include <stdint.h>

#define ABORT(sigterm)                                                  \
do {                                                                \
  printf("Error in file: %s\tfunc: %s\tline: %i\n", __FILE__, __FUNCTION__, __LINE__); \
  myexit(sigterm);                                                \
} while(0)

#define  STEPS 10         // Number of integration intervals between two snapshots
#define  MAXGRIDOUT 30
#define  MAXGALFAC 1
#define  ALLOCPARAMETER 10.0
#define  MAX_NODE_NAME_LEN 50
#define  ABSOLUTEMAXSNAPS 1000

#define  GRAVITY     6.672e-8
#define  SOLAR_MASS  1.989e33
#define  SOLAR_LUM   3.826e33
#define  RAD_CONST   7.565e-15
#define  AVOGADRO    6.0222e23
#define  BOLTZMANN   1.3806e-16
#define  GAS_CONST   8.31425e7
#define  C           2.9979e10
#define  PLANCK      6.6262e-27
#define  CM_PER_MPC  3.085678e24
#define  PROTONMASS  1.6726e-24
#define  HUBBLE      3.2407789e-18   /* in h/sec */

#define  SEC_PER_MEGAYEAR   3.155e13
#define  SEC_PER_YEAR       3.155e7

#define	 CUBE(x) (x*x*x)

#define MAXLEN 1024

#ifdef NDEBUG
#define XASSERT(EXP, ...)                                do{} while(0)
#else
#define XASSERT(EXP, ...)                                              \
    do { if (!(EXP)) {                                                  \
            printf("Error in file: %s\tfunc: %s\tline: %d with expression `"#EXP"'\n", __FILE__, __FUNCTION__, __LINE__); \
            printf(__VA_ARGS__);                                        \
            fflush(stdout);                                             \
            ABORT(EXIT_FAILURE);                                         \
        } \
    } while (0)
#endif

#ifdef NDEBUG
#define XPRINT(EXP, ...)                                do{} while(0)
#else
#define XPRINT(EXP, ...)                                              \
    do { if (!(EXP)) {                                                  \
            printf("Warning in file: %s\tfunc: %s\tline: %d with expression `"#EXP"'\n", __FILE__, __FUNCTION__, __LINE__); \
            printf(__VA_ARGS__);                                        \
            fflush(stdout);                                             \
        } \
    } while (0)
#endif

struct GALAXY_INPUT
{
  int   SnapNum;
  int   Type;

  long long   GalaxyIndex;
  long long   CentralGalaxyIndex;
  int   SAGEHaloIndex;
  int   SAGETreeIndex;
  int   SimulationFOFHaloIndex;

  int   mergeType;  //0=none; 1=minor merger; 2=major merger; 3=disk instability; 4=disrupt to ICS
  int   mergeIntoID;
  int   mergeIntoSnapNum;
  float dT;

  // (sub)halo properties
  float Pos[3];
  float Vel[3];
  float Spin[3];
  int   Len;
  float Mvir;
  float CentralMvir;
  float Rvir;
  float Vvir;
  float Vmax;
  float VelDisp;

  // baryonic reservoirs
  float ColdGas;
  float StellarMass;
  float BulgeMass;
  float HotGas;
  float EjectedMass;
  float BlackHoleMass;
  float ICS;

  // metals
  float MetalsColdGas;
  float MetalsStellarMass;
  float MetalsBulgeMass;
  float MetalsHotGas;
  float MetalsEjectedMass;
  float MetalsICS;

  // to calculate magnitudes
  float SfrDisk;
  float SfrBulge;
  float SfrDiskZ;
  float SfrBulgeZ;

  // misc
  float DiskScaleRadius;
  float Cooling;
  float Heating;
  float QuasarModeBHaccretionMass;
  float TimeOfLastMajorMerger;
  float TimeOfLastMinorMerger;
  float OutflowRate;

  // infall properties
  float infallMvir;
  float infallVvir;
  float infallVmax;

  // NOTE: Because the code has moved to pointers for the galaxy-grid information
  // They are no longer stored explicitly in the galaxy input struct.

}*Gal;

struct GALAXY_GRID
{
  int TreeNr;

  int32_t *Type;
  int32_t *FoFNr;
  int32_t *History; // Integers that describe the grid position at every redshift.

  float *ColdGas;
  float *HotGas;
  float *EjectedMass;

  float *DustColdGas;
  float *DustHotGas;
  float *DustEjectedMass;

  float *BHMass;
  float *StellarMass; // Units of 1.0e10 Msun/h.
  float *SFR; // Units of Msun yr^-1
  float *Z; // NOT solar metallicity, actual metallicity.
  float *FoFMass; // Units of 1.0e10 Msun/h.
  float *EjectedFraction; // Unitless (fraction).
  int *LenHistory; // Number of particles in FoF Halo.

  int *QuasarActivity;
  int *QuasarSubstep;
  float *DynamicalTime;

  int *LenMergerGal;

  float *ReionMod;

  float *GridNgamma_HI;
  float *Gridfesc;

}*GalGrid;

struct GRID_STRUCT
{

  int32_t GridSize;
  uint64_t NumCellsTotal;
  int32_t NumGrids;

  struct GRID_PROPERTIES_STRUCT
  {
    float *SFR;
    float *StellarMass;
    float *Nion_HI;
    float *Nion_HeI;
    float *Nion_HeII;

    uint64_t *GalCount;

    // TODO: Move these to their own struct.
    /* These properties are per galaxy (not per grid cell) but I'm lazy. */
    int32_t *SnapshotGalaxy;
    float *fescGalaxy;
    float *MvirGalaxy;
    float *MstarGalaxy;
    float *NgammaGalaxy;
    float *NgammafescGalaxy;
  }*GridProperties;
}*Grid;

struct halo_data
{
  // merger tree pointers
  int Descendant;
  int FirstProgenitor;
  int NextProgenitor;
  int FirstHaloInFOFgroup;
  int NextHaloInFOFgroup;

  // properties of halo
  int Len;
  float M_Mean200, Mvir, M_TopHat;  // for Millennium, Mvir=M_Crit200
  float Pos[3];
  float Vel[3];
  float VelDisp;
  float Vmax;
  float Spin[3];
  long long MostBoundID;

  // original position in simulation tree files
  int SnapNum;
  int FileNr;
  int SubhaloIndex;
  float SubHalfMass;
}
*Halo;

struct meraxes_halo_data
{
  double SFR;
  double StellarMass;
  double Mvir;
  double ColdGas;
  double HotGas;
  double EjectedGas;
  double MetalsColdGas;
  double Pos[3];

}
*meraxes_Halo;

extern double UnitLength_in_cm,
  UnitTime_in_s,
  UnitVelocity_in_cm_per_s,
  UnitMass_in_g,
  RhoCrit,
  UnitPressure_in_cgs,
  UnitDensity_in_cgs,
  UnitCoolingRate_in_cgs,
  UnitEnergy_in_cgs,
  UnitTime_in_Megayears,
  G,
  Hubble,
  a0, ar;

extern int Ntrees; // Number of trees per file.
extern int64_t NtotGals; // Number of galaxies per file.
extern int *GalsForTree;

extern int Snaplistlen;
extern double ZZ[ABSOLUTEMAXSNAPS];
extern double AA[ABSOLUTEMAXSNAPS];
extern double Age[ABSOLUTEMAXSNAPS];

extern gsl_rng *random_generator;

extern int MAXSNAPS;

// Parameter File Variables //

extern int FirstFile;    // first and last file for processing
extern int LastFile;

extern int NumGals;     // Total number of galaxies stored for current tree
extern int MaxGals;     // Maximum number of galaxies allowed for current tree

extern int LastSnapShotNr;

extern char GalaxiesInputDir[MAXLEN];
extern char GridOutputDir[512];
extern char FileNameGalaxies[512];
extern char SimulationDir[512];
extern char TreeName[512];
extern char FileWithSnapList[512];

extern int TotHalos;
extern int totNHalos; // Total number of halos in a file.
extern int TotGalaxies[ABSOLUTEMAXSNAPS];
extern int *TreeNgals[ABSOLUTEMAXSNAPS];

extern int *FirstHaloInSnap;

extern int *TreeNHalos;
extern int *TreeFirstHalo;

#ifdef MPI
int ThisTask, NTask, nodeNameLen;
char *ThisNode;
#endif

extern double Omega;
extern double OmegaLambda;
extern double PartMass;
extern double BoxSize;
extern int GridSize;
extern int self_consistent;
extern int GridSnap;
extern int NGrid;

extern double Hubble_h;
extern double EnergySNcode, EnergySN;
extern double EtaSNcode, EtaSN;

// recipe flags
extern int ReionizationOn;
extern int SupernovaRecipeOn;
extern int DiskInstabilityOn;
extern int AGNrecipeOn;
extern int SFprescription;

// recipe parameters
extern double RecycleFraction;
extern double Yield;
extern double FracZleaveDisk;
extern double ReIncorporationFactor;
extern double ThreshMajorMerger;
extern double BaryonFrac;
extern double SfrEfficiency;
extern double FeedbackReheatingEpsilon;
extern double FeedbackEjectionEfficiency;
extern double RadioModeEfficiency;
extern double QuasarModeEfficiency;
extern double BlackHoleGrowthRate;
extern double Reionization_z0;
extern double Reionization_zr;
extern double ThresholdSatDisruption;

extern int LowSnap;
extern int HighSnap;
extern int ListOutputSnaps[ABSOLUTEMAXSNAPS];
extern int ListOutputGrid[ABSOLUTEMAXSNAPS];

extern int Verbose;

// Input parameters for the photon prescription //
extern int PhotonPrescription;
extern int fescPrescription;
extern double fesc;

extern double MH_low;
extern double fesc_low;
extern double MH_high;
extern double fesc_high;

extern double alpha;
extern double beta;

extern double quasar_baseline;
extern double quasar_boosted;
extern double N_dyntime;

extern int HaloPartCut;

extern int32_t *QuasarActivityToggle;
extern int32_t *QuasarActivitySubstep;
extern int32_t *QuasarSnapshot;
extern float *TargetQuasarTime;
extern float *QuasarBoostActiveTime;
extern float *QuasarFractionalPhoton;

extern int QuasarEventsAbovePartCut;
extern int QuasarEventsBelowPartCut;

extern float sum_photons;
#endif


#ifndef _CHAIN_H_
#define _CHAIN_H_

/* ---------------------------------------------------------
   headers
   --------------------------------------------------------- */
#include <stdio.h>
#include <string>
#include <stdlib.h>
#include <iostream>
#include <vector>

// my headers
#include <md.h>
#include <debug.h>

#include "dcdio.h"

// headers 3rd
/* #include <armadillo> */
/* #include <gsl/gsl_math.h> */
/* #include <gsl/gsl_eigen.h> */

/* #include "boost/multi_array.hpp" */
#include <cassert>

/* SYMLINK: Eigen /usr/local/include/. */
/* [dale~/sop_dev/contacts/segment]$lt /usr/local/include/eigen                        */
/* lrwxrwxrwx 1 root root 25 09.25.2015 21:04 /usr/local/include/eigen -> /usr/include/eigen3/Eigen/ */
/* #include<Eigen/Eigenvalues> */
/* #include <Eigen/Dense> */

#ifdef INERTIA
#include <Eigen/Core>
#include <Eigen/Eigenvalues>
using namespace Eigen;
#endif // INERTIA

/* using namespace Eigen; */

 // namespaces
/* using namespace arma; */

/* ---------------------------------------------------------
   definitions
   --------------------------------------------------------- */
/* 2000, 100;  700,20; 500,50 */
#if defined ONEMOL || defined ALLATOM
#define MOLECULE_INITIAL_SIZE 6900
#define MOLECULE_INITIAL_SIZE_MT 6900
#else
#define MOLECULE_INITIAL_SIZE 700
#define MOLECULE_INITIAL_SIZE_MT 700
#endif // ONEMOL
#define MAX_CONTACTS 24 /* per atom */

class Contact {
public:
    /* int resid; */
    int cresid;

    int total;

    int index; // index
    int cindex; // contacted index

    double eh; // energy scaling (well depth)
    double distance;

    // Constructor
    Contact (); // Constructor declared.

    // Destructors:
    ~Contact() {
        /* delete [] pos; */
    };

};
inline Contact::Contact() {
    cresid = -1;
    index = -1;
    cindex = -1;
    total = -1;
    eh = 0.0; // keep at 0.0, used in assign_contact_eh as default
    distance = 0.0;
}

typedef struct Chain Molecule;

class Chain {
public:
    char filename[46];

    int chainid;
    int num_atoms;
    int num_atoms_ca;

    int index;
    int findex;
    int pf_pos_low; // for selecting index near midpoint of centroids, curvature of PF
    int pf_pos_high;
    int file_line_begin;
    int file_line_end;

    char atomtype[MOLECULE_INITIAL_SIZE][4];
    char resname[MOLECULE_INITIAL_SIZE][4];
    int resid[MOLECULE_INITIAL_SIZE];
    int indices[MOLECULE_INITIAL_SIZE]; // not used yet.

    Vector pos[MOLECULE_INITIAL_SIZE];
    Contact contacts[MOLECULE_INITIAL_SIZE][MAX_CONTACTS];

    int total_num_contacts_2x;
    int tally_num_contacts_2x[MOLECULE_INITIAL_SIZE];
    int num_contacts_2x_persist_ref[MOLECULE_INITIAL_SIZE];

    Vector centroid;
    int nearest_targets[32];
    double nearest_targets_dist[32];
    int nearestneighbors_chainid[8];
    double nearestneighbors_dist[8];

    int LonN; // contacted monomer
    int LatE;
    int LonS;
    int LatW;
    int LonN_total2x; // total
    int LatE_total2x;
    int LonS_total2x;
    int LatW_total2x;

    // general inter
    Contact intercontacts[MOLECULE_INITIAL_SIZE][MAX_CONTACTS];

    // latlon contacts
    Contact contactsLonN[MOLECULE_INITIAL_SIZE][MAX_CONTACTS];
    Contact contactsLatE[MOLECULE_INITIAL_SIZE][MAX_CONTACTS];
    Contact contactsLonS[MOLECULE_INITIAL_SIZE][MAX_CONTACTS];
    Contact contactsLatW[MOLECULE_INITIAL_SIZE][MAX_CONTACTS];

    // Constructors:
    Chain(int a=-1, int b = 0, double p = 0.0, double q = -1.0) {
        chainid = a;
        num_atoms = a;
        num_atoms_ca = a;
        index = a;
        findex = a;
        pf_pos_low = a;
        pf_pos_high = a;
        file_line_begin = a;
        file_line_end = a;
        total_num_contacts_2x = a;

        centroid.x = p;
        centroid.y = p;
        centroid.z = p;

        LonN = a;
        LonS = a;
        LatW = a;
        LatE = a;

        // initialize to 0.0 p
        for (int i=0; i < MOLECULE_INITIAL_SIZE; i++ ) {
            pos[i].x = p;
            pos[i].y = p;
            pos[i].z = p;

            // initialize to 0 b
            tally_num_contacts_2x[i] = b;
            num_contacts_2x_persist_ref[i] = b;

            for ( int j=0; j < MAX_CONTACTS; j++ ) {
                contacts[i][j].cresid = a;
                contacts[i][j].distance = p;
            }
            for ( int j=0; j < 8; j++ ) {
                nearestneighbors_chainid[j] = -1;
                nearestneighbors_dist[j] = p;
            }
            for ( int j=0; j < 32; j++ ) {
                nearest_targets[j] = a;
            }
        }
    }

    // Destructors:
    ~Chain() {
        /* delete [] pos; */
        /* std::cout << "Object is being deleted" << std::endl; */
    };

    // Declarations:
    void print_prop();
    void assign_indices();
    void fprintf_indices();
    void GetMomentofInertiaTensor();
    void ComputeCentroid();
    void print_centroid();

    int countcontacts(Contact (*map)[MAX_CONTACTS]);

};
inline void Chain::ComputeCentroid() {
    int i;
    double xtot = 0.0, ytot = 0.0, ztot = 0.0;
    /* printf("computing centroid ca: %d\n",num_atoms_ca); */
    for ( int i=0; i < num_atoms_ca; i++ ) {
        /* printf("%d\n",i); */
        xtot += pos[i].x;
        ytot += pos[i].y;
        ztot += pos[i].z;
    }
    centroid.x = xtot / num_atoms_ca;
    centroid.y = ytot / num_atoms_ca;
    centroid.z = ztot / num_atoms_ca;
    // PRINT HERE
    /* debug("c(%d): %f %f %f\n",chainid,centroid.x,centroid.y,centroid.z); */
}

// amazing!!
/* By declared that method with inline keyword, compiler will either inline */
/*     the whole method or, if it decides not to, it will generate anonymous */
/*     method (same method but with some unique name for a given object file), */
/*     so there will be no conflicts in object files. For example: */
/* class foo { */
/* public: */
/*   void bar (); */
/* }; */
/* inline void foo::bar () */
/* { */
/* } */
inline void Chain::print_prop() {
    /* printf("id: %d ",chainid); */
    /* printf(" filename: %s\n",filename); */
    /* printf("CA: %d ",num_atoms_ca); */
    /* printf(" index(%d) ",index); */
    /* printf(" final_index(%d)\n",findex); */
    /* printf("begin_end: [ %d %d ]\n\n",file_line_begin,file_line_end); */

    /* debug("id: %d   CA: %d\n",chainid,num_atoms_ca); */
    /* debug(" filename: %s\n",filename); */
    /* debug("CA: %d \n",num_atoms_ca); */
    /* debug(" index(%d) \n",index); */
    /* debug(" final_index(%d)\n",findex); */
    /* debug("begin_end: [ %d %d ]\n",file_line_begin,file_line_end); */

    printf("centroid: %f %f %f\n",centroid.x,centroid.y,centroid.z);
}
inline void Chain::print_centroid() {
    printf("centroid: %f %f %f\n",centroid.x,centroid.y,centroid.z);
}
inline void Chain::assign_indices() {
    /* printf("id: %d ",chainid); */
    /* printf(" filename: %s\n",filename); */
    /* printf("CA: %d ",num_atoms_ca); */
    /* printf(" index(%d) ",index); */
    /* printf(" final_index(%d)\n",findex); */
    /* printf("begin_end: [ %d %d ]\n\n",file_line_begin,file_line_end); */

    debug("id: %d   atoms:%d   CA:%d\n",chainid,num_atoms,num_atoms_ca);
    /* debug(" filename: %s\n",filename); */
    /* debug("CA: %d \n",num_atoms_ca); */
    /* debug(" index(%d) \n",index); */
    /* debug(" final_index(%d)\n",findex); */
    debug("begin_end: [ %d %d ]\n",file_line_begin,file_line_end);

    for( int i=0; i < num_atoms_ca; i++ ) {
        indices[i] = index + i;
    }
}
inline void Chain::fprintf_indices() {
    /* printf(" index(%d) ",index); */
    /* printf(" final_index(%d)\n",findex); */
    printf(" %d__[%d|%d]\n",chainid,index,findex);

    FILE * fp_indices;
    fp_indices = fopen("indices_foreach_segments.dat","a+");
    for ( int i=0; i < num_atoms_ca; i++ ) {
        fprintf(fp_indices,"%6d  %6d  %6d\n",chainid,index,findex);
    }
    fclose(fp_indices);

    /* for( int i=0; i < num_atoms_ca; i++ ) { */
    /*     /\* indices[i] = index + i; *\/ */
    /*     printf(" %d",indices[i]); */
    /*     printf(" ") */
    /* } */
    /* printf("\n"); */
}

inline int Chain::countcontacts(Contact (*map)[MAX_CONTACTS]) {

    /* printf("declare countcontacts!\n"); */
    int count = 0;

    // using 0,3 indices. 1,4, 2,5, ... 430,433
    for(int i=0; i<MOLECULE_INITIAL_SIZE; i++){
        // for(int j=0; j<MAX_CONTACTS; j++){
        for(int k=0; k<MAX_CONTACTS; k++){

            if(map[i][k].cresid == -1){
                break;
            } else {
                count ++;
                // printf("%d-%d (%d)  %d/50\n",i,map[i][k].cresid,count,k);
            }
            // printf(" %d",map[i][k].index);
            // map[num1][k].cindex = chain[num1].indices[j];
            // double eh; // energy scaling (well depth)
            // map[num1][k].distance = dist;

            // if(map[i][k].cresid != -1){
            //     count ++;
            // } // if
            // } // for 3
            // } // if
        } // for 2
    } // for 1

    if(count != 0){
        debug("declare total contacts in this map are: %d\n",count);
    } else {
        debug("empty map.\n");
    }
    // // map[chain_num].total = count;

    return count;
}

#ifdef INERTIA
inline void Chain::GetMomentofInertiaTensor() {

    /* printf("chain: %d;  atoms: %d;\n",chainid,num_atoms_ca); */
    double x2=0.0,y2=0.0,z2=0.0;
    double xy=0.0,xz=0.0,yz=0.0;

    double Ixx=0.0,Iyy=0.0,Izz=0.0;
    double Ixy=0.0,Ixz=0.0,Iyz=0.0;

    /* http://farside.ph.utexas.edu/teaching/336k/Newtonhtml/node64.html moment of Inertia Tensor */
    /* http://farside.ph.utexas.edu/teaching/336k/Newtonhtml/node67.html get eigenvectors */
    for(int a=0; a<num_atoms_ca; a++) {
    /* for(int a=0; a<5; a++) { */
        /* printf("%d",a); */
        /* printf("%f %f %f\n",pos[a].x,pos[a].y,pos[a].z); */
        x2 += pos[a].x * pos[a].x;
        y2 += pos[a].y * pos[a].y;
        z2 += pos[a].z * pos[a].z;

        xy += pos[a].x * pos[a].y;
        xz += pos[a].x * pos[a].z;
        yz += pos[a].y * pos[a].z;

        Ixx += y2 + z2;
        Iyy += x2 + z2;
        Izz += x2 + y2;

        Ixy += pos[a].x * pos[a].y;
        Ixz += pos[a].x * pos[a].z;
        Iyz += pos[a].y * pos[a].z;
    }

    Ixy = Ixy * -1.0;
    Ixz = Ixz * -1.0;
    Iyz = Iyz * -1.0;

    printf("xx_yy_zz: %f %f %f\n",Ixx,Iyy,Izz);
    printf("xy_xz_yz: %f %f %f\n",Ixy,Ixz,Iyz);

    // begin eigen library

    Matrix3d A(3,3);
    A(0,0) = Ixx;
    A(0,1) = Ixy;
    A(0,2) = Ixz;

    A(1,0) = Ixy;
    A(1,1) = Iyy;
    A(1,2) = Iyz;

    A(2,0) = Ixz;
    A(2,1) = Iyz;
    A(2,2) = Izz;
    /* std::cout << "Here is the matrix A:\n" << A << std::endl; */

    // build matrix
    double InertiaTensor[3][3] = {
        {Ixx, Ixy, Ixz},
        {Ixy, Iyy, Iyz},
        {Ixz, Iyz, Izz},
    };

    // Ixy xz yz are altered beyond this point.
    double lowest_value_diag = Ixx;
    if ( Iyy < Ixx ) {
        if ( Izz < Iyy ) {
            lowest_value_diag = Izz;
        } else {
            lowest_value_diag = Iyy;
        }
    } else if ( Izz < Ixx ) {
        lowest_value_diag = Izz;
    }

    double lowest_value_offdiag;
    if ( Ixy < 0.0 ) {
        Ixy = -1.0 * Ixy;
    }
    if ( Ixz < 0.0 ) {
        Ixz = -1.0 * Ixz;
    }
    if ( Iyz < 0.0 ) {
        Iyz = -1.0 * Iyz;
    }

    lowest_value_offdiag = Ixy;
    if ( Ixz < Ixy ) {
        if ( Iyz < Ixz ) {
            lowest_value_offdiag = Iyz;
        } else {
            lowest_value_offdiag = Ixz;
        }
    } else if ( Iyz < Ixy ) {
        lowest_value_offdiag = Iyz;
    }

    double lowest_value;
    if ( lowest_value_offdiag < lowest_value_diag ) {
        lowest_value = lowest_value_offdiag;
    } else {
        lowest_value = lowest_value_offdiag;
    }

    printf("lv: %f\n",lowest_value);
    lowest_value = lowest_value * 0.95;
    /* printf("lv: %f\n",lowest_value); */
    // Matrix entries * lowest_value;

    /* // ORIGINAL EXAMPLE */
    /* // Create a 3D array that is 3 x 4 x 2 */
    /* typedef boost::multi_array<double, 3> array_type; */
    /* typedef array_type::index index; */
    /* array_type A(boost::extents[3][4][2]); */

    /* // Assign values to the elements */
    /* int values = 0; */
    /* for(index i = 0; i != 3; ++i) */
    /*     for(index j = 0; j != 4; ++j) */
    /*         for(index k = 0; k != 2; ++k) */
    /*             A[i][j][k] = values++; */

    /* // Verify values */
    /* int verify = 0; */
    /* for(index i = 0; i != 3; ++i) */
    /*     for(index j = 0; j != 4; ++j) */
    /*         for(index k = 0; k != 2; ++k) */
    /*             assert(A[i][j][k] == verify++); */
    /* /\* return 0; *\/ */

    /* ---------------------------------------------------------
       boost! no eigenvalue/eigenvector
       --------------------------------------------------------- */
    /* // Create a 3D array that is 3 x 4 x 2 */
    /* typedef boost::multi_array<double, 2> array_type; */
    /* typedef array_type::index index; */
    /* array_type A(boost::extents[3][3]); */
    /* array_type B(boost::extents[3][3]); */

    /* // x row */
    /* A[0][0] = Ixx; */
    /* A[0][1] = Ixy; */
    /* A[0][2] = Ixz; */

    /* // y row */
    /* A[1][0] = Ixy; */
    /* A[1][1] = Iyy; */
    /* A[1][2] = Iyz; */

    /* // y row */
    /* A[2][0] = Ixz; */
    /* A[2][1] = Iyz; */
    /* A[2][2] = Izz; */

    /* /\* // Verify values *\/ */
    /* /\* int verify = 0; *\/ */
    /* /\* for(index i = 0; i != 3; i++) { *\/ */
    /* /\*     for(index j = 0; j != 3; j++) { *\/ */
    /* /\*         /\\* assert(A[i][j] == verify++); *\\/ *\/ */
    /* /\*         // reassign by lowest_value scaling *\/ */
    /* /\*         printf("A: %f\n",A[i][j]); *\/ */
    /* /\*     } *\/ */
    /* /\* } *\/ */

    /* // Verify values A */
    /* for(index i = 0; i != 3; i++) { */
    /*     for(index j = 0; j != 3; j++) { */
    /*         // reassign by lowest_value scaling */
    /*         A[i][j] = A[i][j] / lowest_value; */
    /*         printf("A/d: %f\n",A[i][j]); */
    /*     } */
    /* } */
    /* ---------------------------------------------------------
       boost! no eigenvalue/eigenvector - END
       --------------------------------------------------------- */

    /* gsl_matrix_view m = gsl_matrix_view_array (InertiaTensor, 3, 3); */
    /* gsl_vector *eval = gsl_vector_alloc (3); */
    /* gsl_matrix *evec = gsl_matrix_alloc (3, 3); */

    /* gsl_eigen_symmv_workspace * w = gsl_eigen_symmv_alloc (3); */
    /* gsl_eigen_symmv (&m.matrix, eval, evec, w); */
    /* gsl_eigen_symmv_free (w); */
    /* gsl_eigen_symmv_sort (eval,evec,GSL_EIGEN_SORT_ABS_ASC); */
    /* { */
    /*     int i; */

    /*     for (i = 0; i < 4; i++) */
    /*     { */
    /*         double eval_i */
    /*             = gsl_vector_get (eval, i); */
    /*         gsl_vector_view evec_i */
    /*             = gsl_matrix_column (evec, i); */

    /*         printf ("eigenvalue = %g\n", eval_i); */
    /*         printf ("eigenvector = \n"); */
    /*         gsl_vector_fprintf (stdout, */
    /*                             &evec_i.vector, "%g"); */
    /*     } */
    /* } */

    /* gsl_vector_free (eval); */
    /* gsl_matrix_free (evec); */

    /* return 0; */

    // eigen continued
    A = A / lowest_value;
    std::cout << "Here is the matrix A:\n" << A << std::endl;

    VectorXcd eivals = A.eigenvalues();
    std::cout << "The eigenvalues of the 3x3 matrix of ones are:" << std::endl << eivals << std::endl;

    /* VectorXcd eivecs = A.eigenvectors(); */
    /* std::cout << "The eigenvalues of the 3x3 matrix of ones are:" << std::endl << eivecs << std::endl; */

    EigenSolver<MatrixXd> es(A);
    std::cout << "The first eigenvector of the 3x3 matrix of ones is:"
              << std::endl << es.eigenvectors().col(0) << std::endl;

    std::cout << "The second eigenvector of the 3x3 matrix of ones is:"
              << std::endl << es.eigenvectors().col(1) << std::endl;

    std::cout << "The third eigenvector of the 3x3 matrix of ones is:"
              << std::endl << es.eigenvectors().col(2) << std::endl;

    /* Matrix3d m = Matrix3d::(3,3); */
    /* cout << "m =" << endl << m << endl; */

}
#endif // INERTIA

/* class Map { */
/*     /\* int num_atoms,max_contacts_per_atom; *\/ */
/* public: */

/*     /\* std::vector< std::vector< std::vector<int>>> map; *\/ */
/*     std::vector< std::vector<int> > map; */
/*     /\* std::vector *\/ */
/*     std::vector<Contact> contacts; */

/*     // Constructor */
/*     /\* Map(); *\/ */

/*     /\* declarations: *\/ */
/*     /\* void extend_map(int); *\/ */
/*     void build_map_default(Chain *chain, int cid); // use chain_ref */

/* }; */
/* /\* inline Map::extend_map(int ca){ *\/ */
/* /\*     for (int i=0; i<ca; i++) { *\/ */
/* /\*         map.push_back(i); // ADD the ROW. *\/ */
/* /\*     } *\/ */
/* /\* } *\/ */
/* inline void Map::build_map_default(Chain *chain,int cid){ */
/*     // default: 8A */

/*     /\* int count; *\/ */
/*     double dist; */
/*     Contact x; */
/*         /\* ,y; *\/ */

/*     for (int i=0; i<chain[cid].num_atoms_ca; i++) { */
/*         /\* printf("%d\n",i); *\/ */

/*         for(int j=i; j<chain[cid].num_atoms_ca; j++) { */

/*             if ( i >= j-2 && i <= j+2) { */
/*                 // exclude j = i +/- 2 */
/*                 /\* printf("%d\t no --> %d j: %d %d\n",i,j,j-2,j+2); *\/ */
/*             } else { */

/*                 /\* printf("%d\n",j); *\/ */
/*                 dist = distance(chain[cid].pos[i],chain[cid].pos[j]); */
/*                 /\* printf("%8.4f\n",dist); *\/ */

/*                 if ( dist <= 8.0 ) { */

/*                     x.cresid = j; */
/*                     x.distance = dist; */
/*                     x.index = i + chain[cid].index; */
/*                     x.cindex = j + chain[cid].index; */

/*                     contacts.insert(contacts.end(),x); */
/*                 } /\* END 8.0 *\/ */
/*             } // else/if */

/*         } // j */

/*         /\* map.push_back(contacts); *\/ */
/*         /\* contacts.clear(); *\/ */
/*     } // i */

/*     /\* std::vector<int>::iterator it; *\/ */
/*     /\* for (it=contacts.begin(); it<contacts.end(); it++) { *\/ */
/*     /\*     // std::cout << *it << ' ' << std::endl; *\/ */
/*     /\*     printf("%d ",*it); *\/ */
/*     /\* } *\/ */

/*     /\* std::vector<int>::iterator iter; // declare a general iterator *\/ */
/*     for ( int k = contacts.begin(); k < contacts.end(); k++){ */
/*         printf("%d\n",contacts[k].index); */
/*     } */

/* } */

/* inline Map::Map() { */
/*     // constructor */

/*     for (int i=0; i<num_atoms; i++ ) { */
/*         /\* printf("%d\n",i); *\/ */
/*         for (int j=0; j<max_contacts_per_atom; j++){ */
/*             /\* if ( mol->contacts[i][j].cresid != 0 || mol->contacts[i][j].distance != 0.0) { *\/ */
/*             /\* printf("%d %f\n",mol->contacts[i][j].cresid,mol->contacts[i][j].distance); *\/ */
/*             contacts[i][j].cresid = -1; */
/*             contacts[i][j].distance = 0.0; */
/*         } */
/*     } */
/* } */
/* inline void Map::get_contacts() { */

/* } */
/* inline void Map::set_values (int natoms, int max_contacts) { */
/*     num_atoms = natoms; */
/*     max_contacts_per_atom = max_contacts; */

/* } */

/* ---------------------------------------------------------
   Molecule preparations:
   --------------------------------------------------------- */
void build_contact_map(Molecule *mol);
void build_latlon_contact_map(Chain *chain, int num_chains, int start, int stop,\
                              Contact (*map)[MAX_CONTACTS],int sel);
void build_contact_map_inter_monomer(Chain *chain,int cid, Contact (*map)[MAX_CONTACTS], int sel);
void build_contact_map_inter(Chain *chain,int cid1,int cid2,Contact (*map)[MAX_CONTACTS]);

void compare_contacts(Chain *chref,Chain *chain,int cid, int sel);
void evaluate_original_contacts_now(Chain *chref,Chain *chain,int cid, int sel);

void print_contact_map(Contact (*map)[MAX_CONTACTS]);
void fprintf_count_of_contacts_2x_persist_from_reference( Molecule *m1, Molecule *m2);
void fprintf_bond_vector_angle_with_tension_vector( Molecule *m1, Molecule *m2 );
void fprintf_contacts_for_one_monomer_face(Chain *chain, int num_chains, int sel);
void fprintf_contact_map_gsop(Contact (*map)[MAX_CONTACTS]);
void fprintf_all_indices(Chain *chref,int num_chains);

void tubulin_monomers_angles( Molecule *m1, Molecule *m2, int r1, int r2, FILE *fp);
void get_tubulin_centroid( Molecule *m1, Vector *com);
void get_centroid_from_array( Vector arr_centroid[],int i, int j, Vector *centroid);
Vector get_centroid_from_centroids(Chain *chain,int start_chain,int stop_chain);
void get_centroid_movement(Chain *chain_ref,Chain *chain_later,int num_chains);
void get_centroid_xyzposition_movement(Chain *chain_later,int chains_to_use,FILE *fp_xyzpos);
/* void get_centroid_xyzposition_movement(chain_later,chains_to_use,fp_xyzpos); */

/* void get_latangle_from35centroids(Chain *chain_ref,Chain *chain_later,int m,int sel); */
void get_latangle_from35centroids(Chain *chain_ref,Chain *chain_later,int m,FILE *fp,int sel);
std::vector<double> get_3angles_dimerbyalpha(Chain *chain_ref,Chain *chain_later,int cid);

/* void get_latangle_from35centroids(Chain *chain_ref,Chain *chain_later,int m,int sel,FILE *fp_ang); */

void get_midpoint_reference_indices(Chain *chain,int i,int *index_ref_i,int *index_ref_c,int sel);
/* get_midpoint_reference_indices(chain_ref,i,&index_ref_i,&index_ref_c,1); */

void compute_nearest_neighbors(Chain *chain, int num_chains);
void determine_latlon_neighbors(Chain *chains, int num_chains, Vector axis);
void get_monomer_monomer_info( Molecule *m1, Molecule *m2,\
                               Vector ref_norm, Vector m1centroid,\
                               Vector m2centroid );

/* void load_dcd_coords_to_chain(dcdhandle *dcd,Chain *chain,int num_chains); */
/* void load_chain_coords_to_timestep(Chain *chain,int num_chains,\ */
/*                                    const molfile_timestep_t *ts); */

void print_chain_coords(Chain *chain,int num_chains);

// overloaded.
void assign_contact_eh(Contact (*map)[MAX_CONTACTS],float eh,int low_resid,\
                       int high_resid,int low_cresid,int high_cresid);
void assign_contact_eh(Contact (*map)[MAX_CONTACTS],float eh);

/* void dcd_to_chain(ddc,chain_0); */
/* void dcd_to_chain(dcdhandle *v,Chain *chain); */

/* void load_chain_coords_to_timestep(dcdhandle *dcd,Chain *chain,int num_chains,\ */
/*                                    void *v, const molfile_timestep_t *ts,int natoms); */
/* load_chain_coords_to_timestep(dcdw,chain_later,chains_to_use,vw,&timestep_w,natoms_w); */

/* static int read_next_timestep(void *v, int natoms, molfile_timestep_t *ts) { */
/* static int write_timestep(void *v, const molfile_timestep_t *ts) { */

#endif


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_vector.h>
#include <gsl/gsl_interp.h>

#include "aXe_grism.h"
#include "aXe_utils.h"
#include "spce_PET.h"
#include "inout_aper.h"
#include "trace_conf.h"
#include "aper_conf.h"
#include "spc_sex.h"
#include "disp_conf.h"
#include "spc_wl_calib.h"
#include "spce_binning.h"
#include "spc_optimum.h"
#include "spc_spc.h"
#include "fringe_conf.h"
#include "spc_resp.h"
#include "spc_FITScards.h"

#define AXE_IMAGE_PATH   "AXE_IMAGE_PATH"
#define AXE_OUTPUT_PATH  "AXE_OUTPUT_PATH"
#define AXE_DRIZZLE_PATH "AXE_DRIZZLE_PATH"
#define AXE_CONFIG_PATH  "AXE_CONFIG_PATH"

int
main (int argc, char *argv[])
{
  char *opt;

  char grism_file[MAXCHAR];
  char grism_file_path[MAXCHAR];

  char aper_file[MAXCHAR];
  char aper_file_path[MAXCHAR];

  char obj_PET_file[MAXCHAR];
  char obj_PET_file_path[MAXCHAR];

  char bck_PET_file[MAXCHAR];
  char bck_PET_file_path[MAXCHAR];

  char conf_file[MAXCHAR];
  char conf_file_path[MAXCHAR];

  char SPC_file[MAXCHAR];
  char SPC_file_path[MAXCHAR];

  char SPC_opt_file[MAXCHAR];
  char SPC_opt_file_path[MAXCHAR];

  char WHT_file[MAXCHAR];
  char WHT_file_path[MAXCHAR];

  char label[MAXCHAR];

  int i, index, dobck = 0, noflux = 1;

  object **oblist;

  FITScards      *cards;

  ap_pixel *obj_PET = NULL, *bck_PET = NULL;

  observation *obs;

  //tracestruct *trace;
  aperture_conf *conf;

  spectrum *obj_spec = NULL, *bck_spec = NULL, *sobj_spec = NULL;
  spectrum *resp;
  response_function *resp_func;
  calib_function *wl_calibration;

  fitsfile *OPET_ptr, *BPET_ptr;
  int f_status = 0;

  fitsfile *SPC_ptr, *SPC_opt_ptr, *WHT_ptr;

  gsl_matrix   *weights;
  drzstamp     *modvar;

  int obj_aperID, obj_beamID, objindex;
  int bck_aperID, bck_beamID;

  char table[MAXCHAR], table_path[MAXCHAR];
  //char comment[FLEN_COMMENT];
  int empty;
  int drizzle;
  int quant_cont=0;
  int opt_weights=0;
  int for_grism=0;
  int smooth_conv=0;

  d_point smooth_params;

  double exptime;
  double sky_cps;

  drzstamp_dim  dimension;
  gsl_matrix *coverage = NULL;

  if (((argc < 3))
      || (opt = get_online_option ("help", argc, argv)))
    {
      fprintf (stdout,
	       "aXe_PET2SPC Version %s:\n"
	       "             aXe task that produces 1-D, binned spectra using information\n"
	       "             contained in a OPET and a BPET. A 1-D spectrum is generated\n"
	       "             for each beam in each of both the OPET and the BPET files\n"
	       "             The binned background spectra of each beam (order) is then\n"
	       "             subtraced from the corresponding spectra form the OPET. Th\ne"
	       "             background subtraction (and reading a BPET altogether can be\n"
	       "             avoided by using the -noBPET option\n"
	       "             An SPC file, a multi-extension FITS file containing binned\n"
	       "             spectra is produced. Each extension (named after the beam ID,\n"
	       "             e.g. 11B for aperture (object) 11,beam (order) B) contains the\n"
	       "             following columns:\n"
	       "\n"
	       "              LAMBDA      ; the wavelength (in A)\n"
	       "              TCOUNT      ; the total number of counts (in DN)\n"
	       "              TERROR      ; the error in TERRORS (in DN)\n"
	       "              BCOUNT      ; the estimated number of counts from the\n"
	       "                            background (in DN)\n"
	       "              BERROR      ; the error in BCOUNTS (in DN)\n"
	       "              COUNT       ; the estimated number of counts from the\n"
	       "                            object (in DN)\n"
	       "              ERROR       ; the error in COUNTS (in DN)\n"
	       "              FLUX        ; the estimated flux (in erg/s/cm^2/A)\n"
	       "              FERROR      ; the error in FLUX (in erg/s/cm^s/A)\n"
	       "              WEIGHT      ; weight (in pixels)\n"
	       "\n"
	       "             Input FITS mages are looked for in $AXE_IMAGE_PATH\n"
	       "             aXe config file is looked for in $AXE_CONFIG_PATH\n"
	       "             All outputs are writen to $AXE_OUTPUT_PATH\n"
	       "\n"
	       "Usage:\n"
	       "     aXe_PET2SPC [g/prism image filename] [aXe config file name] [options]\n"
	       "\n"
	       "Options:\n"
	       "             -noBPET         - to disable the use of a BPET file\n"
	       "             -noflux         - to disable the flux calibration\n"
	       "             -drz            - use $AXE_DRIZZLE_PATH to locate the grism-, OAF-\n"
	       "                             - and PET-files instead of $AXE_IMAGE/OUTPUT_PATH\n"
	       "             -in_AF=[string] - overwrite the default input Aperture File name\n"
	       "             -OPET=[string]  - overwrite the default input Object PET file name\n"
	       "             -BPET=[string]  - overwrite the default input Background PET\n"
	       "                               file name\n"
	       "             -out_SPC=[string] - overwrite the default output SPC file name\n"
	       "\n"
	       "Example:\n"
	       "       ./aXe_PET2SPC slim_grism.fits SLIM.conf.A.0\n"
	       "\n",RELEASE);
      exit (1);
    }

  // make a general opening statement
  fprintf (stdout, "aXe_PET2SPC: Starting...\n");

  // get the name of the flt-file
  index = 0;
  strcpy (grism_file, argv[++index]);
  if ((opt = get_online_option ("drz", argc, argv)))
    {
      build_path (AXE_DRIZZLE_PATH, grism_file, grism_file_path);
      drizzle=1;
    }
  else
    {
      build_path (AXE_IMAGE_PATH, grism_file, grism_file_path);
      drizzle=0;
    }

  // get the name of the configuration file
  strcpy (conf_file, argv[++index]);
  build_path (AXE_CONFIG_PATH, conf_file, conf_file_path);

  // load the configuration file
  conf = get_aperture_descriptor (conf_file_path);

  // Determine where the various extensions are in the FITS file
  get_extension_numbers(grism_file_path, conf,conf->optkey1,conf->optval1);

  // Build aperture file name
  replace_file_extension (grism_file, aper_file, ".fits",
			  ".OAF", conf->science_numext);
  if (drizzle)
    build_path (AXE_DRIZZLE_PATH, aper_file, aper_file_path);
  else
    build_path (AXE_OUTPUT_PATH, aper_file, aper_file_path);

  // Build object PET file name
  replace_file_extension (grism_file, obj_PET_file, ".fits",
			  ".PET.fits", conf->science_numext);
  // make the total filename
  if (drizzle)
    build_path (AXE_DRIZZLE_PATH, obj_PET_file, obj_PET_file_path);
  else
    build_path (AXE_OUTPUT_PATH, obj_PET_file, obj_PET_file_path);

  // Build background PET file name
  replace_file_extension (grism_file, bck_PET_file, ".fits",
			  ".BCK.PET.fits", conf->science_numext);

  // make the total filename
  if (drizzle)
    build_path (AXE_DRIZZLE_PATH, bck_PET_file, bck_PET_file_path);
  else
    build_path (AXE_OUTPUT_PATH, bck_PET_file, bck_PET_file_path);

  // make a non-standard AF name if necessary
  if ((opt = get_online_option ("in_AF", argc, argv)))
    {
      strcpy(aper_file,opt);
      strcpy(aper_file_path,opt);
    }

  // make a non-standard PET name if necessary
  if ((opt = get_online_option ("OPET", argc, argv)))
    {
      strcpy(obj_PET_file,opt);
      strcpy(obj_PET_file_path,opt);
    }

  // make a non-standard BPET name if necessary
  if ((opt = get_online_option ("BPET", argc, argv)))
    {
      strcpy(bck_PET_file,opt);
      strcpy(bck_PET_file_path,opt);
    }

  // set the flagg for no background subtraction
  if ((opt = get_online_option ("noBPET",argc,argv)))
    {
      dobck = 0;
      strcpy(bck_PET_file,"None");
      strcpy(bck_PET_file_path, "None");
    }
  else
    {
      dobck = 1;
    }

  // set the flagg for flux
  if ((opt = get_online_option ("noflux",argc,argv)))
    noflux = 1;
  else
    noflux = 0;

  // check for the weights flagg,
  // set the file name if the flagg is set
  if ((opt = get_online_option ("opt_weights",argc,argv)))
    opt_weights = 1;
  else
    opt_weights = 0;

  // read the trigger for smoothing the sensitivity
  if ((opt = get_online_option ("smooth_conv",argc,argv)))
    smooth_conv = 1;
  else
    smooth_conv = 0;

  if ((opt = get_online_option ("out_SPC", argc, argv)))
    {
      strcpy (SPC_file, opt);
      strcpy (SPC_file_path, opt);

      if (opt_weights)
	{
	  replace_file_extension (SPC_file, SPC_opt_file, ".fits",
			      "_opt.SPC.fits", -1);
	  replace_file_extension (SPC_file_path, SPC_opt_file_path, ".fits",
			      "_opt.SPC.fits", -1);
	  replace_file_extension (SPC_opt_file, WHT_file, ".SPC.fits",
				  ".WHT.fits", -1);
	  replace_file_extension (SPC_opt_file_path, WHT_file_path, ".SPC.fits",
				  ".WHT.fits", -1);
	}
    }
  else
    {
      replace_file_extension (obj_PET_file, SPC_file, ".PET.fits",
			      ".SPC.fits", -1);
      if (drizzle)
	build_path (AXE_DRIZZLE_PATH, SPC_file, SPC_file_path);
      else
	build_path (AXE_OUTPUT_PATH, SPC_file, SPC_file_path);

      if (opt_weights)
	{
	  replace_file_extension (obj_PET_file, SPC_opt_file, ".PET.fits",
				  "_opt.SPC.fits", -1);
	  replace_file_extension (SPC_opt_file, WHT_file, ".SPC.fits",
				  ".WHT.fits", -1);
	  if (drizzle)
	    {
	      build_path (AXE_DRIZZLE_PATH, SPC_opt_file, SPC_opt_file_path);
	      build_path (AXE_DRIZZLE_PATH, WHT_file, WHT_file_path);
	    }
	  else
	    {
	      build_path (AXE_OUTPUT_PATH, SPC_opt_file, SPC_opt_file_path);
	      build_path (AXE_OUTPUT_PATH, WHT_file, WHT_file_path);
	    }
	}
    }

  // check the configuration file for the smoothin keywords
  if (!check_conf_for_smoothing(conf, smooth_conv))
    aXe_message(aXe_M_FATAL, __FILE__, __LINE__,
        "aXe_PET2SP: Either the configuration file %s does not contain\n"
        "the necessary keywords for the smoothing (POBJSIZE, SMFACTOR),\n"
        "or one of these keywords has an unreasonable value < 0.0!\n",
        conf_file_path);

  // give feedback onto the screen:
  // report on input and output
  // and also on specific parameter
  fprintf (stdout, "aXe_PET2SPC: Input configuration file name:   %s\n",
	   conf_file_path);
  fprintf (stdout, "aXe_PET2SPC: Input Object Aperture file name: %s\n",
	   aper_file_path);
  fprintf (stdout, "aXe_PET2SPC: Input Object PET file name:      %s\n",
	   obj_PET_file_path);
  if (dobck)
    fprintf (stdout, "aXe_PET2SPC: Input Background PET file name:  %s\n",
	     bck_PET_file_path);
  fprintf (stdout, "aXe_PET2SPC: Output SPC file name:            %s\n",
	   SPC_file_path);
  if (opt_weights)
    {
      fprintf (stdout, "aXe_PET2SPC: Computing optimal weights.\n");
      fprintf (stdout, "aXe_PET2SPC: Optimized SPC file name:         %s\n",
	       SPC_opt_file_path);
      fprintf (stdout, "aXe_PET2SPC: Output WHT file name:            %s\n",
	       WHT_file_path);
    }
  if (!noflux)
    {
      fprintf (stdout, "aXe_PET2SPC: Performing flux calibration.\n");
      if (smooth_conv)
	fprintf (stdout, "aXe_PET2SPC: Using smoothed sensitivity curves.\n");
    }
  fprintf (stdout, "\n\n");

  //
  // try to get the descriptor 'exptime' from the 'sci'-extension
  //
  exptime = (double)get_float_from_keyword(grism_file_path, conf->science_numext, conf->exptimekey);
  if (isnan(exptime))
    exptime = (double)get_float_from_keyword(grism_file_path, 1, conf->exptimekey);
  if (isnan(exptime))
    exptime = 1.0;

  //
  // try to get the descriptor 'SKY_CPS' from the 'sci'-extension
  //
  sky_cps = (double)get_float_from_keyword(grism_file_path, conf->science_numext, "SKY_CPS");
  if (isnan(sky_cps))
    sky_cps = 0.0;

  //  Open the OPET file for reading
  fits_open_file (&OPET_ptr, obj_PET_file_path, READONLY, &f_status);
  if (f_status)
    {
      ffrprt (stderr, f_status);
      aXe_message (aXe_M_FATAL, __FILE__, __LINE__,
		   "aXe_PET2SPC: Could not open file: %s\n",
		   obj_PET_file_path);
    }

  // check whether the contamination is quantitative
  quant_cont = check_quantitative_contamination(OPET_ptr);

  if (opt_weights && !quant_cont)
    aXe_message (aXe_M_FATAL, __FILE__, __LINE__,
		 "aXe_PET2SPC: The optimal extractions needs quantitative contamination! "
		 " Please re-run aXe with Gauss or Fluxcube contamination!");

  obs = load_dummy_observation ();
  /* Loading the object list */
  fprintf (stdout, "aXe_PET2SPC: Loading object aperture list...");
  oblist = file_to_object_list_seq (aper_file_path, obs);
  fprintf (stdout,"%d objects loaded.\n",object_list_size(oblist));

  if (dobck)
    {
      //  Open the file for reading
      fits_open_file (&BPET_ptr, bck_PET_file_path, READONLY, &f_status);
      if (f_status)
	{
	  ffrprt (stderr, f_status);
	  aXe_message (aXe_M_FATAL, __FILE__, __LINE__,
		       "aXe_PET2SP: Could not open file: %s\n",
		       bck_PET_file_path);
	}
    }

  /* Copy the header info from the grism image */
  SPC_ptr = create_SPC_opened (SPC_file_path,1);
  cards = get_FITS_cards_opened(OPET_ptr);
  put_FITS_cards_opened(SPC_ptr, cards);
  free_FITScards(cards);

  if (opt_weights)
    {
      cards = get_FITS_cards_opened(OPET_ptr);

      // open the WHT file and add the header keywords
      WHT_ptr = create_FITSimage_opened (WHT_file_path, 1);
      put_FITS_cards_opened(WHT_ptr, cards);

      // open the opt_SPC and add the header keywords
      SPC_opt_ptr = create_SPC_opened (SPC_opt_file_path,1);
      put_FITS_cards_opened(SPC_opt_ptr, cards);

      // delete the header keywords
      free_FITScards(cards);
    }

  // do something only if there
  // exist valid objects
  i = 0;
  if (oblist!=NULL)
    {

      // turn until the end of the PET is reached
      while (1)
	{
	  empty=0;

	  // Get the PET for this object
	  obj_PET = get_ALL_from_next_in_PET(OPET_ptr, &obj_aperID, &obj_beamID);

	  // load the background PET if requested
	  if (dobck)
	    {
	      bck_PET = get_ALL_from_next_in_PET(BPET_ptr, &bck_aperID, &bck_beamID);
	      if ((bck_aperID!=obj_aperID)||(bck_beamID!=obj_beamID))
		aXe_message (aXe_M_FATAL, __FILE__, __LINE__,
			     "Background PET and Object PET extensions are not"
			     " in the same order and cannot be combined.\n");
	    }

	  // end of PET reached: the break condition
	  if ((obj_aperID==-1) && (obj_beamID==-1))
	    break;

	  // signal an empty PET
	  if (obj_PET==NULL)
	    empty=1;

	  // give feedback to the screen
	  fprintf (stdout, "aXe_PET2SPC: BEAM %d; %d%c\n", i, obj_aperID, BEAM(obj_beamID));fflush(stdout);

	  // identify the object which matches the PET
	  objindex =  find_object_in_object_list(oblist,obj_aperID);

	  // look whether we are for grisms or prisms
	  for_grism = check_for_grism (conf_file_path, obj_beamID);
	  wl_calibration  = get_calfunc_for_beam(oblist[objindex]->beams[obj_beamID], for_grism, conf_file_path, conf);

	  // compute the object spectrum
	  obj_spec = bin_naive (obj_PET, oblist[objindex]->beams[obj_beamID].width,
				oblist[objindex]->beams[obj_beamID].orient,	quant_cont);

	  // check for the existence of a background PET
	  if (dobck)
	    {
	      // compute the background spectrum
	      bck_spec = bin_naive (bck_PET,
				    oblist[objindex]->beams[bck_beamID].width,
				    oblist[objindex]->beams[bck_beamID].orient, quant_cont);
	    }
	  else
	    {
	      // create a dummy background spectrum
	      bck_spec = empty_counts_spectrum_copy(obj_spec);
	    }

	  // subtract the background spectrum from the
	  // object (or forground) spectrum
	  sobj_spec = subtract_spectra (obj_spec, bck_spec);

	  if(!noflux)
	    {
	      get_troughput_table_name(conf_file_path,
				       oblist[objindex]->beams[obj_beamID].ID,
				       table);
	      if (strcmp(table,"None"))
		{
		  build_path (AXE_CONFIG_PATH, table, table_path);
		  resp=get_response_function_from_FITS(table_path,2);
		  resp_func = create_response_function(table_path);
		  if (resp->spec_len <2)
		    {
		      aXe_message (aXe_M_WARN1, __FILE__, __LINE__,
				   "Throughput table %s contains only %d"
				   " values. No sensitivity curve was applied.\n",
				   table_path,resp->spec_len);
		    }
		  else
		    {
		      fprintf(stdout,"aXe_PET2SPC: Applying sensitivity contained in %s\n",table_path);
		      smooth_params = get_smooth_pars_for_beam(conf, smooth_conv, oblist[objindex]->beams[obj_beamID]);
		      if (smooth_params.x > 0.0)
		        {
		          // apply a smoothed flux conversion
			        apply_smoothed_response(wl_calibration, for_grism, quant_cont, resp_func, smooth_params, sobj_spec);
			       }
		      else
			   {
			       // apply a normal flux conversion
			       apply_response_function(sobj_spec, resp, quant_cont);
			   }
		    }
		  // free the memory of the
		  // response functions
		  free_spectrum(resp);
		  free_response_function(resp_func);
		}
	    }
	  if (empty!=1)
	    {
	      add_spectra_to_SPC_opened (SPC_ptr, obj_spec, bck_spec,
					 sobj_spec, oblist[objindex]->ID, oblist[objindex]->beams[obj_beamID].ID);

	      /* Copy header from OPET extension into this SPC extension */
	      cards = get_FITS_cards_opened(OPET_ptr);
	      put_FITS_cards_opened(SPC_ptr,cards);
	      free_FITScards(cards);

	    }

	  free_spectrum (bck_spec);
	  free_spectrum (sobj_spec);
	  free_spectrum (obj_spec);

	  if (opt_weights)
	    {
	      // get the dimension in trace length
	      // and crossdispersion
	      dimension = get_all_dims(obj_PET, bck_PET,
				       oblist[objindex]->beams[obj_beamID], dobck);

	      // check for empty PET
	      if (!dimension.resolution)
		{
		  // create dummies in case of empty PET's
		  weights = get_default_weight();
		  modvar  = get_default_modvar();
		}
	      else
		{
		  // prepare the PET's by computing the inverse variance.
		  // Also the trace distances are shifted by 0.5
		  // to get a sampling comparable to the unweighted
		  // extraction
		  prepare_inv_variance(obj_PET, bck_PET, dobck, conf, exptime, sky_cps, 0.0);
		  // compute the inverse variance and the profile
		  // image in the trace distance - crossdispersion plane
		  modvar = compute_modvar(obj_PET, oblist[objindex]->beams[obj_beamID], dimension);

		  // compute the optimal weights
		  weights = comp_allweight(modvar);

		}

	      if (dimension.resolution && empty != 1)
		{
		  sprintf (label, "WHT_%d%c", obj_aperID, BEAM (obj_beamID));
		  gsl_to_FITSimage_opened (weights, WHT_ptr ,0,label);

		  // make and store the default header
		  cards = beam_to_FITScards(oblist[objindex],obj_beamID);
		  put_FITS_cards_opened(WHT_ptr,cards);
		  free_FITScards(cards);
		}

	      // create the optimal weighted
	      // foreground spectrum
	      obj_spec = bin_optimal (obj_PET,oblist[objindex]->beams[obj_beamID],
				      quant_cont, weights, dimension, coverage);

	      // check for the presence of a background PET
	      if (dobck)
		{
		  // create the optimal weighted
		  // background spectrum
		  bck_spec = bin_optimal (bck_PET,oblist[objindex]->beams[obj_beamID],
					  quant_cont, weights, dimension, coverage);
		}
	      else
		{
		  // make a dummy background spectrum
		  bck_spec = empty_counts_spectrum_copy(obj_spec);
		}

	      // release memory
	      gsl_matrix_free(weights);
	      free_drzstamp(modvar);

	  // subtract the background spectrum from the
	  // object (or forground) spectrum
	  sobj_spec = subtract_spectra (obj_spec, bck_spec);

	  if(!noflux)
	    {
	      get_troughput_table_name(conf_file_path,
				       oblist[objindex]->beams[obj_beamID].ID,
				       table);
	    if (strcmp(table,"None"))
	      {
		build_path (AXE_CONFIG_PATH, table, table_path);
		resp=get_response_function_from_FITS(table_path,2);
		resp_func = create_response_function(table_path);
		if (resp->spec_len <2)
		  {
		    aXe_message (aXe_M_WARN1, __FILE__, __LINE__,
				 "Throughput table %s contains only %d"
				 " values. No sensitivity curve was applied.\n",
				 table_path,resp->spec_len);
		  }
		else
		  {
		    fprintf(stdout,"aXe_PET2SPC: Applying sensitivity contained in %s\n",table_path);
		    smooth_params = get_smooth_pars_for_beam(conf, smooth_conv, oblist[objindex]->beams[obj_beamID]);
        if (smooth_params.x > 0.0)
          {
            apply_smoothed_response(wl_calibration, for_grism, quant_cont, resp_func, smooth_params, sobj_spec);
          }
		    else
		      {
			apply_response_function(sobj_spec, resp, quant_cont);
		      }
		  }
		// free the memory
		free_spectrum(resp);
		free_response_function(resp_func);
	      }
	    }
	  if (empty!=1)
	    {
	      add_spectra_to_SPC_opened (SPC_opt_ptr, obj_spec, bck_spec,
					 sobj_spec, oblist[objindex]->ID, oblist[objindex]->beams[obj_beamID].ID);

	      /* Copy header from OPET extension into this SPC extension */
	      cards = get_FITS_cards_opened(OPET_ptr);
	      put_FITS_cards_opened(SPC_opt_ptr,cards);
	      free_FITScards(cards);

	    }

	  free_spectrum (bck_spec);
	  free_spectrum (sobj_spec);
	  free_spectrum (obj_spec);
	    }

	  // free memory
	  free_calib (wl_calibration);
	  if (bck_PET!=NULL)
	    free (bck_PET);
	  if (obj_PET!=NULL)
	    free (obj_PET);
	  i++;
        }
    }

  fits_close_file (SPC_ptr, &f_status);
  if (f_status)
    {
      ffrprt (stderr, f_status);
      aXe_message (aXe_M_FATAL, __FILE__, __LINE__,
		   "aXe_PET2SPC: " "Error closing SPC: %s\n",
		   SPC_file_path);
    }

  if (opt_weights)
    {
      fits_close_file (WHT_ptr, &f_status);
      if (f_status)
	{
	  ffrprt (stderr, f_status);
	  aXe_message (aXe_M_FATAL, __FILE__, __LINE__,
		       "aXe_PET2SPC: " "Error closing WHT: %s\n",
		       WHT_file_path);
	}
      fits_close_file (SPC_opt_ptr, &f_status);
      if (f_status)
	{
	  ffrprt (stderr, f_status);
	  aXe_message (aXe_M_FATAL, __FILE__, __LINE__,
		       "aXe_PET2SPC: " "Error closing PSC: %s\n",
		       SPC_opt_file_path);
	}
    }

  if (oblist!=NULL)
    free_oblist (oblist);

  fprintf (stdout, "aXe_PET2SPC: Done...\n");
  exit (0);
}


#include <mpi.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <gsl/gsl_math.h>
#include <sys/stat.h>
#include <sys/types.h>

#include "allvars.h"
#include "proto.h"

#ifdef SUBFIND

#include "fof.h"
#include "subfind.h"

/*! Structure for communication during the density computation. Holds data that is sent to other processors.
 */
static struct contamdata_in
{
  MyDouble Pos[3];
  MyFloat R200;
  int NodeList[NODELISTLENGTH];
}
 *ContamIn, *ContamGet;

static struct contamdata_out
{
  double ContaminationMass;
  int ContaminationLen;
}
 *ContamResult, *ContamOut;

void subfind_contamination(void)
{
  int i, j, ndone, ndone_flag, dummy, count;
  int ngrp, sendTask, recvTask, place, nexport, nimport;
  struct unbind_data *d;

  d = (struct unbind_data *) mymalloc(NumPart * sizeof(struct unbind_data));

  for(i = 0, count = 0; i < NumPart; i++)
#ifdef DENSITY_SPLIT_BY_TYPE
    if(!((1 << P[i].Type) & (DENSITY_SPLIT_BY_TYPE)))
#else
    if(!((1 << P[i].Type) & (FOF_PRIMARY_LINK_TYPES)))
#endif
      d[count++].index = i;

  force_treebuild(count, d);	/* construct tree only with boundary particles */

  myfree(d);

  /* allocate buffers to arrange communication */

  All.BunchSize =
    (int) ((All.BufferSize * 1024 * 1024) / (sizeof(struct data_index) + sizeof(struct data_nodelist) +
					     sizeof(struct contamdata_in) + sizeof(struct contamdata_out) +
					     sizemax(sizeof(struct contamdata_in),
						     sizeof(struct contamdata_out))));
  DataIndexTable = (struct data_index *) mymalloc(All.BunchSize * sizeof(struct data_index));
  DataNodeList = (struct data_nodelist *) mymalloc(All.BunchSize * sizeof(struct data_nodelist));

  /* we will repeat the whole thing for those groups where we didn't converge to a SO radius yet */

  i = 0;			/* begin with this index */

  do
    {
      for(j = 0; j < NTask; j++)
	{
	  Send_count[j] = 0;
	  Exportflag[j] = -1;
	}

      /* do local particles and prepare export list */

      for(nexport = 0; i < Ngroups; i++)
	{
	  if(Group[i].R_Mean200 > 0)
	    {
	      if(subfind_contamination_evaluate(i, 0, &nexport, Send_count) < 0)
		break;
	    }
	  else
	    {
	      Group[i].ContaminationLen = 0;
	      Group[i].ContaminationMass = 0;
	    }
	}

      qsort(DataIndexTable, nexport, sizeof(struct data_index), data_index_compare);

      MPI_Allgather(Send_count, NTask, MPI_INT, Sendcount_matrix, NTask, MPI_INT, MPI_COMM_WORLD);

      for(j = 0, nimport = 0, Recv_offset[0] = 0, Send_offset[0] = 0; j < NTask; j++)
	{
	  Recv_count[j] = Sendcount_matrix[j * NTask + ThisTask];
	  nimport += Recv_count[j];

	  if(j > 0)
	    {
	      Send_offset[j] = Send_offset[j - 1] + Send_count[j - 1];
	      Recv_offset[j] = Recv_offset[j - 1] + Recv_count[j - 1];
	    }
	}

      ContamGet = (struct contamdata_in *) mymalloc(nimport * sizeof(struct contamdata_in));
      ContamIn = (struct contamdata_in *) mymalloc(nexport * sizeof(struct contamdata_in));

      /* prepare particle data for export */
      for(j = 0; j < nexport; j++)
	{
	  place = DataIndexTable[j].Index;

	  ContamIn[j].Pos[0] = Group[place].Pos[0];
	  ContamIn[j].Pos[1] = Group[place].Pos[1];
	  ContamIn[j].Pos[2] = Group[place].Pos[2];
	  ContamIn[j].R200 = Group[place].R_Mean200;

	  memcpy(ContamIn[j].NodeList,
		 DataNodeList[DataIndexTable[j].IndexGet].NodeList, NODELISTLENGTH * sizeof(int));
	}

      /* exchange data */
      for(ngrp = 1; ngrp < (1 << PTask); ngrp++)
	{
	  sendTask = ThisTask;
	  recvTask = ThisTask ^ ngrp;

	  if(recvTask < NTask)
	    {
	      if(Send_count[recvTask] > 0 || Recv_count[recvTask] > 0)
		{
		  /* get the data */
		  MPI_Sendrecv(&ContamIn[Send_offset[recvTask]],
			       Send_count[recvTask] * sizeof(struct contamdata_in), MPI_BYTE,
			       recvTask, TAG_DENS_A,
			       &ContamGet[Recv_offset[recvTask]],
			       Recv_count[recvTask] * sizeof(struct contamdata_in), MPI_BYTE,
			       recvTask, TAG_DENS_A, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
		}
	    }
	}

      myfree(ContamIn);
      ContamResult = (struct contamdata_out *) mymalloc(nimport * sizeof(struct contamdata_out));
      ContamOut = (struct contamdata_out *) mymalloc(nexport * sizeof(struct contamdata_out));

      /* now do the locations that were sent to us */
      for(j = 0; j < nimport; j++)
	subfind_contamination_evaluate(j, 1, &dummy, &dummy);

      if(i >= Ngroups)
	ndone_flag = 1;
      else
	ndone_flag = 0;

      MPI_Allreduce(&ndone_flag, &ndone, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD);

      /* get the result */
      for(ngrp = 1; ngrp < (1 << PTask); ngrp++)
	{
	  sendTask = ThisTask;
	  recvTask = ThisTask ^ ngrp;
	  if(recvTask < NTask)
	    {
	      if(Send_count[recvTask] > 0 || Recv_count[recvTask] > 0)
		{
		  /* send the results */
		  MPI_Sendrecv(&ContamResult[Recv_offset[recvTask]],
			       Recv_count[recvTask] * sizeof(struct contamdata_out),
			       MPI_BYTE, recvTask, TAG_DENS_B,
			       &ContamOut[Send_offset[recvTask]],
			       Send_count[recvTask] * sizeof(struct contamdata_out),
			       MPI_BYTE, recvTask, TAG_DENS_B, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
		}
	    }
	}

      /* add the result to the local particles */
      for(j = 0; j < nexport; j++)
	{
	  place = DataIndexTable[j].Index;
	  Group[place].ContaminationLen += ContamOut[j].ContaminationLen;
	  Group[place].ContaminationMass += ContamOut[j].ContaminationMass;
	}

      myfree(ContamOut);
      myfree(ContamResult);
      myfree(ContamGet);
    }
  while(ndone < NTask);

  myfree(DataNodeList);
  myfree(DataIndexTable);
}

/*! This function represents the core of the SPH density computation. The
 *  target particle may either be local, or reside in the communication
 *  buffer.
 */
int subfind_contamination_evaluate(int target, int mode, int *nexport, int *nsend_local)
{
  int startnode, listindex = 0;
  double h, mass, masssum;
  int count, countsum;
  MyDouble *pos;

  masssum = 0;
  countsum = 0;

  if(mode == 0)
    {
      pos = Group[target].Pos;
      h = Group[target].R_Mean200;
    }
  else
    {
      pos = ContamGet[target].Pos;
      h = ContamGet[target].R200;
    }

  if(mode == 0)
    {
      startnode = All.MaxPart;	/* root node */
    }
  else
    {
      startnode = ContamGet[target].NodeList[0];
      startnode = Nodes[startnode].u.d.nextnode;	/* open it */
    }

  while(startnode >= 0)
    {
      while(startnode >= 0)
	{
	  count =
	    subfind_contamination_treefind(pos, h, target, &startnode, mode, nexport, nsend_local, &mass);

	  if(count < 0)
	    return -1;

	  masssum += mass;
	  countsum += count;
	}

      if(mode == 1)
	{
	  listindex++;
	  if(listindex < NODELISTLENGTH)
	    {
	      startnode = ContamGet[target].NodeList[listindex];
	      if(startnode >= 0)
		startnode = Nodes[startnode].u.d.nextnode;	/* open it */
	    }
	}
    }

  if(mode == 0)
    {
      Group[target].ContaminationMass = masssum;
      Group[target].ContaminationLen = countsum;
    }
  else
    {
      ContamResult[target].ContaminationMass = masssum;
      ContamResult[target].ContaminationLen = countsum;
    }

  return 0;
}

int subfind_contamination_treefind(MyDouble searchcenter[3], MyFloat hsml, int target, int *startnode,
				   int mode, int *nexport, int *nsend_local, double *Mass)
{
  int no, p, task, nexport_save;
  struct NODE *current;
  double mass;
  int count;
  MyDouble dx, dy, dz, dist, r2;

#ifdef PERIODIC
  MyDouble xtmp;
#endif
  nexport_save = *nexport;

  mass = 0;
  count = 0;

  no = *startnode;

  while(no >= 0)
    {
      if(no < All.MaxPart)	/* single particle */
	{
	  p = no;
	  no = Nextnode[no];

	  dist = hsml;
	  dx = NGB_PERIODIC_LONG_X(P[p].Pos[0] - searchcenter[0]);
	  if(dx > dist)
	    continue;
	  dy = NGB_PERIODIC_LONG_Y(P[p].Pos[1] - searchcenter[1]);
	  if(dy > dist)
	    continue;
	  dz = NGB_PERIODIC_LONG_Z(P[p].Pos[2] - searchcenter[2]);
	  if(dz > dist)
	    continue;
	  if(dx * dx + dy * dy + dz * dz > dist * dist)
	    continue;

	  mass += P[p].Mass;
	  count++;
	}
      else
	{
	  if(no >= All.MaxPart + MaxNodes)	/* pseudo particle */
	    {
	      if(mode == 1)
		endrun(12312);

	      if(mode == 0)
		{
		  if(Exportflag[task = DomainTask[no - (All.MaxPart + MaxNodes)]] != target)
		    {
		      Exportflag[task] = target;
		      Exportnodecount[task] = NODELISTLENGTH;
		    }

		  if(Exportnodecount[task] == NODELISTLENGTH)
		    {
		      if(*nexport >= All.BunchSize)
			{
			  *nexport = nexport_save;
			  if(nexport_save == 0)
			    endrun(13005);	/* in this case, the buffer is too small to process even a single particle */
			  for(task = 0; task < NTask; task++)
			    nsend_local[task] = 0;
			  for(no = 0; no < nexport_save; no++)
			    nsend_local[DataIndexTable[no].Task]++;
			  return -1;
			}
		      Exportnodecount[task] = 0;
		      Exportindex[task] = *nexport;
		      DataIndexTable[*nexport].Task = task;
		      DataIndexTable[*nexport].Index = target;
		      DataIndexTable[*nexport].IndexGet = *nexport;
		      *nexport = *nexport + 1;
		      nsend_local[task]++;
		    }

		  DataNodeList[Exportindex[task]].NodeList[Exportnodecount[task]++] =
		    DomainNodeIndex[no - (All.MaxPart + MaxNodes)];

		  if(Exportnodecount[task] < NODELISTLENGTH)
		    DataNodeList[Exportindex[task]].NodeList[Exportnodecount[task]] = -1;
		}

	      no = Nextnode[no - MaxNodes];
	      continue;
	    }

	  current = &Nodes[no];

	  if(mode == 1)
	    {
	      if(current->u.d.bitflags & (1 << BITFLAG_TOPLEVEL))	/* we reached a top-level node again, which means that we are done with the branch */
		{
		  *startnode = -1;
		  *Mass = mass;
		  return count;
		}
	    }

	  no = current->u.d.sibling;	/* in case the node can be discarded */

	  dist = hsml + 0.5 * current->len;;
	  dx = NGB_PERIODIC_LONG_X(current->center[0] - searchcenter[0]);
	  if(dx > dist)
	    continue;
	  dy = NGB_PERIODIC_LONG_Y(current->center[1] - searchcenter[1]);
	  if(dy > dist)
	    continue;
	  dz = NGB_PERIODIC_LONG_Z(current->center[2] - searchcenter[2]);
	  if(dz > dist)
	    continue;
	  /* now test against the minimal sphere enclosing everything */
	  dist += FACT1 * current->len;
	  if((r2 = (dx * dx + dy * dy + dz * dz)) > dist * dist)
	    continue;

	  no = current->u.d.nextnode;	/* ok, we need to open the node */
	}
    }

  *startnode = -1;

  *Mass = mass;
  return count;
}

#endif


/**************************************************************************/
/* This is the main simulation routine. It initializes either from given  */
/* inputs or from a checkpoint, runs the simulation, and assembles the    */
/* final results.                                                         */
/**************************************************************************/

#include "init.h"
#include "vader.h"
#include "setup.h"
#include <string.h>
#include <gsl/gsl_errno.h>

double
vader(
      /* Restart checkpoint name (empty string for new start) */
      const char *restart_file,
      /* Time parameters */
      const double tStart, const double tEnd,
      /* Equation of state parameters */
      const bool eos_func, const double gamma_val,
      const double delta_val,
      /* Dimensionless viscosity parameters */
      const bool alpha_func, const double alpha_val,
      /* Inner boundary condition parameters */
      const pres_bc_type ibc_pres, const enth_bc_type ibc_enth,
      const bool ibc_func, const double ibc_pres_val,
      const double ibc_enth_val,
      /* Outer boundary condition parameters */
      const pres_bc_type obc_pres, const enth_bc_type obc_enth,
      const bool obc_func, const double obc_pres_val,
      const double obc_enth_val,
      /* Source function parameters */
      const bool massSrc_func, const double massSrc_val,
      const bool intEnSrc_func, const double intEnSrc_val,
      /* Control and method parameters */
      const double dtStart,
      const double dtMin,
      const double dtTol,
      const double errTol,
      const double maxDtIncrease,
      const unsigned long maxIter,
      const unsigned long interpOrder,
      const unsigned long maxStep,
      const bool useBE,
      const bool preTimestep_func,
      const bool postTimestep_func,
      const unsigned long verbosity,
      /* Output control parameters */
      const unsigned long nSave, const double *tSave,
      const unsigned long nUserOut,
      const bool *userOutCum,
      const bool *writeCheckpoint,
      const char *checkname,
      const bool userReadCheckpoint,
      const bool userWriteCheckpoint,
      /* Computational grid (leave as NULL for a restart) */
      grid **grd_ptr,
      /* Starting data (leave as NULL for a restart) */
      double **col_init, double **pres_init, double **eInt_init,
      /* User-defined extra parameters */
      void *params,
      /* Diagnostic outputs */
      unsigned long *nStep, unsigned long *nIter,
      unsigned long *nFail,
      /* Output values; all the doubles should be passed in set to
	 NULL, and will be allocated appropriately */
      unsigned long *nOut,
      double **tOut_ptr, double **colOut_ptr,
      double **presOut_ptr, double **eIntOut_ptr,
      double **mBndOut_ptr, double **eBndOut_ptr,
      double **mSrcOut_ptr, double **eSrcOut_ptr,
      double **userOut_ptr
#ifdef TESTING_MODE
      /* Parameters used in code tests */
      , double *residSum, unsigned long *iterStep,
      double *driverTime, double *advanceTime,
      double *nextIterTime, double *userTime
#endif
      ) {

  setup_status set_stat;
  gsl_error_handler_t *gslerr;
  double *col, *pres, *eInt;
  double *tOut, *colOut, *presOut, *eIntOut, *mBndOut, *eBndOut,
    *mSrcOut, *eSrcOut, *userOut;
  const double *tSave_tmp;
  const bool *writeCheckpoint_tmp, *userOutCum_tmp;
  grid *grd;
  unsigned long nOutMax, savePtr, chkNum;
  wksp *w;
  bool writeOut, restart;
  double t, dt, tSimEnd;

  /* Turn off GSL error handling, since we'll handle errors manually */
  gslerr = gsl_set_error_handler_off();

  /* Sanity check on the interpolation order */
  if ((interpOrder < 1) || (interpOrder > 3)) {
    fprintf(stderr, "vader: error: interpOrder must be 1, 2, or 3\n");
    return(-1.0);
  }

  /* Do the initial setup */
  if (!restart_file) restart = false;
  else if (strlen(restart_file) == 0) restart = false;
  else restart = true;
  if (!restart) {

    /* We're starting new, so the initial data and grid should already
       be present, so make our working pointers point to them */
    grd = *grd_ptr;
    col = *col_init;
    pres = *pres_init;
    if (eos_func) eInt = *eInt_init;
    else eInt = NULL;

    /* Initialize time and counters */
    t = tStart;
    *nStep = 1;
    *nIter = *nFail = 0;
    nOutMax = nSave;
    *nOut = savePtr = chkNum = 0;

    /* Call the setup routine; this stores the first outputs if
       necessary and computes the first time step */
    set_stat =
      setup_new(tStart, tEnd,
		eos_func, gamma_val, delta_val, alpha_func, alpha_val,
		ibc_pres, ibc_enth, ibc_func, ibc_pres_val, ibc_enth_val,
		obc_pres, obc_enth, obc_func, obc_pres_val, obc_enth_val,
		massSrc_func, massSrc_val, intEnSrc_func, intEnSrc_val,
		dtStart, dtMin, dtTol, errTol, maxDtIncrease,
		maxIter, interpOrder, maxStep, useBE,
		userWriteCheckpoint, verbosity,
		nSave, tSave, nUserOut, writeCheckpoint,
		checkname,
		grd, &w, col, pres, eInt, params,
		tOut_ptr, colOut_ptr, presOut_ptr, eIntOut_ptr, mBndOut_ptr,
		eBndOut_ptr, mSrcOut_ptr, eSrcOut_ptr, userOut_ptr,
		&dt, nOut, &savePtr, &writeOut, &chkNum
#ifdef TESTING_MODE
		, residSum, iterStep, advanceTime, nextIterTime,
		userTime
#endif
		);
    if (set_stat != GOOD_START) return -LARGE;

  } else {

    /* Set up from the checkpoint */
    set_stat =
      setup_checkpoint(restart_file, tStart, tEnd,
		       eos_func, massSrc_func, intEnSrc_func,
		       userReadCheckpoint, verbosity, nSave, tSave,
		       nUserOut, userOutCum, &grd, &col, &pres, &eInt,
		       nStep, nIter, nFail,
		       tOut_ptr, colOut_ptr, presOut_ptr, eIntOut_ptr,
		       mBndOut_ptr, eBndOut_ptr, mSrcOut_ptr, eSrcOut_ptr,
		       userOut_ptr, params, &w, &t, &dt, nOut, &savePtr,
		       &nOutMax, &writeOut, &chkNum);
    if (set_stat != GOOD_START) return -LARGE;

    /* Point inputs to allocated memory */
    *grd_ptr = grd;
    *col_init = col;
    *pres_init = pres;
    if (eos_func) *eInt_init = eInt;
  }

  /* Set pointers to output blocks and output controls; note that some
     of the control flags might be NULL, and we have to make sure to
     preserve those values, because they are significant in driver */
  tOut = *tOut_ptr;
  colOut = *colOut_ptr;
  presOut = *presOut_ptr;
  if (eos_func) eIntOut = *eIntOut_ptr; else eIntOut = NULL;
  mBndOut = *mBndOut_ptr;
  eBndOut = *eBndOut_ptr;
  if (massSrc_func) mSrcOut = *mSrcOut_ptr; else mSrcOut = NULL;
  if (massSrc_func || intEnSrc_func) eSrcOut = *eSrcOut_ptr;
  else eSrcOut = NULL;
  if (nUserOut > 0) userOut = *userOut_ptr; else userOut = NULL;
  if (tSave) tSave_tmp = tSave + savePtr; else tSave_tmp = NULL;
  if (writeCheckpoint) writeCheckpoint_tmp = writeCheckpoint + savePtr;
  else writeCheckpoint_tmp = NULL;
  if (userOutCum) userOutCum_tmp = userOutCum + savePtr;
  else userOutCum_tmp = NULL;

  /* Now call the driver routine to run the simulation */
  tSimEnd = driver(t, tEnd, eos_func, gamma_val, delta_val,
		   alpha_func, alpha_val,
		   ibc_pres, ibc_enth, ibc_func,
		   ibc_pres_val, ibc_enth_val,
		   obc_pres, obc_enth, obc_func,
		   obc_pres_val, obc_enth_val,
		   massSrc_func, massSrc_val,
		   intEnSrc_func, intEnSrc_val,
		   dt, dtMin, dtTol, errTol, maxDtIncrease,
		   maxIter, interpOrder, maxStep,
		   useBE, preTimestep_func, postTimestep_func,
		   verbosity, nOutMax - *nOut,
		   tSave_tmp, nUserOut, userOutCum_tmp,
		   writeCheckpoint_tmp, checkname,
		   userWriteCheckpoint, writeOut,
		   chkNum, grd, w, col, pres, eInt,
		   params, nStep, nIter, nFail,
		   nOut, tOut, colOut, presOut, eIntOut,
		   mBndOut, eBndOut, mSrcOut, eSrcOut,
		   userOut
#ifdef TESTING_MODE
		   , residSum, iterStep, driverTime, advanceTime,
		   nextIterTime, userTime
#endif
		   );

  /* If we didn't use up all the output slots because the calculation
     terminated early, free unneeded memory */
  if (*nOut < nOutMax) {
    outputResize(*nOut, eos_func, massSrc_func, intEnSrc_func,
		 nUserOut, grd, tOut_ptr, colOut_ptr, presOut_ptr,
		 eIntOut_ptr, mBndOut_ptr, eBndOut_ptr, mSrcOut_ptr,
		 eSrcOut_ptr, userOut_ptr);
  }

  /* Free the workspace */
  wkspFree(w);

  /* Return */
  return tSimEnd;
}