// METSlib source file - tabu-search.hh                          -*- C++ -*-
/*
 * Software License Agreement (BSD License)
 *
 *  Copyright (c) 2006-2012, Mirko Maischberger <mirko.maischberger@gmail.com>
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 *
 */

#ifndef METS_TABU_SEARCH_HH_
#define METS_TABU_SEARCH_HH_

namespace mets {

  /// @defgroup tabu_search Tabu Search
  /// @{

  /// @brief Function object expressing an
  /// aspiration criteria
  ///
  /// An aspiration criteria is a criteria used
  /// to override the tabu list. When the aspiration
  /// criteria is met a move is made even if it's in 
  /// the tabu-list
  ///
  /// Aspiration critera can be chained so a criteria can decorate
  /// another criteria
  class aspiration_criteria_chain
  {
  public:
    /// @brief Constructor.
    /// 
    /// @param next Optional next criteria in the chain.
    explicit
    aspiration_criteria_chain(aspiration_criteria_chain *next = nullptr)
      : next_m(next) 
    { }
    
    /// purposely not implemented (see Effective C++)
    aspiration_criteria_chain(const aspiration_criteria_chain& other);
    /// purposely not implemented (see Effective C++)
    aspiration_criteria_chain& 
    operator=(const aspiration_criteria_chain& other);

    /// @brief Virtual destructor.
    virtual 
    ~aspiration_criteria_chain() = default;

    /// @brief A method to reset this aspiration criteria chain to its
    /// original state.
    virtual void 
    reset();

    /// @brief This is a callback function from the algorithm that
    /// tells us that a move was accepted.
    ///
    /// You can use this function to update the aspiration criteria
    /// based on the current search status. (e.g. record the best cost
    /// for a best ever criteria)
    ///
    /// @param fs The current working solution (after applying move).
    /// @param mov The accepted move (the move just made).
    /// @return True if the move is to be accepted.
    virtual void 
    accept(feasible_solution& fs, move& mov, gol_type evaluation);
    
    /// @brief The function that decides if we shoud accept a tabu move
    ///
    /// @param fs The current working solution (before applying move).
    /// @param mov The move to be made (the move that is being evaluated).
    /// @return True if the move is to be accepted.
    virtual bool 
    operator()(feasible_solution& fs, move& mov, gol_type evaluation) const;
    
  protected:
    aspiration_criteria_chain* next_m;
  };

  ///
  /// @brief An abstract tabu list
  /// 
  /// This is chainable so that tabu lists can be decorated with
  /// other tabu lists.
  class tabu_list_chain
  {
  public:
    tabu_list_chain();
    /// purposely not implemented (see Effective C++)
    tabu_list_chain(const tabu_list_chain&);
    /// purposely not implemented (see Effective C++)
    tabu_list_chain& operator=(const tabu_list_chain&);

    /// Create an abstract tabu list with a certain tenure
    explicit
    tabu_list_chain(unsigned int tenure) 
      : next_m(nullptr), tenure_m(tenure) 
    { }

    /// @brief Create an abstract tabu list with a certain tenure and
    /// a chained tabu list that decorates this one
    tabu_list_chain(tabu_list_chain* next, unsigned int tenure) 
      : next_m(next), tenure_m(tenure) 
    { }

    /// @brief Virtual destructor
    virtual 
    ~tabu_list_chain() = default;

    ///
    /// @brief Make a move tabu when starting from a certain solution.
    /// 
    /// Different implementation can remember "tenure" moves, 
    /// "tenure" solutions or some other peculiar fact that
    /// will avoid cycling.
    ///
    /// Mind you! The solution here is the solution *before* applying
    /// the move: this is for efficiency reason.
    ///
    /// @param sol The current working solution
    /// @param mov The move to make tabu
    virtual void 
    tabu(feasible_solution& sol, move& mov) = 0;

    /// @brief True if the move is tabu for the given solution.
    ///
    /// Different implementation can remember "tenure" moves, 
    /// "tenure" solutions or some other peculiar fact that
    /// will avoid cycling. So it's not defined at this stage 
    /// if a move will be tabu or not at a certain state of the
    /// search: this depends on the implementation.
    ///
    /// Mind you! The solution here is the solution *before* applying
    /// the move: this is for efficiency reason.
    ///
    /// @param sol The current working solution
    /// @param mov The move to make tabu
    virtual bool 
    is_tabu(feasible_solution& sol, move& mov) const = 0;

    ///
    /// @brief Tenure of this tabu list.
    ///
    /// The tenure is the length of the tabu-list (the order of the
    /// tabu memory)
    ///
    virtual unsigned int
    tenure() const
    { return tenure_m; }

    ///
    /// @brief Tenure of this tabu list.
    ///
    /// @param tenure: the new tenure of the list.
    ///
    virtual void
    tenure(unsigned int tenure) 
    { tenure_m = tenure; }

  protected:
    tabu_list_chain* next_m;
    unsigned int tenure_m;
  };
  
  ///
  /// @brief Tabu Search algorithm.
  ///
  /// This implements decorator pattern. You can build many
  /// different solvers decorating tabu_search class in different
  /// ways.
  /// 
  template<typename move_manager_type>
  class tabu_search : public abstract_search<move_manager_type>
  {
  public:
    using search_type = tabu_search<move_manager_type>;
    /// @brief Creates a tabu Search instance.
    ///
    /// @param starting_solution  The working solution (this
    /// will be modified during search).
    ///
    /// @param best_recorder A solution recorder used to record the
    /// best solution found during the search.
    ///
    /// @param move_manager_inst A problem specific implementation of the
    /// move_manager_type used to generate the neighborhood.
    ///
    /// @param tabus The tabu list used to decorate this search
    /// instance.
    ///
    /// @param aspiration The aspiration criteria to use in this tabu
    /// search.
    ///
    /// @param termination The termination criteria used to terminate the
    /// search process, this is an extension to the standard Simulated
    /// Annealing: you can give a termination criteria that termiantes
    /// when temperature reaches 0.
    ///
    tabu_search(feasible_solution& starting_solution, 
		solution_recorder& best_recorder, 
		move_manager_type& move_manager_inst,
		tabu_list_chain& tabus,
		aspiration_criteria_chain& aspiration,
		termination_criteria_chain& termination);

    tabu_search(const search_type&);
    search_type& operator=(const search_type&);

    virtual 
    ~tabu_search() = default;

    /// @brief This method starts the tabu search process.
    /// 
    /// Remember that this is a minimization process.
    ///
    /// An exception mets::no_moves_error is risen when no move
    /// is possible.
    void 
    search();
    
    enum {
      ASPIRATION_CRITERIA_MET = abstract_search<move_manager_type>::LAST,
      LAST
    };

    /// @brief The tabu list used by this tabu search
    const tabu_list_chain& 
    get_tabu_list() const { return tabu_list_m; }
    
    /// @brief The aspiration criteria used by this tabu search
    const aspiration_criteria_chain& 
    get_aspiration_criteria() const { return aspiration_criteria_m; }

    /// @brief The termination criteria used by this tabu search
    const termination_criteria_chain& 
    get_termination_criteria() const { return termination_criteria_m; }
  protected:
    tabu_list_chain& tabu_list_m;
    aspiration_criteria_chain& aspiration_criteria_m;
    termination_criteria_chain& termination_criteria_m;
  };

  /// @brief Simplistic implementation of a tabu-list.
  ///
  /// This class implements one of the simplest and less
  /// memory hungry tabu lists. This tabu list memorizes
  /// only the moves (not the solutions).
  ///
  /// Moves must be of mets::mana_move type.
  ///
  /// The comparison between moves is demanded to the
  /// move implementation.
  ///
  /// A mets::mana_move is tabu if it's in the tabu list by means 
  /// of its operator== and hash function.
  class simple_tabu_list 
    : public tabu_list_chain
  {
  public:
    /// @brief Ctor. Makes a tabu list of the specified tenure.
    ///
    /// @param tenure Tenure (length) of the tabu list
    simple_tabu_list(unsigned int tenure) 
      : tabu_list_chain(tenure), 
	tabu_moves_m(), 
	tabu_hash_m(tenure) {}

    /// @brief Ctor. Makes a tabu list of the specified tenure.
    ///
    /// @param tenure Tenure (length) of the tabu list
    /// @param next Next list to invoke when this returns false
    simple_tabu_list(tabu_list_chain* next, unsigned int tenure) 
      : tabu_list_chain(next, tenure), 
	tabu_moves_m(), 
	tabu_hash_m(tenure) {}

    /// @brief Destructor
    ~simple_tabu_list() override;

    /// @brief Make move a tabu.
    /// 
    /// This implementation simply remembers "tenure" moves.
    ///
    /// @param sol The current working solution
    /// @param mov The move to make tabu
    void
    tabu(feasible_solution& sol, /* const */ move& mov) override;

    /// @brief True if the move is tabu for the given solution.
    ///
    /// This implementation considers tabu each move already made less
    /// then tenure() moves ago.
    ///
    /// @param sol The current working solution
    /// @param mov The move to make tabu
    /// @return True if this move was already made during the last
    /// tenure iterations
    bool
    is_tabu(feasible_solution& sol, move& mov) const override;

  protected:
    using move_list_type = std::deque<move *>;
#if defined (METSLIB_TR1_BOOST)
    using move_map_type = boost::unordered_map<mana_move *, int, mana_move_hash, dereferenced_equal_to<mana_move *> >;
#elif defined (METSLIB_HAVE_UNORDERED_MAP) && !defined (METSLIB_TR1_MIXED_NAMESPACE)
    typedef std::unordered_map<
      mana_move*, // Key type
      int, //insert a move and the number of times it's present in the list
      mana_move_hash, 
      dereferenced_equal_to<mana_move*> > move_map_type;
#else
    typedef std::tr1::unordered_map<
      mana_move*, // Key type
      int, //insert a move and the number of times it's present in the list
      mana_move_hash, 
      dereferenced_equal_to<mana_move*> > move_map_type;
#endif    
    move_list_type tabu_moves_m;
    move_map_type tabu_hash_m;
  };

  /// @brief Aspiration criteria implementation.
  ///
  /// This is one of the best known aspiration criteria
  /// ready to be used in your tabu-search implementation.
  ///
  /// This aspiration criteria is met when a tabu move would result in
  /// a global improvement.
  class best_ever_criteria : public aspiration_criteria_chain
  {
  public:
    explicit
    best_ever_criteria(double min_improvement = 1e-6);

    explicit
    best_ever_criteria(aspiration_criteria_chain* next,
		       double min_improvement = 1e-6);
    
    void 
    reset() override;

    void
    accept(feasible_solution& fs, move& mov, gol_type evaluation) override;

    bool 
    operator()(feasible_solution& fs, move& mov, gol_type evaluation) const override;

  protected:
    gol_type best_m;
    gol_type tolerance_m;
  };
  
  /// @}
}

template<typename move_manager_t>
mets::tabu_search<move_manager_t>::
tabu_search (feasible_solution& starting_solution, 
	     solution_recorder& best_recorder, 
	     move_manager_t& move_manager_inst,
	     tabu_list_chain& tabus,
	     aspiration_criteria_chain& aspiration,
	     termination_criteria_chain& termination)
  : abstract_search<move_manager_t>(starting_solution, 
				    best_recorder, 
				    move_manager_inst),
    tabu_list_m(tabus),
    aspiration_criteria_m(aspiration),
    termination_criteria_m(termination)
{}

template<typename move_manager_t>
void mets::tabu_search<move_manager_t>::search()
{
  using base_t = abstract_search<move_manager_t>;
  while(!termination_criteria_m(base_t::working_solution_m))
    {
      // call listeners
      base_t::step_m = base_t::ITERATION_BEGIN;
      this->notify();

      base_t::moves_m.refresh(base_t::working_solution_m);
      
      typename move_manager_t::iterator best_movit = base_t::moves_m.end(); 
      gol_type best_move_cost = std::numeric_limits<gol_type>::max();
      
      for(typename move_manager_t::iterator movit = base_t::moves_m.begin(); 
	  movit != base_t::moves_m.end(); ++movit)
	{
	  // evaluate proposed move
	  gol_type cost = (*movit)->evaluate(base_t::working_solution_m);
	  
	  // save tabu status
	  bool is_tabu = tabu_list_m.is_tabu(base_t::working_solution_m, 
						 **movit);

	  // for each non-tabu move record the best one
	  if(cost < best_move_cost)
	    {
	      
	      bool aspiration_criteria_met = false;
	      
	      // not interesting if this is not a tabu move (and if we
	      // are not improving over other moves)
	      if(is_tabu) 
		{
		  aspiration_criteria_met = 
		    aspiration_criteria_m(base_t::working_solution_m, 
					  **movit,
					  cost);
		}
	      
	      if(!is_tabu || aspiration_criteria_met)
		{
		  best_move_cost = cost;
		  best_movit = base_t::current_move_m = movit;
		  if(aspiration_criteria_met)
		    {
		      base_t::step_m = ASPIRATION_CRITERIA_MET;
		      this->notify();
		    }
		}
	    }
	} // end for each move
      
      if(best_movit == base_t::moves_m.end())
	throw no_moves_error();

      // make move tabu
      tabu_list_m.tabu(base_t::working_solution_m, **best_movit);

      // do the best non tabu move (unless overridden by aspiration
      // criteria, of course)
      (*best_movit)->apply(base_t::working_solution_m);

      // call listeners
      base_t::step_m = base_t::MOVE_MADE;
      this->notify();
      
      aspiration_criteria_m.accept(base_t::working_solution_m, 
				   **best_movit, 
				   best_move_cost);
      
      if(base_t::solution_recorder_m.accept(base_t::working_solution_m))
	{
	  base_t::step_m = base_t::IMPROVEMENT_MADE;
	  this->notify();
	}

      // call listeners
      base_t::step_m = base_t::ITERATION_END;
      this->notify();
      
    } // end while(!termination)
}

// chain of responsibility

inline void
mets::tabu_list_chain::tabu(feasible_solution& sol, /* const */ move& mov)
{
  if(next_m)
    next_m->tabu(sol, mov);
}

inline bool
mets::tabu_list_chain::is_tabu(feasible_solution& sol, /* const */ move& mov) const
{
  if(next_m) 
    return next_m->is_tabu(sol, mov);
  else 
    return false;
}

inline mets::simple_tabu_list::~simple_tabu_list()
{ 
  // Disable lint for third-party code
  // NOLINTNEXTLINE(modernize-loop-convert)
  for(move_map_type::iterator m = tabu_hash_m.begin();
      m!=tabu_hash_m.end(); ++m)
    delete m->first;
}

inline void
mets::simple_tabu_list::tabu(feasible_solution& sol, /* const */ move& mov)
{
  mana_move* mc = 
    dynamic_cast<mana_move&>(mov).opposite_of();

  // This does nothing if the move was already tabu (can happen when
  // aspiration criteria is met).
  std::pair<move_map_type::iterator, bool> 
    insert_result = tabu_hash_m.insert(std::make_pair(mc, 1));

  // If it was already in the map, increase the counter
  if(!insert_result.second) 
    {
      insert_result.first->second++;
      delete mc;
      mc = insert_result.first->first;
    }
  // Always add the move at the end of the list (when aspiration
  // criteria is met a move can be present more than one time in this
  // list: this is correct, so the last made move is always the last
  // in the queue). 
  tabu_moves_m.push_back(mc);

  // Since we use the hash size, the tenure is the number of different
  // moves on the list (the tabu_moves_m can have more than tenure
  // elements)
  while(tabu_hash_m.size() > this->tenure())
    {
      // update hash map *and* list structures
      move_map_type::iterator elem = tabu_hash_m.find
	(dynamic_cast<mana_move*>(tabu_moves_m.front()));
      elem->second--;
      if(elem->second == 0) 
	{
	  mana_move* tmp = elem->first;
	  tabu_hash_m.erase(elem);
	  delete tmp;
	}
      tabu_moves_m.pop_front();
    }
  tabu_list_chain::tabu(sol, mov);
}

inline bool
mets::simple_tabu_list::is_tabu(feasible_solution& sol, move& mov) const
{
  // hash set. very fast but requires C++ ISO TR1 extension
  // and an hash function in every move (Omega(1)).
  bool tabu = (tabu_hash_m.find(&dynamic_cast<mana_move&>(mov)) 
	       != tabu_hash_m.end());

  if(tabu)
    return true;

  return tabu_list_chain::is_tabu(sol, mov);
}

//////////////////////////////////////////////////////////////////////////
// aspiration_criteria_chain
inline void 
mets::aspiration_criteria_chain::reset()
{
  if(next_m)
    return next_m->reset();
}

inline void 
mets::aspiration_criteria_chain::accept(feasible_solution& fs, 
					move& mov,
					gol_type eval)
{
  if(next_m) next_m->accept(fs, mov, eval);
}

inline bool 
mets::aspiration_criteria_chain::operator()(feasible_solution& fs, 
					    move& mov,
					    gol_type eval) const
{
  if(next_m)
    return next_m->operator()(fs, mov, eval);
  else
    return false;
}

//////////////////////////////////////////////////////////////////////////
// best_ever_criteria
inline mets::best_ever_criteria::best_ever_criteria(double tolerance) 
  : aspiration_criteria_chain(),
    best_m(std::numeric_limits<gol_type>::max()),
    tolerance_m(tolerance)
{ }

inline mets::best_ever_criteria::best_ever_criteria(aspiration_criteria_chain* next, double tolerance) 
  : aspiration_criteria_chain(next),
    best_m(std::numeric_limits<gol_type>::max()),
    tolerance_m(tolerance)
{ }
    
inline void 
mets::best_ever_criteria::reset()
{
  best_m = std::numeric_limits<mets::gol_type>::max();
  aspiration_criteria_chain::reset();
}

inline void
mets::best_ever_criteria::accept(feasible_solution& fs, 
				 move& mov, 
				 gol_type eval) 
{
  best_m = std::min(dynamic_cast<const evaluable_solution&>(fs).cost_function(), best_m);
  aspiration_criteria_chain::accept(fs, mov, eval);
}  

inline bool 
mets::best_ever_criteria::operator()(feasible_solution& fs, 
				     move& mov, 
				     gol_type eval) const
{ 
  /// the solution is the solution before applying mov.
  if(eval < best_m - tolerance_m)
    return true;
  else
    return aspiration_criteria_chain::operator()(fs, mov, eval); 
}

#endif
