package multinomad.tools.convex;

import java.util.ArrayList;

import multinomad.config.Configuration;
import multinomad.config.LoadProperties;
import multinomad.individuals.DoubleIndividual;
import multinomad.individuals.Population;

public class ConvexFunction {

	/**
	 * Let X be a convex set in a real vector space and let f: X -> R be a function f.
	 * f is called convex if:
	 * For every x1 and x2 in X, and every t in [0,1]:
	 * f(tx1+(1-t)x2) <= tf(x1) + (1-t)f(x2)
	 * 
	 * We make t=0.5. We can explore a set of points between x1 and x2 by recursively halving the interval. 
	 * 
	 * @param i1 first individual containing the vector x1
	 * @param i2 second individual containing the vector x2
	 * @param list Must be initialized. It keeps a list with all the individuals explored
	 * @param currentDepth initially 0
	 * @param maxDepth The depth of the tree to explore. 1 means we just test a single intermediate point; 
	 * a depth of 2 means 1 + 2 intermediate points to explore (i.e. 3). A depth of 3 would explore a max of 1 + 2 + 4 intermediate points (i.e. 7)   
	 * @return true if in maxDepth we did not detect any violation of convexity.
	 */
	public static boolean convexFunction(DoubleIndividual i1, DoubleIndividual i2, ArrayList<DoubleIndividual> list, int currentDepth, int maxDepth) {
		if (currentDepth >= maxDepth)
			return true;
		else {
			DoubleIndividual imean = averageIndividual(i1, i2);
			list.add(imean);
			boolean first = convexFunction(i1, imean, list, currentDepth+1, maxDepth);
			boolean second = convexFunction(imean, i2, list, currentDepth+1, maxDepth);
			if(isConvex(i1, i2, imean)) {
				return first && second;
			}else
				return false;
		}
	}
	
	
	private static DoubleIndividual averageIndividual(DoubleIndividual i1, DoubleIndividual i2) {
		double[] xx = new double[i1.getChr().getLength()];
		double[] x1 = i1.getChr().asdouble();
		double[] x2 = i2.getChr().asdouble();
		for(int i=0; i< xx.length;i++) {
			xx[i] = (x1[i]+x2[i])/2.0;
		}
		DoubleIndividual imean = new DoubleIndividual(xx);
		imean.setFitness(Configuration.f.evaluate(xx));
		return imean;
	}
	
	/**
	 * Let X be a convex set in a real vector space and let f: X -> R be a function f.
	 * f is called convex if:
	 * For every x1 and x2 in X, and every t in [0,1]:
	 * f(tx1+(1-t)x2) <= tf(x1) + (1-t)f(x2)
	 * 
	 * We assume that (tx1+(1-t)x2) is imean (i.e. the average between i1 and i2), therefore t=0.5
	 * @return true if imean doesn't violate the inequality above.
	 */
	private static boolean isConvex (DoubleIndividual i1, DoubleIndividual i2, DoubleIndividual imean) {
		double firstterm = imean.getFitness();
		double tfx1 = 0.5*i1.getFitness();
		double tfx2 = 0.5*i2.getFitness();
		
		if (firstterm >= (tfx1 + tfx2)) // Without lost of generality we talk about a maximization problem 
			return true;
		else
			return false;
		
	}
	
	public static void main(String[] args) {
		LoadProperties lp = new LoadProperties(args,null);
		Configuration.setConfiguration(lp);
		
//		int lbound = (int) Configuration.f.getBounds().get(0).lower;
//		int ubound = (int) Configuration.f.getBounds().get(0).upper;
//		
//		for(double i=lbound; i<=ubound; i+=0.1) {
//			double[] x= {i};
//			DoubleIndividual ind = new DoubleIndividual(x);
//			ind.setFitness(Configuration.f.evaluate(x));
//			System.out.println(x[0]+" "+Configuration.f.evaluate(x));
//		}
		
		Population pop = new Population(false);
		double [] x ={5};
		DoubleIndividual i1 = new DoubleIndividual(x);
		i1.setFitness(Configuration.f.evaluate(x));
		double [] x2 ={8};
		DoubleIndividual i2 = new DoubleIndividual(x2);
		i2.setFitness(Configuration.f.evaluate(x2));
		pop.add(i1);
		pop.add(i2);

		for(DoubleIndividual ind : pop) {
			System.out.println("Initial: "+ ind.asString());
		}
		
		i1 = pop.getP().get(0);
		i2 = pop.getP().get(1);
		ArrayList<DoubleIndividual> list = new ArrayList<DoubleIndividual>();
		
		boolean isConvex = convexFunction(i1, i2, list, 0, 2);
		
		if(isConvex){
			System.err.println("Yes, it's convex");
		}else {
			System.err.println("No, it isn't convex");
		}
		
		for (DoubleIndividual ind : list) {
			System.out.println("List: "+ ind.asString());
		}
	}
	
}
