Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
state_search_common.H
Go to the documentation of this file.
1/*
2 Aleph_w
3
4 Data structures & Algorithms
5 version 2.0.0b
6 https://github.com/lrleon/Aleph-w
7
8 This file is part of Aleph-w library
9
10 Copyright (c) 2002-2026 Leandro Rabindranath Leon
11
12 Permission is hereby granted, free of charge, to any person obtaining a copy
13 of this software and associated documentation files (the "Software"), to deal
14 in the Software without restriction, including without limitation the rights
15 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 copies of the Software, and to permit persons to whom the Software is
17 furnished to do so, subject to the following conditions:
18
19 The above copyright notice and this permission notice shall be included in all
20 copies or substantial portions of the Software.
21
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 SOFTWARE.
29*/
30
49# ifndef STATE_SEARCH_COMMON_H
50# define STATE_SEARCH_COMMON_H
51
52#include <chrono>
53#include <concepts>
54#include <cstddef>
55#include <limits>
56#include <optional>
57#include <type_traits>
58#include <utility>
59
60#include <ah-concepts.H>
61#include <ah-errors.H>
62#include <htlist.H>
64#include <tpl_array.H>
65#include <tpl_hash.H>
66
67namespace Aleph {
68
70inline constexpr size_t Search_Unlimited = std::numeric_limits<size_t>::max();
71
80
87template <typename T>
88concept SearchState = std::movable<T> and std::copy_constructible<T>;
89
95template <typename T>
96concept SearchMove = std::movable<T> and std::copy_constructible<T>;
97
99template <SearchMove Move>
101
102using SearchClock = std::chrono::steady_clock;
103
104[[nodiscard]] inline double search_elapsed_ms(const SearchClock::duration &duration) noexcept
105{
106 return std::chrono::duration<double, std::milli>(duration).count();
107}
108
116template <typename Key,
117 typename Data,
118 template <typename, typename, class> class HashMapTable = MapOLhash,
121
128template <typename Key,
129 template <typename, class> class HashSetTable = OLhashTable,
132
142template <typename Set, typename Key>
143concept VisitedStateSet = requires(Set &set, const Key &key) {
144 { set.contains(key) } -> std::convertible_to<bool>;
145 set.insert(key);
146};
147
162template <typename Map, typename Key, typename Objective>
163concept VisitedBoundMap = requires(Map &map, const Key &key, const Objective &obj) {
164 map.insert(key, obj);
165 map.remove(key);
166 { map.search(key) == nullptr } -> std::convertible_to<bool>;
167 map.search(key)->second = obj;
168};
169
190template <typename Domain>
192 requires(Domain &domain,
193 const typename Domain::State &state,
194 const typename Domain::Move &move) {
195 { domain.bound_after(state, move) }
196 -> std::convertible_to<typename Domain::Objective>;
197 };
198
215template <typename Domain>
217 requires(Domain &domain,
218 const typename Domain::State &state,
219 const typename Domain::Move &move) {
220 { domain.evaluate_after(state, move) }
221 -> std::convertible_to<typename Domain::Score>;
222 };
223
242template <typename Engine>
243concept SupportsBestFirst = Engine::supports_best_first;
244
261
275
276template <SearchMove Move>
277inline void reserve_search_path(SearchPath<Move> &path, const SearchLimits &limits)
278{
279 if (limits.max_depth != Search_Unlimited)
280 path.reserve(limits.max_depth + 1);
281}
282
315
325template <SearchState State, SearchMove Move>
327{
328 State state;
330 size_t depth = 0;
331};
332
340template <typename Solution>
342{
343 bool operator()(const Solution &, const Solution &) const noexcept
344 {
345 return false;
346 }
347};
348
351{
357 template <typename Solution>
358 bool operator()(const Solution &) const noexcept
359 {
360 return true;
361 }
362};
363
369template <typename Visitor, typename Solution>
370concept SearchSolutionVisitor = requires(Visitor &visitor, const Solution &solution) {
371 { visitor(solution) } -> std::convertible_to<bool>;
372};
373
382template <typename Solution>
384{
385public:
387 using Solution_Type = Solution;
390
393
397 explicit SearchSolutionCollector(const size_t max_solutions) : limit_(max_solutions)
398 {
400 << "SearchSolutionCollector: max_solutions must be positive";
401 }
402
404 bool operator()(const Solution &solution)
405 {
406 solutions_.append(solution);
407 ++count_;
408 return count_ < limit_;
409 }
410
413 {
415 count_ = 0;
416 }
417
420 {
421 return count_;
422 }
423
426 {
427 return count_ == 0;
428 }
429
432 {
433 return solutions_;
434 }
435
438 {
439 return solutions_;
440 }
441
442private:
444 size_t count_ = 0;
446};
447
456template <typename Solution, typename Compare = KeepFirstSolution<Solution>>
459{
460public:
462 using Solution_Type = Solution;
464 using Compare_Type = Compare;
465
467 BestSolution() = default;
468
472 explicit BestSolution(Compare compare) : compare_(std::move(compare))
473 {
474 // empty
475 }
476
479 {
480 return current_.has_value();
481 }
482
485 {
486 current_.reset();
487 }
488
492 [[nodiscard]] const Solution &get() const
493 {
495 << "BestSolution::get: no incumbent solution available";
496 return *current_;
497 }
498
502 [[nodiscard]] Solution &get()
503 {
505 << "BestSolution::get: no incumbent solution available";
506 return *current_;
507 }
508
510 [[nodiscard]] const Compare &compare() const noexcept
511 {
512 return compare_;
513 }
514
518 bool consider(const Solution &candidate)
519 {
520 if (not current_.has_value() or compare_(candidate, *current_))
521 {
523 return true;
524 }
525
526 return false;
527 }
528
532 bool consider(Solution &&candidate)
533 {
534 if (not current_.has_value() or compare_(candidate, *current_))
535 {
536 current_ = std::move(candidate);
537 return true;
538 }
539
540 return false;
541 }
542
543private:
544 Compare compare_ = {};
545 std::optional<Solution> current_;
546};
547
549template <typename Solution, typename Compare = KeepFirstSolution<Solution>>
551
560template <typename Domain>
561concept SuccessorGenerator = requires(Domain &d, const typename Domain::State &state) {
562 typename Domain::State;
563 typename Domain::Move;
566 {
567 d.for_each_successor(state,
568 [](const typename Domain::Move &) -> bool
569 {
570 return true;
571 })
572 } -> std::convertible_to<bool>;
573};
574
590template <typename Domain>
591concept GoalPredicate = requires(Domain &d, const typename Domain::State &state) {
592 { d.is_goal(state) } -> std::convertible_to<bool>;
593};
594
596template <typename Domain>
597concept TerminalPredicate = requires(const Domain &d, const typename Domain::State &state) {
598 { d.is_terminal(state) } -> std::convertible_to<bool>;
599};
600
607template <typename Domain>
608concept DomainPruner = requires(Domain &d, const typename Domain::State &state, const size_t depth) {
609 { d.should_prune(state, depth) } -> std::convertible_to<bool>;
610};
611
613template <typename Domain>
614concept MoveKeyProvider = requires(Domain &d, const typename Domain::Move &move) {
615 typename Domain::Move_Key;
616 { d.move_key(move) } -> std::convertible_to<typename Domain::Move_Key>;
617};
618
623template <typename Provider>
624concept SearchStateKeyProvider = requires(const Provider &provider, const typename Provider::State &state) {
625 typename Provider::State_Key;
626 { provider.state_key(state) } -> std::convertible_to<typename Provider::State_Key>;
627};
628
635template <typename Domain>
636concept HeuristicEvaluator = requires(Domain &d, const typename Domain::State &state) {
637 typename Domain::Distance;
638 { d.heuristic(state) } -> std::convertible_to<typename Domain::Distance>;
639};
640
646template <typename Domain>
648 = requires(Domain &d, const typename Domain::State &state, const typename Domain::Move &move) {
649 typename Domain::Distance;
650 { d.cost(state, move) } -> std::convertible_to<typename Domain::Distance>;
651 };
652
664template <typename Domain>
667and requires(Domain &d, typename Domain::State &state, const typename Domain::Move &move) {
668 { d.apply(state, move) } -> std::same_as<void>;
669 { d.undo(state, move) } -> std::same_as<void>;
670 };
671
677template <typename Solution, typename Compare = KeepFirstSolution<Solution>>
729
737namespace search_engine_detail {
738
749template <typename Domain>
750[[nodiscard]] bool is_terminal_state(const Domain &domain,
751 const typename Domain::State &state)
752{
753 if constexpr (TerminalPredicate<Domain>)
754 return domain.is_terminal(state);
755 else
756 return false;
757}
758
770template <typename Domain>
772 const typename Domain::State &state,
773 const size_t depth)
774{
775 if constexpr (DomainPruner<Domain>)
776 return domain.should_prune(state, depth);
777 else
778 return false;
779}
780
789template <typename Result>
790void register_visit(const size_t depth, Result &result)
791{
792 ++result.stats.visited_states;
793 if (depth > result.stats.max_depth_reached)
794 result.stats.max_depth_reached = depth;
795}
796
809template <typename Result>
810[[nodiscard]] bool expansion_limit_reached(Result &result, const SearchLimits &limits)
811{
812 if (result.stats.expanded_states >= limits.max_expansions)
813 {
814 ++result.stats.limit_hits;
815 result.status = SearchStatus::LimitReached;
816 return true;
817 }
818
819 return false;
820}
821
842template <typename Result>
843[[nodiscard]] bool stop_after_solution(Result &result,
844 const ExplorationPolicy &policy,
845 const SearchLimits &limits)
846{
847 if (limits.max_solutions != Search_Unlimited
848 and result.stats.solutions_found >= limits.max_solutions)
849 {
850 ++result.stats.limit_hits;
851 result.status = SearchStatus::LimitReached;
852 return true;
853 }
854
855 if (policy.stop_at_first_solution)
856 {
857 result.status = SearchStatus::StoppedOnSolution;
858 return true;
859 }
860
861 return false;
862}
863
864} // end namespace search_engine_detail
865
866} // end namespace Aleph
867
868# endif // STATE_SEARCH_COMMON_H
C++20 concepts for constraining comparison functors.
Exception handling system with formatted messages for Aleph-w.
#define ah_runtime_error_unless(C)
Throws std::runtime_error if condition does NOT hold.
Definition ah-errors.H:250
#define ah_invalid_argument_if(C)
Throws std::invalid_argument if condition holds.
Definition ah-errors.H:639
void reserve(size_t cap)
Reserves cap cells into the array.
Definition tpl_array.H:315
Tracks the best solution seen so far according to a comparator.
bool has_value() const noexcept
Return true if an incumbent exists.
Solution & get()
Mutable access to the current incumbent.
std::optional< Solution > current_
Compare Compare_Type
Type of the comparison policy.
bool consider(Solution &&candidate)
Consider a candidate by move.
const Compare & compare() const noexcept
Access the ordering functor used by this incumbent.
void clear() noexcept
Remove the stored incumbent, if any.
BestSolution(Compare compare)
Build a tracker with a custom comparator.
bool consider(const Solution &candidate)
Consider a candidate by copy.
BestSolution()=default
Build an empty incumbent tracker.
const Solution & get() const
Read the current incumbent.
Solution Solution_Type
Type of stored solutions.
T & append(const T &item)
Definition htlist.H:1271
void empty() noexcept
empty the list
Definition htlist.H:1389
Open addressing hash table with linear probing collision resolution.
Definition tpl_olhash.H:170
Collector that stores accepted solutions in an Aleph list.
bool operator()(const Solution &solution)
Append one solution and report whether enumeration should continue.
Container_Type & solutions() noexcept
Mutable access to the collected solutions.
size_t size() const noexcept
Number of collected solutions.
const Container_Type & solutions() const noexcept
Access the collected solutions.
bool is_empty() const noexcept
Return true if no solution has been collected.
SearchSolutionCollector(const size_t max_solutions)
Build a collector that stops after max_solutions.
void clear() noexcept
Remove all collected solutions.
Solution Solution_Type
Type of collected solutions.
SearchSolutionCollector()=default
Build a collector with no limit.
Concept for domains that provide the cost of applying a move.
Minimal contract for DFS/backtracking domains.
A callable that takes two const T& and returns bool.
Definition ah-concepts.H:67
Optional concept for domain-side pruning hooks.
Concept for domains that can recognize goal states.
Concept for domains that provide an admissible heuristic.
Concept for domains that can estimate a move's bound without modifying state (incremental bound for B...
Concept for adversarial-search domains that can estimate a child score without modifying state (incre...
Optional concept for domains that can key moves for history heuristics.
Minimal requirement for search moves.
Concept for callbacks invoked on each accepted solution.
Generic concept for domains that can expose a stable state key.
Minimal requirement for mutable search states.
Concept for lazy successor generation.
Concept that checks whether a search engine supports the Best_First exploration strategy at compile t...
Optional concept for explicit non-solution terminal states.
Concept for a map tracking the best bound seen per visited state.
Concept for a set suitable for tracking globally visited states.
Singly linked list implementations with head-tail access.
bool should_prune_state(Domain &domain, const typename Domain::State &state, const size_t depth)
Dispatch helper for the optional should_prune hook.
bool is_terminal_state(const Domain &domain, const typename Domain::State &state)
Dispatch helper for the optional is_terminal hook.
bool stop_after_solution(Result &result, const ExplorationPolicy &policy, const SearchLimits &limits)
Decide whether to stop the search after a solution was accepted.
void register_visit(const size_t depth, Result &result)
Update visit counters and max-depth statistic.
bool expansion_limit_reached(Result &result, const SearchLimits &limits)
Check whether the expansion limit has been reached.
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
and
Check uniqueness with explicit hash + equality functors.
std::chrono::steady_clock SearchClock
Monotonic clock used for search timings.
constexpr size_t Search_Unlimited
Sentinel used by SearchLimits to mean "no bound".
SearchStatus
Final state of a search execution.
@ LimitReached
Search stopped because an external hard limit was hit.
@ Exhausted
Search space within the configured bounds was exhausted.
@ StoppedOnSolution
Search stopped because solution handling requested it.
@ NotStarted
Search object exists but no traversal has run yet.
void reserve_search_path(SearchPath< Move > &path, const SearchLimits &limits)
Divide_Conquer_DP_Result< Cost > divide_and_conquer_partition_dp(const size_t groups, const size_t n, Transition_Cost_Fn transition_cost, const Cost inf=dp_optimization_detail::default_inf< Cost >())
Optimize partition DP using divide-and-conquer optimization.
double search_elapsed_ms(const SearchClock::duration &duration) noexcept
MoveOrderingMode
Built-in move-ordering modes supported by the framework.
@ Domain
Preserve the order emitted by for_each_successor().
STL namespace.
Shared support for configurable successor ordering heuristics.
Default solution visitor that always continues the search.
bool operator()(const Solution &) const noexcept
Accepts any solution and returns true.
Exploration controls shared across engines.
bool stop_at_first_solution
Stop when the first goal is found.
Strategy strategy
Traversal strategy.
MoveOrderingMode move_ordering
Successor-ordering mode.
Strategy
Traversal strategy supported by the engine.
@ Best_First
Priority-guided expansion; only supported by Branch_And_Bound.
@ Depth_First
Recursive depth-first traversal (all engines).
bool use_history_heuristic
Enable experimental history heuristic where supported.
bool use_killer_moves
Enable experimental killer heuristic where supported.
Comparison policy that keeps the first solution seen.
bool operator()(const Solution &, const Solution &) const noexcept
Open addressing hash map using linear probing.
Statistics collected by engines that reorder successor batches.
Hard bounds applied by the search engine.
size_t max_expansions
Maximum expanded states.
size_t max_solutions
Maximum accepted solutions.
size_t max_depth
Maximum expansion depth.
Aggregates the outcome of one search execution.
bool limit_reached() const noexcept
Return true if a hard search limit stopped the traversal.
SearchStats stats
Statistics collected during the run.
bool found_solution() const noexcept
Return true if at least one solution was retained.
bool stopped_on_solution() const noexcept
Return true if search stopped because solution handling requested it.
ExplorationPolicy policy
Exploration policy used for the run.
Solution Solution_Type
Type of solutions in this result.
SearchResult()=default
Build an empty result.
Compare Compare_Type
Type of the comparison policy.
bool exhausted() const noexcept
Return true if the search exhausted the configured region.
SearchLimits limits
Limits used for the run.
SearchStatus status
Final execution state.
Incumbent_Type best_solution
Best incumbent retained by the engine.
SearchResult(Compare compare)
Build a result with a specific solution comparator.
Snapshot of a concrete solution encountered during the traversal.
SearchPath< Move > path
Move sequence leading to state.
State state
Snapshot of the terminal state.
size_t depth
Path depth of the solution.
Counters collected during a search run.
size_t limit_hits
Number of hard-limit stops triggered.
size_t pruned_by_visited
States skipped because already seen (visited-set duplicate suppression).
size_t pruned_by_domain
States discarded by domain-side pruning.
size_t terminal_states
Non-solution terminal states cut by the domain.
double elapsed_ms
Wall-clock time spent inside the search call.
MoveOrderingStats move_ordering
Successor-ordering activity for this run.
size_t pruned_by_depth
States not expanded due to max depth.
size_t generated_successors
Number of successor moves emitted.
size_t max_depth_reached
Deepest path depth visited.
size_t solutions_found
Number of goal states accepted.
size_t expanded_states
Number of non-terminal states expanded.
size_t visited_states
Number of states entered by the engine.
Dynamic array container with automatic resizing.
Unified hash table interface.