Nav2 Navigation Stack - humble  humble
ROS 2 Navigation Stack
a_star.cpp
1 // Copyright (c) 2020, Samsung Research America
2 // Copyright (c) 2020, Applied Electric Vehicles Pty Ltd
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License. Reserved.
15 
16 #include <omp.h>
17 #include <cmath>
18 #include <stdexcept>
19 #include <memory>
20 #include <algorithm>
21 #include <limits>
22 #include <type_traits>
23 #include <chrono>
24 #include <thread>
25 #include <utility>
26 #include <vector>
27 
28 #include "nav2_smac_planner/a_star.hpp"
29 using namespace std::chrono; // NOLINT
30 
31 namespace nav2_smac_planner
32 {
33 
34 template<typename NodeT>
36  const MotionModel & motion_model,
37  const SearchInfo & search_info)
38 : _traverse_unknown(true),
39  _is_initialized(false),
40  _max_iterations(0),
41  _max_planning_time(0),
42  _x_size(0),
43  _y_size(0),
44  _search_info(search_info),
45  _goal_coordinates(Coordinates()),
46  _start(nullptr),
47  _goal(nullptr),
48  _motion_model(motion_model)
49 {
50  _graph.reserve(100000);
51 }
52 
53 template<typename NodeT>
55 {
56 }
57 
58 template<typename NodeT>
60  const bool & allow_unknown,
61  int & max_iterations,
62  const int & max_on_approach_iterations,
63  const double & max_planning_time,
64  const float & lookup_table_size,
65  const unsigned int & dim_3_size)
66 {
67  _traverse_unknown = allow_unknown;
68  _max_iterations = max_iterations;
69  _max_on_approach_iterations = max_on_approach_iterations;
70  _max_planning_time = max_planning_time;
71  if (!_is_initialized) {
72  NodeT::precomputeDistanceHeuristic(lookup_table_size, _motion_model, dim_3_size, _search_info);
73  }
74  _is_initialized = true;
75  _dim3_size = dim_3_size;
76  _expander = std::make_unique<AnalyticExpansion<NodeT>>(
77  _motion_model, _search_info, _traverse_unknown, _dim3_size);
78 }
79 
80 template<>
82  const bool & allow_unknown,
83  int & max_iterations,
84  const int & max_on_approach_iterations,
85  const double & max_planning_time,
86  const float & /*lookup_table_size*/,
87  const unsigned int & dim_3_size)
88 {
89  _traverse_unknown = allow_unknown;
90  _max_iterations = max_iterations;
91  _max_on_approach_iterations = max_on_approach_iterations;
92  _max_planning_time = max_planning_time;
93 
94  if (dim_3_size != 1) {
95  throw std::runtime_error("Node type Node2D cannot be given non-1 dim 3 quantization.");
96  }
97  _dim3_size = dim_3_size;
98  _expander = std::make_unique<AnalyticExpansion<Node2D>>(
99  _motion_model, _search_info, _traverse_unknown, _dim3_size);
100 }
101 
102 template<typename NodeT>
104 {
105  _collision_checker = collision_checker;
106  _costmap = collision_checker->getCostmap();
107  unsigned int x_size = _costmap->getSizeInCellsX();
108  unsigned int y_size = _costmap->getSizeInCellsY();
109 
110  clearGraph();
111 
112  if (getSizeX() != x_size || getSizeY() != y_size) {
113  _x_size = x_size;
114  _y_size = y_size;
115  NodeT::initMotionModel(_motion_model, _x_size, _y_size, _dim3_size, _search_info);
116  }
117  _expander->setCollisionChecker(collision_checker);
118 }
119 
120 template<typename NodeT>
121 typename AStarAlgorithm<NodeT>::NodePtr AStarAlgorithm<NodeT>::addToGraph(
122  const unsigned int & index)
123 {
124  auto iter = _graph.find(index);
125  if (iter != _graph.end()) {
126  return &(iter->second);
127  }
128 
129  return &(_graph.emplace(index, NodeT(index)).first->second);
130 }
131 
132 template<>
134  const unsigned int & mx,
135  const unsigned int & my,
136  const unsigned int & dim_3)
137 {
138  if (dim_3 != 0) {
139  throw std::runtime_error("Node type Node2D cannot be given non-zero starting dim 3.");
140  }
141  _start = addToGraph(Node2D::getIndex(mx, my, getSizeX()));
142 }
143 
144 template<typename NodeT>
146  const unsigned int & mx,
147  const unsigned int & my,
148  const unsigned int & dim_3)
149 {
150  _start = addToGraph(NodeT::getIndex(mx, my, dim_3));
151  _start->setPose(
152  Coordinates(
153  static_cast<float>(mx),
154  static_cast<float>(my),
155  static_cast<float>(dim_3)));
156 }
157 
158 template<>
160  const unsigned int & mx,
161  const unsigned int & my,
162  const unsigned int & dim_3)
163 {
164  if (dim_3 != 0) {
165  throw std::runtime_error("Node type Node2D cannot be given non-zero goal dim 3.");
166  }
167 
168  _goal = addToGraph(Node2D::getIndex(mx, my, getSizeX()));
169  _goal_coordinates = Node2D::Coordinates(mx, my);
170 }
171 
172 template<typename NodeT>
174  const unsigned int & mx,
175  const unsigned int & my,
176  const unsigned int & dim_3)
177 {
178  _goal = addToGraph(NodeT::getIndex(mx, my, dim_3));
179 
180  typename NodeT::Coordinates goal_coords(
181  static_cast<float>(mx),
182  static_cast<float>(my),
183  static_cast<float>(dim_3));
184 
185  if (!_search_info.cache_obstacle_heuristic || goal_coords != _goal_coordinates) {
186  if (!_start) {
187  throw std::runtime_error("Start must be set before goal.");
188  }
189 
190  NodeT::resetObstacleHeuristic(_costmap, _start->pose.x, _start->pose.y, mx, my);
191  }
192 
193  _goal_coordinates = goal_coords;
194  _goal->setPose(_goal_coordinates);
195 }
196 
197 template<typename NodeT>
199 {
200  // Check if graph was filled in
201  if (_graph.empty()) {
202  throw std::runtime_error("Failed to compute path, no costmap given.");
203  }
204 
205  // Check if points were filled in
206  if (!_start || !_goal) {
207  throw std::runtime_error("Failed to compute path, no valid start or goal given.");
208  }
209 
210  // Check if ending point is valid
211  if (getToleranceHeuristic() < 0.001 &&
212  !_goal->isNodeValid(_traverse_unknown, _collision_checker))
213  {
214  throw std::runtime_error("Failed to compute path, goal is occupied with no tolerance.");
215  }
216 
217  // Check if starting point is valid
218  if (!_start->isNodeValid(_traverse_unknown, _collision_checker)) {
219  throw std::runtime_error("Starting point in lethal space! Cannot create feasible plan.");
220  }
221 
222  return true;
223 }
224 
225 template<typename NodeT>
227  CoordinateVector & path, int & iterations,
228  const float & tolerance)
229 {
230  steady_clock::time_point start_time = steady_clock::now();
231  _tolerance = tolerance;
232  _best_heuristic_node = {std::numeric_limits<float>::max(), 0};
233  clearQueue();
234 
235  if (!areInputsValid()) {
236  return false;
237  }
238 
239  // 0) Add starting point to the open set
240  addNode(0.0, getStart());
241  getStart()->setAccumulatedCost(0.0);
242 
243  // Optimization: preallocate all variables
244  NodePtr current_node = nullptr;
245  NodePtr neighbor = nullptr;
246  NodePtr expansion_result = nullptr;
247  float g_cost = 0.0;
248  NodeVector neighbors;
249  int approach_iterations = 0;
250  NeighborIterator neighbor_iterator;
251  int analytic_iterations = 0;
252  int closest_distance = std::numeric_limits<int>::max();
253 
254  // Given an index, return a node ptr reference if its collision-free and valid
255  const unsigned int max_index = getSizeX() * getSizeY() * getSizeDim3();
256  NodeGetter neighborGetter =
257  [&, this](const unsigned int & index, NodePtr & neighbor_rtn) -> bool
258  {
259  if (index >= max_index) {
260  return false;
261  }
262 
263  neighbor_rtn = addToGraph(index);
264  return true;
265  };
266 
267  while (iterations < getMaxIterations() && !_queue.empty()) {
268  // Check for planning timeout only on every Nth iteration
269  if (iterations % _timing_interval == 0) {
270  std::chrono::duration<double> planning_duration =
271  std::chrono::duration_cast<std::chrono::duration<double>>(steady_clock::now() - start_time);
272  if (static_cast<double>(planning_duration.count()) >= _max_planning_time) {
273  return false;
274  }
275  }
276 
277  // 1) Pick Nbest from O s.t. min(f(Nbest)), remove from queue
278  current_node = getNextNode();
279 
280  // We allow for nodes to be queued multiple times in case
281  // shorter paths result in it, but we can visit only once
282  if (current_node->wasVisited()) {
283  continue;
284  }
285 
286  iterations++;
287 
288  // 2) Mark Nbest as visited
289  current_node->visited();
290 
291  // 2.1) Use an analytic expansion (if available) to generate a path
292  expansion_result = nullptr;
293  expansion_result = _expander->tryAnalyticExpansion(
294  current_node, getGoal(), neighborGetter, analytic_iterations, closest_distance);
295  if (expansion_result != nullptr) {
296  current_node = expansion_result;
297  }
298 
299  // 3) Check if we're at the goal, backtrace if required
300  if (isGoal(current_node)) {
301  return current_node->backtracePath(path);
302  } else if (_best_heuristic_node.first < getToleranceHeuristic()) {
303  // Optimization: Let us find when in tolerance and refine within reason
304  approach_iterations++;
305  if (approach_iterations >= getOnApproachMaxIterations()) {
306  return _graph.at(_best_heuristic_node.second).backtracePath(path);
307  }
308  }
309 
310  // 4) Expand neighbors of Nbest not visited
311  neighbors.clear();
312  current_node->getNeighbors(neighborGetter, _collision_checker, _traverse_unknown, neighbors);
313 
314  for (neighbor_iterator = neighbors.begin();
315  neighbor_iterator != neighbors.end(); ++neighbor_iterator)
316  {
317  neighbor = *neighbor_iterator;
318 
319  // 4.1) Compute the cost to go to this node
320  g_cost = current_node->getAccumulatedCost() + current_node->getTraversalCost(neighbor);
321 
322  // 4.2) If this is a lower cost than prior, we set this as the new cost and new approach
323  if (g_cost < neighbor->getAccumulatedCost()) {
324  neighbor->setAccumulatedCost(g_cost);
325  neighbor->parent = current_node;
326 
327  // 4.3) Add to queue with heuristic cost
328  addNode(g_cost + getHeuristicCost(neighbor), neighbor);
329  }
330  }
331  }
332 
333  if (_best_heuristic_node.first < getToleranceHeuristic()) {
334  // If we run out of serach options, return the path that is closest, if within tolerance.
335  return _graph.at(_best_heuristic_node.second).backtracePath(path);
336  }
337 
338  return false;
339 }
340 
341 template<typename NodeT>
342 bool AStarAlgorithm<NodeT>::isGoal(NodePtr & node)
343 {
344  return node == getGoal();
345 }
346 
347 template<typename NodeT>
348 typename AStarAlgorithm<NodeT>::NodePtr & AStarAlgorithm<NodeT>::getStart()
349 {
350  return _start;
351 }
352 
353 template<typename NodeT>
354 typename AStarAlgorithm<NodeT>::NodePtr & AStarAlgorithm<NodeT>::getGoal()
355 {
356  return _goal;
357 }
358 
359 template<typename NodeT>
360 typename AStarAlgorithm<NodeT>::NodePtr AStarAlgorithm<NodeT>::getNextNode()
361 {
362  NodeBasic<NodeT> node = _queue.top().second;
363  _queue.pop();
364  node.processSearchNode();
365  return node.graph_node_ptr;
366 }
367 
368 template<typename NodeT>
369 void AStarAlgorithm<NodeT>::addNode(const float & cost, NodePtr & node)
370 {
371  NodeBasic<NodeT> queued_node(node->getIndex());
372  queued_node.populateSearchNode(node);
373  _queue.emplace(cost, queued_node);
374 }
375 
376 template<typename NodeT>
377 float AStarAlgorithm<NodeT>::getHeuristicCost(const NodePtr & node)
378 {
379  const Coordinates node_coords =
380  NodeT::getCoords(node->getIndex(), getSizeX(), getSizeDim3());
381  float heuristic = NodeT::getHeuristicCost(
382  node_coords, _goal_coordinates, _costmap);
383 
384  if (heuristic < _best_heuristic_node.first) {
385  _best_heuristic_node = {heuristic, node->getIndex()};
386  }
387 
388  return heuristic;
389 }
390 
391 template<typename NodeT>
393 {
394  NodeQueue q;
395  std::swap(_queue, q);
396 }
397 
398 template<typename NodeT>
400 {
401  Graph g;
402  std::swap(_graph, g);
403  _graph.reserve(100000);
404 }
405 
406 template<typename NodeT>
408 {
409  return _max_iterations;
410 }
411 
412 template<typename NodeT>
414 {
415  return _max_on_approach_iterations;
416 }
417 
418 template<typename NodeT>
420 {
421  return _tolerance;
422 }
423 
424 template<typename NodeT>
426 {
427  return _x_size;
428 }
429 
430 template<typename NodeT>
432 {
433  return _y_size;
434 }
435 
436 template<typename NodeT>
438 {
439  return _dim3_size;
440 }
441 
442 // Instantiate algorithm for the supported template types
443 template class AStarAlgorithm<Node2D>;
444 template class AStarAlgorithm<NodeHybrid>;
445 template class AStarAlgorithm<NodeLattice>;
446 
447 } // namespace nav2_smac_planner
unsigned int getSizeInCellsX() const
Accessor for the x size of the costmap in cells.
Definition: costmap_2d.cpp:501
CostmapT getCostmap()
Get the current costmap object.
An A* implementation for planning in a costmap. Templated based on the Node type.
Definition: a_star.hpp:47
~AStarAlgorithm()
A destructor for nav2_smac_planner::AStarAlgorithm.
Definition: a_star.cpp:54
unsigned int & getSizeDim3()
Get number of angle quantization bins (SE2) or Z coordinate (XYZ)
Definition: a_star.cpp:437
NodePtr addToGraph(const unsigned int &index)
Adds node to graph.
Definition: a_star.cpp:121
void initialize(const bool &allow_unknown, int &max_iterations, const int &max_on_approach_iterations, const double &max_planning_time, const float &lookup_table_size, const unsigned int &dim_3_size)
Initialization of the planner with defaults.
Definition: a_star.cpp:59
void setGoal(const unsigned int &mx, const unsigned int &my, const unsigned int &dim_3)
Set the goal for planning, as a node index.
Definition: a_star.cpp:173
int & getOnApproachMaxIterations()
Get maximum number of on-approach iterations after within threshold.
Definition: a_star.cpp:413
void setCollisionChecker(GridCollisionChecker *collision_checker)
Sets the collision checker to use.
Definition: a_star.cpp:103
NodePtr getNextNode()
Get pointer to next goal in open set.
Definition: a_star.cpp:360
bool areInputsValid()
Check if inputs to planner are valid.
Definition: a_star.cpp:198
void clearQueue()
Clear hueristic queue of nodes to search.
Definition: a_star.cpp:392
int & getMaxIterations()
Get maximum number of iterations to plan.
Definition: a_star.cpp:407
void clearGraph()
Clear graph of nodes searched.
Definition: a_star.cpp:399
unsigned int & getSizeY()
Get size of graph in Y.
Definition: a_star.cpp:431
void setStart(const unsigned int &mx, const unsigned int &my, const unsigned int &dim_3)
Set the starting pose for planning, as a node index.
Definition: a_star.cpp:145
void addNode(const float &cost, NodePtr &node)
Add a node to the open set.
Definition: a_star.cpp:369
unsigned int & getSizeX()
Get size of graph in X.
Definition: a_star.cpp:425
NodePtr & getGoal()
Get pointer reference to goal node.
Definition: a_star.cpp:354
bool isGoal(NodePtr &node)
Check if this node is the goal node.
Definition: a_star.cpp:342
float getHeuristicCost(const NodePtr &node)
Get cost of heuristic of node.
Definition: a_star.cpp:377
float & getToleranceHeuristic()
Get tolerance, in node nodes.
Definition: a_star.cpp:419
NodePtr & getStart()
Get pointer reference to starting node.
Definition: a_star.cpp:348
bool createPath(CoordinateVector &path, int &num_iterations, const float &tolerance)
Creating path from given costmap, start, and goal.
Definition: a_star.cpp:226
A costmap grid collision checker.
unsigned int & getIndex()
Gets cell index.
Definition: node_2d.hpp:161
NodeBasic implementation for priority queue insertion.
Definition: node_basic.hpp:46
void populateSearchNode(NodeT *&node)
Take a NodeBasic and populate it with any necessary state cached in the queue for NodeT.
void processSearchNode()
Take a NodeBasic and populate it with any necessary state cached in the queue for NodeTs.
Definition: node_basic.cpp:21
Search properties and penalties.
Definition: types.hpp:36