test_astar.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. #include <catch2/catch.hpp>
  2. #include "libslic3r/BoundingBox.hpp"
  3. #include "libslic3r/AStar.hpp"
  4. #include "libslic3r/Execution/ExecutionSeq.hpp"
  5. #include "libslic3r/PointGrid.hpp"
  6. using namespace Slic3r;
  7. TEST_CASE("Testing basic invariants of AStar", "[AStar]") {
  8. struct DummyTracer {
  9. using Node = int;
  10. int goal = 0;
  11. float distance(int a, int b) const { return a - b; }
  12. float goal_heuristic(int n) const { return n == goal ? -1.f : 0.f; }
  13. size_t unique_id(int n) const { return n; }
  14. void foreach_reachable(int, std::function<bool(int)>) const {}
  15. };
  16. std::vector<int> out;
  17. SECTION("Output is empty when source is also the destination") {
  18. bool found = astar::search_route(DummyTracer{}, 0, std::back_inserter(out));
  19. REQUIRE(out.empty());
  20. REQUIRE(found);
  21. }
  22. SECTION("Return false when there is no route to destination") {
  23. bool found = astar::search_route(DummyTracer{}, 1, std::back_inserter(out));
  24. REQUIRE(!found);
  25. REQUIRE(out.empty());
  26. }
  27. }
  28. struct PointGridTracer3D {
  29. using Node = size_t;
  30. const PointGrid<float> &grid;
  31. size_t final;
  32. PointGridTracer3D(const PointGrid<float> &g, size_t goal) :
  33. grid{g}, final{goal} {}
  34. template<class Fn>
  35. void foreach_reachable(size_t from, Fn &&fn) const
  36. {
  37. Vec3i from_crd = grid.get_coord(from);
  38. REQUIRE(grid.get_idx(from_crd) == from);
  39. if (size_t i = grid.get_idx(from_crd + Vec3i{ 1, 0, 0}); i < grid.point_count()) fn(i);
  40. if (size_t i = grid.get_idx(from_crd + Vec3i{ 0, 1, 0}); i < grid.point_count()) fn(i);
  41. if (size_t i = grid.get_idx(from_crd + Vec3i{ 0, 0, 1}); i < grid.point_count()) fn(i);
  42. if (size_t i = grid.get_idx(from_crd + Vec3i{ 1, 1, 0}); i < grid.point_count()) fn(i);
  43. if (size_t i = grid.get_idx(from_crd + Vec3i{ 0, 1, 1}); i < grid.point_count()) fn(i);
  44. if (size_t i = grid.get_idx(from_crd + Vec3i{ 1, 1, 1}); i < grid.point_count()) fn(i);
  45. if (size_t i = grid.get_idx(from_crd + Vec3i{-1, 0, 0}); from_crd.x() > 0 && i < grid.point_count()) fn(i);
  46. if (size_t i = grid.get_idx(from_crd + Vec3i{ 0, -1, 0}); from_crd.y() > 0 && i < grid.point_count()) fn(i);
  47. if (size_t i = grid.get_idx(from_crd + Vec3i{ 0, 0, -1}); from_crd.z() > 0 && i < grid.point_count()) fn(i);
  48. if (size_t i = grid.get_idx(from_crd + Vec3i{-1, -1, 0}); from_crd.x() > 0 && from_crd.y() > 0 && i < grid.point_count()) fn(i);
  49. if (size_t i = grid.get_idx(from_crd + Vec3i{ 0, -1, -1}); from_crd.y() > 0 && from_crd.z() && i < grid.point_count()) fn(i);
  50. if (size_t i = grid.get_idx(from_crd + Vec3i{-1, -1, -1}); from_crd.x() > 0 && from_crd.y() > 0 && from_crd.z() && i < grid.point_count()) fn(i);
  51. }
  52. float distance(size_t a, size_t b) const
  53. {
  54. return (grid.get(a) - grid.get(b)).squaredNorm();
  55. }
  56. float goal_heuristic(size_t n) const
  57. {
  58. return n == final ? -1.f : (grid.get(n) - grid.get(final)).squaredNorm();
  59. }
  60. size_t unique_id(size_t n) const { return n; }
  61. };
  62. template<class Node, class Cmp = std::less<Node>>
  63. bool has_duplicates(const std::vector<Node> &res, Cmp cmp = {})
  64. {
  65. auto cpy = res;
  66. std::sort(cpy.begin(), cpy.end(), cmp);
  67. auto it = std::unique(cpy.begin(), cpy.end());
  68. return it != cpy.end();
  69. }
  70. TEST_CASE("astar algorithm test over 3D point grid", "[AStar]") {
  71. auto vol = BoundingBox3Base<Vec3f>{{0.f, 0.f, 0.f}, {1.f, 1.f, 1.f}};
  72. auto pgrid = point_grid(ex_seq, vol, {0.1f, 0.1f, 0.1f});
  73. size_t target = pgrid.point_count() - 1;
  74. PointGridTracer3D pgt{pgrid, target};
  75. std::vector<size_t> out;
  76. bool found = astar::search_route(pgt, 0, std::back_inserter(out));
  77. REQUIRE(found);
  78. REQUIRE(!out.empty());
  79. REQUIRE(out.front() == target);
  80. #ifndef NDEBUG
  81. std::cout << "Route taken: ";
  82. for (auto it = out.rbegin(); it != out.rend(); ++it) {
  83. std::cout << "(" << pgrid.get_coord(*it).transpose() << ") ";
  84. }
  85. std::cout << std::endl;
  86. #endif
  87. REQUIRE(!has_duplicates(out)); // No duplicates in output
  88. }
  89. enum CellValue {ON, OFF};
  90. struct CellGridTracer2D_AllDirs {
  91. using Node = Vec2i;
  92. static constexpr auto Cols = size_t(5);
  93. static constexpr auto Rows = size_t(8);
  94. static constexpr size_t GridSize = Cols * Rows;
  95. const std::array<std::array<CellValue, Cols>, Rows> &grid;
  96. Vec2i goal;
  97. CellGridTracer2D_AllDirs(const std::array<std::array<CellValue, Cols>, Rows> &g,
  98. const Vec2i &goal_)
  99. : grid{g}, goal{goal_}
  100. {}
  101. template<class Fn>
  102. void foreach_reachable(const Vec2i &src, Fn &&fn) const
  103. {
  104. auto is_inside = [](const Vec2i& v) { return v.x() >= 0 && v.x() < int(Cols) && v.y() >= 0 && v.y() < int(Rows); };
  105. if (Vec2i crd = src + Vec2i{0, 1}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd);
  106. if (Vec2i crd = src + Vec2i{1, 0}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd);
  107. if (Vec2i crd = src + Vec2i{1, 1}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd);
  108. if (Vec2i crd = src + Vec2i{0, -1}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd);
  109. if (Vec2i crd = src + Vec2i{-1, 0}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd);
  110. if (Vec2i crd = src + Vec2i{-1, -1}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd);
  111. if (Vec2i crd = src + Vec2i{1, -1}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd);
  112. if (Vec2i crd = src + Vec2i{-1, 1}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd);
  113. }
  114. float distance(const Vec2i & a, const Vec2i & b) const { return (a - b).squaredNorm(); }
  115. float goal_heuristic(const Vec2i & n) const { return n == goal ? -1.f : (n - goal).squaredNorm(); }
  116. size_t unique_id(const Vec2i & n) const { return n.y() * Cols + n.x(); }
  117. };
  118. struct CellGridTracer2D_Axis {
  119. using Node = Vec2i;
  120. static constexpr auto Cols = size_t(5);
  121. static constexpr auto Rows = size_t(8);
  122. static constexpr size_t GridSize = Cols * Rows;
  123. const std::array<std::array<CellValue, Cols>, Rows> &grid;
  124. Vec2i goal;
  125. CellGridTracer2D_Axis(
  126. const std::array<std::array<CellValue, Cols>, Rows> &g,
  127. const Vec2i &goal_)
  128. : grid{g}, goal{goal_}
  129. {}
  130. template<class Fn>
  131. void foreach_reachable(const Vec2i &src, Fn &&fn) const
  132. {
  133. auto is_inside = [](const Vec2i& v) { return v.x() >= 0 && v.x() < int(Cols) && v.y() >= 0 && v.y() < int(Rows); };
  134. if (Vec2i crd = src + Vec2i{0, 1}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd);
  135. if (Vec2i crd = src + Vec2i{0, -1}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd);
  136. if (Vec2i crd = src + Vec2i{1, 0}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd);
  137. if (Vec2i crd = src + Vec2i{-1, 0}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd);
  138. }
  139. float distance(const Vec2i & a, const Vec2i & b) const { return (a - b).squaredNorm(); }
  140. float goal_heuristic(const Vec2i &n) const
  141. {
  142. int manhattan_dst = std::abs(n.x() - goal.x()) +
  143. std::abs(n.y() - goal.y());
  144. return n == goal ? -1.f : manhattan_dst;
  145. }
  146. size_t unique_id(const Vec2i & n) const { return n.y() * Cols + n.x(); }
  147. };
  148. using TestClasses = std::tuple< CellGridTracer2D_AllDirs, CellGridTracer2D_Axis >;
  149. TEMPLATE_LIST_TEST_CASE("Astar should avoid simple barrier", "[AStar]", TestClasses) {
  150. std::array<std::array<CellValue, 5>, 8> grid = {{
  151. {ON , ON , ON , ON , ON},
  152. {ON , ON , ON , ON , ON},
  153. {ON , ON , ON , ON , ON},
  154. {ON , ON , ON , ON , ON},
  155. {ON , ON , ON , ON , ON},
  156. {ON , OFF, OFF, OFF, ON},
  157. {ON , ON , ON , ON , ON},
  158. {ON , ON , ON , ON , ON}
  159. }};
  160. Vec2i dst = {2, 0};
  161. TestType cgt{grid, dst};
  162. std::vector<Vec2i> out;
  163. bool found = astar::search_route(cgt, {2, 7}, std::back_inserter(out));
  164. REQUIRE(found);
  165. REQUIRE(!out.empty());
  166. REQUIRE(out.front() == dst);
  167. REQUIRE(!has_duplicates(out, [](const Vec2i &a, const Vec2i &b) {
  168. return a.x() == b.x() ? a.y() < b.y() : a.x() < b.x();
  169. }));
  170. #ifndef NDEBUG
  171. std::cout << "Route taken: ";
  172. for (auto it = out.rbegin(); it != out.rend(); ++it) {
  173. std::cout << "(" << it->transpose() << ") ";
  174. }
  175. std::cout << std::endl;
  176. #endif
  177. }
  178. TEMPLATE_LIST_TEST_CASE("Astar should manage to avoid arbitrary barriers", "[AStar]", TestClasses) {
  179. std::array<std::array<CellValue, 5>, 8> grid = {{
  180. {ON , ON , ON , ON , ON},
  181. {ON , ON , ON , OFF, ON},
  182. {OFF, OFF, ON , OFF, ON},
  183. {ON , ON , ON , OFF, ON},
  184. {ON , OFF, ON , OFF, ON},
  185. {ON , OFF, ON , ON , ON},
  186. {ON , OFF, ON , OFF, ON},
  187. {ON , ON , ON , ON , ON}
  188. }};
  189. Vec2i dst = {0, 0};
  190. TestType cgt{grid, dst};
  191. std::vector<Vec2i> out;
  192. bool found = astar::search_route(cgt, {0, 7}, std::back_inserter(out));
  193. REQUIRE(found);
  194. REQUIRE(!out.empty());
  195. REQUIRE(out.front() == dst);
  196. REQUIRE(!has_duplicates(out, [](const Vec2i &a, const Vec2i &b) {
  197. return a.x() == b.x() ? a.y() < b.y() : a.x() < b.x();
  198. }));
  199. #ifndef NDEBUG
  200. std::cout << "Route taken: ";
  201. for (auto it = out.rbegin(); it != out.rend(); ++it) {
  202. std::cout << "(" << it->transpose() << ") ";
  203. }
  204. std::cout << std::endl;
  205. #endif
  206. }
  207. TEMPLATE_LIST_TEST_CASE("Astar should find the way out of a labyrinth", "[AStar]", TestClasses) {
  208. std::array<std::array<CellValue, 5>, 8> grid = {{
  209. {ON , ON , ON , ON , ON },
  210. {ON , OFF, OFF, OFF, OFF},
  211. {ON , ON , ON , ON , ON },
  212. {OFF, OFF, OFF, OFF, ON },
  213. {ON , ON , ON , ON , ON },
  214. {ON , OFF, OFF, OFF, OFF},
  215. {ON , ON , ON , ON , ON },
  216. {OFF, OFF, OFF, OFF, ON }
  217. }};
  218. Vec2i dst = {4, 0};
  219. TestType cgt{grid, dst};
  220. std::vector<Vec2i> out;
  221. bool found = astar::search_route(cgt, {4, 7}, std::back_inserter(out));
  222. REQUIRE(found);
  223. REQUIRE(!out.empty());
  224. REQUIRE(out.front() == dst);
  225. REQUIRE(!has_duplicates(out, [](const Vec2i &a, const Vec2i &b) {
  226. return a.x() == b.x() ? a.y() < b.y() : a.x() < b.x();
  227. }));
  228. #ifndef NDEBUG
  229. std::cout << "Route taken: ";
  230. for (auto it = out.rbegin(); it != out.rend(); ++it) {
  231. std::cout << "(" << it->transpose() << ") ";
  232. }
  233. std::cout << std::endl;
  234. #endif
  235. }
  236. TEST_CASE("Zero heuristic function should result in dijsktra's algo", "[AStar]")
  237. {
  238. struct GraphTracer {
  239. using Node = size_t;
  240. using QNode = astar::QNode<GraphTracer>;
  241. struct Edge
  242. {
  243. size_t to_id = size_t(-1);
  244. float cost = 0.f;
  245. bool operator <(const Edge &e) const { return to_id < e.to_id; }
  246. };
  247. struct ENode: public QNode {
  248. std::vector<Edge> edges;
  249. ENode(size_t node_id, std::initializer_list<Edge> edgelist)
  250. : QNode{node_id}, edges(edgelist)
  251. {}
  252. ENode &operator=(const QNode &q)
  253. {
  254. assert(node == q.node);
  255. g = q.g;
  256. h = q.h;
  257. parent = q.parent;
  258. queue_id = q.queue_id;
  259. return *this;
  260. }
  261. };
  262. // Example graph from
  263. // https://www.geeksforgeeks.org/dijkstras-shortest-path-algorithm-greedy-algo-7/?ref=lbp
  264. std::vector<ENode> nodes = {
  265. {0, {{1, 4.f}, {7, 8.f}}},
  266. {1, {{0, 4.f}, {2, 8.f}, {7, 11.f}}},
  267. {2, {{1, 8.f}, {3, 7.f}, {5, 4.f}, {8, 2.f}}},
  268. {3, {{2, 7.f}, {4, 9.f}, {5, 14.f}}},
  269. {4, {{3, 9.f}, {5, 10.f}}},
  270. {5, {{2, 4.f}, {3, 14.f}, {4, 10.f}, {6, 2.f}}},
  271. {6, {{5, 2.f}, {7, 1.f}, {8, 6.f}}},
  272. {7, {{0, 8.f}, {1, 11.f}, {6, 1.f}, {8, 7.f}}},
  273. {8, {{2, 2.f}, {6, 6.f}, {7, 7.f}}}
  274. };
  275. float distance(size_t a, size_t b) const {
  276. float ret = std::numeric_limits<float>::infinity();
  277. if (a < nodes.size()) {
  278. auto it = std::lower_bound(nodes[a].edges.begin(),
  279. nodes[a].edges.end(),
  280. Edge{b, 0.f});
  281. if (it != nodes[a].edges.end()) {
  282. ret = it->cost;
  283. }
  284. }
  285. return ret;
  286. }
  287. float goal_heuristic(size_t) const { return 0.f; }
  288. size_t unique_id(size_t n) const { return n; }
  289. void foreach_reachable(size_t n, std::function<bool(int)> fn) const
  290. {
  291. if (n < nodes.size()) {
  292. for (const Edge &e : nodes[n].edges)
  293. fn(e.to_id);
  294. }
  295. }
  296. } graph;
  297. std::vector<size_t> out;
  298. // 'graph.nodes' is able to be a node cache (it simulates an associative container)
  299. bool found = astar::search_route(graph, size_t(0), std::back_inserter(out), graph.nodes);
  300. // But should not crash or loop infinitely.
  301. REQUIRE(!found);
  302. // Without a destination, there is no output. But the algorithm should halt.
  303. REQUIRE(out.empty());
  304. // Source node should have it's parent unset
  305. REQUIRE(graph.nodes[0].parent == astar::Unassigned);
  306. // All other nodes should have their parents set
  307. for (size_t i = 1; i < graph.nodes.size(); ++i)
  308. REQUIRE(graph.nodes[i].parent != astar::Unassigned);
  309. std::array<float, 9> ref_distances = {0.f, 4.f, 12.f, 19.f, 21.f,
  310. 11.f, 9.f, 8.f, 14.f};
  311. // Try to trace each node back to the source node. Each of them should
  312. // arrive to the source within less hops than the full number of nodes.
  313. for (size_t i = 0, k = 0; i < graph.nodes.size(); ++i, k = 0) {
  314. GraphTracer::QNode *q = &graph.nodes[i];
  315. REQUIRE(q->g == Approx(ref_distances[i]));
  316. while (k++ < graph.nodes.size() && q->parent != astar::Unassigned)
  317. q = &graph.nodes[q->parent];
  318. REQUIRE(q->parent == astar::Unassigned);
  319. }
  320. }