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 models::Path & path)
205 {
206  const auto goal_idx = path.x.shape(0) - 1;
207  const auto goal_x = path.x(goal_idx);
208  const auto goal_y = path.y(goal_idx);
209 
210  if (goal_checker) {
211  geometry_msgs::msg::Pose pose_tolerance;
212  geometry_msgs::msg::Twist velocity_tolerance;
213  goal_checker->getTolerances(pose_tolerance, velocity_tolerance);
214 
215  const auto pose_tolerance_sq = pose_tolerance.position.x * pose_tolerance.position.x;
216 
217  auto dx = robot.position.x - goal_x;
218  auto dy = robot.position.y - goal_y;
219 
220  auto dist_sq = dx * dx + dy * dy;
221 
222  if (dist_sq < pose_tolerance_sq) {
223  return true;
224  }
225  }
226 
227  return false;
228 }
229 
237 inline bool withinPositionGoalTolerance(
238  float pose_tolerance,
239  const geometry_msgs::msg::Pose & robot,
240  const models::Path & path)
241 {
242  const auto goal_idx = path.x.shape(0) - 1;
243  const auto goal_x = path.x(goal_idx);
244  const auto goal_y = path.y(goal_idx);
245 
246  const auto pose_tolerance_sq = pose_tolerance * pose_tolerance;
247 
248  auto dx = robot.position.x - goal_x;
249  auto dy = robot.position.y - goal_y;
250 
251  auto dist_sq = dx * dx + dy * dy;
252 
253  if (dist_sq < pose_tolerance_sq) {
254  return true;
255  }
256 
257  return false;
258 }
259 
267 template<typename T>
268 auto normalize_angles(const T & angles)
269 {
270  auto && theta = xt::eval(xt::fmod(angles + M_PI, 2.0 * M_PI));
271  return xt::eval(xt::where(theta <= 0.0, theta + M_PI, theta - M_PI));
272 }
273 
287 template<typename F, typename T>
288 auto shortest_angular_distance(
289  const F & from,
290  const T & to)
291 {
292  return normalize_angles(to - from);
293 }
294 
301 inline size_t findPathFurthestReachedPoint(const CriticData & data)
302 {
303  const auto traj_x = xt::view(data.trajectories.x, xt::all(), -1, xt::newaxis());
304  const auto traj_y = xt::view(data.trajectories.y, xt::all(), -1, xt::newaxis());
305 
306  const auto dx = data.path.x - traj_x;
307  const auto dy = data.path.y - traj_y;
308 
309  const auto dists = dx * dx + dy * dy;
310 
311  size_t max_id_by_trajectories = 0, min_id_by_path = 0;
312  float min_distance_by_path = std::numeric_limits<float>::max();
313  float cur_dist = 0.0f;
314 
315  for (size_t i = 0; i < dists.shape(0); i++) {
316  min_id_by_path = 0;
317  min_distance_by_path = std::numeric_limits<float>::max();
318  for (size_t j = 0; j < dists.shape(1); j++) {
319  cur_dist = dists(i, j);
320  if (cur_dist < min_distance_by_path) {
321  min_distance_by_path = cur_dist;
322  min_id_by_path = j;
323  }
324  }
325  max_id_by_trajectories = std::max(max_id_by_trajectories, min_id_by_path);
326  }
327  return max_id_by_trajectories;
328 }
329 
336 inline size_t findPathTrajectoryInitialPoint(const CriticData & data)
337 {
338  // First point should be the same for all trajectories from initial conditions
339  const auto dx = data.path.x - data.trajectories.x(0, 0);
340  const auto dy = data.path.y - data.trajectories.y(0, 0);
341  const auto dists = dx * dx + dy * dy;
342 
343  float min_distance_by_path = std::numeric_limits<float>::max();
344  size_t min_id = 0;
345  for (size_t j = 0; j < dists.shape(0); j++) {
346  if (dists(j) < min_distance_by_path) {
347  min_distance_by_path = dists(j);
348  min_id = j;
349  }
350  }
351 
352  return min_id;
353 }
354 
359 inline void setPathFurthestPointIfNotSet(CriticData & data)
360 {
361  if (!data.furthest_reached_path_point) {
362  data.furthest_reached_path_point = findPathFurthestReachedPoint(data);
363  }
364 }
365 
370 inline void findPathCosts(
371  CriticData & data,
372  std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros)
373 {
374  auto * costmap = costmap_ros->getCostmap();
375  unsigned int map_x, map_y;
376  const size_t path_segments_count = data.path.x.shape(0) - 1;
377  data.path_pts_valid = std::vector<bool>(path_segments_count, false);
378  for (unsigned int idx = 0; idx < path_segments_count; idx++) {
379  const auto path_x = data.path.x(idx);
380  const auto path_y = data.path.y(idx);
381  if (!costmap->worldToMap(path_x, path_y, map_x, map_y)) {
382  (*data.path_pts_valid)[idx] = false;
383  continue;
384  }
385 
386  switch (costmap->getCost(map_x, map_y)) {
387  using namespace nav2_costmap_2d; // NOLINT
388  case (LETHAL_OBSTACLE):
389  (*data.path_pts_valid)[idx] = false;
390  continue;
391  case (INSCRIBED_INFLATED_OBSTACLE):
392  (*data.path_pts_valid)[idx] = false;
393  continue;
394  case (NO_INFORMATION):
395  const bool is_tracking_unknown =
396  costmap_ros->getLayeredCostmap()->isTrackingUnknown();
397  (*data.path_pts_valid)[idx] = is_tracking_unknown ? true : false;
398  continue;
399  }
400 
401  (*data.path_pts_valid)[idx] = true;
402  }
403 }
404 
409 inline void setPathCostsIfNotSet(
410  CriticData & data,
411  std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros)
412 {
413  if (!data.path_pts_valid) {
414  findPathCosts(data, costmap_ros);
415  }
416 }
417 
426 inline float posePointAngle(
427  const geometry_msgs::msg::Pose & pose, double point_x, double point_y, bool forward_preference)
428 {
429  float pose_x = pose.position.x;
430  float pose_y = pose.position.y;
431  float pose_yaw = tf2::getYaw(pose.orientation);
432 
433  float yaw = atan2f(point_y - pose_y, point_x - pose_x);
434 
435  // If no preference for forward, return smallest angle either in heading or 180 of heading
436  if (!forward_preference) {
437  return std::min(
438  fabs(angles::shortest_angular_distance(yaw, pose_yaw)),
439  fabs(angles::shortest_angular_distance(yaw, angles::normalize_angle(pose_yaw + M_PI))));
440  }
441 
442  return fabs(angles::shortest_angular_distance(yaw, pose_yaw));
443 }
444 
451 inline void savitskyGolayFilter(
452  models::ControlSequence & control_sequence,
453  std::array<mppi::models::Control, 4> & control_history,
454  const models::OptimizerSettings & settings)
455 {
456  // Savitzky-Golay Quadratic, 9-point Coefficients
457  xt::xarray<float> filter = {-21.0, 14.0, 39.0, 54.0, 59.0, 54.0, 39.0, 14.0, -21.0};
458  filter /= 231.0;
459 
460  const unsigned int num_sequences = control_sequence.vx.shape(0) - 1;
461 
462  // Too short to smooth meaningfully
463  if (num_sequences < 20) {
464  return;
465  }
466 
467  auto applyFilter = [&](const xt::xarray<float> & data) -> float {
468  return xt::sum(data * filter, {0}, immediate)();
469  };
470 
471  auto applyFilterOverAxis =
472  [&](xt::xtensor<float, 1> & sequence,
473  const float hist_0, const float hist_1, const float hist_2, const float hist_3) -> void
474  {
475  unsigned int idx = 0;
476  sequence(idx) = applyFilter(
477  {
478  hist_0,
479  hist_1,
480  hist_2,
481  hist_3,
482  sequence(idx),
483  sequence(idx + 1),
484  sequence(idx + 2),
485  sequence(idx + 3),
486  sequence(idx + 4)});
487 
488  idx++;
489  sequence(idx) = applyFilter(
490  {
491  hist_1,
492  hist_2,
493  hist_3,
494  sequence(idx - 1),
495  sequence(idx),
496  sequence(idx + 1),
497  sequence(idx + 2),
498  sequence(idx + 3),
499  sequence(idx + 4)});
500 
501  idx++;
502  sequence(idx) = applyFilter(
503  {
504  hist_2,
505  hist_3,
506  sequence(idx - 2),
507  sequence(idx - 1),
508  sequence(idx),
509  sequence(idx + 1),
510  sequence(idx + 2),
511  sequence(idx + 3),
512  sequence(idx + 4)});
513 
514  idx++;
515  sequence(idx) = applyFilter(
516  {
517  hist_3,
518  sequence(idx - 3),
519  sequence(idx - 2),
520  sequence(idx - 1),
521  sequence(idx),
522  sequence(idx + 1),
523  sequence(idx + 2),
524  sequence(idx + 3),
525  sequence(idx + 4)});
526 
527  for (idx = 4; idx != num_sequences - 4; idx++) {
528  sequence(idx) = applyFilter(
529  {
530  sequence(idx - 4),
531  sequence(idx - 3),
532  sequence(idx - 2),
533  sequence(idx - 1),
534  sequence(idx),
535  sequence(idx + 1),
536  sequence(idx + 2),
537  sequence(idx + 3),
538  sequence(idx + 4)});
539  }
540 
541  idx++;
542  sequence(idx) = applyFilter(
543  {
544  sequence(idx - 4),
545  sequence(idx - 3),
546  sequence(idx - 2),
547  sequence(idx - 1),
548  sequence(idx),
549  sequence(idx + 1),
550  sequence(idx + 2),
551  sequence(idx + 3),
552  sequence(idx + 3)});
553 
554  idx++;
555  sequence(idx) = applyFilter(
556  {
557  sequence(idx - 4),
558  sequence(idx - 3),
559  sequence(idx - 2),
560  sequence(idx - 1),
561  sequence(idx),
562  sequence(idx + 1),
563  sequence(idx + 2),
564  sequence(idx + 2),
565  sequence(idx + 2)});
566 
567  idx++;
568  sequence(idx) = applyFilter(
569  {
570  sequence(idx - 4),
571  sequence(idx - 3),
572  sequence(idx - 2),
573  sequence(idx - 1),
574  sequence(idx),
575  sequence(idx + 1),
576  sequence(idx + 1),
577  sequence(idx + 1),
578  sequence(idx + 1)});
579 
580  idx++;
581  sequence(idx) = applyFilter(
582  {
583  sequence(idx - 4),
584  sequence(idx - 3),
585  sequence(idx - 2),
586  sequence(idx - 1),
587  sequence(idx),
588  sequence(idx),
589  sequence(idx),
590  sequence(idx),
591  sequence(idx)});
592  };
593 
594  // Filter trajectories
595  applyFilterOverAxis(
596  control_sequence.vx, control_history[0].vx,
597  control_history[1].vx, control_history[2].vx, control_history[3].vx);
598  applyFilterOverAxis(
599  control_sequence.vy, control_history[0].vy,
600  control_history[1].vy, control_history[2].vy, control_history[3].vy);
601  applyFilterOverAxis(
602  control_sequence.wz, control_history[0].wz,
603  control_history[1].wz, control_history[2].wz, control_history[3].wz);
604 
605  // Update control history
606  unsigned int offset = settings.shift_control_sequence ? 1 : 0;
607  control_history[0] = control_history[1];
608  control_history[1] = control_history[2];
609  control_history[2] = control_history[3];
610  control_history[3] = {
611  control_sequence.vx(offset),
612  control_sequence.vy(offset),
613  control_sequence.wz(offset)};
614 }
615 
621 inline unsigned int findFirstPathInversion(nav_msgs::msg::Path & path)
622 {
623  // At least 3 poses for a possible inversion
624  if (path.poses.size() < 3) {
625  return path.poses.size();
626  }
627 
628  // Iterating through the path to determine the position of the path inversion
629  for (unsigned int idx = 1; idx < path.poses.size() - 1; ++idx) {
630  // We have two vectors for the dot product OA and AB. Determining the vectors.
631  float oa_x = path.poses[idx].pose.position.x -
632  path.poses[idx - 1].pose.position.x;
633  float oa_y = path.poses[idx].pose.position.y -
634  path.poses[idx - 1].pose.position.y;
635  float ab_x = path.poses[idx + 1].pose.position.x -
636  path.poses[idx].pose.position.x;
637  float ab_y = path.poses[idx + 1].pose.position.y -
638  path.poses[idx].pose.position.y;
639 
640  // Checking for the existance of cusp, in the path, using the dot product.
641  float dot_product = (oa_x * ab_x) + (oa_y * ab_y);
642  if (dot_product < 0.0) {
643  return idx + 1;
644  }
645  }
646 
647  return path.poses.size();
648 }
649 
655 inline unsigned int removePosesAfterFirstInversion(nav_msgs::msg::Path & path)
656 {
657  nav_msgs::msg::Path cropped_path = path;
658  const unsigned int first_after_inversion = findFirstPathInversion(cropped_path);
659  if (first_after_inversion == path.poses.size()) {
660  return 0u;
661  }
662 
663  cropped_path.poses.erase(
664  cropped_path.poses.begin() + first_after_inversion, cropped_path.poses.end());
665  path = cropped_path;
666  return first_after_inversion;
667 }
668 
674 inline size_t findClosestPathPt(const std::vector<float> & vec, float dist, size_t init = 0)
675 {
676  auto iter = std::lower_bound(vec.begin() + init, vec.end(), dist);
677  if (iter == vec.begin() + init) {
678  return 0;
679  }
680  if (dist - *(iter - 1) < *iter - dist) {
681  return iter - 1 - vec.begin();
682  }
683  return iter - vec.begin();
684 }
685 
686 } // namespace mppi::utils
687 
688 #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