Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
Branch_And_Bound.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
47# ifndef BRANCH_AND_BOUND_H
48# define BRANCH_AND_BOUND_H
49
50#include <concepts>
51#include <utility>
52
53#include <ah-errors.H>
54#include <state_search_common.H>
55#include <Transposition_Table.H>
56#include <tpl_dynBinHeap.H>
57
58namespace Aleph {
59
62{
63 Minimize,
65};
66
68template <std::totally_ordered Value>
70{
73
74 static bool better(const Value &candidate, const Value &incumbent) noexcept
75 {
76 return candidate > incumbent;
77 }
78
79 static bool can_improve(const Value &bound, const Value &incumbent) noexcept
80 {
81 return bound > incumbent;
82 }
83
84 static bool more_promising(const Value &lhs, const Value &rhs) noexcept
85 {
86 return lhs > rhs;
87 }
88};
89
91template <std::totally_ordered Value>
93{
96
97 static bool better(const Value &candidate, const Value &incumbent) noexcept
98 {
99 return candidate < incumbent;
100 }
101
102 static bool can_improve(const Value &bound, const Value &incumbent) noexcept
103 {
104 return bound < incumbent;
105 }
106
107 static bool more_promising(const Value &lhs, const Value &rhs) noexcept
108 {
109 return lhs < rhs;
110 }
111};
112
114template <typename Policy, typename Value>
116 = requires(const Policy &policy, const Value &a, const Value &b) {
117 { policy.better(a, b) } -> std::convertible_to<bool>;
118 { policy.can_improve(a, b) } -> std::convertible_to<bool>;
119 { policy.more_promising(a, b) } -> std::convertible_to<bool>;
120 };
121
123template <SearchState State, SearchMove Move, std::totally_ordered Objective>
125{
126 using Objective_Type = Objective;
127
128 Objective objective_value = Objective{};
129};
130
132template <typename Solution, typename ObjectivePolicy>
134{
136
137 bool operator()(const Solution &lhs, const Solution &rhs) const noexcept
138 {
139 return objective.better(lhs.objective_value, rhs.objective_value);
140 }
141};
142
144template <typename Solution, typename ObjectivePolicy>
146{
147public:
148 using Solution_Type = Solution;
149 using Objective_Type = typename Solution::Objective_Type;
152
154
160
162 {
163 return best_.has_value();
164 }
165
166 [[nodiscard]] const Solution &get() const
167 {
168 return best_.get();
169 }
170
172 {
173 return get().objective_value;
174 }
175
177 {
178 return objective_;
179 }
180
181 bool consider(const Solution &solution)
182 {
183 return best_.consider(solution);
184 }
185
186 bool consider(Solution &&solution)
187 {
188 return best_.consider(std::move(solution));
189 }
190
191 [[nodiscard]] bool can_improve(const Objective_Type &bound) const noexcept
192 {
193 return not has_value() or objective_.can_improve(bound, best_value());
194 }
195
196private:
199};
200
207
209template <typename Solution, typename ObjectivePolicy>
249
251template <typename Domain>
252concept CompleteSolutionPredicate = requires(Domain &domain, const typename Domain::State &state) {
253 { domain.is_complete(state) } -> std::convertible_to<bool>;
254};
255
257template <typename Domain>
258concept OptimizationEvaluator = requires(Domain &domain, const typename Domain::State &state) {
259 typename Domain::Objective;
260 { domain.objective_value(state) } -> std::convertible_to<typename Domain::Objective>;
261 { domain.bound(state) } -> std::convertible_to<typename Domain::Objective>;
262};
263
277template <typename Domain>
280and requires(Domain &domain, typename Domain::State &state, const typename Domain::Move &move) {
283 { domain.apply(state, move) } -> std::same_as<void>;
284 { domain.undo(state, move) } -> std::same_as<void>;
285 };
286
287
299{
300public:
304 using State = typename Domain::State;
306 using Move = typename Domain::Move;
308 using Objective = typename Domain::Objective;
315
325 static constexpr bool supports_best_first = true;
326
327private:
335
337 {
339
340 bool operator()(const FrontierNode &lhs, const FrontierNode &rhs) const noexcept
341 {
342 if (objective.more_promising(lhs.bound_value, rhs.bound_value))
343 return true;
344
345 if (objective.more_promising(rhs.bound_value, lhs.bound_value))
346 return false;
347
348 return lhs.depth < rhs.depth;
349 }
350 };
351
353
354public:
363
367 const SearchLimits &limits = {},
370 {
371 // empty
372 }
373
375 {
376 return domain_;
377 }
378
380 {
381 return domain_;
382 }
383
385 {
386 return policy_;
387 }
388
390 {
391 return limits_;
392 }
393
398
399 void set_policy(const ExplorationPolicy &policy) noexcept
400 {
401 policy_ = policy;
402 }
403
404 void set_limits(const SearchLimits &limits) noexcept
405 {
406 limits_ = limits;
407 }
408
415
417 template <typename OnSolution>
420 {
422 << "SearchLimits::max_solutions must be positive or Search_Unlimited";
424
425 Result result(objective_);
426 result.policy = policy_;
427 result.limits = limits_;
428 const auto start_time = SearchClock::now();
429
430 switch (policy_.strategy)
431 {
434 break;
435
437 search_best_first(std::move(initial_state), result, on_solution);
438 break;
439
440 default:
442 << "Branch_And_Bound received an unsupported exploration strategy";
443 }
444
445 if (result.status == SearchStatus::NotStarted)
447
448 result.stats.elapsed_ms = search_elapsed_ms(SearchClock::now() - start_time);
449
450 return result;
451 }
452
453 template <typename OnSolution>
456 {
457 auto handler = std::forward<OnSolution>(on_solution);
458 return search(std::move(initial_state), handler);
459 }
460
475 template <typename VisitedMap>
478 {
479 using State_Key = typename Domain::State_Key;
481 "Visited map must satisfy VisitedBoundMap concept");
482
484 return search(std::move(initial_state), visited_map, on_solution);
485 }
486
488 template <typename VisitedMap, typename OnSolution>
492 {
493 using State_Key = typename Domain::State_Key;
495 "Visited map must satisfy VisitedBoundMap concept");
496
498 << "SearchLimits::max_solutions must be positive or Search_Unlimited";
500 << "Visited-map search only supports Depth_First strategy";
502
503 Result result(objective_);
504 result.policy = policy_;
505 result.limits = limits_;
506 const auto start_time = SearchClock::now();
507
508 SearchPath<Move> path;
511
512 if (result.status == SearchStatus::NotStarted)
514
515 result.stats.elapsed_ms = search_elapsed_ms(SearchClock::now() - start_time);
516
517 return result;
518 }
519
520private:
525
543 template <typename Recurse>
545 State &state,
546 SearchPath<Move> &path,
547 Result &result,
548 bool &stop,
549 Recurse &&recurse)
550 {
552 bool applied = false;
553 bool path_appended = false;
554 try
555 {
556 domain_.apply(state, move);
557 applied = true;
558 path.append(move);
559 path_appended = true;
560 stop = std::forward<Recurse>(recurse)();
561 }
562 catch (...)
563 {
564 if (path_appended)
565 (void) path.remove_last();
566 if (applied)
567 domain_.undo(state, move);
568 throw;
569 }
570 (void) path.remove_last();
571 domain_.undo(state, move);
572 return not stop;
573 }
574
579
581 {
583 << "Branch_And_Bound does not support MoveOrderingMode::Estimated_Score";
585 << "Branch_And_Bound does not use killer heuristics";
587 << "Branch_And_Bound does not use history heuristics";
588 }
589
590 [[nodiscard]] bool stop_after_solution(Result &result) const
591 {
593 }
594
596 {
598 }
599
600 static void register_visit(const size_t depth, Result &result)
601 {
603 }
604
606 {
608 size_t ordinal = 0;
609
610 (void) domain_.for_each_successor(state,
611 [&](const Move &move) -> bool
612 {
614 ranked.move = move;
615 ranked.ordinal = ordinal++;
616
618 {
619 // Fast path: compute the ordering bound without apply/undo.
620 ranked.priority = domain_.bound_after(state, move);
621 }
622 else
623 {
624 bool applied = false;
625 try
626 {
627 domain_.apply(state, move);
628 applied = true;
629 ranked.priority = domain_.bound(state);
630 }
631 catch (...)
632 {
633 if (applied)
634 domain_.undo(state, move);
635 throw;
636 }
637 domain_.undo(state, move);
638 }
639
641 moves.append(std::move(ranked));
642 return true;
643 });
644
645 if (not moves.is_empty())
646 {
648 result.stats.move_ordering.ordered_moves += moves.size();
649 }
650
651 sort_ranked_moves(moves,
652 [this](const Objective &lhs, const Objective &rhs) noexcept
653 {
654 return objective_.more_promising(lhs, rhs);
655 },
656 false,
657 false);
658
659 return moves;
660 }
661
662 template <typename OnSolution>
665 const SearchPath<Move> &path,
666 const size_t depth,
667 Result &result,
669 {
670 ++result.stats.solutions_found;
671 Solution solution;
672 solution.state = state;
673 solution.path = path;
674 solution.depth = depth;
675 solution.objective_value = domain_.objective_value(state);
676
677 if (result.incumbent.consider(solution))
678 ++result.stats.incumbent_updates;
679
680 if (not on_solution(solution))
681 {
683 return true;
684 }
685
686 return stop_after_solution(result);
687 }
688
689 template <typename OnSolution>
691 [[nodiscard]] bool dfs(State &state,
692 SearchPath<Move> &path,
693 const size_t depth,
694 Result &result,
696 {
697 register_visit(depth, result);
698
699 if (domain_.is_complete(state))
700 return handle_complete_solution(state, path, depth, result, on_solution);
701
703 {
704 ++result.stats.terminal_states;
705 return false;
706 }
707
708 if (depth >= limits_.max_depth)
709 {
710 ++result.stats.pruned_by_depth;
711 return false;
712 }
713
715 {
716 ++result.stats.pruned_by_domain;
717 return false;
718 }
719
720 const Objective bound_value = domain_.bound(state);
721 if (not result.incumbent.can_improve(bound_value))
722 {
723 ++result.stats.pruned_by_bound;
724 return false;
725 }
726
727 if (expansion_limit_reached(result))
728 return true;
729
730 ++result.stats.expanded_states;
731
732 bool stop = false;
733
734 auto explore_move = [&](const Move &move) -> bool
735 {
736 return apply_move_and_recurse(move, state, path, result, stop,
737 [&]() { return dfs(state, path, depth + 1, result, on_solution); });
738 };
739
741 {
742 for (auto ordered_moves = collect_ordered_moves(state, result);
743 const auto &ranked : ordered_moves)
744 if (not explore_move(ranked.move))
745 break;
746 }
747 else
748 (void) domain_.for_each_successor(state,
749 [&](const Move &move) -> bool
750 {
751 return explore_move(move);
752 });
753
754 return stop;
755 }
756
757 template <typename OnSolution>
765
766 template <typename OnSolution, typename VisitedMap>
769 [[nodiscard]] bool dfs_visited(State &state,
770 SearchPath<Move> &path,
771 const size_t depth,
772 Result &result,
774 VisitedMap &visited)
775 {
776 using State_Key = typename Domain::State_Key;
778 "Visited map must satisfy VisitedBoundMap concept");
779
780 register_visit(depth, result);
781
782 if (domain_.is_complete(state))
783 return handle_complete_solution(state, path, depth, result, on_solution);
784
786 {
787 ++result.stats.terminal_states;
788 return false;
789 }
790
791 if (depth >= limits_.max_depth)
792 {
793 ++result.stats.pruned_by_depth;
794 return false;
795 }
796
798 {
799 ++result.stats.pruned_by_domain;
800 return false;
801 }
802
803 const Objective bound_value = domain_.bound(state);
804 if (not result.incumbent.can_improve(bound_value))
805 {
806 ++result.stats.pruned_by_bound;
807 return false;
808 }
809
810 const auto key = domain_.state_key(state);
812 bool was_inserted = false;
813 if (auto *pair = visited.search(key); pair != nullptr)
814 {
815 if (not objective_.more_promising(bound_value, pair->second))
816 {
817 ++result.stats.pruned_by_bound;
818 return false;
819 }
820 old_visited_bound = pair->second;
821 pair->second = bound_value;
822 }
823 else
824 {
825 visited.insert(key, bound_value);
826 was_inserted = true;
827 }
828
829 auto rollback_visited = [&]() {
830 if (was_inserted)
831 visited.remove(key);
832 else if (auto *pair = visited.search(key); pair != nullptr)
833 pair->second = old_visited_bound;
834 };
835
836 if (expansion_limit_reached(result))
837 return true;
838
839 ++result.stats.expanded_states;
840
841 bool stop = false;
842
843 try
844 {
845 auto explore_move = [&](const Move &move) -> bool
846 {
847 return apply_move_and_recurse(move, state, path, result, stop,
848 [&]() { return dfs_visited(state, path, depth + 1, result, on_solution, visited); });
849 };
850
852 {
853 for (auto ordered_moves = collect_ordered_moves(state, result);
854 const auto &ranked : ordered_moves)
855 if (not explore_move(ranked.move))
856 break;
857 }
858 else
859 (void) domain_.for_each_successor(state,
860 [&](const Move &move) -> bool
861 {
862 return explore_move(move);
863 });
864 }
865 catch (...)
866 {
868 throw;
869 }
870
871 return stop;
872 }
873
874 template <typename OnSolution>
877 SearchPath<Move> path,
878 const size_t depth,
879 Result &result,
882 {
883 register_visit(depth, result);
884
885 if (domain_.is_complete(state))
886 return handle_complete_solution(state, path, depth, result, on_solution);
887
889 {
890 ++result.stats.terminal_states;
891 return false;
892 }
893
894 if (depth >= limits_.max_depth)
895 {
896 ++result.stats.pruned_by_depth;
897 return false;
898 }
899
901 {
902 ++result.stats.pruned_by_domain;
903 return false;
904 }
905
906 const Objective bound_value = domain_.bound(state);
907 if (not result.incumbent.can_improve(bound_value))
908 {
909 ++result.stats.pruned_by_bound;
910 return false;
911 }
912
913 frontier.put(FrontierNode{std::move(state), std::move(path), depth, bound_value});
914 return false;
915 }
916
917 template <typename OnSolution>
920 {
925 std::move(initial_state), std::move(root_path), 0, result, frontier, on_solution))
926 return;
927
928 while (not frontier.is_empty())
929 {
930 if (result.incumbent.has_value()
931 and not result.incumbent.can_improve(frontier.top().bound_value))
932 {
933 result.stats.pruned_by_bound += frontier.size();
934 frontier.empty();
935 return;
936 }
937
938 if (expansion_limit_reached(result))
939 return;
940
941 FrontierNode node = frontier.getMin();
942 ++result.stats.expanded_states;
943
944 bool stop = false;
945 (void) domain_.for_each_successor(node.state,
946 [&](const Move &move) -> bool
947 {
949 State child_state = node.state;
951 child_path.reserve(node.path.size() + 1);
952 child_path.append(move);
953 domain_.apply(child_state, move);
955 std::move(child_path),
956 node.depth + 1,
957 result,
958 frontier,
960 return not stop;
961 });
962
963 if (stop)
964 return;
965 }
966 }
967};
968
970template <BranchAndBoundDomain Domain,
974 Domain domain,
975 typename Domain::State initial_state,
977 SearchLimits limits = {},
979{
981 policy,
982 limits,
983 std::move(objective));
984 return engine.search(std::move(initial_state));
985}
986
988template <BranchAndBoundDomain Domain,
989 typename OnSolution,
992 and SearchSolutionVisitor<
996 Domain domain,
997 typename Domain::State initial_state,
1000 SearchLimits limits = {},
1002{
1004 policy,
1005 limits,
1006 std::move(objective));
1007 return engine.search(std::move(initial_state), on_solution);
1008}
1009
1010} // end namespace Aleph
1011
1012# endif // BRANCH_AND_BOUND_H
Generic memoization / transposition-table support for state search.
Exception handling system with formatted messages for Aleph-w.
#define ah_invalid_argument_if(C)
Throws std::invalid_argument if condition holds.
Definition ah-errors.H:639
constexpr size_t size() const noexcept
Return the number of elements stored in the stack.
Definition tpl_array.H:351
constexpr bool is_empty() const noexcept
Checks if the container is empty.
Definition tpl_array.H:348
T & append(const T &data)
Append a copy of data
Definition tpl_array.H:245
Tracks the best solution seen so far according to a comparator.
Reusable branch-and-bound engine over implicit state spaces.
typename Domain::Move Move
Move type.
static ExplorationPolicy default_policy() noexcept
Return the default branch-and-bound exploration policy.
void set_policy(const ExplorationPolicy &policy) noexcept
const ExplorationPolicy & policy() const noexcept
static void register_visit(const size_t depth, Result &result)
void validate_ordering_configuration() const
and SearchSolutionVisitor< OnSolution, Solution > Result search(State initial_state, VisitedMap &visited_map, OnSolution &on_solution)
Branch and bound with visited-bound map and solution callback.
void search_best_first(State initial_state, Result &result, OnSolution &on_solution)
and SearchStateKeyProvider< Domain > bool dfs_visited(State &state, SearchPath< Move > &path, const size_t depth, Result &result, OnSolution &on_solution, VisitedMap &visited)
bool expansion_limit_reached(Result &result) const
typename Domain::State State
Concrete search state type.
bool apply_move_and_recurse(const Move &move, State &state, SearchPath< Move > &path, Result &result, bool &stop, Recurse &&recurse)
Apply one move, recurse, then undo — shared by dfs and dfs_visited.
Branch_And_Bound(Domain domain, ExplorationPolicy policy=default_policy(), const SearchLimits &limits={}, ObjectivePolicy objective={})
Build a branch-and-bound engine bound to one optimization domain.
Result search(State initial_state, OnSolution &&on_solution)
bool dfs(State &state, SearchPath< Move > &path, const size_t depth, Result &result, OnSolution &on_solution)
bool process_best_first_candidate(State state, SearchPath< Move > path, const size_t depth, Result &result, Frontier &frontier, OnSolution &on_solution)
void set_limits(const SearchLimits &limits) noexcept
Result search(State initial_state)
Execute branch and bound and keep only the incumbent.
bool stop_after_solution(Result &result) const
bool handle_complete_solution(const State &state, const SearchPath< Move > &path, const size_t depth, Result &result, OnSolution &on_solution)
Domain & domain() noexcept
Array< RankedMove< Move, Objective > > collect_ordered_moves(State &state, Result &result)
const Domain & domain() const noexcept
void search_depth_first(State &initial_state, Result &result, OnSolution &on_solution)
static constexpr bool supports_best_first
Compile-time marker: Branch_And_Bound supports both Depth_First and Best_First strategies.
Result search(State initial_state, OnSolution &on_solution)
Execute branch and bound with a callback/collector per solution.
Result search(State initial_state, VisitedMap &visited_map)
Branch and bound with a visited-bound map (DFS only).
Domain Domain_Type
Type of the problem domain.
ExplorationPolicy policy_
const SearchLimits & limits() const noexcept
bool ordering_active_for_depth_first() const noexcept
const ObjectivePolicy & objective_policy() const noexcept
typename Domain::Objective Objective
Type of the metric being optimized.
Dynamic heap of elements of type T ordered by a comparison functor.
Global incumbent manager for branch and bound.
bool consider(Solution &&solution)
BestSolution< Solution, Compare_Type > best_
typename Solution::Objective_Type Objective_Type
bool has_value() const noexcept
const ObjectivePolicy & objective() const noexcept
ObjectiveIncumbent(ObjectivePolicy objective)
const Objective_Type & best_value() const
bool can_improve(const Objective_Type &bound) const noexcept
bool consider(const Solution &solution)
const Solution & get() const
Minimal contract for branch-and-bound domains.
Concept for objective policies used by branch and bound.
Concept for complete-solution predicates in optimization domains.
Concept for domains that can estimate a move's bound without modifying state (incremental bound for B...
Concept for domains that provide an objective and an optimistic bound.
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 for a map tracking the best bound seen per visited state.
static Geom_Number objective(const Point &p)
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.
void sort_ranked_moves(Array< RankedMove< Move, Priority > > &moves, BetterPriority better_priority, const bool prefer_killer, const bool prefer_history)
Sort one materialized move batch using priority and optional hooks.
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.
std::pair< First, Second > pair
Alias to std::pair kept for backwards compatibility.
Definition ahPair.H:89
auto branch_and_bound_search(Domain domain, typename Domain::State initial_state, ExplorationPolicy policy=Branch_And_Bound< Domain, ObjectivePolicy >::default_policy(), SearchLimits limits={}, ObjectivePolicy objective={})
Convenience wrapper for one-shot branch and bound.
OptimizationSense
Optimization direction supported by branch and bound.
@ Maximize
Larger objective values are better.
@ Minimize
Smaller objective values are better.
double search_elapsed_ms(const SearchClock::duration &duration) noexcept
@ Estimated_Bound
Rank successors by an optimistic child bound.
@ Domain
Preserve the order emitted by for_each_successor().
@ Estimated_Score
Rank successors by a cheap heuristic score estimate.
STL namespace.
Common infrastructure for implicit state-space search.
Result of a branch-and-bound execution.
bool found_solution() const noexcept
BranchAndBoundResult(ObjectivePolicy objective)
bool limit_reached() const noexcept
bool exhausted() const noexcept
bool stopped_on_solution() const noexcept
SearchLimits limits
Hard limits used during the run.
ExplorationPolicy policy
Exploration policy used during the run.
BranchAndBoundStats stats
Collected branch-and-bound statistics.
SearchStatus status
Final execution status.
Incumbent_Type incumbent
Global incumbent for the run.
Branch-and-bound specific statistics.
size_t incumbent_updates
Number of times the incumbent improved.
size_t pruned_by_bound
Nodes pruned because their bound cannot beat the incumbent.
bool operator()(const FrontierNode &lhs, const FrontierNode &rhs) const noexcept
Default solution visitor that always continues the search.
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.
@ 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.
Objective policy for maximization problems.
static bool can_improve(const Value &bound, const Value &incumbent) noexcept
static bool better(const Value &candidate, const Value &incumbent) noexcept
static bool more_promising(const Value &lhs, const Value &rhs) noexcept
static constexpr OptimizationSense sense
Objective policy for minimization problems.
static constexpr OptimizationSense sense
static bool more_promising(const Value &lhs, const Value &rhs) noexcept
static bool can_improve(const Value &bound, const Value &incumbent) noexcept
static bool better(const Value &candidate, const Value &incumbent) noexcept
size_t priority_estimates
Number of score/bound estimates computed for ordering.
size_t ordered_moves
Number of individual moves considered by ordering.
size_t ordered_batches
Number of successor batches materialized and ordered.
Snapshot of a complete optimization solution.
Objective objective_value
Objective value of the complete solution.
Compare two optimization solutions by objective quality.
bool operator()(const Solution &lhs, const Solution &rhs) const noexcept
One move plus the metadata used by ordering comparators.
Move move
The candidate move.
Hard bounds applied by the search engine.
size_t max_solutions
Maximum accepted solutions.
size_t max_depth
Maximum expansion depth.
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 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 solutions_found
Number of goal states accepted.
size_t expanded_states
Number of non-terminal states expanded.
static mt19937 engine
Dynamic binary heap with node-based storage.