36#include <gtest/gtest.h>
47struct ArtificialTreeState
53struct ArtificialDecisionTreeDomain
60 using State = ArtificialTreeState;
61 using State_Key = std::uint64_t;
63 size_t leaf_depth = 2;
65 [[
nodiscard]] State_Key state_key(
const State &state)
const noexcept
67 return (
static_cast<State_Key
>(state.depth) << 32)
68 ^
static_cast<State_Key
>(state.code);
71 bool is_goal(
const State &state)
const
73 return state.depth == leaf_depth
and (state.code == 5
or state.code == 6);
78 return state.depth == leaf_depth;
81 void apply(State &state,
const Move &move)
const
83 state.code = state.code*2 + (move.label ==
'R' ? 1u : 0u);
87 void undo(State &state,
const Move &move)
const
90 state.code = (state.code - (move.label ==
'R' ? 1u : 0u))/2;
93 template <
typename Visitor>
94 bool for_each_successor(
const State &state,
Visitor visit)
const
96 if (state.depth >= leaf_depth)
102 return visit(Move{
'R'});
115 explicit NQueensState(
const size_t size = 0)
119 used_columns(
size, 0),
120 used_diag_down(
size == 0 ? 0 : 2*
size - 1, 0),
121 used_diag_up(
size == 0 ? 0 : 2*
size - 1, 0)
134 using State = NQueensState;
135 using State_Key = std::uint64_t;
139 [[
nodiscard]] State_Key state_key(
const State &state)
const noexcept
141 State_Key key =
static_cast<State_Key
>(state.row);
142 for (
size_t row = 0;
row < state.n; ++
row)
144 const int col = state.queens[
row];
145 key = key *
static_cast<State_Key
>(1315423911u)
146 +
static_cast<State_Key
>(
col + 2);
152 bool is_goal(
const State &state)
const
154 return state.row == n;
157 void apply(State &state,
const Move &move)
const
159 const size_t row = state.row;
160 const size_t down =
row + state.n - 1 - move.col;
161 const size_t up =
row + move.col;
163 state.queens[
row] =
static_cast<int>(move.col);
164 state.used_columns[move.col] = 1;
165 state.used_diag_down[
down] = 1;
166 state.used_diag_up[
up] = 1;
170 void undo(State &state,
const Move &move)
const
173 const size_t row = state.row;
174 const size_t down =
row + state.n - 1 - move.col;
175 const size_t up =
row + move.col;
177 state.queens[
row] = -1;
178 state.used_columns[move.col] = 0;
179 state.used_diag_down[
down] = 0;
180 state.used_diag_up[
up] = 0;
183 template <
typename Visitor>
184 bool for_each_successor(
const State &state,
Visitor visit)
const
191 const size_t down = state.row + state.n - 1 -
col;
192 const size_t up = state.row +
col;
193 if (state.used_columns[
col]
or state.used_diag_down[
down]
or state.used_diag_up[
up])
210 explicit SubsetSumState(
const size_t n = 0)
211 : index(0),
sum(0), chosen(n, 0)
225 using State = SubsetSumState;
226 using State_Key = std::uint64_t;
228 explicit SubsetSumDomain(
const Array<int> &values,
const int target)
230 suffix_remaining_(values.
size() + 1, 0),
233 for (
size_t i = values_.size(); i > 0; --i)
234 suffix_remaining_[i - 1] = suffix_remaining_[i] + values_[i - 1];
237 bool is_goal(
const State &state)
const
239 return state.sum == target_;
244 return state.index == values_.size();
247 bool should_prune(
const State &state,
const size_t)
const
249 return state.sum > target_
or state.sum + suffix_remaining_[state.index] < target_;
252 void apply(State &state,
const Move &move)
const
255 state.sum += values_[state.index];
257 state.chosen[state.index] = move.take ? 1 : 0;
261 void undo(State &state,
const Move &move)
const
265 state.sum -= values_[state.index];
267 state.chosen[state.index] = 0;
270 template <
typename Visitor>
271 bool for_each_successor(
const State &state,
Visitor visit)
const
273 if (state.index >= values_.size())
279 return visit(Move{
false});
282 [[
nodiscard]] State_Key state_key(
const State &state)
const noexcept
284 State_Key key =
static_cast<State_Key
>(state.index);
285 key = key *
static_cast<State_Key
>(2654435761u)
286 +
static_cast<State_Key
>(state.sum ^ 0x9e3779b9);
287 for (
const auto pick : state.chosen)
298struct TinyIDAStarState
304struct TinyIDAStarDomain
312 using State = TinyIDAStarState;
313 using State_Key = std::uint64_t;
318 Array<Move>{Move{1, 4}, Move{2, 1}},
323 heuristic_{6, 2, 6, 5, 0}
328 [[
nodiscard]] State_Key state_key(
const State &state)
const noexcept
330 return (
static_cast<State_Key
>(state.node) << 32) |
static_cast<State_Key
>(state.history.size());
333 [[
nodiscard]]
bool is_goal(
const State &state)
const noexcept
335 return state.node == goal_;
340 return adjacency_[state.node].
is_empty();
343 void apply(State &state,
const Move &move)
const
345 state.history.append(state.node);
346 state.node = move.to;
349 void undo(State &state,
const Move &)
const
351 if (state.history.is_empty())
357 const size_t previous = state.history[state.history.size() - 1];
358 (
void) state.history.remove_last();
359 state.node = previous;
362 template <
typename Visitor>
363 bool for_each_successor(
const State &state,
Visitor visit)
const
365 for (
const auto &move : adjacency_[state.node])
376 return heuristic_[state.node];
390struct ThrowingApplyIDAState
392 std::shared_ptr<bool> undo_called;
396struct ThrowingApplyIDADomain
404 using State = ThrowingApplyIDAState;
405 using State_Key = std::uint64_t;
408 [[
nodiscard]] State_Key state_key(
const State &state)
const noexcept
413 [[
nodiscard]]
bool is_goal(
const State &)
const noexcept
420 return state.node != 0;
423 void apply(State &state,
const Move &)
const
429 void undo(State &state,
const Move &)
const
431 *state.undo_called =
true;
435 template <
typename Visitor>
436 bool for_each_successor(
const State &state,
Visitor visit)
const
441 return visit(Move{1, 1});
455struct ThrowingPostApplyIDAState
457 std::shared_ptr<bool> undo_called;
461struct ThrowingPostApplyIDADomain
469 using State = ThrowingPostApplyIDAState;
470 using State_Key = std::uint64_t;
473 [[
nodiscard]] State_Key state_key(
const State &state)
const noexcept
478 [[
nodiscard]]
bool is_goal(
const State &)
const noexcept
485 return state.node != 0;
488 void apply(State &state,
const Move &move)
const
490 state.node = move.to;
493 void undo(State &state,
const Move &)
const
495 *state.undo_called =
true;
499 template <
typename Visitor>
500 bool for_each_successor(
const State &state,
Visitor visit)
const
505 return visit(Move{1, 1});
541 explicit NoDefaultMove(
const int d) :
delta(d) {}
544struct NoCopyAssignMove
547 NoCopyAssignMove() =
default;
548 NoCopyAssignMove(
const NoCopyAssignMove &) =
default;
549 NoCopyAssignMove(NoCopyAssignMove &&) =
default;
550 NoCopyAssignMove &operator=(NoCopyAssignMove &&) =
default;
551 NoCopyAssignMove &operator=(
const NoCopyAssignMove &) =
delete;
560struct NoCopyAssignState
563 NoCopyAssignState() =
default;
564 NoCopyAssignState(
const NoCopyAssignState &) =
default;
565 NoCopyAssignState(NoCopyAssignState &&) =
default;
566 NoCopyAssignState &operator=(NoCopyAssignState &&) =
default;
567 NoCopyAssignState &operator=(
const NoCopyAssignState &) =
delete;
579template <
typename Move>
583 for (
const auto &move : path)
591 for (
size_t row = 0;
row < state.n; ++
row)
592 signature.push_back(
static_cast<char>(
'0' + state.queens[
row]));
599 for (
const auto pick : state.chosen)
608 signature.push_back(
static_cast<char>(
'0' + node));
609 for (
const auto &move : path)
612 signature.push_back(
static_cast<char>(
'0' + node));
617template <
typename SolutionList,
typename Projector>
622 for (
auto it = solutions.get_it(); it.has_curr(); it.next_ne())
663 auto *entry =
memo.insert(1, 11);
671 ArtificialDecisionTreeDomain domain;
674 auto result =
engine.search(ArtificialTreeState{});
678 EXPECT_EQ(result.stats.visited_states, 4u);
679 EXPECT_EQ(result.stats.expanded_states, 2u);
680 EXPECT_EQ(result.stats.generated_successors, 3u);
681 EXPECT_EQ(result.stats.solutions_found, 1u);
682 EXPECT_EQ(result.stats.terminal_states, 1u);
683 EXPECT_EQ(result.stats.max_depth_reached, 2u);
690 ArtificialDecisionTreeDomain domain;
700 EXPECT_EQ(result.stats.visited_states, 7u);
701 EXPECT_EQ(result.stats.expanded_states, 3u);
702 EXPECT_EQ(result.stats.generated_successors, 6u);
703 EXPECT_EQ(result.stats.solutions_found, 2u);
704 EXPECT_EQ(result.stats.terminal_states, 2u);
707 [](
const auto &solution)
709 return path_signature(solution.path);
712 [](
const auto &solution)
714 return path_signature(solution.path);
720 ArtificialDecisionTreeDomain domain;
734 EXPECT_EQ(result.stats.visited_states, 3u);
735 EXPECT_EQ(result.stats.expanded_states, 1u);
736 EXPECT_EQ(result.stats.pruned_by_depth, 2u);
742 NQueensDomain domain{4};
753 EXPECT_EQ(result.stats.solutions_found, 2u);
755 EXPECT_EQ(result.best_solution.get().depth, 4u);
757 [](
const auto &solution)
759 return nqueens_signature(solution.state);
762 [](
const auto &solution)
764 return nqueens_signature(solution.state);
770 NQueensDomain domain{4};
778 auto result =
engine.search(NQueensState(4));
782 EXPECT_GT(result.stats.pruned_by_depth, 0u);
787 SubsetSumDomain domain(
Array<int>{4, 1, 1, 2}, 2);
798 EXPECT_EQ(result.stats.solutions_found, 2u);
800 EXPECT_GT(result.stats.pruned_by_domain, 0u);
802 [](
const auto &solution)
804 return subset_sum_signature(solution.state);
807 [](
const auto &solution)
809 return subset_sum_signature(solution.state);
815 SubsetSumDomain domain(
Array<int>{4, 1, 1, 2}, 2);
828 EXPECT_EQ(result.stats.solutions_found, 1u);
834 TinyIDAStarDomain domain;
837 auto result =
engine.search(TinyIDAStarDomain::State{});
842 EXPECT_EQ(result.best_solution.get().path.size(), 2u);
845 EXPECT_EQ(result.iterations[0].threshold, 6);
850 TinyIDAStarDomain domain;
855 auto result =
engine.
search(TinyIDAStarDomain::State{});
859 EXPECT_GT(result.stats.pruned_by_depth, 0u);
864 TinyIDAStarDomain domain;
880 EXPECT_EQ(result.stats.solutions_found, 1u);
885 auto undo_called = std::make_shared<bool>(
false);
886 ThrowingApplyIDADomain domain;
889 EXPECT_THROW((
void)
engine.search(ThrowingApplyIDAState{undo_called, 0}), std::runtime_error);
895 auto undo_called = std::make_shared<bool>(
false);
896 ThrowingPostApplyIDADomain domain;
899 EXPECT_THROW((
void)
engine.search(ThrowingPostApplyIDAState{undo_called, 0}), std::runtime_error);
911 ArtificialDecisionTreeDomain domain;
919 auto result =
engine.search(ArtificialTreeState{});
923 EXPECT_EQ(result.stats.visited_states, 1u);
924 EXPECT_EQ(result.stats.expanded_states, 0u);
925 EXPECT_EQ(result.stats.pruned_by_depth, 1u);
926 EXPECT_EQ(result.stats.generated_successors, 0u);
933 ArtificialDecisionTreeDomain domain;
941 auto result =
engine.search(ArtificialTreeState{});
944 EXPECT_EQ(result.stats.expanded_states, 1u);
945 EXPECT_GE(result.stats.generated_successors, 1u);
946 EXPECT_LE(result.stats.generated_successors, 2u);
947 EXPECT_EQ(result.stats.solutions_found, 0u);
962 using State = ArtificialTreeState;
963 using State_Key = size_t;
965 [[
nodiscard]] State_Key state_key(
const State &state)
const noexcept
970 bool is_goal(
const State &)
const
975 void apply(State &state,
const Move &)
const
980 void undo(State &state,
const Move &)
const
985 template <
typename Visitor>
986 bool for_each_successor(
const State &,
Visitor visit)
const
988 return visit(Move{1});
996 RootGoalDomain domain;
999 auto result =
engine.search(ArtificialTreeState{});
1003 EXPECT_EQ(result.stats.solutions_found, 1u);
1004 EXPECT_EQ(result.stats.visited_states, 1u);
1005 EXPECT_EQ(result.stats.expanded_states, 0u);
1006 EXPECT_EQ(result.best_solution.get().depth, 0u);
1007 EXPECT_TRUE(result.best_solution.get().path.is_empty());
1014 RootGoalDomain domain;
1023 EXPECT_EQ(result.stats.solutions_found, 1u);
1024 EXPECT_EQ(result.best_solution.get().depth, 0u);
1039struct CyclicGraphState
1052 using State = CyclicGraphState;
1062 return state.node == 3;
1065 void apply(
State &state,
const Move &move)
const
1067 state.node = move.to;
1070 void undo(
State &state,
const Move &move)
const
1072 state.node = move.from;
1075 template <
typename Visitor>
1081 return visit(Move{0, 1});
1085 return visit(Move{1, 2});
1087 return visit(Move{2, 3});
1102 auto result =
engine.search(CyclicGraphState{}, visited);
1105 EXPECT_EQ(result.best_solution.get().state.node, 3u);
1106 EXPECT_GT(result.stats.pruned_by_visited, 0u);
1121struct MultiPathState
1126struct MultiPathDomain
1134 using State = MultiPathState;
1135 using State_Key =
int;
1137 static constexpr int S = 1;
1138 static constexpr int G = 2;
1139 static constexpr int A = 3;
1140 static constexpr int B = 4;
1142 [[
nodiscard]] State_Key state_key(
const State &state)
const noexcept
1147 bool is_goal(
const State &state)
const
1149 return state.node ==
G;
1152 void apply(State &state,
const Move &move)
const
1154 state.node = move.to;
1157 void undo(State &state,
const Move &move)
const
1159 state.node = move.from;
1162 template <
typename Visitor>
1163 bool for_each_successor(
const State &state,
Visitor visit)
const
1170 return visit(Move{0, A});
1174 return visit(Move{A, B});
1176 return visit(Move{B,
S});
1187 MultiPathDomain domain;
1194 auto result =
engine.search(MultiPathState{}, visited);
1197 EXPECT_EQ(result.stats.solutions_found, 1u);
1198 EXPECT_EQ(result.stats.pruned_by_visited, 1u);
1199 EXPECT_EQ(result.best_solution.get().state.node, MultiPathDomain::G);
1205 TinyIDAStarDomain domain;
1210 auto result =
engine.
search(TinyIDAStarDomain::State{});
1214 EXPECT_GT(result.stats.pruned_by_depth, 0u);
1215 EXPECT_EQ(result.stats.expanded_states, 0u);
1222struct IDAStarRootGoalDomain
1230 using State = TinyIDAStarState;
1231 using State_Key = size_t;
1234 [[
nodiscard]] State_Key state_key(
const State &state)
const noexcept
1239 bool is_goal(
const State &state)
const
1241 return state.node == 0;
1249 void apply(State &state,
const Move &move)
const
1251 state.history.append(state.node);
1252 state.node =
static_cast<size_t>(move.to);
1255 void undo(State &state,
const Move &)
const
1257 if (state.history.is_empty())
1260 const size_t prev = state.history[state.history.size() - 1];
1261 (
void) state.history.remove_last();
1265 template <
typename Visitor>
1266 bool for_each_successor(
const State &,
Visitor visit)
const
1268 return visit(Move{1, 5});
1286 IDAStarRootGoalDomain domain;
1289 auto result =
engine.search(TinyIDAStarState{});
1293 EXPECT_EQ(result.stats.solutions_found, 1u);
1294 EXPECT_TRUE(result.best_solution.get().path.is_empty());
1302struct ZeroCostIDADomain
1310 using State = TinyIDAStarState;
1311 using State_Key = size_t;
1314 [[
nodiscard]] State_Key state_key(
const State &state)
const noexcept
1319 bool is_goal(
const State &state)
const
1321 return state.node == 2;
1324 void apply(State &state,
const Move &move)
const
1326 state.history.append(state.node);
1327 state.node = move.to;
1330 void undo(State &state,
const Move &)
const
1332 if (state.history.is_empty())
1335 const size_t prev = state.history[state.history.size() - 1];
1336 (
void) state.history.remove_last();
1340 template <
typename Visitor>
1341 bool for_each_successor(
const State &state,
Visitor visit)
const
1343 if (state.node == 0)
1344 return visit(Move{1, 0});
1345 if (state.node == 1)
1346 return visit(Move{2, 0});
1365 ZeroCostIDADomain domain;
1368 auto result =
engine.search(TinyIDAStarState{});
1372 EXPECT_EQ(result.stats.solutions_found, 1u);
1378 NQueensDomain domain{0};
1389 EXPECT_EQ(result.stats.solutions_found, 1u);
1397 NQueensDomain domain{1};
1408 EXPECT_EQ(result.stats.solutions_found, 1u);
1416 NQueensDomain domain{8};
1427 EXPECT_EQ(result.stats.solutions_found, 92u);
1452struct NoPathIDADomain
1460 using State = TinyIDAStarState;
1461 using State_Key = size_t;
1464 [[
nodiscard]] State_Key state_key(
const State &state)
const noexcept
1469 [[
nodiscard]]
bool is_goal(
const State &state)
const noexcept
1471 return state.node == 9;
1474 void apply(State &state,
const Move &move)
const
1476 state.history.append(state.node);
1477 state.node = move.to;
1480 void undo(State &state,
const Move &)
const
1482 if (state.history.is_empty())
1484 const size_t prev = state.history[state.history.size() - 1];
1485 (
void) state.history.remove_last();
1489 template <
typename Visitor>
1490 bool for_each_successor(
const State &state,
Visitor visit)
const
1493 if (state.node == 0)
1494 return visit(Move{1, 1});
1518struct InadmissibleHeuristicDomain
1526 using State = TinyIDAStarState;
1527 using State_Key = size_t;
1530 [[
nodiscard]] State_Key state_key(
const State &state)
const noexcept
1535 [[
nodiscard]]
bool is_goal(
const State &state)
const noexcept
1537 return state.node == 2;
1540 void apply(State &state,
const Move &move)
const
1542 state.history.append(state.node);
1543 state.node = move.to;
1546 void undo(State &state,
const Move &)
const
1548 if (state.history.is_empty())
1550 const size_t prev = state.history[state.history.size() - 1];
1551 (
void) state.history.remove_last();
1555 template <
typename Visitor>
1556 bool for_each_successor(
const State &state,
Visitor visit)
const
1558 if (state.node == 0)
1559 return visit(Move{1, 1});
1560 if (state.node == 1)
1561 return visit(Move{2, 1});
1568 return state.node == 2 ? 0.0 : 100.0;
1581 NoPathIDADomain domain;
1584 auto result =
engine.search(TinyIDAStarState{});
1589 EXPECT_EQ(result.iterations[result.iterations.size() - 1].next_threshold,
1590 ida_star_detail::distance_unreachable<double>());
1595 InadmissibleHeuristicDomain domain;
1598 auto result =
engine.search(TinyIDAStarState{});
Umbrella header for the implicit state-space search framework.
IDA* (Iterative Deepening A*) over implicit state spaces.
Exception handling system with formatted messages for Aleph-w.
#define ah_runtime_error()
Throws std::runtime_error unconditionally.
Simple dynamic array with automatic resizing and functional operations.
constexpr size_t size() const noexcept
Return the number of elements stored in the stack.
constexpr bool is_empty() const noexcept
Checks if the container is empty.
T & append(const T &data)
Append a copy of data
Tracks the best solution seen so far according to a comparator.
bool has_value() const noexcept
Return true if an incumbent exists.
bool consider(const Solution &candidate)
Consider a candidate by copy.
const Solution & get() const
Read the current incumbent.
Recursive depth-first backtracking over an implicit state space.
Result search(State initial_state)
Execute a depth-first backtracking search from initial_state.
IDA* engine for implicit state spaces with an admissible heuristic.
Result search(State initial_state)
Run IDA* from initial_state.
Graph implemented with double-linked adjacency lists.
Collector that stores accepted solutions in an Aleph list.
Minimal std::expected-style result type for C++20.
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
constexpr size_t Search_Unlimited
Sentinel used by SearchLimits to mean "no bound".
and
Check uniqueness with explicit hash + equality functors.
std::string code(Node *root)
Compute a string with the Lukasiewicz`s word of a tree.
T sum(const Container &container, const T &init=T{})
Compute sum of all elements.
Exploration controls shared across engines.
bool stop_at_first_solution
Stop when the first goal is found.
Strategy strategy
Traversal strategy.
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.
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.
size_t visited_states
Number of states entered by the engine.
void apply(State &s, const Move &m) const noexcept
State_Key state_key(const State &s) const noexcept
bool for_each_successor(const State &s, Visitor visit) const
void undo(State &s, const Move &m) const noexcept
bool is_goal(const State &s) const noexcept