/*-------------------------------------------------------------------------------------*/
/*  NOMAD - Nonsmooth Optimization by Mesh Adaptive Direct search - version 3.0.0      */
/*                                                                                     */
/*  Copyright (C) 2001-2008  Mark Abramson        - the Boeing Company, Seattle        */
/*                           Charles Audet        - Ecole Polytechnique, Montreal      */
/*                           Gilles Couture       - Ecole Polytechnique, Montreal      */
/*                           John Dennis          - Rice University, Houston           */
/*                           Sebastien Le Digabel - Ecole Polytechnique, Montreal      */
/*                                                                                     */
/*  funded in part by AFOSR and Exxon Mobil                                            */
/*                                                                                     */
/*  Author: Sebastien Le Digabel                                                       */
/*                                                                                     */
/*  Contact information:                                                               */
/*    Ecole Polytechnique de Montreal - GERAD                                          */
/*    C.P. 6079, Succ. Centre-ville, Montreal (Quebec) H3C 3A7 Canada                  */
/*    e-mail: Sebastien.Le.Digabel@gerad.ca                                            */
/*    phone : 1-514-340-6053 #6673                                                     */
/*    fax   : 1-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, either version 3 of the License, or (at your option) any later         */
/*  version.                                                                           */
/*                                                                                     */
/*  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, see <http://www.gnu.org/licenses/>.                               */
/*                                                                                     */
/*  You can find information on the NOMAD software at www.gerad.ca/nomad               */
/*-------------------------------------------------------------------------------------*/
#include "Evaluator.hpp"
using namespace std;

/*------------------------------------------------------------------*/
/*                            constructor                           */
/*------------------------------------------------------------------*/
/*  Parameters::_bb_exe is used to construct _bb_exe and _bb_nbo:   */
/*    Parameters::_bb_exe can have similar black-box names, while   */
/*    . _bb_exe will have unique black-box names                    */
/*    . _bb_exe includes the black-box path                         */
/*------------------------------------------------------------------*/
Evaluator::Evaluator ( const Parameters & p , ostream & out ) : _p(p) , _out(out) {

  if ( _p.get_bb_exe().empty() )
    return;

  // _bbe_exe and _bb_nbo construction:
  list<string>::const_iterator it = _p.get_bb_exe().begin();
  _bb_exe.push_back(*it);
  _bb_nbo.push_back(1);
  ++it;
  
  list<string>::const_iterator end = _p.get_bb_exe().end();
  while ( it != end ) {
    if ( *it != _bb_exe[_bb_exe.size()-1] ) {
      _bb_exe.push_back(*it);
      _bb_nbo.push_back(1);
    }
    else
      ++_bb_nbo[_bb_exe.size()-1];
    ++it;
  }

  // we check that _bb_exe contains unique names and we add the problem path:
  size_t k , l , n = _bb_exe.size() , nm1 = n-1;
  for ( k = 0 ; k < nm1 ; ++k ) {
    for ( l = k+1 ; l < n ; ++l )
      if ( _bb_exe[k] == _bb_exe[l] )
	throw NOMAD::Exception ( __FILE__ , __LINE__ , "problem with executable names" );
  }

  // construction of _sgte_exe:
  bool   sgte = _p.has_surrogates();
  string err;
  if ( sgte ) {
    for ( k = 0 ; k < n ; ++k ) {

      _sgte_exe.push_back ( _p.get_sgte_exe(_bb_exe[k]) );

      if ( _sgte_exe[_sgte_exe.size()-1].empty() ) {
	err = "black-box executable \"" + _bb_exe[k] + "\" has no surrogate";
	throw NOMAD::Exception ( __FILE__ , __LINE__ , err );
      }
    }
  }

  // process blakc-box executables (check and add problem path):
  for ( k = 0 ; k < n ; ++k ) {
    process_bb_exe_name ( _bb_exe[k] );
    if (sgte)
      process_bb_exe_name ( _sgte_exe[k] );
  }

  // black-box names and indexes display:
#ifdef DEBUG
  if ( !_bb_exe.empty() ) {
    _out << "black-box executables:\n";
    for ( k = 0 ; k < n ; ++k )
      _out << "\tbb #" << static_cast<int>(k) << ": \"" << _bb_exe[k] << "\" #outputs="
	   << _bb_nbo[k] << endl;
  }
  if ( !_sgte_exe.empty() ) {
    _out << "surrogate executables:\n";
    for ( k = 0 ; k < n ; ++k )
      _out << "\tsgte #" << static_cast<int>(k) << ": \"" << _sgte_exe[k] << endl;
  }
#endif
}

/*-----------------------------------------------------------------*/
/*               process a black-box executable name               */
/*-----------------------------------------------------------------*/
void Evaluator::process_bb_exe_name ( string & bb_exe ) const {

  string       err;
  list<string> bb_exe_words;

  NOMAD::get_words ( bb_exe , bb_exe_words );

  if ( bb_exe_words.empty() ) {
    err = "problem with executable \"" + bb_exe + "\"";
    throw NOMAD::Exception ( __FILE__ , __LINE__ , err );
  }

  string problem_dir = _p.get_problem_dir();

  // bb_exe is composed of several words (it is a command):
  if ( bb_exe_words.size() > 1 ) {

    bb_exe.clear();
    
    list<string>::const_iterator it = bb_exe_words.begin() , end = bb_exe_words.end();
    while (true) {

      if ( (*it)[0] != '$' ) {
 	bb_exe += problem_dir;
	bb_exe += *it;
      }
      else
 	bb_exe += it->substr ( 1 , it->size()-1 );

      ++it;

      if ( it == end )
	break;

      bb_exe += " ";
    }
  }

  // bb_exe is just composed of one name (it is an executable):
  else {
    if ( bb_exe[0] != '$' )
      bb_exe = problem_dir + bb_exe;
    else
      bb_exe = bb_exe.substr ( 1 , bb_exe.size()-1 );
    if ( !NOMAD::check_exe_file ( bb_exe ) ) {
      err = "\"" + bb_exe + "\" is not a valid executable file";
      throw NOMAD::Exception ( __FILE__ , __LINE__ , err );
    }
  }
}

/*-----------------------------------------------------------------*/
/*  check the constraints to decide if eval. have to be stopped    */
/*        (if h > h_max or if a 'EB' constraint is violated)       */
/*-----------------------------------------------------------------*/
bool Evaluator::interrupt_evaluations ( const Eval_Point    & x     ,
					const NOMAD::Double & h_max   ) const {

  int                            nbo     = _p.get_bb_nb_outputs();
  const vector<bb_output_type> & bbot    = _p.get_bb_output_types();
  const Point                  & bbo     = x.get_bb_outputs();
  NOMAD::Double                  h       = 0.0;
  bool                           check_h = h_max.is_defined();

  for ( int i = 0 ; i < nbo ; ++i ) {

    if ( bbo[i].is_defined()                     &&
	 (bbot[i] == _EB_ || bbot[i] == _PEB_E_) &&
	 bbo[i] > _p.get_h_min() )
      return true;
    
    if ( check_h && bbo[i].is_defined() &&
	 (bbot[i]==_F_ || bbot[i]==_PB_ || bbot[i]==_PEB_P_ ) ) {
      if ( bbo[i] > _p.get_h_min() ) {
	switch ( _p.get_h_norm() ) {
	case _L1_:
	  h += bbo[i];
	  break;
	case _L2_:
	  h += bbo[i].pow2();
	  break;
	case _LINF_:
	  if ( bbo[i] > h )
	    h = bbo[i];
	  break;
	}

	if ( _p.get_h_norm() == _L2_ ) {
	  if ( h > h_max.pow2() )
	    return true;
	}
	else if ( h > h_max )
	  return true;
      }
    }
  }
  return false;
}

/*---------------------------------------------------------*/
/*    compute f(x) from the black-box outputs of a point   */
/*      (define a Multi_Obj_Evaluator to treat more than   */
/*       one objective)                                    */
/*---------------------------------------------------------*/
void Evaluator::compute_f ( Eval_Point & x ) const {

  if ( x.get_bb_outputs().size() != _p.get_bb_nb_outputs() ) {
    string err = "Evaluator::compute_f(x): x has a wrong number of black-box outputs ("
      + NOMAD::itos(x.get_bb_outputs().size()) + " != "
      + NOMAD::itos(_p.get_bb_nb_outputs()) + ")";
    throw NOMAD::Exception ( __FILE__ , __LINE__ , err );
  }
  
  x.set_f ( x.get_bb_outputs()[*(_p.get_index_obj().begin())] );
}

/*---------------------------------------------------------*/
/*    compute h(x) from the black-box outputs of a point   */
/*    set also the flag 'EB_ok' of the point               */
/*---------------------------------------------------------*/
void Evaluator::compute_h ( Eval_Point & x ) const {

  if ( x.get_bb_outputs().size() != _p.get_bb_nb_outputs() ) {
    string err = "Evaluator::compute_h(x): x has a wrong number of black-box outputs ("
      + NOMAD::itos(x.get_bb_outputs().size()) + " != "
      + NOMAD::itos(_p.get_bb_nb_outputs()) + ")";
    throw NOMAD::Exception ( __FILE__ , __LINE__ , err );
  }

  int                            nbo  = _p.get_bb_nb_outputs();
  const vector<bb_output_type> & bbot = _p.get_bb_output_types();
  const Point                  & bbo  = x.get_bb_outputs();
  NOMAD::Double                  h    = 0.0 , bboi;

  x.set_EB_ok ( true );

  for ( int i = 0 ; i < nbo ; ++i ) {

    bboi = bbo[i];

    if ( bboi.is_defined()                       &&
	 (bbot[i] == _EB_ || bbot[i] == _PEB_E_) &&
	 bboi > _p.get_h_min() ) {
      h.clear();
      x.set_h     ( h     );
      x.set_EB_ok ( false );
      return;
    }
    
    if ( bboi.is_defined() &&
	 (bbot[i]==_F_ || bbot[i]==_PB_ || bbot[i]==_PEB_P_ ) ) {
      if ( bboi > _p.get_h_min() ) {
	switch ( _p.get_h_norm() ) {
	case _L1_:
	  h += bboi;
	  break;
	case _L2_:
	  h += bboi * bboi;
	  break;
	case _LINF_:
	  if ( bboi > h )
	    h = bboi;
	  break;
	}
      }
    }
  }

  if ( _p.get_h_norm() == _L2_ )
    h = h.sqrt();

  x.set_h(h);
}

/*------------------------------------------------------------------*/
/*      . evaluate the black boxes at a given Eval_Point            */
/*      . the function returns true if the evaluation did not fail  */
/*      . set count_eval=true to count the evaluation               */
/*------------------------------------------------------------------*/
bool Evaluator::eval_x ( Eval_Point          & x          ,
			 const NOMAD::Double & h_max      ,
			 bool                & count_eval   ) const {

  count_eval = false;

  if ( _bb_exe.empty() || !x.is_complete() )
    throw NOMAD::Exception ( __FILE__ , __LINE__ ,
		   "Evaluator: no BB_EXE defined (black-box executable names)" );

  string tmp_dir = _p.get_tmp_dir();
  string sint    = "." + NOMAD::itos ( _p.get_seed() )
                 + "." + NOMAD::itos ( x.get_tag() ) + ".";

  // black-box input file writing:
  // -----------------------------
  string bb_input_file_name
    = tmp_dir + BLACK_BOX_INPUT_FILE_PREFIX  + sint + BLACK_BOX_INPUT_FILE_EXT;

  string bb_output_file_name
    = tmp_dir + BLACK_BOX_OUTPUT_FILE_PREFIX + sint + BLACK_BOX_OUTPUT_FILE_EXT;
 
  ofstream out ( bb_input_file_name.c_str() );
  if ( out.fail() ) {
    string err = "could not open file black-box input file " + bb_input_file_name;
    throw NOMAD::Exception ( __FILE__ , __LINE__ , err );
  }

  // include seed:
  if ( _p.get_bb_input_include_seed() )
    out << _p.get_seed() << " ";

  // include tag:
  if ( _p.get_bb_input_include_tag() )
    out << x.get_tag() << " ";

  out.setf(ios::fixed);
  out.precision(DISPLAY_PRECISION_BB);
  x.Point::display ( out , " " , -1 , -1 );
  out << endl;

  out.close();

  if ( out.fail() )
    return false;

  x.set_eval_ok (true);

  string   cmd , bb_exe;
  ifstream in;
  bool     failed;

  // system call to evaluate the black-box:
  // --------------------------------------
  size_t bn = _bb_exe.size();
  for ( size_t k = 0 ; k < bn ; ++k ) {

    // executable name:
    bb_exe = ( x.is_surrogate() ) ? _sgte_exe[k] : _bb_exe[k];

    // system command:
    cmd = bb_exe + " " + bb_input_file_name;

    // redirection ? if no, the black-box has to create
    // the output file 'bb_output_file_name':
    if ( _p.get_bb_redirection() )
      cmd += " > " + bb_output_file_name;

    if ( _p.get_display_degree() > 3 )
      _out << "\tEvaluator::eval_x() system command: \"" << cmd << "\"" << endl;

    // the evaluation:
    failed = ( system(cmd.c_str()) != 0 );
    count_eval  = true;

    // the evaluation failed:
    if ( failed ) {
      
      x.set_eval_ok (false);

      // we stop the evaluations:
      break;
    }

    // reading of the black-box output file:
    // -------------------------------------
    else {
    
      // bb-output file reading:
      in.open( bb_output_file_name.c_str() );
      
      Point bbo(_bb_nbo[k]);

      failed = false;
      try {
	in >> bbo;
      }
      catch ( const Point::Bad_Input & ) {
	failed = true;
      }

      in.close();

      // the eval. failed:
      if ( failed ) {
	x.set_eval_ok (false);
	break;
      }
      
      // the eval. is ok:
      else
	x.set_bb_output(bbo);

      // stop the evaluations if h > h_max or if a 'EB' constraint is violated:
      if ( k < _bb_exe.size()-1 && interrupt_evaluations ( x , h_max ) )
	break;
    }
  }

  // delete the black-box input and output files:
  // --------------------------------------------
  remove ( bb_input_file_name.c_str()  );
  remove ( bb_output_file_name.c_str() );

  return x.is_eval_ok();
}
