#ifndef PHI_SVM
#define PHI_SVM
#include <float.h>
#include <vector>
#include <list>
#include <fstream>
#include <iostream>
#include <cmath>
#include <cassert>
#include <cstring>
#include <cstdlib>
#include <sys/time.h>

// BLAS dependency
#ifdef USE_MKL
#include <mkl.h>
#else
typedef int MKL_INT;
#if defined __APPLE__
#include <Accelerate/Accelerate.h>
#else
extern "C" {
#include <cblas.h>
}
#endif
#endif

#include <string>
#include <boost/serialization/serialization.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/iostreams/stream.hpp>
#include <boost/iostreams/device/back_inserter.hpp>

#include <sstream>

using namespace std;

#define BLOCKSIZE 256
#define REDUCE0 0x00000001
#define REDUCE1 0x00000002
#define MAX_PITCH 262144
#define MAX_POINTS (MAX_PITCH / sizeof(float) - 2)
#define MAXITERATIONS 100000
#define intDivideRoundUp(a, b) (a % b != 0) ? (a / b + 1) : (a / b)

enum KernelType {
  NEWLINEAR,
  NEWGAUSSIAN,
  NEWPRECOMPUTED
};

struct Kernel_params {
  float gamma;
  float coef0;
  int degree;
  float b;
  std::string kernel_type;
};

enum SelectionHeuristic {
  FIRSTORDER,
  SECONDORDER,
  RANDOM,
  ADAPTIVE
};

class PhiSVMModel {
public:
  int nSamples;
  int nDimension;
  float epsilon;
  float bLow;
  float bHigh;
  float* alpha;
  float* f;
  float* kernelDiag;
  float* data;
  float* labels;
  PhiSVMModel() { // default constructor for training from the scratch
  }
  PhiSVMModel(int maxPoints, int maxDimension) {  // constructor for getting an existing model
    alpha = new float[maxPoints];
    f = new float[maxPoints];
    kernelDiag = new float[maxPoints];
    data = new float[maxPoints*maxDimension];
    labels = new float[maxPoints];
  }
  ~PhiSVMModel() {  // leave the data and labels to others to delete
    delete alpha;
    delete f;
    delete kernelDiag;
  }
};

class NewCache {
 public:
  NewCache(int nPointsIn, int cacheSizeIn);
  ~NewCache();
  void findData(const int index, int& offset, bool& compute);
  void search(const int index, int& offset, bool& compute);
  void printCache();
  void printStatistics();

 private:
  int nPoints;
  int cacheSize;
  class DirectoryEntry {
   public:
    enum {
      NEVER,
      EVICTED,
      INCACHE
    };
    DirectoryEntry();
    int status;
    int location;
    std::list<int>::iterator lruListEntry;
  };

  std::vector<DirectoryEntry> directory;
  std::list<int> lruList;
  int occupancy;
  int hits;
  int compulsoryMisses;
  int capacityMisses;
};

class Controller {
 public:
  Controller(float initialGap, SelectionHeuristic currentMethodIn,
             int samplingIntervalIn, int problemSize);
  void addIteration(float gap);
  void print();
  SelectionHeuristic getMethod();

 private:
  bool adaptive;
  int samplingInterval;
  std::vector<float> progress;
  std::vector<int> method;
  SelectionHeuristic currentMethod;
  std::vector<float> rates;
  int timeSinceInspection;
  int inspectionPeriod;
  int beginningOfEpoch;
  int middleOfEpoch;
  int currentInspectionPhase;
  float filter(int begin, int end);
  float findRate(struct timeval* start, struct timeval* finish, int beginning,
                 int end);
  struct timeval start;
  struct timeval mid;
  struct timeval finish;
};

bool hasTwoTypes(float* labels, int n);

float crossValidation(float* data, int nPoints, int nDimension,
                               int nFolds, float* labels, Kernel_params* kp,
                               float cost, SelectionHeuristic heuristicMethod,
                               float epsilon, float tolerance,
                               float* transposedData, bool shuffle);

float incrementalCrossValidation(PhiSVMModel** models, float* data, int nPoints, int nDimension,
                               int nFolds, float* labels, Kernel_params* kp,
                               float cost, SelectionHeuristic heuristicMethod,
                               float epsilon, float tolerance,
                               float* transposedData, int maxNPoints);

void svmGroupClasses(int nPoints, float* labels, int** start_ret,
                     int** count_ret, int* perm);
PhiSVMModel* performTraining(float* data, int nPoints, int nDimension, float* labels,
                     Kernel_params* kp, float cost,
                     SelectionHeuristic heuristicMethod, float epsilon,
                     float tolerance, float* transposedData, PhiSVMModel* curModel);
PhiSVMModel* performOnlineTraining(int nOldPoints, Kernel_params* kp, float cost,
                     SelectionHeuristic heuristicMethod, float epsilon,
                     float tolerance, PhiSVMModel* curModel);

template <int Kernel>
void firstOrder(float* devData, int devDataPitchInFloats,
                float* devTransposedData, int devTransposedDataPitchInFloats,
                float* devLabels, int nPoints, int nDimension, float epsilon,
                float cEpsilon, float* devAlpha, float* devF, float alpha1Diff,
                float alpha2Diff, int iLow, int iHigh, float parameterA,
                float parameterB, float parameterC, float* devCache,
                int devCachePitchInFloats, int iLowCacheIndex,
                int iHighCacheIndex, float* devKernelDiag, void* devResult,
                float cost, bool iLowCompute, bool iHighCompute, int nthreads);
void launchFirstOrder(bool iLowCompute, bool iHighCompute, int kType,
                      int nPoints, int nDimension, float* devData,
                      int devDataPitchInFloats, float* devTransposedData,
                      int devTransposedDataPitchInFloats, float* devLabels,
                      float epsilon, float cEpsilon, float* devAlpha,
                      float* devF, float sAlpha1Diff, float sAlpha2Diff,
                      int iLow, int iHigh, float parameterA, float parameterB,
                      float parameterC, float* devCache,
                      int devCachePitchInFloats, int iLowCacheIndex,
                      int iHighCacheIndex, float* devKernelDiag,
                      void* devResult, float cost, int nthreads);
template <int Kernel>
void secondOrder(float* devData, int devDataPitchInFloats,
                 float* devTransposedData, int devTransposedDataPitchInFloats,
                 float* devLabels, int nPoints, int nDimension, float epsilon,
                 float cEpsilon, float* devAlpha, float* devF, float alpha1Diff,
                 float alpha2Diff, int iLow, int iHigh, float parameterA,
                 float parameterB, float parameterC, float* devCache,
                 int devCachePitchInFloats, int iLowCacheIndex,
                 int iHighCacheIndex, float* devKernelDiag, void* devResult,
                 float cost, float bHigh, bool iHighCompute, int nthreads);
void launchSecondOrder(bool iLowCompute, bool iHighCompute, int kType,
                       int nPoints, int nDimension, float* devData,
                       int devDataPitchInFloats, float* devTransposedData,
                       int devTransposedDataPitchInFloats, float* devLabels,
                       float epsilon, float cEpsilon, float* devAlpha,
                       float* devF, float sAlpha1Diff, float sAlpha2Diff,
                       int iLow, int iHigh, float parameterA, float parameterB,
                       float parameterC, NewCache* kernelCache, float* devCache,
                       int devCachePitchInFloats, int iLowCacheIndex,
                       int iHighCacheIndex, float* devKernelDiag,
                       void* devResult, float cost, int iteration,
                       int nthreads);
template <int Kernel>
void initializeArrays(float* devData, int devDataPitchInFloats, float* devCache,
                      int devCachePitchInFloats, int nPoints, int nDimension,
                      float parameterA, float parameterB, float parameterC,
                      float* devKernelDiag, float* devAlpha, float* devF,
                      float* devLabels, int nthreads);
void launchInitialization(float* devData, int devDataPitchInFloats,
                          float* devCache, int devCachePitchInFloats,
                          int nPoints, int nDimension, int kType,
                          float parameterA, float parameterB, float parameterC,
                          float* devKernelDiag, float* devAlpha, float* devF,
                          float* devLabels, int nthreads);
template <int Kernel>
void takeFirstStep(void* devResult, float* devKernelDiag, float* devData,
                   int devDataPitchInFloats, float* devCache,
                   int devCachePitchInFloats, float* devAlpha, float cost,
                   int nDimension, int iLow, int iHigh, float parameterA,
                   float parameterB, float parameterC);
void launchTakeFirstStep(void* devResult, float* devKernelDiag, float* devData,
                         int devDataPitchInFloats, float* devCache,
                         int devCachePitchInFloats, float* devAlpha, float cost,
                         int nDimension, int iLow, int iHigh, int kType,
                         float parameterA, float parameterB, float parameterC,
                         int nthreads);
void performClassification(float* data, int nData, int nDimension,
                           Kernel_params* kp, float** p_result, PhiSVMModel* model);
void computeKernels(float* devNorms, int devNormsPitchInFloats, float* devAlpha,
                    int nPoints, int nSV, const KernelType kType, int degree,
                    float b, float* devResult);
float kernel(const float v, const int degree, const KernelType kType);
void makeSelfDots(float* devSource, int devSourcePitchInFloats, float* devDest,
                  int sourceCount, int sourceLength);
void makeDots(float* devDots, int devDotsPitchInFloats, float* devSVDots,
              float* devDataDots, int nSV, int nPoints);

class DumpModel {
private:
  friend class boost::serialization::access;
  template<class Archive>
  void serialize(Archive & ar, const unsigned int version) {
    ar & nSamples;
    ar & nDimension;
    for (int i=0; i<nSamples*nDimension; i++) {
      ar & trainingData[i];
    }
    ar & phiSVMModel->nSamples;  // redundant with Model::nSamples
    ar & phiSVMModel->nDimension;  // for precomputed kernel, should be the same as nSamples
    ar & phiSVMModel->epsilon;
    ar & phiSVMModel->bLow;
    ar & phiSVMModel->bHigh;
    for (int i=0; i<phiSVMModel->nSamples; i++) {
      ar & phiSVMModel->alpha[i];
    }
    for (int i=0; i<phiSVMModel->nSamples; i++) {
      ar & phiSVMModel->f[i];
    }
    for (int i=0; i<phiSVMModel->nSamples; i++) {
      ar & phiSVMModel->kernelDiag[i];
    }
    // the data here for precomputed kernel is the kernel matrix
    for (int i=0; i<phiSVMModel->nSamples*phiSVMModel->nDimension; i++) {
      ar & phiSVMModel->data[i];
    }
    for (int i=0; i<phiSVMModel->nSamples; i++) {
      ar & phiSVMModel->labels[i];
    }
  }
public:
  int nSamples;
  int nDimension;
  float* trainingData;
  PhiSVMModel* phiSVMModel;
  DumpModel() {}
  DumpModel(int s, int d) {
    nSamples = s;
    nDimension = d;
    trainingData = new float[s*d];
    phiSVMModel = new PhiSVMModel(MAX_POINTS, MAX_POINTS);
  }
  ~DumpModel() {
    //delete trainingData;
    //delete phiSVMModel->data;
    //delete phiSVMModel->labels;
    //delete phiSVMModel;
  }
};

std::string serialize_DumpModel(DumpModel* model_ptr);
DumpModel* deserialize_DumpModel(std::string, int, int);
void DumpModelToDisk(std::string modelStr);
#endif


/*
 * File:   particle.h
 * Author: adithya
 *
 * Created on December 15, 2014, 7:23 PM
 */

#ifndef PARTICLE_H
#define	PARTICLE_H

//#include "myVector.h"
//#include "quaternion.h"
//#include "species.h"
//#include "simulationParameters.h"
//
//#include <fstream>
//#include <sstream>
//#include <vector>
//
//#include <gsl/gsl_rng.h>
//#include <gsl/gsl_randist.h>

#include "myVector.h"
#include "quaternion.h"
#include "main.h"

class particle
{
public:
    particle();
    particle(const particle& orig);
    virtual ~particle();
public:
    int particleID;/**This ID helps identifying the particle in question*/
    int speciesType;/**Is the species type of the particle, which in turn holds information about the particles' physical properties.*/
    int particleType;/**Describes the type of the particle, 1 if a particle with a domain(eGFRD), 2 if LD*/
    //std::vector < std::pair <int, int> > complexList;

    std::vector < std::pair <int, int> > complexList;/**Gives the list of all other particles (if any)
                                                      connectected to this particle in a complex
                                                      and the patches of the particle that are occupied*/
    /**
     * @memberVariableOfSpeciesClass is a vector that contains the
     * the state of all patches
     * for a substrate
     * 0 is unphosphorylated
     * 1 is phosphorylated
     * for an enzyme its always 1
     */
    std::vector<int> patchState;

    myVector particlePosition;  /**3D position of the centre of the particle*/
    myVector force;             /**force on the particle*/

    quaternion q;               /**The orientation of the particle in 3 dimensions*/
    quaternion torque;          /**The torque of the particle*/

    std::vector<myVector> patchPos;          /** 3D position of the centre of the patch of the particle which is used to
                                 visualize the rotation*/

    /*GREENS FUNCTIONS RELATED VARIABLES*/
    myVector nextEscapePosition;/**The center of the particle when the particle escapes*/
    myVector burstPosition;/**center of the particle when the domain bursts*/
    double R;/**This is the radius of displacement of the particle from the original position, after bursting*/

     /*DOMAIN RELATED MEMBER VARIABLES*/
    int nextReactionType;/**if 0 then nothing (just Brownian step)
                            if 1 then monomolecular dissociation
                            if 2 hopping
                            if 3 phosphorylation/dephosphorylation*/
    int nextEventType;/**If 0 its escape(from the domain)
                         if 1 its reaction
                         */
    double nextEscapeTime;/**The time at which the next escape occurs*/
    double nextReactionTime;/**The time at which the next reaction occurs
                             minimum of dissociation time, hopping time and phosphorylation time*/
    double nextEventTime;/**The time at which the next event occurs, the min of the above two values*/
    double timeOfConstructionOfDomain;/**Global time at which the domain is constructed*/
    double domainRadius;/**radius of the domain around the particle*/

    bool operator < (const particle temp) const
    {
        return (this->nextEventTime < temp.nextEventTime);
    }

     /**Function prints out the particle class
     */
    friend std::ostream& operator<< (std::ostream &out, particle &part);

    /**
     This function draws a random position at the sphere of domain radius if the particle escapes
     */
    void drawNextEscapePosition();

    /**
     This function draws a new position for the particle if the domain is prematurely burst
     */
    void drawBurstPosition (double time);

    /**
     This function gets a next event time and type using greens functions for a single domain
     */
    void drawNextEventTimeAndType ();

    /**
     This function builds a domain on the particle which calls the function

     @param nearestNeighborDistance radius of the domain to be built
     */
    void buildADomain(double nearestNeighborDistance);

    /**
     This function bursts the given domain it can be used for bursting a domain, moving a particle or converting a particle to a LD particle

     @param instruction escape, burst or convert
     */
    void burstTheDomain(std::string instruction);

    /**
     This dissociates a particle in a domain
     */
    void dissociateParticleFromDomain();

};/**Particle holds all information related to the particle in the simulation*/

#endif	/* PARTICLE_H */


#define _MAIN
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <gbpLib.h>
#include <gbpHalos.h>
#include <sys/stat.h>
#include <gsl/gsl_errno.h>

#define GADGET_BUFFER_SIZE_LOCAL 128 * SID_SIZE_OF_MEGABYTE

#define _FILE_OFFSET_BITS 64

typedef struct params_info_local params_info_local;
struct params_info_local {
    GBPREAL *x_array;
    GBPREAL *y_array;
    GBPREAL *z_array;
    GBPREAL *vx_array;
    GBPREAL *vy_array;
    GBPREAL *vz_array;
    size_t * ids_array;
    size_t * halo_sort_index;
    size_t   first_particle_offset;
};

double p_i_local(void *params, int i_coord, int j_particle);
double p_i_local(void *params, int i_coord, int j_particle) {
    double r_val;
    size_t k_particle = ((params_info_local *)params)->halo_sort_index[((params_info_local *)params)->first_particle_offset + j_particle];
    switch(i_coord) {
        case 0:
            r_val = ((params_info_local *)params)->x_array[k_particle];
            break;
        case 1:
            r_val = ((params_info_local *)params)->y_array[k_particle];
            break;
        case 2:
            r_val = ((params_info_local *)params)->z_array[k_particle];
            break;
    }
    return (r_val);
}

double v_i_local(void *params, int i_coord, int j_particle);
double v_i_local(void *params, int i_coord, int j_particle) {
    double r_val;
    size_t k_particle = ((params_info_local *)params)->halo_sort_index[((params_info_local *)params)->first_particle_offset + j_particle];
    switch(i_coord) {
        case 0:
            r_val = ((params_info_local *)params)->vx_array[k_particle];
            break;
        case 1:
            r_val = ((params_info_local *)params)->vy_array[k_particle];
            break;
        case 2:
            r_val = ((params_info_local *)params)->vz_array[k_particle];
            break;
    }
    return (r_val);
}

size_t id_i_local(void *params, int j_particle);
size_t id_i_local(void *params, int j_particle) {
    size_t k_particle = ((params_info_local *)params)->halo_sort_index[((params_info_local *)params)->first_particle_offset + j_particle];
    return (((params_info_local *)params)->ids_array[k_particle]);
}

void read_gadget_binary_local(char *filename_root_in, int snapshot_number, plist_info *plist);
void read_gadget_binary_local(char *filename_root_in, int snapshot_number, plist_info *plist) {
    char **            pname;
    char *             name_initpositions;
    char               filename[SID_MAX_FILENAME_LENGTH];
    char *             read_catalog;
    size_t             i, j, k, l, jj;
    size_t             i_list_offset[N_GADGET_TYPE];
    size_t             n_particles_file;
    size_t             n_particles_kept;
    size_t             n_of_type_rank[N_GADGET_TYPE];
    size_t             n_of_type[N_GADGET_TYPE];
    int                n_type_used;
    int                n_particles_all_in_groups;
    int                n_particles_in_groups;
    size_t             n_particles_rank;
    size_t             n_particles_all;
    size_t             i_p, i_p_temp;
    size_t             id_min, id_max;
    double             mass_array[N_GADGET_TYPE];
    int                unused[GADGET_HEADER_SIZE];
    size_t             n_all[N_GADGET_TYPE];
    int                n_all_tmp[N_GADGET_TYPE];
    int                flag_metals;
    int                flag_ages;
    int                flag_entropyICs;
    double             expansion_factor;
    double             h_Hubble;
    double             box_size;
    double             d_value;
    double             d1_value;
    double             d2_value;
    double             d3_value;
    float              f_value;
    int                i_value;
    int                i1_value;
    int                i2_value;
    int                i3_value;
    int                n_warning;
    GBPREAL *          x_array[N_GADGET_TYPE];
    GBPREAL *          y_array[N_GADGET_TYPE];
    GBPREAL *          z_array[N_GADGET_TYPE];
    GBPREAL *          vx_array[N_GADGET_TYPE];
    GBPREAL *          vy_array[N_GADGET_TYPE];
    GBPREAL *          vz_array[N_GADGET_TYPE];
    size_t *           id_array[N_GADGET_TYPE];
    size_t *           id_list;
    size_t *           id_list_offset;
    size_t *           id_list_in;
    size_t *           id_list_index;
    size_t             n_id_list;
    unsigned int       record_length_open;
    unsigned int       record_length_close;
    int                n_return;
    size_t             s_load;
    char *             keep;
    int                n_keep[N_GADGET_TYPE];
    int                flag_keep_IDs      = GBP_TRUE;
    int                flag_multifile     = GBP_FALSE;
    int                flag_multimass     = GBP_FALSE;
    int                flag_file_type     = 0;
    int                flag_filefound     = GBP_FALSE;
    int                flag_gas           = GBP_FALSE;
    int                flag_initpositions = GBP_FALSE;
    int                flag_no_velocities = GBP_FALSE;
    int                flag_LONGIDS       = GBP_FALSE;
    int                flag_read_marked   = GBP_FALSE;
    int                flag_read_catalog  = GBP_FALSE;
    int                flag_all_read_all  = GBP_FALSE;
    int                i_file;
    int                i_rank;
    int                n_files;
    int                n_files_in;
    double             x_scale;
    double             y_scale;
    double             z_scale;
    double             x_offset;
    double             y_offset;
    double             z_offset;
    double             x_period;
    double             y_period;
    double             z_period;
    int                flag_xscale;
    int                flag_yscale;
    int                flag_zscale;
    int                flag_xoffset;
    int                flag_yoffset;
    int                flag_zoffset;
    int                flag_xperiod;
    int                flag_yperiod;
    int                flag_zperiod;
    FILE *             fp;
    void *             buffer;
    size_t *           buffer_index = NULL;
    void *             buffer_i;
    size_t             id_search;
    size_t             id_value;
    size_t             n_buffer;
    int                n_non_zero;
    int                seed = 1073743;
    int                read_mode;
    int                mark_mode;
    double             x_min_read;
    double             x_max_read;
    double             y_min_read;
    double             y_max_read;
    double             z_min_read;
    double             z_max_read;
    double             x_min_bcast;
    double             x_max_bcast;
    double             y_min_bcast;
    double             y_max_bcast;
    double             z_min_bcast;
    double             z_max_bcast;
    gadget_header_info header;

    // Determine file format
    n_files = 1;
    for(i_file = 0; i_file < 3 && !flag_filefound; i_file++) {
        if(i_file == 0)
            sprintf(filename, "%s/snapshot_%03d/snapshot_%03d", filename_root_in, snapshot_number, snapshot_number);
        else if(i_file == 1)
            sprintf(filename, "%s/snapshot_%03d", filename_root_in, snapshot_number);
        else if(i_file == 2)
            sprintf(filename, "%s_%03d", filename_root_in, snapshot_number);
        fp = fopen(filename, "r");
        if(fp != NULL) {
            flag_filefound = GBP_TRUE;
            flag_multifile = GBP_FALSE;
            flag_file_type = i_file;
        }
        // ... if that doesn't work, check for multi-file
        else {
            strcat(filename, ".0");
            fp = fopen(filename, "r");
            if(fp != NULL) {
                flag_filefound = GBP_TRUE;
                flag_multifile = GBP_TRUE;
                flag_file_type = i_file;
            }
        }
    }

    // A file was found ...
    SID_log("Reading GADGET binary file {%s}...", SID_LOG_OPEN | SID_LOG_TIMER, filename_root_in);
    if(flag_filefound) {
        pname = plist->species;

        // Header record length
        SID_log("Reading header...", SID_LOG_OPEN);
        SID_fread_verify(&record_length_open, 4, 1, fp);
        if(record_length_open != GADGET_HEADER_SIZE)
            SID_log_warning("Problem with GADGET record size (opening size of header is wrong)", SID_ERROR_LOGIC);

        // Read header
        SID_fread_verify(&header, sizeof(gadget_header_info), 1, fp);

        // Number of particles for each species in this file
        for(i = 0; i < N_GADGET_TYPE; i++)
            n_of_type[i] = (size_t)header.n_file[i];

        // Expansion factor (or time)
        expansion_factor = header.time;
        ADaPS_store(&(plist->data), (void *)(&expansion_factor), "expansion_factor", ADaPS_SCALAR_DOUBLE);
        ADaPS_store(&(plist->data), (void *)(&(header.time)), "time", ADaPS_SCALAR_DOUBLE);

        // Redshift
        d_value = (double)header.redshift;
        ADaPS_store(&(plist->data), (void *)(&d_value), "redshift", ADaPS_SCALAR_DOUBLE);

        // Number of particles for each species in all files
        for(i = 0; i < N_GADGET_TYPE; i++)
            n_all[i] = (size_t)header.n_all_lo_word[i];

        // Number of files in this snapshot
        ADaPS_store(&(plist->data), (void *)(&(header.n_files)), "n_files", ADaPS_SCALAR_INT);
        if(flag_multifile)
            n_files = header.n_files;

        // Cosmology

        // Omega_o
        d_value = (double)header.Omega_M;
        ADaPS_store(&(plist->data), (void *)(&d_value), "Omega_M", ADaPS_SCALAR_DOUBLE);

        // Omega_Lambda
        d_value = (double)header.Omega_Lambda;
        ADaPS_store(&(plist->data), (void *)(&d_value), "Omega_Lambda", ADaPS_SCALAR_DOUBLE);

        // Hubble parameter
        h_Hubble = (double)header.h_Hubble;
        if(h_Hubble < 1e-10)
            h_Hubble = 1.;
        box_size = header.box_size * plist->length_unit / h_Hubble;
        ADaPS_store(&(plist->data), (void *)(&box_size), "box_size", ADaPS_SCALAR_DOUBLE);
        ADaPS_store(&(plist->data), (void *)(&h_Hubble), "h_Hubble", ADaPS_SCALAR_DOUBLE);

        for(i = 0; i < N_GADGET_TYPE; i++) {
            mass_array[i] = header.mass_array[i];
            if(header.n_all_hi_word[i] > 0)
                n_all[i] += (((size_t)(header.n_all_hi_word[i])) << 32);
        }

        // Count the total number of particles
        n_particles_all = 0;
        for(i = 0, n_non_zero = 0; i < N_GADGET_TYPE; i++) {
            if(n_all[i] > 0) {
                n_particles_all += n_all[i];
                n_non_zero++;
            }
        }

        // List numbers of particles in the log output
        SID_log("%lld", SID_LOG_CONTINUE, n_particles_all);
        if(n_non_zero > 0)
            SID_log(" (", SID_LOG_CONTINUE, n_particles_all);
        for(i = 0; i < N_GADGET_TYPE; i++) {
            if(n_all[i] > 0) {
                if(i == n_non_zero - 1) {
                    if(n_non_zero > 1)
                        SID_log("and %lld %s", SID_LOG_CONTINUE, n_all[i], pname[i]);
                    else
                        SID_log("%lld %s", SID_LOG_CONTINUE, n_all[i], pname[i]);
                } else {
                    if(n_non_zero > 1)
                        SID_log("%lld %s, ", SID_LOG_CONTINUE, n_all[i], pname[i]);
                    else
                        SID_log("%lld %s", SID_LOG_CONTINUE, n_all[i], pname[i]);
                }
            }
        }
        if(n_non_zero > 0)
            SID_log(") particles...", SID_LOG_CONTINUE);
        else
            SID_log(" particles...", SID_LOG_CONTINUE);

        // Check closing record length
        SID_fread_verify(&record_length_close, 4, 1, fp);
        if(record_length_open != record_length_close)
            SID_log_warning("Problem with GADGET record size (close of header)", SID_ERROR_LOGIC);

        // Close file
        fclose(fp);
        SID_log("Done.", SID_LOG_CLOSE);

        // Store mass array
        for(i = 0; i < N_GADGET_TYPE; i++) {
            if(n_all[i] > 0) {
                mass_array[i] *= plist->mass_unit / h_Hubble;
                if(mass_array[i] != 0.) {
                    d_value = mass_array[i];
                    ADaPS_store(&(plist->data), (void *)(&d_value), "mass_array_%s", ADaPS_SCALAR_DOUBLE, plist->species[i]);
                }
            } else
                mass_array[i] = 0.;
        }

        // Is this a multimass snapshot?
        flag_multimass = GBP_FALSE;
        for(i = 0; i < N_GADGET_TYPE; i++)
            if(n_all[i] > 0 && mass_array[i] == 0.)
                flag_multimass = GBP_TRUE;

        // Is sph being used?
        if(n_all[GADGET_TYPE_GAS] > 0)
            flag_gas = GBP_TRUE;
        else
            flag_gas = GBP_FALSE;

        // Fetch (and sort by id) local group particles
        SID_log("Sorting group particle ids...", SID_LOG_OPEN);
        n_particles_rank          = ((size_t *)ADaPS_fetch(plist->data, "n_particles_%03d", snapshot_number))[0];
        n_particles_all_in_groups = ((size_t *)ADaPS_fetch(plist->data, "n_particles_all_%03d", snapshot_number))[0];
        id_list                   = (size_t *)ADaPS_fetch(plist->data, "particle_ids_%03d", snapshot_number);
        merge_sort((void *)id_list, (size_t)n_particles_rank, &id_list_index, SID_SIZE_T, SORT_COMPUTE_INDEX, GBP_FALSE);
        for(i = 0; i < N_GADGET_TYPE; i++)
            n_of_type_rank[i] = 0;
        n_of_type[GADGET_TYPE_DARK]      = n_particles_all_in_groups;
        n_of_type_rank[GADGET_TYPE_DARK] = n_particles_rank;
        SID_log("Done.", SID_LOG_CLOSE);

        // Check integretry of particle ids
        SID_log("Checking integrity of group particle ids...", SID_LOG_OPEN);
        for(j = 1, n_warning = 0; j < n_particles_rank && n_warning < 10; j++) {
            if(id_list[id_list_index[j]] == id_list[id_list_index[j - 1]] && n_warning < 100) {
                fprintf(stderr, "WARNING: group particle id=%zd is duplicated on rank=%d!\n", id_list[id_list_index[j]], SID.My_rank);
                n_warning++;
            }
        }
        if(n_warning > 0)
            SID_exit_error("Error in group particle ids!", SID_ERROR_LOGIC);
        SID_log("Done.", SID_LOG_CLOSE);

        // Allocate data arrays
        for(i = 0; i < N_GADGET_TYPE; i++) {
            if(n_of_type_rank[i] > 0) {
                id_array[i] = (size_t *)SID_malloc(sizeof(size_t) * (size_t)n_of_type_rank[i]);
                x_array[i]  = (GBPREAL *)SID_malloc(sizeof(GBPREAL) * (size_t)n_of_type_rank[i]);
                y_array[i]  = (GBPREAL *)SID_malloc(sizeof(GBPREAL) * (size_t)n_of_type_rank[i]);
                z_array[i]  = (GBPREAL *)SID_malloc(sizeof(GBPREAL) * (size_t)n_of_type_rank[i]);
                vx_array[i] = (GBPREAL *)SID_malloc(sizeof(GBPREAL) * (size_t)n_of_type_rank[i]);
                vy_array[i] = (GBPREAL *)SID_malloc(sizeof(GBPREAL) * (size_t)n_of_type_rank[i]);
                vz_array[i] = (GBPREAL *)SID_malloc(sizeof(GBPREAL) * (size_t)n_of_type_rank[i]);
            }
        }

        // Set offsets
        for(i = 0; i < N_GADGET_TYPE; i++)
            i_list_offset[i] = 0;

        // Read data
        for(i_file = 0, n_particles_kept = 0; i_file < n_files; i_file++) {
            if(n_files > 1)
                SID_log("Reading file %d of %d...", SID_LOG_OPEN | SID_LOG_TIMER, i_file + 1, n_files);
            else
                SID_log("Performing read...", SID_LOG_OPEN | SID_LOG_TIMER);

            if(flag_file_type == 0)
                sprintf(filename, "%s/snapshot_%03d/snapshot_%03d", filename_root_in, snapshot_number, snapshot_number);
            else if(flag_file_type == 1)
                sprintf(filename, "%s/snapshot_%03d", filename_root_in, snapshot_number);
            else if(flag_file_type == 2)
                sprintf(filename, "%s_%03d", filename_root_in, snapshot_number);
            if(flag_multifile)
                sprintf(filename, "%s.%d", filename, i_file);

            // Read header and set positions pointer
            FILE *fp_positions;
            if(SID.I_am_Master) {
                fp_positions = fopen(filename, "r");
                SID_fread_verify(&record_length_open, sizeof(int), 1, fp_positions);
                SID_fread_verify(&header, sizeof(gadget_header_info), 1, fp_positions);
                SID_fread_verify(&record_length_close, sizeof(int), 1, fp_positions);
                if(record_length_open != record_length_close)
                    SID_log_warning("Problem with GADGET record size (close of header)", SID_ERROR_LOGIC);
                SID_fread_verify(&record_length_open, sizeof(int), 1, fp_positions);
            }
            SID_Bcast(&header, sizeof(gadget_header_info), SID_CHAR, SID_MASTER_RANK, SID_COMM_WORLD);
            for(i = 0, n_particles_file = 0; i < N_GADGET_TYPE; i++)
                n_particles_file += (size_t)header.n_file[i];

            // Set velocities pointer
            FILE *fp_velocities;
            if(SID.I_am_Master) {
                fp_velocities = fopen(filename, "r");
                SID_fread_verify(&record_length_open, sizeof(int), 1, fp_velocities);
                fseeko(fp_velocities, (off_t)record_length_open, SEEK_CUR);
                SID_fread_verify(&record_length_close, sizeof(int), 1, fp_velocities);
                if(record_length_open != record_length_close)
                    SID_log_warning("Problem with GADGET record size (close of header)", SID_ERROR_LOGIC);
                SID_fread_verify(&record_length_open, sizeof(int), 1, fp_velocities);
                fseeko(fp_velocities, (off_t)record_length_open, SEEK_CUR);
                SID_fread_verify(&record_length_close, sizeof(int), 1, fp_velocities);
                if(record_length_open != record_length_close)
                    SID_log_warning("Problem with GADGET record size (close of positions)", SID_ERROR_LOGIC);
                SID_fread_verify(&record_length_open, sizeof(int), 1, fp_velocities);
            }

            // Set IDs pointer
            FILE *fp_IDs;
            if(SID.I_am_Master) {
                fp_IDs = fopen(filename, "r");
                SID_fread_verify(&record_length_open, sizeof(int), 1, fp_IDs);
                fseeko(fp_IDs, (off_t)record_length_open, SEEK_CUR);
                SID_fread_verify(&record_length_close, sizeof(int), 1, fp_IDs);
                if(record_length_open != record_length_close)
                    SID_log_warning("Problem with GADGET record size (close of header)", SID_ERROR_LOGIC);
                SID_fread_verify(&record_length_open, sizeof(int), 1, fp_IDs);
                fseeko(fp_IDs, (off_t)record_length_open, SEEK_CUR);
                SID_fread_verify(&record_length_close, sizeof(int), 1, fp_IDs);
                if(record_length_open != record_length_close)
                    SID_log_warning("Problem with GADGET record size (close of positions)", SID_ERROR_LOGIC);
                SID_fread_verify(&record_length_open, sizeof(int), 1, fp_IDs);
                fseeko(fp_IDs, (off_t)record_length_open, SEEK_CUR);
                SID_fread_verify(&record_length_close, sizeof(int), 1, fp_IDs);
                if(record_length_open != record_length_close)
                    SID_log_warning("Problem with GADGET record size (close of velocities)", SID_ERROR_LOGIC);
                SID_fread_verify(&record_length_open, sizeof(int), 1, fp_IDs);
                if((size_t)record_length_open / n_particles_file == sizeof(long long)) {
                    SID_log("(long long IDs)...", SID_LOG_CONTINUE);
                    flag_LONGIDS = GBP_TRUE;
                } else if((size_t)record_length_open / n_particles_file == sizeof(int)) {
                    SID_log("(int IDs)...", SID_LOG_CONTINUE);
                    flag_LONGIDS = GBP_FALSE;
                } else
                    SID_exit_error(
                            "IDs record length (%d) does not set a sensible id byte size for the number of particles given in the header (%s)",
                            SID_ERROR_LOGIC, record_length_open, n_particles_file);
            }
            SID_Bcast(&flag_LONGIDS, 1, SID_INT, SID_MASTER_RANK, SID_COMM_WORLD);

            // Allocate buffers
            int      n_buffer_max = GBP_MIN(n_particles_file, 4 * 1024 * 1024);
            GBPREAL *buffer_positions;
            GBPREAL *buffer_velocities;
            size_t * buffer_IDs;
            buffer_positions  = (GBPREAL *)SID_malloc(3 * n_buffer_max * sizeof(GBPREAL));
            buffer_velocities = (GBPREAL *)SID_malloc(3 * n_buffer_max * sizeof(GBPREAL));
            buffer_IDs        = (size_t *)SID_malloc(n_buffer_max * sizeof(size_t));

            // Perform read
            int     i_buffer;
            int     n_buffer_left;
            int     n_buffer;
            int     i_species;
            int     i_particle;
            int     j_particle;
            int     i_list;
            size_t *buffer_index = NULL;
            for(i_species = 0, i_buffer = n_buffer_max, i_list = 0, n_buffer_left = n_particles_file; i_species < N_GADGET_TYPE; i_species++) {
                n_keep[i_species] = 0;
                if(header.n_file[i_species] > 0) {
                    for(i_particle = 0; i_particle < header.n_file[i_species]; i_particle++) {
                        // Perform a buffered read
                        if(i_buffer >= n_buffer_max) {
                            n_buffer = GBP_MIN(n_buffer_max, n_buffer_left);
                            if(SID.I_am_Master) {
                                SID_fread_verify(buffer_positions, sizeof(GBPREAL), 3 * n_buffer, fp_positions);
                                SID_fread_verify(buffer_velocities, sizeof(GBPREAL), 3 * n_buffer, fp_velocities);
                                if(!flag_LONGIDS) {
                                    int *buffer_IDs_int = (int *)buffer_IDs;
                                    SID_fread_verify(buffer_IDs_int, sizeof(int), n_buffer, fp_IDs);
                                    for(j_particle = n_buffer - 1; j_particle >= 0; j_particle--)
                                        buffer_IDs[j_particle] = (size_t)buffer_IDs_int[j_particle];
                                } else
                                    SID_fread_verify(buffer_IDs, sizeof(size_t), n_buffer, fp_IDs);
                            }
                            SID_Bcast(buffer_positions, 3 * n_buffer, SID_REAL, SID_MASTER_RANK, SID_COMM_WORLD);
                            SID_Bcast(buffer_velocities, 3 * n_buffer, SID_REAL, SID_MASTER_RANK, SID_COMM_WORLD);
                            SID_Bcast(buffer_IDs, n_buffer, SID_SIZE_T, SID_MASTER_RANK, SID_COMM_WORLD);
                            SID_free(SID_FARG buffer_index);
                            merge_sort(buffer_IDs, (size_t)n_buffer, &buffer_index, SID_SIZE_T, SORT_COMPUTE_INDEX, GBP_FALSE);
                            n_buffer_left -= n_buffer;
                            i_buffer = 0;
                            i_list   = 0;
                        }

                        if(i_list < n_particles_rank) {
                            // Move to the next particle
                            size_t id_test;
                            size_t j_buffer = buffer_index[i_buffer];
                            id_test         = buffer_IDs[j_buffer];
                            while(id_list[id_list_index[i_list]] < id_test && i_list < (n_particles_rank - 1))
                                i_list++;

                            // If we've found a particle, add it to the arrays
                            if(id_list[id_list_index[i_list]] == id_test) {
                                GBPREAL f1_value;
                                GBPREAL f2_value;
                                GBPREAL f3_value;
                                GBPREAL f4_value;
                                GBPREAL f5_value;
                                GBPREAL f6_value;
                                f1_value                                                          = ((GBPREAL *)buffer_positions)[3 * j_buffer + 0];
                                f2_value                                                          = ((GBPREAL *)buffer_positions)[3 * j_buffer + 1];
                                f3_value                                                          = ((GBPREAL *)buffer_positions)[3 * j_buffer + 2];
                                f4_value                                                          = ((GBPREAL *)buffer_velocities)[3 * j_buffer + 0];
                                f5_value                                                          = ((GBPREAL *)buffer_velocities)[3 * j_buffer + 1];
                                f6_value                                                          = ((GBPREAL *)buffer_velocities)[3 * j_buffer + 2];
                                id_array[i_species][n_keep[i_species] + i_list_offset[i_species]] = id_test;
                                x_array[i_species][n_keep[i_species] + i_list_offset[i_species]] =
                                    f1_value * (GBPREAL)(plist->length_unit / h_Hubble);
                                y_array[i_species][n_keep[i_species] + i_list_offset[i_species]] =
                                    f2_value * (GBPREAL)(plist->length_unit / h_Hubble);
                                z_array[i_species][n_keep[i_species] + i_list_offset[i_species]] =
                                    f3_value * (GBPREAL)(plist->length_unit / h_Hubble);
                                vx_array[i_species][n_keep[i_species] + i_list_offset[i_species]] =
                                    f4_value * (GBPREAL)(plist->velocity_unit * sqrt(expansion_factor));
                                vy_array[i_species][n_keep[i_species] + i_list_offset[i_species]] =
                                    f5_value * (GBPREAL)(plist->velocity_unit * sqrt(expansion_factor));
                                vz_array[i_species][n_keep[i_species] + i_list_offset[i_species]] =
                                    f6_value * (GBPREAL)(plist->velocity_unit * sqrt(expansion_factor));
                                n_keep[i_species]++;
                                n_particles_kept++;
                                i_list++;
                            }
                        }
                        i_buffer++;
                    } // i_particle
                }     // if n>0
            }         // i_species

            // Update offsets
            for(i_species = 0; i_species < N_GADGET_TYPE; i_species++)
                i_list_offset[i_species] += n_keep[i_species];

            // Clean-up
            SID_free(SID_FARG buffer_positions);
            SID_free(SID_FARG buffer_velocities);
            SID_free(SID_FARG buffer_IDs);
            SID_free(SID_FARG buffer_index);
            if(SID.I_am_Master) {
                fclose(fp_positions);
                fclose(fp_velocities);
                fclose(fp_IDs);
            }
            SID_log("Done.", SID_LOG_CLOSE);
        } // i_file

        // Check that the right number of particles have been read
        if(n_particles_kept != n_particles_rank)
            SID_log_warning("Rank %d did not receive the right number of particles (ie. %d!=%d)",
                            SID_ERROR_LOGIC,
                            SID.My_rank,
                            n_particles_kept,
                            n_particles_rank);

        // Store everything in the data structure...

        //   ... particle counts ...
        for(i = 0; i < N_GADGET_TYPE; i++) {
            if(n_of_type[i] > 0) {
                SID_Allreduce(&(n_of_type_rank[i]), &(n_of_type[i]), 1, SID_SIZE_T, SID_SUM, SID_COMM_WORLD);
                ADaPS_store(&(plist->data), (void *)(&(n_of_type_rank[i])), "n_%s", ADaPS_SCALAR_SIZE_T, pname[i]);
                ADaPS_store(&(plist->data), (void *)(&(n_of_type[i])), "n_all_%s", ADaPS_SCALAR_SIZE_T, pname[i]);
            }
        }
        ADaPS_store(&(plist->data), (void *)(&n_particles_all), "n_particles_all", ADaPS_SCALAR_SIZE_T);

        //   ... positions ...
        for(i = 0; i < N_GADGET_TYPE; i++) {
            if(n_of_type_rank[i] > 0) {
                ADaPS_store(&(plist->data), (void *)x_array[i], "x_%s", ADaPS_DEFAULT, pname[i]);
                ADaPS_store(&(plist->data), (void *)y_array[i], "y_%s", ADaPS_DEFAULT, pname[i]);
                ADaPS_store(&(plist->data), (void *)z_array[i], "z_%s", ADaPS_DEFAULT, pname[i]);
            }
        }

        //  ... velocities ...
        for(i = 0; i < N_GADGET_TYPE; i++) {
            if(n_of_type_rank[i] > 0) {
                ADaPS_store(&(plist->data), (void *)vx_array[i], "vx_%s", ADaPS_DEFAULT, pname[i]);
                ADaPS_store(&(plist->data), (void *)vy_array[i], "vy_%s", ADaPS_DEFAULT, pname[i]);
                ADaPS_store(&(plist->data), (void *)vz_array[i], "vz_%s", ADaPS_DEFAULT, pname[i]);
            }
        }

        //  ... ids ...
        for(i = 0; i < N_GADGET_TYPE; i++) {
            if(n_of_type_rank[i] > 0)
                ADaPS_store(&(plist->data), (void *)id_array[i], "id_%s", ADaPS_DEFAULT, pname[i]);
        }

        SID_free(SID_FARG id_list_index);

        SID_log("Done.", SID_LOG_CLOSE);
    } else
        SID_exit_error("Could not find file with root {%s}", SID_ERROR_IO_OPEN, filename_root_in);
}

int main(int argc, char *argv[]) {
    plist_info plist;
    char       filename_groups_root[256];
    char       filename_out_root[256];
    char       filename_snapshot_root[256];
    char       filename_snapshot[256];
    char *     filename_number;
    char       filename_output_properties_dir[256];
    char       filename_output_properties[256];
    char       filename_output_profiles_dir[256];
    char       filename_output_profiles[256];
    char       filename_output_indices[256];
    char       filename_output_indices_dir[256];
    char       filename_output_properties_temp[256];
    char       filename_output_profiles_temp[256];
    char       filename_output_indices_temp[256];
    char       group_text_prefix[4];
    int        n_groups_process;
    int        n_groups;
    int        n_groups_all;
    int        i_rank;
    int        i_group;
    int        i_file_lo;
    int        i_file_hi;
    int        i_file;
    int        i_file_skip;
    int        i_particle;
    int        j_particle;
    int        i_process;
    int        n_particles;
    int        n_particles_max;
    GBPREAL *  x_array;
    GBPREAL *  y_array;
    GBPREAL *  z_array;
    GBPREAL *  vx_array;
    GBPREAL *  vy_array;
    GBPREAL *  vz_array;
    int *      n_particles_groups_process;
    int *      n_particles_groups;
    int *      n_particles_subgroups;
    size_t *   group_offset;
    size_t     n_particles_in_groups;
    size_t *   ids_snapshot;
    size_t *   ids_groups;
    size_t *   ids_sort_index;
    size_t *   ids_snapshot_sort_index;
    size_t *   ids_groups_sort_index;
    size_t     n_particles_snapshot;
    int        i_value;
    double     h_Hubble;
    double     Omega_M;
    double     Omega_b;
    double     Omega_Lambda;
    double     f_gas;
    double     Omega_k;
    double     sigma_8;
    double     n_spec;
    double     redshift;
    double     expansion_factor;
    double     box_size;
    double     particle_mass;

    int         r_val;
    struct stat file_stats;
    size_t      n_bytes;
    size_t      n_bytes_buffer;
    void *      buffer;

    FILE *               fp_properties;
    FILE *               fp_profiles;
    FILE *               fp_indices;
    halo_properties_info properties;
    halo_profile_info    profile;
    int                  n_temp;
    int                  n_truncated_local;
    int                  n_truncated;
    int                  largest_truncated;
    int                  largest_truncated_local;
    int                  flag_write_properties = GBP_TRUE;
    int                  flag_write_profiles   = GBP_TRUE;
    int                  flag_write_indices    = GBP_TRUE;
    int                  flag_manual_centre    = GBP_TRUE;

    SID_Init(&argc, &argv, NULL);

    // Fetch user inputs
    strcpy(filename_snapshot_root, argv[1]);
    strcpy(filename_groups_root, argv[2]);
    strcpy(filename_out_root, argv[3]);
    i_file_lo          = atoi(argv[4]);
    i_file_hi          = atoi(argv[5]);
    i_file_skip        = atoi(argv[6]);
    flag_manual_centre = atoi(argv[7]);

    if(!flag_manual_centre)
        flag_write_indices = GBP_FALSE;
    else
        flag_write_indices = GBP_TRUE;

    SID_log("Processing group/subgroup statistics for files #%d->#%d...", SID_LOG_OPEN | SID_LOG_TIMER, i_file_lo, i_file_hi);
    SID_log("Properties structure size=%d bytes", SID_LOG_COMMENT, sizeof(halo_properties_info));
    SID_log("Profiles   structure size=%d bytes", SID_LOG_COMMENT, sizeof(halo_profile_bin_info));

    for(i_file = i_file_lo; i_file <= i_file_hi; i_file += i_file_skip) {
        filename_number = (char *)SID_malloc(sizeof(char) * 10);
        sprintf(filename_number, "%03d", i_file);
        SID_log("Processing file #%d...", SID_LOG_OPEN | SID_LOG_TIMER, i_file);

        // Read group and particle info
        init_plist(&plist, NULL, GADGET_LENGTH, GADGET_MASS, GADGET_VELOCITY);
        // sprintf(filename_number,"read_catalog");
        ADaPS_store(&(plist.data), (void *)filename_number, "read_catalog", ADaPS_DEFAULT);
        read_groups(filename_groups_root, i_file, READ_GROUPS_ALL, &plist, filename_number);
        n_particles_in_groups = ((size_t *)ADaPS_fetch(plist.data, "n_particles_%s", filename_number))[0];
        n_groups_all          = ((int *)ADaPS_fetch(plist.data, "n_groups_all_%s", filename_number))[0];
        if(n_groups_all > 0) {
            read_gadget_binary_local(filename_snapshot_root, i_file, &plist);
            // read_gadget_binary(filename_snapshot_root,i_file,&plist,READ_GADGET_DEFAULT);
            if(ADaPS_exist(plist.data, "id_dark")) {
                n_particles_snapshot = ((size_t *)ADaPS_fetch(plist.data, "n_dark"))[0];
                ids_snapshot         = (size_t *)ADaPS_fetch(plist.data, "id_dark");
                ids_groups           = (size_t *)ADaPS_fetch(plist.data, "particle_ids_%s", filename_number);
                particle_mass        = ((double *)ADaPS_fetch(plist.data, "mass_array_dark"))[0];
                x_array              = (GBPREAL *)ADaPS_fetch(plist.data, "x_dark");
                y_array              = (GBPREAL *)ADaPS_fetch(plist.data, "y_dark");
                z_array              = (GBPREAL *)ADaPS_fetch(plist.data, "z_dark");
                vx_array             = (GBPREAL *)ADaPS_fetch(plist.data, "vx_dark");
                vy_array             = (GBPREAL *)ADaPS_fetch(plist.data, "vy_dark");
                vz_array             = (GBPREAL *)ADaPS_fetch(plist.data, "vz_dark");
            } else
                n_particles_snapshot = 0;

            // Initialize cosmology
            cosmo_info *cosmo = NULL;
            box_size          = ((double *)ADaPS_fetch(plist.data, "box_size"))[0];
            h_Hubble          = ((double *)ADaPS_fetch(plist.data, "h_Hubble"))[0];
            redshift          = ((double *)ADaPS_fetch(plist.data, "redshift"))[0];
            expansion_factor  = ((double *)ADaPS_fetch(plist.data, "expansion_factor"))[0];
            Omega_M           = ((double *)ADaPS_fetch(plist.data, "Omega_M"))[0];
            Omega_Lambda      = ((double *)ADaPS_fetch(plist.data, "Omega_Lambda"))[0];
            Omega_k           = 1. - Omega_Lambda - Omega_M;
            Omega_b           = 0.; // not needed, so doesn't matter
            f_gas             = Omega_b / Omega_M;
            sigma_8           = 0.; // not needed, so doesn't matter
            n_spec            = 0.; // not needed, so doesn't matter
            char cosmo_name[16];
            sprintf(cosmo_name, "Gadget file's");
            init_cosmo(&cosmo, cosmo_name, Omega_Lambda, Omega_M, Omega_k, Omega_b, f_gas, h_Hubble, sigma_8, n_spec);

            // Compute sort indices for ids in snapshot and group catalog
            SID_log("Sorting IDs...", SID_LOG_OPEN | SID_LOG_TIMER);
            if(n_particles_snapshot > 0) {
                merge_sort((void *)ids_snapshot, n_particles_snapshot, &ids_snapshot_sort_index, SID_SIZE_T, SORT_COMPUTE_INDEX, GBP_FALSE);
                merge_sort((void *)ids_groups, n_particles_in_groups, &ids_groups_sort_index, SID_SIZE_T, SORT_COMPUTE_INDEX, GBP_FALSE);

                // Create snapshot-indices for each particle in the group catalog
                ids_sort_index = (size_t *)SID_malloc(sizeof(size_t) * n_particles_in_groups);
                for(i_particle = 0, j_particle = 0; i_particle < n_particles_in_groups; i_particle++) {
                    while(ids_snapshot[ids_snapshot_sort_index[j_particle]] < ids_groups[ids_groups_sort_index[i_particle]]) {
                        j_particle++;
                        if(j_particle >= n_particles_snapshot)
                            SID_exit_error("There's a particle id in the group catalog that's not in the snapshot!",
                                           SID_ERROR_LOGIC);
                    }
                    ids_sort_index[ids_groups_sort_index[i_particle]] = ids_snapshot_sort_index[j_particle];
                }
            } else {
                ids_snapshot_sort_index = NULL;
                ids_groups_sort_index   = NULL;
                ids_sort_index          = NULL;
            }
            SID_log("Done.", SID_LOG_CLOSE);

            // Create the output directory
            if(SID.I_am_Master)
                mkdir(filename_out_root, 02755);

            // Print stats; groups first and then subgroups
            for(i_process = 0; i_process < 2; i_process++) {
                // Initialize a bunch of stuff depending on whether we are
                //   computing group or subgroup properties
                switch(i_process) {
                    case 0:
                        sprintf(group_text_prefix, "");
                        break;
                    case 1:
                        sprintf(group_text_prefix, "sub");
                        break;
                }
                n_groups     = ((int *)ADaPS_fetch(plist.data, "n_%sgroups_%s", group_text_prefix, filename_number))[0];
                n_groups_all = ((int *)ADaPS_fetch(plist.data, "n_%sgroups_all_%s", group_text_prefix, filename_number))[0];

                // Fetch some stuff
                n_particles_groups = (int *)ADaPS_fetch(plist.data, "n_particles_%sgroup_%s", group_text_prefix, filename_number);
                group_offset       = (size_t *)ADaPS_fetch(plist.data, "particle_offset_%sgroup_%s", group_text_prefix, filename_number);

                // Create filenames, directories, etc
                sprintf(filename_output_properties_temp, "%s_%s.catalog_%sgroups_properties", filename_out_root, filename_number, group_text_prefix);
                sprintf(filename_output_profiles_temp, "%s_%s.catalog_%sgroups_profiles", filename_out_root, filename_number, group_text_prefix);
                sprintf(filename_output_indices_temp, "%s_%s.catalog_%sgroups_indices", filename_out_root, filename_number, group_text_prefix);
                if(SID.n_proc > 1) {
                    // Create property filenames
                    if(flag_write_properties) {
                        char properties_root[SID_MAX_FILENAME_LENGTH];
                        strcpy(properties_root, filename_output_properties_temp);
                        strip_path(properties_root);
                        strcpy(filename_output_properties_dir, filename_output_properties_temp);
                        if(SID.I_am_Master)
                            mkdir(filename_output_properties_dir, 02755);
                        SID_Barrier(SID_COMM_WORLD);
                        sprintf(filename_output_properties, "%s/%s.%d", filename_output_properties_dir, properties_root, SID.My_rank);
                    }
                    // Create profile filenames
                    if(flag_write_profiles) {
                        char profiles_root[SID_MAX_FILENAME_LENGTH];
                        strcpy(profiles_root, filename_output_profiles_temp);
                        strip_path(profiles_root);
                        strcpy(filename_output_profiles_dir, filename_output_profiles_temp);
                        if(SID.I_am_Master)
                            mkdir(filename_output_profiles_dir, 02755);
                        SID_Barrier(SID_COMM_WORLD);
                        sprintf(filename_output_profiles, "%s/%s.%d", filename_output_profiles_dir, profiles_root, SID.My_rank);
                    }
                    // Create sort indices filenames
                    if(flag_write_indices) {
                        char indices_root[SID_MAX_FILENAME_LENGTH];
                        strcpy(indices_root, filename_output_indices_temp);
                        strip_path(indices_root);
                        strcpy(filename_output_indices_dir, filename_output_indices_temp);
                        if(SID.I_am_Master)
                            mkdir(filename_output_indices_dir, 02755);
                        SID_Barrier(SID_COMM_WORLD);
                        sprintf(filename_output_indices, "%s/%s.%d", filename_output_indices_dir, indices_root, SID.My_rank);
                    }
                } else {
                    strcpy(filename_output_properties, filename_output_properties_temp);
                    strcpy(filename_output_profiles, filename_output_profiles_temp);
                    strcpy(filename_output_indices, filename_output_indices_temp);
                }
                SID_Barrier(SID_COMM_WORLD); // This makes sure that any created directories are there before proceeding

                // Open files
                SID_log("Processing %sgroups...", SID_LOG_OPEN | SID_LOG_TIMER, group_text_prefix);
                if(flag_write_properties)
                    fp_properties = fopen(filename_output_properties, "w");
                else
                    fp_properties = NULL;
                if(flag_write_profiles)
                    fp_profiles = fopen(filename_output_profiles, "w");
                else
                    fp_profiles = NULL;
                if(flag_write_indices)
                    fp_indices = fopen(filename_output_indices, "w");
                else
                    fp_indices = NULL;

                // Write header
                if(fp_properties != NULL) {
                    fwrite(&(SID.My_rank), sizeof(int), 1, fp_properties);
                    fwrite(&(SID.n_proc), sizeof(int), 1, fp_properties);
                    fwrite(&n_groups, sizeof(int), 1, fp_properties);
                    fwrite(&n_groups_all, sizeof(int), 1, fp_properties);
                }
                if(fp_profiles != NULL) {
                    fwrite(&(SID.My_rank), sizeof(int), 1, fp_profiles);
                    fwrite(&(SID.n_proc), sizeof(int), 1, fp_profiles);
                    fwrite(&n_groups, sizeof(int), 1, fp_profiles);
                    fwrite(&n_groups_all, sizeof(int), 1, fp_profiles);
                }
                if(fp_indices != NULL) {
                    fwrite(&(SID.My_rank), sizeof(int), 1, fp_indices);
                    fwrite(&(SID.n_proc), sizeof(int), 1, fp_indices);
                    fwrite(&n_groups, sizeof(int), 1, fp_indices);
                    fwrite(&n_groups_all, sizeof(int), 1, fp_indices);
                }

                // Turn off the gsl error handler
                gsl_error_handler_t *original_handler;
                original_handler = gsl_set_error_handler_off();

                // Create and write the properties and profiles of each group/subgroup in turn

                // Allocate some temporary arrays for particle positions/velocities
                int     n_particles_alloc;
                double *x;
                double *y;
                double *z;
                double *vx;
                double *vy;
                double *vz;
                double *R;
                size_t *R_index = NULL;
                calc_max(n_particles_groups, &n_particles_alloc, n_groups, SID_INT, CALC_MODE_DEFAULT);
                x  = (double *)SID_malloc(sizeof(double) * n_particles_alloc);
                y  = (double *)SID_malloc(sizeof(double) * n_particles_alloc);
                z  = (double *)SID_malloc(sizeof(double) * n_particles_alloc);
                vx = (double *)SID_malloc(sizeof(double) * n_particles_alloc);
                vy = (double *)SID_malloc(sizeof(double) * n_particles_alloc);
                vz = (double *)SID_malloc(sizeof(double) * n_particles_alloc);
                R  = (double *)SID_malloc(sizeof(double) * n_particles_alloc);

                params_info_local params;
                params.ids_array       = ids_snapshot;
                params.x_array         = x_array;
                params.y_array         = y_array;
                params.z_array         = z_array;
                params.vx_array        = vx_array;
                params.vy_array        = vy_array;
                params.vz_array        = vz_array;
                params.halo_sort_index = ids_sort_index;

                for(i_group = 0, n_truncated_local = 0, largest_truncated_local = 0; i_group < n_groups; i_group++) {
                    params.first_particle_offset = group_offset[i_group];
                    if(compute_group_analysis(&properties,
                                              &profile,
                                              p_i_local,
                                              v_i_local,
                                              id_i_local,
                                              &params,
                                              box_size,
                                              particle_mass,
                                              n_particles_groups[i_group],
                                              expansion_factor,
                                              x,
                                              y,
                                              z,
                                              vx,
                                              vy,
                                              vz,
                                              R,
                                              &R_index,
                                              flag_manual_centre,
                                              GBP_TRUE,
                                              cosmo) != GBP_TRUE) {
                        n_truncated_local++;
                        largest_truncated_local = GBP_MAX(largest_truncated_local, n_particles_groups[i_group]);
                    }
                    write_group_analysis(fp_properties, fp_profiles, fp_indices, &properties, &profile, R_index, n_particles_groups[i_group]);

                    SID_free(SID_FARG R_index);
                }

                // Free arrays
                SID_free(SID_FARG x);
                SID_free(SID_FARG y);
                SID_free(SID_FARG z);
                SID_free(SID_FARG vx);
                SID_free(SID_FARG vy);
                SID_free(SID_FARG vz);
                SID_free(SID_FARG R);

                if(fp_properties != NULL)
                    fclose(fp_properties);
                if(fp_profiles != NULL)
                    fclose(fp_profiles);
                if(fp_indices != NULL)
                    fclose(fp_indices);
                calc_sum_global(&n_truncated_local, &n_truncated, 1, SID_INT, CALC_MODE_DEFAULT, SID_COMM_WORLD);
                calc_max_global(&largest_truncated_local, &largest_truncated, 1, SID_INT, CALC_MODE_DEFAULT, SID_COMM_WORLD);

                // Restore the original handler
                gsl_set_error_handler(original_handler);

                SID_Barrier(SID_COMM_WORLD);
                SID_log(
                    "Done. (f_truncated=%6.2lf%% largest=%d)", SID_LOG_CLOSE, 100. * (double)n_truncated / (double)n_groups_all, largest_truncated);
            }

            // Clean-up
            free_cosmo(&cosmo);
            SID_free(SID_FARG ids_snapshot_sort_index);
            SID_free(SID_FARG ids_groups_sort_index);
            SID_free(SID_FARG ids_sort_index);
        }
        // If the group catalog or snapshot is empty, create an empty file
        else {
            SID_log("Creating empty analysis files...", SID_LOG_OPEN);
            if(SID.I_am_Master) {
                for(i_process = 0; i_process < 2; i_process++) {
                    switch(i_process) {
                        case 0:
                            sprintf(group_text_prefix, "");
                            break;
                        case 1:
                            sprintf(group_text_prefix, "sub");
                            break;
                    }
                    n_groups_all = 0;
                    n_temp       = 1;
                    if(flag_write_properties) {
                        sprintf(
                            filename_output_properties, "%s_%s.catalog_%sgroups_properties", filename_out_root, filename_number, group_text_prefix);
                        fp_properties = fopen(filename_output_properties, "w");
                        fwrite(&(SID.My_rank), sizeof(int), 1, fp_properties);
                        fwrite(&n_temp, sizeof(int), 1, fp_properties);
                        fwrite(&n_groups_all, sizeof(int), 1, fp_properties);
                        fwrite(&n_groups_all, sizeof(int), 1, fp_properties);
                        fclose(fp_properties);
                    }
                    if(flag_write_profiles) {
                        sprintf(filename_output_profiles, "%s_%s.catalog_%sgroups_profiles", filename_out_root, filename_number, group_text_prefix);
                        fp_profiles = fopen(filename_output_profiles, "w");
                        fwrite(&(SID.My_rank), sizeof(int), 1, fp_profiles);
                        fwrite(&n_temp, sizeof(int), 1, fp_profiles);
                        fwrite(&n_groups_all, sizeof(int), 1, fp_profiles);
                        fwrite(&n_groups_all, sizeof(int), 1, fp_profiles);
                        fclose(fp_profiles);
                    }
                }
            }
            SID_log("Done.", SID_LOG_CLOSE);
        }
        free_plist(&plist);
        SID_log("Done.", SID_LOG_CLOSE);
    }

    SID_log("Done.", SID_LOG_CLOSE);
    SID_Finalize();
}


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

#ifndef __GSL_MATRIX_FLOAT_H__
#define __GSL_MATRIX_FLOAT_H__

#include <stdlib.h>
#include <gsl/gsl_types.h>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_check_range.h>
#include <gsl/gsl_vector_float.h>

#undef __BEGIN_DECLS
#undef __END_DECLS
#ifdef __cplusplus
# define __BEGIN_DECLS extern "C" {
# define __END_DECLS }
#else
# define __BEGIN_DECLS /* empty */
# define __END_DECLS /* empty */
#endif

__BEGIN_DECLS

typedef struct
{
  size_t size1;
  size_t size2;
  size_t tda;
  float * data;
  gsl_block_float * block;
  int owner;
} gsl_matrix_float;

typedef struct
{
  gsl_matrix_float matrix;
} _gsl_matrix_float_view;

typedef _gsl_matrix_float_view gsl_matrix_float_view;

typedef struct
{
  gsl_matrix_float matrix;
} _gsl_matrix_float_const_view;

typedef const _gsl_matrix_float_const_view gsl_matrix_float_const_view;

/* Allocation */

GSL_EXPORT
gsl_matrix_float *
gsl_matrix_float_alloc (const size_t n1, const size_t n2);

GSL_EXPORT
gsl_matrix_float *
gsl_matrix_float_calloc (const size_t n1, const size_t n2);

GSL_EXPORT
gsl_matrix_float *
gsl_matrix_float_alloc_from_block (gsl_block_float * b,
                                   const size_t offset,
                                   const size_t n1,
                                   const size_t n2,
                                   const size_t d2);

GSL_EXPORT
gsl_matrix_float *
gsl_matrix_float_alloc_from_matrix (gsl_matrix_float * m,
                                    const size_t k1,
                                    const size_t k2,
                                    const size_t n1,
                                    const size_t n2);

GSL_EXPORT
gsl_vector_float *
gsl_vector_float_alloc_row_from_matrix (gsl_matrix_float * m,
                                        const size_t i);

GSL_EXPORT
gsl_vector_float *
gsl_vector_float_alloc_col_from_matrix (gsl_matrix_float * m,
                                        const size_t j);

GSL_EXPORT void gsl_matrix_float_free (gsl_matrix_float * m);

/* Views */

GSL_EXPORT
_gsl_matrix_float_view
gsl_matrix_float_submatrix (gsl_matrix_float * m,
                            const size_t i, const size_t j,
                            const size_t n1, const size_t n2);

GSL_EXPORT
_gsl_vector_float_view
gsl_matrix_float_row (gsl_matrix_float * m, const size_t i);

GSL_EXPORT
_gsl_vector_float_view
gsl_matrix_float_column (gsl_matrix_float * m, const size_t j);

GSL_EXPORT
_gsl_vector_float_view
gsl_matrix_float_diagonal (gsl_matrix_float * m);

GSL_EXPORT
_gsl_vector_float_view
gsl_matrix_float_subdiagonal (gsl_matrix_float * m, const size_t k);

GSL_EXPORT
_gsl_vector_float_view
gsl_matrix_float_superdiagonal (gsl_matrix_float * m, const size_t k);

GSL_EXPORT
_gsl_matrix_float_view
gsl_matrix_float_view_array (float * base,
                             const size_t n1,
                             const size_t n2);

GSL_EXPORT
_gsl_matrix_float_view
gsl_matrix_float_view_array_with_tda (float * base,
                                      const size_t n1,
                                      const size_t n2,
                                      const size_t tda);

GSL_EXPORT
_gsl_matrix_float_view
gsl_matrix_float_view_vector (gsl_vector_float * v,
                              const size_t n1,
                              const size_t n2);

GSL_EXPORT
_gsl_matrix_float_view
gsl_matrix_float_view_vector_with_tda (gsl_vector_float * v,
                                       const size_t n1,
                                       const size_t n2,
                                       const size_t tda);

GSL_EXPORT
_gsl_matrix_float_const_view
gsl_matrix_float_const_submatrix (const gsl_matrix_float * m,
                                  const size_t i, const size_t j,
                                  const size_t n1, const size_t n2);

GSL_EXPORT
_gsl_vector_float_const_view
gsl_matrix_float_const_row (const gsl_matrix_float * m,
                            const size_t i);

GSL_EXPORT
_gsl_vector_float_const_view
gsl_matrix_float_const_column (const gsl_matrix_float * m,
                               const size_t j);

GSL_EXPORT
_gsl_vector_float_const_view
gsl_matrix_float_const_diagonal (const gsl_matrix_float * m);

GSL_EXPORT
_gsl_vector_float_const_view
gsl_matrix_float_const_subdiagonal (const gsl_matrix_float * m,
                                    const size_t k);

GSL_EXPORT
_gsl_vector_float_const_view
gsl_matrix_float_const_superdiagonal (const gsl_matrix_float * m,
                                      const size_t k);

GSL_EXPORT
_gsl_matrix_float_const_view
gsl_matrix_float_const_view_array (const float * base,
                                   const size_t n1,
                                   const size_t n2);

GSL_EXPORT
_gsl_matrix_float_const_view
gsl_matrix_float_const_view_array_with_tda (const float * base,
                                            const size_t n1,
                                            const size_t n2,
                                            const size_t tda);

GSL_EXPORT
_gsl_matrix_float_const_view
gsl_matrix_float_const_view_vector (const gsl_vector_float * v,
                                    const size_t n1,
                                    const size_t n2);

GSL_EXPORT
_gsl_matrix_float_const_view
gsl_matrix_float_const_view_vector_with_tda (const gsl_vector_float * v,
                                             const size_t n1,
                                             const size_t n2,
                                             const size_t tda);

/* Operations */

GSL_EXPORT float   gsl_matrix_float_get(const gsl_matrix_float * m, const size_t i, const size_t j);
GSL_EXPORT void    gsl_matrix_float_set(gsl_matrix_float * m, const size_t i, const size_t j, const float x);

GSL_EXPORT float * gsl_matrix_float_ptr(gsl_matrix_float * m, const size_t i, const size_t j);
GSL_EXPORT const float * gsl_matrix_float_const_ptr(const gsl_matrix_float * m, const size_t i, const size_t j);

GSL_EXPORT void gsl_matrix_float_set_zero (gsl_matrix_float * m);
GSL_EXPORT void gsl_matrix_float_set_identity (gsl_matrix_float * m);
GSL_EXPORT void gsl_matrix_float_set_all (gsl_matrix_float * m, float x);

GSL_EXPORT int gsl_matrix_float_fread (FILE * stream, gsl_matrix_float * m) ;
GSL_EXPORT int gsl_matrix_float_fwrite (FILE * stream, const gsl_matrix_float * m) ;
GSL_EXPORT int gsl_matrix_float_fscanf (FILE * stream, gsl_matrix_float * m);
GSL_EXPORT int gsl_matrix_float_fprintf (FILE * stream, const gsl_matrix_float * m, const char * format);

GSL_EXPORT int gsl_matrix_float_memcpy(gsl_matrix_float * dest, const gsl_matrix_float * src);
GSL_EXPORT int gsl_matrix_float_swap(gsl_matrix_float * m1, gsl_matrix_float * m2);

GSL_EXPORT int gsl_matrix_float_swap_rows(gsl_matrix_float * m, const size_t i, const size_t j);
GSL_EXPORT int gsl_matrix_float_swap_columns(gsl_matrix_float * m, const size_t i, const size_t j);
GSL_EXPORT int gsl_matrix_float_swap_rowcol(gsl_matrix_float * m, const size_t i, const size_t j);
GSL_EXPORT int gsl_matrix_float_transpose (gsl_matrix_float * m);
GSL_EXPORT int gsl_matrix_float_transpose_memcpy (gsl_matrix_float * dest, const gsl_matrix_float * src);

GSL_EXPORT float gsl_matrix_float_max (const gsl_matrix_float * m);
GSL_EXPORT float gsl_matrix_float_min (const gsl_matrix_float * m);
GSL_EXPORT void gsl_matrix_float_minmax (const gsl_matrix_float * m, float * min_out, float * max_out);

GSL_EXPORT void gsl_matrix_float_max_index (const gsl_matrix_float * m, size_t * imax, size_t *jmax);
GSL_EXPORT void gsl_matrix_float_min_index (const gsl_matrix_float * m, size_t * imin, size_t *jmin);
GSL_EXPORT void gsl_matrix_float_minmax_index (const gsl_matrix_float * m, size_t * imin, size_t * jmin, size_t * imax, size_t * jmax);

GSL_EXPORT int gsl_matrix_float_isnull (const gsl_matrix_float * m);

GSL_EXPORT int gsl_matrix_float_add (gsl_matrix_float * a, const gsl_matrix_float * b);
GSL_EXPORT int gsl_matrix_float_sub (gsl_matrix_float * a, const gsl_matrix_float * b);
GSL_EXPORT int gsl_matrix_float_mul_elements (gsl_matrix_float * a, const gsl_matrix_float * b);
GSL_EXPORT int gsl_matrix_float_div_elements (gsl_matrix_float * a, const gsl_matrix_float * b);
GSL_EXPORT int gsl_matrix_float_scale (gsl_matrix_float * a, const double x);
GSL_EXPORT int gsl_matrix_float_add_constant (gsl_matrix_float * a, const double x);
GSL_EXPORT int gsl_matrix_float_add_diagonal (gsl_matrix_float * a, const double x);

/***********************************************************************/
/* The functions below are obsolete                                    */
/***********************************************************************/
GSL_EXPORT int gsl_matrix_float_get_row(gsl_vector_float * v, const gsl_matrix_float * m, const size_t i);
GSL_EXPORT int gsl_matrix_float_get_col(gsl_vector_float * v, const gsl_matrix_float * m, const size_t j);
GSL_EXPORT int gsl_matrix_float_set_row(gsl_matrix_float * m, const size_t i, const gsl_vector_float * v);
GSL_EXPORT int gsl_matrix_float_set_col(gsl_matrix_float * m, const size_t j, const gsl_vector_float * v);

/* inline functions if you are using GCC */

#ifdef HAVE_INLINE
extern inline
float
gsl_matrix_float_get(const gsl_matrix_float * m, const size_t i, const size_t j)
{
#if GSL_RANGE_CHECK
  if (i >= m->size1)
    {
      GSL_ERROR_VAL("first index out of range", GSL_EINVAL, 0) ;
    }
  else if (j >= m->size2)
    {
      GSL_ERROR_VAL("second index out of range", GSL_EINVAL, 0) ;
    }
#endif
  return m->data[i * m->tda + j] ;
}

extern inline
void
gsl_matrix_float_set(gsl_matrix_float * m, const size_t i, const size_t j, const float x)
{
#if GSL_RANGE_CHECK
  if (i >= m->size1)
    {
      GSL_ERROR_VOID("first index out of range", GSL_EINVAL) ;
    }
  else if (j >= m->size2)
    {
      GSL_ERROR_VOID("second index out of range", GSL_EINVAL) ;
    }
#endif
  m->data[i * m->tda + j] = x ;
}

extern inline
float *
gsl_matrix_float_ptr(gsl_matrix_float * m, const size_t i, const size_t j)
{
#if GSL_RANGE_CHECK
  if (i >= m->size1)
    {
      GSL_ERROR_NULL("first index out of range", GSL_EINVAL) ;
    }
  else if (j >= m->size2)
    {
      GSL_ERROR_NULL("second index out of range", GSL_EINVAL) ;
    }
#endif
  return (float *) (m->data + (i * m->tda + j)) ;
}

extern inline
const float *
gsl_matrix_float_const_ptr(const gsl_matrix_float * m, const size_t i, const size_t j)
{
#if GSL_RANGE_CHECK
  if (i >= m->size1)
    {
      GSL_ERROR_NULL("first index out of range", GSL_EINVAL) ;
    }
  else if (j >= m->size2)
    {
      GSL_ERROR_NULL("second index out of range", GSL_EINVAL) ;
    }
#endif
  return (const float *) (m->data + (i * m->tda + j)) ;
}

#endif

__END_DECLS

#endif /* __GSL_MATRIX_FLOAT_H__ */


#ifndef QDM_MCMC_H
#define QDM_MCMC_H 1

#include <gsl/gsl_matrix.h>
#include <gsl/gsl_rng.h>
#include <gsl/gsl_vector.h>
#include <hdf5.h>

#include "ijk.h"
#include "tau.h"

typedef struct {
  const gsl_rng *rng;

  size_t burn;
  size_t iter;
  size_t thin;

  size_t acc_check;
  size_t spline_df;

  const gsl_vector *xi;
  const gsl_vector *ll;
  const gsl_vector *tau;
  const gsl_matrix *theta;

  double xi_prior_mean;
  double xi_prior_var;
  double xi_tune_sd;

  double theta_min;
  double theta_tune_sd;

  const gsl_vector *x;
  const gsl_vector *y;

  const qdm_tau *t;
  const gsl_vector *m_knots;

  bool truncate;
} qdm_mcmc_parameters;

typedef struct {
  gsl_vector *xi;
  gsl_vector *xi_acc;
  gsl_vector *xi_p;
  gsl_vector *xi_tune;

  gsl_vector *ll;
  gsl_vector *ll_p;

  gsl_vector *tau;
  gsl_vector *tau_p;

  gsl_matrix *theta;
  gsl_matrix *theta_acc;
  gsl_matrix *theta_p;
  gsl_matrix *theta_star;
  gsl_matrix *theta_star_p;
  gsl_matrix *theta_tune;
} qdm_mcmc_workspace;

typedef struct {
  size_t s;

  qdm_ijk *theta;
  qdm_ijk *theta_star;

  qdm_ijk *ll;
  qdm_ijk *tau;

  qdm_ijk *xi;
} qdm_mcmc_results;

typedef struct {
  qdm_mcmc_parameters p;
  qdm_mcmc_workspace w;
  qdm_mcmc_results r;
} qdm_mcmc;

qdm_mcmc *
qdm_mcmc_alloc(
    qdm_mcmc_parameters p
);

void
qdm_mcmc_free(
    qdm_mcmc *mcmc
);

int
qdm_mcmc_run(
    qdm_mcmc *mcmc
);

int
qdm_mcmc_next(
    qdm_mcmc *mcmc
);

int
qdm_mcmc_update_theta(
    qdm_mcmc *mcmc
);

int
qdm_mcmc_update_xi(
    qdm_mcmc *mcmc
);

void
qdm_mcmc_update_tune(
    qdm_mcmc *mcmc
);

int
qdm_mcmc_save(
    qdm_mcmc *mcmc,
    size_t k
);

int
qdm_mcmc_write(
    hid_t id,
    const qdm_mcmc *mcmc
);

int
qdm_mcmc_read(
    hid_t id,
    qdm_mcmc **mcmc
);

#endif /* QDM_MCMC_H */


#include <math.h>
#include <stdlib.h>
#if !defined(__APPLE__)
#include <malloc.h>
#endif
#include <stdio.h>
#include <assert.h>
#include <time.h>
#include <string.h>
#include <stdbool.h>

#include <fftw3.h>

#include <gsl/gsl_errno.h>
#include <gsl/gsl_sf_erf.h>
#include <gsl/gsl_integration.h>
#include <gsl/gsl_spline.h>
#include <gsl/gsl_sf_gamma.h>
#include <gsl/gsl_sf_legendre.h>
#include <gsl/gsl_sf_bessel.h>
#include <gsl/gsl_linalg.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_eigen.h>
#include <gsl/gsl_sf_expint.h>
#include <gsl/gsl_deriv.h>
#include <gsl/gsl_interp2d.h>
#include <gsl/gsl_spline2d.h>

#include "../cosmolike_core/theory/basics.c"
#include "../cosmolike_core/theory/structs.c"
#include "../cosmolike_core/theory/parameters.c"
#include "../cosmolike_core/emu17/P_cb/emu.c"
#include "../cosmolike_core/theory/recompute.c"
#include "../cosmolike_core/theory/cosmo3D.c"
#include "../cosmolike_core/theory/redshift_spline.c"
#include "../cosmolike_core/theory/halo.c"
#include "../cosmolike_core/theory/HOD.c"
#include "../cosmolike_core/theory/pt.c"
#include "../cosmolike_core/theory/cosmo2D_fourier.c"
#include "../cosmolike_core/theory/IA.c"
#include "../cosmolike_core/theory/BAO.c"
#include "../cosmolike_core/theory/external_prior.c"
#include "../cosmolike_core/theory/covariances_3D.c"
#include "../cosmolike_core/theory/covariances_fourier.c"
#include "../cosmolike_core/theory/CMBxLSS_fourier.c"
#include "../cosmolike_core/theory/covariances_CMBxLSS_fourier.c"

#include "../cosmolike_core/theory/covariances_binned_simple.c"
#include "../cosmolike_core/theory/run_covariances_fourier_binned_6x2pt.c"

#include "init_LSSxCMB.c"

int main(int argc, char** argv)
{

  int i,l,m,n,o,s,p,nl1,t,k;
  char OUTFILE[400],filename[400],arg1[400],arg2[400];

  int N_scenarios=2;
  double area_table[2]={12300.0,16500.0}; // Y1 corresponds to DESC SRD Y1, Y6 corresponds to assuming that we cover the full SO area=0.4*fsky and at a depth of 26.1 which is in a range of reasonable scenarios (see https://github.com/LSSTDESC/ObsStrat/tree/static/static )
  double nsource_table[2]={11.0,23.0};
  double nlens_table[2]={18.0,41.0};

  char survey_designation[2][200]={"LSSTxSO_Y1","LSSTxSO_Y6"};

  char source_zfile[2][400]={"src_LSSTY1","src_LSSTY6"};

#ifdef ONESAMPLE
  char lens_zfile[2][400]={"src_LSSTY1","src_LSSTY6"};
  nlens_table[0] = nsource_table[0];
  nlens_table[1] = nsource_table[1];
#else
  char lens_zfile[2][400]={"lens_LSSTY1","lens_LSSTY6"};
#endif

  int hit=atoi(argv[1]);
  Ntable.N_a=100;
  k=1;

  t = atoi(argv[2]);

  //RUN MODE setup
  init_cosmo_runmode("emu");
  // init_binning_fourier(20,30.0,3000.0,3000.0,21.0,10,10);
  init_binning_fourier(15,20.0,3000.0,3000.0,0.0,10,10);
  init_survey(survey_designation[t],nsource_table[t],nlens_table[t],area_table[t]);
  sprintf(arg1,"zdistris/%s",source_zfile[t]);
  sprintf(arg2,"zdistris/%s",lens_zfile[t]);
  init_galaxies(arg1,arg2,"none","none","source_std","LSST_gold");
  init_IA("none","GAMA");
  init_probes("6x2pt");

  if(t==0) init_cmb("so_Y1");
  if(t==1) init_cmb("so_Y5");
  cmb.fsky = survey.area*survey.area_conversion_factor/(4.*M_PI);
  //set l-bins for shear, ggl, clustering, clusterWL

  double lmin=like.lmin;
  double lmax=like.lmax;
  int Nell=like.Ncl;
  int Ncl = Nell;
  double logdl=(log(lmax)-log(lmin))/Nell;
  double *ellmin, *dell;
  ellmin=create_double_vector(0,Nell);
  dell=create_double_vector(0,Nell-1);
  double ellmax;
  for(i=0; i<Nell ; i++){
    ellmin[i]=exp(log(lmin)+(i+0.0)*logdl);
    ellmax = exp(log(lmin)+(i+1.0)*logdl);
    dell[i]=ellmax-ellmin[i];
  }
  ellmin[Nell] = ellmax;
  like.ell = ellmin;

  covparams.ng = 1;
  covparams.cng= 0;

  printf("----------------------------------\n");
  sprintf(survey.name,"%s_area%le_ng%le_nl%le",survey_designation[t],survey.area,survey.n_gal,survey.n_lens);
  printf("area: %le n_source: %le n_lens: %le\n",survey.area,survey.n_gal,survey.n_lens);

  // sprintf(covparams.outdir,"/home/u17/timeifler/covparallel/");
#ifdef ONESAMPLE
  sprintf(covparams.outdir,"out_cov_lsstxso_1sample/");
#else
  sprintf(covparams.outdir,"out_cov_lsstxso/");
  //sprintf(covparams.outdir,"/halo_nobackup/cosmos/teifler/covparallel/");
#endif

  printf("----------------------------------\n");
  if (like.shear_shear)
  {
    sprintf(OUTFILE,"%s_ssss_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);

    for (l=0;l<tomo.shear_Npowerspectra; l++){
      for (m=l;m<tomo.shear_Npowerspectra; m++){
        if(k==hit){
          sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
          // if (fopen(filename, "r") != NULL){exit(1);}
          run_cov_shear_shear_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,l,m,k);
        }

        k=k+1;
      }
    }
  }
  if (like.pos_pos)
  {
    sprintf(OUTFILE,"%s_llll_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    for (l=0;l<tomo.clustering_Npowerspectra; l++){
      for (m=l;m<tomo.clustering_Npowerspectra; m++){
        if(k==hit){
          sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
          // if (fopen(filename, "r") != NULL){exit(1);}
          run_cov_clustering_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,l,m,k);
        }
        k=k+1;
      }
    }
  }
  if (like.shear_pos)
  {
    sprintf(OUTFILE,"%s_lsls_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    for (l=0;l<tomo.ggl_Npowerspectra; l++){
      for (m=l;m<tomo.ggl_Npowerspectra; m++){
        if(k==hit) {
          sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
          // if (fopen(filename, "r") != NULL){exit(1);}
          run_cov_ggl_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,l,m,k);
        }
        k=k+1;
      }
    }
  }
  if (like.shear_pos && like.shear_shear)
  {
    sprintf(OUTFILE,"%s_lsss_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    for (l=0;l<tomo.ggl_Npowerspectra; l++){
      for (m=0;m<tomo.shear_Npowerspectra; m++){
        if(k==hit) {
          sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
          // if (fopen(filename, "r") != NULL){exit(1);}
          run_cov_ggl_shear_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,l,m,k);
        }
        k=k+1;
      }
    }
  }
  if (like.pos_pos && like.shear_shear)
  {
    sprintf(OUTFILE,"%s_llss_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    for (l=0;l<tomo.clustering_Npowerspectra; l++){
      for (m=0;m<tomo.shear_Npowerspectra; m++){
        if(k==hit) {
          sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
          // if (fopen(filename, "r") != NULL){exit(1);}
          run_cov_clustering_shear_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,l,m,k);
        }
        k=k+1;
      }
    }
  }
  if (like.pos_pos && like.shear_pos)
  {
    sprintf(OUTFILE,"%s_llls_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    for (l=0;l<tomo.clustering_Npowerspectra; l++){
      for (m=0;m<tomo.ggl_Npowerspectra; m++){
        if(k==hit) {
          sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
          // if (fopen(filename, "r") != NULL){exit(1);}
          run_cov_clustering_ggl_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,l,m,k);
        }
        k=k+1;
      }
    }
  }
  if (like.gk && like.shear_shear)
  {
    sprintf(OUTFILE,"%s_lkss_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    for (l=0;l<tomo.clustering_Nbin; l++){
      for (m=0;m<tomo.shear_Npowerspectra; m++){
        if(k==hit) {
          sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
          // if (fopen(filename, "r") != NULL){exit(1);}
          run_cov_gk_shear_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,l,m,k);
        }
        k=k+1;
      }
    }
  }
  if (like.ks && like.shear_shear)
  {
    sprintf(OUTFILE,"%s_ksss_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    for (l=0;l<tomo.shear_Nbin; l++){
      for (m=0;m<tomo.shear_Npowerspectra; m++){
        if(k==hit) {
          sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
          // if (fopen(filename, "r") != NULL){exit(1);}
          run_cov_ks_shear_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,l,m,k);
        }
        k=k+1;
      }
    }
  }
  if (like.gk && like.shear_pos)
  {
    sprintf(OUTFILE,"%s_lkls_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    for (l=0;l<tomo.clustering_Nbin; l++){
      for (m=0;m<tomo.ggl_Npowerspectra; m++){
        if(k==hit) {
          sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
          // if (fopen(filename, "r") != NULL){exit(1);}
          run_cov_gk_ggl_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,l,m,k);
        }
        k=k+1;
      }
    }
  }
  if (like.ks && like.shear_pos)
  {
    sprintf(OUTFILE,"%s_ksls_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    for (l=0;l<tomo.shear_Nbin; l++){
      for (m=0;m<tomo.ggl_Npowerspectra; m++){
        if(k==hit) {
          sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
          // if (fopen(filename, "r") != NULL){exit(1);}
          run_cov_ks_ggl_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,l,m,k);
        }
        k=k+1;
      }
    }
  }
  if (like.gk && like.pos_pos)
  {
    sprintf(OUTFILE,"%s_lkll_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    for (l=0;l<tomo.clustering_Nbin; l++){
      for (m=0;m<tomo.clustering_Npowerspectra; m++){
        if(k==hit) {
          sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
          // if (fopen(filename, "r") != NULL){exit(1);}
          run_cov_gk_clustering_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,l,m,k);
        }
        k=k+1;
      }
    }
  }
  if (like.ks && like.pos_pos)
  {
    sprintf(OUTFILE,"%s_ksll_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    for (l=0;l<tomo.shear_Nbin; l++){
      for (m=0;m<tomo.clustering_Npowerspectra; m++){
        if(k==hit) {
          sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
          // if (fopen(filename, "r") != NULL){exit(1);}
          run_cov_ks_clustering_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,l,m,k);
        }
        k=k+1;
      }
    }
  }
  if (like.gk)
  {
    sprintf(OUTFILE,"%s_lklk_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    for (l=0;l<tomo.clustering_Nbin; l++){
      for (m=l;m<tomo.clustering_Nbin; m++){
        if(k==hit) {
          sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
          // if (fopen(filename, "r") != NULL){exit(1);}
          run_cov_gk_gk_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,l,m,k);
        }
        k=k+1;
      }
    }
  }
  if (like.ks && like.gk)
  {
    sprintf(OUTFILE,"%s_kslk_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    for (l=0;l<tomo.shear_Nbin; l++){
      for (m=0;m<tomo.clustering_Nbin; m++){
        if(k==hit) {
          sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
          // if (fopen(filename, "r") != NULL){exit(1);}
          run_cov_ks_gk_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,l,m,k);
        }
        k=k+1;
      }
    }
  }
  if (like.ks)
  {
    sprintf(OUTFILE,"%s_ksks_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    for (l=0;l<tomo.shear_Nbin; l++){
      for (m=l;m<tomo.shear_Nbin; m++){
        if(k==hit) {
          sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
          // if (fopen(filename, "r") != NULL){exit(1);}
          run_cov_ks_ks_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,l,m,k);
        }
        k=k+1;
      }
    }
  }
  ////////

  if (like.kk && like.shear_shear)
  {
    sprintf(OUTFILE,"%s_kkss_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    for (m=0;m<tomo.shear_Npowerspectra; m++){
      if(k==hit) {
        sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
        // if (fopen(filename, "r") != NULL){exit(1);}
        run_cov_kk_shear_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,m,k);
      }
      k=k+1;
    }
  }
  if (like.kk && like.shear_pos)
  {
    sprintf(OUTFILE,"%s_kkls_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    for (m=0;m<tomo.ggl_Npowerspectra; m++){
      if(k==hit) {
        sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
        // if (fopen(filename, "r") != NULL){exit(1);}
        run_cov_kk_ggl_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,m,k);
      }
      k=k+1;
    }
  }
  if (like.kk && like.pos_pos)
  {
    sprintf(OUTFILE,"%s_kkll_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    for (m=0;m<tomo.clustering_Npowerspectra; m++){
      if(k==hit) {
        sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
        // if (fopen(filename, "r") != NULL){exit(1);}
        run_cov_kk_clustering_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,m,k);
      }
      k=k+1;
    }
  }
  if (like.kk && like.gk)
  {
    sprintf(OUTFILE,"%s_kklk_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    for (m=0;m<tomo.clustering_Nbin; m++){
      if(k==hit) {
        sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
        // if (fopen(filename, "r") != NULL){exit(1);}
        run_cov_kk_gk_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,m,k);
      }
      k=k+1;
    }
  }
  if (like.kk && like.ks)
  {
    sprintf(OUTFILE,"%s_kkks_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    for (m=0;m<tomo.shear_Nbin; m++){
      if(k==hit) {
        sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
        // if (fopen(filename, "r") != NULL){exit(1);}
        run_cov_kk_ks_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,m,k);
      }
      k=k+1;
    }
  }
  if (like.kk)
  {
    sprintf(OUTFILE,"%s_kkkk_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    if(k==hit) {
      sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
      // if (fopen(filename, "r") != NULL){exit(1);}
      run_cov_kk_kk_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,k);
    }
    k=k+1;
  }

  printf("number of cov blocks for parallelization: %d\n",k-1);
  printf("-----------------\n");
  printf("PROGRAM EXECUTED\n");
  printf("-----------------\n");
  return 0;
}


/*! \file deleter.h
    \brief gsl_interp_accelとgsl_splineのデリータを宣言・定義したヘッダファイル

    Copyright © 2015 @dc1394 All Rights Reserved.
    This software is released under the BSD 2-Clause License.
*/

#ifndef _DELETER_H_
#define _DELETER_H_

#pragma once

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

namespace getdata {
    //! A function.
    /*!
        gsl_interp_accelへのポインタを解放するラムダ式
        \param acc gsl_interp_accelへのポインタ
    */
    static auto const gsl_interp_accel_deleter = [](gsl_interp_accel * acc) {
        gsl_interp_accel_free(acc);
    };

    //! A function.
    /*!
        gsl_splineへのポインタを解放するラムダ式
        \param spline gsl_splineへのポインタ
    */
    static auto const gsl_spline_deleter = [](gsl_spline * spline) {
        gsl_spline_free(spline);
    };
}

#endif  // _DELETER_H_