/*
 * @(#)NumericalRecipes.java
 *
 * contains routines from 'Numerical Recipes in C' ported from C to Java
 *
 * Copyright 2002 Actelion Ltd., Inc. All Rights Reserved.
 *
 * This software is the proprietary information of Actelion Pharmaceuticals, Ltd.
 * Use is subject to license terms.
 *
 * @original authors W.H.Press, B.P.Fannery, S.A.Teukolsky, W.T.Vettering
 *
 */

package com.actelion.research.util;

public class NumericalRecipes {
	private double _fit_siga,_fit_sigb,_fit_a,_fit_b,_fit_chi2,_fit_q;	// by reference params of fit()
	private double _gser_gamser, _gser_gln;								// by reference params of gser()
	private double _gcf_gammcf, _gcf_gln;								// by reference params of gcf()

	private double ochisq;			// used by mrqmin
	private double[] atry,beta,da;
	private double[][] oneda;
	private int mfit;

	public static void svbksb(double[][] u, double[] w, double[][] v, int m, int n, double[] b, double[] x) {
//	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.
		double[] tmp = new double[n];
		for (int j=0; j<n; j++) {	// Calculate UTB.
			double s=0.0;
			if (w[j] != 0.0) {	// Nonzero result only if wj is nonzero.
				for (int i=0; i<m; i++)
					s += u[i][j]*b[i];
				s /= w[j];	// This is the divide by wj .
				}
			tmp[j]=s;
			}
		for (int j=0; j<n; j++) {	// Matrix multiply by V to get answer.
			double s=0.0;
			for (int jj=0; jj<n; jj++)
				s += v[j][jj]*tmp[jj];
			x[j]=s;
			}
		}


	public static void svdcmp(double[][] a, int m, int n, double[] w, double[][] v) throws Exception {
//	Given a matrix a[1..m][1..n], this routine computes its singular value decomposition, A =
//	U·W·V T. Thematrix U replaces a on output. The diagonal matrix of singular values W is output
//	as a vector w[1..n]. Thematrix V (not the transpose V T ) is output as v[1..n][1..n].
		double anorm,c,f,g,h,s,scale,x,y,z;

		double[] rv1 = new double[n];
		g=scale=0.0;	// Householder reduction to bidiagonal form.
		anorm=0.0;
		int l=0;
		for (int i=0; i<n; i++) {
			l=i+1;
			rv1[i]=scale*g;
			g=s=scale=0.0;
			if (i < m) {
				for (int k=i; k<m; k++)
					scale += Math.abs(a[k][i]);
				if (scale != 0.0) {
					for (int k=i; k<m; k++) {
						a[k][i] /= scale;
						s += a[k][i]*a[k][i];
						}
					f=a[i][i];
					g = -SIGN(Math.sqrt(s),f);
					h=f*g-s;
					a[i][i]=f-g;
					for (int j=l; j<n; j++) {
						s=0.0;
						for (int k=i; k<m; k++)
							s += a[k][i]*a[k][j];
						f=s/h;
						for (int k=i; k<m; k++)
							a[k][j] += f*a[k][i];
						}
					for (int k=i; k<m; k++)
						a[k][i] *= scale;
					}
				}
			w[i]=scale *g;
			g=s=scale=0.0;
			if (i < m && i != n-1) {
				for (int k=l; k<n; k++)
					scale += Math.abs(a[i][k]);
				if (scale != 0.0) {
					for (int k=l; k<n; k++) {
						a[i][k] /= scale;
						s += a[i][k]*a[i][k];
						}
					f=a[i][l];
					g = -SIGN(Math.sqrt(s),f);
					h=f*g-s;
					a[i][l]=f-g;
					for (int k=l; k<n; k++)
						rv1[k]=a[i][k]/h;
					for (int j=l; j<m; j++) {
						s=0.0;
						for (int k=l; k<n; k++)
							s += a[j][k]*a[i][k];
						for (int k=l; k<n; k++)
							a[j][k] += s*rv1[k];
						}
					for (int k=l; k<n; k++)
						a[i][k] *= scale;
					}
				}
			anorm=Math.max(anorm,(Math.abs(w[i])+Math.abs(rv1[i])));
			}
		for (int i=n-1; i>=0; i--) {	// Accumulation of right-hand transformations.
			if (i < n-1) {
				if (g != 0.0) {
					for (int j=l; j<n; j++)	// Double division to avoid possible underflow.
						v[j][i]=(a[i][j]/a[i][l])/g;
					for (int j=l; j<n; j++) {
						s=0.0;
						for (int k=l; k<n; k++)
							s += a[i][k]*v[k][j];
						for (int k=l; k<n; k++)
							v[k][j] += s*v[k][i];
						}
					}
				for (int j=l; j<n; j++)
					v[i][j]=v[j][i]=0.0;
				}
			v[i][i]=1.0;
			g=rv1[i];
			l=i;
			}
		for (int i=Math.min(m,n)-1; i>=0; i--) {	// Accumulation of left-hand transformations.
			l=i+1;
			g=w[i];
			for (int j=l; j<n; j++)
				a[i][j]=0.0;
			if (g != 0.0) {
				g=1.0/g;
				for (int j=l; j<n; j++) {
					s=0.0;
					for (int k=l; k<m; k++)
						s += a[k][i]*a[k][j];
					f=(s/a[i][i])*g;
					for (int k=i; k<m; k++)
						a[k][j] += f*a[k][i];
					}
				for (int j=i; j<m; j++)
					a[j][i] *= g;
				}
			else
				for (int j=i; j<m; j++)
					a[j][i]=0.0;
			++a[i][i];
			}
		for (int k=n-1; k>=0; k--) {	// Diagonalization of the bidiagonal form: Loop over
										// singular values, and over allowed iterations.
			for (int its=1;its<=30;its++) {
				boolean flag = true;
				int nm=0;
				for (l=k; l>=0; l--) {	// Test for splitting.
					nm=l-1;				// Note that rv1[1] is always zero.
					if ((double)(Math.abs(rv1[l])+anorm) == anorm) {
						flag = false;
						break;
						}
					if ((double)(Math.abs(w[nm])+anorm) == anorm)
						break;
					}
				if (flag) {
					c=0.0;	// Cancellation of rv1[l], if l > 1.
					s=1.0;
					for (int i=l; i<k; i++) {
						f=s*rv1[i];
						rv1[i]=c*rv1[i];
						if ((double)(Math.abs(f)+anorm) == anorm)
							break;
						g=w[i];
						h=pythag(f,g);
						w[i]=h;
						h=1.0/h;
						c=g*h;
						s = -f*h;
						for (int j=0; j<m; j++) {
							y=a[j][nm];
							z=a[j][i];
							a[j][nm]=y*c+z*s;
							a[j][i]=z*c-y*s;
							}
						}
					}
				z=w[k];
				if (l == k) {	// Convergence.
					if (z < 0.0) {	// Singular value is made nonnegative.
						w[k] = -z;
						for (int j=0; j<n; j++)
							v[j][k] = -v[j][k];
						}
					break;
					}
				if (its == 30)
					nrerror("no convergence in 30 svdcmp iterations");
				x=w[l];	// Shift from bottom 2-by-2 minor.
				nm=k-1;
				y=w[nm];
				g=rv1[nm];
				h=rv1[k];
				f=((y-z)*(y+z)+(g-h)*(g+h))/(2.0*h*y);
				g=pythag(f,1.0);
				f=((x-z)*(x+z)+h*((y/(f+SIGN(g,f)))-h))/x;
				c=s=1.0;	// Next QR transformation:
				for (int j=l; j<=nm; j++) {
					int i=j+1;
					g=rv1[i];
					y=w[i];
					h=s*g;
					g=c*g;
					z=pythag(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 (int jj=0; jj<n; jj++) {
						x=v[jj][j];
						z=v[jj][i];
						v[jj][j]=x*c+z*s;
						v[jj][i]=z*c-x*s;
						}
					z=pythag(f,h);
					w[j]=z;	// Rotation can be arbitrary if z = 0.
					if (z != 0.0) {
						z=1.0/z;
						c=f*z;
						s=h*z;
						}
					f=c*g+s*y;
					x=c*y-s*g;
					for (int jj=0; jj<m; jj++) {
						y=a[jj][j];
						z=a[jj][i];
						a[jj][j]=y*c+z*s;
						a[jj][i]=z*c-y*s;
						}
					}
				rv1[l]=0.0;
				rv1[k]=f;
				w[k]=x;
				}
			}
		}


	public void fit(double x[], double y[], int ndata, double sig[], int mwt) throws Exception {
//	Given a set of data points x[1..ndata],y[1..ndata] with individual standard deviations
//	sig[1..ndata], fit them to a straight line y = a + bx by minimizing ÷2. Returned are
//	a,b and their respective probable uncertainties siga and sigb, the chi-square chi2, and the
//	goodness-of-fit probability q (that the fit would have ÷2 this large or larger). If mwt=0 on
//	input, then the standard deviations are assumed to be unavailable: q is returned as 1.0 and
//	the normalization of chi2 is to unit standard deviation on all points.

		double sx = 0.0;
		double sy = 0.0;
		double st2 = 0.0;
		double ss = 0.0;

		_fit_b = 0.0;
		if (mwt != 0) {	// Accumulate sums ...
			ss=0.0;
			for (int i=0; i<ndata; i++) {	// ...with weights
				double wt = 1.0/SQR(sig[i]);
				ss += wt;
				sx += x[i]*wt;
				sy += y[i]*wt;
				}
			}
		else {
			for (int i=0; i<ndata; i++) {	// ...or without weights.
				sx += x[i];
				sy += y[i];
				}
			ss=ndata;
			}

		double sxoss=sx/ss;

		if (mwt == 0) {
			for (int i=0; i<ndata; i++) {
				double t=(x[i]-sxoss)/sig[i];
				st2 += t*t;
				_fit_b += t*y[i]/sig[i];
				}
			}
		else {
			for (int i=0; i<ndata; i++) {
				double t = x[i]-sxoss;
				st2 += t*t;
				_fit_b += t*y[i];
				}
			}

		_fit_b /= st2;	// Solve for a, b, óa, and ób.
		_fit_a = (sy-sx*(_fit_b))/ss;
		_fit_siga = Math.sqrt((1.0+sx*sx/(ss*st2))/ss);
		_fit_sigb = Math.sqrt(1.0/st2);
		_fit_chi2 = 0.0;	// Calculate ÷2.
		_fit_q = 1.0;
		if (mwt == 0) {
			for (int i=0; i<ndata; i++)
				_fit_chi2 += SQR(y[i]-(_fit_a)-(_fit_b)*x[i]);
			double sigdat = Math.sqrt((_fit_chi2)/(ndata-2));
				// For unweighted data evaluate typical
				// sig using chi2, and adjust
				// the standard deviations.
			_fit_siga *= sigdat;
			_fit_sigb *= sigdat;
			}
		else {
			for (int i=0; i<ndata; i++)
				_fit_chi2 += SQR((y[i]-(_fit_a)-(_fit_b)*x[i])/sig[i]);
			if (ndata>2)
				_fit_q = gammq(0.5*(ndata-2),0.5*(_fit_chi2));	// Equation (15.2.12).
			}
		}


	public void mrqmin(double x[], double y[], double sig[], int ndata, double a[], int ia[],
				int ma, double[][] covar, double[][] alpha, FittingFunction funcs) throws Exception {
//	Levenberg-Marquardt method, attempting to reduce the value x^2 of a fit between a set of data
//	points x[1..ndata], y[1..ndata] with individual standard deviations sig[1..ndata],
//	and a nonlinear function dependent on ma coeficients 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 x^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 and 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.)
		if (funcs.alamda < 0.0) {	/* Initialization. */
			atry = new double[ma];
			beta = new double[ma];
			da = new double[ma];
			mfit = 0;
			for (int j=0;j<ma;j++)
				if (ia[j] != 0)
					mfit++;
			oneda = new double[mfit][1];
			funcs.alamda=0.001;
			mrqcof(x,y,sig,ndata,a,ia,ma,alpha,beta,funcs);
			ochisq=(funcs.chisq);
			for (int j=0;j<ma;j++)
				atry[j]=a[j];
			}

		for (int j=0;j<mfit;j++) {	/* Alter linearized fitting matrix, by augmenting diagonal elements. */
			for (int k=0;k<mfit;k++)
				covar[j][k]=alpha[j][k];
			covar[j][j]=alpha[j][j]*(1.0+(funcs.alamda));
			oneda[j][0]=beta[j];
			}

		gaussj(covar,mfit,oneda,1);	/* Matrix solution. */
		for (int j=0;j<mfit;j++)
			da[j]=oneda[j][0];

		if (funcs.alamda == 0.0) {	/* Once converged, evaluate covariance matrix. */
			covsrt(covar,ma,ia,mfit);
			oneda = null;
			da = null;
			beta = null;
			atry = null;
			return;
			}

		int jj=-1;
		for (int l=0;l<ma;l++)	/* Did the trial succeed? */
			if (ia[l] != 0)
				atry[l]=a[l]+da[++jj];

		mrqcof(x,y,sig,ndata,atry,ia,ma,covar,da,funcs);
		if (funcs.chisq < ochisq) {	/* Success, accept the new solution. */
			funcs.alamda *= 0.1;
			ochisq=(funcs.chisq);
			for (int j=0;j<mfit;j++) {
				for (int k=0;k<mfit;k++)
					alpha[j][k]=covar[j][k];
				beta[j]=da[j];
				}

			for (int l=0;l<ma;l++)
				a[l]=atry[l];
			}
		else {	/* Failure, increase alamda and return. */
			funcs.alamda *= 10.0;
			funcs.chisq = ochisq;
			}
		}


	private void gser(double a, double x) throws Exception {
//	Returns the incomplete gamma function P(a, x) evaluated
//	by its series representation as gamser.
//	Also returns ln Ã(a) as gln.
		final int ITMAX = 100;
		final double EPS = 3.0e-7;

		_gser_gln = gammln(a);
		if (x <= 0.0) {
			if (x < 0.0)
				nrerror("x less than 0 in routine gser");
			_gser_gamser = 0.0;
			return;
			}
		else {
			double ap = a;
			double del = 1.0/a;
			double sum = del;
			for (int n=1; n<=ITMAX; n++) {
				ap += 1.0;
				del *= x/ap;
				sum += del;
				if (Math.abs(del) < Math.abs(sum)*EPS) {
					_gser_gamser = sum * Math.exp(-x+a*Math.log(x)-(_gser_gln));
					return;
					}
				}
			nrerror("a too large, ITMAX too small in routine gser");
			return;
			}
		}


	private void gcf(double a, double x) throws Exception {
//	original params: (float *gammcf, float a, float x, float *gln)
// Returns the incomplete gamma function Q(a, x) evaluated by its continued
// fraction representation as gammcf. Also returns lnÃ(a) as gln.

		final int ITMAX = 100;			// Maximum allowed number of iterations.
		final double EPS = 3.0e-7;		// Relative accuracy.
		final double FPMIN = 1.0e-30;	// Number near the smallest representable
										// floating-point number.
		_gcf_gln = gammln(a);
		double b = x+1.0-a;	//	Set up for evaluating continued fraction
							//	by modified Lentz’s method (§5.2)
							//	with b0 = 0.
		double c = 1.0 / FPMIN;
		double d = 1.0 / b;
		double h = d;
		int i;
		for (i=1; i<=ITMAX; i++) {	// Iterate to convergence.
			double an = -i*(i-a);
			b += 2.0;
			d = an*d+b;
			if (Math.abs(d) < FPMIN)
				d=FPMIN;
			c = b+an/c;
			if (Math.abs(c) < FPMIN)
				c=FPMIN;
			d = 1.0/d;
			double del = d*c;
			h *= del;
			if (Math.abs(del-1.0) < EPS)
				break;
			}
		if (i > ITMAX)
			nrerror("a too large, ITMAX too small in gcf");
		_gcf_gammcf = Math.exp(-x+a*Math.log(x)-(_gcf_gln))*h; // Put factors in front.
		}


	private double gammq(double a, double x) throws Exception {
//	Returns the incomplete gamma function Q(a, x) \uFFFDß 1 \uFFFD| P(a, x).
		if (x < 0.0 || a <= 0.0)
			nrerror("Invalid arguments in routine gammq");
		if (x < (a+1.0)) {	// Use the series representation
			gser(a, x);
			return 1.0 - _gser_gamser;	// and take its complement.
			}
		else {	// Use the continued fraction representation.
			gcf(a, x);
			return _gcf_gammcf;
			}
		}


	private double gammln(double xx) {
//	Returns the value ln[Ã(xx)] for xx > 0.

//	Internal arithmetic will be done in double precision,
//	a nicety that you can omit if .ve-.gure
//	accuracy is good enough.
		final double[] cof = {	 76.18009172947146,
								-86.50532032941677,
								 24.01409824083091,
								 -1.231739572450155,
								  0.1208650973866179e-2,
								 -0.5395239384953e-5	};
		double x = xx;
		double y = xx;
		double tmp = x + 5.5;
		tmp -= (x+0.5) * Math.log(tmp);
		double ser = 1.000000000190015;
		for (int j=0; j<=5; j++)
			ser += cof[j] / ++y;
		return -tmp + Math.log(2.5066282746310005*ser/x);
		}


	private static void covsrt(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.)
		for (int i=mfit; i<ma; i++)
			for (int j=0; j<=i; j++)
				covar[i][j] = covar[j][i] = 0.0;

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


	private static void mrqcof(double x[], double y[], double sig[], int ndata, double a[], int ia[],
					int ma, double alpha[][], double beta[], FittingFunction funcs) {
//	Used by mrqmin to evaluate the linearized fitting matrix alpha, and vector beta as in (15.5.8),
//	and calculate X2
		double[] dyda = new double[ma];

		int mfit = 0;
		for (int j=0; j<ma; j++)
			if (ia[j] != 0)
				mfit++;

		for (int j=0;j<mfit;j++) {	/* Initialize (symmetric) alpha, beta.	*/
			for (int k=0;k<=j;k++)
				alpha[j][k]=0.0;
			beta[j]=0.0;
			}

		funcs.chisq=0.0;
		for (int i=0;i<ndata;i++) { /* Summation loop over all data. */
			double ymod = funcs.fittingFunction(x[i], a, dyda, ma);
			double sig2i=1.0/(sig[i]*sig[i]);
			double dy=y[i]-ymod;
			int j = -1;
			for (int l=0;l<ma;l++) {
				if (ia[l] != 0) {
					double wt=dyda[l]*sig2i;
					j++;
					int k = -1;
					for (int m=0;m<=l;m++)
						if (ia[m] != 0) alpha[j][++k] += wt*dyda[m];
					beta[j] += dy*wt;
					}
				}
			funcs.chisq += dy*dy*sig2i;
			}

		for (int j=1;j<mfit;j++)	/* Fill in the symmetric side. */
			for (int k=0;k<j;k++)
				alpha[k][j]=alpha[j][k];
		}


	private static void gaussj(double a[][], int n, double b[][], int m) throws Exception {
//	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[] indxc = new int[n];	// The integer arrays ipiv, indxr, and indxc are
		int[] indxr = new int[n];	// used for bookkeeping on the pivoting.
		int[] ipiv = new int[n];
		for (int j=0; j<n; j++)
			ipiv[j]=0;  

		for (int i=0; i<n; i++) {// This is the main loop over the columns to be reduced.
			int irow = 0;
			int icol = 0;
			double big=0.0;
			for (int j=0; j<n; j++)	// This is the outer loop of the search for a pivot element.
				if (ipiv[j] != 1)
					for (int k=0; k<n; k++) {
						if (ipiv[k] == 0) {
							if (Math.abs(a[j][k]) >= big) {
								big=Math.abs(a[j][k]);
								irow=j;
								icol=k;
								}
							}
						}
			++(ipiv[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 (int l=0;l<n;l++)
					SWAP(a[irow][l],a[icol][l]);
				for (int l=0;l<m;l++)
					SWAP(b[irow][l],b[icol][l]);
				}
			indxr[i]=irow;	/*	We are now ready to divide the pivot row by the
										pivot element, located at irow and icol.*/
			indxc[i]=icol;
			if (a[icol][icol] == 0.0)
				nrerror("gaussj: Singular Matrix");

			double pivinv=1.0/a[icol][icol];
			a[icol][icol]=1.0;
			for (int l=0;l<n;l++)
				a[icol][l] *= pivinv;
			for (int l=0;l<m;l++)
				b[icol][l] *= pivinv;
			for (int ll=0;ll<n;ll++)	// Next, we reduce the rows...
				if (ll != icol) {		// ...except for the pivot one, of course.
					double dum=a[ll][icol];
					a[ll][icol]=0.0;
					for (int l=0;l<n;l++)
						a[ll][l] -= a[icol][l]*dum;
					for (int l=0;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 (int l=n-1;l>=0;l--) {
			if (indxr[l] != indxc[l])
				for (int k=0;k<n;k++)
					SWAP(a[k][indxr[l]],a[k][indxc[l]]);
			}	// And we are done.
		}


	private static double pythag(double a, double b) {
//	Computes (a2 + b2)1/2 without destructive underflow or overflow.
		double absa = Math.abs(a);
		double absb = Math.abs(b);
		return (absa > absb) ? absa*Math.sqrt(1.0+SQR(absb/absa))
							 : ((absb == 0.0) ? 0.0 : absb*Math.sqrt(1.0+SQR(absa/absb)));
		}


	private static double SQR(double a) {
		return (a == 0.0) ? 0.0 : a*a;
		}


	private static double SIGN(double a, double b) {
		return (b >= 0.0) ? Math.abs(a) : -Math.abs(a);
		}


	private static void SWAP(double a, double b) {
		double temp = a;
		a = b;
		b = temp;
		}


	private static void nrerror(String err) throws Exception {
		throw new Exception("Numerical Recipes run-time error: "+err);
		}
	}
