/* Copyright 2009-2014 David Hadka
 *
 * This file is part of the MOEA Framework.
 *
 * The MOEA Framework is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * The MOEA Framework is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with the MOEA Framework.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.moeaframework.problem;

import org.apache.commons.math3.linear.RealMatrix;
import org.moeaframework.core.Problem;
import org.moeaframework.core.Solution;
import org.moeaframework.core.variable.EncodingUtils;
import org.moeaframework.core.variable.RealVariable;
import org.moeaframework.util.RotationMatrixBuilder;
import org.moeaframework.util.Vector;

/**
 * Decorator to create rotated instances of test problems.  The rotation is
 * defined by a rotation matrix, which should be orthogonal and have a
 * determinant of 1.  See {@link RotationMatrixBuilder} for a utility to quickly
 * construct rotation matrices.
 * <p>
 * Regardless of the rotation, all rotated instances use expanded lower and
 * upper bounds for the decision variables.  An additional constraint is added
 * to account for solutions existing in this expanded region, which are not in
 * the original unrotated problem.  This expansion is consistent across all
 * rotations, which ensures the volume of the constraint-violating expanded
 * region is constant across all instances of a problem.
 */
public class RotatedProblem implements Problem {
	
	/**
	 * The original unrotated problem.
	 */
	private final Problem problem;
	
	/**
	 * The rotation matrix.
	 */
	private final RealMatrix rotation;
	
	/**
	 * The expanded lower bounds.
	 */
	private final double[] lowerBounds;
	
	/**
	 * The expanded upper bounds.
	 */
	private final double[] upperBounds;
	
	/**
	 * The center of the search space about which the rotation occurs.
	 */
	private final double[] center;
	
	/**
	 * Decorates the specified problem, creating a rotated instance using the
	 * specified rotation matrix.
	 * 
	 * @param problem the original unrotated problem
	 * @param rotation the rotation matrix
	 */
	public RotatedProblem(Problem problem, RealMatrix rotation) {
		super();
		this.problem = problem;
		this.rotation = rotation;
		
		//calculate the expanded lower and upper bounds
		Solution solution = problem.newSolution();
		
		center = new double[getNumberOfVariables()];
		lowerBounds = new double[getNumberOfVariables()];
		upperBounds = new double[getNumberOfVariables()];
		
		for (int i = 0; i < getNumberOfVariables(); i++) {
			RealVariable variable = (RealVariable)solution.getVariable(i);
			
			center[i] = (variable.getLowerBound() + variable.getUpperBound()) /
					2.0;
			lowerBounds[i] = Math.sqrt(2.0) *
					(variable.getLowerBound() - center[i]);
			upperBounds[i] = Math.sqrt(2.0) *
					(variable.getUpperBound() - center[i]);
		}
	}

	@Override
	public String getName() {
		return "Rotated " + problem.getName();
	}

	@Override
	public int getNumberOfVariables() {
		return problem.getNumberOfVariables();
	}

	@Override
	public int getNumberOfObjectives() {
		return problem.getNumberOfObjectives();
	}

	@Override
	public int getNumberOfConstraints() {
		return problem.getNumberOfConstraints() + 1;
	}

	@Override
	public void evaluate(Solution solution) {
		Solution temp = problem.newSolution();
		
		//apply the rotation
		double[] x = EncodingUtils.getReal(solution);
		x = rotation.operate(x);
		x = Vector.add(x, center);
		
		//calculate the bounds violation
		double boundsViolation = 0.0;
		
		for (int i=0; i<getNumberOfVariables(); i++) {
			RealVariable variable = (RealVariable)temp.getVariable(i);
			
			if (x[i] < variable.getLowerBound()) {
				boundsViolation += variable.getLowerBound() - x[i];
				variable.setValue(variable.getLowerBound());
			} else if (x[i] > variable.getUpperBound()) {
				boundsViolation += x[i] - variable.getUpperBound();
				variable.setValue(variable.getUpperBound());
			} else {
				variable.setValue(x[i]);
			}
		}
		
		//evaluate the solution
		problem.evaluate(temp);
		
		//extract the results
		solution.setObjectives(temp.getObjectives());
		
		for (int i = 0; i < problem.getNumberOfConstraints(); i++) {
			solution.setConstraint(i, temp.getConstraint(i));
		}
		
		//set the bounds violation constraint
		solution.setConstraint(problem.getNumberOfConstraints(), 
				boundsViolation);
	}

	@Override
	public Solution newSolution() {
		Solution result = new Solution(getNumberOfVariables(), 
				getNumberOfObjectives(), getNumberOfConstraints());
		
		for (int i = 0; i < getNumberOfVariables(); i++) {
			result.setVariable(i, new RealVariable(lowerBounds[i], 
					upperBounds[i]));
		}
		
		return result;
	}

	@Override
	public void close() {
		problem.close();
	}

}
