Multilevel Planning Tutorial

We show in this tutorial how to setup a multilevel planning task for a 2D rigid body in the plane.

Classical Planning

We assume that you know how to setup a classical planning problem in OMPL. To recap, we need to define

We then construct and solve a planning problem as

//...
// Set start and goal states to pdef
//...
ompl::base::PlannerPtr planner = std::make_shared<geometric::RRT>(si);
planner->setProblemDefinition(pdef);
planner->setup();
planner->solve();

Multilevel Planning

In a Multilevel planning setting, we change this routine to add a finite number of levels of abstraction, which can be exploited by an algorithm (if it supports it).

While the ompl::base::ProblemDefinitionPtr remains the same, we change the ompl::base::SpaceInformationPtr to a vector and the ompl::base::PlannerPtr to a multilevel planner. This means we define the following structures:

  • std::vector<ompl::base::SpaceInformationPtr>, a vector of SpaceInformationPtr, sorted in ascending order depending on number of dimensions. Last element has to be the original state space.
  • ompl::multilevel::QRRT, a specific multilevel planner, has to inherit from ompl::multilevel::BundleSpaceSequence.h

A planning problem can then be solved as

std::vector<ompl::base::SpaceInformationPtr> siVec;
//...
// Build siVec (see below), set start and goal states to pdef
//...
auto planner = std::make_shared<ompl::multilevel::QRRT>(siVec);
planner->setProblemDefinition(pdef);
planner->setup();
planner->solve();

Building multiple levels of abstraction

Let us build a small sequence of two levels for a rigid body in 2D with configuration space \(SE(2)\). You can find this example here

First, we need to construct the ompl::base::SpaceInformationPtr for each level. In this case we use spaces \(\{\mathbb{R}^2, SE(2)\}\), corresponding to an abstraction by projecting onto the position of the rigid body. The space is bounded to be in the unit square \([0,1]^2\)

auto SE2(std::make_shared<ompl::base::SE2StateSpace>());
bounds.setLow(0);
bounds.setHigh(1);
SE2->setBounds(bounds);
ompl::base::SpaceInformationPtr si_SE2(std::make_shared<ompl::base::SpaceInformation>(SE2));
auto R2(std::make_shared<ompl::base::RealVectorStateSpace>(2));
R2->setBounds(0, 1);
ompl::base::SpaceInformationPtr si_R2(std::make_shared<ompl::base::SpaceInformation>(R2));

Second, we need to set the ompl::base::StateValidityChecker for each ompl::base::SpaceInformationPtr. This is done by defining a validity function which evaluates to true if a state is feasible and false if it is infeasible.

For the \(SE(2)\) constraint, we declare all configurations feasible where the rotation is smaller than \(\frac{\pi}{2}\) and where the position is not in a circle of radius \(0.2\) located at \((0.5,0.5)\).

bool boxConstraint(const double values[])
{
const double &x = values[0]-0.5;
const double &y = values[1]-0.5;
double pos_cnstr = sqrt(x*x + y*y);
return (pos_cnstr > 0.2);
}
bool isStateValid_SE2(const ompl::base::State *state)
{
const auto *SE2state = state->as<ompl::base::SE2StateSpace::StateType>();
const auto *R2 = SE2state->as<ompl::base::RealVectorStateSpace::StateType>(0);
const auto *SO2 = SE2state->as<ompl::base::SO2StateSpace::StateType>(1);
return boxConstraint(R2->values) && (SO2->value < boost::math::constants::pi<double>() / 2.0);
}

The validity function for the simplified robot can be constructed by removing some of the constraints. In real experiments, we often remove links or parts of links from a robot, which implicitly removes the constraints. We choose the following constraint

bool isStateValid_R2(const ompl::base::State *state)
{
const auto *R2 = state->as<ompl::base::RealVectorStateSpace::StateType>();
return boxConstraint(R2->values);
}

Finally, we set the validity function and push the ompl::base::SpaceInformationPtr into siVec. Note that the ompl::base::SpaceInformationPtr have to be sorted in ascending order depending on number of dimensions.

si_SE2->setStateValidityChecker(isStateValid_SE2);
si_R2->setStateValidityChecker(isStateValid_R2);
siVec.push_back(si_R2);
siVec.push_back(si_SE2);

NOTE: The runtime of a multilevel planner depends crucially on the sequence of defined. For some spaces, planning time can be very fast, while for others it is can still be outperformed by classical planner such as ompl::geometric::RRT (which is equivalent to running ompl::multilevel::QRRT with a single configuration space). Which spaces work best is still an open research question. A good heuristic is to use more levels the more narrow passages we have in an environment. More information can be found in the QRRT paper.

AnnotatedPlannerDataVertex

Having defined a Multilevel planner, we can utilize it similar to a usual ompl::base::PlannerPtr, i.e. it can be added to any benchmark, and we can get the planner data.

However, the classical PlannerData structure does not know about multiple levels of abstraction. To add this information, we wrote the class ompl::multilevel::PlannerDataVertexAnnotated, which inherits from PlannerDataVertex. PlannerDataVertexAnnotated adds new functionalities, including

  • getLevel(), returns the level the current vertex is on
  • getMaxLevel(), returns the number of levels
  • getBaseState(), returns a state on the current vertex level
  • getState(), returns a state in the original configuration space (this way the behavior is the same as for the classical PlannerData class).

You can use this in your code in the following way:

#include <ompl/multilevel/datastructures/PlannerDataVertexAnnotated.h>
dynamic_cast<ompl::multilevel::PlannerDataVertexAnnotated*>(&pd->getVertex(0));
if(v!=nullptr){
unsigned int level = v->getLevel();
unsigned int maxLevel = v->getMaxLevel();
const ompl::base::State *s_BaseState = v->getBaseState();
const ompl::base::State *s_ConfigurationState = v->getState();
}

Further Information

For more information, please refer to either the general introduction to Multilevel Planning or the demos.

A shared pointer wrapper for ompl::base::SpaceInformation.
const ompl::base::State * getBaseState() const
Returns base state, indepent of mode.
Definition of an abstract state.
Definition: State.h:113
virtual const ompl::base::State * getState() const override
Returns base state in baseMode and total state in totalMode. The total space here is the last element...
An annotated vertex, adding information about its level in the multilevel hierarchy....
const T * as() const
Cast this instance to a desired type.
Definition: State.h:162
A shared pointer wrapper for ompl::base::Planner.
A shared pointer wrapper for ompl::base::ProblemDefinition.
The definition of a state in SO(2)
A state in SE(2): (x, y, yaw)
The lower and upper bounds for an Rn space.