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
280template <typename Domain>
283and requires(Domain &domain, typename Domain::State &state, const typename Domain::Move &move) {
285 // The engine default-constructs its solution snapshots before filling them.
286 requires std::default_initializable<typename Domain::State>;
288 { domain.apply(state, move) } -> std::same_as<void>;
289 { domain.undo(state, move) } -> std::same_as<void>;
290 };
291
303{
304public:
308 using State = typename Domain::State;
310 using Move = typename Domain::Move;
312 using Objective = typename Domain::Objective;
319
329 static constexpr bool supports_best_first = true;
330
331private:
339
341 {
343
344 bool operator()(const FrontierNode &lhs, const FrontierNode &rhs) const noexcept
345 {
346 if (objective.more_promising(lhs.bound_value, rhs.bound_value))
347 return true;
348
349 if (objective.more_promising(rhs.bound_value, lhs.bound_value))
350 return false;
351
352 return lhs.depth < rhs.depth;
353 }
354 };
355
357
358public:
367
371 const SearchLimits &limits = {},
374 {
375 // empty
376 }
377
379 {
380 return domain_;
381 }
382
384 {
385 return domain_;
386 }
387
389 {
390 return policy_;
391 }
392
394 {
395 return limits_;
396 }
397
402
403 void set_policy(const ExplorationPolicy &policy) noexcept
404 {
405 policy_ = policy;
406 }
407
408 void set_limits(const SearchLimits &limits) noexcept
409 {
410 limits_ = limits;
411 }
412
419
421 template <typename OnSolution>
424 {
426 << "SearchLimits::max_solutions must be positive or Search_Unlimited";
428
429 Result result(objective_);
430 result.policy = policy_;
431 result.limits = limits_;
432 const auto start_time = SearchClock::now();
433
434 switch (policy_.strategy)
435 {
438 break;
439
441 search_best_first(std::move(initial_state), result, on_solution);
442 break;
443
444 default:
446 << "Branch_And_Bound received an unsupported exploration strategy";
447 }
448
449 if (result.status == SearchStatus::NotStarted)
451
452 result.stats.elapsed_ms = search_elapsed_ms(SearchClock::now() - start_time);
453
454 return result;
455 }
456
457 template <typename OnSolution>
460 {
461 auto handler = std::forward<OnSolution>(on_solution);
462 return search(std::move(initial_state), handler);
463 }
464
479 template <typename VisitedMap>
482 {
483 using State_Key = typename Domain::State_Key;
485 "Visited map must satisfy VisitedBoundMap concept");
486
488 return search(std::move(initial_state), visited_map, on_solution);
489 }
490
492 template <typename VisitedMap, typename OnSolution>
495 {
496 using State_Key = typename Domain::State_Key;
498 "Visited map must satisfy VisitedBoundMap concept");
499
501 << "SearchLimits::max_solutions must be positive or Search_Unlimited";
503 << "Visited-map search only supports Depth_First strategy";
505
506 Result result(objective_);
507 result.policy = policy_;
508 result.limits = limits_;
509 const auto start_time = SearchClock::now();
510
511 SearchPath<Move> path;
514
515 if (result.status == SearchStatus::NotStarted)
517
518 result.stats.elapsed_ms = search_elapsed_ms(SearchClock::now() - start_time);
519
520 return result;
521 }
522
523private:
528
546 template <typename Recurse>
548 State &state,
549 SearchPath<Move> &path,
550 Result &result,
551 bool &stop,
552 Recurse &&recurse)
553 {
555 bool applied = false;
556 bool path_appended = false;
557 try
558 {
559 domain_.apply(state, move);
560 applied = true;
561 path.append(move);
562 path_appended = true;
563 stop = std::forward<Recurse>(recurse)();
564 }
565 catch (...)
566 {
567 if (path_appended)
568 (void) path.remove_last();
569 if (applied)
570 domain_.undo(state, move);
571 throw;
572 }
573 (void) path.remove_last();
574 domain_.undo(state, move);
575 return not stop;
576 }
577
582
584 {
586 << "Branch_And_Bound does not support MoveOrderingMode::Estimated_Score";
588 << "Branch_And_Bound does not use killer heuristics";
590 << "Branch_And_Bound does not use history heuristics";
591 }
592
593 [[nodiscard]] bool stop_after_solution(Result &result) const
594 {
596 }
597
599 {
601 }
602
603 static void register_visit(const size_t depth, Result &result)
604 {
606 }
607
609 {
611 size_t ordinal = 0;
612
613 (void) domain_.for_each_successor(state,
614 [&](const Move &move) -> bool
615 {
617 ranked.move = move;
618 ranked.ordinal = ordinal++;
619
621 {
622 // Fast path: compute the ordering bound without apply/undo.
623 ranked.priority = domain_.bound_after(state, move);
624 }
625 else
626 {
627 bool applied = false;
628 try
629 {
630 domain_.apply(state, move);
631 applied = true;
632 ranked.priority = domain_.bound(state);
633 }
634 catch (...)
635 {
636 if (applied)
637 domain_.undo(state, move);
638 throw;
639 }
640 domain_.undo(state, move);
641 }
642
644 moves.append(std::move(ranked));
645 return true;
646 });
647
648 if (not moves.is_empty())
649 {
651 result.stats.move_ordering.ordered_moves += moves.size();
652 }
653
654 sort_ranked_moves(moves,
655 [this](const Objective &lhs, const Objective &rhs) noexcept
656 {
657 return objective_.more_promising(lhs, rhs);
658 },
659 false,
660 false);
661
662 return moves;
663 }
664
665 template <typename OnSolution>
668 const SearchPath<Move> &path,
669 const size_t depth,
670 Result &result,
672 {
673 ++result.stats.solutions_found;
674 Solution solution;
675 solution.state = state;
676 solution.path = path;
677 solution.depth = depth;
678 solution.objective_value = domain_.objective_value(state);
679
680 if (result.incumbent.consider(solution))
681 ++result.stats.incumbent_updates;
682
683 if (not on_solution(solution))
684 {
686 return true;
687 }
688
689 return stop_after_solution(result);
690 }
691
692 template <typename OnSolution>
694 [[nodiscard]] bool dfs(State &state,
695 SearchPath<Move> &path,
696 const size_t depth,
697 Result &result,
699 {
700 register_visit(depth, result);
701
702 if (domain_.is_complete(state))
703 return handle_complete_solution(state, path, depth, result, on_solution);
704
706 {
707 ++result.stats.terminal_states;
708 return false;
709 }
710
711 if (depth >= limits_.max_depth)
712 {
713 ++result.stats.pruned_by_depth;
714 return false;
715 }
716
718 {
719 ++result.stats.pruned_by_domain;
720 return false;
721 }
722
723 if (const Objective bound_value = domain_.bound(state);
724 not result.incumbent.can_improve(bound_value))
725 {
726 ++result.stats.pruned_by_bound;
727 return false;
728 }
729
730 if (expansion_limit_reached(result))
731 return true;
732
733 ++result.stats.expanded_states;
734
735 bool stop = false;
736
737 auto explore_move = [&](const Move &move) -> bool
738 {
739 return apply_move_and_recurse(move,
740 state,
741 path,
742 result,
743 stop,
744 [&]()
745 {
746 return dfs(state, path, depth + 1, result, on_solution);
747 });
748 };
749
751 {
752 for (auto ordered_moves = collect_ordered_moves(state, result);
753 const auto &ranked : ordered_moves)
754 if (not explore_move(ranked.move))
755 break;
756 }
757 else
758 (void) domain_.for_each_successor(state,
759 [&](const Move &move) -> bool
760 {
761 return explore_move(move);
762 });
763
764 return stop;
765 }
766
767 template <typename OnSolution>
775
776 template <typename OnSolution, typename VisitedMap>
778 [[nodiscard]] bool dfs_visited(State &state,
779 SearchPath<Move> &path,
780 const size_t depth,
781 Result &result,
783 VisitedMap &visited)
784 {
785 using State_Key = typename Domain::State_Key;
787 "Visited map must satisfy VisitedBoundMap concept");
788
789 register_visit(depth, result);
790
791 if (domain_.is_complete(state))
792 return handle_complete_solution(state, path, depth, result, on_solution);
793
795 {
796 ++result.stats.terminal_states;
797 return false;
798 }
799
800 if (depth >= limits_.max_depth)
801 {
802 ++result.stats.pruned_by_depth;
803 return false;
804 }
805
807 {
808 ++result.stats.pruned_by_domain;
809 return false;
810 }
811
812 const Objective bound_value = domain_.bound(state);
813 if (not result.incumbent.can_improve(bound_value))
814 {
815 ++result.stats.pruned_by_bound;
816 return false;
817 }
818
819 const auto key = domain_.state_key(state);
821 bool was_inserted = false;
822 if (auto *pair = visited.search(key); pair != nullptr)
823 {
824 if (not objective_.more_promising(bound_value, pair->second))
825 {
826 ++result.stats.pruned_by_bound;
827 return false;
828 }
829 old_visited_bound = pair->second;
830 pair->second = bound_value;
831 }
832 else
833 {
834 visited.insert(key, bound_value);
835 was_inserted = true;
836 }
837
838 auto rollback_visited = [&]()
839 {
840 if (was_inserted)
841 visited.remove(key);
842 else if (auto *pair = visited.search(key); pair != nullptr)
843 pair->second = old_visited_bound;
844 };
845
846 if (expansion_limit_reached(result))
847 return true;
848
849 ++result.stats.expanded_states;
850
851 bool stop = false;
852
853 try
854 {
855 auto explore_move = [&](const Move &move) -> bool
856 {
857 return apply_move_and_recurse(move,
858 state,
859 path,
860 result,
861 stop,
862 [&]()
863 {
864 return dfs_visited(state, path, depth + 1, result, on_solution, visited);
865 });
866 };
867
869 {
870 for (auto ordered_moves = collect_ordered_moves(state, result);
871 const auto &ranked : ordered_moves)
872 if (not explore_move(ranked.move))
873 break;
874 }
875 else
876 (void) domain_.for_each_successor(state,
877 [&](const Move &move) -> bool
878 {
879 return explore_move(move);
880 });
881 }
882 catch (...)
883 {
885 throw;
886 }
887
888 return stop;
889 }
890
891 template <typename OnSolution>
894 SearchPath<Move> path,
895 const size_t depth,
896 Result &result,
899 {
900 register_visit(depth, result);
901
902 if (domain_.is_complete(state))
903 return handle_complete_solution(state, path, depth, result, on_solution);
904
906 {
907 ++result.stats.terminal_states;
908 return false;
909 }
910
911 if (depth >= limits_.max_depth)
912 {
913 ++result.stats.pruned_by_depth;
914 return false;
915 }
916
918 {
919 ++result.stats.pruned_by_domain;
920 return false;
921 }
922
923 const Objective bound_value = domain_.bound(state);
924 if (not result.incumbent.can_improve(bound_value))
925 {
926 ++result.stats.pruned_by_bound;
927 return false;
928 }
929
930 frontier.put(FrontierNode{std::move(state), std::move(path), depth, bound_value});
931 return false;
932 }
933
934 template <typename OnSolution>
937 {
942 std::move(initial_state), std::move(root_path), 0, result, frontier, on_solution))
943 return;
944
945 while (not frontier.is_empty())
946 {
947 if (result.incumbent.has_value()
948 and not result.incumbent.can_improve(frontier.top().bound_value))
949 {
950 result.stats.pruned_by_bound += frontier.size();
951 frontier.empty();
952 return;
953 }
954
955 if (expansion_limit_reached(result))
956 return;
957
958 FrontierNode node = frontier.getMin();
959 ++result.stats.expanded_states;
960
961 bool stop = false;
962 (void) domain_.for_each_successor(node.state,
963 [&](const Move &move) -> bool
964 {
966 State child_state = node.state;
968 child_path.reserve(node.path.size() + 1);
969 child_path.append(move);
970 domain_.apply(child_state, move);
972 std::move(child_path),
973 node.depth + 1,
974 result,
975 frontier,
977 return not stop;
978 });
979
980 if (stop)
981 return;
982 }
983 }
984};
985
987template <BranchAndBoundDomain Domain,
991 Domain domain,
992 typename Domain::State initial_state,
994 SearchLimits limits = {},
996{
998 policy,
999 limits,
1000 std::move(objective));
1001 return engine.search(std::move(initial_state));
1002}
1003
1005template <BranchAndBoundDomain Domain,
1006 typename OnSolution,
1009 and SearchSolutionVisitor<
1010 OnSolution,
1013 Domain domain,
1014 typename Domain::State initial_state,
1017 SearchLimits limits = {},
1019{
1021 policy,
1022 limits,
1023 std::move(objective));
1024 return engine.search(std::move(initial_state), on_solution);
1025}
1026
1027} // end namespace Aleph
1028
1029#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:644
constexpr size_t size() const noexcept
Return the number of elements stored in the stack.
Definition tpl_array.H:365
constexpr bool is_empty() const noexcept
Checks if the container is empty.
Definition tpl_array.H:359
T & append(const T &data)
Append a copy of data
Definition tpl_array.H:250
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.
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
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
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.
@ Value
Regular runtime payload stored as Interpreter_Value.
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.
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.