EITstar.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 
37 #include "ompl/geometric/planners/informedtrees/EITstar.h"
38 
39 #include <algorithm>
40 #include <memory>
41 
42 #include "ompl/base/objectives/PathLengthOptimizationObjective.h"
43 #include "ompl/geometric/PathGeometric.h"
44 
45 using namespace std::string_literals;
46 using namespace ompl::geometric::eitstar;
47 
48 namespace ompl
49 {
50  namespace geometric
51  {
52  EITstar::EITstar(const std::shared_ptr<ompl::base::SpaceInformation> &spaceInfo)
53  : ompl::base::Planner(spaceInfo, "EIT*")
54  , graph_(spaceInfo, solutionCost_)
55  , detectionState_(spaceInfo->allocState())
56  , space_(spaceInfo->getStateSpace())
57  , motionValidator_(spaceInfo->getMotionValidator())
58  , solutionCost_()
59  {
60  // Specify EIT*'s planner specs.
62  specs_.multithreaded = false;
64  specs_.optimizingPaths = true;
65  specs_.directed = true;
68 
69  // Register the setting callbacks.
70  declareParam<bool>("use_k_nearest", this, &EITstar::setUseKNearest, &EITstar::getUseKNearest, "0,1");
71  declareParam<double>("rewire_factor", this, &EITstar::setRadiusFactor, &EITstar::getRadiusFactor,
72  "1.0:0.01:3.0");
73  declareParam<std::size_t>("batch_size", this, &EITstar::setBatchSize, &EITstar::getBatchSize, "1:1:10000");
74  declareParam<bool>("use_graph_pruning", this, &EITstar::enablePruning, &EITstar::isPruningEnabled, "0,1");
75  declareParam<bool>("find_approximate_solutions", this, &EITstar::trackApproximateSolutions,
77  declareParam<unsigned int>("set_max_num_goals", this, &EITstar::setMaxNumberOfGoals,
78  &EITstar::getMaxNumberOfGoals, "1:1:1000");
79 
80  // Register the progress properties.
81  addPlannerProgressProperty("iterations INTEGER", [this]() { return std::to_string(iteration_); });
82  addPlannerProgressProperty("best cost DOUBLE", [this]() { return std::to_string(solutionCost_.value()); });
83  addPlannerProgressProperty("state collision checks INTEGER",
84  [this]() { return std::to_string(graph_.getNumberOfSampledStates()); });
85  addPlannerProgressProperty("edge collision checks INTEGER",
86  [this]() { return std::to_string(numCollisionCheckedEdges_); });
87  addPlannerProgressProperty("nearest neighbour calls INTEGER",
88  [this]() { return std::to_string(graph_.getNumberOfNearestNeighborCalls()); });
89  }
90 
92  {
93  spaceInfo_->freeState(detectionState_);
94  }
95 
97  {
98  // Call the base class setup.
99  Planner::setup();
100 
101  // Check that the problem definition is set.
102  if (static_cast<bool>(problem_))
103  {
104  // Default to path length optimization if no objective has been specified.
105  if (!problem_->hasOptimizationObjective())
106  {
107  OMPL_WARN("%s: No optimization objective has been specified. The default is optimizing path "
108  "length.",
109  name_.c_str());
110  problem_->setOptimizationObjective(
111  std::make_shared<ompl::base::PathLengthOptimizationObjective>(spaceInfo_));
112  }
113 
114  if (static_cast<bool>(problem_->getGoal()))
115  {
116  // If we were given a goal, make sure its of appropriate type.
117  if (!(problem_->getGoal()->hasType(ompl::base::GOAL_SAMPLEABLE_REGION)))
118  {
119  OMPL_ERROR("EIT* is currently only implemented for goals that can be cast to "
120  "ompl::base::GOAL_SAMPLEABLE_REGION.");
121  setup_ = false;
122  return;
123  }
124  }
125 
126  // Pull through the optimization objective for direct access.
127  objective_ = problem_->getOptimizationObjective();
128 
129  // Initialize costs to infinity.
130  solutionCost_ = objective_->infiniteCost();
131  reverseCost_ = objective_->infiniteCost();
132  approximateSolutionCost_ = objective_->infiniteCost();
133  approximateSolutionCostToGoal_ = objective_->infiniteCost();
134 
135  // Instantiate the queues.
136  forwardQueue_ = std::make_unique<eitstar::ForwardQueue>(objective_, space_);
137  if (isMultiqueryEnabled_)
138  {
139  reverseQueue_ = std::make_unique<eitstar::ReverseQueue>(objective_, space_,
140  std::isfinite(suboptimalityFactor_));
141  }
142  else
143  {
144  reverseQueue_ = std::make_unique<eitstar::ReverseQueue>(objective_, space_, true);
145  }
146 
147  // Setup the graph with the problem information.
148  graph_.setup(problem_, &pis_);
149 
150  // Create the start vertices.
151  for (const auto &start : graph_.getStartStates())
152  {
153  startVertices_.emplace_back(start->asForwardVertex());
154  }
155 
156  // Create the goal vertices.
157  for (const auto &goal : graph_.getGoalStates())
158  {
159  goalVertices_.emplace_back(goal->asReverseVertex());
160  }
161 
162  // Populate the queues.
163  expandGoalVerticesIntoReverseQueue();
164  expandStartVerticesIntoForwardQueue();
165  }
166  else
167  {
168  setup_ = false;
169  OMPL_WARN("%s: Unable to setup without a problem definition.", name_.c_str());
170  }
171  }
172 
174  {
175  // Check that the planner and state space are setup.
176  auto status = ensureSetup();
177 
178  // Return early if the planner or state space are not setup.
179  if (status == ompl::base::PlannerStatus::StatusType::ABORT)
180  {
181  return status;
182  }
183 
184  // Ensure that the problem has start and goal states before solving.
185  status = ensureStartAndGoalStates(terminationCondition);
186 
187  // Return early if no problem can be solved.
188  if (status == ompl::base::PlannerStatus::StatusType::INVALID_START ||
189  status == ompl::base::PlannerStatus::StatusType::INVALID_GOAL)
190  {
191  return status;
192  }
193 
194  // Let the world know what we're doing.
195  OMPL_INFORM("%s: Solving the given planning problem. The current best solution cost is %.4f", name_.c_str(),
196  solutionCost_.value());
197 
198  // Iterate while we should continue solving the problem.
199  while (continueSolving(terminationCondition))
200  {
201  iterate(terminationCondition);
202  }
203 
204  // Someone might call ProblemDefinition::clearSolutionPaths() between invocations of Planner::sovle(), in
205  // which case previously found solutions are not registered with the problem definition anymore.
206  status = updateSolution();
207 
208  // Let the caller know the status.
209  informAboutPlannerStatus(status);
210  return status;
211  }
212 
214  {
215  if (forwardQueue_)
216  {
217  forwardQueue_->clear();
218  }
219 
220  if (reverseQueue_)
221  {
222  reverseQueue_->clear();
223  }
224 
225  startVertices_.clear();
226  goalVertices_.clear();
227  objective_.reset();
228  graph_.clear();
229 
230  // Reset the solution costs. Cannot use infiniteCost() before resetting the objective because the objective
231  // of a new problem definition objective might define that differently than the old.
232  solutionCost_ = ompl::base::Cost(std::numeric_limits<double>::signaling_NaN());
233  reverseCost_ = ompl::base::Cost(std::numeric_limits<double>::signaling_NaN());
234  approximateSolutionCost_ = ompl::base::Cost(std::numeric_limits<double>::signaling_NaN());
235  approximateSolutionCostToGoal_ = ompl::base::Cost(std::numeric_limits<double>::signaling_NaN());
236 
237  // Reset the tags.
238  reverseSearchTag_ = 1u;
239  startExpansionGraphTag_ = 0u;
240  numSparseCollisionChecksCurrentLevel_ = initialNumSparseCollisionChecks_;
241  numSparseCollisionChecksPreviousLevel_ = 0u;
242  suboptimalityFactor_ = std::numeric_limits<double>::infinity();
243 
244  Planner::clear();
245  setup_ = false;
246  }
247 
249  {
250  if (setup_)
251  {
252  forwardQueue_->clear();
253  reverseQueue_->clear();
254  startVertices_.clear();
255  goalVertices_.clear();
256  graph_.clearQuery();
257  solutionCost_ = objective_->infiniteCost();
258  reverseCost_ = objective_->infiniteCost();
259  approximateSolutionCost_ = objective_->infiniteCost();
260  approximateSolutionCostToGoal_ = objective_->infiniteCost();
261 
262  suboptimalityFactor_ = std::numeric_limits<double>::infinity();
263  restartReverseSearch();
264 
265  numSparseCollisionChecksCurrentLevel_ = initialNumSparseCollisionChecks_;
266 
267  numProcessedEdges_ = 0u;
268 
269  reverseSearchTag_++;
270 
271  if (!isMultiqueryEnabled_)
272  {
273  clear();
274  }
275  setup_ = false;
276  }
277  }
278 
280  {
281  return solutionCost_;
282  }
283 
284  void EITstar::setBatchSize(unsigned int numSamples)
285  {
286  batchSize_ = numSamples;
287  }
288 
289  unsigned int EITstar::getBatchSize() const
290  {
291  return batchSize_;
292  }
293 
295  {
296  initialNumSparseCollisionChecks_ = numChecks;
297  numSparseCollisionChecksCurrentLevel_ = numChecks;
298  numSparseCollisionChecksPreviousLevel_ = 0u;
299  }
300 
301  void EITstar::setRadiusFactor(double factor)
302  {
303  graph_.setRadiusFactor(factor);
304  }
305 
307  {
308  return graph_.getRadiusFactor();
309  }
310 
312  {
313  suboptimalityFactor_ = factor;
314  }
315 
316  void EITstar::enableMultiquery(bool multiquery)
317  {
318  isMultiqueryEnabled_ = multiquery;
319  graph_.enableMultiquery(multiquery);
320  };
321 
323  {
324  return isMultiqueryEnabled_;
325  };
326 
327  void EITstar::setStartGoalPruningThreshold(unsigned int threshold)
328  {
329  graph_.setEffortThreshold(threshold);
330  }
331 
333  {
334  return graph_.getEffortThreshold();
335  }
336 
337  void EITstar::enablePruning(bool enable)
338  {
339  graph_.enablePruning(enable);
340  }
341 
343  {
344  return graph_.isPruningEnabled();
345  }
346 
348  {
349  trackApproximateSolutions_ = track;
350  }
351 
353  {
354  return trackApproximateSolutions_;
355  }
356 
357  void EITstar::setUseKNearest(bool useKNearest)
358  {
359  graph_.setUseKNearest(useKNearest);
360  }
361 
363  {
364  return graph_.getUseKNearest();
365  }
366 
367  void EITstar::setMaxNumberOfGoals(unsigned int numberOfGoals)
368  {
369  graph_.setMaxNumberOfGoals(numberOfGoals);
370  }
371 
372  unsigned int EITstar::getMaxNumberOfGoals() const
373  {
374  return graph_.getMaxNumberOfGoals();
375  }
376 
378  {
379  assert(forwardQueue_);
380  return forwardQueue_->empty();
381  }
382 
384  {
385  assert(reverseQueue_);
386  return reverseQueue_->empty();
387  }
388 
389  std::vector<Edge> EITstar::getForwardQueue() const
390  {
391  return forwardQueue_->getEdges();
392  }
393 
394  std::vector<Edge> EITstar::getReverseQueue() const
395  {
396  return reverseQueue_->getEdges();
397  }
398 
399  std::vector<Edge> EITstar::getReverseTree() const
400  {
401  // Prepare the return value.
402  std::vector<Edge> edges;
403 
404  // Define a helper that recursively gets all reverse edges of a vertex.
405  const std::function<void(const std::shared_ptr<Vertex> &)> getEdgesRecursively =
406  [&edges, &getEdgesRecursively](const std::shared_ptr<Vertex> &vertex)
407  {
408  for (const auto &child : vertex->getChildren())
409  {
410  getEdgesRecursively(child);
411  }
412  // Catch the root case of the recursion.
413  if (auto parent = vertex->getParent().lock())
414  {
415  edges.emplace_back(parent->getState(), vertex->getState());
416  }
417  };
418 
419  // Get the edges of all reverse roots recursively.
420  for (const auto &root : goalVertices_)
421  {
422  getEdgesRecursively(root);
423  }
424 
425  // Return all edges in the reverse tree.
426  return edges;
427  }
428 
430  {
431  assert(forwardQueue_);
432  if (forwardQueue_->empty())
433  {
434  return {};
435  }
436  return forwardQueue_->peek(suboptimalityFactor_);
437  }
438 
440  {
441  assert(reverseQueue_);
442  if (reverseQueue_->empty())
443  {
444  return {};
445  }
446  return reverseQueue_->peek();
447  }
448 
450  {
451  // Get the base class data.
452  Planner::getPlannerData(data);
453 
454  // Add the samples and their outgoing edges.
455  for (const auto &state : graph_.getStates())
456  {
457  // Add the state as a vertex.
458  data.addVertex(base::PlannerDataVertex(state->raw(), state->getId()));
459 
460  // If the sample is in the forward tree, add the outgoing edges.
461  for (const auto &state2 : graph_.getStates())
462  {
463  if (state->isWhitelisted(state2))
464  {
465  data.addEdge(base::PlannerDataVertex(state->raw(), state->getId()),
466  base::PlannerDataVertex(state2->raw(), state2->getId()));
467  }
468  }
469  }
470  }
471 
472  void EITstar::iterate(const ompl::base::PlannerTerminationCondition &terminationCondition)
473  {
474  // If we are in a multiquery setting, we do not want to search the approximation
475  // only consisting of start/goals, since this completely ignores the computational effort we have already
476  // invested Thus, the first thing we do in this instance is adding the first batch of samples.
477  if (isMultiqueryEnabled_ &&
478  graph_.getStates().size() == graph_.getStartStates().size() + graph_.getGoalStates().size())
479  {
480  improveApproximation(terminationCondition);
481  ++iteration_;
482  return;
483  }
484 
485  // First check if the reverse search needs to be continued.
486  if (continueReverseSearch())
487  {
488  iterateReverseSearch();
489  } // If the reverse search is suspended, check if the forward search needs to be continued.
490  else if (continueForwardSearch())
491  {
492  iterateForwardSearch();
493  } // If neither the reverse nor the forward search needs to be continued, improve the approximation.
494  else
495  {
496  improveApproximation(terminationCondition);
497  }
498 
499  // Increment the iteration count.
500  ++iteration_;
501  }
502 
503  void EITstar::iterateForwardSearch()
504  {
505  // Ensure the forward queue is not empty.
506  assert(!forwardQueue_->empty());
507 
508  // Get the top edge from the queue.
509  auto edge = forwardQueue_->pop(suboptimalityFactor_);
510  ++numProcessedEdges_;
511 
512  // Assert the validity of the edge.
513  assert(edge.source->hasForwardVertex());
514  assert(!std::isfinite(suboptimalityFactor_) || isClosed(edge.target->asReverseVertex()));
515 
516  // The edge is a freeby if its parent is already the parent of the child.
517  if (isInForwardTree(edge))
518  {
519  // We expand the target into the queue unless the target is a goal.
520  forwardQueue_->insertOrUpdate(expandUnlessGoal(edge.target));
521  return;
522  }
523 
524  // If the edge can not improve the forward tree, then we're done with it.
525  if (!couldImproveForwardTree(edge))
526  {
527  return;
528  }
529 
530  // The edge could possibly improve the tree, check if it is valid.
531  if (isValid(edge))
532  {
533  // Compute the true edge cost and the target cost through this edge.
534  const auto edgeCost = objective_->motionCost(edge.source->raw(), edge.target->raw());
535  const auto targetCost = combine(edge.source->getCurrentCostToCome(), edgeCost);
536 
537  // Check if the edge can actually improve the forward path and tree.
538  if (isBetter(targetCost, edge.target->getCurrentCostToCome()) &&
539  isBetter(combine(targetCost, edge.target->getAdmissibleCostToGo()), solutionCost_))
540  {
541  // Convenience access to parent and child vertices.
542  auto source = edge.source->asForwardVertex();
543  auto target = edge.target->asForwardVertex();
544 
545  // Update the parent of the child in the forward tree.
546  target->updateParent(source);
547 
548  // Add the child to the parents children.
549  source->addChild(target);
550 
551  // Set the edge cost associated with this parent.
552  target->setEdgeCost(edgeCost);
553 
554  // Update the cost-to-come.
555  edge.target->setCurrentCostToCome(targetCost);
556 
557  // Update the cost of the children.
558  const auto changedVertices = target->updateCurrentCostOfChildren(objective_);
559 
560  // Reflect changes in queue and solution cost.
561  for (const auto &vertex : changedVertices)
562  {
563  // Update any edge in the queue.
564  forwardQueue_->updateIfExists({vertex->getParent().lock()->getState(), vertex->getState()});
565 
566  // Update the solution if the vertex is a goal.
567  if (graph_.isGoal(vertex->getState()))
568  {
569  updateExactSolution(vertex->getState());
570  }
571  }
572 
573  // Expand the outgoing edges into the queue unless this state is the goal state.
574  if (!graph_.isGoal(edge.target))
575  {
576  auto edges = expand(edge.target);
577  edges.erase(std::remove_if(edges.begin(), edges.end(),
578  [&edge](const auto &e)
579  { return e.target->getId() == edge.source->getId(); }),
580  edges.end());
581  forwardQueue_->insertOrUpdate(edges);
582  }
583  else // It is the goal state, update the solution.
584  {
585  updateExactSolution(edge.target);
586  }
587  }
588  }
589  else
590  {
591  // Check if the edge is used in the reverse tree.
592  const bool inReverseTree = edge.source->asReverseVertex()->isParent(edge.target->asReverseVertex()) ||
593  edge.target->asReverseVertex()->isParent(edge.source->asReverseVertex());
594 
595  // If this edge was in the reverse search tree, it could be updated.
596  if (inReverseTree)
597  {
598  // Remember the current number of collision checks and increase it.
599  numSparseCollisionChecksPreviousLevel_ = numSparseCollisionChecksCurrentLevel_;
600  numSparseCollisionChecksCurrentLevel_ = (2u * numSparseCollisionChecksPreviousLevel_) + 1u;
601 
602  // Restart the reverse search.
603  restartReverseSearch();
604 
605  // Rebuild the forward queue.
606  forwardQueue_->rebuild();
607  }
608  }
609  }
610 
611  void EITstar::iterateReverseSearch()
612  {
613  // Ensure the reverse queue is not empty.
614  assert(!reverseQueue_->empty());
615 
616  // Get the top edge from the queue.
617  auto edge = reverseQueue_->pop();
618  auto &source = edge.source;
619  auto &target = edge.target;
620 
621  // The parent vertex must have an associated vertex in the tree.
622  assert(source->hasReverseVertex());
623 
624  // Register the expansion of its parent.
625  source->asReverseVertex()->registerExpansionInReverseSearch(reverseSearchTag_);
626 
627  // The edge is a freeby if its parent is already the parent of the child.
628  if (isInReverseTree(edge))
629  {
630  auto outgoingEdges = expand(target);
631  outgoingEdges.erase(std::remove_if(outgoingEdges.begin(), outgoingEdges.end(),
632  [&source](const auto &e)
633  { return e.target->getId() == source->getId(); }),
634  outgoingEdges.end());
635 
636  // If there are no outoing edges from the target state, we can nosider it expanded.
637  if (outgoingEdges.empty())
638  {
639  target->asReverseVertex()->registerExpansionInReverseSearch(reverseSearchTag_);
640  }
641  else // If there are outgoing edge, add them to or update them in the reverse queue.
642  {
643  reverseQueue_->insertOrUpdate(outgoingEdges);
644  }
645 
646  return;
647  }
648 
649  // Check whether the edge could be valid.
650  if (couldBeValid(edge))
651  {
652  // Compute the heuristic cost.
653  const auto edgeCost = objective_->motionCostHeuristic(source->raw(), target->raw());
654 
655  // Incorporate the edge in the reverse tree if it provides an improvement.
656  const auto effort = estimateEffortToTarget(edge);
657  const bool doesDecreaseEffort = (effort < target->getEstimatedEffortToGo());
658 
659  if ((!isMultiqueryEnabled_ && doesImproveReverseTree(edge, edgeCost)) ||
660  (isMultiqueryEnabled_ &&
661  ((std::isfinite(suboptimalityFactor_) && doesImproveReverseTree(edge, edgeCost)) ||
662  (!std::isfinite(suboptimalityFactor_) && doesDecreaseEffort))))
663  {
664  // Get the parent and child vertices.
665  auto parentVertex = source->asReverseVertex();
666  auto childVertex = target->asReverseVertex();
667 
668  // The child must not be closed.
669  assert(!isClosed(childVertex));
670 
671  // Update the parent of the child in the reverse tree.
672  childVertex->updateParent(parentVertex);
673 
674  // Add the child to the children of the parent.
675  parentVertex->addChild(childVertex);
676 
677  // Update the admissible cost to go.
678  target->setAdmissibleCostToGo(objective_->betterCost(
679  combine(source->getAdmissibleCostToGo(), edgeCost), edge.target->getAdmissibleCostToGo()));
680 
681  // Update the best cost estimate of the target state if this edge can improve it.
682  target->setEstimatedCostToGo(
683  objective_->betterCost(estimateCostToTarget(edge), target->getEstimatedCostToGo()));
684 
685  // Update the best effort estimate of the target state if this edge can improve it.
686  target->setEstimatedEffortToGo(std::min(effort, target->getEstimatedEffortToGo()));
687 
688  // If this edge improves the reverse cost, update it.
689  if (graph_.isStart(target) && isBetter(target->getAdmissibleCostToGo(), reverseCost_))
690  {
691  reverseCost_ = target->getAdmissibleCostToGo();
692  }
693 
694  // Update any edge in the forward queue affected by the target.
695  for (const auto &queueSource : target->getSourcesOfIncomingEdgesInForwardQueue())
696  {
697  forwardQueue_->updateIfExists({queueSource.lock(), target});
698  }
699 
700  // Expand the target state into the reverse queue.
701  auto outgoingEdges = expand(target);
702 
703  outgoingEdges.erase(
704  std::remove_if(outgoingEdges.begin(), outgoingEdges.end(),
705  [&source, this](const auto &e)
706  {
707  if (e.target->getId() == source->getId())
708  {
709  return true;
710  }
711  if (objective_->isFinite(solutionCost_) &&
712  isBetter(solutionCost_, reverseQueue_->computeAdmissibleSolutionCost(e)))
713  {
714  return true;
715  }
716 
717  return false;
718  }),
719  outgoingEdges.end());
720 
721  // If there are no outoing edges from the target state, we can nosider it expanded.
722  if (outgoingEdges.empty())
723  {
724  target->asReverseVertex()->registerExpansionInReverseSearch(reverseSearchTag_);
725  }
726  else // If there are outgoing edge, add them to or update them in the reverse queue.
727  {
728  reverseQueue_->insertOrUpdate(outgoingEdges);
729  }
730  }
731  }
732  }
733 
734  void EITstar::improveApproximation(const ompl::base::PlannerTerminationCondition &terminationCondition)
735  {
736  // Add new states, also prunes states if enabled. The method returns true if all states have been added.
737  if (graph_.addStates(batchSize_, terminationCondition))
738  {
739  // Reset the reverse collision detection.
740  numSparseCollisionChecksCurrentLevel_ = initialNumSparseCollisionChecks_;
741  numSparseCollisionChecksPreviousLevel_ = 0u;
742 
743  // Restart the reverse search.
744  restartReverseSearch();
745 
746  // Reinitialize the forward queue.
747  forwardQueue_->clear();
748  expandStartVerticesIntoForwardQueue();
749  }
750  }
751 
752  ompl::base::PlannerStatus::StatusType EITstar::ensureSetup()
753  {
754  // Call the base planners validity check. This checks if the
755  // planner is setup if not then it calls setup().
756  checkValidity();
757 
758  // Ensure the planner is setup.
759  if (!setup_)
760  {
761  OMPL_ERROR("%s: Called solve without setting up the planner first.", name_.c_str());
762  return ompl::base::PlannerStatus::StatusType::ABORT;
763  }
764 
765  // Ensure the space is setup.
766  if (!spaceInfo_->isSetup())
767  {
768  OMPL_ERROR("%s: Called solve without setting up the state space first.", name_.c_str());
769  return ompl::base::PlannerStatus::StatusType::ABORT;
770  }
771 
772  return ompl::base::PlannerStatus::StatusType::UNKNOWN;
773  }
774 
776  EITstar::ensureStartAndGoalStates(const ompl::base::PlannerTerminationCondition &terminationCondition)
777  {
778  // If the graph currently does not have a start state, try to get one.
779  if (!graph_.hasStartState())
780  {
781  graph_.updateStartAndGoalStates(terminationCondition, &pis_);
782 
783  // If we could not get a start state, then there's nothing to solve.
784  if (!graph_.hasStartState())
785  {
786  OMPL_ERROR("%s: No solution can be found as no start states are available", name_.c_str());
787  return ompl::base::PlannerStatus::StatusType::INVALID_START;
788  }
789  }
790 
791  // If the graph currently does not have a goal state, we wait until we get one.
792  if (!graph_.hasGoalState())
793  {
794  graph_.updateStartAndGoalStates(terminationCondition, &pis_);
795 
796  // If the graph still doesn't have a goal after waiting, then there's nothing to solve.
797  if (!graph_.hasGoalState())
798  {
799  OMPL_ERROR("%s: No solution can be found as no goal states are available", name_.c_str());
800  return ompl::base::PlannerStatus::StatusType::INVALID_GOAL;
801  }
802  }
803 
804  // Would it be worth implementing a 'setup' or 'checked' status type?
805  return ompl::base::PlannerStatus::StatusType::UNKNOWN;
806  }
807 
808  std::shared_ptr<ompl::geometric::PathGeometric>
809  EITstar::getPathToState(const std::shared_ptr<eitstar::State> &state) const
810  {
811  // Allocate a vector for states. The append function of the path inserts states in front of an
812  // std::vector, which is not very efficient. I'll rather iterate over the vector in reverse.
813  std::vector<std::shared_ptr<State>> states;
814  auto current = state;
815 
816  // Collect all states in reverse order of the path (starting from the goal).
817  while (!graph_.isStart(current))
818  {
819  assert(current->asForwardVertex()->getParent().lock());
820  states.emplace_back(current);
821  current = current->asForwardVertex()->getParent().lock()->getState();
822  }
823  states.emplace_back(current);
824 
825  // Append all states to the path in correct order (starting from the start).
826  auto path = std::make_shared<ompl::geometric::PathGeometric>(spaceInfo_);
827  for (auto it = states.crbegin(); it != states.crend(); ++it)
828  {
829  assert(*it);
830  assert((*it)->raw());
831  path->append((*it)->raw());
832  }
833 
834  return path;
835  }
836 
837  bool EITstar::continueSolving(const ompl::base::PlannerTerminationCondition &terminationCondition) const
838  {
839  // We stop solving the problem if:
840  // - The termination condition is satisfied; or
841  // - The current solution satisfies the objective; or
842  // - There is no better solution to be found for the current start goal pair and no new starts or
843  // goals are available
844  // We continue solving the problem if we don't stop solving.
845  return !(terminationCondition || objective_->isSatisfied(solutionCost_) ||
846  (!isBetter(graph_.minPossibleCost(), solutionCost_) && !pis_.haveMoreStartStates() &&
847  !pis_.haveMoreGoalStates()));
848  }
849 
850  unsigned int EITstar::getForwardEffort() const
851  {
852  if (forwardQueue_->size() != 0u)
853  {
854  const auto forwardEdge = forwardQueue_->peek(suboptimalityFactor_);
855  const auto forwardEffort = forwardQueue_->estimateEffort(forwardEdge);
856 
857  return forwardEffort;
858  }
859 
860  return std::numeric_limits<unsigned int>::infinity();
861  }
862 
863  bool EITstar::continueReverseSearch() const
864  {
865  // Never continue the reverse search if the reverse queue is empty.
866  if (reverseQueue_->empty())
867  {
868  return false;
869  }
870  // Always continue the reverse search if the reverse queue is not empty but the forward queue is.
871  else if (forwardQueue_->empty())
872  {
873  return true;
874  }
875 
876  /*
877 
878  If multiquery-planning is enabled, and we are in the process of searching for the initial solution,
879  there are two conditions under which we suspend the reverse search:
880 
881  1. The best edge in the forward search has a closed target (i.e. there is a path to an edge in the
882  forward queue). Since we expand the reverse queue ordered by effort in the case that the suboptimality
883  factor is infite, and multiquery planning is enabled, this is the lowest effort path.
884 
885  2. We already found a possible path, and there is no way that a lower effort path exists.
886 
887  If multiquery-planning is not enabled, there are three conditions under which the reverse search can be
888  suspended:
889 
890  1. The best edge in the forward search has a closed target (admissible cost-to-go estimate), and the
891  reverse search cannot lead to a better solution than the potential solution of this edge.
892 
893  2. All edges in the forward queue have closed targets (admissible cost-to-go estimates).
894 
895  3. We do not care about solution cost and the least-effort edge in the forward queue is connected to the
896  reverse tree.
897 
898  */
899 
900  const auto forwardEdge = forwardQueue_->peek(suboptimalityFactor_);
901 
902  if (!std::isfinite(suboptimalityFactor_) && isMultiqueryEnabled_)
903  {
904  if (!forwardEdge.target->hasReverseVertex())
905  {
906  return true;
907  }
908 
909  const auto forwardEffort = forwardQueue_->estimateEffort(forwardEdge);
910  const auto reverseEffort = reverseQueue_->peekEffort();
911  unsigned int minEffortToCome = 0u;
912 
913  const auto reverseEdge = reverseQueue_->peek();
914  if (!reverseEdge.target->hasForwardVertex())
915  {
916  minEffortToCome = forwardQueue_->getMinEffortToCome();
917  }
918 
919  return reverseEffort + minEffortToCome < forwardEffort;
920  }
921 
922  const bool condition1 = isClosed(forwardEdge.target->asReverseVertex()) &&
923  isBetter(forwardQueue_->getLowerBoundOnOptimalSolutionCost(),
924  reverseQueue_->getLowerBoundOnOptimalSolutionCost());
925 
926  const bool condition2 = !forwardQueue_->containsOpenTargets(reverseSearchTag_);
927 
928  const bool condition3 = !std::isfinite(suboptimalityFactor_) && forwardEdge.target->hasReverseVertex();
929 
930  // The reverse search must be continued if it cannot be suspended.
931  return !(condition1 || condition2 || condition3);
932  }
933 
934  bool EITstar::continueForwardSearch() const
935  {
936  // Never continue to forward search if the forward queue is empty.
937  if (forwardQueue_->empty())
938  {
939  return false;
940  }
941 
942  // The forward search must be continued if the potential solution cost of the best edge is lower than the
943  // current solution cost.
944  return isBetter(forwardQueue_->getLowerBoundOnOptimalSolutionCost(), solutionCost_);
945  }
946 
947  void EITstar::updateExactSolution()
948  {
949  for (const auto &goal : graph_.getGoalStates())
950  {
951  if (goal->hasForwardVertex())
952  {
953  updateExactSolution(goal);
954  }
955  }
956  }
957 
958  ompl::base::PlannerStatus::StatusType EITstar::updateSolution()
959  {
960  updateExactSolution();
961  if (objective_->isFinite(solutionCost_))
962  {
963  return ompl::base::PlannerStatus::StatusType::EXACT_SOLUTION;
964  }
965  else if (trackApproximateSolutions_)
966  {
967  updateApproximateSolution();
968  return ompl::base::PlannerStatus::StatusType::APPROXIMATE_SOLUTION;
969  }
970  else
971  {
972  return ompl::base::PlannerStatus::StatusType::TIMEOUT;
973  }
974  }
975 
976  void EITstar::restartReverseSearch()
977  {
978  reverseQueue_->clear();
979 
980  if (isMultiqueryEnabled_)
981  {
982  reverseQueue_->setCostQueueOrder(std::isfinite(suboptimalityFactor_));
983  }
984  else
985  {
986  reverseQueue_->setCostQueueOrder(true);
987  }
988 
989  goalVertices_.clear();
990  reverseCost_ = objective_->infiniteCost();
991  for (const auto &goal : graph_.getGoalStates())
992  {
993  goalVertices_.emplace_back(goal->asReverseVertex());
994  goal->setAdmissibleCostToGo(objective_->identityCost());
995  goal->setEstimatedCostToGo(objective_->identityCost());
996  goal->setEstimatedEffortToGo(0u);
997  }
998  expandGoalVerticesIntoReverseQueue();
999  ++reverseSearchTag_;
1000  }
1001 
1002  void EITstar::updateApproximateSolution()
1003  {
1004  for (const auto &start : graph_.getStartStates())
1005  {
1006  start->asForwardVertex()->callOnBranch([this](const std::shared_ptr<eitstar::State> &state) -> void
1007  { updateApproximateSolution(state); });
1008  }
1009  }
1010 
1011  void EITstar::updateCurrentCostToCome(const std::shared_ptr<eitstar::State> &state)
1012  {
1013  // There is no updating to do if the state is a start.
1014  if (graph_.isStart(state))
1015  {
1016  return;
1017  }
1018 
1019  // If the state is not in the forward tree, then its current cost to come is infinity.
1020  if (!state->hasForwardVertex())
1021  {
1022  state->setCurrentCostToCome(objective_->infiniteCost());
1023  return;
1024  }
1025 
1026  // If the state is in the forward tree and not the start, then update its cost.
1027  auto forwardVertex = state->asForwardVertex();
1028  state->setCurrentCostToCome(combine(forwardVertex->getParent().lock()->getState()->getCurrentCostToCome(),
1029  forwardVertex->getEdgeCost()));
1030  }
1031 
1032  void EITstar::updateExactSolution(const std::shared_ptr<eitstar::State> &goal)
1033  {
1034  // We update the current goal if
1035  // 1. The new goal has a better cost to come than the old goal
1036  // 2. Or the exact solution we found is no longer registered with the problem definition
1037  if (isBetter(goal->getCurrentCostToCome(), solutionCost_) || !problem_->hasExactSolution())
1038  {
1039  // Update the best cost.
1040  solutionCost_ = goal->getCurrentCostToCome();
1041 
1042  // Register this solution with the problem definition.
1043  ompl::base::PlannerSolution solution(getPathToState(goal));
1044  solution.setPlannerName(name_);
1045  solution.setOptimized(objective_, solutionCost_, objective_->isSatisfied(solutionCost_));
1046  problem_->addSolutionPath(solution);
1047 
1048  if (!std::isfinite(suboptimalityFactor_))
1049  {
1050  // If we found this solution with a suboptimality factor greater than 1, set the factor to one now.
1051  // Empirically, this results in faster convergence, see associated publication for more info.
1052  suboptimalityFactor_ = 1.0;
1053 
1054  if (isMultiqueryEnabled_)
1055  {
1056  restartReverseSearch();
1057  // Reinitialize the forward queue.
1058  forwardQueue_->clear();
1059  expandStartVerticesIntoForwardQueue();
1060  }
1061  }
1062 
1063  // If enabled, pass the intermediate solution back through the callback:
1064  if (static_cast<bool>(pdef_->getIntermediateSolutionCallback()))
1065  {
1066  const auto& path = solution.path_->as<ompl::geometric::PathGeometric>()->getStates();
1067  // the callback requires a vector with const elements
1068  std::vector<const base::State *> const_path(path.begin(), path.end());
1069  pdef_->getIntermediateSolutionCallback()(this, const_path, solutionCost_);
1070  }
1071 
1072  // Let the user know about the new solution.
1073  informAboutNewSolution();
1074  }
1075  }
1076 
1077  void EITstar::updateApproximateSolution(const std::shared_ptr<eitstar::State> &state)
1078  {
1079  assert(trackApproximateSolutions_);
1080  if ((state->hasForwardVertex() || graph_.isStart(state)) && !graph_.isGoal(state))
1081  {
1082  const auto costToGoal = computeCostToGoToGoal(state);
1083  if (isBetter(costToGoal, approximateSolutionCostToGoal_) || !problem_->hasSolution())
1084  {
1085  approximateSolutionCost_ = state->getCurrentCostToCome();
1086  approximateSolutionCostToGoal_ = costToGoal;
1087  ompl::base::PlannerSolution solution(getPathToState(state));
1088  solution.setPlannerName(name_);
1089 
1090  // Set the approximate flag.
1091  solution.setApproximate(costToGoal.value());
1092 
1093  // This solution is approximate and can not satisfy the objective.
1094  solution.setOptimized(objective_, approximateSolutionCost_, false);
1095 
1096  // Let the problem definition know that a new solution exists.
1097  pdef_->addSolutionPath(solution);
1098  }
1099  }
1100  }
1101 
1102  ompl::base::Cost EITstar::computeCostToGoToGoal(const std::shared_ptr<eitstar::State> &state) const
1103  {
1104  auto bestCost = objective_->infiniteCost();
1105  for (const auto &goal : graph_.getGoalStates())
1106  {
1107  bestCost = objective_->betterCost(bestCost, objective_->motionCost(state->raw(), goal->raw()));
1108  }
1109  return bestCost;
1110  }
1111 
1112  void EITstar::informAboutNewSolution() const
1113  {
1114  OMPL_INFORM("%s (%u iterations): Found a new exact solution of cost %.4f. Sampled a total of %u states, %u "
1115  "of which were valid samples (%.1f \%). Processed %u edges, %u of which were collision checked "
1116  "(%.1f \%). The forward search tree has %u vertices. The reverse search tree has %u vertices.",
1117  name_.c_str(), iteration_, solutionCost_.value(), graph_.getNumberOfSampledStates(),
1118  graph_.getNumberOfValidSamples(),
1119  graph_.getNumberOfSampledStates() == 0u ?
1120  0.0 :
1121  100.0 * (static_cast<double>(graph_.getNumberOfValidSamples()) /
1122  static_cast<double>(graph_.getNumberOfSampledStates())),
1123  numProcessedEdges_, numCollisionCheckedEdges_,
1124  numProcessedEdges_ == 0u ? 0.0 :
1125  100.0 * (static_cast<float>(numCollisionCheckedEdges_) /
1126  static_cast<float>(numProcessedEdges_)),
1127  countNumVerticesInForwardTree(), countNumVerticesInReverseTree());
1128  }
1129 
1130  void EITstar::informAboutPlannerStatus(ompl::base::PlannerStatus::StatusType status) const
1131  {
1132  switch (status)
1133  {
1134  case ompl::base::PlannerStatus::StatusType::EXACT_SOLUTION:
1135  {
1136  OMPL_INFORM("%s (%u iterations): Found an exact solution of cost %.4f.", name_.c_str(), iteration_,
1137  solutionCost_.value());
1138  break;
1139  }
1140  case ompl::base::PlannerStatus::StatusType::APPROXIMATE_SOLUTION:
1141  {
1142  OMPL_INFORM("%s (%u iterations): Did not find an exact solution, but found an approximate solution "
1143  "of cost %.4f which is %.4f away from a goal (in cost space).",
1144  name_.c_str(), iteration_, approximateSolutionCost_.value(),
1145  approximateSolutionCostToGoal_.value());
1146  break;
1147  }
1148  case ompl::base::PlannerStatus::StatusType::TIMEOUT:
1149  {
1150  if (trackApproximateSolutions_)
1151  {
1152  OMPL_INFORM("%s (%u iterations): Did not find any solution.", name_.c_str(), iteration_);
1153  }
1154  else
1155  {
1156  OMPL_INFORM("%s (%u iterations): Did not find an exact solution, and tracking approximate "
1157  "solutions is disabled.",
1158  name_.c_str(), iteration_);
1159  }
1160  break;
1161  }
1162  case ompl::base::PlannerStatus::StatusType::UNKNOWN:
1163  case ompl::base::PlannerStatus::StatusType::INVALID_START:
1164  case ompl::base::PlannerStatus::StatusType::INVALID_GOAL:
1165  case ompl::base::PlannerStatus::StatusType::UNRECOGNIZED_GOAL_TYPE:
1166  case ompl::base::PlannerStatus::StatusType::CRASH:
1167  case ompl::base::PlannerStatus::StatusType::ABORT:
1168  case ompl::base::PlannerStatus::StatusType::TYPE_COUNT:
1169  case ompl::base::PlannerStatus::StatusType::INFEASIBLE:
1170  {
1171  OMPL_INFORM("%s (%u iterations): Unable to solve the given planning problem.", name_.c_str(),
1172  iteration_);
1173  }
1174  }
1175  }
1176 
1177  unsigned int EITstar::countNumVerticesInForwardTree() const
1178  {
1179  const auto states = graph_.getStates();
1180  return std::count_if(states.cbegin(), states.cend(),
1181  [](const auto &state) { return state->hasForwardVertex(); });
1182  }
1183 
1184  unsigned int EITstar::countNumVerticesInReverseTree() const
1185  {
1186  const auto states = graph_.getStates();
1187  return std::count_if(states.cbegin(), states.cend(),
1188  [](const auto &state) { return state->hasReverseVertex(); });
1189  }
1190 
1191  bool EITstar::couldImproveForwardPath(const Edge &edge) const
1192  {
1193  // If we currently don't have a solution, the anwer is yes.
1194  if (!objective_->isFinite(solutionCost_))
1195  {
1196  // Compare the costs of the full path heuristic with the current cost of the start state.
1197  const auto heuristicPathCost =
1198  combine(edge.source->getCurrentCostToCome(),
1199  objective_->motionCostHeuristic(edge.source->raw(), edge.target->raw()),
1200  objective_->costToGo(edge.target->raw(), problem_->getGoal().get()));
1201  if (isBetter(heuristicPathCost, solutionCost_))
1202  {
1203  return true;
1204  }
1205  else
1206  {
1207  return false;
1208  }
1209  }
1210  else
1211  {
1212  return true;
1213  }
1214  }
1215 
1216  bool EITstar::couldImproveForwardTree(const Edge &edge) const
1217  {
1218  const auto heuristicCostToCome =
1219  combine(edge.source->getCurrentCostToCome(),
1220  objective_->motionCostHeuristic(edge.source->raw(), edge.target->raw()));
1221  return isBetter(heuristicCostToCome, edge.target->getCurrentCostToCome());
1222  }
1223 
1224  bool EITstar::doesImproveForwardPath(const Edge &edge, const ompl::base::Cost &edgeCost) const
1225  {
1226  // If we don't have a solution yet, the answer is imediately true.
1227  if (!objective_->isFinite(solutionCost_))
1228  {
1229  return true;
1230  }
1231 
1232  // Check whether it can improve the current solution.
1233  return isBetter(
1234  combine(edge.source->getCurrentCostToCome(), edgeCost, edge.target->getLowerBoundCostToGo()),
1235  solutionCost_);
1236  }
1237 
1238  bool EITstar::doesImproveForwardTree(const Edge &edge, const ompl::base::Cost &edgeCost) const
1239  {
1240  return isBetter(combine(edge.source->getCurrentCostToCome(), edgeCost),
1241  edge.target->getCurrentCostToCome());
1242  }
1243 
1244  ompl::base::Cost EITstar::estimateCostToTarget(const eitstar::Edge &edge) const
1245  {
1246  return combine(edge.source->getEstimatedCostToGo(),
1247  objective_->motionCostBestEstimate(edge.source->raw(), edge.target->raw()));
1248  }
1249 
1250  unsigned int EITstar::estimateEffortToTarget(const eitstar::Edge &edge) const
1251  {
1252  // if we previously validated (=whitelisted) an edge, the effort to
1253  // check is zero
1254  std::size_t checksToCome = 0u;
1255  if (edge.source->isWhitelisted(edge.target))
1256  {
1257  checksToCome = 0u;
1258  }
1259  else
1260  {
1261  // Get the segment count for the full resolution.
1262  const std::size_t fullSegmentCount = space_->validSegmentCount(edge.source->raw(), edge.target->raw());
1263  // Get the number of checks already performed on this edge.
1264  const std::size_t performedChecks = edge.target->getIncomingCollisionCheckResolution(edge.source);
1265 
1266  checksToCome = fullSegmentCount - performedChecks;
1267  }
1268 
1269  return edge.source->getEstimatedEffortToGo() + checksToCome;
1270  }
1271 
1272  bool EITstar::isValid(const Edge &edge) const
1273  {
1274  // The number of checks required to determine whether the edge is valid is the valid segment count minus one
1275  // because we know that the source and target states are valid.
1276  const std::size_t numChecks = space_->validSegmentCount(edge.source->raw(), edge.target->raw()) - 1u;
1277 
1278  return isValidAtResolution(edge, numChecks);
1279  }
1280 
1281  bool EITstar::couldBeValid(const Edge &edge) const
1282  {
1283  return isValidAtResolution(edge, numSparseCollisionChecksCurrentLevel_);
1284  }
1285 
1286  bool EITstar::isValidAtResolution(const Edge &edge, std::size_t numChecks) const
1287  {
1288  // Check if the edge is whitelisted.
1289  if (edge.source->isWhitelisted(edge.target))
1290  {
1291  return true;
1292  }
1293 
1294  // If the edge is blacklisted.
1295  if (edge.source->isBlacklisted(edge.target))
1296  {
1297  return false;
1298  }
1299 
1300  // Get the segment count for the full resolution.
1301  const std::size_t fullSegmentCount = space_->validSegmentCount(edge.source->raw(), edge.target->raw());
1302 
1303  // The segment count is the number of checks on this level plus 1, capped by the full resolution segment
1304  // count.
1305  const auto segmentCount = std::min(numChecks + 1u, fullSegmentCount);
1306 
1307  /***
1308  Let's say we want to perform seven collision checks on an edge:
1309 
1310  position of checks: |--------x--------x--------x--------x--------x--------x--------x--------|
1311  indices of checks: 1 2 3 4 5 6 7
1312  order of testing: 4 2 5 1 6 3 7
1313 
1314  We create a queue that holds segments and always test the midpoint of the segments. We start
1315  with the outermost indices and then break the segment in half until the segment collapses to a single
1316  point:
1317 
1318  1. indices = { (1, 7) }
1319  current = (1, 7) -> test midpoint = 4 -> add (1, 3) and (5, 7) to queue
1320 
1321  2. indices = { (1, 3), (5, 7) }
1322  current (1, 3) -> test midpoint = 2 -> add (1, 1) and (3, 3) to queue
1323 
1324  3. indices = { (5, 7), (1, 1), (3, 3) }
1325  current (5, 7) -> test midpoint = 6 -> add (5, 5) and (7, 7) to queue
1326 
1327  4. indices = { (1, 1), (3, 3), (5, 5), (7, 7) }
1328  current (1, 1) -> test midpoint = 1 -> add nothing to the queue
1329 
1330  5. indices = { (3, 3), (5, 5), (7, 7) }
1331  current (3, 3) -> test midpoint = 3 -> add nothing to the queue
1332 
1333  6. indices = { (5, 5) (7, 7) }
1334  current (5, 5) -> test midpoint = 5 -> add nothing to the queue
1335 
1336  7. indices = { (7, 7) }
1337  current (7, 7) -> test midpoint = 7 -> add nothing to the queue
1338  ***/
1339 
1340  // Store the current check number.
1341  std::size_t currentCheck = 1u;
1342 
1343  // Get the number of checks already performed on this edge.
1344  const std::size_t performedChecks = edge.target->getIncomingCollisionCheckResolution(edge.source);
1345 
1346  // Initialize the queue of positions to be tested.
1347  std::queue<std::pair<std::size_t, std::size_t>> indices;
1348  indices.emplace(1u, numChecks);
1349 
1350  // Test states while there are states to be tested.
1351  while (!indices.empty())
1352  {
1353  // Get the current segment.
1354  const auto current = indices.front();
1355 
1356  // Get the midpoint of the segment.
1357  auto mid = (current.first + current.second) / 2;
1358 
1359  // Only do the detection if we haven't tested this state on a previous level.
1360  if (currentCheck > performedChecks)
1361  {
1362  space_->interpolate(edge.source->raw(), edge.target->raw(),
1363  static_cast<double>(mid) / static_cast<double>(segmentCount), detectionState_);
1364 
1365  if (!spaceInfo_->isValid(detectionState_))
1366  {
1367  // Blacklist the edge.
1368  edge.source->blacklist(edge.target);
1369  edge.target->blacklist(edge.source);
1370 
1371  // Register it with the graph.
1372  graph_.registerInvalidEdge(edge);
1373  return false;
1374  }
1375  }
1376 
1377  // Remove the current segment from the queue.
1378  indices.pop();
1379 
1380  // Create the first and second half of the split segment if necessary.
1381  if (current.first < mid)
1382  {
1383  indices.emplace(current.first, mid - 1u);
1384  }
1385  if (current.second > mid)
1386  {
1387  indices.emplace(mid + 1u, current.second);
1388  }
1389 
1390  // Increase the current check number.
1391  ++currentCheck;
1392  }
1393 
1394  // Remember at what resolution this edge was already checked. We're assuming that the number of collision
1395  // checks is symmetric for each edge.
1396  edge.source->setIncomingCollisionCheckResolution(edge.target, currentCheck - 1u);
1397  edge.target->setIncomingCollisionCheckResolution(edge.source, currentCheck - 1u);
1398 
1399  // Whitelist this edge if it was checked at full resolution.
1400  if (segmentCount == fullSegmentCount)
1401  {
1402  ++numCollisionCheckedEdges_;
1403  edge.source->whitelist(edge.target);
1404  edge.target->whitelist(edge.source);
1405 
1406  graph_.registerWhitelistedState(edge.source);
1407  graph_.registerWhitelistedState(edge.target);
1408  }
1409 
1410  return true;
1411  }
1412 
1413  bool EITstar::isBetter(const ompl::base::Cost &lhs, const ompl::base::Cost &rhs) const
1414  {
1415  return objective_->isCostBetterThan(lhs, rhs);
1416  }
1417 
1418  ompl::base::Cost EITstar::combine(const ompl::base::Cost &lhs, const ompl::base::Cost &rhs) const
1419  {
1420  return objective_->combineCosts(lhs, rhs);
1421  }
1422 
1423  void EITstar::expandStartVerticesIntoForwardQueue()
1424  {
1425  for (auto &vertex : startVertices_)
1426  {
1427  forwardQueue_->insertOrUpdate(expand(vertex->getState()));
1428  }
1429  }
1430 
1431  void EITstar::expandGoalVerticesIntoReverseQueue()
1432  {
1433  for (auto &vertex : goalVertices_)
1434  {
1435  reverseQueue_->insertOrUpdate(expand(vertex->getState()));
1436  }
1437  }
1438 
1439  bool EITstar::isClosed(const std::shared_ptr<Vertex> &vertex) const
1440  {
1441  return vertex->getExpandTag() == reverseSearchTag_;
1442  }
1443 
1444  bool EITstar::isInForwardTree(const Edge &edge) const
1445  {
1446  if (!edge.source->hasForwardVertex() || !edge.target->hasForwardVertex())
1447  {
1448  return false;
1449  }
1450 
1451  return edge.target->asForwardVertex()->isParent(edge.source->asForwardVertex());
1452  }
1453 
1454  bool EITstar::isInReverseTree(const Edge &edge) const
1455  {
1456  if (!edge.source->hasReverseVertex() || !edge.target->hasReverseVertex())
1457  {
1458  return false;
1459  }
1460 
1461  return edge.target->asReverseVertex()->isParent(edge.source->asReverseVertex());
1462  }
1463 
1464  bool EITstar::doesImproveReversePath(const Edge &edge) const
1465  {
1466  // If there is no reverse path the answer is ja.
1467  if (!objective_->isFinite(reverseCost_))
1468  {
1469  return true;
1470  }
1471 
1472  // Compare the costs of the full path heuristic with the current cost of the start state.
1473  const auto heuristicPathCost =
1474  combine(edge.source->getAdmissibleCostToGo(),
1475  objective_->motionCostHeuristic(edge.source->raw(), edge.target->raw()),
1476  edge.target->getLowerBoundCostToCome());
1477 
1478  return isBetter(heuristicPathCost, reverseCost_);
1479  }
1480 
1481  bool EITstar::doesImproveReverseTree(const Edge &edge, const ompl::base::Cost &admissibleEdgeCost) const
1482  {
1483  return isBetter(combine(edge.source->getAdmissibleCostToGo(), admissibleEdgeCost),
1484  edge.target->getAdmissibleCostToGo());
1485  }
1486 
1487  std::vector<Edge> EITstar::expandUnlessGoal(const std::shared_ptr<State> &state) const
1488  {
1489  if (graph_.isGoal(state))
1490  {
1491  return {};
1492  }
1493 
1494  return expand(state);
1495  }
1496 
1497  std::vector<Edge> EITstar::expand(const std::shared_ptr<State> &state) const
1498  {
1499  // Only states associated with a vertex in either of the trees should be expanded.
1500  assert(state->hasForwardVertex() || state->hasReverseVertex());
1501 
1502  // Prepare the return variable.
1503  std::vector<Edge> outgoingEdges;
1504 
1505  // Get the neighbors in the current graph.
1506  for (const auto &neighborState : graph_.getNeighbors(state))
1507  {
1508  outgoingEdges.emplace_back(state, neighborState.lock());
1509  }
1510 
1511  // If the state is in the forward search tree, extra edges have to be added.
1512  if (state->hasForwardVertex())
1513  {
1514  // Get the vertex in the forward search tree associated with this state.
1515  auto forwardVertex = state->asForwardVertex();
1516 
1517  // Add the outgoing edge to this vertex's parent in the forward tree, if it exists.
1518  if (!graph_.isStart(state))
1519  {
1520  // If this vertex is not a start state, it must have a parent.
1521  assert(forwardVertex->getParent().lock());
1522 
1523  // Get the state associated with the parent vertex.
1524  auto forwardParentState = forwardVertex->getParent().lock()->getState();
1525 
1526  // Add the edge to the forward tree parent if it has not already being added.
1527  if (std::find_if(outgoingEdges.cbegin(), outgoingEdges.cend(),
1528  [&forwardParentState](const auto &edge) {
1529  return edge.target->getId() == forwardParentState->getId();
1530  }) == outgoingEdges.cend())
1531  {
1532  outgoingEdges.emplace_back(state, forwardParentState);
1533  }
1534  }
1535 
1536  // Add the edge to the forward children.
1537  for (const auto &child : forwardVertex->getChildren())
1538  {
1539  // Get the state associated with the child vertex.
1540  auto forwardChildState = child->getState();
1541 
1542  // Add the edge to the forward tree child if it has not already being added.
1543  if (std::find_if(outgoingEdges.cbegin(), outgoingEdges.cend(),
1544  [&forwardChildState](const auto &edge) {
1545  return edge.target->getId() == forwardChildState->getId();
1546  }) == outgoingEdges.cend())
1547  {
1548  outgoingEdges.emplace_back(state, forwardChildState);
1549  }
1550  }
1551  }
1552 
1553  // If the state is in the reverse search tree, extra edges have to be added.
1554  if (state->hasReverseVertex())
1555  {
1556  // Get the vertex in the reverse search tree associated with this state.
1557  auto reverseVertex = state->asReverseVertex();
1558 
1559  // Add the outgoing edge to this vertex's parent in the reverse tree, if it exists.
1560  if (!graph_.isGoal(state))
1561  {
1562  // If this state is not a goal, it must have a parent.
1563  assert(reverseVertex->getParent().lock());
1564 
1565  // Get the state associated with the parent vertex.
1566  auto reverseParentState = reverseVertex->getParent().lock()->getState();
1567 
1568  // Add the edge to the reverse tree parent if it has not already being added.
1569  if (std::find_if(outgoingEdges.cbegin(), outgoingEdges.cend(),
1570  [&reverseParentState](const auto &edge) {
1571  return edge.target->getId() == reverseParentState->getId();
1572  }) == outgoingEdges.cend())
1573  {
1574  outgoingEdges.emplace_back(state, reverseParentState);
1575  }
1576  }
1577 
1578  // Add the edge to the reverse children.
1579  for (const auto &child : reverseVertex->getChildren())
1580  {
1581  // Get the state associated with the child vertex.
1582  auto reverseChildState = child->getState();
1583 
1584  // Add the edge to the reverse tree parent if it has not already being added.
1585  if (std::find_if(outgoingEdges.cbegin(), outgoingEdges.cend(),
1586  [&reverseChildState](const auto &edge) {
1587  return edge.target->getId() == reverseChildState->getId();
1588  }) == outgoingEdges.cend())
1589  {
1590  outgoingEdges.emplace_back(state, reverseChildState);
1591  }
1592  }
1593  }
1594 
1595  return outgoingEdges;
1596  }
1597  } // namespace geometric
1598 } // namespace ompl
void enableMultiquery(bool multiquery)
Set wheter multiquery is enabled or not.
Definition: EITstar.cpp:316
void clear() override
Clears all internal planner structures but retains settings. Subsequent calls to solve() will start f...
Definition: EITstar.cpp:213
std::shared_ptr< State > source
The parent state of this edge.
Definition: Edge.h:165
bool isMultiqueryEnabled() const
Get wheter multiquery is enabled or not.
Definition: EITstar.cpp:322
void clearQuery() override
Clears all query-specific information, such as start and goal states and search trees....
Definition: EITstar.cpp:248
void setRadiusFactor(double factor)
Sets the radius factor.
Definition: EITstar.cpp:301
eitstar::Edge getNextForwardEdge() const
Returns the next edge in the forward queue.
Definition: EITstar.cpp:429
bool multithreaded
Flag indicating whether multiple threads are used in the computation of the planner.
Definition: Planner.h:256
void enablePruning(bool prune)
Set whether pruning is enabled or not.
Definition: EITstar.cpp:337
void enableMultiquery(bool multiquery)
Enable multiquery usage of the graph.
std::size_t getExpandTag() const
Returns the tag when this vertex was last expanded.
Definition: Vertex.cpp:304
void setMaxNumberOfGoals(unsigned int maxNumberOfGoals)
Sets the maximum number of goals EIT* will sample from sampleable goal regions.
ompl::base::Cost bestCost() const
Returns the cost of the current best solution.
Definition: EITstar.cpp:279
void setUseKNearest(bool useKNearest)
Set whether to use a k-nearest connection model. If false, it uses an r-disc model.
bool getUseKNearest() const
Returns whether to use a k-nearest RGG connection model. If false, EIT* uses an r-disc model.
Definition: EITstar.cpp:362
void setStartGoalPruningThreshold(unsigned int threshold)
Set start/goal pruning threshold.
Definition: EITstar.cpp:327
bool provingSolutionNonExistence
Flag indicating whether the planner is able to prove that no solution path exists.
Definition: Planner.h:272
unsigned int getMaxNumberOfGoals() const
Returns the maximum number of goals EIT* will sample from sampleable goal regions.
bool getUseKNearest() const
Returns whether the graph uses a k-nearest connection model. If false, it uses an r-disc model.
bool isGoal(const std::shared_ptr< State > &state) const
Returns whether the given state is a goal state.
void addPlannerProgressProperty(const std::string &progressPropertyName, const PlannerProgressProperty &prop)
Add a planner progress property called progressPropertyName with a property querying function prop to...
Definition: Planner.h:467
unsigned int getStartGoalPruningThreshold() const
Get threshold at which we prune starts/goals.
Definition: EITstar.cpp:332
A struct for basic edge data.
Definition: Edge.h:152
std::vector< eitstar::Edge > getReverseQueue() const
Returns a copy of the reverse queue.
Definition: EITstar.cpp:394
double value() const
The value of the cost.
Definition: Cost.h:152
Representation of a solution to a planning problem.
void setRadiusFactor(double factor)
Sets the radius factor (eta in the paper).
Definition of a cost value. Can represent the cost of a motion or the cost of a state.
Definition: Cost.h:111
bool isStart(const std::shared_ptr< State > &state) const
Returns whether the given state is a start state.
bool isReverseQueueEmpty() const
Returns true if the reverse queue is empty.
Definition: EITstar.cpp:383
#define OMPL_INFORM(fmt,...)
Log a formatted information string.
Definition: Console.h:68
unsigned int getEffortThreshold() const
Gets the effort threshold.
unsigned int getNumberOfSampledStates() const
Returns the number of sampled states.
bool hasForwardVertex() const
Returns whether the state has an associated forward vertex.
Definition: State.cpp:183
Definition of a geometric path.
Definition: PathGeometric.h:97
Object containing planner generated vertex and edge data. It is assumed that all vertices are unique,...
Definition: PlannerData.h:238
const std::vector< std::shared_ptr< State > > & getStartStates() const
Returns the start states.
const std::vector< std::shared_ptr< State > > & getGoalStates() const
Returns the goal states.
void setup() override
Setup the parts of the planner that rely on the problem definition being set.
Definition: EITstar.cpp:96
std::string name_
The name of this planner.
Definition: Planner.h:483
Encapsulate a termination condition for a motion planner. Planners will call operator() to decide whe...
bool areApproximateSolutionsTracked() const
Returns whether approximate solutions are tracked or not.
Definition: EITstar.cpp:352
void enablePruning(bool prune)
Enable pruning of the graph.
PlannerSpecs specs_
The specifications of the planner (its capabilities)
Definition: Planner.h:486
bool directed
Flag indicating whether the planner is able to account for the fact that the validity of a motion fro...
Definition: Planner.h:269
void trackApproximateSolutions(bool track)
Sets whether to track approximate solutions or not.
Definition: EITstar.cpp:347
void setup(const std::shared_ptr< ompl::base::ProblemDefinition > &problem, ompl::base::PlannerInputStates *inputStates)
Setup the graph with the given problem definition and planner input states.
bool optimizingPaths
Flag indicating whether the planner attempts to optimize the path and reduce its length until the max...
Definition: Planner.h:263
std::vector< eitstar::Edge > getReverseTree() const
Returns copies of the edges in the reverse tree.
Definition: EITstar.cpp:399
void setBatchSize(unsigned int numSamples)
Sets the number of samples per batch.
Definition: EITstar.cpp:284
std::shared_ptr< Vertex > asForwardVertex()
Returns the state as a reverse vertex.
Definition: State.cpp:193
A class to store the exit status of Planner::solve()
~EITstar()
Destructs this instance of EIT*.
Definition: EITstar.cpp:91
ompl::base::PlannerStatus solve(const ompl::base::PlannerTerminationCondition &terminationCondition) override
Solves the provided motion planning problem, respecting the given termination condition.
Definition: EITstar.cpp:173
bool canReportIntermediateSolutions
Flag indicating whether the planner is able to report the computation of intermediate paths.
Definition: Planner.h:275
void clearQuery()
Clears all query-specific structures, such as start and goal states.
bool setup_
Flag indicating whether setup() has been called.
Definition: Planner.h:497
void clear()
Clears all internal planner structures but retains settings. Subsequent calls to solve() will start f...
std::shared_ptr< Vertex > asReverseVertex()
Returns the state as a reverse vertex.
Definition: State.cpp:218
unsigned int getNumberOfNearestNeighborCalls() const
Returns the number of nearest neighbor calls.
void setEffortThreshold(const unsigned int threshold)
Sets the effort threshold.
bool isPruningEnabled() const
Returns whether pruning is enabled or not.
Definition: EITstar.cpp:342
#define OMPL_WARN(fmt,...)
Log a formatted warning string.
Definition: Console.h:66
@ GOAL_SAMPLEABLE_REGION
This bit is set if casting to sampleable goal regions (ompl::base::GoalSampleableRegion) is possible.
Definition: GoalTypes.h:152
std::vector< eitstar::Edge > getForwardQueue() const
Returns a copy of the forward queue.
Definition: EITstar.cpp:389
unsigned int addVertex(const PlannerDataVertex &st)
Adds the given vertex to the graph data. The vertex index is returned. Duplicates are not added....
unsigned int getMaxNumberOfGoals() const
Returns the maximum number of goals EIT* will sample from sampleable goal regions.
Definition: EITstar.cpp:372
std::shared_ptr< State > target
The child state of this edge.
Definition: Edge.h:168
PlannerInputStates pis_
Utility class to extract valid input states
Definition: Planner.h:480
StatusType
The possible values of the status returned by a planner.
bool hasReverseVertex() const
Returns whether the state has an associated reverse vertex.
Definition: State.cpp:188
void setUseKNearest(bool useKNearest)
Set whether to use a k-nearest RGG connection model. If false, EIT* uses an r-disc model.
Definition: EITstar.cpp:357
#define OMPL_ERROR(fmt,...)
Log a formatted error string.
Definition: Console.h:64
bool approximateSolutions
Flag indicating whether the planner is able to compute approximate solutions.
Definition: Planner.h:259
std::vector< std::shared_ptr< State > > getStates() const
Returns all sampled states (that have not been pruned).
void setInitialNumberOfSparseCollisionChecks(std::size_t numChecks)
Sets the initial number of collision checks on the reverse search.
Definition: EITstar.cpp:294
double getRadiusFactor() const
Returns the radius factor.
Definition: EITstar.cpp:306
virtual bool addEdge(unsigned int v1, unsigned int v2, const PlannerDataEdge &edge=PlannerDataEdge(), Cost weight=Cost(1.0))
Adds a directed edge between the given vertex indexes. An optional edge structure and weight can be s...
bool isForwardQueueEmpty() const
Returns true if the forward queue is empty.
Definition: EITstar.cpp:377
void setMaxNumberOfGoals(unsigned int numberOfGoals)
Set the maximum number of goals EIT* will sample from sampleable goal regions.
Definition: EITstar.cpp:367
eitstar::Edge getNextReverseEdge() const
Returns the next edge in the reverse queue.
Definition: EITstar.cpp:439
Base class for a vertex in the PlannerData structure. All derived classes must implement the clone an...
Definition: PlannerData.h:122
bool isPruningEnabled() const
Returns Whether pruning is enabled.
void setSuboptimalityFactor(double factor)
Sets the (initial) suboptimality factor.
Definition: EITstar.cpp:311
void getPlannerData(base::PlannerData &data) const override
Returns the planner data.
Definition: EITstar.cpp:449
GoalType recognizedGoal
The type of goal specification the planner can use.
Definition: Planner.h:253
Main namespace. Contains everything in this library.
Definition: AppBase.h:21
double getRadiusFactor() const
Returns the radius factor (eta in the paper).
unsigned int getBatchSize() const
Returns the number of samples per batch.
Definition: EITstar.cpp:289