/******************************************************************************
* 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/                                      *
******************************************************************************/

// solver.cpp -- Solver class methods.

#include <fstream>
#include <iostream>
using namespace std;
#include "defines.h"
#include "description.h"
#include "evaluator.h"
#include "mesh.h"
#include "parameters.h"
#include "pollcontrol.h"
#include "searchcontrol.h"
#include "solver.h"
#include "surrogatecontrol.h"

// Private methods.
// ****************************************************************************

// This method updates the variables after every iteration.
bool Solver::update(const int search_success, const int poll_success)
{
  // If the search succeeded...
  if (search_success >= 2)
    // We tell PollControl to check if the new incumbent is near the bounds. If
    // it is, the direction set has to be updated.
    pollcontrol->updateDirectionSet();
  // ... or if the poll succeeded and there are speculative searches to do...
  else if ((poll_success >= 2) && (parameters->getSpeculativeSearch() == true))
    {
		if (display_factor >= 7)
			{
			cout << "\nIn 'Solver::update(...)', before SearchControl::update(...)'\n";
			cout << "************************************************************\n";
			cout << "Poll success = " << poll_success << endl;
			}
		
		// This pointer will point to the incumbent found in the current
		// iteration.
		const AugmentedPoint * new_incumbent = NULL;
		// The new incumbent is infeasible.
		if (poll_success == 2)
			new_incumbent = evaluator->getInfeasibleIncumbent();
		// The new incumbent is feasible.
		else
			new_incumbent = evaluator->getFeasibleIncumbent();
				
		int polldir_index = evaluator->getPollDirIndex();
		if (display_factor >= 7)
			cout << "Polldir index = " << polldir_index << endl;
		
		// The speculative search needs to be updated.
		searchcontrol->update(mesh->getMeshSize(),
													pollcontrol->getSuccessfulDirection(polldir_index),
													new_incumbent);
    }
	
  // The infeasible incumbent is updated.
  evaluator->updateInfeasibleIncumbent();
	
  // The iteration was a success if the search or the poll was a success.
  int iteration_success =
    (search_success >= poll_success) ? search_success : poll_success;
	
  if (display_factor >= 5)
    cout << "\nIteration success = " << iteration_success << "\n";
	
  // If the iteration was successful...
  if (iteration_success >= 2)
    {
		// A new incumbent was found. The mesh is coarsened.
		mesh->coarsen();
		// The surrogate information is deleted when there's a new incumbent.
		if (surr_control != NULL)
			surr_control->cleanSurrogate();
		// The iteration was successful.
		return true;
    }
  else
    {
		// If both the search and the poll failed ('0' is returned in this case)
		// we have an iteration failure.
		if ((iteration_success == 0) && (termination_flag == 0))
			{
			// The mesh is refined. If the return value is true, it means that
			// the mesh size reached the mesh size termination criterion.
			if (mesh->refine() == true)
				termination_flag = 1;
			}
		// We tell SurrogateManager to update the surrogate.
		if (surr_control != NULL)
			surr_control->update(evaluator->getFeasibleIncumbent());
		// The iteration is unsuccessful, or partially successful.
		return false;
    }
}

// This method writes a lot of stuff in the output files just before the
// problem is solved.
void Solver::prepareOutputFiles(ofstream & fout, ofstream & fgraph,
																ofstream & history)
{
  // The number of function evaluations and the truth value of the starting
  // point are written in the output file.
  fout << "\n\nRun-time information...\n\n";
  fout << "feval #   f(x)   Point\n";
  fout << "-------   ----   -----\n";
  fout.precision(8);
	
  // This condition tests if the starting point is feasible.
  if (evaluator->feasible())
    {
		// The starting point is written in the output file.
		int teval = evaluator->getTruthEvaluations();
		fout << "   " << teval << "    ";
		evaluator->showFeasibleIncumbent(fout);
		// The starting point is written in the graph file.
		double inc_truth = evaluator->getTruth();
		fgraph << teval << "  " << inc_truth << "\n";
    }
	
  // ***** Right now we don't show a legend in the history file. *****
  // *****************************************************************
  // A legend is written in the history file. First the xi are written...
  // int max_dimension = description->getDimension();
  // for (int i = 1; i <= max_dimension; i++)
  //  history << "x" << i << "\t";
  // ... Then f(x) is written.
  // history << "f(x)" << "\t";
	
  // This method will write the rest of the legend and the starting point.
  evaluator->writeHistoryLegend(history);
}

// Public methods.
// ****************************************************************************

// Default constructor. The Solver object is created at Start-up.
Solver::Solver(const Description * const des, const Parameters * const par)
: description(des), parameters(par)
{
  // Variables are initialized.
  iteration_count = termination_flag = 0;
  display_factor = 0;
  // The seed value is stored.
  srand(parameters->getSeed());
	
  // The Mesh, Evaluator, PollControl and SearchControl objects are created
  // with default values.
  evaluator = new Evaluator(des, par);
  mesh = new Mesh(des, par);
  pollcontrol = new PollControl(des, *evaluator, par);
  searchcontrol = new SearchControl(des, *evaluator, par);
	
  // If the problem uses a surrogate...
  if (description->getSurrogateUse())
    {
		// ...the SurrogateControl object is created: it'll store the points
		// generated by the search and poll, and pass them to the surrogate for
		// recalibration.
		surr_control = new SurrogateControl(description->getDimension(),
																				description->getOutputDimension());
		// PollControl and SearchControl are linked to SurrogateManager.
		pollcontrol->linkWithSurrogate(*surr_control);
		searchcontrol->linkWithSurrogate(*surr_control);
    }
  else
    surr_control = NULL;
}

// Destructor. The created objects are destroyed.
Solver::~Solver()
{
  delete evaluator;
  delete mesh;
  delete pollcontrol;
  delete searchcontrol;
  delete surr_control;
}

// This method returns the current mesh size.
double Solver::getMeshSize() const
{
  return mesh->getMeshSize();
}

// This method returns the incumbent point's truth value.
double Solver::getTruth() const
{
  return evaluator->getTruth();
}

// This method returns the # of new truth evaluations.
int Solver::returnNewTruthEvaluations() const
{
  return evaluator->getNewTruthEvaluations();
}

// This method returns the # of cache evaluations.
int Solver::returnPermCacheHits() const
{
  return evaluator->getPermCacheHits();
}

// This method returns the # of cache evaluations.
int Solver::returnTempCacheHits() const
{
  return evaluator->getTempCacheHits();
}

// This method returns the # of truth evaluations.
int Solver::returnTruthEvaluations() const
{
  return evaluator->getTruthEvaluations();
}

// The user wants to continue the previous run.
void Solver::continueRun()
{
  // The termination flag is reset.
  termination_flag = 0;
  // We alert pointed objects, so they can reinitialize themselves.
  evaluator->continueRun();
  pollcontrol->continueRun();
  searchcontrol->continueRun();
  // The surrogate's recalibration list is deleted.
  if (surr_control != NULL)
    surr_control->cleanList();
}

// The 'endProgram' message is relayed to Evaluator.
void Solver::endProgram()
{
  evaluator->endProgram();
}

// This method evaluates the truth value of the starting point.
void Solver::evaluateStartingPoint()
{
  // The starting point's truth value is evaluated; this condition checks if
  // the starting point exists. If it doesn't exist '-1' is returned.
  if (evaluator->evaluateStartingPoint(termination_flag) != -1)
    {
		// We check if the starting point is feasible or not (if we have general
		// constraints, the starting point could be infeasible).
		evaluator->isStartingPointFeasible();
		// ... We tell Evaluator to update the proximity array.
		evaluator->updateProximityArray();
		// Pollcontrol will possibly update the direction set, according to
		// the updated proximity vector.
		pollcontrol->updateDirectionSet();
    }
}

// This method sends the starting point to Evaluator.
void Solver::fillWithBestPoint(double * coord) const
{
  evaluator->fillWithBestPoint(coord);
}

// The display factor is set.
void Solver::setDisplay(const int display)
{
  display_factor = display;
  // The display factor must be changed in these objects.
  evaluator->setDisplay(display_factor);
  mesh->setDisplay(display_factor);
  pollcontrol->setDisplay(display_factor);
  searchcontrol->setDisplay(display_factor);
  if (surr_control != NULL)
    surr_control->setDisplay(display_factor);
}

// This method outputs the problem's data in an output file, 'fout'.
void Solver::showInput(ofstream & fout) const
{
  if (!fout.is_open())
    return;
	
  fout << "******************\n";
  fout << "*START OF THE RUN*\n";
  fout << "******************\n\n";
	
  fout << "Run-time variables:\n";
  fout << "-------------------\n";
	
  // We output the pointed objects' data.
  if (evaluator != NULL)
    evaluator->showInput(fout);
  if (mesh != NULL)
    mesh->showInput(fout);
}

// The problem solution is written in an output file, 'fout'.
void Solver::showOutput(ofstream & fout) const
{
  if (!fout.is_open())
    return;
  fout << "\nProblem solution:\n";
  fout << "-----------------\n";
  // The final incumbent is written in the file.
  fout << "Best point found: ";
  evaluator->showFeasibleIncumbent(fout);
  fout << "Termination factor: ";
  // We output the activated termination criterion.
  switch(termination_flag)
    {
		case 1  : fout << "Mesh size.\n";
			break;
		case 2  : fout << "Function evaluations.\n";
			break;
		case 3  : fout << "Consecutive failures.\n";
			break;
		case 4  : fout << "Truth evaluations.\n";
			break;
		case 5  : fout << "Iterations.\n";
			break;
		default : cerr << "No termination criterion activated!\n";
    }
  // The final mesh size is written.
  fout << "Final mesh size: " << mesh->getMeshSize() << "\n";
  fout << "Number of iterations: " << iteration_count << "\n";
  // The function, truth and cache counters are written.
  evaluator->showOutput(fout);
}

// Data entry is completed and the starting point is ok: it's time to solve
// the problem. All problem outputs are written in a file, 'fout'.
void Solver::solveProblem(ofstream & fout)
{
  if (display_factor > 0)
		{
			cout << "\n\n**************************\n* START OF THE NOMAD RUN *";
			cout << "\n**************************\n\n";
    }
	
  // We create a new output file, 'fgraph', where we'll write the data needed
  // for the GUI to draw a graph.
  ofstream fgraph("LIBRARY/graph.txt");
  // The history file will contain all the points visited by the algorithm.
  ofstream history("LIBRARY/history.txt");
  // This method will prepare the output files.
  prepareOutputFiles(fout, fgraph, history);
	
	// This object will hold the poll centers.
	const AugmentedPoint *ap_primary = NULL, *ap_secondary = NULL;
  // This variable will determine the success of an iteration.
  bool iteration_success = false;
  // This variable will hold the current mesh size.
  double mesh_size;
  // These arrays will be the poll centers.
  double *primary_poll_center = NULL, *secondary_poll_center = NULL;
  // This variable holds the maximum number of iterations (termination
  // criterion).
  int max_iterations = parameters->getMaxIterations();
  // These two variables will help determine the success of an iteration.
  int poll_success = 0, search_success = 0;
  
  // Main loop. Every pass through the loop is an iteration of the algorithm.
  // Iterations are performed until a termination criterion is activated.
  while (termination_flag == 0)
    {
			// The surrogate manager's list of points is deleted at every iteration.
			if (surr_control != NULL)
				surr_control->cleanList();
			// We ask Mesh for the current mesh size;
			mesh_size = mesh->getMeshSize();
			
			if (display_factor > 3)
				{
					cout << "___________________________________________________________";
  				cout << "___________________________________________________________\n";
				}
					if (display_factor > 0)
					{
					cout << "ITERATION " << iteration_count << " STARTS (";
					cout << evaluator->getTruthEvaluations() << " evaluations, delta = ";
					cout << mesh_size << ")\n";
				}
			
			// Is the primary poll center the feasible incumbent?
			if (evaluator->isPrimaryFeasible() == true)
				{
					ap_primary = evaluator->getFeasibleIncumbent();
					ap_secondary = evaluator->getInfeasibleIncumbent();
				}
			else   // The primary poll center is the infeasible incumbent.
				{
					ap_primary = evaluator->getInfeasibleIncumbent();
					ap_secondary = evaluator->getFeasibleIncumbent();
				}
			
			// The coordinates arrays are extracted from the AugmentedPoint objects.
			primary_poll_center = ap_primary->getPoint();
			// This is necessary because there might not be a secondary poll center.
			if (ap_secondary != NULL)
				secondary_poll_center = ap_secondary->getPoint();
			else
				secondary_poll_center = NULL;
			
			if (display_factor >= 3)
				{
					cout << "    Primary poll center:   " << *ap_primary;
					if (secondary_poll_center != NULL)
						cout << "    Secondary poll center: " << *ap_secondary;
				}
			
			// First we do a search.
			search_success = searchcontrol->search(mesh_size, primary_poll_center,
																						 poll_success, termination_flag,
																						 fout, fgraph, history);
			
			// If the search failed (it returns '0' or '1'  when it does) AND
			// a termination criterion wasn't reached during the search...
			if ((search_success < 2) && (termination_flag == 0))
				{
					if (display_factor > 3)
						cout << "    Poll points:\n";
					// ... we poll.
					poll_success = pollcontrol->poll(mesh_size, primary_poll_center,
																					 secondary_poll_center,
																					 termination_flag, fout, fgraph,
																					 history);
				}
			else
				// The variable must be reset.
				poll_success = 0;
		
			// Update at the end of an iteration.
		
			// SEND OLD POLL CENTERS!!!
			iteration_success = update(search_success, poll_success);
			
			if (display_factor > 3)
				{
				cout << "ITERATION " << iteration_count << " ENDS";
				iteration_success == true ? cout << "  (SUCCESS)\n" :
					cout << "  (FAILURE)\n";
				}
			
			// The iteration counter is incremented.
			++iteration_count;
			// If (termination_flag != 0), another criterion is already activated.
			// If (max_iterations <= 0), the number of iterations is not a criterion.
			if ((termination_flag == 0) && (max_iterations > 0) &&
				// The iteration counter is compared to the termination criterion for
				// maximum number of iterations.
					(iteration_count >= max_iterations))
				// The max number of iterations is reached: the flag is activated.
				termination_flag = 5;
    }
	
  // We're out of the loop: first we get the last value of the truth
  // evaluations counter.
  int teval = evaluator->getTruthEvaluations();
	
  // Then we get the value of the truth evaluations counter the last time
  // a new incumbent was found. If the 2 values are equal it means that the
  // last point evaluated was the new incumbent. Thus the final best point is
  // already written in the output file; in this case we do nothing.
  if (evaluator->getTeval() != teval)
    {
			// The last point evaluated is written in the output file.
			fout << "   " << teval << "    ";
			evaluator->showFeasibleIncumbent(fout);
			if (evaluator->feasible())
				fgraph << teval << "  " << evaluator->getTruth() << "\n";
    }
	
	if (display_factor > 0)
		{
		cout << "\n\n************************\n* END OF THE NOMAD RUN *";
		cout << "\n************************\n\n";
    }
	
	
	// The graph files are closed.
  fgraph.close();
  history.close();
  // The problem solution is written in the output file.
  showOutput(fout);
}
