640 lines
19 KiB
C++
Raw Permalink Normal View History

// 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 = 0)
: 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()
{ }
/// @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(0), 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()
{ }
///
/// @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() {}
/// @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();
/// @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);
/// @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;
protected:
using move_list_type = std::deque<move *>;
#if defined (METSLIB_TR1_BOOST)
typedef boost::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;
#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();
void
accept(feasible_solution& fs, move& mov, gol_type evaluation);
bool
operator()(feasible_solution& fs, move& mov, gol_type evaluation) const;
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()
{
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