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

// functioncontrol.cpp -- FunctionControl class methods. All methods of the
// class are called from Evaluator, which is the only class with direct access
// to FunctionControl.

#include <cmath>
#include <cstring>
#include <fstream>
#include <iostream>
using namespace std;
#include "augmentedpoint.h"
#include "cachecontrol.h"
#include "defines.h"
#include "description.h"
#include "functioncontrol.h"
#include "generalconstraints.h"
#include "parameters.h"
#include "surrfunction.h"
#include "surrogatecontrol.h"
#include "truthfunction.h"

// Private methods.
// ----------------

// This method creates the string containing the system call to execute a black
// box.
char * FunctionControl::createBoxString(char * file, const char * box)
{
  const char * directory = description->getDirectory();
  const char * output_file = "output.txt";
  const char * str1 = "cd ";
  const char * str2 = "; ";
  const char * str3 = " > ";
  file = new char[9 + strlen(directory) + strlen(box) + strlen(output_file)];
  strcpy(file, str1);
  strcat(file, directory);
  strcat(file, str2);
  strcat(file, box);
  strcat(file, str3);
  strcat(file, output_file);
  return file;
}

// This method evaluates the objective function value for a given point 'ap'.
double FunctionControl::giveMeTruth(AugmentedPoint & ap, int & term_flag)
{
  double val = 0.0;
  if (truth != NULL)
    // The truth function is inside Nomad.
    val = truth->evaluate(ap.getPoint());
  else   // We call the truth function black box with a system call.
    {
      system(truth_box);
      ifstream output(output_file, ios::in);
      if ((output >> val) == false)
	val = INFINIT;
      output.close();
    }
  // The truth value is stored in 'ap'.
  ap.setOutput(0, val);

  // The "new_truth_evaluations" counter is incremented.
  ++new_truth_evaluations;
  // The termination criterion for maximum number of new truth evaluations is
  // retrieved from Parameters.
  int max_new_truth_evals = parameters->getNewTruthEvals();
  // If (term_flag != 0), another criterion is already activated.
  // If (max_new_truth_evals <= 0), the number of new truth evaluations is not
  // a termination criterion.
  if ((term_flag == 0) && (max_new_truth_evals > 0) &&
      // The number of new truth evaluations is compared to the criterion.
      (new_truth_evaluations >= max_new_truth_evals))
    {
      if (display_factor >= 5)
	cout << "\nMax number of new truth evaluations reached!\n";
      // The flag is activated.
      term_flag = 4;
    }
  return val;
}

// This method creates strings for the black boxes and files.
void FunctionControl::createBlackBoxesAndFileStrings()
{
  // The truth box string is created.
  truth_box = createBoxString(truth_box, description->getTruth());
  // If required, the surrogate box string is created.
  if (description->getSurrogateUse())
    surrogate_box = createBoxString(surrogate_box,
				    description->getSurrogate());

  // Strings are created fot the input and output files.
  // The input string is created.
  const char * input = description->getInput();
  input_file = new char[strlen(input) + 1];
  strcpy(input_file, input);
  // The output file string is created.
  const char * directory = description->getDirectory();
  const char * name = "output.txt";
  // The string is a concatenation of the problem directory and the file name.
  output_file = new char[strlen(directory) + strlen(name) + 1];
  strcpy(output_file, directory);
  strcat(output_file, name);
}

// The point coordinates are written in the input file.
void FunctionControl::openInputFile(const double * coord)
{
  // The file is opened.
  ofstream input(input_file);
  if (input.fail())
    {
      cerr << "\nError while opening " << input_file << endl;
      exit(0);
    }
  input.precision(64);
  int max_dimension = description->getDimension();
  // Each coordinate is written on a separate line.
  for (int i = 0; i < max_dimension; i++)
    input << coord[i] << "\n";
  // The file is closed.
  input.close();
}

// Public methods.
// ---------------

// Default constructor, called from Evaluator at Start-up.
FunctionControl::FunctionControl(const Description * const des,
				 const Parameters * const par)
  : description(des), parameters(par)
{
  // The strings are initialized to 0 (NULL).
  input_file = output_file = surrogate_box = truth_box = 0;
  // The variables are initialized to 0.
  new_truth_evaluations = truth_evaluations = 0;
  display_factor = 0;

  // The CacheControl object is created if required.
  if (description->getCacheUse())
    cache_control = new CacheControl(description->getDimension(),
				     description->getOutputDimension(),
				     description->getCache());
  else
    cache_control = NULL;

  // The black boxes are created if necessary.
  if (description->getBlackBoxUse())
    {
      createBlackBoxesAndFileStrings();
      surr_f = NULL;
      truth = NULL;
    }
  else   // Black boxes aren't used.
    {
      // These objects contain the user's functions.
      if (description->getSurrogateUse())
	surr_f = new SurrFunction();
      truth = new TruthFunction();
    }

  // If required, the GeneralConstraints object is created.
  if (description->getGeneralConstraintsNumber() > 0)
    gen_cons = new GeneralConstraints(des, par, input_file, output_file);
  else
    gen_cons = NULL;

  // This pointer is set to NULL.
  surr_control = NULL;
}

// Destructor.
FunctionControl::~FunctionControl()
{
  delete cache_control;
  delete gen_cons;
  delete surr_f;
  delete truth;
  // The strings are destroyed.
  if (input_file != 0)
    delete [] input_file;
  if (truth_box != 0)
    delete [] truth_box;
  if (surrogate_box != 0)
    delete [] surrogate_box;
  if (output_file != 0)
    delete [] output_file;
}

// This method evaluates the objective function value for a given point 'ap'.
bool FunctionControl::evaluateTruth(AugmentedPoint & ap, int & term_flag,
				    bool startingPoint)
{
  double val = 0.0;
  int cache_hit = NO_CACHE_HIT, i, max_dimension = description->getDimension();
  // We have to unscale the point's coordinates before we can call the
  // objective function. First we get ap's coordinates...
  double * copy = ap.getPoint();
  // The unscaled coordinates will be put into 'apoint'.
  double * apoint = new double[max_dimension];
  // The scaling vectors are obtained.
  double * scales_add = description->getScalesAdd();
  double * scales_mult = description->getScalesMult();
  // The scaled coordinates are unscaled by this formula:
  for (i = 0; i < max_dimension; i++)
    apoint[i] = (copy[i] - scales_add[i]) * scales_mult[i];

  // apoint's coordinates are written in the input file.
  if (truth == NULL)
    openInputFile(apoint);
  // We check if the point is already in the caches.
  if (cache_control != NULL)
    cache_hit = cache_control->find(ap, apoint);

  // If the point is in the temporary cache, the 'truth_evaluations' counter
  // is not incremented. In all other cases the counter is incremented
  // {point not in the caches, point in the permanent cache}.
  if (cache_hit != TEMP_CACHE_HIT)
    {
      // The "truth_evaluations" counter is incremented.
      ++truth_evaluations;
      // The termination criterion for maximum number of truth evaluations is
      // retrieved from Parameters.
      int max_truth_evals = parameters->getTruthEvals();
      // If (term_flag != 0), another criterion is already activated.
      // If (max_truth_evals <= 0), the number of truth evaluations is not a
      // termination criterion.
      if ((term_flag == 0) && (max_truth_evals > 0) &&
	  // The number of truth evaluations is compared to the criterion.
	  (truth_evaluations >= max_truth_evals))
	{
	  if (display_factor >= 5)
	    cout << "\nMax number of truth evaluations reached!\n";
	  // The flag is activated.
	  term_flag = 2;
	}
      // If there are general constraints, we evaluate the gi(x).
      if (gen_cons != NULL)
	gen_cons->evaluate(ap, startingPoint);
      // If the point wasn't in the caches, its objective function value is
      // evaluated.
      if (cache_hit == NO_CACHE_HIT)
	val = giveMeTruth(ap, term_flag);
      // The point is inserted in the temporary cache (if it wasn't found).
      if (cache_control != NULL)
	cache_control->insert(apoint, ap.getOutputValues());
    }

  // We give the augmented point to the surrogate manager.
  if (surr_control != NULL)
    surr_control->store(ap);
  // apoint's memory is freed.
  delete [] apoint;
  // The method returns 'false' in these cases:
  // 1- The point was in the temporary cache.
  // 2- The truth value is infinite.
  if ((cache_hit == TEMP_CACHE_HIT) || (val == -INFINIT) || (val == INFINIT))
    return false;
  else
    return true;
}

// Returns the number of hits in the permanent cache.
int FunctionControl::getPermCacheHits() const
{
  if (cache_control != NULL)
    return cache_control->getPermCacheHits();
  else
    return 0;
}

// Returns the number of hits in the temporary cache.
int FunctionControl::getTempCacheHits() const
{
  if (cache_control != NULL)
    return cache_control->getTempCacheHits();
  else
    return 0;
}

// The caches are closed at the end of the run.
void FunctionControl::closeCaches() const
{
  if (cache_control != NULL)
    cache_control->closeCaches();
}

// The user wants to continue the run.
void FunctionControl::continueRun()
{
  truth_evaluations = 0;
  new_truth_evaluations = 0;
  if (cache_control != NULL)
    cache_control->continueRun();
}

// This method calculates the surrogate function value for a given augmented
// point, 'ap'.
void FunctionControl::evaluateFlow(AugmentedPoint & ap)
{
  // We have to unscale the point's coordinates before we can call the
  // objective function. First we get ap's coordinates...
  double * copy = ap.getPoint();
  int max_dimension = description->getDimension();
  // The unscaled coordinates will be put into 'apoint'.
  double * apoint = new double[max_dimension];
  // The scaling vectors are obtained.
  double * scales_add = description->getScalesAdd();
  double * scales_mult = description->getScalesMult();
  // The scaled coordinates are multiplied by the scale.
  for (int i = 0; i < max_dimension; i++)
    apoint[i] = (copy[i] - scales_add[i]) * scales_mult[i];
  // We check if the point is already in the caches.
  if ((cache_control != NULL) && (cache_control->find(ap, apoint) > 0))
    {
      // The point is already in the caches.
      if (display_factor >= 7)
	cout << "Cache hit for evaluateFlow!\n";
    }
  else   // The point wasn't in the cache (maybe the cache is not used).
    {
      // The point's surrogate function value is evaluated.
      double val;
      if (surr_f != NULL)
	val = surr_f->evaluate(apoint);
      else
	{
	  openInputFile(apoint);
	  system(surrogate_box);
	  ifstream output(output_file);
	  if ((output >> val) == false)
	    val = INFINIT;
	  output.close();
	}
      ap.setLow(val);
    }
  // apoint's memory is freed.
  delete [] apoint;
}

// This method fills the array 'coord' with the unscaled incumbent.
void FunctionControl::fillWithBestPoint(double * coord, double * incumbent)
{
  int max_dimension = description->getDimension();
  // The scaling vectors are obtained.
  double * scales_add = description->getScalesAdd();
  double * scales_mult = description->getScalesMult();
  for (int i = 0; i < max_dimension; i++)
    coord[i] = (incumbent[i] - scales_add[i]) * scales_mult[i];
}

// This method creates a link to the surrogate manager.
void FunctionControl::linkWithSurrogate(SurrogateControl & surr)
{
  surr_control = & surr;
}

// The display factor is reset.
void FunctionControl::setDisplay(const int display)
{
  display_factor = display;
  // These objects' display factors are also reset.
  if (gen_cons != NULL)
    gen_cons->setDisplay(display_factor);
  if (cache_control != NULL)
    cache_control->setDisplay(display_factor);
}

// showInput -- Writes FunctionControl's variables in a file.
void FunctionControl::showInput(ofstream & fout) const
{
  if (!fout.is_open())
    return;
  if (gen_cons != NULL)
    gen_cons->showInput(fout);
}

// showOutput -- Writes FunctionControl's outputs in a file.
void FunctionControl::showOutput(ofstream & fout) const
{
  if (!fout.is_open())
    return;
  fout << "Truth evaluations: " << truth_evaluations << "\n";
  fout << "New truth evaluations: " << new_truth_evaluations << "\n";
  if (cache_control != NULL)
    cache_control->showOutput(fout);
}

// This method writes the history legend, at the top of the history file.
void FunctionControl::writeHistoryLegend(ofstream & history)
{
  // The item for the surrogate value is written, if needed.
  if (surr_control != NULL)
    history << "surr(x)" << "\t";

  // The items for the general constraints values are written, if needed.
  if (gen_cons != NULL)
    {
      int nb = description->getGeneralConstraintsNumber();
      for (int i = 1; i <= nb; i++)
	history << "g" << i << "(x)" << "\t";
      // The item for h(x) is written.
      history << "h(x)" << "\n\n";
    }
}
