ReverseQueue.cpp
1 /*********************************************************************
2  * Software License Agreement (BSD License)
3  *
4  * Copyright (c) 2019, University of Oxford
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * * Redistributions of source code must retain the above copyright
12  * notice, this list of conditions and the following disclaimer.
13  * * Redistributions in binary form must reproduce the above
14  * copyright notice, this list of conditions and the following
15  * disclaimer in the documentation and/or other materials provided
16  * with the distribution.
17  * * Neither the name of the University of Toronto nor the names of its
18  * contributors may be used to endorse or promote products derived
19  * from this software without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32  * POSSIBILITY OF SUCH DAMAGE.
33  *********************************************************************/
34 
35 // Authors: Marlin Strub
36 #include "ompl/geometric/planners/informedtrees/eitstar/ReverseQueue.h"
37 
38 #include <utility>
39 
40 #include "ompl/geometric/planners/informedtrees/eitstar/Direction.h"
41 #include "ompl/geometric/planners/informedtrees/eitstar/State.h"
42 
43 namespace ompl
44 {
45  namespace geometric
46  {
47  namespace eitstar
48  {
49  ReverseQueue::ReverseQueue(const std::shared_ptr<const ompl::base::OptimizationObjective> &objective,
50  const std::shared_ptr<const ompl::base::StateSpace> &space,
51  const bool isQueueCostOrdered)
52  : isQueueCostOrdered_(isQueueCostOrdered)
53  , objective_(objective)
54  , space_(space)
55  , queue_(isQueueCostOrdered_ ? getCostComparisonOperator() : getEffortComparisonOperator())
56  {
57  }
58 
59  bool ReverseQueue::empty() const
60  {
61  return size() == 0u;
62  }
63 
64  std::size_t ReverseQueue::size() const
65  {
66  return queue_.size();
67  }
68 
69  void ReverseQueue::insertOrUpdate(const Edge &edge)
70  {
71  if (!updateIfExists(edge))
72  {
73  // Compute the keys.
74  const auto key1 = computeAdmissibleSolutionCost(edge);
75  const auto key2 = computeAdmissibleCostToComeToTarget(edge);
76  const auto key3 = computeAdmissibleSolutionEffort(edge);
77  const auto key4 = computeInadmissibleSolutionEffort(edge);
78 
79  // Create the heap element.
80  const auto element = std::make_tuple(key1, key2, key3, key4, edge);
81 
82  // Insert the edge with the key in the queue.
83  const auto elementPointer = queue_.insert(element);
84 
85  // Remember the element.
86  edge.source->asReverseVertex()->outgoingReverseQueueLookup_.emplace_back(elementPointer);
87  }
88  }
89 
90  void ReverseQueue::insertOrUpdate(const std::vector<Edge> &edges)
91  {
92  // Let's do this naively.
93  for (const auto &edge : edges)
94  {
95  insertOrUpdate(edge);
96  }
97  }
98 
99  void ReverseQueue::setCostQueueOrder(const bool isQueueCostOrdered)
100  {
101  isQueueCostOrdered_ = isQueueCostOrdered;
102 
103  if (!empty())
104  {
105  throw std::runtime_error("Can't update ordering of queue if there are elements in it.");
106  }
107  if (isQueueCostOrdered_)
108  {
109  queue_.getComparisonOperator() = getCostComparisonOperator();
110  }
111  else
112  {
113  queue_.getComparisonOperator() = getEffortComparisonOperator();
114  }
115  }
116 
117  const Edge &ReverseQueue::peek() const
118  {
119  if (auto element = queue_.top())
120  {
121  return std::get<4>(element->data);
122  }
123  else
124  {
125  throw std::out_of_range("There are no elements to peek in the reverse queue.");
126  }
127  }
128 
129  unsigned int ReverseQueue::peekEffort() const
130  {
131  if (auto element = queue_.top())
132  {
133  return std::get<2>(element->data);
134  }
135  else
136  {
137  throw std::out_of_range("There are no elements to peek in the reverse queue.");
138  }
139  }
140 
141  bool ReverseQueue::updateIfExists(const Edge &edge)
142  {
143  // Check if the edge is in the queue via the reverse queue pointers.
144  const auto &lookup = edge.source->asReverseVertex()->outgoingReverseQueueLookup_;
145  const auto it = std::find_if(lookup.cbegin(), lookup.cend(), [&edge](const auto &p) {
146  return std::get<4>(p->data).target->getId() == edge.target->getId();
147  });
148 
149  // Indicate that the edge is not in the queue by returning false.
150  if (it == lookup.cend())
151  {
152  return false;
153  }
154 
155  // Update the cost and effort and the position of the edge in the queue.
156  std::get<0>((*it)->data) = computeAdmissibleSolutionCost(edge);
157  std::get<1>((*it)->data) = computeAdmissibleCostToComeToTarget(edge);
158  std::get<2>((*it)->data) = computeAdmissibleSolutionEffort(edge);
159  std::get<3>((*it)->data) = computeInadmissibleSolutionEffort(edge);
160  queue_.update(*it);
161 
162  // Indicate that the edge was updated by returning true.
163  return true;
164  }
165 
167  {
168  return objective_->combineCosts(
169  objective_->combineCosts(edge.source->getAdmissibleCostToGo(),
170  objective_->motionCostHeuristic(edge.target->raw(), edge.source->raw())),
171  edge.target->getLowerBoundCostToCome());
172  }
173 
174  ompl::base::Cost ReverseQueue::computeAdmissibleCostToComeToTarget(const Edge &edge) const
175  {
176  return objective_->combineCosts(
177  edge.source->getAdmissibleCostToGo(),
178  objective_->motionCostHeuristic(edge.target->raw(), edge.source->raw()));
179  }
180 
181  unsigned int ReverseQueue::computeAdmissibleSolutionEffort(const Edge &edge) const
182  {
183  std::size_t edgeEffort = 0u;
184  if (edge.source->isWhitelisted(edge.target))
185  {
186  edgeEffort = 0u;
187  }
188  else
189  {
190  const std::size_t fullSegmentCount =
191  space_->validSegmentCount(edge.source->raw(), edge.target->raw());
192 
193  // Get the number of checks already performed on this edge.
194  const std::size_t performedChecks = edge.target->getIncomingCollisionCheckResolution(edge.source);
195 
196  // Sanity check for the number of performed collision checks.
197  assert(fullSegmentCount >= performedChecks);
198 
199  // The estimated effort is the remaining number of collision checks.
200  edgeEffort = fullSegmentCount - performedChecks;
201  }
202 
203  const unsigned int totalEffort =
204  edge.source->getEstimatedEffortToGo() + edgeEffort + edge.target->getLowerBoundEffortToCome();
205 
206  if (std::numeric_limits<unsigned int>::max() - edgeEffort - edge.source->getEstimatedEffortToGo() <
207  edge.target->getLowerBoundEffortToCome())
208  {
209  return std::numeric_limits<unsigned int>::max();
210  }
211 
212  return totalEffort;
213  }
214 
215  std::function<bool(const ReverseQueue::HeapElement &, const ReverseQueue::HeapElement &)>
216  ReverseQueue::getCostComparisonOperator() const
217  {
218  return [&objective = objective_](const HeapElement &lhs, const HeapElement &rhs) {
219  if (objective->isCostEquivalentTo(std::get<0>(lhs), std::get<0>(rhs)))
220  {
221  if (objective->isCostEquivalentTo(std::get<1>(lhs), std::get<1>(rhs)))
222  {
223  return std::get<2>(lhs) < std::get<2>(rhs);
224  }
225  else
226  {
227  return objective->isCostBetterThan(std::get<1>(lhs), std::get<1>(rhs));
228  }
229  }
230  else
231  {
232  return objective->isCostBetterThan(std::get<0>(lhs), std::get<0>(rhs));
233  }
234  };
235  }
236 
237  std::function<bool(const ReverseQueue::HeapElement &, const ReverseQueue::HeapElement &)>
238  ReverseQueue::getEffortComparisonOperator() const
239  {
240  return [&objective = objective_](const HeapElement &lhs, const HeapElement &rhs) {
241  if (std::get<2>(lhs) == std::get<2>(rhs))
242  {
243  if (std::get<3>(lhs) == std::get<3>(rhs))
244  {
245  if (objective->isCostEquivalentTo(std::get<0>(lhs), std::get<0>(rhs)))
246  {
247  return objective->isCostBetterThan(std::get<1>(lhs), std::get<1>(rhs));
248  }
249  else
250  {
251  return objective->isCostBetterThan(std::get<0>(lhs), std::get<0>(rhs));
252  }
253  }
254  else
255  {
256  return std::get<3>(lhs) < std::get<3>(rhs);
257  }
258  }
259  else
260  {
261  return std::get<2>(lhs) < std::get<2>(rhs);
262  }
263  };
264  }
265 
266  unsigned int ReverseQueue::computeInadmissibleSolutionEffort(const Edge &edge) const
267  {
268  std::size_t edgeEffort = 0u;
269  if (edge.source->isWhitelisted(edge.target))
270  {
271  edgeEffort = 0u;
272  }
273  else
274  {
275  const std::size_t fullSegmentCount =
276  space_->validSegmentCount(edge.source->raw(), edge.target->raw());
277 
278  // Get the number of checks already performed on this edge.
279  const std::size_t performedChecks = edge.target->getIncomingCollisionCheckResolution(edge.source);
280 
281  // Sanity check for the number of performed collision checks.
282  assert(fullSegmentCount >= performedChecks);
283 
284  // The estimated effort is the remaining number of collision checks.
285  edgeEffort = fullSegmentCount - performedChecks;
286  }
287 
288  if (std::numeric_limits<unsigned int>::max() - edgeEffort - edge.source->getEstimatedEffortToGo() <
289  edge.target->getInadmissibleEffortToCome())
290  {
291  return std::numeric_limits<unsigned int>::max();
292  }
293 
294  // return total effort
295  return edge.source->getEstimatedEffortToGo() + edgeEffort + edge.target->getInadmissibleEffortToCome();
296  }
297 
298  Edge ReverseQueue::pop()
299  {
300  assert(!queue_.empty());
301 
302  // Get the top element, i.e., a pair that holds the key and the edge.
303  const auto element = queue_.top();
304 
305  // Copy the data of the top edge.
306  auto edge = std::get<4>(element->data);
307 
308  // If the source state of the edge does not have an associated vertex, it's a bug.
309  assert(edge.source->hasReverseVertex());
310 
311  // Pop the element from the queue.
312  queue_.pop();
313 
314  // Get a reference to the outgoing edge queue lookup of the parent vertex.
315  auto &lookup = edge.source->asReverseVertex()->outgoingReverseQueueLookup_;
316 
317  // Remove the edge from the lookup.
318  auto it = std::find(lookup.begin(), lookup.end(), element);
319 
320  // If the edge is not in the lookup, it's a bug.
321  assert(it != lookup.end());
322 
323  // Swappedy pop.
324  std::iter_swap(it, lookup.rbegin());
325  lookup.pop_back();
326 
327  // Return the element.
328  return edge;
329  }
330 
332  {
333  return std::get<0>(queue_.top()->data);
334  }
335 
336  void ReverseQueue::clear()
337  {
338  // We need to ensure the reverse queue lookup is cleared for all edges in the queue.
339  std::vector<HeapElement> contents;
340  queue_.getContent(contents);
341  for (auto element : contents)
342  {
343  std::get<4>(element).source->asReverseVertex()->outgoingReverseQueueLookup_.clear();
344  }
345  queue_.clear();
346  }
347 
348  std::vector<Edge> ReverseQueue::getEdges() const
349  {
350  std::vector<HeapElement> contents;
351  queue_.getContent(contents);
352  std::vector<Edge> edges;
353  edges.reserve(contents.size());
354  for (const auto &element : contents)
355  {
356  edges.push_back(std::get<4>(element));
357  }
358  return edges;
359  }
360 
361  void ReverseQueue::rebuild()
362  {
363  const auto edges = getEdges();
364  clear();
365  insertOrUpdate(edges);
366  }
367 
368  void ReverseQueue::removeOutgoingEdges(const std::shared_ptr<Vertex> &vertex)
369  {
370  // Remove all elements from the queue.
371  for (const auto element : vertex->outgoingReverseQueueLookup_)
372  {
373  queue_.remove(element);
374  }
375 
376  // Remove all elements from the lookup.
377  vertex->outgoingReverseQueueLookup_.clear();
378  }
379 
380  } // namespace eitstar
381 
382  } // namespace geometric
383 
384 } // namespace ompl
std::shared_ptr< State > source
The parent state of this edge.
Definition: Edge.h:165
ompl::base::Cost getLowerBoundOnOptimalSolutionCost() const
Returns a lower bound on the resolution-optimal solution cost.
std::vector< Edge > getEdges() const
Copies all edges into a vector and returns the vector.
void clear()
Clears the queue, i.e., deletes all elements from it.
LessThan & getComparisonOperator()
Return a reference to the comparison operator.
Definition: BinaryHeap.h:298
Element * top() const
Return the top element. nullptr for an empty heap.
Definition: BinaryHeap.h:184
void pop()
Remove the top element.
Definition: BinaryHeap.h:190
void setCostQueueOrder(const bool isQueueCostOrdered)
Updates the queue ordering depending on the given suboptimality factor.
Edge pop()
Returns and deletes the top element of the queue.
A struct for basic edge data.
Definition: Edge.h:152
Definition of a cost value. Can represent the cost of a motion or the cost of a state.
Definition: Cost.h:111
void clear()
Clear the heap.
Definition: BinaryHeap.h:176
bool empty() const
Check if the heap is empty.
Definition: BinaryHeap.h:259
unsigned int peekEffort() const
Get the effort corresponding to the top edge of the queue.
void getContent(std::vector< _T > &content) const
Get the data stored in this heap.
Definition: BinaryHeap.h:271
void removeOutgoingEdges(const std::shared_ptr< Vertex > &vertex)
Removes the outgoing edges of a vertex from the queue.
ompl::base::Cost computeAdmissibleSolutionCost(const Edge &edge) const
Returns the admissible total potential solution cost of an edge.
void insertOrUpdate(const Edge &edge)
Inserts or updates an element in the queue.
Element * insert(const _T &data)
Add a new element.
Definition: BinaryHeap.h:204
void remove(Element *element)
Remove a specific element.
Definition: BinaryHeap.h:196
std::shared_ptr< State > target
The child state of this edge.
Definition: Edge.h:168
const Edge & peek() const
Get a reference to the top edge in the queue.
unsigned int computeAdmissibleSolutionEffort(const Edge &edge) const
Returns the admissible total potential effort of an edge.
std::size_t size() const
Returns the number of elements in the queue.
void update(Element *element)
Update an element in the heap.
Definition: BinaryHeap.h:250
void rebuild()
Rebuilds the queue.
bool empty() const
Returns whether the queue is empty.
unsigned int size() const
Get the number of elements in the heap.
Definition: BinaryHeap.h:265
Main namespace. Contains everything in this library.
Definition: AppBase.h:21
ReverseQueue(const std::shared_ptr< const ompl::base::OptimizationObjective > &objective, const std::shared_ptr< const ompl::base::StateSpace > &space, const bool isQueueCostOrdered)
Constructs the queue with the given optimization objective and state space.