/******************************************************************************
* NOMAD -  Nonlinear Optimization for Mixed vAriables and Derivatives.        *
* Copyright (C) 2001-2004  Gilles Couture - Ecole Polytechnique,              *
*                          Charles Audet - Ecole Polytechnique,               *
*		           John Dennis - Rice University.                     *
*                                                                             *
* Author: Gilles Couture                                                      *
* École Polytechnique de Montréal - GERAD                                     *
* C.P. 6079, Succ. Centre-ville, Montréal (Québec) H3C 3A7 Canada             *
* Tel:  (514) 340-6053 x6928                                                  *
* Email: Gilles.Couture@gerad.ca                                              *
* Fax: (514) 340-5665                                                         *
*                                                                             *
* This program is free software; you can redistribute it and/or               *
* modify it under the terms of the GNU General Public License                 *
* as published by the Free Software Foundation. You can find the              *
* GNU General Public License at:                                              *
*             http://www.gnu.org/copyleft/gpl.html                            *
*                                                                             *
* This program 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 General Public License for more details.                                *
*                                                                             *
* You should have received a copy of the GNU General Public License           *
* along with this program; if not, write to the Free Software                 *
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. *
*                                                                             *
* You can find information on the NOMAD software at:                          *
*             http://www.gerad.ca/NOMAD/                                      *
******************************************************************************/

// evaluator.cpp -- Evaluator class methods.

#include <cmath>
#include <fstream>
#include <iostream>
using namespace std;
#include "constraints.h"
#include "defines.h"
#include "description.h"
#include "evaluator.h"
#include "filter.h"
#include "functioncontrol.h"
#include "parameters.h"
#include "statistics.h"
#include "surrogatecontrol.h"

// Private methods:
// ****************************************************************************

// This method first asks the Constraints object if the trial point 'ap'
// satisfies the bound constraints. If it does, 'truth' is evaluated.
int Evaluator::evaluateTruth(AugmentedPoint * ap, int & term_flag,
			     bool startingPoint)
{
  // cout << "In 'Evaluator::evaluateTruth(...)'\n";
  // cout << "ap: " << *ap << endl;
  // If there are bound constraints, and the point is outside the bounds...
  if ((constraints != NULL) && (!constraints->check(ap->getPoint())))
    {
      // ... truth is set to a very large number.
      ap->setOutput(0, INFINIT);
      // Returning '-1' tells 'Solver' that the starting point is outside the
      // bounds.
      return -1;
    }
  // In all other cases (there are no bound constraints, or the point is
  // inside the bounds), we evaluate truth.
  return f_control->evaluateTruth(*ap, term_flag, startingPoint);
}

// This method updates the run statistics. It's called when one of the
// incumbents is improved.
void Evaluator::updateStatistics(const int sent_from, const double fx_decrease)
{
  // This switch calls the correct Statistics method, according to 'sent_from'.
  switch (sent_from)
    {
      // The poll generated a better iterate.
      case 0  : stats->addToPollDecrease(fx_decrease);
                break;
      // The initial search generated a better iterate.
      case 1  : stats->addToInitialDecrease(fx_decrease);
	        break;
      // The iterative search generated a better iterate.
      case 2  : stats->addToIterativeDecrease(fx_decrease);
          	break;
      // The speculative search generated a better iterate.
      case 3  : stats->addToDenseDecrease(fx_decrease);
		break;
      default : cerr << "Can't update statistics: Parameter incorrect.\n";
    }
}

// Public methods:
// ****************************************************************************

// Default constructor called by the Solver object at Start-up.
Evaluator::Evaluator(const Description * const des,
		     const Parameters * const par)
  : description(des), parameters(par)
{
  // The variables are initialized to default values.
  consecutive_fails = 0;
  display_factor = polldir_index = teval = 0;

  // The starting point is created.
  starting_point = new AugmentedPoint(description->getDimension(),
				      description->getOutputDimension(),
				      description->getOutputIndex(),
				      description->getStartingPoint(),
				      description->getScalesAdd(),
				      description->getScalesMult());

  // The constraints object is created, if necessary.
  if (description->getBoundsUse())
    constraints = new Constraints(des, par);
  else
    constraints = NULL;

  // The Filter object is created.
  filter = new Filter(des, par);

  // FunctionControl is created.
  f_control = new FunctionControl(des, par);

  // The 'Satistics' object is created.
  stats = new Statistics();
}

// Destructor.
Evaluator::~Evaluator()
{
  // The created objects are destroyed.
  delete starting_point;
  delete constraints;
  delete filter;
  delete f_control;
  delete stats;
}

// The message is relayed to the Filter object.
const AugmentedPoint * Evaluator::getFeasibleIncumbent()
{
  return filter->getFeasibleIncumbent();
}

// The message is relayed to the Filter object.
const AugmentedPoint * Evaluator::getInfeasibleIncumbent()
{
  return filter->getInfeasibleIncumbent();
}

// The message is relayed to the Filter object.
bool Evaluator::feasible() const
{
  return filter->feasible();
}

// The message is relayed to the filter object.
bool Evaluator::isPrimaryFeasible() const
{
	return filter->isPrimaryFeasible();
}

// The message is relayed to the Filter object.
double Evaluator::getTruth() const
{
  return filter->getTruth();
}

// This method returns a pointer to the scaled lower bounds.
double * Evaluator::getLowerBounds() const
{
  return constraints->getLowerBounds();
}

// This method returns a pointer to the scaled upper bounds.
double * Evaluator::getUpperBounds() const
{
  return constraints->getUpperBounds();
}

// This method evaluates the truth value of the starting point.
int Evaluator::evaluateStartingPoint(int & term_flag)
{
  // cout << "In 'Evaluator::evaluateStartingPoint(...)'\n";
  // If the starting point is null, it means that the user wants to continue a
  // run; the starting point is already evaluated, there's nothing to do but
  // return.
  if (starting_point == NULL)
    {
      if (display_factor >= 5)
	cout << "Starting point is null!\n";
      return -1;
    }
  else
    // The starting point exists, its truth value is calculated.
    {
      // cout << "starting point: " << *starting_point << endl;
      // If there's a surrogate and 'ap' is the starting point...
      if (description->getSurrogateUse())
	// ... the starting point's surrogate value is evaluated.
	f_control->evaluateFlow(*starting_point);
      // In all other cases (there are no bound constraints, or the point is
      // inside the bounds), we evaluate truth.
      return evaluateTruth(starting_point, term_flag, true);
    }
}

// Returns FunctionControl's number of truth evaluations.
int Evaluator::getNewTruthEvaluations() const
{
  return f_control->getNewTruthEvaluations();
}

// Returns the number of hits in the permanent cache.
int Evaluator::getPermCacheHits() const
{
  return f_control->getPermCacheHits();
}

// Returns the number of hits in the temporary cache.
int Evaluator::getTempCacheHits() const
{
  return f_control->getTempCacheHits();
}

// Returns FunctionControl's number of truth evaluations.
int Evaluator::getTruthEvaluations() const
{
  return f_control->getTruthEvaluations();
}

// This method evaluates truth for a list of points received as parameters. It
// returns true if a new best iterate was found, false otherwise.
int Evaluator::processThisList(std::vector<AugmentedPoint *> & list_of_points,
															 bool entire_list, int & term_flag,
															 ofstream & fout, ofstream & fgraph,
															 ofstream & history, const int sent_from)
{
  // Local variables we'll need.
  const AugmentedPoint * incumbent = filter->getFeasibleIncumbent();
  double surrogate_tolerance = parameters->getSurrogateTolerance();
  double * scales_add = description->getScalesAdd();
	double * scales_mult = description->getScalesMult();
  int count = 0, iteration_success = 0, output_ok = 1, trial_success = 0;
  std::vector<AugmentedPoint *>::iterator trial;

  // Main loop.
  for (trial = list_of_points.begin(); trial != list_of_points.end(); trial++)
    {
      // If a termination criterion is activated the loop exits.
      if (term_flag > 0)
				break;
		
      // If the trial point's surrogate function value is greater than the feasible
			// incumbent's surrogate function value by a certain factor, we stop evaluating
			// Truth for this list of points. (This is safe because the points are ordered
			// by surrogate function value).
			if ((sent_from > 0) && ((*trial)->getPosition(1) != -1) && (incumbent != NULL) &&
					(abs((*trial)->getLow()) > abs(surrogate_tolerance * incumbent->getLow())))
				{
					if (display_factor >= 5)
						{
							cout << "|Trial's s(x) value| is greater than\n";
							cout << "|surrogate_tolerance * incumbent's s(x) value|\n";
							cout << "We stop evaluating the list of points.\n";
							cout << abs((*trial)->getLow()) << " > ";
							cout << abs(surrogate_tolerance * incumbent->getLow()) << endl;
						}
					break;
				}
      else   // The trial point's truth value is evaluated.
				output_ok = evaluateTruth(*trial, term_flag);
			
      if (display_factor > 3)
				cout << "\t" << count + 1 << ": " << **trial;

      // 'output_ok' is equal to '1' if the point was within the bounds, and
      // not in the temporary cache.
      if (output_ok == 1)
				{
					// Every trial point is unsuccessful at first.
					trial_success = 0;
					// The evaluated point is now written in the history file.
					(*trial)->showHistory(history, scales_add, scales_mult);
					// The trial point is sent to populate the filter.
					trial_success = filter->populate(**trial);
					// The iteration success variable is updated.
					if (trial_success > iteration_success)
						iteration_success = trial_success;
					
					// If a new incumbent was found, these tasks are done:
					if (trial_success >= 2)
						{
							// In this case a new best feasible incumbent was found.
							if (trial_success == 3)
								{
									if ((display_factor >= 5) && (entire_list))
										cout << "New best feasible incumbent found!\n";
									// The new best iterate is written in the output files.
									teval = f_control->getTruthEvaluations();
									fout << "   " << teval << "    ";
									filter->showResults(fout, scales_add, scales_mult);
									fgraph << teval << "  " << (* trial)->getFx() << "\n";
								}

							// The counter value is put into 'polldir_index'. It's used by
							// PollControl when the poll directions are ordered by success.
							polldir_index = count;
							// The consecutive failures counter is put to zero.
							consecutive_fails = 0;
							// We tell the 'Constraints' object to update the proximity vector.
							if (constraints != NULL)
								constraints->updateProximityArray((*trial)->getPoint());
							// The run statistics are updated.
							updateStatistics(sent_from, filter->getFxDecrease());
							// If 'entire_list' is false, it means we stop evaluating points
							// as soon as a better one is found.
							if (entire_list == false)
								break;
						}
					else   // Failure! We don't have a new incumbent.
						{
							// The consecutive failures counter is incremented.
							++consecutive_fails;
							// The termination criterion for maximum number of consecutive
							// failures is retrieved from Parameters.
							int max_consecutive_fails = parameters->getConsecutiveFails();
							
							// If (term_flag != 0), another criterion is already activated.
							// If (max_consecutive_fails <= 0), the number of consecutive
							// failures is not a termination criterion.
							if ((term_flag == 0) && (max_consecutive_fails > 0) &&
							// The number of consecutive failures is compared to the
							// termination criterion.
									(consecutive_fails >= max_consecutive_fails))
								{
									if (display_factor >= 5)
										cout << "\nMax number of consecutive failures reached!\n";
									// The flag is activated.
									term_flag = 3;
								}
						}
				}
      // The counter of loop passes is incremented.
      count++;
    }

  if (display_factor >= 5)
    {
      (sent_from > 0) ? cout << "\nSearch " : cout << "\nPoll ";
      switch (iteration_success)
				{
          case 0  : cout << "failure.\n";
										break;
          case 1  : cout << "partly successful:";
										cout << " New point(s) added to the filter.\n";
										break;
          case 2  : cout << "success:";
										cout << " A new infeasible incumbent was found.\n";
										break;
          case 3  : cout << "success:";
										cout << " A new feasible incumbent was found.\n";
										break;
          default : cerr << "indeterminate: Return value incorrect!\n";
				}
    }
  return iteration_success;
}

// This method returns Constraints' proximity array.
int * Evaluator::getProximityArray() const
{
  return constraints->getProximityArray();
}

// The user wants to continue the previous run from where it ended.
void Evaluator::continueRun()
{
  // The variables are initialized to default values.
  consecutive_fails = 0;
  polldir_index = teval = 0;
  // The Constraints and FunctionControl objects are alerted.
  if (constraints != NULL)
    constraints->continueRun();
  f_control->continueRun();
  // The run statistics are reset.
  stats->reset();
}

// This method takes care of some loose ends at the emd of a run.
void Evaluator::endProgram()
{
  // The filter list is written in the filter graph file.
  if (description->getGeneralConstraintsNumber() > 0)
    filter->writeInGraphFile();
  // The caches must be closed at the end of a run.
  f_control->closeCaches();
}

// This method receives a list of points and sends them to 'FunctionControl'
// so that their Flow values are calculated.
void Evaluator::evaluateFlow(std::vector<AugmentedPoint *> & list_of_points)
{
  std::vector<AugmentedPoint *>::iterator temp;
  // Each point is checked by the Constraints object to see if it satisfies
  // the bound constraints. If it does, Flow is evaluated.
  for (temp = list_of_points.begin(); temp != list_of_points.end(); temp++)
    {
      // If there are bound constraints...
      if ((constraints != NULL) &&
	  // ... we check if the point satisfies them.
	  (constraints->check((*temp)->getPoint()) == false))
	// If it doesn't, Flow is set to a very large number.
	(* temp)->setLow(INFINIT);
      else   // There are no bound constraints, or the point is inside.
	// The point's Flow value is calculated.
	f_control->evaluateFlow(** temp);
    }
}

// This method passes the received parameter to FunctionControl, along with
// the feasible incumbent point's coordinates.
void Evaluator::fillWithBestPoint(double * coord)
{
  f_control->fillWithBestPoint(coord,
			       (filter->getFeasibleIncumbent())->getPoint());
}

// This method checks if the starting point is feasible or not.
void Evaluator::isStartingPointFeasible()
{
  // We have an infeasible starting point in this case:
  // - If there are general constraints in the current problem (the starting
  //   point might be infeasible).
  // - If Hx is greater than zero, the starting point is infeasible.
  if ((description->getGeneralConstraintsNumber() > 0) &&
      (starting_point->getHx() > 0.0))
    {
      // The starting point copy will populate the filter.
      filter->populate(*starting_point);
      // The filter is initialized.
      filter->updateInfeasibleIncumbent();
    }
  else   // The starting point is feasible.
    // The Filter object creates the feasible incumbent (by copying the
    // starting point.
    filter->createFeasibleIncumbent(*starting_point);
}

// The 'linkWithSurrogate(...)' call is relayed to FunctionControl.
void Evaluator::linkWithSurrogate(SurrogateControl & surr) const
{
  f_control->linkWithSurrogate(surr);
}

// This method sets the display factor.
void Evaluator::setDisplay(const int display)
{
  display_factor = display;
  // The display factor is reset in these objects.
  if (constraints != NULL)
    constraints->setDisplay(display_factor);
  filter->setDisplay(display_factor);
  f_control->setDisplay(display_factor);
}

// The message is relayed to the Filter object.
void Evaluator::showFeasibleIncumbent() const
{
  filter->showFeasibleIncumbent();
}

// The message is relayed to the Filter object.
void Evaluator::showFeasibleIncumbent(ofstream & fout) const
{
  filter->showFeasibleIncumbent(fout);
}

// The message is relayed to the Filter object.
void Evaluator::showFilter() const
{
  filter->showList();
}

// Outputs Evaluator's variables.
void Evaluator::showInput(ofstream & fout) const
{
  if (!fout.is_open())
    return;

  bool feasible_start_pt = feasible();
  // The starting point is shown, with its objective function value.
  fout << "Starting point is ";
  feasible_start_pt == false ? fout << "infeasible.\n" : fout << "feasible.\n";
  // If 'starting_point' exists it's shown.
  if (starting_point != NULL)
    fout << *starting_point;
  else   // If it doesn't exist, it means that the user chose to continue a
         // run. The starting point is the best feasible point.
    if (feasible_start_pt)
      fout << *filter->getFeasibleIncumbent();
    else   // If there's no best feasible point, the least infeasible is the
           // starting point.
      fout << *filter->getInfeasibleIncumbent();

  // Now we show the problem's constraints.
  if (constraints != NULL)
    // The constraints variables are shown.
    fout << (* constraints);
  // FunctionControl's variables are shown.
  f_control->showInput(fout);
}

// Once the problem is solved, we output the solution, and counters.
void Evaluator::showOutput(ofstream & fout) const
{
  if (!fout.is_open())
    return;
  // FunctionControl's counters are shown.
  f_control->showOutput(fout);
  fout << "Number of consecutive failures: ";
  // The consecutive failures counter is shown.
  fout << consecutive_fails << "\n\n";
  // The run statistics are written in the file.
  fout << (* stats) << "\n";
}

//
void Evaluator::updateInfeasibleIncumbent()
{
  filter->updateInfeasibleIncumbent();
}

// This method is called only once, from 'Solver::evaluateStartingPoint()'.
void Evaluator::updateProximityArray() const
{
  // This method tells Constraints to update the proximity array according to
  // the starting point.
  if (constraints != NULL)
    constraints->updateProximityArray(starting_point->getPoint());
}

// This method writes the legend and the starting point in the history file.
void Evaluator::writeHistoryLegend(ofstream & history)
{
  // Pointers to these arrays are needed to unscale the starting point (the
  // user must see all the points visited by NOMAD in coordinates he can
  // understand, so we have to unscale them).
  double * scales_add = description->getScalesAdd();
  double * scales_mult = description->getScalesMult();

  // ***** Right now we don't show a legend in the history file. *****
  // *****************************************************************
  // FunctionControl will finish writing the history file legend.
  // f_control->writeHistoryLegend(history);

  // The 'starting point' exists it's written in the history file.
  if (starting_point != NULL)
    {
      starting_point->showHistory(history, scales_add, scales_mult);
      // The starting point was copied in either the filter list (if it was
      // infeasible), or the AugmentedPoint 'incumbent' (if it was feasible).
      // It has also been written in the output and history files. It can be
      // safely destroyed, having served its purposes.
      delete starting_point;
      starting_point = NULL;
    }
  else   // If it doesn't exist, it means that the user chose to continue a
         // run. The starting point is the best feasible point.
    if (feasible())
      (filter->getFeasibleIncumbent())->showHistory(history, scales_add,
						    scales_mult);
    else   // If there's no best feasible point, the least infeasible is the
           // starting point.
      (filter->getInfeasibleIncumbent())->showHistory(history, scales_add,
						      scales_mult);
}
