#ifndef MATH_FNS_H
#define MATH_FNS_H

#ifdef USE_OPENBLAS
extern "C"
{
#include <cblas.h>
}
#endif

struct QuantizationParams
{
	float scale;
	uint8_t zero_point;
};

template<typename Dtype>
void cpu_gemm(bool transA, bool transB, uint64_t M, uint64_t N, uint64_t K, Dtype alpha, Dtype* A, Dtype* B, Dtype beta, Dtype* C);

template<typename Dtype>
void cpu_axpy(uint64_t N, Dtype alpha, Dtype* X, Dtype* Y, Dtype* C); // c = alpha * x + y

template<typename Dtype>
void cpu_axpby(uint64_t N, Dtype alpha, Dtype* X_ptr, Dtype beta, Dtype* Y_ptr, Dtype* C_ptr); // c = alpha * x + beta * y

template<typename Dtype>
void cpu_mul(uint64_t N, Dtype* A_ptr, Dtype* B_ptr, Dtype* C_ptr); // c = a * b

template<typename Dtype>
void cpu_mul(uint64_t N, Dtype alpha, Dtype* A_ptr, Dtype* B_ptr, Dtype beta, Dtype* C_ptr); // c = beta * c + alpha * a * b

template<typename Dtype>
void cpu_mul(uint64_t N, Dtype alpha, Dtype* A_ptr, Dtype* B_ptr); // b = alpha * a

template<typename Dtype>
void cpu_add(uint64_t N, Dtype alpha, Dtype* A_ptr, Dtype* B_ptr); // b = alpha + a

template<typename Dtype>
void cpu_div(uint64_t N, Dtype* A_ptr, Dtype* B_ptr, Dtype* C_ptr); // c += a / b

template<typename Dtype>
void cpu_div(uint64_t N, Dtype alpha, Dtype* A_ptr, Dtype* B_ptr, Dtype beta, Dtype* C_ptr); // c = (beta * c) +  (alpha * a / b)

template<typename Dtype>
void cpu_div_back(uint64_t N, Dtype* A_ptr, Dtype* B_ptr, Dtype* C_ptr, Dtype* D_ptr); // special function for processing div_backward during backpropagation (calculates d += a * (-b) / (c * c))

template<typename Dtype>
void cpu_sig(uint64_t N, const Dtype* A_ptr, Dtype* B_ptr); // b = sig(a)

template<typename Dtype>
void cpu_tanh(uint64_t N, const Dtype* A_ptr, Dtype* B_ptr); // b = tanh(a)

template<typename Dtype>
void cpu_powx(uint64_t N, const Dtype* A_ptr, Dtype x, Dtype* B_ptr); // b = pow(a,x)

template<typename Dtype>
void cpu_copy(uint64_t N, Dtype* A_ptr, Dtype* B_ptr); // b = a

template<typename Dtype>
void cpu_max(const Dtype* src, Dtype* dst, uint64_t* indices, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride);

template<typename Dtype>
void cpu_max_backward(Dtype* dst, const Dtype* src, const uint64_t* indices, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride);

template<typename Dtype>
void cpu_sum(const Dtype* src, Dtype* dst, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride);

template<typename Dtype>
void cpu_sum_backward(Dtype* dst, const Dtype* src, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride);

template<typename Dtype>
void cpu_mean(const Dtype* src, Dtype* dst, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride);

template<typename Dtype>
void cpu_mean_backward(Dtype* dst, const Dtype* src, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride);

template<typename Dtype>
void cpu_var(const Dtype* src, Dtype* dst, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride);

template<typename Dtype>
void cpu_var_backward(Dtype* dst, const Dtype* src, const Dtype* op1, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride);

template<typename Dtype>
void cpu_std(const Dtype* src, Dtype* dst, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride, bool sample_mode = true);

template<typename Dtype>
void cpu_std_backward(Dtype* dst, const Dtype* src, const Dtype* op1, const Dtype* std, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride, bool sample_mode = true);

template<typename Dtype>
void cpu_dropout(Dtype* dst, Dtype* src, unsigned int* mask, unsigned int threshold, Dtype scale, uint64_t len);

template<typename Dtype>
void cpu_sig_backward(Dtype* bottom, const Dtype* top, const Dtype* middle, const uint64_t numels);

template<typename Dtype>
void cpu_tanh_backward(Dtype* bottom, const Dtype* top, const Dtype* middle, const uint64_t numels);

void cpu_transpose(float* src, float* dst, int dim_1, int dim_2,
	int stride_src_dim_1, int stride_src_dim_1_minus_1, int stride_src_dim_2, int stride_src_dim_2_minus_1,
	int stride_trn_dim_1, int stride_trn_dim_1_minus_1, int stride_trn_dim_2, int stride_trn_dim_2_minus_1, uint64_t numels);

void quantized_matmul(bool traspose_A, bool traspose_B, uint64_t M, uint64_t N, uint64_t K, uint8_t alpha, uint8_t* A, uint8_t* B, uint8_t beta, uint8_t* C, QuantizationParams* qparms, int* bias, int* workspace); // workspace must be at least (M + N) * sizeof(int) bytes

//-------------------------------------CUDA functions----------------------------------------------------------------

template<typename Dtype>
void gpu_sum(Dtype* A, Dtype* B, Dtype* C, uint64_t height_A, uint64_t width_A, uint64_t height_B, uint64_t width_B);

template<typename Dtype>
void gpu_sub(Dtype* A, Dtype* B, Dtype* C, uint64_t height_A, uint64_t width_A, uint64_t height_B, uint64_t width_B);

// C =  A * B
template<typename Dtype>
void gpu_mul(uint64_t N, Dtype* A_ptr, Dtype* B_ptr, Dtype* C_ptr);

// C = beta * C + A * B
// supports broadcast semantics
// use this when C dims >= both A and B dims (avoid atomics)
template<typename Dtype>
void gpu_mul(Dtype* A, Dtype* B, Dtype* C, uint64_t height_A, uint64_t width_A, uint64_t height_B, uint64_t width_B, Dtype beta);

// C = C + A * B
// supports broadcast semantics
// use this when C dims < either A or B dims (uses atomics, but no support for uint8_t)
void gpu_mul(float* A, float* B, float* C, uint64_t height_A, uint64_t width_A, uint64_t height_B, uint64_t width_B, uint64_t height_C, uint64_t width_C);

template<typename Dtype>
void gpu_mul(uint64_t N, Dtype alpha, Dtype* A, Dtype* B, Dtype beta, Dtype* C); // c = beta * c + alpha * a * b

template<typename Dtype>
void gpu_mul(uint64_t N, Dtype alpha, Dtype* A_ptr, Dtype* B_ptr); // b = alpha * a

// C =  A / B
// supports broadcast semantics
// use this when C dims >= both A and B dims (avoid atomics)
template<typename Dtype>
void gpu_div(Dtype* A, Dtype* B, Dtype* C, uint64_t height_A, uint64_t width_A, uint64_t height_B, uint64_t width_B);

// C = C + A / B
// use this when C numels == A numels == B numels (no broadcast therefore fastest)
template<typename Dtype>
void gpu_div(uint64_t N, Dtype* A, Dtype* B, Dtype* C);

// C = A / B
// use this when C numels == A numels == B numels (no broadcast therefore fastest)
//template<typename Dtype>
//void gpu_div2(uint64_t N, Dtype* A, Dtype* B, Dtype* C);

// C = C + A / B
// supports broadcast semantics
// use this when C dims < either A or B dims (uses atomics, but no support for uint8_t)
void gpu_div(float* A, float* B, float* C, uint64_t height_A, uint64_t width_A, uint64_t height_B, uint64_t width_B, uint64_t height_C, uint64_t width_C);

// special function for processing div_backward during backpropagation (calculates d += a * (-b) / (c * c))
// supports broadcast semantics
// use this when D dims < A or B or C dims (uses atomics, but no support for uint8_t)
//template<typename Dtype>
void gpu_div_back(float* A, float* B, float* C, float* D, uint64_t height_A, uint64_t width_A, uint64_t height_B, uint64_t width_B, uint64_t height_C, uint64_t width_C, uint64_t height_D, uint64_t width_D);

template<typename Dtype>
void gpu_sum(Dtype* data, Dtype* sum, uint64_t len);

template<typename Dtype>
void gpu_nll(Dtype* input, Dtype* target, Dtype* loss, uint64_t len, uint64_t batches);

template<typename Dtype>
void gpu_scalar_mul(Dtype* A, Dtype* B, Dtype scalar, uint64_t len); // B = scaler * A

// C += alpha * A
// supports broadcast semantics
// use this when C dims < A dims (uses atomics, but no support for uint8_t)
void gpu_scalar_mul(float alpha, float* A, float* C, uint64_t height_A, uint64_t width_A, uint64_t height_C, uint64_t width_C);

template<typename Dtype>
void gpu_fill(Dtype* memory, uint64_t size, Dtype value);

template<typename Dtype>
void gpu_fill(Dtype* memory, uint64_t len, Dtype* value);

void gpu_sgd_step(float* weight_ptr, float* weight_grad_ptr, float* velocity_ptr, int64_t numels, float mo, float wd, float lr);

template<typename Dtype>
void gpu_max(const Dtype* src, Dtype* dst, uint64_t* indices, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride);

template<typename Dtype>
void gpu_max_backward(Dtype* dst, const Dtype* src, const uint64_t* indices, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride);

template<typename Dtype>
void gpu_powx(uint64_t N, const Dtype* A, Dtype x, Dtype* B); // b = pow(a,x)

template<typename Dtype>
void gpu_exp(Dtype* dst, const Dtype* src, const uint64_t numels);

template<typename Dtype>
void gpu_log(Dtype* dst, const Dtype* src, const uint64_t numels);

template<typename Dtype>
void gpu_sig(Dtype* dst, const Dtype* src, const uint64_t numels);

template<typename Dtype>
void gpu_sig_backward(Dtype* bottom, const Dtype* top, const Dtype* middle, const uint64_t numels);

template<typename Dtype>
void gpu_tanh(Dtype* dst, const Dtype* src, const uint64_t numels);

template<typename Dtype>
void gpu_tanh_backward(Dtype* bottom, const Dtype* top, const Dtype* middle, const uint64_t numels);

template<typename Dtype>
void gpu_sum(const Dtype* src, Dtype* dst, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride);

template<typename Dtype>
void gpu_sum_backward(Dtype* dst, const Dtype* src, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride);

template<typename Dtype>
void gpu_add(uint64_t N, Dtype alpha, Dtype* A_ptr, Dtype* B_ptr); // b = alpha + a

template<typename Dtype>
void gpu_axpy(uint64_t N, Dtype alpha, Dtype* X_ptr, Dtype* Y_ptr, Dtype* C_ptr);

template<typename Dtype>
void gpu_axpby(uint64_t N, Dtype alpha, Dtype* X_ptr, Dtype beta, Dtype* Y_ptr, Dtype* C_ptr); // c = alpha * x + beta * y

template<typename Dtype>
void gpu_relu(Dtype* dst, Dtype* src, uint64_t len);

template<typename Dtype>
void gpu_relu_backward(Dtype* bottom, const Dtype* top, const Dtype* middle, const uint64_t len);

template<typename Dtype>
void gpu_dropout(Dtype* dst, Dtype* src, unsigned int* mask, unsigned int threshold, Dtype scale, uint64_t len);

template<typename Dtype>
void gpu_transpose(Dtype* src, Dtype* dst, int dim_1, int dim_2,
	int stride_src_dim_1, int stride_src_dim_1_minus_1, int stride_src_dim_2, int stride_src_dim_2_minus_1,
	int stride_trn_dim_1, int stride_trn_dim_1_minus_1, int stride_trn_dim_2, int stride_trn_dim_2_minus_1, uint64_t numels);

template<typename Dtype>
void gpu_cat(Dtype* dest, Dtype* op1, Dtype* op2, uint64_t dest_stride_1, uint64_t dest_stride_2, uint64_t op1_stride, uint64_t op2_stride_1, uint64_t op2_stride_2, uint64_t dim_offset, uint64_t op1_numels, uint64_t op2_numels);

template<typename Dtype>
void gpu_cat_backward(Dtype* dest, Dtype* src, uint64_t dest_stride, uint64_t src_stride, uint64_t dest_numels);

template<typename Dtype>
void gpu_cat_backward(Dtype* dest, Dtype* src, uint64_t dest_stride_1, uint64_t dest_stride_2, uint64_t src_stride_1, uint64_t src_stride_2, uint64_t dim_offset, uint64_t op1_numels, uint64_t dest_numels);

template<typename Dtype>
void gpu_embedding(Dtype* dst, Dtype* wts, int* indices, uint64_t numels, uint64_t indices_per_batch, unsigned int embedding_dim);

template<typename Dtype>
void gpu_embedding_backward(Dtype* dst, Dtype* wts, int* indices, uint64_t numels, uint64_t indices_per_batch, unsigned int embedding_dim);

template<typename Dtype>
void gpu_sqrt(Dtype* dst, const Dtype* src, const uint64_t numels);

template<typename Dtype>
void gpu_sqrt_backward(Dtype* bottom, const Dtype* top, const Dtype* middle, const uint64_t numels);

template<typename Dtype>
void gpu_mean(const Dtype* src, Dtype* dst, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride);

template<typename Dtype>
void gpu_mean_backward(Dtype* dst, const Dtype* src, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride);

template<typename Dtype>
void gpu_var(const Dtype* src, Dtype* dst, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride);

template<typename Dtype>
void gpu_var_backward(Dtype* dst, const Dtype* src, const Dtype* op1, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride);

template<typename Dtype>
void gpu_std(const Dtype* src, Dtype* dst, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride, bool sample_mode = true);

template<typename Dtype>
void gpu_std_backward(Dtype* dst, const Dtype* src, const Dtype* op1, const Dtype* std, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride, bool sample_mode = true);

#endif // MATH_FNS_H


#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 CR_DIFFUSION_GREEN
#include "cosmic_rays.h"

void compute_diff_weights(int target, int mode);
void scatter_diffusion(int target, int mode);

#define m_p (PROTONMASS * All.HubbleParam / All.UnitMass_in_g)

void greenf_diffusion(void)
{
  int *noffset, *nbuffer, *nsend, *nsend_local, *numlist, *ndonelist;
  int i, j, n;
  int ndone;
  long long ntot, ntotleft;
  int maxfill, source;
  int level, ngrp, sendTask, recvTask;
  int place, nexport;
  double cr_efac_i, kappa_egy;
  double kappa, a3inv, egysum, egytot, egytot_before;
  double meanKineticEnergy, qmeanKin;
  double CR_q_i;
  MPI_Status status;

  if(ThisTask == 0)
    {
      printf("Doing diffusion step with Green function method\n");
      fflush(stdout);
    }

  for(CRpop = 0; CRpop < NUMCRPOP; CRpop++)
    {

      noffset = mymalloc(sizeof(int) * NTask);	/* offsets of bunches in common list */
      nbuffer = mymalloc(sizeof(int) * NTask);
      nsend_local = mymalloc(sizeof(int) * NTask);
      nsend = mymalloc(sizeof(int) * NTask * NTask);
      ndonelist = mymalloc(sizeof(int) * NTask);

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

      egysum = 0;

      for(n = 0, NumSphUpdate = 0; n < N_gas; n++)
	{
	  if(P[n].Type == 0)
	    {
	      NumSphUpdate++;

	      egysum += SphP[n].CR_E0[CRpop];

	      SphP[n].CR_DeltaE[CRpop] = 0;
	      SphP[n].CR_DeltaN[CRpop] = 0;

	      kappa = All.CR_DiffusionCoeff;

	      if(All.CR_DiffusionDensScaling != 0.0)
		kappa *= pow(SphP[n].d.Density * a3inv / All.CR_DiffusionDensZero, All.CR_DiffusionDensScaling);

	      if(All.CR_DiffusionEntropyScaling != 0.0)
		kappa *= pow(SphP[n].Entropy / All.CR_DiffusionEntropyZero, All.CR_DiffusionEntropyScaling);

	      if(SphP[n].CR_E0[CRpop] > 0)
		{
		  CR_q_i = SphP[n].CR_q0[CRpop] * pow(SphP[n].d.Density * a3inv, 0.33333);

		  cr_efac_i =
		    CR_Tab_MeanEnergy(CR_q_i, All.CR_Alpha[CRpop] - 0.3333, CRpop)
		    / CR_Tab_MeanEnergy(CR_q_i, All.CR_Alpha[CRpop], CRpop);

		  kappa *= (All.CR_Alpha[CRpop] - 1) / (All.CR_Alpha[CRpop] - 1.33333) * pow(CR_q_i, 0.3333);

		  kappa_egy = kappa * cr_efac_i;
		}
	      else
		kappa_egy = kappa;

	      SphP[n].CR_Kappa[CRpop] = kappa;
	      SphP[n].CR_Kappa_egy[CRpop] = kappa_egy;
	    }
	}

      MPI_Allreduce(&egysum, &egytot_before, 1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);

      numlist = mymalloc(NTask * sizeof(int) * NTask);
      MPI_Allgather(&NumSphUpdate, 1, MPI_INT, numlist, 1, MPI_INT, MPI_COMM_WORLD);
      for(i = 0, ntot = 0; i < NTask; i++)
	ntot += numlist[i];
      myfree(numlist);

      /* first, sum the weights */

      i = 0;			/* beginn with this index */
      ntotleft = ntot;		/* particles left for all tasks together */

      while(ntotleft > 0)
	{
	  for(j = 0; j < NTask; j++)
	    nsend_local[j] = 0;

	  /* do local particles and prepare export list */

	  for(nexport = 0, ndone = 0; i < NumPart && nexport < All.BunchSizeDensity - NTask; i++)
	    if(P[i].Type == 0)
	      {
		ndone++;

		for(j = 0; j < NTask; j++)
		  Exportflag[j] = 0;

		compute_diff_weights(i, 0);

		for(j = 0; j < NTask; j++)
		  {
		    if(Exportflag[j])
		      {
			DensDataIn[nexport].Pos[0] = P[i].Pos[0];
			DensDataIn[nexport].Pos[1] = P[i].Pos[1];
			DensDataIn[nexport].Pos[2] = P[i].Pos[2];
			DensDataIn[nexport].Hsml = PPP[i].Hsml;
			DensDataIn[nexport].CR_Kappa[CRpop] = SphP[i].CR_Kappa[CRpop];
			DensDataIn[nexport].CR_Kappa_egy[CRpop] = SphP[i].CR_Kappa_egy[CRpop];

			DensDataIn[nexport].Index = i;
			DensDataIn[nexport].Task = j;
			nexport++;
			nsend_local[j]++;
		      }
		  }
	      }

	  qsort(DensDataIn, nexport, sizeof(struct densdata_in), dens_compare_key);

	  for(j = 1, noffset[0] = 0; j < NTask; j++)
	    noffset[j] = noffset[j - 1] + nsend_local[j - 1];

	  MPI_Allgather(nsend_local, NTask, MPI_INT, nsend, NTask, MPI_INT, MPI_COMM_WORLD);

	  /* now do the particles that need to be exported */

	  for(level = 1; level < (1 << PTask); level++)
	    {
	      for(j = 0; j < NTask; j++)
		nbuffer[j] = 0;
	      for(ngrp = level; ngrp < (1 << PTask); ngrp++)
		{
		  maxfill = 0;
		  for(j = 0; j < NTask; j++)
		    {
		      if((j ^ ngrp) < NTask)
			if(maxfill < nbuffer[j] + nsend[(j ^ ngrp) * NTask + j])
			  maxfill = nbuffer[j] + nsend[(j ^ ngrp) * NTask + j];
		    }
		  if(maxfill >= All.BunchSizeDensity)
		    break;

		  sendTask = ThisTask;
		  recvTask = ThisTask ^ ngrp;

		  if(recvTask < NTask)
		    {
		      if(nsend[ThisTask * NTask + recvTask] > 0 || nsend[recvTask * NTask + ThisTask] > 0)
			{
			  /* get the particles */
			  MPI_Sendrecv(&DensDataIn[noffset[recvTask]],
				       nsend_local[recvTask] * sizeof(struct densdata_in), MPI_BYTE,
				       recvTask, TAG_CONDUCT_A,
				       &DensDataGet[nbuffer[ThisTask]],
				       nsend[recvTask * NTask + ThisTask] * sizeof(struct densdata_in),
				       MPI_BYTE, recvTask, TAG_CONDUCT_A, MPI_COMM_WORLD, &status);
			}
		    }

		  for(j = 0; j < NTask; j++)
		    if((j ^ ngrp) < NTask)
		      nbuffer[j] += nsend[(j ^ ngrp) * NTask + j];
		}

	      for(j = 0; j < nbuffer[ThisTask]; j++)
		compute_diff_weights(j, 1);

	      for(j = 0; j < NTask; j++)
		nbuffer[j] = 0;
	      for(ngrp = level; ngrp < (1 << PTask); ngrp++)
		{
		  maxfill = 0;
		  for(j = 0; j < NTask; j++)
		    {
		      if((j ^ ngrp) < NTask)
			if(maxfill < nbuffer[j] + nsend[(j ^ ngrp) * NTask + j])
			  maxfill = nbuffer[j] + nsend[(j ^ ngrp) * NTask + j];
		    }
		  if(maxfill >= All.BunchSizeDensity)
		    break;

		  sendTask = ThisTask;
		  recvTask = ThisTask ^ ngrp;

		  if(recvTask < NTask)
		    {
		      if(nsend[ThisTask * NTask + recvTask] > 0 || nsend[recvTask * NTask + ThisTask] > 0)
			{
			  /* send the results */
			  MPI_Sendrecv(&DensDataResult[nbuffer[ThisTask]],
				       nsend[recvTask * NTask + ThisTask] * sizeof(struct densdata_out),
				       MPI_BYTE, recvTask, TAG_CONDUCT_B,
				       &DensDataPartialResult[noffset[recvTask]],
				       nsend_local[recvTask] * sizeof(struct densdata_out),
				       MPI_BYTE, recvTask, TAG_CONDUCT_B, MPI_COMM_WORLD, &status);

			  /* add the result to the particles */
			  for(j = 0; j < nsend_local[recvTask]; j++)
			    {
			      source = j + noffset[recvTask];
			      place = DensDataIn[source].Index;

			      SphP[place].CR_WeightSum += DensDataPartialResult[source].CR_WeightSum;
			      SphP[place].CR_WeightSum_egy += DensDataPartialResult[source].CR_WeightSum_egy;
			    }
			}
		    }

		  for(j = 0; j < NTask; j++)
		    if((j ^ ngrp) < NTask)
		      nbuffer[j] += nsend[(j ^ ngrp) * NTask + j];
		}
	      level = ngrp - 1;
	    }

	  MPI_Allgather(&ndone, 1, MPI_INT, ndonelist, 1, MPI_INT, MPI_COMM_WORLD);
	  for(j = 0; j < NTask; j++)
	    ntotleft -= ndonelist[j];
	}

  /*************** now do the diffusion step itself */

      numlist = mymalloc(NTask * sizeof(int) * NTask);
      MPI_Allgather(&NumSphUpdate, 1, MPI_INT, numlist, 1, MPI_INT, MPI_COMM_WORLD);
      for(i = 0, ntot = 0; i < NTask; i++)
	ntot += numlist[i];
      myfree(numlist);

      /* first, sum the weights */

      i = 0;			/* beginn with this index */
      ntotleft = ntot;		/* particles left for all tasks together */

      while(ntotleft > 0)
	{
	  for(j = 0; j < NTask; j++)
	    nsend_local[j] = 0;

	  /* do local particles and prepare export list */

	  for(nexport = 0, ndone = 0; i < NumPart && nexport < All.BunchSizeDensity - NTask; i++)
	    if(P[i].Type == 0)
	      {
		ndone++;

		for(j = 0; j < NTask; j++)
		  Exportflag[j] = 0;

		scatter_diffusion(i, 0);

		for(j = 0; j < NTask; j++)
		  {
		    if(Exportflag[j])
		      {
			DensDataIn[nexport].Pos[0] = P[i].Pos[0];
			DensDataIn[nexport].Pos[1] = P[i].Pos[1];
			DensDataIn[nexport].Pos[2] = P[i].Pos[2];

			DensDataIn[nexport].CR_Kappa[CRpop] = SphP[i].CR_Kappa[CRpop];
			DensDataIn[nexport].CR_Kappa_egy[CRpop] = SphP[i].CR_Kappa_egy[CRpop];
			DensDataIn[nexport].CR_WeightSum = SphP[i].CR_WeightSum;
			DensDataIn[nexport].CR_WeightSum_egy = SphP[i].CR_WeightSum_egy;
			DensDataIn[nexport].Hsml = PPP[i].Hsml;
			DensDataIn[nexport].CR_E0[CRpop] = SphP[i].CR_E0[CRpop];
			DensDataIn[nexport].CR_n0[CRpop] = SphP[i].CR_n0[CRpop];

			DensDataIn[nexport].Index = i;
			DensDataIn[nexport].Task = j;
			nexport++;
			nsend_local[j]++;
		      }
		  }
	      }

	  qsort(DensDataIn, nexport, sizeof(struct densdata_in), dens_compare_key);

	  for(j = 1, noffset[0] = 0; j < NTask; j++)
	    noffset[j] = noffset[j - 1] + nsend_local[j - 1];

	  MPI_Allgather(nsend_local, NTask, MPI_INT, nsend, NTask, MPI_INT, MPI_COMM_WORLD);

	  /* now do the particles that need to be exported */

	  for(level = 1; level < (1 << PTask); level++)
	    {
	      for(j = 0; j < NTask; j++)
		nbuffer[j] = 0;
	      for(ngrp = level; ngrp < (1 << PTask); ngrp++)
		{
		  maxfill = 0;
		  for(j = 0; j < NTask; j++)
		    {
		      if((j ^ ngrp) < NTask)
			if(maxfill < nbuffer[j] + nsend[(j ^ ngrp) * NTask + j])
			  maxfill = nbuffer[j] + nsend[(j ^ ngrp) * NTask + j];
		    }
		  if(maxfill >= All.BunchSizeDensity)
		    break;

		  sendTask = ThisTask;
		  recvTask = ThisTask ^ ngrp;

		  if(recvTask < NTask)
		    {
		      if(nsend[ThisTask * NTask + recvTask] > 0 || nsend[recvTask * NTask + ThisTask] > 0)
			{
			  /* get the particles */
			  MPI_Sendrecv(&DensDataIn[noffset[recvTask]],
				       nsend_local[recvTask] * sizeof(struct densdata_in), MPI_BYTE,
				       recvTask, TAG_CONDUCT_A,
				       &DensDataGet[nbuffer[ThisTask]],
				       nsend[recvTask * NTask + ThisTask] * sizeof(struct densdata_in),
				       MPI_BYTE, recvTask, TAG_CONDUCT_A, MPI_COMM_WORLD, &status);
			}
		    }

		  for(j = 0; j < NTask; j++)
		    if((j ^ ngrp) < NTask)
		      nbuffer[j] += nsend[(j ^ ngrp) * NTask + j];
		}

	      for(j = 0; j < nbuffer[ThisTask]; j++)
		scatter_diffusion(j, 1);

	      level = ngrp - 1;
	    }

	  MPI_Allgather(&ndone, 1, MPI_INT, ndonelist, 1, MPI_INT, MPI_COMM_WORLD);
	  for(j = 0; j < NTask; j++)
	    ntotleft -= ndonelist[j];
	}

      /* now set the new cosmic ray prorperties */

      egysum = 0;

      for(n = 0; n < N_gas; n++)
	{
	  if(P[n].Type == 0)
	    {
	      SphP[n].CR_E0[CRpop] = SphP[n].CR_DeltaE[CRpop];
	      SphP[n].CR_n0[CRpop] = SphP[n].CR_DeltaN[CRpop];

	      egysum += SphP[n].CR_E0[CRpop];

	      SphP[n].CR_DeltaE[CRpop] = 0;
	      SphP[n].CR_DeltaN[CRpop] = 0;

	      if(SphP[n].CR_n0[CRpop] > 1.0e-12 && SphP[n].CR_E0[CRpop] > 0)
		{
		  meanKineticEnergy = SphP[n].CR_E0[CRpop] * m_p / SphP[n].CR_n0[CRpop];

		  qmeanKin = CR_q_from_mean_kinetic_energy(meanKineticEnergy[CRpop], CRpop);

		  SphP[n].CR_q0[CRpop] = qmeanKin * pow(SphP[n].d.Density * a3inv, -(1.0 / 3.0));
		  SphP[n].CR_C0[CRpop] = SphP[n].CR_n0[CRpop] * (All.CR_Alpha[CRpop] - 1.0) *
		    pow(SphP[n].CR_q0[CRpop], All.CR_Alpha[CRpop] - 1.0);
		}
	      else
		{
		  SphP[n].CR_E0[CRpop] = 0.0;
		  SphP[n].CR_n0[CRpop] = 0.0;

		  SphP[n].CR_q0[CRpop] = 1.0e10;
		  SphP[n].CR_C0[CRpop] = 0.0;
		}
	    }
	}

      MPI_Allreduce(&egysum, &egytot, 1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
      if(ThisTask == 0)
	{
	  printf("Energy before/after= %g | %g\n", egytot_before, egytot);
	  fflush(stdout);
	}

      myfree(ndonelist);
      myfree(nsend);
      myfree(nsend_local);
      myfree(nbuffer);
      myfree(noffset);

      All.TimeOfLastDiffusion = All.Time;
    }
}

void compute_diff_weights(int target, int mode)
{
  int j, n;
  int startnode, numngb_inbox;
  double h;
  double weightsum, weightsum_egy, kappa, kappa_egy, kappaeff, kappaeff_egy;;
  double dx, dy, dz, r, r2;
  MyFloat *pos;

  if(mode == 0)
    {
      pos = P[target].Pos;
      h = PPP[target].Hsml;
      kappa = SphP[target].CR_Kappa;
      kappa_egy = SphP[target].CR_Kappa_egy;
    }
  else
    {
      pos = DensDataGet[target].Pos;
      h = DensDataGet[target].Hsml;
      kappa = DensDataGet[target].CR_Kappa;
      kappa_egy = DensDataGet[target].CR_Kappa_egy;
    }

  weightsum = weightsum_egy = 0;

  startnode = All.MaxPart;

  kappaeff = 4 * kappa * (All.Time - All.TimeOfLastDiffusion);
  kappaeff_egy = 4 * kappa_egy * (All.Time - All.TimeOfLastDiffusion);

  if(kappaeff > 0)
    {
      do
	{
	  numngb_inbox = ngb_treefind_variable(&pos[0], h, &startnode);

	  for(n = 0; n < numngb_inbox; n++)
	    {
	      j = Ngblist[n];

	      dx = pos[0] - P[j].Pos[0];
	      dy = pos[1] - P[j].Pos[1];
	      dz = pos[2] - P[j].Pos[2];

#ifdef PERIODIC			/*  now find the closest image in the given box size  */
	      if(dx > boxHalf_X)
		dx -= boxSize_X;
	      if(dx < -boxHalf_X)
		dx += boxSize_X;
	      if(dy > boxHalf_Y)
		dy -= boxSize_Y;
	      if(dy < -boxHalf_Y)
		dy += boxSize_Y;
	      if(dz > boxHalf_Z)
		dz -= boxSize_Z;
	      if(dz < -boxHalf_Z)
		dz += boxSize_Z;
#endif
	      r2 = dx * dx + dy * dy + dz * dz;

	      if(r2 < h * h)
		{
		  r = sqrt(r2);

		  weightsum += P[j].Mass * exp(-r2 / kappaeff);
		  weightsum_egy += P[j].Mass * exp(-r2 / kappaeff_egy);
		}
	    }
	}
      while(startnode >= 0);
    }

  if(mode == 0)
    {
      SphP[target].CR_WeightSum = weightsum;
      SphP[target].CR_WeightSum_egy = weightsum_egy;
    }
  else
    {
      DensDataResult[target].CR_WeightSum = weightsum;
      DensDataResult[target].CR_WeightSum_egy = weightsum_egy;
    }

}

void scatter_diffusion(int target, int mode)
{
  int j, n;
  int startnode, numngb_inbox;
  double h, weight;
  double weightsum, kappa, kappaeff, CR_E0, CR_n0;
  double weightsum_egy, kappa_egy, kappaeff_egy;
  double dx, dy, dz, r, r2;
  MyFloat *pos;

  if(mode == 0)
    {
      pos = P[target].Pos;
      h = PPP[target].Hsml;
      kappa = SphP[target].CR_Kappa;
      kappa_egy = SphP[target].CR_Kappa_egy;
      weightsum = SphP[target].CR_WeightSum;
      weightsum_egy = SphP[target].CR_WeightSum_egy;
      CR_E0 = SphP[target].CR_E0;
      CR_n0 = SphP[target].CR_n0;
    }
  else
    {
      pos = DensDataGet[target].Pos;
      h = DensDataGet[target].Hsml;
      kappa = DensDataGet[target].CR_Kappa;
      kappa_egy = DensDataGet[target].CR_Kappa_egy;
      weightsum = DensDataGet[target].CR_WeightSum;
      weightsum_egy = DensDataGet[target].CR_WeightSum_egy;
      CR_E0 = DensDataGet[target].CR_E0;
      CR_n0 = DensDataGet[target].CR_n0;
    }

  startnode = All.MaxPart;

  kappaeff = 4 * kappa * (All.Time - All.TimeOfLastDiffusion);
  kappaeff_egy = 4 * kappa_egy * (All.Time - All.TimeOfLastDiffusion);

  if(kappaeff > 0)
    {
      do
	{
	  numngb_inbox = ngb_treefind_variable(&pos[0], h, &startnode);

	  for(n = 0; n < numngb_inbox; n++)
	    {
	      j = Ngblist[n];

	      dx = pos[0] - P[j].Pos[0];
	      dy = pos[1] - P[j].Pos[1];
	      dz = pos[2] - P[j].Pos[2];

#ifdef PERIODIC			/*  now find the closest image in the given box size  */
	      if(dx > boxHalf_X)
		dx -= boxSize_X;
	      if(dx < -boxHalf_X)
		dx += boxSize_X;
	      if(dy > boxHalf_Y)
		dy -= boxSize_Y;
	      if(dy < -boxHalf_Y)
		dy += boxSize_Y;
	      if(dz > boxHalf_Z)
		dz -= boxSize_Z;
	      if(dz < -boxHalf_Z)
		dz += boxSize_Z;
#endif
	      r2 = dx * dx + dy * dy + dz * dz;

	      if(r2 < h * h)
		{
		  r = sqrt(r2);

		  weight = P[j].Mass * exp(-r2 / kappaeff) / weightsum;
		  SphP[j].CR_DeltaN += CR_n0 * weight;

		  weight = P[j].Mass * exp(-r2 / kappaeff_egy) / weightsum_egy;
		  SphP[j].CR_DeltaE += CR_E0 * weight;

		}
	    }
	}
      while(startnode >= 0);
    }
}

#endif


#include <stdio.h>
#include <math.h>
#include <gsl/gsl_integration.h>
#include <gsl/gsl_math.h>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_odeiv.h>
#define DIM 1
#define NPTS 100

#define abserr 1e-5
#define relerr 1e-5
#define mem 10000
typedef double (* function) (double x, void * params);
double integrate(function f, void * params, double lower_bound, double upper_bound);

//int func (double t, const double y[], double f[], void *params);
//void gsl_ode(gsl_odeiv_system sys, double Xo, double maxtime);


#ifndef IBS_HPP
#define IBS_HPP

#include <array>
#include <assert.h>
#include <memory>
#include <vector>

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

#include "jspec2/force.h"
#include "jspec2/rate.h"

class Lattice;
class IonBeam;

enum class IBSModel {MARTINI, BM, BMC, BMZ};

class IBSSolver {
protected:
    double log_c_ = 0.0;     //Coulomb logarithm.
    double k_ = 0.0;          //Coupling rate in transverse directions.
    bool cache_invalid = true;
    bool ibs_by_element = false; //Calculate and output the ibs rate contribution element by element.

    void ibs_coupling(double &rx, double &ry, double k, double emit_x, double emit_y);
    void ibs_by_element_sddshead(std::ofstream& outfile, int n_element);
public:
    double log_c() const { return log_c_; }
    double k() const { return k_; }
    void set_k(double x) { k_ = x; }
    void set_log_c(double x) { log_c_ = x; }
    void set_ibs_by_element(bool b) {ibs_by_element = b;}
    void invalidate_cache() { cache_invalid = true; }

    IBSSolver(double log_c, double k);

    virtual rate3d rate(const Lattice &lattice, const IonBeam &beam) = 0;
};

class IBSSolver_Martini : public IBSSolver {
private:
    struct TrigonometryStorageUV {
        double sin_u2_cos_v2;
        double g1;
        double g2_1;
        double g2_2;
    };
    struct TrigonometryStorageV {
        double sin_v;
        double cos_v;
    };
    struct TrigonometryStorageU {
        double sin_u;
        double sin_u2;
        double cos_u2;
        double g3;
        std::vector<TrigonometryStorageUV> uv;
    };
    struct OpticalStorage {
        double a;
        double b2;
        double c2;
        double d2;
        double dtld;
        double k1;
        double k2;
        double k3;
    };

    int nu_ = 0;                //Grid number in u direction.
    int nv_ = 0;                //Grid number in v direction.
    int nz_ = 0;                //Grid number in z direction.

    // Scratch variables for IBS calculation (Martini model)
    std::vector<double> sigma_xbet, sigma_xbetp, sigma_y, sigma_yp;
    std::vector<TrigonometryStorageU> storage_u;
    std::vector<TrigonometryStorageV> storage_v;
    std::vector<OpticalStorage> storage_opt;
    std::vector<double> f1, f2, f3;

    void bunch_size(const Lattice &lattice, const IonBeam &beam);
    void abcdk(const Lattice &lattice, const IonBeam &beam);
    void coef_f();
    void f();
    double coef_a(const Lattice &lattice, const IonBeam &beam) const;
public:
    int nu() const { return nu_; }
    int nv() const { return nv_; }
    int nz() const { return nz_; }
    void set_nu(int nu) { assert(nu>0&&"Wrong value of nu in IBS parameters!"); nu_ = nu; invalidate_cache(); }
    void set_nv(int nv) { assert(nv>0&&"Wrong value of nv in IBS parameters!"); nv_ = nv; invalidate_cache(); }
    void set_nz(int nz) { assert(nz>0&&"Wrong value of nz in IBS parameters!"); nz_ = nz; invalidate_cache(); }
    IBSSolver_Martini(int nu, int nv, int nz, double log_c, double k);
    virtual rate3d rate(const Lattice &lattice, const IonBeam &beam) override;
};

class IBSSolver_BM : public IBSSolver {
 private:
     struct OpticalStorage { //variables only depends on the TWISS parameters and the energy.
         double phi;
         double dx2; //D_x * D_x
         double dx_betax_phi_2; // D_x * D_x / (beta_x * beta_x) + phi * phi
         double sqrt_betay; // sqrt(beta_y)
         double gamma_phi_2; // gamma * gamma * phi * phi
     };
     struct Kernels {
         double  psi;
         double sx;
         double sp;
         double sxp;
         double inv_sigma;
     };

     // Scratch variables for IBS calculation (Bjorken-Mtingwa model using Sergei Nagitsev's formula)
     std::vector<OpticalStorage> optical_strage;
     std::vector<Kernels> kernels;
     void init_fixed_var(const Lattice &lattice, const IonBeam &beam);
     void calc_kernels(const Lattice &lattice, const IonBeam &beam);
     double coef_bm(const Lattice &lattice, const IonBeam &beam) const;
 public:
     IBSSolver_BM(double log_c, double k);
     virtual rate3d rate(const Lattice &lattice, const IonBeam &beam) override;

};

class IBSSolver_BMZ : public IBSSolver {
private:
    int nt_;     //Number of steps for integration.
    struct optcl{
        double phi_x2;
        double phi_y2;
        double hx;
        double hy;
        double dx_2_over_beta_x;
        double dy_2_over_beta_y;
        double beta_x_over_hx;
        double hy_beta_x_over_hx;
        double beta_phi_x2;
        double beta_phi_y2;
        double hy_over_beta_y;
    };
    std::vector<optcl> optical;
    double factor = 3;
    void init_optical(const Lattice &lattice);
    double calc_abc(const Lattice &lattice, const IonBeam& beam, int i, double& a, double& b, double& c,
                               double& ax, double& bx, double& ay, double& by,double& al, double& bl);
    double coef(const Lattice &lattice, const IonBeam &beam) const;
    void calc_integral(double a, double b, double c, double ax, double bx, double ay, double by, double al,
                                  double bl, double& ix, double& iy, double& is, int nt, double u);
public:
    IBSSolver_BMZ(int nt, double log_c, double k);
    void set_nt(int n){assert(n>0&&"Wrong value of nt in IBS parameters!"); nt_ = n; invalidate_cache();}
    virtual rate3d rate(const Lattice &lattice, const IonBeam &beam) override;
    void set_factor(double x){factor = x;}
};

class IBSSolver_BM_Complete : public IBSSolver {
private:
    int nt_;
    double inv_ex;
    double inv_ey;
    double inv_dp2;
    double gamma;
    double gamma2;
    double factor = 3;
    struct Itgrl{
        double lambda;
        double lambda_sqrt;
        double ct;   // ct = 1/(1-t)^2
    };
    struct Optc {
        double phix;
        double phiy;
        double hx;
        double hy;
    };

    std::vector<Optc> optc;
    std::vector<Itgrl> itgrl;
    void init_optc(const Lattice &lattice);
    double det(std::array<std::array<double, 3>,3>& l);
    double inv(std::array<std::array<double, 3>,3>& l, std::array<std::array<double, 3>,3>& v);
    double trace(std::array<std::array<double, 3>,3>& l){return l[0][0]+l[1][1]+l[2][2];}
    void calc_l(const Lattice& lattice, int i, std::array<std::array<double, 3>,3>& lh,
                std::array<std::array<double, 3>,3>& lv, std::array<std::array<double, 3>,3>& ls);
    void calc_itgl(int i, std::array<std::array<double, 3>,3>& ii, std::array<std::array<double, 3>,3>& l,
                  std::array<std::array<double, 3>,3>& ll, std::array<std::array<double, 3>,3>& lh,
                  std::array<std::array<double, 3>,3>& lv, std::array<std::array<double, 3>,3>& ls);
    void calc_beam_const(const IonBeam& beam);
    double coef(const Lattice &lattice, const IonBeam &beam) const;
public:
     IBSSolver_BM_Complete(int nt, double log_c, double k);
     void set_factor(double x){factor = x;}
     virtual rate3d rate(const Lattice &lattice, const IonBeam &beam) override;

};
//
//class IBSSolver_BMZ2 : public IBSSolver {
//private:
//    int nt_;     //Number of steps for integration.
//    struct itgrl{
//        double lambda;
////        double lambda_2;
////        double lambda_3;
//        double lambda_sqrt;
//        double ct;   // ct = 1/(1-t)^2
//    };
//    double gamma_2;
//    double gamma_2_inv;
//    double gamma_4;
//    double emit_x_inv;
//    double emit_y_inv;
//    double emit_x2_inv;
//    double emit_y2_inv;
//
//    struct optcl{
//        double phi_x;
//        double phi_y;
//        double dx_2_over_beta_x;
//        double dy_2_over_beta_y;
//    };
//    std::vector<itgrl> integral;
//    std::vector<optcl> optical;
//    struct debug {
//        double  a, b, c, ax, bx, ay, by, al, bl, ix, iy, is;
//    };
//    std::vector<debug> my_debug;
//
//    gsl_integration_workspace *gw;
//
//    size_t limit = 100;
//    double espabs = 1e-12;
//    double esprel = 1e-3;
//    struct P{
//        double a;
//        double b;
//        double c;
//        double ai;
//        double bi;
//    }p;
////    double core(double q, void* params);
//
////    void init_integral(int n);
//    void init_optical(const Lattice &lattice);
//    double calc_abc(const Lattice &lattice, const Beam& beam, int i, double& a, double& b, double& c,
//                               double& ax, double& bx, double& ay, double& by,double& al, double& bl, double& scale);
//    double coef(const Lattice &lattice, const Beam &beam) const;
//    void calc_integral(double a, double b, double c, double ax, double bx, double ay, double by, double al,
//                                  double bl, double& ix, double& iy, double& is, int nt, std::vector<itgrl>& g);
//public:
//    IBSSolver_BMZ(int nt, double log_c, double k);
//    set_nt(int n){assert(n>0&&"Wrong value of nt in IBS parameters!"); nt_ = n; invalidate_cache();}
//    virtual void rate(const Lattice &lattice, const Beam &beam, double &rx, double &ry, double &rs);
//
//    ~IBSSolver_BMZ(){gsl_integration_workspace_free(gw);};
//
//};
//
//
//
//};
#endif


/**
 * \author Sylvain Marsat, University of Maryland - NASA GSFC
 *
 * \brief C header for the instrumental noise for LISA-type detectors.
 *
 * Formulas taken from Królak&al gr-qc/0401108 (c.f. section III).
 *
 */

#ifndef _LISANOISE_H
#define _LISANOISE_H

#define _XOPEN_SOURCE 500

#ifdef __GNUC__
#define UNUSED __attribute__ ((unused))
#else
#define UNUSED
#endif

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <complex.h>
#include <time.h>
#include <unistd.h>
#include <getopt.h>
#include <stdbool.h>
#include <string.h>

#include <gsl/gsl_errno.h>
#include <gsl/gsl_bspline.h>
#include <gsl/gsl_blas.h>
#include <gsl/gsl_min.h>
#include <gsl/gsl_spline.h>
#include <gsl/gsl_complex.h>

#include "constants.h"
#include "LISAgeometry.h"

#if defined(__cplusplus)
extern "C" {
#elif 0
} /* so that editors will match preceding brace */
#endif

/************************************************************************/
/****** Global variables storing min and max f for the noise PSD  *******/

/* Defines bounds in frequency beyond which we don't trust the instrument model anymore - all waveforms will be cut to this range */
/* Here extended range - allows for instance to taper the FD signal for f>1Hz */
#define __LISASimFD_Noise_fLow 1.e-6
#define __LISASimFD_Noise_fHigh 5.
/* Original, more conservative bounds */
//#define __LISASimFD_Noise_fLow 1.e-5
//#define __LISASimFD_Noise_fHigh 1.

/**************************************************************/
/****** Prototypes: functions evaluating the noise PSD  *******/

/* Function returning the relevant noise function, given a set of TDI observables and a channel */
ObjectFunction NoiseFunction(const LISAconstellation *variant, const TDItag tditag, const int nchan);

/* Noise Sn for TDI observables - factors have been scaled out both in the response and the noise */
double SnXYZ(const LISAconstellation *variant, double f);
double Snalphabetagamma(const LISAconstellation *variant, double f);
double SnAXYZ(const LISAconstellation *variant, double f);
double SnEXYZ(const LISAconstellation *variant, double f);
double SnTXYZ(const LISAconstellation *variant, double f);
double SnAalphabetagamma(const LISAconstellation *variant, double f);
double SnEalphabetagamma(const LISAconstellation *variant, double f);
double SnTalphabetagamma(const LISAconstellation *variant, double f);

/* Noise functions for AET(XYZ) without rescaling */
double SnAXYZNoRescaling(const LISAconstellation *variant, double f);
double SnEXYZNoRescaling(const LISAconstellation *variant, double f);
double SnTXYZNoRescaling(const LISAconstellation *variant, double f);

/* Function returning the relevant noise function, given a set of TDI observables and a channel */
/* double (*NoiseFunction(const TDItag tditag, const int chan))(double); */

/* The noise functions themselves */
/* double NoiseSnA(const double f); */
/* double NoiseSnE(const double f); */
/* double NoiseSnT(const double f); */

#if 0
{ /* so that editors will match succeeding brace */
#elif defined(__cplusplus)
}
#endif

#endif /* _LISANOISE_H */


/* rstat/rquantile.c
 *
 * Copyright (C) 2015 Patrick Alken
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include <config.h>
#include <stdlib.h>
#include <math.h>
#include <gsl/gsl_math.h>
#include <gsl/gsl_sort.h>
#include <gsl/gsl_statistics.h>
#include <gsl/gsl_rstat.h>

/*
 * Running quantile calculation based on the paper
 *
 * [1] R. Jain and I. Chlamtac, "The P^2 algorithm for dynamic
 *     calculation of quantiles and histograms without storing
 *     observations", Communications of the ACM, October 1985
 */

static double calc_psq(const double qp1, const double q, const double qm1,
                       const double d, const double np1, const double n, const double nm1);

gsl_rstat_quantile_workspace *
gsl_rstat_quantile_alloc(const double p)
{
  gsl_rstat_quantile_workspace *w;

  w = calloc(1, sizeof(gsl_rstat_quantile_workspace));
  if (w == 0)
    {
      GSL_ERROR_NULL ("failed to allocate space for workspace", GSL_ENOMEM);
    }

  w->p = p;

  gsl_rstat_quantile_reset(w);

  return w;
} /* gsl_rstat_quantile_alloc() */

void
gsl_rstat_quantile_free(gsl_rstat_quantile_workspace *w)
{
  free(w);
} /* gsl_rstat_quantile_free() */

int
gsl_rstat_quantile_reset(gsl_rstat_quantile_workspace *w)
{
  const double p = w->p;
  size_t i;

  /* initialize positions n */
  for (i = 0; i < 5; ++i)
    w->npos[i] = i + 1;

  /* initialize n' */
  w->np[0] = 1.0;
  w->np[1] = 1.0 + 2.0 * p;
  w->np[2] = 1.0 + 4.0 * p;
  w->np[3] = 3.0 + 2.0 * p;
  w->np[4] = 5.0;

  /* initialize dn' */
  w->dnp[0] = 0.0;
  w->dnp[1] = 0.5 * p;
  w->dnp[2] = p;
  w->dnp[3] = 0.5 * (1.0 + p);
  w->dnp[4] = 1.0;

  w->n = 0;

  return GSL_SUCCESS;
}

int
gsl_rstat_quantile_add(const double x, gsl_rstat_quantile_workspace *w)
{
  if (w->n < 5)
    {
      w->q[w->n] = x;
    }
  else
    {
      int i;
      int k = -1;

      if (w->n == 5)
        {
          /* initialization: sort the first five heights */
          gsl_sort(w->q, 1, w->n);
        }

      /* step B1: find k such that q_k <= x < q_{k+1} */
      if (x < w->q[0])
        {
          w->q[0] = x;
          k = 0;
        }
      else if (x >= w->q[4])
        {
          w->q[4] = x;
          k = 3;
        }
      else
        {
          for (i = 0; i <= 3; ++i)
            {
              if (w->q[i] <= x && x < w->q[i + 1])
                {
                  k = i;
                  break;
                }
            }
        }

      if (k < 0)
        {
          /* we could get here if x is nan */
          GSL_ERROR ("invalid input argument x", GSL_EINVAL);
        }

      /* step B2(a): update n_i */
      for (i = k + 1; i <= 4; ++i)
        ++(w->npos[i]);

      /* step B2(b): update n_i' */
      for (i = 0; i < 5; ++i)
        w->np[i] += w->dnp[i];

      /* step B3: update heights */
      for (i = 1; i <= 3; ++i)
        {
          double ni = (double) w->npos[i];
          double d = w->np[i] - ni;

          if ((d >= 1.0 && (w->npos[i + 1] - w->npos[i] > 1)) ||
              (d <= -1.0 && (w->npos[i - 1] - w->npos[i] < -1)))
            {
              int dsign = (d > 0.0) ? 1 : -1;
              double qp1 = w->q[i + 1];
              double qi = w->q[i];
              double qm1 = w->q[i - 1];
              double np1 = (double) w->npos[i + 1];
              double nm1 = (double) w->npos[i - 1];
              double qp = calc_psq(qp1, qi, qm1, (double) dsign,
                                   np1, ni, nm1);

              if (qm1 < qp && qp < qp1)
                w->q[i] = qp;
              else
                {
                  /* use linear formula */
                  w->q[i] += dsign * (w->q[i + dsign] - qi) / ((double) w->npos[i + dsign] - ni);
                }

              w->npos[i] += dsign;
            }
        }
    }

  ++(w->n);

  return GSL_SUCCESS;
} /* gsl_rstat_quantile_add() */

double
gsl_rstat_quantile_get(gsl_rstat_quantile_workspace *w)
{
  if (w->n >= 5)
    {
      return w->q[2];
    }
  else
    {
      /* not yet initialized */
      gsl_sort(w->q, 1, w->n);
      return gsl_stats_quantile_from_sorted_data(w->q, 1, w->n, w->p);
    }
} /* gsl_rstat_quantile_get() */

static double
calc_psq(const double qp1, const double q, const double qm1,
         const double d, const double np1, const double n, const double nm1)
{
  double outer = d / (np1 - nm1);
  double inner_left = (n - nm1 + d) * (qp1 - q) / (np1 - n);
  double inner_right = (np1 - n - d) * (q - qm1) / (n - nm1);

  return q + outer * (inner_left + inner_right);
} /* calc_psq() */


/*
 * CARP/Kaczmarz sweep on diagonally banded matrix. The essential loop is:
 *
 * for each row i
 *     x = x + w*(b(i) - A(i,:)*x)*A(i,:)'
 * end
 *
 * The matrix is given in band storage format, where each row (stored
 * contiguously in memory) of the array R stores a diagonal of the matrix with
 * offset idx(j), such that
 *
 * 	   A(i,i+idx(j)) = R(i,j)
 *
 * use (from MATLAB):
 * 		y = sweepR_mex(R,idx,x,b,w,dir)
 *
 * 		R			- matrix of diagonals of matrix A
 *		idx			- offsets of diagonals
 *		x			- initial guess
 * 		b 			- right hand side (source)
 *		w 			- relaxation parameter (0 <= w <= 2)
 *		dir 		- if dir > 0, go through matrix rows in ascending order.
 * 			  		  if dir < 0, go through matrix rows in descending order.
 * 		n_threads	- OPTIONAL argument to control the number of execution
 * 		threads solving CARP blocks in parallel. The number of threads can also
 * 		be defined via an environment variable (OMP_NUM_THREADS), but this
 * 		optional argument takes precedence. The default number of threads is
 * 		one. Take care if using more than one MATLAB worker per node: each
 * 		MATLAB worker will use OMP_NUM_THREADS, so if there are four workers on
 * 		a node, there will be 4 x OMP_NUM_THREADS parallel CARP sweeps.
 *
 * Author: Art Petrenko, Tristan van Leeuwen
 *         Seismic Laboratory for Imaging and Modeling
 *         Department of Earth, Ocean, and Atmosperic Sciences
 *         The University of British Columbia
 *
 * Date: July, 2014

 * You may use this code only under the conditions and terms of the
 * license contained in the file LICENSE provided with this source
 * code. If you do not agree to these terms you may not use this
 * software.
*/

#include <stdlib.h> /* for getenv */
#include <stddef.h> /* for size_t type */
#include <string.h> /* for memcpy */
#include <pthread.h> /* for threading */

/* The following section allows this file to compile on Mac OS X 10.8.5. Pass
 * the flag -DARCH_MACI64 to the compiler to activate it. */
#ifdef ARCH_MACI64
#include "pthread_barrier.h"
#include <mach/error.h>
typedef wchar_t char16_t;
#else /* not ARCH_MACI64 */
#include <error.h>
#endif /* ARCH_MACI64 */

#include <math.h>
#include <mex.h>
#include <matrix.h>
#include <blas.h>

struct copy_init_guess_data_t
{
	double *copy_src_real, *copy_dst_real;
	double *copy_src_imag, *copy_dst_imag;
	long n_to_copy;
};

struct sweep_data_t
{
	long start_row, end_row, ncol, ny, nx, haloWidth, main_diagonal_offset;
	double *Rr, *Ri, *yr, *yi, *br, *bi;
	long *idx;
	double w;
	int dir;
};

struct average_data_t
{
	double *copy_src_real, *copy_dst_real;
	double *copy_src_imag, *copy_dst_imag;
	double *halo_1_real, *halo_2_real;
	double *halo_1_imag, *halo_2_imag;
	double *halo_dst_real, *halo_dst_imag;
	long n_to_copy, n_in_halo;
};

struct thread_data_t
{
	struct copy_init_guess_data_t copy_init_guess_data;
	struct sweep_data_t sweep_data;
	struct average_data_t average_data;
	pthread_barrier_t *barrier;
};

void *do_sweep(void *thread_args_void)
{
    struct sweep_data_t *thread_args;
	/* Variables contained in thread_args_void struct */
	long start_row, end_row, haloWidth, ncol, ny, nx;
	/* Rr and Ri are pointers to short fat ncol-by-N matrices */
	double *Rr, *Ri, *yr, *yi, *br, *bi;
	long *idx;
	double w;
	int dir;
	/* Temporary storage variables */
	double cr = 0, ci = 0;
	long offset, main_diagonal_offset;

	/* Assign local pointers to data locations in shared memory */
	thread_args = (struct sweep_data_t *) thread_args_void;
	start_row 	= thread_args->start_row;
	end_row 	= thread_args->end_row;
	ncol 		= thread_args->ncol;
	ny 			= thread_args->ny;
	nx			= thread_args->nx;
	haloWidth	= thread_args->haloWidth;
	main_diagonal_offset = thread_args->main_diagonal_offset;
	Rr 			= thread_args->Rr;
	Ri 			= thread_args->Ri;
	idx 		= thread_args->idx;
	yr 			= thread_args->yr;
	yi 			= thread_args->yi;
	br 			= thread_args->br;
	bi 			= thread_args->bi;
	w 			= thread_args->w;
	dir 		= thread_args->dir;

	offset = (start_row == 0 ? 0 : haloWidth - main_diagonal_offset);

	/* Kaczmarz sweep on one row block */
	for(long i = (dir > 0 ? start_row : end_row-1);
		dir > 0 ? i<end_row : i>=start_row;
		dir > 0 ? i++ : i--)
	{

		if (0 <= i + main_diagonal_offset && i + main_diagonal_offset < nx){
			cr = br[i + main_diagonal_offset];
			ci = bi[i + main_diagonal_offset];
		}
		else{
		  //error(1,0,"Discovery of whether the iterate vector is haloed failed.");
		}
		/* First loop over non-zero row elements calculates inner product
		 * of matrix row and CARP iterate */
		for(long j=0, k; j<ncol;j++){
			/* i + idx[j] is the column index for the full Helmholtz matrix.
			 * k is the index into the vector representing the CARP iterate
			 * of the given block. */
			k = i + idx[j] - start_row + offset;
			if(0<=k && k<ny){
				cr -= Rr[i*ncol + j]*yr[k] - Ri[i*ncol + j]*yi[k];
				ci -= Rr[i*ncol + j]*yi[k] + Ri[i*ncol + j]*yr[k];
			}
		}
		/* Second loop over non-zero row elements updates CARP iterate */
		cr *= w;
		ci *= w;
		for(long j=0, k; j<ncol;j++){
			k = i + idx[j] - start_row + offset;
			if(0<=k && k<ny){
				yr[k] +=   cr*Rr[i*ncol + j] + ci*Ri[i*ncol + j];
				yi[k] +=  -cr*Ri[i*ncol + j] + ci*Rr[i*ncol + j];
			}
		}
	}

	return NULL;
}

void *average_halos(void *thread_args_void)
{
	struct average_data_t *thread_args;
	double *copy_src_real, *copy_dst_real;
	double *copy_src_imag, *copy_dst_imag;
	double *halo_1_real, *halo_2_real;
	double *halo_1_imag, *halo_2_imag;
	double *halo_dst_real, *halo_dst_imag;
	size_t n_to_copy, n_in_halo;

	/* Assign local pointer to data locations in shared memory */
	thread_args 	= (struct average_data_t *) thread_args_void;
	copy_src_real	= thread_args->copy_src_real;
	copy_dst_real	= thread_args->copy_dst_real;
	copy_src_imag	= thread_args->copy_src_imag;
	copy_dst_imag	= thread_args->copy_dst_imag;
	n_to_copy		= thread_args->n_to_copy;
	halo_1_real     = thread_args->halo_1_real;
	halo_2_real		= thread_args->halo_2_real;
	halo_dst_real	= thread_args->halo_dst_real;
	halo_1_imag		= thread_args->halo_1_imag;
	halo_2_imag		= thread_args->halo_2_imag;
	halo_dst_imag   = thread_args->halo_dst_imag;
	n_in_halo		= thread_args->n_in_halo;

	/* Copy the non-halo parts of the domain block directly to output array */
	memcpy((void *)copy_dst_real, (void *)copy_src_real, sizeof(double)*n_to_copy);
	memcpy((void *)copy_dst_imag, (void *)copy_src_imag, sizeof(double)*n_to_copy);

	/* Average the halo parts of the domain block and copy to output array.
	 * NOTE: this assumes domain blocks overlap only with their nearest
	 * neighbour segments. */
	for (long i = 0; i < n_in_halo; i++){
		halo_dst_real[i] = (halo_1_real[i] + halo_2_real[i]) / 2;
		halo_dst_imag[i] = (halo_1_imag[i] + halo_2_imag[i]) / 2;
	}

	return NULL;
}

void *sweep_and_average(void *thread_data_void)
{
	struct thread_data_t *thread_data = (struct thread_data_t *) thread_data_void;

	/* Copy the initial guess into the working arrays */
	memcpy((void *)thread_data->copy_init_guess_data.copy_dst_real, (void *)thread_data->copy_init_guess_data.copy_src_real, sizeof(double)*thread_data->copy_init_guess_data.n_to_copy);
	memcpy((void *)thread_data->copy_init_guess_data.copy_dst_imag, (void *)thread_data->copy_init_guess_data.copy_src_imag, sizeof(double)*thread_data->copy_init_guess_data.n_to_copy);
	do_sweep((void *) &(thread_data->sweep_data));

	pthread_barrier_wait(thread_data->barrier);

	average_halos((void *) &(thread_data->average_data));

	return NULL;
}

void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
	/* structs to hold all arguments to each thread in one variable */
	struct sweep_data_t **thread_args_sweep = NULL;
	struct average_data_t **thread_args_average = NULL;
	struct thread_data_t **thread_data = NULL;
	struct copy_init_guess_data_t **copy_init_guess_data = NULL;
	pthread_barrier_t barrier_after_sweep_before_halo_average;

	mwSize ncol, nx;
	ptrdiff_t ncolBlas, idxIncBlas = 1, maxIdxLoc = 0;
	mwSize haloWidth;
	mwSize n_threads = 1;
	char *n_threads_str = NULL;
	double *Rr,*Ri,*idxd = NULL,*xr,*xi,*br,*bi,*yr,*yi;
	long *idx = NULL;
    double w = 0;
	int dir = 1;
	mwSize N=1, numGridPointsPerBlock, main_diagonal_offset;

	/* Flags that are set if memory is allocated within the MEX file */
	int Ri_alloc=0, xi_alloc=0, bi_alloc=0;

	/* a return code flag and segment demarcation arrays */
	mwSize i_thread;
	mwSize *seg_bounds_hi, *seg_bounds_mid, *seg_bounds_row, *seg_bounds_lo;

	/* Threading variables */
	pthread_t *threadIDs = NULL;
	pthread_attr_t attr;

	/* Arrays to hold (overlapping) segments of yr and yi, a pair for each
	 * thread */
	double **yr_seg = NULL, **yi_seg = NULL;

	/* Allow worker threads to join back to main thread once they are done */
	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

	/* Read input arguments; initialize complex part to zero if input is real. */
	N = mxGetN(prhs[0]);
    ncol = mxGetM(prhs[0]);
	ncolBlas = (ptrdiff_t)ncol;
	Rr  = mxGetPr(prhs[0]);
    if(mxIsComplex(prhs[0])){
        Ri  = mxGetPi(prhs[0]);
    }
    else{
        Ri = mxCalloc(N*ncol,sizeof(double));
		Ri_alloc = 1;
    }
	idxd = mxGetPr(prhs[1]);
    nx  = mxGetM(prhs[2]);
	xr  = mxGetPr(prhs[2]);
    if(mxIsComplex(prhs[2])){
        xi  = mxGetPi(prhs[2]);
    }
    else{
        xi = mxCalloc(nx,sizeof(double));
		xi_alloc = 1;
    }
	br  = mxGetPr(prhs[3]);
    if(mxIsComplex(prhs[3])){
        bi  = mxGetPi(prhs[3]);
    }
    else{
        bi = mxCalloc(nx,sizeof(double));
		bi_alloc = 1;
    }
	if (mxGetM(prhs[3]) != nx){
	    mexPrintf('%d %d \n',mxGetM(prhs[3]),nx);
		mexErrMsgIdAndTxt("SLIM_release_apps:tools:algorithms:ThreeDFreqModeling:sweepR_mex:NumElements",
				"The number of elements in the iterate and right hand side vectors must be equal.");
	}
	w   = mxGetScalar(prhs[4]);
	dir = lrint(mxGetScalar(prhs[5]));

	/* The default value for the number of threads can be overridden by an
	 * environment variable. */
	n_threads_str = getenv("OMP_NUM_THREADS");
	if (n_threads_str == NULL){
		n_threads = 1;
	}
	else{
		n_threads = strtol(n_threads_str, NULL, 10);
		if(n_threads < 1){
			n_threads = 1;
		}
	}
	/* The environment variable can in turn be overridden by an optional
	 * argument to the mexFunction. */
	if (nrhs >= 7){
		if(1 <= lrint(mxGetScalar(prhs[6]))){
			n_threads = lrint(mxGetScalar(prhs[6]));
		}
	}

	/* Allocate the final output vector */
	plhs[0]  = mxCreateDoubleMatrix(nx, 1, mxCOMPLEX);
	yr       = mxGetPr(plhs[0]);
    yi       = mxGetPi(plhs[0]);

	/* Check to make sure memory was allocated correctly */
	if (Rr==NULL || Ri==NULL || idxd==NULL || xr==NULL || xi==NULL || br==NULL || bi==NULL ||
	    yr==NULL || yi==NULL){
		mexErrMsgIdAndTxt("SLIM_release_apps:tools:algorithms:ThreeDFreqModeling:sweepR_mex:OutOfMemory",
				"Could not allocate memory for main computational variables.");
	}

	if ((idx = (long *) mxCalloc(ncol, sizeof(long))) == NULL){
		mexErrMsgIdAndTxt("SLIM_release_apps:tools:algorithms:ThreeDFreqModeling:sweepR_mex:OutOfMemory",
				"Could not allocate memory for main computational variables.");
	}
	for (mwSize i=0; i < ncol; i++){
		idx[i] = lrint(idxd[i]);
	}

	/* Compute (half) halo width. Remember that BLAS routines like idamax
	 * return FORTRAN-style indices. */
	maxIdxLoc = idamax_(&ncolBlas, idxd, &idxIncBlas) - 1;
	haloWidth = (mwSize)labs(idx[maxIdxLoc]);

	/* Partition the iterate vector into blocks. Note that the below
	 * partitioning scheme is slighlty different from that in pCARPCG.m in this
	 * directory. The partitioning scheme of pCARPCG corresponds to
	 * distributing a three dimensional array with dimensions given by n
	 * according to Matlab's codistributor1d.defaultPartition(n(3)), and then
	 * vectorizing it. The partition scheme of the present file instead uses
	 * Matlab's codistributor1d.defaultPartion(prod(n)). In other words,
	 * pCARPCG divides the iterate into blocks along the slow dimension,
	 * whereas sweepR_mex.c does not take dimensionality into account, only the
	 * total number of gridpoints. This is done to avoid needing an extra input
	 * parameter with the the system dimensions. The seg_bounds_hi, _lo and
	 * _mid arrays contain indices into non-haloed vectors, while the
	 * seg_bounds_row array contains indices to the rows of the system matrix.
	 *
 	 * yr_seg[i_thread-1]        overlap      yr_seg[i_thread]
	 * ------------------------|-----|-----|-------------------------------
	 *         .----------------^     |     ^-------------------.
	 *    seg_bounds_lo[i_thread], seg_bounds_mid[i_thread], seg_bounds_hi[i_thread]
	 */
	numGridPointsPerBlock = N / n_threads;
	seg_bounds_hi  = (mwSize *)mxCalloc(n_threads+1,sizeof(mwSize));
	seg_bounds_mid = (mwSize *)mxCalloc(n_threads+1,sizeof(mwSize));
	seg_bounds_lo  = (mwSize *)mxCalloc(n_threads+1,sizeof(mwSize));
	seg_bounds_row = (mwSize *)mxCalloc(n_threads+1,sizeof(mwSize));
	if (N == nx){
		main_diagonal_offset = 0;
	}
	else{
		/* The vector is haloed. We are only able to correctly process matrices
		 * with a non-zero main diagonal and symmetric off-main diagonal offsets. */
		if (ncol % 2 != 1){
			mexErrMsgIdAndTxt("SLIM_release_apps:tools:algorithms:ThreeDFreqModeling:sweepR_mex:EvenNumberOfDiags",
					"Input iterate vector appears to be haloed but there is an even number of non-zero diagonals in the system matrix.");
		}
		main_diagonal_offset = idx[ncol/2];
		for (mwSize i = 1; i <= ncol/2; i++){
			if (idx[ncol/2 + i] - main_diagonal_offset != -(idx[ncol/2 - i] - main_diagonal_offset)){
				mexErrMsgIdAndTxt("SLIM_release_apps:tools:algorithms:ThreeDFreqModeling:sweepR_mex:DiagsNotSymmetric",
						"Input iterate vector appears to be haloed but the pattern of non-zero diagonals in the system matrix is not symmetric.");
			}
		}
	}
	for (i_thread=0; i_thread<n_threads; i_thread++){
		/* First domain block */
		if (i_thread==0){
			seg_bounds_hi[i_thread]  = 0;
			seg_bounds_mid[i_thread] = 0;
			seg_bounds_row[i_thread] = 0;
			seg_bounds_lo[i_thread]  = 0;
		}
		/* Other domain blocks */
		else {
			if (i_thread <= N % n_threads) {
				seg_bounds_mid[i_thread] = (numGridPointsPerBlock+1)*i_thread + main_diagonal_offset;
				seg_bounds_row[i_thread] = (numGridPointsPerBlock+1)*i_thread;
				seg_bounds_hi[i_thread]  = (numGridPointsPerBlock+1)*i_thread + haloWidth + main_diagonal_offset;
				seg_bounds_lo[i_thread]  = (numGridPointsPerBlock+1)*i_thread - haloWidth + main_diagonal_offset;
			}
			else{
				seg_bounds_mid[i_thread] = (N % n_threads) + numGridPointsPerBlock*i_thread + main_diagonal_offset;
				seg_bounds_row[i_thread] = (N % n_threads) + numGridPointsPerBlock*i_thread;
				seg_bounds_hi[i_thread]  = (N % n_threads) + numGridPointsPerBlock*i_thread + haloWidth + main_diagonal_offset;
				seg_bounds_lo[i_thread]  = (N % n_threads) + numGridPointsPerBlock*i_thread - haloWidth + main_diagonal_offset;
			}
			/* Check that halos do not overlap each other */
			if (seg_bounds_lo[i_thread] < seg_bounds_hi[i_thread-1]){
				mexErrMsgIdAndTxt("SLIM_release_apps:tools:algorithms:ThreeDFreqModeling:sweepR_mex:TooManyThreads",
						"Too many threads; non-adjacent domain blocks share nearest-neighbour grid points.");
			}
		}
	}
	seg_bounds_lo[n_threads]  = nx;
	seg_bounds_hi[n_threads]  = nx;
	seg_bounds_mid[n_threads] = nx;
	seg_bounds_row[n_threads] = N;

	/* Allocate pointers to segments, to thread arguments and thread IDs */
	thread_args_sweep = (struct sweep_data_t **)mxCalloc(n_threads, sizeof(struct sweep_data_t *));
	if (n_threads > 1) {
		/* Set up a barrier for synchronization of all threads save the master that
		 * executes mexFunction. Note that strictly speaking, only threads working
		 * on domain blocks that share halos need to synchronize with each other.
		 * */
		if (pthread_barrier_init(&barrier_after_sweep_before_halo_average, NULL, n_threads)){
			mexErrMsgIdAndTxt("SLIM_release_apps:tools:algorithms:ThreeDFreqModeling:sweepR_mex:pthreads",
					"Could not initialize pthread barrier.");
		}

		thread_data = (struct thread_data_t **)mxCalloc(n_threads, sizeof(struct thread_data_t *));
		thread_args_average = (struct average_data_t **)mxCalloc(n_threads, sizeof(struct average_data_t *));
		copy_init_guess_data = (struct copy_init_guess_data_t **)mxCalloc(n_threads, sizeof(struct copy_init_guess_data_t *));
		yr_seg = (double **)mxCalloc(n_threads,sizeof(double *));
		yi_seg = (double **)mxCalloc(n_threads,sizeof(double *));
		threadIDs = (pthread_t *)mxCalloc(n_threads,sizeof(pthread_t));
	}
	for (i_thread=0; i_thread<n_threads; i_thread++){
		/* Allocate the segments for each thread */
		if (n_threads > 1) {
			yr_seg[i_thread] = (double *)mxCalloc((seg_bounds_hi[i_thread+1]-seg_bounds_lo[i_thread]),sizeof(double));
			yi_seg[i_thread] = (double *)mxCalloc((seg_bounds_hi[i_thread+1]-seg_bounds_lo[i_thread]),sizeof(double));
			/* Check that segments were allocated correctly */
			if (yr_seg[i_thread]==NULL || yi_seg[i_thread]==NULL){
				mexErrMsgIdAndTxt("SLIM_release_apps:tools:algorithms:ThreeDFreqModeling:sweepR_mex:pthreadsOutOfMemory",
						"Could not allocate memory for thread computational variables.");
			}
			copy_init_guess_data[i_thread] = (struct copy_init_guess_data_t *)mxCalloc(1,sizeof(struct copy_init_guess_data_t));
			copy_init_guess_data[i_thread]->copy_src_real = &xr[seg_bounds_lo[i_thread]];
			copy_init_guess_data[i_thread]->copy_src_imag = &xi[seg_bounds_lo[i_thread]];
			copy_init_guess_data[i_thread]->copy_dst_real = yr_seg[i_thread];
			copy_init_guess_data[i_thread]->copy_dst_imag = yi_seg[i_thread];
			copy_init_guess_data[i_thread]->n_to_copy = (size_t) (seg_bounds_hi[i_thread+1] - seg_bounds_lo[i_thread]);
		}

		/* Set thread arguments */
		thread_args_sweep[i_thread] = (struct sweep_data_t *)mxCalloc(1,sizeof(struct sweep_data_t));
		thread_args_sweep[i_thread]->start_row 	= seg_bounds_row[i_thread];
		thread_args_sweep[i_thread]->end_row   	= seg_bounds_row[i_thread+1];
		thread_args_sweep[i_thread]->ncol 	 	= ncol;
		thread_args_sweep[i_thread]->ny 	 	= seg_bounds_hi[i_thread+1]-seg_bounds_lo[i_thread];
		thread_args_sweep[i_thread]->nx			= nx;
		thread_args_sweep[i_thread]->haloWidth 	= haloWidth*(i_thread ? 1 : 0);
		thread_args_sweep[i_thread]->main_diagonal_offset = main_diagonal_offset;
		thread_args_sweep[i_thread]->Rr  		= Rr;
		thread_args_sweep[i_thread]->Ri  		= Ri;
		thread_args_sweep[i_thread]->idx 		= idx;
		if (n_threads > 1){
			thread_args_sweep[i_thread]->yr 	= yr_seg[i_thread];
			thread_args_sweep[i_thread]->yi 	= yi_seg[i_thread];
		}
		else{
			thread_args_sweep[i_thread]->yr	 	= yr;
			thread_args_sweep[i_thread]->yi	 	= yi;
		}
		thread_args_sweep[i_thread]->br 		= br;
		thread_args_sweep[i_thread]->bi 		= bi;
		thread_args_sweep[i_thread]->w 		 	= w;
		thread_args_sweep[i_thread]->dir 		= dir;

		if (n_threads > 1){
			/* Set the arguments for the averaging threads. Note that each
			 * thread is responsible for averaging the low end of its address
			 * range. The middle of its address range is copied, while the high
			 * end of its address range is left for the next thread. */
			thread_args_average[i_thread] = (struct average_data_t *)mxCalloc(1,sizeof(struct average_data_t));
			thread_args_average[i_thread]->copy_src_real = &(yr_seg[i_thread][seg_bounds_hi[i_thread] - seg_bounds_lo[i_thread]]);
			thread_args_average[i_thread]->copy_dst_real = &(yr[seg_bounds_hi[i_thread]]);
			thread_args_average[i_thread]->copy_src_imag = &(yi_seg[i_thread][seg_bounds_hi[i_thread] - seg_bounds_lo[i_thread]]);
			thread_args_average[i_thread]->copy_dst_imag = &(yi[seg_bounds_hi[i_thread]]);
			thread_args_average[i_thread]->n_to_copy 	 = (size_t) (seg_bounds_lo[i_thread+1] - seg_bounds_hi[i_thread]);
			thread_args_average[i_thread]->halo_1_real   = &(yr_seg[i_thread-1 >= 0 ? i_thread-1 : 0][seg_bounds_lo[i_thread] - seg_bounds_lo[i_thread-1 >= 0 ? i_thread-1 : 0]]);
			thread_args_average[i_thread]->halo_2_real	 = yr_seg[i_thread];
			thread_args_average[i_thread]->halo_dst_real = &(yr[seg_bounds_lo[i_thread]]);
			thread_args_average[i_thread]->halo_1_imag   = &(yi_seg[i_thread-1 >= 0 ? i_thread-1 : 0][seg_bounds_lo[i_thread] - seg_bounds_lo[i_thread-1 >= 0 ? i_thread-1 : 0]]);
			thread_args_average[i_thread]->halo_2_imag	 = yi_seg[i_thread];
			thread_args_average[i_thread]->halo_dst_imag = &(yi[seg_bounds_lo[i_thread]]);
			thread_args_average[i_thread]->n_in_halo 	 = (size_t) (seg_bounds_hi[i_thread] - seg_bounds_lo[i_thread]);

			thread_data[i_thread] = (struct thread_data_t *)mxCalloc(1,sizeof(struct thread_data_t));
			thread_data[i_thread]->copy_init_guess_data = *copy_init_guess_data[i_thread];
			thread_data[i_thread]->sweep_data = *thread_args_sweep[i_thread];
			thread_data[i_thread]->average_data = *thread_args_average[i_thread];
			thread_data[i_thread]->barrier = &barrier_after_sweep_before_halo_average;
		}
	}

	if (n_threads == 1) {
		/* Set the initial guess directly in the output array too */
		memcpy((void *)yr, (void *)xr, sizeof(double)*nx);
		memcpy((void *)yi, (void *)xi, sizeof(double)*nx);
		do_sweep((void *)thread_args_sweep[0]);
	}
	else{
		/* Sweep and average, in separate worker threads */
		for (i_thread=0; i_thread<n_threads; i_thread++){
			if (pthread_create(&threadIDs[i_thread], &attr, sweep_and_average, (void *)thread_data[i_thread])){
				mexErrMsgIdAndTxt("SLIM_release_apps:tools:algorithms:ThreeDFreqModeling:sweepR_mex:pthreadCreate",
						"pthread_create returned non-zero.");
			}
		}

		/* Wait for threads to finish */
		for (i_thread=0; i_thread<n_threads; i_thread++){
			if (pthread_join(threadIDs[i_thread], NULL)){
				mexErrMsgIdAndTxt("SLIM_release_apps:tools:algorithms:ThreeDFreqModeling:sweepR_mex:pthreadCreate",
						"pthread_join returned non-zero.");
			}
		}
	}

	/* Free memory if it was allocated within the MEX file. */
	if (Ri_alloc){
		mxFree(Ri);
	}
	if (xi_alloc){
		mxFree(xi);
	}
	if (bi_alloc){
		mxFree(bi);
	}
	mxFree(seg_bounds_hi);
	mxFree(seg_bounds_mid);
	mxFree(seg_bounds_row);
	mxFree(seg_bounds_lo);
	if (n_threads > 1) {
		mxFree(threadIDs);
	}
	for (i_thread=0; i_thread<n_threads; i_thread++){
		mxFree(thread_args_sweep[i_thread]);
		if (n_threads > 1){
			mxFree(thread_args_average[i_thread]);
			mxFree(thread_data[i_thread]);
			mxFree(copy_init_guess_data[i_thread]);
			mxFree(yr_seg[i_thread]);
			mxFree(yi_seg[i_thread]);
		}
	}
	if (n_threads > 1){
		pthread_barrier_destroy(&barrier_after_sweep_before_halo_average);
		mxFree(thread_args_average);
		mxFree(thread_data);
		mxFree(copy_init_guess_data);
		mxFree(yr_seg);
		mxFree(yi_seg);
	}
	mxFree(idx);
	mxFree(thread_args_sweep);
	pthread_attr_destroy(&attr);

	/* Don't think I need pthread_exit() here, because pthread_join is called above */
	return;
}


/*
ODE: a program to get optime Runge-Kutta and multi-steps methods.

Copyright 2011-2019, Javier Burguete Tolosa.

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

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

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

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

/**
 * \file rk_4_3.c
 * \brief Source file to optimize Runge-Kutta 4 steps 3rd order methods.
 * \author Javier Burguete Tolosa.
 * \copyright Copyright 2011-2019.
 */
#define _GNU_SOURCE
#include <string.h>
#include <math.h>
#include <libxml/parser.h>
#include <glib.h>
#include <libintl.h>
#include <gsl/gsl_rng.h>
#include "config.h"
#include "utils.h"
#include "optimize.h"
#include "rk.h"
#include "rk_4_3.h"

#define DEBUG_RK_4_3 0          ///< macro to debug.

/**
 * Function to obtain the coefficients of a 4 steps 3rd order Runge-Kutta
 * method.
 */
int
rk_tb_4_3 (Optimize * optimize) ///< Optimize struct.
{
  long double *tb, *r;
#if DEBUG_RK_4_3
  fprintf (stderr, "rk_tb_4_3: start\n");
#endif
  tb = optimize->coefficient;
  r = optimize->random_data;
  t4 (tb) = 1.L;
  t1 (tb) = r[0];
  t2 (tb) = r[1];
  b21 (tb) = r[2];
  t3 (tb) = r[3];
  b32 (tb) = r[4];
  b43 (tb) = r[5];
  b42 (tb) = ((1.L / 3.L - b43 (tb) * sqr (t3 (tb)))
              - t1 (tb) * (0.5L - b43 (tb) * t3 (tb)))
    / (t2 (tb) * (t2 (tb) - t1 (tb)));
  if (isnan (b42 (tb)))
    return 0;
  b41 (tb) = (0.5L - b42 (tb) * t2 (tb) - b43 (tb) * t3 (tb)) / t1 (tb);
  if (isnan (b41 (tb)))
    return 0;
  b31 (tb) = ((1.L / 6.L - b42 (tb) * b21 (tb) * t1 (tb)) / b43 (tb)
              - b32 (tb) * t2 (tb)) / t1 (tb);
  if (isnan (b31 (tb)))
    return 0;
  rk_b_4 (tb);
#if DEBUG_RK_4_3
  fprintf (stderr, "rk_tb_4_3: end\n");
#endif
  return 1;
}

/**
 * Function to obtain the coefficients of a 4 steps 3rd order, 4th order in
 * equations depending only in time, Runge-Kutta method.
 */
int
rk_tb_4_3t (Optimize * optimize)        ///< Optimize struct.
{
  long double *tb, *r;
#if DEBUG_RK_4_3
  fprintf (stderr, "rk_tb_4_3t: start\n");
#endif
  tb = optimize->coefficient;
  r = optimize->random_data;
  t4 (tb) = 1.L;
  t1 (tb) = r[0];
  t2 (tb) = r[1];
  b21 (tb) = r[2];
  t3 (tb) = r[3];
  b32 (tb) = r[4];
  b43 (tb) = (0.25L - 1.L / 3.L * t1 (tb)
              - (1.L / 3.L - 0.5L * t1 (tb)) * t2 (tb))
    / (t3 (tb) * (t3 (tb) - t2 (tb)) * (t3 (tb) - t1 (tb)));
  b42 (tb) = ((1.L / 3.L - b43 (tb) * sqr (t3 (tb)))
              - t1 (tb) * (0.5L - b43 (tb) * t3 (tb)))
    / (t2 (tb) * (t2 (tb) - t1 (tb)));
  b41 (tb) = (0.5L - b42 (tb) * t2 (tb) - b43 (tb) * t3 (tb)) / t1 (tb);
  b31 (tb) = ((1.L / 6.L - b42 (tb) * b21 (tb) * t1 (tb)) / b43 (tb)
              - b32 (tb) * t2 (tb)) / t1 (tb);
  rk_b_4 (tb);
#if DEBUG_RK_4_3
  fprintf (stderr, "rk_tb_4_3t: end\n");
#endif
  if (isnan (b31 (tb)) || isnan (b41 (tb)) || isnan (b42 (tb))
      || isnan (b43 (tb)))
    return 0;
  return 1;
}

/**
 * Function to obtain the coefficients of a 4 steps 2nd-3rd order Runge-Kutta
 * pair.
 */
int
rk_tb_4_3p (Optimize * optimize)        ///< Optimize struct.
{
  long double *tb;
#if DEBUG_RK_4_3
  fprintf (stderr, "rk_tb_4_3p: start\n");
#endif
  if (!rk_tb_4_3 (optimize))
    return 0;
  tb = optimize->coefficient;
  e41 (tb) = 0.5L / t1 (tb);
  e42 (tb) = 0.L;
  rk_e_4 (tb);
#if DEBUG_RK_4_3
  rk_print_e (optimize, "rk_tb_4_3p", stderr);
  fprintf (stderr, "rk_tb_4_3p: end\n");
#endif
  return 1;
}

/**
 * Function to obtain the coefficients of a 4 steps 2nd-3rd order, 3rd-4th order
 * in equations depending only in time, Runge-Kutta pair.
 */
int
rk_tb_4_3tp (Optimize * optimize)       ///< Optimize struct.
{
  long double *tb;
#if DEBUG_RK_4_3
  fprintf (stderr, "rk_tb_4_3tp: start\n");
#endif
  if (!rk_tb_4_3t (optimize))
    return 0;
  tb = optimize->coefficient;
  e42 (tb) = (1.L / 3.L - 0.5L * t1 (tb)) / (t2 (tb) * (t2 (tb) - t1 (tb)));
  e41 (tb) = (0.5L - e42 (tb) * t2 (tb)) / t1 (tb);
  rk_e_4 (tb);
#if DEBUG_RK_4_3
  rk_print_e (optimize, "rk_tb_4_3tp", stderr);
  fprintf (stderr, "rk_tb_4_3tp: end\n");
#endif
  if (isnan (e42 (tb)) || isnan (e41 (tb)))
    return 0;
  return 1;
}

/**
 * Function to calculate the objective function of a 4 steps 3rd order
 * Runge-Kutta method.
 *
 * \return objective function value.
 */
long double
rk_objective_tb_4_3 (RK * rk)   ///< RK struct.
{
  long double *tb;
  long double o;
#if DEBUG_RK_4_3
  fprintf (stderr, "rk_objective_tb_4_3: start\n");
#endif
  tb = rk->tb->coefficient;
  o = fminl (0.L, b20 (tb));
  if (b30 (tb) < 0.L)
    o += b30 (tb);
  if (b31 (tb) < 0.L)
    o += b31 (tb);
  if (b40 (tb) < 0.L)
    o += b40 (tb);
  if (b41 (tb) < 0.L)
    o += b41 (tb);
  if (b42 (tb) < 0.L)
    o += b42 (tb);
  if (o < 0.L)
    {
      o = 40.L - o;
      goto end;
    }
  o = 30.L + fmaxl (1.L, fmaxl (t1 (tb), fmaxl (t2 (tb), t3 (tb))));
  if (rk->strong)
    {
      rk_bucle_ac (rk);
      o = fminl (o, *rk->ac0->optimal);
    }
end:
#if DEBUG_RK_4_3
  fprintf (stderr, "rk_objective_tb_4_3: optimal=%Lg\n", o);
  fprintf (stderr, "rk_objective_tb_4_3: end\n");
#endif
  return o;
}

/**
 * Function to calculate the objective function of a 4 steps 3rd order, 4th
 * oder in equations depending only in time, Runge-Kutta method.
 *
 * \return objective function value.
 */
long double
rk_objective_tb_4_3t (RK * rk)  ///< RK struct.
{
  long double *tb;
  long double o;
#if DEBUG_RK_4_3
  fprintf (stderr, "rk_objective_tb_4_3t: start\n");
#endif
  tb = rk->tb->coefficient;
  o = fminl (0.L, b20 (tb));
  if (b30 (tb) < 0.L)
    o += b30 (tb);
  if (b31 (tb) < 0.L)
    o += b31 (tb);
  if (b40 (tb) < 0.L)
    o += b40 (tb);
  if (b41 (tb) < 0.L)
    o += b41 (tb);
  if (b42 (tb) < 0.L)
    o += b42 (tb);
  if (b43 (tb) < 0.L)
    o += b43 (tb);
  if (o < 0.L)
    {
      o = 40.L - o;
      goto end;
    }
  o = 30.L + fmaxl (1.L, fmaxl (t1 (tb), fmaxl (t2 (tb), t3 (tb))));
  if (rk->strong)
    {
      rk_bucle_ac (rk);
      o = fminl (o, *rk->ac0->optimal);
    }
end:
#if DEBUG_RK_4_3
  fprintf (stderr, "rk_objective_tb_4_3t: optimal=%Lg\n", o);
  fprintf (stderr, "rk_objective_tb_4_3t: end\n");
#endif
  return o;
}

/**
 * Function to calculate the objective function of a 4 steps 2nd-3rd order
 * Runge-Kutta pair.
 *
 * \return objective function value.
 */
long double
rk_objective_tb_4_3p (RK * rk)  ///< RK struct.
{
  long double *tb;
  long double o;
#if DEBUG_RK_4_3
  fprintf (stderr, "rk_objective_tb_4_3p: start\n");
#endif
  tb = rk->tb->coefficient;
  o = fminl (0.L, b20 (tb));
  if (b30 (tb) < 0.L)
    o += b30 (tb);
  if (b31 (tb) < 0.L)
    o += b31 (tb);
  if (b40 (tb) < 0.L)
    o += b40 (tb);
  if (b41 (tb) < 0.L)
    o += b41 (tb);
  if (b42 (tb) < 0.L)
    o += b42 (tb);
  if (e40 (tb) < 0.L)
    o += e40 (tb);
  if (o < 0.L)
    {
      o = 40.L - o;
      goto end;
    }
  o = 30.L + fmaxl (1.L, fmaxl (t1 (tb), fmaxl (t2 (tb), t3 (tb))));
  if (rk->strong)
    {
      rk_bucle_ac (rk);
      o = fminl (o, *rk->ac0->optimal);
    }
end:
#if DEBUG_RK_4_3
  fprintf (stderr, "rk_objective_tb_4_3p: optimal=%Lg\n", o);
  fprintf (stderr, "rk_objective_tb_4_3p: end\n");
#endif
  return o;
}

/**
 * Function to calculate the objective function of a 4 steps 2nd-3rd order,
 * 3rd-4th oder in equations depending only in time, Runge-Kutta pair.
 *
 * \return objective function value.
 */
long double
rk_objective_tb_4_3tp (RK * rk) ///< RK struct.
{
  long double *tb;
  long double o;
#if DEBUG_RK_4_3
  fprintf (stderr, "rk_objective_tb_4_3tp: start\n");
#endif
  tb = rk->tb->coefficient;
  o = fminl (0.L, b20 (tb));
  if (b30 (tb) < 0.L)
    o += b30 (tb);
  if (b31 (tb) < 0.L)
    o += b31 (tb);
  if (b40 (tb) < 0.L)
    o += b40 (tb);
  if (b41 (tb) < 0.L)
    o += b41 (tb);
  if (b42 (tb) < 0.L)
    o += b42 (tb);
  if (b43 (tb) < 0.L)
    o += b43 (tb);
  if (e40 (tb) < 0.L)
    o += e40 (tb);
  if (e41 (tb) < 0.L)
    o += e41 (tb);
  if (e42 (tb) < 0.L)
    o += e42 (tb);
  if (o < 0.L)
    {
      o = 40.L - o;
      goto end;
    }
  o = 30.L + fmaxl (1.L, fmaxl (t1 (tb), fmaxl (t2 (tb), t3 (tb))));
  if (rk->strong)
    {
      rk_bucle_ac (rk);
      o = fminl (o, *rk->ac0->optimal);
    }
end:
#if DEBUG_RK_4_3
  fprintf (stderr, "rk_objective_tb_4_3tp: optimal=%Lg\n", o);
  fprintf (stderr, "rk_objective_tb_4_3tp: end\n");
#endif
  return o;
}


/**
 * \author Sylvain Marsat, University of Maryland - NASA GSFC
 *
 * \brief C code headers for the implementation of the Fourier domain response for LIGO-VIRGO detectors
 *
 *
 */

#ifndef _LLVFDRESPONSE_H
#define _LLVFDRESPONSE_H

#define _XOPEN_SOURCE 500

#ifdef __GNUC__
#define UNUSED __attribute__ ((unused))
#else
#define UNUSED
#endif

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <complex.h>
#include <time.h>
#include <unistd.h>
#include <getopt.h>
#include <stdbool.h>
#include <string.h>

#include <gsl/gsl_errno.h>
#include <gsl/gsl_bspline.h>
#include <gsl/gsl_blas.h>
#include <gsl/gsl_min.h>
#include <gsl/gsl_spline.h>
#include <gsl/gsl_complex.h>

#include "constants.h"
#include "LLVgeometry.h"
#include "struct.h"
#include "waveform.h"
#include "timeconversion.h"

/**************************************************/
/**************** Prototypes **********************/

/* Function to convert string input network string to Networktag */
Networktag ParseNetworktag(char* string);

/* Core function processing a signal (in the form of a list of modes) through the Fourier-domain LLV response (for a given detector), for given values of the inclination, position in the sky and polarization angle */
int LLVSimFDResponse(
  struct tagListmodesCAmpPhaseFrequencySeries **listhlm,  /* Input: list of modes in Frequency-domain amplitude and phase form as produced by the ROM */
  struct tagListmodesCAmpPhaseFrequencySeries **lists,    /* Output: list of contribution of each mode in the detector signal, in Frequency-domain amplitude and phase form, for the given detector and sky position */
  const double gpstime,                                   /* GPS time (s) when the signal at coalescence reaches geocenter */
  const double ra,                                        /* Position in the sky: J2000.0 right ascension (rad) */
  const double dec,                                       /* Position in the sky: J2000.0 declination (rad) */
  const double inclination,                               /* Inclination of the source (rad) */
  const double psi,                                       /* Polarization angle (rad) */
  const Detectortag tag);                                 /* Tag identifying the detector */

  /* Core function processing a signal (in the form of a list of modes) through the Fourier-domain LLV response, for given values of the inclination, position in the sky and polarization angle */
  int LLVSimFDResponse3Det(
    struct tagListmodesCAmpPhaseFrequencySeries **listDet1,  /* Output: list of contribution of each mode in Frequency-domain amplitude and phase form, in the detector 1 */
    struct tagListmodesCAmpPhaseFrequencySeries **listDet2,  /* Output: list of contribution of each mode in Frequency-domain amplitude and phase form, in the detector 2 */
    struct tagListmodesCAmpPhaseFrequencySeries **listDet3,  /* Output: list of contribution of each mode in Frequency-domain amplitude and phase form, in the detector 3 */
    struct tagListmodesCAmpPhaseFrequencySeries **list,      /* Input: list of modes in Frequency-domain amplitude and phase form as produced by the ROM */
    const double gpstime,                                    /* GPS time (s) when the signal at coalescence reaches geocenter */
    const double ra,                                            /* First angle for the position in the sky */
    const double dec,                                           /* Second angle for the position in the sky */
    const double inclination,                                   /* Inclination of the source */
    const double psi,                                           /* Polarization angle */
    const Networktag tag);                               /* Selector for the detector network */

/* Function setting the response matrix of a given detector, in cartesian coordinates */
void SetMatrixD(
  gsl_matrix* D,                       /* Output: matrix of the detector response Dij */
  const Detectortag tag);              /* Tag identifying the detector */
/* Function setting the position of a detector, in cartesian coordinates */
void SetVectorXd(
  gsl_vector* Xd,                      /* Output: position vector of the detector */
  const Detectortag tag);              /* Tag identifying the detector */

/* Function setting the cartesian coordinates of the wave frame vectors (X,Y,Z), given the position in the sky and polarization */
void SetVectorsXYZ(
  gsl_vector* X,                       /* Output: cartesian vector of the wave frame unit vector X */
  gsl_vector* Y,                       /* Output: cartesian vector of the wave frame unit vector Y */
  gsl_vector* Z,                       /* Output: cartesian vector of the wave frame unit vector Z */
  const double theta,                  /* First angle for the position in the sky (Earth-based spherical angle) */
  const double phi,                    /* Second angle for the position in the sky (Earth-based spherical angle) */
  const double psi);                   /* Polarization angle */

#if 0
{ /* so that editors will match succeeding brace */
#elif defined(__cplusplus)
}
#endif

#endif /* _LLVFDRESPONSE_H */