Nav2 Navigation Stack - humble  humble
ROS 2 Navigation Stack
utils.hpp
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 #ifndef NAV2_MPPI_CONTROLLER__TOOLS__UTILS_HPP_
17 #define NAV2_MPPI_CONTROLLER__TOOLS__UTILS_HPP_
18 
19 #include <algorithm>
20 #include <chrono>
21 #include <string>
22 #include <limits>
23 #include <memory>
24 #include <vector>
25 
26 #include <xtensor/xarray.hpp>
27 #include <xtensor/xnorm.hpp>
28 #include <xtensor/xmath.hpp>
29 #include <xtensor/xview.hpp>
30 
31 #include "angles/angles.h"
32 
33 #include "tf2/utils.h"
34 #include "tf2_geometry_msgs/tf2_geometry_msgs.hpp"
35 
36 #include "geometry_msgs/msg/twist_stamped.hpp"
37 #include "nav_msgs/msg/path.hpp"
38 #include "visualization_msgs/msg/marker_array.hpp"
39 
40 #include "rclcpp/rclcpp.hpp"
41 #include "rclcpp_lifecycle/lifecycle_node.hpp"
42 
43 #include "nav2_util/node_utils.hpp"
44 #include "nav2_core/goal_checker.hpp"
45 
46 #include "nav2_mppi_controller/models/optimizer_settings.hpp"
47 #include "nav2_mppi_controller/models/control_sequence.hpp"
48 #include "nav2_mppi_controller/models/path.hpp"
49 #include "builtin_interfaces/msg/time.hpp"
50 #include "nav2_mppi_controller/critic_data.hpp"
51 
52 namespace mppi::utils
53 {
54 using xt::evaluation_strategy::immediate;
55 
63 inline geometry_msgs::msg::Pose createPose(double x, double y, double z)
64 {
65  geometry_msgs::msg::Pose pose;
66  pose.position.x = x;
67  pose.position.y = y;
68  pose.position.z = z;
69  pose.orientation.w = 1;
70  pose.orientation.x = 0;
71  pose.orientation.y = 0;
72  pose.orientation.z = 0;
73  return pose;
74 }
75 
83 inline geometry_msgs::msg::Vector3 createScale(double x, double y, double z)
84 {
85  geometry_msgs::msg::Vector3 scale;
86  scale.x = x;
87  scale.y = y;
88  scale.z = z;
89  return scale;
90 }
91 
100 inline std_msgs::msg::ColorRGBA createColor(float r, float g, float b, float a)
101 {
102  std_msgs::msg::ColorRGBA color;
103  color.r = r;
104  color.g = g;
105  color.b = b;
106  color.a = a;
107  return color;
108 }
109 
119 inline visualization_msgs::msg::Marker createMarker(
120  int id, const geometry_msgs::msg::Pose & pose, const geometry_msgs::msg::Vector3 & scale,
121  const std_msgs::msg::ColorRGBA & color, const std::string & frame_id, const std::string & ns)
122 {
123  using visualization_msgs::msg::Marker;
124  Marker marker;
125  marker.header.frame_id = frame_id;
126  marker.header.stamp = rclcpp::Time(0, 0);
127  marker.ns = ns;
128  marker.id = id;
129  marker.type = Marker::SPHERE;
130  marker.action = Marker::ADD;
131 
132  marker.pose = pose;
133  marker.scale = scale;
134  marker.color = color;
135  return marker;
136 }
137 
145 inline geometry_msgs::msg::TwistStamped toTwistStamped(
146  float vx, float wz, const builtin_interfaces::msg::Time & stamp, const std::string & frame)
147 {
148  geometry_msgs::msg::TwistStamped twist;
149  twist.header.frame_id = frame;
150  twist.header.stamp = stamp;
151  twist.twist.linear.x = vx;
152  twist.twist.angular.z = wz;
153 
154  return twist;
155 }
156 
165 inline geometry_msgs::msg::TwistStamped toTwistStamped(
166  float vx, float vy, float wz, const builtin_interfaces::msg::Time & stamp,
167  const std::string & frame)
168 {
169  auto twist = toTwistStamped(vx, wz, stamp, frame);
170  twist.twist.linear.y = vy;
171 
172  return twist;
173 }
174 
180 inline models::Path toTensor(const nav_msgs::msg::Path & path)
181 {
182  auto result = models::Path{};
183  result.reset(path.poses.size());
184 
185  for (size_t i = 0; i < path.poses.size(); ++i) {
186  result.x(i) = path.poses[i].pose.position.x;
187  result.y(i) = path.poses[i].pose.position.y;
188  result.yaws(i) = tf2::getYaw(path.poses[i].pose.orientation);
189  }
190 
191  return result;
192 }
193 
201 inline bool withinPositionGoalTolerance(
202  nav2_core::GoalChecker * goal_checker,
203  const geometry_msgs::msg::Pose & robot,
204  const geometry_msgs::msg::Pose & goal)
205 {
206  if (goal_checker) {
207  geometry_msgs::msg::Pose pose_tolerance;
208  geometry_msgs::msg::Twist velocity_tolerance;
209  goal_checker->getTolerances(pose_tolerance, velocity_tolerance);
210 
211  const auto pose_tolerance_sq = pose_tolerance.position.x * pose_tolerance.position.x;
212 
213  auto dx = robot.position.x - goal.position.x;
214  auto dy = robot.position.y - goal.position.y;
215 
216  auto dist_sq = dx * dx + dy * dy;
217 
218  if (dist_sq < pose_tolerance_sq) {
219  return true;
220  }
221  }
222 
223  return false;
224 }
225 
233 inline bool withinPositionGoalTolerance(
234  float pose_tolerance,
235  const geometry_msgs::msg::Pose & robot,
236  const geometry_msgs::msg::Pose & goal)
237 {
238  const double & dist_sq =
239  std::pow(goal.position.x - robot.position.x, 2) +
240  std::pow(goal.position.y - robot.position.y, 2);
241 
242  const float pose_tolerance_sq = pose_tolerance * pose_tolerance;
243 
244  if (dist_sq < pose_tolerance_sq) {
245  return true;
246  }
247 
248  return false;
249 }
250 
258 template<typename T>
259 auto normalize_angles(const T & angles)
260 {
261  auto && theta = xt::eval(xt::fmod(angles + M_PI, 2.0 * M_PI));
262  return xt::eval(xt::where(theta <= 0.0, theta + M_PI, theta - M_PI));
263 }
264 
278 template<typename F, typename T>
279 auto shortest_angular_distance(
280  const F & from,
281  const T & to)
282 {
283  return normalize_angles(to - from);
284 }
285 
292 inline size_t findPathFurthestReachedPoint(const CriticData & data)
293 {
294  const auto traj_x = xt::view(data.trajectories.x, xt::all(), -1, xt::newaxis());
295  const auto traj_y = xt::view(data.trajectories.y, xt::all(), -1, xt::newaxis());
296 
297  const auto dx = data.path.x - traj_x;
298  const auto dy = data.path.y - traj_y;
299 
300  const auto dists = dx * dx + dy * dy;
301 
302  size_t max_id_by_trajectories = 0, min_id_by_path = 0;
303  float min_distance_by_path = std::numeric_limits<float>::max();
304  float cur_dist = 0.0f;
305 
306  for (size_t i = 0; i < dists.shape(0); i++) {
307  min_id_by_path = 0;
308  min_distance_by_path = std::numeric_limits<float>::max();
309  for (size_t j = 0; j < dists.shape(1); j++) {
310  cur_dist = dists(i, j);
311  if (cur_dist < min_distance_by_path) {
312  min_distance_by_path = cur_dist;
313  min_id_by_path = j;
314  }
315  }
316  max_id_by_trajectories = std::max(max_id_by_trajectories, min_id_by_path);
317  }
318  return max_id_by_trajectories;
319 }
320 
327 inline size_t findPathTrajectoryInitialPoint(const CriticData & data)
328 {
329  // First point should be the same for all trajectories from initial conditions
330  const auto dx = data.path.x - data.trajectories.x(0, 0);
331  const auto dy = data.path.y - data.trajectories.y(0, 0);
332  const auto dists = dx * dx + dy * dy;
333 
334  float min_distance_by_path = std::numeric_limits<float>::max();
335  size_t min_id = 0;
336  for (size_t j = 0; j < dists.shape(0); j++) {
337  if (dists(j) < min_distance_by_path) {
338  min_distance_by_path = dists(j);
339  min_id = j;
340  }
341  }
342 
343  return min_id;
344 }
345 
350 inline void setPathFurthestPointIfNotSet(CriticData & data)
351 {
352  if (!data.furthest_reached_path_point) {
353  data.furthest_reached_path_point = findPathFurthestReachedPoint(data);
354  }
355 }
356 
361 inline void findPathCosts(
362  CriticData & data,
363  std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros)
364 {
365  auto * costmap = costmap_ros->getCostmap();
366  unsigned int map_x, map_y;
367  const size_t path_segments_count = data.path.x.shape(0) - 1;
368  data.path_pts_valid = std::vector<bool>(path_segments_count, false);
369  for (unsigned int idx = 0; idx < path_segments_count; idx++) {
370  const auto path_x = data.path.x(idx);
371  const auto path_y = data.path.y(idx);
372  if (!costmap->worldToMap(path_x, path_y, map_x, map_y)) {
373  (*data.path_pts_valid)[idx] = false;
374  continue;
375  }
376 
377  switch (costmap->getCost(map_x, map_y)) {
378  using namespace nav2_costmap_2d; // NOLINT
379  case (LETHAL_OBSTACLE):
380  (*data.path_pts_valid)[idx] = false;
381  continue;
382  case (INSCRIBED_INFLATED_OBSTACLE):
383  (*data.path_pts_valid)[idx] = false;
384  continue;
385  case (NO_INFORMATION):
386  const bool is_tracking_unknown =
387  costmap_ros->getLayeredCostmap()->isTrackingUnknown();
388  (*data.path_pts_valid)[idx] = is_tracking_unknown ? true : false;
389  continue;
390  }
391 
392  (*data.path_pts_valid)[idx] = true;
393  }
394 }
395 
400 inline void setPathCostsIfNotSet(
401  CriticData & data,
402  std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros)
403 {
404  if (!data.path_pts_valid) {
405  findPathCosts(data, costmap_ros);
406  }
407 }
408 
417 inline float posePointAngle(
418  const geometry_msgs::msg::Pose & pose, double point_x, double point_y, bool forward_preference)
419 {
420  float pose_x = pose.position.x;
421  float pose_y = pose.position.y;
422  float pose_yaw = tf2::getYaw(pose.orientation);
423 
424  float yaw = atan2f(point_y - pose_y, point_x - pose_x);
425 
426  // If no preference for forward, return smallest angle either in heading or 180 of heading
427  if (!forward_preference) {
428  return std::min(
429  fabs(angles::shortest_angular_distance(yaw, pose_yaw)),
430  fabs(angles::shortest_angular_distance(yaw, angles::normalize_angle(pose_yaw + M_PI))));
431  }
432 
433  return fabs(angles::shortest_angular_distance(yaw, pose_yaw));
434 }
435 
442 inline void savitskyGolayFilter(
443  models::ControlSequence & control_sequence,
444  std::array<mppi::models::Control, 4> & control_history,
445  const models::OptimizerSettings & settings)
446 {
447  // Savitzky-Golay Quadratic, 9-point Coefficients
448  xt::xarray<float> filter = {-21.0, 14.0, 39.0, 54.0, 59.0, 54.0, 39.0, 14.0, -21.0};
449  filter /= 231.0;
450 
451  const unsigned int num_sequences = control_sequence.vx.shape(0) - 1;
452 
453  // Too short to smooth meaningfully
454  if (num_sequences < 20) {
455  return;
456  }
457 
458  auto applyFilter = [&](const xt::xarray<float> & data) -> float {
459  return xt::sum(data * filter, {0}, immediate)();
460  };
461 
462  auto applyFilterOverAxis =
463  [&](xt::xtensor<float, 1> & sequence,
464  const float hist_0, const float hist_1, const float hist_2, const float hist_3) -> void
465  {
466  unsigned int idx = 0;
467  sequence(idx) = applyFilter(
468  {
469  hist_0,
470  hist_1,
471  hist_2,
472  hist_3,
473  sequence(idx),
474  sequence(idx + 1),
475  sequence(idx + 2),
476  sequence(idx + 3),
477  sequence(idx + 4)});
478 
479  idx++;
480  sequence(idx) = applyFilter(
481  {
482  hist_1,
483  hist_2,
484  hist_3,
485  sequence(idx - 1),
486  sequence(idx),
487  sequence(idx + 1),
488  sequence(idx + 2),
489  sequence(idx + 3),
490  sequence(idx + 4)});
491 
492  idx++;
493  sequence(idx) = applyFilter(
494  {
495  hist_2,
496  hist_3,
497  sequence(idx - 2),
498  sequence(idx - 1),
499  sequence(idx),
500  sequence(idx + 1),
501  sequence(idx + 2),
502  sequence(idx + 3),
503  sequence(idx + 4)});
504 
505  idx++;
506  sequence(idx) = applyFilter(
507  {
508  hist_3,
509  sequence(idx - 3),
510  sequence(idx - 2),
511  sequence(idx - 1),
512  sequence(idx),
513  sequence(idx + 1),
514  sequence(idx + 2),
515  sequence(idx + 3),
516  sequence(idx + 4)});
517 
518  for (idx = 4; idx != num_sequences - 4; idx++) {
519  sequence(idx) = applyFilter(
520  {
521  sequence(idx - 4),
522  sequence(idx - 3),
523  sequence(idx - 2),
524  sequence(idx - 1),
525  sequence(idx),
526  sequence(idx + 1),
527  sequence(idx + 2),
528  sequence(idx + 3),
529  sequence(idx + 4)});
530  }
531 
532  idx++;
533  sequence(idx) = applyFilter(
534  {
535  sequence(idx - 4),
536  sequence(idx - 3),
537  sequence(idx - 2),
538  sequence(idx - 1),
539  sequence(idx),
540  sequence(idx + 1),
541  sequence(idx + 2),
542  sequence(idx + 3),
543  sequence(idx + 3)});
544 
545  idx++;
546  sequence(idx) = applyFilter(
547  {
548  sequence(idx - 4),
549  sequence(idx - 3),
550  sequence(idx - 2),
551  sequence(idx - 1),
552  sequence(idx),
553  sequence(idx + 1),
554  sequence(idx + 2),
555  sequence(idx + 2),
556  sequence(idx + 2)});
557 
558  idx++;
559  sequence(idx) = applyFilter(
560  {
561  sequence(idx - 4),
562  sequence(idx - 3),
563  sequence(idx - 2),
564  sequence(idx - 1),
565  sequence(idx),
566  sequence(idx + 1),
567  sequence(idx + 1),
568  sequence(idx + 1),
569  sequence(idx + 1)});
570 
571  idx++;
572  sequence(idx) = applyFilter(
573  {
574  sequence(idx - 4),
575  sequence(idx - 3),
576  sequence(idx - 2),
577  sequence(idx - 1),
578  sequence(idx),
579  sequence(idx),
580  sequence(idx),
581  sequence(idx),
582  sequence(idx)});
583  };
584 
585  // Filter trajectories
586  applyFilterOverAxis(
587  control_sequence.vx, control_history[0].vx,
588  control_history[1].vx, control_history[2].vx, control_history[3].vx);
589  applyFilterOverAxis(
590  control_sequence.vy, control_history[0].vy,
591  control_history[1].vy, control_history[2].vy, control_history[3].vy);
592  applyFilterOverAxis(
593  control_sequence.wz, control_history[0].wz,
594  control_history[1].wz, control_history[2].wz, control_history[3].wz);
595 
596  // Update control history
597  unsigned int offset = settings.shift_control_sequence ? 1 : 0;
598  control_history[0] = control_history[1];
599  control_history[1] = control_history[2];
600  control_history[2] = control_history[3];
601  control_history[3] = {
602  control_sequence.vx(offset),
603  control_sequence.vy(offset),
604  control_sequence.wz(offset)};
605 }
606 
612 inline unsigned int findFirstPathInversion(nav_msgs::msg::Path & path)
613 {
614  // At least 3 poses for a possible inversion
615  if (path.poses.size() < 3) {
616  return path.poses.size();
617  }
618 
619  // Iterating through the path to determine the position of the path inversion
620  for (unsigned int idx = 1; idx < path.poses.size() - 1; ++idx) {
621  // We have two vectors for the dot product OA and AB. Determining the vectors.
622  float oa_x = path.poses[idx].pose.position.x -
623  path.poses[idx - 1].pose.position.x;
624  float oa_y = path.poses[idx].pose.position.y -
625  path.poses[idx - 1].pose.position.y;
626  float ab_x = path.poses[idx + 1].pose.position.x -
627  path.poses[idx].pose.position.x;
628  float ab_y = path.poses[idx + 1].pose.position.y -
629  path.poses[idx].pose.position.y;
630 
631  // Checking for the existance of cusp, in the path, using the dot product.
632  float dot_product = (oa_x * ab_x) + (oa_y * ab_y);
633  if (dot_product < 0.0) {
634  return idx + 1;
635  }
636  }
637 
638  return path.poses.size();
639 }
640 
646 inline unsigned int removePosesAfterFirstInversion(nav_msgs::msg::Path & path)
647 {
648  nav_msgs::msg::Path cropped_path = path;
649  const unsigned int first_after_inversion = findFirstPathInversion(cropped_path);
650  if (first_after_inversion == path.poses.size()) {
651  return 0u;
652  }
653 
654  cropped_path.poses.erase(
655  cropped_path.poses.begin() + first_after_inversion, cropped_path.poses.end());
656  path = cropped_path;
657  return first_after_inversion;
658 }
659 
665 inline size_t findClosestPathPt(const std::vector<float> & vec, float dist, size_t init = 0)
666 {
667  auto iter = std::lower_bound(vec.begin() + init, vec.end(), dist);
668  if (iter == vec.begin() + init) {
669  return 0;
670  }
671  if (dist - *(iter - 1) < *iter - dist) {
672  return iter - 1 - vec.begin();
673  }
674  return iter - vec.begin();
675 }
676 
677 } // namespace mppi::utils
678 
679 #endif // NAV2_MPPI_CONTROLLER__TOOLS__UTILS_HPP_
Function-object for checking whether a goal has been reached.
virtual bool getTolerances(geometry_msgs::msg::Pose &pose_tolerance, geometry_msgs::msg::Twist &vel_tolerance)=0
Get the maximum possible tolerances used for goal checking in the major types. Any field without a va...
void reset(unsigned int size)
Reset path data.
Definition: path.hpp:36