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

// description.cpp -- class methods for Description.

#include <cmath>
#include <cstring>
#include <fstream>
#include <iostream>
#include <vector>
using namespace std;
#include "defines.h"
#include "description.h"

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

// This method checks if the starting point is inside or outside the bounds. It
// returns true if it's inside, false otherwise.
bool Description::checkStartingPoint()
{
  if (bounds_use == true)
    {
      // Each coordinate has to be inside the bounds.
      for (int i = 0; i < dimension; i++)
	{
	  // If one coordinate is outside the bounds...
	  if ((starting_point[i] < lower_bounds[i]) ||
	      (starting_point[i] > upper_bounds[i]))
	    // the method will return 'false'.
	    return false;
	}
    }
  // The starting point is inside the bounds (or there are no bounds).
  return true;
}

// This method builds the 'scales_mult' array for unbounded problems. The other
// array, 'scales_add' isn't needed.
void Description::automaticScalingNoBounds()
{
  double scale_exponent;
  for(int i = 0; i < dimension; i++)
    {
      if (starting_point[i] != 0.0)
	scale_exponent = log(abs(starting_point[i])) / log(2.0);
      else
	scale_exponent = 0.0;
      // We build the 'scales_mult' array.
      scales_mult[i] = pow(2.0, floor(scale_exponent - 1.0));
    }
}

// This method builds the 'scales_mult' array for bounded problems. The other
// array, 'scales_add' isn't needed.
void Description::automaticScalingWithBounds()
{
  double scale_exponent;
  int finite;
  for(int i = 0; i < dimension; i++)
    {
      finite = ((lower_bounds[i] != 0.0) &&
		(lower_bounds[i] != -INFINIT)) ? 4 : 0;
      finite += ((upper_bounds[i] != 0.0) &&
		 (upper_bounds[i] != INFINIT)) ? 2 : 0;
      if (finite == 0)
	finite += (starting_point[i] != 0.0) ? 1 : 0;
      switch(finite)
	{
	  case 6  : scale_exponent =
		      ((log(abs(lower_bounds[i])) / log(2.0)) +
		       (log(abs(upper_bounds[i])) / log(2.0))) / 2.0;
		    break;
	  case 4  : scale_exponent = log(abs(lower_bounds[i])) / log(2.0);
		    break;
	  case 2  : scale_exponent = log(abs(upper_bounds[i])) / log(2.0);
		    break;
	  case 1  : scale_exponent = log(abs(starting_point[i])) / log(2.0);
		    break;
	  case 0  : scale_exponent = 1.0;
		    break;
	  default : scale_exponent = 1.0;
	            cout << "This is not a possible case!\n";
	}
      // We build the 'scales_mult' array.
      scales_mult[i] = pow(2.0, floor(scale_exponent - 1.0));
    }
}

// This method is called when the user provided bounds for the scaling; i.e all
// variables will be scaled between two real numbers, 'lower' and 'upper'.
void Description::boundScaling()
{
  // The two scaling arrays are built.
  for (int i = 0; i < dimension; i++)
    {
      scales_mult[i] = (upper_bounds[i] - lower_bounds[i]) / (upper - lower);
      scales_add[i] = lower - (lower_bounds[i] / scales_mult[i]);
    }
}

// This method builds the output index once, at the start of a run. This index
// indicates the types of output that are calculated in the run: the objective
// function value (f(x)), the general constraints functions values (gi(x)), the
// transition values and the surrogate function value.
void Description::buildOutputIndex()
{
  // This variable is reset.
  output_dimension = 1;
  // First the output dimension is calculated. It's equal to:
  //    1 + sx + (number of general constraints)
  // where sx = {1 if the problem uses a surrogate
  //             0 otherwise}
  if (surrogate_use)
    output_dimension++;
  if (general_constraints_number > 0)
    output_dimension += general_constraints_number;

  // The 'output_index' array is created.
  output_index = new int[output_dimension];
  // The first position contains the truth function value (represented by '0').
  output_index[0] = 0;
  if (output_dimension > 1)
    {
      int i = 1;
      // If there's a surrogate, its output will go in second position
      // (represented by '1').
      if (surrogate_use)
	output_index[i++] = 1;
      // All other array positions will contain a '2' (representing a general
      // constraint output).
      if (general_constraints_number > 0)
	while (i < output_dimension)
	  output_index[i++] = 2;
    }
}

// This method constructs the two scaling arrays, 'scales_add' and
// 'scales_mult' according to the user's scaling choice.
void Description::buildScales()
{
  switch(scaling_choice)
    {
      // No scaling.
      case 0  : break;
      // The user requested automatic scaling.
      case 1  : if ((lower_bounds == NULL) && (upper_bounds == NULL))
	          automaticScalingNoBounds();
                else
		  automaticScalingWithBounds();
	        break;
      // The user provided a scaling multiplication vector. In that case the
      // array was built in the 'captureArrays' method, there's nothing left to
      // do.
      case 2  : break;
      // The user provided bounds for scaling: the lower and upper bounds must
      // exist for this method to work.
      case 3  : if ((lower_bounds != NULL) && (upper_bounds != NULL) &&
		    (lower != upper))
		  boundScaling();
	        break;
      default : cout << "This is not a correct scaling choice!\n";
    }
}

// This method fills arrays from numbers extracted from some files.
void Description::captureArrays()
{
  // We open the starting point file.
  ifstream fin1(start_pt_file);
  // The starting point array is created.
  starting_point = new double[dimension];
  // We fill up the 'starting_point' array.
  fillArray(fin1, starting_point);
  // The starting point file is closed.
  fin1.close();

  // If there are bound constraints...
  if (constraints != NULL)
    {
      // ... we open the constraints file.
      ifstream fin2(constraints);
      // We create arrays for the lower and upper bounds.
      lower_bounds = new double[dimension];
      upper_bounds = new double[dimension];
      // We fill up the bounds' arrays.
      fillArray(fin2, lower_bounds);
      fillArray(fin2, upper_bounds);
      // The constraints file is closed.
      fin2.close();
    }

  int i;
  // The scales arrays  are created.
  scales_add = new double[dimension];
  scales_mult = new double[dimension];
  // The arrays are initialized to default values. The scales multiplier values
  // are '1.0' and the scales adder values are '0.0', which is the unscaled
  // case.
  for(i = 0; i < dimension; i++)
    {
      scales_add[i] = 0.0;
      scales_mult[i] = 1.0;
    }
  // If the user supplied a scaling vector...
  if (scaling_choice == 2)
    {
      // ... we open the the scales file.
      ifstream fin3(scales);
      // It's filled up with the values extracted from the file.
      fillArray(fin3, scales_mult);
      // The scales file is closed.
      fin3.close();
    }
}

// This method fills the array 'coord' with numbers read from the file 'fin'.
void Description::fillArray(ifstream & fin, double * coord)
{
  char ch;
  char line[80];
  int i = 0;
  // We go in the loop: the next character is extracted from the file.
  while ((i < dimension) && (fin.get(ch)))
    {
      // '%' marks a comment line.
      if (ch == '%')
	// The line is read.
	fin.getline(line, 80);
      else   // If the end of line is reached we do nothing.
	if (ch == '\n')
	  {}
	else   // If it's not a comment line or an end of line, it's a number.
	  {
	    // The character is reinserted in the file.
	    fin.putback(ch);
	    // The number is read and put in the 'coord' array.
	    fin >> coord[i];
	    // The counter 'i' is incremented.
	    i++;
	  }
    }
}

// The arrays are deleted.
void Description::freeArrays()
{
  delete [] lower_bounds;
  delete [] scales_add;
  delete [] scales_mult;
  delete [] starting_point;
  delete [] upper_bounds;
  delete [] output_index;
}

// This method reads the problem file, capturing its variables in the process.
void Description::readProblemFile(ifstream & fin)
{
  char ch;
  char line[80], temp[80];
  bool check_name = false, check_dimension = false, check_gen_cons_nb = false,
    check_surrogate = false, check_black_box = false, check_directory = false,
    check_start_pt = false, check_input = false, check_results = false,
    check_truth_exe = false, check_bounds_use = false, check_bounds = false,
    check_gen_cons = false, check_cache_use = false, check_cache = false,
    check_surr_exe = false, check_scaling = false, check_scales = false,
    check_lower = false, check_upper = false;
  // The file is read line by line in this loop.
  while (!fin.eof())
    {
      // We look at the first character, without putting it in the input buffer
      ch = fin.peek();
      fin >> line;
      // If the first character is '#', it means that we have a comment line.
      // Nothing is done in that case.
      if (ch != '#')
	{
	  // The problem name is extracted:
	  if (!check_name && (strcmp(line, "PROBLEM_NAME") == 0))
	    {
	      fin >> temp;
	      // The memory is freed, then allocated for 'name'.
	      if (name != NULL)
		delete [] name;
	      name = new char[strlen(temp) + 1];
	      // 'temp' is copied into 'name'.
	      strcpy(name, temp);
	      check_name = true;
	    }
	  // The problem dimension (number of variables) is extracted:
	  else if (!check_dimension && (strcmp(line, "DIMENSION") == 0))
	    {
	      fin >> dimension;
	      check_dimension = true;
	    }
	  // The number of general constraints is extracted:
	  else if (!check_gen_cons_nb && (strcmp(line, "GEN_CONS_NB") == 0))
	    {
	      fin >> general_constraints_number;
	      check_gen_cons_nb = true;
	    }
	  // The scaling choice is extracted:
	  else if (!check_scaling && (strcmp(line, "SCALING_CHOICE") == 0))
	    {
	      fin >> scaling_choice;
	      check_scaling = true;
	    }
	  // The lower scaling bound is extracted:
	  else if (!check_lower && (strcmp(line, "LOWER_SCALE") == 0))
	    {
	      fin >> lower;
	      check_lower = true;
	    }
	  // The upper scaling bound is extracted:
	  else if (!check_upper && (strcmp(line, "UPPER_SCALE") == 0))
	    {
	      fin >> upper;
	      check_upper = true;
	    }
	  // The use of a surrogate is extracted:
	  else if (!check_surrogate && (strcmp(line, "USE_SURROGATE") == 0))
	    {
	      fin >> surrogate_use;
	      check_surrogate = true;
	    }
	  // The use of black boxes is extracted:
	  else if (!check_black_box && (strcmp(line, "USE_BLACK_BOXES") == 0))
	    {
	      fin >> black_box_use;
	      check_black_box = true;
	    }
	  // The use of bound constraints is extracted:
	  else if (!check_bounds_use && (strcmp(line, "USE_BOUNDS") == 0))
	    {
	      fin >> bounds_use;
	      check_bounds_use = true;
	    }
	  // The use of the caches is extracted:
	  else if (!check_cache_use && (strcmp(line, "USE_CACHES") == 0))
	    {
	      fin >> cache_use;
	      check_cache_use = true;
	    }
	  // The problem directory is extracted:
	  else if (!check_directory && (strcmp(line, "DIRECTORY") == 0))
	    {
	      fin >> temp;
	      // The memory is freed, then allocated for 'directory'.
	      if (directory != NULL)
		delete [] directory;
	      directory = new char[strlen(temp) + 1];
	      // 'temp' is copied into 'directory'.
	      strcpy(directory, temp);
	      /* We make sure that the directory variable ends with a '/'
		 character. */
	      if (directory[strlen(directory) - 1] != '/')
		strcat (directory, "/");
	      check_directory = true;
	    }
	  // The starting point file is extracted:
	  else if (!check_start_pt && (strcmp(line, "START_PT_FILE") == 0))
	    {
	      fin >> temp;
	      // The memory is freed, then allocated for 'start_pt_file'.
	      if (start_pt_file != NULL)
		delete [] start_pt_file;
	      start_pt_file = new char[strlen(temp) + 1];
	      // 'temp' is copied into 'start_pt_file'.
	      strcpy(start_pt_file, temp);
	      check_start_pt = true;
	    }
	  // The input file is extracted:
	  else if (!check_input && (strcmp(line, "INPUT_FILE") == 0))
	    {
	      fin >> temp;
	      // The memory is freed, then allocated for 'input'.
	      if (input != NULL)
		delete [] input;
	      input = new char[strlen(temp) + 1];
	      // 'temp' is copied into 'input'.
	      strcpy(input, temp);
	      check_input = true;
	    }
	  // The results file is extracted:
	  else if (!check_results && (strcmp(line, "RESULTS_FILE") == 0))
	    {
	      fin >> temp;
	      // The memory is freed, then allocated for 'results'.
	      if (results != NULL)
		delete [] results;
	      results = new char[strlen(temp) + 1];
	      // 'temp' is copied into 'results'.
	      strcpy(results, temp);
	      check_results = true;
	    }
	  // The path of the truth executable is extracted:
	  else if (!check_truth_exe && (strcmp(line, "TRUTH_EXE") == 0))
	    {
	      fin >> temp;
	      // The memory is freed, then allocated for 'truth'.
	      if (truth != NULL)
		delete [] truth;
	      truth = new char[strlen(temp) + 1];
	      // 'temp' is copied into 'truth'.
	      strcpy(truth, temp);
	      check_truth_exe = true;
	    }
	  // The scales file is extracted.
	  else if (!check_scales && (strcmp(line, "SCALES_FILE") == 0))
	    {
	      fin >> temp;
	      // The memory is freed and reallocated for 'scales'.
	      if (scales != NULL)
		delete [] scales;
	      scales = new char[strlen(temp) + 1];
	      // 'temp' is copied into 'scales'.
	      strcpy(scales, temp);
	      check_scales = true;
	    }
	  // The bounds file is extracted:
	  else if (!check_bounds && (strcmp(line, "BOUNDS_FILE") == 0))
	    {
	      fin >> temp;
	      // The memory is freed, then allocated for 'constraints'.
	      if (constraints != NULL)
		delete [] constraints;
	      constraints = new char[strlen(temp) + 1];
	      // 'temp' is copied into 'constraints'.
	      strcpy(constraints, temp);
	      check_bounds = true;
	    }
	  // The general constraints file is extracted:
	  else if (!check_gen_cons && (strcmp(line, "GEN_CONS_FILE") == 0))
	    {
	      fin >> temp;
	      // The memory is freed, then allocated for 'general_constraints'.
	      if (general_constraints != NULL)
		delete [] general_constraints;
	      general_constraints = new char[strlen(temp) + 1];
	      // 'temp' is copied into 'general_constraints'.
	      strcpy(general_constraints, temp);
	      check_gen_cons = true;
	    }
	  // The cache file is extracted:
	  else if (!check_cache && (strcmp(line, "CACHE_FILE") == 0))
	    {
	      fin >> temp;
	      // The memory is freed, then allocated for 'cache'.
	      if (cache != NULL)
		delete [] cache;
	      cache = new char[strlen(temp) + 1];
	      // 'temp' is copied into 'cache'.
	      strcpy(cache, temp);
	      check_cache = true;
	    }
	  // The path of the surrogate executable is extracted:
	  else if (!check_surr_exe && (strcmp(line, "SURROGATE_EXE") == 0))
	    {
	      fin >> temp;
	      // The memory is freed, then allocated for 'surrogate'.
	      if (surrogate != NULL)
		delete [] surrogate;
	      surrogate = new char[strlen(temp) + 1];
	      // 'temp' is copied into 'surrogate'.
	      strcpy(surrogate, temp);
	      check_surr_exe = true;
	    }
	}
    }
}

// This method outputs the scaling arrays to a file.
void Description::showScales(ofstream & fout) const
{
  int i;
  // First 'scales_mult' is written.
  fout << "Multiplication array: (";
  for (i = 0; i < dimension - 1; i++)
    fout << scales_mult[i] << ", ";
  fout << scales_mult[i] << ")\n";
  // Then 'scales_add' is written.
  fout << "Addition array: (";
  for (i = 0; i < dimension - 1; i++)
    fout << scales_add[i] << ", ";
  fout << scales_add[i] << ")\n";
}

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

// Class constructor.
Description::Description(const char * file)
{
  // The boolean variables are initialized.
  black_box_use = bounds_use = cache_use = surrogate_use = false;
  // The strings are initialized to NULL.
  cache = constraints = directory = general_constraints = input = name =
    problem_file = results = scales = start_pt_file = surrogate = truth =
    NULL;
  // The real variables are initialized.
  lower = upper = 0.0;
  // The arrays are initialized.
  lower_bounds = NULL;
  scales_add = NULL;
  scales_mult = NULL;
  starting_point = NULL;
  upper_bounds = NULL;
  // The integer variable is initialized.
  scaling_choice = 0;
  // The integer variables are initialized.
  dimension = general_constraints_number = 0;
  output_dimension = 1;
  output_index = NULL;
  // The problem file must be loaded.
  if (file != NULL)
    loadProblemFile(file);
}

// Destructor.
Description::~Description()
{
  // All the strings are destroyed.
  if (cache != NULL)
    delete [] cache;
  if (constraints != NULL)
    delete [] constraints;
  if (directory != NULL)
    delete [] directory;
  if (general_constraints != NULL)
    delete [] general_constraints;
  if (input != NULL)
    delete [] input;
  if (name != NULL)
    delete [] name;
  if (problem_file != NULL)
    delete [] problem_file;
  if (results != NULL)
    delete [] results;
  if (scales != NULL)
    delete [] scales;
  if (start_pt_file != NULL)
    delete [] start_pt_file;
  if (surrogate != NULL)
    delete [] surrogate;
  if (truth!= NULL)
     delete [] truth;
  // The arrays' memory is freed.
  freeArrays();
}

// This method captures this object's attributes.
bool Description::buildArrays()
{
  // The arrays' memory is freed.
  freeArrays();
  // The array 'output_index' is created.
  buildOutputIndex();
  // The problem dimension, starting point and constraints are sent.
  captureArrays();
  // The scaling arrays are built.
  buildScales();
  // We check if the starting point is inside the bounds.
  return checkStartingPoint();
}

// This method checks if the problem is ready to solve.
bool Description::readyToSolve()
{
  // If any of these strings are null, something is wrong and we can't solve
  // the problem (we return 'false').
  if ((name == NULL)                                                         ||
      (directory == NULL)                                                    ||
      (start_pt_file == NULL)                                                ||
      (input == NULL)                                                        ||
      (results == NULL)                                                      ||
      // The problem must have a truth black box.
      ((black_box_use) && (truth == NULL))                                   ||
      // If a surrogate is used, it must have a surrogate black box.
      ((black_box_use) && (surrogate_use) && (surrogate == NULL))            ||
      // If the problem has general constraints, it must have a general
      // constraints file containing paths for the black boxes.
      ((black_box_use) &&
       (general_constraints_number > 0) && (general_constraints == NULL))    ||
      // If the problem has bound constraints, the bounds file must be set.
      ((bounds_use) && (constraints == NULL))                                ||
      // If the problem uses the caches, the path of the permanent cache must
      // be set.
      ((cache_use) && (cache == NULL))                                       ||
      // If the scaling is user-defined, the scales file must be set.
      ((scaling_choice == 2) && (scales == NULL))                            ||
      // The problem dimension must be set.
      (dimension <= 0))
    return false;
  else   // The Description object is ok.
    return true;
}

// The attributes related to the black boxes are set to the received
// parameters.
void Description::enterBlackBoxes(const bool black_box,
				  const char * truth_exec,
				  const char * general, const char * surr_exec)
{
  black_box_use = black_box;
  // The old strings are destroyed (just a precaution).
  if (truth != NULL)
    {
      delete [] truth;
      truth = NULL;
    }
  if (general_constraints != NULL)
    {
      delete [] general_constraints;
      general_constraints = NULL;
    }
  if (surrogate != NULL)
    {
      delete [] surrogate;
      surrogate = NULL;
    }
  // If the problem uses black boxes, the received strings are copied.
  if (black_box_use)
    {
      // The memory is reserved for 'truth'.
      truth = new char[strlen(truth_exec) + 1];
      // 'truth_exec' is copied into 'truth'.
      strcpy(truth, truth_exec);
      // Maybe there's no general constraints.
      if (general_constraints_number > 0)
	{
	  // The memory is reserved for 'general_constraints'.
	  general_constraints = new char[strlen(general) + 1];
	  // 'general' is copied into 'general_constraints'.
	  strcpy(general_constraints, general);
	}
      // Maybe there's no surrogate.
      if (surrogate_use)
	{
	  // The memory is reserved for 'surrogate'.
	  surrogate = new char[strlen(surr_exec) + 1];
	  // 'surr_exec' is copied into 'surrogate'.
	  strcpy(surrogate, surr_exec);
	}
    }
}

// The attributes related to the constraints are set to the received
// parameters.
void Description::enterConstraints(const bool bounds, const char * bounds_file,
				   const int gen_cons_nbr)
{
  bounds_use = bounds;
  general_constraints_number = gen_cons_nbr;
  // The old string is destroyed (just a precaution).
  if (constraints != NULL)
    {
      delete [] constraints;
      constraints = NULL;
    }
  // If the problem has bound constraints, the received string is copied.
  if (bounds_use)
    {
      // The memory is reserved for 'constraints'.
      constraints = new char[strlen(bounds_file) + 1];
      // 'bounds_file' is copied into 'constraints'.
      strcpy(constraints, bounds_file);
    }
}

// The attributes related to the problem files are set to the received
// parameters.
void Description::enterFiles(const char * dir, const char * in,
			     const char * res)
{
  // The old strings are destroyed (just a precaution).
  if (directory != NULL)
    {
      delete [] directory;
      directory = NULL;
    }
  if (input != NULL)
    {
      delete [] input;
      input = NULL;
    }
  if (results != NULL)
    {
      delete [] results;
      results = NULL;
    }
  // The memory is reserved for 'directory'.
  directory = new char[strlen(dir) + 1];
  // 'dir' is copied into 'directory'.
  strcpy(directory, dir);
  // The memory is reserved for 'input'.
  input = new char[strlen(in) + 1];
  // 'in' is copied into 'input'.
  strcpy(input, in);
  // The memory is reserved for 'results'.
  results = new char[strlen(res) + 1];
  // 'res' is copied into 'results'.
  strcpy(results, res);
}

// The attributes related to the problem options are set to the received
// parameters.
void Description::enterOptions(const bool cache_usage, const char * cache_file,
			       const bool surr_use)
{
  cache_use = cache_usage;
  surrogate_use = surr_use;
  // The old cache string is destroyed.
  if (cache != NULL)
    {
      delete [] cache;
      cache = NULL;
    }
  // If the problem uses the caches, the received parameter is copied.
  if (cache_use)
    {
      // The memory is reserved for 'cache'.
      cache = new char[strlen(cache_file) + 1];
      // 'cache_file' is copied into 'cache'.
      strcpy(cache, cache_file);
    }
}

// The attributes related to the problem definition are set to the received
// parameters.
void Description::enterProblem(const char * prob_name, const int dim,
			       const char * start_pt)
{
  dimension = dim;
  // The old strings are destroyed (just a precaution).
  if (name != NULL)
    {
      delete [] name;
      name = NULL;
    }
  if (start_pt_file != NULL)
    {
      delete [] start_pt_file;
      start_pt_file = NULL;
    }
  // The memory is reserved for 'name'.
  name = new char[strlen(prob_name) + 1];
  // 'prob_name' is copied into 'name'.
  strcpy(name, prob_name);
  // The memory is reserved for 'start_pt_file'.
  start_pt_file = new char[strlen(start_pt) + 1];
  // 'start_pt' is copied into 'start_pt_file'.
  strcpy(start_pt_file, start_pt);
}

// The attributes related to the problem options are set to the received
// parameters.
void Description::enterScales(const int index, const char * scales_file,
			      const double low, const double up)
{
  scaling_choice = index;
  // The old 'scales' string is destroyed.
  if (scales != NULL)
    {
      delete [] scales;
      scales = NULL;
    }
  /* If the user gave us a vector for the scaling, the 'scales' string is
     recreated. */
  if (scaling_choice == 2)
    {
      // The memory is reserved.
      scales = new char[strlen(scales_file) + 1];
      // 'scales_file' is copied into 'scales'.
      strcpy(scales, scales_file);
    }
  else
    // The user gave bounds for the scaling.
    if (scaling_choice == 3)
      {
	lower = low;
	upper = up;
      }
}

// This method opens the problem file and loads the problem parameters in
// memory.
void Description::loadProblemFile(const char * file)
{
  // First the 'problem_file' variable is reset.
  setProblemFile(file);
  // We open the problem data file.
  ifstream fin(file);
  // If the file has been opened ('file' is a correct path)...
  if (fin.is_open())
    {
      // ...we read the file's data with this method.
      readProblemFile(fin);
      // The file is closed.
      fin.close();
    }
  else
    {
      // We alert the user that something is wrong with the path.
      cerr << "Couldn't open problem file " << file << "!!!\n\n";
      exit(0);
    }
}

// This method resets the 'problem_file' variable to 'file'.
void Description::setProblemFile(const char * file)
{
  if (problem_file != NULL)
    delete [] problem_file;
  // The memory is reserved for 'problem_file'.
  problem_file = new char[strlen(file) + 1];
  // 'file' is copied into 'problem_file'.
  strcpy(problem_file, file);
}

// This method writes the object's attributes in the problem file.
void Description::write() const
{
  // First we open the problem file.
  ofstream fout(problem_file);
  if (!fout.is_open())
    {
      cerr << "Couldn't open problem file " << problem_file << "!!!\n";
      exit(0);
    }
  else   // The file is open; the attributes are written in the problem file.
    {
      // The problem name:
      fout << "PROBLEM_NAME\t" << name << "\n";
      // The problem dimension:
      fout << "DIMENSION\t" << dimension << "\n";
      // The scaling choice:
      fout << "SCALING_CHOICE\t" << scaling_choice << "\n";
      // If the user provided a scaling vector, its path is shown:
      if (scaling_choice == 2)
	fout << "SCALES_FILE\t" << scales << "\n";
      // If the user provided scaling bounds, they're shown:
      if (scaling_choice == 3)
	{
	  fout << "LOWER_SCALE\t" << lower << "\n";
	  fout << "UPPER_SCALE\t" << upper << "\n";
	}
      // The number of general constraints:
      fout << "GEN_CONS_NB\t" << general_constraints_number << "\n";
      // The use of a surrogate:
      fout << "USE_SURROGATE\t" << surrogate_use << "\n";
      // The use of black boxes:
      fout << "USE_BLACK_BOXES\t" << black_box_use << "\n";
      // The use of bound constraints:
      fout << "USE_BOUNDS\t" << bounds_use << "\n";
      // The use of the caches:
      fout << "USE_CACHES\t" << cache_use << "\n";
      // The problem directory:
      fout << "DIRECTORY\t" << directory << "\n";
      // The starting point file:
      fout << "START_PT_FILE\t" << start_pt_file << "\n";
      // The input file:
      fout << "INPUT_FILE\t" << input << "\n";
      // The results file:
      fout << "RESULTS_FILE\t" << results << "\n";
      // The path of the truth executable:
      if (black_box_use)
	{
	  fout << "TRUTH_EXE\t" << truth << "\n";
	  // The general constraints file:
	  if (general_constraints_number > 0)
	    fout << "GEN_CONS_FILE\t" << general_constraints << "\n";
	  // The path of the surrogate executable:
	  if (surrogate_use)
	    fout << "SURROGATE_EXE\t" << surrogate << "\n";
	}
      // The bounds file:
      if (bounds_use)
	fout << "BOUNDS_FILE\t" << constraints << "\n";
      // The cache file:
      if (cache_use)
	fout << "CACHE_FILE\t" << cache << "\n";
      // We close the problem file.
      fout.close();
    }
}

// Non-member functions.
// ****************************************************************************

// This method outputs this object's attributes.
ostream & operator<<(ostream & os, const Description & des)
{
  int i;

  os << "*************\n";
  os << "*DESCRIPTION*\n";
  os << "*************\n\n";

  // The problem files are shown.
  os << "Problem files:\n";
  os << "--------------\n";
  os << "Problem name: " << des.name << "\n";
  os << "Description file: " << des.problem_file << "\n";
  os << "Problem directory: " << des.directory << "\n";
  os << "Starting point file: " << des.start_pt_file << "\n";
  os << "Results file: " << des.results << "\n";

  // The black boxes are shown.
  if (des.black_box_use)
    {
      os << "Input file: " << des.input << "\n";
      if (des.general_constraints_number > 0)
	os << "General constraints file: " << des.general_constraints << "\n";
      os << "Truth executable: " << des.truth << "\n";
      if (des.surrogate_use)
	os << "Surrogate executable: " << des.surrogate << "\n";
    }

  // The constraints file is shown.
  if (des.bounds_use)
    os << "Constraints file: " << des.constraints << "\n";

  // The cache file path is shown.
  if (des.cache_use)
    os << "Cache path: " << des.cache << "\n";

  // The scaling choice is shown.
  os << "Scaling choice: ";
  switch(des.scaling_choice)
    {
      case 0  : os << "No scaling.\n";
	        break;
      case 1  : os << "Automatic scaling.\n";
	        break;
      case 2  : os << "User-provided vector at " << des.scales << "\n";
	        break;
      case 3  : os << "User-provided bounds. \nLower bound = " << des.lower;
	        os << ", upper bound = " << des.upper << "\n";
		break;
      default : cerr << "This is not a scaling choice!\n";
    }

  // The specifications are shown.
  os << "\nProblem specifications:\n";
  os << "-----------------------\n";
  os << "Problem dimension: " << des.dimension << "\n";
  os << "Number of outputs: " << des.output_dimension << "\n";
  os << "{Objective function";
  if (des.surrogate_use)
    os << ", surrogate function";
  if (des.general_constraints_number > 0)
    os << ", " << des.general_constraints_number << " general constraints functions";
  os << "}\n";

  // The starting point is shown.
  os << "Starting point: {";
  for (i = 0; i < des.dimension - 1; i++)
    os << des.starting_point[i] << ", ";
  os << des.starting_point[des.dimension - 1] << "}\n";

  // The bounds are shown.
  if (des.bounds_use)
    {
      // First the lower bounds.
      os << "Lower bounds: {";
      for (i = 0; i < des.dimension - 1; i++)
	os << des.lower_bounds[i] << ", ";
      os << des.lower_bounds[des.dimension - 1] << "}\n";
      // Then the upper bounds.
      os << "Upper bounds: {";
      for (i = 0; i < des.dimension - 1; i++)
	os << des.upper_bounds[i] << ", ";
      os << des.upper_bounds[des.dimension - 1] << "}\n";
    }

  // The scaling arrays are shown.
  if (des.scaling_choice > 0)   // '0' means 'no scaling'.
    {
      // The multiplication array is shown.
      os << "Scaling multiplication array: {";
      for (i = 0; i < des.dimension - 1; i++)
	os << des.scales_mult[i] << ", ";
      os << des.scales_mult[des.dimension - 1] << "}\n";
      // The addition array is shown.
      os << "Scaling addition array: {";
      for (i = 0; i < des.dimension - 1; i++)
	os << des.scales_add[i] << ", ";
      os << des.scales_add[des.dimension - 1] << "}\n";
    }
  os << "\n";

  return os;
}
