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

namespace gbc
  {
  template<class _FunctionClass>
  /// \brief N-dimensional minimization using the Nelder-Mead method
  /// http://math.fullerton.edu/mathews/n2003/NelderMeadMod.html
  class Minimization
    {
    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
   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
   bool             PrintLogReplaceWorst;  ///< Print minimization log when worst is replaced.
   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
   long int         Evaluations;///< Records the number of evaluations
   history_list_type History;  ///< History of minimization actions.
   std::ostream    *LogFile;  ///< File to which we print intermediate results

   Minimization(void)
     : Dimension(), Lambda(), Function(), Record_history(false), 
       Debug(false), Proportional(true), PrintLogNoImprovement(false), PrintLogProgress(true), PrintLogReplaceWorst(false), ProgressInterval(0), Point_map(), 
       MaxEvaluations(std::numeric_limits<long int>::max()), Evaluations()
     {
     LogFile=new std::ofstream;
     }

   Minimization(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()), Evaluations()
     {
     LogFile=new std::ofstream;
     setup(init);
     }

   /// \brief Initializes the minimization process.
   ///
   /// We construct a series of unit vector e for each dimension of
   /// the problem. This unit vector is multiplied by the characteristic
   /// length Lambda of the problem and the resulting vector inserted
   /// into Point_map.
   inline void setup(const vector_type& init) ///< The initial vector
     {
     Point_map.clear();
     Point_map.insert(typename point_map_type::value_type(Function->evaluate_from_vector(init),init));
     Evaluations++;
     print_log();
     for(size_t i=0; i < Dimension; ++i)
       {
       vector_type np=init;
       if (Proportional) np[i]*=1.0+Lambda;
       else              np[i]+=Lambda;
       value_type ev=Function->evaluate_from_vector(np);
       if (ev == best_value())
         {
         CERR_WARN(WMINPCIP) << "Minimization: poor choice of initial points, wrong set up of variables or too far from minimum"  << std::endl;
         CERR << "Function result=" << ev << " Lambda=" << Lambda << "Proportional=" << Proportional << " i=" << i << std::endl;
         CERR << "Init    Points=";
         for(size_t n=0; n < Dimension; ++n) CERR << " " << init[n];
         CERR << std::endl;
         CERR << "Current Points=";
         for(size_t n=0; n < Dimension; ++n) CERR << " " << np[n];
         CERR << std::endl;
         CERR << "Prev.   Points=";
         for(size_t n=0; n < Dimension; ++n) CERR << " " << best()[n];
         CERR << std::endl;
         }

       Point_map.insert(typename point_map_type::value_type(ev,np));
       Evaluations++;
       }
     }

   /// \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();
     Evaluations=0;
     Dimension=init.size(); 
     Lambda=l; 
     Function=&f;
     setup(init);
     }

   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_from_vector(w),w));
     Evaluations++;
     }

   /// \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_from_vector(w),w));
     Evaluations++;
     }

   inline vector_type midpoint(void)
     {
     return (best()+next_best())/2.0;
     }

   inline vector_type reflection(void)
     {
     return 2.0*midpoint()-worst();
     }

   inline vector_type expansion(void)
     {
     return 2.0*reflection()-midpoint();
     }

   inline vector_type contraction1(void)
     {
     return (midpoint()+worst())/2.0;
     }

   inline vector_type contraction2(void)
     {
     return (midpoint()+reflection())/2.0;
     }

   /// \brief Checks if new point is better than worst and replace.
   ///
   /// \attention This method performs function evaluation. 
   inline bool replace_worst(const vector_type& w)
     {
     if (PrintLogReplaceWorst)
       {
       *LogFile << "replace_worst: ";
       for(size_t n=0; n < Dimension; ++n) 
          *LogFile << " " << w[n];
       *LogFile << std::endl;
       }
     value_type f=Function->evaluate_from_vector(w);
     Evaluations++;
     if (PrintLogReplaceWorst)
       {
       *LogFile << " f=" << f << std::endl;
       }
     bool r=f < worst_value();
     if (r) worst(f,w);
     return r;
     }

   /// \brief Applies opperations and rules.
   inline void improve(void)
     {
     if(BOOL_DDEBUG(DMIN_BSTN)) 
       {
       for(size_t n=0; n < Dimension; ++n) CERR_DEBUG(DMIN_BSTN) << best()[n] << " " << std::flush;
       CERR_DDEBUG_NH(DMIN_BSTN) << std::endl;
       }
     if (replace_worst(expansion())) 
       {if (Record_history) History.push_back("E"); return;}

     if (replace_worst(reflection())) 
       {if (Record_history) History.push_back("R"); return;}

     if (replace_worst(contraction1()))
       {if (Record_history) History.push_back("C"); return;}

     if (replace_worst(contraction2()))
       {if (Record_history) History.push_back("c"); return;}

     worst(0.5*(best()+worst()));

     next_best(0.5*(best()+next_best()));
     if (Record_history) History.push_back("S");
   
     }

    inline void print_log(void)
      {
      if (LogFile->good())
        {
        *LogFile << 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 << Evaluations << " " << worst_value();
        for(size_t n=0; n < Dimension; ++n) 
          *LogFile << " " << worst()[n];
        *LogFile << std::endl;
        }
      }

    //This is the main function that runs the minimization process
    inline void run(value_type l)
      {
      //Time accounting
      struct timeval start, current;
      gettimeofday(&start, NULL);
      long int seconds_elapsed;
      
      Limit=l;
      print_log();
      long int no_improvement=0;
      bool logged = false;
      while (((worst_value() - best_value()) > Limit) 
             && (no_improvement < MaxEvaluations)) 
        {
        logged = false;
        value_type previous=best_value();
        improve();
        if (previous != 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; }
          *LogFile << "*** MaxEvaluations (" << MaxEvaluations << ")without improvement reached" << std::endl; 
          if (PrintLogReplaceWorst) print_log_worst();
          *LogFile << "***" << std::endl;
          break;
          }
        }
      if (not logged) print_log();//ensures that the last iteration is always logged
      }

    inline void print_history(void)
      {
      std::copy(History.begin(),History.end(),std::ostream_iterator<std::string>(std::cout));
      }
  };
}
#endif
