RandomGeometricGraph.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/RandomGeometricGraph.h"
38 
39 #include <cmath>
40 
41 #include <boost/math/constants/constants.hpp>
42 
43 #include "ompl/base/OptimizationObjective.h"
44 #include "ompl/util/GeometricEquations.h"
45 
46 #include "ompl/geometric/planners/informedtrees/eitstar/Vertex.h"
47 
48 namespace ompl
49 {
50  namespace geometric
51  {
52  namespace eitstar
53  {
54  RandomGeometricGraph::RandomGeometricGraph(const std::shared_ptr<ompl::base::SpaceInformation> &spaceInfo,
55  const ompl::base::Cost &solutionCost)
56 
57  : samples_(8 /* degree */, 4 /* min degree */, 12 /* max degree */, 50 /* max num points per leaf */,
58  500 /* removed cache size */,
59  false /* rebalancing */) // These are the defaults. Play with them.
60  , spaceInfo_(spaceInfo)
61  , space_(spaceInfo->getStateSpace())
62  , dimension_(spaceInfo->getStateDimension())
63  , unitNBallMeasure_(unitNBallMeasure(spaceInfo->getStateDimension()))
64  , solutionCost_(solutionCost)
65  {
66  samples_.setDistanceFunction(
67  [this](const std::shared_ptr<State> &state1, const std::shared_ptr<State> &state2) {
68  return spaceInfo_->distance(state1->state_, state2->state_);
69  });
70  }
71 
72  void RandomGeometricGraph::setup(const std::shared_ptr<ompl::base::ProblemDefinition> &problem,
73  ompl::base::PlannerInputStates *inputStates)
74  {
75  problem_ = problem;
76  objective_ = problem->getOptimizationObjective();
77  k_rgg_ = boost::math::constants::e<double>() +
78  (boost::math::constants::e<double>() / spaceInfo_->getStateDimension());
80 
81  auto numInformedSamples{0u};
82  if (isPruningEnabled_)
83  {
84  prune();
85  numInformedSamples = samples_.size();
86  }
87  else
88  {
89  numInformedSamples = countSamplesInInformedSet();
90  }
91  // Update the radius by considering all informed states.
92  if (useKNearest_)
93  {
94  numNeighbors_ = computeNumberOfNeighbors(numInformedSamples);
95  }
96  else
97  {
98  radius_ = computeRadius(numInformedSamples);
99  }
100 
101  // Update the tag.
102  ++tag_;
103  }
104 
106  {
107  tag_ = 1u;
108  radius_ = std::numeric_limits<double>::infinity();
109  numNeighbors_ = std::numeric_limits<std::size_t>::max();
110  samples_.clear();
111  newSamples_.clear();
112  startStates_.clear();
113  goalStates_.clear();
114  prunedStartStates_.clear();
115  prunedGoalStates_.clear();
116 
117  buffer_.clear();
118  startGoalBuffer_.clear();
119  }
120 
121  void RandomGeometricGraph::pruneStartsAndGoals()
122  {
123  auto startAndGoalStates = startStates_;
124  startAndGoalStates.insert(startAndGoalStates.end(), goalStates_.begin(), goalStates_.end());
125 
126  for (auto &sample : startAndGoalStates)
127  {
128  bool remove = true;
129 
130  // look at the cost that we might save if we keep this one
131  unsigned int maxSingleEdgeSavedEffort = 0u;
132  for (const auto &w : getNeighbors(sample))
133  {
134  if (auto neighbor = w.lock())
135  {
136  if (sample->isWhitelisted(neighbor))
137  {
138  const std::size_t fullSegmentCount =
139  space_->validSegmentCount(sample->raw(), neighbor->raw());
140 
141  if (fullSegmentCount > maxSingleEdgeSavedEffort)
142  {
143  maxSingleEdgeSavedEffort = fullSegmentCount;
144  }
145  }
146  }
147  }
148 
149  if (maxSingleEdgeSavedEffort > effortThreshold_)
150  {
151  remove = false;
152  }
153 
154  if (remove)
155  {
156  samples_.remove(sample);
157  }
158  else
159  {
160  startGoalBuffer_.push_back(sample);
161  }
162  }
163  }
164 
166  {
167  if (isMultiqueryEnabled_)
168  {
169  pruneStartsAndGoals();
170  }
171 
172  startStates_.clear();
173  goalStates_.clear();
174  prunedStartStates_.clear();
175  prunedGoalStates_.clear();
176 
177  newSamples_.clear();
178 
179  samples_.clear();
180  if (isMultiqueryEnabled_)
181  {
182  samples_.add(startGoalBuffer_);
183  }
184 
185  currentNumSamples_ = 0u;
186 
187  tag_++;
188 
189  if (!isMultiqueryEnabled_)
190  {
191  clear();
192  }
193  }
194 
196  const ompl::base::PlannerTerminationCondition &terminationCondition,
197  ompl::base::PlannerInputStates *inputStates)
198  {
199  // We need to keep track of whether a new goal and/or a new start has been added.
200  bool addedNewStartState = false;
201  bool addedNewGoalState = false;
202 
203  // First update the goals. We have to call inputStates->nextGoal(terminationCondition) at least once
204  // (regardless of the return value of inputStates->moreGoalStates()) in case the termination condition
205  // wants us to wait for a goal.
206  if (goalStates_.size() < maxNumGoals_)
207  {
208  do
209  {
210  // Get a new goal. If there are none, or the underlying state is invalid this will be a nullptr.
211  const auto newGoalState =
212  inputStates->nextGoal(terminationCondition);
213 
214  // If there was a new valid goal, register it as such and remember that a goal has been added.
215  if (static_cast<bool>(newGoalState))
216  {
217  ++numValidSamples_;
218 
219  registerGoalState(newGoalState);
220  addedNewGoalState = true;
221  }
222 
223  } while (inputStates->haveMoreGoalStates() && goalStates_.size() < maxNumGoals_ &&
224  !terminationCondition);
225  }
226 
227  // Having updated the goals, we now update the starts.
228  while (inputStates->haveMoreStartStates())
229  {
230  // Get the next start. The returned pointer can be a nullptr (if the state is invalid).
231  const auto newStartState = inputStates->nextStart();
232 
233  // If there is a new valid start, register it as such and remember that a start has been added.
234  if (static_cast<bool>(newStartState))
235  {
236  ++numValidSamples_;
237 
238  registerStartState(newStartState);
239  addedNewStartState = true;
240  }
241  }
242 
243  // If we added a new start and have previously pruned goals, we might want to add the goals back.
244  if (addedNewStartState && !prunedGoalStates_.empty())
245  {
246  // Keep track of the pruned goal vertices that have been revived.
247  std::vector<std::vector<std::shared_ptr<State>>::iterator> revivedGoals;
248 
249  // Let's see if the pruned goal is close enough to any new start to revive it..
250  for (auto it = prunedGoalStates_.begin(); it != prunedGoalStates_.end(); ++it)
251  {
252  // Loop over all start states to get the best cost.
253  auto heuristicCost = objective_->infiniteCost();
254  for (const auto &start : startStates_)
255  {
256  heuristicCost = objective_->betterCost(
257  heuristicCost, objective_->motionCostHeuristic(start->raw(), (*it)->raw()));
258  }
259 
260  // If this goal can possibly improve the current solution, add it back to the graph.
261  if (objective_->isCostBetterThan(heuristicCost, solutionCost_))
262  {
263  registerGoalState((*it)->raw());
264  addedNewGoalState = true;
265  revivedGoals.emplace_back(it);
266  }
267  }
268 
269  // Remove all revived goals from the pruned goals.
270  for (auto &revivedGoal : revivedGoals)
271  {
272  std::iter_swap(revivedGoal, prunedGoalStates_.rbegin());
273  prunedGoalStates_.pop_back();
274  }
275  }
276 
277  // If we added a new goal and have previously pruned starts, we might want to add the starts back.
278  if (addedNewGoalState && !prunedStartStates_.empty())
279  {
280  // Keep track of the pruned goal vertices that have been revived.
281  std::vector<std::vector<std::shared_ptr<State>>::iterator> revivedStarts;
282 
283  // Let's see if the pruned start is close enough to any new goal to revive it..
284  for (auto it = prunedStartStates_.begin(); it != prunedStartStates_.end(); ++it)
285  {
286  // Loop over all start states to get the best cost.
287  auto heuristicCost = objective_->infiniteCost();
288  for (const auto &goal : goalStates_)
289  {
290  heuristicCost = objective_->betterCost(
291  heuristicCost, objective_->motionCostHeuristic(goal->raw(), (*it)->raw()));
292  }
293 
294  // If this start can possibly improve the current solution, add it back to the graph.
295  if (objective_->isCostBetterThan(heuristicCost, solutionCost_))
296  {
297  registerStartState((*it)->raw());
298  addedNewStartState = true;
299  revivedStarts.emplace_back(it);
300  }
301  }
302 
303  // Remove all revived starts from the pruned starts.
304  for (auto &revivedStart : revivedStarts)
305  {
306  std::iter_swap(revivedStart, prunedStartStates_.rbegin());
307  prunedStartStates_.pop_back();
308  }
309  }
310 
311  if (addedNewGoalState || addedNewStartState)
312  {
313  // Allocate a state sampler if we have at least one start and one goal.
314  if (!startStates_.empty() && !goalStates_.empty())
315  {
316  sampler_ =
317  objective_->allocInformedStateSampler(problem_, std::numeric_limits<unsigned int>::max());
318  }
319  }
320 
321  if (!goalStates_.empty() && startStates_.empty())
322  {
323  OMPL_WARN("EIT*: The problem has a goal but not a start. EIT* can not find a solution since "
324  "PlannerInputStates provides no method to wait for a valid start state to appear.");
325  }
326 
327  // Compute the minimum possible cost for this problem given the start and goal states and an admissible
328  // cost heuristic.
329  minPossibleCost_ = objective_->infiniteCost();
330  for (const auto &start : startStates_)
331  {
332  for (const auto &goal : goalStates_)
333  {
334  minPossibleCost_ = objective_->betterCost(
335  minPossibleCost_, objective_->motionCostHeuristic(start->raw(), goal->raw()));
336  }
337  }
338 
339  std::vector<std::shared_ptr<State>> samples;
340  samples_.list(samples);
341  for (auto &state : samples)
342  {
343  initializeState(state);
344  }
345  }
346 
348  {
349  return minPossibleCost_;
350  }
351 
352  void RandomGeometricGraph::setRadiusFactor(double factor)
353  {
354  radiusFactor_ = factor;
355  }
356 
358  {
359  return radiusFactor_;
360  }
361 
362  void RandomGeometricGraph::setEffortThreshold(unsigned int threshold)
363  {
364  effortThreshold_ = threshold;
365  }
366 
367  unsigned int RandomGeometricGraph::getEffortThreshold() const
368  {
369  return effortThreshold_;
370  }
371 
372  const std::vector<std::shared_ptr<State>> &RandomGeometricGraph::getStartStates() const
373  {
374  return startStates_;
375  }
376 
377  const std::vector<std::shared_ptr<State>> &RandomGeometricGraph::getGoalStates() const
378  {
379  return goalStates_;
380  }
381 
383  {
384  return numSampledStates_;
385  }
386 
388  {
389  return numValidSamples_;
390  }
391 
393  {
394  return numNearestNeighborCalls_;
395  }
396 
398  {
399  return !startStates_.empty();
400  }
401 
403  {
404  return !goalStates_.empty();
405  }
406 
407  bool RandomGeometricGraph::isStart(const std::shared_ptr<State> &state) const
408  {
409  return std::any_of(startStates_.begin(), startStates_.end(),
410  [&state](const auto &start) { return state->getId() == start->getId(); });
411  }
412 
413  bool RandomGeometricGraph::isGoal(const std::shared_ptr<State> &state) const
414  {
415  return std::any_of(goalStates_.begin(), goalStates_.end(),
416  [&state](const auto &goal) { return state->getId() == goal->getId(); });
417  }
418 
419  std::vector<std::shared_ptr<State>> RandomGeometricGraph::getStates() const
420  {
421  std::vector<std::shared_ptr<State>> samples;
422  samples_.list(samples);
423  return samples;
424  }
425 
426  void RandomGeometricGraph::registerInvalidEdge(const Edge &edge) const
427  {
428  // Remove the edge from the caches (using the erase-remove idiom). Take this opportunity to remove
429  // expired states from the cache as well.
430  auto &sourceNeighbors = edge.source->neighbors_.second;
431  auto &targetNeighbors = edge.target->neighbors_.second;
432 
433  sourceNeighbors.erase(std::remove_if(sourceNeighbors.begin(), sourceNeighbors.end(),
434  [&edge](const auto &neighbor) {
435  return neighbor.expired() ||
436  neighbor.lock()->getId() == edge.target->getId();
437  }),
438  sourceNeighbors.end());
439 
440  targetNeighbors.erase(std::remove_if(targetNeighbors.begin(), targetNeighbors.end(),
441  [&edge](const auto &neighbor) {
442  return neighbor.expired() ||
443  neighbor.lock()->getId() == edge.source->getId();
444  }),
445  targetNeighbors.end());
446  }
447 
448  std::size_t RandomGeometricGraph::getTag() const
449  {
450  return tag_;
451  }
452 
453  std::shared_ptr<State> RandomGeometricGraph::registerStartState(const ompl::base::State *state)
454  {
455  // Allocate the start state.
456  auto start = std::make_shared<State>(spaceInfo_, objective_);
457  spaceInfo_->copyState(start->raw(), state);
458 
459  // Hold onto it.
460  startStates_.emplace_back(start);
461  samples_.add(start);
462 
463  // Initialize the state.
464  initializeState(start);
465 
466  // Ensure its lower bounds are correct.
467  assert(objective_->isCostEquivalentTo(start->getLowerBoundCostToCome(), objective_->identityCost()));
468  assert(start->getLowerBoundEffortToCome() == 0u);
469 
470  return start;
471  }
472 
473  std::shared_ptr<State> RandomGeometricGraph::registerGoalState(const ompl::base::State *state)
474  {
475  // Allocate the goal state.
476  auto goal = std::make_shared<State>(spaceInfo_, objective_);
477  spaceInfo_->copyState(goal->raw(), state);
478 
479  // Hold onto it.
480  goalStates_.emplace_back(goal);
481  samples_.add(goal);
482 
483  // Initialize the state.
484  initializeState(goal);
485 
486  // Ensure its lower bounds are correct.
487  assert(objective_->isCostEquivalentTo(goal->getLowerBoundCostToGo(), objective_->identityCost()));
488 
489  return goal;
490  }
491 
492  void RandomGeometricGraph::registerWhitelistedState(const std::shared_ptr<State> &state) const
493  {
494  whitelistedStates_.push_back(state);
495  }
496 
497  std::shared_ptr<State> RandomGeometricGraph::getNewSample(const ompl::base::PlannerTerminationCondition& terminationCondition)
498  {
499  // Allocate a new state.
500  auto state = std::make_shared<State>(spaceInfo_, objective_);
501 
502  // Create the requested number of new states.
503  if (currentNumSamples_ < buffer_.size())
504  {
505  // This does at the moment not deal with the fact that samples might become invalid
506  // also, these samples might not be informed samples
507  state = buffer_[currentNumSamples_];
508  }
509  else
510  {
511  bool foundValidSample = false;
512  do // Sample randomly until a valid state is found.
513  {
514  if (isMultiqueryEnabled_)
515  {
516  // If we are doing multiquery planning, we sample uniformly, and reject samples that can
517  // not improve the solution. This means that we need to sample the whole space, and add
518  // the samples to the buffer
519  sampler_->sampleUniform(state->raw(), objective_->infiniteCost());
520  }
521  else
522  {
523  // In case we are not doing multiquery planning, we can still directly sample the informed
524  // set.
525  sampler_->sampleUniform(state->raw(), solutionCost_);
526  }
527 
528  ++numSampledStates_;
529  // Check if the sample is valid.
530  foundValidSample = spaceInfo_->isValid(state->raw());
531  } while (!foundValidSample && !terminationCondition);
532 
533  // The sample is invalid, but we have to return to respect the termination condition.
534  if (!foundValidSample)
535  {
536  return nullptr;
537  }
538 
539  // We've found a valid sample.
540  ++numValidSamples_;
541 
542  // and we add it to the buffer
543  buffer_.emplace_back(state);
544  }
545 
546  ++currentNumSamples_;
547  return state;
548  }
549 
550  bool RandomGeometricGraph::addStates(std::size_t numNewStates,
551  const ompl::base::PlannerTerminationCondition &terminationCondition)
552  {
553  // Assert sanity of used variables.
554  assert(sampler_);
555  assert(objective_);
556 
557  if (numNewStates == 0u)
558  {
559  return true;
560  }
561 
562  // Create the requested number of new states.
563  do
564  {
565  do
566  {
567  // This call can return nullptr if the termination condition is met
568  // before a valid sample is found.
569  const auto state = getNewSample(terminationCondition);
570 
571  // Since we do not do informed sampling, we need to check if the sample could improve
572  // the current solution.
573  if (state != nullptr && !canBePruned(state))
574  {
575  newSamples_.emplace_back(state);
576 
577  // Add this state to the goal states if it is a goal.
578  if (problem_->getGoal()->isSatisfied(state->raw()))
579  {
580  goalStates_.emplace_back(state);
581  }
582  break;
583  }
584  } while (!terminationCondition);
585  } while (newSamples_.size() < numNewStates && !terminationCondition);
586 
587  // Add the new states to the samples.
588  if (newSamples_.size() == numNewStates)
589  {
590  // Count the number of informed states before adding the new states. This saves some counting,
591  // because all new states will be in the informed set, and its known how many of them will be added.
592  // If pruning is enabled, we can do this now. After pruning all remaining states are in the informed
593  // set.
594 
595  auto numInformedSamples{0u};
596 
597  if (isPruningEnabled_)
598  {
599  prune();
600  numInformedSamples = samples_.size();
601  }
602  else
603  {
604  numInformedSamples = countSamplesInInformedSet();
605  }
606 
607  samples_.add(newSamples_);
608 
609  // only initialize samples after all states have been added to the graph
610  for (auto &sample : newSamples_)
611  {
612  initializeState(sample);
613  }
614 
615  newSamples_.clear();
616 
617  // Update the radius by considering all informed states.
618  if (useKNearest_)
619  {
620  numNeighbors_ = computeNumberOfNeighbors(numInformedSamples + numNewStates);
621  }
622  else
623  {
624  radius_ = computeRadius(numInformedSamples + numNewStates);
625  }
626 
627  // Update the tag.
628  ++tag_;
629 
630  return true;
631  }
632  return false;
633  }
634 
635  void RandomGeometricGraph::enablePruning(bool prune)
636  {
637  isPruningEnabled_ = prune;
638  }
639 
641  {
642  return isPruningEnabled_;
643  }
644 
645  void RandomGeometricGraph::enableMultiquery(bool multiquery)
646  {
647  isMultiqueryEnabled_ = multiquery;
648  }
649 
651  {
652  return isMultiqueryEnabled_;
653  }
654 
655  void RandomGeometricGraph::setUseKNearest(bool useKNearest)
656  {
657  useKNearest_ = useKNearest;
658  }
659 
661  {
662  return useKNearest_;
663  }
664 
665  void RandomGeometricGraph::setMaxNumberOfGoals(unsigned int maxNumberOfGoals)
666  {
667  maxNumGoals_ = maxNumberOfGoals;
668  }
669 
670  unsigned int RandomGeometricGraph::getMaxNumberOfGoals() const
671  {
672  return maxNumGoals_;
673  }
674 
675  std::vector<std::weak_ptr<State>>
676  RandomGeometricGraph::getNeighbors(const std::shared_ptr<State> &state) const
677  {
678  assert(state);
679 
680  // If the neighbors cache of the vertex isn't up to date, update it.
681  if (state->neighbors_.first != tag_)
682  {
683  // copy the whitelisted vertices
684  std::vector<std::shared_ptr<State>> whitelistedNeighbors;
685  if (isMultiqueryEnabled_)
686  {
687  std::vector<std::shared_ptr<State>> samples;
688  samples_.list(samples);
689 
690  if (std::find(samples.begin(), samples.end(), state) != samples.end())
691  {
692  std::copy_if(samples.begin(), samples.end(), std::back_inserter(whitelistedNeighbors),
693  [&state](const auto v) { return state->isWhitelisted(v); });
694  }
695  }
696 
697  // The cache is invalid, let's clear all vertices.
698  state->neighbors_.second.clear();
699 
700  // Get the neighbors by performing a nearest neighbor search.
701  std::vector<std::shared_ptr<State>> neighbors;
702  if (useKNearest_)
703  {
704  samples_.nearestK(state, numNeighbors_, neighbors);
705  }
706  else
707  {
708  samples_.nearestR(state, radius_, neighbors);
709  }
710 
711  // add whitelisted neighbours to the vector even if they are above the radius
712  if (isMultiqueryEnabled_)
713  {
714  std::copy_if(whitelistedNeighbors.begin(), whitelistedNeighbors.end(),
715  std::back_inserter(neighbors), [&neighbors](const auto v) {
716  return std::find(neighbors.begin(), neighbors.end(), v) == neighbors.end();
717  });
718  }
719 
720  // We dont want to connect to blacklisted neighbors and the querying state itself.
721  const auto connectionPredicate = [&state, this](const std::shared_ptr<State> &neighbor) {
722  return !state->isBlacklisted(neighbor) && (state->id_ != neighbor->id_) &&
723  !(isGoal(state) && isGoal(neighbor));
724  };
725 
726  // Cache the neighbors that are not blacklisted and not the state itself.
727  std::copy_if(neighbors.begin(), neighbors.end(), std::back_inserter(state->neighbors_.second),
728  connectionPredicate);
729 
730  // Update the tag of the cache.
731  state->neighbors_.first = tag_;
732 
733  // Increase the counter of nearest neighbor calls.
734  ++numNearestNeighborCalls_;
735  }
736 
737  // The cache is now guaranteed to be up to date.
738  auto neighbors = state->neighbors_.second;
739 
740  // Add the forward parent and children.
741  if (state->hasForwardVertex())
742  {
743  const auto forwardVertex = state->asForwardVertex();
744 
745  // Add the parent.
746  if (auto forwardParent = forwardVertex->getParent().lock())
747  {
748  neighbors.emplace_back(forwardParent->getState());
749  }
750 
751  // Add the children.
752  const auto &forwardChildren = forwardVertex->getChildren();
753  for (const auto &child : forwardChildren)
754  {
755  neighbors.emplace_back(child->getState());
756  }
757  }
758 
759  // Add the reverse parent and children.
760  if (state->hasReverseVertex())
761  {
762  const auto reverseVertex = state->asReverseVertex();
763 
764  // Add the parent.
765  if (auto reverseParent = reverseVertex->getParent().lock())
766  {
767  neighbors.emplace_back(reverseParent->getState());
768  }
769 
770  // Add the children.
771  const auto &reverseChildren = reverseVertex->getChildren();
772  for (const auto &child : reverseChildren)
773  {
774  neighbors.emplace_back(child->getState());
775  }
776  }
777 
778  return neighbors;
779  }
780 
782  {
783  // Is there really no way of doing this without looping over all samples?
784  std::vector<std::shared_ptr<State>> samples;
785  samples_.list(samples);
786 
787  // Prepare a vector of samples to be pruned.
788  std::vector<std::shared_ptr<State>> samplesToBePruned;
789 
790  // Check each sample if it can be pruned.
791  for (const auto &sample : samples)
792  {
793  if (canBePruned(sample))
794  {
795  if (isStart(sample))
796  {
797  prunedStartStates_.emplace_back(sample);
798  }
799  else if (isGoal(sample))
800  {
801  prunedGoalStates_.emplace_back(sample);
802  }
803 
804  samplesToBePruned.emplace_back(sample);
805  }
806  }
807 
808  // Remove all samples to be pruned.
809  for (const auto &sample : samplesToBePruned)
810  {
811  // Remove the sample from the graph.
812  samples_.remove(sample);
813 
814  // Remove it from both search trees.
815  if (sample->hasForwardVertex())
816  {
817  auto forwardVertex = sample->asForwardVertex();
818  if (auto parent = forwardVertex->getParent().lock())
819  {
820  forwardVertex->resetParent();
821  parent->removeChild(forwardVertex);
822  }
823  }
824  if (sample->hasReverseVertex())
825  {
826  auto reverseVertex = sample->asReverseVertex();
827  if (auto parent = reverseVertex->getParent().lock())
828  {
829  reverseVertex->resetParent();
830  parent->removeChild(reverseVertex);
831  }
832  }
833  }
834 
835  // If any sample was pruned, this has invalidated the nearest neighbor cache.
836  if (!samplesToBePruned.empty())
837  {
838  ++tag_;
839  }
840  }
841 
842  std::size_t RandomGeometricGraph::countSamplesInInformedSet() const
843  {
844  // Is there really no way of doing this without looping over all samples?
845  std::vector<std::shared_ptr<State>> samples;
846  samples_.list(samples);
847 
848  // Count the number of samples that can possibly improve the solution and subtract the start and goal
849  // states from this number, as they're not technically uniformly distributed.
850  return std::count_if(samples.begin(), samples.end(),
851  [this](const auto &sample) { return !canBePruned(sample); }) -
852  startStates_.size() - goalStates_.size();
853  }
854 
855  bool RandomGeometricGraph::canBePruned(const std::shared_ptr<State> &state) const
856  {
857  // If we don't have a solution, no state can be pruned.
858  if (!objective_->isFinite(solutionCost_))
859  {
860  return false;
861  }
862  else
863  {
864  // Get the heuristic cost to come.
865  const auto costToCome = lowerBoundCostToCome(state);
866 
867  // Get the heuristic cost to go.
868  const auto costToGo = lowerBoundCostToGo(state);
869 
870  // Return whether the current solution is better than the lower bound potential solution.
871  return objective_->isCostBetterThan(solutionCost_, objective_->combineCosts(costToCome, costToGo));
872  }
873  }
874 
875  ompl::base::Cost RandomGeometricGraph::lowerBoundCostToCome(const std::shared_ptr<State> &state) const
876  {
877  // Get the preferred start for this state.
878  auto bestCost = objective_->infiniteCost();
879  for (const auto &start : startStates_)
880  {
881  bestCost =
882  objective_->betterCost(objective_->motionCostHeuristic(start->raw(), state->raw()), bestCost);
883  }
884 
885  return bestCost;
886  }
887 
888  unsigned int RandomGeometricGraph::lowerBoundEffortToCome(const std::shared_ptr<State> &state) const
889  {
890  // If we previously validated any states, the lower bound effort to come is 0.
891  // (it is possible to compute a better bound, but empirically, it is not worth the computational
892  // effort it takes to compute this better bound.)
893  if (isMultiqueryEnabled_ && whitelistedStates_.size() != 0u)
894  {
895  return 0u;
896  }
897 
898  // If there's no whitelisted states, we can quickly compute a better bound:
899  // The minimum number of collision checks from any of the starts to the state we are checking for.
900  unsigned int lowerBoundEffort = std::numeric_limits<unsigned int>::max();
901  for (const auto &start : startStates_)
902  {
903  lowerBoundEffort =
904  std::min(lowerBoundEffort, space_->validSegmentCount(start->raw(), state->raw()));
905  }
906 
907  return lowerBoundEffort;
908  }
909 
910  unsigned int RandomGeometricGraph::inadmissibleEffortToCome(const std::shared_ptr<State> &state) const
911  {
912  auto inadmissibleEffort = std::numeric_limits<unsigned int>::max();
913  for (const auto &start : startStates_)
914  {
915  inadmissibleEffort =
916  std::min(inadmissibleEffort, space_->validSegmentCount(start->raw(), state->raw()));
917  }
918  return inadmissibleEffort;
919  }
920 
921  ompl::base::Cost RandomGeometricGraph::lowerBoundCostToGo(const std::shared_ptr<State> &state) const
922  {
923  // Get the preferred goal for this state.
924  auto bestCost = objective_->infiniteCost();
925  for (const auto &goal : goalStates_)
926  {
927  bestCost =
928  objective_->betterCost(objective_->motionCostHeuristic(state->raw(), goal->raw()), bestCost);
929  }
930 
931  return bestCost;
932  }
933 
934  void RandomGeometricGraph::initializeState(const std::shared_ptr<State> &state)
935  {
936  // Set the lower bounds.
937  state->setLowerBoundCostToCome(lowerBoundCostToCome(state));
938  state->setLowerBoundEffortToCome(lowerBoundEffortToCome(state));
939  state->setInadmissibleEffortToCome(inadmissibleEffortToCome(state));
940  state->setLowerBoundCostToGo(lowerBoundCostToGo(state));
941 
942  // Set the current cost to come.
943  if (isStart(state))
944  {
945  state->setCurrentCostToCome(objective_->identityCost());
946  }
947  else
948  {
949  state->setCurrentCostToCome(objective_->infiniteCost());
950  }
951 
952  // Set the estimated heuristics.
953  if (isGoal(state))
954  {
955  state->setAdmissibleCostToGo(objective_->identityCost());
956  state->setEstimatedCostToGo(objective_->identityCost());
957  state->setEstimatedEffortToGo(0u);
958  }
959  else
960  {
961  state->setAdmissibleCostToGo(objective_->infiniteCost());
962  state->setEstimatedCostToGo(objective_->infiniteCost());
963  state->setEstimatedEffortToGo(std::numeric_limits<std::size_t>::max());
964  }
965  }
966 
967  std::size_t RandomGeometricGraph::computeNumberOfNeighbors(std::size_t numInformedSamples) const
968  {
969  return std::ceil(radiusFactor_ * k_rgg_ * std::log(static_cast<double>(numInformedSamples)));
970  }
971 
972  double RandomGeometricGraph::computeRadius(std::size_t numInformedSamples) const
973  {
974  // Compute and return the radius. Note to self: double / int -> double. You looked it up. It's fine.
975  // RRT*
976  // return radiusFactor_ *
977  // std::pow(2.0 * (1.0 + 1.0 / dimension_) *
978  // (sampler_->getInformedMeasure(solutionCost_) / unitNBallMeasure_) *
979  // (std::log(static_cast<double>(numInformedSamples)) / numInformedSamples),
980  // 1.0 / dimension_);
981 
982  // FMT*
983  // return 2.0 * radiusFactor_ *
984  // std::pow((1.0 / dimension_) * (sampler_->getInformedMeasure(solutionCost_) /
985  // unitNBallMeasure_) *
986  // (std::log(static_cast<double>(numInformedSamples)) / numInformedSamples),
987  // 1.0 / dimension_);
988 
989  // PRM*
990  return radiusFactor_ * 2.0 *
991  std::pow((1.0 + 1.0 / dimension_) *
992  (sampler_->getInformedMeasure(solutionCost_) / unitNBallMeasure_) *
993  (std::log(static_cast<double>(numInformedSamples)) / numInformedSamples),
994  1.0 / dimension_);
995  }
996 
997  } // namespace eitstar
998 
999  } // namespace geometric
1000 
1001 } // namespace ompl
PlannerTerminationCondition plannerAlwaysTerminatingCondition()
Simple termination condition that always returns true. The termination condition will always be met.
bool haveMoreStartStates() const
Check if there are more potential start states.
Definition: Planner.cpp:339
Helper class to extract valid start & goal states. Usually used internally by planners.
Definition: Planner.h:141
bool hasStartState() const
Returns whether a start state is available.
void enableMultiquery(bool multiquery)
Enable multiquery usage of the graph.
void setMaxNumberOfGoals(unsigned int maxNumberOfGoals)
Sets the maximum number of goals EIT* will sample from sampleable goal regions.
void setUseKNearest(bool useKNearest)
Set whether to use a k-nearest connection model. If false, it uses an r-disc model.
void updateStartAndGoalStates(const ompl::base::PlannerTerminationCondition &terminationCondition, ompl::base::PlannerInputStates *inputStates)
Adds new starts and goals to the graph if available and creates a new informed sampler if necessary.
unsigned int getMaxNumberOfGoals() const
Returns the maximum number of goals EIT* will sample from sampleable goal regions.
RandomGeometricGraph(const std::shared_ptr< ompl::base::SpaceInformation > &spaceInfo, const ompl::base::Cost &solutionCost)
Constructs a random geometric graph with the given space information and reference to the current sol...
bool isMultiqueryEnabled() const
Returns Whether multiquery usage of the graph is enabled.
Definition of an abstract state.
Definition: State.h:113
bool getUseKNearest() const
Returns whether the graph uses a k-nearest connection model. If false, it uses an r-disc model.
unsigned int inadmissibleEffortToCome(const std::shared_ptr< State > &state) const
Returns the inadmissible effort to come.
bool isGoal(const std::shared_ptr< State > &state) const
Returns whether the given state is a goal state.
unsigned int getNumberOfValidSamples() const
Returns the number of valid samples.
void log(const char *file, int line, LogLevel level, const char *m,...)
Root level logging function. This should not be invoked directly, but rather used via a logging macro...
Definition: Console.cpp:120
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.
std::size_t getTag() const
Returns the tag of the current RGG.
unsigned int getEffortThreshold() const
Gets the effort threshold.
unsigned int getNumberOfSampledStates() const
Returns the number of sampled states.
bool addStates(std::size_t numStates, const ompl::base::PlannerTerminationCondition &terminationCondition)
Samples random states and adds them to the graph.
const std::vector< std::shared_ptr< State > > & getStartStates() const
Returns the start states.
double unitNBallMeasure(unsigned int N)
The Lebesgue measure (i.e., "volume") of an n-dimensional ball with a unit radius.
const std::vector< std::shared_ptr< State > > & getGoalStates() const
Returns the goal states.
Encapsulate a termination condition for a motion planner. Planners will call operator() to decide whe...
const State * nextStart()
Return the next valid start state or nullptr if no more valid start states are available.
Definition: Planner.cpp:228
void enablePruning(bool prune)
Enable pruning of the graph.
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.
std::shared_ptr< State > registerStartState(const ompl::base::State *start)
Sets the start state.
void prune()
Prunes the graph of states that can not improve the current solution.
const State * nextGoal(const PlannerTerminationCondition &ptc)
Return the next valid goal state or nullptr if no more valid goal states are available....
Definition: Planner.cpp:265
void clearQuery()
Clears all query-specific structures, such as start and goal states.
ompl::base::Cost minPossibleCost() const
Returns the minimum possible cost for the current problem, using admissible cost estimates to calcula...
const OptimizationObjectivePtr & getOptimizationObjective() const
Get the optimization objective to be considered during planning.
void clear()
Clears all internal planner structures but retains settings. Subsequent calls to solve() will start f...
bool hasGoalState() const
Returns whether a goal state is available.
unsigned int getNumberOfNearestNeighborCalls() const
Returns the number of nearest neighbor calls.
void setEffortThreshold(const unsigned int threshold)
Sets the effort threshold.
#define OMPL_WARN(fmt,...)
Log a formatted warning string.
Definition: Console.h:66
std::vector< std::weak_ptr< State > > getNeighbors(const std::shared_ptr< State > &state) const
Returns the neighbors of a state.
std::shared_ptr< State > registerGoalState(const ompl::base::State *goal)
Sets the goal state.
void registerWhitelistedState(const std::shared_ptr< State > &state) const
Registers a whitelisted state.
bool haveMoreGoalStates() const
Check if there are more potential goal states.
Definition: Planner.cpp:346
std::vector< std::shared_ptr< State > > getStates() const
Returns all sampled states (that have not been pruned).
void registerInvalidEdge(const Edge &edge) const
Registers an invalid edge.
bool isPruningEnabled() const
Returns Whether pruning is enabled.
Main namespace. Contains everything in this library.
Definition: AppBase.h:21
double getRadiusFactor() const
Returns the radius factor (eta in the paper).