Loading...
Searching...
No Matches
ImplicitGraph.cpp
1/*********************************************************************
2 * Software License Agreement (BSD License)
3 *
4 * Copyright (c) 2025, University of New Hamphsire
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
36#include <ompl/base/SpaceInformation.h>
37#include <ompl/base/objectives/PathLengthOptimizationObjective.h>
38#include <ompl/base/objectives/StateCostIntegralObjective.h>
39#include <ompl/base/objectives/MaximizeMinClearanceObjective.h>
40#include <ompl/base/spaces/RealVectorStateSpace.h>
41#include "ompl/geometric/planners/lazyinformedtrees/blitstar/ImplicitGraph.h"
42
43#include <cmath>
44
45#include <boost/math/constants/constants.hpp>
46
47#include "ompl/util/GeometricEquations.h"
48using namespace std;
49namespace ob = ompl::base;
50namespace ompl
51{
52 namespace geometric
53 {
54 namespace blitstar
55 {
57 : batchId_(1u), solutionCost_(solutionCost)
58 {
59 }
60
61 void ImplicitGraph::setup(const ompl::base::SpaceInformationPtr &spaceInformation,
62 const ompl::base::ProblemDefinitionPtr &problemDefinition,
64 {
65 vertices_.setDistanceFunction([this](const std::shared_ptr<Vertex> &a, const std::shared_ptr<Vertex> &b)
66 { return spaceInformation_->distance(a->getState(), b->getState()); });
67 spaceInformation_ = spaceInformation;
68 problemDefinition_ = problemDefinition;
69 objective_ = problemDefinition->getOptimizationObjective();
70 k_rgg_ = boost::math::constants::e<double>() +
71 (boost::math::constants::e<double>() / spaceInformation->getStateDimension());
73 }
74
76 {
77 batchId_ = 1u;
78 radius_ = std::numeric_limits<double>::infinity();
79 numNeighbors_ = std::numeric_limits<std::size_t>::max();
80 vertices_.clear();
81 startVertices_.clear();
82 goalVertices_.clear();
83 prunedStartVertices_.clear();
84 prunedGoalVertices_.clear();
85 numSampledStates_ = 0u;
86 numValidSamples_ = 0u;
87 }
88
89 void ImplicitGraph::setRewireFactor(double rewireFactor)
90 {
91 rewireFactor_ = rewireFactor;
92 }
93
95 {
96 return rewireFactor_;
97 }
98
99 void ImplicitGraph::setMaxNumberOfGoals(unsigned int maxNumberOfGoals)
100 {
101 maxNumGoals_ = maxNumberOfGoals;
102 }
103
105 {
106 return maxNumGoals_;
107 }
108
109 void ImplicitGraph::setUseKNearest(bool useKNearest)
110 {
111 useKNearest_ = useKNearest;
112 }
113
115 {
116 return useKNearest_;
117 }
118
120 {
121 // Create a vertex corresponding to this state.
122 auto startVertex = std::make_shared<Vertex>(spaceInformation_, problemDefinition_, batchId_);
123
124 // Copy the state into the vertex's state.
125 spaceInformation_->copyState(startVertex->getState(), startState);
126
127 // By definition, this has identity cost-to-come.
128 startVertex->setCostToComeFromStart(objective_->identityCost());
129
130 // Add the start vertex to the set of vertices.
131 vertices_.add(startVertex);
132
133 // Remember it as a start vertex.
134 startVertices_.emplace_back(startVertex);
135 }
136
138 {
139 // Create a vertex corresponding to this state.
140 auto goalVertex = std::make_shared<Vertex>(spaceInformation_, problemDefinition_, batchId_);
141
142 // Copy the state into the vertex's state.
143 spaceInformation_->copyState(goalVertex->getState(), goalState);
144
145 // Add the goal vertex to the set of vertices.
146 vertices_.add(goalVertex);
147
148 // Remember it as a goal vertex.
149 goalVertices_.emplace_back(goalVertex);
150 }
151
153 {
154 return !startVertices_.empty();
155 }
156
158 {
159 return !goalVertices_.empty();
160 }
161
162 void
165 {
166 // We need to keep track whether a new goal and/or a new start has been added.
167 bool addedNewGoalState = false;
168 bool addedNewStartState = false;
169
170 // First update the goals. We have to call inputStates->nextGoal(terminationCondition) at least once
171 // (regardless of the return value of inputStates->moreGoalStates()) in case the termination condition
172 // wants us to wait for a goal.
173 do
174 {
175 // Get a new goal. If there are none, or the underlying state is invalid this will be a nullptr.
176 auto newGoalState = inputStates->nextGoal(terminationCondition);
177
178 // If there was a new valid goal, register it as such and remember that a goal has been added.
179 if (static_cast<bool>(newGoalState))
180 {
181 registerGoalState(newGoalState);
182 addedNewGoalState = true;
183 }
184
185 } while (inputStates->haveMoreGoalStates() && goalVertices_.size() <= maxNumGoals_);
186
187 // Having updated the goals, we now update the starts.
188 while (inputStates->haveMoreStartStates())
189 {
190 // Get the next start. The returned pointer can be a nullptr (if the state is invalid).
191 auto newStartState = inputStates->nextStart();
192
193 // If there is a new valid start, register it as such and remember that a start has been added.
194 if (static_cast<bool>(newStartState))
195 {
196 registerStartState(newStartState);
197 addedNewStartState = true;
198 }
199 }
200
201 // If we added a new start and have previously pruned goals, we might want to add the goals back.
202 if (addedNewStartState && !prunedGoalVertices_.empty())
203 {
204 // Keep track of the pruned goal vertices that have been revived.
205 std::vector<std::vector<std::shared_ptr<Vertex>>::iterator> revivedGoals;
206
207 // Let's see if the pruned goal is close enough to any new start to revive it..
208 for (auto it = prunedGoalVertices_.begin(); it != prunedGoalVertices_.end(); ++it)
209 {
210 // Loop over all start states to get the best cost.
211 auto heuristicCost = objective_->infiniteCost();
212 for (const auto &start : startVertices_)
213 {
214 heuristicCost = objective_->betterCost(
215 heuristicCost, objective_->motionCostHeuristic(start->getState(), (*it)->getState()));
216 }
217
218 // If this goal can possibly improve the current solution, add it back to the graph.
219 if (objective_->isCostBetterThan(heuristicCost, solutionCost_))
220 {
221 registerGoalState((*it)->getState());
222 addedNewGoalState = true;
223 revivedGoals.emplace_back(it);
224 }
225 }
226
227 // Remove all revived goals from the pruned goals.
228 for (const auto &revivedGoal : revivedGoals)
229 {
230 std::iter_swap(revivedGoal, prunedGoalVertices_.rbegin());
231 prunedGoalVertices_.pop_back();
232 }
233 }
234
235 // If we added a new goal and have previously pruned starts, we might want to add the starts back.
236 if (addedNewGoalState && !prunedStartVertices_.empty())
237 {
238 // Keep track of the pruned goal vertices that have been revived.
239 std::vector<std::vector<std::shared_ptr<Vertex>>::iterator> revivedStarts;
240
241 // Let's see if the pruned start is close enough to any new goal to revive it..
242 for (auto it = prunedStartVertices_.begin(); it != prunedStartVertices_.end(); ++it)
243 {
244 // Loop over all start states to get the best cost.
245 auto heuristicCost = objective_->infiniteCost();
246 for (const auto &goal : goalVertices_)
247 {
248 heuristicCost = objective_->betterCost(
249 heuristicCost, objective_->motionCostHeuristic(goal->getState(), (*it)->getState()));
250 }
251
252 // If this goal can possibly improve the current solution, add it back to the graph.
253 if (objective_->isCostBetterThan(heuristicCost, solutionCost_))
254 {
255 registerStartState((*it)->getState());
256 addedNewStartState = true;
257 revivedStarts.emplace_back(it);
258 }
259 }
260
261 // Remove all revived goals from the pruned goals.
262 for (const auto &revivedStart : revivedStarts)
263 {
264 std::iter_swap(revivedStart, prunedStartVertices_.rbegin());
265 prunedStartVertices_.pop_back();
266 }
267 }
268
269 if (addedNewGoalState || addedNewStartState)
270 {
271 // Allocate a state sampler if we have at least one start and one goal.
272 if (!startVertices_.empty() && !goalVertices_.empty())
273 {
274 sampler_ = objective_->allocInformedStateSampler(problemDefinition_,
275 std::numeric_limits<unsigned int>::max());
276 }
277 }
278
279 if (!goalVertices_.empty() && startVertices_.empty())
280 {
281 OMPL_WARN("BLIT* (ImplicitGraph): The problem has a goal but not a start. BLIT* can not find a "
282 "solution since PlannerInputStates provides no method to wait for a valid start state to "
283 "appear.");
284 }
285 }
286
287 std::size_t ImplicitGraph::computeNumberOfSamplesInInformedSet() const
288 {
289 // Loop over all vertices and count the ones in the informed set.
290 std::size_t numberOfSamplesInInformedSet{0u};
291 for (const auto &vertex : getVertices())
292 {
293 // Get the best cost to come from any start.
294 auto costToCome = objective_->infiniteCost();
295 for (const auto &start : startVertices_)
296 {
297 costToCome = objective_->betterCost(
298 costToCome, objective_->motionCostHeuristic(start->getState(), vertex->getState()));
299 }
300
301 // Get the best cost to go to any goal.
302 auto costToGo = objective_->infiniteCost();
303 for (const auto &goal : goalVertices_)
304 {
305 costToGo = objective_->betterCost(
306 costToCome, objective_->motionCostHeuristic(vertex->getState(), goal->getState()));
307 }
308 // If this can possibly improve the current solution, it is in the informed set.
309 if (objective_->isCostBetterThan(objective_->combineCosts(costToCome, costToGo), solutionCost_))
310 {
311 ++numberOfSamplesInInformedSet;
312 }
313 }
314
315 return numberOfSamplesInInformedSet;
316 }
317
318 bool ImplicitGraph::addSamples(std::size_t numNewSamples,
319 const ompl::base::PlannerTerminationCondition &terminationCondition,
320 bool need_prune) //, bool needPrune
321 {
322 // If there are no states to be added, then there's nothing to do.
323 if (numNewSamples == 0u)
324 {
325 return true;
326 }
327
328 if (!terminationCondition && need_prune)
329 {
330 prune();
331 }
332 // Ensure there's enough space for the new samples.
333 newSamples_.reserve(numNewSamples);
334 do
335 {
336 // Create a new vertex.
337 newSamples_.emplace_back(std::make_shared<Vertex>(spaceInformation_, problemDefinition_, batchId_));
338 do
339 {
340 // Sample the associated state uniformly within the informed set.
341 sampler_->sampleUniform(newSamples_.back()->getState(), solutionCost_);
342 // You have to calculate kinodynamic solution cost here.
343
344 // Count how many states we've checked.
345 ++numSampledStates_;
346 } while (!(spaceInformation_->getStateValidityChecker()->isValid(newSamples_.back()->getState())));
347
348 // Calculating the lower bound of cost-to-go and cost-to-come for a promising vertex
349 ompl::base::Cost costToCome =
350 objective_->motionCostHeuristic(startVertices_[0u]->getState(), newSamples_.back()->getState());
351 ompl::base::Cost costToGoal =
352 objective_->motionCostHeuristic(newSamples_.back()->getState(), goalVertices_[0u]->getState());
353 newSamples_.back()->setLowerCostBoundToStart(costToCome);
354 newSamples_.back()->setLowerCostBoundToGoal(costToGoal);
355
356 ++numValidSamples_;
357 } while (newSamples_.size() < numNewSamples && !terminationCondition);
358
359 if (newSamples_.size() == numNewSamples)
360 {
361 // First get the number of samples inside the informed set.
362 auto numSamplesInInformedSet = computeNumberOfSamplesInInformedSet();
363 if (useKNearest_)
364 {
365 numNeighbors_ = computeNumberOfNeighbors(numSamplesInInformedSet + numNewSamples);
366 }
367 else
368 {
369 radius_ = computeConnectionRadius(numSamplesInInformedSet + numNewSamples);
370 }
371
372 // Add all new vertices to the nearest neighbor structure.
373 vertices_.add(newSamples_);
374 newSamples_.clear();
375
376 // Update the batch id.
377 ++batchId_;
378
379 return true;
380 }
381
382 return false;
383 }
384
386 {
387 return vertices_.size();
388 }
389 std::size_t ImplicitGraph::getNumNeighbors() const
390 {
391 return numNeighbors_;
392 }
394 {
395 return radius_;
396 }
397
398 std::vector<std::shared_ptr<Vertex>>
399 ImplicitGraph::getNeighbors(const std::shared_ptr<Vertex> &vertex) const
400 {
401 // Return cached neighbors if available.
402 if (vertex->hasCachedNeighbors())
403 {
404 return vertex->getNeighbors();
405 }
406 else
407 {
408 ++numNearestNeighborsCalls_;
409 std::vector<std::shared_ptr<Vertex>> neighbors{};
410 if (useKNearest_)
411 {
412 vertices_.nearestK(vertex, numNeighbors_, neighbors);
413 }
414 else
415 {
416 vertices_.nearestR(vertex, radius_, neighbors);
417 }
418 vertex->cacheNeighbors(neighbors);
419 return neighbors;
420 }
421 }
422
423 bool ImplicitGraph::isStart(const std::shared_ptr<Vertex> &vertex) const
424 {
425 for (const auto &start : startVertices_)
426 {
427 if (vertex->getId() == start->getId())
428 {
429 return true;
430 }
431 }
432 return false;
433 }
434
435 bool ImplicitGraph::isGoal(const std::shared_ptr<Vertex> &vertex) const
436 {
437 for (const auto &goal : goalVertices_)
438 {
439 if (vertex->getId() == goal->getId())
440 {
441 return true;
442 }
443 }
444 return false;
445 }
446
447 const std::vector<std::shared_ptr<Vertex>> &ImplicitGraph::getStartVertices() const
448 {
449 return startVertices_;
450 }
451
452 const std::vector<std::shared_ptr<Vertex>> &ImplicitGraph::getGoalVertices() const
453 {
454 return goalVertices_;
455 }
456
457 std::vector<std::shared_ptr<Vertex>> ImplicitGraph::getVertices() const
458 {
459 std::vector<std::shared_ptr<Vertex>> vertices;
460 vertices_.list(vertices);
461 return vertices;
462 }
463
465 {
466 if (!objective_->isFinite(solutionCost_))
467 {
468 return;
469 }
470 std::vector<std::shared_ptr<Vertex>> vertices;
471 vertices_.list(vertices);
472
473 // Prepare the vector of vertices to be pruned.
474 std::vector<std::shared_ptr<Vertex>> verticesToBePruned;
475 // Check each vertex whether it can be pruned.
476 for (const auto &vertex : vertices)
477 {
478 // Check if the combination of the admissible costToCome and costToGo estimates results in a path
479 // that is more expensive than the current solution.
480 if (!canPossiblyImproveSolution(vertex))
481 {
482 // We keep track of pruned start and goal vertices. This is because if the user adds start or
483 // goal states after we have pruned start or goal states, we might want to reconsider pruned
484 // start or goal states.
485 verticesToBePruned.emplace_back(vertex);
486 }
487 else
488 {
489 // If vertex can still provide a better solution, reset it to be unexpanded
490 vertex->resetMeet();
491 vertex->resetReverseId();
492 vertex->resetForwardId();
493 vertex->resetForwardExpanded();
494 vertex->resetReverseExpanded();
495 }
496 }
497
498 // Remove all vertices to be pruned.
499 for (const auto &vertex : verticesToBePruned)
500 {
501 // Remove it from both search trees.
502 if (vertex->hasReverseParent())
503 {
504 vertex->getReverseParent()->removeFromReverseChildren(vertex->getId());
505 vertex->resetReverseParent();
506 }
507 if (vertex->hasForwardParent())
508 {
509 vertex->getForwardParent()->removeFromForwardChildren(vertex->getId());
510 vertex->resetForwardParent();
511 }
512
513 // Remove it from the nearest neighbor struct.
514 vertices_.remove(vertex);
515 }
516 }
517
519 {
520 return numSampledStates_;
521 }
522
524 {
525 return numValidSamples_;
526 }
527
529 {
530 // Each sampled state is checked for collision. Only sampled states are checked for collision (number of
531 // collision checked edges don't count here.)
532 return numSampledStates_;
533 }
534
536 {
537 return numNearestNeighborsCalls_;
538 }
539
540 double ImplicitGraph::computeConnectionRadius(std::size_t numSamples) const
541 {
542 // Define the dimension as a helper variable.
543 auto dimension = static_cast<double>(spaceInformation_->getStateDimension());
544
545 // Compute the RRT* factor.
546 return rewireFactor_ *
547 std::pow(2.0 * (1.0 + 1.0 / dimension) *
548 (sampler_->getInformedMeasure(solutionCost_) /
549 unitNBallMeasure(spaceInformation_->getStateDimension())) *
550 (std::log(static_cast<double>(numSamples)) / static_cast<double>(numSamples)),
551 1.0 / dimension);
552
553 // // Compute the FMT* factor.
554 // return 2.0 * rewireFactor_ *
555 // std::pow((1.0 / dimension) *
556 // (sampler_->getInformedMeasure(*solutionCost_.lock()) /
557 // unitNBallMeasure(spaceInformation_->getStateDimension())) *
558 // (std::log(static_cast<double>(numSamples)) / numSamples),
559 // 1.0 / dimension);
560 }
561
562 std::size_t ImplicitGraph::computeNumberOfNeighbors(std::size_t numSamples) const
563 {
564 return std::ceil(rewireFactor_ * k_rgg_ * std::log(static_cast<double>(numSamples)));
565 }
566
567 bool ImplicitGraph::canPossiblyImproveSolution(const std::shared_ptr<Vertex> &vertex) const
568 {
569 // Check if the combination of the admissible costToCome and costToGo estimates results in a path
570 // that is more expensive than the current solution.
571 return objective_->isCostBetterThan(
572 objective_->combineCosts(vertex->getLowerCostBoundToStart(), vertex->getLowerCostBoundToGoal()),
573 solutionCost_);
574 }
575
576 } // namespace blitstar
577
578 } // namespace geometric
579
580} // 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
void setRewireFactor(double rewireFactor)
Set the rewire factor of the RGG.
unsigned int getMaxNumberOfGoals() const
Get the maximum number of goals BLIT* will sample from sampleable goal regions.
bool isGoal(const std::shared_ptr< Vertex > &vertex) const
Checks whether the vertex is a goal vertex.
std::size_t getNumberOfValidSamples() const
Returns the total number of valid samples found.
std::size_t getNumberOfStateCollisionChecks() const
Get the number of state collision checks.
std::size_t getNumVertices() const
Gets the number of samples in the graph.
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.
double getRewireFactor() const
Get the reqire factor of the RGG.
void setMaxNumberOfGoals(unsigned int maxNumberOfGoals)
Set the maximum number of goals BLIT* will sample from sampleable goal regions.
const std::vector< std::shared_ptr< Vertex > > & getStartVertices() const
Get the start vertices.
void registerStartState(const ompl::base::State *const startState)
Registers a state as a start state.
bool hasAStartState() const
Returns whether the graph has a goal state.
std::size_t getNumberOfSampledStates() const
Returns the total number of sampled states.
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.
double getConnectionRadius() const
Gets the RGG connection radius.
ImplicitGraph(const ompl::base::Cost &solutionCost)
Constructs an implicit graph.
std::size_t getNumberOfNearestNeighborCalls() const
Get the number of nearest neighbor calls.
const std::vector< std::shared_ptr< Vertex > > & getGoalVertices() const
Get the goal vertices.
bool hasAGoalState() const
Returns whether the graph has a goal state.
void registerGoalState(const ompl::base::State *const goalState)
Registers a state as a goal state.
void clear()
Resets the graph to its construction state, without resetting options.
bool getUseKNearest() const
Whether the graph uses a k-nearest connection model. If false, it uses an r-disc model.
void setUseKNearest(bool useKNearest)
Whether to use a k-nearest connection model. If false, it uses an r-disc model.
void prune()
Prune all samples that can not contribute to a solution better than the current one.
std::vector< std::shared_ptr< Vertex > > getNeighbors(const std::shared_ptr< Vertex > &vertex) const
Get neighbors of a vertex.
bool addSamples(std::size_t numNewSamples, const ompl::base::PlannerTerminationCondition &terminationCondition, bool need_prune)
Adds a batch of samples and returns the samples it has added.
std::vector< std::shared_ptr< Vertex > > getVertices() const
Get all vertices.
bool isStart(const std::shared_ptr< Vertex > &vertex) const
Checks whether the vertex is a start vertex.
#define OMPL_WARN(fmt,...)
Log a formatted warning string.
Definition Console.h:66
This namespace contains sampling based planning routines shared by both planning under geometric cons...
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.
STL namespace.