Nav2 Navigation Stack - lyrical  lyrical
ROS 2 Navigation Stack
node_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 <algorithm>
16 #include <chrono>
17 #include <cmath>
18 #include <fstream>
19 #include <limits>
20 #include <memory>
21 #include <queue>
22 #include <string>
23 #include <vector>
24 
25 #include "angles/angles.h"
26 
27 #include "ompl/base/ScopedState.h"
28 #include "ompl/base/spaces/DubinsStateSpace.h"
29 #include "ompl/base/spaces/ReedsSheppStateSpace.h"
30 #include "ompl/base/spaces/SE2StateSpace.h"
31 
32 #include "nav2_smac_planner/node_lattice.hpp"
33 
34 using namespace std::chrono; // NOLINT
35 
36 namespace nav2_smac_planner
37 {
38 
39 // Each of these tables are the projected motion models through
40 // time and space applied to the search on the current node in
41 // continuous map-coordinates (e.g. not meters but partial map cells)
42 // Currently, these are set to project *at minimum* into a neighboring
43 // cell. Though this could be later modified to project a certain
44 // amount of time or particular distance forward.
45 void LatticeMotionTable::initMotionModel(
46  unsigned int & size_x_in,
47  SearchInfo & search_info)
48 {
49  size_x = size_x_in;
50  change_penalty = search_info.change_penalty;
51  non_straight_penalty = search_info.non_straight_penalty;
52  cost_penalty = search_info.cost_penalty;
53  reverse_penalty = search_info.reverse_penalty;
54  travel_distance_reward = 1.0f - search_info.retrospective_penalty;
55  allow_reverse_expansion = search_info.allow_reverse_expansion;
56  rotation_penalty = search_info.rotation_penalty;
57  min_turning_radius = search_info.minimum_turning_radius;
58  downsample_obstacle_heuristic = search_info.downsample_obstacle_heuristic;
59  use_quadratic_cost_penalty = search_info.use_quadratic_cost_penalty;
60 
61  if (current_lattice_filepath == search_info.lattice_filepath) {
62  return;
63  }
64  current_lattice_filepath = search_info.lattice_filepath;
65 
66  // Get the metadata about this minimum control set
67  lattice_metadata = getLatticeMetadata(current_lattice_filepath);
68  std::ifstream latticeFile(current_lattice_filepath);
69  if (!latticeFile.is_open()) {
70  throw std::runtime_error("Could not open lattice file");
71  }
72  nlohmann::json json;
73  latticeFile >> json;
74  num_angle_quantization = lattice_metadata.number_of_headings;
75 
76  if (!state_space) {
77  if (lattice_metadata.motion_model == "omni") {
78  // Holonomic robots: straight-line analytic expansion
79  state_space = std::make_shared<ompl::base::SE2StateSpace>();
80  motion_model = MotionModel::OMNI;
81  } else if (!allow_reverse_expansion) {
82  state_space = std::make_shared<ompl::base::DubinsStateSpace>(
83  lattice_metadata.min_turning_radius);
84  motion_model = MotionModel::DUBIN;
85  } else {
86  state_space = std::make_shared<ompl::base::ReedsSheppStateSpace>(
87  lattice_metadata.min_turning_radius);
88  motion_model = MotionModel::REEDS_SHEPP;
89  }
90  }
91 
92  // Populate the motion primitives at each heading angle
93  float prev_start_angle = 0.0;
94  std::vector<MotionPrimitive> primitives;
95  nlohmann::json json_primitives = json["primitives"];
96  for (unsigned int i = 0; i < json_primitives.size(); ++i) {
97  MotionPrimitive new_primitive;
98  fromJsonToMotionPrimitive(json_primitives[i], new_primitive);
99 
100  if (prev_start_angle != new_primitive.start_angle) {
101  motion_primitives.push_back(primitives);
102  primitives.clear();
103  prev_start_angle = new_primitive.start_angle;
104  }
105  primitives.push_back(new_primitive);
106  }
107  motion_primitives.push_back(primitives);
108 
109  // Populate useful precomputed values to be leveraged
110  trig_values.reserve(lattice_metadata.number_of_headings);
111  for (unsigned int i = 0; i < lattice_metadata.heading_angles.size(); ++i) {
112  trig_values.emplace_back(
113  cos(lattice_metadata.heading_angles[i]),
114  sin(lattice_metadata.heading_angles[i]));
115  }
116 }
117 
118 MotionPrimitivePtrs LatticeMotionTable::getMotionPrimitives(
119  const NodeLattice * node,
120  unsigned int & direction_change_index)
121 {
122  MotionPrimitives & prims_at_heading = motion_primitives[node->pose.theta];
123  MotionPrimitivePtrs primitive_projection_list;
124  for (unsigned int i = 0; i != prims_at_heading.size(); i++) {
125  primitive_projection_list.push_back(&prims_at_heading[i]);
126  }
127 
128  // direction change index
129  direction_change_index = static_cast<unsigned int>(primitive_projection_list.size());
130 
131  if (allow_reverse_expansion) {
132  // Find normalized heading bin of the reverse expansion
133  double reserve_heading = node->pose.theta - (num_angle_quantization / 2);
134  if (reserve_heading < 0) {
135  reserve_heading += num_angle_quantization;
136  }
137  if (reserve_heading > num_angle_quantization) {
138  reserve_heading -= num_angle_quantization;
139  }
140 
141  MotionPrimitives & prims_at_reverse_heading = motion_primitives[reserve_heading];
142  for (unsigned int i = 0; i != prims_at_reverse_heading.size(); i++) {
143  primitive_projection_list.push_back(&prims_at_reverse_heading[i]);
144  }
145  }
146 
147  return primitive_projection_list;
148 }
149 
150 LatticeMetadata LatticeMotionTable::getLatticeMetadata(const std::string & lattice_filepath)
151 {
152  std::ifstream lattice_file(lattice_filepath);
153  if (!lattice_file.is_open()) {
154  throw std::runtime_error("Could not open lattice file!");
155  }
156 
157  nlohmann::json j;
158  lattice_file >> j;
159  LatticeMetadata metadata;
160  fromJsonToMetaData(j["lattice_metadata"], metadata);
161  return metadata;
162 }
163 
164 unsigned int LatticeMotionTable::getClosestAngularBin(const double & theta)
165 {
166  float min_dist = std::numeric_limits<float>::max();
167  unsigned int closest_idx = 0;
168  float dist = 0.0;
169  for (unsigned int i = 0; i != lattice_metadata.heading_angles.size(); i++) {
170  dist = fabs(angles::shortest_angular_distance(theta, lattice_metadata.heading_angles[i]));
171  if (dist < min_dist) {
172  min_dist = dist;
173  closest_idx = i;
174  }
175  }
176  return closest_idx;
177 }
178 
179 float & LatticeMotionTable::getAngleFromBin(const unsigned int & bin_idx)
180 {
181  return lattice_metadata.heading_angles[bin_idx];
182 }
183 
184 double LatticeMotionTable::getAngle(const double & theta)
185 {
186  return getClosestAngularBin(theta);
187 }
188 
189 NodeLattice::NodeLattice(const uint64_t index, NodeContext * ctx)
190 : parent(nullptr),
191  pose(0.0f, 0.0f, 0.0f),
192  _cell_cost(std::numeric_limits<float>::quiet_NaN()),
193  _accumulated_cost(std::numeric_limits<float>::max()),
194  _index(index),
195  _was_visited(false),
196  _motion_primitive(nullptr),
197  _backwards(false),
198  _is_node_valid(false),
199  _ctx(ctx)
200 {
201 }
202 
204 {
205  parent = nullptr;
206 }
207 
209 {
210  parent = nullptr;
211  _cell_cost = std::numeric_limits<float>::quiet_NaN();
212  _accumulated_cost = std::numeric_limits<float>::max();
213  _was_visited = false;
214  pose.x = 0.0f;
215  pose.y = 0.0f;
216  pose.theta = 0.0f;
217  _motion_primitive = nullptr;
218  _backwards = false;
219  _is_node_valid = false;
220 }
221 
223  const bool & traverse_unknown,
224  GridCollisionChecker * collision_checker,
225  MotionPrimitive * motion_primitive,
226  bool is_backwards)
227 {
228  // Already found, we can return the result
229  if (!std::isnan(_cell_cost)) {
230  return _is_node_valid;
231  }
232 
233  // Check primitive end pose
234  // Convert grid quantization of primitives to radians, then collision checker quantization
235  const double bin_size = 2.0 * M_PI / collision_checker->getPrecomputedAngles().size();
236  const double angle = std::fmod(
237  _ctx->motion_table.getAngleFromBin(this->pose.theta),
238  2.0 * M_PI) / bin_size;
239  if (collision_checker->inCollision(
240  this->pose.x, this->pose.y, angle /*bin in collision checker*/, traverse_unknown))
241  {
242  _is_node_valid = false;
243  _cell_cost = collision_checker->getCost();
244  return false;
245  }
246 
247  // Set the cost of a node to the highest cost across the primitive
248  float max_cell_cost = collision_checker->getCost();
249 
250  // If valid motion primitives are set, check intermediary poses > 1 cell apart
251  if (motion_primitive) {
252  const float & grid_resolution = _ctx->motion_table.lattice_metadata.grid_resolution;
253  const float & resolution_diag_sq = 2.0 * grid_resolution * grid_resolution;
254  MotionPose last_pose(1e9, 1e9, 1e9, TurnDirection::UNKNOWN);
255  MotionPose pose_dist(0.0, 0.0, 0.0, TurnDirection::UNKNOWN);
256 
257  // Back out the initial node starting point to move motion primitive relative to
258  MotionPose initial_pose, prim_pose;
259  initial_pose._x = this->pose.x - (motion_primitive->poses.back()._x / grid_resolution);
260  initial_pose._y = this->pose.y - (motion_primitive->poses.back()._y / grid_resolution);
261  initial_pose._theta = _ctx->motion_table.getAngleFromBin(motion_primitive->start_angle);
262 
263  for (auto it = motion_primitive->poses.begin(); it != motion_primitive->poses.end(); ++it) {
264  // poses are in metric coordinates from (0, 0), not grid space yet
265  pose_dist = *it - last_pose;
266  // Avoid square roots by (hypot(x, y) > res) == (x*x+y*y > diag*diag)
267  if (pose_dist._x * pose_dist._x + pose_dist._y * pose_dist._y > resolution_diag_sq) {
268  last_pose = *it;
269  // Convert primitive pose into grid space if it should be checked
270  prim_pose._x = initial_pose._x + (it->_x / grid_resolution);
271  prim_pose._y = initial_pose._y + (it->_y / grid_resolution);
272  // If reversing, invert the angle because the robot is backing into the primitive
273  // not driving forward with it
274  if (is_backwards) {
275  prim_pose._theta = std::fmod(it->_theta + M_PI, 2.0 * M_PI);
276  } else {
277  prim_pose._theta = std::fmod(it->_theta, 2.0 * M_PI);
278  }
279  if (collision_checker->inCollision(
280  prim_pose._x,
281  prim_pose._y,
282  prim_pose._theta / bin_size /*bin in collision checker*/,
283  traverse_unknown))
284  {
285  _is_node_valid = false;
286  _cell_cost = std::max(max_cell_cost, collision_checker->getCost());
287  return false;
288  }
289  max_cell_cost = std::max(max_cell_cost, collision_checker->getCost());
290  }
291  }
292  }
293 
294  _cell_cost = max_cell_cost;
295  _is_node_valid = true;
296  return _is_node_valid;
297 }
298 
300 {
301  const float normalized_cost = child->getCost() / 252.0;
302  if (std::isnan(normalized_cost)) {
303  throw std::runtime_error(
304  "Node attempted to get traversal "
305  "cost without a known collision cost!");
306  }
307 
308  // this is the first node
309  const MotionPrimitive * prim = this->getMotionPrimitive();
310  const MotionPrimitive * transition_prim = child->getMotionPrimitive();
311  const float prim_length =
312  transition_prim->trajectory_length / _ctx->motion_table.lattice_metadata.grid_resolution;
313  if (prim == nullptr) {
314  return prim_length;
315  }
316 
317  // Pure rotation in place 1 angular bin in either direction
318  if (transition_prim->trajectory_length < 1e-4) {
319  return _ctx->motion_table.rotation_penalty *
320  (1.0 + _ctx->motion_table.cost_penalty * normalized_cost);
321  }
322 
323  float travel_cost = 0.0;
324  float travel_cost_raw = 0.0;
325  if (_ctx->motion_table.use_quadratic_cost_penalty) {
326  travel_cost_raw = prim_length *
327  (_ctx->motion_table.travel_distance_reward +
328  _ctx->motion_table.cost_penalty * normalized_cost * normalized_cost);
329  } else {
330  travel_cost_raw = prim_length *
331  (_ctx->motion_table.travel_distance_reward +
332  _ctx->motion_table.cost_penalty * normalized_cost);
333  }
334 
335  if (transition_prim->arc_length < 0.001) {
336  // New motion is a straight motion, no additional costs to be applied
337  travel_cost = travel_cost_raw;
338  } else {
339  if (prim->left_turn == transition_prim->left_turn) {
340  // Turning motion but keeps in same general direction: encourages to commit to actions
341  travel_cost = travel_cost_raw * _ctx->motion_table.non_straight_penalty;
342  } else {
343  // Turning motion and velocity directions: penalizes wiggling.
344  travel_cost = travel_cost_raw *
345  (_ctx->motion_table.non_straight_penalty + _ctx->motion_table.change_penalty);
346  }
347  }
348 
349  // If backwards flag is set, this primitive is moving in reverse
350  if (child->isBackward()) {
351  // reverse direction
352  travel_cost *= _ctx->motion_table.reverse_penalty;
353  }
354 
355  return travel_cost;
356 }
357 
359  const Coordinates & node_coords,
360  const CoordinateVector & goals_coords)
361 {
362  // get obstacle heuristic value
363  // obstacle heuristic does not depend on goal heading
364  const float obstacle_heuristic = _ctx->obstacle_heuristic->getObstacleHeuristic(
365  node_coords, _ctx->motion_table.cost_penalty,
366  _ctx->motion_table.use_quadratic_cost_penalty,
367  _ctx->motion_table.downsample_obstacle_heuristic);
368  float distance_heuristic = std::numeric_limits<float>::max();
369  for (unsigned int i = 0; i < goals_coords.size(); i++) {
370  distance_heuristic = std::min(
371  distance_heuristic,
372  _ctx->distance_heuristic->getDistanceHeuristic(node_coords, goals_coords[i],
373  obstacle_heuristic, _ctx->motion_table));
374  }
375  return std::max(obstacle_heuristic, distance_heuristic);
376 }
377 
379  NodeContext * ctx,
380  const MotionModel & motion_model,
381  unsigned int & size_x,
382  unsigned int & /*size_y*/,
383  unsigned int & /*num_angle_quantization*/,
384  SearchInfo & search_info)
385 {
386  if (motion_model != MotionModel::STATE_LATTICE) {
387  throw std::runtime_error(
388  "Invalid motion model for Lattice node. Please select"
389  " STATE_LATTICE and provide a valid lattice file.");
390  }
391 
392  ctx->motion_table.initMotionModel(size_x, search_info);
393 }
394 
396  std::function<bool(const uint64_t &,
397  nav2_smac_planner::NodeLattice * &)> & NeighborGetter,
398  GridCollisionChecker * collision_checker,
399  const bool & traverse_unknown,
400  NodeVector & neighbors)
401 {
402  uint64_t index = 0;
403  bool backwards = false;
404  NodePtr neighbor = nullptr;
405  Coordinates initial_node_coords, motion_projection;
406  unsigned int direction_change_index = 0;
407  MotionPrimitivePtrs motion_primitives = _ctx->motion_table.getMotionPrimitives(
408  this,
409  direction_change_index);
410  const float & grid_resolution = _ctx->motion_table.lattice_metadata.grid_resolution;
411 
412  for (unsigned int i = 0; i != motion_primitives.size(); i++) {
413  const MotionPose & end_pose = motion_primitives[i]->poses.back();
414  motion_projection.x = this->pose.x + (end_pose._x / grid_resolution);
415  motion_projection.y = this->pose.y + (end_pose._y / grid_resolution);
416  motion_projection.theta = motion_primitives[i]->end_angle /*this is the ending angular bin*/;
417 
418  // if i >= idx, then we're in a reversing primitive. In that situation,
419  // the orientation of the robot is mirrored from what it would otherwise
420  // appear to be from the motion primitives file. We want to take this into
421  // account in case the robot base footprint is asymmetric.
422  backwards = false;
423  if (i >= direction_change_index) {
424  backwards = true;
425  float opposite_heading_theta =
426  motion_projection.theta - (_ctx->motion_table.num_angle_quantization / 2);
427  if (opposite_heading_theta < 0) {
428  opposite_heading_theta += _ctx->motion_table.num_angle_quantization;
429  }
430  if (opposite_heading_theta > _ctx->motion_table.num_angle_quantization) {
431  opposite_heading_theta -= _ctx->motion_table.num_angle_quantization;
432  }
433  motion_projection.theta = opposite_heading_theta;
434  }
435 
436  index = NodeLattice::getIndex(
437  static_cast<unsigned int>(motion_projection.x),
438  static_cast<unsigned int>(motion_projection.y),
439  static_cast<unsigned int>(motion_projection.theta),
440  _ctx->motion_table.size_x, _ctx->motion_table.num_angle_quantization);
441 
442  if (NeighborGetter(index, neighbor) && !neighbor->wasVisited()) {
443  // Cache the initial pose in case it was visited but valid
444  // don't want to disrupt continuous coordinate expansion
445  initial_node_coords = neighbor->pose;
446 
447  neighbor->setPose(
448  Coordinates(
449  motion_projection.x,
450  motion_projection.y,
451  motion_projection.theta));
452 
453  // Using a special isNodeValid API here, giving the motion primitive to use to
454  // validity check the transition of the current node to the new node over
455  if (neighbor->isNodeValid(
456  traverse_unknown, collision_checker, motion_primitives[i], backwards))
457  {
458  neighbor->setMotionPrimitive(motion_primitives[i]);
459  // Marking if this search was obtained in the reverse direction
460  neighbor->backwards(backwards);
461  neighbors.push_back(neighbor);
462  } else {
463  neighbor->setPose(initial_node_coords);
464  }
465  }
466  }
467 }
468 
469 bool NodeLattice::backtracePath(CoordinateVector & path)
470 {
471  if (!this->parent) {
472  return false;
473  }
474 
475  NodePtr current_node = this;
476 
477  while (current_node->parent) {
478  addNodeToPath(current_node, path);
479  current_node = current_node->parent;
480  }
481 
482  // add start to path
483  addNodeToPath(current_node, path);
484 
485  return true;
486 }
487 
489  NodeLattice::NodePtr current_node,
490  NodeLattice::CoordinateVector & path)
491 {
492  Coordinates initial_pose, prim_pose;
493  const MotionPrimitive * prim = current_node->getMotionPrimitive();
494  const float & grid_resolution = _ctx->motion_table.lattice_metadata.grid_resolution;
495  // if motion primitive is valid, then was searched (rather than analytically expanded),
496  // include dense path of subpoints making up the primitive at grid resolution
497  if (prim) {
498  initial_pose.x = current_node->pose.x - (prim->poses.back()._x / grid_resolution);
499  initial_pose.y = current_node->pose.y - (prim->poses.back()._y / grid_resolution);
500  initial_pose.theta = _ctx->motion_table.getAngleFromBin(prim->start_angle);
501 
502  for (auto it = prim->poses.crbegin(); it != prim->poses.crend(); ++it) {
503  // Convert primitive pose into grid space if it should be checked
504  prim_pose.x = initial_pose.x + (it->_x / grid_resolution);
505  prim_pose.y = initial_pose.y + (it->_y / grid_resolution);
506  // If reversing, invert the angle because the robot is backing into the primitive
507  // not driving forward with it
508  if (current_node->isBackward()) {
509  prim_pose.theta = std::fmod(it->_theta + M_PI, 2.0 * M_PI);
510  } else {
511  prim_pose.theta = it->_theta;
512  }
513  path.push_back(prim_pose);
514  }
515  } else {
516  // For analytic expansion nodes where there is no valid motion primitive
517  path.push_back(current_node->pose);
518  path.back().theta = _ctx->motion_table.getAngleFromBin(path.back().theta);
519  }
520 }
521 
522 } // namespace nav2_smac_planner
A costmap grid collision checker.
bool inCollision(const float &x, const float &y, const float &theta, const bool &traverse_unknown)
Check if in collision with costmap and footprint at pose.
std::vector< float > & getPrecomputedAngles()
Get the angles of the precomputed footprint orientations.
float getCost()
Get cost at footprint pose in costmap.
NodeLattice implementation for graph, Hybrid-A*.
void getNeighbors(std::function< bool(const uint64_t &, nav2_smac_planner::NodeLattice *&)> &validity_checker, GridCollisionChecker *collision_checker, const bool &traverse_unknown, NodeVector &neighbors)
Retrieve all valid neighbors of a node.
void backwards(bool back=true)
Sets that this primitive is moving in reverse.
uint64_t getIndex()
Gets cell index.
static void initMotionModel(NodeContext *ctx, const MotionModel &motion_model, unsigned int &size_x, unsigned int &size_y, unsigned int &angle_quantization, SearchInfo &search_info)
Initialize motion models.
~NodeLattice()
A destructor for nav2_smac_planner::NodeLattice.
float getCost()
Gets the costmap cost at this node.
void reset()
Reset method for new search.
bool backtracePath(CoordinateVector &path)
Set the starting pose for planning, as a node index.
float getTraversalCost(const NodePtr &child)
Get traversal cost of parent node to child node.
bool isNodeValid(const bool &traverse_unknown, GridCollisionChecker *collision_checker, MotionPrimitive *primitive=nullptr, bool is_backwards=false)
Check if this node is valid.
float getHeuristicCost(const Coordinates &node_coords, const CoordinateVector &goals_coords)
Get cost of heuristic of node.
void addNodeToPath(NodePtr current_node, CoordinateVector &path)
add node to the path
bool isBackward()
Gets if this primitive is moving in reverse.
bool wasVisited()
Gets if cell has been visited in search.
MotionPrimitive *& getMotionPrimitive()
Gets the motion primitive used to achieve node in search.
void setMotionPrimitive(MotionPrimitive *prim)
Sets the motion primitive used to achieve node in search.
void setPose(const Coordinates &pose_in)
setting continuous coordinate search poses (in partial-cells)
Implementation of coordinate2d structure.
Definition: types.hpp:224
A struct of all lattice metadata.
Definition: types.hpp:155
void initMotionModel(unsigned int &size_x_in, SearchInfo &search_info)
Initializing state lattice planner's motion model.
MotionPrimitivePtrs getMotionPrimitives(const NodeLattice *node, unsigned int &direction_change_index)
Get projections of motion models.
float & getAngleFromBin(const unsigned int &bin_idx)
Get the raw orientation from an angular bin.
A struct for poses in motion primitives.
Definition: types.hpp:119
A struct of all motion primitive data.
Definition: types.hpp:169
Search properties and penalties.
Definition: types.hpp:38