Nav2 Navigation Stack - humble  humble
ROS 2 Navigation Stack
smac_planner_lattice.cpp
1 // Copyright (c) 2021, Samsung Research America
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License. Reserved.
14 
15 #include <string>
16 #include <memory>
17 #include <vector>
18 #include <algorithm>
19 #include <limits>
20 
21 #include "Eigen/Core"
22 #include "nav2_smac_planner/smac_planner_lattice.hpp"
23 
24 // #define BENCHMARK_TESTING
25 
26 namespace nav2_smac_planner
27 {
28 
29 using namespace std::chrono; // NOLINT
30 using rcl_interfaces::msg::ParameterType;
31 
33 : _a_star(nullptr),
34  _collision_checker(nullptr, 1, nullptr),
35  _smoother(nullptr),
36  _costmap(nullptr)
37 {
38 }
39 
41 {
42  RCLCPP_INFO(
43  _logger, "Destroying plugin %s of type SmacPlannerLattice",
44  _name.c_str());
45 }
46 
48  const rclcpp_lifecycle::LifecycleNode::WeakPtr & parent,
49  std::string name, std::shared_ptr<tf2_ros::Buffer>/*tf*/,
50  std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros)
51 {
52  _node = parent;
53  auto node = parent.lock();
54  _logger = node->get_logger();
55  _clock = node->get_clock();
56  _costmap = costmap_ros->getCostmap();
57  _costmap_ros = costmap_ros;
58  _name = name;
59  _global_frame = costmap_ros->getGlobalFrameID();
60  _raw_plan_publisher = node->create_publisher<nav_msgs::msg::Path>("unsmoothed_plan", 1);
61 
62  RCLCPP_INFO(_logger, "Configuring %s of type SmacPlannerLattice", name.c_str());
63 
64  // General planner params
65  double analytic_expansion_max_length_m;
66  bool smooth_path;
67 
68  nav2_util::declare_parameter_if_not_declared(
69  node, name + ".tolerance", rclcpp::ParameterValue(0.25));
70  _tolerance = static_cast<float>(node->get_parameter(name + ".tolerance").as_double());
71  nav2_util::declare_parameter_if_not_declared(
72  node, name + ".allow_unknown", rclcpp::ParameterValue(true));
73  node->get_parameter(name + ".allow_unknown", _allow_unknown);
74  nav2_util::declare_parameter_if_not_declared(
75  node, name + ".max_iterations", rclcpp::ParameterValue(1000000));
76  node->get_parameter(name + ".max_iterations", _max_iterations);
77  nav2_util::declare_parameter_if_not_declared(
78  node, name + ".max_on_approach_iterations", rclcpp::ParameterValue(1000));
79  node->get_parameter(name + ".max_on_approach_iterations", _max_on_approach_iterations);
80  nav2_util::declare_parameter_if_not_declared(
81  node, name + ".smooth_path", rclcpp::ParameterValue(true));
82  node->get_parameter(name + ".smooth_path", smooth_path);
83 
84  // Default to a well rounded model: 16 bin, 0.4m turning radius, ackermann model
85  nav2_util::declare_parameter_if_not_declared(
86  node, name + ".lattice_filepath", rclcpp::ParameterValue(
87  ament_index_cpp::get_package_share_directory("nav2_smac_planner") +
88  "/sample_primitives/5cm_resolution/0.5m_turning_radius/ackermann/output.json"));
89  node->get_parameter(name + ".lattice_filepath", _search_info.lattice_filepath);
90  nav2_util::declare_parameter_if_not_declared(
91  node, name + ".cache_obstacle_heuristic", rclcpp::ParameterValue(false));
92  node->get_parameter(name + ".cache_obstacle_heuristic", _search_info.cache_obstacle_heuristic);
93  nav2_util::declare_parameter_if_not_declared(
94  node, name + ".reverse_penalty", rclcpp::ParameterValue(2.0));
95  node->get_parameter(name + ".reverse_penalty", _search_info.reverse_penalty);
96  nav2_util::declare_parameter_if_not_declared(
97  node, name + ".change_penalty", rclcpp::ParameterValue(0.05));
98  node->get_parameter(name + ".change_penalty", _search_info.change_penalty);
99  nav2_util::declare_parameter_if_not_declared(
100  node, name + ".non_straight_penalty", rclcpp::ParameterValue(1.05));
101  node->get_parameter(name + ".non_straight_penalty", _search_info.non_straight_penalty);
102  nav2_util::declare_parameter_if_not_declared(
103  node, name + ".cost_penalty", rclcpp::ParameterValue(2.0));
104  node->get_parameter(name + ".cost_penalty", _search_info.cost_penalty);
105  nav2_util::declare_parameter_if_not_declared(
106  node, name + ".retrospective_penalty", rclcpp::ParameterValue(0.015));
107  node->get_parameter(name + ".retrospective_penalty", _search_info.retrospective_penalty);
108  nav2_util::declare_parameter_if_not_declared(
109  node, name + ".rotation_penalty", rclcpp::ParameterValue(5.0));
110  node->get_parameter(name + ".rotation_penalty", _search_info.rotation_penalty);
111  nav2_util::declare_parameter_if_not_declared(
112  node, name + ".analytic_expansion_ratio", rclcpp::ParameterValue(3.5));
113  node->get_parameter(name + ".analytic_expansion_ratio", _search_info.analytic_expansion_ratio);
114  nav2_util::declare_parameter_if_not_declared(
115  node, name + ".analytic_expansion_max_length", rclcpp::ParameterValue(3.0));
116  node->get_parameter(name + ".analytic_expansion_max_length", analytic_expansion_max_length_m);
117  _search_info.analytic_expansion_max_length =
118  analytic_expansion_max_length_m / _costmap->getResolution();
119 
120  nav2_util::declare_parameter_if_not_declared(
121  node, name + ".max_planning_time", rclcpp::ParameterValue(5.0));
122  node->get_parameter(name + ".max_planning_time", _max_planning_time);
123  nav2_util::declare_parameter_if_not_declared(
124  node, name + ".lookup_table_size", rclcpp::ParameterValue(20.0));
125  node->get_parameter(name + ".lookup_table_size", _lookup_table_size);
126  nav2_util::declare_parameter_if_not_declared(
127  node, name + ".allow_reverse_expansion", rclcpp::ParameterValue(false));
128  node->get_parameter(name + ".allow_reverse_expansion", _search_info.allow_reverse_expansion);
129 
130  _metadata = LatticeMotionTable::getLatticeMetadata(_search_info.lattice_filepath);
131  _search_info.minimum_turning_radius =
132  _metadata.min_turning_radius / (_costmap->getResolution());
133  _motion_model = MotionModel::STATE_LATTICE;
134 
135  if (_max_on_approach_iterations <= 0) {
136  RCLCPP_INFO(
137  _logger, "On approach iteration selected as <= 0, "
138  "disabling tolerance and on approach iterations.");
139  _max_on_approach_iterations = std::numeric_limits<int>::max();
140  }
141 
142  if (_max_iterations <= 0) {
143  RCLCPP_INFO(
144  _logger, "maximum iteration selected as <= 0, "
145  "disabling maximum iterations.");
146  _max_iterations = std::numeric_limits<int>::max();
147  }
148 
149  float lookup_table_dim =
150  static_cast<float>(_lookup_table_size) /
151  static_cast<float>(_costmap->getResolution());
152 
153  // Make sure its a whole number
154  lookup_table_dim = static_cast<float>(static_cast<int>(lookup_table_dim));
155 
156  // Make sure its an odd number
157  if (static_cast<int>(lookup_table_dim) % 2 == 0) {
158  RCLCPP_INFO(
159  _logger,
160  "Even sized heuristic lookup table size set %f, increasing size by 1 to make odd",
161  lookup_table_dim);
162  lookup_table_dim += 1.0;
163  }
164 
165  // Initialize collision checker using 72 evenly sized bins instead of the lattice
166  // heading angles. This is done so that we have precomputed angles every 5 degrees.
167  // If we used the sparse lattice headings (usually 16), then when we attempt to collision
168  // check for intermediary points of the primitives, we're forced to round to one of the 16
169  // increments causing "wobbly" checks that could cause larger robots to virtually show collisions
170  // in valid configurations. This approximation helps to bound orientation error for all checks
171  // in exchange for slight inaccuracies in the collision headings in terminal search states.
172  _collision_checker = GridCollisionChecker(_costmap, 72u, node);
173  _collision_checker.setFootprint(
174  costmap_ros->getRobotFootprint(),
175  costmap_ros->getUseRadius(),
176  findCircumscribedCost(costmap_ros));
177 
178  // Initialize A* template
179  _a_star = std::make_unique<AStarAlgorithm<NodeLattice>>(_motion_model, _search_info);
180  _a_star->initialize(
181  _allow_unknown,
182  _max_iterations,
183  _max_on_approach_iterations,
184  _max_planning_time,
185  lookup_table_dim,
186  _metadata.number_of_headings);
187 
188  // Initialize path smoother
189  if (smooth_path) {
190  SmootherParams params;
191  params.get(node, name);
192  _smoother = std::make_unique<Smoother>(params);
193  _smoother->initialize(_metadata.min_turning_radius);
194  }
195 
196  RCLCPP_INFO(
197  _logger, "Configured plugin %s of type SmacPlannerLattice with "
198  "maximum iterations %i, max on approach iterations %i, "
199  "and %s. Tolerance %.2f. Using motion model: %s. State lattice file: %s.",
200  _name.c_str(), _max_iterations, _max_on_approach_iterations,
201  _allow_unknown ? "allowing unknown traversal" : "not allowing unknown traversal",
202  _tolerance, toString(_motion_model).c_str(), _search_info.lattice_filepath.c_str());
203 }
204 
206 {
207  RCLCPP_INFO(
208  _logger, "Activating plugin %s of type SmacPlannerLattice",
209  _name.c_str());
210  _raw_plan_publisher->on_activate();
211  auto node = _node.lock();
212  // Add callback for dynamic parameters
213  _dyn_params_handler = node->add_on_set_parameters_callback(
214  std::bind(&SmacPlannerLattice::dynamicParametersCallback, this, std::placeholders::_1));
215 }
216 
218 {
219  RCLCPP_INFO(
220  _logger, "Deactivating plugin %s of type SmacPlannerLattice",
221  _name.c_str());
222  _raw_plan_publisher->on_deactivate();
223  _dyn_params_handler.reset();
224 }
225 
227 {
228  RCLCPP_INFO(
229  _logger, "Cleaning up plugin %s of type SmacPlannerLattice",
230  _name.c_str());
231  _a_star.reset();
232  _smoother.reset();
233  _raw_plan_publisher.reset();
234 }
235 
236 nav_msgs::msg::Path SmacPlannerLattice::createPlan(
237  const geometry_msgs::msg::PoseStamped & start,
238  const geometry_msgs::msg::PoseStamped & goal)
239 {
240  std::lock_guard<std::mutex> lock_reinit(_mutex);
241  steady_clock::time_point a = steady_clock::now();
242 
243  std::unique_lock<nav2_costmap_2d::Costmap2D::mutex_t> lock(*(_costmap->getMutex()));
244 
245  // Set collision checker and costmap information
246  _collision_checker.setFootprint(
247  _costmap_ros->getRobotFootprint(),
248  _costmap_ros->getUseRadius(),
249  findCircumscribedCost(_costmap_ros));
250  _a_star->setCollisionChecker(&_collision_checker);
251 
252  // Set starting point, in A* bin search coordinates
253  unsigned int mx_start, my_start, mx_goal, my_goal;
254  _costmap->worldToMap(start.pose.position.x, start.pose.position.y, mx_start, my_start);
255  unsigned int start_bin =
256  NodeLattice::motion_table.getClosestAngularBin(tf2::getYaw(start.pose.orientation));
257  _a_star->setStart(
258  mx_start, my_start, start_bin);
259 
260  // Set goal point, in A* bin search coordinates
261  _costmap->worldToMap(goal.pose.position.x, goal.pose.position.y, mx_goal, my_goal);
262  unsigned int goal_bin =
263  NodeLattice::motion_table.getClosestAngularBin(tf2::getYaw(goal.pose.orientation));
264  _a_star->setGoal(
265  mx_goal, my_goal, goal_bin);
266 
267  // Setup message
268  nav_msgs::msg::Path plan;
269  plan.header.stamp = _clock->now();
270  plan.header.frame_id = _global_frame;
271  geometry_msgs::msg::PoseStamped pose;
272  pose.header = plan.header;
273  pose.pose.position.z = 0.0;
274  pose.pose.orientation.x = 0.0;
275  pose.pose.orientation.y = 0.0;
276  pose.pose.orientation.z = 0.0;
277  pose.pose.orientation.w = 1.0;
278 
279  // Corner case of start and goal being on the same cell
280  if (std::floor(mx_start) == std::floor(mx_goal) &&
281  std::floor(my_start) == std::floor(my_goal) &&
282  start_bin == goal_bin)
283  {
284  pose.pose = start.pose;
285  pose.pose.orientation = goal.pose.orientation;
286  plan.poses.push_back(pose);
287 
288  // Publish raw path for debug
289  if (_raw_plan_publisher->get_subscription_count() > 0) {
290  _raw_plan_publisher->publish(plan);
291  }
292 
293  return plan;
294  }
295 
296  // Compute plan
297  NodeLattice::CoordinateVector path;
298  int num_iterations = 0;
299  std::string error;
300  try {
301  if (!_a_star->createPath(
302  path, num_iterations, _tolerance / static_cast<float>(_costmap->getResolution())))
303  {
304  if (num_iterations < _a_star->getMaxIterations()) {
305  error = std::string("no valid path found");
306  } else {
307  error = std::string("exceeded maximum iterations");
308  }
309  }
310  } catch (const std::runtime_error & e) {
311  error = "invalid use: ";
312  error += e.what();
313  }
314 
315  if (!error.empty()) {
316  RCLCPP_WARN(
317  _logger,
318  "%s: failed to create plan, %s.",
319  _name.c_str(), error.c_str());
320  return plan;
321  }
322 
323  // Convert to world coordinates
324  plan.poses.reserve(path.size());
325  geometry_msgs::msg::PoseStamped last_pose = pose;
326  for (int i = path.size() - 1; i >= 0; --i) {
327  pose.pose = getWorldCoords(path[i].x, path[i].y, _costmap);
328  pose.pose.orientation = getWorldOrientation(path[i].theta);
329  if (fabs(pose.pose.position.x - last_pose.pose.position.x) < 1e-4 &&
330  fabs(pose.pose.position.y - last_pose.pose.position.y) < 1e-4 &&
331  fabs(tf2::getYaw(pose.pose.orientation) - tf2::getYaw(last_pose.pose.orientation)) < 1e-4)
332  {
333  RCLCPP_DEBUG(
334  _logger,
335  "Removed a path from the path due to replication. "
336  "Make sure your minimum control set does not contain duplicate values!");
337  continue;
338  }
339  last_pose = pose;
340  plan.poses.push_back(pose);
341  }
342 
343  // Publish raw path for debug
344  if (_raw_plan_publisher->get_subscription_count() > 0) {
345  _raw_plan_publisher->publish(plan);
346  }
347 
348  // Find how much time we have left to do smoothing
349  steady_clock::time_point b = steady_clock::now();
350  duration<double> time_span = duration_cast<duration<double>>(b - a);
351  double time_remaining = _max_planning_time - static_cast<double>(time_span.count());
352 
353 #ifdef BENCHMARK_TESTING
354  std::cout << "It took " << time_span.count() * 1000 <<
355  " milliseconds with " << num_iterations << " iterations." << std::endl;
356 #endif
357 
358  // Smooth plan
359  if (_smoother && num_iterations > 1) {
360  _smoother->smooth(plan, _costmap, time_remaining);
361  }
362 
363 #ifdef BENCHMARK_TESTING
364  steady_clock::time_point c = steady_clock::now();
365  duration<double> time_span2 = duration_cast<duration<double>>(c - b);
366  std::cout << "It took " << time_span2.count() * 1000 <<
367  " milliseconds to smooth path." << std::endl;
368 #endif
369 
370  return plan;
371 }
372 
373 rcl_interfaces::msg::SetParametersResult
374 SmacPlannerLattice::dynamicParametersCallback(std::vector<rclcpp::Parameter> parameters)
375 {
376  rcl_interfaces::msg::SetParametersResult result;
377  std::lock_guard<std::mutex> lock_reinit(_mutex);
378 
379  bool reinit_a_star = false;
380  bool reinit_smoother = false;
381 
382  for (auto parameter : parameters) {
383  const auto & type = parameter.get_type();
384  const auto & name = parameter.get_name();
385 
386  if (type == ParameterType::PARAMETER_DOUBLE) {
387  if (name == _name + ".max_planning_time") {
388  reinit_a_star = true;
389  _max_planning_time = parameter.as_double();
390  } else if (name == _name + ".tolerance") {
391  _tolerance = static_cast<float>(parameter.as_double());
392  } else if (name == _name + ".lookup_table_size") {
393  reinit_a_star = true;
394  _lookup_table_size = parameter.as_double();
395  } else if (name == _name + ".reverse_penalty") {
396  reinit_a_star = true;
397  _search_info.reverse_penalty = static_cast<float>(parameter.as_double());
398  } else if (name == _name + ".change_penalty") {
399  reinit_a_star = true;
400  _search_info.change_penalty = static_cast<float>(parameter.as_double());
401  } else if (name == _name + ".non_straight_penalty") {
402  reinit_a_star = true;
403  _search_info.non_straight_penalty = static_cast<float>(parameter.as_double());
404  } else if (name == _name + ".cost_penalty") {
405  reinit_a_star = true;
406  _search_info.cost_penalty = static_cast<float>(parameter.as_double());
407  } else if (name == _name + ".rotation_penalty") {
408  reinit_a_star = true;
409  _search_info.rotation_penalty = static_cast<float>(parameter.as_double());
410  } else if (name == _name + ".analytic_expansion_ratio") {
411  reinit_a_star = true;
412  _search_info.analytic_expansion_ratio = static_cast<float>(parameter.as_double());
413  } else if (name == _name + ".analytic_expansion_max_length") {
414  reinit_a_star = true;
415  _search_info.analytic_expansion_max_length =
416  static_cast<float>(parameter.as_double()) / _costmap->getResolution();
417  }
418  } else if (type == ParameterType::PARAMETER_BOOL) {
419  if (name == _name + ".allow_unknown") {
420  reinit_a_star = true;
421  _allow_unknown = parameter.as_bool();
422  } else if (name == _name + ".cache_obstacle_heuristic") {
423  reinit_a_star = true;
424  _search_info.cache_obstacle_heuristic = parameter.as_bool();
425  } else if (name == _name + ".allow_reverse_expansion") {
426  reinit_a_star = true;
427  _search_info.allow_reverse_expansion = parameter.as_bool();
428  } else if (name == _name + ".smooth_path") {
429  if (parameter.as_bool()) {
430  reinit_smoother = true;
431  } else {
432  _smoother.reset();
433  }
434  }
435  } else if (type == ParameterType::PARAMETER_INTEGER) {
436  if (name == _name + ".max_iterations") {
437  reinit_a_star = true;
438  _max_iterations = parameter.as_int();
439  if (_max_iterations <= 0) {
440  RCLCPP_INFO(
441  _logger, "maximum iteration selected as <= 0, "
442  "disabling maximum iterations.");
443  _max_iterations = std::numeric_limits<int>::max();
444  }
445  }
446  } else if (name == _name + ".max_on_approach_iterations") {
447  reinit_a_star = true;
448  _max_on_approach_iterations = parameter.as_int();
449  if (_max_on_approach_iterations <= 0) {
450  RCLCPP_INFO(
451  _logger, "On approach iteration selected as <= 0, "
452  "disabling tolerance and on approach iterations.");
453  _max_on_approach_iterations = std::numeric_limits<int>::max();
454  }
455  } else if (type == ParameterType::PARAMETER_STRING) {
456  if (name == _name + ".lattice_filepath") {
457  reinit_a_star = true;
458  if (_smoother) {
459  reinit_smoother = true;
460  }
461  _search_info.lattice_filepath = parameter.as_string();
462  _metadata = LatticeMotionTable::getLatticeMetadata(_search_info.lattice_filepath);
463  _search_info.minimum_turning_radius =
464  _metadata.min_turning_radius / (_costmap->getResolution());
465  }
466  }
467  }
468 
469  // Re-init if needed with mutex lock (to avoid re-init while creating a plan)
470  if (reinit_a_star || reinit_smoother) {
471  // convert to grid coordinates
472  _search_info.minimum_turning_radius =
473  _metadata.min_turning_radius / (_costmap->getResolution());
474  float lookup_table_dim =
475  static_cast<float>(_lookup_table_size) /
476  static_cast<float>(_costmap->getResolution());
477 
478  // Make sure its a whole number
479  lookup_table_dim = static_cast<float>(static_cast<int>(lookup_table_dim));
480 
481  // Make sure its an odd number
482  if (static_cast<int>(lookup_table_dim) % 2 == 0) {
483  RCLCPP_INFO(
484  _logger,
485  "Even sized heuristic lookup table size set %f, increasing size by 1 to make odd",
486  lookup_table_dim);
487  lookup_table_dim += 1.0;
488  }
489 
490  // Re-Initialize smoother
491  if (reinit_smoother) {
492  auto node = _node.lock();
493  SmootherParams params;
494  params.get(node, _name);
495  _smoother = std::make_unique<Smoother>(params);
496  _smoother->initialize(_metadata.min_turning_radius);
497  }
498 
499  // Re-Initialize A* template
500  if (reinit_a_star) {
501  _a_star = std::make_unique<AStarAlgorithm<NodeLattice>>(_motion_model, _search_info);
502  _a_star->initialize(
503  _allow_unknown,
504  _max_iterations,
505  _max_on_approach_iterations,
506  _max_planning_time,
507  lookup_table_dim,
508  _metadata.number_of_headings);
509  }
510  }
511 
512  result.successful = true;
513  return result;
514 }
515 
516 } // namespace nav2_smac_planner
517 
518 #include "pluginlib/class_list_macros.hpp"
Abstract interface for global planners to adhere to with pluginlib.
bool worldToMap(double wx, double wy, unsigned int &mx, unsigned int &my) const
Convert from world coordinates to map coordinates.
Definition: costmap_2d.cpp:287
double getResolution() const
Accessor for the resolution of the costmap.
Definition: costmap_2d.cpp:531
A costmap grid collision checker.
void setFootprint(const nav2_costmap_2d::Footprint &footprint, const bool &radius, const double &possible_inscribed_cost)
A constructor for nav2_smac_planner::GridCollisionChecker for use when irregular bin intervals are ap...
void deactivate() override
Deactivate lifecycle node.
void configure(const rclcpp_lifecycle::LifecycleNode::WeakPtr &parent, std::string name, std::shared_ptr< tf2_ros::Buffer > tf, std::shared_ptr< nav2_costmap_2d::Costmap2DROS > costmap_ros) override
Configuring plugin.
nav_msgs::msg::Path createPlan(const geometry_msgs::msg::PoseStamped &start, const geometry_msgs::msg::PoseStamped &goal) override
Creating a plan from start and goal poses.
rcl_interfaces::msg::SetParametersResult dynamicParametersCallback(std::vector< rclcpp::Parameter > parameters)
Callback executed when a paramter change is detected.
void cleanup() override
Cleanup lifecycle node.
void activate() override
Activate lifecycle node.
static LatticeMetadata getLatticeMetadata(const std::string &lattice_filepath)
Get file metadata needed.
Parameters for the smoother cost function.
void get(std::shared_ptr< rclcpp_lifecycle::LifecycleNode > node, const std::string &name)
Get params from ROS parameter.
Definition: types.hpp:70