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

#include "amdmsFit.h"

#ifndef M_PI
#define M_PI            3.14159265358979323846  /* pi */
#endif /* M_PI */

#define TOL 1.0e-5 /* Default value for single precision and variables scaled to order unity. */

#define MAX(a,b) ((a) > (b) ? (a) : (b))
#define MIN(a,b) ((a) < (b) ? (a) : (b))

static double sqrarg;
#define SQR(a) ((sqrarg=(a)) == 0.0 ? 0.0 : sqrarg*sqrarg)

static double maxarg1,maxarg2;
#define FMAX(a,b) (maxarg1=(a),maxarg2=(b),(maxarg1) > (maxarg2) ? (maxarg1) : (maxarg2))

static int iminarg1,iminarg2;
#define IMIN(a,b) (iminarg1=(a),iminarg2=(b),(iminarg1) < (iminarg2) ? (iminarg1) : (iminarg2))

#define SIGN(a,b) ((b) >= 0.0 ? fabs(a) : -fabs(a))

static amdmsCOMPL amdmsCalcQuality(amdmsFIT_ENV *env, int n, double *x, double *y, double *ye);

static amdmsCOMPL amdmsFitLine(void *henv, int n, double *x, double *y);
static double amdmsEvalLine(void *henv, double x);

static amdmsCOMPL amdmsFitExp(void *env, int n, double *x, double *y);
static double amdmsEvalExp(void *env, double x);

static amdmsCOMPL amdmsFitLog(void *henv, int n, double *x, double *y);
static double amdmsEvalLog(void *henv, double x);

static double amdmsCalcPythag(double a, double b);
static amdmsCOMPL amdmsDecompose(amdmsFIT_LINEAR_ENV *env, int m, int n);
static void amdmsSolveLinear(amdmsFIT_LINEAR_ENV *env, int m, int n);
static void amdmsCalcCovarianceLinear(amdmsFIT_LINEAR_ENV *env);
static amdmsCOMPL amdmsFitSVDLinear(amdmsFIT_LINEAR_ENV *env, int n, double *x, double *y, double *ye);
static amdmsCOMPL amdmsAllocSpaceLinear(amdmsFIT_LINEAR_ENV *env, int nCs, int nDPs);
static double amdmsEvalLinear(void *henv, double x);

static void amdmsPolynomialBaseFunc(double x, int np, double *p);
static void amdmsRayBaseFunc(double x, int np, double *p);

static void amdmsExpandCovariance(double **covar, int ma, int ia[], int mfit);
static void amdmsGaussJordan(amdmsFIT_NONLINEAR_ENV *env,
			    double **a, int n, double **b, int m);
static void amdmsEvaluate(amdmsFIT_NONLINEAR_ENV *env,
			 int ndata, double x[], double y[], double sig[],
			 double a[], double **alpha, double beta[]);
static void amdmsLevenbergMarquardt(amdmsFIT_NONLINEAR_ENV *env,
				   int ndata, double *x, double *y, double *ye);
static amdmsCOMPL amdmsAllocSpaceNonLinear(amdmsFIT_NONLINEAR_ENV *env, int nCs);
static amdmsCOMPL amdmsFitNonLinear(void *henv, int n, double *x, double *y, double *ye);
static double amdmsEvalNonLinear(void *henv, double x);

static void amdmsNonLinearPixelInit(void *henv, int n, double *x, double *y);
static void amdmsNonLinearPixelBaseFunc(double x, int na, double *a, double *y, double *dyda);

static void amdmsElectronicBiasInit(void *henv, int n, double *x, double *y);
static void amdmsElectronicBiasBaseFunc(double x, int na, double *a, double *y, double *dyda);

static void amdmsConversionFactorInit(void *henv, int n, double *x, double *y);
static void amdmsConversionFactorBaseFunc(double x, int na, double *a, double *y, double *dyda);

static amdmsCOMPL amdmsFindDataSaturation(amdmsFIT_DATA_ENV *env);
static double amdmsTryDataFit(amdmsFIT_DATA_ENV *env);
static amdmsCOMPL amdmsAllocSpaceData(amdmsFIT_DATA_ENV *env, int nDPs);
static double amdmsEvalData(void *henv, double x);

static amdmsCOMPL amdmsFindSmoothDataOutliers(amdmsFIT_SMOOTH_DATA_ENV *env);
static amdmsCOMPL amdmsFindSmoothDataSaturation(amdmsFIT_SMOOTH_DATA_ENV *env);
static amdmsCOMPL amdmsAllocSpaceSmoothData(amdmsFIT_SMOOTH_DATA_ENV *env, int nDPs);
static amdmsCOMPL amdmsFitSmoothData(void *henv, int n, double *x, double *y, double *ye);
static double amdmsEvalSmoothData(void *henv, double x);

amdmsCOMPL amdmsCalcQuality(amdmsFIT_ENV *env, int n, double *x, double *y, double *ye)
{
  int    i;
  double minY = 0.0;
  double maxY = 0.0;
  double val = 0.0;
  double tmp = 0.0;
  
  if ((env == NULL) || (env->eval == NULL) || (x == NULL) || (y == NULL)) {
    return amdmsFAILURE;
  }
  env->chi2 = 0.0;
  env->absDist2 = 0.0;
  env->relDist2 = 0.0;
  for (i = 0; i < n; i++) {
    val = env->eval(env, x[i]);
    tmp = y[i] - val;
    if (i == 0) {
      minY = val;
      maxY = val;
    } else {
      minY = MIN(minY, val);
      maxY = MAX(maxY, val);
    }
    env->absDist2 += tmp*tmp;
    if (ye != NULL) {
      tmp /= ye[i];
    }
    env->chi2 += tmp*tmp;
  }
  env->chi2 /= (double)n;
  env->absDist2 /= (double)n;
  if ((maxY - minY) > 0.001) {
    env->relDist2 = env->absDist2/(maxY - minY)/(maxY - minY);
  } else {
    env->relDist2 = env->absDist2;
  }
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsFitLine(void *henv, int n, double *x, double *y)
{
  amdmsFIT_ENV *env = (amdmsFIT_ENV*)henv;
  int      i;
  double   sx   = 0.0;
  double   sxx  = 0.0;
  double   sy   = 0.0;
  double   syy  = 0.0;
  double   sxy  = 0.0;
  double   ssxx = 0.0;
  double   ssyy = 0.0;
  double   ssxy = 0.0;
  double   xq   = 0.0;
  double   yq   = 0.0;
  double   s    = 0.0;

  if (env == NULL) {
    return amdmsFAILURE;
  }
  for (i = 0; i < n; i++) {
    /* amdmsInfo(__FILE__, __LINE__, "  x=%.4e, y=%.4e", x[i], y[i]); */
    sx  += x[i];
    sxx += x[i]*x[i];
    sy  += y[i];
    syy += y[i]*y[i];
    sxy += x[i]*y[i];
  }
  xq = sx/(double)n;
  yq = sy/(double)n;
  ssxx = sxx - (double)n*xq*xq;
  ssyy = syy - (double)n*yq*yq;
  ssxy = sxy - (double)n*xq*yq;
  if (ssxx == 0.0) {
    amdmsWarning(__FILE__, __LINE__, "ssxx == 0.0");
    return amdmsFAILURE;
  }
  env->a[1] = ssxy/ssxx;
  env->a[0] = yq - env->a[1]*xq;
  s = sqrt((ssyy - ssxy*ssxy/ssxx)/(double)(n - 2));
  env->ae[0] = s*sqrt(1/(double)n + xq*xq/ssxx);
  env->ae[1] = s/sqrt(ssxx);
  /* amdmsInfo(__FILE__, __LINE__, "y=%.4e + %.4e*x", env->a[0], env->a[1]); */
  return amdmsSUCCESS;
}

double amdmsEvalLine(void *henv, double x)
{
  amdmsFIT_ENV *env = (amdmsFIT_ENV*)henv;

  if (env == NULL) {
    return 0.0;
  } else {
    return (env->a[0] + env->a[1]*x);
  }
}

amdmsCOMPL amdmsFitExp(void *henv, int n, double *x, double *y)
{
  amdmsFIT_ENV *env = (amdmsFIT_ENV*)henv;
  int      i;
  double   sy   = 0.0;
  double   sxy  = 0.0;
  double   syy  = 0.0;
  double   sxxy = 0.0;
  double   sxyy = 0.0;
  
  if (env == NULL) {
    return amdmsFAILURE;
  }
  for (i = 0; i < n; i++) {
    sy   += y[i];
    sxy  += x[i]*y[i];
    syy  += y[i]*log(y[i]);
    sxxy += x[i]*x[i]*y[i];
    sxyy += x[i]*y[i]*log(y[i]);
  }
  env->a[0] = (sxxy*syy - sxy*sxyy)/(sy*sxxy - sxy*sxy);
  env->a[1] = (sy*sxyy - sxy*syy)/(sy*sxxy - sxy*sxy);
  env->a[0] = exp(env->a[0]);
  env->ae[0] = 0.0;
  env->ae[0] = 0.0;
  return amdmsSUCCESS;
}

double amdmsEvalExp(void *henv, double x)
{
  amdmsFIT_ENV *env = (amdmsFIT_ENV*)henv;

  if (env == NULL) {
    return 0.0;
  } else {
    return (env->a[0]*exp(env->a[1]*x));
  }
}

amdmsCOMPL amdmsFitLog(void *henv, int n, double *x, double *y)
{
  amdmsFIT_ENV *env = (amdmsFIT_ENV*)henv;
  int      i;
  double   sx   = 0.0;
  double   sxx  = 0.0;
  double   sy   = 0.0;
  double   syy  = 0.0;
  double   sxy  = 0.0;
  double   ssxx = 0.0;
  double   ssyy = 0.0;
  double   ssxy = 0.0;
  double   xq   = 0.0;
  double   yq   = 0.0;
  double   s    = 0.0;

  if (env == NULL) {
    return amdmsFAILURE;
  }
  for (i = 0; i < n; i++) {
    sx  += log(x[i]);
    sxx += log(x[i])*log(x[i]);
    sy  += y[i];
    syy += y[i]*y[i];
    sxy += log(x[i])*y[i];
  }
  xq = sx/(double)n;
  yq = sy/(double)n;
  ssxx = sxx - (double)n*xq*xq;
  ssyy = syy - (double)n*yq*yq;
  ssxy = sxy - (double)n*xq*yq;
  env->a[1] = (n*sxy - sx*sy)/(n*sxx - sx*sx);
  env->a[0] = (sy - env->a[1]*sx)/n;
  s = 0.0;
  env->ae[0] = 0.0;
  env->ae[0] = 0.0;
  return amdmsSUCCESS;
}

double amdmsEvalLog(void *henv, double x)
{
  amdmsFIT_ENV *env = (amdmsFIT_ENV*)henv;

  if (env == NULL) {
    return 0.0;
  } else {
    return (env->a[0] + env->a[1]*log(x));
  }
}

/*
  Computes (a^2 + b^2)^1/2 without destructive underflow or overflow.
*/
double amdmsCalcPythag(double a, double b)
{
  double absa,absb;

  absa=fabs(a);
  absb=fabs(b);
  if (absa > absb) {
    return absa*sqrt(1.0+SQR(absb/absa));
  } else {
    return (absb == 0.0 ? 0.0 : absb*sqrt(1.0+SQR(absa/absb)));
  }
}

/*
  Given a matrix a[1..m][1..n], this routine computes its singular value decomposition,
  A = U * W * V T . The matrix U replaces a on output. The diagonal matrix of singular
  values W is output as a vector w[1..n]. The matrix V (not the transpose V T )is
  output as v[1..n][1..n].
*/
amdmsCOMPL amdmsDecompose(amdmsFIT_LINEAR_ENV *env, int m, int n)
{
  int flag,i,its,j,jj,k,l,nm;
  double anorm,c,f,g,h,s,scale,x,y,z, rv1[amdmsMAX_COEFF + 1];

  g=scale=anorm=0.0; /* Householder reduction to bidiagonal form. */
  l = 0;
  nm = 0;
  for (i=1;i<=n;i++) {
    l=i+1;
    rv1[i]=scale*g;
    g=s=scale=0.0;
    if (i <= m) {
      for (k=i;k<=m;k++) {
	scale += fabs(env->matU[k][i]);
      }
      if (scale) {
	for (k=i;k<=m;k++) {
	  env->matU[k][i] /= scale;
	  s += env->matU[k][i]*env->matU[k][i];
	}
	f=env->matU[i][i];
	g = -SIGN(sqrt(s),f);
	h=f*g-s;
	env->matU[i][i]=f-g;
	for (j=l;j<=n;j++) {
	  for (s=0.0,k=i;k<=m;k++) {
	    s += env->matU[k][i]*env->matU[k][j];
	  }
	  f=s/h;
	  for (k=i;k<=m;k++) {
	    env->matU[k][j] += f*env->matU[k][i];
	  }
	}
	for (k=i;k<=m;k++) {
	  env->matU[k][i] *= scale;
	}
      }
    }
    env->vecW[i]=scale *g;
    g=s=scale=0.0;
    if (i <= m && i != n) {
      for (k=l;k<=n;k++) {
	scale += fabs(env->matU[i][k]);
      }
      if (scale) {
	for (k=l;k<=n;k++) {
	  env->matU[i][k] /= scale;
	  s += env->matU[i][k]*env->matU[i][k];
	}
	f=env->matU[i][l];
	g = -SIGN(sqrt(s),f);
	h=f*g-s; env->matU[i][l]=f-g;
	for (k=l;k<=n;k++) {
	  rv1[k]=env->matU[i][k]/h;
	}
	for (j=l;j<=m;j++) {
	  for (s=0.0,k=l;k<=n;k++) {
	    s += env->matU[j][k]*env->matU[i][k];
	  }
	  for (k=l;k<=n;k++) {
	    env->matU[j][k] += s*rv1[k];
	  }
	}
	for (k=l;k<=n;k++) {
	  env->matU[i][k] *= scale;
	}
      }
    }
    anorm=FMAX(anorm,(fabs(env->vecW[i])+fabs(rv1[i])));
  }
  for (i=n;i>=1;i--) {
    /* Accumulation of right-hand transformations. */
    if (i < n) {
      if (g) {
	for (j=l;j<=n;j++) {
	  /* Double division to avoid possible underflow. */
	  env->matV[j][i]=(env->matU[i][j]/env->matU[i][l])/g;
	}
	for (j=l;j<=n;j++) {
	  for (s=0.0,k=l;k<=n;k++) {
	    s += env->matU[i][k]*env->matV[k][j];
	  }
	  for (k=l;k<=n;k++) {
	    env->matV[k][j] += s*env->matV[k][i];
	  }
	}
      }
      for (j=l;j<=n;j++) {
	env->matV[i][j]=env->matV[j][i]=0.0;
      }
    }
    env->matV[i][i]=1.0;
    g=rv1[i];
    l=i;
  }
  for (i=IMIN(m,n);i>=1;i--) {
    /* Accumulation of left-hand transformations. */
    l=i+1;
    g=env->vecW[i];
    for (j=l;j<=n;j++) {
      env->matU[i][j]=0.0;
    }
    if (g) {
      g=1.0/g;
      for (j=l;j<=n;j++) {
	for (s=0.0,k=l;k<=m;k++) {
	  s += env->matU[k][i]*env->matU[k][j];
	}
	f=(s/env->matU[i][i])*g;
	for (k=i;k<=m;k++) {
	  env->matU[k][j] += f*env->matU[k][i];
	}
      }
      for (j=i;j<=m;j++) {
	env->matU[j][i] *= g;
      }
    } else {
      for (j=i;j<=m;j++) {
	env->matU[j][i]=0.0;
      }
    }
    ++env->matU[i][i];
  }
  for (k=n;k>=1;k--) {
    /* Diagonalization of the bidiagonal form:
       Loop over singular values,and over allowed iterations.*/
    for (its=1;its<=30;its++) {
      flag=1;
      for (l=k;l>=1;l--) {
	/* Test for splitting. */
	nm=l-1; /* Note that rv1[1] is always zero. */
	if ((double)(fabs(rv1[l])+anorm) == anorm) {
	  flag=0;
	  break;
	}
	if ((double)(fabs(env->vecW[nm])+anorm) == anorm) {
	  break;
	}
      }
      if (flag) {
	c=0.0; /* Cancellation of rv1[l],if l > 1 */
	s=1.0;
	for (i=l;i<=k;i++) {
	  f=s*rv1[i];
	  rv1[i]=c*rv1[i];
	  if ((double)(fabs(f)+anorm) == anorm) {
	    break;
	  }
	  g=env->vecW[i];
	  h=amdmsCalcPythag(f,g);
	  env->vecW[i]=h;
	  h=1.0/h;
	  c=g*h;
	  s = -f*h;
	  for (j=1;j<=m;j++) {
	    y=env->matU[j][nm];
	    z=env->matU[j][i];
	    env->matU[j][nm]=y*c+z*s;
	    env->matU[j][i]=z*c-y*s;
	  }
	}
      }
      z=env->vecW[k];
      if (l == k) {
	/* Convergence. */
	if (z < 0.0) {
	  /* Singular value is made nonnegative. */
	  env->vecW[k] = -z;
	  for (j=1;j<=n;j++) {
	    env->matV[j][k] = -env->matV[j][k];
	  }
	}
	break;
      }
      if (its == 30) {
	amdmsError(__FILE__, __LINE__, "no convergence in 30 svdcmp iterations");
	return amdmsFAILURE;
      }
      x=env->vecW[l]; /* Shift from bottom 2-by-2 minor. */
      nm=k-1;
      y=env->vecW[nm];
      g=rv1[nm];
      h=rv1[k];
      f=((y-z)*(y+z)+(g-h)*(g+h))/(2.0*h*y);
      g=amdmsCalcPythag(f,1.0);
      f=((x-z)*(x+z)+h*((y/(f+SIGN(g,f)))-h))/x;
      c=s=1.0; /* Next QR transformation: */
      for (j=l;j<=nm;j++) {
	i=j+1;
	g=rv1[i];
	y=env->vecW[i];
	h=s*g;
	g=c*g;
	z=amdmsCalcPythag(f,h);
	rv1[j]=z;
	c=f/z;
	s=h/z;
	f=x*c+g*s;
	g = g*c-x*s;
	h=y*s;
	y *=c;
	for (jj=1;jj<=n;jj++) {
	  x=env->matV[jj][j];
	  z=env->matV[jj][i];
	  env->matV[jj][j]=x*c+z*s;
	  env->matV[jj][i]=z*c-x*s;
	}
	z=amdmsCalcPythag(f,h);
	env->vecW[j]=z; /* Rotation can be arbitrary if z =0 */
	if (z) {
	  z=1.0/z;
	  c=f*z;
	  s=h*z;
	}
	f=c*g+s*y;
	x=c*y-s*g;
	for (jj=1;jj<=m;jj++) {
	  y=env->matU[jj][j];
	  z=env->matU[jj][i];
	  env->matU[jj][j]=y*c+z*s;
	  env->matU[jj][i]=z*c-y*s;
	}
      }
      rv1[l]=0.0;
      rv1[k]=f;
      env->vecW[k]=x;
    }
  }
  return amdmsSUCCESS;
}

/*
  Solves A * X = B for a vector X where A is specified by
  the arrays u[1..m][1..n],w[1..n], v[1..n][1..n] as returned by svdcmp.
  m and n are the dimensions of a and will be equal for square matrices. 
  b[1..m] is the input right-hand side. x[1..n] is the output solution vector.
  No input quantities are destroyed, so the routine may be called sequentially with
  different b's.
*/
void amdmsSolveLinear(amdmsFIT_LINEAR_ENV *env, int m, int n)
{
  int      jj, j, i;
  double   s, tmp[amdmsMAX_COEFF + 1];

  for (j = 1; j <= n; j++) {
    /* Calculate U T B */
    s=0.0;
    if (env->vecW[j]) {
      /* Nonzero result only if wj is nonzero. */
      for (i = 1; i <= m; i++) {
	s += env->matU[i][j]*env->vecB[i];
      }
      s /= env->vecW[j]; /* This is the divide by wj . */
    }
    tmp[j]=s;
  }
  for (j = 1; j <= n; j++) {
    /* Matrix multiply by V to get answer. */
    s=0.0;
    for (jj = 1; jj <= n; jj++) {
      s += env->matV[j][jj]*tmp[jj];
    }
    env->env.a[j]=s;
  }
}

/*
  To evaluate the covariance matrix cvm[1..ma][1..ma] of the fit for ma parameters
  obtained by svdfit, call this routine with matrices v[1..ma][1..ma] w[1..ma] as
  returned from svdfit.
*/
void amdmsCalcCovarianceLinear(amdmsFIT_LINEAR_ENV *env)
{
  int k,j,i;
  double sum, wti[amdmsMAX_COEFF + 1];

  for (i = 1; i <= env->env.nCoefficients; i++) {
    wti[i]=0.0;
    if (env->vecW[i]) {
      wti[i]=1.0/(env->vecW[i]*env->vecW[i]);
    }
  }
  for (i = 1; i <= env->env.nCoefficients; i++) {
    /* Sum contributions to covariance matrix (15.4.20). */
    for (j = 1; j <= i; j++) {
      for (sum = 0.0, k = 1; k <= env->env.nCoefficients; k++) {
	sum += env->matV[i][k]*env->matV[j][k]*wti[k];
      }
      env->matCvm[j][i] = env->matCvm[i][j] = sum;
    }
  }
}

/*
  Given a set of data points x[1..ndata], y[1..ndata] with individual standard deviations
  sig[1..ndata], use chi^2 minimization to determine the coeffcients a[1..ma] of the
  fitting function y = SUM(ai * afunci(x)). Here we solve the fitting equations using
  singular value decomposition of the ndata by ma matrix as in 2.6. Arrays u[1..ndata][1..ma],
  v[1..ma][1..ma], and w[1..ma] provide workspace on input; on output they define the
  singular value decomposition, and can be used to obtain the covariance matrix.
  The program returns values for the ma fit parameters a, and chi^2, chisq The user supplies
  a routine funcs(x,afunc,ma) that returns the ma basis functions evaluated at x = x in
  the array afunc[1..ma].
  Reference: Numerical recipes in C: chapter 15.4, page 678
*/
amdmsCOMPL amdmsFitSVDLinear(amdmsFIT_LINEAR_ENV *env, int n, double *x, double *y, double *ye)
{
  int      j, i;
  double   wmax, tmp, thresh, afunc[amdmsMAX_COEFF + 1];

  for (i = 1; i <= n; i++) {
    /* Accumulate coeffcients of the fitting matrix. */
    env->base(x[i], env->env.nCoefficients, afunc);
    tmp = 1.0/ye[i];
    for (j = 1; j <= env->env.nCoefficients; j++) {
      env->matU[i][j]=afunc[j]*tmp;
    }
    env->vecB[i]=y[i]*tmp;
  }
  if (amdmsDecompose(env, n, env->env.nCoefficients) != amdmsSUCCESS) {
    /* Singular value decomposition. */
    return amdmsFAILURE;
  }
  wmax=0.0; /* Edit the singular values,given TOL from the #define statement, between here ... */
  for (j = 1; j <= env->env.nCoefficients; j++) {
    if (env->vecW[j] > wmax) {
      wmax=env->vecW[j];
    }
  }
  thresh=TOL*wmax;
  for (j = 1; j <= env->env.nCoefficients; j++) {
    if (env->vecW[j] < thresh) {
      env->vecW[j]=0.0; /* ...and here. */
    }
  }
  amdmsSolveLinear(env, n, env->env.nCoefficients);
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsAllocSpaceLinear(amdmsFIT_LINEAR_ENV *env, int nCs, int nDPs)
{
  int     i;
  double *m;
  int     coeffFlag;
  int     dataFlag;

  if (env == NULL) {
    return amdmsFAILURE;
  }
  coeffFlag = (env->env.nCoefficients < nCs);
  dataFlag = (env->env.nDataPoints < nDPs);
  env->env.nCoefficients = nCs;
  env->env.nDataPoints = nDPs;
  /* allocate matrix U */
  if (dataFlag || coeffFlag || (env->matU == NULL)) {
    if (env->matU != NULL) {
      free(env->matU[0]);
      free(env->matU);
      env->matU = NULL;
    }
    m = (double *)calloc((size_t)(nDPs + 1)*(nCs + 1), sizeof(double));
    if (m == NULL) {
      amdmsFatal(__FILE__, __LINE__, "memory allocation failure (m)");
      return amdmsFAILURE;
    }
    env->matU = (double **)calloc((size_t)nDPs + 1, sizeof(double*));
    if (env->matU == NULL) {
      free(m);
      amdmsFatal(__FILE__, __LINE__, "memory allocation failure (matU)");
      return amdmsFAILURE;
    }
    for (i = 0; i <= nDPs; i++) {
      env->matU[i] = m + i*(nCs + 1);
    }
  }
  /* allocate matrix V */
  if (coeffFlag || (env->matV == NULL)) {
    if (env->matV != NULL) {
      free(env->matV[0]);
      free(env->matV);
      env->matV = NULL;
    }
    m = (double *)calloc((size_t)(nCs + 1)*(nCs + 1), sizeof(double));
    if (m == NULL) {
      amdmsFatal(__FILE__, __LINE__, "memory allocation failure (m)");
      return amdmsFAILURE;
    }
    env->matV = (double **)calloc((size_t)nCs + 1, sizeof(double*));
    if (env->matV == NULL) {
      free(m);
      amdmsFatal(__FILE__, __LINE__, "memory allocation failure (matV)");
      return amdmsFAILURE;
    }
    for (i = 0; i <= nCs; i++) {
      env->matV[i] = m + i*(nCs + 1);
    }
  }
  /* allocate vector W */
  if (coeffFlag || (env->vecW == NULL)) {
    if (env->vecW != NULL) {
      free(env->vecW);
      env->vecW = NULL;
    }
    env->vecW = (double *)calloc((size_t)nCs + 1, sizeof(double));
    if (env->vecW == NULL) {
      return amdmsFAILURE;
    }
  }
  /* allocate vector B */
  if (dataFlag || (env->vecB == NULL)) {
    if (env->vecB != NULL) {
      free(env->vecB);
      env->vecB = NULL;
    }
    env->vecB = (double *)calloc((size_t)nDPs + 1, sizeof(double));
    if (env->vecB == NULL) {
      return amdmsFAILURE;
    }
  }
  /* allocate matrix CVM */
  if (coeffFlag || (env->matCvm == NULL)) {
    if (env->matCvm != NULL) {
      free(env->matCvm[0]);
      free(env->matCvm);
      env->matCvm = NULL;
    }
    m = (double *)calloc((size_t)(nCs + 1)*(nCs + 1), sizeof(double));
    if (m == NULL) {
      return amdmsFAILURE;
    }
    env->matCvm = (double **)calloc((size_t)nCs + 1, sizeof(double*));
    if (env->matCvm == NULL) {
      free(m);
      return amdmsFAILURE;
    }
    for (i = 0; i <= nCs; i++) {
      env->matCvm[i] = m + i*(nCs + 1);
    }
  }
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsFitLinear(void *henv, int n, double *x, double *y, double *ye)
{
  amdmsFIT_LINEAR_ENV   *env = (amdmsFIT_LINEAR_ENV*)henv;
  int                   i;
  int                   retVal;

  if (env == NULL) {
    return amdmsFAILURE;
  }
  /* amdmsDebug(__FILE__, __LINE__, "amdmsFitLinear(.., %d, .., .., ..)", n); */
  if (amdmsAllocSpaceLinear(env, env->env.nCoefficients, n) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  for (i = 0; i <= env->env.nCoefficients; i++) {
    env->env.a[i] = 0.0;
    env->env.ae[i] = 0.0;
  }
  retVal = amdmsFitSVDLinear(env, n, x - 1, y - 1, ye - 1);
  if (!retVal) {
    amdmsInfo(__FILE__, __LINE__, " no fit possible!!!");
    return 0;
  } else {
    amdmsCalcCovarianceLinear(env);
    for (i = 0; i < env->env.nCoefficients; i++) {
      env->env.a[i] = env->env.a[i + 1];
      env->env.ae[i] = sqrt(env->matCvm[i + 1][i + 1]);
    }
    amdmsCalcQuality(&(env->env), n, x, y, ye);
  }
  /*
  amdmsInfo(__FILE__, __LINE__, " fitted parameters:");
  for (i = 0; i < env->env.nCoefficients; i++) {
    amdmsInfo(__FILE__, __LINE__, " a%d = %.4e [%.4e]", i, a[i], ae[i]);
  }
  amdmsInfo(__FILE__, __LINE__, " chi^2     = %12.4f", env->env.chi2);
  amdmsInfo(__FILE__, __LINE__, " absDist^2 = %12.8f", env->env.absDist2);
  amdmsInfo(__FILE__, __LINE__, " relDist^2 = %12.8f", env->env.relDist2);
  amdmsInfo(__FILE__, __LINE__, " covariance matrix:");
  for (i = 1; i <= env->env.nCoefficients; i++) {
    for (j = 1; j <= env->env.nCoefficients; j++) {
      amdmsInfo(__FILE__, __LINE__, " a%d:a%d = %12.4f", i - 1, j - 1, env->matCvm[i][j]);
    }
  }
  */
  return 1;
}

double amdmsEvalLinear(void *henv, double x)
{
  amdmsFIT_LINEAR_ENV   *env = (amdmsFIT_LINEAR_ENV*)henv;
  int                  i;
  double               y = 0;
  double               afunc[amdmsMAX_COEFF + 1];

  if (env == NULL) {
    return 0.0;
  }
  env->base(x, env->env.nCoefficients, afunc);
  for (i = 0; i < env->env.nCoefficients; i++) {
    y += env->env.a[i]*afunc[i + 1];
  }
  return y;
}

void amdmsPolynomialBaseFunc(double x, int np, double *p)
{
  int i;

  p[1] = 1.0;
  for (i = 2; i <= np; i++) {
    p[i] = p[i - 1]*x;
  }
}

void amdmsRayBaseFunc(double x, int np, double *p)
{
  p[1] = x;
}

#define SWAP(a,b) {temp=(a);(a)=(b);(b)=temp;}

void amdmsExpandCovariance(double **covar, int ma, int ia[], int mfit)
/*
 * Expand in storage the covariance matrix covar so as to take into account
 * parameters that are being held fixed.
 * (For the latter,return zero covariances.)
 */
{
  int i,j,k;
  double temp;

  for (i = mfit + 1; i <= ma; i++) {
    for (j = 1; j <= i; j++) {
      covar[i][j]=covar[j][i]=0.0;
    }
  }
  k=mfit;
  for (j = ma; j >= 1; j--) {
    if (ia[j]) {
      for (i = 1; i <= ma; i++) {
	SWAP(covar[i][k],covar[i][j]);
      }
      for (i = 1; i <= ma; i++) {
	SWAP(covar[k][i],covar[j][i]);
      }
      k--;
    }
  }
}

void amdmsGaussJordan(amdmsFIT_NONLINEAR_ENV *env, double **a, int n, double **b, int m)
/* 
 * Linear equation solution by Gauss-Jordan elimination, equation (2.1.1) above.
 * a[1..n][1..n] is the input matrix. b[1..n][1..m] is input containing
 * the m right-hand side vectors. On output, a is replaced by its matrix
 * inverse, and b is replaced by the corresponding set of solution vectors.
 */
{
  int i,icol = 1,irow = 1,j,k,l,ll;
  double big,dum,pivinv,temp;

  /* The integer arrays ipiv, indxr,andindxc are used for bookkeeping on the pivoting. */
  for (j = 1; j <= n; j++) {
    env->ivecIpiv[j]=0;
  }
  for (i = 1; i <= n; i++) {
    /* This is the main loop over the columns to be reduced. */
    big=0.0;
    for (j = 1; j <= n; j++) {
      /* This is the outer loop of the search for a pivot element. */
      if (env->ivecIpiv[j] != 1) {
	for (k = 1; k <= n; k++) {
	  if (env->ivecIpiv[k] == 0) {
	    if (fabs(a[j][k]) >= big) {
	      big=fabs(a[j][k]);
	      irow=j; icol=k;
	    }
	  }
	}
      }
    }
    ++(env->ivecIpiv[icol]);
    /*
     * We now have the pivot element, so we interchange rows, if needed,
     * to put the pivot element on the diagonal. The columns are not
     * physically interchanged, only relabeled:
     * indxc[i], the column of the ith pivot element, is the ith column
     * that is reduced, while indxr[i] is the row in which that pivot
     * element was originally located. If indxr[i] != indxc[i] there is
     * an implied column interchange. With this form of bookkeeping,
     * the solution b's will end up in the correct order, and the inverse
     * matrix will be scrambled by columns.
     */
    if (irow != icol) {
      for (l=1;l<=n;l++) {
	SWAP(a[irow][l],a[icol][l]);
      }
      for (l=1;l<=m;l++) {
	SWAP(b[irow][l],b[icol][l]);
      }
    }
    /* We are now ready to divide the pivot row by the pivot element, located at irow and icol. */
    env->ivecIndxr[i]=irow;
    env->ivecIndxc[i]=icol;
    if (a[icol][icol] == 0.0) {
      amdmsError(__FILE__, __LINE__, "GaussJordan, Singular Matrix");
      return;
    }
    pivinv=1.0/a[icol][icol];
    a[icol][icol]=1.0;
    for (l=1;l<=n;l++) {
      a[icol][l] *= pivinv;
    }
    for (l = 1; l <= m; l++) {
      b[icol][l] *= pivinv;
    }
    for (ll = 1; ll <= n; ll++) {
      /* Next, we reduce the rows... */
      if (ll != icol) {
	/* ...except for the pivot one, of course. */
	dum=a[ll][icol];
	a[ll][icol]=0.0;
	for (l = 1 ; l <= n; l++) {
	  a[ll][l] -= a[icol][l]*dum;
	}
	for (l = 1; l <= m; l++) {
	  b[ll][l] -= b[icol][l]*dum;
	}
      }
    }
  }
  /*
   * This is the end of the main loop over columns of the reduction. It only
   * remains to unscramble the solution in view of the column interchanges.
   * We do this by interchanging pairs of columns in the reverse order that
   * the permutation was built up.
   */
  for (l = n ; l >= 1; l--) {
    if (env->ivecIndxr[l] != env->ivecIndxc[l]) {
      for (k = 1; k <= n; k++) {
	SWAP(a[k][env->ivecIndxr[l]],a[k][env->ivecIndxc[l]]);
      }
    }
  }
  /* And we are done. */
}

void amdmsEvaluate(amdmsFIT_NONLINEAR_ENV *env, int ndata, double x[], double y[], double sig[],
		  double a[], double **alpha, double beta[])
/*
 * Used by mrqmin to evaluate the linearize fitting matrix alpha and
 * vector beta as in (15.5.8), and calculate chi^2.
 */
{
  int ma = env->env.nCoefficients;
  int *ia = env->ivecIa;
  int i,j,k,l,m,mfit=0;
  double ymod,wt,sig2i,dy;

  for (j = 1; j <= ma; j++) {
    if (ia[j]) {
      mfit++;
    }
  }
  for (j = 1; j <= mfit; j++) {
    /* Initialize (symmetric) alpha beta */
    for (k = 1; k <= j; k++) {
      alpha[j][k]=0.0;
    }
    beta[j]=0.0;
  }
  env->env.chi2 = 0.0;
  for (i = 1; i <= ndata; i++) {
    /* Summation loop over all data. */
    env->base(x[i], ma, a, &ymod, env->vecDyda);
    sig2i=1.0/(sig[i]*sig[i]);
    dy=y[i]-ymod;
    for (j = 0, l = 1; l <= ma; l++) {
      if (ia[l]) {
	wt=env->vecDyda[l]*sig2i;
	for (j++, k = 0, m = 1; m <= l; m++) {
	  if (ia[m]) {
	    alpha[j][++k] += wt*env->vecDyda[m];
	  }
	}
	beta[j] += dy*wt;
      }
    } 
    env->env.chi2 += dy*dy*sig2i; /* And find chi^2. */
  }
  for (j = 2; j <= mfit; j++) {
    /* Fill in the symmetric side. */
    for (k = 1; k < j; k++) {
      alpha[k][j]=alpha[j][k];
    }
  }
}

void amdmsLevenbergMarquardt(amdmsFIT_NONLINEAR_ENV *env, 
			    int ndata, double *x, double *y, double *ye)
/*
 * Levenberg-Marquardt method, attempting to reduce the value chi^2 of a fit
 * between a set of data points x[1..ndata], y[1..ndata] with individual
 * standard deviations ye[1..ndata], and a nonlinear function dependent on
 * ma coeffcients a[1..ma].
 * The input array ia[1..ma] indicates by nonzero entries those components
 * of a that should be fitted for, and by zero entries those components that
 * should be held fixed at their input values.
 * The program returns current best-fit values for the parameters a[1..ma],
 * and chi^2 = chisq. The arrays covar[1..ma][1..ma], alpha[1..ma][1..ma]
 * are used as working space during most iterations.
 * Supply a routine funcs(x,a,yfit,dyda,ma) that evaluates the fitting function
 * yfit, and its derivatives dyda[1..ma] with respect to the fitting
 * parameters a at x.
 * On the first call provide an initial guess for the parameters a, and set
 * alamda < 0 for initialization (which then sets alamda=.001). If a step
 * succeeds chisq becomes smaller an alamda decreases by a factor of 10.
 * If a step fails alamda grows by a factor of 10. You must call this routine
 * repeatedly until convergence is achieved. Then, make one final call with
 * alamda=0 so that covar[1..ma][1..ma] returns the covariance matrix,
 * and alpha the curvature matrix. (Parameters held fixed will return zero
 * covariances.)
 */
{
  int j,k,l;
  int ma = env->env.nCoefficients;
  int *ia = env->ivecIa;

  if (env->alamda < 0.0) {
    /* Initialization. */
    for (env->mfit=0,j=1;j<=ma;j++)
      if (ia[j]) {
	env->mfit++;
      }
    env->alamda=0.001;
    amdmsEvaluate(env, ndata, x, y, ye, env->env.a, env->matAlpha, env->vecBeta);
    env->ochi2 = env->env.chi2;
    for (j=1;j<=ma;j++) {
      env->vecAtry[j]=env->env.a[j];
    }
  }
  for (j=1;j<=env->mfit;j++) {
    /* Alter linearized fitting matrix, by augmenting diagonal elements. */
    for (k=1;k<=env->mfit;k++) {
      env->matCvm[j][k]=env->matAlpha[j][k];
    }
    env->matCvm[j][j]=env->matAlpha[j][j]*(1.0+env->alamda);
    env->matOneda[j][1]=env->vecBeta[j];
  }
  amdmsGaussJordan(env, env->matCvm,env->mfit,env->matOneda,1); /* Matrix solution. */
  for (j=1;j<=env->mfit;j++) {
    env->vecDa[j]=env->matOneda[j][1];
  }
  if (env->alamda == 0.0) {
    /* Once converged,evaluate covariance matrix. */
    amdmsExpandCovariance(env->matCvm,ma,ia,env->mfit);
    amdmsExpandCovariance(env->matAlpha,ma,ia,env->mfit);/* Spread out alpha to its full size too. */
    return;
  }
  for (j=0,l=1;l<=ma;l++) {
    /* Did the trial succeed? */
    if (ia[l]) {
      env->vecAtry[l]=env->env.a[l]+env->vecDa[++j];
    }
  }
  amdmsEvaluate(env, ndata, x, y, ye, env->vecAtry, env->matCvm, env->vecDa);
  if (env->env.chi2 < env->ochi2) {
    /* Success, accept the new solution. */
    env->alamda *= 0.1;
    env->ochi2 = env->env.chi2;
    for (j=1;j<=env->mfit;j++) {
      for (k=1;k<=env->mfit;k++) {
	env->matAlpha[j][k]=env->matCvm[j][k];
      }
      env->vecBeta[j]=env->vecDa[j];
    }
    for (l=1;l<=ma;l++) {
      env->env.a[l]=env->vecAtry[l];
    }
  } else {
    /* Failure, increase alamda an return. */
    env->alamda *= 10.0;
    env->env.chi2 = env->ochi2;
  }
}

amdmsCOMPL amdmsAllocSpaceNonLinear(amdmsFIT_NONLINEAR_ENV *env, int nCs)
{
  int       i;
  double   *m;
  int       coeffFlag;
  
  if (env == NULL) {
    return amdmsFAILURE;
  }
  coeffFlag = (env->env.nCoefficients < nCs);
  env->env.nCoefficients = nCs;
  /* allocate vector indxc */
  if (coeffFlag || (env->ivecIndxc == NULL)) {
    env->ivecIndxc = (int *)realloc(env->ivecIndxc, (nCs + 1)*sizeof(int));
    if (env->ivecIndxc == NULL) {
      return amdmsFAILURE;
    }
  }
  /* allocate vector indxr */
  if (coeffFlag || (env->ivecIndxr == NULL)){
    env->ivecIndxr = (int *)realloc(env->ivecIndxr, (nCs + 1)*sizeof(int));
    if (env->ivecIndxr == NULL) {
      return amdmsFAILURE;
    }
  }
  /* allocate vector ipiv */
  if (coeffFlag || (env->ivecIpiv == NULL)){
    env->ivecIpiv = (int *)realloc(env->ivecIpiv, (nCs + 1)*sizeof(int));
    if (env->ivecIpiv == NULL) {
      return amdmsFAILURE;
    }
  }
  /* allocate vector dyda */
  if (coeffFlag || (env->vecDyda == NULL)){
    env->vecDyda = (double *)realloc(env->vecDyda, (nCs + 1)*sizeof(double));
    if (env->vecDyda == NULL) {
      return amdmsFAILURE;
    }
  }
  /* allocate vector vecAtry */
  if (coeffFlag || (env->vecAtry == NULL)){
    env->vecAtry = (double *)realloc(env->vecAtry, (nCs + 1)*sizeof(double));
    if (env->vecAtry == NULL) {
      return amdmsFAILURE;
    }
  }
  /* allocate vector  */
  if (coeffFlag || (env->vecBeta == NULL)){
    env->vecBeta = (double *)realloc(env->vecBeta, (nCs + 1)*sizeof(double));
    if (env->vecBeta == NULL) {
      return amdmsFAILURE;
    }
  }
  /* allocate vector  */
  if (coeffFlag || (env->vecDa == NULL)){
    env->vecDa = (double *)realloc(env->vecDa, (nCs + 1)*sizeof(double));
    if (env->vecDa == NULL) {
      return amdmsFAILURE;
    }
  }
  /* allocate matrix V */
  if (coeffFlag || (env->matOneda == NULL)) {
    if (env->matOneda != NULL) {
      free(env->matOneda[0]);
      free(env->matOneda);
      env->matOneda = NULL;
    }
    m = (double *)calloc((size_t)(nCs + 1)*(nCs + 1), sizeof(double));
    if (m == NULL) {
      return amdmsFAILURE;
    }
    env->matOneda = (double **)calloc((size_t)(nCs + 1), sizeof(double*));
    if (env->matOneda == NULL) {
      free(m);
      return amdmsFAILURE;
    }
    for (i = 0; i <= nCs; i++) {
      env->matOneda[i] = m + i*(nCs + 1);
    }
  }
  /* allocate vector ia */
  if (coeffFlag || (env->ivecIa == NULL)){
    env->ivecIa = (int *)realloc(env->ivecIa, (nCs + 1)*sizeof(int));
    if (env->ivecIa == NULL) {
      return amdmsFAILURE;
    }
  }
  /* allocate matrix CVM */
  if (coeffFlag || (env->matCvm == NULL)) {
    if (env->matCvm != NULL) {
      free(env->matCvm[0]);
      free(env->matCvm);
      env->matCvm = NULL;
    }
    m = (double *)calloc((size_t)(nCs + 1)*(nCs + 1), sizeof(double));
    if (m == NULL) {
      return amdmsFAILURE;
    }
    env->matCvm = (double **)calloc((size_t)(nCs + 1), sizeof(double*));
    if (env->matCvm == NULL) {
      free(m);
      return amdmsFAILURE;
    }
    for (i = 0; i <= nCs; i++) {
      env->matCvm[i] = m + i*(nCs + 1);
    }
  }
  /* allocate matrix Alpha */
  if (coeffFlag || (env->matAlpha == NULL)) {
    if (env->matAlpha != NULL) {
      free(env->matAlpha[0]);
      free(env->matAlpha);
      env->matAlpha = NULL;
    }
    m = (double *)calloc(((size_t)nCs + 1)*(nCs + 1), sizeof(double));
    if (m == NULL) {
      return amdmsFAILURE;
    }
    env->matAlpha = (double **)calloc((size_t)(nCs + 1), sizeof(double*));
    if (env->matAlpha == NULL) {
      free(m);
      return amdmsFAILURE;
    }
    for (i = 0; i <= nCs; i++) {
      env->matAlpha[i] = m + i*(nCs + 1);
    }
  }
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsFitNonLinear(void *henv, int n, double *x, double *y, double *ye)
{
  amdmsFIT_NONLINEAR_ENV  *env = (amdmsFIT_NONLINEAR_ENV*)henv;
  int      i, j;
  double   oldChisq;
  double   oldAlamda;
  int      iIter;
    
  /* amdmsDebug(__FILE__, __LINE__, "amdmsFitNonLinear(.., %d, .., .., ..)", n); */
  if (amdmsAllocSpaceNonLinear(env, env->env.nCoefficients) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  for (i = 0; i <= env->env.nCoefficients; i++) {
    env->ivecIndxc[i] = 0;
    env->ivecIndxr[i] = 0;
    env->ivecIpiv[i] = 0;
    env->vecDyda[i] = 0.0;
    env->vecAtry[i] = 0.0;
    env->vecBeta[i] = 0.0;
    env->vecDa[i] = 0.0;
    env->ivecIa[i] = 1;
    for (j = 0; j <= env->env.nCoefficients; j++) {
      env->matOneda[i][j] = 0.0;
      env->matCvm[i][j] = 0.0;
      env->matAlpha[i][j] = 0.0;
    }
  }
  for (i = 0; i <= env->env.nCoefficients; i++) {
    env->env.ae[i] = 0.0;
  }
  env->ochi2 = 0.0;
  env->mfit = 0;
  env->alamda = -1.0;
  iIter = 0;
  env->init(henv, n, x, y, ye);
  amdmsLevenbergMarquardt(env, n, x - 1, y - 1, ye - 1);
  iIter++;
  /* amdmsInfo(__FILE__, __LINE__, "Iteration %d:", iIter); */
  /* amdmsInfo(__FILE__, __LINE__, "  chi^2     = %12.4f\", env->env.chi2);*/
  oldChisq = env->env.chi2;
  oldAlamda = env->alamda;
  do {
    amdmsLevenbergMarquardt(env, n, x - 1, y - 1, ye - 1);
    iIter++;
    /* amdmsInfo(__FILE__, __LINE__, "Iteration %d:", iIter); */
    /* amdmsInfo(__FILE__, __LINE__, "  chi^2     = %12.4f", env->env.chi2); */
    if (env->env.chi2 >= oldChisq) {
      oldChisq = env->env.chi2;
      continue;
    } else if (env->env.chi2 >= 0.999*oldChisq) {
      break;
    } else {
      oldChisq = env->env.chi2;
    }
  } while (iIter < 100);
  env->alamda = 0.0;
  amdmsLevenbergMarquardt(env, n, x - 1, y - 1, ye - 1);
  /* amdmsInfo(__FILE__, __LINE__, "Iteration %d:", iIter); */
  /* amdmsInfo(__FILE__, __LINE__, "  chi^2     = %12.4f", env->env.chi2); */
  for (i = 0; i < env->env.nCoefficients; i++) {
    env->env.a[i] = env->env.a[i + 1];
    env->env.ae[i] = sqrt(env->matCvm[i + 1][i + 1]);
  }
  amdmsCalcQuality(henv, n, x, y, ye);
  /*
  amdmsInfo(__FILE__, __LINE__, " fitted parameters:");
  for (i = 0; i < env->env.nCoefficients; i++) {
    amdmsInfo(__FILE__, __LINE__, " a%d = %.4e [%.4e]", i, a[i], ae[i]);
  }
  amdmsInfo(__FILE__, __LINE__, " chi^2     = %12.4f", env->env.chi2);
  amdmsInfo(__FILE__, __LINE__, " absDist^2 = %12.8f", env->env.absDist2);
  amdmsInfo(__FILE__, __LINE__, " relDist^2 = %12.8f", env->env.relDist2);
  amdmsInfo(__FILE__, __LINE__, " covariance matrix:");
  for (i = 1; i <= env->env.nCoefficients; i++) {
    for (j = 1; j <= env->env.nCoefficients; j++) {
      amdmsInfo(__FILE__, __LINE__, " a%d:a%d = %12.4f", i - 1, j - 1, env->matCvm[i][j]);
    }
  }
  */
  return amdmsSUCCESS;
}

double amdmsEvalNonLinear(void *henv, double x)
{
  amdmsFIT_NONLINEAR_ENV  *env = (amdmsFIT_NONLINEAR_ENV*)henv;
  double                  y = 0;
  double                  afunc[amdmsMAX_COEFF + 1];

  if (env == NULL) {
    return 0.0;
  }
  env->base(x, env->env.nCoefficients, env->env.a - 1, &y, afunc);
  return y;
}

void amdmsNonLinearPixelInit(void *henv, int n, double *x, double *y)
{
  amdmsFIT_NONLINEAR_ENV  *env = (amdmsFIT_NONLINEAR_ENV*)henv;
  env->env.a[1] = 1.0;
  env->env.a[2] = 0.0001;
}

void amdmsNonLinearPixelBaseFunc(double x, int na, double *a, double *y, double *dyda)
{
    *y = a[1]*x/(1 + a[2]*x);
    dyda[1] = x/(1 + a[2]*x);
    dyda[2] = (1 + a[2]*x - a[1]*x*x)/(1 + a[2]*x)/(1 + a[2]*x);
}

void amdmsGeneralExpInit(void *henv, int n, double *x, double *y)
{
  amdmsFIT_NONLINEAR_ENV  *env = (amdmsFIT_NONLINEAR_ENV*)henv;
  env->env.a[1] = 1.0;
  env->env.a[2] = 1.0;
  env->env.a[3] = 1.0;
}

void amdmsGeneralExpBaseFunc(double x, int na, double *a, double *y, double *dyda)
{
    *y = a[1] + a[2]*exp(a[3]*x);
    dyda[1] = 1.0;
    dyda[2] = exp(a[3]*x);
    dyda[3] = a[2]*x*exp(a[3]*x);
}

#define amdmsEBFIT_NCOEFF 3

void amdmsElectronicBiasInit(void *henv, int n, double *x, double *y)
{
  amdmsFIT_NONLINEAR_ENV  *env = (amdmsFIT_NONLINEAR_ENV*)henv;
  env->env.a[1] = 1.0;
  env->env.a[2] = 1.0;
  env->env.a[3] = -1.0;
}

void amdmsElectronicBiasBaseFunc(double x, int na, double *a, double *y, double *dyda)
{
    *y = a[1] + a[2]*exp(a[3]*x);
    dyda[1] = 1.0;
    dyda[2] = exp(a[3]*x);
    dyda[3] = a[2]*x*exp(a[3]*x);
}

/*
void amdmsElectronicBiasInit(void *henv, int n, double *x, double *y, double *ye)
{
  amdmsFIT_NONLINEAR_ENV  *env = (amdmsFIT_NONLINEAR_ENV*)henv;
  env->env.a[1] = 1.0;
  env->env.a[2] = 1.0;
  env->env.a[3] = -1.0;
  env->env.a[4] = 1.0;
  env->env.a[5] = 0.5;
  env->env.a[6] = -1.0;
}

void amdmsElectronicBiasBaseFunc(double x, int na, double *a, double *y, double *dyda)
{
  *y = a[1] + a[2]*exp(a[3]*x) + a[4]*pow(x, a[5])*exp(a[6]*x);
  dyda[1] = 1.0;
  dyda[2] = exp(a[3]*x);
  dyda[3] = a[2]*x*exp(a[3]*x);
  dyda[4] = pow(x, a[5])*exp(a[6]*x);
  dyda[5] = a[4]*pow(x, a[5])*exp(a[6]*x)*log(x);
  dyda[6] = x*a[4]*pow(x, a[5])*exp(a[6]*x);
}
*/

/*
void amdmsElectronicBiasInit(void *henv, int n, double *x, double *y, double *ye)
{
  amdmsFIT_NONLINEAR_ENV  *env = (amdmsFIT_NONLINEAR_ENV*)henv;
  env->env.a[1] = 1.0;
  env->env.a[2] = -1.0;
  env->env.a[3] = 1.0;
  env->env.a[4] = 1.0;
  env->env.a[5] = 1.0;
  env->env.a[6] = -1.0;
}

void amdmsElectronicBiasBaseFunc(double x, int na, double *a, double *y, double *dyda)
{
  double fa, fb;

  fa = a[1] + a[2]*x + a[3]*x*x;
  fb = a[4] + a[5]*x + a[6]*x*x;
  *y = fa/fb;
  dyda[1] = 1.0/fb;
  dyda[2] = x/fb;
  dyda[3] = x*x/fb;
  dyda[4] = -fa/fb/fb;
  dyda[5] = -fa*x/fb/fb;
  dyda[6] = -fa*x*x/fb/fb;
}
*/

#define amdmsCFFIT_NCOEFF 3

/* NDU = a + g*VDU + c*g^2*VDU^2 */
/* a[1] = a */
/* a[2] = g */
/* a[3] = c */
void amdmsConversionFactorInit(void *henv, int n, double *x, double *y)
{
  amdmsFIT_NONLINEAR_ENV  *env = (amdmsFIT_NONLINEAR_ENV*)henv;
  env->env.a[1] = 0.0;
  env->env.a[2] = 4.0;
  env->env.a[3] = 0.001;
}

void amdmsConversionFactorBaseFunc(double x, int na, double *a, double *y, double *dyda)
{
    *y = a[1] + a[2]*x + a[3]*a[2]*a[2]*x*x;
    dyda[1] = 1.0;
    dyda[2] = x + 2*a[2]*a[1]*x*x;
    dyda[3] = a[2]*a[2]*x*x;
}

amdmsCOMPL amdmsFindDataSaturation(amdmsFIT_DATA_ENV *env)
{
  int         iPoint;
  int         i;
  int         clusterPoint;
  int         clusterSize;
  double      boxX;
  double      boxY;
  double      hminX;
  double      hmaxX;
  double      hminY;
  double      hmaxY;
  double      minX;
  double      maxX;
  double      minY;
  double      maxY;
  int         bestClusterDP;
  int         bestClusterSize;
  int         bestClusterFlags[100];
  
  if (!env->satFlag) {
    return amdmsSUCCESS;
  }
  env->firstDP = env->env.nDataPoints - 1;
  env->lastDP = 0;
  for (iPoint = 0; iPoint < env->env.nDataPoints; iPoint++) {
    if (env->useFlags[iPoint]) {
      env->firstDP = MIN(env->firstDP, iPoint);
      env->lastDP = MAX(env->lastDP, iPoint);
    }
  }
  if (env->firstDP == (env->env.nDataPoints - 1)) {
    /* all use flags are false */
    return amdmsFAILURE;
  }
  /* calculate the minima and maxima of x and y */
  minX = env->valX[env->firstDP];
  maxX = env->valX[env->firstDP];
  minY = env->valY[env->firstDP];
  maxY = env->valY[env->firstDP];
  for (iPoint = env->firstDP + 1; iPoint <= env->lastDP; iPoint++) {
    if (env->useFlags[iPoint]) {
      minX = MIN(minX, env->valX[iPoint]);
      maxX = MAX(maxX, env->valX[iPoint]);
      minY = MIN(minY, env->valY[iPoint]);
      maxY = MAX(maxY, env->valY[iPoint]);
    }
  }
  /* try to find a cluster of points which are close together */
  boxX = env->satWidth*(maxX - minX);
  boxY = env->satHeight*(maxY - minY);
  /* amdmsDebug(__FILE__, __LINE__, "#  saturation characteristics:"); */
  /* amdmsDebug(__FILE__, __LINE__, "#    x = [%12.4f .. %12.4f] y = [%12.4f .. %12.4f]", minX, maxX, minY, maxY); */
  /* amdmsDebug(__FILE__, __LINE__, "#    box = %12.4f, %12.4f", boxX, boxY); */
  bestClusterDP = -1;
  bestClusterSize = 1;
  for (clusterPoint = env->lastDP; clusterPoint >= (env->lastDP - 1); clusterPoint--) {
    for (iPoint = env->firstDP; iPoint <= env->lastDP; iPoint++) {
      env->useFlags[iPoint] = 1;  /* set use flags to true for all valid data points */
    }
    env->useFlags[clusterPoint] = 0;
    clusterSize = 1;
    /* now try to find more points of the cluster */
    for (iPoint = env->lastDP; iPoint >= env->firstDP; iPoint--) {
      if (env->useFlags[iPoint] == 0) continue;
      /* set current point as candidate */
      env->useFlags[iPoint] = 0;
      /* now calculate the box of all candidate points */
      hminX = env->valX[clusterPoint];
      hmaxX = env->valX[clusterPoint];
      hminY = env->valY[clusterPoint];
      hmaxY = env->valY[clusterPoint];
      for (i = env->firstDP; i <= env->lastDP; i++) {
	if (env->useFlags[i] == 0) {
	  hminX = MIN(hminX, env->valX[i]);
	  hmaxX = MAX(hmaxX, env->valX[i]);
	  hminY = MIN(hminY, env->valY[i]);
	  hmaxY = MAX(hmaxY, env->valY[i]);
	}
      }
      if (((hmaxX - hminX) > boxX) || ((hmaxY - hminY) > boxY)) {
	/* candidate point is outside of the allowed box around the cluster */
	env->useFlags[iPoint] = 1;
      } else {
	clusterSize++;
      }
    }
    /* delete all singular points except the cluster point */
    for (iPoint = env->firstDP + 1; iPoint < env->lastDP; iPoint++) {
      if (iPoint == clusterPoint) continue;
      if (!env->useFlags[iPoint] && env->useFlags[iPoint - 1] && env->useFlags[iPoint + 1]) {
	env->useFlags[iPoint] = 1;
	clusterSize--;
      }
    }
    if (clusterSize > 1) {
      /* at least two data points are close together */
      /* look if there is a data point who's neighbours are in the cluster */
      for (iPoint = env->firstDP + 1; iPoint < env->lastDP; iPoint++) {
	if (env->useFlags[iPoint] && !env->useFlags[iPoint - 1] && !env->useFlags[iPoint + 1]) {
	  env->useFlags[iPoint] = 0;
	  clusterSize++;
	}
      }
      /* clear all points after the found cluster */
      for (iPoint = env->lastDP; iPoint >= env->firstDP; iPoint--) {
	if (env->useFlags[iPoint] == 0) break;
	env->useFlags[iPoint] = 0;
	clusterSize++;
      }
      if (clusterSize > bestClusterSize) {
	bestClusterDP = clusterPoint;
	bestClusterSize = clusterSize;
	for (iPoint = env->firstDP; iPoint <= env->lastDP; iPoint++) {
	  /* copy only the new flags */
	  bestClusterFlags[iPoint] = env->useFlags[iPoint];
	}
      }
      /* amdmsDebug(__FILE__, __LINE__, "#  cluster found, contains the following datapoints:"); */
      for (iPoint = 0; iPoint < env->env.nDataPoints; iPoint++) {
	if (env->useFlags[iPoint] == 0) {
	  /* amdmsDebug(__FILE__, __LINE__, "#    %2d  %12.4f  %12.4f", iPoint, valX[iPoint], valY[iPoint]); */
	}
      }
    }
  }
  if (bestClusterDP != -1) {
    for (iPoint = env->firstDP; iPoint <= env->lastDP; iPoint++) {
      /* copy only the new flags */
      if (bestClusterFlags[iPoint]) {
	env->satDP = iPoint; /* set satDP to the last valid data point */
      }
      env->useFlags[iPoint] = bestClusterFlags[iPoint];
    }
  }
  return amdmsSUCCESS;
}

double amdmsTryDataFit(amdmsFIT_DATA_ENV *env)
{
  int         nDP;
  int         iPoint;
  /* int         iCoeff; */

  /* copy used data points into arrays */
  nDP = 0;
  for (iPoint = 0; iPoint < env->env.nDataPoints; iPoint++) {
    if (env->useFlags[iPoint]) {
      env->usedX[nDP] = env->valX[iPoint];
      env->usedY[nDP] = env->valY[iPoint];
      if (env->valYErr == NULL) {
	  env->usedYErr[nDP] = 1.0;
      } else {
	  env->usedYErr[nDP] = env->valYErr[iPoint];
      }
      nDP++;
    }
  }
  if (amdmsDoFit(env->fit, nDP, env->usedX, env->usedY, env->usedYErr) != amdmsSUCCESS) {
    return 0.0;
  }
  /*
  amdmsDebug(__FILE__, __LINE__, "#********************************");
  amdmsDebug(__FILE__, __LINE__, "#  fit with %d data points:", nDP);
  for (iPoint = 0; iPoint < env->env.nDataPoints; iPoint++) {
    if (env->useFlags[iPoint]) {
      amdmsDebug(__FILE__, __LINE__, " %d", iPoint);
    }
  }
  for (iCoeff = 0; iCoeff < env->fit->nCoefficients; iCoeff++) {
    amdmsDebug(__FILE__, __LINE__, "#    a%d = %.4e, stdev a%d = %.4e", iCoeff, env->fit->a[iCoeff], iCoeff, env->fit->ae[iCoeff]);
  }
  amdmsDebug(__FILE__, __LINE__, "#    chi^2 = %.4e absDist^2 = %.4e relDist^2 = %.4e",
			     env->fit->chi2, env->fit->absDist2, env->fit->relDist2);
  */
  return env->fit->relDist2;
}

amdmsCOMPL amdmsAllocSpaceData(amdmsFIT_DATA_ENV *env, int nDPs)
{
  int i;

  if (env == NULL) {
    return amdmsFAILURE;
  }
  if (env->env.nDataPoints == nDPs) {
    return amdmsSUCCESS;
  }
  env->env.nDataPoints = nDPs;
  /* allocate vector usedX */
  env->usedX = (double *)realloc(env->usedX, (nDPs + 1)*sizeof(double));
  if (env->usedX == NULL) {
    return amdmsFAILURE;
  }
  /* allocate vector usedY */
  env->usedY = (double *)realloc(env->usedY, (nDPs + 1)*sizeof(double));
  if (env->usedY == NULL) {
    return amdmsFAILURE;
  }
  /* allocate vector usedYErr */
  env->usedYErr = (double *)realloc(env->usedYErr, (nDPs + 1)*sizeof(double));
  if (env->usedYErr == NULL) {
    return amdmsFAILURE;
  }
  /* allocate vector useFlags */
  env->useFlags = (int *)realloc(env->useFlags, (nDPs + 1)*sizeof(int));
  if (env->useFlags == NULL) {
    return amdmsFAILURE;
  }
  for (i = 0; i < nDPs; i++) {
    env->usedX[i] = 0.0;
    env->usedY[i] = 0.0;
    env->usedYErr[i] = 0.0;
    env->useFlags[i] = 0;
  }
  return amdmsSUCCESS;
}

#define amdmsMIN_DATAPOINTS 4

amdmsCOMPL amdmsFitData(void *henv, int n, double *x, double *y, double *ye)
{
  amdmsFIT_DATA_ENV  *env = (amdmsFIT_DATA_ENV*)henv;
  int                iPoint;
  int                nDP;
  double             refQuality;
  int                currDP;
  double             currQuality;
  int                foundDP;
  double             foundQuality;
  int                nDelCounter;

  if (amdmsAllocSpaceData(env, n) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  env->env.nDataPoints = n;
  env->valX = x;
  env->valY = y;
  env->valYErr = ye;
  for (iPoint = 0; iPoint < env->env.nDataPoints; iPoint++) {
    env->useFlags[iPoint] = 1;
  }
  env->satDP = -1;
  if (amdmsFindDataSaturation(env) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  nDP = 0;
  env->firstDP = env->env.nDataPoints - 1;
  env->lastDP = 0;
  for (iPoint = 0; iPoint < env->env.nDataPoints; iPoint++) {
    if (env->useFlags[iPoint]) {
      env->firstDP = MIN(env->firstDP, iPoint);
      env->lastDP = MAX(env->lastDP, iPoint);
      nDP++;
    }
  }
  refQuality = amdmsTryDataFit(env); /* use all data points */
  /* first descard data points from the end */
  nDelCounter = env->nDelEnd;
  while ((nDP > amdmsMIN_DATAPOINTS) && (nDelCounter > 0)) {
    /* test if we can improve the quality by discarding the last point */
    env->useFlags[env->lastDP] = 0;  /* do not use the last data point */
    currQuality = amdmsTryDataFit(env);
    if (currQuality < (1.0 - env->minStep)*refQuality) {
      /* we can improve the qualitiy of the fit */
      env->lastDP--;
      refQuality = currQuality;
      nDP--;
      nDelCounter--;
    } else {
      /* no improvement possible */
      env->useFlags[env->lastDP] = 1;  /* use last data point and finish */
      break;
    }
  }
  /* then discard data points from the beginning */
  nDelCounter = env->nDelStart;
  while ((nDP > amdmsMIN_DATAPOINTS) && (nDelCounter > 0)) {
    /* test if we can improve the quality by discarding the first point */
    env->useFlags[env->firstDP] = 0;  /* do not use the first data point */
    currQuality = amdmsTryDataFit(env);
    if (currQuality < (1.0 - env->minStep)*refQuality) {
      /* we can improve the qualitiy of the fit */
      env->firstDP++;
      refQuality = currQuality;
      nDP--;
      nDelCounter--;
    } else {
      /* no improvement possible */
      env->useFlags[env->firstDP] = 1;  /* use current data point */
      break;
    }
  }
  /* test if we can improve the quality by discarding one data point including the first and the last point */
  nDelCounter = env->nDelMiddle;
  while ((nDP > amdmsMIN_DATAPOINTS) && (nDelCounter > 0)) {
    foundDP = -1;
    foundQuality = refQuality;
    for (currDP = env->firstDP; currDP <= env->lastDP; currDP++) {
      if (env->useFlags[currDP] == 0) continue;
      env->useFlags[currDP] = 0;  /* do not use current data point */
      currQuality = amdmsTryDataFit(env);
      if (currQuality < (1.0 - env->minStep)*refQuality) {
	foundDP = currDP;
	foundQuality = currQuality;
      }
      env->useFlags[currDP] = 1;  /* use current data point again */
    }
    if (foundDP != -1) {
      /* we have a possible invalid data point found! */
      env->useFlags[foundDP] = 0;
      refQuality = foundQuality;
      nDP--;
      nDelCounter--;
    } else {
      /* no improvement possible */
      break;
    }
  }
  /* do the real fit */
  amdmsTryDataFit(env);
  return amdmsSUCCESS;
}

double amdmsEvalData(void *henv, double x)
{
  amdmsFIT_DATA_ENV *env = (amdmsFIT_DATA_ENV*)henv;

  if (env == NULL) {
    return 0.0;
  } else {
    return amdmsEvalFit(env->fit, x);
  }
}

amdmsCOMPL amdmsFindSmoothDataOutliers(amdmsFIT_SMOOTH_DATA_ENV *env)
{
  int     i;
  double  limit;

  /* amdmsDebug(__FILE__, __LINE__, "#  FindOutliers():"); */
  env->avgSlope = 0.0;
  env->avgSlopeDifference = 0.0;
  if (env->env.env.nDataPoints < 5) {
    /* not enough data points, we need at least five values */
    return amdmsFAILURE;
  }
  /* calculate all slopes of a polyline as angles [-PI .. +PI] */
  /* amdmsDebug(__FILE__, __LINE__, "#     Slopes:"); */
  for (i = 0; i < env->env.env.nDataPoints - 1; i++) {
    double dx = env->env.valX[i + 1] - env->env.valX[i];
    double dy = env->env.valY[i + 1] - env->env.valY[i];
    env->slopes[i] = atan2(dy, dx);
    /* amdmsDebug(__FILE__, __LINE__, "#        %3d =  %12.4f", i, env->slopes[i]); */
    env->avgSlope += env->slopes[i];
  }
  env->avgSlope /= (double)(env->env.env.nDataPoints - 1);
  /* amdmsDebug(__FILE__, __LINE__, "#        avg =  %12.4f", avgSlope); */
  /* calculate slope difference */
  /* amdmsDebug(__FILE__, __LINE__, "#     Slope differences:"); */
  for (i = 1; i < env->env.env.nDataPoints - 1; i++) {
    env->slopeDifferences[i] = env->slopes[i] - env->slopes[i - 1];
    /* amdmsDebug(__FILE__, __LINE__, "#        %3d =  %12.4f", i, env->slopeDifferences[i]); */
    env->avgSlopeDifference += fabs(env->slopeDifferences[i]);
  }
  env->avgSlopeDifference = fabs(env->avgSlopeDifference/(double)(env->env.env.nDataPoints - 2)); /* average slope difference */
  /* amdmsDebug(__FILE__, __LINE__, "#        avg =  %12.4f", env->avgSlopeDifference); */
  limit = env->avgSlopeDifference*env->slopeLimit;
  /* test special case #1: first data point, assume that all other data points are correct */
  if (fabs(env->slopeDifferences[1]) > limit) {
    /* amdmsDebug(__FILE__, __LINE__, "#   outlier found %d", 0); */
    env->env.useFlags[0] = 0;
  }
  /* test special case #2: last data point, assume that all other data points are correct */
  if (fabs(env->slopeDifferences[env->env.env.nDataPoints - 2]) > limit) {
    /* amdmsDebug(__FILE__, __LINE__, "#   outlier found %d", nDataPoints - 1); */
    env->env.useFlags[env->env.env.nDataPoints - 1] = 0;
  }
  /* test all intermediate cases */
  for (i = 2; i < env->env.env.nDataPoints - 2; i++) {
    if (fabs(env->slopeDifferences[i]) <= limit) continue;
    if (fabs(env->slopeDifferences[i + 1]) <= limit) continue;
    /* amdmsDebug(__FILE__, __LINE__, "#   outlier found %d", i); */
    env->env.useFlags[i] = 0;
    i++;
  }
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsFindSmoothDataSaturation(amdmsFIT_SMOOTH_DATA_ENV *env)
{
  int         i;
  int         lowerSlopeCount = 0;
  double      lowerSlope = 0.0;
  int         upperSlopeCount = 0;
  double      upperSlope = 0.0;
  double      limit;
  int         satFound = 0;
  int         belowFlag = 0;

  /* amdmsDebug(__FILE__, __LINE__, "#  FindSaturation():"); */
  /* step #1: test if two dominant slopes are present */
  for (i = 0; i < env->env.env.nDataPoints - 1; i++) {
    if (env->env.useFlags[i] == 0) continue; /* skip outliers */
    if (env->slopes[i] < env->avgSlope) {
      lowerSlopeCount++;
      lowerSlope += env->slopes[i];
    } else {
      upperSlopeCount++;
      upperSlope += env->slopes[i];
    }
  }
  lowerSlope /= (double)lowerSlopeCount;
  upperSlope /= (double)upperSlopeCount;
  if (fabs(lowerSlope - upperSlope) < 10.0/180.0*M_PI) {
    /* difference between the two slopes is less than 10 degrees -> no saturation present */
    return amdmsSUCCESS;
  }
  /* step #2: adjust slope limit */
  limit = 0.5*(lowerSlope + upperSlope);
  /* step #3: find the direction of the saturation limit (below of above the limit) */
  for (i = 0; i < env->env.env.nDataPoints; i++) {
    if (env->env.useFlags[i] == 0) continue; /* skip outliers */
    if (env->slopes[i] > limit) {
      belowFlag = 1;
    } else {
      belowFlag = 0;
    }
    break;
  }
  /* amdmsDebug(__FILE__, __LINE__, "#   saturation limit %.4f", limit); */
  /* step #4: mark data points */
  for (i = 0; i < env->env.env.nDataPoints - 1; i++) {
    if (env->env.useFlags[i] == 0) continue; /* skip outliers */
    if ((belowFlag && (env->slopes[i] < limit)) || (!belowFlag && (env->slopes[i] > limit))) {
      /* amdmsDebug(__FILE__, __LINE__, "#   saturation found %d", i); */
      env->env.useFlags[i] = 0;
      satFound = 1;
    }
  }
  if (satFound && (env->env.useFlags[env->env.env.nDataPoints - 1] != 0)) {
    /* amdmsDebug(__FILE__, __LINE__, "#   saturation found %d", nDataPoints - 1); */
    env->env.useFlags[env->env.env.nDataPoints - 1] = 0;
  }
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsAllocSpaceSmoothData(amdmsFIT_SMOOTH_DATA_ENV *env, int nDPs)
{
  if (env->env.env.nDataPoints == nDPs) {
    return amdmsSUCCESS;
  }
  if (amdmsAllocSpaceData(&(env->env), nDPs) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  /* allocate vector slopes */
  env->slopes = (double *)realloc(env->slopes, (nDPs + 1)*sizeof(double));
  if (env->slopes == NULL) {
    return amdmsFAILURE;
  }
  /* allocate vector slopeDifferences */
  env->slopeDifferences = (double *)realloc(env->slopeDifferences, (nDPs + 1)*sizeof(double));
  if (env->slopeDifferences == NULL) {
    return amdmsFAILURE;
  }
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsFitSmoothData(void *henv, int n, double *x, double *y, double *ye)
{
  amdmsFIT_SMOOTH_DATA_ENV *env = (amdmsFIT_SMOOTH_DATA_ENV*)henv;
  int                      iPoint;
  int                      nDP;
  double                   refQuality;
  double                   currQuality;
  int                      nDelCounter;
  
  if (amdmsAllocSpaceSmoothData(env, n) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  env->env.env.nDataPoints = n;
  env->env.valX = x;
  env->env.valY = y;
  env->env.valYErr = ye;
  for (iPoint = 0; iPoint < env->env.env.nDataPoints; iPoint++) {
    env->env.useFlags[iPoint] = 1;
  }
  env->env.satDP = -1;
  if (amdmsFindSmoothDataOutliers(env) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  if (amdmsFindSmoothDataSaturation(env) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  nDP = 0;
  env->env.firstDP = env->env.env.nDataPoints - 1;
  env->env.lastDP = 0;
  for (iPoint = 0; iPoint < env->env.env.nDataPoints; iPoint++) {
    if (env->env.useFlags[iPoint]) {
      env->env.firstDP = MIN(env->env.firstDP, iPoint);
      env->env.lastDP = MAX(env->env.lastDP, iPoint);
      nDP++;
    }
  }
  refQuality = amdmsTryDataFit(&(env->env)); /* use all data points */
  /* first descard data points from the end */
  nDelCounter = env->env.nDelEnd;
  while ((nDP > amdmsMIN_DATAPOINTS) && (nDelCounter > 0)) {
    while (env->env.useFlags[env->env.lastDP] == 0) {
      env->env.lastDP--;
    }
    /* test if we can improve the quality by discarding the last point */
    env->env.useFlags[env->env.lastDP] = 0;  /* do not use the last data point */
    currQuality = amdmsTryDataFit(&(env->env));
    if (currQuality < (1.0 - env->env.minStep)*refQuality) {
      /* we can improve the qualitiy of the fit */
      /* amdmsDebug(__FILE__, __LINE__, "#   discard %d", env->env.lastDP); */
      env->env.lastDP--;
      refQuality = currQuality;
      nDP--;
      nDelCounter--;
    } else {
      /* no improvement possible */
      env->env.useFlags[env->env.lastDP] = 1;  /* use last data point and finish */
      break;
    }
  }
  /* then discard data points from the beginning */
  nDelCounter = env->env.nDelStart;
  while ((nDP > amdmsMIN_DATAPOINTS) && (nDelCounter > 0)) {
    while (env->env.useFlags[env->env.firstDP] == 0) {
      env->env.firstDP++;
    }
    /* test if we can improve the quality by discarding the first point */
    env->env.useFlags[env->env.firstDP] = 0;  /* do not use the first data point */
    currQuality = amdmsTryDataFit(&(env->env));
    if (currQuality < (1.0 - env->env.minStep)*refQuality) {
      /* we can improve the qualitiy of the fit */
      /* amdmsDebug(__FILE__, __LINE__, "#   discard %d", firstDP); */
      env->env.firstDP++;
      refQuality = currQuality;
      nDP--;
      nDelCounter--;
    } else {
      /* no improvement possible */
      env->env.useFlags[env->env.firstDP] = 1;  /* use current data point */
      break;
    }
  }
  /* do the real fit */
  amdmsTryDataFit(&(env->env));
  return amdmsSUCCESS;
}

double amdmsEvalSmoothData(void *henv, double x)
{
  amdmsFIT_SMOOTH_DATA_ENV *env = (amdmsFIT_SMOOTH_DATA_ENV*)henv;

  if (env == NULL) {
    return 0.0;
  } else {
    return amdmsEvalData(&(env->env), x);
  }
}

amdmsCOMPL amdmsCreateFit(amdmsFIT_ENV **env,
			  amdmsFIT_FUNC func,
			  amdmsFIT_EVAL eval,
			  int nCoefficients)
{
  amdmsFIT_ENV           *henv = NULL;
  int  i;

  if (*env == NULL) {
    henv = (amdmsFIT_ENV*)calloc((size_t)1, sizeof(amdmsFIT_ENV));
    if (henv == NULL) {
      return amdmsFAILURE;
    }
    henv->allocated = 1;
    *env = henv;
  } else {
    henv = *env;
    henv->allocated = 0;
  }
  henv->func = func;
  henv->eval = eval;
  henv->nCoefficients = nCoefficients;
  henv->nDataPoints = 0;
  henv->chi2 = 0.0;
  henv->absDist2 = 0.0;
  henv->relDist2 = 0.0;
  henv->fitLowerLimit = 0.0;
  henv->fitUpperLimit = 0.0;
  for (i = 0; i < amdmsMAX_COEFF; i++) {
    henv->a[i] = 0.0;
    henv->ae[i] = 0.0;
  }
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsDestroyFit(amdmsFIT_ENV **env)
{
  amdmsFIT_ENV           *henv = NULL;

  if (env == NULL) {
    return amdmsFAILURE;
  }
  if (*env == NULL) {
    return amdmsSUCCESS;
  }
  henv = (amdmsFIT_ENV*)(*env);
  if (henv->allocated) {
    henv->allocated = 0;
    free(henv);
    *env = NULL;
  }
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsCreateStraightLineFit(amdmsFIT_ENV **env)
{
  return amdmsCreateFit(env, amdmsFitLine, amdmsEvalLine, 2);
}

amdmsCOMPL amdmsCreateExponentialFit(amdmsFIT_ENV **env)
{
  return amdmsCreateFit(env, amdmsFitExp, amdmsEvalExp, 2);
}

amdmsCOMPL amdmsCreateLogarithmicFit(amdmsFIT_ENV **env)
{
  return amdmsCreateFit(env, amdmsFitLog, amdmsEvalLog, 2);
}

amdmsCOMPL amdmsCreateLinearFit(amdmsFIT_LINEAR_ENV **env,
				   amdmsFIT_LINEAR_BASE base,
				   int nCoefficients)
{
  amdmsFIT_ENV           *henv = NULL;
  amdmsFIT_LINEAR_ENV    *hhenv = NULL;

  /* allocate the structure if neccessary */
  if (*env == NULL) {
    hhenv = (amdmsFIT_LINEAR_ENV*)calloc((size_t)1, sizeof(amdmsFIT_LINEAR_ENV));
    if (hhenv == NULL) {
      return amdmsFAILURE;
    }
    henv = &(hhenv->env);
    /* call the initialization of a simple fit environment */
    if (amdmsCreateFit(&henv, amdmsFitLinear, amdmsEvalLinear, nCoefficients) != amdmsSUCCESS) {
      free(hhenv);
      return amdmsFAILURE;
    }
    hhenv->allocated = 1;
    *env = hhenv;
  } else {
    /* call the initialization of a simple fit environment */
    hhenv = *env;
    henv = &(hhenv->env);
    if (amdmsCreateFit(&henv, amdmsFitLinear, amdmsEvalLinear, nCoefficients) != amdmsSUCCESS) {
      return amdmsFAILURE;
    }
    hhenv->allocated = 0;
  }
  /* initialize only the additional members */
  hhenv->base = base;
  hhenv->matU = NULL;
  hhenv->matV = NULL;
  hhenv->vecW = NULL;
  hhenv->vecB = NULL;
  hhenv->matCvm = NULL;
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsDestroyLinearFit(amdmsFIT_LINEAR_ENV **env)
{
  amdmsFIT_LINEAR_ENV   *henv = NULL;
  amdmsFIT_ENV          *hhenv = NULL;

  if (env == NULL) {
    return amdmsFAILURE;
  }
  if (*env == NULL) {
    return amdmsSUCCESS;
  }
  henv = *env;
  if (henv->matU != NULL) {
    free(henv->matU[0]);
    free(henv->matU);
    henv->matU = NULL;
  }
  if (henv->matV != NULL) {
    free(henv->matV[0]);
    free(henv->matV);
    henv->matV = NULL;
  }
  if (henv->matCvm != NULL) {
    free(henv->matCvm[0]);
    free(henv->matCvm);
    henv->matCvm = NULL;
  }
  if (henv->vecW != NULL) {
    free(henv->vecW);
    henv->vecW = NULL;
  }
  if (henv->vecB != NULL) {
    free(henv->vecB);
    henv->vecB = NULL;
  }
  hhenv = &(henv->env);
  if (amdmsDestroyFit(&hhenv) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  if (henv->allocated) {
    henv->allocated = 0;
    free(henv);
    *env = NULL;
  }
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsCreatePolynomialFit(amdmsFIT_LINEAR_ENV **env, int degree)
{
  return amdmsCreateLinearFit(env, amdmsPolynomialBaseFunc, degree + 1);
}

amdmsCOMPL amdmsCreateRayFit(amdmsFIT_LINEAR_ENV **env)
{
  return amdmsCreateLinearFit(env, amdmsRayBaseFunc, 1);
}

amdmsCOMPL amdmsCreateNonLinearFit(amdmsFIT_NONLINEAR_ENV **env,
				      amdmsFIT_NONLINEAR_INIT init,
				      amdmsFIT_NONLINEAR_BASE base,
				      int nCoefficients)
{
  amdmsFIT_ENV            *henv = NULL;
  amdmsFIT_NONLINEAR_ENV  *hhenv = NULL;

  /* allocate the structure if neccessary */
  if (*env == NULL) {
    hhenv = (amdmsFIT_NONLINEAR_ENV*)calloc((size_t)1, sizeof(amdmsFIT_NONLINEAR_ENV));
    if (hhenv == NULL) {
      return amdmsFAILURE;
    }
    henv = &(hhenv->env);
    /* call the initialization of a simple fit environment */
    if (amdmsCreateFit(&henv, amdmsFitNonLinear, amdmsEvalNonLinear, nCoefficients) != amdmsSUCCESS) {
      free(hhenv);
      return amdmsFAILURE;
    }
    hhenv->allocated = 1;
    *env = hhenv;
  } else {
    /* call the initialization of a simple fit environment */
    hhenv = *env;
    henv = &(hhenv->env);
    if (amdmsCreateFit(&henv, amdmsFitNonLinear, amdmsEvalNonLinear, nCoefficients) != amdmsSUCCESS) {
      return amdmsFAILURE;
    }
    hhenv->allocated = 0;
  }
  /* initialize only the additional members */
  hhenv->init = init;
  hhenv->base = base;
  hhenv->ivecIndxc = NULL;
  hhenv->ivecIndxr = NULL;
  hhenv->ivecIpiv = NULL;
  hhenv->vecDyda = NULL;
  hhenv->ochi2 = 0.0;
  hhenv->vecAtry = NULL;
  hhenv->vecBeta = NULL;
  hhenv->vecDa = NULL;
  hhenv->matOneda = NULL;
  hhenv->mfit = 0;
  hhenv->ivecIa = NULL;
  hhenv->alamda = 0.0;
  hhenv->matCvm = NULL;
  hhenv->matAlpha = NULL;
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsDestroyNonLinearFit(amdmsFIT_NONLINEAR_ENV **env)
{
  amdmsFIT_NONLINEAR_ENV   *henv = NULL;
  amdmsFIT_ENV             *hhenv = NULL;

  if (env == NULL) {
    return amdmsFAILURE;
  }
  if (*env == NULL) {
    return amdmsSUCCESS;
  }
  henv = *env;
  if (henv->matOneda != NULL) {
    free(henv->matOneda[0]);
    free(henv->matOneda);
    henv->matOneda = NULL;
  }
  if (henv->matCvm != NULL) {
    free(henv->matCvm[0]);
    free(henv->matCvm);
    henv->matCvm = NULL;
  }
  if (henv->matAlpha != NULL) {
    free(henv->matAlpha[0]);
    free(henv->matAlpha);
    henv->matAlpha = NULL;
  }
  if (henv->ivecIndxc != NULL) {
    free(henv->ivecIndxc);
    henv->ivecIndxc = NULL;
  }
  if (henv->ivecIndxr != NULL) {
    free(henv->ivecIndxr);
    henv->ivecIndxr = NULL;
  }
  if (henv->ivecIpiv != NULL) {
    free(henv->ivecIpiv);
    henv->ivecIpiv = NULL;
  }
  if (henv->vecDyda != NULL) {
    free(henv->vecDyda);
    henv->vecDyda = NULL;
  }
  if (henv->vecAtry != NULL) {
    free(henv->vecAtry);
    henv->vecAtry = NULL;
  }
  if (henv->vecBeta != NULL) {
    free(henv->vecBeta);
    henv->vecBeta = NULL;
  }
  if (henv->vecDa != NULL) {
    free(henv->vecDa);
    henv->vecDa = NULL;
  }
  if (henv->ivecIa != NULL) {
    free(henv->ivecIa);
    henv->ivecIa = NULL;
  }
  hhenv = &(henv->env);
  if (amdmsDestroyFit(&hhenv) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  if (henv->allocated) {
    henv->allocated = 0;
    free(henv);
    *env = NULL;
  }
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsCreateNonLinearPixelFit(amdmsFIT_NONLINEAR_ENV **env)
{
  return amdmsCreateNonLinearFit(env, amdmsNonLinearPixelInit, amdmsNonLinearPixelBaseFunc, 2);
}

amdmsCOMPL amdmsCreateElectronicBiasFit(amdmsFIT_NONLINEAR_ENV **env)
{
  return amdmsCreateNonLinearFit(env, amdmsElectronicBiasInit, amdmsElectronicBiasBaseFunc, amdmsEBFIT_NCOEFF);
}

amdmsCOMPL amdmsCreateGeneralExpFit(amdmsFIT_NONLINEAR_ENV **env)
{
  return amdmsCreateNonLinearFit(env, amdmsGeneralExpInit, amdmsGeneralExpBaseFunc, 3);
}

amdmsCOMPL amdmsCreateConversionFactorFit(amdmsFIT_NONLINEAR_ENV **env)
{
  return amdmsCreateNonLinearFit(env, amdmsConversionFactorInit, amdmsConversionFactorBaseFunc, amdmsCFFIT_NCOEFF);
}

amdmsCOMPL amdmsCreateDataFit(amdmsFIT_DATA_ENV **env, amdmsFIT_ENV *fit)
{
  amdmsFIT_ENV        *henv = NULL;
  amdmsFIT_DATA_ENV   *hhenv;

  /* allocate the structure if neccessary */
  if (*env == NULL) {
    hhenv = (amdmsFIT_DATA_ENV*)calloc((size_t)1, sizeof(amdmsFIT_DATA_ENV));
    if (hhenv == NULL) {
      return amdmsFAILURE;
    }
    henv = &(hhenv->env);
    /* call the initialization of a simple fit environment */
    if (amdmsCreateFit(&henv, amdmsFitData, amdmsEvalData, fit->nCoefficients) != amdmsSUCCESS) {
      free(hhenv);
      return amdmsFAILURE;
    }
    hhenv->allocated = 1;
    *env = hhenv;
  } else {
    /* call the initialization of a simple fit environment */
    hhenv = *env;
    henv = &(hhenv->env);
    if (amdmsCreateFit(&henv, amdmsFitData, amdmsEvalData, fit->nCoefficients) != amdmsSUCCESS) {
      return amdmsFAILURE;
    }
    hhenv = *env;
    hhenv->allocated = 0;
  }
  hhenv = *env;
  hhenv->fit = fit;
  hhenv->valX = NULL;
  hhenv->valY = NULL;
  hhenv->valYErr = NULL;
  hhenv->useFlags = NULL;
  hhenv->usedX = NULL;
  hhenv->usedY = NULL;
  hhenv->usedYErr = NULL;
  hhenv->satFlag = 0;
  hhenv->satWidth = 0.0;
  hhenv->satHeight = 0.0;
  hhenv->satDP = 0;
  hhenv->firstDP = 0;
  hhenv->lastDP = 0;
  hhenv->nDelStart = 0;
  hhenv->nDelMiddle = 1;
  hhenv->nDelEnd = 0;
  hhenv->minStep = 0.2;
  hhenv->maxRelDist = 0.01;
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsDestroyDataFit(amdmsFIT_DATA_ENV **env)
{
  amdmsFIT_DATA_ENV   *henv = NULL;
  amdmsFIT_ENV        *hhenv = NULL;

  if (env == NULL) {
    return amdmsFAILURE;
  }
  if (*env == NULL) {
    return amdmsSUCCESS;
  }
  henv = *env;
  if (henv->useFlags != NULL) {
    free(henv->useFlags);
    henv->useFlags = NULL;
  }
  if (henv->usedX != NULL) {
    free(henv->usedX);
    henv->usedX = NULL;
  }
  if (henv->usedY != NULL) {
    free(henv->usedY);
    henv->usedY = NULL;
  }
  if (henv->usedYErr != NULL) {
    free(henv->usedYErr);
    henv->usedYErr = NULL;
  }
  hhenv = &(henv->env);
  if (amdmsDestroyFit(&hhenv) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  if (henv->allocated) {
    henv->allocated = 0;
    free(henv);
    *env = NULL;
  }
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsCreateSmoothDataFit(amdmsFIT_SMOOTH_DATA_ENV **env, amdmsFIT_ENV *fit)
{
  amdmsFIT_DATA_ENV          *henv = NULL;
  amdmsFIT_SMOOTH_DATA_ENV   *hhenv;

  /* allocate the structure if neccessary */
  if (*env == NULL) {
    hhenv = (amdmsFIT_SMOOTH_DATA_ENV*)calloc((size_t)1, sizeof(amdmsFIT_SMOOTH_DATA_ENV));
    if (hhenv == NULL) {
      return amdmsFAILURE;
    }
    henv = &(hhenv->env);
    /* call the initialization of a simple fit environment */
    if (amdmsCreateDataFit(&henv, fit) != amdmsSUCCESS) {
      free(hhenv);
      return amdmsFAILURE;
    }
    *env = hhenv;
    hhenv->allocated = 1;
  } else {
    /* call the initialization of a simple fit environment */
    hhenv = *env;
    henv = &(hhenv->env);
    if (amdmsCreateDataFit(&henv, fit) != amdmsSUCCESS) {
      return amdmsFAILURE;
    }
    hhenv = *env;
    hhenv->allocated = 0;
  }
  hhenv = *env;
  hhenv->env.env.func = amdmsFitSmoothData;
  hhenv->env.env.eval = amdmsEvalSmoothData;
  hhenv->slopeLimit = 5.0; /* 5 times the average slope */
  hhenv->slopes = NULL;
  hhenv->slopeDifferences = NULL;
  hhenv->avgSlope = 0.0;
  hhenv->avgSlopeDifference = 0.0;
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsDestroySmoothDataFit(amdmsFIT_SMOOTH_DATA_ENV **env)
{
  amdmsFIT_SMOOTH_DATA_ENV   *henv = NULL;
  amdmsFIT_DATA_ENV          *hhenv = NULL;

  if (env == NULL) {
    return amdmsFAILURE;
  }
  if (*env == NULL) {
    return amdmsSUCCESS;
  }
  henv = *env;
  if (henv->slopes != NULL) {
    free(henv->slopes);
    henv->slopes = NULL;
  }
  if (henv->slopeDifferences != NULL) {
    free(henv->slopeDifferences);
    henv->slopeDifferences = NULL;
  }
  hhenv = &(henv->env);
  if (amdmsDestroyDataFit(&hhenv) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  if (henv->allocated) {
    henv->allocated = 0;
    free(henv);
    *env = NULL;
  }
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsDoFit(amdmsFIT_ENV *env, int n, double *x, double *y, double *ye)
{
  if (env == NULL) {
    return amdmsFAILURE;
  }
  if (env->func == NULL) {
    return amdmsFAILURE;
  }
  if (env->func(env, n, x, y, ye) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  return amdmsCalcQuality(env, n, x, y, ye);
}

double amdmsEvalFit(amdmsFIT_ENV *env, double x)
{
  if (env == NULL) {
    return 0.0;
  }
  if (env->eval == NULL) {
    return 0.0;
  }
  return env->eval(env, x);
}

amdmsCOMPL amdmsCalcFitLimits(amdmsFIT_ENV *env, int n, double *x, double *y, double *ye, double relLimit)
{
  int    i;
  int    lowerLimitFlag = 0;
  double val = 0.0;
  double tmp = 0.0;
  double limit = 0.0;
  
  if ((env == NULL) || (env->eval == NULL) || (x == NULL) || (y == NULL)) {
    return amdmsFAILURE;
  }
  for (i = 0; i < n; i++) {
    val = env->eval(env, x[i]);
    tmp = fabs(y[i] - val);
    if (y[i] != 0.0) {
      limit = y[i]*relLimit;
      if (ye != NULL) {
	limit = MAX(limit, ye[i]);
      }
      if (tmp <= limit) {
	env->fitUpperLimit = x[i];
	if (!lowerLimitFlag)
	  {
	    env->fitLowerLimit = x[i];
	    lowerLimitFlag = 1;
	  }
      }
    } else {
      limit = ye[i];
      if (ye != NULL) {
	limit = MAX(limit, ye[i]);
      }
      if (tmp <= limit) {
	env->fitUpperLimit = x[i];
	if (!lowerLimitFlag) {
	  env->fitLowerLimit = x[i];
	  lowerLimitFlag = 1;
	}
      }
    }
  }
  return amdmsSUCCESS;
}

/* Graphic Gems IV, ed. Paul S. Heckbert,
   IV.1 Smoothing and Interpolation with Finite Differences, page 241
*/
amdmsCOMPL amdmsSmoothDataByFiniteDiff1(double *y, double *z, double lambda, int m)
     /* Smoothing and interpolation with first differences.
	Input:  data(y): vector from 0 to m - 1.
	Input:  smoothing parameter (lambda, length (m).
	Output: smoothed vector (z): vector from 0 to m - 1.
     */
{
  int      i;
  int      i1;
  double  *c = NULL;
  double  *d = NULL;

  c = (double*)calloc((size_t)m, sizeof(double));
  if (c == NULL) {
    amdmsFatal(__FILE__, __LINE__, "memory allocation failure (c)");
    return amdmsFAILURE;
  }
  d = (double*)calloc((size_t)m, sizeof(double));
  if (d == NULL) {
    amdmsFatal(__FILE__, __LINE__, "memory allocation failure (d)");
    free(c);
    return amdmsFAILURE;
  }
  d[0] = 1.0 + lambda;
  c[0] = -lambda/d[0];
  z[0] = y[0];
  for (i = 1; i < m - 1; i++) {
    i1 = i - 1;
    d[i] = 1.0 + 2*lambda - c[i1]*c[i1]*d[i1];
    c[i] = -lambda/d[i];
    z[i] = y[i] - c[i1]*z[i1];
  }
  d[m - 1] = 1.0 + lambda - c[m - 2]*c[m - 2]*d[m - 2];
  z[m - 1] = (y[m - 1] - c[m - 2]*z[m - 2])/d[m - 1];
  for (i = m - 2; 0 <= i; i--) {
    z[i] = z[i]/d[i] - c[i]*z[i + 1];
  }
  free(c);
  free(d);
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsSmoothDataByFiniteDiff1W(double *w, double *y, double *z, double lambda, int m)
     /* Smoothing and interpolation with first differences.
	Input:  weights (w), data(y): vector from 0 to m - 1.
	Input:  smoothing parameter (lambda, length (m).
	Output: smoothed vector (z): vector from 0 to m - 1.
     */
{
  int      i;
  int      i1;
  double  *c = NULL;
  double  *d = NULL;

  c = (double*)calloc((size_t)m, sizeof(double));
  if (c == NULL) {
    amdmsFatal(__FILE__, __LINE__, "memory allocation failure (c)");
    return amdmsFAILURE;
  }
  d = (double*)calloc((size_t)m, sizeof(double));
  if (d == NULL) {
    amdmsFatal(__FILE__, __LINE__, "memory allocation failure (d)");
    free(c);
    return amdmsFAILURE;
  }
  d[0] = w[0] + lambda;
  c[0] = -lambda/d[0];
  z[0] = w[0]*y[0];
  for (i = 1; i < m - 1; i++) {
    i1 = i - 1;
    d[i] = w[i] + 2*lambda - c[i1]*c[i1]*d[i1];
    c[i] = -lambda/d[i];
    z[i] = w[i]*y[i] - c[i1]*z[i1];
  }
  d[m - 1] = w[m - 1] + lambda - c[m - 2]*c[m - 2]*d[m - 2];
  z[m - 1] = (w[m - 1]*y[m - 1] - c[m - 2]*z[m - 2])/d[m - 1];
  for (i = m - 2; 0 <= i; i--) {
    z[i] = z[i]/d[i] - c[i]*z[i + 1];
  }
  free(c);
  free(d);
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsSmoothDataByFiniteDiff2(double *y, double *z, double lambda, int m)
     /* Smoothing and interpolation with second differences.
	Input:  data(y): vector from 0 to m - 1.
	Input:  smoothing parameter (lambda, length (m).
	Output: smoothed vector (z): vector from 0 to m - 1.
     */
{
  int      i;
  int      i1;
  int      i2;
  double  *c = NULL;
  double  *d = NULL;
  double  *e = NULL;

  c = (double*)calloc((size_t)m, sizeof(double));
  if (c == NULL) {
    amdmsFatal(__FILE__, __LINE__, "memory allocation failure (c)");
    return amdmsFAILURE;
  }
  d = (double*)calloc((size_t)m, sizeof(double));
  if (d == NULL) {
    amdmsFatal(__FILE__, __LINE__, "memory allocation failure (d)");
    free(c);
    return amdmsFAILURE;
  }
  e = (double*)calloc((size_t)m, sizeof(double));
  if (e == NULL) {
    amdmsFatal(__FILE__, __LINE__, "memory allocation failure (e)");
    free(c);
    free(d);
    return amdmsFAILURE;
  }
  d[0] = 1.0 + lambda;
  c[0] = -2*lambda/d[0];
  e[0] = lambda/d[0];
  z[0] = y[0];
  d[1] = 1.0 + 5*lambda - d[0]*c[0]*c[0];
  c[1] = (-4*lambda - d[0]*c[0]*e[0])/d[1];
  e[1] = lambda/d[1];
  z[1] = y[1] - c[0]*z[0];
  for (i = 2; i < m - 2; i++) {
    i1 = i - 1;
    i2 = i - 2;
    d[i] = 1.0 + 6*lambda - c[i1]*c[i1]*d[i1] - e[i2]*e[i2]*d[i2];
    c[i] = (-4*lambda - d[i1]*c[i1]*e[i1])/d[i];
    e[i] = lambda/d[i];
    z[i] = y[i] - c[i1]*z[i1] - e[i2]*z[i2];
  }
  i1 = m - 3;
  i2 = m - 4;
  d[m - 2] = 1.0 + 5*lambda - c[i1]*c[i1]*d[i1] - e[i2]*e[i2]*d[i2];
  c[m - 2] = (-2*lambda - d[i1]*c[i1]*e[i1])/d[m - 2];
  z[m - 2] = y[m - 2] - c[i1]*z[i1] - e[i2]*z[i2];
  i1 = m - 2;
  i2 = m - 3;
  d[m - 1] = 1.0 + lambda - c[i1]*c[i1]*d[i1] - e[i2]*e[i2]*d[i2];
  z[m - 1] = (y[m - 1] - c[i1]*z[i1] - e[i2]*z[i2])/d[m - 1];
  z[m - 2] = z[m - 2]/d[m - 2] - c[m - 2]*z[m - 1];
  for (i = m - 3; 0 <= i; i--) {
    z[i] = z[i]/d[i] - c[i]/z[i + 1] - e[i]*z[i + 2];
  }
  free(c);
  free(d);
  free(e);
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsSmoothDataByFiniteDiff2W(double *w, double *y, double *z, double lambda, int m)
     /* Smoothing and interpolation with second differences.
	Input:  weights (w), data(y): vector from 0 to m - 1.
	Input:  smoothing parameter (lambda, length (m).
	Output: smoothed vector (z): vector from 0 to m - 1.
     */
{
  int      i;
  int      i1;
  int      i2;
  double  *c = NULL;
  double  *d = NULL;
  double  *e = NULL;

  c = (double*)calloc((size_t)m, sizeof(double));
  if (c == NULL) {
    amdmsFatal(__FILE__, __LINE__, "memory allocation failure (c)");
    return amdmsFAILURE;
  }
  d = (double*)calloc((size_t)m, sizeof(double));
  if (d == NULL) {
    amdmsFatal(__FILE__, __LINE__, "memory allocation failure (d)");
    free(c);
    return amdmsFAILURE;
  }
  e = (double*)calloc((size_t)m, sizeof(double));
  if (e == NULL) {
    amdmsFatal(__FILE__, __LINE__, "memory allocation failure (e)");
    free(c);
    free(d);
    return amdmsFAILURE;
  }
  d[0] = w[0] + lambda;
  c[0] = -2*lambda/d[0];
  e[0] = lambda/d[0];
  z[0] = w[0]*y[0];
  d[1] = w[1] + 5*lambda - d[0]*c[0]*c[0];
  c[1] = (-4*lambda - d[0]*c[0]*e[0])/d[1];
  e[1] = lambda/d[1];
  z[1] = w[1]*y[1] - c[0]*z[0];
  for (i = 2; i < m - 2; i++) {
    i1 = i - 1;
    i2 = i - 2;
    d[i] = w[i] + 6*lambda - c[i1]*c[i1]*d[i1] - e[i2]*e[i2]*d[i2];
    c[i] = (-4*lambda - d[i1]*c[i1]*e[i1])/d[i];
    e[i] = lambda/d[i];
    z[i] = w[i]*y[i] - c[i1]*z[i1] - e[i2]*z[i2];
  }
  i1 = m - 3;
  i2 = m - 4;
  d[m - 2] = w[m - 2] + 5*lambda - c[i1]*c[i1]*d[i1] - e[i2]*e[i2]*d[i2];
  c[m - 2] = (-2*lambda - d[i1]*c[i1]*e[i1])/d[m - 2];
  z[m - 2] = w[m - 2]*y[m - 2] - c[i1]*z[i1] - e[i2]*z[i2];
  i1 = m - 2;
  i2 = m - 3;
  d[m - 1] = w[m - 1] + lambda - c[i1]*c[i1]*d[i1] - e[i2]*e[i2]*d[i2];
  z[m - 1] = (w[m - 1]*y[m - 1] - c[i1]*z[i1] - e[i2]*z[i2])/d[m - 1];
  z[m - 2] = z[m - 2]/d[m - 2] - c[m - 2]*z[m - 1];
  for (i = m - 3; 0 <= i; i--) {
    z[i] = z[i]/d[i] - c[i]*z[i + 1] - e[i]*z[i + 2];
  }
  free(c);
  free(d);
  free(e);
  return amdmsSUCCESS;
}
