#ifndef DifEvo_hh
#define DifEvo_hh

//
// An implementation of the Differential Evolution for continuous
// function optimization, an algorithm by Kenneth Price and Rainer Storn.
// See: http://www.icsi.berkeley.edu/~storn/code.html
//
//  /***************************************************************
//  **                                                            **
//  **        D I F F E R E N T I A L     E V O L U T I O N       **
//  **                                                            **
//  ** Program: de.c                                              **
//  ** Version: 3.6                                               **
//  **                                                            **
//  ** Authors: Dr. Rainer Storn                                  **
//  **          c/o ICSI, 1947 Center Street, Suite 600           **
//  **          Berkeley, CA 94707                                **
//  **          Tel.:   510-642-4274 (extension 192)              **
//  **          Fax.:   510-643-7684                              **
//  **          E-mail: storn@icsi.berkeley.edu                   **
//  **          WWW: http://http.icsi.berkeley.edu/~storn/        **
//  **          on leave from                                     **
//  **          Siemens AG, ZFE T SN 2, Otto-Hahn Ring 6          **
//  **          D-81739 Muenchen, Germany                         **
//  **          Tel:    636-40502                                 **
//  **          Fax:    636-44577                                 **
//  **          E-mail: rainer.storn@zfe.siemens.de               **
//  **                                                            **
//  **          Kenneth Price                                     **
//  **          836 Owl Circle                                    **
//  **          Vacaville, CA 95687                               **
//  **          E-mail: kprice@solano.community.net               **
//  **                                                            **
//  ** This program implements some variants of Differential      **
//  ** Evolution (DE) as described in part in the techreport      **
//  ** tr-95-012.ps of ICSI. You can get this report either via   **
//  ** ftp.icsi.berkeley.edu/pub/techreports/1995/tr-95-012.ps.Z  **
//  ** or via WWW: http://http.icsi.berkeley.edu/~storn/litera.html*
//  ** A more extended version of tr-95-012.ps is submitted for   **
//  ** publication in the Journal Evolutionary Computation.       **
//  **                                                            **
//  ** You may use this program for any purpose, give it to any   **
//  ** person or change it according to your needs as long as you **
//  ** are referring to Rainer Storn and Ken Price as the origi-  **
//  ** nators of the the DE idea.                                 **
//  ** If you have questions concerning DE feel free to contact   **
//  ** us. We also will be happy to know about your experiences   **
//  ** with DE and your suggestions of improvement.               **
//  **                                                            **
//  ***************************************************************/
//
// Differential Evolution Solver Class
// Based on algorithms developed by Dr. Rainer Storn & Kenneth Price
// Written By: Lester E. Godwin
//             PushCorp, Inc.
//             Dallas, Texas
//             972-840-0208 x102
//             godwin@pushcorp.com
// Created: 6/8/98
// Last Modified: 6/8/98
// Revision: 1.0
//

#include "sherpa/MersenneTwister.h"

#include "Opt.hh"
#include "Simplex.hh"

namespace sherpa {

  template <typename Func, typename Data, typename Algo, typename real>
  class DifEvo {

  public:
    typedef DifEvo<Func, Data, Algo, real> MyDifEvo;
    typedef void (MyDifEvo::*StrategyFuncPtr)(int, real, real, int,
                                              const sherpa::Simplex &,
                                              const ParVal<real> &, MTRand &,
                                              ParVal<real> &);

    enum Strategy {
                   Best1Exp,
                   Rand1Exp,
                   RandToBest1Exp,
                   Best2Exp,
                   Rand2Exp,
                   Best1Bin,
                   Rand1Bin,
                   RandToBest1Bin,
                   Best2Bin,
                   Rand2Bin
    };

    DifEvo(Func func, Data xdata)
      : usr_func(func), usr_data(xdata), local_opt(func, xdata),
        strategy_func_ptr(0) {}

    DifEvo(Func func, Data xdata, int num)
      : usr_func(func), usr_data(xdata), local_opt(func, xdata, num),
        strategy_func_ptr(0) {}

    // DifEvo( Func func, Data xdata, int mfct )
    //   : Opt<Data, real>( xdata ), usr_func( func ), usr_data(xdata),
    //     local_opt( func, xdata, mfct ), strategy_func_ptr( 0 ) { }

    int operator()(int verbose, int maxnfev, real tol, int population_size,
                   int seed, real cross_over_probability, real scale_factor,
                   const sherpa::Bounds<real> &bounds, int npar, ParVal<real> &par,
                   int &nfev) {

      int ierr = EXIT_SUCCESS;
      nfev = 0;

      try {

        if (bounds.are_pars_outside_limits(npar, par))
          throw sherpa::OptErr(sherpa::OptErr::OutOfBound);
        ierr = difevo(verbose, maxnfev, tol, population_size, seed,
                      cross_over_probability, scale_factor, bounds, npar, par,
                      nfev);

      } catch (OptErr &oe) {

        if (verbose)
          std::cerr << oe << '\n';
        ierr = oe.err;

      } catch (std::runtime_error &re) {

        if (verbose)
          std::cerr << re.what() << '\n';
        ierr = OptErr::Unknown;

      } catch (std::exception &e) {

        if (verbose)
          std::cerr << e.what() << '\n';
        ierr = OptErr::Unknown;
      }

      return ierr;
    }

  private:
    Func usr_func;
    Data usr_data;
    Algo local_opt;
    StrategyFuncPtr strategy_func_ptr;

    void choose_strategy(int strategy) {

      switch (strategy) {
      case Best1Exp:
        // strategy DE0 not in the paper
        strategy_func_ptr = &DifEvo<Func, Data, Algo, real>::best1exp;
        break;
      case Rand1Exp:
        // strategy DE1 in the techreport
        strategy_func_ptr = &DifEvo<Func, Data, Algo, real>::rand1exp;
        break;
      case RandToBest1Exp:
        // similar to DE2 but generally better
        strategy_func_ptr = &DifEvo<Func, Data, Algo, real>::randtobest1exp;
        break;
      case Best2Exp:
        // is another powerful strategy worth trying
        strategy_func_ptr = &DifEvo<Func, Data, Algo, real>::best2exp;
        break;
      case Rand2Exp:
        // seems to be a robust optimizer for many functions
        strategy_func_ptr = &DifEvo<Func, Data, Algo, real>::rand2exp;
        break;
      case Best1Bin:
        // Essentially same strategies but BINOMIAL CROSSOVER
        strategy_func_ptr = &DifEvo<Func, Data, Algo, real>::best1bin;
        break;
      case Rand1Bin:
        // Essentially same strategies but BINOMIAL CROSSOVER
        strategy_func_ptr = &DifEvo<Func, Data, Algo, real>::rand1bin;
        break;
      case RandToBest1Bin:
        // Essentially same strategies but BINOMIAL CROSSOVER
        strategy_func_ptr = &DifEvo<Func, Data, Algo, real>::randtobest1bin;
        break;
      case Best2Bin:
        // Essentially same strategies but BINOMIAL CROSSOVER
        strategy_func_ptr = &DifEvo<Func, Data, Algo, real>::best2bin;
        break;
      case Rand2Bin:
        // Essentially same strategies but BINOMIAL CROSSOVER
        strategy_func_ptr = &DifEvo<Func, Data, Algo, real>::rand2bin;
        break;
      default:
        strategy_func_ptr = &DifEvo<Func, Data, Algo, real>::best1exp;
        break;
      }

      //
      // Choice of strategy
      // We have tried to come up with a sensible naming-convention: DE/x/y/z
      // DE :  stands for Differential Evolution
      // x  :  a string which denotes the vector to be perturbed
      // y  :  number of difference vectors taken for perturbation of x
      // z  :  crossover method (exp = exponential, bin = binomial)
      //
      // There are some simple rules which are worth following:
      // 1)  F is usually between 0.5 and 1 (in rare cases > 1)
      // 2)  CR is between 0 and 1 with 0., 0.3, 0.7 and 1. being worth to be
      //     tried first
      // 3)  To start off NP = 10*D is a reasonable choice. Increase NP if
      //     misconvergence happens.
      // 4)  If you increase NP, F usually has to be decreased
      // 5)  When the DE/best... schemes fail DE/rand... usually works and
      //     vice versa
      //
    }

    int difevo(int verbose, int maxnfev, real tol, int population_size, int seed,
               real cross_over_probability, real scale_factor,
               const sherpa::Bounds<real> &bounds, int npar, ParVal<real> &par,
               int &nfev) {

      int ierr = EXIT_SUCCESS;
      par[npar] = std::numeric_limits<real>::max();
      population_size = std::abs(population_size);

      MTRand mt_rand(seed);

      //
      // For each row of the 2D-array population and children:
      // the columns [ 0, npar - 1 ] contain the parameters, and
      // the column npar contains the function values:
      // (*usrfunc)( population(ii,0), ... population(ii,npar-1) ) =
      //                                                   population(ii,npar);
      //
      // The array shall have dimension: population(population_size, npar + 1)
      //
      // Will use the class Simplex since it has all the the infrastructure
      // that is needed to check for convergence although it is not a simplex
      // in the classic sense.
      //
      const Array1D<real> &low = bounds.get_lb();
      const Array1D<real> &high = bounds.get_ub();
      Simplex population(population_size, npar);
      for (int ii = 0; ii < population_size; ++ii) {
        for (int jj = 0; jj < npar; ++jj)
          population[ii][jj] =
            low[jj] + (high[jj] - low[jj]) * mt_rand.randDblExc();
        population[ii][npar] = std::numeric_limits<real>::max();
      }

      //
      // allocate an extra element to store the function value
      //
      ParVal<real> trial_solution(npar);
      const real tol_sqr = tol * tol;
      const int simplex_tst = 0;

      ierr = local_opt.minimize(maxnfev - nfev, tol, bounds, npar, par, par[npar], nfev);
      if (EXIT_SUCCESS != ierr)
        return ierr;

      for (; nfev < maxnfev;) {

        for (int candidate = 0; candidate < population_size && nfev < maxnfev;
             ++candidate) {

          trial_solution = population[candidate];

          for (int strategy = 0; strategy < 10; ++strategy) {

            choose_strategy(strategy);

            (this->*strategy_func_ptr)(candidate, cross_over_probability,
                                       scale_factor, npar, population, par,
                                       mt_rand, trial_solution);

            local_opt.eval_func(maxnfev, bounds, npar, trial_solution, nfev);

            if (trial_solution < population[candidate]) {

              population[candidate] = trial_solution;

              if (trial_solution < par) {

                ierr = local_opt.minimize(maxnfev - nfev, tol, bounds, npar,
                                          trial_solution, trial_solution[npar],
                                          nfev);
                if (EXIT_SUCCESS != ierr)
                  return ierr;

                par = trial_solution;

                if (verbose > 0)
                  std::cout << par << '\n';

              } // if ( trial_solution[ npar ] < par[ npar ] ) {

              population.sort();
              if (population.check_convergence(tol, tol_sqr, simplex_tst))
                return EXIT_SUCCESS;

            } // if ( trial_solution[ npar ] < population( ...

          } // for ( int strategy = 0; strategy < 10; ++strategy )

        } // for ( int candidate=0; candidate < population_size &&

      } // for ( ; nfev < maxnfev; )

      return ierr;

    } // difevo

    //
    // EXPONENTIAL CROSSOVER
    //
    // DE/best/1/exp
    // Our oldest strategy but still not bad. However, we have found several
    // optimization problems where misconvergence occurs.
    //
    void best1exp(int candidate, real xprob, real sfactor, int npar,
                  const sherpa::Simplex &population, const ParVal<real> &par,
                  MTRand &mt_rand, ParVal<real> &trial_solution) {

      int r1, r2;
      select_samples(candidate, population.nrows(), mt_rand, &r1, &r2);
      int n = mt_rand.randInt(npar - 1);
      for (int ii = 0; mt_rand.rand() < xprob && ii < npar; ++ii) {
        trial_solution[n] =
          par[n] + sfactor * (population[r1][n] - population[r2][n]);
        n = (n + 1) % npar;
      }

      return;
    }

    //
    // DE/rand/1/exp
    // This is one of my favourite strategies. It works especially well when
    // the "bestit[]"-schemes experience misconvergence. Try e.g.
    // F=0.7 and CR=0.5 as a first guess.
    //
    void rand1exp(int candidate, real xprob, real sfactor, int npar,
                  const sherpa::Simplex &population, const ParVal<real> &par,
                  MTRand &mt_rand, ParVal<real> &trial_solution) {

      int r1, r2, r3;
      select_samples(candidate, population.nrows(), mt_rand, &r1, &r2, &r3);
      int n = mt_rand.randInt(npar - 1);
      for (int ii = 0; mt_rand.rand() < xprob && ii < npar; ++ii) {
        trial_solution[n] = population[r1][n] +
          +sfactor * (population[r2][n] - population[r3][n]);
        n = (n + 1) % npar;
      }

      return;
    }

    //
    // DE/rand-to-best/1/exp
    // This strategy seems to be one of the best strategies. Try F=0.85 and
    // CR=1. If you get misconvergence try to increase NP. If this doesn't
    // help you should play around with all three control variables.
    //
    void randtobest1exp(int candidate, real xprob, real sfactor, int npar,
                        const sherpa::Simplex &population,
                        const ParVal<real> &par, MTRand &mt_rand,
                        ParVal<real> &trial_solution) {

      int r1, r2;
      select_samples(candidate, population.nrows(), mt_rand, &r1, &r2);
      int n = mt_rand.randInt(npar - 1);
      for (int ii = 0; mt_rand.rand() < xprob && ii < npar; ++ii) {
        trial_solution[n] += sfactor * (par[n] - trial_solution[n]) +
          sfactor * (population[r1][n] - population[r2][n]);
        n = (n + 1) % npar;
      }

      return;
    }

    void best2exp(int candidate, real xprob, real sfactor, int npar,
                  const sherpa::Simplex &population, const ParVal<real> &par,
                  MTRand &mt_rand, ParVal<real> &trial_solution) {

      int r1, r2, r3, r4;
      select_samples(candidate, population.nrows(), mt_rand, &r1, &r2, &r3, &r4);
      int n = mt_rand.randInt(npar - 1);
      for (int ii = 0; mt_rand.rand() < xprob && ii < npar; ++ii) {
        trial_solution[n] = par[n] +
          sfactor * (population[r1][n] + population[r2][n] -
                     -population[r3][n] - population[r4][n]);
        n = (n + 1) % npar;
      }

      return;
    }

    void rand2exp(int candidate, real xprob, real sfactor, int npar,
                  const sherpa::Simplex &population, const ParVal<real> &par,
                  MTRand &mt_rand, ParVal<real> &trial_solution) {

      int r1, r2, r3, r4, r5;
      select_samples(candidate, population.nrows(), mt_rand, &r1, &r2, &r3, &r4,
                     &r5);
      int n = mt_rand.randInt(npar - 1);
      for (int ii = 0; mt_rand.rand() < xprob && ii < npar; ++ii) {
        trial_solution[n] = population[r1][n] +
          sfactor * (population[r2][n] + population[r3][n] -
                     population[r4][n] - population[r5][n]);
        n = (n + 1) % npar;
      }

      return;
    }

    void best1bin(int candidate, real xprob, real sfactor, int npar,
                  const sherpa::Simplex &population, const ParVal<real> &par,
                  MTRand &mt_rand, ParVal<real> &trial_solution) {

      int r1, r2;
      select_samples(candidate, population.nrows(), mt_rand, &r1, &r2);
      int n = mt_rand.randInt(npar - 1);
      for (int ii = 0; ii < npar; ++ii) {
        if (mt_rand.rand() < xprob || npar - 1 == ii)
          trial_solution[n] =
            par[n] + sfactor * (population[r1][n] - population[r2][n]);
        n = (n + 1) % npar;
      }

      return;
    }

    void rand1bin(int candidate, real xprob, real sfactor, int npar,
                  const sherpa::Simplex &population, const ParVal<real> &par,
                  MTRand &mt_rand, ParVal<real> &trial_solution) {

      int r1, r2, r3;
      select_samples(candidate, population.nrows(), mt_rand, &r1, &r2, &r3);
      int n = mt_rand.randInt(npar - 1);
      for (int ii = 0; ii < npar; ++ii) {
        if (mt_rand.rand() < xprob || npar - 1 == ii)
          trial_solution[n] = population[r1][n] +
            sfactor * (population[r2][n] - population[r3][n]);
        n = (n + 1) % npar;
      }

      return;
    }

    void randtobest1bin(int candidate, real xprob, real sfactor, int npar,
                        const sherpa::Simplex &population,
                        const ParVal<real> &par, MTRand &mt_rand,
                        ParVal<real> &trial_solution) {

      int r1, r2;
      select_samples(candidate, population.nrows(), mt_rand, &r1, &r2);
      int n = mt_rand.randInt(npar - 1);
      for (int ii = 0; ii < npar; ++ii) {
        if (mt_rand.rand() < xprob || npar - 1 == ii)
          trial_solution[n] += sfactor * (par[n] - trial_solution[n]) +
            sfactor * (population[r1][n] - population[r2][n]);
        n = (n + 1) % npar;
      }

      return;
    }

    void best2bin(int candidate, real xprob, real sfactor, int npar,
                  const sherpa::Simplex &population, const ParVal<real> &par,
                  MTRand &mt_rand, ParVal<real> &trial_solution) {

      int r1, r2, r3, r4;
      select_samples(candidate, population.nrows(), mt_rand, &r1, &r2, &r3, &r4);
      int n = mt_rand.randInt(npar - 1);
      for (int ii = 0; ii < npar; ++ii) {
        if (mt_rand.rand() < xprob || npar - 1 == ii)
          trial_solution[n] = par[n] +
            sfactor * (population[r1][n] + population[r2][n] -
                       population[r3][n] - population[r4][n]);
        n = (n + 1) % npar;
      }

      return;
    }

    void rand2bin(int candidate, real xprob, real sfactor, int npar,
                  const sherpa::Simplex &population, const ParVal<real> &par,
                  MTRand &mt_rand, ParVal<real> &trial_solution) {

      int r1, r2, r3, r4, r5;
      select_samples(candidate, population.nrows(), mt_rand, &r1, &r2, &r3, &r4,
                     &r5);
      int n = mt_rand.randInt(npar - 1);
      for (int ii = 0; ii < npar; ++ii) {
        // perform npar binomial trials
        if (mt_rand.rand() < xprob || npar - 1 == ii)
          trial_solution[n] = population[r1][n] +
            sfactor * (population[r2][n] + population[r3][n] -
                       population[r4][n] - population[r5][n]);
        n = (n + 1) % npar;
      }

      return;
    }

    static void select_samples(int candidate, int npop, MTRand &mt_rand, int *r1,
                               int *r2 = 0, int *r3 = 0, int *r4 = 0,
                               int *r5 = 0) {
      if (r1) {
        do {
          *r1 = mt_rand.randInt(npop - 1);
        } while (*r1 == candidate);
      }

      if (r2) {
        do {
          *r2 = mt_rand.randInt(npop - 1);
        } while ((*r2 == candidate) || (*r2 == *r1));
      }

      if (r3) {
        do {
          *r3 = mt_rand.randInt(npop - 1);
        } while ((*r3 == candidate) || (*r3 == *r2) || (*r3 == *r1));
      }

      if (r4) {
        do {
          *r4 = mt_rand.randInt(npop - 1);
        } while ((*r4 == candidate) || (*r4 == *r3) || (*r4 == *r2) ||
                 (*r4 == *r1));
      }

      if (r5) {
        do {
          *r5 = mt_rand.randInt(npop - 1);
        } while ((*r5 == candidate) || (*r5 == *r4) || (*r5 == *r3) ||
                 (*r5 == *r2) || (*r5 == *r1));
      }

      return;
    }

  }; // class DifEvo

} // namespace

#endif
