Loading...
Searching...
No Matches
ImplicitGraph.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 names of the copyright holders nor the names of its
18 * contributors may be used to endorse or promote products derived
19 * from this software without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 * POSSIBILITY OF SUCH DAMAGE.
33 *********************************************************************/
34
35// Authors: Marlin Strub
36#include "ompl/geometric/planners/informedtrees/aitstar/ImplicitGraph.h"
37
38#include <cmath>
39
40#include <boost/math/constants/constants.hpp>
41
42#include "ompl/util/GeometricEquations.h"
43
44namespace ompl
45{
46 namespace geometric
47 {
48 namespace aitstar
49 {
51 : batchId_(1u), solutionCost_(solutionCost)
52 {
53 }
54
55 void ImplicitGraph::setup(const ompl::base::SpaceInformationPtr &spaceInformation,
56 const ompl::base::ProblemDefinitionPtr &problemDefinition,
58 {
59 vertices_.setDistanceFunction([this](const std::shared_ptr<Vertex> &a, const std::shared_ptr<Vertex> &b)
60 { return spaceInformation_->distance(a->getState(), b->getState()); });
61 spaceInformation_ = spaceInformation;
62 problemDefinition_ = problemDefinition;
63 objective_ = problemDefinition->getOptimizationObjective();
64 k_rgg_ = boost::math::constants::e<double>() +
65 (boost::math::constants::e<double>() / spaceInformation->getStateDimension());
67 }
68
70 {
71 batchId_ = 1u;
72 radius_ = std::numeric_limits<double>::infinity();
73 numNeighbors_ = std::numeric_limits<std::size_t>::max();
74 vertices_.clear();
75 startVertices_.clear();
76 goalVertices_.clear();
77 prunedStartVertices_.clear();
78 prunedGoalVertices_.clear();
79 numSampledStates_ = 0u;
80 numValidSamples_ = 0u;
81 }
82
83 void ImplicitGraph::setRewireFactor(double rewireFactor)
84 {
85 rewireFactor_ = rewireFactor;
86 }
87
89 {
90 return rewireFactor_;
91 }
92
93 void ImplicitGraph::setMaxNumberOfGoals(unsigned int maxNumberOfGoals)
94 {
95 maxNumGoals_ = maxNumberOfGoals;
96 }
97
99 {
100 return maxNumGoals_;
101 }
102
103 void ImplicitGraph::setUseKNearest(bool useKNearest)
104 {
105 useKNearest_ = useKNearest;
106 }
107
109 {
110 return useKNearest_;
111 }
112
114 {
115 // Create a vertex corresponding to this state.
116 auto startVertex = std::make_shared<Vertex>(spaceInformation_, problemDefinition_, batchId_);
117
118 // Copy the state into the vertex's state.
119 spaceInformation_->copyState(startVertex->getState(), startState);
120
121 // By definition, this has identity cost-to-come.
122 startVertex->setCostToComeFromStart(objective_->identityCost());
123
124 // Add the start vertex to the set of vertices.
125 vertices_.add(startVertex);
126
127 // Remember it as a start vertex.
128 startVertices_.emplace_back(startVertex);
129 }
130
132 {
133 // Create a vertex corresponding to this state.
134 auto goalVertex = std::make_shared<Vertex>(spaceInformation_, problemDefinition_, batchId_);
135
136 // Copy the state into the vertex's state.
137 spaceInformation_->copyState(goalVertex->getState(), goalState);
138
139 // Add the goal vertex to the set of vertices.
140 vertices_.add(goalVertex);
141
142 // Remember it as a goal vertex.
143 goalVertices_.emplace_back(goalVertex);
144 }
145
147 {
148 return !startVertices_.empty();
149 }
150
152 {
153 return !goalVertices_.empty();
154 }
155
156 void
159 {
160 // We need to keep track whether a new goal and/or a new start has been added.
161 bool addedNewGoalState = false;
162 bool addedNewStartState = false;
163
164 // First update the goals. We have to call inputStates->nextGoal(terminationCondition) at least once
165 // (regardless of the return value of inputStates->moreGoalStates()) in case the termination condition
166 // wants us to wait for a goal.
167 do
168 {
169 // Get a new goal. If there are none, or the underlying state is invalid this will be a nullptr.
170 auto newGoalState = inputStates->nextGoal(terminationCondition);
171
172 // If there was a new valid goal, register it as such and remember that a goal has been added.
173 if (static_cast<bool>(newGoalState))
174 {
175 registerGoalState(newGoalState);
176 addedNewGoalState = true;
177 }
178
179 } while (inputStates->haveMoreGoalStates() && goalVertices_.size() <= maxNumGoals_);
180
181 // Having updated the goals, we now update the starts.
182 while (inputStates->haveMoreStartStates())
183 {
184 // Get the next start. The returned pointer can be a nullptr (if the state is invalid).
185 auto newStartState = inputStates->nextStart();
186
187 // If there is a new valid start, register it as such and remember that a start has been added.
188 if (static_cast<bool>(newStartState))
189 {
190 registerStartState(newStartState);
191 addedNewStartState = true;
192 }
193 }
194
195 // If we added a new start and have previously pruned goals, we might want to add the goals back.
196 if (addedNewStartState && !prunedGoalVertices_.empty())
197 {
198 // Keep track of the pruned goal vertices that have been revived.
199 std::vector<std::vector<std::shared_ptr<Vertex>>::iterator> revivedGoals;
200
201 // Let's see if the pruned goal is close enough to any new start to revive it..
202 for (auto it = prunedGoalVertices_.begin(); it != prunedGoalVertices_.end(); ++it)
203 {
204 // Loop over all start states to get the best cost.
205 auto heuristicCost = objective_->infiniteCost();
206 for (const auto &start : startVertices_)
207 {
208 heuristicCost = objective_->betterCost(
209 heuristicCost, objective_->motionCostHeuristic(start->getState(), (*it)->getState()));
210 }
211
212 // If this goal can possibly improve the current solution, add it back to the graph.
213 if (objective_->isCostBetterThan(heuristicCost, solutionCost_))
214 {
215 registerGoalState((*it)->getState());
216 addedNewGoalState = true;
217 revivedGoals.emplace_back(it);
218 }
219 }
220
221 // Remove all revived goals from the pruned goals.
222 for (const auto &revivedGoal : revivedGoals)
223 {
224 std::iter_swap(revivedGoal, prunedGoalVertices_.rbegin());
225 prunedGoalVertices_.pop_back();
226 }
227 }
228
229 // If we added a new goal and have previously pruned starts, we might want to add the starts back.
230 if (addedNewGoalState && !prunedStartVertices_.empty())
231 {
232 // Keep track of the pruned goal vertices that have been revived.
233 std::vector<std::vector<std::shared_ptr<Vertex>>::iterator> revivedStarts;
234
235 // Let's see if the pruned start is close enough to any new goal to revive it..
236 for (auto it = prunedStartVertices_.begin(); it != prunedStartVertices_.end(); ++it)
237 {
238 // Loop over all start states to get the best cost.
239 auto heuristicCost = objective_->infiniteCost();
240 for (const auto &goal : goalVertices_)
241 {
242 heuristicCost = objective_->betterCost(
243 heuristicCost, objective_->motionCostHeuristic(goal->getState(), (*it)->getState()));
244 }
245
246 // If this goal can possibly improve the current solution, add it back to the graph.
247 if (objective_->isCostBetterThan(heuristicCost, solutionCost_))
248 {
249 registerStartState((*it)->getState());
250 addedNewStartState = true;
251 revivedStarts.emplace_back(it);
252 }
253 }
254
255 // Remove all revived goals from the pruned goals.
256 for (const auto &revivedStart : revivedStarts)
257 {
258 std::iter_swap(revivedStart, prunedStartVertices_.rbegin());
259 prunedStartVertices_.pop_back();
260 }
261 }
262
263 if (addedNewGoalState || addedNewStartState)
264 {
265 // Allocate a state sampler if we have at least one start and one goal.
266 if (!startVertices_.empty() && !goalVertices_.empty())
267 {
268 sampler_ = objective_->allocInformedStateSampler(problemDefinition_,
269 std::numeric_limits<unsigned int>::max());
270 }
271 }
272
273 if (!goalVertices_.empty() && startVertices_.empty())
274 {
275 OMPL_WARN("AIT* (ImplicitGraph): The problem has a goal but not a start. AIT* can not find a "
276 "solution since PlannerInputStates provides no method to wait for a valid start state to "
277 "appear.");
278 }
279 }
280
281 std::size_t ImplicitGraph::computeNumberOfSamplesInInformedSet() const
282 {
283 // Loop over all vertices and count the ones in the informed set.
284 std::size_t numberOfSamplesInInformedSet{0u};
285 for (const auto &vertex : getVertices())
286 {
287 // Get the best cost to come from any start.
288 auto costToCome = objective_->infiniteCost();
289 for (const auto &start : startVertices_)
290 {
291 costToCome = objective_->betterCost(
292 costToCome, objective_->motionCostHeuristic(start->getState(), vertex->getState()));
293 }
294
295 // Get the best cost to go to any goal.
296 auto costToGo = objective_->infiniteCost();
297 for (const auto &goal : goalVertices_)
298 {
299 costToGo = objective_->betterCost(
300 costToCome, objective_->motionCostHeuristic(vertex->getState(), goal->getState()));
301 }
302
303 // If this can possibly improve the current solution, it is in the informed set.
304 if (objective_->isCostBetterThan(objective_->combineCosts(costToCome, costToGo), solutionCost_))
305 {
306 ++numberOfSamplesInInformedSet;
307 }
308 }
309
310 return numberOfSamplesInInformedSet;
311 }
312
313 bool ImplicitGraph::addSamples(std::size_t numNewSamples,
314 const ompl::base::PlannerTerminationCondition &terminationCondition)
315 {
316 // If there are no states to be added, then there's nothing to do.
317 if (numNewSamples == 0u)
318 {
319 return true;
320 }
321
322 // Ensure there's enough space for the new samples.
323 newSamples_.reserve(numNewSamples);
324
325 do
326 {
327 // Create a new vertex.
328 newSamples_.emplace_back(std::make_shared<Vertex>(spaceInformation_, problemDefinition_, batchId_));
329
330 bool foundValidSample = false;
331 do
332 {
333 // Sample the associated state uniformly within the informed set.
334 sampler_->sampleUniform(newSamples_.back()->getState(), solutionCost_);
335
336 // Count how many states we've checked.
337 ++numSampledStates_;
338
339 // Check if the sample is valid.
340 foundValidSample =
341 spaceInformation_->getStateValidityChecker()->isValid(newSamples_.back()->getState());
342 } while (!foundValidSample && !terminationCondition);
343
344 // The sample can be invalid if the termination condition is met.
345 if (foundValidSample)
346 {
347 // If this state happens to satisfy the goal condition, add it as such.
348 if (problemDefinition_->getGoal()->isSatisfied(newSamples_.back()->getState()))
349 {
350 goalVertices_.emplace_back(newSamples_.back());
351 newSamples_.back()->setCostToComeFromGoal(objective_->identityCost());
352 }
353
354 ++numValidSamples_;
355 }
356 else
357 {
358 // Remove the invalid sample.
359 newSamples_.pop_back();
360 }
361 } while (newSamples_.size() < numNewSamples && !terminationCondition);
362
363 if (newSamples_.size() == numNewSamples)
364 {
365 // First get the number of samples inside the informed set.
366 auto numSamplesInInformedSet = computeNumberOfSamplesInInformedSet();
367
368 if (useKNearest_)
369 {
370 numNeighbors_ = computeNumberOfNeighbors(numSamplesInInformedSet + numNewSamples -
371 startVertices_.size() - goalVertices_.size());
372 }
373 else
374 {
375 radius_ = computeConnectionRadius(numSamplesInInformedSet + numNewSamples -
376 startVertices_.size() - goalVertices_.size());
377 }
378
379 // Add all new vertices to the nearest neighbor structure.
380 vertices_.add(newSamples_);
381 newSamples_.clear();
382
383 // Update the batch id.
384 ++batchId_;
385
386 return true;
387 }
388
389 return false;
390 }
391
393 {
394 return vertices_.size();
395 }
396
398 {
399 return radius_;
400 }
401
402 std::vector<std::shared_ptr<Vertex>>
403 ImplicitGraph::getNeighbors(const std::shared_ptr<Vertex> &vertex) const
404 {
405 // Return cached neighbors if available.
406 if (vertex->hasCachedNeighbors())
407 {
408 return vertex->getNeighbors();
409 }
410 else
411 {
412 ++numNearestNeighborsCalls_;
413 std::vector<std::shared_ptr<Vertex>> neighbors{};
414 if (useKNearest_)
415 {
416 vertices_.nearestK(vertex, numNeighbors_, neighbors);
417 }
418 else
419 {
420 vertices_.nearestR(vertex, radius_, neighbors);
421 }
422 vertex->cacheNeighbors(neighbors);
423 return neighbors;
424 }
425 }
426
427 bool ImplicitGraph::isStart(const std::shared_ptr<Vertex> &vertex) const
428 {
429 for (const auto &start : startVertices_)
430 {
431 if (vertex->getId() == start->getId())
432 {
433 return true;
434 }
435 }
436 return false;
437 }
438
439 bool ImplicitGraph::isGoal(const std::shared_ptr<Vertex> &vertex) const
440 {
441 for (const auto &goal : goalVertices_)
442 {
443 if (vertex->getId() == goal->getId())
444 {
445 return true;
446 }
447 }
448 return false;
449 }
450
451 const std::vector<std::shared_ptr<Vertex>> &ImplicitGraph::getStartVertices() const
452 {
453 return startVertices_;
454 }
455
456 const std::vector<std::shared_ptr<Vertex>> &ImplicitGraph::getGoalVertices() const
457 {
458 return goalVertices_;
459 }
460
461 std::vector<std::shared_ptr<Vertex>> ImplicitGraph::getVertices() const
462 {
463 std::vector<std::shared_ptr<Vertex>> vertices;
464 vertices_.list(vertices);
465 return vertices;
466 }
467
469 {
470 if (!objective_->isFinite(solutionCost_))
471 {
472 return;
473 }
474
475 std::vector<std::shared_ptr<Vertex>> vertices;
476 vertices_.list(vertices);
477
478 // Prepare the vector of vertices to be pruned.
479 std::vector<std::shared_ptr<Vertex>> verticesToBePruned;
480
481 // Check each vertex whether it can be pruned.
482 for (const auto &vertex : vertices)
483 {
484 // Check if the combination of the admissible costToCome and costToGo estimates results in a path
485 // that is more expensive than the current solution.
486 if (!canPossiblyImproveSolution(vertex))
487 {
488 // We keep track of pruned start and goal vertices. This is because if the user adds start or
489 // goal states after we have pruned start or goal states, we might want to reconsider pruned
490 // start or goal states.
491 if (isGoal(vertex))
492 {
493 prunedGoalVertices_.emplace_back(vertex);
494 }
495 else if (isStart(vertex))
496 {
497 prunedStartVertices_.emplace_back(vertex);
498 }
499 verticesToBePruned.emplace_back(vertex);
500 }
501 }
502
503 // Remove all vertices to be pruned.
504 for (const auto &vertex : verticesToBePruned)
505 {
506 // Remove it from both search trees.
507 if (vertex->hasReverseParent())
508 {
509 vertex->getReverseParent()->removeFromReverseChildren(vertex->getId());
510 vertex->resetReverseParent();
511 }
512 vertex->invalidateReverseBranch();
513 if (vertex->hasForwardParent())
514 {
515 vertex->getForwardParent()->removeFromForwardChildren(vertex->getId());
516 vertex->resetForwardParent();
517 }
518 vertex->invalidateForwardBranch();
519
520 // Remove it from the nearest neighbor struct.
521 vertices_.remove(vertex);
522 }
523
524 // Assert that the forward and reverse queue are empty?
525 }
526
528 {
529 return numSampledStates_;
530 }
531
533 {
534 return numValidSamples_;
535 }
536
538 {
539 // Each sampled state is checked for collision. Only sampled states are checked for collision (number of
540 // collision checked edges don't count here.)
541 return numSampledStates_;
542 }
543
545 {
546 return numNearestNeighborsCalls_;
547 }
548
549 double ImplicitGraph::computeConnectionRadius(std::size_t numSamples) const
550 {
551 // Define the dimension as a helper variable.
552 auto dimension = static_cast<double>(spaceInformation_->getStateDimension());
553
554 // Compute the RRT* factor.
555 return rewireFactor_ *
556 std::pow(2.0 * (1.0 + 1.0 / dimension) *
557 (sampler_->getInformedMeasure(solutionCost_) /
558 unitNBallMeasure(spaceInformation_->getStateDimension())) *
559 (std::log(static_cast<double>(numSamples)) / static_cast<double>(numSamples)),
560 1.0 / dimension);
561
562 // // Compute the FMT* factor.
563 // return 2.0 * rewireFactor_ *
564 // std::pow((1.0 / dimension) *
565 // (sampler_->getInformedMeasure(*solutionCost_.lock()) /
566 // unitNBallMeasure(spaceInformation_->getStateDimension())) *
567 // (std::log(static_cast<double>(numSamples)) / numSamples),
568 // 1.0 / dimension);
569 }
570
571 std::size_t ImplicitGraph::computeNumberOfNeighbors(std::size_t numSamples) const
572 {
573 return std::ceil(rewireFactor_ * k_rgg_ * std::log(static_cast<double>(numSamples)));
574 }
575
576 bool ImplicitGraph::canPossiblyImproveSolution(const std::shared_ptr<Vertex> &vertex) const
577 {
578 // Get the preferred start for this vertex.
579 auto bestCostToCome = objective_->infiniteCost();
580 for (const auto &start : startVertices_)
581 {
582 auto costToCome = objective_->motionCostHeuristic(start->getState(), vertex->getState());
583 if (objective_->isCostBetterThan(costToCome, bestCostToCome))
584 {
585 bestCostToCome = costToCome;
586 }
587 }
588
589 // Check if the combination of the admissible costToCome and costToGo estimates results in a path
590 // that is more expensive than the current solution.
591 return objective_->isCostBetterThan(
592 objective_->combineCosts(
593 bestCostToCome, objective_->costToGo(vertex->getState(), problemDefinition_->getGoal().get())),
594 solutionCost_);
595 }
596
597 } // namespace aitstar
598
599 } // namespace geometric
600
601} // namespace ompl
Definition of a cost value. Can represent the cost of a motion or the cost of a state.
Definition Cost.h:48
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...
Definition of an abstract state.
Definition State.h:50
bool isStart(const std::shared_ptr< Vertex > &vertex) const
Checks whether the vertex is a start vertex.
bool hasAStartState() const
Returns whether the graph has a goal state.
std::size_t getNumberOfNearestNeighborCalls() const
Get the number of nearest neighbor calls.
const std::vector< std::shared_ptr< Vertex > > & getStartVertices() const
Get the start vertices.
std::vector< std::shared_ptr< Vertex > > getVertices() const
Get all vertices.
void setMaxNumberOfGoals(unsigned int maxNumberOfGoals)
Set the maximum number of goals AIT* will sample from sampleable goal regions.
bool isGoal(const std::shared_ptr< Vertex > &vertex) const
Checks whether the vertex is a goal vertex.
double getRewireFactor() const
Get the reqire factor of the RGG.
std::size_t getNumberOfStateCollisionChecks() const
Get the number of state collision checks.
std::size_t getNumberOfValidSamples() const
Returns the total number of valid samples found.
void prune()
Prune all samples that can not contribute to a solution better than the current one.
void updateStartAndGoalStates(const ompl::base::PlannerTerminationCondition &terminationCondition, ompl::base::PlannerInputStates *inputStates)
Adds new start and goals to the graph if avavilable and creates a new informed sampler if necessary.
void registerStartState(const ompl::base::State *const startState)
Registers a state as a start state.
void clear()
Resets the graph to its construction state, without resetting options.
void registerGoalState(const ompl::base::State *const goalState)
Registers a state as a goal state.
const std::vector< std::shared_ptr< Vertex > > & getGoalVertices() const
Get the goal vertices.
double getConnectionRadius() const
Gets the RGG connection radius.
bool addSamples(std::size_t numNewSamples, const ompl::base::PlannerTerminationCondition &terminationCondition)
Adds a batch of samples and returns the samples it has added.
void setup(const ompl::base::SpaceInformationPtr &spaceInformation, const ompl::base::ProblemDefinitionPtr &problemDefinition, ompl::base::PlannerInputStates *inputStates)
The setup method for the graph. Needed to have it on the stack.
ImplicitGraph(const ompl::base::Cost &solutionCost)
Constructs an implicit graph.
std::vector< std::shared_ptr< Vertex > > getNeighbors(const std::shared_ptr< Vertex > &vertex) const
Get neighbors of a vertex.
std::size_t getNumVertices() const
Gets the number of samples in the graph.
bool hasAGoalState() const
Returns whether the graph has a goal state.
bool getUseKNearest() const
Whether the graph uses a k-nearest connection model. If false, it uses an r-disc model.
std::size_t getNumberOfSampledStates() const
Returns the total number of sampled states.
void setRewireFactor(double rewireFactor)
Set the rewire factor of the RGG.
unsigned int getMaxNumberOfGoals() const
Get the maximum number of goals AIT* will sample from sampleable goal regions.
void setUseKNearest(bool useKNearest)
Whether to use a k-nearest connection model. If false, it uses an r-disc model.
#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.