13#include <unordered_map>
20struct AdjacencyMatrix;
37template<
typename State>
39 std::is_class_v<State>
and
40 std::is_class_v<typename State::base_type>
and
41 std::movable<State>
and
42 requires (
const State &S,
const State *parent) {
43 { S.g() } -> std::convertible_to<double>;
44 { S.decrease_g(parent,
double(0)) } -> std::same_as<void>;
48template<
typename State,
typename... Context>
50 requires (
const State &S, Context&... ctx) {
51 { State::num_partitions(ctx...) } -> std::convertible_to<unsigned>;
52 { S.partition_id(ctx...) } -> std::convertible_to<unsigned>;
58template<
typename Heuristic,
typename... Context>
61 requires (Heuristic &H,
const typename Heuristic::state_type &S, Context&... ctx) {
62 { H.operator()(S, ctx...) } -> std::convertible_to<double>;
65template<
typename Heurisitc>
71template<
typename Search,
typename... Context>
75 std::same_as<typename Search::state_type, typename Search::heuristic_type::state_type>
and
78 typename Search::state_type state,
79 typename Search::heuristic_type &heuristic,
80 typename Search::expand_type ex,
83 { search.search(state, ex, heuristic, ctx...) };
103 static_assert(HasRegularQueue or HasBeamQueue,
"must have at least one kind of queue");
124#define DEF_COUNTER(NAME) \
126 std::size_t num_##NAME##_ = 0; \
127 void inc_##NAME() { ++num_##NAME##_; } \
129 std::size_t num_##NAME() const { return num_##NAME##_; }
131#define DEF_COUNTER(NAME) \
132 void inc_##NAME() { } \
133 std::size_t num_##NAME() const { return 0; }
156 typename heap_type::handle_type handle;
158 StateInfo() =
delete;
159 StateInfo(
double h,
heap_type *queue) : h(h), queue(queue) { }
160 StateInfo(
const StateInfo&) =
delete;
161 StateInfo(StateInfo&&) =
default;
162 StateInfo & operator=(StateInfo&&) =
default;
169 std::hash<state_type>,
170 std::equal_to<state_type>,
171 typename Config::template allocator_type<map_value_type>
181 template<
bool Partition>
197 std::cout <<
"\n " << P.size();
198 std::cout << std::endl;
204 return partitions_[state.partition_id(context...)];
208 return partitions_[state.partition_id(context...)];
249 std::size_t
size()
const {
return states_.size(); }
300 M_insist(new_factor >= 0.f,
"factor must not be negative");
301 M_insist(new_factor != 0.f,
"factor must not be 0, use heuristic zero for Dijkstra`s search algorithm");
309 std::cout <<
"found cheaper path with least_path_cost = " << cost << std::endl;
313 template<
bool ToBeamQueue>
315 static_assert(not ToBeamQueue or HasBeamQueue,
"ToBeamQueue implies HasBeamQueue");
316 static_assert(ToBeamQueue or HasRegularQueue,
"not ToBeamQueue implies HasRegularQueue");
328 const auto min_path_cost = [&]() {
333 return state.g() + h;
339 inc_pruned_by_cost();
341 }
else if (expand_type::is_goal(state, context...)) [[unlikely]] {
350 if (
auto it = P.find(state); it == P.end()) [[likely]] {
352 it = P.emplace_hint(it, std::move(state), StateInfo(h, &Q));
354 it->second.handle = Q.push(&*it);
358 M_insist(it->second.h == h,
"must not have a different heuristic value for the same state");
363 if constexpr (HasRegularQueue)
364 it->second.queue->erase(it->second.handle);
365 if (state.g() < it->first.g())
367 it->first.decrease_g(state.parent(), state.g());
371 if constexpr (HasRegularQueue)
372 inc_regular_to_beam();
375 }
else if (state.g() >= it->first.g()) [[likely]] {
381 M_insist(state.g() < it->first.g(),
"the state was reached on a cheaper path");
383 it->first.decrease_g(state.parent(), state.g());
384 if (it->second.queue ==
nullptr) {
386 it->second.handle = Q.push(&*it);
387 it->second.queue = &Q;
390 M_insist(it->second.queue == &Q,
"the state must already be in its destinated queue");
391 Q.increase(it->second.handle);
397 const auto new_g = state.g();
398 auto [it, res] = P.try_emplace(std::move(state), StateInfo(h, &Q));
404 if (new_g < it->first.g())
412 if constexpr (HasRegularQueue) {
413 push<false>(std::move(state), h, context...);
425 const auto min_path_cost = [&]() {
430 return state.g() + h;
436 inc_pruned_by_cost();
438 }
else if (expand_type::is_goal(state, context...)) [[unlikely]] {
445 if (
auto it = P.find(state); it == P.end()) {
447 it = P.emplace_hint(it, std::move(state), StateInfo(h, &
regular_queue_));
453 M_insist(it->second.h == h,
"must not have a different heuristic value for the same state");
455 if (state.g() < it->first.g()) {
457 it->first.decrease_g(state.parent(), state.g());
459 it->second.queue->increase(it->second.handle);
468 push<not HasRegularQueue>(std::move(state), h, context...);
473 push<true>(std::move(state), h, context...);
480 std::pair<const state_type&, double>
pop() {
490 M_insist(ptr,
"ptr must have been set, the queues must not have been empty");
492 auto &entry = *
static_cast<typename map_type::value_type*
>(ptr);
493 entry.second.queue =
nullptr;
494 return { entry.first, entry.second.h };
498 return partition(state, context...).find(state);
500 typename map_type::iterator
end(
const state_type &state, Context&... context) {
501 return partition(state, context...).end();
503 typename map_type::const_iterator
find(
const state_type &state, Context&... context)
const {
504 return partition(state, context...).find(state);
506 typename map_type::const_iterator
end(
const state_type &state, Context&... context)
const {
507 return partition(state, context...).end();
512 if constexpr (HasRegularQueue)
514 if constexpr (HasBeamQueue)
521#define X(NAME) num_##NAME() << " " #NAME
522 out <<
X(
new)
", " <<
X(duplicates)
", ";
523 if (HasRegularQueue
and HasBeamQueue)
524 out <<
X(regular_to_beam)
", ";
526 out <<
X(none_to_beam)
", ";
527 out <<
X(discarded)
", " <<
X(cheaper)
", " <<
X(decrease_key)
", " <<
X(pruned_by_cost);
542 << SM.
beam_queue_.size() <<
" currently in beam queue";
546 void dump(std::ostream &out)
const { out << *
this << std::endl; }
551 heuristic_search_state State,
554 bool HasRegularQueue,
562 auto left =
static_cast<typename map_type::value_type*
>(p_left);
563 auto right =
static_cast<typename map_type::value_type*
>(p_right);
565 return left->first.g() + left->second.h > right->first.g() + right->second.h;
568template<
typename Config>
570 std::is_class_v<Config>
and
571 requires { { Config::PerformCostBasedPruning } -> std::convertible_to<bool>; }
and
572 requires { { Config::PerformWeightedSearch } -> std::convertible_to<bool>; }
and
573 std::integral<
decltype(Config::BeamWidth::num)>
and
574 std::integral<
decltype(Config::BeamWidth::den)>
and
575 requires { { Config::Lazy } -> std::convertible_to<bool>; }
and
576 requires { { Config::IsMonotone } -> std::convertible_to<bool>; }
and
577 requires { { Config::PerformAnytimeSearch} -> std::convertible_to<bool>; };
581template<SearchConfigConcept StaticConfig>
607template<
typename state_type,
typename... Context>
608concept has_mark =
requires (
state_type state, Context... context) { state.reset_marked(state, context...); };
628 static constexpr bool use_weighted_search = StaticConfig::PerformWeightedSearch;
630 static constexpr float beam_width = StaticConfig::BeamWidth::num / StaticConfig::BeamWidth::den;
632 static constexpr bool use_beam_search = StaticConfig::BeamWidth::num != 0;
634 static constexpr bool use_dynamic_beam_sarch = use_beam_search
and beam_width < 1.f;
636 static constexpr bool is_lazy = StaticConfig::Lazy;
638 static constexpr bool is_monotone = StaticConfig::IsMonotone;
640 static constexpr float BEAM_FACTOR = .2f;
642 static constexpr bool use_anytime_search = StaticConfig::PerformAnytimeSearch;
650 not (use_beam_search
and is_monotone),
658 float weighting_factor_ = 1.;
661#define DEF_COUNTER(NAME) \
663 std::size_t num_##NAME##_ = 0; \
664 void inc_##NAME() { ++num_##NAME##_; } \
666 std::size_t num_##NAME() const { return num_##NAME##_; }
668#define DEF_COUNTER(NAME) \
669 void inc_##NAME() { } \
670 std::size_t num_##NAME() const { return 0; }
678 struct weighted_state
683 weighted_state(
state_type state,
double h) : state(std::move(state)), h(h) { }
684 weighted_state(
const weighted_state&) =
delete;
685 weighted_state(weighted_state&&) =
default;
686 weighted_state & operator=(weighted_state&&) =
default;
688 bool operator<(
const weighted_state &other)
const {
return this->weight() < other.weight(); }
689 bool operator>(
const weighted_state &other)
const {
return this->weight() > other.weight(); }
690 bool operator<=(
const weighted_state &other)
const {
return this->weight() <= other.weight(); }
691 bool operator>=(
const weighted_state &other)
const {
return this->weight() >= other.weight(); }
694 double weight()
const {
return state.g() + h; }
706 : state_manager_(context...)
708 if constexpr (use_beam_search) {
709 if constexpr (not use_dynamic_beam_sarch)
710 candidates.reserve(beam_width + 1);
726 M_insist(new_factor >= 0.f,
"factor must not be negative");
727 M_insist(new_factor != 0.f,
"weight must not be 0, use heuristic zero for Dijkstra`s search algorithm");
728 return std::exchange(weighting_factor_, new_factor);
736 const State & search(state_type initial_state, expand_type expand, heuristic_type &heuristic,
741 state_manager_.
clear();
753 auto &top = candidates.front();
755 M_insist(std::is_heap(candidates.begin(), candidates.end()),
"candidates must always be a max-heap");
756 for (
auto &elem : candidates)
757 M_insist(top >= elem,
"the top candidate at the front must be no less than any other candidate");
759 if (candidates.size() < beam_width) {
761 candidates.emplace_back(std::move(state), h);
762 if (candidates.size() == beam_width)
763 std::make_heap(candidates.begin(), candidates.end());
764 }
else if (state.g() + h >= top.state.g() + top.h) {
770 M_insist(candidates.size() == beam_width);
771 M_insist(std::is_heap(candidates.begin(), candidates.end()));
772 candidates.emplace_back(std::move(state), h);
773 std::pop_heap(candidates.begin(), candidates.end());
774 weighted_state worst_candidate = std::move(candidates.back());
775 candidates.pop_back();
776 M_insist(std::is_heap(candidates.begin(), candidates.end()));
777 M_insist(candidates.size() == beam_width);
779 for (
auto &elem : candidates)
780 M_insist(worst_candidate >= elem,
"worst candidate must be no less than any other candidate");
782 state_manager_.
push_regular_queue(std::move(worst_candidate.state), worst_candidate.h, context...);
788 std::sort(candidates.begin(), candidates.end());
789 const std::size_t num_beamed = std::ceil(candidates.size() * BEAM_FACTOR);
790 M_insist(not candidates.size() or num_beamed,
"if the state has successors, at least one must be in the beam");
791 auto it = candidates.begin();
793 for (
auto end = it + num_beamed; it != end; ++it) {
795 expand.reset_marked(it->state, context...);
796 state_manager_.
push_beam_queue(std::move(it->state), it->h, context...);
799 for (
auto end = candidates.end(); it != end; ++it)
808 double h_current_state;
809 if (
auto it = state_manager_.
find(state, context...); it == state_manager_.
end(state, context...)) {
810 if constexpr (use_weighted_search)
811 h_current_state = weighting_factor() * heuristic(state, context...);
813 h_current_state = heuristic(state, context...);
815 inc_cached_heuristic_value();
816 h_current_state = it->second.h;
818 expand(state, [&callback, h_current_state](
state_type successor) {
819 callback(std::move(successor), h_current_state);
828 expand(state, [
this, callback=std::move(callback), &state, &heuristic, &context...](
state_type successor) {
829 if (auto it = state_manager_.find(successor, context...); it == state_manager_.end(state, context...)) {
830 if constexpr (use_weighted_search) {
831 const double h = weighting_factor() * heuristic(successor, context...);
832 callback(std::move(successor), h);
834 const double h = heuristic(successor, context...);
835 callback(std::move(successor), h);
838 inc_cached_heuristic_value();
839 callback(std::move(successor), it->second.h);
848 if constexpr (is_lazy)
849 for_each_successor_lazily(std::move(callback), state, heuristic, expand, context...);
851 for_each_successor_eagerly(std::move(callback), state, heuristic, expand, context...);
856 if constexpr (use_dynamic_beam_sarch) {
859 for_each_successor([
this](
state_type successor,
double h) {
860 candidates.emplace_back(std::move(successor), h);
861 }, state, heuristic, expand, context...);
862 beam_dynamic(expand, context...);
863 }
else if constexpr (use_beam_search) {
866 for_each_successor([
this, &context...](
state_type successor,
double h) {
867 beam(std::move(successor), h, context...);
868 }, state, heuristic, expand, context...);
870 for (
auto &s : candidates) {
872 expand.reset_marked(s.state, context...);
873 state_manager_.push_beam_queue(std::move(s.state), s.h, context...);
877 for_each_successor([
this, &context...](
state_type successor,
double h) {
878 state_manager_.push_regular_queue(std::move(successor), h, context...);
879 }, state, heuristic, expand, context...);
885 return out << AStar.
state_manager_ <<
", used cached heuristic value " << AStar.num_cached_heuristic_value()
889 void dump(std::ostream &out)
const { out << *
this << std::endl; }
894 heuristic_search_state State,
897 SearchConfigConcept StaticConfig,
900requires heuristic_search_heuristic<Heuristic, Context...>
908 if constexpr (use_weighted_search) {
914 if constexpr (StaticConfig::PerformCostBasedPruning) {
919 state_manager_.update_least_path_cost(
920 std::nextafter(config.
upper_bound, std::numeric_limits<double>::infinity())
927 if constexpr (use_anytime_search) {
941 state_manager_.template push<use_beam_search and is_monotone>(std::move(initial_state), 0, context...);
944 while (not state_manager_.queues_empty()
and have_budget()) {
945 M_insist(not (is_monotone
and use_beam_search) or not state_manager_.is_beam_queue_empty(),
946 "the beam queue must not run empty with beam search on a monotone search space");
947 auto top = state_manager_.pop();
950 if (expand_type::is_goal(state, context...))
953 explore_state(state, heuristic, expand, context...);
956 throw std::logic_error(
"goal state unreachable from provided initial state");
#define DEF_COUNTER(NAME)
and(sizeof(T)==4) U64x1 reinterpret_to_U64(m
static Options & Get()
Return a reference to the single Options instance.
Implements a small and efficient set over integers in the range of 0 to 63 (including).
Relies on the rules of aggregate initialization
OptField< StaticConfig::PerformCostBasedPruning, std::vector< std::pair< Subproblem, Subproblem > > > initial_plan
Initial plan for the query.
SearchConfiguration()=default
OptField< StaticConfig::PerformCostBasedPruning, double > upper_bound
Upper bound on the cost of plans to consider.
OptField< StaticConfig::PerformAnytimeSearch, uint64_t > expansion_budget
Budget for the maximum number of expansions.
SearchConfiguration(SearchConfiguration &&)=default
OptField< StaticConfig::PerformWeightedSearch, float > weighting_factor
The weighting factor for the heuristic.
const map_type & operator()(const state_type &, Context &...) const
Partitions(const Partitions &)=delete
map_type & operator()(const state_type &, Context &...)
Partitions(Partitions &&)=default
std::vector< map_type > partitions_
partition_const_iterator end() const
partition_const_reverse_iterator rend() const
partition_reverse_iterator rend()
partition_const_iterator cbegin() const
partition_const_reverse_iterator crend() const
partition_const_iterator cend() const
partition_const_reverse_iterator rbegin() const
Partitions(const Partitions &)=delete
Partitions(Partitions &&)=default
partition_iterator begin()
partition_reverse_iterator rbegin()
partition_const_iterator begin() const
partition_const_reverse_iterator crbegin() const
Partitions(Context &... context)
map_type & operator()(const state_type &state, Context &... context)
const map_type & operator()(const state_type &state, Context &... context) const
comparator for map entries based on states' g + h value
bool operator()(pointer_type p_left, pointer_type p_right) const
Tracks states and their presence in queues.
bool is_regular_queue_empty() const
void update_least_path_cost(double cost)
Update the least_path_cost to cost.
StateManager & operator=(StateManager &&)=default
(new) DEF_COUNTER(duplicates) DEF_COUNTER(regular_to_beam) DEF_COUNTER(none_to_beam) DEF_COUNTER(discarded) DEF_COUNTER(cheaper) DEF_COUNTER(decrease_key) DEF_COUNTER(pruned_by_cost) public std::pair< const state_type, StateInfo > map_value_type
partition_const_reverse_iterator partitions_crbegin() const
partition_reverse_iterator partitions_rbegin()
void push_regular_queue(state_type state, double h, Context &... context)
StateManager(StateManager &&)=default
partition_const_iterator partitions_begin() const
bool queues_empty() const
map_type::iterator end(const state_type &state, Context &... context)
heap_type regular_queue_
map of all states ever explored, mapping state to its info
partition_const_reverse_iterator partitions_rbegin() const
typename std::vector< map_type >::const_reverse_iterator partition_const_reverse_iterator
partition_reverse_iterator partitions_rend()
bool is_beam_queue_empty() const
StateManager(Context &... context)
std::size_t num_states_seen() const
void dump(std::ostream &out) const
partition_iterator partitions_end()
static constexpr bool use_weighted_search
range< partition_iterator > partitions()
static constexpr bool detect_duplicates
partition_const_reverse_iterator partitions_rend() const
typename std::vector< map_type >::const_iterator partition_const_iterator
double least_path_cost
the cost of the cheapest, complete path found yet; can be used for additional pruning
partition_iterator partitions_begin()
partition_const_reverse_iterator partitions_crend() const
static constexpr bool has_regular_queue
partition_const_iterator partitions_end() const
partition_const_iterator partitions_cend() const
map_type::const_iterator end(const state_type &state, Context &... context) const
range< partition_const_iterator > partitions() const
typename Config::template heap_type< pointer_type, typename Config::template compare< comparator > > heap_type
the type of heap to implement the priority queues
static constexpr bool has_beam_queue
map_type & partition(const state_type &state, Context &... context)
StateManager(const StateManager &)=delete
Partitions< supports_partitioning< State, Context... > > partitions_
map of all states ever explored, mapping state to its info; partitioned by state partition id
typename std::vector< map_type >::reverse_iterator partition_reverse_iterator
typename std::vector< map_type >::iterator partition_iterator
std::unordered_map< state_type, StateInfo, std::hash< state_type >, std::equal_to< state_type >, typename Config::template allocator_type< map_value_type > > map_type
const map_type & partition(const state_type &state, Context &... context) const
float weighting_factor() const
Returns the weighting factor for the heuristic value.
void * pointer_type
type for a pointer to an entry in the map of states
std::size_t num_states_in_regular_queue() const
float weighting_factor_
the weighting factor for the heuistic value of a state
partition_const_iterator partitions_cbegin() const
map_type::const_iterator find(const state_type &state, Context &... context) const
void push(state_type state, double h, Context &... context)
friend std::ostream & operator<<(std::ostream &out, const StateManager &SM)
std::size_t num_states_in_beam_queue() const
void print_counters(std::ostream &out) const
static constexpr bool enable_cost_based_pruning
map_type::iterator find(const state_type &state, Context &... context)
float weighting_factor(float new_factor)
Set the weighting factor for the heuristic value and returns the old value.
std::pair< const state_type &, double > pop()
void push_beam_queue(state_type state, double h, Context &... context)
budget_exhausted_exception(std::string message)
Implements a generic A* search algorithm.
void explore_state(const state_type &state, heuristic_type &heuristic, expand_type &expand, Context &... context)
Explores the given state.
std::function< void(state_type, double)> callback_t
genericAStar(genericAStar &&)=default
const state_manager_type & state_manager() const
float weighting_factor() const
Returns the weighting factor for the heuristic value.
friend std::ostream & operator<<(std::ostream &out, const genericAStar &AStar)
genericAStar & operator=(genericAStar &&)=default
float weighting_factor(float new_factor)
Set the weighting factor for the heuristic value and returns the old value.
void for_each_successor_lazily(callback_t &&callback, const state_type &state, heuristic_type &heuristic, expand_type &expand, Context &... context)
Expands the given state by lazily evaluating the heuristic function.
const State & search(state_type initial_state, expand_type expand, heuristic_type &heuristic, const SearchConfiguration< StaticConfig > &config, Context &... context)
Search for a path from the given initial_state to a goal state.
std::vector< weighted_state > candidates
candidates for the beam
DEF_COUNTER(cached_heuristic_value) struct weighted_state
A state plus its heuristic value.
StaticConfig static_search_config
void beam(state_type state, double h, Context &... context)
state_manager_type state_manager_
the search's state manager, tracking heuristic value, checking for duplicates, and ordering by f-val...
void dump(std::ostream &out) const
void for_each_successor_eagerly(callback_t &&callback, const state_type &state, heuristic_type &heuristic, expand_type &expand, Context &... context)
Expands the given state by eagerly evaluating the heuristic function.
void for_each_successor(callback_t &&callback, const state_type &state, heuristic_type &heuristic, expand_type &expand, Context &... context)
Expands the given state according to is_lazy.
void beam_dynamic(expand_type &expand, Context &... context)
genericAStar(const genericAStar &)=delete
void clear()
Resets the state of the search.
genericAStar(Context &... context)