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_, 100u);
269 }
270 }
271
272 if (!goalVertices_.empty() && startVertices_.empty())
273 {
274 OMPL_WARN("AIT* (ImplicitGraph): The problem has a goal but not a start. AIT* can not find a "
275 "solution since PlannerInputStates provides no method to wait for a valid start state to "
276 "appear.");
277 }
278 }
279
280 std::size_t ImplicitGraph::computeNumberOfSamplesInInformedSet() const
281 {
282 // Loop over all vertices and count the ones in the informed set.
283 std::size_t numberOfSamplesInInformedSet{0u};
284 for (const auto &vertex : getVertices())
285 {
286 // Get the best cost to come from any start.
287 auto costToCome = objective_->infiniteCost();
288 for (const auto &start : startVertices_)
289 {
290 costToCome = objective_->betterCost(
291 costToCome, objective_->motionCostHeuristic(start->getState(), vertex->getState()));
292 }
293
294 // Get the best cost to go to any goal.
295 auto costToGo = objective_->infiniteCost();
296 for (const auto &goal : goalVertices_)
297 {
298 costToGo = objective_->betterCost(
299 costToCome, objective_->motionCostHeuristic(vertex->getState(), goal->getState()));
300 }
301
302 // If this can possibly improve the current solution, it is in the informed set.
303 if (objective_->isCostBetterThan(objective_->combineCosts(costToCome, costToGo), solutionCost_))
304 {
305 ++numberOfSamplesInInformedSet;
306 }
307 }
308
309 return numberOfSamplesInInformedSet;
310 }
311
312 bool ImplicitGraph::addSamples(std::size_t numNewSamples,
313 const ompl::base::PlannerTerminationCondition &terminationCondition)
314 {
315 // If there are no states to be added, then there's nothing to do.
316 if (numNewSamples == 0u)
317 {
318 return true;
319 }
320
321 // Ensure there's enough space for the new samples.
322 newSamples_.reserve(numNewSamples);
323
324 do
325 {
326 // Create a new vertex.
327 newSamples_.emplace_back(std::make_shared<Vertex>(spaceInformation_, problemDefinition_, batchId_));
328
329 bool foundValidSample = false;
330 do
331 {
332 // Sample the associated state uniformly within the informed set.
333 sampler_->sampleUniform(newSamples_.back()->getState(), solutionCost_);
334
335 // Count how many states we've checked.
336 ++numSampledStates_;
337
338 // Check if the sample is valid.
339 foundValidSample =
340 spaceInformation_->getStateValidityChecker()->isValid(newSamples_.back()->getState());
341 } while (!foundValidSample && !terminationCondition);
342
343 // The sample can be invalid if the termination condition is met.
344 if (foundValidSample)
345 {
346 // If this state happens to satisfy the goal condition, add it as such.
347 if (problemDefinition_->getGoal()->isSatisfied(newSamples_.back()->getState()))
348 {
349 goalVertices_.emplace_back(newSamples_.back());
350 newSamples_.back()->setCostToComeFromGoal(objective_->identityCost());
351 }
352
353 ++numValidSamples_;
354 }
355 else
356 {
357 // Remove the invalid sample.
358 newSamples_.pop_back();
359 }
360 } while (newSamples_.size() < numNewSamples && !terminationCondition);
361
362 if (newSamples_.size() == numNewSamples)
363 {
364 // First get the number of samples inside the informed set.
365 auto numSamplesInInformedSet = computeNumberOfSamplesInInformedSet();
366
367 if (useKNearest_)
368 {
369 numNeighbors_ = computeNumberOfNeighbors(numSamplesInInformedSet + numNewSamples -
370 startVertices_.size() - goalVertices_.size());
371 }
372 else
373 {
374 radius_ = computeConnectionRadius(numSamplesInInformedSet + numNewSamples -
375 startVertices_.size() - goalVertices_.size());
376 }
377
378 // Add all new vertices to the nearest neighbor structure.
379 vertices_.add(newSamples_);
380 newSamples_.clear();
381
382 // Update the batch id.
383 ++batchId_;
384
385 return true;
386 }
387
388 return false;
389 }
390
392 {
393 return vertices_.size();
394 }
395
397 {
398 return radius_;
399 }
400
401 std::vector<std::shared_ptr<Vertex>>
402 ImplicitGraph::getNeighbors(const std::shared_ptr<Vertex> &vertex) const
403 {
404 // Return cached neighbors if available.
405 if (vertex->hasCachedNeighbors())
406 {
407 return vertex->getNeighbors();
408 }
409 else
410 {
411 ++numNearestNeighborsCalls_;
412 std::vector<std::shared_ptr<Vertex>> neighbors{};
413 if (useKNearest_)
414 {
415 vertices_.nearestK(vertex, numNeighbors_, neighbors);
416 }
417 else
418 {
419 vertices_.nearestR(vertex, radius_, neighbors);
420 }
421 vertex->cacheNeighbors(neighbors);
422 return neighbors;
423 }
424 }
425
426 bool ImplicitGraph::isStart(const std::shared_ptr<Vertex> &vertex) const
427 {
428 for (const auto &start : startVertices_)
429 {
430 if (vertex->getId() == start->getId())
431 {
432 return true;
433 }
434 }
435 return false;
436 }
437
438 bool ImplicitGraph::isGoal(const std::shared_ptr<Vertex> &vertex) const
439 {
440 for (const auto &goal : goalVertices_)
441 {
442 if (vertex->getId() == goal->getId())
443 {
444 return true;
445 }
446 }
447 return false;
448 }
449
450 const std::vector<std::shared_ptr<Vertex>> &ImplicitGraph::getStartVertices() const
451 {
452 return startVertices_;
453 }
454
455 const std::vector<std::shared_ptr<Vertex>> &ImplicitGraph::getGoalVertices() const
456 {
457 return goalVertices_;
458 }
459
460 std::vector<std::shared_ptr<Vertex>> ImplicitGraph::getVertices() const
461 {
462 std::vector<std::shared_ptr<Vertex>> vertices;
463 vertices_.list(vertices);
464 return vertices;
465 }
466
468 {
469 if (!objective_->isFinite(solutionCost_))
470 {
471 return;
472 }
473
474 std::vector<std::shared_ptr<Vertex>> vertices;
475 vertices_.list(vertices);
476
477 // Prepare the vector of vertices to be pruned.
478 std::vector<std::shared_ptr<Vertex>> verticesToBePruned;
479
480 // Check each vertex whether it can be pruned.
481 for (const auto &vertex : vertices)
482 {
483 // Check if the combination of the admissible costToCome and costToGo estimates results in a path
484 // that is more expensive than the current solution.
485 if (!canPossiblyImproveSolution(vertex))
486 {
487 // We keep track of pruned start and goal vertices. This is because if the user adds start or
488 // goal states after we have pruned start or goal states, we might want to reconsider pruned
489 // start or goal states.
490 if (isGoal(vertex))
491 {
492 prunedGoalVertices_.emplace_back(vertex);
493 }
494 else if (isStart(vertex))
495 {
496 prunedStartVertices_.emplace_back(vertex);
497 }
498 verticesToBePruned.emplace_back(vertex);
499 }
500 }
501
502 // Remove all vertices to be pruned.
503 for (const auto &vertex : verticesToBePruned)
504 {
505 // Remove it from both search trees.
506 if (vertex->hasReverseParent())
507 {
508 vertex->getReverseParent()->removeFromReverseChildren(vertex->getId());
509 vertex->resetReverseParent();
510 }
511 vertex->invalidateReverseBranch();
512 if (vertex->hasForwardParent())
513 {
514 vertex->getForwardParent()->removeFromForwardChildren(vertex->getId());
515 vertex->resetForwardParent();
516 }
517 vertex->invalidateForwardBranch();
518
519 // Remove it from the nearest neighbor struct.
520 vertices_.remove(vertex);
521 }
522
523 // Assert that the forward and reverse queue are empty?
524 }
525
527 {
528 return numSampledStates_;
529 }
530
532 {
533 return numValidSamples_;
534 }
535
537 {
538 // Each sampled state is checked for collision. Only sampled states are checked for collision (number of
539 // collision checked edges don't count here.)
540 return numSampledStates_;
541 }
542
544 {
545 return numNearestNeighborsCalls_;
546 }
547
548 double ImplicitGraph::computeConnectionRadius(std::size_t numSamples) const
549 {
550 // Define the dimension as a helper variable.
551 auto dimension = static_cast<double>(spaceInformation_->getStateDimension());
552
553 // Compute the RRT* factor.
554 return rewireFactor_ *
555 std::pow(2.0 * (1.0 + 1.0 / dimension) *
556 (sampler_->getInformedMeasure(solutionCost_) /
557 unitNBallMeasure(spaceInformation_->getStateDimension())) *
558 (std::log(static_cast<double>(numSamples)) / static_cast<double>(numSamples)),
559 1.0 / dimension);
560
561 // // Compute the FMT* factor.
562 // return 2.0 * rewireFactor_ *
563 // std::pow((1.0 / dimension) *
564 // (sampler_->getInformedMeasure(*solutionCost_.lock()) /
565 // unitNBallMeasure(spaceInformation_->getStateDimension())) *
566 // (std::log(static_cast<double>(numSamples)) / numSamples),
567 // 1.0 / dimension);
568 }
569
570 std::size_t ImplicitGraph::computeNumberOfNeighbors(std::size_t numSamples) const
571 {
572 return std::ceil(rewireFactor_ * k_rgg_ * std::log(static_cast<double>(numSamples)));
573 }
574
575 bool ImplicitGraph::canPossiblyImproveSolution(const std::shared_ptr<Vertex> &vertex) const
576 {
577 // Get the preferred start for this vertex.
578 auto bestCostToCome = objective_->infiniteCost();
579 for (const auto &start : startVertices_)
580 {
581 auto costToCome = objective_->motionCostHeuristic(start->getState(), vertex->getState());
582 if (objective_->isCostBetterThan(costToCome, bestCostToCome))
583 {
584 bestCostToCome = costToCome;
585 }
586 }
587
588 // Check if the combination of the admissible costToCome and costToGo estimates results in a path
589 // that is more expensive than the current solution.
590 return objective_->isCostBetterThan(
591 objective_->combineCosts(
592 bestCostToCome, objective_->costToGo(vertex->getState(), problemDefinition_->getGoal().get())),
593 solutionCost_);
594 }
595
596 } // namespace aitstar
597
598 } // namespace geometric
599
600} // 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.