Loading...
Searching...
No Matches
EITstar.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.h"
38
39#include <algorithm>
40#include <memory>
41
42#include "ompl/base/objectives/PathLengthOptimizationObjective.h"
43#include "ompl/geometric/PathGeometric.h"
44
45using namespace std::string_literals;
46using namespace ompl::geometric::eitstar;
47
48namespace ompl
49{
50 namespace geometric
51 {
52 EITstar::EITstar(const std::shared_ptr<ompl::base::SpaceInformation> &spaceInfo)
53 : ompl::base::Planner(spaceInfo, "EIT*")
54 , graph_(spaceInfo, solutionCost_)
55 , detectionState_(spaceInfo->allocState())
56 , space_(spaceInfo->getStateSpace())
57 , motionValidator_(spaceInfo->getMotionValidator())
58 , solutionCost_()
59 {
60 // Specify EIT*'s planner specs.
61 specs_.recognizedGoal = base::GOAL_SAMPLEABLE_REGION;
62 specs_.multithreaded = false;
63 specs_.approximateSolutions = true;
64 specs_.optimizingPaths = true;
65 specs_.directed = true;
66 specs_.provingSolutionNonExistence = false;
67 specs_.canReportIntermediateSolutions = true;
68
69 // Register the setting callbacks.
72 "1.0:0.01:3.0");
74 declareParam<bool>("use_graph_pruning", this, &EITstar::enablePruning, &EITstar::isPruningEnabled, "0,1");
75 declareParam<bool>("find_approximate_solutions", this, &EITstar::trackApproximateSolutions,
78 &EITstar::getMaxNumberOfGoals, "1:1:1000");
79
80 // Register the progress properties.
81 addPlannerProgressProperty("iterations INTEGER", [this]() { return std::to_string(iteration_); });
82 addPlannerProgressProperty("best cost DOUBLE", [this]() { return std::to_string(solutionCost_.value()); });
83 addPlannerProgressProperty("state collision checks INTEGER",
84 [this]() { return std::to_string(graph_.getNumberOfSampledStates()); });
85 addPlannerProgressProperty("edge collision checks INTEGER",
86 [this]() { return std::to_string(numCollisionCheckedEdges_); });
87 addPlannerProgressProperty("nearest neighbour calls INTEGER",
88 [this]() { return std::to_string(graph_.getNumberOfNearestNeighborCalls()); });
89 }
90
92 {
93 spaceInfo_->freeState(detectionState_);
94 }
95
97 {
98 // Call the base class setup.
99 Planner::setup();
100
101 // Check that the problem definition is set.
102 if (static_cast<bool>(problem_))
103 {
104 // Default to path length optimization if no objective has been specified.
105 if (!problem_->hasOptimizationObjective())
106 {
107 OMPL_WARN("%s: No optimization objective has been specified. The default is optimizing path "
108 "length.",
109 name_.c_str());
110 problem_->setOptimizationObjective(
111 std::make_shared<ompl::base::PathLengthOptimizationObjective>(spaceInfo_));
112 }
113
114 if (static_cast<bool>(problem_->getGoal()))
115 {
116 // If we were given a goal, make sure its of appropriate type.
117 if (!(problem_->getGoal()->hasType(ompl::base::GOAL_SAMPLEABLE_REGION)))
118 {
119 OMPL_ERROR("EIT* is currently only implemented for goals that can be cast to "
120 "ompl::base::GOAL_SAMPLEABLE_REGION.");
121 setup_ = false;
122 return;
123 }
124 }
125
126 // Pull through the optimization objective for direct access.
127 objective_ = problem_->getOptimizationObjective();
128
129 // Initialize costs to infinity.
130 solutionCost_ = objective_->infiniteCost();
131 reverseCost_ = objective_->infiniteCost();
132 approximateSolutionCost_ = objective_->infiniteCost();
133 approximateSolutionCostToGoal_ = objective_->infiniteCost();
134
135 // Instantiate the queues.
136 forwardQueue_ = std::make_unique<eitstar::ForwardQueue>(objective_, space_);
137 if (isMultiqueryEnabled_)
138 {
139 reverseQueue_ = std::make_unique<eitstar::ReverseQueue>(objective_, space_,
140 std::isfinite(suboptimalityFactor_));
141 }
142 else
143 {
144 reverseQueue_ = std::make_unique<eitstar::ReverseQueue>(objective_, space_, true);
145 }
146
147 // Setup the graph with the problem information.
148 graph_.setup(problem_, &pis_);
149
150 // Create the start vertices.
151 for (const auto &start : graph_.getStartStates())
152 {
153 startVertices_.emplace_back(start->asForwardVertex());
154 }
155
156 // Create the goal vertices.
157 for (const auto &goal : graph_.getGoalStates())
158 {
159 goalVertices_.emplace_back(goal->asReverseVertex());
160 }
161
162 // Populate the queues.
163 expandGoalVerticesIntoReverseQueue();
164 expandStartVerticesIntoForwardQueue();
165 }
166 else
167 {
168 setup_ = false;
169 OMPL_WARN("%s: Unable to setup without a problem definition.", name_.c_str());
170 }
171 }
172
174 {
175 // Check that the planner and state space are setup.
176 auto status = ensureSetup();
177
178 // Return early if the planner or state space are not setup.
180 {
181 return status;
182 }
183
184 // Ensure that the problem has start and goal states before solving.
185 status = ensureStartAndGoalStates(terminationCondition);
186
187 // Return early if no problem can be solved.
190 {
191 return status;
192 }
193
194 // Let the world know what we're doing.
195 OMPL_INFORM("%s: Solving the given planning problem. The current best solution cost is %.4f", name_.c_str(),
196 solutionCost_.value());
197
198 // Iterate while we should continue solving the problem.
199 while (continueSolving(terminationCondition))
200 {
201 iterate(terminationCondition);
202 }
203
204 // Someone might call ProblemDefinition::clearSolutionPaths() between invocations of Planner::sovle(), in
205 // which case previously found solutions are not registered with the problem definition anymore.
206 status = updateSolution();
207
208 // Let the caller know the status.
209 informAboutPlannerStatus(status);
210 return status;
211 }
212
214 {
215 if (forwardQueue_)
216 {
217 forwardQueue_->clear();
218 }
219
220 if (reverseQueue_)
221 {
222 reverseQueue_->clear();
223 }
224
225 startVertices_.clear();
226 goalVertices_.clear();
227 objective_.reset();
228 graph_.clear();
229
230 // Reset the solution costs. Cannot use infiniteCost() before resetting the objective because the objective
231 // of a new problem definition objective might define that differently than the old.
232 solutionCost_ = ompl::base::Cost(std::numeric_limits<double>::signaling_NaN());
233 reverseCost_ = ompl::base::Cost(std::numeric_limits<double>::signaling_NaN());
234 approximateSolutionCost_ = ompl::base::Cost(std::numeric_limits<double>::signaling_NaN());
235 approximateSolutionCostToGoal_ = ompl::base::Cost(std::numeric_limits<double>::signaling_NaN());
236
237 // Reset the tags.
238 reverseSearchTag_ = 1u;
239 startExpansionGraphTag_ = 0u;
240 numSparseCollisionChecksCurrentLevel_ = initialNumSparseCollisionChecks_;
241 numSparseCollisionChecksPreviousLevel_ = 0u;
242 suboptimalityFactor_ = std::numeric_limits<double>::infinity();
243
244 Planner::clear();
245 setup_ = false;
246 }
247
249 {
250 if (setup_)
251 {
252 forwardQueue_->clear();
253 reverseQueue_->clear();
254 startVertices_.clear();
255 goalVertices_.clear();
256 graph_.clearQuery();
257 solutionCost_ = objective_->infiniteCost();
258 reverseCost_ = objective_->infiniteCost();
259 approximateSolutionCost_ = objective_->infiniteCost();
260 approximateSolutionCostToGoal_ = objective_->infiniteCost();
261
262 suboptimalityFactor_ = std::numeric_limits<double>::infinity();
263 restartReverseSearch();
264
265 numSparseCollisionChecksCurrentLevel_ = initialNumSparseCollisionChecks_;
266
267 numProcessedEdges_ = 0u;
268
269 reverseSearchTag_++;
270
271 if (!isMultiqueryEnabled_)
272 {
273 clear();
274 }
275 setup_ = false;
276 }
277 }
278
280 {
281 return solutionCost_;
282 }
283
284 void EITstar::setBatchSize(unsigned int numSamples)
285 {
286 batchSize_ = numSamples;
287 }
288
289 unsigned int EITstar::getBatchSize() const
290 {
291 return batchSize_;
292 }
293
295 {
296 initialNumSparseCollisionChecks_ = numChecks;
297 numSparseCollisionChecksCurrentLevel_ = numChecks;
298 numSparseCollisionChecksPreviousLevel_ = 0u;
299 }
300
301 void EITstar::setRadiusFactor(double factor)
302 {
303 graph_.setRadiusFactor(factor);
304 }
305
307 {
308 return graph_.getRadiusFactor();
309 }
310
312 {
313 suboptimalityFactor_ = factor;
314 }
315
316 void EITstar::enableMultiquery(bool multiquery)
317 {
318 isMultiqueryEnabled_ = multiquery;
319 graph_.enableMultiquery(multiquery);
320 };
321
323 {
324 return isMultiqueryEnabled_;
325 };
326
327 void EITstar::setStartGoalPruningThreshold(unsigned int threshold)
328 {
329 graph_.setEffortThreshold(threshold);
330 }
331
333 {
334 return graph_.getEffortThreshold();
335 }
336
337 void EITstar::enablePruning(bool enable)
338 {
339 graph_.enablePruning(enable);
340 }
341
343 {
344 return graph_.isPruningEnabled();
345 }
346
348 {
349 trackApproximateSolutions_ = track;
350 }
351
353 {
354 return trackApproximateSolutions_;
355 }
356
357 void EITstar::setUseKNearest(bool useKNearest)
358 {
359 graph_.setUseKNearest(useKNearest);
360 }
361
363 {
364 return graph_.getUseKNearest();
365 }
366
367 void EITstar::setMaxNumberOfGoals(unsigned int numberOfGoals)
368 {
369 graph_.setMaxNumberOfGoals(numberOfGoals);
370 }
371
372 unsigned int EITstar::getMaxNumberOfGoals() const
373 {
374 return graph_.getMaxNumberOfGoals();
375 }
376
378 {
379 assert(forwardQueue_);
380 return forwardQueue_->empty();
381 }
382
384 {
385 assert(reverseQueue_);
386 return reverseQueue_->empty();
387 }
388
389 std::vector<Edge> EITstar::getForwardQueue() const
390 {
391 return forwardQueue_->getEdges();
392 }
393
394 std::vector<Edge> EITstar::getReverseQueue() const
395 {
396 return reverseQueue_->getEdges();
397 }
398
399 std::vector<Edge> EITstar::getReverseTree() const
400 {
401 // Prepare the return value.
402 std::vector<Edge> edges;
403
404 // Define a helper that recursively gets all reverse edges of a vertex.
405 const std::function<void(const std::shared_ptr<Vertex> &)> getEdgesRecursively =
406 [&edges, &getEdgesRecursively](const std::shared_ptr<Vertex> &vertex)
407 {
408 for (const auto &child : vertex->getChildren())
409 {
410 getEdgesRecursively(child);
411 }
412 // Catch the root case of the recursion.
413 if (auto parent = vertex->getParent().lock())
414 {
415 edges.emplace_back(parent->getState(), vertex->getState());
416 }
417 };
418
419 // Get the edges of all reverse roots recursively.
420 for (const auto &root : goalVertices_)
421 {
422 getEdgesRecursively(root);
423 }
424
425 // Return all edges in the reverse tree.
426 return edges;
427 }
428
430 {
431 assert(forwardQueue_);
432 if (forwardQueue_->empty())
433 {
434 return {};
435 }
436 return forwardQueue_->peek(suboptimalityFactor_);
437 }
438
440 {
441 assert(reverseQueue_);
442 if (reverseQueue_->empty())
443 {
444 return {};
445 }
446 return reverseQueue_->peek();
447 }
448
450 {
451 // Get the base class data.
452 Planner::getPlannerData(data);
453
454 // Add the samples and their outgoing edges.
455 for (const auto &state : graph_.getStates())
456 {
457 // Add the state as a vertex.
458 data.addVertex(base::PlannerDataVertex(state->raw(), state->getId()));
459
460 // If the sample is in the forward tree, add the outgoing edges.
461 for (const auto &state2 : graph_.getStates())
462 {
463 if (state->isWhitelisted(state2))
464 {
465 data.addEdge(base::PlannerDataVertex(state->raw(), state->getId()),
466 base::PlannerDataVertex(state2->raw(), state2->getId()));
467 }
468 }
469 }
470 }
471
472 void EITstar::iterate(const ompl::base::PlannerTerminationCondition &terminationCondition)
473 {
474 // If we are in a multiquery setting, we do not want to search the approximation
475 // only consisting of start/goals, since this completely ignores the computational effort we have already
476 // invested Thus, the first thing we do in this instance is adding the first batch of samples.
477 if (isMultiqueryEnabled_ &&
478 graph_.getStates().size() == graph_.getStartStates().size() + graph_.getGoalStates().size())
479 {
480 improveApproximation(terminationCondition);
481 ++iteration_;
482 return;
483 }
484
485 // First check if the reverse search needs to be continued.
486 if (continueReverseSearch())
487 {
488 iterateReverseSearch();
489 } // If the reverse search is suspended, check if the forward search needs to be continued.
490 else if (continueForwardSearch())
491 {
492 iterateForwardSearch();
493 } // If neither the reverse nor the forward search needs to be continued, improve the approximation.
494 else
495 {
496 improveApproximation(terminationCondition);
497 }
498
499 // Increment the iteration count.
500 ++iteration_;
501 }
502
503 void EITstar::iterateForwardSearch()
504 {
505 // Ensure the forward queue is not empty.
506 assert(!forwardQueue_->empty());
507
508 // Get the top edge from the queue.
509 auto edge = forwardQueue_->pop(suboptimalityFactor_);
510 ++numProcessedEdges_;
511
512 // Assert the validity of the edge.
513 assert(edge.source->hasForwardVertex());
514 assert(!std::isfinite(suboptimalityFactor_) || isClosed(edge.target->asReverseVertex()));
515
516 // The edge is a freeby if its parent is already the parent of the child.
517 if (isInForwardTree(edge))
518 {
519 // We expand the target into the queue unless the target is a goal.
520 forwardQueue_->insertOrUpdate(expandUnlessGoal(edge.target));
521 return;
522 }
523
524 // If the edge can not improve the forward tree, then we're done with it.
525 if (!couldImproveForwardTree(edge))
526 {
527 return;
528 }
529
530 // The edge could possibly improve the tree, check if it is valid.
531 if (isValid(edge))
532 {
533 // Compute the true edge cost and the target cost through this edge.
534 const auto edgeCost = objective_->motionCost(edge.source->raw(), edge.target->raw());
535 const auto targetCost = combine(edge.source->getCurrentCostToCome(), edgeCost);
536
537 // Check if the edge can actually improve the forward path and tree.
538 if (isBetter(targetCost, edge.target->getCurrentCostToCome()) &&
539 isBetter(combine(targetCost, edge.target->getAdmissibleCostToGo()), solutionCost_))
540 {
541 // Convenience access to parent and child vertices.
542 auto source = edge.source->asForwardVertex();
543 auto target = edge.target->asForwardVertex();
544
545 // Update the parent of the child in the forward tree.
546 target->updateParent(source);
547
548 // Add the child to the parents children.
549 source->addChild(target);
550
551 // Set the edge cost associated with this parent.
552 target->setEdgeCost(edgeCost);
553
554 // Update the cost-to-come.
555 edge.target->setCurrentCostToCome(targetCost);
556
557 // Update the cost of the children.
558 const auto changedVertices = target->updateCurrentCostOfChildren(objective_);
559
560 // Reflect changes in queue and solution cost.
561 for (const auto &vertex : changedVertices)
562 {
563 // Update any edge in the queue.
564 forwardQueue_->updateIfExists({vertex->getParent().lock()->getState(), vertex->getState()});
565
566 // Update the solution if the vertex is a goal.
567 if (graph_.isGoal(vertex->getState()))
568 {
569 updateExactSolution(vertex->getState());
570 }
571 }
572
573 // Expand the outgoing edges into the queue unless this state is the goal state.
574 if (!graph_.isGoal(edge.target))
575 {
576 auto edges = expand(edge.target);
577 edges.erase(std::remove_if(edges.begin(), edges.end(), [&edge](const auto &e)
578 { return e.target->getId() == edge.source->getId(); }),
579 edges.end());
580 forwardQueue_->insertOrUpdate(edges);
581 }
582 else // It is the goal state, update the solution.
583 {
584 updateExactSolution(edge.target);
585 }
586 }
587 }
588 else
589 {
590 // Check if the edge is used in the reverse tree.
591 const bool inReverseTree = edge.source->asReverseVertex()->isParent(edge.target->asReverseVertex()) ||
592 edge.target->asReverseVertex()->isParent(edge.source->asReverseVertex());
593
594 // If this edge was in the reverse search tree, it could be updated.
595 if (inReverseTree)
596 {
597 // Remember the current number of collision checks and increase it.
598 numSparseCollisionChecksPreviousLevel_ = numSparseCollisionChecksCurrentLevel_;
599 numSparseCollisionChecksCurrentLevel_ = (2u * numSparseCollisionChecksPreviousLevel_) + 1u;
600
601 // Restart the reverse search.
602 restartReverseSearch();
603
604 // Rebuild the forward queue.
605 forwardQueue_->rebuild();
606 }
607 }
608 }
609
610 void EITstar::iterateReverseSearch()
611 {
612 // Ensure the reverse queue is not empty.
613 assert(!reverseQueue_->empty());
614
615 // Get the top edge from the queue.
616 auto edge = reverseQueue_->pop();
617 auto &source = edge.source;
618 auto &target = edge.target;
619
620 // The parent vertex must have an associated vertex in the tree.
621 assert(source->hasReverseVertex());
622
623 // Register the expansion of its parent.
624 source->asReverseVertex()->registerExpansionInReverseSearch(reverseSearchTag_);
625
626 // The edge is a freeby if its parent is already the parent of the child.
627 if (isInReverseTree(edge))
628 {
629 auto outgoingEdges = expand(target);
630 outgoingEdges.erase(std::remove_if(outgoingEdges.begin(), outgoingEdges.end(), [&source](const auto &e)
631 { return e.target->getId() == source->getId(); }),
632 outgoingEdges.end());
633
634 // If there are no outoing edges from the target state, we can nosider it expanded.
635 if (outgoingEdges.empty())
636 {
637 target->asReverseVertex()->registerExpansionInReverseSearch(reverseSearchTag_);
638 }
639 else // If there are outgoing edge, add them to or update them in the reverse queue.
640 {
641 reverseQueue_->insertOrUpdate(outgoingEdges);
642 }
643
644 return;
645 }
646
647 // Check whether the edge could be valid.
648 if (couldBeValid(edge))
649 {
650 // Compute the heuristic cost.
651 const auto edgeCost = objective_->motionCostHeuristic(source->raw(), target->raw());
652
653 // Incorporate the edge in the reverse tree if it provides an improvement.
654 const auto effort = estimateEffortToTarget(edge);
655 const bool doesDecreaseEffort = (effort < target->getEstimatedEffortToGo());
656
657 if ((!isMultiqueryEnabled_ && doesImproveReverseTree(edge, edgeCost)) ||
658 (isMultiqueryEnabled_ &&
659 ((std::isfinite(suboptimalityFactor_) && doesImproveReverseTree(edge, edgeCost)) ||
660 (!std::isfinite(suboptimalityFactor_) && doesDecreaseEffort))))
661 {
662 // Get the parent and child vertices.
663 auto parentVertex = source->asReverseVertex();
664 auto childVertex = target->asReverseVertex();
665
666 // The child must not be closed.
667 assert(!isClosed(childVertex));
668
669 // Update the parent of the child in the reverse tree.
670 childVertex->updateParent(parentVertex);
671
672 // Add the child to the children of the parent.
673 parentVertex->addChild(childVertex);
674
675 // Update the admissible cost to go.
676 target->setAdmissibleCostToGo(objective_->betterCost(
677 combine(source->getAdmissibleCostToGo(), edgeCost), edge.target->getAdmissibleCostToGo()));
678
679 // Update the best cost estimate of the target state if this edge can improve it.
680 target->setEstimatedCostToGo(
681 objective_->betterCost(estimateCostToTarget(edge), target->getEstimatedCostToGo()));
682
683 // Update the best effort estimate of the target state if this edge can improve it.
684 target->setEstimatedEffortToGo(std::min(effort, target->getEstimatedEffortToGo()));
685
686 // If this edge improves the reverse cost, update it.
687 if (graph_.isStart(target) && isBetter(target->getAdmissibleCostToGo(), reverseCost_))
688 {
689 reverseCost_ = target->getAdmissibleCostToGo();
690 }
691
692 // Update any edge in the forward queue affected by the target.
693 for (const auto &queueSource : target->getSourcesOfIncomingEdgesInForwardQueue())
694 {
695 forwardQueue_->updateIfExists({queueSource.lock(), target});
696 }
697
698 // Expand the target state into the reverse queue.
699 auto outgoingEdges = expand(target);
700
701 outgoingEdges.erase(
702 std::remove_if(outgoingEdges.begin(), outgoingEdges.end(),
703 [&source, this](const auto &e)
704 {
705 if (e.target->getId() == source->getId())
706 {
707 return true;
708 }
709 if (objective_->isFinite(solutionCost_) &&
710 isBetter(solutionCost_, reverseQueue_->computeAdmissibleSolutionCost(e)))
711 {
712 return true;
713 }
714
715 return false;
716 }),
717 outgoingEdges.end());
718
719 // If there are no outoing edges from the target state, we can nosider it expanded.
720 if (outgoingEdges.empty())
721 {
722 target->asReverseVertex()->registerExpansionInReverseSearch(reverseSearchTag_);
723 }
724 else // If there are outgoing edge, add them to or update them in the reverse queue.
725 {
726 reverseQueue_->insertOrUpdate(outgoingEdges);
727 }
728 }
729 }
730 }
731
732 void EITstar::improveApproximation(const ompl::base::PlannerTerminationCondition &terminationCondition)
733 {
734 // Add new states, also prunes states if enabled. The method returns true if all states have been added.
735 if (graph_.addStates(batchSize_, terminationCondition))
736 {
737 // Reset the reverse collision detection.
738 numSparseCollisionChecksCurrentLevel_ = initialNumSparseCollisionChecks_;
739 numSparseCollisionChecksPreviousLevel_ = 0u;
740
741 // Restart the reverse search.
742 restartReverseSearch();
743
744 // Reinitialize the forward queue.
745 forwardQueue_->clear();
746 expandStartVerticesIntoForwardQueue();
747 }
748 }
749
750 ompl::base::PlannerStatus::StatusType EITstar::ensureSetup()
751 {
752 // Call the base planners validity check. This checks if the
753 // planner is setup if not then it calls setup().
754 checkValidity();
755
756 // Ensure the planner is setup.
757 if (!setup_)
758 {
759 OMPL_ERROR("%s: Called solve without setting up the planner first.", name_.c_str());
761 }
762
763 // Ensure the space is setup.
764 if (!spaceInfo_->isSetup())
765 {
766 OMPL_ERROR("%s: Called solve without setting up the state space first.", name_.c_str());
768 }
769
771 }
772
774 EITstar::ensureStartAndGoalStates(const ompl::base::PlannerTerminationCondition &terminationCondition)
775 {
776 // If the graph currently does not have a start state, try to get one.
777 if (!graph_.hasStartState())
778 {
779 graph_.updateStartAndGoalStates(terminationCondition, &pis_);
780
781 // If we could not get a start state, then there's nothing to solve.
782 if (!graph_.hasStartState())
783 {
784 OMPL_ERROR("%s: No solution can be found as no start states are available", name_.c_str());
786 }
787 }
788
789 // If the graph currently does not have a goal state, we wait until we get one.
790 if (!graph_.hasGoalState())
791 {
792 graph_.updateStartAndGoalStates(terminationCondition, &pis_);
793
794 // If the graph still doesn't have a goal after waiting, then there's nothing to solve.
795 if (!graph_.hasGoalState())
796 {
797 OMPL_ERROR("%s: No solution can be found as no goal states are available", name_.c_str());
799 }
800 }
801
802 // Would it be worth implementing a 'setup' or 'checked' status type?
804 }
805
806 std::shared_ptr<ompl::geometric::PathGeometric>
807 EITstar::getPathToState(const std::shared_ptr<eitstar::State> &state) const
808 {
809 // Allocate a vector for states. The append function of the path inserts states in front of an
810 // std::vector, which is not very efficient. I'll rather iterate over the vector in reverse.
811 std::vector<std::shared_ptr<State>> states;
812 auto current = state;
813
814 // Collect all states in reverse order of the path (starting from the goal).
815 while (!graph_.isStart(current))
816 {
817 assert(current->asForwardVertex()->getParent().lock());
818 states.emplace_back(current);
819 current = current->asForwardVertex()->getParent().lock()->getState();
820 }
821 states.emplace_back(current);
822
823 // Append all states to the path in correct order (starting from the start).
824 auto path = std::make_shared<ompl::geometric::PathGeometric>(spaceInfo_);
825 for (auto it = states.crbegin(); it != states.crend(); ++it)
826 {
827 assert(*it);
828 assert((*it)->raw());
829 path->append((*it)->raw());
830 }
831
832 return path;
833 }
834
835 bool EITstar::continueSolving(const ompl::base::PlannerTerminationCondition &terminationCondition) const
836 {
837 // We stop solving the problem if:
838 // - The termination condition is satisfied; or
839 // - The current solution satisfies the objective; or
840 // - There is no better solution to be found for the current start goal pair and no new starts or
841 // goals are available
842 // We continue solving the problem if we don't stop solving.
843 return !(terminationCondition || objective_->isSatisfied(solutionCost_) ||
844 (!isBetter(graph_.minPossibleCost(), solutionCost_) && !pis_.haveMoreStartStates() &&
845 !pis_.haveMoreGoalStates()));
846 }
847
848 unsigned int EITstar::getForwardEffort() const
849 {
850 if (forwardQueue_->size() != 0u)
851 {
852 const auto forwardEdge = forwardQueue_->peek(suboptimalityFactor_);
853 const auto forwardEffort = forwardQueue_->estimateEffort(forwardEdge);
854
855 return forwardEffort;
856 }
857
858 return std::numeric_limits<unsigned int>::infinity();
859 }
860
861 bool EITstar::continueReverseSearch() const
862 {
863 // Never continue the reverse search if the reverse queue is empty.
864 if (reverseQueue_->empty())
865 {
866 return false;
867 }
868 // Always continue the reverse search if the reverse queue is not empty but the forward queue is.
869 else if (forwardQueue_->empty())
870 {
871 return true;
872 }
873
874 /*
875
876 If multiquery-planning is enabled, and we are in the process of searching for the initial solution,
877 there are two conditions under which we suspend the reverse search:
878
879 1. The best edge in the forward search has a closed target (i.e. there is a path to an edge in the
880 forward queue). Since we expand the reverse queue ordered by effort in the case that the suboptimality
881 factor is infite, and multiquery planning is enabled, this is the lowest effort path.
882
883 2. We already found a possible path, and there is no way that a lower effort path exists.
884
885 If multiquery-planning is not enabled, there are three conditions under which the reverse search can be
886 suspended:
887
888 1. The best edge in the forward search has a closed target (admissible cost-to-go estimate), and the
889 reverse search cannot lead to a better solution than the potential solution of this edge.
890
891 2. All edges in the forward queue have closed targets (admissible cost-to-go estimates).
892
893 3. We do not care about solution cost and the least-effort edge in the forward queue is connected to the
894 reverse tree.
895
896 */
897
898 const auto forwardEdge = forwardQueue_->peek(suboptimalityFactor_);
899
900 if (!std::isfinite(suboptimalityFactor_) && isMultiqueryEnabled_)
901 {
902 if (!forwardEdge.target->hasReverseVertex())
903 {
904 return true;
905 }
906
907 const auto forwardEffort = forwardQueue_->estimateEffort(forwardEdge);
908 const auto reverseEffort = reverseQueue_->peekEffort();
909 unsigned int minEffortToCome = 0u;
910
911 const auto reverseEdge = reverseQueue_->peek();
912 if (!reverseEdge.target->hasForwardVertex())
913 {
914 minEffortToCome = forwardQueue_->getMinEffortToCome();
915 }
916
917 return reverseEffort + minEffortToCome < forwardEffort;
918 }
919
920 const bool condition1 = isClosed(forwardEdge.target->asReverseVertex()) &&
921 isBetter(forwardQueue_->getLowerBoundOnOptimalSolutionCost(),
922 reverseQueue_->getLowerBoundOnOptimalSolutionCost());
923
924 const bool condition2 = !forwardQueue_->containsOpenTargets(reverseSearchTag_);
925
926 const bool condition3 = !std::isfinite(suboptimalityFactor_) && forwardEdge.target->hasReverseVertex();
927
928 // The reverse search must be continued if it cannot be suspended.
929 return !(condition1 || condition2 || condition3);
930 }
931
932 bool EITstar::continueForwardSearch() const
933 {
934 // Never continue to forward search if the forward queue is empty.
935 if (forwardQueue_->empty())
936 {
937 return false;
938 }
939
940 // The forward search must be continued if the potential solution cost of the best edge is lower than the
941 // current solution cost.
942 return isBetter(forwardQueue_->getLowerBoundOnOptimalSolutionCost(), solutionCost_);
943 }
944
945 void EITstar::updateExactSolution()
946 {
947 for (const auto &goal : graph_.getGoalStates())
948 {
949 if (goal->hasForwardVertex())
950 {
951 updateExactSolution(goal);
952 }
953 }
954 }
955
956 ompl::base::PlannerStatus::StatusType EITstar::updateSolution()
957 {
958 updateExactSolution();
959 if (objective_->isFinite(solutionCost_))
960 {
962 }
963 else if (trackApproximateSolutions_)
964 {
965 updateApproximateSolution();
967 }
968 else
969 {
971 }
972 }
973
974 void EITstar::restartReverseSearch()
975 {
976 reverseQueue_->clear();
977
978 if (isMultiqueryEnabled_)
979 {
980 reverseQueue_->setCostQueueOrder(std::isfinite(suboptimalityFactor_));
981 }
982 else
983 {
984 reverseQueue_->setCostQueueOrder(true);
985 }
986
987 goalVertices_.clear();
988 reverseCost_ = objective_->infiniteCost();
989 for (const auto &goal : graph_.getGoalStates())
990 {
991 goalVertices_.emplace_back(goal->asReverseVertex());
992 goal->setAdmissibleCostToGo(objective_->identityCost());
993 goal->setEstimatedCostToGo(objective_->identityCost());
994 goal->setEstimatedEffortToGo(0u);
995 }
996 expandGoalVerticesIntoReverseQueue();
997 ++reverseSearchTag_;
998 }
999
1000 void EITstar::updateApproximateSolution()
1001 {
1002 for (const auto &start : graph_.getStartStates())
1003 {
1004 start->asForwardVertex()->callOnBranch([this](const std::shared_ptr<eitstar::State> &state) -> void
1005 { updateApproximateSolution(state); });
1006 }
1007 }
1008
1009 void EITstar::updateCurrentCostToCome(const std::shared_ptr<eitstar::State> &state)
1010 {
1011 // There is no updating to do if the state is a start.
1012 if (graph_.isStart(state))
1013 {
1014 return;
1015 }
1016
1017 // If the state is not in the forward tree, then its current cost to come is infinity.
1018 if (!state->hasForwardVertex())
1019 {
1020 state->setCurrentCostToCome(objective_->infiniteCost());
1021 return;
1022 }
1023
1024 // If the state is in the forward tree and not the start, then update its cost.
1025 auto forwardVertex = state->asForwardVertex();
1026 state->setCurrentCostToCome(combine(forwardVertex->getParent().lock()->getState()->getCurrentCostToCome(),
1027 forwardVertex->getEdgeCost()));
1028 }
1029
1030 void EITstar::updateExactSolution(const std::shared_ptr<eitstar::State> &goal)
1031 {
1032 // We update the current goal if
1033 // 1. The new goal has a better cost to come than the old goal
1034 // 2. Or the exact solution we found is no longer registered with the problem definition
1035 if (isBetter(goal->getCurrentCostToCome(), solutionCost_) || !problem_->hasExactSolution())
1036 {
1037 // Update the best cost.
1038 solutionCost_ = goal->getCurrentCostToCome();
1039
1040 // Register this solution with the problem definition.
1041 ompl::base::PlannerSolution solution(getPathToState(goal));
1042 solution.setPlannerName(name_);
1043 solution.setOptimized(objective_, solutionCost_, objective_->isSatisfied(solutionCost_));
1044 problem_->addSolutionPath(solution);
1045
1046 if (!std::isfinite(suboptimalityFactor_))
1047 {
1048 // If we found this solution with a suboptimality factor greater than 1, set the factor to one now.
1049 // Empirically, this results in faster convergence, see associated publication for more info.
1050 suboptimalityFactor_ = 1.0;
1051
1052 if (isMultiqueryEnabled_)
1053 {
1054 restartReverseSearch();
1055 // Reinitialize the forward queue.
1056 forwardQueue_->clear();
1057 expandStartVerticesIntoForwardQueue();
1058 }
1059 }
1060
1061 // If enabled, pass the intermediate solution back through the callback:
1062 if (static_cast<bool>(pdef_->getIntermediateSolutionCallback()))
1063 {
1064 const auto &path = solution.path_->as<ompl::geometric::PathGeometric>()->getStates();
1065 // the callback requires a vector with const elements
1066 std::vector<const base::State *> const_path(path.begin(), path.end());
1067 pdef_->getIntermediateSolutionCallback()(this, const_path, solutionCost_);
1068 }
1069
1070 // Let the user know about the new solution.
1071 informAboutNewSolution();
1072 }
1073 }
1074
1075 void EITstar::updateApproximateSolution(const std::shared_ptr<eitstar::State> &state)
1076 {
1077 assert(trackApproximateSolutions_);
1078 if ((state->hasForwardVertex() || graph_.isStart(state)) && !graph_.isGoal(state))
1079 {
1080 const auto costToGoal = computeCostToGoToGoal(state);
1081 if (isBetter(costToGoal, approximateSolutionCostToGoal_) || !problem_->hasSolution())
1082 {
1083 approximateSolutionCost_ = state->getCurrentCostToCome();
1084 approximateSolutionCostToGoal_ = costToGoal;
1085 ompl::base::PlannerSolution solution(getPathToState(state));
1086 solution.setPlannerName(name_);
1087
1088 // Set the approximate flag.
1089 solution.setApproximate(costToGoal.value());
1090
1091 // This solution is approximate and can not satisfy the objective.
1092 solution.setOptimized(objective_, approximateSolutionCost_, false);
1093
1094 // Let the problem definition know that a new solution exists.
1095 pdef_->addSolutionPath(solution);
1096 }
1097 }
1098 }
1099
1100 ompl::base::Cost EITstar::computeCostToGoToGoal(const std::shared_ptr<eitstar::State> &state) const
1101 {
1102 auto bestCost = objective_->infiniteCost();
1103 for (const auto &goal : graph_.getGoalStates())
1104 {
1105 bestCost = objective_->betterCost(bestCost, objective_->motionCost(state->raw(), goal->raw()));
1106 }
1107 return bestCost;
1108 }
1109
1110 void EITstar::informAboutNewSolution() const
1111 {
1112 OMPL_INFORM("%s (%u iterations): Found a new exact solution of cost %.4f. Sampled a total of %u states, %u "
1113 "of which were valid samples (%.1f \%). Processed %u edges, %u of which were collision checked "
1114 "(%.1f \%). The forward search tree has %u vertices. The reverse search tree has %u vertices.",
1115 name_.c_str(), iteration_, solutionCost_.value(), graph_.getNumberOfSampledStates(),
1116 graph_.getNumberOfValidSamples(),
1117 graph_.getNumberOfSampledStates() == 0u ?
1118 0.0 :
1119 100.0 * (static_cast<double>(graph_.getNumberOfValidSamples()) /
1120 static_cast<double>(graph_.getNumberOfSampledStates())),
1121 numProcessedEdges_, numCollisionCheckedEdges_,
1122 numProcessedEdges_ == 0u ? 0.0 :
1123 100.0 * (static_cast<float>(numCollisionCheckedEdges_) /
1124 static_cast<float>(numProcessedEdges_)),
1125 countNumVerticesInForwardTree(), countNumVerticesInReverseTree());
1126 }
1127
1128 void EITstar::informAboutPlannerStatus(ompl::base::PlannerStatus::StatusType status) const
1129 {
1130 switch (status)
1131 {
1133 {
1134 OMPL_INFORM("%s (%u iterations): Found an exact solution of cost %.4f.", name_.c_str(), iteration_,
1135 solutionCost_.value());
1136 break;
1137 }
1139 {
1140 OMPL_INFORM("%s (%u iterations): Did not find an exact solution, but found an approximate solution "
1141 "of cost %.4f which is %.4f away from a goal (in cost space).",
1142 name_.c_str(), iteration_, approximateSolutionCost_.value(),
1143 approximateSolutionCostToGoal_.value());
1144 break;
1145 }
1147 {
1148 if (trackApproximateSolutions_)
1149 {
1150 OMPL_INFORM("%s (%u iterations): Did not find any solution.", name_.c_str(), iteration_);
1151 }
1152 else
1153 {
1154 OMPL_INFORM("%s (%u iterations): Did not find an exact solution, and tracking approximate "
1155 "solutions is disabled.",
1156 name_.c_str(), iteration_);
1157 }
1158 break;
1159 }
1168 {
1169 OMPL_INFORM("%s (%u iterations): Unable to solve the given planning problem.", name_.c_str(),
1170 iteration_);
1171 }
1172 }
1173 }
1174
1175 unsigned int EITstar::countNumVerticesInForwardTree() const
1176 {
1177 const auto states = graph_.getStates();
1178 return std::count_if(states.cbegin(), states.cend(),
1179 [](const auto &state) { return state->hasForwardVertex(); });
1180 }
1181
1182 unsigned int EITstar::countNumVerticesInReverseTree() const
1183 {
1184 const auto states = graph_.getStates();
1185 return std::count_if(states.cbegin(), states.cend(),
1186 [](const auto &state) { return state->hasReverseVertex(); });
1187 }
1188
1189 bool EITstar::couldImproveForwardPath(const Edge &edge) const
1190 {
1191 // If we currently don't have a solution, the anwer is yes.
1192 if (!objective_->isFinite(solutionCost_))
1193 {
1194 // Compare the costs of the full path heuristic with the current cost of the start state.
1195 const auto heuristicPathCost =
1196 combine(edge.source->getCurrentCostToCome(),
1197 objective_->motionCostHeuristic(edge.source->raw(), edge.target->raw()),
1198 objective_->costToGo(edge.target->raw(), problem_->getGoal().get()));
1199 if (isBetter(heuristicPathCost, solutionCost_))
1200 {
1201 return true;
1202 }
1203 else
1204 {
1205 return false;
1206 }
1207 }
1208 else
1209 {
1210 return true;
1211 }
1212 }
1213
1214 bool EITstar::couldImproveForwardTree(const Edge &edge) const
1215 {
1216 const auto heuristicCostToCome =
1217 combine(edge.source->getCurrentCostToCome(),
1218 objective_->motionCostHeuristic(edge.source->raw(), edge.target->raw()));
1219 return isBetter(heuristicCostToCome, edge.target->getCurrentCostToCome());
1220 }
1221
1222 bool EITstar::doesImproveForwardPath(const Edge &edge, const ompl::base::Cost &edgeCost) const
1223 {
1224 // If we don't have a solution yet, the answer is imediately true.
1225 if (!objective_->isFinite(solutionCost_))
1226 {
1227 return true;
1228 }
1229
1230 // Check whether it can improve the current solution.
1231 return isBetter(
1232 combine(edge.source->getCurrentCostToCome(), edgeCost, edge.target->getLowerBoundCostToGo()),
1233 solutionCost_);
1234 }
1235
1236 bool EITstar::doesImproveForwardTree(const Edge &edge, const ompl::base::Cost &edgeCost) const
1237 {
1238 return isBetter(combine(edge.source->getCurrentCostToCome(), edgeCost),
1239 edge.target->getCurrentCostToCome());
1240 }
1241
1242 ompl::base::Cost EITstar::estimateCostToTarget(const eitstar::Edge &edge) const
1243 {
1244 return combine(edge.source->getEstimatedCostToGo(),
1245 objective_->motionCostBestEstimate(edge.source->raw(), edge.target->raw()));
1246 }
1247
1248 unsigned int EITstar::estimateEffortToTarget(const eitstar::Edge &edge) const
1249 {
1250 // if we previously validated (=whitelisted) an edge, the effort to
1251 // check is zero
1252 std::size_t checksToCome = 0u;
1253 if (edge.source->isWhitelisted(edge.target))
1254 {
1255 checksToCome = 0u;
1256 }
1257 else
1258 {
1259 // Get the segment count for the full resolution.
1260 const std::size_t fullSegmentCount = space_->validSegmentCount(edge.source->raw(), edge.target->raw());
1261 // Get the number of checks already performed on this edge.
1262 const std::size_t performedChecks = edge.target->getIncomingCollisionCheckResolution(edge.source);
1263
1264 checksToCome = fullSegmentCount - performedChecks;
1265 }
1266
1267 return edge.source->getEstimatedEffortToGo() + checksToCome;
1268 }
1269
1270 bool EITstar::isValid(const Edge &edge) const
1271 {
1272 // The number of checks required to determine whether the edge is valid is the valid segment count minus one
1273 // because we know that the source and target states are valid.
1274 const std::size_t numChecks = space_->validSegmentCount(edge.source->raw(), edge.target->raw()) - 1u;
1275
1276 return isValidAtResolution(edge, numChecks);
1277 }
1278
1279 bool EITstar::couldBeValid(const Edge &edge) const
1280 {
1281 return isValidAtResolution(edge, numSparseCollisionChecksCurrentLevel_);
1282 }
1283
1284 bool EITstar::isValidAtResolution(const Edge &edge, std::size_t numChecks) const
1285 {
1286 // Check if the edge is whitelisted.
1287 if (edge.source->isWhitelisted(edge.target))
1288 {
1289 return true;
1290 }
1291
1292 // If the edge is blacklisted.
1293 if (edge.source->isBlacklisted(edge.target))
1294 {
1295 return false;
1296 }
1297
1298 // Get the segment count for the full resolution.
1299 const std::size_t fullSegmentCount = space_->validSegmentCount(edge.source->raw(), edge.target->raw());
1300
1301 // The segment count is the number of checks on this level plus 1, capped by the full resolution segment
1302 // count.
1303 const auto segmentCount = std::min(numChecks + 1u, fullSegmentCount);
1304
1305 /***
1306 Let's say we want to perform seven collision checks on an edge:
1307
1308 position of checks: |--------x--------x--------x--------x--------x--------x--------x--------|
1309 indices of checks: 1 2 3 4 5 6 7
1310 order of testing: 4 2 5 1 6 3 7
1311
1312 We create a queue that holds segments and always test the midpoint of the segments. We start
1313 with the outermost indices and then break the segment in half until the segment collapses to a single
1314 point:
1315
1316 1. indices = { (1, 7) }
1317 current = (1, 7) -> test midpoint = 4 -> add (1, 3) and (5, 7) to queue
1318
1319 2. indices = { (1, 3), (5, 7) }
1320 current (1, 3) -> test midpoint = 2 -> add (1, 1) and (3, 3) to queue
1321
1322 3. indices = { (5, 7), (1, 1), (3, 3) }
1323 current (5, 7) -> test midpoint = 6 -> add (5, 5) and (7, 7) to queue
1324
1325 4. indices = { (1, 1), (3, 3), (5, 5), (7, 7) }
1326 current (1, 1) -> test midpoint = 1 -> add nothing to the queue
1327
1328 5. indices = { (3, 3), (5, 5), (7, 7) }
1329 current (3, 3) -> test midpoint = 3 -> add nothing to the queue
1330
1331 6. indices = { (5, 5) (7, 7) }
1332 current (5, 5) -> test midpoint = 5 -> add nothing to the queue
1333
1334 7. indices = { (7, 7) }
1335 current (7, 7) -> test midpoint = 7 -> add nothing to the queue
1336 ***/
1337
1338 // Store the current check number.
1339 std::size_t currentCheck = 1u;
1340
1341 // Get the number of checks already performed on this edge.
1342 const std::size_t performedChecks = edge.target->getIncomingCollisionCheckResolution(edge.source);
1343
1344 // Initialize the queue of positions to be tested.
1345 std::queue<std::pair<std::size_t, std::size_t>> indices;
1346 indices.emplace(1u, numChecks);
1347
1348 // Test states while there are states to be tested.
1349 while (!indices.empty())
1350 {
1351 // Get the current segment.
1352 const auto current = indices.front();
1353
1354 // Get the midpoint of the segment.
1355 auto mid = (current.first + current.second) / 2;
1356
1357 // Only do the detection if we haven't tested this state on a previous level.
1358 if (currentCheck > performedChecks)
1359 {
1360 space_->interpolate(edge.source->raw(), edge.target->raw(),
1361 static_cast<double>(mid) / static_cast<double>(segmentCount), detectionState_);
1362
1363 if (!spaceInfo_->isValid(detectionState_))
1364 {
1365 // Blacklist the edge.
1366 edge.source->blacklist(edge.target);
1367 edge.target->blacklist(edge.source);
1368
1369 // Register it with the graph.
1370 graph_.registerInvalidEdge(edge);
1371 return false;
1372 }
1373 }
1374
1375 // Remove the current segment from the queue.
1376 indices.pop();
1377
1378 // Create the first and second half of the split segment if necessary.
1379 if (current.first < mid)
1380 {
1381 indices.emplace(current.first, mid - 1u);
1382 }
1383 if (current.second > mid)
1384 {
1385 indices.emplace(mid + 1u, current.second);
1386 }
1387
1388 // Increase the current check number.
1389 ++currentCheck;
1390 }
1391
1392 // Remember at what resolution this edge was already checked. We're assuming that the number of collision
1393 // checks is symmetric for each edge.
1394 edge.source->setIncomingCollisionCheckResolution(edge.target, currentCheck - 1u);
1395 edge.target->setIncomingCollisionCheckResolution(edge.source, currentCheck - 1u);
1396
1397 // Whitelist this edge if it was checked at full resolution.
1398 if (segmentCount == fullSegmentCount)
1399 {
1400 ++numCollisionCheckedEdges_;
1401 edge.source->whitelist(edge.target);
1402 edge.target->whitelist(edge.source);
1403
1404 graph_.registerWhitelistedState(edge.source);
1405 graph_.registerWhitelistedState(edge.target);
1406 }
1407
1408 return true;
1409 }
1410
1411 bool EITstar::isBetter(const ompl::base::Cost &lhs, const ompl::base::Cost &rhs) const
1412 {
1413 return objective_->isCostBetterThan(lhs, rhs);
1414 }
1415
1416 ompl::base::Cost EITstar::combine(const ompl::base::Cost &lhs, const ompl::base::Cost &rhs) const
1417 {
1418 return objective_->combineCosts(lhs, rhs);
1419 }
1420
1421 void EITstar::expandStartVerticesIntoForwardQueue()
1422 {
1423 for (auto &vertex : startVertices_)
1424 {
1425 forwardQueue_->insertOrUpdate(expand(vertex->getState()));
1426 }
1427 }
1428
1429 void EITstar::expandGoalVerticesIntoReverseQueue()
1430 {
1431 for (auto &vertex : goalVertices_)
1432 {
1433 reverseQueue_->insertOrUpdate(expand(vertex->getState()));
1434 }
1435 }
1436
1437 bool EITstar::isClosed(const std::shared_ptr<Vertex> &vertex) const
1438 {
1439 return vertex->getExpandTag() == reverseSearchTag_;
1440 }
1441
1442 bool EITstar::isInForwardTree(const Edge &edge) const
1443 {
1444 if (!edge.source->hasForwardVertex() || !edge.target->hasForwardVertex())
1445 {
1446 return false;
1447 }
1448
1449 return edge.target->asForwardVertex()->isParent(edge.source->asForwardVertex());
1450 }
1451
1452 bool EITstar::isInReverseTree(const Edge &edge) const
1453 {
1454 if (!edge.source->hasReverseVertex() || !edge.target->hasReverseVertex())
1455 {
1456 return false;
1457 }
1458
1459 return edge.target->asReverseVertex()->isParent(edge.source->asReverseVertex());
1460 }
1461
1462 bool EITstar::doesImproveReversePath(const Edge &edge) const
1463 {
1464 // If there is no reverse path the answer is ja.
1465 if (!objective_->isFinite(reverseCost_))
1466 {
1467 return true;
1468 }
1469
1470 // Compare the costs of the full path heuristic with the current cost of the start state.
1471 const auto heuristicPathCost =
1472 combine(edge.source->getAdmissibleCostToGo(),
1473 objective_->motionCostHeuristic(edge.source->raw(), edge.target->raw()),
1474 edge.target->getLowerBoundCostToCome());
1475
1476 return isBetter(heuristicPathCost, reverseCost_);
1477 }
1478
1479 bool EITstar::doesImproveReverseTree(const Edge &edge, const ompl::base::Cost &admissibleEdgeCost) const
1480 {
1481 return isBetter(combine(edge.source->getAdmissibleCostToGo(), admissibleEdgeCost),
1482 edge.target->getAdmissibleCostToGo());
1483 }
1484
1485 std::vector<Edge> EITstar::expandUnlessGoal(const std::shared_ptr<State> &state) const
1486 {
1487 if (graph_.isGoal(state))
1488 {
1489 return {};
1490 }
1491
1492 return expand(state);
1493 }
1494
1495 std::vector<Edge> EITstar::expand(const std::shared_ptr<State> &state) const
1496 {
1497 // Only states associated with a vertex in either of the trees should be expanded.
1498 assert(state->hasForwardVertex() || state->hasReverseVertex());
1499
1500 // Prepare the return variable.
1501 std::vector<Edge> outgoingEdges;
1502
1503 // Get the neighbors in the current graph.
1504 for (const auto &neighborState : graph_.getNeighbors(state))
1505 {
1506 outgoingEdges.emplace_back(state, neighborState.lock());
1507 }
1508
1509 // If the state is in the forward search tree, extra edges have to be added.
1510 if (state->hasForwardVertex())
1511 {
1512 // Get the vertex in the forward search tree associated with this state.
1513 auto forwardVertex = state->asForwardVertex();
1514
1515 // Add the outgoing edge to this vertex's parent in the forward tree, if it exists.
1516 if (!graph_.isStart(state))
1517 {
1518 // If this vertex is not a start state, it must have a parent.
1519 assert(forwardVertex->getParent().lock());
1520
1521 // Get the state associated with the parent vertex.
1522 auto forwardParentState = forwardVertex->getParent().lock()->getState();
1523
1524 // Add the edge to the forward tree parent if it has not already being added.
1525 if (std::find_if(
1526 outgoingEdges.cbegin(), outgoingEdges.cend(), [&forwardParentState](const auto &edge)
1527 { return edge.target->getId() == forwardParentState->getId(); }) == outgoingEdges.cend())
1528 {
1529 outgoingEdges.emplace_back(state, forwardParentState);
1530 }
1531 }
1532
1533 // Add the edge to the forward children.
1534 for (const auto &child : forwardVertex->getChildren())
1535 {
1536 // Get the state associated with the child vertex.
1537 auto forwardChildState = child->getState();
1538
1539 // Add the edge to the forward tree child if it has not already being added.
1540 if (std::find_if(
1541 outgoingEdges.cbegin(), outgoingEdges.cend(), [&forwardChildState](const auto &edge)
1542 { return edge.target->getId() == forwardChildState->getId(); }) == outgoingEdges.cend())
1543 {
1544 outgoingEdges.emplace_back(state, forwardChildState);
1545 }
1546 }
1547 }
1548
1549 // If the state is in the reverse search tree, extra edges have to be added.
1550 if (state->hasReverseVertex())
1551 {
1552 // Get the vertex in the reverse search tree associated with this state.
1553 auto reverseVertex = state->asReverseVertex();
1554
1555 // Add the outgoing edge to this vertex's parent in the reverse tree, if it exists.
1556 if (!graph_.isGoal(state))
1557 {
1558 // If this state is not a goal, it must have a parent.
1559 assert(reverseVertex->getParent().lock());
1560
1561 // Get the state associated with the parent vertex.
1562 auto reverseParentState = reverseVertex->getParent().lock()->getState();
1563
1564 // Add the edge to the reverse tree parent if it has not already being added.
1565 if (std::find_if(
1566 outgoingEdges.cbegin(), outgoingEdges.cend(), [&reverseParentState](const auto &edge)
1567 { return edge.target->getId() == reverseParentState->getId(); }) == outgoingEdges.cend())
1568 {
1569 outgoingEdges.emplace_back(state, reverseParentState);
1570 }
1571 }
1572
1573 // Add the edge to the reverse children.
1574 for (const auto &child : reverseVertex->getChildren())
1575 {
1576 // Get the state associated with the child vertex.
1577 auto reverseChildState = child->getState();
1578
1579 // Add the edge to the reverse tree parent if it has not already being added.
1580 if (std::find_if(
1581 outgoingEdges.cbegin(), outgoingEdges.cend(), [&reverseChildState](const auto &edge)
1582 { return edge.target->getId() == reverseChildState->getId(); }) == outgoingEdges.cend())
1583 {
1584 outgoingEdges.emplace_back(state, reverseChildState);
1585 }
1586 }
1587 }
1588
1589 return outgoingEdges;
1590 }
1591 } // namespace geometric
1592} // namespace ompl
Definition of a cost value. Can represent the cost of a motion or the cost of a state.
Definition Cost.h:48
Base class for a vertex in the PlannerData structure. All derived classes must implement the clone an...
Definition PlannerData.h:59
Object containing planner generated vertex and edge data. It is assumed that all vertices are unique,...
virtual bool addEdge(unsigned int v1, unsigned int v2, const PlannerDataEdge &edge=PlannerDataEdge(), Cost weight=Cost(1.0))
Adds a directed edge between the given vertex indexes. An optional edge structure and weight can be s...
unsigned int addVertex(const PlannerDataVertex &st)
Adds the given vertex to the graph data. The vertex index is returned. Duplicates are not added....
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
void addPlannerProgressProperty(const std::string &progressPropertyName, const PlannerProgressProperty &prop)
Add a planner progress property called progressPropertyName with a property querying function prop to...
Definition Planner.h:394
PlannerSpecs specs_
The specifications of the planner (its capabilities).
Definition Planner.h:413
void declareParam(const std::string &name, const PlannerType &planner, const SetterType &setter, const GetterType &getter, const std::string &rangeSuggestion="")
This function declares a parameter for this planner instance, and specifies the setter and getter fun...
Definition Planner.h:371
std::string name_
The name of this planner.
Definition Planner.h:410
bool setup_
Flag indicating whether setup() has been called.
Definition Planner.h:424
bool areApproximateSolutionsTracked() const
Returns whether approximate solutions are tracked or not.
Definition EITstar.cpp:352
bool isReverseQueueEmpty() const
Returns true if the reverse queue is empty.
Definition EITstar.cpp:383
unsigned int getStartGoalPruningThreshold() const
Get threshold at which we prune starts/goals.
Definition EITstar.cpp:332
void setStartGoalPruningThreshold(unsigned int threshold)
Set start/goal pruning threshold.
Definition EITstar.cpp:327
ompl::base::Cost bestCost() const
Returns the cost of the current best solution.
Definition EITstar.cpp:279
double getRadiusFactor() const
Returns the radius factor.
Definition EITstar.cpp:306
bool isMultiqueryEnabled() const
Get wheter multiquery is enabled or not.
Definition EITstar.cpp:322
void clear() override
Clears all internal planner structures but retains settings. Subsequent calls to solve() will start f...
Definition EITstar.cpp:213
void enableMultiquery(bool multiquery)
Set wheter multiquery is enabled or not.
Definition EITstar.cpp:316
void setBatchSize(unsigned int numSamples)
Sets the number of samples per batch.
Definition EITstar.cpp:284
void setup() override
Setup the parts of the planner that rely on the problem definition being set.
Definition EITstar.cpp:96
void setRadiusFactor(double factor)
Sets the radius factor.
Definition EITstar.cpp:301
~EITstar()
Destructs this instance of EIT*.
Definition EITstar.cpp:91
void setMaxNumberOfGoals(unsigned int numberOfGoals)
Set the maximum number of goals EIT* will sample from sampleable goal regions.
Definition EITstar.cpp:367
eitstar::Edge getNextReverseEdge() const
Returns the next edge in the reverse queue.
Definition EITstar.cpp:439
void trackApproximateSolutions(bool track)
Sets whether to track approximate solutions or not.
Definition EITstar.cpp:347
std::vector< eitstar::Edge > getReverseQueue() const
Returns a copy of the reverse queue.
Definition EITstar.cpp:394
std::vector< eitstar::Edge > getForwardQueue() const
Returns a copy of the forward queue.
Definition EITstar.cpp:389
void setInitialNumberOfSparseCollisionChecks(std::size_t numChecks)
Sets the initial number of collision checks on the reverse search.
Definition EITstar.cpp:294
eitstar::Edge getNextForwardEdge() const
Returns the next edge in the forward queue.
Definition EITstar.cpp:429
unsigned int getForwardEffort() const
Returns the effort of the edge at the top of the forward queue.
Definition EITstar.cpp:848
ompl::base::PlannerStatus solve(const ompl::base::PlannerTerminationCondition &terminationCondition) override
Solves the provided motion planning problem, respecting the given termination condition.
Definition EITstar.cpp:173
void setSuboptimalityFactor(double factor)
Sets the (initial) suboptimality factor.
Definition EITstar.cpp:311
void enablePruning(bool prune)
Set whether pruning is enabled or not.
Definition EITstar.cpp:337
EITstar(const std::shared_ptr< ompl::base::SpaceInformation > &spaceInfo)
Constructs an instance of EIT* using the provided space information.
Definition EITstar.cpp:52
void getPlannerData(base::PlannerData &data) const override
Returns the planner data.
Definition EITstar.cpp:449
bool isForwardQueueEmpty() const
Returns true if the forward queue is empty.
Definition EITstar.cpp:377
unsigned int getMaxNumberOfGoals() const
Returns the maximum number of goals EIT* will sample from sampleable goal regions.
Definition EITstar.cpp:372
std::vector< eitstar::Edge > getReverseTree() const
Returns copies of the edges in the reverse tree.
Definition EITstar.cpp:399
void setUseKNearest(bool useKNearest)
Set whether to use a k-nearest RGG connection model. If false, EIT* uses an r-disc model.
Definition EITstar.cpp:357
unsigned int getBatchSize() const
Returns the number of samples per batch.
Definition EITstar.cpp:289
bool getUseKNearest() const
Returns whether to use a k-nearest RGG connection model. If false, EIT* uses an r-disc model.
Definition EITstar.cpp:362
void clearQuery() override
Clears all query-specific information, such as start and goal states and search trees....
Definition EITstar.cpp:248
bool isPruningEnabled() const
Returns whether pruning is enabled or not.
Definition EITstar.cpp:342
#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
#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...
@ GOAL_SAMPLEABLE_REGION
This bit is set if casting to sampleable goal regions (ompl::base::GoalSampleableRegion) is possible.
Definition GoalTypes.h:56
This namespace contains code that is specific to planning under geometric constraints.
Main namespace. Contains everything in this library.
A class to store the exit status of Planner::solve().
StatusType
The possible values of the status returned by a planner.
@ ABORT
The planner did not find a solution for some other reason.
@ 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.
@ UNKNOWN
Uninitialized status.
@ TYPE_COUNT
The number of possible status values.
@ INFEASIBLE
The planner decided that problem is infeasible.
@ CRASH
The planner crashed.
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