#include <gsl/gsl_multifit.h>
#include <gsl/gsl_multifit_nlin.h>
#include <gsl/gsl_blas.h>
#include <gsl/gsl_sf.h>
#include <gsl/gsl_poly.h>
#include <gsl/gsl_version.h>
#include <limits>
#include "fitting.h"
#include "../dpuser/mpfit/mpfitAST.h"

#ifdef WIN
#pragma warning (disable: 4018) // disable warning for comparing signed and unsigned
#endif /* WIN */

#ifdef DPQT
#include <QProcess>
#include <QWriteLocker>
#include "QFitsGlobal.h"
#endif

#ifndef WIN
#include <vector>
#include <sys/wait.h>
#endif



#define FEPS 1e-100

/* Fit an N-th order polynomial to some data */
double polyfit1d(Fits &result, const Fits &xv, const Fits &yv, const Fits &errv, const int degree) {
    int i, j, n;
    gsl_matrix *X, *cov;
    gsl_vector *y, *w, *c;
    double value;
    double chisq;

    n = xv.Nelements();
    int nn = 0;

    X = gsl_matrix_alloc(n, degree + 1);
    y = gsl_vector_alloc(n);
    w = gsl_vector_alloc(n);

    c = gsl_vector_alloc(degree + 1);
    cov = gsl_matrix_alloc(degree + 1, degree + 1);

    for (i = 0; i < n; i++) {
        if (gsl_finite(yv.ValueAt(i))) {
            value = xv.ValueAt(i);
            for (j = 0; j <= degree; j++) {
                gsl_matrix_set(X, i, j, pow(value, j));
            }
            gsl_vector_set(y, i, yv.ValueAt(i));
            value = errv.ValueAt(i);
            gsl_vector_set(w, i, 1.0 / (value * value));
        } else {
            for (j = 0; j <= degree; j++) {
                gsl_matrix_set(X, i, j, 0.0);
            }
            gsl_vector_set(y, i, 0.0);
            gsl_vector_set(w, i, 1e100);
        }
    }

    gsl_multifit_linear_workspace *work = gsl_multifit_linear_alloc(n, degree + 1);
    gsl_multifit_wlinear(X, w, y, c, cov, &chisq, work);
    gsl_multifit_linear_free(work);

    result.create(degree + 1, 2, R8);

    for (i = 0; i <= degree; i++) result.r8data[i] = gsl_vector_get(c, i);
    for (i = 0; i <= degree; i++) result.r8data[degree+1+i] = sqrt(gsl_matrix_get(cov, i, i));

    gsl_matrix_free(X);
    gsl_vector_free(y);
    gsl_vector_free(w);
    gsl_vector_free(c);
    gsl_matrix_free(cov);

    return chisq;
}

// roots of a 1D polynomial
int polyroots(Fits &polynomial, Fits &roots, int derivative) {
    int i, d;
    bool will_be_complex = FALSE;
    int order = polynomial.Nelements();
    int gsl_rv, rv = -1;

    if (order < derivative) return rv;
    if (derivative < 0) derivative = 0;

    double *a = (double *)malloc(order * sizeof(double));
    double *z = (double *)malloc(2 * (order-1) * sizeof(double));

    for (i = 0; i < order; i++) a[i] = polynomial[i];
    for (d = 0; d < derivative; d++) {
        for (i = 0; i < order - 1; i++) {
            a[i] = (i+1) * a[i+1];
        }
        order--;
    }
    gsl_poly_complex_workspace * w = gsl_poly_complex_workspace_alloc (order);
    gsl_rv = gsl_poly_complex_solve (a, order, w, z);
    gsl_poly_complex_workspace_free (w);

    if (gsl_rv == GSL_SUCCESS) {
        for (i = 0; i < order - 1; i++) {
            if (z[2*i+1] != 0.0) will_be_complex = TRUE;
            dp_debug("z%d = %+.18f %+.18f\n", i, z[2*i], z[2*i+1]);
        }
        if (will_be_complex) {
            roots.create(order - 1, 1, C16);
            for (i = 0; i < order-1; i++) {
                roots.cdata[i].r = z[2*i];
                roots.cdata[i].i = z[2*i+1];
            }
        } else {
            roots.create(order - 1, 1, R8);
            for (i = 0; i < order-1; i++) {
                roots.r8data[i] = z[2*i];
            }
        }
        rv = order;
    }
    free(a);
    free(z);

    return rv;
}

// evaluate a gaussian

void evaluate_gauss(Fits &y, const Fits &x, const Fits &params) {
    int i;

    double B = params.ValueAt(0);
    double A = params.ValueAt(1);
    double C = params.ValueAt(2);
    double w = params.ValueAt(3);
    y.create(x.Naxis(1), 1, R8);
    for (i = 0; i < x.Naxis(1); i++) {
        double xx = x.ValueAt(i);
        double diff=xx-C;
        y.r8data[i] = A*exp(-FOUR_LN2*diff*diff/(w*w))+B;
    }
}

// evaluate a gaussian

double evaluate_gauss(const double x, const Fits &params) {
    int i;

    double B = params.ValueAt(0);
    double A = params.ValueAt(1);
    double C = params.ValueAt(2);
    double w = params.ValueAt(3);
    double diff=x-C;
    return A*exp(-FOUR_LN2*diff*diff/(w*w))+B;
}

/* Fit a gaussian; initial estimates to be supplied in result */

struct data {
    dpint64 n;
    double *X;
    double *y;
    double *sigma;
};

/* The gauss function itself */
#define FOUR_LN2 4.0*log(2.0)
int gauss_f (const gsl_vector * x, void *params,
        gsl_vector * f)
{
  dpint64 n = ((struct data *)params)->n;
  double *X = ((struct data *)params)->X;
  double *y = ((struct data *)params)->y;
  double *sigma = ((struct data *) params)->sigma;

  double B = gsl_vector_get (x, 0);
  double A = gsl_vector_get (x, 1);
  double C = gsl_vector_get (x, 2);
  double w = gsl_vector_get (x, 3);

  dpint64 i;

  for (i = 0; i < n; i++)
    {
      double diff=X[i]-C;
      double Yi = A*exp(-FOUR_LN2*diff*diff/(w*w))+B;
      gsl_vector_set (f, i, (Yi - y[i])/sigma[i]);
    }

  return GSL_SUCCESS;
}

int gauss_df (const gsl_vector * x, void *params,
         gsl_matrix * J)
{
  dpint64 n = ((struct data *)params)->n;
  double *X = ((struct data *)params)->X;
  double *sigma = ((struct data *) params)->sigma;

  double A = gsl_vector_get (x, 1);
  double C = gsl_vector_get (x, 2);
  double w = gsl_vector_get (x, 3);

  dpint64 i;

  for (i = 0; i < n; i++)
    {
      /* Jacobian matrix J(i,j) = dfi / dxj, */
      /* where fi = (Yi - yi)/sigma[i],      */
      /* Yi = y=A*exp[-(Xi-xc)^2/(2*w*w)]+B  */
      /* and the xj are the parameters (B,A,C,w) */

      double s=sigma[i];
      double diff=X[i]-C;
      double e =exp(-FOUR_LN2*diff*diff/(w*w))/s;

      gsl_matrix_set (J, i, 0, 1.0 / s);
      gsl_matrix_set (J, i, 1, exp((-FOUR_LN2*X[i]*X[i] + 2.*FOUR_LN2*C*X[i] - FOUR_LN2*C*C) / (w*w)) / s);
      gsl_matrix_set (J, i, 2, exp((-FOUR_LN2*X[i]*X[i] + 2.*FOUR_LN2*C*X[i] - FOUR_LN2*C*C) / (w*w)) * (2.*A*FOUR_LN2)/(w*w)*(X[i]-C) / s);
      gsl_matrix_set (J, i, 3, 2.*A*FOUR_LN2*exp((-FOUR_LN2*X[i]*X[i] + 2.*FOUR_LN2*C*X[i] - FOUR_LN2*C*C) / (w*w)) * (X[i]*X[i]-2.*C*X[i]+C*C) / (w*w*w) / s);
    }
  return GSL_SUCCESS;
}

int gauss_fdf (const gsl_vector * x, void *params,
          gsl_vector * f, gsl_matrix * J)
{
  gauss_f (x, params, f);
  gauss_df (x, params, J);

  return GSL_SUCCESS;
}

double gaussfit(Fits &result, const Fits &xv, const Fits &yv, const Fits &errv) {
    dpint64 n = xv.Nelements();
    dpint64 nn = 0;
    double *y, *sigma, *X;
    int i;

    gsl_vector *XX = gsl_vector_alloc(n);
    X = (double *)malloc(n * sizeof(double));
    sigma = (double *)malloc(n * sizeof(double));
    y = (double *)malloc(n * sizeof(double));

    for (i = 0; i < n; i++) {
        if (gsl_finite(yv.ValueAt(i))) {
            X[nn] = xv.ValueAt(i);
            y[nn] = yv.ValueAt(i);
            sigma[nn] = errv.ValueAt(i);
            gsl_vector_set(XX, nn, X[nn]);
            nn++;
        }
    }
		
    const gsl_multifit_fdfsolver_type *T;
    gsl_multifit_fdfsolver *s;
    const dpint64 p = 4;

    T = gsl_multifit_fdfsolver_lmsder;
    s = gsl_multifit_fdfsolver_alloc (T, n, p);

    if (nn < 4 || s == NULL) {
        result.create(4, 2, R8);
        for (i = 0; i < 8; i++) result.r8data[i] = std::numeric_limits<double>::quiet_NaN();
        free(X);
        free(sigma);
        free(y);
        if (s != NULL) gsl_multifit_fdfsolver_free (s);
        return std::numeric_limits<double>::quiet_NaN();
    }
    n = nn;

    gsl_matrix *covar = gsl_matrix_alloc (p, p);
    struct data d = { n, X, y, sigma};
    gsl_multifit_function_fdf f;

    double x_init[4];
    for (i = 0; i < 4; i++) x_init[i] = result.ValueAt(i);

    gsl_vector_view x = gsl_vector_view_array (x_init, p);

    f.f = &gauss_f;
    f.df = &gauss_df;
    f.fdf = &gauss_fdf;
    f.n = n;
    f.p = p;
    f.params = &d;

    gsl_multifit_fdfsolver_set (s, &f, &x.vector);

    int status;
    dpint64 iter = 0;

    do {
        iter++;
        status = gsl_multifit_fdfsolver_iterate (s);

        if (status)
            break;

        status = gsl_multifit_test_delta (s->dx,s->x,1e-5,1e-5);
    } while (status == GSL_CONTINUE && iter < 500);

#if GSL_MAJOR_VERSION == 2
    gsl_matrix *J = gsl_matrix_alloc(n, p);
    gsl_multifit_fdfsolver_jac(s, J);
    gsl_multifit_covar (J, 0.0, covar);
    gsl_matrix_free (J);
#else
    gsl_multifit_covar (s->J, 0.0, covar);
#endif

#define FIT(i) gsl_vector_get(s->x, i)
#define ERR(i) sqrt(gsl_matrix_get(covar,i,i))

    result.create(4, 2, R8);

    result.r8data[0] = FIT(0);
    result.r8data[1] = FIT(1);
    result.r8data[2] = FIT(2);
    result.r8data[3] = FIT(3);

    double chi2dof = sqrt(pow(gsl_blas_dnrm2(s->f), 2.0) / (n-p));
    result.r8data[4] = ERR(0) * chi2dof;
    result.r8data[5] = ERR(1) * chi2dof;
    result.r8data[6] = ERR(2) * chi2dof;
    result.r8data[7] = ERR(3) * chi2dof;

//	printf ("1      = %.5f +/- %.5f\n", FIT(0), ERR(0));
//	printf ("2      = %.5f +/- %.5f\n", FIT(1), ERR(1));
//	printf ("3      = %.5f +/- %.5f\n", FIT(2), ERR(2));
//	printf ("4      = %.5f +/- %.5f\n", FIT(3), ERR(3));

    double chi = gsl_blas_dnrm2(s->f);
//	printf("chisq/dof = %g\n",  pow(chi, 2.0)/ (n - p));

//	printf ("status = %s\n", gsl_strerror (status));

    gsl_multifit_fdfsolver_free (s);
    gsl_matrix_free(covar);
    gsl_vector_free(XX);
    free(X);
    free(sigma);
    free(y);

    if (iter == 1 || iter == 500) return -1.0;
    return pow(chi, 2.0)/ (n - p);
}

// evaluate multiple gaussians with a straight line

void evaluate_multigauss(Fits &y, const Fits &x, const Fits &params) {
    int i, g, ngauss;

    y.create(x.Naxis(1), 1, R8);

    double B = params.ValueAt(0);
    double D = params.ValueAt(1);

    for (i = 0; i < x.Naxis(1); i++) {
        y.r8data[i] = B+D*x.ValueAt(i);
    }

    ngauss = (params.Naxis(1) - 2) / 3;
    for (g = 0; g < ngauss; g++) {
        double A = params.ValueAt(2+g*3);
        double C = params.ValueAt(3+g*3);
        double w = params.ValueAt(4+g*3);
        for (i = 0; i < x.Naxis(1); i++) {
            double xx = x.ValueAt(i);
            double diff=xx-C;
            y.r8data[i] += A*exp(-FOUR_LN2*diff*diff/(w*w));
        }
    }
}

// fit multiple gaussians with a straight line
int multigauss_n;

int multigauss_f (const gsl_vector * x, void *params,
        gsl_vector * f)
{
  dpint64 n = ((struct data *)params)->n;
  double *X = ((struct data *)params)->X;
  double *y = ((struct data *)params)->y;
  double *sigma = ((struct data *) params)->sigma;

  double B = gsl_vector_get (x, 0);
  double D = gsl_vector_get (x, 1);

  dpint64 i;
    int g;

    for (i = 0; i < n; i++) {
        gsl_vector_set(f, i, B+D*X[i]);
    }

    for (g = 0; g < multigauss_n; g++) {
        double A = gsl_vector_get (x, 2+g*3);
        double C = gsl_vector_get (x, 3+g*3);
        double w = gsl_vector_get (x, 4+g*3);
        for (i = 0; i < n; i++) {
            double diff=X[i]-C;
            gsl_vector_set(f, i, gsl_vector_get(f, i) + A*exp(-FOUR_LN2*diff*diff/(w*w)));
        }

    }
    for (i = 0; i < n; i++) {
        gsl_vector_set (f, i, (gsl_vector_get(f, i) - y[i])/sigma[i]);
    }

  return GSL_SUCCESS;
}

int multigauss_df (const gsl_vector * x, void *params,
         gsl_matrix * J)
{
  dpint64 n = ((struct data *)params)->n;
  double *X = ((struct data *)params)->X;
  double *sigma = ((struct data *) params)->sigma;

  double D = gsl_vector_get (x, 1);
  double A = gsl_vector_get (x, 2);
  double C = gsl_vector_get (x, 3);
  double w = gsl_vector_get (x, 4);

  dpint64 i;
  int g;

  for (i = 0; i < n; i++) {
      double s=sigma[i];
      double diff=X[i]-C;

      gsl_matrix_set (J, i, 0, 1.0 / s);
      gsl_matrix_set (J, i, 1, D / s);
      gsl_matrix_set (J, i, 2, exp((-FOUR_LN2*X[i]*X[i] + 2.*FOUR_LN2*C*X[i] - FOUR_LN2*C*C) / (w*w)) / s);
      gsl_matrix_set (J, i, 3, exp((-FOUR_LN2*X[i]*X[i] + 2.*FOUR_LN2*C*X[i] - FOUR_LN2*C*C) / (w*w)) * (2.*A*FOUR_LN2)/(w*w)*(X[i]-C) / s);
      gsl_matrix_set (J, i, 4, 2.*A*FOUR_LN2*exp((-FOUR_LN2*X[i]*X[i] + 2.*FOUR_LN2*C*X[i] - FOUR_LN2*C*C) / (w*w)) * (X[i]*X[i]-2.*C*X[i]+C*C) / (w*w*w) / s);

        for (g = 1; g < multigauss_n; g++) {
            double A = gsl_vector_get (x, 2+g*3);
            double C = gsl_vector_get (x, 3+g*3);
            double w = gsl_vector_get (x, 4+g*3);
            double diff=X[i]-C;
            gsl_matrix_set (J, i, 2+g*3, exp((-FOUR_LN2*X[i]*X[i] + 2.*FOUR_LN2*C*X[i] - FOUR_LN2*C*C) / (w*w)) / s);
            gsl_matrix_set (J, i, 3+g*3, exp((-FOUR_LN2*X[i]*X[i] + 2.*FOUR_LN2*C*X[i] - FOUR_LN2*C*C) / (w*w)) * (2.*A*FOUR_LN2)/(w*w)*(X[i]-C) / s);
            gsl_matrix_set (J, i, 4+g*3, 2.*A*FOUR_LN2*exp((-FOUR_LN2*X[i]*X[i] + 2.*FOUR_LN2*C*X[i] - FOUR_LN2*C*C) / (w*w)) * (X[i]*X[i]-2.*C*X[i]+C*C) / (w*w*w) / s);
        }
    }
  return GSL_SUCCESS;
}

int multigauss_fdf (const gsl_vector * x, void *params,
          gsl_vector * f, gsl_matrix * J)
{
  multigauss_f (x, params, f);
  multigauss_df (x, params, J);

  return GSL_SUCCESS;
}

double multigaussfit(Fits &result, const Fits &xv, const Fits &yv, const Fits &errv) {
    dpint64 n = xv.Nelements();
    dpint64 nn = 0;
    double *y, *sigma, *X;
    int i;

    gsl_vector *XX = gsl_vector_alloc(n);
    X = (double *)malloc(n * sizeof(double));
    sigma = (double *)malloc(n * sizeof(double));
    y = (double *)malloc(n * sizeof(double));

    for (i = 0; i < n; i++) {
        if (gsl_finite(yv.ValueAt(i))) {
            X[nn] = xv.ValueAt(i);
            y[nn] = yv.ValueAt(i);
            sigma[nn] = errv.ValueAt(i);
            gsl_vector_set(XX, nn, X[nn]);
            nn++;
        }
    }
    n = nn;

    const gsl_multifit_fdfsolver_type *T;
    gsl_multifit_fdfsolver *s;
    const dpint64 p = result.Naxis(1);
    T = gsl_multifit_fdfsolver_lmsder;
    s = gsl_multifit_fdfsolver_alloc (T, n, p);

    if (s == NULL) { // not enough degrees of freedom
        free(X);
        free(y);
        free(sigma);
        gsl_vector_free(XX);
        return 0.0;
    }

    multigauss_n = (result.Naxis(1) - 2) / 3;
    gsl_matrix *covar = gsl_matrix_alloc (p, p);
    struct data d = { n, X, y, sigma};
    gsl_multifit_function_fdf f;

    double *x_init;
    x_init = (double *)malloc(p * sizeof(double));
    for (i = 0; i < p; i++) x_init[i] = result.ValueAt(i);

    gsl_vector_view x = gsl_vector_view_array (x_init, p);

    f.f = &multigauss_f;
    f.df = &multigauss_df;
    f.fdf = &multigauss_fdf;
    f.n = n;
    f.p = p;
    f.params = &d;

    gsl_multifit_fdfsolver_set (s, &f, &x.vector);

    int status;
    dpint64 iter = 0;

    do {
        iter++;
        status = gsl_multifit_fdfsolver_iterate (s);

        if (status)
            break;

        status = gsl_multifit_test_delta (s->dx,s->x,1e-5,1e-5);
    } while (status == GSL_CONTINUE && iter < 500);

#if GSL_MAJOR_VERSION == 2
    gsl_matrix *J = gsl_matrix_alloc(n, p);
    gsl_multifit_fdfsolver_jac(s, J);
    gsl_multifit_covar (J, 0.0, covar);
    gsl_matrix_free (J);
#else
    gsl_multifit_covar (s->J, 0.0, covar);
#endif

#define FIT(i) gsl_vector_get(s->x, i)
#define ERR(i) sqrt(gsl_matrix_get(covar,i,i))

    result.create(p, 2, R8);

    i = 2;
    result.r8data[0] = FIT(0);
    result.r8data[1] = FIT(1);
    for (i = 2; i < p; i++) result.r8data[i] = FIT(i);

    double chi2dof = sqrt(pow(gsl_blas_dnrm2(s->f), 2.0) / (n-p));
    result.r8data[p] = ERR(0) * chi2dof;
    result.r8data[p+1] = ERR(1) * chi2dof;
    for (i = 2; i < p; i++) result.r8data[p+i] = ERR(i) * chi2dof;

//	printf ("1      = %.5f +/- %.5f\n", FIT(0), ERR(0));
//	printf ("2      = %.5f +/- %.5f\n", FIT(1), ERR(1));
//	printf ("3      = %.5f +/- %.5f\n", FIT(2), ERR(2));
//	printf ("4      = %.5f +/- %.5f\n", FIT(3), ERR(3));

    double chi = gsl_blas_dnrm2(s->f);
//	printf("chisq/dof = %g\n",  pow(chi, 2.0)/ (n - p));

//	printf ("status = %s\n", gsl_strerror (status));

    gsl_multifit_fdfsolver_free (s);
    gsl_matrix_free(covar);
    gsl_vector_free(XX);
    free(X);
    free(sigma);
    free(y);
    free(x_init);

    return pow(chi, 2.0)/ (n - p);
}

// evaluate multiple functions with a straight line

void evaluate_multifunc(Fits &y, const Fits &x, const Fits &params, const Fits &types) {
    int i, g, nfuncs;

    y.create(x.Naxis(1), 1, R8);

// first, the straight line
    double B = params.ValueAt(0);
    double D = params.ValueAt(1);

    for (i = 0; i < x.Naxis(1); i++) {
        y.r8data[i] = B+D*x.ValueAt(i);
    }

    nfuncs = (params.Naxis(1) - 2) / 3;
    for (g = 0; g < nfuncs; g++) {
        double A = params.ValueAt(2+g*3);
        double C = params.ValueAt(3+g*3);
        double w = params.ValueAt(4+g*3);

        if ((int)types.ValueAt(g) == 0) {
            for (i = 0; i < x.Naxis(1); i++) {
                double xx = x.ValueAt(i);
                double diff=xx-C;
                y.r8data[i] += A*exp(-FOUR_LN2*diff*diff/(w*w));
            }
        } else if ((int)types.ValueAt(g) == 1) {
            for (i = 0; i < x.Naxis(1); i++) {
                double xx = x.ValueAt(i);
                double diff=xx-C;
                y.r8data[i] += A/(1.0 + 4.0 * (diff*diff/(w*w)));
            }
        }
    }
}

// fit multiple functions with a straight line
int multifunc_n;
unsigned char *multifunc_type;

int multifunc_f (const gsl_vector * x, void *params,
        gsl_vector * f)
{
  dpint64 n = ((struct data *)params)->n;
  double *X = ((struct data *)params)->X;
  double *y = ((struct data *)params)->y;
  double *sigma = ((struct data *) params)->sigma;

  double B = gsl_vector_get (x, 0);
  double D = gsl_vector_get (x, 1);

  dpint64 i;
    int g;

    for (i = 0; i < n; i++) {
        gsl_vector_set(f, i, B+D*X[i]);
    }

    for (g = 0; g < multifunc_n; g++) {
        double A = gsl_vector_get (x, 2+g*3);
        double C = gsl_vector_get (x, 3+g*3);
        double w = gsl_vector_get (x, 4+g*3);
        for (i = 0; i < n; i++) {
            double diff=X[i]-C;

            if (multifunc_type[g] == 0) {
                gsl_vector_set(f, i, gsl_vector_get(f, i) + A*exp(-FOUR_LN2*diff*diff/(w*w)));
            } else if (multifunc_type[g] == 1) {
                gsl_vector_set(f, i, gsl_vector_get(f, i) + A/(1.0 + 4.0 * (diff*diff/(w*w))));
            }
        }

    }
    for (i = 0; i < n; i++) {
        gsl_vector_set (f, i, (gsl_vector_get(f, i) - y[i])/sigma[i]);
    }

  return GSL_SUCCESS;
}

int multifunc_df (const gsl_vector * x, void *params,
         gsl_matrix * J)
{
  dpint64 n = ((struct data *)params)->n;
  double *X = ((struct data *)params)->X;
  double *sigma = ((struct data *) params)->sigma;

  double D = gsl_vector_get (x, 1);
  double A = gsl_vector_get (x, 2);
  double C = gsl_vector_get (x, 3);
  double w = gsl_vector_get (x, 4);

  dpint64 i;
  int g;

  for (i = 0; i < n; i++) {
      double s=sigma[i];
      double diff=X[i]-C;

      gsl_matrix_set (J, i, 0, 1.0 / s);
      gsl_matrix_set (J, i, 1, X[i] / s);

      for (g = 0; g < multifunc_n; g++) {
            double A = gsl_vector_get (x, 2+g*3);
            double C = gsl_vector_get (x, 3+g*3);
            double w = gsl_vector_get (x, 4+g*3);
            double diff=X[i]-C;
            if (multifunc_type[g] == 0) {
                gsl_matrix_set (J, i, 2+g*3, exp((-FOUR_LN2*X[i]*X[i] + 2.*FOUR_LN2*C*X[i] - FOUR_LN2*C*C) / (w*w)) / s);
                gsl_matrix_set (J, i, 3+g*3, exp((-FOUR_LN2*X[i]*X[i] + 2.*FOUR_LN2*C*X[i] - FOUR_LN2*C*C) / (w*w)) * (2.*A*FOUR_LN2)/(w*w)*(X[i]-C) / s);
                gsl_matrix_set (J, i, 4+g*3, 2.*A*FOUR_LN2*exp((-FOUR_LN2*X[i]*X[i] + 2.*FOUR_LN2*C*X[i] - FOUR_LN2*C*C) / (w*w)) * (X[i]*X[i]-2.*C*X[i]+C*C) / (w*w*w) / s);
            } else if (multifunc_type[g] == 1) {
                gsl_matrix_set (J, i, 2+g*3, (w*w / (4.0 * (diff*diff) + (w*w))) / s);
                gsl_matrix_set (J, i, 3+g*3, (8.0 * A*w*w*diff / ((4.0*diff*diff+w*w)*(4.0*diff*diff+w*w))) / s);
                gsl_matrix_set (J, i, 4+g*3, (8.0 * A*w*diff*diff / ((4.0*diff*diff+w*w)*(4.0*diff*diff+w*w))) / s);
            }
        }
    }
  return GSL_SUCCESS;
}

int multifunc_fdf (const gsl_vector * x, void *params,
          gsl_vector * f, gsl_matrix * J)
{
  multifunc_f (x, params, f);
  multifunc_df (x, params, J);

  return GSL_SUCCESS;
}

bool multifuncfit(Fits &result, double *reduced_chisq, const Fits &xv, const Fits &yv, const Fits &errv, const Fits &types) {
    if ((result.Naxis(1) - 2) / 3 == 0) { // only straight line fit
        *reduced_chisq = polyfit1d(result, xv, yv, errv, 1);
        return TRUE;
    }

    dpint64 n = xv.Nelements();
    dpint64 nn = 0;
    double *y, *sigma, *X;
    double tolerance;
    int i;
    gsl_vector *g;

    gsl_vector *XX = gsl_vector_alloc(n);
    X = (double *)malloc(n * sizeof(double));
    sigma = (double *)malloc(n * sizeof(double));
    y = (double *)malloc(n * sizeof(double));

    for (i = 0; i < n; i++) {
        if (gsl_finite(yv.ValueAt(i))) {
            X[nn] = xv.ValueAt(i);
            y[nn] = yv.ValueAt(i);
            sigma[nn] = errv.ValueAt(i);
            gsl_vector_set(XX, nn, X[nn]);
            nn++;
        }
    }
    n = nn;

    tolerance = 1e-5;

    const gsl_multifit_fdfsolver_type *T;
    gsl_multifit_fdfsolver *s;
    const dpint64 p = result.Naxis(1);

    T = gsl_multifit_fdfsolver_lmsder;
    s = gsl_multifit_fdfsolver_alloc (T, n, p);

    if (s == NULL) { // not enough degrees of freedom
        free(X);
        free(y);
        free(sigma);
        gsl_vector_free(XX);
        return false;
    }

    multifunc_n = (result.Naxis(1) - 2) / 3;
    multifunc_type = (unsigned char *)malloc(multifunc_n * sizeof(unsigned char));
    for (i = 0; i < multifunc_n; i++) multifunc_type[i] = (int)types.ValueAt(i);

    gsl_matrix *covar = gsl_matrix_alloc (p, p);
    struct data d = { n, X, y, sigma};
    gsl_multifit_function_fdf f;

    double *x_init;
    x_init = (double *)malloc(p * sizeof(double));
    for (i = 0; i < p; i++) x_init[i] = result.ValueAt(i);

    gsl_vector_view x = gsl_vector_view_array (x_init, p);

    f.f = &multifunc_f;
    f.df = &multifunc_df;
    f.fdf = &multifunc_fdf;
    f.n = n;
    f.p = p;
    f.params = &d;

    gsl_multifit_fdfsolver_set (s, &f, &x.vector);

    int status;
    dpint64 iter = 0;

    g = gsl_vector_alloc(f.p);

    do {
        iter++;
        status = gsl_multifit_fdfsolver_iterate (s);

        if (status) {
            break;
        }

        status = gsl_multifit_test_delta (s->dx,s->x,tolerance,tolerance);
    } while (status == GSL_CONTINUE && iter < 5000);
//	} while (status == GSL_CONTINUE);

#if GSL_MAJOR_VERSION == 2
    gsl_matrix *J = gsl_matrix_alloc(n, p);
    gsl_multifit_fdfsolver_jac(s, J);
    gsl_multifit_covar (J, 0.0, covar);
    gsl_matrix_free (J);
#else
    gsl_multifit_covar (s->J, 0.0, covar);
#endif

#define FIT(i) gsl_vector_get(s->x, i)
#define ERR(i) sqrt(gsl_matrix_get(covar,i,i))

    result.create(p, 2, R8);

    i = 2;
    result.r8data[0] = FIT(0);
    result.r8data[1] = FIT(1);
    for (i = 2; i < p; i++) result.r8data[i] = FIT(i);

    double chi2dof = sqrt(pow(gsl_blas_dnrm2(s->f), 2.0) / (n-p));
    result.r8data[p] = ERR(0) * chi2dof;
    result.r8data[p+1] = ERR(1) * chi2dof;
    for (i = 2; i < p; i++) result.r8data[p+i] = ERR(i) * chi2dof;

//	printf ("1      = %.5f +/- %.5f\n", FIT(0), ERR(0));
//	printf ("2      = %.5f +/- %.5f\n", FIT(1), ERR(1));
//	printf ("3      = %.5f +/- %.5f\n", FIT(2), ERR(2));
//	printf ("4      = %.5f +/- %.5f\n", FIT(3), ERR(3));

    double chi = gsl_blas_dnrm2(s->f);
//	printf("chisq/dof = %g\n",  pow(chi, 2.0)/ (n - p));

//	printf ("status = %s\n", gsl_strerror (status));

    gsl_multifit_fdfsolver_free (s);
    gsl_matrix_free(covar);
    gsl_vector_free(XX);
    free(X);
    free(sigma);
    free(y);
    free(x_init);
    free(multifunc_type);

    *reduced_chisq = pow(chi, 2.0)/ (n - p);

    return (status == GSL_SUCCESS);
}
/* Fit a 2D gaussian; initial estimates to be supplied in result */

int gauss2d_x, gauss2d_y; // size of the 2D array

/* The 2D gauss function itself */
int gauss2d_f (const gsl_vector * x, void *params,
        gsl_vector * f)
{
  dpint64 n = ((struct data *)params)->n;
  double *X = ((struct data *)params)->X;
  double *y = ((struct data *)params)->y;
  double *sigma = ((struct data *) params)->sigma;

  double offset  = gsl_vector_get (x, 0); // constant offset
  double height  = gsl_vector_get (x, 1); // height of gaussian
  double xcenter = gsl_vector_get (x, 2); // center_x
  double ycenter = gsl_vector_get (x, 3); // center_y
  double fwhm1   = gsl_vector_get (x, 4); // fwhm1
  double fwhm2   = gsl_vector_get (x, 5); // fwhm2
  double angle   = gsl_vector_get (x, 6); // angle
//  angle = angle / 180. * M_PI;

  dpint64 i;
    double xc = cos(angle);
    double xs = sin(angle);
  double xx, yy;
  xx = 0.0;
  yy = 1.0;

    for (i = 0; i < gauss2d_x*gauss2d_y; i++)
    {
        xx++;
        if (xx > gauss2d_x) {
            xx = 1.;
            yy++;
        }
      double dx2 = ((double)xx - xcenter) * xc + ((double)yy - ycenter) * xs;
      double dy2 = -((double)xx - xcenter) * xs + ((double)yy - ycenter) * xc;
      double Yi = height*exp(-FOUR_LN2*dx2*dx2/(fwhm1*fwhm1)-FOUR_LN2*dy2*dy2/(fwhm2*fwhm2) )+offset;
      gsl_vector_set (f, i, (Yi - y[i])/sigma[i]);
    }

  return GSL_SUCCESS;
}

int gauss2d_df (const gsl_vector * x, void *params,
         gsl_matrix * J)
{
  dpint64 n = ((struct data *)params)->n;
  double *X = ((struct data *)params)->X;
  double *sigma = ((struct data *) params)->sigma;

  double offset  = gsl_vector_get (x, 0); // constant offset
  double height  = gsl_vector_get (x, 1); // height of gaussian
  double xcenter = gsl_vector_get (x, 2); // center_x
  double ycenter = gsl_vector_get (x, 3); // center_y
  double fwhm1   = gsl_vector_get (x, 4); // fwhm1
  double fwhm2   = gsl_vector_get (x, 5); // fwhm2
  double angle   = gsl_vector_get (x, 6); // angle
//  angle = angle / 180. * M_PI;

  dpint64 i = 0;

  double xc = cos(angle);
  double xs = sin(angle);
  double fwhm1_2 = fwhm1 * fwhm1;
  double fwhm2_2 = fwhm2 * fwhm2;
  double xx, yy;
  xx = 0.0;
  yy = 1.0;

    for (i = 0; i < gauss2d_x*gauss2d_y; i++)
    {
        xx++;
        if (xx > gauss2d_x) {
            xx = 1.;
            yy++;
        }
      /* Jacobian matrix J(i,j) = dfi / dxj, */
      /* where fi = (Yi - yi)/sigma[i],      */
      /* Yi = y=A*exp[-(Xi-xc)^2/(2*w*w)]+B  */
      /* and the xj are the parameters (B,A,C,w) */

      double s=sigma[i];
      double dx = (xx - xcenter) * xc + (yy - ycenter) * xs;
      double dy = -(xx - xcenter) * xs + (yy - ycenter) * xc;
      double Yi = exp(-FOUR_LN2*dx*dx/fwhm1_2-FOUR_LN2*dy*dy/fwhm2_2);

      gsl_matrix_set (J, i, 0, 1.0 / s);
      gsl_matrix_set (J, i, 1, Yi / s);
      Yi *= height * FOUR_LN2 * 2.0;
      gsl_matrix_set (J, i, 2, Yi * (dx/fwhm1_2*xc - dy/fwhm2_2*xs) / s);
      gsl_matrix_set (J, i, 3, Yi * (dx/fwhm1_2*xs + dy/fwhm2_2*xc) / s);
      gsl_matrix_set (J, i, 4, Yi * dx*dx/(fwhm1_2*fwhm1) / s);
      gsl_matrix_set (J, i, 5, Yi * dy*dy/(fwhm2_2*fwhm2) / s);
      gsl_matrix_set (J, i, 6, Yi * (dx*dy/fwhm1_2 + dy*dx/fwhm2_2) / s);
    }
  return GSL_SUCCESS;
}

int gauss2d_fdf (const gsl_vector * x, void *params,
          gsl_vector * f, gsl_matrix * J)
{
  gauss2d_f (x, params, f);
  gauss2d_df (x, params, J);

  return GSL_SUCCESS;
}

double gauss2dfit(Fits &result, const Fits &xv, const Fits &yv, const Fits &errv) {
    const dpint64 n = xv.Nelements();
    dpint64 nn = 0;
    double *y, *sigma, *X;
    int i;

    if (n < 9) return 0.0;
    gauss2d_x = xv.Naxis(1);
    gauss2d_y = xv.Naxis(2);
    gsl_vector *XX = gsl_vector_alloc(n);
    X = (double *)malloc(n * sizeof(double));
    sigma = (double *)malloc(n * sizeof(double));
    y = (double *)malloc(n * sizeof(double));

    for (i = 0; i < n; i++) {
        if (gsl_finite(yv.ValueAt(i))) {
            X[i] = xv.ValueAt(i);
            y[i] = yv.ValueAt(i);
            sigma[i] = errv.ValueAt(i);
        } else {
            X[i] = xv.ValueAt(i);
            y[i] = 0.0;
            sigma[i] = 1e100;
            nn++;
        }
        gsl_vector_set(XX, i, X[i]);
    }

    const gsl_multifit_fdfsolver_type *T;
    gsl_multifit_fdfsolver *s;
    const dpint64 p = 7;

    T = gsl_multifit_fdfsolver_lmsder;
    s = gsl_multifit_fdfsolver_alloc (T, n, p);

    if (s == NULL) { // not enough degrees of freedom
        free(X);
        free(y);
        free(sigma);
        gsl_vector_free(XX);
        return 0.0;
    }

    gsl_matrix *covar = gsl_matrix_alloc (p, p);
    struct data d = { n, X, y, sigma};
    gsl_multifit_function_fdf f;

    double x_init[7];
    for (i = 0; i < 7; i++) x_init[i] = result.ValueAt(i);

    gsl_vector_view x = gsl_vector_view_array (x_init, p);

    f.f = &gauss2d_f;
    f.df = &gauss2d_df;
    f.fdf = &gauss2d_fdf;
    f.n = n;
    f.p = p;
    f.params = &d;

    gsl_multifit_fdfsolver_set (s, &f, &x.vector);

    int status;
    dpint64 iter = 0;

    do {
        iter++;
        status = gsl_multifit_fdfsolver_iterate (s);

        if (status)
            break;

        status = gsl_multifit_test_delta (s->dx,s->x,1e-5,1e-5);
    } while (status == GSL_CONTINUE && iter < 50);

#if GSL_MAJOR_VERSION == 2
    gsl_matrix *J = gsl_matrix_alloc(n, p);
    gsl_multifit_fdfsolver_jac(s, J);
    gsl_multifit_covar (J, 0.0, covar);
    gsl_matrix_free (J);
#else
    gsl_multifit_covar (s->J, 0.0, covar);
#endif

#define FIT(i) gsl_vector_get(s->x, i)
#define ERR(i) sqrt(gsl_matrix_get(covar,i,i))

    result.create(7, 2, R8);

    result.r8data[0] = FIT(0);
    result.r8data[1] = FIT(1);
    result.r8data[2] = fabs(FIT(2));
    result.r8data[3] = fabs(FIT(3));
    result.r8data[4] = FIT(4);
    result.r8data[5] = FIT(5);
    result.r8data[6] = FIT(6) * 180. / M_PI;

    double chi2dof = sqrt(pow(gsl_blas_dnrm2(s->f), 2.0) / (n-p));
    result.r8data[7] = ERR(0) * chi2dof;
    result.r8data[8] = ERR(1) * chi2dof;
    result.r8data[9] = ERR(2) * chi2dof;
    result.r8data[10] = ERR(3) * chi2dof;
    result.r8data[11] = ERR(4) * chi2dof;
    result.r8data[12] = ERR(5) * chi2dof;
    result.r8data[13] = ERR(6) * 180. / M_PI * chi2dof;

//	printf ("1      = %.5f +/- %.5f\n", FIT(0), ERR(0));
// 	printf ("2      = %.5f +/- %.5f\n", FIT(1), ERR(1));
// 	printf ("3      = %.5f +/- %.5f\n", FIT(2), ERR(2));
// 	printf ("4      = %.5f +/- %.5f\n", FIT(3), ERR(3));
// 	printf ("5      = %.5f +/- %.5f\n", FIT(4), ERR(4));
// 	printf ("6      = %.5f +/- %.5f\n", FIT(5), ERR(5));
// 	printf ("7      = %.5f +/- %.5f\n", FIT(6), ERR(6));

    double chi = gsl_blas_dnrm2(s->f);
//	printf("chisq/dof = %g\n",  pow(chi, 2.0)/ (n - p));

//	printf ("status = %s\n", gsl_strerror (status));

    gsl_multifit_fdfsolver_free (s);
    gsl_vector_free(XX);
    free(X);
    free(sigma);
    free(y);
    gsl_matrix_free(covar);

    return pow(chi, 2.0)/ (n - p - nn);
}

double gauss2dsimplefit(Fits &result, Fits &yv, int x, int y, int w) {
    Fits subarray;

    while (x-w < 1) x++;
    while (y-w < 1) y++;
    while (x+w > yv.Naxis(1)) x--;
    while (y+w > yv.Naxis(2)) y--;
    yv.extractRange(subarray, x-w, x+w, y-w, y+w, 1, 1);
    Fits xdummy;
    xdummy.copy(subarray);
    xdummy.cblank();
    xdummy.smooth(4);
    int xguess, yguess;
    double maxguess, offsetguess, fwhm1guess, fwhm2guess;

    maxguess = xdummy.max_pos(&xguess, &yguess);

    maxguess = subarray.ValueAt(subarray.F_I(xguess, yguess));
    offsetguess = subarray.get_median();
    maxguess -= offsetguess;

    Fits xline, yline;
    xline.create(w*2+1, 1, R4);
    yline.create(w*2+1, 1, R4);
    int i;
    for (i = 0; i < w*2+1; i++) {
        xline.r4data[i] = i+1;
        yline.r4data[i] = subarray.ValueAt(subarray.F_I(xguess, i+1));
    }
//	subarray.extractRange(yline, xguess, xguess, -1, -1, 1, 1);
    Fits err;
    err.copy(xline);
    err *= 0.;
    err += 1.;
    result.create(4, 1, R8);
    result.r8data[0] = offsetguess;
    result.r8data[1] = maxguess;
    result.r8data[2] = xguess;
    result.r8data[3] = 1.;
    gaussfit(result, xline, yline, err);
    fwhm1guess = result.r8data[3];
    if (fwhm1guess > w) fwhm1guess = w;
    offsetguess = result.r8data[0];

    for (i = 0; i < w*2+1; i++) {
        yline.r4data[i] = subarray.ValueAt(subarray.F_I(i+1, yguess));
    }
//	subarray.extractRange(yline, -1, -1, yguess, yguess, 1, 1);
    result.r8data[0] = offsetguess;
    result.r8data[1] = maxguess;
    result.r8data[2] = yguess;
    result.r8data[3] = 1.;
    gaussfit(result, xline, yline, err);
    fwhm2guess = result.r8data[3];
    if (fwhm2guess > w) fwhm2guess = w;
    offsetguess = (offsetguess + result.r8data[0]) / 2.;

    result.create(7, 1, R8);
    result.r8data[0] = offsetguess;
    result.r8data[1] = maxguess;
    result.r8data[2] = xguess;
    result.r8data[3] = yguess;
    result.r8data[4] = fwhm1guess;
    result.r8data[5] = fwhm2guess;
    result.r8data[6] = 0.;

    err.copy(subarray);
    err *= 0.;
    err += .1;

    xdummy.copy(subarray);

    double chisq;
    chisq = gauss2dfit(result, xdummy, subarray, err);
    result.r8data[2] += (double)(x - w - 1);
    result.r8data[3] += (double)(y - w - 1);

    result.extensionType = BINTABLE;

    return chisq;
}

/* Fit a 2D moffat; initial estimates to be supplied in result */

int moffat2d_x, moffat2d_y; // size of the 2D array

/* The 2D moffat function itself */
int moffat2d_f (const gsl_vector * x, void *params,
        gsl_vector * f)
{
  dpint64 n = ((struct data *)params)->n;
  double *X = ((struct data *)params)->X;
  double *y = ((struct data *)params)->y;
  double *sigma = ((struct data *) params)->sigma;

  double offset  = gsl_vector_get (x, 0); // constant offset
  double height  = gsl_vector_get (x, 1); // height of moffat
  double power   = gsl_vector_get (x, 2); // power of moffat
/*
  double xcenter = gsl_vector_get (x, 3); // center_x
  double ycenter = gsl_vector_get (x, 4); // center_y
  double fwhm1   = gsl_vector_get (x, 5); // fwhm1
  double fwhm2   = gsl_vector_get (x, 6); // fwhm2
  double angle   = gsl_vector_get (x, 7); // angle
*/
//  angle = angle / 180. * M_PI;
  double xcenter = 129.;
  double ycenter = 129.;
  double fwhm1 = 10.;
  double fwhm2 = 20.;
  double angle = 30. * M_PI / 180.;

  dpint64 i;
    double xc = cos(angle);
    double xs = sin(angle);
  double xx, yy;
  xx = 0.0;
  yy = 1.0;

    for (i = 0; i < moffat2d_x*moffat2d_y; i++)
    {
        xx++;
        if (xx > moffat2d_x) {
            xx = 1.;
            yy++;
        }
      double dx2 = ((double)xx - xcenter) * xc + ((double)yy - ycenter) * xs;
      double dy2 = -((double)xx - xcenter) * xs + ((double)yy - ycenter) * xc;
      double Yi = height/pow(1.0+4.0*dx2*dx2/(fwhm1*fwhm1)+4.0*dy2*dy2/(fwhm2*fwhm2), power)+offset;
      gsl_vector_set (f, i, (Yi - y[i])/sigma[i]);
    }

  return GSL_SUCCESS;
}

int moffat2d_df (const gsl_vector * x, void *params,
         gsl_matrix * J)
{
  dpint64 n = ((struct data *)params)->n;
  double *X = ((struct data *)params)->X;
  double *sigma = ((struct data *) params)->sigma;

  double offset  = gsl_vector_get (x, 0); // constant offset
  double height  = gsl_vector_get (x, 1); // height of moffat
  double power   = gsl_vector_get (x, 2); // power of moffat
/*
  double xcenter = gsl_vector_get (x, 3); // center_x
  double ycenter = gsl_vector_get (x, 4); // center_y
  double fwhm1   = gsl_vector_get (x, 5); // fwhm1
  double fwhm2   = gsl_vector_get (x, 6); // fwhm2
  double angle   = gsl_vector_get (x, 7); // angle
//  angle = angle / 180. * M_PI;
*/
  double xcenter = 129.;
  double ycenter = 129.;
  double fwhm1 = 10.;
  double fwhm2 = 20.;
  double angle = 30. * M_PI / 180.;

  dpint64 i = 0;

  double xc = cos(angle);
  double xs = sin(angle);
  double fwhm1_2 = fwhm1 * fwhm1;
  double fwhm2_2 = fwhm2 * fwhm2;
  double xx, yy;
  xx = 0.0;
  yy = 1.0;

    for (i = 0; i < moffat2d_x*moffat2d_y; i++)
    {
        xx++;
        if (xx > moffat2d_x) {
            xx = 1.;
            yy++;
        }
      /* Jacobian matrix J(i,j) = dfi / dxj, */
      /* where fi = (Yi - yi)/sigma[i],      */
      /* Yi = y=A*exp[-(Xi-xc)^2/(2*w*w)]+B  */
      /* and the xj are the parameters (B,A,C,w) */

      double s=sigma[i];
      double dx = (xx - xcenter) * xc + (yy - ycenter) * xs;
      double dy = -(xx - xcenter) * xs + (yy - ycenter) * xc;
      double u = dx*dx/fwhm1_2 + dy*dy/fwhm2_2;
      double Yi = 1.0+4.0*u;

      gsl_matrix_set (J, i, 0, 1.0 / s);
      gsl_matrix_set (J, i, 1, pow(Yi, -power) / s);
      Yi = -power * height * pow(Yi, -power-1.0);
      gsl_matrix_set (J, i, 2, Yi / s);
//      gsl_matrix_set (J, i, 3, 4.0 * Yi * (-2.0*dx*xc/fwhm1_2 + 2.0*dy*xs/fwhm2_2) / s);
//      gsl_matrix_set (J, i, 4, 4.0 * Yi * (-2.0*dx*xs/fwhm1_2 - 2.0*dy*xc/fwhm2_2) / s);
//      gsl_matrix_set (J, i, 5, 4.0 * Yi * (-2.0*dx*dx/fwhm1_2/fwhm1) / s);
//      gsl_matrix_set (J, i, 6, 4.0 * Yi * (-2.0*dy*dy/fwhm2_2/fwhm2) / s);
//      gsl_matrix_set (J, i, 7, 4.0 * Yi * (2.0*dx*dy/fwhm1_2 - 2.0*dx*dy/fwhm2_2) / s);
    }
  return GSL_SUCCESS;
}

int moffat2d_fdf (const gsl_vector * x, void *params,
          gsl_vector * f, gsl_matrix * J)
{
  moffat2d_f (x, params, f);
  moffat2d_df (x, params, J);

  return GSL_SUCCESS;
}

double moffat2dfit(Fits &result, const Fits &xv, const Fits &yv, const Fits &errv) {
    const dpint64 n = xv.Nelements();
    dpint64 nn = 0;
    double *y, *sigma, *X;
    int i;

    moffat2d_x = xv.Naxis(1);
    moffat2d_y = xv.Naxis(2);
    gsl_vector *XX = gsl_vector_alloc(n);
    X = (double *)malloc(n * sizeof(double));
    sigma = (double *)malloc(n * sizeof(double));
    y = (double *)malloc(n * sizeof(double));

    for (i = 0; i < n; i++) {
        if (gsl_finite(yv.ValueAt(i))) {
            X[i] = xv.ValueAt(i);
            y[i] = yv.ValueAt(i);
            sigma[i] = errv.ValueAt(i);
        } else {
            X[i] = xv.ValueAt(i);
            y[i] = 0.0;
            sigma[i] = 1e100;
            nn++;
        }
        gsl_vector_set(XX, i, X[i]);
    }

    const gsl_multifit_fdfsolver_type *T;
    gsl_multifit_fdfsolver *s;
    const dpint64 p = 3;

    T = gsl_multifit_fdfsolver_lmsder;
    s = gsl_multifit_fdfsolver_alloc (T, n, p);

    if (s == NULL) { // not enough degrees of freedom
        free(X);
        free(y);
        free(sigma);
        gsl_vector_free(XX);
        return 0.0;
    }

    gsl_matrix *covar = gsl_matrix_alloc (p, p);
    struct data d = { n, X, y, sigma};
    gsl_multifit_function_fdf f;
//@
    double x_init[3];
//@
    for (i = 0; i < 3; i++) x_init[i] = result.ValueAt(i);

    gsl_vector_view x = gsl_vector_view_array (x_init, p);

    f.f = &moffat2d_f;
    f.df = &moffat2d_df;
    f.fdf = &moffat2d_fdf;
    f.n = n;
    f.p = p;
    f.params = &d;

    gsl_multifit_fdfsolver_set (s, &f, &x.vector);

    int status;
    dpint64 iter = 0;

    do {
        iter++;
        status = gsl_multifit_fdfsolver_iterate (s);

        if (status)
            break;

        status = gsl_multifit_test_delta (s->dx,s->x,1e-5,1e-5);
    } while (status == GSL_CONTINUE && iter < 50);

#if GSL_MAJOR_VERSION == 2
    gsl_matrix *J = gsl_matrix_alloc(n, p);
    gsl_multifit_fdfsolver_jac(s, J);
    gsl_multifit_covar (J, 0.0, covar);
    gsl_matrix_free (J);
#else
    gsl_multifit_covar (s->J, 0.0, covar);
#endif

#define FIT(i) gsl_vector_get(s->x, i)
#define ERR(i) sqrt(gsl_matrix_get(covar,i,i))

    result.create(8, 2, R8);

    result.r8data[0] = FIT(0);
    result.r8data[1] = FIT(1);
    result.r8data[2] = FIT(2);
//	result.r8data[3] = fabs(FIT(3));
//	result.r8data[4] = fabs(FIT(4));
//	result.r8data[5] = FIT(5);
//	result.r8data[6] = FIT(6);
//	result.r8data[7] = FIT(7) * 180. / M_PI;
/*
    double chi2dof = sqrt(pow(gsl_blas_dnrm2(s->f), 2.0) / (n-p));
    result.r8data[8] = ERR(0) * chi2dof;
    result.r8data[9] = ERR(1) * chi2dof;
    result.r8data[10] = ERR(2) * chi2dof;
    result.r8data[11] = ERR(3) * chi2dof;
    result.r8data[12] = ERR(4) * chi2dof;
    result.r8data[13] = ERR(5) * chi2dof;
    result.r8data[14] = ERR(6) * chi2dof;
    result.r8data[15] = ERR(7) * chi2dof * 180. / M_PI;

    printf ("1      = %.5f +/- %.5f\n", FIT(0), ERR(0));
    printf ("2      = %.5f +/- %.5f\n", FIT(1), ERR(1));
    printf ("3      = %.5f +/- %.5f\n", FIT(2), ERR(2));
    printf ("4      = %.5f +/- %.5f\n", FIT(3), ERR(3));
    printf ("5      = %.5f +/- %.5f\n", FIT(4), ERR(4));
    printf ("6      = %.5f +/- %.5f\n", FIT(5), ERR(5));
    printf ("7      = %.5f +/- %.5f\n", FIT(6), ERR(6));
    printf ("8      = %.5f +/- %.5f\n", FIT(7), ERR(7));
*/
    double chi = gsl_blas_dnrm2(s->f);
/*
    printf("chisq/dof = %g\n",  pow(chi, 2.0)/ (n - p));

    printf ("status = %s\n", gsl_strerror (status));
*/
    gsl_multifit_fdfsolver_free (s);
    gsl_matrix_free(covar);
    gsl_vector_free(XX);
    free(X);
    free(sigma);
    free(y);

    return pow(chi, 2.0)/ (n - p - nn);
}

double moffat2dsimplefit(Fits &result, Fits &yv, int x, int y, int w) {
    Fits subarray;

    while (x-w < 1) x++;
    while (y-w < 1) y++;
    while (x+w > yv.Naxis(1)) x--;
    while (y+w > yv.Naxis(2)) y--;
    yv.extractRange(subarray, x-w, x+w, y-w, y+w, 1, 1);
    Fits xdummy;
    xdummy.copy(subarray);
    xdummy.cblank();
    xdummy.smooth(4);
    int xguess, yguess;
    double maxguess, offsetguess, fwhm1guess, fwhm2guess;

    maxguess = xdummy.max_pos(&xguess, &yguess);
    maxguess = subarray.ValueAt(subarray.F_I(xguess, yguess));
    offsetguess = subarray.get_median();
    maxguess -= offsetguess;

    Fits xline, yline;
    xline.create(w*2+1, 1, R4);
    yline.create(w*2+1, 1, R4);
    int i;
    for (i = 0; i < w*2+1; i++) {
        xline.r4data[i] = i+1;
        yline.r4data[i] = subarray.ValueAt(subarray.F_I(xguess, i+1));
    }
//	subarray.extractRange(yline, xguess, xguess, -1, -1, 1, 1);
    Fits err;
    err.copy(xline);
    err *= 0.;
    err += 1.;
    result.create(4, 1, R8);
    result.r8data[0] = offsetguess;
    result.r8data[1] = maxguess;
    result.r8data[2] = xguess;
    result.r8data[3] = 1.;
    gaussfit(result, xline, yline, err);
    fwhm1guess = result.r8data[3];
    offsetguess = result.r8data[0];

    for (i = 0; i < w*2+1; i++) {
        yline.r4data[i] = subarray.ValueAt(subarray.F_I(i+1, yguess));
    }
//	subarray.extractRange(yline, -1, -1, yguess, yguess, 1, 1);
    result.r8data[0] = offsetguess;
    result.r8data[1] = maxguess;
    result.r8data[2] = yguess;
    result.r8data[3] = 1.;
    gaussfit(result, xline, yline, err);
    fwhm2guess = result.r8data[3];
    offsetguess = (offsetguess + result.r8data[0]) / 2.;

    result.create(8, 1, R8);
    result.r8data[0] = offsetguess;
    result.r8data[1] = maxguess;
    result.r8data[2] = 1.0;
    result.r8data[3] = xguess;
    result.r8data[4] = yguess;
    result.r8data[5] = fwhm1guess;
    result.r8data[6] = fwhm2guess;
    result.r8data[7] = 0.;
/*	result.r8data[0] = 10.0139;
    result.r8data[1] = 2.98608;
    result.r8data[2] = 1.0;
    result.r8data[3] = xguess;
    result.r8data[4] = yguess;
    result.r8data[5] = 6.43649/2.;
    result.r8data[6] = 6.43649/2.;
    result.r8data[7] = 0.;*/

    err.copy(subarray);
    err *= 0.;
    err += 1.;

    xdummy.copy(subarray);

    double chisq;
    chisq = moffat2dfit(result, xdummy, subarray, err);
    result.r8data[3] += (double)(x - w - 1);
    result.r8data[4] += (double)(y - w - 1);

    return chisq;
}

/* Fit multiple 2D gaussians; initial estimates to be supplied in result */

int multigauss2d_x, multigauss2d_y; // size of the 2D array
int multigauss2d_n; // number of gaussians

/* The 2D gaussian functions themselves */
int multigauss2d_f (const gsl_vector * x, void *params, gsl_vector * f) {
    dpint64 n = ((struct data *)params)->n;

    double *X = ((struct data *)params)->X;
    double *y = ((struct data *)params)->y;
    double *sigma = ((struct data *) params)->sigma;

    double offset  = gsl_vector_get (x, 0); // constant offset
    double fwhm    = gsl_vector_get (x, 1); // constant fwhm

    double *height = (double *)malloc(multigauss2d_n * sizeof(double));
    double *xcenter = (double *)malloc(multigauss2d_n * sizeof(double));
    double *ycenter = (double *)malloc(multigauss2d_n * sizeof(double));

    int nnn;

    for (nnn = 0; nnn < multigauss2d_n; nnn++) {
        height[nnn] = gsl_vector_get(x, 2 + nnn*3);
        xcenter[nnn] = gsl_vector_get(x, 2 + nnn*3 + 1);
        ycenter[nnn] = gsl_vector_get(x, 2 + nnn*3 + 2);
    }

    dpint64 i, nn;
    double xx, yy, dx2, dy2, Yi;
    double MINUS_FOUR_LN2_FWHM2 = -FOUR_LN2 / (fwhm * fwhm);

    xx = 0.0;
    yy = 1.0;
    for (i = 0; i < multigauss2d_x*multigauss2d_y; i++) {
        xx++;
        if (xx > multigauss2d_x) {
            xx = 1.;
            yy++;
        }
        Yi = 0.0;
        for (nn = 0; nn < multigauss2d_n; nn++) {
            dx2 = xx - xcenter[nn];
            dy2 = yy - ycenter[nn];
            Yi += height[nn]*exp(MINUS_FOUR_LN2_FWHM2*(dx2*dx2+dy2*dy2))+offset;
        }
        gsl_vector_set (f, i, (Yi - y[i])/sigma[i]);
    }

    free(height);
    free(xcenter);
    free(ycenter);

    return GSL_SUCCESS;
}

int multigauss2d_df (const gsl_vector * x, void *params, gsl_matrix * J) {
    dpint64 n = ((struct data *)params)->n;
    double *X = ((struct data *)params)->X;
    double *sigma = ((struct data *) params)->sigma;

    double offset  = gsl_vector_get (x, 0); // constant offset
    double fwhm    = gsl_vector_get (x, 1); // constant fwhm

    double MINUS_FOUR_LN2_FWHM2 = -FOUR_LN2 / (fwhm * fwhm);
    double EIGHT_LN2_FWHM2 = FOUR_LN2 / (fwhm * fwhm);
    double EIGHT_LN2_FWHM3 = EIGHT_LN2_FWHM2 / fwhm;

    double *height = (double *)malloc(multigauss2d_n * sizeof(double));
    double *xcenter = (double *)malloc(multigauss2d_n * sizeof(double));
    double *ycenter = (double *)malloc(multigauss2d_n * sizeof(double));

    int nnn;

    for (nnn = 0; nnn < multigauss2d_n; nnn++) {
        height[nnn] = gsl_vector_get(x, 2 + nnn*3);
        xcenter[nnn] = gsl_vector_get(x, 2 + nnn*3 + 1);
        ycenter[nnn] = gsl_vector_get(x, 2 + nnn*3 + 2);
    }

    dpint64 i = 0;

    double xx, yy;
    xx = 0.0;
    yy = 1.0;
    double Yi, dx, dy;

    for (i = 0; i < multigauss2d_x*multigauss2d_y; i++) {
        xx++;
        if (xx > multigauss2d_x) {
            xx = 1.;
            yy++;
        }
      /* Jacobian matrix J(i,j) = dfi / dxj, */
      /* where fi = (Yi - yi)/sigma[i],      */
      /* Yi = y=A*exp[-(Xi-xc)^2/(2*w*w)]+B  */
      /* and the xj are the parameters (B,A,C,w) */

        double s=sigma[i];

        gsl_matrix_set (J, i, 0, 1.0 / s);

        Yi = 0.0;
        for (nnn = 0; nnn < multigauss2d_n; nnn++) {
            dx = xx - xcenter[nnn];
            dy = yy - ycenter[nnn];
            Yi += height[nnn]*exp(MINUS_FOUR_LN2_FWHM2*(dx*dx + dy*dy)) * EIGHT_LN2_FWHM3 * (dx*dx+dy*dy);
        }
        gsl_matrix_set (J, i, 1, Yi / s);
        for (nnn = 0; nnn < multigauss2d_n; nnn++) {
            dx = xx - xcenter[nnn];
            dy = yy - ycenter[nnn];
            Yi = exp(MINUS_FOUR_LN2_FWHM2*(dx*dx + dy*dy));
            gsl_matrix_set (J, i, 2 + nnn*3, Yi / s);
            gsl_matrix_set (J, i, 2 + nnn*3 + 1, height[nnn]*Yi*EIGHT_LN2_FWHM2*dx / s);
            gsl_matrix_set (J, i, 2 + nnn*3 + 2, height[nnn]*Yi*EIGHT_LN2_FWHM2*dy / s);
        }
    }

    free(height);
    free(xcenter);
    free(ycenter);

    return GSL_SUCCESS;
}

int multigauss2d_fdf (const gsl_vector * x, void *params,
          gsl_vector * f, gsl_matrix * J)
{
  multigauss2d_f (x, params, f);
  multigauss2d_df (x, params, J);

  return GSL_SUCCESS;
}

double multigauss2dfit(Fits &result, const Fits &yv, const Fits &errv, const int ngauss) {
    const dpint64 n = yv.Nelements();
    dpint64 nn = 0;
    double *y, *sigma, *X;
    int i;

    multigauss2d_x = yv.Naxis(1);
    multigauss2d_y = yv.Naxis(2);
    multigauss2d_n = ngauss;
    gsl_vector *XX = gsl_vector_alloc(n);
    X = (double *)malloc(n * sizeof(double));
    sigma = (double *)malloc(n * sizeof(double));
    y = (double *)malloc(n * sizeof(double));

    for (i = 0; i < n; i++) {
        if (gsl_finite(yv.ValueAt(i))) {
            X[i] = yv.ValueAt(i);
            y[i] = yv.ValueAt(i);
            sigma[i] = errv.ValueAt(i);
        } else {
            X[i] = 0.0;
            y[i] = 0.0;
            sigma[i] = 1e100;
            nn++;
        }
        gsl_vector_set(XX, i, X[i]);
    }

    const gsl_multifit_fdfsolver_type *T;
    gsl_multifit_fdfsolver *s;
    const dpint64 p = 2 + ngauss*3;

    T = gsl_multifit_fdfsolver_lmsder;
    s = gsl_multifit_fdfsolver_alloc (T, n, p);

    if (s == NULL) { // not enough degrees of freedom
        free(X);
        free(y);
        free(sigma);
        gsl_vector_free(XX);
        return 0.0;
    }

    gsl_matrix *covar = gsl_matrix_alloc (p, p);
    struct data d = { n, X, y, sigma};
    gsl_multifit_function_fdf f;

    double *x_init = (double *)malloc(p * sizeof(double));
    for (i = 0; i < p; i++) x_init[i] = result.ValueAt(i);

    gsl_vector_view x = gsl_vector_view_array (x_init, p);

    f.f = &multigauss2d_f;
    f.df = &multigauss2d_df;
    f.fdf = &multigauss2d_fdf;
    f.n = n;
    f.p = p;
    f.params = &d;

    gsl_multifit_fdfsolver_set (s, &f, &x.vector);

    int status;
    dpint64 iter = 0;

    do {
        iter++;
        status = gsl_multifit_fdfsolver_iterate (s);

        if (status)
            break;

        status = gsl_multifit_test_delta (s->dx,s->x,1e-5,1e-5);
    } while (status == GSL_CONTINUE && iter < 50);

#if GSL_MAJOR_VERSION == 2
    gsl_matrix *J = gsl_matrix_alloc(n, p);
    gsl_multifit_fdfsolver_jac(s, J);
    gsl_multifit_covar (J, 0.0, covar);
    gsl_matrix_free (J);
#else
    gsl_multifit_covar (s->J, 0.0, covar);
#endif

#define FIT(i) gsl_vector_get(s->x, i)
#define ERR(i) sqrt(gsl_matrix_get(covar,i,i))

    result.create(p, 2, R8);

    double chi2dof = sqrt(pow(gsl_blas_dnrm2(s->f), 2.0) / (n-p));
    for (i = 0; i < p; i++) {
        result.r8data[i] = gsl_vector_get(s->x, i);
        result.r8data[i+p] = sqrt(gsl_matrix_get(covar,i,i)) * chi2dof;
    }

//	printf ("1      = %.5f +/- %.5f\n", FIT(0), ERR(0));
// 	printf ("2      = %.5f +/- %.5f\n", FIT(1), ERR(1));
// 	printf ("3      = %.5f +/- %.5f\n", FIT(2), ERR(2));
// 	printf ("4      = %.5f +/- %.5f\n", FIT(3), ERR(3));
// 	printf ("5      = %.5f +/- %.5f\n", FIT(4), ERR(4));
// 	printf ("6      = %.5f +/- %.5f\n", FIT(5), ERR(5));
// 	printf ("7      = %.5f +/- %.5f\n", FIT(6), ERR(6));

    double chi = gsl_blas_dnrm2(s->f);
//	printf("chisq/dof = %g\n",  pow(chi, 2.0)/ (n - p));

//	printf ("status = %s\n", gsl_strerror (status));

    gsl_multifit_fdfsolver_free (s);
    gsl_matrix_free(covar);
    gsl_vector_free(XX);
    free(X);
    free(sigma);
    free(y);
    free(x_init);

    return pow(chi, 2.0)/ (n - p - nn);
}

/* The sinc function itself */

int sinc_f (const gsl_vector * x, void *params,
        gsl_vector * f)
{
  dpint64 n = ((struct data *)params)->n;
  double *X = ((struct data *)params)->X;
  double *y = ((struct data *)params)->y;
  double *sigma = ((struct data *) params)->sigma;

  double B = gsl_vector_get (x, 0);
  double A = gsl_vector_get (x, 1);
  double C = gsl_vector_get (x, 2);
  double w = gsl_vector_get (x, 3);

  dpint64 i;
  for (i = 0; i < n; i++)
    {
      double diff=X[i]-C;
      double Yi = A*sinc(diff/w)+B;
      gsl_vector_set (f, i, (Yi - y[i])/sigma[i]);
    }

  return GSL_SUCCESS;
}

int sinc_df (const gsl_vector * x, void *params,
         gsl_matrix * J)
{
  dpint64 n = ((struct data *)params)->n;
  double *X = ((struct data *)params)->X;
  double *sigma = ((struct data *) params)->sigma;

  double A = gsl_vector_get (x, 1);
  double C = gsl_vector_get (x, 2);
  double w = gsl_vector_get (x, 3);

  dpint64 i;

  for (i = 0; i < n; i++)
    {
      /* Jacobian matrix J(i,j) = dfi / dxj, */
      /* where fi = (Yi - yi)/sigma[i],      */
      /* Yi = y=A*exp[-(Xi-xc)^2/(2*w*w)]+B  */
      /* and the xj are the parameters (B,A,C,w) */

      double s=sigma[i];
      double arg=(X[i]-C) / w;

      gsl_matrix_set (J, i, 0, 1.0 / s);
      gsl_matrix_set (J, i, 1, sinc(arg) / s);
      gsl_matrix_set (J, i, 2, -A / w * cosc(arg) / s);
      gsl_matrix_set (J, i, 3, A / w * (sinc(arg) - cos(arg)) / s);
    }
  return GSL_SUCCESS;
}

int sinc_fdf (const gsl_vector * x, void *params,
          gsl_vector * f, gsl_matrix * J)
{
  sinc_f (x, params, f);
  sinc_df (x, params, J);

  return GSL_SUCCESS;
}

double sincfit(Fits &result, const Fits &xv, const Fits &yv, const Fits &errv) {
    dpint64 n = xv.Nelements();
    dpint64 nn = 0;
    double *y, *sigma, *X;
    int i;

    gsl_vector *XX = gsl_vector_alloc(n);
    X = (double *)malloc(n * sizeof(double));
    sigma = (double *)malloc(n * sizeof(double));
    y = (double *)malloc(n * sizeof(double));

    for (i = 0; i < n; i++) {
        if (gsl_finite(yv.ValueAt(i))) {
            X[nn] = xv.ValueAt(i);
            y[nn] = yv.ValueAt(i);
            sigma[nn] = errv.ValueAt(i);
            gsl_vector_set(XX, nn, X[nn]);
            nn++;
        }
    }
    n = nn;

    const gsl_multifit_fdfsolver_type *T;
    gsl_multifit_fdfsolver *s;
    const dpint64 p = 4;

    T = gsl_multifit_fdfsolver_lmsder;
    s = gsl_multifit_fdfsolver_alloc (T, n, p);

    if (s == NULL) { // not enough degrees of freedom
        free(X);
        free(y);
        free(sigma);
        gsl_vector_free(XX);
        return 0.0;
    }

    gsl_matrix *covar = gsl_matrix_alloc (p, p);
    struct data d = { n, X, y, sigma};
    gsl_multifit_function_fdf f;

    double x_init[4];
    for (i = 0; i < 4; i++) x_init[i] = result.ValueAt(i);

    gsl_vector_view x = gsl_vector_view_array (x_init, p);

    f.f = &sinc_f;
    f.df = &sinc_df;
    f.fdf = &sinc_fdf;
    f.n = n;
    f.p = p;
    f.params = &d;

    gsl_multifit_fdfsolver_set (s, &f, &x.vector);

    int status;
    dpint64 iter = 0;

    do {
        iter++;
        status = gsl_multifit_fdfsolver_iterate (s);
        if (status)
            break;

        status = gsl_multifit_test_delta (s->dx,s->x,1e-5,1e-5);
    } while (status == GSL_CONTINUE && iter < 50);

#if GSL_MAJOR_VERSION == 2
    gsl_matrix *J = gsl_matrix_alloc(n, p);
    gsl_multifit_fdfsolver_jac(s, J);
    gsl_multifit_covar (J, 0.0, covar);
    gsl_matrix_free (J);
#else
    gsl_multifit_covar (s->J, 0.0, covar);
#endif

#define FIT(i) gsl_vector_get(s->x, i)
#define ERR(i) sqrt(gsl_matrix_get(covar,i,i))

    result.create(4, 2, R8);

    result.r8data[0] = FIT(0);
    result.r8data[1] = FIT(1);
    result.r8data[2] = FIT(2);
    result.r8data[3] = FIT(3);

    double chi2dof = sqrt(pow(gsl_blas_dnrm2(s->f), 2.0) / (n-p));
    result.r8data[4] = ERR(0) * chi2dof;
    result.r8data[5] = ERR(1) * chi2dof;
    result.r8data[6] = ERR(2) * chi2dof;
    result.r8data[7] = ERR(3) * chi2dof;

//	printf ("1      = %.5f +/- %.5f\n", FIT(0), ERR(0));
//	printf ("2      = %.5f +/- %.5f\n", FIT(1), ERR(1));
//	printf ("3      = %.5f +/- %.5f\n", FIT(2), ERR(2));
//	printf ("4      = %.5f +/- %.5f\n", FIT(3), ERR(3));

    double chi = gsl_blas_dnrm2(s->f);
//	printf("chisq/dof = %g\n",  pow(chi, 2.0)/ (n - p));

//	printf ("status = %s\n", gsl_strerror (status));

    gsl_multifit_fdfsolver_free (s);
    gsl_matrix_free(covar);
    gsl_vector_free(XX);
    free(X);
    free(sigma);
    free(y);

    return pow(chi, 2.0)/ (n - p);
}

/* The sin function itself */

int sin_f (const gsl_vector * x, void *params,
        gsl_vector * f)
{
  dpint64 n = ((struct data *)params)->n;
  double *X = ((struct data *)params)->X;
  double *y = ((struct data *)params)->y;
  double *sigma = ((struct data *) params)->sigma;

  double B = gsl_vector_get (x, 0);
  double A = gsl_vector_get (x, 1);
  double phi = gsl_vector_get (x, 2);

  dpint64 i;
  for (i = 0; i < n; i++)
    {
      double Yi = A*sin(X[i] + phi)+B;
      gsl_vector_set (f, i, (Yi - y[i])/sigma[i]);
    }

  return GSL_SUCCESS;
}

int sin_df (const gsl_vector * x, void *params,
         gsl_matrix * J)
{
  dpint64 n = ((struct data *)params)->n;
  double *X = ((struct data *)params)->X;
  double *sigma = ((struct data *) params)->sigma;

  double A = gsl_vector_get (x, 1);
  double phi = gsl_vector_get (x, 2);

  dpint64 i;

  for (i = 0; i < n; i++)
    {
      /* Jacobian matrix J(i,j) = dfi / dxj, */
      /* where fi = (Yi - yi)/sigma[i],      */
      /* Yi = y=A*exp[-(Xi-xc)^2/(2*w*w)]+B  */
      /* and the xj are the parameters (B,A,C,w) */

      double s=sigma[i];

      gsl_matrix_set (J, i, 0, 1.0 / s);
      gsl_matrix_set (J, i, 1, sin(X[i] + phi) / s);
      gsl_matrix_set (J, i, 2, A * cos(X[i] + phi) / s);
    }
  return GSL_SUCCESS;
}

int sin_fdf (const gsl_vector * x, void *params,
          gsl_vector * f, gsl_matrix * J)
{
  sin_f (x, params, f);
  sin_df (x, params, J);

  return GSL_SUCCESS;
}

double sinfit(Fits &result, const Fits &xv, const Fits &yv, const Fits &errv) {
    dpint64 n = xv.Nelements();
    dpint64 nn = 0;
    double *y, *sigma, *X;
    int i;

    gsl_vector *XX = gsl_vector_alloc(n);
    X = (double *)malloc(n * sizeof(double));
    sigma = (double *)malloc(n * sizeof(double));
    y = (double *)malloc(n * sizeof(double));

    for (i = 0; i < n; i++) {
        if (gsl_finite(yv.ValueAt(i))) {
            X[nn] = xv.ValueAt(i);
            y[nn] = yv.ValueAt(i);
            sigma[nn] = errv.ValueAt(i);
            gsl_vector_set(XX, nn, X[nn]);
            nn++;
        }
    }
    n = nn;

    const gsl_multifit_fdfsolver_type *T;
    gsl_multifit_fdfsolver *s;
    const dpint64 p = 3;

    T = gsl_multifit_fdfsolver_lmsder;
    s = gsl_multifit_fdfsolver_alloc (T, n, p);

    if (s == NULL) { // not enough degrees of freedom
        free(X);
        free(y);
        free(sigma);
        gsl_vector_free(XX);
        return 0.0;
    }

    gsl_matrix *covar = gsl_matrix_alloc (p, p);
    struct data d = { n, X, y, sigma};
    gsl_multifit_function_fdf f;

    double x_init[3];
    for (i = 0; i < 3; i++) x_init[i] = result.ValueAt(i);

    gsl_vector_view x = gsl_vector_view_array (x_init, p);

    f.f = &sin_f;
    f.df = &sin_df;
    f.fdf = &sin_fdf;
    f.n = n;
    f.p = p;
    f.params = &d;

    gsl_multifit_fdfsolver_set (s, &f, &x.vector);

    int status;
    dpint64 iter = 0;

    do {
        iter++;
        status = gsl_multifit_fdfsolver_iterate (s);
        if (status)
            break;

        status = gsl_multifit_test_delta (s->dx,s->x,1e-5,1e-5);
    } while (status == GSL_CONTINUE && iter < 50);

#if GSL_MAJOR_VERSION == 2
    gsl_matrix *J = gsl_matrix_alloc(n, p);
    gsl_multifit_fdfsolver_jac(s, J);
    gsl_multifit_covar (J, 0.0, covar);
    gsl_matrix_free (J);
#else
    gsl_multifit_covar (s->J, 0.0, covar);
#endif

#define FIT(i) gsl_vector_get(s->x, i)
#define ERR(i) sqrt(gsl_matrix_get(covar,i,i))

    result.create(3, 2, R8);

    result.r8data[0] = FIT(0);
    result.r8data[1] = FIT(1);
    result.r8data[2] = FIT(2);

    double chi2dof = sqrt(pow(gsl_blas_dnrm2(s->f), 2.0) / (n-p));
    result.r8data[3] = ERR(0) * chi2dof;
    result.r8data[4] = ERR(1) * chi2dof;
    result.r8data[5] = ERR(2) * chi2dof;

//	printf ("1      = %.5f +/- %.5f\n", FIT(0), ERR(0));
//	printf ("2      = %.5f +/- %.5f\n", FIT(1), ERR(1));
//	printf ("3      = %.5f +/- %.5f\n", FIT(2), ERR(2));
//	printf ("4      = %.5f +/- %.5f\n", FIT(3), ERR(3));

    double chi = gsl_blas_dnrm2(s->f);
//	printf("chisq/dof = %g\n",  pow(chi, 2.0)/ (n - p));

//	printf ("status = %s\n", gsl_strerror (status));

    gsl_multifit_fdfsolver_free (s);
    gsl_matrix_free(covar);
    gsl_vector_free(XX);
    free(X);
    free(sigma);
    free(y);

    return pow(chi, 2.0)/ (n - p);
}

/* The sin function itself */

int sin_f2 (const gsl_vector * x, void *params,
        gsl_vector * f)
{
  dpint64 n = ((struct data *)params)->n;
  double *X = ((struct data *)params)->X;
  double *y = ((struct data *)params)->y;
  double *sigma = ((struct data *) params)->sigma;

  double A = gsl_vector_get (x, 0);
  double phi = gsl_vector_get (x, 1);

  dpint64 i;
  for (i = 0; i < n; i++)
    {
      double Yi = A*sin(X[i] + phi);
      gsl_vector_set (f, i, (Yi - y[i])/sigma[i]);
    }

  return GSL_SUCCESS;
}

int sin_df2 (const gsl_vector * x, void *params,
         gsl_matrix * J)
{
  dpint64 n = ((struct data *)params)->n;
  double *X = ((struct data *)params)->X;
  double *sigma = ((struct data *) params)->sigma;

  double A = gsl_vector_get (x, 0);
  double phi = gsl_vector_get (x, 1);

  dpint64 i;

  for (i = 0; i < n; i++)
    {
      /* Jacobian matrix J(i,j) = dfi / dxj, */
      /* where fi = (Yi - yi)/sigma[i],      */
      /* Yi = y=A*exp[-(Xi-xc)^2/(2*w*w)]+B  */
      /* and the xj are the parameters (B,A,C,w) */

      double s=sigma[i];

      gsl_matrix_set (J, i, 0, sin(X[i] + phi) / s);
      gsl_matrix_set (J, i, 1, A * cos(X[i] + phi) / s);
    }
  return GSL_SUCCESS;
}

int sin_fdf2 (const gsl_vector * x, void *params,
          gsl_vector * f, gsl_matrix * J)
{
  sin_f2 (x, params, f);
  sin_df2 (x, params, J);

  return GSL_SUCCESS;
}

double sinfit2(Fits &result, const Fits &xv, const Fits &yv, const Fits &errv) {
    dpint64 n = xv.Nelements();
    dpint64 nn = 0;
    double *y, *sigma, *X;
    int i;

    gsl_vector *XX = gsl_vector_alloc(n);
    X = (double *)malloc(n * sizeof(double));
    sigma = (double *)malloc(n * sizeof(double));
    y = (double *)malloc(n * sizeof(double));

    for (i = 0; i < n; i++) {
        if (gsl_finite(yv.ValueAt(i))) {
            X[nn] = xv.ValueAt(i);
            y[nn] = yv.ValueAt(i);
            sigma[nn] = errv.ValueAt(i);
            gsl_vector_set(XX, nn, X[nn]);
            nn++;
        }
    }
    n = nn;

    const gsl_multifit_fdfsolver_type *T;
    gsl_multifit_fdfsolver *s;
    const dpint64 p = 2;

    T = gsl_multifit_fdfsolver_lmsder;
    s = gsl_multifit_fdfsolver_alloc (T, n, p);

    if (s == NULL) { // not enough degrees of freedom
        free(X);
        free(y);
        free(sigma);
        gsl_vector_free(XX);
        return 0.0;
    }

    gsl_matrix *covar = gsl_matrix_alloc (p, p);
    struct data d = { n, X, y, sigma};
    gsl_multifit_function_fdf f;

    double x_init[2];
    for (i = 0; i < 2; i++) x_init[i] = result.ValueAt(i);

    gsl_vector_view x = gsl_vector_view_array (x_init, p);

    f.f = &sin_f2;
    f.df = &sin_df2;
    f.fdf = &sin_fdf2;
    f.n = n;
    f.p = p;
    f.params = &d;

    gsl_multifit_fdfsolver_set (s, &f, &x.vector);

    int status;
    dpint64 iter = 0;

    do {
        iter++;
        status = gsl_multifit_fdfsolver_iterate (s);
        if (status)
            break;

        status = gsl_multifit_test_delta (s->dx,s->x,1e-5,1e-5);
    } while (status == GSL_CONTINUE && iter < 50);

#if GSL_MAJOR_VERSION == 2
    gsl_matrix *J = gsl_matrix_alloc(n, p);
    gsl_multifit_fdfsolver_jac(s, J);
    gsl_multifit_covar (J, 0.0, covar);
    gsl_matrix_free (J);
#else
    gsl_multifit_covar (s->J, 0.0, covar);
#endif

#define FIT(i) gsl_vector_get(s->x, i)
#define ERR(i) sqrt(gsl_matrix_get(covar,i,i))

    result.create(2, 2, R8);

    result.r8data[0] = FIT(0);
    result.r8data[1] = FIT(1);

    double chi2dof = sqrt(pow(gsl_blas_dnrm2(s->f), 2.0) / (n-p));
    result.r8data[2] = ERR(0) * chi2dof;
    result.r8data[3] = ERR(1) * chi2dof;

//	printf ("1      = %.5f +/- %.5f\n", FIT(0), ERR(0));
//	printf ("2      = %.5f +/- %.5f\n", FIT(1), ERR(1));
//	printf ("3      = %.5f +/- %.5f\n", FIT(2), ERR(2));
//	printf ("4      = %.5f +/- %.5f\n", FIT(3), ERR(3));

    double chi = gsl_blas_dnrm2(s->f);
//	printf("chisq/dof = %g\n",  pow(chi, 2.0)/ (n - p));

//	printf ("status = %s\n", gsl_strerror (status));

    gsl_multifit_fdfsolver_free (s);
    gsl_matrix_free(covar);
    gsl_vector_free(XX);
    free(X);
    free(sigma);
    free(y);

    return pow(chi, 2.0)/ (n - p);
}

// used in transcoords & transmatrix
double fitrotation(double *trafo, double **cov_matrix, const int nstars, const double *xref, const double *yref, const double *xim, const double *yim, const Fits *master_stars)
{
    gsl_matrix *X, *cov;
    gsl_vector *y, *c;
    double chisq;

    int p = 4;			// number of parameters (x0, y0, f, alpha)

    // GSL allocations
    X = gsl_matrix_alloc(2*nstars, p);
    y = gsl_vector_alloc(2*nstars);
    c = gsl_vector_alloc(p);
    cov = gsl_matrix_alloc(p, p);
    gsl_multifit_linear_workspace *work = gsl_multifit_linear_alloc(2*nstars, p);

    // set image values into y-vector
    int j = 0, i = 0;
    for (i = 0; i < nstars ; i++) {
        gsl_vector_set(y, j, xim[i]);	// x-coordinate
        gsl_vector_set(y, j+1, yim[i]);	// y-coordinate
        j += 2;
    }

    // set reference values into X-matrix
    j = 0;
    for (i = 0; i < nstars ; i++) {
        gsl_matrix_set(X, j,   0, xref[i]);	// x-coordinate
        gsl_matrix_set(X, j,   1, -yref[i]);	// y-coordinate
        gsl_matrix_set(X, j,   2, 1);
        gsl_matrix_set(X, j,   3, 0);
        gsl_matrix_set(X, j+1, 0, yref[i]);	// y-coordinate
        gsl_matrix_set(X, j+1, 1, xref[i]);	// x-coordinate
        gsl_matrix_set(X, j+1, 2, 0);
        gsl_matrix_set(X, j+1, 3, 1);
        j += 2;
    }

    // fit
    gsl_multifit_linear(X, y, c, cov, &chisq, work);

    // copy covariance matrix
    for (i = 0; i < p ; i++) {
        for (j = 0; j < p ; j++) {
            cov_matrix[i][j] = gsl_matrix_get(cov,i,j);
        }
    }

    // calculate parameters
    double pi = acos(-1.0);

    double phi = gsl_vector_get(c, 0);
    double nju = gsl_vector_get(c, 1);
    double x0 = gsl_vector_get(c, 2);
    double y0 = gsl_vector_get(c, 3);

    double a = atan(nju/phi);
    double f = sqrt(phi*phi+nju*nju);

    // set return values
    if (master_stars == NULL) {
        // fitrotation() was called from dpuser-function transmatrix
        trafo[0] = x0;
        trafo[1] = y0;
        trafo[2] = f;
        trafo[3] = a;
    } else {
        // fitrotation() was called from dpuser-function transcoords
        trafo[0] = x0;
        trafo[1] = y0;
        trafo[2] = phi;
        trafo[3] = nju;
    }

    // GSL deallocations
    gsl_multifit_linear_free(work);
    gsl_matrix_free(X);
    gsl_vector_free(y);
    gsl_vector_free(c);
    gsl_matrix_free(cov);

    return chisq;
}

/* Fit a Sersic; initial estimates to be supplied in result */

/* The sersic function itself */
double sersic_fixed_slm = 0.0;

int sersic_f (const gsl_vector * x, void *params,
        gsl_vector * f)
{
  dpint64 n = ((struct data *)params)->n;
  double *X = ((struct data *)params)->X;
  double *y = ((struct data *)params)->y;
  double *sigma = ((struct data *) params)->sigma;

  double c = gsl_vector_get (x, 0);
  double Ie = gsl_vector_get (x, 1);
  double Re = gsl_vector_get (x, 2);
  double slm;
  if (sersic_fixed_slm == 0.0)
      slm   = gsl_vector_get (x, 3);	// slm:   shape of light model
  else
      slm = sersic_fixed_slm;

    // some substitutions
  double bn = 1.9992*slm - 0.3271;
  double slm_inv = 1.0 / slm;

  double /*Ri,*/ RRi, powslmi, Yi;
  for (dpint64 i = 0; i < n; i++)
  {
        RRi = X[i] / Re;
        powslmi = pow(RRi, slm_inv);
        Yi = c + Ie * exp(-1.0 - bn * powslmi);
        gsl_vector_set (f, i, (Yi - y[i])/sigma[i]);
   }

  return GSL_SUCCESS;
}

int sersic_df (const gsl_vector * x, void *params,
         gsl_matrix * J)
{
  dpint64 n = ((struct data *)params)->n;
  double *X = ((struct data *)params)->X;
  double *sigma = ((struct data *) params)->sigma;

  double c = gsl_vector_get (x, 0);
  double Ie = gsl_vector_get (x, 1);
  double Re = gsl_vector_get (x, 2);
  double slm;
  if (sersic_fixed_slm == 0.0)
      slm   = gsl_vector_get (x, 3);	// slm:   shape of light model
  else
      slm = sersic_fixed_slm;
    // substitution
  double bn = 1.9992*slm - 0.3271;
  double slm_inv = 1.0 / slm;
  double Re2 = pow(Re, 2);
  double slm2 = pow(slm, 2);

  double Ri = 0.0, RRi, powslmi, Yi, s, powslmi1;
  for (dpint64 i = 0; i < n; i++)
    {
      /* Jacobian matrix J(i,j) = dfi / dxj, */
      /* where fi = (Yi - yi)/sigma[i],      */
      /* Yi = y=A*exp[-(Xi-xc)^2/(2*w*w)]+B  */
      /* and the xj are the parameters (B,A,C,w) */
      s=sigma[i];

      // some substitutions
      RRi = X[i] / Re;
      powslmi = pow(RRi, slm_inv);
      powslmi1 = pow(RRi, slm_inv - 1.0);
      Yi = exp(-1.0 - bn * powslmi);

     gsl_matrix_set (J, i, 0, ( 1.0 ) / s);																				// dfi/dc
      gsl_matrix_set (J, i, 1, ( Yi ) / s);																				// dfi/dIe
      if (fabs(Ri) > FEPS) {
          gsl_matrix_set (J, i, 2, ( (bn*Yi*Ie*X[i]*powslmi1)/(Re2*slm) ) / s);									// dfi/dRe
          if (sersic_fixed_slm == 0.0) {
                gsl_matrix_set (J, i, 3, ( Yi * Ie * (-1.9992*powslmi + (powslmi*bn*log(RRi))/slm2) ) / s);	// dfi/dslm

          }
      } else {
          gsl_matrix_set (J, i, 2, 0.0);	// dfi/dRe
            if (sersic_fixed_slm == 0.0)
                gsl_matrix_set (J, i, 3, 0.0);	// dfi/dslm
      }
  }
  return GSL_SUCCESS;
}

int sersic_fdf (const gsl_vector * x, void *params,
          gsl_vector * f, gsl_matrix * J)
{
  sersic_f (x, params, f);
  sersic_df (x, params, J);

  return GSL_SUCCESS;
}

double sersicfit(Fits &result, const Fits &xv, const Fits &yv, const Fits &errv) {
    dpint64 n = xv.Nelements();
    dpint64 nn = 0;
    double *y, *sigma, *X;
    int i;

    // check if n-Parameter (called sln in code) should be fixed or not
    // if last value of result (= sln) is negative, it won't be fitted but kept fixed (sign is removed)
    sersic_fixed_slm = 0.0;
    if (result.ValueAt(3) < 0)
        sersic_fixed_slm = -result.ValueAt(3);

    gsl_vector *XX = gsl_vector_alloc(n);
    X = (double *)malloc(n * sizeof(double));
    sigma = (double *)malloc(n * sizeof(double));
    y = (double *)malloc(n * sizeof(double));

    for (i = 0; i < n; i++) {
        if (gsl_finite(yv.ValueAt(i))) {
            X[nn] = xv.ValueAt(i);
            y[nn] = yv.ValueAt(i);
            sigma[nn] = errv.ValueAt(i);
            gsl_vector_set(XX, nn, X[nn]);
            nn++;
        }
    }
    n = nn;

    const gsl_multifit_fdfsolver_type *T;
    gsl_multifit_fdfsolver *s;

    int nrPars;
    if (sersic_fixed_slm == 0.0)
        nrPars = 4;
    else
        nrPars = 3;

    const dpint64 p = nrPars;

    T = gsl_multifit_fdfsolver_lmsder;
    s = gsl_multifit_fdfsolver_alloc (T, n, p);

    if (s == NULL) { // not enough degrees of freedom
        free(X);
        free(y);
        free(sigma);
        gsl_vector_free(XX);
        return 0.0;
    }

    gsl_matrix *covar = gsl_matrix_alloc (p, p);
    struct data d = { n, X, y, sigma};
    gsl_multifit_function_fdf f;

    double *x_init = (double *)malloc(sizeof(double) * nrPars);
    for (i = 0; i < nrPars; i++) x_init[i] = result.ValueAt(i);

    gsl_vector_view x = gsl_vector_view_array (x_init, p);

    f.f = &sersic_f;
    f.df = &sersic_df;
    f.fdf = &sersic_fdf;
    f.n = n;
    f.p = p;
    f.params = &d;
    gsl_multifit_fdfsolver_set (s, &f, &x.vector);

    int status;
    dpint64 iter = 0;

    do {
        iter++;
        status = gsl_multifit_fdfsolver_iterate (s);

        if (status)
            break;

        status = gsl_multifit_test_delta (s->dx,s->x,1e-5,1e-5);
    } while (status == GSL_CONTINUE && iter < 500);

#if GSL_MAJOR_VERSION == 2
    gsl_matrix *J = gsl_matrix_alloc(n, p);
    gsl_multifit_fdfsolver_jac(s, J);
    gsl_multifit_covar (J, 0.0, covar);
    gsl_matrix_free (J);
#else
    gsl_multifit_covar (s->J, 0.0, covar);
#endif

#define FIT(i) gsl_vector_get(s->x, i)
#define ERR(i) sqrt(gsl_matrix_get(covar,i,i))

    result.create(nrPars, 2, R8);
    double chi2dof = sqrt(pow(gsl_blas_dnrm2(s->f), 2.0) / (n-p));

    result.r8data[0] = FIT(0);
    result.r8data[1] = FIT(1);
    result.r8data[2] = FIT(2);

    if (sersic_fixed_slm == 0.0) {
        result.r8data[3] = FIT(3);

        result.r8data[4] = ERR(0) * chi2dof;
        result.r8data[5] = ERR(1) * chi2dof;
        result.r8data[6] = ERR(2) * chi2dof;
        result.r8data[7] = ERR(3) * chi2dof;
    }
    else {
        result.r8data[3] = ERR(0) * chi2dof;
        result.r8data[4] = ERR(1) * chi2dof;
        result.r8data[5] = ERR(2) * chi2dof;
    }



    double chi = gsl_blas_dnrm2(s->f);

    gsl_multifit_fdfsolver_free (s);
    gsl_matrix_free(covar);
    gsl_vector_free(XX);
    free(X);
    free(sigma);
    free(y);
    free(x_init);

    return pow(chi, 2.0)/ (n - p);
}

/* Fit a 2D Sersic-function; initial estimates to be supplied in result */

int sersic2d_x, sersic2d_y; // size of the 2D array

/* The 2D sersic function itself */
/* Reference: Peng et al., AJ 124:266(2002) */
//Alex
int sersic2d_f (const gsl_vector * x, void *params,
                    gsl_vector * f)
{
    dpint64 n = ((struct data *)params)->n;
    double *X = ((struct data *)params)->X;
    double *y = ((struct data *)params)->y;
    double *sigma = ((struct data *) params)->sigma;

    double c     = gsl_vector_get (x, 0);		// c:     constant offset
    double Ie    = gsl_vector_get (x, 1);		// Ie:    Intensity at Re of gaussian
    double Re    = gsl_vector_get (x, 2);		// Re:    eff. radius  that encloses half of total light
    double x0    = gsl_vector_get (x, 3);		// x0:    center_x
    double y0    = gsl_vector_get (x, 4);		// y0:    center_y
    double angle = gsl_vector_get (x, 5);		// angle: rotation
    double q     = gsl_vector_get (x, 6);		// q:     diskiness
    double slm;											// slm:   shape of light model
    if (sersic_fixed_slm == 0.0)
        slm   = gsl_vector_get (x, 7);	// slm:   shape of light model
    else
        slm = sersic_fixed_slm;

    // some substitutions
    double cos_a = cos(angle);
    double sin_a = sin(angle);
    double bn = 1.9992*slm - 0.3271;

    double xx = 0.0, yy = 1.0;
    for (dpint64 i = 0; i < sersic2d_x * sersic2d_y; i++)
    {
        xx++;
        if (xx > sersic2d_x) {
            xx = 1.0;
            yy++;
        }

        // some substitutions
        double xp = ((double)xx - x0) * cos_a + ((double)yy - y0) * sin_a;
        double yp = -((double)xx - x0) * sin_a + ((double)yy - y0) * cos_a;
        double R = sqrt(pow(xp, 2) + pow(yp/q, 2));

        // Sersic-function
        double Yi = c + Ie * exp(-1.0 - bn * pow(R/Re, 1.0/slm) );
        gsl_vector_set (f, i, (Yi - y[i])/sigma[i]);
    }

    return GSL_SUCCESS;
}

//Alex
int sersic2d_df (const gsl_vector * x, void *params,
                     gsl_matrix * J)
{
    dpint64 n = ((struct data *)params)->n;
    double *X = ((struct data *)params)->X;
    double *sigma = ((struct data *) params)->sigma;

    double c     = gsl_vector_get (x, 0);		// c:     constant offset
    double Ie    = gsl_vector_get (x, 1);		// Ie:    Intensity at Re of gaussian
    double Re    = gsl_vector_get (x, 2);		// Re:    eff. radius  that encloses half of total light
    double x0    = gsl_vector_get (x, 3);		// x0:    center_x
    double y0    = gsl_vector_get (x, 4);		// y0:    center_y
    double angle = gsl_vector_get (x, 5);		// angle: rotation
    double q     = gsl_vector_get (x, 6);		// q:     diskiness
    double slm;											// slm:   shape of light model
    if (sersic_fixed_slm == 0.0)
        slm   = gsl_vector_get (x, 7);	// slm:   shape of light model
    else
        slm = sersic_fixed_slm;


    // some substitutions
    double cos_a = cos(angle);
    double sin_a = sin(angle);
    double bn = 1.9992*slm - 0.3271;
    double slm2 = pow(slm, 2);
    double slm_inv = 1.0 / slm;
    double q2 = pow(q, 2);
    double Re2 = pow(Re, 2);

    double sin_a2 = pow(sin_a, 2);
    double cos_a2 = pow(cos_a, 2);

    double xx = 0.0, yy = 1.0;

    for (dpint64 i = 0; i < sersic2d_x * sersic2d_y; i++)
    {
        xx++;
        if (xx > sersic2d_x) {
            xx = 1.0;
            yy++;
        }

        double s=sigma[i];

        // some substitutions
        double xsi = (double)xx - x0;
        double ysi = (double)yy - y0;
        double xpi = xsi * cos_a + ysi * sin_a;
        double ypi = -xsi * sin_a + ysi * cos_a;
        double ypi2 = pow(ypi, 2);
        double Ri = sqrt(pow(xpi, 2) + pow(ypi/q, 2));
        double RRi = Ri / Re;
        double powslmi = pow(RRi, slm_inv);
        double powslmi1 = pow(RRi, slm_inv - 1.0);
        double powslmi2 = pow(RRi, slm_inv - 2.0);
        double Yi = exp(-1.0 - bn * powslmi);
        double bnYiIe = bn*Yi*Ie;

        /* Jacobian matrix J(i,j) = dfi / dxj,                                  */
        /* where fi = (Yi - yi)/sigma[i],                                       */
        /* Yi = c + Ie * exp(-bn * pow(R/Re, 1/slm) - 1)                        */
        /* and the xj are the parameters (c, Ie, Re, bn, x0, y0, angle, q, slm) */
        gsl_matrix_set (J, i, 0, 1.0 / s);																																// dfi/dc
        gsl_matrix_set (J, i, 1, Yi / s);																																// dfi/dIe

        if (fabs(Ri) > FEPS) {
            gsl_matrix_set (J, i, 2, ( (bnYiIe * Ri * powslmi1) / (slm*Re2) ) / s);																			// dfi/dRe
            gsl_matrix_set (J, i, 3, ( bnYiIe * powslmi2/(q2*Re2*slm) * ( q2*xsi*cos_a2 + (q2 - 1)*ysi*cos_a*sin_a + xsi*sin_a2 ) ) / s);	// dfi/dx0
            gsl_matrix_set (J, i, 4, ( bnYiIe*powslmi2/(q2*Re2*slm) * ( q2*ysi*sin_a2 + (q2 - 1)*xsi*cos_a*sin_a + ysi*cos_a2 ) ) / s);	// dfi/dy0
            gsl_matrix_set (J, i, 5, ( -bnYiIe*powslmi2*ypi*xpi/(Re2*slm) * (1.0 - 1.0/q2) ) / s);														// dfi/dangle
            gsl_matrix_set (J, i, 6, ( (bnYiIe * ypi2 * powslmi1) / (pow(q, 3)*Re*slm*Ri) ) / s);														// dfi/dq
            if (sersic_fixed_slm == 0.0)
                gsl_matrix_set (J, i, 7, ( Yi * Ie * (-1.9992*powslmi + (powslmi*bn*log(RRi))/slm2) ) / s);											// dfi/dslm
        }
        else {
            gsl_matrix_set (J, i, 2, 0.0);		// dfi/dRe
            gsl_matrix_set (J, i, 3, 0.0);		// dfi/dx0
            gsl_matrix_set (J, i, 4, 0.0);		// dfi/dy0
            gsl_matrix_set (J, i, 5, 0.0);		// dfi/dangle
            gsl_matrix_set (J, i, 6, 0.0);		// dfi/dq
            if (sersic_fixed_slm == 0.0)
                gsl_matrix_set (J, i, 7, 0.0);	// dfi/dslm
        }
    }
    return GSL_SUCCESS;
}

//Alex
int sersic2d_fdf (const gsl_vector * x, void *params,
                      gsl_vector * f, gsl_matrix * J)
{
    sersic2d_f (x, params, f);
    sersic2d_df (x, params, J);

    return GSL_SUCCESS;
}

//Alex
double sersic2dfit(Fits &result, const Fits &xv, const Fits &yv, const Fits &errv) {
    const dpint64 n = xv.Nelements();
    dpint64 nn = 0;
    double *y, *sigma, *X;
    int i;

    if (n < 16) return 0.0;

    // check if n-Parameter (called sln in code) should be fixed or not
    // if last value of result (= sln) is negative, it won't be fitted but kept fixed (sign is removed)
    sersic_fixed_slm = 0;
    if (result.ValueAt(7) < 0)
        sersic_fixed_slm = -result.ValueAt(7);

    sersic2d_x = xv.Naxis(1);
    sersic2d_y = xv.Naxis(2);
    gsl_vector *XX = gsl_vector_alloc(n);
    X = (double *)malloc(n * sizeof(double));
    sigma = (double *)malloc(n * sizeof(double));
    y = (double *)malloc(n * sizeof(double));

    for (i = 0; i < n; i++) {
        if (gsl_finite(yv.ValueAt(i))) {
            X[i] = xv.ValueAt(i);
            y[i] = yv.ValueAt(i);
            sigma[i] = errv.ValueAt(i);
        } else {
            X[i] = xv.ValueAt(i);
            y[i] = 0.0;
            sigma[i] = 1e100;
            nn++;
        }
        gsl_vector_set(XX, i, X[i]);
    }

    const gsl_multifit_fdfsolver_type *T;
    gsl_multifit_fdfsolver *s;

    int nrPars;
    if (sersic_fixed_slm == 0.0)
        nrPars = 8;
    else
        nrPars = 7;

    const dpint64 p = nrPars;

    T = gsl_multifit_fdfsolver_lmsder;
    s = gsl_multifit_fdfsolver_alloc (T, n, p);

    if (s == NULL) { // not enough degrees of freedom
        free(X);
        free(y);
        free(sigma);
        gsl_vector_free(XX);
        return 0.0;
    }

    gsl_matrix *covar = gsl_matrix_alloc (p, p);
    struct data d = { n, X, y, sigma};
    gsl_multifit_function_fdf f;

    double *x_init = (double *)malloc(sizeof(double) * nrPars);
    for (i = 0; i < nrPars; i++) x_init[i] = result.ValueAt(i);

    gsl_vector_view x = gsl_vector_view_array (x_init, p);

    f.f = &sersic2d_f;
    f.df = &sersic2d_df;
    f.fdf = &sersic2d_fdf;
    f.n = n;
    f.p = p;
    f.params = &d;

    gsl_multifit_fdfsolver_set (s, &f, &x.vector);

    int status;
    dpint64 iter = 0;

    do {
        iter++;
        status = gsl_multifit_fdfsolver_iterate (s);

        if (status)
            break;

        status = gsl_multifit_test_delta (s->dx,s->x,1e-5,1e-5);
    } while (status == GSL_CONTINUE && iter < 50);

#if GSL_MAJOR_VERSION == 2
    gsl_matrix *J = gsl_matrix_alloc(n, p);
    gsl_multifit_fdfsolver_jac(s, J);
    gsl_multifit_covar (J, 0.0, covar);
    gsl_matrix_free (J);
#else
    gsl_multifit_covar (s->J, 0.0, covar);
#endif

#define FIT(i) gsl_vector_get(s->x, i)
#define ERR(i) sqrt(gsl_matrix_get(covar,i,i))

    double chi2dof = sqrt(pow(gsl_blas_dnrm2(s->f), 2.0) / (n-p));

    result.create(nrPars, 2, R8);

    result.r8data[0] = FIT(0);
    result.r8data[1] = FIT(1);
    result.r8data[2] = FIT(2);
    result.r8data[3] = FIT(3);
    result.r8data[4] = FIT(4);
    result.r8data[5] = FIT(5) * 180. / M_PI;
    result.r8data[6] = FIT(6);

    if (sersic_fixed_slm == 0.0) {
        result.r8data[7] = FIT(7);

        result.r8data[8] = ERR(0) * chi2dof;
        result.r8data[9] = ERR(1) * chi2dof;
        result.r8data[10] = ERR(2) * chi2dof;
        result.r8data[11] = ERR(3) * chi2dof;
        result.r8data[12] = ERR(4) * chi2dof;
        result.r8data[13] = ERR(5) * 180. / M_PI * chi2dof;
        result.r8data[14] = ERR(6) * chi2dof;
        result.r8data[15] = ERR(7) * chi2dof;
    } else {
        result.r8data[7] = ERR(0) * chi2dof;
        result.r8data[8] = ERR(1) * chi2dof;
        result.r8data[9] = ERR(2) * chi2dof;
        result.r8data[10] = ERR(3) * chi2dof;
        result.r8data[11] = ERR(4) * chi2dof;
        result.r8data[12] = ERR(5) * 180. / M_PI * chi2dof;
        result.r8data[13] = ERR(6) * chi2dof;
    }

    double chi = gsl_blas_dnrm2(s->f);

    gsl_multifit_fdfsolver_free (s);
    gsl_vector_free(XX);
    free(X);
    free(sigma);
    free(y);
    gsl_matrix_free(covar);
    free(x_init);

    return pow(chi, 2.0)/ (n - p - nn);
}

//Alex
double sersic2dsimplefit(Fits &result, Fits &yv, int x, int y, int w, double fixed_n) {
    // Extract subarray (less computation)
		dp_debug("sersic2dsimplefit: %i %i %i %f", x, y, w, fixed_n);
        Fits subarray;
    while (x-w < 1) x++;
    while (y-w < 1) y++;
    while (x+w > yv.Naxis(1)) x--;
    while (y+w > yv.Naxis(2)) y--;
    yv.extractRange(subarray, x-w, x+w, y-w, y+w, 1, 1);

    // Extract max_pos, max_value, offset
    int xguess, yguess;
    double maxguess, offsetguess;
    subarray.cblank();
    subarray.smooth(4);
    maxguess = subarray.max_pos(&xguess, &yguess);
    maxguess = subarray.ValueAt(subarray.F_I(xguess, yguess));
    offsetguess = subarray.get_median();
    maxguess -= offsetguess;

    yv.extractRange(subarray, x-w, x+w, y-w, y+w, 1, 1);	// reset subarray

    Fits xline, yline, err;

    xline.create(w+1, 1, R4);
    yline.create(w+1, 1, R4);

    err.copy(xline);
    err *= 0.;
    err += 1.;

    // create cut in x-direction, but on half of the data!
    int i = 0;
    for (i = 0; i < w+1; i++) {
        xline.r4data[i] = i+1;
        yline.r4data[i] = subarray.ValueAt(subarray.F_I(xguess, i+1+w));
    }

    result.create(4, 1, R8);
    result.r8data[0] = offsetguess;														//	dp_output("XGUESS\n");
    result.r8data[1] = maxguess;
    result.r8data[2] = 1.0;
    if (fixed_n < 0)
        result.r8data[3] = 1.0;
    else
        result.r8data[3] = -fixed_n;													//	dp_output("estimates: c: %g, Ie: %g, Re: %g, n: %g,\n", result.r8data[0], result.r8data[1], result.r8data[2], result.r8data[3]);
    sersicfit(result, xline, yline, err);
    double c_x = result.r8data[0];
    double Ie_x = result.r8data[1];
    double Re_x = result.r8data[2];
    double n_x = result.r8data[3];														//	dp_output("fitted: c: %g, Ie: %g, Re: %g, n: %g,\n", c_x, Ie_x, Re_x, n_x);

    // create cut in x-direction, but on half of the data!
    for (i = 0; i < w+1; i++) {
        yline.r4data[i] = subarray.ValueAt(subarray.F_I(i+1+w, yguess));
    }

    result.r8data[0] = offsetguess;														//	dp_output("YGUESS\n");
    result.r8data[1] = maxguess;
    result.r8data[2] = 1.0;
    if (fixed_n < 0)
        result.r8data[3] = 1.0;
    else
        result.r8data[3] = -fixed_n;													//	dp_output("estimates: c: %g, Ie: %g, Re: %g, n: %g,\n", result.r8data[0], result.r8data[1], result.r8data[2], result.r8data[3]);
    sersicfit(result, xline, yline, err);
    double c_y = result.r8data[0];
    double Ie_y = result.r8data[1];
    double Re_y = result.r8data[2];
    double n_y = result.r8data[3];														//	dp_output("fitted: c: %g, Ie: %g, Re: %g, n: %g,\n", c_y, Ie_y, Re_y, n_y);

    // estimated 2D-sersic parameters
    result.create(8, 1, R8);															//	dp_output("2D_GUESS\n");
    result.r8data[0] = (c_y + c_x) / 2.0;
    result.r8data[1] = (Ie_y + Ie_x) / 2.0;
    result.r8data[2] = (Re_y + Re_x) / 2.0;
    result.r8data[3] = xguess;
    result.r8data[4] = yguess;
    result.r8data[5] = 0.0;	//angle	M_PI/3.0;//
    result.r8data[6] = 0.7;	// diskiness
    if (fixed_n < 0) {
        result.r8data[7] = (n_y + n_x) / 2.0;
        if (result.r8data[7] > 4.0) result.r8data[7] = 4.0;
        if (result.r8data[7] < 0.5) result.r8data[7] = 0.5;
    } else
        result.r8data[7] = -fixed_n;

//	dp_output("estimates: c: %g, Ie: %g, Re: %g, x0: %g, y0: %g, ang: %g, q: %g, n: %g\n", result.r8data[0], result.r8data[1], result.r8data[2], result.r8data[3], result.r8data[4], result.r8data[5], result.r8data[6], result.r8data[7]);

    err.copy(subarray);
    err *= 0.;
    err += .1;

    double chisq;
    chisq = sersic2dfit(result, subarray, subarray, err);
//	dp_output("fitted: c: %g, Ie: %g, Re: %g, x0: %g, y0: %g, ang: %g, q: %g, n: %g\n", result.r8data[0], result.r8data[1], result.r8data[2], result.r8data[3], result.r8data[4], result.r8data[5], result.r8data[6], result.r8data[7]);
    result.r8data[3] += (double)(x - w - 1);
    result.r8data[4] += (double)(y - w - 1);

    result.extensionType = BINTABLE;

    return chisq;
}

//#include <gsl/gsl_cblas.h>
//#include <gsl/gsl_linalg.h>
//#include <gsl/gsl_permutation.h>

//void polyfit_blas(Fits &result, const Fits &f, const Fits &xv, const Fits &yv, const Fits &errv, const int degree)
//{
//    int deg = 3;
//    int i, j, n, ind_degree, ii_x, ii_y;
//    double val_x, val_y, chisq;
//
//    n = xv.Nelements(); // 9
//
//    gsl_matrix *X = gsl_matrix_alloc(n, n);
//    gsl_vector *ff = gsl_vector_alloc(n),
//               *cc = gsl_vector_alloc(n);
//
//
//
//
//    // X-matrix aufbauen
//    for (i = 0; i < n; i++) {
//        j = 0;
//        val_x = xv.ValueAt(i) + 1;
//        val_y = yv.ValueAt(i) + 1;
//
//    //        for (ind_degree = 0;  ind_degree <= degree; ind_degree++) {
//        for (ind_degree = 1;  ind_degree <= degree; ind_degree++) {
//            ii_x = ind_degree;
//            ii_y = 0;
//            while (ii_x >= 0) {
//double fff = pow(val_x, ii_x)*pow(val_y,ii_y);
//                gsl_matrix_set(X, i, j, fff);
//                ii_x--;
//                ii_y++;
//                j++;    // next term in X-matrix
//            }
//        }
//    }
//
//
//    for(i=0; i<n; i++) {
//        gsl_vector_set(ff, i, f.ValueAt(i));
//    }
//
//    gsl_permutation * p = gsl_permutation_alloc(n);
////    gsl_matrix *my_h= gsl_matrix_alloc(n, n);
//    gsl_matrix *my_hinv= gsl_matrix_alloc (n, n);
//
////    gsl_matrix_set(my_h, 0, 0, 1);
////    gsl_matrix_set(my_h, 0, 1, 2);
////    gsl_matrix_set(my_h, 1, 0, 3);
////    gsl_matrix_set(my_h, 1, 1, 4);
////
//
//    int signum;
//    gsl_linalg_LU_decomp (X, p, &signum);
//    gsl_linalg_LU_invert (X, p, my_hinv);
//
//
//int bbbb = gsl_blas_dgemv(CblasNoTrans, 1.0, my_hinv, ff, 1.0, cc);
//
//    double aa = gsl_matrix_get(my_hinv, 0, 0);
//    aa = gsl_matrix_get(my_hinv, 0, 1);
//    aa = gsl_matrix_get(my_hinv, 1, 0);
//    aa = gsl_matrix_get(my_hinv, 1, 1);
//
//    result.create(n, 1, R8);
//
//    for (i = 0; i < n; i++) {
//        result.r8data[i] = gsl_vector_get(cc, i);
//    }
//
//
//    gsl_permutation_free(p);
//    gsl_matrix_free(X);
//    gsl_matrix_free(my_hinv);
//    gsl_vector_free(ff);
//    gsl_vector_free(cc);
//}

// Alex
/* Fit an N-th order polynomial to 2D data */
double polyfit2d(Fits &result, const Fits &f, const Fits &xv, const Fits &yv, const Fits &errv, const int degree)
{
    // global? oder aufrufende Fkt
    gsl_matrix *X, *cov;
    gsl_vector *y, *w, *c;

    int i, j, n, ind_degree, ii_x, ii_y;
    double val_x, val_y;
    double chisq;
    // degree2 represents the number of terms in the polynomial in function of its degree
    int degree2 = 1;
    for (i = 1; i <= degree; i++) {
        degree2 += i + 1;
    }

    n = xv.Nelements();

    // global? oder aufrufende Fkt
    X = gsl_matrix_alloc(n, degree2);
    y = gsl_vector_alloc(n);
    w = gsl_vector_alloc(n);
    c = gsl_vector_alloc(degree2);
    cov = gsl_matrix_alloc(degree2, degree2);

    for (i = 0; i < n; i++) {
        j = 0;

        if (gsl_finite(f.ValueAt(i))) {
            val_x = xv.ValueAt(i);
            val_y = yv.ValueAt(i);

            for (ind_degree = 0;  ind_degree <= degree; ind_degree++) {
                ii_x = ind_degree;
                ii_y = 0;
                while (ii_x >= 0) {
                    gsl_matrix_set(X, i, j, pow(val_x, ii_x)*pow(val_y,ii_y));
                    ii_x--;
                    ii_y++;
                    j++;    // next term in X-matrix
                }
            }

            gsl_vector_set(y, i, f.ValueAt(i));
            val_x = errv.ValueAt(i);
            gsl_vector_set(w, i, 1.0 / (val_x * val_x));
        } else {
            for (ind_degree = 0;  ind_degree <= degree; ind_degree++) {
                ii_x = ind_degree;
                ii_y = 0;
                while (ii_x >= 0) {
                    gsl_matrix_set(X, i, j, 0.0);
                    ii_x--;
                    ii_y++;
                    j++;    // next term in X-matrix
                }
            }

            gsl_vector_set(y, i, 0.0);
            gsl_vector_set(w, i, 1e100);
        }
    }

    gsl_multifit_linear_workspace *work = gsl_multifit_linear_alloc(n, degree2);
    gsl_multifit_wlinear(X, w, y, c, cov, &chisq, work);
    gsl_multifit_linear_free(work);

    // in aufrufende Fkt verlagern für performance-gewinn

    result.create(degree2, 2, R8);

    for (i = 0; i < degree2; i++) {
        result.r8data[i] = gsl_vector_get(c, i);
    }

    for (i = 0; i < degree2; i++) {
        result.r8data[degree2 + i] = sqrt(gsl_matrix_get(cov, i, i));
    }

    // global? oder aufrufende Fkt
    gsl_matrix_free(X);
    gsl_vector_free(y);
    gsl_vector_free(w);
    gsl_vector_free(c);
    gsl_matrix_free(cov);

    return chisq;
}

// data structure for test2dfit
struct test2dfit_struct {
    double *y;
    double *err;
};

int test2dfit_naxis1, test2dfit_naxis2;

// The function that computes the deviates
int test2dfit_f(int m, int n, double *p, double *dy, double **dvec, void *vars) {
	struct test2dfit_struct *v = (struct test2dfit_struct *) vars;
  double *y = v->y;
  double *sigma = v->err;
	
  double offset  = p[0]; // constant offset
  double fwhm1   = p[1]; // fwhm1
  double fwhm2   = p[2]; // fwhm2
  double angle   = p[3]; // angle

  dpint64 i, j;
  Fits model, g;
  model.create(test2dfit_naxis1, test2dfit_naxis2, R8);
  model.add(offset);

  for (i = 0, j = 4; i < (n-3) / 3; i++, j += 3) {
      g.qgauss(test2dfit_naxis1, test2dfit_naxis2, p[j+1], p[j+2], fwhm1, fwhm2, angle, 6);
      g.mul(p[j]);
      model.add(g);
  }

dp_debug("%f %f %f %f", offset, fwhm1, fwhm2, angle);

    for (i = 0; i < test2dfit_naxis1*test2dfit_naxis2; i++)
    {
			dy[i] = (model[i] - y[i])/sigma[i];
    }

    return 0;
}

/*
hhh=gauss(129,129,10,30,50)
print test2dfit(hhh,hhh*0+.1,[0,10,10,0,1,127,129]),/values

hhh=gauss(129,129,10,30,50)+gauss(180,170,12,28,45)
print test2dfit(hhh,hhh*0+.1,[0,10,10,0,1,127,129,1,178,175]),/values


image='s2/refim.fits'
image = image[73:123,87:147]
starlist = [[34,5],[23,7],[30,12],[44,19],[47,21],[8,15],[14,20],[22,24],[28,25],[17,31],[12,38],[22,42],[31,38],[39,37],[33,45],[43,48],[31,57],[26,52],[26,57],[16,52]]
estimate=fits(4+nelements(starlist)+naxis2(starlist))
estimate[1]=quantile(image,.2)
estimate[2]=4
estimate[3]=4
estimate[4]=0
for i=1, naxis2(starlist) {
estimate[(i-1)*3+5]=image[starlist[1,i],starlist[2,i]]
estimate[(i-1)*3+6]=starlist[1,i]
estimate[(i-1)*3+7]=starlist[2,i]
}
result= test2dfit(image,image*0+1,estimate)
fitimage = image * 0 + result[1]
fwhm=3
for i=1, naxis2(starlist) {
fitimage += gauss(result[(i-1)*3+6], result[(i-1)*3+7], fwhm) * result[(i-1)*3+5]
}

*/

double test2dfit(Fits &result, const Fits &xv, const Fits &yv, const Fits &errv) {
	struct test2dfit_struct v;
    mp_par *pars;
    double *p, *perr;
	mp_result res;
	mp_config config;
	int status;
	int i;
    int npars = result.Nelements();
		
	v.y = (double *)malloc(yv.Nelements() * sizeof(double));
	v.err = (double *)malloc(errv.Nelements() * sizeof(double));
    pars = (mp_par *)malloc(npars * sizeof(mp_par));
    p = (double *)malloc(npars * sizeof(double));
    perr = (double *)malloc(npars * sizeof(double));

	for (i = 0; i < yv.Nelements(); i++) {
		v.y[i] = yv.ValueAt(i);
		v.err[i] = errv.ValueAt(i);
	}
	dp_debug("1\n");
	
    for (i = 0; i < npars; i++) p[i] = result[i];
		
	memset(&res, 0, sizeof(res));
	res.xerror = perr;
    for (i = 0; i < npars; i++) memset(&pars[i], 0, sizeof(mp_par));

// limit fwhm to positive values
    pars[1].limited[0] = 1;
    pars[1].limits[0] = 0.;
    pars[2].limited[0] = 1;
    pars[2].limits[0] = 0.;

// limit angle to -2pi...2pi

    pars[3].limited[0] = 1;
    pars[3].limits[0] = -360.0;
    pars[3].limited[1] = 1;
    pars[3].limits[1] = 360.;

// limit position to +/- 2 pixels
    for (int j = 4; j < npars; j += 3) {
        pars[j+1].limited[0] = 1;
        pars[j+1].limits[0] = p[j+1] - 2.;
        pars[j+1].limited[1] = 1;
        pars[j+1].limits[1] = p[j+1] + 2.;
        pars[j+2].limited[0] = 1;
        pars[j+2].limits[0] = p[j+2] - 2.;
        pars[j+2].limited[1] = 1;
        pars[j+2].limits[1] = p[j+2] + 2.;
    }

	memset(&config, 0, sizeof(config));
	config.epsfcn = 1.19209e-07;

	dp_debug("2\n");
	test2dfit_naxis1 = yv.Naxis(1);
	test2dfit_naxis2 = yv.Naxis(2);
	
    status = mpfit(&test2dfit_f, yv.Nelements(), npars, p, pars, &config, (void *) &v, &res);
		
	dp_debug("3\n");
	free(v.y);
	free(v.err);
		
    dp_debug("straightlinefit: Result is: %f+-%f %f+-%f, chisq = %f\n", p[0], perr[0], p[1], perr[1], res.bestnorm);

    result.create(npars, 2, R8);

    for (i = 0; i < npars; i++) {
        result.r8data[i] = p[i];
        result.r8data[i+npars] = perr[i];
    }

    free(p);
    free(perr);
    free(pars);

		return res.bestnorm;
}

// data structure for sersic2dsmoothfit
struct sersic2dsmooth_struct {
    double *y;
    double *err;
};

int sersic2dsmooth_naxis1, sersic2dsmooth_naxis2;
double sersic2dsmooth_seeing;

int sersic2dsmooth(int m, int n, double *p, double *dy, double **dvec, void *vars) {
    double Re, x0, y0, angle, q, nn;
		double *y, *err;
		int n1, n2;
    int i;
		Fits _tmp;
    struct sersic2dsmooth_struct *v = (struct sersic2dsmooth_struct *) vars;

dp_debug("%f %f %f %f %f %f %f %f", p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]);
// sanity check: If any of the parameters is NaN (or Inf, etc.), bail out
	for (i = 0; i < n; i++) if (!gsl_finite(p[i])) return -1;
	
    Re = p[2];
    x0 = p[3];
    y0 = p[4];
    angle = p[5];
    q = p[6];
    nn = p[7];
    n1 = sersic2dsmooth_naxis1;
    n2 = sersic2dsmooth_naxis2;

    _tmp.sersic(n1, n2, Re, x0, y0, angle, q, nn);
    _tmp.mul(p[1]);
    _tmp.add(p[0]);
    _tmp.smooth(sersic2dsmooth_seeing, TRUE);

	y = v->y;
	err = v->err;
    for (i = 0; i < m; i++) {
        dy[i] = (y[i] - _tmp.r8data[i]) / err[i];
    }

    return 0;
}

// Fit a smoothed 2D sersic, initial estimate to be supplied in result
// if n (i.e. result[7]) is negative, the sersic index n should be kept constant (and the negative of fixed_n)
double sersic2dsmoothfit(Fits &result, Fits &yv, Fits &err, double smooth) {
	struct sersic2dsmooth_struct v;
	double p[8], perr[8];
	mp_result res;
	mp_par pars[8];
	mp_config config;
	int status;
	int i;
		
	v.y = (double *)malloc(yv.Nelements() * sizeof(double));
	v.err = (double *)malloc(err.Nelements() * sizeof(double));

	for (i = 0; i < yv.Nelements(); i++) {
		if (gsl_finite(yv[i]) && gsl_finite(err[i])) {
			v.y[i] = yv[i];
			v.err[i] = err[i];
		} else {
			v.y[i] = 0.;
			v.err[i] = 1e100;
		}
	}
	dp_debug("1\n");
	
	for (i = 0; i <= 7; i++) p[i] = result[i];
		
	memset(&res, 0, sizeof(res));
	res.xerror = perr;
	memset(&pars[0], 0, sizeof(pars));
	
// keep angle between 0 and 360 degrees
	pars[2].limited[0] = 1;
	pars[2].limits[0] = 0.1;
	pars[5].limited[0] = 1;
	pars[5].limits[0] = 0.0;
	pars[5].limited[1] = 1;
	pars[5].limits[1] = 360.;
	pars[6].limited[0] = 1;
	pars[6].limits[0] = 0.1;
	pars[6].limited[1] = 1;
	pars[6].limits[1] = 4.;
	pars[7].limited[0] = 1;
	pars[7].limits[0] = 0.1;

// fix n if requested
	if (p[7] < 0.) {
		pars[7].fixed = 1;
		p[7] = -p[7];
	}
	memset(&config, 0, sizeof(config));
//	config.epsfcn = 1.19209e-07;
	dp_debug("2\n");
	sersic2dsmooth_naxis1 = yv.Naxis(1);
	sersic2dsmooth_naxis2 = yv.Naxis(2);
	sersic2dsmooth_seeing = smooth;
	
	status = mpfit(&sersic2dsmooth, yv.Nelements(), 8, p, pars, &config, (void *) &v, &res);
		
	dp_debug("3\n");
	free(v.y);
	free(v.err);
		
		dp_debug("straightlinefit: Result is: %f+-%f %f+-%f, chisq = %f\n", p[0], perr[0], p[1], perr[1], res.bestnorm);
		
		result.create(8, 2, R8);
		result.r8data[0] = p[0];
		result.r8data[1] = p[1];
		result.r8data[2] = p[2];
		result.r8data[3] = p[3];
		result.r8data[4] = p[4];
		result.r8data[5] = p[5];
		result.r8data[6] = p[6];
		result.r8data[7] = p[7];
		result.r8data[8] = perr[0];
		result.r8data[9] = perr[1];
		result.r8data[10] = perr[2];
		result.r8data[11] = perr[3];
		result.r8data[12] = perr[4];
		result.r8data[13] = perr[5];
		result.r8data[14] = perr[6];
		result.r8data[15] = perr[7];

		return res.bestnorm;
}

// Fit a smoothed sersic
double sersic2dsmoothsimplefit(Fits &result, Fits &yv, Fits &err, int x, int y, int w, double smooth, double fixed_n) {
	double chisq;
	
// get initial estimate from sersic2dsimplefit
	sersic2dsimplefit(result, yv, x, y, w, -1);

	Fits subarray, errsubarray;
	while (x-w < 1) x++;
	while (y-w < 1) y++;
	while (x+w > yv.Naxis(1)) x--;
	while (y+w > yv.Naxis(2)) y--;
	yv.extractRange(subarray, x-w, x+w, y-w, y+w, 1, 1);
	err.extractRange(errsubarray, x-w, x+w, y-w, y+w, 1, 1);

	result.r8data[3] -= (double)(x - w - 1);
	result.r8data[4] -= (double)(y - w - 1);
	dp_debug("sersic2dsmoothsimplefit: Initial estimate is: %f %f %f %f %f %f %f %f\n", result[0], result[1], result[2], result[3], result[4], result[5], result[6], result[7]);
fixed_n = -1.;
	if (fixed_n < 0.) result.setValue(fixed_n, 8, 1);
	chisq = sersic2dsmoothfit(result, subarray, errsubarray, smooth);

	result.r8data[3] += (double)(x - w - 1);
	result.r8data[4] += (double)(y - w - 1);

	return chisq;
}

// data structure for straightlinefit
struct straightlinefit_struct {
    double *x;
		double *y;
		double *err_x;
		double *err_y;
};

int linfunc(int m, int n, double *p, double *dy, double **dvec, void *vars)
{
  int i;
  struct straightlinefit_struct *v = (struct straightlinefit_struct *) vars;
  double *x, *y, *ex, *ey, f;

  x = v->x;
  y = v->y;
	ex = v->err_x;
  ey = v->err_y;

  for (i=0; i<m; i++) {
    f = p[0] + p[1]*x[i];     /* Linear fit function; note f = a + b*x */
    dy[i] = (y[i] - f)/sqrt(ex[i]*ex[i] + ey[i]*ey[i]);
  }

  return 0;
}

// Fit a straight line to data with errors in both coordinates
double straightlinefit(Fits &result, Fits &xv, Fits &yv, Fits &xerr, Fits &yerr) {
// get initial estimate from polyfit1d
    Fits estimate;
		polyfit1d(estimate, xv, yv, yerr, 1);
		dp_debug("straightlinefit: Initial estimate is: %f %f\n", estimate[0], estimate[1]);
		
		struct straightlinefit_struct v;
		double p[2], perr[2];
		mp_result res;
		int status;
		int i;
		
		v.x = (double *)malloc(xv.Nelements() * sizeof(double));
		v.y = (double *)malloc(xv.Nelements() * sizeof(double));
		v.err_x = (double *)malloc(xv.Nelements() * sizeof(double));
		v.err_y = (double *)malloc(xv.Nelements() * sizeof(double));
		for (i = 0; i < xv.Nelements(); i++) {
			v.x[i] = xv[i];
			v.y[i] = yv[i];
			v.err_x[i] = xerr[i];
			v.err_y[i] = yerr[i];
		}
		dp_debug("1\n");
		p[0] = estimate[0];
		p[1] = estimate[1];
		
		
    memset(&res, 0, sizeof(res));
		res.xerror = perr;
		dp_debug("2\n");
		status = mpfit(linfunc, xv.Nelements(), 2, p, 0, 0, (void *) &v, &res);
		
		dp_debug("3\n");
		free(v.x);
		free(v.y);
		free(v.err_x);
		free(v.err_y);
		
		dp_debug("straightlinefit: Result is: %f+-%f %f+-%f, chisq = %f\n", p[0], perr[0], p[1], perr[1], res.bestnorm);
		
		result.create(2, 2, R8);
		result.r8data[0] = p[0];
		result.r8data[1] = p[1];
		result.r8data[2] = perr[0];
		result.r8data[3] = perr[1];

		return res.bestnorm;
}

//
// example code for QFitsView
//
/*
x=[1:10];y=5*[1:10];est=[1,1,1]
print mpfit(x,y,x*0+0.1,est,"a+b*x+c*x*x"),/values
print mpfit(x,y,x*0+0.1,est,"mpfituser.c"),/values
*/

/*
fff="a*abs(sin(x+phi))^e"
x=[1:1000]/200;
y=abs(sin(x+.3))^.5
hhh=mpfit(x,y,x*0+0.01,[1,1,1],fff)

cd "/Users/ott/Desktop"
x=import("genzel_nontrivial.txt")[1,*]
y=import("genzel_nontrivial.txt")[2,*]
err=x*0+0.05
estimate=[1,50,1,1,500,7000,1]
fff="a*(exp(-2.77259*((x)/b)^2) + c*(exp(-2.77259*((x-944)/b)^2) +0.32*exp(-2.77259*((x+673.8)/b)^2) ) + g* (b/k) * (exp(-2.77259*((x-f)/k)^2) + i*exp(-2.77259*((x-f-944)/k)^2) + i*0.32*exp(-2.77259*((x-f+673.8)/k)^2) ) )"
fff="a*(exp(-2.77259*pow((x)/b,2)) + c*(exp(-2.77259*pow((x-944)/b,2)) +0.32*exp(-2.77259*pow((x+673.8)/b,2)) ) + e* (b/g) * (exp(-2.77259*pow((x-d)/g,2)) + f*exp(-2.77259*pow((x-d-944)/g,2)) + f*0.32*exp(-2.77259*pow((x-d+673.8)/g,2)) ) )"
print mpfit(x,y,err,estimate,fff),/values
*/


// data structure for mpfituser
struct mpfituser_struct {
    double *x;
    double *y;
    double *err_y;
};

mp_config dpMpfitConfig;
dpString mpfit_compile_output;

int userfunction_parsed(int m, int n, double *p, double *dy, double **dvec, void *vars)
{
  int i, j;
  struct mpfituser_struct *v = (struct mpfituser_struct *) vars;
  double *x, *y, *ey, f;

  x = v->x;
  y = v->y;
  ey = v->err_y;

  for (j = 0; j < mpfit_sorted_variables.size(); j++) {
      if (mpfit_sorted_variables[j].size() == 1) {
          mpfit_quick_vars[mpfit_sorted_variables[j][0]] = p[j];
      } else {
          mpfit_vars[mpfit_sorted_variables[j]] = p[j];
      }
  }
  for (i=0; i<m; i++) {
    // evaluate user function
      mpfit_quick_vars['x'] = x[i];
      f = mpfitAST->evaluate();
    dy[i] = (y[i] - f)/ey[i];
  }

  return 0;
}

double mpfit_fit_userstring(Fits &result, Fits &xv, Fits &yv, Fits &yerr, Fits &estimate, dpString &funct) {
    dpString function;
    double f;
    for (int i = 0; i < funct.size(); i++) if (!isspace(funct[i])) function.push_back(funct[i]);
    function.push_back(';');
    mpfitASTNode *oldAST = mpfitAST;
    try {
        main_mpfit(function);
        delete oldAST;
    } catch (dpuserTypeException e) {
        mpfitAST = oldAST;
        mpfit_compile_output = "Evaluation of user function failed";
        return -1;
    }

    if (mpfit_sorted_variables.size() != estimate.Naxis(1)) {
        dp_output("Number of initial estimates (%i) does not match number of fit parameters (%i)", estimate.Naxis(1), mpfit_sorted_variables.size());
        return -1;
    }

    // execute fit
    struct mpfituser_struct v;
    double *p, *perr;
    mp_result res;
    mp_par *pars = NULL;
    mp_config *conf = NULL;
    int status;
    int i;

    v.x = (double *)malloc(xv.Nelements() * sizeof(double));
    v.y = (double *)malloc(xv.Nelements() * sizeof(double));
    v.err_y = (double *)malloc(xv.Nelements() * sizeof(double));
    for (i = 0; i < xv.Nelements(); i++) {
        v.x[i] = xv[i];
        v.y[i] = yv[i];
        v.err_y[i] = yerr[i];
    }
    p = (double *)malloc(estimate.Naxis(1) * sizeof(double));
    perr = (double *)malloc(estimate.Naxis(1) * sizeof(double));
    for (i = 0; i < estimate.Naxis(1); i++) {
        p[i] = estimate[i];
    }
    memset(&res, 0, sizeof(res));
    res.xerror = perr;

    conf = (mp_config *)malloc(sizeof(mp_config));
    memset(conf, 0, sizeof(mp_config));
    conf->covtol = dpMpfitConfig.covtol;
    conf->epsfcn = dpMpfitConfig.epsfcn;
    conf->ftol = dpMpfitConfig.ftol;
    conf->gtol = dpMpfitConfig.gtol;
    conf->maxfev = dpMpfitConfig.maxfev;
    conf->maxiter = dpMpfitConfig.maxiter;
    conf->stepfactor = dpMpfitConfig.stepfactor;
    conf->xtol = dpMpfitConfig.xtol;

    conf->nofinitecheck = 1;

    // Parameter constrains - if estimate has Naxis(2) == 4

    if (estimate.Naxis(2) == 4) {
        pars = (mp_par *)malloc(estimate.Naxis(1) * sizeof(mp_par));
        memset(&pars[0], 0, estimate.Naxis(1) * sizeof(mp_par));
        bool fixed, limited1, limited2;
        double flag;
        for (int i = 0; i < estimate.Naxis(1); i++) {
            fixed = limited1 = limited2 = false;
            flag = estimate.ValueAt(estimate.C_I(i, 1));
            if (flag < -0.5) fixed = true;
            else if (flag > 0.5 && flag < 1.5) limited1 = true;
            else if (flag > 1.5 && flag < 2.5) limited2 = true;
            else if (flag > 2.5 && flag < 3.5) limited1 = limited2 = true;
            if (limited1) {
                pars[i].limited[0] = true;
                pars[i].limits[0] = estimate.ValueAt(estimate.C_I(i, 2));
            }
            if (limited2) {
                pars[i].limited[1] = true;
                pars[i].limits[1] = estimate.ValueAt(estimate.C_I(i, 3));
            }
            if (fixed) {
                pars[i].fixed = true;
            }
        }
    }

    status = mpfit(userfunction_parsed, xv.Nelements(), estimate.Naxis(1), p, pars, conf, (void *) &v, &res);

    switch (status) {
        case MP_ERR_INPUT: dp_output("General input parameter error"); break;
        case MP_ERR_NAN: dp_output("User function produced non-finite values"); break;
        case MP_ERR_FUNC: dp_output("No user function was supplied"); break;
        case MP_ERR_NPOINTS: dp_output("No user data points were supplied"); break;
        case MP_ERR_NFREE: dp_output("No free parameters"); break;
        case MP_ERR_MEMORY: dp_output("Memory allocation error"); break;
        case MP_ERR_INITBOUNDS: dp_output("Initial values inconsistent w constraints"); break;
        case MP_ERR_BOUNDS: dp_output("Initial constraints inconsistent"); break;
        case MP_ERR_PARAM: dp_output("General input parameter error"); break;
        case MP_ERR_DOF: dp_output("Not enough degrees of freedom"); break;

/* Potential success status codes */
        case MP_OK_CHI: dp_output("Convergence in chi-square value"); break;
        case MP_OK_PAR: dp_output("Convergence in parameter value"); break;
        case MP_OK_BOTH: dp_output("Both MP_OK_PAR and MP_OK_CHI hold"); break;
        case MP_OK_DIR: dp_output("Convergence in orthogonality"); break;
        case MP_MAXITER: dp_output("Maximum number of iterations reached"); break;
        case MP_FTOL: dp_output("ftol is too small; no further improvement"); break;
        case MP_XTOL: dp_output("xtol is too small; no further improvement"); break;
        case MP_GTOL: dp_output("gtol is too small; no further improvement"); break;
        default: dp_output("unknow success code"); break;
    }

    free(v.x);
    free(v.y);
    free(v.err_y);
    free(conf);
    if (pars) free(pars);

    result.create(estimate.Naxis(1), 2, R8);

    for (i = 0; i < estimate.Naxis(1); i++) {
        dp_debug("Parameter %i: %f +- %f", i+1, p[i], perr[i]);
        result.r8data[result.C_I(i, 0)] = p[i];
        result.r8data[result.C_I(i, 1)] = perr[i];
    }
    free(p);
    free(perr);

    return res.bestnorm;
}

int mpfit_evaluate_userstring(Fits &result, Fits &xv, Fits &parameters, dpString &funct) {
    dpString function;
    double v, f;
    for (int i = 0; i < funct.size(); i++) if (!isspace(funct[i])) function.push_back(funct[i]);
    function.push_back(';');
    mpfitASTNode *oldAST = mpfitAST;
    try {
        main_mpfit(function);
        delete oldAST;
    } catch (dpuserTypeException e) {
        mpfitAST = oldAST;
        return -1;
    }

    if (mpfit_sorted_variables.size() != parameters.Naxis(1)) {
        dp_output("Number of parameters (%i) does not match number of variables (%i)", parameters.Naxis(1), mpfit_sorted_variables.size());
        return -1;
    }

    result.copy(xv);

    for (int i = 0; i < mpfit_sorted_variables.size(); i++) {
        if (mpfit_sorted_variables.at(i).size() == 1) {
            mpfit_quick_vars[mpfit_sorted_variables.at(i)[0]] = parameters.r8data[i];
        } else {
            mpfit_vars[mpfit_sorted_variables.at(i).c_str()] = parameters.r8data[i];
        }
    }

    for (int i = 0; i < xv.Nelements(); i++) {
        mpfit_quick_vars['x'] = xv.ValueAt(xv.C_I(i));
        f = mpfitAST->evaluate();
        result.setValue(f, i+1, 1, 1);
    }
    return 0;
}

/* TODO: From here on, this is the interface to compile, load, and fit functions loaded externally
 * (using LoadLibrary or dlopen). This code here would convert dpuser code to c++ and then compile.
 * We don't support this functionality anymore (the compile command is gone). We can and should,
 * therefore, support loading external code written in C (or fortran?) and fit that using mpfit.
 *
 * Until this is done, we "comment" the code using #ifdef MPFIT_C_INTERFACE
 */

#ifdef MPFIT_C_INTERFACE

//
// example code for C
//
/*
#include <math.h>

extern "C" double mpfituser(double x, double *p) {
    return p[0] + x * p[1] + x * x * p[2];
}
*/

// example code for fortran
/*
      REAL*8 FUNCTION MPFITUSER(X,P)
      REAL*8 X,P(*)
      MPFITUSER = P(1) + X * P(2) + P(3) * X**2;
      RETURN
      END
*/


// Fit a user supplied function to data using mpfit. The user supplied function will
// be read usind dlload and friends and must be named "mpfituser" in mpfituser.dll

#ifdef WIN
#include <windows.h>
//#include <platform.h>
#else
#include <unistd.h>
#include <dlfcn.h>
#endif

typedef double (*MpFitUser0C)(double, double*);
MpFitUser0C mpfituser0c;

typedef double (*MpFitUser0F)(double*, double*);
MpFitUser0F mpfituser0f;

bool mpfitIsFortran = false;

int userfunction(int m, int n, double *p, double *dy, double **dvec, void *vars)
{
  int i;
  struct mpfituser_struct *v = (struct mpfituser_struct *) vars;
  double *x, *y, *ey, f;

  x = v->x;
  y = v->y;
  ey = v->err_y;

  for (i=0; i<m; i++) {
    // evaluate user function
    if (!mpfitIsFortran) {
        f = mpfituser0c(x[i], p);
    } else {
        f = mpfituser0f(&x[i], p);
    }
    dy[i] = (y[i] - f)/ey[i];
  }

  return 0;
}

//
// NOTE: THE TEST PROGRAM ITSELF
//
#include <iostream>
#include <fstream>
int mpfit_write_testprogram(dpString fname) {
    std::ofstream f;
    f.open(fname.c_str());
    f << "#include <stdlib.h>\n";
    f << "#include <stdio.h>\n";
#ifdef WIN
    f << "#include <windows.h>\n";
#else
    f << "#include <dlfcn.h>\n";
#endif
    f << "typedef double (*MpFitUser0C)(double, double*);\n";
    f << "MpFitUser0C mpfituser0c;\n";

    f << "typedef double (*MpFitUser0F)(double*, double*);\n";
    f << "MpFitUser0F mpfituser0f;\n";

    f << "bool mpfitIsFortran = false;\n";

    f << "int main(int argc, char *argv[]) {\n";
#ifdef WIN
    f << "    DWORD dwMode = SetErrorMode(SEM_NOGPFAULTERRORBOX);\n";
    f << "    SetErrorMode(dwMode | SEM_NOGPFAULTERRORBOX);\n";
#endif
    f << "    int i;\n";
    f << "    double x, f, *p;\n";

    f << "    p = (double *)malloc((argc-2) * sizeof(double));\n";
// printf("mpfittest: nargs is %i\n", argc);
    f << "    for (i = 2; i < argc; i++) p[i-2]=atof(argv[i]);\n";
    f << "    x = atof(argv[1]);\n";

    f << "// Load library\n";
    f << "    void *libHandle = NULL;\n";
#ifdef WIN
    // Windows
    f << "    libHandle = LoadLibrary(\"./mpfituser.dll\");\n";
#else
    // Linux
    f << "    libHandle = dlopen(\"./mpfituser.dll\", RTLD_LAZY|RTLD_GLOBAL);\n";
#endif
    f << "    if (libHandle == NULL) {\n";
//            dp_output("Could not open dynamic library\n");\n";
    f << "            exit(-1);\n";
    f << "    }\n";
#ifdef WIN
    // Windows
    f << "    // try C-code\n";
    f << "    mpfituser0c = (MpFitUser0C)GetProcAddress((HINSTANCE)libHandle, \"mpfituser\");\n";
    f << "    if (mpfituser0c == NULL) {\n";
    f << "        // try Fortran-code\n";
    f << "        mpfituser0f = (MpFitUser0F)GetProcAddress((HINSTANCE)libHandle, \"mpfituser_\");\n";
    f << "        if (mpfituser0f == NULL) {\n";
//            dp_output("Could not resolve symbol mpfituser\n");
    f << "            FreeLibrary((HINSTANCE)libHandle);\n";
    f << "            exit(-1);\n";
    f << "        }\n";
    f << "        mpfitIsFortran = true;\n";
    f << "    }\n";
#else
    // Linux
    f << "    // try C-code\n";
    f << "    mpfituser0c = (MpFitUser0C)dlsym(libHandle, \"mpfituser\");\n";
    f << "    if (mpfituser0c == NULL) {\n";
    f << "        // try Fortran-code\n";
    f << "        mpfituser0f = (MpFitUser0F)dlsym(libHandle, \"mpfituser_\");\n";
    f << "        if (mpfituser0f == NULL) {\n";
//            dp_output("Could not resolve symbol mpfituser\n");\n";
    f << "            dlclose(libHandle);\n";
    f << "            exit(-1);\n";
    f << "        }\n";
    f << "        mpfitIsFortran = true;\n";
    f << "    }\n";
#endif

    f << "    if (!mpfitIsFortran) {\n";
    f << "        f = mpfituser0c(x, p);\n";
    f << "    } else {\n";
    f << "        f = mpfituser0f(&x, p);\n";
    f << "    }\n";
//    printf("output: %f\n", f);

    f << "    free(p);\n";

//	if (mpfitIsFortran) return 667;
    f << "    exit(666);\n";
    f << "}\n";

    f.close();

    return 0;
}

dpString mpfit_get_temppath() {
#ifdef WIN
    // Get tempDir-path
    char FullName[MAX_PATH + 1];
    GetTempPath(MAX_PATH, FullName);
    dpString tempPath = dpString(FullName);
#else
    dpString tempPath = dpString(P_tmpdir);
#endif /* WIN */
    if (tempPath[tempPath.size()-1] != '/')
        tempPath.append("/");
    return tempPath;
}

dpString mpfit_get_tempfile(dpString prefix) {
#ifdef WIN
    // Get tempDir-path
    char FullName[MAX_PATH + 1], FileName[MAX_PATH + 1];
    GetTempPath(MAX_PATH, FullName);
    GetTempFileName(FullName, prefix.c_str(), 0, FileName);
    dpString tempPath = dpString(FileName);
#else
    dpString tempPath = dpString(std::tmpnam(NULL)); // causes warning: the use of `tmpnam' is dangerous, better use `mkstemp'
#endif /* WIN */
    return tempPath;
}

dpString mpfit_write_userfunction(const dpString &funct, const dpString &variables) {
    mpfit_compile_output.clear();
    dpString fname = mpfit_get_tempfile("mpfituser") + ".cpp";
    std::ofstream f;
    f.open(fname.c_str());
    if (!f.is_open()) {
        mpfit_compile_output = "Could not open file " + fname + " for writing.";
        return dpString("");
    }
    f << "#include <math.h>\n";
    f << "extern \"C\" double mpfituser(double x, double *p) {\n";
    if (variables.length() > 0) f << variables << "\n";
    f << "return ";
    f << funct;
    f << ";\n}\n";
    f.close();
    return fname;
}

dpStringList mpfit_find_variables(const dpString &funct) {
    dpString rv;

    // try to do this with regex
    rv = funct;
    rv.replace(dpRegExp("[a-zA-Z0-9_ ]*\\("), " ");
    rv.replace(dpRegExp("[0-9]*\\.[0-9]+([eEdD][-+]?[0-9]+)?"), " ");
    rv.replace(dpRegExp("[0-9]+\\.?([eEdD][-+]?[0-9]+)?"), " ");
    rv.replace("+", " ");
    rv.replace("-", " ");
    rv.replace("*", " ");
    rv.replace("/", " ");
    rv.replace(",", " ");
    rv.replace(")", " ");
    rv.replace("(", " ");
    rv.replace("^", " ");
    rv.replace("x ", " ");
    rv.replace(" x", " ");
    rv.replace(".", " ");
    rv = rv.simplifyWhiteSpace();

    /*
    dpString fname = mpfit_write_userfunction(funct, "");
    dpString cmd = "g++ -c " + fname + " 2>&1";
    dpStringList output;
    char buf[BUFSIZ];
    FILE *ptr;
    if ((ptr = popen(cmd.c_str(), "r")) != NULL) {
             while (fgets(buf, BUFSIZ, ptr) != NULL)
                 output.append(dpString(buf));
             pclose(ptr);
    }
    for (int i = 0; i < output.size(); i++) {
        dpString line = output.at(i);
        if (line.lower().contains("error")) {
            int start, end;
            start = line.find_first_of("'??");
            if (start != -1) {
                end = line.find_last_of("'??");
                for (int c = start + 1; c < end; c++) {
                    if (isalnum(line[c]) || line[c] == '_') {
                        rv.push_back(line[c]);
                    }
                }
                rv.append(" ");
            }
        }
    }
    */

    rv = rv.stripWhiteSpace();
    dpStringList rrv = dpStringList::split(' ', rv);
    dpStringList returnValue;

    std::map<dpString, int> mrv;
    for (int i = 0; i < rrv.size(); i++) {
        if (mrv.count(rrv.at(i)) == 0) {
            returnValue.append(rrv.at(i));
            mrv[rrv.at(i)] = 1;
        }
    }

    return returnValue;
}

dpString mpfit_build_variable_declarations(dpStringList &vars) {
    dpString rv;

    rv = "double ";

    char numstr[25];
    int firstone = 0;
    for (int i = 0; i < vars.size(); i++) {
        if (firstone > 0) rv += ", ";
        rv += vars.at(i);
        sprintf(numstr, "=p[%d]", firstone);
        rv += numstr;
        firstone++;
    }
    rv += ";";

    return rv;
}

int main_dpuser2c(char *fname);

dpString mpfit_parse_dpuser(dpString &funct) {
    /*
    std::ofstream f;
    f.open((mpfit_get_temppath() + "mpfituser.dpuser").c_str());
    f << "function mpfituser, x, p {\n";
    f << mpfit_find_variables(funct).replace("double", "").replace(",", ";") + "\n";
    f << "mpfituser = " + funct + "\n}\n";
    f.close();

    main_dpuser2c((char *)((mpfit_get_temppath() + "mpfituser.dpuser").c_str()));
    */

    dpString w(funct);
    w.replace(" ", "");
    while (w.contains('^')) {
        // find backward the first argument

        int pos = w.find_first_of('^');
        int bracketcount = 0;
        pos--;
        int end = pos;
        char c = w[pos];
        // go backward until either beginning, or another operator
        while ((c != '+' && c != '-' && c != '/' && c != '^' && c != '*' && pos > 0) || (bracketcount > 0)) {
            if (c == ')') bracketcount++;
            else if (c == '(') bracketcount--;
            pos--;
            c = w[pos];
        }
        if (bracketcount < 0) pos++;
        int rstart = pos + 1;
        dpString first = w.mid(rstart, end-pos);

        // find forward the first argument

        pos = w.find_first_of('^');
        bracketcount = 0;
        pos++;
        int start = pos;
        c = w[pos];
        // go forward until either end, or another operator
        while ((c != '+' && c != '-' && c != '/' && c != '-' && c != '*' && pos < w.size()) || (bracketcount > 0))  {
            if (c == '(') bracketcount++;
            else if (c == ')') bracketcount--;
            pos++;
            c = w[pos];
        }
        if (bracketcount < 0) pos--;
        dpString second = w.mid(start, pos-start);
        int rend = pos - 1;

        w.replace(rstart, rend - rstart + 1, dpString("pow(" + first + "," + second + ")").c_str());
    }

    return w;
}

// run a command in a separate process. Returns -1 if the command fails, else zero
// the error output of the command is stored in the global variable mpfit_compile_output
int mpfit_spawn_command(dpString command) {
    mpfit_compile_output.clear();

#ifdef DPQT
    QProcess process;
    process.start(command.c_str());
    process.waitForFinished(-1);
    mpfit_compile_output.append(process.readAllStandardError().constData());
    int e = process.exitCode();
    if (e == 0 || e == 66 || e == 666) {
// command succeeded
        dp_output(process.readAllStandardError().constData());
        return e;
    }
#else
    dpString cmd(command);
    cmd += " 2>&1";
    char buf[BUFSIZ];
    FILE *ptr;
    if ((ptr = popen(cmd.c_str(), "r")) != NULL) {
             while (fgets(buf, BUFSIZ, ptr) != NULL) {
                 mpfit_compile_output.append(buf);
                 dp_output("%s", buf);
             }
             return pclose(ptr);
    }
#endif
    return -1;
}

int mpfit_compile_testprogram(const dpString &fname) {
// create compile string for program
    dpString cmd;

    cmd = "g++ " + fname + ".cpp -o " + fname;
#ifdef LINUX
    cmd += " -ldl";
#endif

// compile program
    return mpfit_spawn_command(cmd);
/*
#ifdef DPQT
    QProcess process;
    process.start(cmd.c_str());
    process.waitForFinished(-1);
    if (process.exitCode() != 0) {
// compile failed
        dp_output(process.readAllStandardError().constData());
        return process.exitCode();
    }
#else
    return system(cmd.c_str());
#endif
*/
}

int mpfit_compile_userfunction(const dpString &fname) {
// create compile string for dll
    dpString cmd;
    if (fname.right(1) == "f") {
        cmd = "gfortran -shared " + fname + " -o " + fname + ".dll";
    } else {
#ifdef LINUX
        cmd = "g++ -shared -fPIC " + fname + " -o " + fname + ".dll";
#else
        cmd = "g++ -shared " + fname + " -o " + fname + ".dll";
//        cmd = "sleep 5";
#endif
    }
    return mpfit_spawn_command(cmd);
    /*
    cmd += " 2>&1";

// compile dll
    char buf[BUFSIZ];
    FILE *ptr;
    mpfit_compile_output.clear();
    if ((ptr = popen(cmd.c_str(), "r")) != NULL) {
             while (fgets(buf, BUFSIZ, ptr) != NULL) {
                 mpfit_compile_output.append(buf);
                 dp_output("%s", buf);
             }
             return pclose(ptr);
    }
    return -1;
    */
}

// test if test-program crashes when executing dll
int mpfit_test_userfunction(double value, const Fits &estimate) {
    char stmp[64];

    sprintf(stmp, "%f", value);
#ifdef WIN
    dpString command;
    command.append(mpfit_get_temppath() + "mpfittest ");
    command.append(stmp);
#else
    std::vector<char *> commandVector;
    commandVector.push_back((char *)((mpfit_get_temppath() + "mpfittest").c_str()));
    commandVector.push_back(stmp);
#endif
    for (int i = 0; i < estimate.Nelements(); i++) {
        snprintf(stmp, 64, " %f", estimate.ValueAt(i));
#ifdef WIN
        command.append(stmp);
#else
        commandVector.push_back(stmp);
#endif
    }
    int teststatus;
#ifdef WIN
    teststatus = mpfit_spawn_command(command);
/*    teststatus = system(command.c_str()); */
    if (teststatus != 666) {
        return -1;
    }
#else
    commandVector.push_back(NULL);
    pid_t pid;

    if ((pid = fork()) < 0) {
        dp_output("Could not execute test program\n");
        return -1;
    } else if (pid == 0) {
        char **pcommand = &commandVector[0];
        if (execvp((mpfit_get_temppath() + "mpfittest").c_str(), pcommand) < 0) {
            exit(66);
        }
    } else {
        while (wait(&teststatus) != pid);
    }
    if (teststatus == -1) {
        return -5;
    }
    if (WIFSIGNALED(teststatus)) {
        switch (WTERMSIG(teststatus)) {
            case SIGINT:
                return -2;
                break;
            case SIGSEGV:
                return -3;
                break;
            case SIGQUIT:
                return -4;
                break;
            default:
                return -1;
                break;
        }
    }
    if (WIFEXITED(teststatus)) {
        if (WEXITSTATUS(teststatus) == 66) {
            dp_output("Could not execute test program\n");
            return -5;
        }
    }
#endif
    return 0;
}

// Load library
void *mpfit_libHandle = NULL;

int mpfit_load_userfunction(dpString &fname, Fits &xv, Fits &estimate) {
    // compile user function
    int r;
    r = mpfit_compile_userfunction(fname);
    if (r != 0) {
        dp_output("Compilation of user function %s unsuccessful. Return code is %i\n", fname.c_str(), r);
        return -1;
    }

    mpfit_write_testprogram(mpfit_get_temppath() + "mpfittest.cpp");
    mpfit_compile_testprogram(mpfit_get_temppath() + "mpfittest");
    // check that code runs
    if (mpfit_test_userfunction(xv.ValueAt(0), estimate) != 0) {
        dp_output("Test program crashed. Check your userfunction.\n");
        mpfit_compile_output = "Test program crashed. Check your userfunction.";
        return -1;
    }

#ifdef WIN
    // Windows
    mpfit_libHandle = LoadLibrary((fname + ".dll").c_str());
#else
    // Linux
    mpfit_libHandle = dlopen((fname + ".dll").c_str(), RTLD_LAZY|RTLD_GLOBAL);
#endif
    if (mpfit_libHandle == NULL) {
            dp_output("Could not open dynamic library\n");
            return -1.;
    }

    // load user function
#ifdef WIN
    // Windows
    // try C-code
    mpfituser0c = (MpFitUser0C)GetProcAddress((HINSTANCE)mpfit_libHandle, "mpfituser");
    if (mpfituser0c == NULL) {
        // try Fortran-code
        mpfituser0f = (MpFitUser0F)GetProcAddress((HINSTANCE)mpfit_libHandle, "mpfituser_");
        if (mpfituser0f == NULL) {
            dp_output("Could not resolve symbol mpfituser\n");
            FreeLibrary((HINSTANCE)mpfit_libHandle);
            return -1.;
        }
        mpfitIsFortran = true;
    }
#else
    // Linux
    mpfituser0c = (MpFitUser0C)dlsym(mpfit_libHandle, "mpfituser");
    if (mpfituser0c == NULL) {
        // try Fortran-code
        mpfituser0f = (MpFitUser0F)dlsym(mpfit_libHandle, "mpfituser_");
        if (mpfituser0f == NULL) {
            dp_output("Could not resolve symbol mpfituser\n");
            dlclose(mpfit_libHandle);
            return -1.;
        }
        mpfitIsFortran = true;
    }
#endif
    return 0;
}

double mpfit_userfunction(Fits &result, Fits &xv, Fits &yv, Fits &yerr, Fits &estimate, dpString &fname) {

    if (mpfit_load_userfunction(fname, xv, estimate) != 0) return -1;

    // execute fit
    struct mpfituser_struct v;
    double *p, *perr;
    mp_result res;
    mp_par *pars = NULL;
    mp_config *conf = NULL;
    int status;
    int i;

    v.x = (double *)malloc(xv.Nelements() * sizeof(double));
    v.y = (double *)malloc(xv.Nelements() * sizeof(double));
    v.err_y = (double *)malloc(xv.Nelements() * sizeof(double));
    for (i = 0; i < xv.Nelements(); i++) {
        v.x[i] = xv[i];
        v.y[i] = yv[i];
        v.err_y[i] = yerr[i];
    }
    p = (double *)malloc(estimate.Naxis(1) * sizeof(double));
    perr = (double *)malloc(estimate.Naxis(1) * sizeof(double));
    for (i = 0; i < estimate.Naxis(1); i++) {
        p[i] = estimate[i];
    }
    memset(&res, 0, sizeof(res));
    res.xerror = perr;

    conf = (mp_config *)malloc(sizeof(mp_config));
    memset(conf, 0, sizeof(mp_config));
    conf->nofinitecheck = 1;

    // Parameter constrains - if estimate has Naxis(2) == 4

    if (estimate.Naxis(2) == 4) {
        pars = (mp_par *)malloc(estimate.Naxis(1) * sizeof(mp_par));
        memset(&pars[0], 0, estimate.Naxis(1) * sizeof(mp_par));
        bool fixed, limited1, limited2;
        double flag;
        for (int i = 0; i < estimate.Naxis(1); i++) {
            fixed = limited1 = limited2 = false;
            flag = estimate.ValueAt(estimate.C_I(i, 1));
            if (flag < -0.5) fixed = true;
            else if (flag > 0.5 && flag < 1.5) limited1 = true;
            else if (flag > 1.5 && flag < 2.5) limited2 = true;
            else if (flag > 2.5 && flag < 3.5) limited1 = limited2 = true;
            if (limited1) {
                pars[i].limited[0] = true;
                pars[i].limits[0] = estimate.ValueAt(estimate.C_I(i, 2));
            }
            if (limited2) {
                pars[i].limited[1] = true;
                pars[i].limits[1] = estimate.ValueAt(estimate.C_I(i, 3));
            }
            if (fixed) {
                pars[i].fixed = true;
            }
        }
    }

    status = mpfit(userfunction, xv.Nelements(), estimate.Naxis(1), p, pars, conf, (void *) &v, &res);

    free(v.x);
    free(v.y);
    free(v.err_y);
    free(conf);
    if (pars) free(pars);

    result.create(estimate.Naxis(1), 2, R8);

    for (i = 0; i < estimate.Naxis(1); i++) {
        result.r8data[result.C_I(i, 0)] = p[i];
        result.r8data[result.C_I(i, 1)] = perr[i];
    }

#ifdef WIN
    FreeLibrary((HINSTANCE)mpfit_libHandle);
#else
    dlclose(mpfit_libHandle);
#endif
    free(p);
    free(perr);

    return res.bestnorm;
}

// Fit a function given as string
double mpfit_functionstring(Fits &result, Fits &xv, Fits &yv, Fits &yerr, Fits &estimate, dpString &funct) {

    dpString fff = mpfit_parse_dpuser(funct);

    // evaluate which variables are used
    dpStringList variableList = mpfit_find_variables(fff);
    dpString variables = mpfit_build_variable_declarations(variableList);

    // write c code
    dpString fname = mpfit_write_userfunction(fff, variables);

    return mpfit_userfunction(result, xv, yv, yerr, estimate, fname);
}

int mpfit_evaluate_userfunction(Fits &result, Fits &xv, Fits &parameters, dpString &fname) {
    if (mpfit_load_userfunction(fname, xv, parameters) != 0) return -1;

    double f, v;
    result.copy(xv);
    Fits par;
    par.copy(parameters);
    par.setType(R8);
    for (int i = 0; i < xv.Nelements(); i++) {
        v = xv.ValueAt(xv.C_I(i));
        if (!mpfitIsFortran) {
            f = mpfituser0c(v, par.r8data);
        } else {
            f = mpfituser0f(&v, par.r8data);
        }
        result.setValue(f, i+1, 1, 1);
    }
#ifdef WIN
    FreeLibrary((HINSTANCE)mpfit_libHandle);
#else
    dlclose(mpfit_libHandle);
#endif
    return 0;
}

#endif /* MPFIT_C_INTERFACE */
