// @(#)root/mathmore:$Id$
// Author: L. Moneta Thu Jan 25 11:13:48 2007

/**********************************************************************
 *                                                                    *
 * Copyright (c) 2006  LCG ROOT Math Team, CERN/PH-SFT                *
 *                                                                    *
 * This library 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 2     *
 * of the License, or (at your option) any later version.             *
 *                                                                    *
 * This library 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 library (see file COPYING); if not, write          *
 * to the Free Software Foundation, Inc., 59 Temple Place, Suite      *
 * 330, Boston, MA 02111-1307 USA, or contact the author.             *
 *                                                                    *
 **********************************************************************/

// Header file for class GSLSimAnnealing

#ifndef ROOT_Math_GSLSimAnnealing
#define ROOT_Math_GSLSimAnnealing

#include "Math/IFunctionfwd.h"

#include <algorithm>
#include <vector>
#include <algorithm>

namespace ROOT {

   namespace Math {

      class GSLRandomEngine;

//_____________________________________________________________________________
/**
   GSLSimAnFunc class description.
   Interface class for the  objetive function to be used in simulated annealing
   If user wants to re-implement some of the methods (like the one defining the metric) which are used by the
   the simulated annealing algorithm must build a user derived class.
   NOTE: Derived classes must re-implement the assignment and copy constructor to call them of the parent class

   @ingroup MultiMin
 */
class GSLSimAnFunc {
public:

   /**
      construct from an interface of a multi-dimensional function
    */
   GSLSimAnFunc(const ROOT::Math::IMultiGenFunction & func, const double * x);

   /**
      construct from an interface of a multi-dimensional function
      Use optionally a scale factor (for each coordinate) which can  be used to scale the step sizes
      (this is used for example by the minimization algorithm)
    */
   GSLSimAnFunc(const ROOT::Math::IMultiGenFunction & func, const double * x, const double * scale);

protected:

   /**
      derived classes might need to re-define completely the class
    */
   GSLSimAnFunc() :
      fFunc(nullptr)
   {}

public:


   /// virtual destructor (no operations)
   virtual ~GSLSimAnFunc() { } //


   /**
      fast copy method called by GSL simulated annealing internally
      copy only the things which have been changed
      must be re-implemented by derived classes if needed
   */
   virtual GSLSimAnFunc & FastCopy(const GSLSimAnFunc & f);


   /**
      clone method. Needs to be re-implemented by the derived classes for deep  copying
    */
   virtual GSLSimAnFunc * Clone() const {
      return new GSLSimAnFunc(*this);
   }

   /**
      evaluate the energy ( objective function value)
      re-implement by derived classes if needed to be modified
    */
   virtual double Energy() const;

   /**
      change the x[i] value using a random value urndm generated between [0,1]
      up to a maximum value maxstep
      re-implement by derived classes if needed to be modified
    */
   virtual void Step(const GSLRandomEngine & r, double maxstep);

   /**
      calculate the distance (metric) between  this one and another configuration
      Presently a cartesian metric is used.
      re-implement by derived classes if needed to be modified
    */
   virtual double Distance(const GSLSimAnFunc & func) const;

   /**
      print the position in the standard output std::ostream
      GSL prints in addition n iteration, n function calls, temperature and energy
      re-implement by derived classes if necessary
    */
   virtual void Print();

   /**
       change the x values (used by sim annealing to take a step)
    */
   void SetX(const double * x) {
      std::copy(x, x+ fX.size(), fX.begin() );
   }

   template <class IT>
   void SetX(IT begin, IT end) {
      std::copy(begin, end, fX.begin() );
   }

   unsigned int NDim() const { return fX.size(); }

   double X(unsigned int i) const { return fX[i]; }

   const std::vector<double> &  X() const { return fX; }

   double Scale(unsigned int i) const { return fScale[i]; }

   void SetX(unsigned int i, double x) { fX[i] = x; }

   // use compiler generated  copy ctror and assignment operators

private:

   std::vector<double>  fX;
   std::vector<double>  fScale;
   const ROOT::Math::IMultiGenFunction * fFunc;

};

//_____________________________________________________
/**
    structure holding the simulated annealing parameters

   @ingroup MultiMin
*/
struct GSLSimAnParams {

   // constructor with some default values
   GSLSimAnParams() {
      n_tries =    200;
      iters_fixed_T =  10;
      step_size =   10;
      // the following parameters are for the Boltzmann distribution */
      k = 1.0;
      t_initial =  0.002;
      mu_t =  1.005;
      t_min = 2.0E-6;
   }


   int n_tries;            // number of points to try for each step
   int iters_fixed_T;      // number of iterations at each temperature
   double step_size;       // max step size used in random walk
   /// parameters for the Boltzman distribution
   double k;
   double t_initial;
   double mu_t;
   double t_min;
};

//___________________________________________________________________________
/**
   GSLSimAnnealing class for performing  a simulated annealing search of
   a multidimensional function

   @ingroup MultiMin
*/
class GSLSimAnnealing {

public:

   /**
      Default constructor
   */
   GSLSimAnnealing ();

   /**
      Destructor (no operations)
   */
   ~GSLSimAnnealing ()  {}

   // usually copying is non trivial, so we delete this
   GSLSimAnnealing(const GSLSimAnnealing &) = delete;
   GSLSimAnnealing & operator = (const GSLSimAnnealing & rhs) = delete;
   GSLSimAnnealing(GSLSimAnnealing &&) = delete;
   GSLSimAnnealing & operator = (GSLSimAnnealing && rhs) = delete;

   /**
      solve the simulated annealing given a multi-dim function, the initial vector parameters
      and a vector containing the scaling factors for the parameters
   */
   int Solve(const ROOT::Math::IMultiGenFunction & func, const double * x0, const double * scale, double * xmin, bool debug = false);

   /**
      solve the simulated annealing given a GSLSimAnFunc object
      The object will contain the initial state at the beginning and the final minimum state at the end
   */
   int Solve(GSLSimAnFunc & func, bool debug = false);


   GSLSimAnParams & Params() { return fParams; }
   const GSLSimAnParams & Params() const { return fParams; }
   void SetParams(const GSLSimAnParams & params) { fParams = params; }

private:

   GSLSimAnParams fParams; // parameters for GSLSimAnnealig

};

   } // end namespace Math

} // end namespace ROOT


#endif /* ROOT_Math_GSLSimAnnealing */
