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