/* histogram/pdf.c
 *
 * Copyright (C) 1996, 1997, 1998, 1999, 2000, 2007, 2009 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 3 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include <config.h>
#include <stdlib.h>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_histogram.h>

#include "find.c"

double
gsl_histogram_pdf_sample (const gsl_histogram_pdf * p, double r)
{
  size_t i;
  int status;

/* Wrap the exclusive top of the bin down to the inclusive bottom of
   the bin. Since this is a single point it should not affect the
   distribution. */

  if (r == 1.0)
    {
      r = 0.0;
    }

  status = find (p->n, p->sum, r, &i);

  if (status)
    {
      GSL_ERROR_VAL ("cannot find r in cumulative pdf", GSL_EDOM, 0);
    }
  else
    {
      double delta = (r - p->sum[i]) / (p->sum[i + 1] - p->sum[i]);
      double x = p->range[i] + delta * (p->range[i + 1] - p->range[i]);
      return x;
    }
}

gsl_histogram_pdf *
gsl_histogram_pdf_alloc (const size_t n)
{
  gsl_histogram_pdf *p;

  if (n == 0)
    {
      GSL_ERROR_VAL ("histogram pdf length n must be positive integer",
                        GSL_EDOM, 0);
    }

  p = (gsl_histogram_pdf *) malloc (sizeof (gsl_histogram_pdf));

  if (p == 0)
    {
      GSL_ERROR_VAL ("failed to allocate space for histogram pdf struct",
                        GSL_ENOMEM, 0);
    }

  p->range = (double *) malloc ((n + 1) * sizeof (double));

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

      GSL_ERROR_VAL ("failed to allocate space for histogram pdf ranges",
                        GSL_ENOMEM, 0);
    }

  p->sum = (double *) malloc ((n + 1) * sizeof (double));

  if (p->sum == 0)
    {
      free (p->range);
      free (p);         /* exception in constructor, avoid memory leak */

      GSL_ERROR_VAL ("failed to allocate space for histogram pdf sums",
                        GSL_ENOMEM, 0);
    }

  p->n = n;

  return p;
}

int
gsl_histogram_pdf_init (gsl_histogram_pdf * p, const gsl_histogram * h)
{
  size_t i;
  size_t n = p->n;

  if (n != h->n)
    {
      GSL_ERROR ("histogram length must match pdf length", GSL_EINVAL);
    }

  for (i = 0; i < n; i++)
    {
      if (h->bin[i] < 0)
        {
          GSL_ERROR ("histogram bins must be non-negative to compute"
                     "a probability distribution", GSL_EDOM);
        }
    }

  for (i = 0; i < n + 1; i++)
    {
      p->range[i] = h->range[i];
    }

  {
    double mean = 0, sum = 0;

    for (i = 0; i < n; i++)
      {
        mean += (h->bin[i] - mean) / ((double) (i + 1));
      }

    p->sum[0] = 0;

    for (i = 0; i < n; i++)
      {
        sum += (h->bin[i] / mean) / n;
        p->sum[i + 1] = sum;
      }
  }

  return GSL_SUCCESS;
}

void
gsl_histogram_pdf_free (gsl_histogram_pdf * p)
{
  RETURN_IF_NULL (p);
  free (p->range);
  free (p->sum);
  free (p);
}


/*
  code to do weak lensing power spectrum computations

  Matthew Becker, UofC 2011
*/

#include <gsl/gsl_spline.h>

#ifndef _WEAKLENS_
#define _WEAKLENS_

typedef struct {
  int wlNum;
  double lmin;
  double lmax;
  double tmin;
  double tmax;
} weaklensData;

extern weaklensData wlData;

typedef struct {
  int initFlag;
  int currCosmoNum;
  int currWLNum;
  double zs1;
  double zs2;
  double chis1;
  double chis2;
  double chiLim;
  double sn;
  double ell; //temp var for integrals
  gsl_spline *spline;
  gsl_interp_accel *accel;
} _lensPowerSpectra;

typedef _lensPowerSpectra *lensPowerSpectra;

typedef struct {
  int initFlag;
  int currCosmoNum;
  int currWLNum;
  lensPowerSpectra lps;
  double theta; //temp var for integrals
  gsl_spline *splineP;
  gsl_interp_accel *accelP;
  gsl_spline *splineM;
  gsl_interp_accel *accelM;
} _lensCorrFunc;

typedef _lensCorrFunc * lensCorrFunc;

//functions to compute lensing power and cross spectra
double nonlinear_powspec_for_lens(double k, double a);
double lens_power_spectrum(double ell, lensPowerSpectra lps);
lensPowerSpectra init_lens_power_spectrum(double zs1, double zs2);
void free_lens_power_spectrum(lensPowerSpectra lps);

//functions to compute lens corr func.
double lens_corr_func_minus(double theta, lensCorrFunc lcf);
double lens_corr_func_plus(double theta, lensCorrFunc lcf);
lensCorrFunc init_lens_corr_func(lensPowerSpectra lps);
void free_lens_corr_func(lensCorrFunc lcf);

#endif /* _WEAKLENS_ */


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

/* Things to check: Not sure I'm using the correct error macros - should I be
   using the ones in the GSL manual? Based the ones here on what i see in
   coupling.c. */

#include <config.h>
#include <stdlib.h>
#include <error.h>
#include <gsl/gsl_math.h>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_sf_gamma.h>
#include <gsl/gsl_sf_result.h>
#include <gsl/gsl_sf_trig.h>
#include <gsl/gsl_sf_exp.h>
#include <gsl/gsl_sf_pow_int.h>
#include <gsl/gsl_sf_wigner.h>

/* Static prototypes. */
static int istriangle (const int two_ja, const int two_jb, const int two_jc);
static int lndelta_e (const int two_j1, const int two_j2, const int two_j3,
		      gsl_sf_result * lndelta);

/* This macro returns 1 if n is even, -1 if n is odd. */
#define PHASE(n) (GSL_IS_ODD(n) ? -1.0 : 1.0)

static int
istriangle (const int two_ja, const int two_jb, const int two_jc)
     /* Returns 0 if the triangle condition is met, and the sum of twice the
        angular momenta is even. Arguments are twice the value of the angular
        momenta. */
{
  if ((two_jc <= two_ja + two_jb) && (two_jc >= abs (two_ja - two_jb))
      && (GSL_IS_EVEN (two_ja + two_jb + two_jc)))
    return 1;
  else
    return 0;
}

static int
lndelta_e (const int two_j1, const int two_j2,
           const int two_j3, gsl_sf_result * lndelta)
     /* Calculates the natural log of the delta function - Zare Eq. A-2. Note:
        values returned from gsl_sf_ln_fact_e are always positive. */
{
  gsl_sf_result a1, a2, a3, a4;
  int status;

  status = gsl_sf_lnfact_e ((two_j1 + two_j2 - two_j3) / 2, &a1);
  status += gsl_sf_lnfact_e ((two_j1 - two_j2 + two_j3) / 2, &a2);
  status += gsl_sf_lnfact_e ((-two_j1 + two_j2 + two_j3) / 2, &a3);
  status += gsl_sf_lnfact_e ((two_j1 + two_j2 + two_j3) / 2 + 1, &a4);

  if (status != GSL_SUCCESS)
    OVERFLOW_ERROR (lndelta);

  lndelta->val = 0.5 * (a1.val + a2.val + a3.val - a4.val);
  lndelta->err = 0.5 * (a1.err + a2.err + a3.err + a4.err);
  lndelta->err += 2.0 * GSL_DBL_EPSILON * (a1.val + a2.val + a3.val + a4.val);

  return GSL_SUCCESS;
}

int
gsl_sf_wigner_3j_e (const int two_j1, const int two_j2, const int two_j3,
                    const int two_m1, const int two_m2, const int two_m3,
                    gsl_sf_result * result)
     /* Returns the value of the wigner 3j symbol - Zare Eq. A-1. Note that in
        Zare's book, this equation contains typos. */
{
  int v, vmin, vmax, t1, t2, t3, t4, t5, status;
  gsl_sf_result a, a1, a2, a3, a4, a5, a6, a7, a8;

  if ((two_j1 < 0) || (two_j2 < 0) || (two_j3 < 0) ||
      (abs (two_m1) > two_j1) || (abs (two_m2) > two_j2) ||
      (abs (two_m3) > two_j3) || GSL_IS_ODD (two_j1 + two_m1) ||
      GSL_IS_ODD (two_j2 + two_m2) || GSL_IS_ODD (two_j3 + two_m3))
    DOMAIN_ERROR (result);

  result->val = 0.0;
  result->err = 0.0;

  if ((!istriangle (two_j1, two_j2, two_j3)) ||
      (two_m1 + two_m2 + two_m3) != 0)
    return GSL_SUCCESS;

  t1 = (two_j1 + two_j2 - two_j3) / 2;
  t2 = (two_j1 - two_m1) / 2;
  t3 = (two_j2 + two_m2) / 2;
  t4 = (two_j3 - two_j2 + two_m1) / 2;
  t5 = (two_j3 - two_j1 - two_m2) / 2;

  vmin = (t4 < t5) ? -t4 : -t5;
  vmin = (vmin < 0) ? 0 : vmin;

  vmax = t1;
  vmax = (t2 < vmax) ? t2 : vmax;
  vmax = (t3 < vmax) ? t3 : vmax;

  if (vmin > vmax)
    return GSL_SUCCESS;

  status = gsl_sf_lnfact_e ((two_j1 + two_m1) / 2, &a1);
  status += gsl_sf_lnfact_e (t2, &a2);
  status += gsl_sf_lnfact_e (t3, &a3);
  status += gsl_sf_lnfact_e ((two_j2 - two_m2) / 2, &a4);
  status += gsl_sf_lnfact_e ((two_j3 + two_m3) / 2, &a5);
  status += gsl_sf_lnfact_e ((two_j3 - two_m3) / 2, &a6);
  status += lndelta_e (two_j1, two_j2, two_j3, &a7);

  if (status != GSL_SUCCESS)
    OVERFLOW_ERROR (result);

  a.val =
    0.5 * (a1.val + a2.val + a3.val + a4.val + a5.val + a6.val) + a7.val;
  a.err =
    0.5 * (a1.err + a2.err + a3.err + a4.err + a5.err + a6.err) + a7.err;
  a.err += 2.0 * GSL_DBL_EPSILON * a.val;

  for (v = vmin; v <= vmax; v++)
    {
      status += gsl_sf_lnfact_e (v, &a1);
      status += gsl_sf_lnfact_e (t1 - v, &a2);
      status += gsl_sf_lnfact_e (t2 - v, &a3);
      status += gsl_sf_lnfact_e (t3 - v, &a4);
      status += gsl_sf_lnfact_e (t4 + v, &a5);
      status += gsl_sf_lnfact_e (t5 + v, &a6);
      if (status != GSL_SUCCESS)
        OVERFLOW_ERROR (result);

      a7.val = a.val - (a1.val + a2.val + a3.val + a4.val + a5.val + a6.val);
      a7.err = (a.err + a1.err + a2.err + a3.err + a4.err + a5.err + a6.err);
      a7.err += 2.0 * GSL_DBL_EPSILON *
        (a.val + a1.val + a2.val + a3.val + a4.val + a5.val + a6.val);

      status += gsl_sf_exp_err_e (a7.val, a7.err, &a8);
      if (status != GSL_SUCCESS)
        OVERFLOW_ERROR (result);

      result->val += PHASE (v) * a8.val;
      result->err += 2.0 * GSL_DBL_EPSILON * fabs (result->val);
    }

  result->val *= PHASE ((two_j1 - two_j2 - two_m3) / 2);
  result->err += 2.0 * GSL_DBL_EPSILON * fabs (result->val);

  return GSL_SUCCESS;
}

int
gsl_sf_wigner_6j_e (const int two_j1, const int two_j2, const int two_j3,
                    const int two_j4, const int two_j5, const int two_j6,
                    gsl_sf_result * result)
     /* Returns the value of the 6j symbol - Zare Eq. A-4 */
{
  int v, vmin, vmax, t1, t2, t3, t4, t5, t6, t7, status;
  gsl_sf_result a, a1, a2, a3, a4;

  if ((two_j1 < 0) || (two_j2 < 0) || (two_j3 < 0) ||
      (two_j4 < 0) || (two_j5 < 0) || (two_j6 < 0))
    DOMAIN_ERROR (result);

  result->val = 0.0;
  result->err = 0.0;

  if (!istriangle (two_j1, two_j2, two_j3) ||
      !istriangle (two_j1, two_j5, two_j6) ||
      !istriangle (two_j4, two_j2, two_j6) ||
      !istriangle (two_j4, two_j5, two_j3))
    return GSL_SUCCESS;

  t1 = (two_j1 + two_j2 + two_j3) / 2;
  t2 = (two_j1 + two_j5 + two_j6) / 2;
  t3 = (two_j4 + two_j2 + two_j6) / 2;
  t4 = (two_j4 + two_j5 + two_j3) / 2;
  t5 = (two_j1 + two_j2 + two_j4 + two_j5) / 2;
  t6 = (two_j2 + two_j3 + two_j5 + two_j6) / 2;
  t7 = (two_j3 + two_j1 + two_j6 + two_j4) / 2;

  vmin = t1;
  vmin = (t2 > vmin) ? t2 : vmin;
  vmin = (t3 > vmin) ? t3 : vmin;
  vmin = (t4 > vmin) ? t4 : vmin;

  vmax = t5;
  vmax = (t6 < vmax) ? t6 : vmax;
  vmax = (t7 < vmax) ? t7 : vmax;

  if (vmin > vmax)
    return GSL_SUCCESS;

  status = lndelta_e (two_j1, two_j2, two_j3, &a1);
  status += lndelta_e (two_j1, two_j5, two_j6, &a2);
  status += lndelta_e (two_j4, two_j2, two_j6, &a3);
  status += lndelta_e (two_j4, two_j5, two_j3, &a4);

  if (status != GSL_SUCCESS)
    OVERFLOW_ERROR (result);

  a.val = a1.val + a2.val + a3.val + a4.val;
  a.err = a1.err + a2.err + a3.err + a4.err;
  a.err += 2.0 * GSL_DBL_EPSILON * a.val;

  for (v = vmin; v <= vmax; v++)
    {
      gsl_sf_result b1, b2, b3, b4, b5, b6, b7, b8, b9, b10;
      status += gsl_sf_lnfact_e (v + 1, &b1);
      status += gsl_sf_lnfact_e (v - t1, &b2);
      status += gsl_sf_lnfact_e (v - t2, &b3);
      status += gsl_sf_lnfact_e (v - t3, &b4);
      status += gsl_sf_lnfact_e (v - t4, &b5);
      status += gsl_sf_lnfact_e (t5 - v, &b6);
      status += gsl_sf_lnfact_e (t6 - v, &b7);
      status += gsl_sf_lnfact_e (t7 - v, &b8);
      if (status != GSL_SUCCESS)
        OVERFLOW_ERROR (result);

      b9.val = a.val + b1.val - b2.val - b3.val - b4.val - b5.val - b6.val -
        b7.val - b8.val;
      b9.err = a.err + b1.err + b2.err + b3.err + b4.err + b5.err + b6.err +
        b7.err + b8.err;
      b9.err += 2.0 * GSL_DBL_EPSILON * (a.val + b1.val + b2.val + b3.val +
                                         b4.val + b5.val + b6.val + b7.val +
                                         b8.val);

      status += gsl_sf_exp_err_e (b9.val, b9.err, &b10);
      if (status != GSL_SUCCESS)
        OVERFLOW_ERROR (result);

      result->val += b10.val * PHASE (v);
      result->err += b10.err;
      result->err += 2.0 * GSL_DBL_EPSILON * fabs (result->val);
    }

  return GSL_SUCCESS;
}

int
gsl_sf_wigner_9j_e (const int two_j1, const int two_j2, const int two_j3,
                    const int two_j4, const int two_j5, const int two_j6,
                    const int two_j7, const int two_j8, const int two_j9,
                    gsl_sf_result * result)
    /* Returns the value of 9j symbol - Zare Eq. 4.24 */
{
  int two_k, two_kmin, two_kmax, status = GSL_SUCCESS;

  if ((two_j1 < 0) || (two_j2 < 0) || (two_j3 < 0) ||
      (two_j4 < 0) || (two_j5 < 0) || (two_j6 < 0) ||
      (two_j7 < 0) || (two_j8 < 0) || (two_j9 < 0))
    DOMAIN_ERROR (result);

  result->val = 0.0;
  result->err = 0.0;

  if (!istriangle (two_j1, two_j2, two_j3) ||
      !istriangle (two_j4, two_j5, two_j6) ||
      !istriangle (two_j7, two_j8, two_j9) ||
      !istriangle (two_j1, two_j4, two_j7) ||
      !istriangle (two_j2, two_j5, two_j8) ||
      !istriangle (two_j3, two_j6, two_j9))
    return GSL_SUCCESS;

  two_kmin = abs (two_j1 - two_j9);
  two_kmin =
    (abs (two_j8 - two_j4) < two_kmin) ? abs (two_j8 - two_j4) : two_kmin;
  two_kmin =
    (abs (two_j6 - two_j2) < two_kmin) ? abs (two_j6 - two_j2) : two_kmin;

  two_kmax = (two_j1 + two_j9);
  two_kmax = (two_j8 + two_j4 > two_kmax) ? two_j8 + two_j4 : two_kmax;
  two_kmax = (two_j6 + two_j2 > two_kmax) ? two_j6 + two_j2 : two_kmax;

  if (two_kmax < two_kmin)
    {
      result->val = 0.0;
      result->err = 0.0;
      return GSL_SUCCESS;
    }

  for (two_k = two_kmin; two_k <= two_kmax; two_k += 2)
    {
      gsl_sf_result a1, a2, a3;
      status += gsl_sf_wigner_6j_e (two_j1, two_j4, two_j7,
                                    two_j8, two_j9, two_k, &a1);
      status +=
        gsl_sf_wigner_6j_e (two_j2, two_j5, two_j8,
                            two_j4, two_k, two_j6, &a2);
      status +=
        gsl_sf_wigner_6j_e (two_j3, two_j6, two_j9,
                            two_k, two_j1, two_j2, &a3);
      if (status != GSL_SUCCESS)
        OVERFLOW_ERROR (result);

      result->val += (two_k + 1.0) * PHASE (two_k) * a1.val * a2.val * a3.val;

      result->err += result->val * (fabs (a1.err / a1.val) +
                                    fabs (a2.err / a2.val) +
                                    fabs (a3.err / a3.val));
      result->err += 2.0 * GSL_DBL_EPSILON * fabs (result->val);
    }
  return GSL_SUCCESS;
}

int
gsl_sf_wigner_drot_e (const int two_j, const int two_m1, const int two_m2,
                      const double theta, gsl_sf_result * result)
     /* Returns the value of the reduced rotation matrix - Zare Eq. 3.57. (which
        contains typos).  Angle theta is in radians. */
{
  int v, vmin, vmax, t1, t2, t3, t4, tv, status = GSL_SUCCESS;
  gsl_sf_result a, a1, a2, a3, a4, st, ct;

  if ((two_j) < 0 || abs (two_m1) > two_j || abs (two_m2) > two_j ||
      (GSL_IS_ODD (two_j + two_m1)) || (GSL_IS_ODD (two_j + two_m2)))
    DOMAIN_ERROR (result);

  result->val = 0.0;
  result->err = 0.0;

  t1 = (two_j + two_m1) / 2;
  t2 = (two_j - two_m2) / 2;
  t3 = (two_m2 - two_m1) / 2;
  t4 = (2 * two_j + two_m1 - two_m2) / 2;

  vmin = (-t3 < 0) ? 0 : -t3;
  vmax = (t2 < t1) ? t2 : t1;

  if (vmin > vmax)
    return GSL_SUCCESS;

  status += gsl_sf_cos_e (theta * 0.5, &ct);
  status += gsl_sf_sin_e (theta * 0.5, &st);
  if (status != GSL_SUCCESS)
    OVERFLOW_ERROR (result);

  status += gsl_sf_lnfact_e (t1, &a1);
  status += gsl_sf_lnfact_e (t2, &a2);
  status += gsl_sf_lnfact_e ((two_j - two_m1) / 2, &a3);
  status += gsl_sf_lnfact_e ((two_j + two_m2) / 2, &a4);
  if (status != GSL_SUCCESS)
    OVERFLOW_ERROR (result);

  a.val = 0.5 * (a1.val + a2.val + a3.val + a4.val);
  a.err = 0.5 * (a1.err + a2.err + a3.err + a4.err);
  a.err += 2.0 * GSL_DBL_EPSILON * a.val;

  for (v = vmin; v <= vmax; v++)
    {
      gsl_sf_result b1, b2, b3, b4, b5, b6, b7, b8;
      int i1, i2;

      status += gsl_sf_lnfact_e (t1 - v, &b1);
      status += gsl_sf_lnfact_e (t2 - v, &b2);
      status += gsl_sf_lnfact_e (t3 + v, &b3);
      status += gsl_sf_lnfact_e (v, &b4);
      if (status != GSL_SUCCESS)
        OVERFLOW_ERROR (result);

      b5.val = a.val - b1.val - b2.val - b3.val - b4.val;
      b5.err = a.err + b1.err + b2.err + b3.err + b4.err;
      b5.err +=
        2.0 * GSL_DBL_EPSILON * (a.val + b1.val + b2.val + b3.val + b4.val);

      status += gsl_sf_exp_err_e (b5.val, b5.err, &b6);
      if (status != GSL_SUCCESS)
        OVERFLOW_ERROR (result);

      tv = 2 * v;
      i1 = t4 - tv;
      i2 = t3 + tv;

      status += gsl_sf_pow_int_e (ct.val, i1, &b7);
      status += gsl_sf_pow_int_e (st.val, i2, &b8);
      if (status != GSL_SUCCESS)
        OVERFLOW_ERROR (result);

      /* Bolt on error in pow_int for the error in the input values */
      b7.err += i1 * fabs (ct.err);
      b8.err += i2 * fabs (st.err);

      result->val += PHASE (v) * b6.val * b7.val * b8.val;

      result->err += fabs (result->val) *
        (fabs (b6.err / b6.val) + fabs (b7.err / b7.val) +
         fabs (b8.err / b8.val));
      result->err += 2.0 * GSL_DBL_EPSILON * fabs (result->val);
    }

  return GSL_SUCCESS;
}

#include "eval.h"

double
gsl_sf_wigner_3j (const int two_j1, const int two_j2, const int two_j3,
                  const int two_m1, const int two_m2, const int two_m3)
{
  EVAL_RESULT (gsl_sf_wigner_3j_e (two_j1, two_j2, two_j3,
                                   two_m1, two_m2, two_m3, &result));
}

double
gsl_sf_wigner_6j (const int two_j1, const int two_j2, const int two_j3,
                  const int two_j4, const int two_j5, const int two_j6)
{
  EVAL_RESULT (gsl_sf_wigner_6j_e (two_j1, two_j2, two_j3,
                                   two_j4, two_j5, two_j6, &result));
}

double
gsl_sf_wigner_9j (const int two_j1, const int two_j2, const int two_j3,
                  const int two_j4, const int two_j5, const int two_j6,
                  const int two_j7, const int two_j8, const int two_j9)
{
  EVAL_RESULT (gsl_sf_wigner_9j_e (two_j1, two_j2, two_j3,
                                   two_j4, two_j5, two_j6,
                                   two_j7, two_j8, two_j9, &result));
}

double
gsl_sf_wigner_drot (const int two_j, const int two_m1, const int two_m2,
                    const double theta)
{
  EVAL_RESULT (gsl_sf_wigner_drot_e (two_j, two_m1, two_m2, theta, &result));
}

#undef PHASE


/*
    This code is written by <albanese@fbk.it>.
    (C) 2010 mlpy Developers.

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

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

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <stdlib.h>
#include <math.h>
#include <float.h>
#include <gsl/gsl_rng.h>
#include <gsl/gsl_randist.h>

#define MIN( A , B ) ((A) < (B) ? (A) : (B))

#define INIT_STD 0
#define INIT_PLUSPLUS 1

void init_std(double *data,      /* data points (nn points x pp dimensions) */
	      double *means,     /* means (kk clusters x pp dimensions) */
	      int nn,            /* number od data points */
	      int pp,            /* number of dimensions */
	      int kk,            /* number of clusters */
	      unsigned long seed /* random seed for init */
	      )
{
  int n, p, k;
  int *ridx;
  const gsl_rng_type * T;
  gsl_rng * r;

  T = gsl_rng_default;
  r = gsl_rng_alloc (T);
  gsl_rng_set (r, seed);

  ridx = (int *) malloc (nn * sizeof(int));

  for (n=0; n<nn; n++)
    ridx[n] = n;

  gsl_ran_shuffle (r, ridx, nn, sizeof (int));

  for (k=0; k<kk; k++)
    for (p=0; p<pp; p++)
      means[p + (k * pp)] = data[p + (ridx[k] * pp)];

  free(ridx);
}

/* for init_plus */
void
dist_min(double *a, double *b, int nn)
{
  int n;

  for (n=0; n<nn; n++)
    a[n] = MIN (a[n], b[n]);
}

/* for init_plus */
int
idx_max(double *a, int nn)
{
  int n, idx = 0;
  double max = -DBL_MAX;

  for (n=0; n<nn; n++)
    if (a[n] > max)
      {
	max = a[n];
	idx = n;
      }

  return idx;
}

void
init_plus(double *data,      /* data points (nn points x pp dimensions) */
	  double *means,     /* means (kk clusters x pp dimensions) */
	  int nn,            /* number od data points */
	  int pp,            /* number of dimensions */
	  int kk,            /* number of clusters */
	  unsigned long seed /* random seed for init */
	  )
{
  int n, p, k;
  double *dist, *distk;
  int sidx;

  const gsl_rng_type *T;
  gsl_rng *r;

  T = gsl_rng_default;
  r = gsl_rng_alloc (T);
  gsl_rng_set (r, seed);

  dist = (double *) malloc (nn * sizeof(double));
  distk = (double *) malloc (nn * sizeof(double));

  /* first mean (randomly selected) */
  sidx = (int) gsl_rng_uniform_int (r, nn);
  gsl_rng_free(r);
  for (p=0; p<pp; p++)
    means[p] = data[p + (sidx * pp)];

  /* initialize dist */
  for (n=0; n<nn; n++) /* for each data point */
    dist[n] = DBL_MAX;

  for (k=0; k<kk-1; k++)
    {
      /* for each data point x compute distance from mean k */
      for (n=0; n<nn; n++)
	{
	  distk[n] = 0.0;
	  for (p=0; p<pp; p++)
	    distk[n] += pow(data[p + (n * pp)] - means[p + (k * pp)], 2);
	}

      /* for each data point x, compute the distance between x and */
      /* the nearest center that has already been chosen */
      dist_min (dist, distk, nn);

      /* add one new data point as a new center, using */
      sidx = idx_max (dist, nn);

      for (p=0; p<pp; p++)
	means[p + ((k+1) * pp)] = data[p + (sidx * pp)];
    }

  free(dist);
  free(distk);
}

/* assignment step */
int
a_step(double *data,  /* data points (nn x pp) */
       double *means, /* means (kk x pp) */
       int *cls,      /* cluster assignement for each data point (nn) */
       int *nelems,   /* number of elements of each cluster (kk) */
       int nn,        /* number od data points */
       int pp,        /* number of dimensions */
       int kk         /* number of clusters */
       )
{
  int n, p, k, kn = 0;
  double dist, dmin;
  int changed = 0;

  for (k=0; k<kk; k++)
    nelems[k] = 0;

  for (n=0; n<nn; n++) /* for each data point */
    {
      dmin = DBL_MAX;
      for (k=0; k<kk; k++) /* for each cluster */
	{
	  /* compute distance */
	  dist = 0.0;
	  for (p=0; p<pp; p++)
	    dist += pow(data[p + (n * pp)] - means[p + (k * pp)], 2);

	  /* remember the cluster k if dist < dmin */
	  if (dist < dmin)
	    {
	      dmin = dist;
	      kn = k;
	    }
	}

      /* if the cluster assignement change */
      if (kn != cls[n])
	changed++;

      /* update clusters and number of elements of each cluster */
      cls[n] = kn;
      nelems[kn]++;
    }

  return changed;
}

/* update step */
int
u_step(double *data,  /* data points (nn x pp) */
       double *means, /* means (kk x pp) */
       int *cls,      /* cluster assignement for each data point (nn) */
       int *nelems,   /* number of elements of each cluster (kk) */
       int nn,        /* number od data points */
       int pp,        /* number of dimensions */
       int kk         /* number of clusters */
       )
{
  int n, p, k;

  /* reset means */
  for (k=0; k<kk; k++)
    for (p=0; p<pp; p++)
      means[p + (k * pp)] = 0.0;

  for (n=0; n<nn; n++) /* for each data point */
    for (p=0; p<pp; p++) /* for each dimension */
      means[p + (cls[n] * pp)] += data[p + (n * pp)];

  for (k=0; k<kk; k++)
    if (nelems[k] > 0)
      for (p=0; p<pp; p++)
	means[p + (k * pp)] /= nelems[k];

  return 1;
}

int
km(double *data,  /* data points (nn x pp) */
   double *means, /* initialized means (kk x pp) */
   int *cls,      /* cluster assignement for each data point (nn) */
   int nn,        /* number od data points */
   int pp,        /* number of dimensions */
   int kk         /* number of clusters */
   )
{
  int n;
  int ret, steps = 0;
  int changed = -1;
  int *nelems; /* number of elements of each cluster (kk) */

  nelems = (int *) malloc (kk * sizeof(int));

  /* init cls */
  for (n=0; n<nn; n++)
    cls[n] = -1.0;

  /* k-means algorithm  */
  while (changed != 0)
    {
      changed = a_step (data, means, cls, nelems, nn, pp, kk);
      ret = u_step (data, means, cls, nelems, nn, pp, kk);
      steps++;
    }

  free(nelems);

  return steps;
}


//CELL operation: does output side of Jordan RNN layer.
//The inputs here are from the IN stage, so reduced to N driving input time-series in X.

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <cblas.h>

#ifdef __cplusplus
namespace codee {
extern "C" {
#endif

int jordan_s (float *Y, const float *X, const float *U, float *Y1, const float *W, const float *B, const size_t N, const size_t T, const char iscolmajor, const size_t dim);
int jordan_d (double *Y, const double *X, const double *U, double *Y1, const double *W, const double *B, const size_t N, const size_t T, const char iscolmajor, const size_t dim);

int jordan_s (float *Y, const float *X, const float *U, float *Y1, const float *W, const float *B, const size_t N, const size_t T, const char iscolmajor, const size_t dim)
{
    const float o = 1.0f;

    float *H;
    if (!(H=(float *)malloc(N*sizeof(float)))) { fprintf(stderr,"error in jordan_s: problem with malloc. "); perror("malloc"); return 1; }

    if (N==1u)
    {
        H[0] = 1.0f / (1.0f+expf(-X[0]-U[0]*Y1[0]));
        Y[0] = 1.0f / (1.0f+expf(-B[0]-W[0]*H[0]));
        for (size_t t=1; t<T; ++t)
        {
            H[0] = 1.0f / (1.0f+expf(-X[t]-U[0]*Y[t-1]));
            Y[t] = 1.0f / (1.0f+expf(-B[0]-W[0]*H[0]));
        }
    }
    else
    {
        if (dim==0u)
        {
            if (iscolmajor)
            {
                for (size_t n=0u; n<N; ++n) { cblas_scopy((int)T,&B[n],0,&Y[n],(int)N); }
                cblas_scopy((int)N,&X[0],1,H,1);
                cblas_sgemv(CblasColMajor,CblasNoTrans,(int)N,(int)N,o,U,(int)N,Y1,1,o,H,1);
                for (size_t n=0u; n<N; ++n) { H[n] = 1.0f/(1.0f+expf(-H[n])); }
                cblas_sgemv(CblasColMajor,CblasNoTrans,(int)N,(int)N,o,W,(int)N,H,1,o,&Y[0],1);
                for (size_t n=0u; n<N; ++n) { Y[n] = 1.0f/(1.0f+expf(-Y[n])); }
                for (size_t t=1; t<T; ++t)
                {
                    cblas_scopy((int)N,&X[t*N],1,H,1);
                    cblas_sgemv(CblasColMajor,CblasNoTrans,(int)N,(int)N,o,U,(int)N,&Y[(t-1)*N],1,o,H,1);
                    for (size_t n=0u; n<N; ++n) { H[n] = 1.0f/(1.0f+expf(-H[n])); }
                    cblas_sgemv(CblasColMajor,CblasNoTrans,(int)N,(int)N,o,W,(int)N,H,1,o,&Y[t*N],1);
                    for (size_t n=0u; n<N; ++n) { Y[t*N+n] = 1.0f/(1.0f+expf(-Y[t*N+n])); }
                }
            }
            else
            {
                for (size_t n=0u; n<N; ++n) { cblas_scopy((int)T,&B[n],0,&Y[n*T],1); }
                cblas_scopy((int)N,&X[0],(int)T,H,1);
                cblas_sgemv(CblasRowMajor,CblasNoTrans,(int)N,(int)N,o,U,(int)N,Y1,1,o,H,1);
                for (size_t n=0u; n<N; ++n) { H[n] = 1.0f/(1.0f+expf(-H[n])); }
                cblas_sgemv(CblasRowMajor,CblasNoTrans,(int)N,(int)N,o,W,(int)N,H,1,o,&Y[0],(int)T);
                for (size_t n=0u; n<N; ++n) { Y[n*T] = 1.0f/(1.0f+expf(-Y[n*T])); }
                for (size_t t=1; t<T; ++t)
                {
                    cblas_scopy((int)N,&X[t],(int)T,H,1);
                    cblas_sgemv(CblasRowMajor,CblasNoTrans,(int)N,(int)N,o,U,(int)N,&Y[t-1],(int)T,o,H,1);
                    for (size_t n=0u; n<N; ++n) { H[n] = 1.0f/(1.0f+expf(-H[n])); }
                    cblas_sgemv(CblasRowMajor,CblasNoTrans,(int)N,(int)N,o,W,(int)N,H,1,o,&Y[t],(int)T);
                    for (size_t n=0u; n<N; ++n) { Y[t+n*T] = 1.0f/(1.0f+expf(-Y[t+n*T])); }
                }
            }
        }
        else if (dim==1u)
        {
            if (iscolmajor)
            {
                for (size_t n=0u; n<N; ++n) { cblas_scopy((int)T,&B[n],0,&Y[n*T],1); }
                cblas_scopy((int)N,&X[0],(int)T,H,1);
                cblas_sgemv(CblasColMajor,CblasTrans,(int)N,(int)N,o,U,(int)N,Y1,1,o,H,1);
                for (size_t n=0u; n<N; ++n) { H[n] = 1.0f/(1.0f+expf(-H[n])); }
                cblas_sgemv(CblasColMajor,CblasTrans,(int)N,(int)N,o,W,(int)N,H,1,o,&Y[0],(int)T);
                for (size_t n=0u; n<N; ++n) { Y[n*T] = 1.0f/(1.0f+expf(-Y[n*T])); }
                for (size_t t=1; t<T; ++t)
                {
                    cblas_scopy((int)N,&X[t],(int)T,H,1);
                    cblas_sgemv(CblasColMajor,CblasTrans,(int)N,(int)N,o,U,(int)N,&Y[t-1],(int)T,o,H,1);
                    for (size_t n=0u; n<N; ++n) { H[n] = 1.0f/(1.0f+expf(-H[n])); }
                    cblas_sgemv(CblasColMajor,CblasTrans,(int)N,(int)N,o,W,(int)N,H,1,o,&Y[t],(int)T);
                    for (size_t n=0u; n<N; ++n) { Y[t+n*T] = 1.0f/(1.0f+expf(-Y[t+n*T])); }
                }
            }
            else
            {
                for (size_t n=0u; n<N; ++n) { cblas_scopy((int)T,&B[n],0,&Y[n],(int)N); }
                cblas_scopy((int)N,&X[0],1,H,1);
                cblas_sgemv(CblasRowMajor,CblasTrans,(int)N,(int)N,o,U,(int)N,Y1,1,o,H,1);
                for (size_t n=0u; n<N; ++n) { H[n] = 1.0f/(1.0f+expf(-H[n])); }
                cblas_sgemv(CblasRowMajor,CblasTrans,(int)N,(int)N,o,W,(int)N,H,1,o,&Y[0],1);
                for (size_t n=0u; n<N; ++n) { Y[n] = 1.0f/(1.0f+expf(-Y[n])); }
                for (size_t t=1; t<T; ++t)
                {
                    cblas_scopy((int)N,&X[t*N],1,H,1);
                    cblas_sgemv(CblasRowMajor,CblasTrans,(int)N,(int)N,o,U,(int)N,&Y[(t-1)*N],1,o,H,1);
                    for (size_t n=0u; n<N; ++n) { H[n] = 1.0f/(1.0f+expf(-H[n])); }
                    cblas_sgemv(CblasRowMajor,CblasTrans,(int)N,(int)N,o,W,(int)N,H,1,o,&Y[t*N],1);
                    for (size_t n=0u; n<N; ++n) { Y[t*N+n] = 1.0f/(1.0f+expf(-Y[t*N+n])); }
                }
            }
        }
        else
        {
            fprintf(stderr,"error in jordan_s: dim must be 0 or 1.\n"); return 1;
        }
    }

    return 0;
}

int jordan_d (double *Y, const double *X, const double *U, double *Y1, const double *W, const double *B, const size_t N, const size_t T, const char iscolmajor, const size_t dim)
{
    const double o = 1.0;

    double *H;
    if (!(H=(double *)malloc(N*sizeof(double)))) { fprintf(stderr,"error in jordan_d: problem with malloc. "); perror("malloc"); return 1; }

    if (N==1u)
    {
        H[0] = 1.0 / (1.0+exp(-X[0]-U[0]*Y1[0]));
        Y[0] = 1.0 / (1.0+exp(-B[0]-W[0]*H[0]));
        for (size_t t=1; t<T; ++t)
        {
            H[0] = 1.0 / (1.0+exp(-X[t]-U[0]*Y[t-1]));
            Y[t] = 1.0 / (1.0+exp(-B[0]-W[0]*H[0]));
        }
    }
    else
    {
        if (dim==0u)
        {
            if (iscolmajor)
            {
                for (size_t n=0u; n<N; ++n) { cblas_dcopy((int)T,&B[n],0,&Y[n],(int)N); }
                cblas_dcopy((int)N,&X[0],1,H,1);
                cblas_dgemv(CblasColMajor,CblasNoTrans,(int)N,(int)N,o,U,(int)N,Y1,1,o,H,1);
                for (size_t n=0u; n<N; ++n) { H[n] = 1.0/(1.0+exp(-H[n])); }
                cblas_dgemv(CblasColMajor,CblasNoTrans,(int)N,(int)N,o,W,(int)N,H,1,o,&Y[0],1);
                for (size_t n=0u; n<N; ++n) { Y[n] = 1.0/(1.0+exp(-Y[n])); }
                for (size_t t=1; t<T; ++t)
                {
                    cblas_dcopy((int)N,&X[t*N],1,H,1);
                    cblas_dgemv(CblasColMajor,CblasNoTrans,(int)N,(int)N,o,U,(int)N,&Y[(t-1)*N],1,o,H,1);
                    for (size_t n=0u; n<N; ++n) { H[n] = 1.0/(1.0+exp(-H[n])); }
                    cblas_dgemv(CblasColMajor,CblasNoTrans,(int)N,(int)N,o,W,(int)N,H,1,o,&Y[t*N],1);
                    for (size_t n=0u; n<N; ++n) { Y[t*N+n] = 1.0/(1.0+exp(-Y[t*N+n])); }
                }
            }
            else
            {
                for (size_t n=0u; n<N; ++n) { cblas_dcopy((int)T,&B[n],0,&Y[n*T],1); }
                cblas_dcopy((int)N,&X[0],(int)T,H,1);
                cblas_dgemv(CblasRowMajor,CblasNoTrans,(int)N,(int)N,o,U,(int)N,Y1,1,o,H,1);
                for (size_t n=0u; n<N; ++n) { H[n] = 1.0/(1.0+exp(-H[n])); }
                cblas_dgemv(CblasRowMajor,CblasNoTrans,(int)N,(int)N,o,W,(int)N,H,1,o,&Y[0],(int)T);
                for (size_t n=0u; n<N; ++n) { Y[n*T] = 1.0/(1.0+exp(-Y[n*T])); }
                for (size_t t=1; t<T; ++t)
                {
                    cblas_dcopy((int)N,&X[t],(int)T,H,1);
                    cblas_dgemv(CblasRowMajor,CblasNoTrans,(int)N,(int)N,o,U,(int)N,&Y[t-1],(int)T,o,H,1);
                    for (size_t n=0u; n<N; ++n) { H[n] = 1.0/(1.0+exp(-H[n])); }
                    cblas_dgemv(CblasRowMajor,CblasNoTrans,(int)N,(int)N,o,W,(int)N,H,1,o,&Y[t],(int)T);
                    for (size_t n=0u; n<N; ++n) { Y[t+n*T] = 1.0/(1.0+exp(-Y[t+n*T])); }
                }
            }
        }
        else if (dim==1u)
        {
            if (iscolmajor)
            {
                for (size_t n=0u; n<N; ++n) { cblas_dcopy((int)T,&B[n],0,&Y[n*T],1); }
                cblas_dcopy((int)N,&X[0],(int)T,H,1);
                cblas_dgemv(CblasColMajor,CblasTrans,(int)N,(int)N,o,U,(int)N,Y1,1,o,H,1);
                for (size_t n=0u; n<N; ++n) { H[n] = 1.0/(1.0+exp(-H[n])); }
                cblas_dgemv(CblasColMajor,CblasTrans,(int)N,(int)N,o,W,(int)N,H,1,o,&Y[0],(int)T);
                for (size_t n=0u; n<N; ++n) { Y[n*T] = 1.0/(1.0+exp(-Y[n*T])); }
                for (size_t t=1; t<T; ++t)
                {
                    cblas_dcopy((int)N,&X[t],(int)T,H,1);
                    cblas_dgemv(CblasColMajor,CblasTrans,(int)N,(int)N,o,U,(int)N,&Y[t-1],(int)T,o,H,1);
                    for (size_t n=0u; n<N; ++n) { H[n] = 1.0/(1.0+exp(-H[n])); }
                    cblas_dgemv(CblasColMajor,CblasTrans,(int)N,(int)N,o,W,(int)N,H,1,o,&Y[t],(int)T);
                    for (size_t n=0u; n<N; ++n) { Y[t+n*T] = 1.0/(1.0+exp(-Y[t+n*T])); }
                }
            }
            else
            {
                for (size_t n=0u; n<N; ++n) { cblas_dcopy((int)T,&B[n],0,&Y[n],(int)N); }
                cblas_dcopy((int)N,&X[0],1,H,1);
                cblas_dgemv(CblasRowMajor,CblasTrans,(int)N,(int)N,o,U,(int)N,Y1,1,o,H,1);
                for (size_t n=0u; n<N; ++n) { H[n] = 1.0/(1.0+exp(-H[n])); }
                cblas_dgemv(CblasRowMajor,CblasTrans,(int)N,(int)N,o,W,(int)N,H,1,o,&Y[0],1);
                for (size_t n=0u; n<N; ++n) { Y[n] = 1.0/(1.0+exp(-Y[n])); }
                for (size_t t=1; t<T; ++t)
                {
                    cblas_dcopy((int)N,&X[t*N],1,H,1);
                    cblas_dgemv(CblasRowMajor,CblasTrans,(int)N,(int)N,o,U,(int)N,&Y[(t-1)*N],1,o,H,1);
                    for (size_t n=0u; n<N; ++n) { H[n] = 1.0/(1.0+exp(-H[n])); }
                    cblas_dgemv(CblasRowMajor,CblasTrans,(int)N,(int)N,o,W,(int)N,H,1,o,&Y[t*N],1);
                    for (size_t n=0u; n<N; ++n) { Y[t*N+n] = 1.0/(1.0+exp(-Y[t*N+n])); }
                }
            }
        }
        else
        {
            fprintf(stderr,"error in jordan_d: dim must be 0 or 1.\n"); return 1;
        }
    }

    return 0;
}

#ifdef __cplusplus
}
}
#endif


/*
Copyright (C) 2019-2020 JingWeiZhangHuai <jingweizhanghuai@163.com>
Licensed under the Apache License, Version 2.0; you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>

#include <cblas.h>
#include "morn_tensor.h"

void DeconvTensorToMatData(MTensor *tns,int bc,float *mdata,int knl_height,int knl_width,int y_stride,int x_stride)
{
    int height = tns->height;
    int width  = tns->width;
    int channel= tns->channel;

    int out_width = width*x_stride;
    int out_height=height*y_stride;
    int mwidth = knl_height*knl_width*channel+1;
    int mheight= out_height*out_width;

    memset(mdata,0,mwidth*mheight*sizeof(float));

    float *tdata = tns->data[bc];
    int tsize = tns->height*tns->width;

    int i,j,c;
    for(j=0;j<mheight;j++)
    {
        int n=j/out_width-knl_height/2;
        int m=j%out_width-knl_width/2;
        for(i=0;i<mwidth-1;i+=channel)
        {
            int h= i/(knl_width*channel)         +n;if(h%y_stride!=0) continue;h=h/y_stride;
            int w=(i%(knl_width*channel))/channel+m;if(w%x_stride!=0) continue;w=w/x_stride;

            if(h<0)h=0;else if(h>=height)h=height-1;
            if(w<0)w=0;else if(w>= width)w= width-1;

            for(c=0;c<channel;c++)
                mdata[(j*mwidth)+i+c]=tdata[c*tsize+h*width+w];
        }
        mdata[(j*mwidth)+mwidth-1]=1.0f;
    }
}

void DeconvMatDataToTensor(float *mdata,MTensor *tns,int bc,int knl_height,int knl_width,int y_stride,int x_stride)
{
    int height = tns->height;
    int width  = tns->width;
    int channel= tns->channel;

    int out_width = width*x_stride;
    int out_height=height*y_stride;

    int mwidth = knl_height*knl_width*channel+1;
    int mheight= out_height*out_width;

    float *tdata = tns->data[bc];
    int tsize = tns->height*tns->width;

    int i,j,c;
    for(j=0;j<mheight;j++)
    {
        int n=j/out_width-knl_height/2;
        int m=j%out_width-knl_width/2;
        for(i=0;i<mwidth-1;i+=channel)
        {
            int h= i/(knl_width*channel)         +n;if(h%y_stride!=0) continue;h=h/y_stride;
            int w=(i%(knl_width*channel))/channel+m;if(w%x_stride!=0) continue;w=w/x_stride;

            if(h<0)h=0;else if(h>=height)h=height-1;
            if(w<0)w=0;else if(w>= width)w= width-1;

            for(c=0;c<channel;c++)
                tdata[c*tsize+h*width+w]+=mdata[(j*mwidth)+i+c];
        }
        mdata[(j*mwidth)+mwidth-1]=1.0f;
    }
}

struct TensorDeconvPara
{
    MLayer *prev;

    int knl_num;
    int knl_height;
    int knl_width;

    int x_stride;
    int y_stride;

    int res_valid;

    float rate;
    float decay;
    float momentum;
};
void *mTensorDeconvPara(MFile *ini,char *name)
{
    struct TensorDeconvPara *para = (struct TensorDeconvPara *)mMalloc(sizeof(struct TensorDeconvPara));

    char *value = mINIRead(ini,name,"prev");
    para->prev = mNetworkLayer(ini,value);
    mException((para->prev == NULL),EXIT,"invalid prev");

    para->res_valid = (strcmp("Input",mLayerType(para->prev))!=0);

    value = mINIRead(ini,name,"knl_num");
    if(value != NULL) para->knl_num= atoi(value);else para->knl_num= 1;

    value = mINIRead(ini,name,"knl_height");
    if(value != NULL) para->knl_height= atoi(value);else para->knl_height= 1;

    value = mINIRead(ini,name,"knl_width");
    if(value != NULL) para->knl_width= atoi(value);else para->knl_width= 1;

    value = mINIRead(ini,name,"x_stride");
    if(value != NULL) para->x_stride= atoi(value);else para->x_stride= 1;

    value = mINIRead(ini,name,"y_stride");
    if(value != NULL) para->y_stride= atoi(value);else para->y_stride= 1;

    value = mINIRead(ini,name,"rate");
    if(value != NULL) para->rate = atof(value);
    else
    {
        value = mINIRead(ini,"para","rate");
        if(value != NULL) para->rate = atof(value);
        else              para->rate = 0.001;
    }

    value = mINIRead(ini,name,"decay");
    if(value != NULL) para->decay = atof(value);
    else
    {
        value = mINIRead(ini,"para","decay");
        if(value != NULL) para->decay = atof(value);
        else              para->decay = 0.01;
    }
    mException((para->decay<0.0f)||(para->decay>=1.0f),EXIT,"invalid para decay");

    value = mINIRead(ini,name,"momentum");
    if(value != NULL) para->momentum = atof(value);
    else
    {
        value = mINIRead(ini,"para","momentum");
        if(value != NULL) para->momentum = atof(value);
        else              para->momentum = 0.9;
    }

    return para;
}

struct HandleTensorDeconv
{
    float *mat;
    float *kernel;
    float *update;
};
void endTensorDeconv(void *info)
{
    struct HandleTensorDeconv *handle = (struct HandleTensorDeconv *)info;
    if(handle->mat   != NULL) mFree(handle->mat);
    if(handle->kernel!= NULL) mFree(handle->kernel);
    if(handle->update!= NULL) mFree(handle->update);
}
#define HASH_TensorDeconv 0x9087d39c
void TensorDeconvSet(MLayer *layer)
{
    if(layer->state != DFLT) return;
    struct TensorDeconvPara *para = (struct TensorDeconvPara *)(layer->para);
    MTensor *in = para->prev->tns;
    MTensor *res= para->prev->res;
    MTensor *out=layer->tns;

    MHandle *hdl=mHandle(out,TensorDeconv);
    struct HandleTensorDeconv *handle = (struct HandleTensorDeconv *)(hdl->handle);

    int out_height= in->height*para->y_stride;
    int out_width = in->width *para->x_stride;
    int mheight = (out_height*out_width);
    int mwidth = para->knl_height*para->knl_width*in->channel+1;
    int data_size = para->knl_num*mwidth;

    mTensorRedefine(out,in->batch,para->knl_num,out_height,out_width,NULL);
    if(morn_network_flag == MORN_TRAIN)
    {
        if(INVALID_TENSOR(res)) mTensorRedefine(res,in->batch,in->channel,in->height,in->width,in->data);
        else                    mTensorRedefine(res,in->batch,in->channel,in->height,in->width,NULL);

        if(handle->update != NULL) mFree(handle->update);
        handle->update =(float *)mMalloc(data_size*sizeof(float));
        memset(handle->update,0,data_size*sizeof(float));
    }

    if(handle->kernel !=NULL) mFree(handle->kernel);
    handle->kernel = (float *)mMalloc(data_size*sizeof(float));

    if(morn_network_parafile==NULL)
    {
        float scale = sqrt(2.0f/mwidth);
        for(int i=0;i<data_size;i++)
            handle->kernel[i] = scale*mNormalRand(0.0f,1.0f);
    }
    else
        mNetworkParaRead(layer,"kernel",handle->kernel,data_size*sizeof(float));

    if(handle->mat!=NULL) mFree(handle->mat);
    handle->mat = (float *)mMalloc(mheight*mwidth*sizeof(float));

    hdl->valid = 1;
}

void mTensorDeconvForward(MLayer *layer)
{
    mException(INVALID_POINTER(layer),EXIT,"invalid input");
    mException(strcmp("Deconv",mLayerType(layer)),EXIT,"invalid layer type");
    struct TensorDeconvPara *para = (struct TensorDeconvPara *)(layer->para);
    MTensor *in = para->prev->tns;
    MTensor *out=layer->tns;

    TensorDeconvSet(layer);

    MHandle *hdl=mHandle(out,TensorDeconv);
    struct HandleTensorDeconv *handle = (struct HandleTensorDeconv *)(hdl->handle);

    int mheight = (out->height*out->width);
    int mwidth = para->knl_height*para->knl_width*in->channel+1;

    float *kernel_data= handle->kernel;
    float *in_data = handle->mat;

    for(int b=0;b<in->batch;b++)
    {
        DeconvTensorToMatData(in,b,in_data,para->knl_height,para->knl_width,para->y_stride,para->x_stride);
        float *out_data = out->data[b];

        in_data[mwidth-1]=1.0f;

        cblas_sgemm(CblasRowMajor,CblasNoTrans,CblasTrans,
                    para->knl_num,mheight,mwidth,
                    1.0f,
                    kernel_data,mwidth,
                        in_data,mwidth,
               0.0f,   out_data,mheight);
    }

    layer->state = MORN_FORWARD;
}

void mTensorDeconvBackward(MLayer *layer)
{
    mException(INVALID_POINTER(layer),EXIT,"invalid input");
    mException(strcmp("Deconv",mLayerType(layer)),EXIT,"invalid layer type");
    struct TensorDeconvPara *para = (struct TensorDeconvPara *)(layer->para);

    MTensor *in = para->prev->tns;
    MTensor *res= para->prev->res;
    MTensor *out=layer->res;

    MHandle *hdl=mHandle(layer->tns,TensorDeconv);
    struct HandleTensorDeconv *handle = (struct HandleTensorDeconv *)(hdl->handle);
    mException((hdl->valid == 0),EXIT,"no forward operate");

    int mheight = (out->height*out->width);
    int mwidth = para->knl_height*para->knl_width*in->channel+1;

    float *kernel_data= handle->kernel;
    float *update_data= handle->update;
    float *    in_data= handle->mat;
    float *   res_data= handle->mat;

    mNetworkParaWrite(layer,"kernel",kernel_data,para->knl_num*mwidth*sizeof(float));

    for(int b=0;b<in->batch;b++)
    {
        DeconvTensorToMatData(in,b,in_data,para->knl_height,para->knl_width,para->y_stride,para->x_stride);
        float *out_data = out->data[b];

        in_data[mwidth-1]=1.0f;

        cblas_sgemm(CblasRowMajor,CblasNoTrans,CblasNoTrans,
                    para->knl_num,mwidth,mheight,
                    1.0f,
                       out_data,mheight,
                        in_data,mwidth,
                    (b==0)?para->momentum:1.0f,
                    update_data,mwidth);
    }
    cblas_saxpby(para->knl_num*mwidth,
                 (0.0f-(para->rate/(float)(in->batch))),update_data,1,
                 (1.0f-(para->decay*para->rate))       ,kernel_data,1);

    if(para->res_valid==0) return;

    if(para->prev->state == MORN_FORWARD)
    {
        for(int b=0;b<res->batch;b++)
            memset(res->data[b],0,in->height*in->width*in->channel*sizeof(float));
        para->prev->state = MORN_BACKWARD;
    }

    for(int b=0;b<in->batch;b++)
    {
        float *out_data = out->data[b];

        cblas_sgemm(CblasRowMajor,CblasTrans,CblasNoTrans,
                    mheight,mwidth,para->knl_num,
                    1.0f,
                       out_data,mheight,
                    kernel_data,mwidth,
                0.0,   res_data,mwidth);

        DeconvMatDataToTensor(res_data,res,b,para->knl_height,para->knl_width,para->y_stride,para->x_stride);
    }
}


//
//  emu.c
//  Created by Earl Lawrence on 11/10/16.
//  Modified E Chisari 05/10/17 for CCL
//  For details on the license, see ../LICENSE_COSMICEMU
//  in this repository.
//

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>

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

#include "ccl.h"

#include "ccl_emu17_params.h"
#include "ccl_emu17.h"

// Sizes of stuff
static int m[2] = {111, 36}, neta=2808, peta[2]={7, 28}, rs=8, p=8;

// Kriging basis computed by emuInit
// Sizes of each basis will be peta[ee] and m[ee]
static double KrigBasis[2][28][111];

// Need to combine some things into a big thing.
static double beta[2][28][8];
static double w[2][28][111];
static double lamws[2][28];
static double lamz[2][28];

// Initialization to compute the kriging basis for the two parts
static void emuInit() {

    int ee, i, j, k, l;
    double cov;
    gsl_matrix *SigmaSim;
    gsl_vector *b;

    // Because of the structure of this emu, I need to do this horrible part first.
    // Fill in the correlation lenghths
    for(i=0; i<7; i++) {
        for(j=0; j<8; j++) {
            beta[0][i][j] = beta1[i][j];
        }
    }

    for(i=0; i<28; i++) {
        for(j=0; j<8; j++) {
            beta[1][i][j] = beta2[i][j];
        }
    }

    // Fill in the PC weights
    for(i=0; i<7; i++) {
        for(j=0; j<111; j++) {
            w[0][i][j] = w1[i][j];
        }
    }

    for(i=0; i<28; i++) {
        for(j=0; j<36; j++) {
            w[1][i][j] = w2[i][j];
        }
    }

    // Fill in the precisions
    for(i=0; i<7; i++) {
        lamws[0][i] = lamws1[i];
        lamz[0][i] = lamz1[i];
    }

    for(i=0; i<28; i++) {
        lamws[1][i] = lamws2[i];
        lamz[1][i] = lamz2[i];
    }

    // This emu has two parts: one that uses all m[0] of the data for the the first peta[0] components
    // and another that uses only the m[1] complete data for the next peta[1] components.
    for(ee=0; ee<2; ee++) {

        // Allocate some stuff
        SigmaSim = gsl_matrix_alloc(m[ee], m[ee]);
        b = gsl_vector_alloc(m[ee]);

        // Loop over the basis
        for(i=0; i<peta[ee]; i++) {

            // Loop over the number of simulations
            for(j=0; j<m[ee]; j++) {

                // Diagonal term
                gsl_matrix_set(SigmaSim, j, j, (1.0/lamz[ee][i]) + (1.0/lamws[ee][i]));

                // Off-diagonals
                for(k=0; k<j; k++) {

                    // compute the covariance
                    cov = 0.0;
                    for(l=0; l<p; l++) {
                        cov -= beta[ee][i][l]*pow(x[j][l] - x[k][l], 2.0);
                    } // for(l=0; l<p; l++)
                    cov = exp(cov) / lamz[ee][i];

                    // put the covariance where it belongs
                    gsl_matrix_set(SigmaSim, j, k, cov);
                    gsl_matrix_set(SigmaSim, k, j, cov);

                } // for(k=0; k<j; k++)

                // Vector for the PC weights
                gsl_vector_set(b, j, w[ee][i][j]);

            } // for(j=0; j<m[ee]; j++)

            // Cholesky and solve
            gsl_linalg_cholesky_decomp(SigmaSim);
            gsl_linalg_cholesky_svx(SigmaSim, b);

            // Put b where it belongs in the Kriging basis
            for(j=0; j<m[ee]; j++) {
                KrigBasis[ee][i][j] = gsl_vector_get(b, j);
            }

        } // for(i=0; i<peta[ee]; i++)

        // Clean this up
        gsl_matrix_free(SigmaSim);
        gsl_vector_free(b);

    } // for(ee=0; ee<2; ee+)

} // emuInit()

// Actual emulation
void ccl_pkemu(double *xstar, double **ystar, int* status, ccl_cosmology* cosmo) {

    static double inited=0;
    int ee, i, j, k;
    double wstar[peta[0]+peta[1]];
    double Sigmastar[2][peta[1]][m[0]];
    double ystaremu[neta];
    *ystar=(double *)malloc(sizeof(double)*NK_EMU);
    double ybyz[rs];
    double logc;
    double xstarstd[p];
    int zmatch;
    gsl_spline *zinterp = gsl_spline_alloc(gsl_interp_cspline, rs);
    gsl_interp_accel *accel = gsl_interp_accel_alloc();

    // Initialize if necessary
    if(inited==0) {
        emuInit();
        inited = 1;
    }

    // Transform w_a into (-w_0-w_a)^(1/4)
    xstar[6] = pow(-xstar[5]-xstar[6], 0.25);
    // Check the inputs to make sure we're interpolating.
    for(i=0; i<p; i++) {
        if((xstar[i] < xmin[i]) || (xstar[i] > xmax[i])) {
            switch(i) {
                case 0:
                    ccl_cosmology_set_status_message(cosmo,
                            "ccl_pkemu(): omega_m must be between %f and %f.\n",
                            xmin[i], xmax[i]);
                    break;
                case 1:
                    ccl_cosmology_set_status_message(cosmo,
                            "ccl_pkemu(): omega_b must be between %f and %f.\n",
                            xmin[i], xmax[i]);
                    break;
                case 2:
                    ccl_cosmology_set_status_message(cosmo,
                            "ccl_pkemu(): sigma8 must be between %f and %f.\n",
                            xmin[i], xmax[i]);
                    break;
                case 3:
                    ccl_cosmology_set_status_message(cosmo,
                            "ccl_pkemu(): h must be between %f and %f.\n",
                            xmin[i], xmax[i]);
                    break;
                case 4:
                    ccl_cosmology_set_status_message(cosmo,
                            "ccl_pkemu(): n_s must be between %f and %f.\n",
                            xmin[i], xmax[i]);
                    break;
                case 5:
                    ccl_cosmology_set_status_message(cosmo,
                            "ccl_pkemu(): w_0 must be between %f and %f.\n",
                            xmin[i], xmax[i]);
                    break;
                case 6:
                    ccl_cosmology_set_status_message(cosmo,
                            "ccl_pkemu(): (-w_0-w_a)^(1/4) must be between %f and %f.\n",
                            xmin[i], xmax[i]);
                    break;
                case 7:
                    ccl_cosmology_set_status_message(cosmo,
                            "ccl_pkemu(): omega_nu must be between %f and %f.\n",
                            xmin[i], xmax[i]);
                    break;
            }
            *status = CCL_ERROR_EMULATOR_BOUND;
            ccl_raise_exception(*status, cosmo->status_message);
	    gsl_spline_free(zinterp);
	    gsl_interp_accel_free(accel);
            return;
        }
    } // for(i=0; i<p; i++)
    if((xstar[p] < z[0]) || (xstar[p] > z[rs-1])) {
        ccl_cosmology_set_status_message(cosmo,
                "ccl_pkemu(): z must be between %f and %f.\n",
                z[0], z[rs-1]);
        *status = CCL_ERROR_EMULATOR_BOUND;
        ccl_raise_exception(*status, cosmo->status_message);
	gsl_spline_free(zinterp);
	gsl_interp_accel_free(accel);
        return;
    }

    // Standardize the inputs
    for(i=0; i<p; i++) {
        xstarstd[i] = (xstar[i] - xmin[i]) / xrange[i];
    }

    // compute the covariances between the new input and sims for all the PCs.
    for(ee=0; ee<2; ee++) {
        for(i=0; i<peta[ee]; i++) {
            for(j=0; j<m[ee]; j++) {
                logc = 0.0;
                for(k=0; k<p; k++) {
                    logc -= beta[ee][i][k]*pow(x[j][k] - xstarstd[k], 2.0);
                }
                Sigmastar[ee][i][j] = exp(logc) / lamz[ee][i];
            }
        }
    }

    // Compute wstar for the first chunk.
    for(i=0; i<peta[0]; i++) {
        wstar[i]=0.0;
        for(j=0; j<m[0]; j++) {
            wstar[i] += Sigmastar[0][i][j] * KrigBasis[0][i][j];
        }
    }
    // Compute wstar for the second chunk.
    for(i=0; i<peta[1]; i++) {
        wstar[peta[0]+i]=0.0;
        for(j=0; j<m[1]; j++) {
            wstar[peta[0]+i] += Sigmastar[1][i][j] * KrigBasis[1][i][j];
        }
    }

    /*
    for(i=0; i<peta[0]+peta[1]; i++) {
        printf("%f\n", wstar[i]);
    }
     */

    // Compute ystar, the new output
    for(i=0; i<neta; i++) {
        ystaremu[i] = 0.0;
        for(j=0; j<peta[0]+peta[1]; j++) {
            ystaremu[i] += K[i][j]*wstar[j];
        }
        ystaremu[i] = ystaremu[i]*sd + mean[i];

        // Convert to P(k)
        //ystaremu[i] = ystaremu[i] - 1.5*log10(mode[i % NK_EMU]);
        //ystaremu[i] = 2*M_PI*M_PI*pow(10, ystaremu[i]);
    }

    // Interpolate to the desired redshift
    // Natural cubic spline interpolation over z.

    // First check to see if the requested z is one of the training z.
    zmatch = -1;
    for(i=0; i<rs; i++) {
        if(xstar[p] == z[i]) {
            zmatch = rs-i-1;
        }
    }

    // z doesn't match a training z, interpolate
    if(zmatch == -1) {
        for(i=0; i<NK_EMU; i++) {
            for(j=0; j<rs; j++) {
                ybyz[rs-j-1] = ystaremu[j*NK_EMU+i];
            }
            gsl_spline_init(zinterp, z, ybyz, rs);
            (*ystar)[i] = gsl_spline_eval(zinterp, xstar[p], accel);
            gsl_interp_accel_reset(accel);
        }

    } else { //otherwise, copy in the emulated z without interpolating
        for(i=0; i<NK_EMU; i++) {
	  (*ystar)[i] = ystaremu[zmatch*NK_EMU + i];
        }
    }

    gsl_spline_free(zinterp);
    gsl_interp_accel_free(accel);

    // Convert to P(k)
    for(i=0; i<NK_EMU; i++) {
      (*ystar)[i] = (*ystar)[i] - 1.5*log10(mode[i]) + log10(2) + 2*log10(M_PI);
      (*ystar)[i] = pow(10, (*ystar)[i]);
    }
}


/**
 * \author Sylvain Marsat, University of Maryland - NASA GSFC
 *
 * \brief C header for functions computing coeffcients of Not-A-Knot and Quadratic splines in matrix form.
 *
 */

#ifndef _SPLINECOEFFS_H
#define _SPLINECOEFFS_H

#define _XOPEN_SOURCE 500

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

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

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

#include "constants.h"
#include "struct.h"

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

void BuildNotAKnotSpline(
  gsl_matrix* splinecoeffs,   /* Output: matrix containing all the spline coeffs (already allocated) */
  gsl_vector* vectx,          /* Input: vector x*/
  gsl_vector* vecty,          /* Input: vector y */
  int n);                      /* Size of x, y, and of output matrix */

void BuildQuadSpline(
  gsl_matrix* splinecoeffs,   /* Output: matrix containing all the spline coeffs (already allocated) */
  gsl_vector* vectx,          /* Input: vector x*/
  gsl_vector* vecty,          /* Input: vector y */
  int n);                      /* Size of x, y, and of output matrix */

void BuildSplineCoeffs(
  CAmpPhaseSpline** splines,                  /*  */
  CAmpPhaseFrequencySeries* freqseries);      /*  */

void BuildListmodesCAmpPhaseSpline(
  ListmodesCAmpPhaseSpline** listspline,              /* Output: list of modes of splines in matrix form */
  ListmodesCAmpPhaseFrequencySeries* listh);          /* Input: list of modes in amplitude/phase form */

/* Functions for spline evaluation */

/* Note: for the spines in matrix form, the first column contains the x values, so the coeffs start at 1 */
double EvalCubic(
  gsl_vector* coeffs,  /**/
  double eps,          /**/
  double eps2,         /**/
  double eps3);         /**/

double EvalQuad(
  gsl_vector* coeffs,  /**/
  double eps,          /**/
  double eps2);         /**/

void EvalCAmpPhaseSpline(
  CAmpPhaseSpline* splines,                     //input
  CAmpPhaseFrequencySeries* freqseries);  //in/out defines CAmpPhase from defined freqs

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

#endif /* _SPLINECOEFFS_H */


#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <string.h>
#include <float.h>
#include <time.h>
#include <errno.h>
#include <gsl/gsl_linalg.h>
#include <gsl/gsl_blas.h>
#include <gsl/gsl_errno.h>

#include "function.h"
#include "norm.h"

// Exception system configuration
#define ERR_T int
#define ERR_V ERRNO
#define OK 0
#include "exceptions.h"

// Input
#define MAX_BUF 256
#define MATCH(line, str) !strncmp(line, str, strlen(str))

// User options
#define DEFAULT_TOLERANCE 1.0e-12
#define DEFAULT_REL_TOL 1
#define DEFAULT_MAX_ZERO_DIST 1.0e-12
#define DEFAULT_USER_NORM 0
#define DEFAULT_MAX_ITER 25
#define DEFAULT_MAX_DIV_ITER 10
#define DEFAULT_JX_REUSE 5

struct options {
	double tolerance;
	char rel_tol;
	double max_zero_dist;
	char user_norm;
	unsigned int max_iter;
	unsigned int max_div_iter;
	unsigned int jx_reuse;
};

// Result
struct additional_data {
	double max_error;
	double* fx;
	unsigned int iter_count;
	double delta_t;
};

// Function declarations

/*
 * The result will be stored in x and x0 will contain the difference between x
 * and the result of the previous iteration.
 */
ERR_T findroot(int dim, double* x0, double* x, struct options* options,
		struct additional_data* data);

ERR_T norm(int dim, double* x, double* n, struct options* options);

ERR_T input_data(char* path, int* dim, double** x0, struct options* options);

void output_result(int dim, double* result, struct additional_data* data);

void output_vector(int dim, double* x);

void handler(const char* reason, const char* file, int line, int gsl_errno);

// Program

int main(int argc, char** argv) {
	int dim;
	char* path;
	double* x = NULL;
	double* x0 = NULL;
	struct options options;
	struct additional_data additional_data;

	// Save the conf file path
	TRY(1, argc == 2)
	path = argv[1];

	// Initialize options with default data
	options.tolerance = DEFAULT_TOLERANCE;
	options.rel_tol = DEFAULT_REL_TOL;
	options.max_zero_dist = DEFAULT_MAX_ZERO_DIST;
	options.user_norm = DEFAULT_USER_NORM;
	options.max_iter = DEFAULT_MAX_ITER;
	options.max_div_iter = DEFAULT_MAX_DIV_ITER;
	options.jx_reuse = DEFAULT_JX_REUSE;

	// Get conf file data
	TRY(2, input_data(path, &dim, &x0, &options) == OK)

	// Find root
	TRY(3, (x = (double*) malloc(dim * sizeof(double))) != NULL)
	TRY(4, findroot(dim, x0, x, &options, &additional_data) == OK)

	output_result(dim, x, &additional_data);

	RETURN(0)

	EXCEPT(
		case 1:
			printf("Usage: findroot path\n");
			break;
		case 2:
			fprintf(stderr,
					"[x] Ezin izan da konfigurazio fitxategia ondo "
					"irakurri.\n");
			break;
		case 3:
			fprintf(stderr,
					"[x] Ezin izan da memoria nahikoa erreserbatu.\n");
			printf("[?] MEMORIA KOPURUA: %lu\n", dim * sizeof(double));
			break;
		case 4:
			fprintf(stderr, "[x] Ezin izan da emaitza kalkulatu.\n");
			break;
	)

	FINALLY(
		free(x);
		free(x0);
	)
}

ERR_T findroot(int dim, double* x0, double* x, struct options* options,
		struct additional_data* data) {
	int s;
	unsigned int iter_count, iter_div_count, jx_reuse_count;
	double max_err, max_err_prev, zero_dist;
	double* fx = NULL;
	double* jx = NULL;
	clock_t begin, end;

	// INITIALIZATION

	TRY(1, (fx = (double*) malloc(dim * sizeof(double))) != NULL)
	TRY(2, (jx = (double*) malloc(dim * dim * sizeof(double))) != NULL)

	gsl_set_error_handler(&handler);

	gsl_vector_view x_gsl = gsl_vector_view_array(x, dim);
	gsl_vector_view x0_gsl = gsl_vector_view_array(x0, dim);
	gsl_vector_view fx_gsl = gsl_vector_view_array(fx, dim);
	gsl_matrix_view jx_gsl = gsl_matrix_view_array(jx, dim, dim);
	gsl_permutation* p = gsl_permutation_alloc(dim);

	// NEWTON-RAPHSON LOOP
	begin = clock();

	/*
	 * a, b and c are vectors of dim dimensions
	 * FX and JX are square matrixes of dim dimensions
	 *
	 * b = a - c
	 * WHERE JX * c = FX
	 *
	 * a beign the result of the previous iteration (or the initial point)
	 * b beign the result of the current iteration (or the final result)
	 */

	// x0 == a

	memcpy(x, x0, dim * sizeof(double));

	// x == a
	// x0 == a

	iter_count = 0;
	iter_div_count = 0;
	max_err = DBL_MAX;
	max_err_prev = DBL_MAX;
	jx_reuse_count = options->jx_reuse;

	do {
		f(dim, x, fx);

		// Reusage of the Jacobian matrix
		if (jx_reuse_count == options->jx_reuse) {
			jakobiarra(dim, x, jx);
			TRY(3, gsl_linalg_LU_decomp(&jx_gsl.matrix, p, &s) == OK)
			jx_reuse_count = 0;
		} else {
			++jx_reuse_count;
		}

		TRY(4,
			gsl_linalg_LU_solve(
				&jx_gsl.matrix, p, &fx_gsl.vector, &x0_gsl.vector) == OK
		)

		// x == a
		// x0 == c

		TRY(5, gsl_vector_sub(&x_gsl.vector, &x0_gsl.vector) == OK)

		TRY(6, norm(dim, x0, &max_err, options) == OK)

		// Relative error
		if (options->rel_tol) {
			// zero_dist is reused to save memory
			TRY(7, norm(dim, x, &zero_dist, options) == OK)
			max_err /= zero_dist;
		}

		// Zero dist
		TRY(8, norm(dim, fx, &zero_dist, options) == OK)

		// x == b
		// x0 == c

		// UPDATES

		++iter_count;

		// Track divergence iterations
		if (max_err > max_err_prev) {
			++iter_div_count;
		} else {
			iter_div_count = 0;
		}
		max_err_prev = max_err;

	} while ((max_err > options->tolerance ||
			zero_dist > options->max_zero_dist) &&
			iter_count < options->max_iter &&
			iter_div_count < options->max_div_iter);

	end = clock();

	if (iter_count == options->max_iter)
		printf("[!] Iterazio mugara iritsi da.\n");
	if (iter_div_count == options->max_div_iter)
		printf("[!] Iterazio dibergente mugara iritsi da.\n");

	data->max_error = max_err;
	data->fx = fx;
	data->iter_count = iter_count;
	data->delta_t = (double) (end - begin) / (double) CLOCKS_PER_SEC;

	EXCEPT(
		case 1:
			fprintf(stderr,
					"[x] Ezin izan da memoria nahikoa erreserbatu.\n");
			printf("[?] MEMORIA KOPURUA: %lu\n", dim * sizeof(double));
			break;
		case 2:
			fprintf(stderr,
					"[x] Ezin izan da memoria nahikoa erreserbatu.\n");
			printf("[?] MEMORIA KOPURUA: %lun", dim * dim * sizeof(double));
			break;
		case 3:
			fprintf(stderr,
					"[x] Ezin izan da JX * x = FX ekuazio sistema "
					"linealaren LU deskonposaketa egin.\n");
			printf("[?] HURBILPEN PARTZIALA: ");
			output_vector(dim, x);
			printf("[?] ERRORE MAXIMOA: %.*g\n", DBL_DIG, max_err);
			printf("[?] F(X): ");
			output_vector(dim, fx);
			printf("[?] ITERAZIO_KOPURUA: %u\n", iter_count);
			break;
		case 4:
			fprintf(stderr,
					"[x] Ezin izan da JX * x = FX ekuazio sistema lineala "
					"ebatzi LU deskonposaketa erabiliz.\n");
			printf("[?] HURBILPEN PARTZIALA: ");
			output_vector(dim, x);
			printf("[?] ERRORE MAXIMOA: %.*g\n", DBL_DIG, max_err);
			printf("[?] F(X): ");
			output_vector(dim, fx);
			printf("[?] ITERAZIO_KOPURUA: %u\n", iter_count);
			break;
		case 5:
			fprintf(stderr,
					"[x] Bektoreen arteko kenketa egitean errore kritiko "
					"bat egon da.");
			printf("[?] F(X): ");
			output_vector(dim, fx);
			printf("[?] ITERAZIO_KOPURUA: %u\n", iter_count);
			break;
		case 6:
			fprintf(stderr,
					"[x] JX * x = FX ekuazio sistema linealaren emaitzaren "
					"norma kalkulatzean errore kritiko bat egon da.\n");
			printf("[?] F(X): ");
			output_vector(dim, fx);
			printf("[?] ITERAZIO_KOPURUA: %u\n", iter_count);
			break;
		case 7:
			fprintf(stderr,
					"[x] x-ren norma kalkulatzean errore kritiko bat egon "
					"da.\n");
			printf("[?] HURBILPEN PARTZIALA: ");
			output_vector(dim, x);
			printf("[?] F(X): ");
			output_vector(dim, fx);
			printf("[?] ITERAZIO_KOPURUA: %u\n", iter_count);
			break;
		case 8:
			fprintf(stderr,
					"[x] f(x)-ren norma kalkulatzean errore kritiko bat egon "
					"da.\n");
			printf("[?] HURBILPEN PARTZIALA: ");
			output_vector(dim, x);
			printf("[?] ERRORE MAXIMOA: %.*g\n", DBL_DIG, max_err);
			output_vector(dim, fx);
			printf("[?] ITERAZIO_KOPURUA: %u\n", iter_count);
			break;
	)

	FINALLY(
		free(fx);
		free(jx);
		gsl_permutation_free(p);
	)
}

ERR_T norm(int dim, double* x, double* n, struct options* options) {
	if (options->user_norm) {
		norma(dim, x, n);
	} else {
		gsl_vector_view x_gsl = gsl_vector_view_array(x, dim);
		TRY(1, (*n = gsl_blas_dnrm2(&x_gsl.vector)) >= 0.0)
	}

	EXCEPT(
		case 1:
			fprintf(stderr, "[x] Ezin izan da norma kalkulatu, posible da "
					"bektorea handiegia izatea.\n");
			break;
	)

	FINALLY()
}

ERR_T input_data(char* path, int* dim, double** x0, struct options* options) {
	FILE* f;
	char line[MAX_BUF];
	int count = 0;

	TRY(1, (f = fopen(path, "r")) != NULL)

	// Read dimension
	*dim = 0;
	while (fgets(line, MAX_BUF, f) != NULL) {
		if (line[0] != '#' && (line[0] != '\0')) {
			if (MATCH(line, "dimentsioa")) {
				TRY(2, sscanf(line, "dimentsioa %d", dim) == 1)
				TRY(3, *dim > 0)
				break;
			}
		}
	}

	TRY(4, *dim != 0)

	TRY(5, (*x0 = (double*) malloc((*dim) * sizeof(double))) != NULL)

	// Read x0 and optional parameters
	while (fgets(line, MAX_BUF, f) != NULL) {
		if (line[0] != '#' && (line[0] != '\0')) {
			if (MATCH(line, "tolerantzia")) {
				TRY(6,
					sscanf(line, "tolerantzia %lf", &(options->tolerance)) == 1)
				TRY(7, options->tolerance > 0.0)
			} else if (MATCH(line, "erlatiboa")) {
				if (MATCH(line, "erlatiboa bai")) {
					options->rel_tol = 1;
				} else if (MATCH(line, "erlatiboa ez")) {
					options->rel_tol = 0;
				} else {
					TRY(8, 0);
				}
			} else if (MATCH(line, "max_zero_distantzia")) {
				TRY(9,
					sscanf(line, "max_zero_distantzia %lf",
							&(options->max_zero_dist)) == 1)
				TRY(10, options->max_zero_dist > 0.0)
			} else if (MATCH(line, "ordezko_norma")) {
				if (MATCH(line, "ordezko_norma bai")) {
					options->user_norm = 1;
				} else if (MATCH(line, "ordezko_norma ez")) {
					options->user_norm = 0;
				} else {
					TRY(11, 0);
				}
			} else if (MATCH(line, "iterazio_maximoa")) {
				TRY(12,	sscanf(line, "iterazio_maximoa %u",
							&(options->max_iter)) == 1)
				TRY(13, options->max_iter != 0)
			} else if (MATCH(line, "dibergentzia_iterazio_maximoa")) {
				TRY(14, sscanf(line, "dibergentzia_iterazio_maximoa %u",
						&(options->max_div_iter)) == 1)
			} else if (MATCH(line, "jakobiar_berrerabilpena")) {
				TRY(15, sscanf(line, "jakobiar_berrerabilpena %u",
							&(options->jx_reuse)) == 1)
			} else {
				TRY(16, count <= *dim)
				TRY(17, sscanf(line, "%lf", (*x0) + count) == 1)
				++count;
			}
		}
	}

	TRY(18, count == *dim)

	EXCEPT(
		case 1:
			fprintf(stdout,
					"[x] Ezin izan da konfigurazio fitxategia ireki.\n");
			break;
		case 2:
			fprintf(stdout,
					"[x] Sintaxi desegokia dimentsioa emateko lerroan\n");
			printf("[?] LERROA: %s", line);
			break;
		case 3:
			fprintf(stdout,
					"[x] Dimentsioak zero baina handiagoa izan behar du.\n");
			printf("[?] LERROA: %s", line);
			break;
		case 4:
			fprintf(stdout, "[x] Ez da aurkitu dimentsioa.\n");
			break;
		case 5:
			fprintf(stdout,
					"[x] Ezin izan da X0-rentzat memoria erreserbatu\n.");
			printf("[?] MEMORIA KOPURUA: %lu", (*dim) * sizeof(double));
			break;
		case 6:
			fprintf(stdout,
					"[x] Sintaxi desegokia tolerantzia emateko lerroan\n.");
			printf("[?] LERROA: %s", line);
			break;
		case 7:
			fprintf(stdout,
					"[x] Tolerantziak zero baina handiagoa izan behar du.\n");
			printf("[?] LERROA: %s", line);
			break;
		case 8:
			fprintf(stdout,
					"[x] Sintaxi desegokia tolerantzia erlatiboa aukeratzeko "
					"lerroan. Aukera honek 'bai' eta 'ez' balioak hartu "
					"ditzake soilik.\n");
			printf("[?] LERROA: %s", line);
			break;
		case 9:
			fprintf(stdout,
					"[x] Sintaxi okerra zerotik distantzia maximoa emateko "
					"lerroan.\n");
			printf("[?] LERROA: %s", line);
			break;
		case 10:
			fprintf(stdout,
					"[x] Zerotik distantzia maximoak zero baina handiagoa izan "
					"behar du.\n");
			printf("[?] LERROA: %s", line);
			break;
		case 11:
			fprintf(stdout,
					"[x] Sintaxi desegokia ordezko norma aukeratzeko "
					"lerroan. Aukera honek 'bai' eta 'ez' balioak hartu "
					"ditzake soilik.\n");
			printf("[?] LERROA: %s", line);
			break;
		case 12:
			fprintf(stdout,
					"[x] Sintaxi okerra iterazio kopuru maximoa emateko "
					"lerroan. Kopuruak ezin du negatiboa izan.\n");
			printf("[?] LERROA: %s", line);
			break;
		case 13:
			fprintf(stdout,
					"[x] Iterazio kopuru maximoak ezin du zero izan.\n");
			printf("[?] LERROA: %s", line);
			break;
		case 14:
			fprintf(stdout,
					"[x] Sintaxi okerra iterazio dibergente kopuru maximoa "
					"emateko lerroan. Kopuruak ezin du negatiboa izan.\n");
			printf("[?] LERROA: %s", line);
			break;
		case 15:
			fprintf(stdout,
					"[x] Sintaxi okerra jakobiarraren berrerabilpen ratioa "
					"emateko lerroan. Ratioak ezin du negatiboa izan.\n");
			printf("[?] LERROA: %s", line);
			break;
		case 16:
			fprintf(stdout,
					"[x] X0-ren elementu kopurura dimentsioa baina handiagoa "
					"da.\n");
			printf("[?] DIMENTSIOA: %i\n", *dim);
			break;
		case 17:
			fprintf(stdout,
					"[x] Sintaxi desegokia.\n");
			printf("[?] LERROA: %s", line);
			break;
		case 18:
			fprintf(stdout,
					"[x] Dimentsioa eta X0-ren tamaina ez datoz bat.\n");
			printf("[?] DIMENTSIOA: %i\n", *dim);
			printf("[?] TAMAINA: %i\n", count);
			break;
		case 19:
			fprintf(stdout,
					"[x] Konfigurazio fitxategia ixtean errore kritiko bat "
					"egon da.\n");
			break;
	)

	FINALLY(
		if (f != NULL) {
			TRY(19, fclose(f) == OK);

			// On fail, so that it doesn't try again and again
			f = NULL;
		}
	)
}

void output_result(int dim, double* result, struct additional_data* data) {
	printf("\n");

	printf("Erroa: ");
	output_vector(dim, result);

	printf("Errore maximoa: %.*g\n", DBL_DIG, data->max_error);

	printf("Erroaren irudia: ");
	output_vector(dim, data->fx);

	printf("Iterazio kopurua: %u\n", data->iter_count);

	printf("Denbora: %.*g seg\n", DBL_DIG, data->delta_t);

	printf("\n");
}

void output_vector(int dim, double* x) {
	int i;

	printf("(");
	printf("%.*g", DBL_DIG, x[0]);
	for (i = 1; i < dim; ++i)
		printf(", %.*g", DBL_DIG, x[i]);
	printf(")\n");
}

void handler(const char* reason, const char* file, int line, int gsl_errno) {
	fprintf(stderr, "[x] ");

	switch(gsl_errno) {
		case GSL_EDOM:
			fprintf(stderr, "GSL DOMAIN ERROR: ");
			break;
		case GSL_ERANGE:
			fprintf(stderr, "GSL RANGE ERROR: ");
			break;
		case GSL_ENOMEM:
			fprintf(stderr, "GSL NO MEMORY AVAILABLE: ");
			break;
		case GSL_EINVAL:
			fprintf(stderr, "GSL INVALID ARGUMENT: ");
			break;
		default:
			fprintf(stderr, "GSL ERROR: ");
			break;
	}

	fprintf(stderr, "%s\n", reason);
}


#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <mpfr.h>
#include <petsc.h>

#include "ellipsoid/ellipsoid.h"
#include "tanhsinh.h"

#undef __FUNCT__
#define __FUNCT__ "NormConstantIntFixed"
PetscErrorCode NormConstantIntFixed(EllipsoidalSystem *e, PetscInt n, PetscInt p, PetscInt prec, PetscInt nPts, PetscReal *intVals, PetscReal *normConst)
{
  PetscErrorCode ierr;
  PetscInt flopCount;
  PetscFunctionBegin;
  flopCount = 0;

  FuncInfo2 ctx1 = { .e = e, .n = n, .p = p, .numeratorType = 0, .denomSign = 1 };
  FuncInfo2 ctx2 = { .e = e, .n = n, .p = p, .numeratorType = 1, .denomSign = 1 };
  FuncInfo2 ctx3 = { .e = e, .n = n, .p = p, .numeratorType = 0, .denomSign = -1 };
  FuncInfo2 ctx4 = { .e = e, .n = n, .p = p, .numeratorType = 1, .denomSign = -1 };

  PetscReal integrals[4];

  mpfr_t mpfrzero;
  mpfr_t mpfrone;
  mpfr_inits(mpfrzero, mpfrone, NULL);
  mpfr_set_d(mpfrzero, 0.0, MPFR_RNDN);
  mpfr_set_d(mpfrone, 1.0, MPFR_RNDN);

  ierr = DEQuad((PetscErrorCode (*)(mpfr_t*, mpfr_t*, void*)) normFunction1, e->hp_h, e->hp_k, prec, nPts, integrals+0, &ctx1);
  intVals[0] = integrals[0];
  ierr = DEQuad((PetscErrorCode (*)(mpfr_t*, mpfr_t*, void*)) normFunction1, e->hp_h, e->hp_k, prec, nPts, integrals+1, &ctx2);
  intVals[1] = integrals[1];
  ierr = DEQuad((PetscErrorCode (*)(mpfr_t*, mpfr_t*, void*)) normFunction1, mpfrzero, e->hp_h, prec, nPts, integrals+2, &ctx3);
  intVals[2] = integrals[2];
  ierr = DEQuad((PetscErrorCode (*)(mpfr_t*, mpfr_t*, void*)) normFunction1, mpfrzero, e->hp_h, prec, nPts, integrals+3, &ctx4);
  intVals[3] = integrals[3];
  *normConst = 8.0*(integrals[2]*integrals[1] - integrals[0]*integrals[3]); flopCount += 4;

  mpfr_clears(mpfrzero, mpfrone, NULL);

  ierr = PetscLogFlops(flopCount);CHKERRQ(ierr);
  PetscFunctionReturn(0);
}

#undef __FUNCT__
#define __FUNCT__ "NormConstantIntFixedSE"
PetscErrorCode NormConstantIntFixedSE(EllipsoidalSystem *e, PetscInt n, PetscInt p, PetscInt prec, PetscInt nPts, PetscReal *normConst)
{
  PetscErrorCode ierr;
  PetscInt flopCount;
  PetscFunctionBegin;
  flopCount = 0;

  FuncInfo2 ctx1 = { .e = e, .n = n, .p = p, .numeratorType = 0, .denomSign = 1 };
  FuncInfo2 ctx2 = { .e = e, .n = n, .p = p, .numeratorType = 1, .denomSign = 1 };
  FuncInfo2 ctx3 = { .e = e, .n = n, .p = p, .numeratorType = 0, .denomSign = -1 };
  FuncInfo2 ctx4 = { .e = e, .n = n, .p = p, .numeratorType = 1, .denomSign = -1 };

  PetscReal integrals[4];

  mpfr_t mpfrzero;
  mpfr_t mpfrone;
  mpfr_inits(mpfrzero, mpfrone, NULL);
  mpfr_set_d(mpfrzero, 0.0, MPFR_RNDN);
  mpfr_set_d(mpfrone, 1.0, MPFR_RNDN);

  ierr = SEQuad((PetscErrorCode (*)(mpfr_t*, mpfr_t*, void*)) normFunction1, e->hp_h, e->hp_k, prec, nPts, integrals+0, &ctx1);

  ierr = SEQuad((PetscErrorCode (*)(mpfr_t*, mpfr_t*, void*)) normFunction1, e->hp_h, e->hp_k, prec, nPts, integrals+1, &ctx2);

  ierr = SEQuad((PetscErrorCode (*)(mpfr_t*, mpfr_t*, void*)) normFunction1, mpfrzero, e->hp_h, prec, nPts, integrals+2, &ctx3);

  ierr = SEQuad((PetscErrorCode (*)(mpfr_t*, mpfr_t*, void*)) normFunction1, mpfrzero, e->hp_h, prec, nPts, integrals+3, &ctx4);

  *normConst = 8.0*(integrals[2]*integrals[1] - integrals[0]*integrals[3]); flopCount += 4;

  mpfr_clears(mpfrzero, mpfrone, NULL);

  ierr = PetscLogFlops(flopCount);CHKERRQ(ierr);
  PetscFunctionReturn(0);
}

#undef __FUNCT__
#define __FUNCT__ "NormConstantIntFixedERF"
PetscErrorCode NormConstantIntFixedERF(EllipsoidalSystem *e, PetscInt n, PetscInt p, PetscInt prec, PetscInt nPts, PetscReal *normConst)
{
  PetscErrorCode ierr;
  PetscInt flopCount;
  PetscFunctionBegin;
  flopCount = 0;

  FuncInfo2 ctx1 = { .e = e, .n = n, .p = p, .numeratorType = 0, .denomSign = 1 };
  FuncInfo2 ctx2 = { .e = e, .n = n, .p = p, .numeratorType = 1, .denomSign = 1 };
  FuncInfo2 ctx3 = { .e = e, .n = n, .p = p, .numeratorType = 0, .denomSign = -1 };
  FuncInfo2 ctx4 = { .e = e, .n = n, .p = p, .numeratorType = 1, .denomSign = -1 };

  PetscReal integrals[4];

  mpfr_t mpfrzero;
  mpfr_t mpfrone;
  mpfr_inits(mpfrzero, mpfrone, NULL);
  mpfr_set_d(mpfrzero, 0.0, MPFR_RNDN);
  mpfr_set_d(mpfrone, 1.0, MPFR_RNDN);

  ierr = ERFQuad((PetscErrorCode (*)(mpfr_t*, mpfr_t*, void*)) normFunction1, e->hp_h, e->hp_k, prec, nPts, integrals+0, &ctx1);
  //ierr = ERFQuad((PetscErrorCode (*)(mpfr_t*, mpfr_t*, void*)) inte, mpfrzero, mpfrone, prec, nPts, integrals+0, &ctx1);
  //printf("inte: %15.15f\n", integrals[0]);
  ierr = ERFQuad((PetscErrorCode (*)(mpfr_t*, mpfr_t*, void*)) normFunction1, e->hp_h, e->hp_k, prec, nPts, integrals+1, &ctx2);

  ierr = ERFQuad((PetscErrorCode (*)(mpfr_t*, mpfr_t*, void*)) normFunction1, mpfrzero, e->hp_h, prec, nPts, integrals+2, &ctx3);

  ierr = ERFQuad((PetscErrorCode (*)(mpfr_t*, mpfr_t*, void*)) normFunction1, mpfrzero, e->hp_h, prec, nPts, integrals+3, &ctx4);

  *normConst = 8.0*(integrals[2]*integrals[1] - integrals[0]*integrals[3]); flopCount += 4;

  mpfr_clears(mpfrzero, mpfrone, NULL);

  ierr = PetscLogFlops(flopCount);CHKERRQ(ierr);
  PetscFunctionReturn(0);
}

#undef __FUNCT__
#define __FUNCT__ "NormPlot"
PetscErrorCode NormPlot()
{
  const PetscInt MPFR_PREC = 128;
  const PetscInt NUM_SOLUTIONS = 100;
  const PetscInt POINTS_MIN = 4;
  const PetscInt POINTS_STEP = 2;
  const PetscInt prec = 16;
  const PetscReal a = 3.0;
  const PetscReal b = 2.0;
  const PetscReal c = 1.0;
  PetscReal solExact;
  PetscReal intExact[4];
  PetscReal solutions[NUM_SOLUTIONS];
  PetscReal errors   [NUM_SOLUTIONS];
  PetscReal intSols[4*NUM_SOLUTIONS];
  PetscReal intErrors[4*NUM_SOLUTIONS];
  PetscReal sol2, sol3, sol4;
  PetscLogEvent flopCounts[NUM_SOLUTIONS];
  EllipsoidalSystem e;
  PetscErrorCode ierr;
  PetscInt i;
  PetscEventPerfInfo info;
  PetscInt n = 21;
  PetscInt p = 22;
  PetscFunctionBegin;

  mpfr_set_default_prec(4*MPFR_PREC);
  initEllipsoidalSystem(&e, a, b, c, MPFR_PREC);

  /* calculate "exact" solution */
  mpfr_t exactsol;
  mpfr_init(exactsol);
  ierr = calcNormalizationMPFR(&e, n, p, &exactsol);
  ierr = calcNormalization(&e, n, p, &solExact);CHKERRQ(ierr);
  ierr = calcNormalization2(&e, n, p, intExact, &solExact);CHKERRQ(ierr);
  solExact = mpfr_get_d(exactsol, MPFR_RNDN);
  printf("old norm constant: %15.15f\n", solExact);

  /* calculate approximate solutions and record flops */
  char text[40] = "%d points";
  char sText[40];
  for(i=0; i<NUM_SOLUTIONS; ++i) {
    sprintf(sText, text, POINTS_MIN + POINTS_STEP*i);
    ierr = PetscLogEventRegister(sText, 0, flopCounts+i);CHKERRQ(ierr);
    ierr = PetscLogEventBegin(flopCounts[i], 0, 0, 0, 0);CHKERRQ(ierr);
    ierr = NormConstantIntFixed(&e, n, p, prec, POINTS_MIN + POINTS_STEP*i, intSols+4*i, solutions+i);CHKERRQ(ierr);
    ierr = PetscLogEventEnd(flopCounts[i], 0, 0, 0, 0);CHKERRQ(ierr);
    printf("new norm constant: %15.15f\n", solutions[i]);
  }

  /* calculate errors */
  for(i=0; i<NUM_SOLUTIONS; ++i) {
    errors[i] = PetscAbsReal((solExact - solutions[i])/solExact);
    intErrors[4*i+0] = PetscAbsReal((intExact[0] - intSols[4*i+0])/intExact[0]);
    intErrors[4*i+1] = PetscAbsReal((intExact[1] - intSols[4*i+1])/intExact[1]);
    intErrors[4*i+2] = PetscAbsReal((intExact[2] - intSols[4*i+2])/intExact[2]);
    intErrors[4*i+3] = PetscAbsReal((intExact[3] - intSols[4*i+3])/intExact[3]);
    printf("errors[%d] = %15.15f\n", i, errors[i]);
  }

  FILE *fp1 = fopen("out/normInt1Prec.txt", "w");
  FILE *fp2 = fopen("out/normInt2Prec.txt", "w");
  FILE *fp3 = fopen("out/normInt3Prec.txt", "w");
  FILE *fp4 = fopen("out/normInt4Prec.txt", "w");
  FILE *fp = fopen("out/normWorkPrec.txt", "w");
  fprintf(fp, "points flops error\n");
  fprintf(fp1, "points error\n");
  fprintf(fp2, "points error\n");
  fprintf(fp3, "points error\n");
  fprintf(fp4, "points error\n");
  for(i=0; i<NUM_SOLUTIONS; ++i) {
    ierr = PetscLogEventGetPerfInfo(PETSC_DETERMINE, flopCounts[i], &info);CHKERRQ(ierr);
    fprintf(fp, "%d %4.4e %4.4e\n", POINTS_MIN + POINTS_STEP*i, info.flops, errors[i]);
    fprintf(fp1, "%d %4.4e\n", POINTS_MIN + POINTS_STEP*i, intErrors[4*i+0]);
    fprintf(fp2, "%d %4.4e\n", POINTS_MIN + POINTS_STEP*i, intErrors[4*i+1]);
    fprintf(fp3, "%d %4.4e\n", POINTS_MIN + POINTS_STEP*i, intErrors[4*i+2]);
    fprintf(fp4, "%d %4.4e\n", POINTS_MIN + POINTS_STEP*i, intErrors[4*i+3]);
  }
  fclose(fp);
  fclose(fp1);
  fclose(fp2);
  fclose(fp3);
  mpfr_clear(exactsol);

  PetscFunctionReturn(0);
}

/*
NormPlot2 - plots convergence of a normalization
            constant for three different ellipsoidal
	    systems to compare convergence
 */

#undef __FUNCT__
#define __FUNCT__ "NormPlot2"
PetscErrorCode NormPlot2()
{
  const PetscInt NUM_SOLUTIONS = 43;
  const PetscInt POINTS_MIN = 4;
  const PetscInt POINTS_STEP = 2;
  const PetscInt prec = 16;
  const PetscReal a = 3.0;
  const PetscReal b = 2.0;
  const PetscReal c = 1.0;
  PetscReal solExact;
  PetscReal intExact[4];
  PetscReal solutions[NUM_SOLUTIONS];
  PetscReal errors   [NUM_SOLUTIONS];
  PetscReal intSols[4*NUM_SOLUTIONS];
  PetscReal intErrors[4*NUM_SOLUTIONS];
  PetscReal sol2, sol3, sol4;
  PetscLogEvent flopCounts[NUM_SOLUTIONS];
  EllipsoidalSystem e1, e2, e3;
  EllipsoidalSystem *e;
  PetscErrorCode ierr;
  PetscInt i;
  PetscEventPerfInfo info;
  PetscInt n = 3;
  PetscInt p = 5;
  PetscFunctionBegin;

  ierr = initEllipsoidalSystem(&e1, 2.5, 2.0, 1.0, 64);CHKERRQ(ierr);
  ierr = initEllipsoidalSystem(&e2, 2.5, 2.0, 1.5, 64);CHKERRQ(ierr);
  ierr = initEllipsoidalSystem(&e3, 2.5, 2.0, 1.89, 64);CHKERRQ(ierr);

  for(PetscInt num=0; num<3; ++num) {
    if(num==0)
      e = &e1;
    else if(num==1)
      e = &e2;
    else
      e = &e3;
    /* calculate "exact" solution */
    ierr = calcNormalization(e, n, p, &solExact);CHKERRQ(ierr);
    ierr = calcNormalization2(e, n, p, intExact, &solExact);CHKERRQ(ierr);
    printf("old norm constant: %15.15f\n", solExact);

    /* calculate approximate solutions and record flops */
    char text[40] = "%d points";
    char sText[40];
    for(i=0; i<NUM_SOLUTIONS; ++i) {
      sprintf(sText, text, POINTS_MIN + POINTS_STEP*i);
      ierr = PetscLogEventRegister(sText, 0, flopCounts+i);CHKERRQ(ierr);
      ierr = PetscLogEventBegin(flopCounts[i], 0, 0, 0, 0);CHKERRQ(ierr);
      ierr = NormConstantIntFixed(e, n, p, prec, POINTS_MIN + POINTS_STEP*i, intSols+4*i, solutions+i);CHKERRQ(ierr);
      ierr = PetscLogEventEnd(flopCounts[i], 0, 0, 0, 0);CHKERRQ(ierr);
      printf("new norm constant: %15.15f\n", solutions[i]);
    }

    /* calculate errors */
    for(i=0; i<NUM_SOLUTIONS; ++i) {
      errors[i] = PetscAbsReal((solExact - solutions[i])/solExact);
      intErrors[4*i+0] = PetscAbsReal((intExact[0] - intSols[4*i+0])/intExact[0]);
      intErrors[4*i+1] = PetscAbsReal((intExact[1] - intSols[4*i+1])/intExact[1]);
      intErrors[4*i+2] = PetscAbsReal((intExact[2] - intSols[4*i+2])/intExact[2]);
      intErrors[4*i+3] = PetscAbsReal((intExact[3] - intSols[4*i+3])/intExact[3]);
      printf("errors[%d] = %15.15f\n", i, errors[i]);
    }
    FILE *fp1, *fp2, *fp3, *fp4, *fp;
    if(num==0) {
      fp1 = fopen("out/e1normInt1Prec.txt", "w");
      fp2 = fopen("out/e1normInt2Prec.txt", "w");
      fp3 = fopen("out/e1normInt3Prec.txt", "w");
      fp4 = fopen("out/e1normInt4Prec.txt", "w");
      fp = fopen("out/e1normWorkPrec.txt", "w");
    }
    else if(num==1) {
      fp1 = fopen("out/e2normInt1Prec.txt", "w");
      fp2 = fopen("out/e2normInt2Prec.txt", "w");
      fp3 = fopen("out/e2normInt3Prec.txt", "w");
      fp4 = fopen("out/e2normInt4Prec.txt", "w");
      fp = fopen("out/e2normWorkPrec.txt", "w");
    }
    else {
      fp1 = fopen("out/e3normInt1Prec.txt", "w");
      fp2 = fopen("out/e3normInt2Prec.txt", "w");
      fp3 = fopen("out/e3normInt3Prec.txt", "w");
      fp4 = fopen("out/e3normInt4Prec.txt", "w");
      fp = fopen("out/e3normWorkPrec.txt", "w");
    }
    fprintf(fp, "points flops error\n");
    fprintf(fp1, "points error\n");
    fprintf(fp2, "points error\n");
    fprintf(fp3, "points error\n");
    fprintf(fp4, "points error\n");
    for(i=0; i<NUM_SOLUTIONS; ++i) {
      ierr = PetscLogEventGetPerfInfo(PETSC_DETERMINE, flopCounts[i], &info);CHKERRQ(ierr);
      fprintf(fp, "%d %4.4e %4.4e\n", POINTS_MIN + POINTS_STEP*i, info.flops, errors[i]);
      fprintf(fp1, "%d %4.4e\n", POINTS_MIN + POINTS_STEP*i, intErrors[4*i+0]);
      fprintf(fp2, "%d %4.4e\n", POINTS_MIN + POINTS_STEP*i, intErrors[4*i+1]);
      fprintf(fp3, "%d %4.4e\n", POINTS_MIN + POINTS_STEP*i, intErrors[4*i+2]);
      fprintf(fp4, "%d %4.4e\n", POINTS_MIN + POINTS_STEP*i, intErrors[4*i+3]);
    }
    fclose(fp);
    fclose(fp1);
    fclose(fp2);
    fclose(fp3);

  }

  PetscFunctionReturn(0);
}

#undef __FUNCT__
#define __FUNCT__ "NormPlotSE"
PetscErrorCode NormPlotSE()
{
  const PetscInt NUM_SOLUTIONS = 60;
  const PetscInt POINTS_MIN = 4;
  const PetscInt POINTS_STEP = 2;
  const PetscInt prec = 16;
  const PetscReal a = 3.0;
  const PetscReal b = 2.0;
  const PetscReal c = 1.0;
  PetscReal solOld;
  PetscReal solutions[NUM_SOLUTIONS];
  PetscReal errors   [NUM_SOLUTIONS];
  PetscReal sol2, sol3, sol4;
  PetscLogEvent flopCounts[NUM_SOLUTIONS];
  EllipsoidalSystem e;
  PetscErrorCode ierr;
  PetscInt i;
  PetscEventPerfInfo info;
  PetscInt n = 3;
  PetscInt p = 5;
  PetscFunctionBegin;

  initEllipsoidalSystem(&e, a, b, c, 64);

  /* calculate approximate solutions and record flops */
  char text[40] = "%d points";
  char sText[40];
  for(i=0; i<NUM_SOLUTIONS; ++i) {
    sprintf(sText, text, POINTS_MIN + POINTS_STEP*i);
    ierr = PetscLogEventRegister(sText, 0, flopCounts+i);CHKERRQ(ierr);
    ierr = PetscLogEventBegin(flopCounts[i], 0, 0, 0, 0);CHKERRQ(ierr);
    ierr = NormConstantIntFixedSE(&e, n, p, prec, POINTS_MIN + POINTS_STEP*i, solutions+i);CHKERRQ(ierr);
    ierr = PetscLogEventEnd(flopCounts[i], 0, 0, 0, 0);CHKERRQ(ierr);
    printf("new norm constant (N=%d): %15.15f\n", POINTS_MIN+POINTS_STEP*i, solutions[i]);
  }

  /* calculate "exact" solution */
  ierr = calcNormalization(&e, n, p, &solOld);CHKERRQ(ierr);
  printf("old norm constant: %15.15f\n", solOld);

  /* calculate errors */
  for(i=0; i<NUM_SOLUTIONS; ++i) {
    errors[i] = PetscAbsReal((solOld - solutions[i])/solOld);
    printf("errors[%d] = %15.15f\n", i, errors[i]);
  }

  FILE *fp = fopen("out/normWorkPrecSE.txt", "w");
  fprintf(fp, "points flops error\n");
  for(i=0; i<NUM_SOLUTIONS; ++i) {
    ierr = PetscLogEventGetPerfInfo(PETSC_DETERMINE, flopCounts[i], &info);CHKERRQ(ierr);
    fprintf(fp, "%d %4.4e %4.4e\n", POINTS_MIN + POINTS_STEP*i, info.flops, errors[i]);
  }
  fclose(fp);

  PetscFunctionReturn(0);
}

#undef __FUNCT__
#define __FUNCT__ "NormPlotERF"
PetscErrorCode NormPlotERF()
{
  const PetscInt NUM_SOLUTIONS = 50;
  const PetscInt POINTS_MIN = 4;
  const PetscInt POINTS_STEP = 2;
  const PetscInt prec = 16;
  const PetscReal a = 3.0;
  const PetscReal b = 2.0;
  const PetscReal c = 1.0;
  PetscReal solOld;
  PetscReal solutions[NUM_SOLUTIONS];
  PetscReal errors   [NUM_SOLUTIONS];
  PetscReal sol2, sol3, sol4;
  PetscLogEvent flopCounts[NUM_SOLUTIONS];
  EllipsoidalSystem e;
  PetscErrorCode ierr;
  PetscInt i;
  PetscEventPerfInfo info;
  PetscInt n = 5;
  PetscInt p = 3;
  PetscFunctionBegin;

  initEllipsoidalSystem(&e, a, b, c, 64);

  /* calculate "exact" solution */
  ierr = calcNormalization(&e, n, p, &solOld);CHKERRQ(ierr);
  printf("old norm constant: %15.15f\n", solOld);

  /* calculate approximate solutions and record flops */
  char text[40] = "%d points";
  char sText[40];
  for(i=0; i<NUM_SOLUTIONS; ++i) {
    sprintf(sText, text, POINTS_MIN + POINTS_STEP*i);
    ierr = PetscLogEventRegister(sText, 0, flopCounts+i);CHKERRQ(ierr);
    ierr = PetscLogEventBegin(flopCounts[i], 0, 0, 0, 0);CHKERRQ(ierr);
    ierr = NormConstantIntFixedERF(&e, n, p, prec, POINTS_MIN + POINTS_STEP*i, solutions+i);CHKERRQ(ierr);
    ierr = PetscLogEventEnd(flopCounts[i], 0, 0, 0, 0);CHKERRQ(ierr);
    printf("new norm constant (N=%d): %15.15f\n", POINTS_MIN+POINTS_STEP*i, solutions[i]);
  }

  /* calculate errors */
  for(i=0; i<NUM_SOLUTIONS; ++i) {
    errors[i] = PetscAbsReal((solOld - solutions[i])/solOld);
    printf("errors[%d] = %15.15f\n", i, errors[i]);
  }

  FILE *fp = fopen("out/normWorkPrecERF.txt", "w");
  fprintf(fp, "points flops error\n");
  for(i=0; i<NUM_SOLUTIONS; ++i) {
    ierr = PetscLogEventGetPerfInfo(PETSC_DETERMINE, flopCounts[i], &info);CHKERRQ(ierr);
    fprintf(fp, "%d %4.4e %4.4e\n", POINTS_MIN + POINTS_STEP*i, info.flops, errors[i]);
  }
  fclose(fp);

  PetscFunctionReturn(0);
}

#undef __FUNCT__
#define __FUNCT__ "main"
PetscErrorCode main(int argc, char **argv)
{

  const PetscInt NUM_DIGITS = 128;
  const PetscInt PRECISION  = 32;
  mpfr_t a, b;
  mpfr_t val;
  PetscErrorCode ierr;
  PetscFunctionBeginUser;
  const PetscInt nPts = 10;

  /* initializations */
  ierr = PetscInitialize(&argc, &argv, NULL, NULL);CHKERRQ(ierr);
  ierr = PetscLogDefaultBegin();CHKERRQ(ierr);

  mpfr_set_default_prec(4*NUM_DIGITS);
  mpfr_inits(a, b, val, NULL);
  mpfr_set_d(a, 2.0, MPFR_RNDN);
  mpfr_set_d(b, 3.0, MPFR_RNDN);

  /*
  int fac = 1;
  for(int i=0; i<10; ++i) {
    printf("\n########################################################\n");
    ierr = DEQuad(inte, a, b, PRECISION, fac*nPts, &val, NULL);CHKERRQ(ierr);
    fac *= 2;
  }
  */

  ierr = NormPlot();CHKERRQ(ierr);
  //ierr = NormPlot2();CHKERRQ(ierr);
  //ierr = NormPlotSE();CHKERRQ(ierr);
  //ierr = NormPlotERF();CHKERRQ(ierr);

  ierr = PetscFinalize();CHKERRQ(ierr);
  PetscFunctionReturn(0);
}