selfdriving
MotionPrimitivesTree.h
Go to the documentation of this file.
1 /* -------------------------------------------------------------------------
2  * SelfDriving C++ library based on PTGs and mrpt-nav
3  * Copyright (C) 2019-2022 Jose Luis Blanco, University of Almeria
4  * See LICENSE for license information.
5  * ------------------------------------------------------------------------- */
6 
7 #pragma once
8 
10 #include <mpp/data/SE2_KinState.h>
11 #include <mpp/data/basic_types.h>
12 #include <mpp/data/ptg_t.h>
13 #include <mrpt/containers/traits_map.h>
14 #include <mrpt/core/optional_ref.h>
15 #include <mrpt/graphs/CDirectedTree.h>
16 #include <mrpt/math/wrap2pi.h>
17 #include <mrpt/nav/tpspace/CParameterizedTrajectoryGenerator.h>
18 #include <mrpt/poses/CPose2D.h>
19 
20 #include <cstdint>
21 #include <deque>
22 #include <list>
23 #include <optional>
24 #include <set>
25 #include <tuple>
26 
27 namespace mpp
28 {
29 using mrpt::graphs::TNodeID;
30 
31 /** Generic base for metrics */
32 template <class node_t>
34 
35 template <class node_t>
37 
38 struct TPS_point
39 {
40  TPS_point() = default;
41  ~TPS_point() = default;
42 
45  : k(_k), step(_step), speed(_speed)
46  {
47  }
48 
52 };
53 inline bool operator<(const TPS_point& a, const TPS_point& b)
54 {
55  return (a.k < b.k) || (a.k == b.k && a.step < b.step) ||
56  (a.k == b.k && a.step == b.step && a.speed < b.speed);
57 }
58 
59 /** A tree with nodes being vehicle poses, and edges potential valid motion
60  * primitives between them.
61  *
62  * This class provides storage for the nodes, and RRT* construction helper
63  * methods.
64  *
65  * See base class mrpt::graphs::CDirectedTree for the API to access edges.
66  *
67  * *Changes history*:
68  * - 06/MAR/2014: Creation (MB)
69  * - 21/JAN/2015: Refactoring (JLBC)
70  * - 2020-2021: Adapted to TPS-RRT* (JLBC)
71  */
72 template <class NODE_TYPE_DATA, class EDGE_TYPE>
73 class MotionPrimitivesTree : public mrpt::graphs::CDirectedTree<EDGE_TYPE>
74 {
75  public:
76  struct node_t : public NODE_TYPE_DATA
77  {
78  /** Duplicated ID (it's also in the map::iterator->first), but put here
79  * to make it available in path_t */
80  TNodeID nodeID_ = mrpt::graphs::INVALID_NODEID;
81 
82  /** Does not have value for the root, a valid ID otherwise */
83  std::optional<TNodeID> parentID_;
84 
85  /** cost of reaching this node from the root (=0 for the root) */
87 
88  node_t() = default;
90  TNodeID nodeID, const std::optional<TNodeID>& parentID,
91  const NODE_TYPE_DATA& data, cost_t cost)
92  : NODE_TYPE_DATA(data),
93  nodeID_(nodeID),
94  parentID_(parentID),
95  cost_(cost)
96  {
97  }
98  };
99 
100  using base_t = mrpt::graphs::CDirectedTree<EDGE_TYPE>;
101  using edge_t = EDGE_TYPE;
102  using edge_sequence_t = std::list<edge_t*>;
103 
104  /** Use deque to reduce memory reallocs. */
106  {
107  template <class KEY, class VALUE>
108  using map = mrpt::containers::map_as_vector<
109  KEY, VALUE, typename std::deque<std::pair<KEY, VALUE>>>;
110  };
111 
112  /** Map: TNode_ID => Node info */
113  using node_map_t = std::map<TNodeID, node_t>;
114  // typename map_traits_map_as_deque::template map<TNodeID, node_t>;
115 
116  /** A topological path up-tree.
117  *
118  * \note We use std::list since we need a container with `push_front()`.
119  */
120  using path_t = std::list<node_t>;
121 
123  const TNodeID parentId, const TNodeID newChildId,
124  const NODE_TYPE_DATA& newChildNodeData, const EDGE_TYPE& newEdgeData)
125  {
126  // edge:
127  auto& parentEdges = base_t::edges_to_children[parentId];
128  const bool dirChildToParent = false;
129 
130  parentEdges.emplace_back(newChildId, dirChildToParent, newEdgeData);
131 
132  const cost_t newCost = nodes_.at(parentId).cost_ + newEdgeData.cost;
133 
134  // node:
135  nodes_[newChildId] =
136  node_t(newChildId, parentId, newChildNodeData, newCost);
137  }
138 
140  const TNodeID parentId, const TNodeID childId,
141  const EDGE_TYPE& newEdgeData)
142  {
143  // edge:
144  auto& parentEdges = base_t::edges_to_children[parentId];
145  bool updateDone = false;
146  // const bool dirChildToParent = false;
147  for (auto& edge : parentEdges)
148  {
149  if (edge.id == childId)
150  {
151  edge.data = newEdgeData;
152  updateDone = true;
153  break;
154  }
155  }
156  if (!updateDone)
157  {
158  THROW_EXCEPTION_FMT(
159  "[update_node_and_edge] Error: Could not find edge from "
160  "parent #%s -> node #%s",
161  std::to_string(parentId).c_str(),
162  std::to_string(childId).c_str());
163  }
164 
165  const cost_t newCost = nodes_.at(parentId).cost_ + newEdgeData.cost;
166 
167  // node:
168  auto& node = nodes_.at(childId);
169  node.parentID_ = parentId;
170  node.cost_ = newCost;
171  }
172 
174  const TNodeID nodeId, const EDGE_TYPE& newEdgeFromParent)
175  {
176  auto& node = nodes_.at(nodeId);
177 
178  // Remove old edge:
179  const auto oldParentId = *node.parentID_;
180  auto& oldParentEdges = base_t::edges_to_children[oldParentId];
181  bool deletionDone = false;
182  for (auto it = oldParentEdges.begin(); it != oldParentEdges.end(); ++it)
183  {
184  if (it->id == nodeId)
185  {
186  oldParentEdges.erase(it);
187  deletionDone = true;
188  break;
189  }
190  }
191  if (!deletionDone)
192  {
193  THROW_EXCEPTION_FMT(
194  "[rewire_node_parent] Error: Could not find edge from former "
195  "parent #%s -> node #%s",
196  std::to_string(oldParentId).c_str(),
197  std::to_string(nodeId).c_str());
198  }
199 
200  // add edge:
201  const TNodeID parentId = newEdgeFromParent.parentId;
202  typename base_t::TListEdges& parentEdges =
203  base_t::edges_to_children[parentId];
204  const bool dirChildToParent = false;
205  parentEdges.emplace_back(nodeId, dirChildToParent, newEdgeFromParent);
206 
207  const cost_t newCost =
208  nodes_.at(parentId).cost_ + newEdgeFromParent.cost;
209 
210  // update existing node info:
211  ASSERT_LE_(newCost, node.cost_);
212 
213  node.parentID_ = parentId;
214  node.cost_ = newCost;
215  }
216 
217  const EDGE_TYPE& edge_to_parent(const TNodeID nodeId) const
218  {
219  auto& node = nodes_.at(nodeId);
220  const auto& parentEdges = base_t::edges_to_children.at(*node.parentID_);
221  for (const auto& edge : parentEdges)
222  {
223  if (edge.id == nodeId) return edge.data;
224  }
225  THROW_EXCEPTION_FMT(
226  "Could not find edge to parent for node #%s",
227  std::to_string(nodeId).c_str());
228  }
229 
230  /** Insert a node without edges (should be used only for a tree root node)
231  */
233  const TNodeID node_id, const NODE_TYPE_DATA& node_data)
234  {
235  ASSERTMSG_(
236  nodes_.empty(), "insert_root_node() called on a non-empty tree");
237  cost_t zeroCost = 0;
238  nodes_[node_id] = node_t(node_id, {}, node_data, zeroCost);
239  }
240 
241  TNodeID next_free_node_ID() const { return nodes_.size(); }
242 
243  /** read-only access to nodes.
244  * \sa insert_node_and_edge, insert_node
245  */
246  const node_map_t& nodes() const { return nodes_; }
247 
248  /** Write-access to node data (use with caution) */
249  NODE_TYPE_DATA& node_state(const TNodeID nodeId)
250  {
251  return nodes_.at(nodeId);
252  }
253 
254  /** Builds the path (sequence of nodes, with info about next edge) up-tree
255  * from a `target_node` towards the root
256  * - path_t nodes are ordered in the direction ROOT -> start_node
257  * - ed
258  */
259  std::tuple<path_t, edge_sequence_t> backtrack_path(
260  const TNodeID target_node) const
261  {
262  path_t outPath;
263  edge_sequence_t edgeList;
264 
265  auto it_src = nodes_.find(target_node);
266  if (it_src == nodes_.end())
267  throw std::runtime_error(
268  "backtrackPath: target_node not found in tree!");
269  const node_t* node = &it_src->second;
270  for (;;)
271  {
272  outPath.push_front(*node);
273 
274  auto next_node_id = node->parentID_;
275  if (!next_node_id.has_value())
276  {
277  // root reached: finished
278  break;
279  }
280  else
281  {
282  const EDGE_TYPE& edge = edge_to_parent(node->nodeID_);
283  edgeList.push_front(const_cast<EDGE_TYPE*>(&edge));
284 
285  auto it_next = nodes_.find(next_node_id.value());
286  if (it_next == nodes_.end())
287  throw std::runtime_error(
288  "backtrackPath: Node ID not found during tree "
289  "traversal!");
290  node = &it_next->second;
291  }
292  }
293  return {outPath, edgeList};
294  }
295 
296  private:
297  /** Info per node */
299 
300 }; // end TMoveTree
301 
302 /** Pose metric for SE(2) limited to a given PTG manifold. NOTE: This 'metric'
303  * is NOT symmetric for all PTGs: d(a,b)!=d(b,a) */
304 template <>
306 {
307  // Note: ptg is not const since we'll need to update its dynamic state
308  PoseDistanceMetric_TPS(ptg_t& ptg, const double headingTolerance)
309  : ptg_(ptg), headingTolerance_(headingTolerance)
310  {
311  }
312 
314  const SE2_KinState& a, const mrpt::math::TPose2D& b,
315  const distance_t d) const
316  {
317  if (std::abs(a.pose.x - b.x) > d) return true;
318  if (std::abs(a.pose.y - b.y) > d) return true;
319  if (std::abs(mrpt::math::angDistance(a.pose.phi, b.phi)) > d)
320  return true;
321  return false;
322  }
323  std::optional<std::tuple<distance_t, trajectory_index_t>> distance(
324  const SE2_KinState& src, const mrpt::math::TPose2D& dst,
325  bool ignoreDstHeading) const
326  {
327  normalized_distance_t normDist;
329  const auto relPose = dst - src.pose;
330  auto localSrcVel = src.vel;
331  localSrcVel.rotate(-src.pose.phi);
332 
333  ptg_t::TNavDynamicState dynState;
334  dynState.relTarget = relPose;
335  dynState.targetRelSpeed = 1.0; // TODO! (?)
336  dynState.curVelLocal = localSrcVel;
337 
338  ptg_.updateNavDynamicState(dynState);
339 
340  bool tp_point_is_exact =
341  ptg_.inverseMap_WS2TP(relPose.x, relPose.y, k, normDist);
342 
343  distance_t d = normDist * ptg_.getRefDistance();
344 
345  if (tp_point_is_exact)
346  {
347  uint32_t ptg_step;
348  ptg_.getPathStepForDist(k, d, ptg_step);
349  const auto reconsRelPose = ptg_.getPathPose(k, ptg_step);
350  const double headingError =
351  ignoreDstHeading ? .0
352  : std::abs(mrpt::math::angDistance(
353  reconsRelPose.phi, relPose.phi));
354 
355  if (headingError > headingTolerance_) tp_point_is_exact = false;
356  }
357 
358  if (tp_point_is_exact)
359  {
360  // de-normalize distance
361  if (d == 0 &&
362  (relPose.x != 0 || relPose.y != 0 || relPose.phi != 0))
363  {
364  // Due to the discrete nature of PTG paths, in rare cases
365  // we have d=0 despite the target is actually not exactly, but
366  // very close to the origin:
367  d = relPose.norm() +
368  std::abs(relPose.phi) * ptg_.getRefDistance();
369  }
370  return {{d, k}};
371  }
372  else
373  {
374  // not in range: we can't evaluate this distance!
375  return {};
376  }
377  }
378 
379  private:
381  const double headingTolerance_;
382 };
383 
384 /** Pose metric for SE(2) on the actual Lie group, i.e. NOT limited to a given
385  * PTG manifold, and ignoring velocities. */
386 template <>
388 {
389  // Note: ptg is not const since we'll need to update its dynamic state
390  PoseDistanceMetric_Lie(const double phiWeight) : phiWeight_(phiWeight) {}
391 
393  const mrpt::math::TPose2D& a, const mrpt::math::TPose2D& b,
394  const distance_t d) const
395  {
396  if (std::abs(a.x - b.x) > d) return true;
397  if (std::abs(a.y - b.y) > d) return true;
398  if (phiWeight_ * std::abs(mrpt::math::angDistance(a.phi, b.phi)) > d)
399  return true;
400  return false;
401  }
402 
404  const mrpt::math::TPose2D& src, const mrpt::math::TPose2D& dst) const
405  {
406  const auto relPose = dst - src;
407  return relPose.norm() + phiWeight_ * std::abs(relPose.phi);
408  }
409 
410  private:
411  double phiWeight_ = 0.1;
412 };
413 
414 /** tree data structure for planning in SE2 within TP-Space manifolds */
417 
418 /** @} */
419 } // namespace mpp
ptg_step_t step
Definition: MotionPrimitivesTree.h:50
cost_t cost_
Definition: MotionPrimitivesTree.h:86
Definition: MotionPrimitivesTree.h:73
std::list< node_t > path_t
Definition: MotionPrimitivesTree.h:120
Definition: MotionPrimitivesTree.h:33
distance_t distance(const mrpt::math::TPose2D &src, const mrpt::math::TPose2D &dst) const
Definition: MotionPrimitivesTree.h:403
double cost_t
Definition: basic_types.h:25
PoseDistanceMetric_TPS(ptg_t &ptg, const double headingTolerance)
Definition: MotionPrimitivesTree.h:308
Definition: MotionPrimitivesTree.h:105
TPS_point()=default
Definition: bestTrajectory.h:15
const EDGE_TYPE & edge_to_parent(const TNodeID nodeId) const
Definition: MotionPrimitivesTree.h:217
PoseDistanceMetric_Lie(const double phiWeight)
Definition: MotionPrimitivesTree.h:390
node_map_t nodes_
Definition: MotionPrimitivesTree.h:298
void rewire_node_parent(const TNodeID nodeId, const EDGE_TYPE &newEdgeFromParent)
Definition: MotionPrimitivesTree.h:173
void update_node_and_edge(const TNodeID parentId, const TNodeID childId, const EDGE_TYPE &newEdgeData)
Definition: MotionPrimitivesTree.h:139
Definition: SE2_KinState.h:65
NODE_TYPE_DATA & node_state(const TNodeID nodeId)
Definition: MotionPrimitivesTree.h:249
Definition: MotionPrimitivesTree.h:36
bool cannotBeNearerThan(const SE2_KinState &a, const mrpt::math::TPose2D &b, const distance_t d) const
Definition: MotionPrimitivesTree.h:313
std::optional< TNodeID > parentID_
Definition: MotionPrimitivesTree.h:83
double distance_t
Definition: basic_types.h:16
bool cannotBeNearerThan(const mrpt::math::TPose2D &a, const mrpt::math::TPose2D &b, const distance_t d) const
Definition: MotionPrimitivesTree.h:392
mrpt::graphs::CDirectedTree< MoveEdgeSE2_TPS > base_t
Definition: MotionPrimitivesTree.h:100
mrpt::containers::map_as_vector< KEY, VALUE, typename std::deque< std::pair< KEY, VALUE > >> map
Definition: MotionPrimitivesTree.h:109
std::list< edge_t *> edge_sequence_t
Definition: MotionPrimitivesTree.h:102
std::optional< std::tuple< distance_t, trajectory_index_t > > distance(const SE2_KinState &src, const mrpt::math::TPose2D &dst, bool ignoreDstHeading) const
Definition: MotionPrimitivesTree.h:323
mrpt::math::TPose2D pose
global pose (x,y,phi)
Definition: SE2_KinState.h:69
Definition: MotionPrimitivesTree.h:38
std::tuple< path_t, edge_sequence_t > backtrack_path(const TNodeID target_node) const
Definition: MotionPrimitivesTree.h:259
node_t(TNodeID nodeID, const std::optional< TNodeID > &parentID, const NODE_TYPE_DATA &data, cost_t cost)
Definition: MotionPrimitivesTree.h:89
Definition: MoveEdgeSE2_TPS.h:19
double normalized_speed_t
Definition: basic_types.h:33
const double headingTolerance_
Definition: MotionPrimitivesTree.h:381
Definition: MotionPrimitivesTree.h:76
trajectory_index_t k
Definition: MotionPrimitivesTree.h:49
int trajectory_index_t
Definition: basic_types.h:28
std::map< TNodeID, node_t > node_map_t
Definition: MotionPrimitivesTree.h:113
bool operator<(const TPS_point &a, const TPS_point &b)
Definition: MotionPrimitivesTree.h:53
double normalized_distance_t
Definition: basic_types.h:19
const node_map_t & nodes() const
Definition: MotionPrimitivesTree.h:246
TPS_point(trajectory_index_t _k, ptg_step_t _step, normalized_speed_t _speed)
Definition: MotionPrimitivesTree.h:43
void insert_root_node(const TNodeID node_id, const NODE_TYPE_DATA &node_data)
Definition: MotionPrimitivesTree.h:232
mrpt::nav::CParameterizedTrajectoryGenerator ptg_t
Definition: ptg_t.h:13
TNodeID next_free_node_ID() const
Definition: MotionPrimitivesTree.h:241
normalized_speed_t speed
Definition: MotionPrimitivesTree.h:51
ptg_t & ptg_
Definition: MotionPrimitivesTree.h:380
void insert_node_and_edge(const TNodeID parentId, const TNodeID newChildId, const NODE_TYPE_DATA &newChildNodeData, const EDGE_TYPE &newEdgeData)
Definition: MotionPrimitivesTree.h:122
mrpt::math::TTwist2D vel
global velocity (vx,vy,omega)
Definition: SE2_KinState.h:70
~TPS_point()=default
uint32_t ptg_step_t
Definition: basic_types.h:22