35#include <gtest/gtest.h>
59struct ArtificialMaxDomain
61 using State = ArtificialState;
62 using Move = ArtificialMove;
63 using Objective =
int;
65 bool is_complete(
const State &state)
const
67 return state.depth == 2;
70 Objective objective_value(
const State &state)
const
75 Objective bound(
const State &state)
const
82 default:
return state.value;
86 void apply(State &state,
const Move &move)
const
88 state.code = move.target_code;
89 state.value += move.delta;
93 void undo(State &state,
const Move &move)
const
96 state.value -= move.delta;
100 template <
typename Visitor>
101 bool for_each_successor(
const State &state,
Visitor visit)
const
103 if (state.depth >= 2)
111 return visit(Move{3, 0,
'R'});
116 return visit(Move{5, 9,
'R'});
121 return visit(Move{7, 4,
'R'});
129struct ArtificialMaxBadOrderDomain
131 using State = ArtificialState;
132 using Move = ArtificialMove;
133 using Objective =
int;
135 bool is_complete(
const State &state)
const
137 return state.depth == 2;
140 Objective objective_value(
const State &state)
const
145 Objective bound(
const State &state)
const
152 default:
return state.value;
156 void apply(State &state,
const Move &move)
const
158 state.code = move.target_code;
159 state.value += move.delta;
163 void undo(State &state,
const Move &move)
const
166 state.value -= move.delta;
170 template <
typename Visitor>
171 bool for_each_successor(
const State &state,
Visitor visit)
const
173 if (state.depth >= 2)
181 return visit(Move{2, 0,
'L'});
186 return visit(Move{5, 9,
'R'});
191 return visit(Move{7, 4,
'R'});
199struct ArtificialMaxVisitedDomain : ArtificialMaxDomain
201 using State_Key =
int;
203 [[
nodiscard]] State_Key state_key(
const State &state)
const noexcept
209struct ArtificialMinDomain
211 using State = ArtificialState;
212 using Move = ArtificialMove;
213 using Objective =
int;
215 bool is_complete(
const State &state)
const
217 return state.depth == 2;
220 Objective objective_value(
const State &state)
const
225 Objective bound(
const State &state)
const
232 default:
return state.value;
236 void apply(State &state,
const Move &move)
const
238 state.code = move.target_code;
239 state.value += move.delta;
243 void undo(State &state,
const Move &move)
const
246 state.value -= move.delta;
250 template <
typename Visitor>
251 bool for_each_successor(
const State &state,
Visitor visit)
const
253 if (state.depth >= 2)
261 return visit(Move{3, 0,
'R'});
266 return visit(Move{5, 6,
'R'});
271 return visit(Move{7, 9,
'R'});
287 : index(0), weight(0),
value(0), chosen(n, 0)
293class KnapsackBBDomain
309 suffix_values_(items.
size() + 1, 0.0),
313 for (
size_t i = items_.size(); i > 0; --i)
314 suffix_values_[i - 1] = suffix_values_[i] + items_[i - 1].
value;
317 bool is_complete(
const State &state)
const
319 return state.index == items_.size();
322 Objective objective_value(
const State &state)
const
327 Objective bound(
const State &state)
const
329 if (
not use_fractional_bound_)
330 return state.value + suffix_values_[state.index];
333 int remaining = capacity_ - state.weight;
335 for (
size_t i = state.index; i < items_.size()
and remaining > 0; ++i)
336 if (items_[i].weight <= remaining)
339 remaining -= items_[i].weight;
343 optimistic += items_[i].value * (
static_cast<Objective
>(remaining)/
344 static_cast<Objective
>(items_[i].weight));
351 void apply(State &state,
const Move &move)
const
355 state.weight += items_[state.index].weight;
356 state.value += items_[state.index].value;
357 state.chosen[state.index] = 1;
360 state.chosen[state.index] = 0;
365 void undo(State &state,
const Move &move)
const
370 state.weight -= items_[state.index].weight;
371 state.value -= items_[state.index].value;
374 state.chosen[state.index] = 0;
377 template <
typename Visitor>
378 bool for_each_successor(
const State &state,
Visitor visit)
const
380 if (state.index >= items_.size())
383 if (state.weight + items_[state.index].weight <= capacity_)
387 return visit(Move{
false});
394 bool use_fractional_bound_ =
true;
397struct AssignmentState
404 explicit AssignmentState(
const size_t n = 0)
405 :
row(0), total_cost(0), used_columns(n, 0), assigned_column(n, -1)
411class AssignmentBBDomain
419 using State = AssignmentState;
420 using Objective =
int;
428 bool is_complete(
const State &state)
const
430 return state.row == costs_.size();
433 Objective objective_value(
const State &state)
const
435 return state.total_cost;
438 Objective bound(
const State &state)
const
442 for (
size_t row = state.row;
row < costs_.size(); ++
row)
455 void apply(State &state,
const Move &move)
const
457 state.total_cost += costs_[state.row][move.col];
458 state.used_columns[move.col] = 1;
459 state.assigned_column[state.row] =
static_cast<int>(move.col);
463 void undo(State &state,
const Move &move)
const
466 state.total_cost -= costs_[state.row][move.col];
467 state.used_columns[move.col] = 0;
468 state.assigned_column[state.row] = -1;
471 template <
typename Visitor>
472 bool for_each_successor(
const State &state,
Visitor visit)
const
474 if (state.row >= costs_.size())
477 for (
size_t col = 0;
col < costs_[state.row].size(); ++
col)
488struct ThrowingApplyState
490 std::shared_ptr<bool> undo_called;
494struct ThrowingApplyMove
499struct ThrowingApplyBBDomain
501 using State = ThrowingApplyState;
502 using Move = ThrowingApplyMove;
503 using Objective =
int;
505 bool is_complete(
const State &state)
const
507 return state.node == 1;
510 Objective objective_value(
const State &)
const
515 Objective bound(
const State &)
const
520 void apply(State &state,
const Move &move)
const
528 void undo(State &state,
const Move &)
const
530 *state.undo_called =
true;
534 template <
typename Visitor>
535 bool for_each_successor(
const State &state,
Visitor visit)
const
540 return visit(Move{
true});
544struct ThrowingApplyVisitedBBDomain : ThrowingApplyBBDomain
546 using State_Key = size_t;
548 [[
nodiscard]] State_Key state_key(
const State &state)
const noexcept
566struct NoDefaultBBState
569 explicit NoDefaultBBState(
const size_t d) : depth(d) {}
572struct NoDefaultStateBBDomain
574 using State = NoDefaultBBState;
575 using Move = ArtificialMove;
576 using Objective =
int;
578 bool is_complete(
const State &state)
const {
return state.depth == 1; }
579 Objective objective_value(
const State &)
const {
return 0; }
580 Objective bound(
const State &)
const {
return 0; }
581 void apply(State &state,
const Move &)
const { ++state.depth; }
582 void undo(State &state,
const Move &)
const { --state.depth; }
584 template <
typename Visitor>
585 bool for_each_successor(
const State &,
Visitor)
const {
return true; }
596 for (
const auto &move : path)
597 out.push_back(move.label);
627struct IncrementalBoundDomain
629 using State = ArtificialState;
630 using Move = ArtificialMove;
631 using Objective =
int;
633 mutable size_t bound_after_calls = 0;
634 mutable size_t apply_calls = 0;
636 bool is_complete(
const State &state)
const
638 return state.depth == 2;
641 Objective objective_value(
const State &state)
const
646 Objective bound(
const State &state)
const
653 default:
return state.value;
657 Objective
bound_after(
const State &,
const Move &move)
const
661 switch (move.target_code)
665 default:
return move.delta;
669 void apply(State &state,
const Move &move)
const
672 state.code = move.target_code;
673 state.value += move.delta;
677 void undo(State &state,
const Move &move)
const
680 state.value -= move.delta;
684 template <
typename Visitor>
685 bool for_each_successor(
const State &state,
Visitor visit)
const
687 if (state.depth >= 2)
695 return visit(Move{3, 0,
'R'});
700 return visit(Move{5, 9,
'R'});
705 return visit(Move{7, 4,
'R'});
724 first.objective_value = 5;
734 ArtificialMaxDomain domain;
741 EXPECT_EQ(result.incumbent.best_value(), 9);
743 EXPECT_EQ(result.stats.solutions_found, 2u);
744 EXPECT_EQ(result.stats.pruned_by_bound, 1u);
745 EXPECT_EQ(result.stats.incumbent_updates, 2u);
751 ArtificialMaxVisitedDomain domain;
755 auto result =
engine.search(ArtificialState{}, visited);
764 ArtificialMinDomain domain;
767 policy.
strategy = ExplorationPolicy::Strategy::Best_First;
770 auto result =
engine.search(ArtificialState{});
774 EXPECT_EQ(result.incumbent.best_value(), 6);
776 EXPECT_GT(result.stats.pruned_by_bound, 0u);
781 ArtificialMaxBadOrderDomain domain;
790 best_policy.strategy = ExplorationPolicy::Strategy::Best_First;
808 auto undo_called = std::make_shared<bool>(
false);
809 ThrowingApplyBBDomain domain;
815 EXPECT_THROW((
void)
engine.search(ThrowingApplyState{undo_called, 0}), std::runtime_error);
821 auto undo_called = std::make_shared<bool>(
false);
822 ThrowingApplyBBDomain domain;
825 EXPECT_THROW((
void)
engine.search(ThrowingApplyState{undo_called, 0}), std::runtime_error);
831 auto undo_called = std::make_shared<bool>(
false);
832 ThrowingApplyVisitedBBDomain domain;
848 void apply(State &state,
const Move &move)
const
854 Objective
bound(
const State &state)
const
864 auto undo_called = std::make_shared<bool>(
false);
868 EXPECT_THROW((
void)
engine.search(ThrowingApplyState{undo_called, 0}), std::runtime_error);
883 auto undo_called = std::make_shared<bool>(
false);
889 EXPECT_THROW((
void)
engine.search(ThrowingApplyState{undo_called, 0}, visited), std::runtime_error);
894 *undo_called =
false;
895 EXPECT_THROW((
void)
engine.search(ThrowingApplyState{undo_called, 0}, visited), std::runtime_error);
902 ArtificialMaxDomain domain;
910 EXPECT_EQ(result.stats.solutions_found, 1u);
911 EXPECT_EQ(result.incumbent.best_value(), 7);
918 {2, 40.0}, {5, 30.0}, {10, 50.0}, {5, 10.0}
920 constexpr int capacity = 16;
922 KnapsackBBDomain domain(items, capacity,
true);
934 {2, 40.0}, {5, 30.0}, {10, 50.0}, {5, 10.0}
936 constexpr int capacity = 16;
938 KnapsackBBDomain domain(items, capacity,
true);
942 policy.
strategy = ExplorationPolicy::Strategy::Best_First;
953 {10, 100.0}, {9, 80.0}, {9, 80.0}, {9, 80.0}
955 constexpr int capacity = 10;
977 AssignmentBBDomain domain(
costs);
991 EXPECT_EQ(result.incumbent.best_value(), 13);
1003 AssignmentBBDomain domain(
costs);
1013 policy.
strategy = ExplorationPolicy::Strategy::Best_First;
1031 ArtificialMaxDomain domain;
1039 auto result =
engine.search(ArtificialState{});
1042 EXPECT_GT(result.stats.pruned_by_depth, 0u);
1049 ArtificialMaxDomain domain;
1057 auto result =
engine.search(ArtificialState{});
1060 EXPECT_EQ(result.stats.expanded_states, 1u);
1069 {2, 40.0}, {5, 30.0}, {10, 50.0}
1071 constexpr int capacity = 0;
1073 KnapsackBBDomain domain(items, capacity,
true);
1086 IncrementalBoundDomain domain;
1092 auto result =
engine.search(ArtificialState{});
1096 EXPECT_EQ(result.incumbent.best_value(), 9);
1102 EXPECT_GT(result.stats.move_ordering.priority_estimates, 0u);
Classical knapsack problem variants (0/1, unbounded, bounded).
Umbrella header for the implicit state-space search framework.
#define ah_runtime_error()
Throws std::runtime_error unconditionally.
size_t size_t int32_t value
size_t size_t int32_t * out
Simple dynamic array with automatic resizing and functional operations.
constexpr size_t size() const noexcept
Return the number of elements stored in the stack.
Reusable branch-and-bound engine over implicit state spaces.
static ExplorationPolicy default_policy() noexcept
Return the default branch-and-bound exploration policy.
Result search(State initial_state)
Execute branch and bound and keep only the incumbent.
Global incumbent manager for branch and bound.
Collector that stores accepted solutions in an Aleph list.
size_t blossom_maximum_cardinality_matching(const GT &g, DynDlist< typename GT::Arc * > &matching, SA sa=SA())
Alias of compute_maximum_cardinality_general_matching().
constexpr std::uint32_t delta
File contains only the cells that changed relative to a baseline.
Main namespace for Aleph-w library functions.
size_t size(Node *root) noexcept
and
Check uniqueness with explicit hash + equality functors.
std::string code(Node *root)
Compute a string with the Lukasiewicz`s word of a tree.
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.
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.
An item for knapsack problems.
Objective policy for maximization problems.
Objective policy for minimization problems.
Snapshot of a complete optimization solution.
Hard bounds applied by the search engine.
size_t max_expansions
Maximum expanded states.
size_t max_depth
Maximum expansion depth.
void apply(State &state, const Move &move) const
bool is_complete(const State &) const
Objective bound(const State &state) const
State_Key state_key(const State &state) const noexcept