package optimization.linesearch;

import optimization.gradientBasedMethods.ProjectedObjective;
import optimization.util.Interpolation;
import optimization.util.MathUtils;





/**
 * Implements Armijo Rule Line search along the projection arc (Non-Linear Programming page 230)
 * To be used with Projected gradient Methods.
 * 
 * Recall that armijo tries successive step sizes alpha until the sufficient decrease is satisfied:
 * f(x+alpha*direction) < f(x) + alpha*c1*grad(f)*direction
 * 
 * In this case we are optimizing over a convex set X so we must guarantee that the new point stays inside the 
 * constraints.
 * First the direction as to be feasible (inside constraints) and will be define as:
 * d = (x_k_f - x_k) where x_k_f is a feasible point.
 * so the armijo condition can be rewritten as:
 * f(x+alpha(x_k_f - x_k)) < f(x) + c1*grad(f)*(x_k_f - x_k)
 * and x_k_f is defined as:
 * [x_k-alpha*grad(f)]+
 * where []+ mean a projection to the feasibility set.
 * So this means that we take a step on the negative gradient (gradient descent) and then obtain then project
 * that point to the feasibility set. 
 * Note that if the point is already feasible then we are back to the normal armijo rule.
 * 
 * @author javg
 *
 */
public class ArmijoLineSearchMinimizationAlongProjectionArc implements LineSearchMethod{

	/**
	 * How much should the step size decrease at each iteration.
	 */
	double contractionFactor = 0.5;
	double c1 = 0.0001;
	
	
	double initialStep;
	int maxIterations = 100;
			
	
	double sigma1 = 0.1;
	double sigma2 = 0.9;
	
	//Experiment
	double previousStepPicked = -1;;
	double previousInitGradientDot = -1;
	double currentInitGradientDot = -1;
	
	GenericPickFirstStep strategy;
	
	
	public void reset(){
		previousStepPicked = -1;;
		previousInitGradientDot = -1;
		currentInitGradientDot = -1;
	}

	
	public ArmijoLineSearchMinimizationAlongProjectionArc(){
		this.initialStep = 1;
	}
	
	public ArmijoLineSearchMinimizationAlongProjectionArc(GenericPickFirstStep strategy){
		this.strategy = strategy;
		this.initialStep = strategy.getFirstStep(this);
	}
	
	
	public void setInitialStep(double initial){
		this.initialStep = initial;
	}
	
	/**
	 * 
	 */
	
	public double getStepSize(DifferentiableLineSearchObjective o) {	

		
		//Should update all in the objective
		initialStep = strategy.getFirstStep(this);
		o.updateAlpha(initialStep);	
		previousInitGradientDot=currentInitGradientDot;
		currentInitGradientDot=o.getCurrentGradient();
		int nrIterations = 0;
	
		//Armijo rule, the current value has to be smaller than the original value plus a small step of the gradient
		while(o.getCurrentValue()  >
			o.getOriginalValue() + c1*(o.getCurrentGradient())){			
//			System.out.println("curr value "+o.getCurrentValue());
//			System.out.println("original value "+o.getOriginalValue());
//			System.out.println("GRADIENT decrease" +(MathUtils.dotProduct(o.o.gradient,
//					MathUtils.arrayMinus(o.originalParameters,((ProjectedObjective)o.o).auxParameters))));
//			System.out.println("GRADIENT SAVED" + o.getCurrentGradient());
			if(nrIterations >= maxIterations){
				System.out.println("Could not find a step leaving line search with -1");
				o.printLineSearchSteps();
				return -1;
			}
			double alpha=o.getAlpha();
			double alphaTemp = 
				Interpolation.quadraticInterpolation(o.getOriginalValue(), o.getInitialGradient(), alpha, o.getCurrentValue());
			if(alphaTemp >= sigma1 || alphaTemp <= sigma2*o.getAlpha()){
				alpha = alphaTemp;
			}else{
				alpha = alpha*contractionFactor;
			}
//			double alpha =obj.getAlpha()*contractionFactor;
			o.updateAlpha(alpha);
			nrIterations++;			
		}
//		System.out.println("curr value "+o.getCurrentValue());
//		System.out.println("original value "+o.getOriginalValue());
//		System.out.println("sufficient decrease" +c1*o.getCurrentGradient());
//		System.out.println("Leavning line search used:");
//		o.printSmallLineSearchSteps();	
		
		previousStepPicked = o.getAlpha();
		return o.getAlpha();
	}
	
	public double getInitialGradient() {
		return currentInitGradientDot;
		
	}

	public double getPreviousInitialGradient() {
		return previousInitGradientDot;
	}

	public double getPreviousStepUsed() {
		return previousStepPicked;
	}
		
}