Nav2 Navigation Stack - humble  humble
ROS 2 Navigation Stack
cost_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/cost_critic.hpp"
18 #include "nav2_core/exceptions.hpp"
19 
20 namespace mppi::critics
21 {
22 
24 {
25  auto getParam = parameters_handler_->getParamGetter(name_);
26  getParam(consider_footprint_, "consider_footprint", false);
27  getParam(power_, "cost_power", 1);
28  getParam(weight_, "cost_weight", 3.81);
29  getParam(critical_cost_, "critical_cost", 300.0);
30  getParam(collision_cost_, "collision_cost", 1000000.0);
31  getParam(near_goal_distance_, "near_goal_distance", 0.5);
32  getParam(inflation_layer_name_, "inflation_layer_name", std::string(""));
33 
34  // Normalized by cost value to put in same regime as other weights
35  weight_ /= 254.0f;
36 
37  // Normalize weight when parameter is changed dynamically as well
38  auto weightDynamicCb = [&](const rclcpp::Parameter & weight) {
39  weight_ = weight.as_double() / 254.0f;
40  };
41  parameters_handler_->addDynamicParamCallback(name_ + ".cost_weight", weightDynamicCb);
42 
43  collision_checker_.setCostmap(costmap_);
44  possibly_inscribed_cost_ = findCircumscribedCost(costmap_ros_);
45 
46  if (possibly_inscribed_cost_ < 1.0f) {
47  RCLCPP_ERROR(
48  logger_,
49  "Inflation layer either not found or inflation is not set sufficiently for "
50  "optimized non-circular collision checking capabilities. It is HIGHLY recommended to set"
51  " the inflation radius to be at MINIMUM half of the robot's largest cross-section. See "
52  "github.com/ros-planning/navigation2/tree/main/nav2_smac_planner#potential-fields"
53  " for full instructions. This will substantially impact run-time performance.");
54  }
55 
56  if (costmap_ros_->getUseRadius() == consider_footprint_) {
57  RCLCPP_WARN(
58  logger_,
59  "Inconsistent configuration in collision checking. Please verify the robot's shape settings "
60  "in both the costmap and the cost critic.");
61  if (costmap_ros_->getUseRadius()) {
63  "Considering footprint in collision checking but no robot footprint provided in the "
64  "costmap.");
65  }
66  }
67 
68  RCLCPP_INFO(
69  logger_,
70  "InflationCostCritic instantiated with %d power and %f / %f weights. "
71  "Critic will collision check based on %s cost.",
72  power_, critical_cost_, weight_, consider_footprint_ ?
73  "footprint" : "circular");
74 }
75 
77  std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap)
78 {
79  bool inflation_layer_found = false;
80  double result = -1.0;
81  const double circum_radius = costmap->getLayeredCostmap()->getCircumscribedRadius();
82  if (static_cast<float>(circum_radius) == circumscribed_radius_) {
83  // early return if footprint size is unchanged
84  return circumscribed_cost_;
85  }
86 
87  // check if the costmap has an inflation layer
88  for (auto layer = costmap->getLayeredCostmap()->getPlugins()->begin();
89  layer != costmap->getLayeredCostmap()->getPlugins()->end();
90  ++layer)
91  {
92  auto inflation_layer = std::dynamic_pointer_cast<nav2_costmap_2d::InflationLayer>(*layer);
93  if (!inflation_layer ||
94  (!inflation_layer_name_.empty() &&
95  inflation_layer->getName() != inflation_layer_name_))
96  {
97  continue;
98  }
99 
100  inflation_layer_found = true;
101  const double resolution = costmap->getCostmap()->getResolution();
102  result = inflation_layer->computeCost(circum_radius / resolution);
103  }
104 
105  if (!inflation_layer_found) {
106  RCLCPP_WARN(
107  logger_,
108  "No inflation layer found in costmap configuration. "
109  "If this is an SE2-collision checking plugin, it cannot use costmap potential "
110  "field to speed up collision checking by only checking the full footprint "
111  "when robot is within possibly-inscribed radius of an obstacle. This may "
112  "significantly slow down planning times and not avoid anything but absolute collisions!");
113  }
114 
115  circumscribed_radius_ = static_cast<float>(circum_radius);
116  circumscribed_cost_ = static_cast<float>(result);
117 
118  return circumscribed_cost_;
119 }
120 
122 {
123  using xt::evaluation_strategy::immediate;
124  if (!enabled_) {
125  return;
126  }
127 
128  if (consider_footprint_) {
129  // footprint may have changed since initialization if user has dynamic footprints
130  possibly_inscribed_cost_ = findCircumscribedCost(costmap_ros_);
131  }
132 
133  // If near the goal, don't apply the preferential term since the goal is near obstacles
134  bool near_goal = false;
135  if (utils::withinPositionGoalTolerance(near_goal_distance_, data.state.pose.pose, data.goal)) {
136  near_goal = true;
137  }
138 
139  auto && repulsive_cost = xt::xtensor<float, 1>::from_shape({data.costs.shape(0)});
140  repulsive_cost.fill(0.0);
141 
142  const size_t traj_len = data.trajectories.x.shape(1);
143  bool all_trajectories_collide = true;
144  for (size_t i = 0; i < data.trajectories.x.shape(0); ++i) {
145  bool trajectory_collide = false;
146  const auto & traj = data.trajectories;
147  float pose_cost;
148 
149  for (size_t j = 0; j < traj_len; j++) {
150  // The costAtPose doesn't use orientation
151  // The footprintCostAtPose will always return "INSCRIBED" if footprint is over it
152  // So the center point has more information than the footprint
153  pose_cost = costAtPose(traj.x(i, j), traj.y(i, j));
154  if (pose_cost < 1.0f) {continue;} // In free space
155 
156  if (inCollision(pose_cost, traj.x(i, j), traj.y(i, j), traj.yaws(i, j))) {
157  trajectory_collide = true;
158  break;
159  }
160 
161  // Let near-collision trajectory points be punished severely
162  // Note that we collision check based on the footprint actual,
163  // but score based on the center-point cost regardless
164  using namespace nav2_costmap_2d; // NOLINT
165  if (pose_cost >= INSCRIBED_INFLATED_OBSTACLE) {
166  repulsive_cost[i] += critical_cost_;
167  } else if (!near_goal) { // Generally prefer trajectories further from obstacles
168  repulsive_cost[i] += pose_cost;
169  }
170  }
171 
172  if (!trajectory_collide) {
173  all_trajectories_collide = false;
174  } else {
175  repulsive_cost[i] = collision_cost_;
176  }
177  }
178 
179  data.costs += xt::pow((weight_ * repulsive_cost / traj_len), power_);
180  data.fail_flag = all_trajectories_collide;
181 }
182 
188 bool CostCritic::inCollision(float cost, float x, float y, float theta)
189 {
190  bool is_tracking_unknown =
191  costmap_ros_->getLayeredCostmap()->isTrackingUnknown();
192 
193  // If consider_footprint_ check footprint scort for collision
194  if (consider_footprint_ &&
195  (cost >= possibly_inscribed_cost_ || possibly_inscribed_cost_ < 1.0f))
196  {
197  cost = static_cast<float>(collision_checker_.footprintCostAtPose(
198  x, y, theta, costmap_ros_->getRobotFootprint()));
199  }
200 
201  switch (static_cast<unsigned char>(cost)) {
202  using namespace nav2_costmap_2d; // NOLINT
203  case (LETHAL_OBSTACLE):
204  return true;
205  case (INSCRIBED_INFLATED_OBSTACLE):
206  return consider_footprint_ ? false : true;
207  case (NO_INFORMATION):
208  return is_tracking_unknown ? false : true;
209  }
210 
211  return false;
212 }
213 
214 float CostCritic::costAtPose(float x, float y)
215 {
216  using namespace nav2_costmap_2d; // NOLINT
217  unsigned int x_i, y_i;
218  if (!collision_checker_.worldToMap(x, y, x_i, y_i)) {
219  return nav2_costmap_2d::NO_INFORMATION;
220  }
221 
222  return collision_checker_.pointCost(x_i, y_i);
223 }
224 
225 } // namespace mppi::critics
226 
227 #include <pluginlib/class_list_macros.hpp>
228 
229 PLUGINLIB_EXPORT_CLASS(
void addDynamicParamCallback(const std::string &name, T &&callback)
Set a parameter to a dynamic parameter callback.
auto getParamGetter(const std::string &ns)
Get an object to retreive parameters.
Critic objective function for avoiding obstacles using costmap's inflated cost.
Definition: cost_critic.hpp:36
bool inCollision(float cost, float x, float y, float theta)
Checks if cost represents a collision.
float costAtPose(float x, float y)
cost at a robot pose
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...
Definition: cost_critic.cpp:76
void initialize() override
Initialize critic.
Definition: cost_critic.cpp:23
void score(CriticData &data) override
Evaluate cost related to obstacle avoidance.
Abstract critic objective function to score trajectories.
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.
Data to pass to critics for scoring, including state, trajectories, pruned path, global goal,...
Definition: critic_data.hpp:39