//  LBayes.java

package lt.ktu.gmj.methods;

import java.lang.Math;
import lt.ktu.gmj.*;

//
//---------------------------------------------------------

public class LBayes extends MethodBase {

	private double aniu, beta;

	static private final double GAMA0   = 0.2;
	static private final double COEF    = 0.8;
	static private final double EPS2    = 1e-6;
	static private final double EPS3    = 1e-6;
	static private final double EPS4    = 1e-10;
	static private final double B1      = 1e-6;
	static private final double V1      = 1e-6;
	static private final double B2      = 1e-2;
	static private final double V2      = 1.0;
	static private final int	CONV    = 1;
	static private final int	SL_CONV = 2;
	
	private double M1; private int N2;

	private Point x1Pnt;
	private Point x2Pnt, dmlPnt, dml1Pnt, abPnt;

	public LBayes ()
	{
		iterations = 1000; 
		/*initialPoints = 0; */
		aniu = 0.05; 
		beta = 0.9;
	}
	
	public void setParameters(int _iterations, int _initialPoints)
	{
		iterations = _iterations; 
	}


	
	//------------------------------------------------------
	//======================================================
	public Result run (ResultLogger logger, Task task)
	{
		Domain domain = task.domain();
		Result result = new Result(), bestres = new Result();

		int n = domain.dimensions();
		M1 = (COEF * iterations - 2); N2 = 2 * n;

		x1Pnt = new Point (domain.defaultPoint); x2Pnt = new Point (domain); dmlPnt = new Point (domain);
		dml1Pnt = new Point (domain); abPnt = new Point (domain);

		double f11, f13, meandisp[] = new double [2];
		int    i, m;
		int    n2 = N2, am2 = 1, an0 = 1, lo = CONV, lb = 1;
		double a1 = 0.;

		result.point = x1Pnt; lbayes_dispersion (x1Pnt, x2Pnt, meandisp, n2, task);
		result.value = meandisp[0];
		//logger.log (result);
		f13 = n2 * meandisp [0]; f11 = meandisp [1]; result.iteration++;

		for (i = 0; i < n; i++) abPnt.x [i] = 0.5 * (domain.max [i] - domain.min [i]);

		for (m = 0; m < iterations; m++, result.iteration++) {
			result = new Result (result.iteration, result.point, result.value);
			double hm0 = GAMA0 * Math.pow ((double) (m + 1), -aniu),
      			   hm, s1, am, d1, epsm, sig0 = Math.sqrt (f11), a3, f14;
			x1Pnt = new Point (result.point); x2Pnt = new Point (result.point);

			for (i = 0; i < n; i++) {
				double dx = hm0 * abPnt.x [i];
				x1Pnt.x [i] = increment (result.point.x [i], domain.min [i], domain.max [i],  dx);
				x2Pnt.x [i] = increment (result.point.x [i], domain.min [i], domain.max [i], -dx);
				dmlPnt.x [i]  = 2 * dx * (task.f (x1Pnt) - task.f (x2Pnt));
				dmlPnt.x [i] /= x1Pnt.x [i] - x2Pnt.x [i]; x1Pnt.x [i] = x2Pnt.x [i] = result.point.x [i]; }

			if (lb > 1) for (i = 0; i < n; i++) dmlPnt.x [i] = (dmlPnt.x [i] + dml1Pnt.x [i] * (lb - 1)) / lb;
			s1 = dmlPnt.norma () + EPS4; hm = Math.sqrt (hm0) / s1;

			for (i = 0; i < n; i++) {
				double dx = hm * abPnt.x [i] * dmlPnt.x [i];
				x1Pnt.x [i] = increment (result.point.x [i], domain.min [i], domain.max [i],  dx);
				x2Pnt.x [i] = increment (result.point.x [i], domain.min [i], domain.max [i], -dx); }

			double hs = 0., hs1 = 0., hs2 = 0.;
			for (i = 0; i < n; i++) {
				hs  += Math.pow (((x2Pnt.x [i] - x1Pnt.x [i]) / abPnt.x [i]), 2);
				hs1 += Math.pow (((x1Pnt.x [i] - result.point.x [i]) / abPnt.x [i]), 2) + EPS3;
				hs2 += Math.pow (((x2Pnt.x [i] - result.point.x [i]) / abPnt.x [i]), 2) + EPS3; }

			hs  = Math.sqrt (hs); hs1 = Math.sqrt (hs1); hs2 = Math.sqrt (hs2);
			am  = task.f (x1Pnt) * hs2 + task.f (x2Pnt) * hs1 - result.value * hs;
			am *= 2 * hm0; am /= hs1 * hs2 * (hs1 + hs2); d1 = am;

			if (am < B1) d1 = V1; if (am < B2 && m == 0) d1 = V2;
			a1 += d1 / hm0; epsm = hm0 * a1 / (m + 1);

			if (m < M1) { double sigl;
				if (f11 * (n2 - 1) / n2 < EPS2) { f11 = 2 * EPS2; n2 = 2; }
				sig0 = Math.sqrt (f11); sigl = hm0 * sig0 * Math.sqrt (n / (2. * lb)) / d1;
				if (lo != CONV) if (s1 >= sigl) lo = CONV; else an0++; else
				if (s1 <= 0.5 * sigl) { lo = SL_CONV; an0++; }
			} else an0++;

			a3 = (Math.pow ((double) (an0), beta) + 2) * epsm;
			for (i = 0; i < n; i++) x1Pnt.x [i] = increment (result.point.x [i], 
				domain.min [i], domain.max [i], -abPnt.x [i] * dmlPnt.x [i] / a3);
			
			lbayes_dispersion (x1Pnt, x2Pnt, meandisp, n2, task); f14 = n2 * meandisp[0];

			if (meandisp [0] <= result.value + 0.5 * sig0 / Math.sqrt((double) (n2))) {
				result.point = x1Pnt; result.value = meandisp [0]; f13 = f14;
				f11 = (meandisp[1] + f11 * (++am2 - 1)) / am2; lb = 1;
			} else {
				f13 += task.f (result.point);
				result.value = f13 / ((am2 == 1) ? (lb + 2 * n) : (lb + n2)); 
				lb++; dml1Pnt = new Point (dmlPnt); }
			
			if (result.value < bestres.value) bestres = result;
			//logger.log (result);
		} // for (m = 0; m < iterations - 1; m++, result.iteration++) *
		
		return bestres;
	}
	//
	//======================================================
	private void dispersion (double x[], int n_of_pnts, double meandisp[])
	{
		int i; meandisp[0] = 0.0; meandisp[1] = 0.0;
		for (i = 0; i < n_of_pnts; i++) {meandisp[0] += x[i]; meandisp[1] += x[i] * x[i];}
		meandisp[0] /= n_of_pnts;
		meandisp[1]  = (meandisp[1] - n_of_pnts * meandisp[0] * meandisp[0]) / (n_of_pnts - 1);
	}
	//
	//======================================================
	private void lbayes_dispersion (Point xPnt, Point x1Pnt, 
									double meandisp[], int n2, Task task)
	{
		double x[] = new double [n2];
		for (int i = 0; i < n2; i++) 
			x1Pnt.x [(i < x1Pnt.x.length) ? i : 0] = x [i] = task.f (xPnt);
		dispersion (x, n2, meandisp);
	}
	//
	//======================================================
	protected double increment (double x, double a, double b, double dx)
	{
		x += dx; return (dx > 0.0) ? ((x > b) ? b : x) : ((x < a) ? a : x);
	}
	//
};