//  Unt.java

package lt.ktu.gmj.methods;

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

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

public class Unt extends IPMethodBase {

	private static final int I100 = 100;

	private static final int I70 = 70;

	private static final int I40 = 40;

	private static final int I10 = 10;

	private static final int I7 = 7;

	private static final int I5 = 5;

	private static final int I3 = 3;

	private static final int K5 = 5;

	private static final double HALF = 0.5;

	private int n_of_points;

	private int ls[];

	private int ks[];

	private int nx[][];

	private double am[];

	private double ams[];

	private double graff[];

	private double del[];

	private double z[];

	private double work[];

	private double fs[];

	private double a2[][];

	private int nl[];

	private int nh[];

	private double ab[];

	public Unt() {
		iterations = 1000;
		initialPoints = 10; // n_of_points = 0;
	}

	public void setParameters(int _iterations, int _initialPoints) {
		iterations = _iterations;
		initialPoints = _initialPoints;
	}

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

		int n = domain.dimensions(); // .length;
		ks = new int[K5];
		nh = new int[I5];
		nl = new int[I10];
		ab = new double[I10];
		ls = new int[iterations];
		nx = new int[I10][I100];
		am = new double[n];
		ams = new double[n];
		graff = new double[n];
		del = new double[n];
		z = new double[n];
		work = new double[initialPoints];
		fs = new double[Math.min(initialPoints / I5, I10)];
		a2 = new double[I5][n];

		int kau = 0, i, k, n1, kt, kx, k4, k2, nb, m, kxl;
		double s1, s2, af, za, yl, xl, zm, d1, rr;
		double fi[] = new double[1], dal[] = new double[2], smds[] = new double[2];

		integer_array_fill(ls, iterations, 0);
		s2 = distance(domain.min, domain.max) * HALF;
		for (i = 0; i < n; i++)
			// .length
			del[i] = (domain.max[i] - domain.min[i]) * 1.0e-4;
		af = (domain.max[0] - domain.min[0]) / I10;
		integer_array_fill(nl, I10, -1);
		for (i = 0; i < I10; i++)
			ab[i] = domain.min[0] + i * af;
		// zi = r1mach (2);
		for (n_of_points = 0; n_of_points < initialPoints; n_of_points++) {
			m = n_of_points % I10;
			fi[0] = ab[m] + af;

			random_point(z, 0, ab, m, fi, 0, 1);
			random_point(z, 1, domain.min, 1, domain.max, 1, n - 1);

			Result r = new Result(n_of_points, new Point(MakePoint(z, domain)),
					fp(z, n, ab, nx, nl, n_of_points, task));
			logger.log(r);

			if (r.value < result.value)
				result = r;
		}
		// estimation of k-th MAXimal function value za *
		af *= HALF;
		k = Math.min(initialPoints / I5, I10);
		if (k == 0)
			return result;
		for (i = 0; i < k; i++)
			fs[i] = logger.logAt(i).value;
		n1 = array_minimum(fs);

		za = fs[n1];
		for (i = k; i < initialPoints; i++) {
			fi[0] = logger.logAt(i).value;
			if (fi[0] > za) {
				fs[n1] = fi[0];
				n1 = array_minimum(fs);
				za = fs[n1];
			}
		}
		fi[0] = Math.abs(result.value - za);

		if ((fi[0] < r1mach(3)) || (fi[0] > Math.sqrt(r1mach(2))))
			return result;

		kt = 1;
		vert(n, initialPoints, work, nh, am, ab, a2, af, nl, nx, ls, za, dal,
				ams, result.value, logger);
		nb = -1;
		yl = 0.7 * result.value + 0.3 * za;
		kx = 0;
		k4 = I3;
		xl = 0.;
		k2 = (iterations + initialPoints) / 2;
		// main loop of global MINimization *

		Point zPnt, x1Pnt = new Point(domain), x2Pnt = new Point(domain), workPnt = new Point(
				MakePoint(work, domain));
		Point xsPnt[] = new Point[K5];
		for (int tmpv = 0; tmpv < K5; tmpv++)
			xsPnt[tmpv] = new Point(domain);

		for (i = initialPoints; i < k2; i++) {
			double f1, f2, f3 = r1mach(2), f4 = r1mach(2);
			kt = 1;
			for (m = 0; m < I40; m++) {
				zPnt = new RandomPoint(domain);
				zm = 0.1;
				f1 = fu(zPnt, zm, yl, smds, nh, am, ab, a2, kt, n_of_points,
						af, nl, nx, nb, ls, za, dal, ams, result.value, logger);
				zm = 1.0;
				f2 = fu(zPnt, zm, yl, smds, nh, am, ab, a2, kt, n_of_points,
						af, nl, nx, nb, ls, za, dal, ams, result.value, logger);
				if (f1 < f3) {
					f3 = f1;
					x2Pnt = new Point(zPnt);
				}
				if (f2 < f4) {
					f4 = f2;
					x1Pnt = new Point(zPnt);
				}
			}
			zm = -1.0;
			kt = 1;
			s1 = s2 / Math.pow((double) n_of_points, 1.0 / n);
			graunt(x1Pnt, domain, s1, zm, yl, smds, nh, am, ab, a2, kt,
					n_of_points, af, nl, nx, nb, ls, za, dal, ams,
					result.value, workPnt, graff, del, logger);

			Result r = new Result(n_of_points++, x1Pnt, f1 = fp(x1Pnt.x, n, ab,
					nx, nl, n_of_points, task));
			logger.log(r);
			if (r.value < result.value)
				result = r;

			zm = -0.1;
			kt = 1;
			s1 = s2 / Math.pow((double) n_of_points, 1.0 / n);
			graunt(x2Pnt, domain, s1, zm, yl, smds, nh, am, ab, a2, kt,
					n_of_points, af, nl, nx, nb, ls, za, dal, ams,
					result.value, workPnt, graff, del, logger);
			d1 = x1Pnt.distance(x2Pnt);
			kxl = 0;
			if (d1 <= 0.01 * s2) {
				kxl = 1;
				x2Pnt = new RandomPoint(domain);
			}

			r = new Result(n_of_points++, x2Pnt, f2 = fp(x2Pnt.x, n, ab, nx,
					nl, n_of_points, task));
			logger.log(r);
			if (r.value < result.value)
				result = r;

			if (kxl == 1) {
				f2 = f1;
				x2Pnt = x1Pnt;
			}
			kx++;
			xl = ((kx - 1.) * xl + f2) / kx;
			if (k % I10 == I10 - 1)
				yl = xl;
			n1 = -1;
			for (m = 0; m < I5; m++) {
				i = nh[m];
				ls[i]++;
				if (ls[i] == I7) {
					fi[0] = logger.logAt((i < n_of_points) ? i
							: n_of_points - 1).value;
					if (n1 == -1) {
						f3 = fi[0];
						n1 = i;
					} else if (fi[0] < f3) {
						f3 = fi[0];
						n1 = i;
					}
				}
			}
			if (n1 == -1)
				continue;
			if (f2 > f3)
				zPnt = logger.logAt((n1 < n_of_points) ? n1 : n_of_points - 1).point;
			else
				zPnt = x2Pnt;
			// estimation of local MINimum of fu *
			kt = 3;
			graunt(zPnt, domain, s1, zm, yl, smds, nh, am, ab, a2, kt,
					n_of_points, af, nl, nx, nb, ls, za, dal, ams,
					result.value, workPnt, graff, del, logger);
			n1 = nh[0];
			fi[0] = logger.logAt((n1 < n_of_points) ? n1 : n_of_points - 1).value;
			ls[n1] = I7;
			for (m = 1; m < I5; m++) {
				i = nh[m];
				rr = logger.logAt((i < n_of_points) ? i : n_of_points - 1).value;
				if (rr < fi[0]) {
					n1 = i;
					fi[0] = rr;
				}
				ls[i] = I7;
			}
			zPnt = logger.logAt((n1 < n_of_points) ? n1 : n_of_points - 1).point;
			if (kau != 0) {
				for (i = 0; i < kau; i++) {
					rr = norm(xsPnt[i], x1Pnt, am);
					if (rr >= 0.04)
						continue;
					rr = fs[(i < fs.length) ? i : fs.length - 1];
					if (fi[0] < rr) {
						xsPnt[i] = zPnt;
						fs[(i < fs.length) ? i : fs.length - 1] = fi[0];
					}
					n1 = ks[i];
					if (n1 >= k4)
						return result;// density of points in local MINimum
										// area equals 3 *
					ks[i] = n1 + 1;
					break;
				}
				if (n1 < k4)
					continue;
			}
			ks[kau] = 1;
			fs[(kau < fs.length) ? kau : fs.length - 1] = fi[0];
			xsPnt[kau] = zPnt;
			if (++kau >= K5)
				return result;// number of local minimuma equals 5 *
		} // for (i = initialPoints; i < k2; i++) *
		return result;
	}

	// ------------------------------------------------------
	// ======================================================
	private void vert(int n, int n_of_pnts, double x[], int nh[], double am[],
			double ab[], double a2[][], double af, int nl[], int nx[][],
			int ls[], double za, double dal[], double ams[], double zi,
			ResultLogger logger)
	// estimation of scale factors *
	{
		int m, k, kt, nb;
		double v, fi;
		double t_fs[] = new double[2];
		double smds[] = new double[2];
		kt = 1;
		for (k = 0; k < n; k++) {
			t_fs[0] = 0.0;
			for (m = 0; m < n_of_pnts; m++)
				x[m] = logger.logAt(m).point.x[k];
			dispersion(x, n_of_pnts, t_fs);
			am[k] = t_fs[1] = n_of_pnts / (t_fs[1] * (n_of_pnts - 1));
			ams[k] = Math.sqrt(t_fs[1]);
		}
		dal[0] = 1.0;
		t_fs[1] = v = 0.0;
		for (nb = 0; nb < n_of_pnts; nb++) {
			fi = logger.logAt(nb).value;
			if (fi >= za)
				continue;
			Point point = new Point(logger.logAt(nb).point);
			smd(smds, point, nh, am, ab, a2, kt, n_of_pnts, af, nl, nx, nb, ls,
					za, dal, ams, zi, logger);
			t_fs[1] += smds[1] * (smds[0] - fi) * (smds[0] - fi);
			v += smds[1] * smds[1];
		}
		dal[0] = t_fs[1] / v;
	}

	// ------------------------------------------------------
	// ======================================================
	private void smd(double smds[], Point point, int nh[], double am[],
			double ab[], double a2[][], int kt, int n_of_pnts, double af,
			int nl[], int nx[][], int nb, int ls[], double za, double dal[],
			double ams[], double zi, ResultLogger logger)
	// calculates expected value and conditional uncertainity *
	{
		System.out.println("n_of_pnts " + n_of_pnts);
		double at[] = new double[I5];
		double an[] = new double[I5];
		double dz, r2, r3, z, r1, b1, rm, rr, s, v, v2, v3;
		int k1, l1, j, m, i, k, ks, kr, kn, n2;

		if (kt % 2 == 0)
			for (i = 0; i < I5; i++)
				at[i] = norm(point, logger.logAt((nh[i] < n_of_points) ? nh[i]
						: n_of_points - 1).point, am);
		else if (n_of_pnts > I70) {
			rr = point.x[0];
			k = I10 - 2;
			for (i = 1; i < I10; i++)
				if (rr < ab[i]) {
					k = i - 1;
					if (k != 0 && rr < ab[k] + af)
						k--;
					break;
				}
			ks = -1;
			kr = 1;
			kn = nl[k];
			for (i = 0; i < I5; i++) {
				do {
					if (ks >= kn) {
						ks = -1;
						kr = 2;
						k++;
						kn = nl[k];
					}
					ks++;
					n2 = nx[k][ks];
				} while (n2 == nb);
				nh[i] = n2;
				at[i] = norm(point, logger.logAt((n2 < n_of_points) ? n2
						: n_of_points - 1).point, am);
			}
			m = array_maximum(at);
			rm = at[m];
			while (kr != 2 || ks != kn) {
				if (ks >= kn) {
					ks = -1;
					kr = 2;
					k++;
					kn = nl[k];
				}
				ks++;
				n2 = nx[k][ks];
				if (n2 == nb)
					continue;
				rr = norm(point, logger.logAt((n2 < n_of_points) ? n2
						: n_of_points - 1).point, am);
				if (rr >= rm)
					continue;
				nh[m] = n2;
				at[m] = rr;
				m = array_maximum(at);
				rm = at[m];
			}
		} else {
			n2 = 0;
			for (i = 0; i < I5; i++) {
				if (n2 == nb)
					n2++;
				nh[i] = n2;
				at[i] = norm(point, logger.logAt((n2 < n_of_points) ? n2
						: n_of_points - 1).point, am);
				n2++;
			}
			m = array_maximum(at);
			rm = at[m];
			for (i = n2; i < n_of_pnts - 2; i++) {
				if (i == nb)
					continue;
				rr = norm(point, logger.logAt((i < n_of_points) ? i
						: n_of_points - 1).point, am);
				if (rr >= rm)
					continue;
				nh[m] = i;
				at[m] = rr;
				m = array_maximum(at);
				rm = at[m];
			}
		}
		for (i = 0; i < I5; i++) {
			rr = at[i];
			rm = Math.sqrt(rr);
			at[i] = rm;
			an[i] = Math.max(r1mach(3), Math.exp(-rr / 0.3) / (rm + 1.0e-3));
		}
		if (kt < 3) {
			s = v = v2 = v3 = 0.0;
			for (j = 0; j < I5; j++) {
				i = nh[j];
				smds[1] = 0.0;
				if (nb != -1
						&& logger
								.logAt((i < n_of_points) ? i : n_of_points - 1).value > za)
					return;
				rr = at[j];
				b1 = an[j];
				s += b1
						* logger.logAt((i < n_of_points) ? i : n_of_points - 1).value;
				if (ls[(i < ls.length) ? i : ls.length - 1] < I7) {
					v3 += b1;
					v2 += rr * b1;
				} else {
					rm = rr / 5.0;
					r1 = Math.max(r1mach(3), Math.exp(-rm * rm / 0.3)
							/ (rm + 1.0e-3));
					v3 += r1;
					v2 += r1 * rm;
				}
				v += b1;
			}
			smds[0] = s / v;
			smds[1] = dal[0] * v2 / v3;
		} else {
			if (kt == 3) {
				rm = r1mach(3);
				for (i = 0; i < I5; i++) {
					b1 = 0.0;
					j = nh[i];
					z = logger.logAt((j < n_of_points) ? j : n_of_points - 1).value;
					for (l1 = 0; l1 < point.x.length; l1++) {
						r3 = ams[l1];
						r2 = logger.logAt((j < n_of_points) ? j
								: n_of_points - 1).point.x[l1];
						s = v = 0.;
						for (k = 0; k < I5; k++) {
							if (k == i)
								continue;
							k1 = nh[k];
							r1 = an[k];
							rr = norm(logger.logAt((j < n_of_points) ? j
									: n_of_points - 1).point, logger
									.logAt((k1 < n_of_points) ? k1
											: n_of_points - 1).point, am);
							v += r1;
							s += (logger.logAt((k1 < n_of_points) ? k1
									: n_of_points - 1).value - z)
									* (logger.logAt((k1 < n_of_points) ? k1
											: n_of_points - 1).point.x[l1] - r2)
									* r1 * r3 / rr;
						}
						a2[i][l1] = dal[1] = s / v;
						b1 += dal[1] * dal[1];
					}
					rm = Math.max(rm, b1);
				}
				dal[1] = HALF * (za - zi) / Math.sqrt(rm);
			}
			s = v = 0.;
			for (i = 0; i < I5; i++) {
				rr = at[i];
				j = nh[i];
				b1 = an[i];
				dz = 0.0;
				for (k = 0; k < point.x.length; k++)
					dz += a2[i][k]
							* (point.x[k] - logger.logAt((j < n_of_points) ? j
									: n_of_points - 1).point.x[k]) * ams[k];
				v += b1;
				s += b1
						* (logger
								.logAt((j < n_of_points) ? j : n_of_points - 1).value + dz
								* dal[1] / (dal[1] + rr));
			}
			smds[0] = s / v;
		}
	}

	// ------------------------------------------------------
	// ======================================================
	private double fu(Point point, double zm, double yl, double smds[],
			int nh[], double am[], double ab[], double a2[][], int kt,
			int n_of_pnts, double af, int nl[], int nx[][], int nb, int ls[],
			double za, double da1[], double ams[], double zi,
			ResultLogger logger)
	// function that to be MINimized *
	{
		double z = Math.abs(zm);

		if (kt < 3) {
			if (zm < HALF)
				smd(smds, point, nh, am, ab, a2, kt, n_of_pnts, af, nl, nx, nb,
						ls, za, da1, ams, zi, logger);
			smds[1] = Math.max(r1mach(3), smds[1]);
			if (z > HALF)
				return ((smds[0] - zi + (za - zi) * z) / Math.sqrt(smds[1]));
			else
				return ((smds[0] + yl - 2 * zi) / Math.sqrt(smds[1]));
		} else {
			smd(smds, point, nh, am, ab, a2, kt, n_of_pnts, af, nl, nx, nb, ls,
					za, da1, ams, zi, logger);
			return smds[0];
		}
	}

	// ------------------------------------------------------
	// ======================================================
	private void graunt(Point xPnt, Domain domain, double s, double zm,
			double yl, double smds[], int nh[], double am[], double ab[],
			double a2[][], int kt, int n_of_pnts, double af, int nl[],
			int nx[][], int nb, int ls[], double za, double da1[],
			double ams[], double zi, Point zPnt, double ff[], double del[],
			ResultLogger logger) {
		double eps, y;
		y = fu(xPnt, zm, yl, smds, nh, am, ab, a2, kt, n_of_pnts, af, nl, nx,
				nb, ls, za, da1, ams, zi, logger);
		eps = 0.03 * s;
		kt++;
		zPnt = new Point(xPnt);
		for (;;) {
			double sg;
			int i;
			for (i = 0; i < xPnt.x.length; i++) {
				double x1 = xPnt.x[i], f1, f2, d1 = del[i];
				if (x1 > domain.max[i] - eps || x1 < domain.min[i] + eps) {
					ff[i] = 0.;
					continue;
				}
				zPnt.x[i] = x1 + d1;
				f1 = fu(zPnt, zm, yl, smds, nh, am, ab, a2, kt, n_of_pnts, af,
						nl, nx, nb, ls, za, da1, ams, zi, logger);
				zPnt.x[i] = x1 - d1;
				f2 = fu(zPnt, zm, yl, smds, nh, am, ab, a2, kt, n_of_pnts, af,
						nl, nx, nb, ls, za, da1, ams, zi, logger);
				ff[i] = (f1 - f2) / (2 * d1);
				zPnt.x[i] = x1;
			}
			sg = MakePoint(ff, domain).norma();
			if (sg < r1mach(3))
				return;
			for (;;) {
				double dx1 = s / sg, f1;
				for (i = 0; i < zPnt.x.length; i++) {
					double dx = dx1 * ff[i];
					zPnt.x[i] = increment(xPnt.x[i], domain.min[i],
							domain.max[i], -dx);
				}
				kt--;
				f1 = fu(zPnt, zm, yl, smds, nh, am, ab, a2, kt, n_of_pnts, af,
						nl, nx, nb, ls, za, da1, ams, zi, logger);
				kt++;
				if (f1 < y) {
					y = f1;
					xPnt = new Point(zPnt);
					break;
				} else
					s *= HALF;
				if (s < eps)
					return;
			}
		}
	}

	// ------------------------------------------------------
	// ======================================================
	private double fp(double x[], int n, double ab[], int nx[][], int nl[],
			int n_of_pnts, Task task) {
		double y = task.f(MakePoint(x, task.domain())), rr = x[0];
		int k = I10 - 1;

		for (int i = 1; i < I10; i++)
			if (rr < ab[i]) {
				k = i - 1;
				break;
			}
		nl[k]++;
		nx[k][nl[k]] = n_of_pnts + 1;
		return y;
	}

	// ------------------------------------------------------
	// ======================================================
	private double distance(double x[], double y[]) {
		double dist = 0;
		for (int i = 0; i < x.length; i++) {
			double s = x[i] - y[i];
			dist += s * s;
		}
		return Math.sqrt(dist);
	}

	//
	// ======================================================
	private double norm(Point p1, Point p2, double am[]) {
		double r = 0.0;
		for (int i = 0; i < p2.x.length; i++) {
			double t = p1.x[i] - p2.x[i];
			r += am[i] * t * t;
		}
		return (r);
	}

	//
	// ======================================================
	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 Point MakePoint(double x[], Domain domain) {
		Point pt = new Point(domain);
		pt.x = new double[x.length];
		System.arraycopy(x, 0, pt.x, 0, pt.x.length);
		return pt;
	}

	//
	// ======================================================
	private void random_point(double x[], int xs, double a[], int as,
			double b[], int bs, int n) {
		for (int i = 0; i < n; i++)
			x[i + xs] = Math.random() * (b[i + bs] - a[i + as]) + a[i + as];
	}

	//
	// ======================================================
	static private final double r1mach(int i) {
		switch (i) {
		case 1:
			return Double.MIN_VALUE;
		case 2:
			return Double.MAX_VALUE;
		case 3:
		case 4:
			return 1E-8;
		}
		return 0;
	}

	//
	// ======================================================
	protected double increment(double x, double a, double b, double dx) {
		x += dx;
		if (dx > 0.0)
			if (x > b)
				return b;
			else
				return x;
		else if (x < a)
			return a;
		else
			return x;
	}

	//
	// ======================================================
	private void integer_array_fill(int dest[], int dim, int sample) {
		while (dim-- > 0)
			dest[dim] = sample;
	}

	//
	// ======================================================
	private int array_minimum(double x[]) {
		if (x.length == 0)
			return 0;
		double min_val = x[0];
		int index = 0;
		for (int i = 1; i < x.length; i++)
			if (x[i] < min_val) {
				min_val = x[i];
				index = i;
			}
		return index;
	}

	//
	// ======================================================
	private int array_maximum(double x[]) {
		if (x.length == 0)
			return 0;
		double max_val = x[0];
		int index = 0;
		for (int i = 1; i < x.length; i++)
			if (x[i] > max_val) {
				max_val = x[i];
				index = i;
			}
		return index;
	}
};