Loading...
Searching...
No Matches
AOXRRTConnect.cpp
1/*********************************************************************
2 * Software License Agreement (BSD License)
3 *
4 * Copyright (c) 2025, Queen's University
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 Queen's University 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/* Author: Tyler Wilson */
36
37#include "ompl/geometric/planners/rrt/AOXRRTConnect.h"
38#include "ompl/base/goals/GoalSampleableRegion.h"
39#include "ompl/tools/config/SelfConfig.h"
40#include "ompl/util/String.h"
41
42#include "ompl/base/objectives/PathLengthOptimizationObjective.h"
43#include "ompl/base/samplers/InformedStateSampler.h"
44
45#include <stdexcept>
46
47ompl::geometric::AOXRRTConnect::AOXRRTConnect(const base::SpaceInformationPtr &si) : base::Planner(si, "AOXRRTConnect")
48{
49 specs_.recognizedGoal = base::GOAL_SAMPLEABLE_REGION;
50 specs_.directed = true;
51
52 Planner::declareParam<double>("range", this, &AOXRRTConnect::setRange, &AOXRRTConnect::getRange, "0.:1.:10000.");
53
54 connectionPoint_ = std::make_pair<base::State *, base::State *>(nullptr, nullptr);
55}
56
57ompl::geometric::AOXRRTConnect::~AOXRRTConnect()
58{
59 freeMemory();
60}
61
63{
64 Planner::setup();
67
68 if (pdef_)
69 {
70 if (pdef_->hasOptimizationObjective())
71 opt_ = pdef_->getOptimizationObjective();
72 else
73 {
74 opt_ = std::make_shared<base::PathLengthOptimizationObjective>(si_);
75
76 /* Store the new objective in the problem def'n */
77 pdef_->setOptimizationObjective(opt_);
78 }
79 }
80 else
81 {
82 OMPL_INFORM("%s: problem definition is not set, deferring setup completion...", getName().c_str());
83 setup_ = false;
84 }
85
86 if (!tStart_)
88 if (!tGoal_)
90
91 reset(true);
92}
93
95{
96 std::vector<Motion *> motions;
97
98 if (tStart_)
99 {
100 tStart_->list(motions);
101 for (auto &motion : motions)
102 {
103 if (motion->state != nullptr)
104 si_->freeState(motion->state);
105 delete motion;
106 }
107 }
108
109 if (tGoal_)
110 {
111 tGoal_->list(motions);
112 for (auto &motion : motions)
113 {
114 if (motion->state != nullptr)
115 si_->freeState(motion->state);
116 delete motion;
117 }
118 }
119}
120
121void ompl::geometric::AOXRRTConnect::reset(bool solvedProblem)
122{
123 tStart_->clear();
124 tGoal_->clear();
125
126 /* If we have a reasonable cost bound, then use the AOX distance function */
127 if (bestCost_.value() < std::numeric_limits<double>::infinity())
128 {
129 tStart_->setDistanceFunction([this](const Motion *a, const Motion *b) { return aoxDistanceFunction(a, b); });
130 tGoal_->setDistanceFunction([this](const Motion *a, const Motion *b) { return aoxDistanceFunction(a, b); });
131 }
132 else /* Use standard distance function if no cost bound is set */
133 {
134 tStart_->setDistanceFunction([this](const Motion *a, const Motion *b)
135 { return euclideanDistanceFunction(a, b); });
136 tGoal_->setDistanceFunction([this](const Motion *a, const Motion *b)
137 { return euclideanDistanceFunction(a, b); });
138 }
139
140 /* Start from the start tree */
141 startTree_ = true;
142 sampleAttempts = 0;
143
144 /* If we didn't solve the problem, loosen our internal reset conditions
145 This is for probabilistic completeness, so that the planner can still
146 eventually find a solution that may require more vertices/samples */
147 if (!solvedProblem)
148 {
149 maxInternalSamples += maxInternalSamplesIncrement;
150 maxInternalVertices += maxInternalVerticesIncrement;
151 }
152 else
153 {
154 /* If we solved the problem, reset our internal reset conditions as well */
155 maxInternalSamples = maxInternalSamplesIncrement;
156 maxInternalVertices = maxInternalVerticesIncrement;
157 }
158}
159
160void ompl::geometric::AOXRRTConnect::setPathCost(double pc)
161{
162 bestCost_ = base::Cost(pc);
163 opt_->setCostThreshold(bestCost_);
164}
165
167 float rootDist, TreeData &tree)
168{
169 Motion *nearest_motion;
170 std::vector<Motion *> nearest_vec;
171
172 rootDist += rootDistPadding;
173
174 tree->nearestR(sampled_motion, rootDist, nearest_vec);
175 if (nearest_vec.empty())
176 {
177 OMPL_ERROR("%s: Failed to find any neighbours for a state", getName().c_str());
178 return nullptr;
179 }
180
181 int idx = 0;
182 nearest_motion = nearest_vec[idx];
183 auto nearest_distance = si_->distance(sampled_motion->state, nearest_motion->state);
184
185 while (nearest_motion->cost > 0 && sampled_motion->cost < nearest_motion->cost + nearest_distance)
186 {
187 idx++;
188 nearest_motion = nearest_vec[idx];
189 nearest_distance = si_->distance(sampled_motion->state, nearest_motion->state);
190 }
191
192 return nearest_motion;
193}
194
196 Motion *rmotion)
197{
198 auto g_hat = tgi.start ? si_->distance(rmotion->state, startState) : si_->distance(rmotion->state, goalState);
199
200 auto *root_motion = new Motion(si_);
201 si_->copyState(root_motion->state, tgi.start ? startState : goalState);
202 root_motion->cost = 0;
203
204 auto rootDist = tree->getDistanceFunction()(rmotion, root_motion);
205 Motion *nmotion = findNeighbour(rmotion, rootDist, tree);
206
207 /* If there were no neighbours */
208 if (nmotion == nullptr)
209 {
210 si_->freeState(root_motion->state);
211 delete root_motion;
212 return TRAPPED;
213 }
214
215 /* assume we can reach the state we go towards */
216 bool reach = true;
217
218 /* find state to add */
219 base::State *dstate = rmotion->state;
220 double d = si_->distance(nmotion->state, rmotion->state);
221 if (d > maxDistance_)
222 {
223 si_->getStateSpace()->interpolate(nmotion->state, rmotion->state, maxDistance_ / d, tgi.xstate);
224
225 /* Check if we have moved at all. Due to some stranger state spaces (e.g., the constrained state spaces),
226 * interpolate can fail and no progress is made. Without this check, the algorithm gets stuck in a loop as it
227 * thinks it is making progress, when none is actually occurring. */
228 if (si_->equalStates(nmotion->state, tgi.xstate))
229 {
230 si_->freeState(root_motion->state);
231 delete root_motion;
232 return TRAPPED;
233 }
234
235 dstate = tgi.xstate;
236 reach = false;
237 }
238
239 bool validMotion = tgi.start ? si_->checkMotion(nmotion->state, dstate) :
240 si_->isValid(dstate) && si_->checkMotion(dstate, nmotion->state);
241
242 if (!validMotion)
243 {
244 si_->freeState(root_motion->state);
245 delete root_motion;
246 return TRAPPED;
247 }
248
249 /* Cost resampling for new vertices */
250 /* Don't bother cost resampling if we don't have a meaningful cost range yet */
251 if (!rmotion->connecting && bestCost_.value() < std::numeric_limits<double>::infinity())
252 {
253 si_->copyState(rmotion->state, dstate);
254 g_hat = tgi.start ? si_->distance(dstate, startState) : si_->distance(dstate, goalState);
255
256 int remaining_resample_attempts = maxResampleAttempts_;
257 while (validMotion && remaining_resample_attempts > 0)
258 {
259 remaining_resample_attempts--;
260
261 auto new_cost = si_->distance(nmotion->state, dstate) + nmotion->cost;
262 double c_range = new_cost - g_hat;
263 double cost_sample = rng_.uniformReal(0, 1);
264 double c_rand = (cost_sample * c_range) + g_hat;
265 rmotion->cost = c_rand;
266
267 auto rootDist = tree->getDistanceFunction()(rmotion, root_motion);
268 Motion *n_nmotion = findNeighbour(rmotion, rootDist, tree);
269
270 if (new_cost <= si_->distance(n_nmotion->state, dstate) + n_nmotion->cost || c_range == 0)
271 {
272 validMotion = false;
273 }
274 else
275 {
276 validMotion = si_->checkMotion(n_nmotion->state, dstate);
277
278 if (validMotion)
279 {
280 new_cost = si_->distance(n_nmotion->state, dstate) + n_nmotion->cost;
281 nmotion = n_nmotion;
282 reach = true;
283 }
284 }
285 }
286 }
287
288 auto *motion = new Motion(si_);
289 si_->copyState(motion->state, dstate);
290 motion->parent = nmotion;
291 motion->root = nmotion->root;
292 motion->cost = si_->distance(nmotion->state, motion->state) + nmotion->cost;
293 tree->add(motion);
294
295 tgi.xmotion = motion;
296
297 si_->freeState(root_motion->state);
298 delete root_motion;
299 return reach ? REACHED : ADVANCED;
300}
301
303{
305 auto *goal = dynamic_cast<base::GoalSampleableRegion *>(pdef_->getGoal().get());
306
307 _ptc = ptc;
308
309 TreeGrowingInfo tgi;
310 tgi.xstate = si_->allocState();
311
312 Motion *approxsol = nullptr;
313 double approxdif = std::numeric_limits<double>::infinity();
314 auto *rmotion = new Motion(si_);
315 auto *cmotion = new Motion(si_);
316 base::State *rstate = rmotion->state;
317 bool solved = false;
318 GrowState gs;
319
320 if (goal == nullptr)
321 {
322 OMPL_ERROR("%s: Unknown type of goal", getName().c_str());
324 }
325
326 while (const base::State *st = pis_.nextStart())
327 {
328 auto *motion = new Motion(si_);
329 si_->copyState(motion->state, st);
330 motion->root = motion->state;
331 motion->cost = 0;
332 tStart_->add(motion);
333 startState = motion->state;
334
335 /* Straight line check for first search loop
336 Nested here for PDT visualize, which calls solve for each iteration */
337 si_->copyState(rstate, startState);
338 gs = REACHED;
339 }
340
341 if (tStart_->size() == 0)
342 {
343 OMPL_ERROR("%s: Motion planning start tree could not be initialized!", getName().c_str());
345 }
346
347 if (!goal->couldSample())
348 {
349 OMPL_ERROR("%s: Insufficient states in sampleable goal region", getName().c_str());
351 }
352
353 if (!sampler_)
354 {
355 /* We are using informed sampling, this can end-up reverting to rejection sampling in some cases */
356 OMPL_INFORM("%s: Using informed sampling.", getName().c_str());
357 sampler_ = opt_->allocInformedStateSampler(pdef_, 100);
358 }
359
360 OMPL_INFORM("%s: Started planning with %u states. Seeking a solution better than %.5f.", getName().c_str(),
361 (int)(tStart_->size() + tGoal_->size()), bestCost_);
362
363 /* Planner may restart early if it fails to find a solution and start the search anew */
364 while (!ptc && !internalResetCondition())
365 {
366 TreeData &tree = startTree_ ? tStart_ : tGoal_;
367 TreeData &otherTree = !startTree_ ? tStart_ : tGoal_;
368 tgi.start = startTree_;
369
370 if (tGoal_->size() == 0 || pis_.getSampledGoalsCount() < tGoal_->size() / 2)
371 {
372 const base::State *st = tGoal_->size() == 0 ? pis_.nextGoal(ptc) : pis_.nextGoal();
373 if (st != nullptr)
374 {
375 auto *motion = new Motion(si_);
376 si_->copyState(motion->state, st);
377 motion->root = motion->state;
378 motion->cost = 0;
379
380 /* Straight line check for start of planning
381 Nested here for PDT visualize, which calls solve for each iteration */
382 if (tGoal_->size() == 0)
383 {
384 tgi.xmotion = motion;
385 }
386
387 tGoal_->add(motion);
388 goalState = motion->state;
389 }
390
391 if (tGoal_->size() == 0)
392 {
393 OMPL_ERROR("%s: Unable to sample any valid states for goal tree", getName().c_str());
394 break;
395 }
396 }
397
398 if (gs != TRAPPED)
399 {
400 /* remember which motion was added in the last grow */
401 Motion *addedMotion = tgi.xmotion;
402
403 /* if reached, it means we used rstate directly, no need to copy again */
404 if (gs != REACHED)
405 si_->copyState(rstate, tgi.xstate);
406
407 /* Switch tree to try and connect from other side */
408 tgi.start = !tgi.start;
409
410 si_->copyState(cmotion->state, rmotion->state);
411 cmotion->cost = bestCost_.value() - tgi.xmotion->cost;
412 cmotion->connecting = true;
413
414 /* if initial progress cannot be done from the otherTree, restore tgi.start */
415 GrowState gsc = growTree(otherTree, tgi, cmotion);
416 if (gsc == TRAPPED)
417 {
418 /* Switch tree back if connect from other side failed */
419 tgi.start = !tgi.start;
420 }
421
422 /* Keep trying to connect until we reach the other tree or fail to advance */
423 while (gsc == ADVANCED)
424 {
425 gsc = growTree(otherTree, tgi, cmotion);
426 }
427
428 Motion *startMotion = tgi.start ? tgi.xmotion : addedMotion;
429 Motion *goalMotion = tgi.start ? addedMotion : tgi.xmotion;
430
431 /* if we connected the trees in a valid way (start and goal pair is valid)*/
432 if (gsc == REACHED && goal->isStartGoalPairValid(startMotion->root, goalMotion->root))
433 {
434 /* it must be the case that either the start tree or the goal tree has made some progress
435 so one of the parents is not nullptr. We go one step 'back' to avoid having a duplicate state
436 on the solution path */
437 if (startMotion->parent != nullptr)
438 startMotion = startMotion->parent;
439 else
440 goalMotion = goalMotion->parent;
441
442 connectionPoint_ = std::make_pair(startMotion->state, goalMotion->state);
443
444 /* construct the solution path */
445 Motion *solution = startMotion;
446 std::vector<Motion *> mpath1;
447 while (solution != nullptr)
448 {
449 mpath1.push_back(solution);
450 solution = solution->parent;
451 }
452
453 solution = goalMotion;
454 std::vector<Motion *> mpath2;
455 while (solution != nullptr)
456 {
457 mpath2.push_back(solution);
458 solution = solution->parent;
459 }
460
461 auto path(std::make_shared<PathGeometric>(si_));
462 path->getStates().reserve(mpath1.size() + mpath2.size());
463 for (int i = mpath1.size() - 1; i >= 0; --i)
464 path->append(mpath1[i]->state);
465 for (auto &i : mpath2)
466 path->append(i->state);
467
468 pdef_->addSolutionPath(path, false, 0.0, getName());
469 foundPath = path;
470
471 solved = true;
472
473 OMPL_INFORM("%s: Found a solution of cost %.5f", getName().c_str(), path->length());
474 break;
475 }
476 else
477 {
478 /* We didn't reach the goal, but if we were extending the start
479 tree, then we can mark/improve the approximate path so far. */
480 if (tgi.start)
481 {
482 /* We were working from the startTree. */
483 double dist = 0.0;
484 goal->isSatisfied(tgi.xmotion->state, &dist);
485 if (dist < approxdif)
486 {
487 approxdif = dist;
488 approxsol = tgi.xmotion;
489 }
490 }
491 }
492 }
493
494 float asize = tree->size();
495 float bsize = otherTree->size();
496 float ratio = std::abs(asize - bsize) / asize;
497
498 /* Swap trees if our balanced RRTC condition is met */
499 if (ratio < 1.f)
500 {
502 }
503 tgi.start = startTree_;
504
505 /* Sample random states until we find a valid and useful sample */
506 bool placed_sampled = false;
507 double c_rand;
508 double c_range;
509 do
510 {
511 placed_sampled = sampler_->sampleUniform(rstate, bestCost_);
512 sampleAttempts++;
513
514 double cost_sample = rng_.uniformReal(0, 1);
515 auto g_hat =
516 tgi.start ? si_->distance(rmotion->state, startState) : si_->distance(rmotion->state, goalState);
517 auto h_hat =
518 !tgi.start ? si_->distance(rmotion->state, startState) : si_->distance(rmotion->state, goalState);
519 auto f_hat = g_hat + h_hat;
520
521 c_range = bestCost_.value() - f_hat;
522 c_rand = (cost_sample * c_range) + g_hat;
523
524 } while (!ptc && (!placed_sampled || c_range < 0));
525 rmotion->cost = c_rand;
526
527 /* Extend towards our new sample */
528 /* I can't change references when I swap the trees, so this will have to do */
529 TreeData &temp_tree = startTree_ ? tStart_ : tGoal_;
530 gs = growTree(temp_tree, tgi, rmotion);
531 }
532
533 /* Cleanup our memory */
534 si_->freeState(tgi.xstate);
535 si_->freeState(rstate);
536 delete rmotion;
537 delete cmotion;
538
539 OMPL_INFORM("%s: Created %u states (%u start + %u goal)", getName().c_str(), tStart_->size() + tGoal_->size(),
540 tStart_->size(), tGoal_->size());
541
542 if (approxsol && !solved)
543 {
544 /* construct the solution path */
545 std::vector<Motion *> mpath;
546 while (approxsol != nullptr)
547 {
548 mpath.push_back(approxsol);
549 approxsol = approxsol->parent;
550 }
551
552 auto path(std::make_shared<PathGeometric>(si_));
553 for (int i = mpath.size() - 1; i >= 0; --i)
554 path->append(mpath[i]->state);
555 pdef_->addSolutionPath(path, true, approxdif, getName());
557 }
558
560}
Abstract definition of a goal region that can be sampled.
Encapsulate a termination condition for a motion planner. Planners will call operator() to decide whe...
PlannerInputStates pis_
Utility class to extract valid input states.
Definition Planner.h:407
PlannerSpecs specs_
The specifications of the planner (its capabilities).
Definition Planner.h:413
ProblemDefinitionPtr pdef_
The user set problem definition.
Definition Planner.h:404
const std::string & getName() const
Get the name of the planner.
Definition Planner.cpp:56
SpaceInformationPtr si_
The space information for which planning is done.
Definition Planner.h:401
virtual void checkValidity()
Check to see if the planner is in a working state (setup has been called, a goal was set,...
Definition Planner.cpp:106
bool setup_
Flag indicating whether setup() has been called.
Definition Planner.h:424
Definition of an abstract state.
Definition State.h:50
Representation of a motion.
TreeData tStart_
The start tree.
void setup() override
Perform extra configuration steps, if needed. This call will also issue a call to ompl::base::SpaceIn...
void setRange(double distance)
Set the range the planner is supposed to use.
double getRange() const
Get the range the planner is using.
long int maxResampleAttempts_
Maximum allowed cost resampling iterations before moving on.
Motion * findNeighbour(Motion *sampled_motion, float rootDist, TreeData &tree)
Find a valid neighbour with asymmetric distance funtion via iteration.
GrowState
The state of the tree after an attempt to extend it.
std::pair< base::State *, base::State * > connectionPoint_
The pair of states in each tree connected during planning. Used for PlannerData computation.
TreeData tGoal_
The goal tree.
bool startTree_
A flag that toggles between expanding the start tree (true) or goal tree (false).
bool internalResetCondition()
Check if the inner loop planner met its condition to terminate.
base::PlannerStatus solve(const base::PlannerTerminationCondition &ptc) override
Function that can solve the motion planning problem. This function can be called multiple times on th...
base::OptimizationObjectivePtr opt_
Objective we're optimizing.
RNG rng_
The random number generator.
base::PathPtr foundPath
Path found by the algorithm.
std::shared_ptr< NearestNeighbors< Motion * > > TreeData
A nearest-neighbor datastructure representing a tree of motions.
GrowState growTree(TreeData &tree, TreeGrowingInfo &tgi, Motion *rmotion)
Grow a tree towards a random state.
base::InformedSamplerPtr sampler_
State sampler.
void freeMemory()
Free the memory allocated by this planner.
base::Cost bestCost_
Best cost found so far by algorithm.
base::PlannerTerminationCondition _ptc
Outer loop termination condition for AORRTC.
double maxDistance_
The maximum length of a motion to be added to a tree.
AOXRRTConnect(const base::SpaceInformationPtr &si)
Constructor.
This class contains methods that automatically configure various parameters for motion planning....
Definition SelfConfig.h:59
static NearestNeighbors< _T > * getDefaultNearestNeighbors(const base::Planner *planner)
Select a default nearest neighbor datastructure for the given space.
Definition SelfConfig.h:105
void configurePlannerRange(double &range)
Compute what a good length for motion segments is.
#define OMPL_INFORM(fmt,...)
Log a formatted information string.
Definition Console.h:68
#define OMPL_ERROR(fmt,...)
Log a formatted error string.
Definition Console.h:64
This namespace contains sampling based planning routines shared by both planning under geometric cons...
@ GOAL_SAMPLEABLE_REGION
This bit is set if casting to sampleable goal regions (ompl::base::GoalSampleableRegion) is possible.
Definition GoalTypes.h:56
A class to store the exit status of Planner::solve().
@ INVALID_START
Invalid start state or no start state specified.
@ EXACT_SOLUTION
The planner found an exact solution.
@ INVALID_GOAL
Invalid goal state.
@ UNRECOGNIZED_GOAL_TYPE
The goal is of a type that a planner does not recognize.
@ APPROXIMATE_SOLUTION
The planner found an approximate solution.
@ TIMEOUT
The planner failed to find a solution.
Information attached to growing a tree of motions (used internally).