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
189 for (
size_t col = 0; col < n; ++col)
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});
543template <
typename Move>
547 for (
const auto &move : path)
555 for (
size_t row = 0; row < state.n; ++row)
556 signature.push_back(
static_cast<char>(
'0' + state.queens[row]));
563 for (
const auto pick : state.chosen)
572 signature.push_back(
static_cast<char>(
'0' + node));
573 for (
const auto &move : path)
576 signature.push_back(
static_cast<char>(
'0' + node));
581template <
typename SolutionList,
typename Projector>
586 for (
auto it = solutions.get_it(); it.has_curr(); it.next_ne())
627 auto *entry =
memo.insert(1, 11);
635 ArtificialDecisionTreeDomain domain;
638 auto result =
engine.search(ArtificialTreeState{});
642 EXPECT_EQ(result.stats.visited_states, 4u);
643 EXPECT_EQ(result.stats.expanded_states, 2u);
644 EXPECT_EQ(result.stats.generated_successors, 3u);
645 EXPECT_EQ(result.stats.solutions_found, 1u);
646 EXPECT_EQ(result.stats.terminal_states, 1u);
647 EXPECT_EQ(result.stats.max_depth_reached, 2u);
654 ArtificialDecisionTreeDomain domain;
664 EXPECT_EQ(result.stats.visited_states, 7u);
665 EXPECT_EQ(result.stats.expanded_states, 3u);
666 EXPECT_EQ(result.stats.generated_successors, 6u);
667 EXPECT_EQ(result.stats.solutions_found, 2u);
668 EXPECT_EQ(result.stats.terminal_states, 2u);
671 [](
const auto &solution)
673 return path_signature(solution.path);
676 [](
const auto &solution)
678 return path_signature(solution.path);
684 ArtificialDecisionTreeDomain domain;
698 EXPECT_EQ(result.stats.visited_states, 3u);
699 EXPECT_EQ(result.stats.expanded_states, 1u);
700 EXPECT_EQ(result.stats.pruned_by_depth, 2u);
706 NQueensDomain domain{4};
717 EXPECT_EQ(result.stats.solutions_found, 2u);
719 EXPECT_EQ(result.best_solution.get().depth, 4u);
721 [](
const auto &solution)
723 return nqueens_signature(solution.state);
726 [](
const auto &solution)
728 return nqueens_signature(solution.state);
734 NQueensDomain domain{4};
742 auto result =
engine.search(NQueensState(4));
746 EXPECT_GT(result.stats.pruned_by_depth, 0u);
751 SubsetSumDomain domain(
Array<int>{4, 1, 1, 2}, 2);
762 EXPECT_EQ(result.stats.solutions_found, 2u);
764 EXPECT_GT(result.stats.pruned_by_domain, 0u);
766 [](
const auto &solution)
768 return subset_sum_signature(solution.state);
771 [](
const auto &solution)
773 return subset_sum_signature(solution.state);
779 SubsetSumDomain domain(
Array<int>{4, 1, 1, 2}, 2);
792 EXPECT_EQ(result.stats.solutions_found, 1u);
798 TinyIDAStarDomain domain;
801 auto result =
engine.search(TinyIDAStarDomain::State{});
806 EXPECT_EQ(result.best_solution.get().path.size(), 2u);
809 EXPECT_EQ(result.iterations[0].threshold, 6);
814 TinyIDAStarDomain domain;
819 auto result =
engine.
search(TinyIDAStarDomain::State{});
823 EXPECT_GT(result.stats.pruned_by_depth, 0u);
828 TinyIDAStarDomain domain;
844 EXPECT_EQ(result.stats.solutions_found, 1u);
849 auto undo_called = std::make_shared<bool>(
false);
850 ThrowingApplyIDADomain domain;
853 EXPECT_THROW((
void)
engine.search(ThrowingApplyIDAState{undo_called, 0}), std::runtime_error);
859 auto undo_called = std::make_shared<bool>(
false);
860 ThrowingPostApplyIDADomain domain;
863 EXPECT_THROW((
void)
engine.search(ThrowingPostApplyIDAState{undo_called, 0}), std::runtime_error);
875 ArtificialDecisionTreeDomain domain;
883 auto result =
engine.search(ArtificialTreeState{});
887 EXPECT_EQ(result.stats.visited_states, 1u);
888 EXPECT_EQ(result.stats.expanded_states, 0u);
889 EXPECT_EQ(result.stats.pruned_by_depth, 1u);
890 EXPECT_EQ(result.stats.generated_successors, 0u);
897 ArtificialDecisionTreeDomain domain;
905 auto result =
engine.search(ArtificialTreeState{});
908 EXPECT_EQ(result.stats.expanded_states, 1u);
909 EXPECT_GE(result.stats.generated_successors, 1u);
910 EXPECT_LE(result.stats.generated_successors, 2u);
911 EXPECT_EQ(result.stats.solutions_found, 0u);
926 using State = ArtificialTreeState;
927 using State_Key = size_t;
929 [[
nodiscard]] State_Key state_key(
const State &state)
const noexcept
934 bool is_goal(
const State &)
const
939 void apply(State &state,
const Move &)
const
944 void undo(State &state,
const Move &)
const
949 template <
typename Visitor>
950 bool for_each_successor(
const State &,
Visitor visit)
const
952 return visit(Move{1});
960 RootGoalDomain domain;
963 auto result =
engine.search(ArtificialTreeState{});
967 EXPECT_EQ(result.stats.solutions_found, 1u);
968 EXPECT_EQ(result.stats.visited_states, 1u);
969 EXPECT_EQ(result.stats.expanded_states, 0u);
970 EXPECT_EQ(result.best_solution.get().depth, 0u);
971 EXPECT_TRUE(result.best_solution.get().path.is_empty());
978 RootGoalDomain domain;
987 EXPECT_EQ(result.stats.solutions_found, 1u);
988 EXPECT_EQ(result.best_solution.get().depth, 0u);
1003struct CyclicGraphState
1016 using State = CyclicGraphState;
1026 return state.node == 3;
1029 void apply(
State &state,
const Move &move)
const
1031 state.node = move.to;
1034 void undo(
State &state,
const Move &move)
const
1036 state.node = move.from;
1039 template <
typename Visitor>
1045 return visit(Move{0, 1});
1049 return visit(Move{1, 2});
1051 return visit(Move{2, 3});
1066 auto result =
engine.search(CyclicGraphState{}, visited);
1069 EXPECT_EQ(result.best_solution.get().state.node, 3u);
1070 EXPECT_GT(result.stats.pruned_by_visited, 0u);
1085struct MultiPathState
1090struct MultiPathDomain
1098 using State = MultiPathState;
1099 using State_Key =
int;
1101 static constexpr int S = 1;
1102 static constexpr int G = 2;
1103 static constexpr int A = 3;
1104 static constexpr int B = 4;
1106 [[
nodiscard]] State_Key state_key(
const State &state)
const noexcept
1111 bool is_goal(
const State &state)
const
1113 return state.node ==
G;
1116 void apply(State &state,
const Move &move)
const
1118 state.node = move.to;
1121 void undo(State &state,
const Move &move)
const
1123 state.node = move.from;
1126 template <
typename Visitor>
1127 bool for_each_successor(
const State &state,
Visitor visit)
const
1134 return visit(Move{0, A});
1136 return visit(Move{S,
G});
1138 return visit(Move{A, B});
1140 return visit(Move{B, S});
1151 MultiPathDomain domain;
1158 auto result =
engine.search(MultiPathState{}, visited);
1161 EXPECT_EQ(result.stats.solutions_found, 1u);
1162 EXPECT_EQ(result.stats.pruned_by_visited, 1u);
1163 EXPECT_EQ(result.best_solution.get().state.node, MultiPathDomain::G);
1169 TinyIDAStarDomain domain;
1174 auto result =
engine.
search(TinyIDAStarDomain::State{});
1178 EXPECT_GT(result.stats.pruned_by_depth, 0u);
1179 EXPECT_EQ(result.stats.expanded_states, 0u);
1186struct IDAStarRootGoalDomain
1194 using State = TinyIDAStarState;
1195 using State_Key = size_t;
1198 [[
nodiscard]] State_Key state_key(
const State &state)
const noexcept
1203 bool is_goal(
const State &state)
const
1205 return state.node == 0;
1213 void apply(State &state,
const Move &move)
const
1215 state.history.append(state.node);
1216 state.node =
static_cast<size_t>(move.to);
1219 void undo(State &state,
const Move &)
const
1221 if (state.history.is_empty())
1224 const size_t prev = state.history[state.history.size() - 1];
1225 (
void) state.history.remove_last();
1229 template <
typename Visitor>
1230 bool for_each_successor(
const State &,
Visitor visit)
const
1232 return visit(Move{1, 5});
1250 IDAStarRootGoalDomain domain;
1253 auto result =
engine.search(TinyIDAStarState{});
1257 EXPECT_EQ(result.stats.solutions_found, 1u);
1258 EXPECT_TRUE(result.best_solution.get().path.is_empty());
1266struct ZeroCostIDADomain
1274 using State = TinyIDAStarState;
1275 using State_Key = size_t;
1278 [[
nodiscard]] State_Key state_key(
const State &state)
const noexcept
1283 bool is_goal(
const State &state)
const
1285 return state.node == 2;
1288 void apply(State &state,
const Move &move)
const
1290 state.history.append(state.node);
1291 state.node = move.to;
1294 void undo(State &state,
const Move &)
const
1296 if (state.history.is_empty())
1299 const size_t prev = state.history[state.history.size() - 1];
1300 (
void) state.history.remove_last();
1304 template <
typename Visitor>
1305 bool for_each_successor(
const State &state,
Visitor visit)
const
1307 if (state.node == 0)
1308 return visit(Move{1, 0});
1309 if (state.node == 1)
1310 return visit(Move{2, 0});
1329 ZeroCostIDADomain domain;
1332 auto result =
engine.search(TinyIDAStarState{});
1336 EXPECT_EQ(result.stats.solutions_found, 1u);
1342 NQueensDomain domain{0};
1353 EXPECT_EQ(result.stats.solutions_found, 1u);
1361 NQueensDomain domain{1};
1372 EXPECT_EQ(result.stats.solutions_found, 1u);
1380 NQueensDomain domain{8};
1391 EXPECT_EQ(result.stats.solutions_found, 92u);
1416struct NoPathIDADomain
1424 using State = TinyIDAStarState;
1425 using State_Key = size_t;
1428 [[
nodiscard]] State_Key state_key(
const State &state)
const noexcept
1433 [[
nodiscard]]
bool is_goal(
const State &state)
const noexcept
1435 return state.node == 9;
1438 void apply(State &state,
const Move &move)
const
1440 state.history.append(state.node);
1441 state.node = move.to;
1444 void undo(State &state,
const Move &)
const
1446 if (state.history.is_empty())
1448 const size_t prev = state.history[state.history.size() - 1];
1449 (
void) state.history.remove_last();
1453 template <
typename Visitor>
1454 bool for_each_successor(
const State &state,
Visitor visit)
const
1457 if (state.node == 0)
1458 return visit(Move{1, 1});
1482struct InadmissibleHeuristicDomain
1490 using State = TinyIDAStarState;
1491 using State_Key = size_t;
1494 [[
nodiscard]] State_Key state_key(
const State &state)
const noexcept
1499 [[
nodiscard]]
bool is_goal(
const State &state)
const noexcept
1501 return state.node == 2;
1504 void apply(State &state,
const Move &move)
const
1506 state.history.append(state.node);
1507 state.node = move.to;
1510 void undo(State &state,
const Move &)
const
1512 if (state.history.is_empty())
1514 const size_t prev = state.history[state.history.size() - 1];
1515 (
void) state.history.remove_last();
1519 template <
typename Visitor>
1520 bool for_each_successor(
const State &state,
Visitor visit)
const
1522 if (state.node == 0)
1523 return visit(Move{1, 1});
1524 if (state.node == 1)
1525 return visit(Move{2, 1});
1532 return state.node == 2 ? 0.0 : 100.0;
1545 NoPathIDADomain domain;
1548 auto result =
engine.search(TinyIDAStarState{});
1553 EXPECT_EQ(result.iterations[result.iterations.size() - 1].next_threshold,
1554 ida_star_detail::distance_unreachable<double>());
1559 InadmissibleHeuristicDomain domain;
1562 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.
Main namespace for Aleph-w library functions.
and
Check uniqueness with explicit hash + equality functors.
size_t size(Node *root) noexcept
constexpr size_t Search_Unlimited
Sentinel used by SearchLimits to mean "no bound".
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::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