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, my;
254  _costmap->worldToMap(start.pose.position.x, start.pose.position.y, mx, my);
255  _a_star->setStart(
256  mx, my,
257  NodeLattice::motion_table.getClosestAngularBin(tf2::getYaw(start.pose.orientation)));
258 
259  // Set goal point, in A* bin search coordinates
260  _costmap->worldToMap(goal.pose.position.x, goal.pose.position.y, mx, my);
261  _a_star->setGoal(
262  mx, my,
263  NodeLattice::motion_table.getClosestAngularBin(tf2::getYaw(goal.pose.orientation)));
264 
265  // Setup message
266  nav_msgs::msg::Path plan;
267  plan.header.stamp = _clock->now();
268  plan.header.frame_id = _global_frame;
269  geometry_msgs::msg::PoseStamped pose;
270  pose.header = plan.header;
271  pose.pose.position.z = 0.0;
272  pose.pose.orientation.x = 0.0;
273  pose.pose.orientation.y = 0.0;
274  pose.pose.orientation.z = 0.0;
275  pose.pose.orientation.w = 1.0;
276 
277  // Compute plan
278  NodeLattice::CoordinateVector path;
279  int num_iterations = 0;
280  std::string error;
281  try {
282  if (!_a_star->createPath(
283  path, num_iterations, _tolerance / static_cast<float>(_costmap->getResolution())))
284  {
285  if (num_iterations < _a_star->getMaxIterations()) {
286  error = std::string("no valid path found");
287  } else {
288  error = std::string("exceeded maximum iterations");
289  }
290  }
291  } catch (const std::runtime_error & e) {
292  error = "invalid use: ";
293  error += e.what();
294  }
295 
296  if (!error.empty()) {
297  RCLCPP_WARN(
298  _logger,
299  "%s: failed to create plan, %s.",
300  _name.c_str(), error.c_str());
301  return plan;
302  }
303 
304  // Convert to world coordinates
305  plan.poses.reserve(path.size());
306  geometry_msgs::msg::PoseStamped last_pose = pose;
307  for (int i = path.size() - 1; i >= 0; --i) {
308  pose.pose = getWorldCoords(path[i].x, path[i].y, _costmap);
309  pose.pose.orientation = getWorldOrientation(path[i].theta);
310  if (fabs(pose.pose.position.x - last_pose.pose.position.x) < 1e-4 &&
311  fabs(pose.pose.position.y - last_pose.pose.position.y) < 1e-4 &&
312  fabs(tf2::getYaw(pose.pose.orientation) - tf2::getYaw(last_pose.pose.orientation)) < 1e-4)
313  {
314  RCLCPP_DEBUG(
315  _logger,
316  "Removed a path from the path due to replication. "
317  "Make sure your minimum control set does not contain duplicate values!");
318  continue;
319  }
320  last_pose = pose;
321  plan.poses.push_back(pose);
322  }
323 
324  // Publish raw path for debug
325  if (_raw_plan_publisher->get_subscription_count() > 0) {
326  _raw_plan_publisher->publish(plan);
327  }
328 
329  // Find how much time we have left to do smoothing
330  steady_clock::time_point b = steady_clock::now();
331  duration<double> time_span = duration_cast<duration<double>>(b - a);
332  double time_remaining = _max_planning_time - static_cast<double>(time_span.count());
333 
334 #ifdef BENCHMARK_TESTING
335  std::cout << "It took " << time_span.count() * 1000 <<
336  " milliseconds with " << num_iterations << " iterations." << std::endl;
337 #endif
338 
339  // Smooth plan
340  if (_smoother && num_iterations > 1) {
341  _smoother->smooth(plan, _costmap, time_remaining);
342  }
343 
344 #ifdef BENCHMARK_TESTING
345  steady_clock::time_point c = steady_clock::now();
346  duration<double> time_span2 = duration_cast<duration<double>>(c - b);
347  std::cout << "It took " << time_span2.count() * 1000 <<
348  " milliseconds to smooth path." << std::endl;
349 #endif
350 
351  return plan;
352 }
353 
354 rcl_interfaces::msg::SetParametersResult
355 SmacPlannerLattice::dynamicParametersCallback(std::vector<rclcpp::Parameter> parameters)
356 {
357  rcl_interfaces::msg::SetParametersResult result;
358  std::lock_guard<std::mutex> lock_reinit(_mutex);
359 
360  bool reinit_a_star = false;
361  bool reinit_smoother = false;
362 
363  for (auto parameter : parameters) {
364  const auto & type = parameter.get_type();
365  const auto & name = parameter.get_name();
366 
367  if (type == ParameterType::PARAMETER_DOUBLE) {
368  if (name == _name + ".max_planning_time") {
369  reinit_a_star = true;
370  _max_planning_time = parameter.as_double();
371  } else if (name == _name + ".tolerance") {
372  _tolerance = static_cast<float>(parameter.as_double());
373  } else if (name == _name + ".lookup_table_size") {
374  reinit_a_star = true;
375  _lookup_table_size = parameter.as_double();
376  } else if (name == _name + ".reverse_penalty") {
377  reinit_a_star = true;
378  _search_info.reverse_penalty = static_cast<float>(parameter.as_double());
379  } else if (name == _name + ".change_penalty") {
380  reinit_a_star = true;
381  _search_info.change_penalty = static_cast<float>(parameter.as_double());
382  } else if (name == _name + ".non_straight_penalty") {
383  reinit_a_star = true;
384  _search_info.non_straight_penalty = static_cast<float>(parameter.as_double());
385  } else if (name == _name + ".cost_penalty") {
386  reinit_a_star = true;
387  _search_info.cost_penalty = static_cast<float>(parameter.as_double());
388  } else if (name == _name + ".rotation_penalty") {
389  reinit_a_star = true;
390  _search_info.rotation_penalty = static_cast<float>(parameter.as_double());
391  } else if (name == _name + ".analytic_expansion_ratio") {
392  reinit_a_star = true;
393  _search_info.analytic_expansion_ratio = static_cast<float>(parameter.as_double());
394  } else if (name == _name + ".analytic_expansion_max_length") {
395  reinit_a_star = true;
396  _search_info.analytic_expansion_max_length =
397  static_cast<float>(parameter.as_double()) / _costmap->getResolution();
398  }
399  } else if (type == ParameterType::PARAMETER_BOOL) {
400  if (name == _name + ".allow_unknown") {
401  reinit_a_star = true;
402  _allow_unknown = parameter.as_bool();
403  } else if (name == _name + ".cache_obstacle_heuristic") {
404  reinit_a_star = true;
405  _search_info.cache_obstacle_heuristic = parameter.as_bool();
406  } else if (name == _name + ".allow_reverse_expansion") {
407  reinit_a_star = true;
408  _search_info.allow_reverse_expansion = parameter.as_bool();
409  } else if (name == _name + ".smooth_path") {
410  if (parameter.as_bool()) {
411  reinit_smoother = true;
412  } else {
413  _smoother.reset();
414  }
415  }
416  } else if (type == ParameterType::PARAMETER_INTEGER) {
417  if (name == _name + ".max_iterations") {
418  reinit_a_star = true;
419  _max_iterations = parameter.as_int();
420  if (_max_iterations <= 0) {
421  RCLCPP_INFO(
422  _logger, "maximum iteration selected as <= 0, "
423  "disabling maximum iterations.");
424  _max_iterations = std::numeric_limits<int>::max();
425  }
426  }
427  } else if (name == _name + ".max_on_approach_iterations") {
428  reinit_a_star = true;
429  _max_on_approach_iterations = parameter.as_int();
430  if (_max_on_approach_iterations <= 0) {
431  RCLCPP_INFO(
432  _logger, "On approach iteration selected as <= 0, "
433  "disabling tolerance and on approach iterations.");
434  _max_on_approach_iterations = std::numeric_limits<int>::max();
435  }
436  } else if (type == ParameterType::PARAMETER_STRING) {
437  if (name == _name + ".lattice_filepath") {
438  reinit_a_star = true;
439  if (_smoother) {
440  reinit_smoother = true;
441  }
442  _search_info.lattice_filepath = parameter.as_string();
443  _metadata = LatticeMotionTable::getLatticeMetadata(_search_info.lattice_filepath);
444  _search_info.minimum_turning_radius =
445  _metadata.min_turning_radius / (_costmap->getResolution());
446  }
447  }
448  }
449 
450  // Re-init if needed with mutex lock (to avoid re-init while creating a plan)
451  if (reinit_a_star || reinit_smoother) {
452  // convert to grid coordinates
453  _search_info.minimum_turning_radius =
454  _metadata.min_turning_radius / (_costmap->getResolution());
455  float lookup_table_dim =
456  static_cast<float>(_lookup_table_size) /
457  static_cast<float>(_costmap->getResolution());
458 
459  // Make sure its a whole number
460  lookup_table_dim = static_cast<float>(static_cast<int>(lookup_table_dim));
461 
462  // Make sure its an odd number
463  if (static_cast<int>(lookup_table_dim) % 2 == 0) {
464  RCLCPP_INFO(
465  _logger,
466  "Even sized heuristic lookup table size set %f, increasing size by 1 to make odd",
467  lookup_table_dim);
468  lookup_table_dim += 1.0;
469  }
470 
471  // Re-Initialize smoother
472  if (reinit_smoother) {
473  auto node = _node.lock();
474  SmootherParams params;
475  params.get(node, _name);
476  _smoother = std::make_unique<Smoother>(params);
477  _smoother->initialize(_metadata.min_turning_radius);
478  }
479 
480  // Re-Initialize A* template
481  if (reinit_a_star) {
482  _a_star = std::make_unique<AStarAlgorithm<NodeLattice>>(_motion_model, _search_info);
483  _a_star->initialize(
484  _allow_unknown,
485  _max_iterations,
486  _max_on_approach_iterations,
487  _max_planning_time,
488  lookup_table_dim,
489  _metadata.number_of_headings);
490  }
491  }
492 
493  result.successful = true;
494  return result;
495 }
496 
497 } // namespace nav2_smac_planner
498 
499 #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