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

Copyright 2011-2019, Javier Burguete Tolosa.

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

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

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

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

/**
 * \file optimize.c
 * \brief Source file with optimize functions.
 * \author Javier Burguete Tolosa.
 * \copyright Copyright 2011-2019.
 */
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <float.h>
#include <math.h>
#include <libxml/parser.h>
#include <glib.h>
#include <libintl.h>
#include <gsl/gsl_rng.h>
#if HAVE_MPI
#include <mpi.h>
#endif
#include "config.h"
#include "utils.h"
#include "optimize.h"

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

GMutex mutex[1];                ///< GMutex struct.
FILE *file_variables = NULL;    ///< random variables file.
int rank;                       ///< MPI rank.
int nnodes;                     ///< MPI nodes number.
unsigned nthreads;              ///< threads number.

/**
 * Function to print the random variables on a file.
 */
void
optimize_print_random (Optimize * optimize,     ///< Optimize struct.
                       FILE * file)     ///< file.
{
  unsigned int i, n;
  n = optimize->nfree;
  for (i = 0; i < n; ++i)
    fprintf (file, "o%d:%.19Le;\n", i, optimize->value_optimal[i]);
  for (i = 0; i < n; ++i)
    fprintf (file, "m%d:%.19Le;\n", i, optimize->minimum[i]);
  for (i = 0; i < n; ++i)
    fprintf (file, "i%d:%.19Le;\n", i, optimize->interval[i]);
}

/**
 * Function to perform every optimization step.
 */
void
optimize_step (Optimize * optimize)     ///< Optimize struct.
{
  long double *is, *vo, *vo2, *random;
  long double o, o2, v, f;
  unsigned long long int ii, nrandom;
  unsigned int i, j, k, n, nfree;

#if DEBUG_OPTIMIZE
  fprintf (stderr, "optimize_step: start\n");
#endif

  // save optimal values
#if DEBUG_OPTIMIZE
  fprintf (stderr, "optimize_step: save optimal values\n");
#endif
  nfree = optimize->nfree;
  o2 = *optimize->optimal;
  vo = (long double *) alloca (nfree * sizeof (long double));
  vo2 = (long double *) alloca (nfree * sizeof (long double));
  memcpy (vo, optimize->value_optimal, nfree * sizeof (long double));

  // optimization algorithm sampling
#if DEBUG_OPTIMIZE
  fprintf (stderr, "optimize_step: optimization algorithm sampling\n");
  fprintf (stderr, "optimize_step: nsimulations=%Lu\n", optimize->nsimulations);
#endif
  random = optimize->random_data;
  ii = optimize->nsimulations * (rank * nthreads + optimize->thread)
    / (nnodes * nthreads);
  nrandom = optimize->nsimulations * (rank * nthreads + optimize->thread + 1)
    / (nnodes * nthreads);
  for (; ii < nrandom; ++ii)
    {

      // random freedom degrees
#if DEBUG_OPTIMIZE
      fprintf (stderr, "optimize_step: random freedom degrees\n");
      fprintf (stderr, "optimize_step: simulation=%Lu\n", ii);
#endif
      optimize_generate_freedom (optimize, ii);

      // method coefficients
#if DEBUG_OPTIMIZE
      fprintf (stderr, "optimize_step: method coefficients\n");
#endif
      if (!optimize->method (optimize))
        o = INFINITY;
      else
        o = optimize->objective (optimize);
      if (o < o2)
        {
          o2 = o;
          memcpy (vo, random, nfree * sizeof (long double));
        }
      if (file_variables)
        {
          g_mutex_lock (mutex);
          print_variables (random, nfree, file_variables);
          fprintf (file_variables, "%.19Le\n", o);
          g_mutex_unlock (mutex);
        }
    }

  // array of intervals to climb around the optimal
#if DEBUG_OPTIMIZE
  fprintf (stderr,
           "optimize_step: array of intervals to climb around the optimal\n");
#endif
  is = (long double *) alloca (nfree * sizeof (long double));
  for (j = 0; j < nfree; ++j)
    is[j] = optimize->interval0[j] * optimize->climbing_factor;

  // hill climbing algorithm bucle
#if DEBUG_OPTIMIZE
  fprintf (stderr, "optimize_step: hill climbing algorithm bucle\n");
#endif
  memcpy (vo2, vo, nfree * sizeof (long double));
  n = optimize->nclimbings;
  for (i = 0; i < n; ++i)
    {
      memcpy (random, vo, nfree * sizeof (long double));
      for (j = k = 0; j < nfree; ++j)
        {
          v = vo[j];
          random[j] = v + is[j];
          if (!optimize->method (optimize))
            o = INFINITY;
          else
            o = optimize->objective (optimize);
          if (o < o2)
            {
              k = 1;
              o2 = o;
              memcpy (vo2, random, nfree * sizeof (long double));
            }
          if (file_variables)
            {
              g_mutex_lock (mutex);
              print_variables (random, nfree, file_variables);
              fprintf (file_variables, "%.19Le\n", o);
              g_mutex_unlock (mutex);
            }
          random[j] = fmaxl (0.L, v - is[j]);
          if (!optimize->method (optimize))
            o = INFINITY;
          else
            o = optimize->objective (optimize);
          if (o < o2)
            {
              k = 1;
              o2 = o;
              memcpy (vo2, random, nfree * sizeof (long double));
            }
          if (file_variables)
            {
              g_mutex_lock (mutex);
              print_variables (random, nfree, file_variables);
              fprintf (file_variables, "%.19Le\n", o);
              g_mutex_unlock (mutex);
            }
          random[j] = v;
        }

      // update optimal values and increase or reduce intervals if converging or
      // not
      if (!k)
        f = 0.5L;
      else
        {
          f = 1.2L;
          memcpy (vo, vo2, nfree * sizeof (long double));
        }
      for (j = 0; j < nfree; ++j)
        is[j] *= f;
    }

  // update optimal values
#if DEBUG_OPTIMIZE
  fprintf (stderr, "optimize_step: update optimal values\n");
#endif
  if (o2 < *optimize->optimal)
    {
      g_mutex_lock (mutex);
      *optimize->optimal = o2;
      memcpy (optimize->value_optimal, vo2, nfree * sizeof (long double));
      g_mutex_unlock (mutex);
    }

#if DEBUG_OPTIMIZE
  fprintf (stderr, "optimize_step: end\n");
#endif
}

/**
 * Function to init required variables on an Optimize struct data.
 */
void
optimize_init (Optimize * optimize,     ///< Optimize struct.
               gsl_rng * rng,   ///< GSL pseudo-random number generator struct.
               unsigned int thread)     ///< thread number.
{
#if DEBUG_OPTIMIZE
  fprintf (stderr, "optimize_init: start\n");
#endif
#if DEBUG_OPTIMIZE
  fprintf (stderr, "optimize_init: nsimulations=%Lu nfree=%u size=%u\n",
           optimize->nsimulations, optimize->nfree, optimize->size);
#endif
  optimize->random_data
    = (long double *) g_slice_alloc (optimize->nfree * sizeof (long double));
  optimize->coefficient
    = (long double *) g_slice_alloc (optimize->size * sizeof (long double));
  optimize->minimum
    = (long double *) g_slice_alloc (optimize->nfree * sizeof (long double));
  optimize->interval
    = (long double *) g_slice_alloc (optimize->nfree * sizeof (long double));
  memcpy (optimize->minimum, optimize->minimum0,
          optimize->nfree * sizeof (long double));
  memcpy (optimize->interval, optimize->interval0,
          optimize->nfree * sizeof (long double));
  optimize->rng = rng;
  optimize->thread = thread;
#if DEBUG_OPTIMIZE
  fprintf (stderr, "optimize_init: end\n");
#endif
}

/**
 * Function to free the memory allocated by an Optimize struct.
 */
void
optimize_delete (Optimize * optimize)   ///< Optimize struct.
{
  g_slice_free1 (optimize->nfree * sizeof (long double), optimize->interval);
  g_slice_free1 (optimize->nfree * sizeof (long double), optimize->minimum);
  g_slice_free1 (optimize->size * sizeof (long double), optimize->coefficient);
  g_slice_free1 (optimize->nfree * sizeof (long double), optimize->random_data);
}

/**
 * Function to do the optimization bucle.
 */
void
optimize_bucle (Optimize * optimize)    ///< Optimize struct.
{
  GThread *thread[nthreads];
#if HAVE_MPI
  long double *vo;
  MPI_Status status;
#endif
  unsigned int i, j, nfree;

#if DEBUG_OPTIMIZE
  fprintf (stderr, "optimize_bucle: start\n");
  fprintf (stderr, "optimize_bucle: nfree=%u\n", optimize->nfree);
#endif

  // Allocate local array of optimal values
#if DEBUG_OPTIMIZE
  fprintf (stderr, "optimize_bucle: allocate local array of optimal values\n");
#endif
  nfree = optimize->nfree;
#if HAVE_MPI
  vo = (long double *) alloca ((1 + nfree) * sizeof (long double));
  printf ("Rank=%d NNodes=%d\n", rank, nnodes);
#endif

  // Init some parameters
#if DEBUG_OPTIMIZE
  fprintf (stderr, "optimize_bucle: init some parameters\n");
#endif
  *optimize->optimal = INFINITY;
  for (i = 0; i < nfree; ++i)
    optimize->value_optimal[i]
      = optimize->minimum[i] + 0.5L * optimize->interval[i];

  // Iterate
#if DEBUG_OPTIMIZE
  fprintf (stderr, "optimize_bucle: iterate\n");
#endif
  for (i = 0; i < optimize->niterations; ++i)
    {

      // Optimization step parallelized for every node by GThreads
      if (nthreads > 1)
        {
          for (j = 0; j < nthreads; ++j)
            thread[j]
              = g_thread_new (NULL,
                              (GThreadFunc) (void (*)(void)) optimize_step,
                              (void *) (optimize + j));
          for (j = 0; j < nthreads; ++j)
            g_thread_join (thread[j]);
        }
      else
        optimize_step (optimize);

#if HAVE_MPI
      if (rank > 0)
        {

          // Secondary nodes send the optimal coefficients to the master node
          vo[0] = *optimize->optimal;
          memcpy (vo + 1, optimize->value_optimal,
                  nfree * sizeof (long double));
          MPI_Send (vo, 1 + nfree, MPI_LONG_DOUBLE, 0, 1, MPI_COMM_WORLD);

          // Secondary nodes receive the optimal coefficients
          MPI_Recv (vo, 1 + nfree, MPI_LONG_DOUBLE, 0, 1, MPI_COMM_WORLD,
                    &status);
          *optimize->optimal = vo[0];
          memcpy (optimize->value_optimal, vo + 1,
                  nfree * sizeof (long double));
        }
      else
        {
          printf ("rank=%d optimal=%.19Le\n", rank, *optimize->optimal);

          for (j = 1; j < nnodes; ++j)
            {

              // Master node receives the optimal coefficients obtained by
              // secondary nodes
              MPI_Recv (vo, 1 + nfree, MPI_LONG_DOUBLE, j, 1, MPI_COMM_WORLD,
                        &status);

              // Master node selects the optimal coefficients
              if (vo[0] < *optimize->optimal)
                {
                  *optimize->optimal = vo[0];
                  memcpy (optimize->value_optimal, vo + 1,
                          nfree * sizeof (long double));
                }
            }

          // Master node sends the optimal coefficients to secondary nodes
          vo[0] = *optimize->optimal;
          memcpy (vo + 1, optimize->value_optimal,
                  nfree * sizeof (long double));
          for (j = 1; j < nnodes; ++j)
            MPI_Send (vo, 1 + nfree, MPI_LONG_DOUBLE, j, 1, MPI_COMM_WORLD);
        }

#endif

      // Print the optimal coefficients
#if DEBUG_OPTIMIZE
      optimize_print_random (optimize, stderr);
      fprintf (stderr, "optimal=%.19Le\n", *optimize->optimal);
#endif

      // Updating coefficient intervals to converge
      optimize_converge (optimize);

      // Iterate
#if HAVE_MPI
      printf ("Rank %u\n", rank);
#endif
      printf ("Iteration %u Optimal %.19Le\n", i + 1, *optimize->optimal);
    }

#if DEBUG_OPTIMIZE
  fprintf (stderr, "optimize_bucle: end\n");
#endif
}

/**
 * Function to create an Optimize struct data.
 */
void
optimize_create (Optimize * optimize,   ///< Optimize struct.
                 long double *optimal,
                 ///< pointer to the optimal objective function value.
                 long double *value_optimal)
                 ///< array of optimal freedom degree values.
{
  unsigned long long int nsimulations;
  unsigned int i, nfree;
#if DEBUG_OPTIMIZE
  fprintf (stderr, "optimize_create: start\n");
#endif
  optimize->optimal = optimal;
  optimize->value_optimal = value_optimal;
  nfree = optimize->nfree;
  optimize->nsimulations = nsimulations = optimize->nvariable;
  for (i = 1; i < nfree; ++i)
    optimize->nsimulations *= nsimulations;
  optimize->nclimbings *= nfree;
#if DEBUG_OPTIMIZE
  fprintf (stderr, "optimize_create nsimulations=%Lu nclimbings=%u nfree=%u\n",
           optimize->nsimulations, optimize->nclimbings, nfree);
  fprintf (stderr, "optimize_create: end\n");
#endif
}

/**
 * Function to read the Optimize struct data on a XML node.
 *
 * \return 1 on success, 0 on error.
 */
int
optimize_read (Optimize * optimize,     ///< Optimize struct.
               xmlNode * node)  ///< XML node.
{
  int code;
#if DEBUG_OPTIMIZE
  fprintf (stderr, "optimize_read: start\n");
#endif
  optimize->nvariable = xml_node_get_uint (node, XML_NSIMULATIONS, &code);
  if (code || !optimize->nvariable)
    {
      error_message = g_strdup (_("Bad simulations number"));
      goto exit_on_error;
    }
  optimize->nclimbings
    = xml_node_get_uint_with_default (node, XML_NCLIMBINGS, 0, &code);
  if (code)
    {
      error_message = g_strdup (_("Bad hill climbings number"));
      goto exit_on_error;
    }
  optimize->niterations = xml_node_get_uint (node, XML_NITERATIONS, &code);
  if (code || !optimize->niterations)
    {
      error_message = g_strdup (_("Bad iterations number"));
      goto exit_on_error;
    }
  optimize->convergence_factor
    = xml_node_get_float (node, XML_CONVERGENCE_FACTOR, &code);
  if (code || optimize->convergence_factor < LDBL_EPSILON)
    {
      error_message = g_strdup (_("Bad convergence factor"));
      goto exit_on_error;
    }
  optimize->climbing_factor
    = xml_node_get_float (node, XML_CLIMBING_FACTOR, &code);
  if (code || optimize->climbing_factor < LDBL_EPSILON)
    {
      error_message = g_strdup (_("Bad climging factor"));
      goto exit_on_error;
    }
#if DEBUG_OPTIMIZE
  fprintf (stderr, "optimize_read: end\n");
#endif
  return 1;

exit_on_error:
#if DEBUG_OPTIMIZE
  fprintf (stderr, "optimize_read: end\n");
#endif
  return 0;
}


/* Copyright (C) 2015 Atsushi Togo */
/* All rights reserved. */

/* This file is part of phonopy. */

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

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

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

/* * Neither the name of the phonopy project nor the names of its */
/*   contributors may be used to endorse or promote products derived */
/*   from this software without specific prior written permission. */

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

#include <Python.h>
#include <stdio.h>
#include <stdlib.h>
#include <numpy/arrayobject.h>
#include <lapacke.h>
#include "phonoc_array.h"
#include "phonon4_h/fc4.h"
#include "phonon4_h/real_to_reciprocal.h"
#include "phonon4_h/frequency_shift.h"

static PyObject * py_get_fc4_normal_for_frequency_shift(PyObject *self, PyObject *args);
static PyObject * py_get_fc4_frequency_shifts(PyObject *self, PyObject *args);
static PyObject * py_real_to_reciprocal4(PyObject *self, PyObject *args);
static PyObject * py_reciprocal_to_normal4(PyObject *self, PyObject *args);
static PyObject * py_set_phonons_grid_points(PyObject *self, PyObject *args);
static PyObject * py_distribute_fc4(PyObject *self, PyObject *args);
static PyObject * py_rotate_delta_fc3s_elem(PyObject *self, PyObject *args);
static PyObject * py_set_translational_invariance_fc4(PyObject *self,
						      PyObject *args);
static PyObject * py_set_permutation_symmetry_fc4(PyObject *self,
						  PyObject *args);
static PyObject * py_get_drift_fc4(PyObject *self, PyObject *args);

static PyMethodDef functions[] = {
  {"fc4_normal_for_frequency_shift", py_get_fc4_normal_for_frequency_shift, METH_VARARGS, "Calculate fc4 normal for frequency shift"},
  {"fc4_frequency_shifts", py_get_fc4_frequency_shifts, METH_VARARGS, "Calculate fc4 frequency shift"},
  {"real_to_reciprocal4", py_real_to_reciprocal4, METH_VARARGS, "Transform fc4 of real space to reciprocal space"},
  {"reciprocal_to_normal4", py_reciprocal_to_normal4, METH_VARARGS, "Transform fc4 of reciprocal space to normal coordinate in special case for frequency shift"},
  {"phonons_grid_points", py_set_phonons_grid_points, METH_VARARGS, "Set phonons on grid points"},
  {"distribute_fc4", py_distribute_fc4, METH_VARARGS, "Distribute least fc4 to full fc4"},
  {"rotate_delta_fc3s_elem", py_rotate_delta_fc3s_elem, METH_VARARGS, "Rotate delta fc3s for a set of atomic indices"},
  {"translational_invariance_fc4", py_set_translational_invariance_fc4, METH_VARARGS, "Set translational invariance for fc4"},
  {"permutation_symmetry_fc4", py_set_permutation_symmetry_fc4, METH_VARARGS, "Set permutation symmetry for fc4"},
  {"drift_fc4", py_get_drift_fc4, METH_VARARGS, "Get drifts of fc4"},
  {NULL, NULL, 0, NULL}
};

PyMODINIT_FUNC init_phono4py(void)
{
  Py_InitModule3("_phono4py", functions, "C-extension for phono4py\n\n...\n");
  return;
}

static PyObject * py_get_fc4_normal_for_frequency_shift(PyObject *self,
							PyObject *args)
{
  PyArrayObject* fc4_normal_py;
  PyArrayObject* frequencies_py;
  PyArrayObject* eigenvectors_py;
  PyArrayObject* grid_points1_py;
  PyArrayObject* grid_address_py;
  PyArrayObject* mesh_py;
  PyArrayObject* fc4_py;
  PyArrayObject* shortest_vectors_py;
  PyArrayObject* multiplicity_py;
  PyArrayObject* masses_py;
  PyArrayObject* p2s_map_py;
  PyArrayObject* s2p_map_py;
  PyArrayObject* band_indicies_py;
  double cutoff_frequency;
  int grid_point0;

  if (!PyArg_ParseTuple(args, "OOOiOOOOOOOOOOd",
			&fc4_normal_py,
			&frequencies_py,
			&eigenvectors_py,
			&grid_point0,
			&grid_points1_py,
			&grid_address_py,
			&mesh_py,
			&fc4_py,
			&shortest_vectors_py,
			&multiplicity_py,
			&masses_py,
			&p2s_map_py,
			&s2p_map_py,
			&band_indicies_py,
			&cutoff_frequency)) {
    return NULL;
  }

  double* fc4_normal = (double*)fc4_normal_py->data;
  double* freqs = (double*)frequencies_py->data;
  /* npy_cdouble and lapack_complex_double may not be compatible. */
  /* So eigenvectors should not be used in Python side */
  lapack_complex_double* eigvecs =
    (lapack_complex_double*)eigenvectors_py->data;
  Iarray* grid_points1 = convert_to_iarray(grid_points1_py);
  const int* grid_address = (int*)grid_address_py->data;
  const int* mesh = (int*)mesh_py->data;
  double* fc4 = (double*)fc4_py->data;
  Darray* svecs = convert_to_darray(shortest_vectors_py);
  Iarray* multi = convert_to_iarray(multiplicity_py);
  const double* masses = (double*)masses_py->data;
  const int* p2s = (int*)p2s_map_py->data;
  const int* s2p = (int*)s2p_map_py->data;
  Iarray* band_indicies = convert_to_iarray(band_indicies_py);

  get_fc4_normal_for_frequency_shift(fc4_normal,
				     freqs,
				     eigvecs,
				     grid_point0,
				     grid_points1,
				     grid_address,
				     mesh,
				     fc4,
				     svecs,
				     multi,
				     masses,
				     p2s,
				     s2p,
				     band_indicies,
				     cutoff_frequency);

  free(grid_points1);
  free(svecs);
  free(multi);
  free(band_indicies);

  Py_RETURN_NONE;
}

static PyObject * py_get_fc4_frequency_shifts(PyObject *self, PyObject *args)
{
  PyArrayObject* frequency_shifts_py;
  PyArrayObject* fc4_normal_py;
  PyArrayObject* frequencies_py;
  PyArrayObject* grid_points1_py;
  PyArrayObject* temperatures_py;
  PyArrayObject* band_indicies_py;
  double unit_conversion_factor;

  if (!PyArg_ParseTuple(args, "OOOOOOd",
			&frequency_shifts_py,
			&fc4_normal_py,
			&frequencies_py,
			&grid_points1_py,
			&temperatures_py,
			&band_indicies_py,
			&unit_conversion_factor)) {
    return NULL;
  }

  double* freq_shifts = (double*)frequency_shifts_py->data;
  double* fc4_normal = (double*)fc4_normal_py->data;
  double* freqs = (double*)frequencies_py->data;
  Iarray* grid_points1 = convert_to_iarray(grid_points1_py);
  Darray* temperatures = convert_to_darray(temperatures_py);
  int* band_indicies = (int*)band_indicies_py->data;
  const int num_band0 = (int)band_indicies_py->dimensions[0];
  const int num_band = (int)frequencies_py->dimensions[1];

  get_fc4_frequency_shifts(freq_shifts,
			   fc4_normal,
			   freqs,
			   grid_points1,
			   temperatures,
			   band_indicies,
			   num_band0,
			   num_band,
			   unit_conversion_factor);

  free(grid_points1);
  free(temperatures);

  Py_RETURN_NONE;
}

static PyObject * py_real_to_reciprocal4(PyObject *self, PyObject *args)
{
  PyArrayObject* fc4_py;
  PyArrayObject* fc4_reciprocal_py;
  PyArrayObject* q_py;
  PyArrayObject* shortest_vectors;
  PyArrayObject* multiplicity;
  PyArrayObject* p2s_map;
  PyArrayObject* s2p_map;

  if (!PyArg_ParseTuple(args, "OOOOOOO",
			&fc4_reciprocal_py,
			&fc4_py,
			&q_py,
			&shortest_vectors,
			&multiplicity,
			&p2s_map,
			&s2p_map)) {
    return NULL;
  }

  double* fc4 = (double*)fc4_py->data;
  lapack_complex_double* fc4_reciprocal =
    (lapack_complex_double*)fc4_reciprocal_py->data;
  Darray* svecs = convert_to_darray(shortest_vectors);
  Iarray* multi = convert_to_iarray(multiplicity);
  const int* p2s = (int*)p2s_map->data;
  const int* s2p = (int*)s2p_map->data;
  const double* q = (double*)q_py->data;

  real_to_reciprocal4(fc4_reciprocal,
		      q,
		      fc4,
		      svecs,
		      multi,
		      p2s,
		      s2p);

  free(svecs);
  free(multi);

  Py_RETURN_NONE;
}

static PyObject * py_reciprocal_to_normal4(PyObject *self, PyObject *args)
{
  PyArrayObject* fc4_normal_py;
  PyArrayObject* fc4_reciprocal_py;
  PyArrayObject* frequencies_py;
  PyArrayObject* eigenvectors_py;
  PyArrayObject* grid_points_py;
  PyArrayObject* masses_py;
  PyArrayObject* band_indicies_py;
  double cutoff_frequency;

  if (!PyArg_ParseTuple(args, "OOOOOOOd",
			&fc4_normal_py,
			&fc4_reciprocal_py,
			&frequencies_py,
			&eigenvectors_py,
			&grid_points_py,
			&masses_py,
			&band_indicies_py,
			&cutoff_frequency)) {
    return NULL;
  }

  lapack_complex_double* fc4_normal =
    (lapack_complex_double*)fc4_normal_py->data;
  const lapack_complex_double* fc4_reciprocal =
    (lapack_complex_double*)fc4_reciprocal_py->data;
  const lapack_complex_double* eigenvectors =
    (lapack_complex_double*)eigenvectors_py->data;
  const double* frequencies = (double*)frequencies_py->data;
  const int* grid_points = (int*)grid_points_py->data;
  const double* masses = (double*)masses_py->data;
  const int* band_indices = (int*)band_indicies_py->data;
  const int num_band0 = (int)band_indicies_py->dimensions[0];
  const int num_band = (int)frequencies_py->dimensions[1];

  reciprocal_to_normal4(fc4_normal,
			fc4_reciprocal,
			frequencies + grid_points[0] * num_band,
			frequencies + grid_points[1] * num_band,
			eigenvectors + grid_points[0] * num_band * num_band,
			eigenvectors + grid_points[1] * num_band * num_band,
			masses,
			band_indices,
			num_band0,
			num_band,
			cutoff_frequency);

  Py_RETURN_NONE;
}

static PyObject * py_set_phonons_grid_points(PyObject *self, PyObject *args)
{
  PyArrayObject* frequencies;
  PyArrayObject* eigenvectors;
  PyArrayObject* phonon_done_py;
  PyArrayObject* grid_points_py;
  PyArrayObject* grid_address_py;
  PyArrayObject* mesh_py;
  PyArrayObject* shortest_vectors_fc2;
  PyArrayObject* multiplicity_fc2;
  PyArrayObject* fc2_py;
  PyArrayObject* atomic_masses_fc2;
  PyArrayObject* p2s_map_fc2;
  PyArrayObject* s2p_map_fc2;
  PyArrayObject* reciprocal_lattice;
  PyArrayObject* born_effective_charge;
  PyArrayObject* q_direction;
  PyArrayObject* dielectric_constant;
  double nac_factor, unit_conversion_factor;
  char uplo;

  if (!PyArg_ParseTuple(args, "OOOOOOOOOOOOdOOOOdc",
			&frequencies,
			&eigenvectors,
			&phonon_done_py,
			&grid_points_py,
			&grid_address_py,
			&mesh_py,
			&fc2_py,
			&shortest_vectors_fc2,
			&multiplicity_fc2,
			&atomic_masses_fc2,
			&p2s_map_fc2,
			&s2p_map_fc2,
			&unit_conversion_factor,
			&born_effective_charge,
			&dielectric_constant,
			&reciprocal_lattice,
			&q_direction,
			&nac_factor,
			&uplo)) {
    return NULL;
  }

  double* born;
  double* dielectric;
  double *q_dir;
  Darray* freqs = convert_to_darray(frequencies);
  /* npy_cdouble and lapack_complex_double may not be compatible. */
  /* So eigenvectors should not be used in Python side */
  Carray* eigvecs = convert_to_carray(eigenvectors);
  char* phonon_done = (char*)phonon_done_py->data;
  Iarray* grid_points = convert_to_iarray(grid_points_py);
  const int* grid_address = (int*)grid_address_py->data;
  const int* mesh = (int*)mesh_py->data;
  Darray* fc2 = convert_to_darray(fc2_py);
  Darray* svecs_fc2 = convert_to_darray(shortest_vectors_fc2);
  Iarray* multi_fc2 = convert_to_iarray(multiplicity_fc2);
  const double* masses_fc2 = (double*)atomic_masses_fc2->data;
  const int* p2s_fc2 = (int*)p2s_map_fc2->data;
  const int* s2p_fc2 = (int*)s2p_map_fc2->data;
  const double* rec_lat = (double*)reciprocal_lattice->data;
  if ((PyObject*)born_effective_charge == Py_None) {
    born = NULL;
  } else {
    born = (double*)born_effective_charge->data;
  }
  if ((PyObject*)dielectric_constant == Py_None) {
    dielectric = NULL;
  } else {
    dielectric = (double*)dielectric_constant->data;
  }
  if ((PyObject*)q_direction == Py_None) {
    q_dir = NULL;
  } else {
    q_dir = (double*)q_direction->data;
  }

  set_phonons_for_frequency_shift(freqs,
				  eigvecs,
				  phonon_done,
				  grid_points,
				  grid_address,
				  mesh,
				  fc2,
				  svecs_fc2,
				  multi_fc2,
				  masses_fc2,
				  p2s_fc2,
				  s2p_fc2,
				  unit_conversion_factor,
				  born,
				  dielectric,
				  rec_lat,
				  q_dir,
				  nac_factor,
				  uplo);

  free(freqs);
  free(eigvecs);
  free(grid_points);
  free(fc2);
  free(svecs_fc2);
  free(multi_fc2);

  Py_RETURN_NONE;
}

static PyObject * py_distribute_fc4(PyObject *self, PyObject *args)
{
  PyArrayObject* fc4_copy_py;
  PyArrayObject* fc4_py;
  int fourth_atom;
  PyArrayObject* rotation_cart_inv;
  PyArrayObject* atom_mapping_py;

  if (!PyArg_ParseTuple(args, "OOiOO",
			&fc4_copy_py,
			&fc4_py,
			&fourth_atom,
			&atom_mapping_py,
			&rotation_cart_inv)) {
    return NULL;
  }

  double* fc4_copy = (double*)fc4_copy_py->data;
  const double* fc4 = (double*)fc4_py->data;
  const double* rot_cart_inv = (double*)rotation_cart_inv->data;
  const int* atom_mapping = (int*)atom_mapping_py->data;
  const int num_atom = (int)atom_mapping_py->dimensions[0];

  return PyInt_FromLong((long) distribute_fc4(fc4_copy,
					      fc4,
					      fourth_atom,
					      atom_mapping,
					      num_atom,
					      rot_cart_inv));
}

static PyObject * py_rotate_delta_fc3s_elem(PyObject *self, PyObject *args)
{
  PyArrayObject* rotated_delta_fc3s_py;
  PyArrayObject* delta_fc3s_py;
  PyArrayObject* atom_mappings_of_rotations_py;
  PyArrayObject* site_symmetries_cartesian_py;
  int atom1, atom2, atom3;

  if (!PyArg_ParseTuple(args, "OOOOiii",
			&rotated_delta_fc3s_py,
			&delta_fc3s_py,
			&atom_mappings_of_rotations_py,
			&site_symmetries_cartesian_py,
			&atom1,
			&atom2,
			&atom3)) {
    return NULL;
  }

  double* rotated_delta_fc3s = (double*)rotated_delta_fc3s_py->data;
  const double* delta_fc3s = (double*)delta_fc3s_py->data;
  const int* rot_map_syms = (int*)atom_mappings_of_rotations_py->data;
  const double* site_syms_cart = (double*)site_symmetries_cartesian_py->data;
  const int num_rot = (int)site_symmetries_cartesian_py->dimensions[0];
  const int num_delta_fc3s = (int)delta_fc3s_py->dimensions[0];
  const int num_atom = (int)delta_fc3s_py->dimensions[1];

  return PyInt_FromLong((long) rotate_delta_fc3s_elem(rotated_delta_fc3s,
						      delta_fc3s,
						      rot_map_syms,
						      site_syms_cart,
						      num_rot,
						      num_delta_fc3s,
						      atom1,
						      atom2,
						      atom3,
						      num_atom));
}

static PyObject * py_set_translational_invariance_fc4(PyObject *self,
						      PyObject *args)
{
  PyArrayObject* fc4_py;
  int index;

  if (!PyArg_ParseTuple(args, "Oi",
			&fc4_py,
			&index)) {
    return NULL;
  }

  double* fc4 = (double*)fc4_py->data;
  const int num_atom = (int)fc4_py->dimensions[0];

  set_translational_invariance_fc4_per_index(fc4, num_atom, index);

  Py_RETURN_NONE;
}

static PyObject * py_set_permutation_symmetry_fc4(PyObject *self, PyObject *args)
{
  PyArrayObject* fc4_py;

  if (!PyArg_ParseTuple(args, "O",
			&fc4_py)) {
    return NULL;
  }

  double* fc4 = (double*)fc4_py->data;
  const int num_atom = (int)fc4_py->dimensions[0];

  set_permutation_symmetry_fc4(fc4, num_atom);

  Py_RETURN_NONE;
}

static PyObject * py_get_drift_fc4(PyObject *self, PyObject *args)
{
  PyArrayObject* fc4_py;

  if (!PyArg_ParseTuple(args, "O",
			&fc4_py)) {
    return NULL;
  }

  double* fc4 = (double*)fc4_py->data;
  const int num_atom = (int)fc4_py->dimensions[0];

  int i;
  double drift[4];
  PyObject* drift_py;

  get_drift_fc4(drift, fc4, num_atom);
  drift_py = PyList_New(4);

  for (i = 0; i < 4; i++) {
    PyList_SetItem(drift_py, i, PyFloat_FromDouble(drift[i]));
  }

  return drift_py;
}


/* multimin/fminimizer.c
 *
 * Copyright (C) 2002 Tuomo Keskitalo, Ivo Alxneit
 *
 * 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.
 */

#include <config.h>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_multimin.h>

gsl_multimin_fminimizer *
gsl_multimin_fminimizer_alloc (const gsl_multimin_fminimizer_type * T,
                               size_t n)
{
  int status;

  gsl_multimin_fminimizer *s =
    (gsl_multimin_fminimizer *) malloc (sizeof (gsl_multimin_fminimizer));

  if (s == 0)
    {
      GSL_ERROR_VAL ("failed to allocate space for minimizer struct",
                     GSL_ENOMEM, 0);
    }

  s->type = T;

  s->x = gsl_vector_calloc (n);

  if (s->x == 0)
    {
      free (s);
      GSL_ERROR_VAL ("failed to allocate space for x", GSL_ENOMEM, 0);
    }

  s->state = malloc (T->size);

  if (s->state == 0)
    {
      gsl_vector_free (s->x);
      free (s);
      GSL_ERROR_VAL ("failed to allocate space for minimizer state",
                     GSL_ENOMEM, 0);
    }

  status = (T->alloc) (s->state, n);

  if (status != GSL_SUCCESS)
    {
      free (s->state);
      gsl_vector_free (s->x);
      free (s);

      GSL_ERROR_VAL ("failed to initialize minimizer state", GSL_ENOMEM, 0);
    }

  return s;
}

int
gsl_multimin_fminimizer_set (gsl_multimin_fminimizer * s,
                             gsl_multimin_function * f,
                             const gsl_vector * x,
                             const gsl_vector * step_size)
{
  if (s->x->size != f->n)
    {
      GSL_ERROR ("function incompatible with solver size", GSL_EBADLEN);
    }

  if (x->size != f->n || step_size->size != f->n)
    {
      GSL_ERROR ("vector length not compatible with function", GSL_EBADLEN);
    }

  s->f = f;

  gsl_vector_memcpy (s->x,x);

  return (s->type->set) (s->state, s->f, s->x, &(s->size), step_size);
}

void
gsl_multimin_fminimizer_free (gsl_multimin_fminimizer * s)
{
  (s->type->free) (s->state);
  free (s->state);
  gsl_vector_free (s->x);
  free (s);
}

int
gsl_multimin_fminimizer_iterate (gsl_multimin_fminimizer * s)
{
  return (s->type->iterate) (s->state, s->f, s->x, &(s->size), &(s->fval));
}

const char *
gsl_multimin_fminimizer_name (const gsl_multimin_fminimizer * s)
{
  return s->type->name;
}

gsl_vector *
gsl_multimin_fminimizer_x (const gsl_multimin_fminimizer * s)
{
  return s->x;
}

double
gsl_multimin_fminimizer_minimum (const gsl_multimin_fminimizer * s)
{
  return s->fval;
}

double
gsl_multimin_fminimizer_size (const gsl_multimin_fminimizer * s)
{
  return s->size;
}


/*
** read data matrix, map of voxel addresses
**
** G.Lohmann, Jan 2013
*/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>

#include <gsl/gsl_matrix.h>
#include <gsl/gsl_vector.h>
#include <gsl/gsl_blas.h>
#include <gsl/gsl_sort.h>

#include "viaio/Vlib.h"
#include "viaio/VImage.h"
#include "viaio/mu.h"

#define SQR(x) ((x) * (x))
#define ABS(x) ((x) > 0 ? (x) : -(x))

VImage *VImagePointer(VAttrList list,int *nt)
{
  VImage tmp;
  VAttrListPosn posn;
  int i,ntimesteps,nrows,ncols,nslices;

  /* get image dimensions, read functional data */
  nslices = 0;
  for (VFirstAttr (list, & posn); VAttrExists (& posn); VNextAttr (& posn)) {
    if (VGetAttrRepn (& posn) != VImageRepn) continue;
    VGetAttrValue (& posn, NULL,VImageRepn, & tmp);
    if (VPixelRepn(tmp) != VShortRepn) continue;
    nslices++;
  }
  /* VDestroyImage(tmp); */
  if (nslices < 1) VError(" no slices");

  VImage *src = (VImage *) VCalloc(nslices,sizeof(VImage));
  i = ntimesteps = nrows = ncols = 0;
  for (VFirstAttr (list, & posn); VAttrExists (& posn); VNextAttr (& posn)) {
    if (VGetAttrRepn (& posn) != VImageRepn) continue;
    VGetAttrValue (& posn, NULL,VImageRepn, & src[i]);
    if (VPixelRepn(src[i]) != VShortRepn) continue;
    if (VImageNBands(src[i]) > ntimesteps) ntimesteps = VImageNBands(src[i]);
    if (VImageNRows(src[i])  > nrows)  nrows = VImageNRows(src[i]);
    if (VImageNColumns(src[i]) > ncols) ncols = VImageNColumns(src[i]);
    i++;
  }
  nslices = i;
  if (nslices < 1) return NULL;
  if (ntimesteps < 2) return NULL;
  *nt = ntimesteps;
  return src;
}

/* read functional data */
void VReadImagePointer(VAttrList list,VImage *src)
{
  VAttrListPosn posn;

  int i = 0;
  for (VFirstAttr (list, & posn); VAttrExists (& posn); VNextAttr (& posn)) {
    if (VGetAttrRepn (& posn) != VImageRepn) continue;
    VGetAttrValue (& posn, NULL,VImageRepn, & src[i]);
    if (VPixelRepn(src[i]) != VShortRepn) continue;
    i++;
  }
}

/* check if mask covers data. If not, set surplus voxels to zero */
long VMaskCoverage(VAttrList list,VImage mask)
{
  VImage src=NULL;
  VAttrListPosn posn;
  int b,r,c;
  long count=0;

  b = 0;
  for (VFirstAttr (list, & posn); VAttrExists (& posn); VNextAttr (& posn)) {
    if (VGetAttrRepn (& posn) != VImageRepn) continue;
    VGetAttrValue (& posn, NULL,VImageRepn, & src);
    if (VPixelRepn(src) != VShortRepn) continue;
    if (VImageNRows(src) < 3) continue;

    if (VImageNRows(src) != VImageNRows(mask))
      VError(" inconsistent nrows: %d,  mask: %d",VImageNRows(src),VImageNRows(mask));
    if (VImageNColumns(src) != VImageNColumns(mask))
      VError(" inconsistent ncols: %d,  mask: %d",VImageNColumns(src),VImageNColumns(mask));

    for (r=0; r<VImageNRows(src); r++) {
      for (c=0; c<VImageNColumns(src); c++) {
	if (b >= VImageNBands(mask)) VError("VMaskCoverage, illegal addr, band= %d",b);
	float u = VGetPixel(mask,b,r,c);
	int j = (int)VPixel(src,0,r,c,VShort);
	if (j == 0 && u > 0.3) {
	  VSetPixel(mask,b,r,c,0);
	  count++;
	}
      }
    }
    b++;
  }
  return count;
}

/* check if data matrix contains zero voxels */
void VCheckMatrix(gsl_matrix_float *X)
{
  long i,j,nvox=X->size1,nt=X->size2;
  double sum1,sum2,nx,mean,var,tiny=1.0e-4;

  long count = 0;
  nx = (double)nt;
  for (i=0; i<nvox; i++) {
    const float *arr1 = gsl_matrix_float_const_ptr(X,i,0);
    sum1 = sum2 = 0;
    for (j=0; j<nt; j++) {
      const double u = (double)arr1[j];
      sum1 += u;
      sum2 += u*u;
    }
    mean = sum1/nx;
    var = (sum2 - nx * mean * mean) / (nx - 1.0);
    if (var < tiny) count++;
  }
  if (count > 0)
    VWarning(" number of empty voxels: %ld",count);
}

VImage VoxelMap(VImage mask,size_t *nvoxels)
{
  int b,r,c;
  int nslices = VImageNBands(mask);
  int nrows = VImageNRows(mask);
  int ncols = VImageNColumns(mask);

  /* count number of non-zero voxels */
  size_t nvox = 0;
  for (b=0; b<nslices; b++) {
    for (r=0; r<nrows; r++) {
      for (c=0; c<ncols; c++) {
	float u = VGetPixel(mask,b,r,c);
	if (ABS(u) < 0.5) continue;
	nvox++;
      }
    }
  }
  (*nvoxels) = nvox;

  /* voxel addresses */
  VImage map = VCreateImage(1,4,nvox,VShortRepn);
  if (map == NULL) VError(" error allocating addr map");
  VFillImage(map,VAllBands,0);
  VImage xmap=NULL;
  VExtractAttr (VImageAttrList(mask),"map",NULL,VImageRepn,&xmap,FALSE);
  VCopyImageAttrs (mask,map);
  VSetAttr(VImageAttrList(map),"nvoxels",NULL,VLongRepn,(VLong)nvox);
  VSetAttr(VImageAttrList(map),"nslices",NULL,VLongRepn,(VLong)nslices);
  VSetAttr(VImageAttrList(map),"nrows",NULL,VLongRepn,(VLong)nrows);
  VSetAttr(VImageAttrList(map),"ncols",NULL,VLongRepn,(VLong)ncols);

  VPixel(map,0,3,0,VShort) = nslices;
  VPixel(map,0,3,1,VShort) = nrows;
  VPixel(map,0,3,2,VShort) = ncols;

  size_t i = 0;
  for (b=0; b<nslices; b++) {
    for (r=0; r<nrows; r++) {
      for (c=0; c<ncols; c++) {
	float u = VGetPixel(mask,b,r,c);
	if (ABS(u) < 0.5) continue;
	VPixel(map,0,0,i,VShort) = b;
	VPixel(map,0,1,i,VShort) = r;
	VPixel(map,0,2,i,VShort) = c;
	i++;
      }
    }
  }
  return map;
}

/* two-pass formula, correct for round-off error */
void VNormalize(float *data,int nt,VBoolean stddev)
{
  int j;
  float ave,var,sd,nx,s,u,tiny=1.0e-6;

  nx = (float)nt;
  ave = 0;
  for (j=0; j<nt; j++) ave += data[j];
  ave /= nx;

  var = u = 0;
  for (j=0; j<nt; j++) {
    s = data[j]-ave;
    u   += s;
    var += s*s;
  }
  var=(var-u*u/nx)/(nx-1);
  sd = sqrt(var);
  if (sd < tiny) {
    for (j=0; j<nt; j++) data[j] = 0;
  }
  else {
    if (stddev==FALSE) sd = 1.0;  /* only subtract mean !!!! */
    for (j=0; j<nt; j++) {
      u = data[j];
      data[j] = (u-ave)/sd;
    }
  }
}

/*  copy data to input matrix X, contains fMRI time series */
void VDataMatrix(VImage *src,int first,int len,VImage map,gsl_matrix_float *X)
{
  long i,j,k,nvox;
  int b,r,c;
  float *data=NULL;

  data = (float *) VCalloc(len,sizeof(float));

  gsl_matrix_float_set_zero (X);
  nvox = VImageNColumns(map);
  if (nvox != (long)X->size1) VError(" err, %ld %ld",(long)X->size1,nvox);
  if (len  != (long)X->size2) VError(" err, %ld %ld",(long)X->size2,len);

  for (i=0; i<nvox; i++) {

    b = VPixel(map,0,0,i,VShort);
    r = VPixel(map,0,1,i,VShort);
    c = VPixel(map,0,2,i,VShort);

    if ((first+len) > VImageNBands(src[b])) VError(" illegal len addr, %d  %d %d %d",
						   b,first,len,VImageNBands(src[b]));
    if (r >= VImageNRows(src[b])) VError(" illegal row addr");
    if (c >= VImageNColumns(src[b])) VError(" illegal column addr");

    k = 0;
    for (j=first; j<first+len; j++) {
      data[k] = (float) VGetPixel(src[b],j,r,c);
      k++;
    }
    VNormalize(data,len,TRUE);

    float *ptr = gsl_matrix_float_ptr(X,i,0);
    for (j=0; j<len; j++) *ptr++ = data[j];
  }
  VFree(data);
}


/* monte/gsl_monte_miser.h
 *
 * Copyright (C) 1996, 1997, 1998, 1999, 2000 Michael Booth
 *
 * 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.
 */

/* Author: MJB */

#ifndef __GSL_MONTE_MISER_H__
#define __GSL_MONTE_MISER_H__

#include <gsl/gsl_rng.h>
#include <gsl/gsl_monte.h>
#include <gsl/gsl_monte_plain.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 min_calls;
  size_t min_calls_per_bisection;
  double dither;
  double estimate_frac;
  double alpha;
  size_t dim;
  int estimate_style;
  int depth;
  int verbose;
  double * x;
  double * xmid;
  double * sigma_l;
  double * sigma_r;
  double * fmax_l;
  double * fmax_r;
  double * fmin_l;
  double * fmin_r;
  double * fsum_l;
  double * fsum_r;
  double * fsum2_l;
  double * fsum2_r;
  size_t * hits_l;
  size_t * hits_r;
} gsl_monte_miser_state;

int gsl_monte_miser_integrate(gsl_monte_function * f,
                              const double xl[], const double xh[],
                              size_t dim, size_t calls,
                              gsl_rng *r,
                              gsl_monte_miser_state* state,
                              double *result, double *abserr);

gsl_monte_miser_state* gsl_monte_miser_alloc(size_t dim);

int gsl_monte_miser_init(gsl_monte_miser_state* state);

void gsl_monte_miser_free(gsl_monte_miser_state* state);

__END_DECLS

#endif /* __GSL_MONTE_MISER_H__ */


/* Copyright 2013 Perttu Luukko

 * This file is part of libeemd.

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

 * libeemd 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 libeemd.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef _EEMD_H_
#define _EEMD_H_

#ifndef EEMD_DEBUG
#define EEMD_DEBUG 0
#endif

#if EEMD_DEBUG == 0
#ifndef NDEBUG
#define NDEBUG
#endif
#endif

#include <assert.h>
#include <limits.h>
#include <string.h>
#include <math.h>
#include <stdbool.h>
#include <gsl/gsl_statistics_double.h>
#include <gsl/gsl_rng.h>
#include <gsl/gsl_randist.h>
#include <gsl/gsl_vector.h>
#include <gsl/gsl_linalg.h>
#include <gsl/gsl_poly.h>

#ifdef _OPENMP
#include <omp.h>
#endif

// Possible error codes returned by functions eemd, ceemdan and
// emd_evaluate_spline
typedef enum {
	EMD_SUCCESS = 0,
	// Errors from invalid parameters
	EMD_INVALID_ENSEMBLE_SIZE = 1,
	EMD_INVALID_NOISE_STRENGTH = 2,
	EMD_NOISE_ADDED_TO_EMD = 3,
	EMD_NO_NOISE_ADDED_TO_EEMD = 4,
	EMD_NO_CONVERGENCE_POSSIBLE = 5,
	EMD_NOT_ENOUGH_POINTS_FOR_SPLINE = 6,
	EMD_INVALID_SPLINE_POINTS = 7,
	// Other errors
	EMD_GSL_ERROR = 8
} libeemd_error_code;

// Helper functions to print an error message if an error occured
void emd_report_if_error(libeemd_error_code err);
void emd_report_to_file_if_error(FILE* file, libeemd_error_code err);

// Main EEMD decomposition routine as described in:
//   Z. Wu and N. Huang,
//   Ensemble Empirical Mode Decomposition: A Noise-Assisted Data Analysis
//   Method, Advances in Adaptive Data Analysis,
//   Vol. 1, No. 1 (2009) 1–41
//
// Parameters 'input' and 'N' denote the input data and its length,
// respectively. Output from the routine is written to array 'output', which
// needs to be able to store at least N*M doubles, where M is the number of
// Intrinsic Mode Functions (IMFs) to compute. If M is set to zero, a value of
// M = emd_num_imfs(N) will be used, which corresponds to a maximal number of
// IMFs. Note that the final residual is also counted as an IMF in this
// respect, so you most likely want at least num_imfs=2. The following
// parameters are the ensemble size and the relative noise standard deviation,
// respectively. These are followed by the parameters for the stopping
// criterion. The stopping parameter can be defined by a S-number (see the
// article for details) or a fixed number of siftings. If both are specified,
// the sifting ends when either criterion is fulfilled. The final parameter is
// the seed given to the random number generator. A value of zero denotes a
// RNG-specific default value.
libeemd_error_code eemd(double const* restrict input, size_t N,
		double* restrict output, size_t M,
		unsigned int ensemble_size, double noise_strength, unsigned int
		S_number, unsigned int num_siftings, unsigned long int rng_seed);

// A complete variant of EEMD as described in:
//   M. Torres et al,
//   A Complete Ensemble Empirical Mode Decomposition with Adaptive Noise
//   IEEE Int. Conf. on Acoust., Speech and Signal Proc. ICASSP-11,
//   (2011) 4144-4147
//
// Parameters are identical to routine eemd
libeemd_error_code ceemdan(double const* restrict input, size_t N,
		double* restrict output, size_t M,
		unsigned int ensemble_size, double noise_strength, unsigned int
		S_number, unsigned int num_siftings, unsigned long int rng_seed);

// A method for finding the local minima and maxima from input data specified
// with parameters x and N. The memory for storing the coordinates of the
// extrema and their number are passed as the rest of the parameters. The
// arrays for the coordinates must be at least size N. The method also counts
// the number of zero crossings in the data, and saves the results into the
// pointer given as num_zero_crossings_ptr.
void emd_find_extrema(double const* restrict x, size_t N,
		double* restrict maxx, double* restrict maxy, size_t* num_max_ptr,
		double* restrict minx, double* restrict miny, size_t* num_min_ptr,
		size_t* num_zero_crossings_ptr);

// Return the number of IMFs that can be extracted from input data of length N,
// including the final residual.
size_t emd_num_imfs(size_t N);

// This routine evaluates a cubic spline with nodes defined by the arrays x and
// y, each of length N. The spline is evaluated using the not-a-node end point
// conditions (same as Matlab). The y values of the spline curve will be
// evaluated at integer points from 0 to x[N-1], and these y values will be
// written to the array spline_y. The endpoint x[N-1] is assumed to be an
// integer, and the x values are assumed to be in ascending order, with x[0]
// equal to 0. The workspace required is 5*N-10 doubles, except that N==2
// requires no extra memory. For N<=3 the routine falls back to polynomial
// interpolation, same as Matlab.
//
// This routine is mainly exported so that it can be tested separately to
// produce identical results to the Matlab routine 'spline'.
libeemd_error_code emd_evaluate_spline(double const* restrict x, double const* restrict y,
		size_t N, double* restrict spline_y, double* spline_workspace);

#endif // _EEMD_H_


#include <qdm.h>

#include <gsl/gsl_math.h>
#include <gsl/gsl_randist.h>
#include <hdf5.h>
#include <hdf5_hl.h>

static
void
qdm_mcmc_workspace_init(
    qdm_mcmc *mcmc
)
{
  mcmc->w.xi      = gsl_vector_calloc(mcmc->p.xi->size);
  mcmc->w.xi_acc  = gsl_vector_calloc(mcmc->p.xi->size);
  mcmc->w.xi_p    = gsl_vector_calloc(mcmc->p.xi->size);
  mcmc->w.xi_tune = gsl_vector_calloc(mcmc->p.xi->size);

  gsl_vector_memcpy(mcmc->w.xi,   mcmc->p.xi);
  gsl_vector_memcpy(mcmc->w.xi_p, mcmc->p.xi);

  gsl_vector_set_all(mcmc->w.xi_tune, mcmc->p.xi_tune_sd);

  mcmc->w.ll   = gsl_vector_calloc(mcmc->p.ll->size);
  mcmc->w.ll_p = gsl_vector_calloc(mcmc->p.ll->size);

  gsl_vector_memcpy(mcmc->w.ll,   mcmc->p.ll);
  gsl_vector_memcpy(mcmc->w.ll_p, mcmc->p.ll);

  mcmc->w.tau   = gsl_vector_calloc(mcmc->p.tau->size);
  mcmc->w.tau_p = gsl_vector_calloc(mcmc->p.tau->size);

  gsl_vector_memcpy(mcmc->w.tau,   mcmc->p.tau);
  gsl_vector_memcpy(mcmc->w.tau_p, mcmc->p.tau);

  mcmc->w.theta        = gsl_matrix_calloc(mcmc->p.theta->size1, mcmc->p.theta->size2);
  mcmc->w.theta_acc    = gsl_matrix_calloc(mcmc->p.theta->size1, mcmc->p.theta->size2);
  mcmc->w.theta_p      = gsl_matrix_calloc(mcmc->p.theta->size1, mcmc->p.theta->size2);
  mcmc->w.theta_star   = gsl_matrix_calloc(mcmc->p.theta->size1, mcmc->p.theta->size2);
  mcmc->w.theta_star_p = gsl_matrix_calloc(mcmc->p.theta->size1, mcmc->p.theta->size2);
  mcmc->w.theta_tune   = gsl_matrix_calloc(mcmc->p.theta->size1, mcmc->p.theta->size2);

  gsl_matrix_memcpy(mcmc->w.theta,      mcmc->p.theta);
  gsl_matrix_memcpy(mcmc->w.theta_star, mcmc->p.theta);

  gsl_matrix_set_all(mcmc->w.theta_tune, mcmc->p.theta_tune_sd);
}

static
void
qdm_mcmc_workspace_fini(
    qdm_mcmc *mcmc
)
{
  gsl_vector_free(mcmc->w.xi);
  gsl_vector_free(mcmc->w.xi_acc);
  gsl_vector_free(mcmc->w.xi_p);
  gsl_vector_free(mcmc->w.xi_tune);

  gsl_vector_free(mcmc->w.ll);
  gsl_vector_free(mcmc->w.ll_p);

  gsl_vector_free(mcmc->w.tau);
  gsl_vector_free(mcmc->w.tau_p);

  gsl_matrix_free(mcmc->w.theta);
  gsl_matrix_free(mcmc->w.theta_acc);
  gsl_matrix_free(mcmc->w.theta_p);
  gsl_matrix_free(mcmc->w.theta_star);
  gsl_matrix_free(mcmc->w.theta_star_p);
  gsl_matrix_free(mcmc->w.theta_tune);

  mcmc->w.xi      = NULL;
  mcmc->w.xi_acc  = NULL;
  mcmc->w.xi_p    = NULL;
  mcmc->w.xi_tune = NULL;

  mcmc->w.ll   = NULL;
  mcmc->w.ll_p = NULL;

  mcmc->w.tau   = NULL;
  mcmc->w.tau_p = NULL;

  mcmc->w.theta        = NULL;
  mcmc->w.theta_acc    = NULL;
  mcmc->w.theta_p      = NULL;
  mcmc->w.theta_star   = NULL;
  mcmc->w.theta_star_p = NULL;
  mcmc->w.theta_tune   = NULL;
}

static
void
qdm_mcmc_results_init(
    qdm_mcmc *mcmc
)
{
  mcmc->r.s = mcmc->p.iter / mcmc->p.thin;

  mcmc->r.theta      = qdm_ijk_calloc(mcmc->p.theta->size1, mcmc->p.theta->size2, mcmc->r.s);
  mcmc->r.theta_star = qdm_ijk_calloc(mcmc->p.theta->size1, mcmc->p.theta->size2, mcmc->r.s);

  mcmc->r.ll  = qdm_ijk_calloc(1, mcmc->p.ll->size, mcmc->r.s);
  mcmc->r.tau = qdm_ijk_calloc(1, mcmc->p.tau->size, mcmc->r.s);

  mcmc->r.xi = qdm_ijk_calloc(1, mcmc->p.xi->size, mcmc->r.s);
}

static
void
qdm_mcmc_results_fini(
    qdm_mcmc *mcmc
)
{
  qdm_ijk_free(mcmc->r.theta);
  qdm_ijk_free(mcmc->r.theta_star);

  qdm_ijk_free(mcmc->r.ll);
  qdm_ijk_free(mcmc->r.tau);

  qdm_ijk_free(mcmc->r.xi);

  mcmc->r.theta      = NULL;
  mcmc->r.theta_star = NULL;

  mcmc->r.ll  = NULL;
  mcmc->r.tau = NULL;

  mcmc->r.xi = NULL;
}

qdm_mcmc *
qdm_mcmc_alloc(
    qdm_mcmc_parameters p
)
{
  qdm_mcmc *mcmc = malloc(sizeof(qdm_mcmc));

  mcmc->p = p;

  qdm_mcmc_workspace_init(mcmc);
  qdm_mcmc_results_init(mcmc);

  return mcmc;
}

void
qdm_mcmc_free(
    qdm_mcmc *mcmc
)
{
  if (mcmc == NULL) {
    return;
  }

  qdm_mcmc_results_fini(mcmc);
  qdm_mcmc_workspace_fini(mcmc);

  free(mcmc);
}

int
qdm_mcmc_run(
    qdm_mcmc *mcmc
)
{
  int status = 0;

  for (size_t i = 0; i < mcmc->p.burn; i++) {
    status = qdm_mcmc_next(mcmc);
    if (status != 0) {
      goto cleanup;
    }

    if (i % mcmc->p.acc_check == 0) {
      qdm_mcmc_update_tune(mcmc);
    }
  }

  for (size_t i = 0; i < mcmc->p.iter; i++) {
    status = qdm_mcmc_next(mcmc);
    if (status != 0) {
      goto cleanup;
    }

    if (i % mcmc->p.thin == 0) {
      status = qdm_mcmc_save(mcmc, i / mcmc->p.thin);
      if (status != 0) {
        goto cleanup;
      }
    }
  }

cleanup:
  return status;
}

int
qdm_mcmc_next(
    qdm_mcmc *mcmc
)
{
  int status = 0;

  status = qdm_mcmc_update_theta(mcmc);
  if (status != 0) {
    goto cleanup;
  }

  status = qdm_mcmc_update_xi(mcmc);
  if (status != 0) {
    goto cleanup;
  }

cleanup:
  return status;
}

void
qdm_mcmc_update_tune(
    qdm_mcmc *mcmc
)
{
  for (size_t p = 0; p < mcmc->w.theta->size1; p++) {
    for (size_t m = 0; m < mcmc->w.theta->size2; m++) {
      if (gsl_matrix_get(mcmc->w.theta_acc, p, m) / (double)mcmc->p.acc_check > 0.5) {
        gsl_matrix_set(mcmc->w.theta_tune, p, m, gsl_min(gsl_matrix_get(mcmc->w.theta_tune, p, m) * 1.2, 5));
      }

      if (gsl_matrix_get(mcmc->w.theta_acc, p, m) / (double)mcmc->p.acc_check < 0.3) {
        gsl_matrix_set(mcmc->w.theta_tune, p, m, gsl_matrix_get(mcmc->w.theta_tune, p, m) * 0.8);
      }
    }
  }

  if (gsl_vector_get(mcmc->w.xi_acc, 0) / mcmc->p.acc_check > 0.5) {
    gsl_vector_set(mcmc->w.xi_tune, 0, gsl_min(gsl_vector_get(mcmc->w.xi_tune, 0) * 1.2, 5));
  }
  if (gsl_vector_get(mcmc->w.xi_acc, 0) / mcmc->p.acc_check < 0.3) {
    gsl_vector_set(mcmc->w.xi_tune, 0, gsl_vector_get(mcmc->w.xi_tune, 0) * 0.8);
  }

  if (gsl_vector_get(mcmc->w.xi_acc, 1) / mcmc->p.acc_check > 0.5) {
    gsl_vector_set(mcmc->w.xi_tune, 1, gsl_min(gsl_vector_get(mcmc->w.xi_tune, 1) * 1.2, 5));
  }
  if (gsl_vector_get(mcmc->w.xi_acc, 1) / mcmc->p.acc_check < 0.3) {
    gsl_vector_set(mcmc->w.xi_tune, 1, gsl_vector_get(mcmc->w.xi_tune, 1) * 0.8);
  }

  /* Reset the acceptance counter. */
  gsl_matrix_set_zero(mcmc->w.theta_acc);
  gsl_vector_set_zero(mcmc->w.xi_acc);
}

int
qdm_mcmc_update_theta(
    qdm_mcmc *mcmc
)
{
  int status = 0;

  /* Update the mth spline. */
  size_t p_max = mcmc->w.theta->size1;
  if (mcmc->p.truncate) {
    p_max = 1;
  }

  for (size_t p = 0; p < p_max; p++) {
    for (size_t m = 0; m < mcmc->w.theta->size2; m++) {
      /* Propose new theta star. */
      status = gsl_matrix_memcpy(mcmc->w.theta_star_p, mcmc->w.theta_star);
      if (status != 0) {
        goto cleanup;
      }

      double proposal =
        gsl_matrix_get(mcmc->w.theta_tune, p, m) *
        gsl_ran_ugaussian(mcmc->p.rng) +
        gsl_matrix_get(mcmc->w.theta_star, p, m);

      gsl_matrix_set(mcmc->w.theta_star_p, p, m, proposal);

      gsl_matrix_memcpy(mcmc->w.theta_p, mcmc->w.theta_star_p);
      qdm_theta_matrix_constrain(mcmc->w.theta_p, mcmc->p.theta_min);

      for (size_t i = 0; i < mcmc->p.y->size; i++) {
        qdm_logl_3(
            gsl_vector_ptr(mcmc->w.ll_p, i),
            gsl_vector_ptr(mcmc->w.tau_p, i),

            gsl_vector_get(mcmc->p.x, i),
            gsl_vector_get(mcmc->p.y, i),

            mcmc->p.t,
            mcmc->w.xi,

            mcmc->w.theta_p
        );
      }

      double ratio = qdm_vector_sum(mcmc->w.ll_p) - qdm_vector_sum(mcmc->w.ll);

      if (log(gsl_rng_uniform(mcmc->p.rng)) < ratio) {
        status = gsl_matrix_memcpy(mcmc->w.theta_star, mcmc->w.theta_star_p);
        if (status != 0) {
          goto cleanup;
        }

        status = gsl_matrix_memcpy(mcmc->w.theta, mcmc->w.theta_p);
        if (status != 0) {
          goto cleanup;
        }

        status = gsl_vector_memcpy(mcmc->w.ll, mcmc->w.ll_p);
        if (status != 0) {
          goto cleanup;
        }

        status = gsl_vector_memcpy(mcmc->w.tau, mcmc->w.tau_p);
        if (status != 0) {
          goto cleanup;
        }

        gsl_matrix_set(mcmc->w.theta_acc, p, m, gsl_matrix_get(mcmc->w.theta_acc, p, m) + 1);
      }
    }
  }

cleanup:
  return status;
}

int
qdm_mcmc_update_xi(
    qdm_mcmc *mcmc
)
{
  int status = 0;

  /* Update xi_low. */
  {
    double xi_low = gsl_vector_get(mcmc->w.xi, 0);
    double xi_low_p = exp(log(xi_low) + gsl_vector_get(mcmc->w.xi_tune, 0) * gsl_ran_ugaussian(mcmc->p.rng));

    gsl_vector_memcpy(mcmc->w.xi_p, mcmc->w.xi);
    gsl_vector_set(mcmc->w.xi_p, 0, xi_low_p);

    for (size_t i = 0; i < mcmc->p.y->size; i++) {
      qdm_logl_3(
          gsl_vector_ptr(mcmc->w.ll_p, i),
          gsl_vector_ptr(mcmc->w.tau_p, i),

          gsl_vector_get(mcmc->p.x, i),
          gsl_vector_get(mcmc->p.y, i),

          mcmc->p.t,
          mcmc->w.xi_p,

          mcmc->w.theta
      );
    }

    double ratio = -0.5 * (1 / mcmc->p.xi_prior_var) *  pow(log(xi_low_p) - mcmc->p.xi_prior_mean, 2) +
                    0.5 * (1 / mcmc->p.xi_prior_var) * (pow(log(xi_low  ) - mcmc->p.xi_prior_mean, 2) + qdm_vector_sum(mcmc->w.ll_p) - qdm_vector_sum(mcmc->w.ll));
    if (log(gsl_rng_uniform(mcmc->p.rng)) < ratio) {
      gsl_vector_set(mcmc->w.xi, 0, xi_low_p);

      status = gsl_vector_memcpy(mcmc->w.ll, mcmc->w.ll_p);
      if (status != 0) {
        goto cleanup;
      }

      gsl_vector_set(mcmc->w.xi_acc, 0, gsl_vector_get(mcmc->w.xi_acc, 0) + 1);
    }
  }

  /* Update xi_high. */
  {
    double xi_high = gsl_vector_get(mcmc->w.xi, 1);
    double xi_high_p = exp(log(xi_high) + gsl_vector_get(mcmc->w.xi_tune, 1) * gsl_ran_ugaussian(mcmc->p.rng));

    gsl_vector_memcpy(mcmc->w.xi_p, mcmc->w.xi);
    gsl_vector_set(mcmc->w.xi_p, 1, xi_high_p);

    for (size_t i = 0; i < mcmc->p.y->size; i++) {
      qdm_logl_3(
          gsl_vector_ptr(mcmc->w.ll_p, i),
          gsl_vector_ptr(mcmc->w.tau_p, i),

          gsl_vector_get(mcmc->p.x, i),
          gsl_vector_get(mcmc->p.y, i),

          mcmc->p.t,
          mcmc->w.xi_p,

          mcmc->w.theta
      );
    }

    double ratio = -0.5 * (1 / mcmc->p.xi_prior_var) *  pow(log(xi_high_p) - mcmc->p.xi_prior_mean, 2) +
                    0.5 * (1 / mcmc->p.xi_prior_var) * (pow(log(xi_high  ) - mcmc->p.xi_prior_mean, 2) + qdm_vector_sum(mcmc->w.ll_p) - qdm_vector_sum(mcmc->w.ll));
    if (log(gsl_rng_uniform(mcmc->p.rng)) < ratio) {
      gsl_vector_set(mcmc->w.xi, 1, xi_high_p);

      status = gsl_vector_memcpy(mcmc->w.ll, mcmc->w.ll_p);
      if (status != 0) {
        goto cleanup;
      }

      gsl_vector_set(mcmc->w.xi_acc, 1, gsl_vector_get(mcmc->w.xi_acc, 1) + 1);
    }
  }

cleanup:
  return status;
}

int
qdm_mcmc_save(
    qdm_mcmc *mcmc,
    size_t k
)
{
  int status = 0;

  {
    gsl_matrix_view m = qdm_ijk_get_ij(mcmc->r.theta, k);

    status = gsl_matrix_memcpy(&m.matrix, mcmc->w.theta);
    if (status != 0) {
      goto cleanup;
    }
  }

  {
    gsl_matrix_view m = qdm_ijk_get_ij(mcmc->r.theta_star, k);

    status = gsl_matrix_memcpy(&m.matrix, mcmc->w.theta_star);
    if (status != 0) {
      goto cleanup;
    }
  }

  {
    gsl_matrix_view m = qdm_ijk_get_ij(mcmc->r.ll, k);

    status = gsl_matrix_set_row(&m.matrix, 0, mcmc->w.ll);
    if (status != 0) {
      goto cleanup;
    }
  }

  {
    gsl_matrix_view m = qdm_ijk_get_ij(mcmc->r.tau, k);

    status = gsl_matrix_set_row(&m.matrix, 0, mcmc->w.tau);
    if (status != 0) {
      goto cleanup;
    }
  }

  {
    gsl_matrix_view m = qdm_ijk_get_ij(mcmc->r.xi, k);

    status = gsl_matrix_set_row(&m.matrix, 0, mcmc->w.xi);
    if (status != 0) {
      goto cleanup;
    }
  }

cleanup:
  return status;
}

int
qdm_mcmc_write(
    hid_t id,
    const qdm_mcmc *mcmc
)
{
  int status = 0;

  status = qdm_ijk_write(id, "theta", mcmc->r.theta);
  if (status != 0) {
    goto cleanup;
  }

  status = qdm_ijk_write(id, "theta_star", mcmc->r.theta_star);
  if (status != 0) {
    goto cleanup;
  }

  status = qdm_ijk_write(id, "ll", mcmc->r.ll);
  if (status != 0) {
    goto cleanup;
  }

  status = qdm_ijk_write(id, "tau", mcmc->r.tau);
  if (status != 0) {
    goto cleanup;
  }

  status = qdm_ijk_write(id, "xi", mcmc->r.xi);
  if (status != 0) {
    goto cleanup;
  }

cleanup:
  return status;
}

int
qdm_mcmc_read(
    hid_t id,
    qdm_mcmc **mcmc
)
{
  int status = 0;

  *mcmc = malloc(sizeof(qdm_mcmc));

  status = qdm_ijk_read(id, "theta", &(*mcmc)->r.theta);
  if (status != 0) {
    goto cleanup;
  }

  status = qdm_ijk_read(id, "theta_star", &(*mcmc)->r.theta_star);
  if (status != 0) {
    goto cleanup;
  }

  status = qdm_ijk_read(id, "ll", &(*mcmc)->r.ll);
  if (status != 0) {
    goto cleanup;
  }

  status = qdm_ijk_read(id, "tau", &(*mcmc)->r.tau);
  if (status != 0) {
    goto cleanup;
  }

  status = qdm_ijk_read(id, "xi", &(*mcmc)->r.xi);
  if (status != 0) {
    goto cleanup;
  }

  (*mcmc)->r.s = (*mcmc)->r.theta->size3;

cleanup:
  return status;
}


/* ode-initval/odeiv.c
 *
 * Copyright (C) 1996, 1997, 1998, 1999, 2000 Gerard Jungman
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/* Author:  G. Jungman
 */
#include <config.h>
#include <stdlib.h>
#include <gsl/gsl_errno.h>
#include "gsl_odeiv.h"

gsl_odeiv_step *
gsl_odeiv_step_alloc(const gsl_odeiv_step_type * T, size_t dim)
{
  gsl_odeiv_step *s = (gsl_odeiv_step *) malloc (sizeof (gsl_odeiv_step));

  if (s == 0)
    {
      GSL_ERROR_NULL ("failed to allocate space for ode struct", GSL_ENOMEM);
    };

  s->type = T;
  s->dimension = dim;

  s->state = s->type->alloc(dim);

  if (s->state == 0)
    {
      free (s);		/* exception in constructor, avoid memory leak */

      GSL_ERROR_NULL ("failed to allocate space for ode state",	GSL_ENOMEM);
    };

  return s;
}

const char *
gsl_odeiv_step_name(const gsl_odeiv_step * s)
{
  return s->type->name;
}

unsigned int
gsl_odeiv_step_order(const gsl_odeiv_step * s)
{
  return s->type->order(s->state);
}

int
gsl_odeiv_step_apply(
  gsl_odeiv_step * s,
  double t,
  double h,
  double y[],
  double yerr[],
  const double dydt_in[],
  double dydt_out[],
  const gsl_odeiv_system * dydt)
{
  return s->type->apply(s->state, s->dimension, t, h, y, yerr, dydt_in, dydt_out, dydt);
}

int
gsl_odeiv_step_reset(gsl_odeiv_step * s)
{
  return s->type->reset(s->state, s->dimension);
}

void
gsl_odeiv_step_free(gsl_odeiv_step * s)
{
  s->type->free(s->state);
  free(s);
}


/* -*- linux-c -*- */
/* triple.c

   Copyright (C) 2002-2004 John M. Fregeau

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

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

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

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

/* print the usage */
void print_usage(FILE *stream)
{
	fprintf(stream, "USAGE:\n");
	fprintf(stream, "  triple [options...]\n");
	fprintf(stream, "\n");
	fprintf(stream, "OPTIONS:\n");
	fprintf(stream, "  -m --m000 <m000/MSUN>        : set mass of star 0 of inner binary of triple [%.6g]\n", FB_M000/FB_CONST_MSUN);
	fprintf(stream, "  -n --m001 <m001/MSUN>        : set mass of star 1 of inner binary of triple [%.6g]\n", FB_M001/FB_CONST_MSUN);
	fprintf(stream, "  -o --m01 <m01/MSUN>          : set mass of outer star of triple [%.6g]\n", FB_M01/FB_CONST_MSUN);
	fprintf(stream, "  -r --r000 <r000/RSUN>        : set radius of star 0 of inner binary of triple [%.6g]\n", FB_R000/FB_CONST_RSUN);
	fprintf(stream, "  -g --r001 <r001/RSUN>        : set radius of star 1 of inner binary of triple [%.6g]\n", FB_R001/FB_CONST_RSUN);
	fprintf(stream, "  -i --r01 <r01/RSUN>          : set radius of outer star of triple [%.6g]\n", FB_R01/FB_CONST_RSUN);
	fprintf(stream, "  -a --a00 <a00/AU>            : set inner semimajor axis of triple [%.6g]\n", FB_A00/FB_CONST_AU);
	fprintf(stream, "  -Q --a0 <a0/AU>              : set outer semimajor axis of triple [%.6g]\n", FB_A0/FB_CONST_AU);
	fprintf(stream, "  -e --e00 <e00>               : set inner eccentricity of triple [%.6g]\n", FB_E00);
	fprintf(stream, "  -F --e0 <e0>                 : set outer eccentricity of triple [%.6g]\n", FB_E0);
	fprintf(stream, "  -t --tstop <tstop/t_dyn>     : set stopping time [%.6g]\n", FB_TSTOP);
	fprintf(stream, "  -D --dt <dt/t_dyn>           : set approximate output dt [%.6g]\n", FB_DT);
	fprintf(stream, "  -c --tcpustop <tcpustop/sec> : set cpu stopping time [%.6g]\n", FB_TCPUSTOP);
	fprintf(stream, "  -A --absacc <absacc>         : set integrator's absolute accuracy [%.6g]\n", FB_ABSACC);
	fprintf(stream, "  -R --relacc <relacc>         : set integrator's relative accuracy [%.6g]\n", FB_RELACC);
	fprintf(stream, "  -N --ncount <ncount>         : set number of integration steps between calls\n");
	fprintf(stream, "                                 to fb_classify() [%d]\n", FB_NCOUNT);
	fprintf(stream, "  -z --tidaltol <tidaltol>     : set tidal tolerance [%.6g]\n", FB_TIDALTOL);
	fprintf(stream, "  -x --fexp <f_exp>            : set expansion factor of merger product [%.6g]\n", FB_FEXP);
	fprintf(stream, "  -k --ks                      : turn K-S regularization on or off [%d]\n", FB_KS);
	fprintf(stream, "  -s --seed                    : set random seed [%ld]\n", FB_SEED);
	fprintf(stream, "  -d --debug                   : turn on debugging\n");
	fprintf(stream, "  -V --version                 : print version info\n");
	fprintf(stream, "  -h --help                    : display this help text\n");
}

/* calculate the units used */
int calc_units(fb_obj_t *obj[1], fb_units_t *units)
{
	double m0, m00, m01, m000, m001, a00, a0;

	m0 = obj[0]->m;
	m00 = obj[0]->obj[0]->m;
	m01 = obj[0]->obj[1]->m;
	m000 = obj[0]->obj[0]->obj[0]->m;
	m001 = obj[0]->obj[0]->obj[1]->m;

	a0 = obj[0]->a;
	a00 = obj[0]->obj[0]->a;

	/* Unit of velocity is approximate relative orbital speed of inner binary,
	   unit of length is semimajor axis of inner binary;
	   therefore, unit of time is approximately 1 inner orbital period. */
	units->v = sqrt(FB_CONST_G*(m000+m001)/a00);
	units->l = a00;
	units->t = units->l / units->v;
	units->m = units->l * fb_sqr(units->v) / FB_CONST_G;
	units->E = units->m * fb_sqr(units->v);

	return(0);
}

/* the main attraction */
int main(int argc, char *argv[])
{
	int i, j;
	unsigned long int seed;
	double m000, m001, m01, r000, r001, r01, a00, a0, e00, e0;
	double Ei, Lint[3], Li[3], t;
	fb_hier_t hier;
	fb_input_t input;
	fb_ret_t retval;
	fb_units_t units;
	char string1[FB_MAX_STRING_LENGTH], string2[FB_MAX_STRING_LENGTH];
	gsl_rng *rng;
	const gsl_rng_type *rng_type=gsl_rng_mt19937;
	const char *short_opts = "m:n:o:r:g:i:a:Q:e:F:t:D:c:A:R:N:z:x:k:s:dVh";
	const struct option long_opts[] = {
		{"m000", required_argument, NULL, 'm'},
		{"m001", required_argument, NULL, 'n'},
		{"m01", required_argument, NULL, 'o'},
		{"r000", required_argument, NULL, 'r'},
		{"r001", required_argument, NULL, 'g'},
		{"r01", required_argument, NULL, 'i'},
		{"a00", required_argument, NULL, 'a'},
		{"a0", required_argument, NULL, 'Q'},
		{"e00", required_argument, NULL, 'e'},
		{"e0", required_argument, NULL, 'F'},
		{"tstop", required_argument, NULL, 't'},
		{"dt", required_argument, NULL, 'D'},
		{"tcpustop", required_argument, NULL, 'c'},
		{"absacc", required_argument, NULL, 'A'},
		{"relacc", required_argument, NULL, 'R'},
		{"ncount", required_argument, NULL, 'N'},
		{"tidaltol", required_argument, NULL, 'z'},
		{"fexp", required_argument, NULL, 'x'},
		{"ks", required_argument, NULL, 'k'},
		{"seed", required_argument, NULL, 's'},
		{"debug", no_argument, NULL, 'd'},
		{"version", no_argument, NULL, 'V'},
		{"help", no_argument, NULL, 'h'},
		{NULL, 0, NULL, 0}
	};

	/* set parameters to default values */
	m000 = FB_M000;
	m001 = FB_M001;
	m01 = FB_M01;
	r000 = FB_R000;
	r001 = FB_R001;
	r01 = FB_R01;
	a00 = FB_A00;
	a0 = FB_A0;
	e00 = FB_E00;
	e0 = FB_E0;
	input.ks = FB_KS;
	input.tstop = FB_TSTOP;
	input.Dflag = 0;
	input.dt = FB_DT;
	input.tcpustop = FB_TCPUSTOP;
	input.absacc = FB_ABSACC;
	input.relacc = FB_RELACC;
	input.ncount = FB_NCOUNT;
	input.tidaltol = FB_TIDALTOL;
	input.fexp = FB_FEXP;
	seed = FB_SEED;
	fb_debug = FB_DEBUG;

	while ((i = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) {
		switch (i) {
		case 'm':
			m000 = atof(optarg) * FB_CONST_MSUN;
			break;
		case 'n':
			m001 = atof(optarg) * FB_CONST_MSUN;
			break;
		case 'o':
			m01 = atof(optarg) * FB_CONST_MSUN;
			break;
		case 'r':
			r000 = atof(optarg) * FB_CONST_RSUN;
			break;
		case 'g':
			r001 = atof(optarg) * FB_CONST_RSUN;
			break;
		case 'i':
			r01 = atof(optarg) * FB_CONST_RSUN;
			break;
		case 'a':
			a00 = atof(optarg) * FB_CONST_AU;
			break;
		case 'Q':
			a0 = atof(optarg) * FB_CONST_AU;
			break;
		case 'e':
			e00 = atof(optarg);
			if (e00 >= 1.0) {
				fprintf(stderr, "e00 must be less than 1\n");
				return(1);
			}
			break;
		case 'F':
			e0 = atof(optarg);
			if (e0 >= 1.0) {
				fprintf(stderr, "e0 must be less than 1\n");
				return(1);
			}
			break;
		case 't':
			input.tstop = atof(optarg);
			break;
		case 'D':
			input.Dflag = 1;
			input.dt = atof(optarg);
			break;
		case 'c':
			input.tcpustop = atof(optarg);
			break;
		case 'A':
			input.absacc = atof(optarg);
			break;
		case 'R':
			input.relacc = atof(optarg);
			break;
		case 'N':
			input.ncount = atoi(optarg);
			break;
		case 'z':
			input.tidaltol = atof(optarg);
			break;
		case 'x':
			input.fexp = atof(optarg);
			break;
		case 'k':
			input.ks = atoi(optarg);
			break;
		case 's':
			seed = atol(optarg);
			break;
		case 'd':
			fb_debug = 1;
			break;
		case 'V':
			fb_print_version(stdout);
			return(0);
		case 'h':
			fb_print_version(stdout);
			fprintf(stdout, "\n");
			print_usage(stdout);
			return(0);
		default:
			break;
		}
	}

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

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

	/* put stuff in log entry */
	snprintf(input.firstlogentry, FB_MAX_LOGENTRY_LENGTH, "  command line:");
	for (i=0; i<argc; i++) {
		snprintf(&(input.firstlogentry[strlen(input.firstlogentry)]),
			 FB_MAX_LOGENTRY_LENGTH-strlen(input.firstlogentry), " %s", argv[i]);
	}
	snprintf(&(input.firstlogentry[strlen(input.firstlogentry)]),
		 FB_MAX_LOGENTRY_LENGTH-strlen(input.firstlogentry), "\n");

	/* print out values of paramaters */
	fprintf(stderr, "PARAMETERS:\n");
	fprintf(stderr, "  ks=%d  seed=%ld\n", input.ks, seed);
	fprintf(stderr, "  a00=%.6g AU  e00=%.6g  m000=%.6g MSUN  m001=%.6g MSUN  r000=%.6g RSUN  r001=%.6g RSUN\n", \
		a00/FB_CONST_AU, e00, m000/FB_CONST_MSUN, m001/FB_CONST_MSUN, r000/FB_CONST_RSUN, r001/FB_CONST_RSUN);
	fprintf(stderr, "  a0=%.6g AU  e0=%.6g  m01=%.6g MSUN  r01=%.6g RSUN\n", \
		a0/FB_CONST_AU, e0, m01/FB_CONST_MSUN, r01/FB_CONST_RSUN);
	fprintf(stderr, "  tstop=%.6g  tcpustop=%.6g\n", \
		input.tstop, input.tcpustop);
	fprintf(stderr, "  tidaltol=%.6g  abs_acc=%.6g  rel_acc=%.6g  ncount=%d  fexp=%.6g\n\n", \
		input.tidaltol, input.absacc, input.relacc, input.ncount, input.fexp);

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

	/* create hierarchies */
	hier.narr[2] = 1;
	hier.narr[3] = 1;
	/* inner binary of triple */
	hier.hier[hier.hi[2]+0].obj[0] = &(hier.hier[hier.hi[1]+0]);
	hier.hier[hier.hi[2]+0].obj[1] = &(hier.hier[hier.hi[1]+1]);
	hier.hier[hier.hi[2]+0].t = t;
	/* outer binary of triple */
	hier.hier[hier.hi[3]+0].obj[0] = &(hier.hier[hier.hi[2]+0]);
	hier.hier[hier.hi[3]+0].obj[1] = &(hier.hier[hier.hi[1]+2]);
	hier.hier[hier.hi[3]+0].t = t;

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

	hier.hier[hier.hi[1]+0].R = r000;
	hier.hier[hier.hi[1]+1].R = r001;
	hier.hier[hier.hi[1]+2].R = r01;

	hier.hier[hier.hi[1]+0].m = m000;
	hier.hier[hier.hi[1]+1].m = m001;
	hier.hier[hier.hi[1]+2].m = m01;

	hier.hier[hier.hi[2]+0].m = m000 + m001;
	hier.hier[hier.hi[3]+0].m = m000 + m001 + m01;

	hier.hier[hier.hi[2]+0].a = a00;
	hier.hier[hier.hi[3]+0].a = a0;

	hier.hier[hier.hi[2]+0].e = e00;
	hier.hier[hier.hi[3]+0].e = e0;

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

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

	/* place triple at origin */
	for (j=0; j<3; j++) {
		hier.obj[0]->x[j] = 0.0;
		hier.obj[0]->v[j] = 0.0;
	}

	/* randomize binary orientations and downsync */
	fb_randorient(&(hier.hier[hier.hi[3]+0]), rng);
	fb_downsync(&(hier.hier[hier.hi[3]+0]), t);
	fb_randorient(&(hier.hier[hier.hi[2]+0]), rng);
	fb_downsync(&(hier.hier[hier.hi[2]+0]), t);

	fprintf(stderr, "UNITS:\n");
	fprintf(stderr, "  v=%.6g km/s  l=%.6g AU  t=t_dyn=%.6g yr\n", \
		units.v/1.0e5, units.l/FB_CONST_AU, units.t/FB_CONST_YR);
	fprintf(stderr, "  M=%.6g M_sun  E=%.6g erg\n\n", units.m/FB_CONST_MSUN, units.E);

	/* trickle down properties (not sure if this is actually needed here, but it doesn't harm anything) */
	fb_trickle(&hier, t);

	/* store the initial energy and angular momentum*/
	Ei = fb_petot(&(hier.hier[hier.hi[1]]), hier.nstar) + fb_ketot(&(hier.hier[hier.hi[1]]), hier.nstar) +
		fb_einttot(&(hier.hier[hier.hi[1]]), hier.nstar);
	fb_angmom(&(hier.hier[hier.hi[1]]), hier.nstar, Li);
	fb_angmomint(&(hier.hier[hier.hi[1]]), hier.nstar, Lint);
	for (j=0; j<3; j++) {
		Li[j] += Lint[j];
	}

	/* integrate along */
	fb_dprintf("calling fewbody()...\n");

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

	/* print information to screen */
	fprintf(stderr, "OUTCOME:\n");
	if (retval.retval == 1) {
		fprintf(stderr, "  encounter complete:  t=%.6g (%.6g yr)  %s  (%s)\n\n",
			t, t * units.t/FB_CONST_YR,
			fb_sprint_hier(hier, string1),
			fb_sprint_hier_hr(hier, string2));
	} else {
		fprintf(stderr, "  encounter NOT complete:  t=%.6g (%.6g yr)  %s  (%s)\n\n",
			t, t * units.t/FB_CONST_YR,
			fb_sprint_hier(hier, string1),
			fb_sprint_hier_hr(hier, string2));
	}

	fb_dprintf("there were %ld integration steps\n", retval.count);
	fb_dprintf("fb_classify() was called %ld times\n", retval.iclassify);

	fprintf(stderr, "FINAL:\n");
	fprintf(stderr, "  t_final=%.6g (%.6g yr)  t_cpu=%.6g s\n", \
		t, t*units.t/FB_CONST_YR, retval.tcpu);

	fprintf(stderr, "  L0=%.6g  DeltaL/L0=%.6g  DeltaL=%.6g\n", fb_mod(Li), retval.DeltaLfrac, retval.DeltaL);
	fprintf(stderr, "  E0=%.6g  DeltaE/E0=%.6g  DeltaE=%.6g\n", Ei, retval.DeltaEfrac, retval.DeltaE);
	fprintf(stderr, "  Rmin=%.6g (%.6g RSUN)  Rmin_i=%d  Rmin_j=%d\n", \
		retval.Rmin, retval.Rmin*units.l/FB_CONST_RSUN, retval.Rmin_i, retval.Rmin_j);
	fprintf(stderr, "  Nosc=%d (%s)\n", retval.Nosc, (retval.Nosc>=1?"resonance":"non-resonance"));

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

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

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


/* -*- linux-c -*- */
/* fewbody.h

   Copyright (C) 2002-2004 John M. Fregeau

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

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

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

#ifndef _FEWBODY_H
#define _FEWBODY_H 1

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

/* version information */
#define FB_VERSION "0.26"
#define FB_NICK "Oblivion"
#define FB_DATE "Mon Sep  6 09:05:47 EDT 2010"

/* dimensionless constants */
#define FB_CONST_PI 3.141592653589793238462643

/* constants, in cgs units */
#define FB_CONST_MSUN 1.989e+33
#define FB_CONST_RSUN 6.9599e+10
#define FB_CONST_C 2.99792458e+10
#define FB_CONST_G 6.67259e-8
#define FB_CONST_AU 1.496e+13
#define FB_CONST_PARSEC 3.0857e+18
#define FB_CONST_YR 3.155693e+7

/* these usually shouldn't need to be changed */
#define FB_H 1.0e-2
#define FB_SSTOP GSL_POSINF
#define FB_AMIN GSL_POSINF
#define FB_RMIN GSL_POSINF
#define FB_ROOTSOLVER_MAX_ITER 100
#define FB_ROOTSOLVER_ABS_ACC 1.0e-11
#define FB_ROOTSOLVER_REL_ACC 1.0e-11
#define FB_MAX_STRING_LENGTH 2048
#define FB_MAX_LOGENTRY_LENGTH (32 * FB_MAX_STRING_LENGTH)

/* a struct containing the units used */
typedef struct{
	double v; /* velocity */
	double l; /* length */
	double t; /* time */
	double m; /* mass */
	double E; /* energy */
} fb_units_t;

/* the fundamental object */
typedef struct fb_obj{
	int ncoll; /* total number of stars collided together in this star */
	long *id; /* numeric id array */
	char idstring[FB_MAX_STRING_LENGTH]; /* string id */
	double m; /* mass */
	double R; /* radius */
	double Eint; /* internal energy (used to check energy conservation) */
	double Lint[3]; /* internal ang mom (used to check ang mom conservation) */
	double x[3]; /* position */
	double v[3]; /* velocity */
	int n; /* total number of stars in hierarchy */
	struct fb_obj *obj[2]; /* pointers to children */
	double a; /* semimajor axis */
	double e; /* eccentricity */
	double Lhat[3]; /* angular momentum vector */
	double Ahat[3]; /* Runge-Lenz vector */
	double t; /* time at which node was upsynced */
	double mean_anom; /* mean anomaly when node was upsynced */
} fb_obj_t;

/* parameters for the K-S integrator */
typedef struct{
	int nstar; /* number of actual stars */
	int kstar; /* nstar*(nstar-1)/2, number of separations */
	double *m; /* m[nstar] */
	double *M; /* M[kstar] */
	double **amat; /* amat[nstar][kstar] */
	double **Tmat; /* Tmat[kstar][kstar] */
	double Einit; /* initial energy used in integration scheme */
} fb_ks_params_t;

/* parameters for the non-regularized integrator */
typedef struct{
	int nstar; /* number of actual stars */
	double *m; /* m[nstar] */
} fb_nonks_params_t;

/* the hierarchy data structure */
typedef struct{
	int nstarinit; /* initial number of stars (may not equal nstar if there are collisions) */
	int nstar; /* number of stars */
	int nobj; /* number of binary trees */
	int *hi; /* hierarchical index array */
	int *narr; /* narr[i] = number of hierarchical objects with i elements */
	fb_obj_t *hier; /* memory location of hierarchy information */
	fb_obj_t **obj; /* array of pointers to top nodes of binary trees */
} fb_hier_t;

/* input parameters */
typedef struct{
	int ks; /* 0=no regularization, 1=K-S regularization */
	double tstop; /* stopping time, in units of t_dyn */
	int Dflag; /* 0=don't print to stdout, 1=print to stdout */
	double dt; /* time interval between printouts will always be greater than this value */
	double tcpustop; /* cpu stopping time, in units of seconds */
	double absacc; /* absolute accuracy of the integrator */
	double relacc; /* relative accuracy of the integrator */
	int ncount; /* number of integration steps between each call to fb_classify() */
	double tidaltol; /* tidal tolerance */
	char firstlogentry[FB_MAX_LOGENTRY_LENGTH]; /* first entry to put in printout log */
	double fexp; /* expansion factor for a merger product: R = f_exp (R_1+R_2) */
} fb_input_t;

/* return parameters */
typedef struct{
	long count; /* number of integration steps */
	int retval; /* return value; 1=success, 0=failure */
	long iclassify; /* number of times classify was called */
	double tcpu; /* cpu time taken */
	double DeltaE; /* change in energy */
	double DeltaEfrac; /* change in energy, as a fraction of initial energy */
	double DeltaL; /* change in ang. mom. */
	double DeltaLfrac; /* change in ang. mom., as a fraction of initial ang. mom. */
	double Rmin; /* minimum distance of close approach during interaction */
	int Rmin_i; /* index of star i participating in minimum close approach */
	int Rmin_j; /* index of star j participating in minimum close approach */
	int Nosc; /* number of oscillations of the quantity s^2 (McMillan & Hut 1996) (Nosc=Nmin-1, so resonance if Nosc>=1) */
} fb_ret_t;

/* fewbody.c */
fb_ret_t fewbody(fb_input_t input, fb_hier_t *hier, double *t);

/* fewbody_classify.c */
int fb_classify(fb_hier_t *hier, double t, double tidaltol);
int fb_is_stable(fb_obj_t *obj);
int fb_is_stable_binary(fb_obj_t *obj);
int fb_is_stable_triple(fb_obj_t *obj);
int fb_is_stable_quad(fb_obj_t *obj);
int fb_mardling(fb_obj_t *obj, int ib, int is);

/* fewbody_coll.c */
int fb_is_collision(double r, double R1, double R2);
int fb_collide(fb_hier_t *hier, double f_exp);
void fb_merge(fb_obj_t *obj1, fb_obj_t *obj2, int nstarinit, double f_exp);

/* fewbody_hier.c */
void fb_malloc_hier(fb_hier_t *hier);
void fb_init_hier(fb_hier_t *hier);
void fb_free_hier(fb_hier_t hier);
void fb_trickle(fb_hier_t *hier, double t);
void fb_elkcirt(fb_hier_t *hier, double t);
int fb_create_indices(int *hi, int nstar);
int fb_n_hier(fb_obj_t *obj);
char *fb_sprint_hier(fb_hier_t hier, char string[FB_MAX_STRING_LENGTH]);
char *fb_sprint_hier_hr(fb_hier_t hier, char string[FB_MAX_STRING_LENGTH]);
void fb_upsync(fb_obj_t *obj, double t);
void fb_randorient(fb_obj_t *obj, gsl_rng *rng);
void fb_downsync(fb_obj_t *obj, double t);
void fb_objcpy(fb_obj_t *obj1, fb_obj_t *obj2);

/* fewbody_int.c */
void fb_malloc_ks_params(fb_ks_params_t *ks_params);
void fb_init_ks_params(fb_ks_params_t *ks_params, fb_hier_t hier);
void fb_free_ks_params(fb_ks_params_t ks_params);
void fb_malloc_nonks_params(fb_nonks_params_t *nonks_params);
void fb_init_nonks_params(fb_nonks_params_t *nonks_params, fb_hier_t hier);
void fb_free_nonks_params(fb_nonks_params_t nonks_params);

/* fewbody_io.c */
void fb_print_version(FILE *stream);
void fb_print_story(fb_obj_t *star, int nstar, double t, char *logentry);

/* fewbody_isolate.c */
int fb_collapse(fb_hier_t *hier, double t, double tidaltol);
int fb_expand(fb_hier_t *hier, double t, double tidaltol);

/* fewbody_ks.c */
double fb_ks_dot(double x[4], double y[4]);
double fb_ks_mod(double x[4]);
void fb_calc_Q(double q[4], double Q[4]);
void fb_calc_ksmat(double Q[4], double Qmat[4][4]);
void fb_calc_amat(double **a, int nstar, int kstar);
void fb_calc_Tmat(double **a, double *m, double **T, int nstar, int kstar);
int fb_ks_func(double s, const double *y, double *f, void *params);
double fb_ks_Einit(const double *y, fb_ks_params_t params);
void fb_euclidean_to_ks(fb_obj_t **star, double *y, int nstar, int kstar);
void fb_ks_to_euclidean(double *y, fb_obj_t **star, int nstar, int kstar);

/* fewbody_nonks.c */
int fb_nonks_func(double t, const double *y, double *f, void *params);
int fb_nonks_jac(double t, const double *y, double *dfdy, double *dfdt, void *params);
void fb_euclidean_to_nonks(fb_obj_t **star, double *y, int nstar);
void fb_nonks_to_euclidean(double *y, fb_obj_t **star, int nstar);

/* fewbody_scat.c */
void fb_init_scattering(fb_obj_t *obj0, fb_obj_t *obj1, double vinf, double b, double rtid);
void fb_normalize(fb_hier_t *hier, fb_units_t units);

/* fewbody_utils.c */
double *fb_malloc_vector(int n);
double **fb_malloc_matrix(int nr, int nc);
void fb_free_vector(double *v);
void fb_free_matrix(double **m);
double fb_sqr(double x);
double fb_cub(double x);
double fb_dot(double x[3], double y[3]);
double fb_mod(double x[3]);
int fb_cross(double x[3], double y[3], double z[3]);
int fb_angmom(fb_obj_t *star, int nstar, double L[3]);
void fb_angmomint(fb_obj_t *star, int nstar, double L[3]);
double fb_einttot(fb_obj_t *star, int nstar);
double fb_petot(fb_obj_t *star, int nstar);
double fb_ketot(fb_obj_t *star, int nstar);
double fb_outerpetot(fb_obj_t **obj, int nobj);
double fb_outerketot(fb_obj_t **obj, int nobj);
double fb_kepler(double e, double mean_anom);
double fb_keplerfunc(double mean_anom, void *params);
double fb_reltide(fb_obj_t *bin, fb_obj_t *single, double r);

/* fewbody_ui.c */
int fbui_new_hier(fb_hier_t *hier, int n);
int fbui_delete_hier(fb_hier_t *hier);
fb_obj_t *fbui_hierarchy_element(fb_hier_t *hier, int n, int m);
fb_obj_t *fbui_hierarchy_single(fb_hier_t *hier, int m);
fb_obj_t *fbui_hierarchy_binary(fb_hier_t *hier, int m);
fb_obj_t *fbui_hierarchy_triple(fb_hier_t *hier, int m);
fb_obj_t *fbui_hierarchy_quadruple(fb_hier_t *hier, int m);
fb_obj_t *fbui_hierarchy_quintuple(fb_hier_t *hier, int m);
fb_obj_t *fbui_hierarchy_sextuple(fb_hier_t *hier, int m);
fb_obj_t *fbui_hierarchy_septuple(fb_hier_t *hier, int m);
fb_obj_t *fbui_hierarchy_octuple(fb_hier_t *hier, int m);
fb_obj_t *fbui_hierarchy_nonuple(fb_hier_t *hier, int m);
fb_obj_t *fbui_hierarchy_decuple(fb_hier_t *hier, int m);
int fbui_make_pair(fb_obj_t *parentobj, fb_obj_t *obj1, fb_obj_t *obj2);
int fbui_initialize_single(fb_obj_t *single, long id, char idstring[FB_MAX_STRING_LENGTH]);
fb_obj_t *fbui_tree(fb_hier_t *hier, int n);
int fbui_obj_ncoll_set(fb_obj_t *obj, int ncoll);
int fbui_obj_ncoll_get(fb_obj_t *obj);
int fbui_obj_id_set(fb_obj_t *obj, int index, long id);
long fbui_obj_id_get(fb_obj_t *obj, int index);
int fbui_obj_idstring_set(fb_obj_t *obj, char idstring[FB_MAX_STRING_LENGTH]);
char *fbui_obj_idstring_get(fb_obj_t *obj);
int fbui_obj_mass_set(fb_obj_t *obj, double mass);
double fbui_obj_mass_get(fb_obj_t *obj);
int fbui_obj_radius_set(fb_obj_t *obj, double radius);
double fbui_obj_radius_get(fb_obj_t *obj);
int fbui_obj_Eint_set(fb_obj_t *obj, double Eint);
double fbui_obj_Eint_get(fb_obj_t *obj);
int fbui_obj_Lint_set(fb_obj_t *obj, double Lint[3]);
int fbui_obj_Linti_set(fb_obj_t *obj, double Lint0, double Lint1, double Lint2);
double *fbui_obj_Lint_get(fb_obj_t *obj);
int fbui_obj_x_set(fb_obj_t *obj, double x[3]);
int fbui_obj_xi_set(fb_obj_t *obj, double x0, double x1, double x2);
double *fbui_obj_x_get(fb_obj_t *obj);
int fbui_obj_v_set(fb_obj_t *obj, double v[3]);
int fbui_obj_vi_set(fb_obj_t *obj, double v0, double v1, double v2);
double *fbui_obj_v_get(fb_obj_t *obj);
int fbui_obj_n_set(fb_obj_t *obj, int n);
int fbui_obj_n_get(fb_obj_t *obj);
int fbui_obj_obj_set(fb_obj_t *obj, int i, fb_obj_t *objtopointto);
int fbui_obj_left_child_set(fb_obj_t *obj, fb_obj_t *objtopointto);
int fbui_obj_right_child_set(fb_obj_t *obj, fb_obj_t *objtopointto);
fb_obj_t *fbui_obj_obj_get(fb_obj_t *obj, int i);
fb_obj_t *fbui_obj_left_child_get(fb_obj_t *obj);
fb_obj_t *fbui_obj_right_child_get(fb_obj_t *obj);
int fbui_obj_a_set(fb_obj_t *obj, double a);
double fbui_obj_a_get(fb_obj_t *obj);
int fbui_obj_e_set(fb_obj_t *obj, double e);
double fbui_obj_e_get(fb_obj_t *obj);
int fbui_obj_Lhat_set(fb_obj_t *obj, double Lhat[3]);
int fbui_obj_Lhati_set(fb_obj_t *obj, double Lhat0, double Lhat1, double Lhat2);
double *fbui_obj_Lhat_get(fb_obj_t *obj);
int fbui_obj_Ahat_set(fb_obj_t *obj, double Ahat[3]);
int fbui_obj_Ahati_set(fb_obj_t *obj, double Ahat0, double Ahat1, double Ahat2);
double *fbui_obj_Ahat_get(fb_obj_t *obj);
int fbui_obj_t_set(fb_obj_t *obj, double t);
double fbui_obj_t_get(fb_obj_t *obj);
int fbui_obj_mean_anom_set(fb_obj_t *obj, double mean_anom);
double fbui_obj_mean_anom_get(fb_obj_t *obj);

/* macros */
/* The variadic macro syntax here conforms to the C99 standard, but for some
   reason won't compile on Mac OSX with gcc. */
/* #define fb_dprintf(...) if (fb_debug) fprintf(stderr, __VA_ARGS__) */
/* The variadic macro syntax here is the old gcc standard, and compiles on
   Mac OSX with gcc. */
#define fb_dprintf(args...) if (fb_debug) fprintf(stderr, args)
#define FB_MIN(a, b) ((a)<=(b)?(a):(b))
#define FB_MAX(a, b) ((a)>=(b)?(a):(b))
#define FB_DELTA(i, j) ((i)==(j)?1:0)
#define FB_KS_K(i, j, nstar) ((i)*(nstar)-((i)+1)*((i)+2)/2+(j))

/* there is just one global variable */
extern int fb_debug;

#endif /* fewbody.h */