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
98template <typename T>
99concept SearchMove = Aleph::ArrayStorable<T> and std::copyable<T>;
100
102template <SearchMove Move>
104
105using SearchClock = std::chrono::steady_clock;
106
107[[nodiscard]] inline double search_elapsed_ms(const SearchClock::duration &duration) noexcept
108{
109 return std::chrono::duration<double, std::milli>(duration).count();
110}
111
119template <typename Key,
120 typename Data,
121 template <typename, typename, class> class HashMapTable = MapOLhash,
124
131template <typename Key,
132 template <typename, class> class HashSetTable = OLhashTable,
135
145template <typename Set, typename Key>
146concept VisitedStateSet = requires(Set &set, const Key &key) {
147 { set.contains(key) } -> std::convertible_to<bool>;
148 set.insert(key);
149};
150
165template <typename Map, typename Key, typename Objective>
166concept VisitedBoundMap = requires(Map &map, const Key &key, const Objective &obj) {
167 map.insert(key, obj);
168 map.remove(key);
169 { map.search(key) == nullptr } -> std::convertible_to<bool>;
170 map.search(key)->second = obj;
171};
172
193template <typename Domain>
195 requires(Domain &domain,
196 const typename Domain::State &state,
197 const typename Domain::Move &move) {
198 { domain.bound_after(state, move) }
199 -> std::convertible_to<typename Domain::Objective>;
200 };
201
218template <typename Domain>
220 requires(Domain &domain,
221 const typename Domain::State &state,
222 const typename Domain::Move &move) {
223 { domain.evaluate_after(state, move) }
224 -> std::convertible_to<typename Domain::Score>;
225 };
226
245template <typename Engine>
246concept SupportsBestFirst = Engine::supports_best_first;
247
264
278
279template <SearchMove Move>
280inline void reserve_search_path(SearchPath<Move> &path, const SearchLimits &limits)
281{
282 if (limits.max_depth != Search_Unlimited)
283 path.reserve(limits.max_depth + 1);
284}
285
318
328template <SearchState State, SearchMove Move>
330{
331 State state;
333 size_t depth = 0;
334};
335
343template <typename Solution>
345{
346 bool operator()(const Solution &, const Solution &) const noexcept
347 {
348 return false;
349 }
350};
351
354{
360 template <typename Solution>
361 bool operator()(const Solution &) const noexcept
362 {
363 return true;
364 }
365};
366
372template <typename Visitor, typename Solution>
373concept SearchSolutionVisitor = requires(Visitor &visitor, const Solution &solution) {
374 { visitor(solution) } -> std::convertible_to<bool>;
375};
376
385template <typename Solution>
387{
388public:
390 using Solution_Type = Solution;
393
396
400 explicit SearchSolutionCollector(const size_t max_solutions) : limit_(max_solutions)
401 {
403 << "SearchSolutionCollector: max_solutions must be positive";
404 }
405
407 bool operator()(const Solution &solution)
408 {
409 solutions_.append(solution);
410 ++count_;
411 return count_ < limit_;
412 }
413
416 {
418 count_ = 0;
419 }
420
423 {
424 return count_;
425 }
426
429 {
430 return count_ == 0;
431 }
432
435 {
436 return solutions_;
437 }
438
441 {
442 return solutions_;
443 }
444
445private:
447 size_t count_ = 0;
449};
450
459template <typename Solution, typename Compare = KeepFirstSolution<Solution>>
462{
463public:
465 using Solution_Type = Solution;
467 using Compare_Type = Compare;
468
470 BestSolution() = default;
471
475 explicit BestSolution(Compare compare) : compare_(std::move(compare))
476 {
477 // empty
478 }
479
482 {
483 return current_.has_value();
484 }
485
488 {
489 current_.reset();
490 }
491
495 [[nodiscard]] const Solution &get() const
496 {
498 << "BestSolution::get: no incumbent solution available";
499 return *current_;
500 }
501
505 [[nodiscard]] Solution &get()
506 {
508 << "BestSolution::get: no incumbent solution available";
509 return *current_;
510 }
511
513 [[nodiscard]] const Compare &compare() const noexcept
514 {
515 return compare_;
516 }
517
521 bool consider(const Solution &candidate)
522 {
523 if (not current_.has_value() or compare_(candidate, *current_))
524 {
526 return true;
527 }
528
529 return false;
530 }
531
535 bool consider(Solution &&candidate)
536 {
537 if (not current_.has_value() or compare_(candidate, *current_))
538 {
539 current_ = std::move(candidate);
540 return true;
541 }
542
543 return false;
544 }
545
546private:
547 Compare compare_ = {};
548 std::optional<Solution> current_;
549};
550
552template <typename Solution, typename Compare = KeepFirstSolution<Solution>>
554
563template <typename Domain>
564concept SuccessorGenerator = requires(Domain &d, const typename Domain::State &state) {
565 typename Domain::State;
566 typename Domain::Move;
569 {
570 d.for_each_successor(state,
571 [](const typename Domain::Move &) -> bool
572 {
573 return true;
574 })
575 } -> std::convertible_to<bool>;
576};
577
593template <typename Domain>
594concept GoalPredicate = requires(Domain &d, const typename Domain::State &state) {
595 { d.is_goal(state) } -> std::convertible_to<bool>;
596};
597
599template <typename Domain>
600concept TerminalPredicate = requires(const Domain &d, const typename Domain::State &state) {
601 { d.is_terminal(state) } -> std::convertible_to<bool>;
602};
603
610template <typename Domain>
611concept DomainPruner = requires(Domain &d, const typename Domain::State &state, const size_t depth) {
612 { d.should_prune(state, depth) } -> std::convertible_to<bool>;
613};
614
616template <typename Domain>
617concept MoveKeyProvider = requires(Domain &d, const typename Domain::Move &move) {
618 typename Domain::Move_Key;
619 { d.move_key(move) } -> std::convertible_to<typename Domain::Move_Key>;
620};
621
626template <typename Provider>
627concept SearchStateKeyProvider = requires(const Provider &provider, const typename Provider::State &state) {
628 typename Provider::State_Key;
629 { provider.state_key(state) } -> std::convertible_to<typename Provider::State_Key>;
630};
631
638template <typename Domain>
639concept HeuristicEvaluator = requires(Domain &d, const typename Domain::State &state) {
640 typename Domain::Distance;
641 { d.heuristic(state) } -> std::convertible_to<typename Domain::Distance>;
642};
643
649template <typename Domain>
651 = requires(Domain &d, const typename Domain::State &state, const typename Domain::Move &move) {
652 typename Domain::Distance;
653 { d.cost(state, move) } -> std::convertible_to<typename Domain::Distance>;
654 };
655
667template <typename Domain>
670and requires(Domain &d, typename Domain::State &state, const typename Domain::Move &move) {
671 { d.apply(state, move) } -> std::same_as<void>;
672 { d.undo(state, move) } -> std::same_as<void>;
673 };
674
680template <typename Solution, typename Compare = KeepFirstSolution<Solution>>
732
740namespace search_engine_detail {
741
752template <typename Domain>
753[[nodiscard]] bool is_terminal_state(const Domain &domain,
754 const typename Domain::State &state)
755{
756 if constexpr (TerminalPredicate<Domain>)
757 return domain.is_terminal(state);
758 else
759 return false;
760}
761
773template <typename Domain>
775 const typename Domain::State &state,
776 const size_t depth)
777{
778 if constexpr (DomainPruner<Domain>)
779 return domain.should_prune(state, depth);
780 else
781 return false;
782}
783
792template <typename Result>
793void register_visit(const size_t depth, Result &result)
794{
795 ++result.stats.visited_states;
796 if (depth > result.stats.max_depth_reached)
797 result.stats.max_depth_reached = depth;
798}
799
812template <typename Result>
813[[nodiscard]] bool expansion_limit_reached(Result &result, const SearchLimits &limits)
814{
815 if (result.stats.expanded_states >= limits.max_expansions)
816 {
817 ++result.stats.limit_hits;
818 result.status = SearchStatus::LimitReached;
819 return true;
820 }
821
822 return false;
823}
824
845template <typename Result>
846[[nodiscard]] bool stop_after_solution(Result &result,
847 const ExplorationPolicy &policy,
848 const SearchLimits &limits)
849{
850 if (limits.max_solutions != Search_Unlimited
851 and result.stats.solutions_found >= limits.max_solutions)
852 {
853 ++result.stats.limit_hits;
854 result.status = SearchStatus::LimitReached;
855 return true;
856 }
857
858 if (policy.stop_at_first_solution)
859 {
860 result.status = SearchStatus::StoppedOnSolution;
861 return true;
862 }
863
864 return false;
865}
866
867} // end namespace search_engine_detail
868
869} // end namespace Aleph
870
871# endif // STATE_SEARCH_COMMON_H
C++20 concepts hub: comparison, BST policy, and Aleph container concepts.
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:255
#define ah_invalid_argument_if(C)
Throws std::invalid_argument if condition holds.
Definition ah-errors.H:644
void reserve(size_t cap)
Reserves cap cells into the array.
Definition tpl_array.H:320
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.
An element type the Aleph arrays can store.
Minimal contract for DFS/backtracking domains.
A callable that takes two const T& and returns bool.
Definition ah-concepts.H:98
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.
size_t blossom_maximum_cardinality_matching(const GT &g, DynDlist< typename GT::Arc * > &matching, SA sa=SA())
Alias of compute_maximum_cardinality_general_matching().
Definition Blossom.H:466
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
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)
and
Check uniqueness with explicit hash + equality functors.
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.