Nav2 Navigation Stack - rolling  main
ROS 2 Navigation Stack
obstacles_critic.cpp
1 // Copyright (c) 2022 Samsung Research America, @artofnothingness Alexey Budyakov
2 // Copyright (c) 2023 Open Navigation LLC
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.
15 
16 #include <cmath>
17 #include "nav2_mppi_controller/critics/obstacles_critic.hpp"
18 #include "nav2_costmap_2d/inflation_layer.hpp"
19 #include "nav2_core/controller_exceptions.hpp"
20 
21 namespace mppi::critics
22 {
23 
25 {
26  auto getParentParam = parameters_handler_->getParamGetter(parent_name_);
27  getParentParam(enforce_path_inversion_, "enforce_path_inversion", false);
28 
29  auto getParam = parameters_handler_->getParamGetter(name_);
30  getParam(consider_footprint_, "consider_footprint", false);
31  getParam(power_, "cost_power", 1);
32  getParam(repulsion_weight_, "repulsion_weight", 1.5f);
33  getParam(critical_weight_, "critical_weight", 20.0f);
34  getParam(collision_cost_, "collision_cost", 100000.0f);
35  getParam(collision_margin_distance_, "collision_margin_distance", 0.10f);
36  getParam(near_goal_distance_, "near_goal_distance", 0.5f);
37  getParam(inflation_layer_name_, "inflation_layer_name", std::string(""));
38 
39  collision_checker_.setCostmap(costmap_);
40  possible_collision_cost_ = findCircumscribedCost(costmap_ros_);
41 
42  if (possible_collision_cost_ < 1.0f) {
43  RCLCPP_ERROR(
44  logger_,
45  "Inflation layer either not found or inflation is not set sufficiently for "
46  "optimized non-circular collision checking capabilities. It is HIGHLY recommended to set"
47  " the inflation radius to be at MINIMUM half of the robot's largest cross-section. See "
48  "github.com/ros-planning/navigation2/tree/main/nav2_smac_planner#potential-fields"
49  " for full instructions. This will substantially impact run-time performance.");
50  }
51 
52  if (costmap_ros_->getUseRadius() == consider_footprint_) {
53  RCLCPP_WARN(
54  logger_,
55  "Inconsistent configuration in collision checking. Please verify the robot's shape settings "
56  "in both the costmap and the obstacle critic.");
57  if (costmap_ros_->getUseRadius()) {
59  "Considering footprint in collision checking but no robot footprint provided in the "
60  "costmap.");
61  }
62  }
63 
64  RCLCPP_INFO(
65  logger_,
66  "ObstaclesCritic instantiated with %d power and %f / %f weights. "
67  "Critic will collision check based on %s cost.",
68  power_, critical_weight_, repulsion_weight_, consider_footprint_ ?
69  "footprint" : "circular");
70 }
71 
73  std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap)
74 {
75  double result = -1.0;
76  const double circum_radius = costmap->getLayeredCostmap()->getCircumscribedRadius();
77  if (static_cast<float>(circum_radius) == circumscribed_radius_) {
78  // early return if footprint size is unchanged
79  return circumscribed_cost_;
80  }
81 
82  // check if the costmap has an inflation layer
83  const auto inflation_layer = nav2_costmap_2d::InflationLayer::getInflationLayer(
84  costmap,
85  inflation_layer_name_);
86  if (inflation_layer != nullptr) {
87  const double resolution = costmap->getCostmap()->getResolution();
88  result = inflation_layer->computeCost(circum_radius / resolution);
89  inflation_scale_factor_ = static_cast<float>(inflation_layer->getCostScalingFactor());
90  inflation_radius_ = static_cast<float>(inflation_layer->getInflationRadius());
91  } else {
92  RCLCPP_WARN(
93  logger_,
94  "No inflation layer found in costmap configuration. "
95  "If this is an SE2-collision checking plugin, it cannot use costmap potential "
96  "field to speed up collision checking by only checking the full footprint "
97  "when robot is within possibly-inscribed radius of an obstacle. This may "
98  "significantly slow down planning times and not avoid anything but absolute collisions!");
99  }
100 
101  circumscribed_radius_ = static_cast<float>(circum_radius);
102  circumscribed_cost_ = static_cast<float>(result);
103 
104  return circumscribed_cost_;
105 }
106 
108 {
109  const float scale_factor = inflation_scale_factor_;
110  const float min_radius = costmap_ros_->getLayeredCostmap()->getInscribedRadius();
111  float dist_to_obj = (scale_factor * min_radius - log(cost.cost) + log(253.0f)) / scale_factor;
112 
113  // If not footprint collision checking, the cost is using the center point cost and
114  // needs the radius subtracted to obtain the closest distance to the object
115  if (!cost.using_footprint) {
116  dist_to_obj -= min_radius;
117  }
118 
119  return dist_to_obj;
120 }
121 
123 {
124  if (!enabled_) {
125  return;
126  }
127 
128  if (consider_footprint_) {
129  // footprint may have changed since initialization if user has dynamic footprints
130  possible_collision_cost_ = findCircumscribedCost(costmap_ros_);
131  }
132 
133  geometry_msgs::msg::Pose goal = utils::getCriticGoal(data, enforce_path_inversion_);
134 
135  // If near the goal, don't apply the preferential term since the goal is near obstacles
136  bool near_goal = false;
137  if (utils::withinPositionGoalTolerance(near_goal_distance_, data.state.pose.pose, goal)) {
138  near_goal = true;
139  }
140 
141  Eigen::ArrayXf raw_cost = Eigen::ArrayXf::Zero(data.costs.size());
142  Eigen::ArrayXf repulsive_cost = Eigen::ArrayXf::Zero(data.costs.size());
143 
144  const unsigned int traj_len = data.trajectories.x.cols();
145  const unsigned int batch_size = data.trajectories.x.rows();
146  bool all_trajectories_collide = true;
147 
148  for(unsigned int i = 0; i != batch_size; i++) {
149  bool trajectory_collide = false;
150  float traj_cost = 0.0f;
151  const auto & traj = data.trajectories;
152  CollisionCost pose_cost;
153  raw_cost(i) = 0.0f;
154  repulsive_cost(i) = 0.0f;
155 
156  for(unsigned int j = 0; j != traj_len; j++) {
157  pose_cost = costAtPose(traj.x(i, j), traj.y(i, j), traj.yaws(i, j));
158  if (pose_cost.cost < 1.0f) {continue;} // In free space
159 
160  if (inCollision(pose_cost.cost)) {
161  trajectory_collide = true;
162  break;
163  }
164 
165  // Cannot process repulsion if inflation layer does not exist
166  if (inflation_radius_ == 0.0f || inflation_scale_factor_ == 0.0f) {
167  continue;
168  }
169 
170  const float dist_to_obj = distanceToObstacle(pose_cost);
171 
172  // Let near-collision trajectory points be punished severely
173  if (dist_to_obj < collision_margin_distance_) {
174  traj_cost += (collision_margin_distance_ - dist_to_obj);
175  }
176 
177  // Generally prefer trajectories further from obstacles
178  if (!near_goal) {
179  repulsive_cost[i] += inflation_radius_ - dist_to_obj;
180  }
181  }
182 
183  if (!trajectory_collide) {all_trajectories_collide = false;}
184  raw_cost(i) = trajectory_collide ? collision_cost_ : traj_cost;
185  }
186 
187  // Normalize repulsive cost by trajectory length & lowest score to not overweight importance
188  // This is a preferential cost, not collision cost, to be tuned relative to desired behaviors
189  auto repulsive_cost_normalized = (repulsive_cost - repulsive_cost.minCoeff()) / traj_len;
190 
191  if (power_ > 1u) {
192  data.costs +=
193  ((critical_weight_ * raw_cost) + (repulsion_weight_ * repulsive_cost_normalized)).pow(power_);
194  } else {
195  data.costs += (critical_weight_ * raw_cost) + (repulsion_weight_ * repulsive_cost_normalized);
196  }
197 
198  data.fail_flag = all_trajectories_collide;
199 }
200 
206 bool ObstaclesCritic::inCollision(float cost) const
207 {
208  bool is_tracking_unknown =
209  costmap_ros_->getLayeredCostmap()->isTrackingUnknown();
210 
211  using namespace nav2_costmap_2d; // NOLINT
212  switch (static_cast<unsigned char>(cost)) {
213  case (LETHAL_OBSTACLE):
214  return true;
215  case (INSCRIBED_INFLATED_OBSTACLE):
216  return consider_footprint_ ? false : true;
217  case (NO_INFORMATION):
218  return is_tracking_unknown ? false : true;
219  }
220 
221  return false;
222 }
223 
224 CollisionCost ObstaclesCritic::costAtPose(float x, float y, float theta)
225 {
226  CollisionCost collision_cost;
227  float & cost = collision_cost.cost;
228  collision_cost.using_footprint = false;
229  unsigned int x_i, y_i;
230  if (!collision_checker_.worldToMap(x, y, x_i, y_i)) {
231  cost = nav2_costmap_2d::NO_INFORMATION;
232  return collision_cost;
233  }
234  cost = collision_checker_.pointCost(x_i, y_i);
235 
236  if (consider_footprint_ &&
237  (cost >= possible_collision_cost_ || possible_collision_cost_ < 1.0f))
238  {
239  cost = static_cast<float>(collision_checker_.footprintCostAtPose(
240  x, y, theta, costmap_ros_->getRobotFootprint()));
241  collision_cost.using_footprint = true;
242  }
243 
244  return collision_cost;
245 }
246 
247 } // namespace mppi::critics
248 
249 #include <pluginlib/class_list_macros.hpp>
250 
251 PLUGINLIB_EXPORT_CLASS(
auto getParamGetter(const std::string &ns)
Get an object to retrieve parameters.
Abstract critic objective function to score trajectories.
bool inCollision(float cost) const
Checks if cost represents a collision.
float findCircumscribedCost(std::shared_ptr< nav2_costmap_2d::Costmap2DROS > costmap)
Find the min cost of the inflation decay function for which the robot MAY be in collision in any orie...
CollisionCost costAtPose(float x, float y, float theta)
cost at a robot pose
float distanceToObstacle(const CollisionCost &cost)
Distance to obstacle from cost.
void initialize() override
Initialize critic.
void score(CriticData &data) override
Evaluate cost related to obstacle avoidance.
bool worldToMap(double wx, double wy, unsigned int &mx, unsigned int &my)
Get the map coordinates from a world point.
double footprintCostAtPose(double x, double y, double theta, const Footprint &footprint)
Find the footprint cost a a post with an unoriented footprint.
void setCostmap(CostmapT costmap)
Set the current costmap object to use for collision detection.
double pointCost(int x, int y) const
Get the cost of a point.
Data to pass to critics for scoring, including state, trajectories, pruned path, global goal,...
Definition: critic_data.hpp:40
Utility for storing cost information.