/// $Revision: 1330 $
/// \author Gerald Weber <gweberbh@gmail.com>
#ifndef GBC_GSLMINIMIZATION_H
#define GBC_GSLMINIMIZATION_H "$Id: GSLMinimization.h 1330 2022-12-12 21:29:43Z ge $"
#include <valarray>
#include <fstream>
#include <iterator>
#include <limits>
#include <gsl/gsl_multimin.h>
#include <sys/time.h>
#include "MathAux.h"
#include "ErrorCodes.h"
#undef max

namespace gbc
  {
  template<class _FunctionClass>
  class GSLMinimization
    {
    public:
      typedef _FunctionClass                       function_type;
      typedef typename _FunctionClass::value_type  value_type;
      typedef typename _FunctionClass::vector_type vector_type;

      typedef std::map<value_type,vector_type> point_map_type;
      typedef std::vector<vector_type>         unit_vector_type;
      typedef std::list<std::string>           history_list_type;

   size_t Dimension;           ///< The dimension of the coordinate vectors
   value_type       Lambda;    ///< Characteristic length of the problem
   static function_type*   Function;  ///< The actual function to minimize
   bool             Record_history; ///< Check if history should be recorded
   bool             Debug;          ///< Check if debug info should be printed
   bool             Proportional;   ///< Check if initialization should be proportional to initial value
   bool             PrintLogNoImprovement; ///< Print minimization log even when no improvement was reached.
   bool             PrintLogProgress;      ///< Print minimization log when there is an improvement.
   long int         ProgressInterval;      ///< in seconds, default 0 s, meaning PrintLogProgress all the time
   value_type       Limit;     ///< Limit difference between the worst and the best
   point_map_type   Point_map; ///< Map of values and coordinates
   long int         MaxEvaluations; ///< Maximum number of evaluations
   history_list_type History;  ///< History of minimization actions.
   std::ostream    *LogFile;  ///< File to which we print intermediate results
   const gsl_multimin_fminimizer_type *fminimizer_type;
   gsl_multimin_fminimizer            *fminimizer;
   gsl_multimin_function              multimin_function;
   bool fminimizer_setup;
   vector_type BestPoints;
   
   typedef double (*gsl_fp_type)(const gsl_vector*, void*);
   
   GSLMinimization(void)
     : Dimension(), Lambda(), Record_history(false), 
       Debug(false), Proportional(true), PrintLogNoImprovement(false), PrintLogProgress(true), ProgressInterval(0), Point_map(), 
       MaxEvaluations(std::numeric_limits<long int>::max()), fminimizer_setup(false)
     {
     LogFile=new std::ofstream;
     }
     
   ~GSLMinimization(void)
     {
     //fminimizer_setup is used to avoid calling the destructor when fminimizer was not
     //used, otherwise we get segmentation faults
     if (fminimizer_setup) gsl_multimin_fminimizer_free(fminimizer);
     }

   GSLMinimization(function_type& f,          ///< The function that will be minimized
                const vector_type& init,   ///< The initial vector
                const value_type& l,       ///< The characteristic length
                bool prop=true)            ///< Controls if l will be added (false) or multiplied (true)
     : Dimension(init.size()), Lambda(l), //Function(&f), 
       Record_history(false), 
       Debug(false), Proportional(prop), Point_map(), 
       MaxEvaluations(std::numeric_limits<long int>::max())
     {
     Function=&f;
     LogFile=new std::ofstream;
     setup(init);
     }

   /// \brief Initializes the minimization process.
   ///
   inline void setup(const vector_type& init) ///< The initial vector
     {
     BestPoints.resize(Dimension);

     Point_map.clear();
     Point_map.insert(typename point_map_type::value_type(Function->evaluate_from_vector(init),init));
     this->print_log();
     
     gsl_vector       *points;
     points = gsl_vector_alloc (Dimension);

     for(size_t i=0; i < Dimension; ++i)
      {
      gsl_vector_set (points, i, init[i]);
      }

     gsl_vector       *steps;
     steps  = gsl_vector_alloc (Dimension);
     gsl_vector_set_all (steps, Lambda);

     multimin_function.n = Dimension;
     multimin_function.f = my_function;
     multimin_function.params = NULL; // we are not passing additional parameters this way
     double par[5] = {1.0, 2.0, 10.0, 20.0, 30.0};
     multimin_function.params = par; //this is not used, however adding a NULL pointer causes crashes 

     //This function initializes the minimizer, running 3 evaluations at the points defined
     //by points and stepped by steps
     fminimizer_type = gsl_multimin_fminimizer_nmsimplex2;
     fminimizer = gsl_multimin_fminimizer_alloc (fminimizer_type, Dimension);
     fminimizer_setup=true;
     int status=gsl_multimin_fminimizer_set (fminimizer, &multimin_function, points, steps);
     if (status != GSL_SUCCESS)
       {
       CERR_ERROR(ERRGSLERR) << "gsl_strerror=" << gsl_strerror (status) << std::endl;
       CERR_TERM
       }
     gsl_vector_free(points);
     gsl_vector_free(steps);
     }

   /// \brief Resets the minimization process.
   ///
   /// This clears History and Evaluations.
   inline void reset(function_type& f,        ///< The function that will be minimized
                     const vector_type& init, ///< The initial vector
                     const value_type& l)     ///< The characteristic length
     {
     History.clear();
     Dimension=init.size(); 
     Lambda=l; 
     Function=&f;
     Function->Evaluations=0;
     setup(init);
     }
     
   static double my_function (const gsl_vector*v, void *params)
     {
     vector_type np(v->size);
     for(size_t i=0; i < v->size; ++i)
       {
       np[i]=gsl_vector_get(v, i);
       //std::cout << "np[" << i << "]=" << np[i] << std::endl;
       }
    
    value_type evaluation_result=Function->evaluate_from_vector(np);
    //std::cout << "eval=" << evaluation_result <<  std::endl;
    
 
    return evaluation_result;
    }
    
    

   inline void limit(value_type& l) {Limit=l;}

   inline void show_points(void)
     {
     typename point_map_type::const_iterator it;
     for(it=Point_map.begin(); it!= Point_map.end(); ++it)
       {
       std::cout << it->first << ": ";
       for(size_t i=0; i < Dimension; ++i)
         {
         std::cout << it->second[i] << ",";
         }
       std::cout << std::endl;
       } 
     }
     
   /// \brief The best vector in Point_map.
   ///
   /// \returns the best vector, which is always the first element of Poit_map.
   inline vector_type& best(void)
     {
     return Point_map.begin()->second;
     }

   /// \brief The current best value of the minimization
   ///
   /// \returns the best value, which is always the first element of Poit_map.
   inline value_type best_value(void)
     {
     return Point_map.begin()->first;
     }
   /// \brief The current next-best value of the minimization
   ///
   /// \returns the next-best value, which is always the second element of Poit_map.
   inline vector_type& next_best(void)
     {
     return (++Point_map.begin())->second;
     }

   /// \brief Erases old next-best point and replaces it.
   ///
   /// \attention This method performs function evaluation. 
   inline void next_best(const vector_type& w)
     {
     Point_map.erase((++Point_map.begin())->first);
     Point_map.insert(typename point_map_type::value_type(Function->evaluate(w),w));
     }

   /// \brief The worst vector of the minimization
   ///
   /// \returns the worst vector, which is always the last element of Poit_map.
   inline vector_type& worst(void)
     {
     return Point_map.rbegin()->second;
     }
   
   /// \brief The worst value of the minimization
   ///
   /// \returns the worst value, which is always the last element of Poit_map.
   inline value_type worst_value(void)
     {
     return Point_map.rbegin()->first;
     }

   /// \brief Erases old worst point and replaces it.
   inline void worst(value_type& e, const vector_type& w)
     {
     Point_map.erase(Point_map.rbegin()->first);
     Point_map.insert(typename point_map_type::value_type(e,w));
     }

   /// \brief Erases old worst point and replaces it.
   ///
   /// \attention This method performs function evaluation. 
   inline void worst(const vector_type& w)
     {
     Point_map.erase(Point_map.rbegin()->first);
     Point_map.insert(typename point_map_type::value_type(Function->evaluate(w),w));
     }
 
   inline void run(value_type l)
      {
      //Time accounting
      struct timeval start, current;
      gettimeofday(&start, NULL);
      long int seconds_elapsed;

      Limit=l;
      long int no_improvement=0;
      int status=GSL_CONTINUE;
      vector_type np(Dimension);
      double previous_best=std::numeric_limits<double>::max();
      bool logged = false;
      while(status == GSL_CONTINUE)
        {
        logged = false;
        double status = gsl_multimin_fminimizer_iterate(fminimizer);
        if (status == GSL_SUCCESS || status == GSL_CONTINUE)
          {    
          double size = gsl_multimin_fminimizer_size (fminimizer);
          status = gsl_multimin_test_size (size, Limit);
          for(size_t i=0; i < Dimension; ++i)
            {
            np[i]=gsl_vector_get (gsl_multimin_fminimizer_x(fminimizer), i);
            }
          Point_map.erase(Point_map.rbegin()->first);
          Point_map.insert(typename point_map_type::value_type(gsl_multimin_fminimizer_minimum(fminimizer),np));
          if (previous_best != best_value()) 
            {
            if (PrintLogProgress) 
              {
              gettimeofday(&current, NULL);
              seconds_elapsed=(current.tv_sec - start.tv_sec);
              if (seconds_elapsed >= ProgressInterval) 
                {
                print_log(); logged = true;
                start=current;
                }
              }
            no_improvement=0;
            }
          else 
            {
            if (PrintLogNoImprovement) { print_log(); logged = true; }
            no_improvement++;
            }
          if (no_improvement >= MaxEvaluations)
            {
            if (not logged) { print_log(); logged = true; }//ensures that the last iteration is always logged
            *LogFile << "*** Round stopped after MaxEvaluations (" << MaxEvaluations << ") without improvement reached, best chi2 is " <<  best_value() << std::endl; 
            break;
            }
          previous_best=best_value();
          }
        else {CERR_ERROR(ERRGSLERR) << "gsl_strerror=" << gsl_strerror (status) << std::endl; CERR_TERM}
        if (status == GSL_SUCCESS || status == GSL_ENOPROG)
          {
          if (not logged) { print_log(); logged = true; } //ensures that the last iteration is always logged
          *LogFile << "*** Round stopped with status ";
          if (status == GSL_SUCCESS) *LogFile << "GSL_SUCCESS";
          if (status == GSL_ENOPROG) *LogFile << "GSL_ENOPROG";
          *LogFile << " best chi2 is " <<  best_value();
          *LogFile  << std::endl; 
          break;
          }
        }
      if (not logged) print_log(); 
      }
      

    inline void print_log(void)
      {
      if (LogFile->good())
        {
        *LogFile << Function->Evaluations << " " << best_value();
        for(size_t n=0; n < Dimension; ++n) 
          *LogFile << " " << best()[n];
        *LogFile << std::endl;
        }
      }
    inline void print_log_worst(void)
      {
      if (LogFile->good())
        {
        *LogFile << Function->Evaluations << " " << worst();
        for(size_t n=0; n < Dimension; ++n) 
          *LogFile << " " << worst()[n];
        *LogFile << std::endl;
         }
      }
      
    inline void print_history(void)
      {
      std::copy(History.begin(),History.end(),std::ostream_iterator<std::string>(std::cout));
      }
  };
}
#endif
