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_ =
315 objective_->allocInformedStateSampler(problem_, std::numeric_limits<unsigned int>::max());
316 }
317 }
318
319 if (!goalStates_.empty() && startStates_.empty())
320 {
321 OMPL_WARN("EIT*: The problem has a goal but not a start. EIT* can not find a solution since "
322 "PlannerInputStates provides no method to wait for a valid start state to appear.");
323 }
324
325 // Compute the minimum possible cost for this problem given the start and goal states and an admissible
326 // cost heuristic.
327 minPossibleCost_ = objective_->infiniteCost();
328 for (const auto &start : startStates_)
329 {
330 for (const auto &goal : goalStates_)
331 {
332 minPossibleCost_ = objective_->betterCost(
333 minPossibleCost_, objective_->motionCostHeuristic(start->raw(), goal->raw()));
334 }
335 }
336
337 std::vector<std::shared_ptr<State>> samples;
338 samples_.list(samples);
339 for (auto &state : samples)
340 {
341 initializeState(state);
342 }
343 }
344
346 {
347 return minPossibleCost_;
348 }
349
351 {
352 radiusFactor_ = factor;
353 }
354
356 {
357 return radiusFactor_;
358 }
359
360 void RandomGeometricGraph::setEffortThreshold(unsigned int threshold)
361 {
362 effortThreshold_ = threshold;
363 }
364
366 {
367 return effortThreshold_;
368 }
369
370 const std::vector<std::shared_ptr<State>> &RandomGeometricGraph::getStartStates() const
371 {
372 return startStates_;
373 }
374
375 const std::vector<std::shared_ptr<State>> &RandomGeometricGraph::getGoalStates() const
376 {
377 return goalStates_;
378 }
379
381 {
382 return numSampledStates_;
383 }
384
386 {
387 return numValidSamples_;
388 }
389
391 {
392 return numNearestNeighborCalls_;
393 }
394
396 {
397 return !startStates_.empty();
398 }
399
401 {
402 return !goalStates_.empty();
403 }
404
405 bool RandomGeometricGraph::isStart(const std::shared_ptr<State> &state) const
406 {
407 return std::any_of(startStates_.begin(), startStates_.end(),
408 [&state](const auto &start) { return state->getId() == start->getId(); });
409 }
410
411 bool RandomGeometricGraph::isGoal(const std::shared_ptr<State> &state) const
412 {
413 return std::any_of(goalStates_.begin(), goalStates_.end(),
414 [&state](const auto &goal) { return state->getId() == goal->getId(); });
415 }
416
417 std::vector<std::shared_ptr<State>> RandomGeometricGraph::getStates() const
418 {
419 std::vector<std::shared_ptr<State>> samples;
420 samples_.list(samples);
421 return samples;
422 }
423
425 {
426 // Remove the edge from the caches (using the erase-remove idiom). Take this opportunity to remove
427 // expired states from the cache as well.
428 auto &sourceNeighbors = edge.source->neighbors_.second;
429 auto &targetNeighbors = edge.target->neighbors_.second;
430
431 sourceNeighbors.erase(
432 std::remove_if(sourceNeighbors.begin(), sourceNeighbors.end(), [&edge](const auto &neighbor)
433 { return neighbor.expired() || neighbor.lock()->getId() == edge.target->getId(); }),
434 sourceNeighbors.end());
435
436 targetNeighbors.erase(
437 std::remove_if(targetNeighbors.begin(), targetNeighbors.end(), [&edge](const auto &neighbor)
438 { return neighbor.expired() || neighbor.lock()->getId() == edge.source->getId(); }),
439 targetNeighbors.end());
440 }
441
443 {
444 return tag_;
445 }
446
448 {
449 // Allocate the start state.
450 auto start = std::make_shared<State>(spaceInfo_, objective_);
451 spaceInfo_->copyState(start->raw(), state);
452
453 // Hold onto it.
454 startStates_.emplace_back(start);
455 samples_.add(start);
456
457 // Initialize the state.
458 initializeState(start);
459
460 // Ensure its lower bounds are correct.
461 assert(objective_->isCostEquivalentTo(start->getLowerBoundCostToCome(), objective_->identityCost()));
462 assert(start->getLowerBoundEffortToCome() == 0u);
463
464 return start;
465 }
466
468 {
469 // Allocate the goal state.
470 auto goal = std::make_shared<State>(spaceInfo_, objective_);
471 spaceInfo_->copyState(goal->raw(), state);
472
473 // Hold onto it.
474 goalStates_.emplace_back(goal);
475 samples_.add(goal);
476
477 // Initialize the state.
478 initializeState(goal);
479
480 // Ensure its lower bounds are correct.
481 assert(objective_->isCostEquivalentTo(goal->getLowerBoundCostToGo(), objective_->identityCost()));
482
483 return goal;
484 }
485
486 void RandomGeometricGraph::registerWhitelistedState(const std::shared_ptr<State> &state) const
487 {
488 whitelistedStates_.push_back(state);
489 }
490
491 std::shared_ptr<State>
492 RandomGeometricGraph::getNewSample(const ompl::base::PlannerTerminationCondition &terminationCondition)
493 {
494 // Allocate a new state.
495 auto state = std::make_shared<State>(spaceInfo_, objective_);
496
497 // Create the requested number of new states.
498 if (currentNumSamples_ < buffer_.size())
499 {
500 // This does at the moment not deal with the fact that samples might become invalid
501 // also, these samples might not be informed samples
502 state = buffer_[currentNumSamples_];
503 }
504 else
505 {
506 bool foundValidSample = false;
507 do // Sample randomly until a valid state is found.
508 {
509 if (isMultiqueryEnabled_)
510 {
511 // If we are doing multiquery planning, we sample uniformly, and reject samples that can
512 // not improve the solution. This means that we need to sample the whole space, and add
513 // the samples to the buffer
514 sampler_->sampleUniform(state->raw(), objective_->infiniteCost());
515 }
516 else
517 {
518 // In case we are not doing multiquery planning, we can still directly sample the informed
519 // set.
520 sampler_->sampleUniform(state->raw(), solutionCost_);
521 }
522
523 ++numSampledStates_;
524 // Check if the sample is valid.
525 foundValidSample = spaceInfo_->isValid(state->raw());
526 } while (!foundValidSample && !terminationCondition);
527
528 // The sample is invalid, but we have to return to respect the termination condition.
529 if (!foundValidSample)
530 {
531 return nullptr;
532 }
533
534 // We've found a valid sample.
535 ++numValidSamples_;
536
537 // and we add it to the buffer
538 buffer_.emplace_back(state);
539 }
540
541 ++currentNumSamples_;
542 return state;
543 }
544
545 bool RandomGeometricGraph::addStates(std::size_t numNewStates,
546 const ompl::base::PlannerTerminationCondition &terminationCondition)
547 {
548 // Assert sanity of used variables.
549 assert(sampler_);
550 assert(objective_);
551
552 if (numNewStates == 0u)
553 {
554 return true;
555 }
556
557 // Create the requested number of new states.
558 do
559 {
560 do
561 {
562 // This call can return nullptr if the termination condition is met
563 // before a valid sample is found.
564 const auto state = getNewSample(terminationCondition);
565
566 // Since we do not do informed sampling, we need to check if the sample could improve
567 // the current solution.
568 if (state != nullptr && !canBePruned(state))
569 {
570 newSamples_.emplace_back(state);
571
572 // Add this state to the goal states if it is a goal.
573 if (problem_->getGoal()->isSatisfied(state->raw()))
574 {
575 goalStates_.emplace_back(state);
576 }
577 break;
578 }
579 } while (!terminationCondition);
580 } while (newSamples_.size() < numNewStates && !terminationCondition);
581
582 // Add the new states to the samples.
583 if (newSamples_.size() == numNewStates)
584 {
585 // Count the number of informed states before adding the new states. This saves some counting,
586 // because all new states will be in the informed set, and its known how many of them will be added.
587 // If pruning is enabled, we can do this now. After pruning all remaining states are in the informed
588 // set.
589
590 auto numInformedSamples{0u};
591
592 if (isPruningEnabled_)
593 {
594 prune();
595 numInformedSamples = samples_.size();
596 }
597 else
598 {
599 numInformedSamples = countSamplesInInformedSet();
600 }
601
602 samples_.add(newSamples_);
603
604 // only initialize samples after all states have been added to the graph
605 for (auto &sample : newSamples_)
606 {
607 initializeState(sample);
608 }
609
610 newSamples_.clear();
611
612 // Update the radius by considering all informed states.
613 if (useKNearest_)
614 {
615 numNeighbors_ = computeNumberOfNeighbors(numInformedSamples + numNewStates);
616 }
617 else
618 {
619 radius_ = computeRadius(numInformedSamples + numNewStates);
620 }
621
622 // Update the tag.
623 ++tag_;
624
625 return true;
626 }
627 return false;
628 }
629
631 {
632 isPruningEnabled_ = prune;
633 }
634
636 {
637 return isPruningEnabled_;
638 }
639
641 {
642 isMultiqueryEnabled_ = multiquery;
643 }
644
646 {
647 return isMultiqueryEnabled_;
648 }
649
651 {
652 useKNearest_ = useKNearest;
653 }
654
656 {
657 return useKNearest_;
658 }
659
660 void RandomGeometricGraph::setMaxNumberOfGoals(unsigned int maxNumberOfGoals)
661 {
662 maxNumGoals_ = maxNumberOfGoals;
663 }
664
666 {
667 return maxNumGoals_;
668 }
669
670 std::vector<std::weak_ptr<State>>
671 RandomGeometricGraph::getNeighbors(const std::shared_ptr<State> &state) const
672 {
673 assert(state);
674
675 // If the neighbors cache of the vertex isn't up to date, update it.
676 if (state->neighbors_.first != tag_)
677 {
678 // copy the whitelisted vertices
679 std::vector<std::shared_ptr<State>> whitelistedNeighbors;
680 if (isMultiqueryEnabled_)
681 {
682 std::vector<std::shared_ptr<State>> samples;
683 samples_.list(samples);
684
685 if (std::find(samples.begin(), samples.end(), state) != samples.end())
686 {
687 std::copy_if(samples.begin(), samples.end(), std::back_inserter(whitelistedNeighbors),
688 [&state](const auto v) { return state->isWhitelisted(v); });
689 }
690 }
691
692 // The cache is invalid, let's clear all vertices.
693 state->neighbors_.second.clear();
694
695 // Get the neighbors by performing a nearest neighbor search.
696 std::vector<std::shared_ptr<State>> neighbors;
697 if (useKNearest_)
698 {
699 samples_.nearestK(state, numNeighbors_, neighbors);
700 }
701 else
702 {
703 samples_.nearestR(state, radius_, neighbors);
704 }
705
706 // add whitelisted neighbours to the vector even if they are above the radius
707 if (isMultiqueryEnabled_)
708 {
709 std::copy_if(whitelistedNeighbors.begin(), whitelistedNeighbors.end(),
710 std::back_inserter(neighbors), [&neighbors](const auto v)
711 { return std::find(neighbors.begin(), neighbors.end(), v) == neighbors.end(); });
712 }
713
714 // We dont want to connect to blacklisted neighbors and the querying state itself.
715 const auto connectionPredicate = [&state, this](const std::shared_ptr<State> &neighbor)
716 {
717 return !state->isBlacklisted(neighbor) && (state->id_ != neighbor->id_) &&
718 !(isGoal(state) && isGoal(neighbor));
719 };
720
721 // Cache the neighbors that are not blacklisted and not the state itself.
722 std::copy_if(neighbors.begin(), neighbors.end(), std::back_inserter(state->neighbors_.second),
723 connectionPredicate);
724
725 // Update the tag of the cache.
726 state->neighbors_.first = tag_;
727
728 // Increase the counter of nearest neighbor calls.
729 ++numNearestNeighborCalls_;
730 }
731
732 // The cache is now guaranteed to be up to date.
733 auto neighbors = state->neighbors_.second;
734
735 // Add the forward parent and children.
736 if (state->hasForwardVertex())
737 {
738 const auto forwardVertex = state->asForwardVertex();
739
740 // Add the parent.
741 if (auto forwardParent = forwardVertex->getParent().lock())
742 {
743 neighbors.emplace_back(forwardParent->getState());
744 }
745
746 // Add the children.
747 const auto &forwardChildren = forwardVertex->getChildren();
748 for (const auto &child : forwardChildren)
749 {
750 neighbors.emplace_back(child->getState());
751 }
752 }
753
754 // Add the reverse parent and children.
755 if (state->hasReverseVertex())
756 {
757 const auto reverseVertex = state->asReverseVertex();
758
759 // Add the parent.
760 if (auto reverseParent = reverseVertex->getParent().lock())
761 {
762 neighbors.emplace_back(reverseParent->getState());
763 }
764
765 // Add the children.
766 const auto &reverseChildren = reverseVertex->getChildren();
767 for (const auto &child : reverseChildren)
768 {
769 neighbors.emplace_back(child->getState());
770 }
771 }
772
773 return neighbors;
774 }
775
777 {
778 // Is there really no way of doing this without looping over all samples?
779 std::vector<std::shared_ptr<State>> samples;
780 samples_.list(samples);
781
782 // Prepare a vector of samples to be pruned.
783 std::vector<std::shared_ptr<State>> samplesToBePruned;
784
785 // Check each sample if it can be pruned.
786 for (const auto &sample : samples)
787 {
788 if (canBePruned(sample))
789 {
790 if (isStart(sample))
791 {
792 prunedStartStates_.emplace_back(sample);
793 }
794 else if (isGoal(sample))
795 {
796 prunedGoalStates_.emplace_back(sample);
797 }
798
799 samplesToBePruned.emplace_back(sample);
800 }
801 }
802
803 // Remove all samples to be pruned.
804 for (const auto &sample : samplesToBePruned)
805 {
806 // Remove the sample from the graph.
807 samples_.remove(sample);
808
809 // Remove it from both search trees.
810 if (sample->hasForwardVertex())
811 {
812 auto forwardVertex = sample->asForwardVertex();
813 if (auto parent = forwardVertex->getParent().lock())
814 {
815 forwardVertex->resetParent();
816 parent->removeChild(forwardVertex);
817 }
818 }
819 if (sample->hasReverseVertex())
820 {
821 auto reverseVertex = sample->asReverseVertex();
822 if (auto parent = reverseVertex->getParent().lock())
823 {
824 reverseVertex->resetParent();
825 parent->removeChild(reverseVertex);
826 }
827 }
828 }
829
830 // If any sample was pruned, this has invalidated the nearest neighbor cache.
831 if (!samplesToBePruned.empty())
832 {
833 ++tag_;
834 }
835 }
836
837 std::size_t RandomGeometricGraph::countSamplesInInformedSet() const
838 {
839 // Is there really no way of doing this without looping over all samples?
840 std::vector<std::shared_ptr<State>> samples;
841 samples_.list(samples);
842
843 // Count the number of samples that can possibly improve the solution and subtract the start and goal
844 // states from this number, as they're not technically uniformly distributed.
845 return std::count_if(samples.begin(), samples.end(),
846 [this](const auto &sample) { return !canBePruned(sample); }) -
847 startStates_.size() - goalStates_.size();
848 }
849
850 bool RandomGeometricGraph::canBePruned(const std::shared_ptr<State> &state) const
851 {
852 // If we don't have a solution, no state can be pruned.
853 if (!objective_->isFinite(solutionCost_))
854 {
855 return false;
856 }
857 else
858 {
859 // Get the heuristic cost to come.
860 const auto costToCome = lowerBoundCostToCome(state);
861
862 // Get the heuristic cost to go.
863 const auto costToGo = lowerBoundCostToGo(state);
864
865 // Return whether the current solution is better than the lower bound potential solution.
866 return objective_->isCostBetterThan(solutionCost_, objective_->combineCosts(costToCome, costToGo));
867 }
868 }
869
870 ompl::base::Cost RandomGeometricGraph::lowerBoundCostToCome(const std::shared_ptr<State> &state) const
871 {
872 // Get the preferred start for this state.
873 auto bestCost = objective_->infiniteCost();
874 for (const auto &start : startStates_)
875 {
876 bestCost =
877 objective_->betterCost(objective_->motionCostHeuristic(start->raw(), state->raw()), bestCost);
878 }
879
880 return bestCost;
881 }
882
883 unsigned int RandomGeometricGraph::lowerBoundEffortToCome(const std::shared_ptr<State> &state) const
884 {
885 // If we previously validated any states, the lower bound effort to come is 0.
886 // (it is possible to compute a better bound, but empirically, it is not worth the computational
887 // effort it takes to compute this better bound.)
888 if (isMultiqueryEnabled_ && whitelistedStates_.size() != 0u)
889 {
890 return 0u;
891 }
892
893 // If there's no whitelisted states, we can quickly compute a better bound:
894 // The minimum number of collision checks from any of the starts to the state we are checking for.
895 unsigned int lowerBoundEffort = std::numeric_limits<unsigned int>::max();
896 for (const auto &start : startStates_)
897 {
898 lowerBoundEffort =
899 std::min(lowerBoundEffort, space_->validSegmentCount(start->raw(), state->raw()));
900 }
901
902 return lowerBoundEffort;
903 }
904
905 unsigned int RandomGeometricGraph::inadmissibleEffortToCome(const std::shared_ptr<State> &state) const
906 {
907 auto inadmissibleEffort = std::numeric_limits<unsigned int>::max();
908 for (const auto &start : startStates_)
909 {
910 inadmissibleEffort =
911 std::min(inadmissibleEffort, space_->validSegmentCount(start->raw(), state->raw()));
912 }
913 return inadmissibleEffort;
914 }
915
916 ompl::base::Cost RandomGeometricGraph::lowerBoundCostToGo(const std::shared_ptr<State> &state) const
917 {
918 // Get the preferred goal for this state.
919 auto bestCost = objective_->infiniteCost();
920 for (const auto &goal : goalStates_)
921 {
922 bestCost =
923 objective_->betterCost(objective_->motionCostHeuristic(state->raw(), goal->raw()), bestCost);
924 }
925
926 return bestCost;
927 }
928
929 void RandomGeometricGraph::initializeState(const std::shared_ptr<State> &state)
930 {
931 // Set the lower bounds.
932 state->setLowerBoundCostToCome(lowerBoundCostToCome(state));
933 state->setLowerBoundEffortToCome(lowerBoundEffortToCome(state));
934 state->setInadmissibleEffortToCome(inadmissibleEffortToCome(state));
935 state->setLowerBoundCostToGo(lowerBoundCostToGo(state));
936
937 // Set the current cost to come.
938 if (isStart(state))
939 {
940 state->setCurrentCostToCome(objective_->identityCost());
941 }
942 else
943 {
944 state->setCurrentCostToCome(objective_->infiniteCost());
945 }
946
947 // Set the estimated heuristics.
948 if (isGoal(state))
949 {
950 state->setAdmissibleCostToGo(objective_->identityCost());
951 state->setEstimatedCostToGo(objective_->identityCost());
952 state->setEstimatedEffortToGo(0u);
953 }
954 else
955 {
956 state->setAdmissibleCostToGo(objective_->infiniteCost());
957 state->setEstimatedCostToGo(objective_->infiniteCost());
958 state->setEstimatedEffortToGo(std::numeric_limits<std::size_t>::max());
959 }
960 }
961
962 std::size_t RandomGeometricGraph::computeNumberOfNeighbors(std::size_t numInformedSamples) const
963 {
964 return std::ceil(radiusFactor_ * k_rgg_ * std::log(static_cast<double>(numInformedSamples)));
965 }
966
967 double RandomGeometricGraph::computeRadius(std::size_t numInformedSamples) const
968 {
969 // Compute and return the radius. Note to self: double / int -> double. You looked it up. It's fine.
970 // RRT*
971 // return radiusFactor_ *
972 // std::pow(2.0 * (1.0 + 1.0 / dimension_) *
973 // (sampler_->getInformedMeasure(solutionCost_) / unitNBallMeasure_) *
974 // (std::log(static_cast<double>(numInformedSamples)) / numInformedSamples),
975 // 1.0 / dimension_);
976
977 // FMT*
978 // return 2.0 * radiusFactor_ *
979 // std::pow((1.0 / dimension_) * (sampler_->getInformedMeasure(solutionCost_) /
980 // unitNBallMeasure_) *
981 // (std::log(static_cast<double>(numInformedSamples)) / numInformedSamples),
982 // 1.0 / dimension_);
983
984 // PRM*
985 return radiusFactor_ * 2.0 *
986 std::pow((1.0 + 1.0 / dimension_) *
987 (sampler_->getInformedMeasure(solutionCost_) / unitNBallMeasure_) *
988 (std::log(static_cast<double>(numInformedSamples)) / numInformedSamples),
989 1.0 / dimension_);
990 }
991
992 } // namespace eitstar
993
994 } // namespace geometric
995
996} // 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