Loading...
Searching...
No Matches
Motion Planning for Realistic Robot Arms with VAMP

This tutorial shows how to use VAMP (Vector-Accelerated Motion Planning) with OMPL for robot motion planning. VAMP provides SIMD-accelerated collision checking and forward kinematics, processing multiple robot configurations simultaneously using vectorized instructions. By plugging VAMP into OMPL's planning framework, you get the flexibility of OMPL's planner library with the speed of VAMP's collision checking.

VAMP ships with built-in support for several robots including the Franka Panda, UR5, and Fetch. In this tutorial we will plan for a 7-DOF Panda arm navigating around sphere obstacles. In this tutorial we use the Python bindings for OMPL. There is also a pure C++ version of this demo; see VAMPPlanning.cpp.


The Panda arm inside a cage of 14 sphere obstacles.

Prerequisites

Install the required Python packages:

pip install -r demos/vamp/requirements.txt

You will also need the OMPL Python bindings installed. See the installation instructions for details.

Bridging VAMP and OMPL

OMPL is designed to be agnostic to the robot and environment representation. To use VAMP's collision checking with OMPL, we need to implement three bridge classes that translate between OMPL's state representation and VAMP's configuration representation.

State Space

First, we create a state space that uses VAMP's robot module to define the joint limits. This is a simple wrapper around ompl::base::RealVectorStateSpace that reads bounds directly from the VAMP robot:

from ompl import base as ob
import vamp
class VampStateSpace(ob.RealVectorStateSpace):
def __init__(self, robot: vamp.robot):
super().__init__(robot.dimension())
self.robot = robot
self.dimension = robot.dimension()
bounds = ob.RealVectorBounds(self.dimension)
upper_bounds = robot.upper_bounds()
lower_bounds = robot.lower_bounds()
for i in range(self.dimension):
bounds.setLow(i, lower_bounds[i])
bounds.setHigh(i, upper_bounds[i])
self.setBounds(bounds)
The lower and upper bounds for an Rn space.
A state space representing Rn. The distance function is the L2 norm.

The robot.dimension() call returns the number of joints (7 for the Panda), and robot.lower_bounds() / robot.upper_bounds() return the joint limits. This is all OMPL needs to sample and interpolate in the configuration space.

State Validity Checker

Next, we implement a state validity checker that delegates to VAMP's robot.validate() function. This checks whether a single configuration is collision-free against the environment:

class VampStateValidityChecker(ob.StateValidityChecker):
def __init__(self, si: ob.SpaceInformation, env: vamp.Environment, robot: vamp.robot):
super().__init__(si)
self.env = env
self.robot = robot
self.dimension = robot.dimension()
def isValid(self, state: ob.State) -> bool:
config = state[0:self.dimension]
return self.robot.validate(config, self.env)
Abstract definition for a class checking the validity of states. The implementation of this class mus...

The key line is self.robot.validate(config, self.env), which uses VAMP's vectorized collision checking to test the configuration against all obstacles in the environment.

Motion Validator

Finally, we implement a motion validator that checks whether an entire straight-line motion between two configurations is collision-free. VAMP provides robot.validate_motion() for this purpose, which internally checks configurations along the path at an appropriate resolution:

class VampMotionValidator(ob.MotionValidator):
def __init__(self, si: ob.SpaceInformation, env: vamp.Environment, robot: vamp.robot):
super().__init__(si)
self.env = env
self.robot = robot
self.dimension = robot.dimension()
def checkMotion(self, s1: ob.State, s2: ob.State) -> bool:
config1 = s1[0:self.dimension]
config2 = s2[0:self.dimension]
return self.robot.validate_motion(config1, config2, self.env)
Abstract definition for a class checking the validity of motions – path segments between states....
Definition of an abstract state.
Definition State.h:50

Note: By providing a custom ompl::base::MotionValidator, we bypass OMPL's default ompl::base::DiscreteMotionValidator, which would otherwise call isValid() at discrete steps along the motion. VAMP's validate_motion() handles the entire edge check internally in a single call.

These three classes are provided in the vamp_state_space.py module included with the demo.

Setting Up the Planning Problem

With the bridge classes in place, we can set up a complete planning problem. We will plan for a Panda arm navigating through a cage of sphere obstacles.

Creating the Environment

First, define the obstacle environment. VAMP represents obstacles as geometric primitives (spheres, boxes, cylinders). Here we create a cage of 14 spheres:

import vamp
import numpy as np
# Sphere obstacle centers
problem = [
[0.55, 0, 0.25],
[0.35, 0.35, 0.25],
[0, 0.55, 0.25],
[-0.55, 0, 0.25],
[-0.35, -0.35, 0.25],
[0, -0.55, 0.25],
[0.35, -0.35, 0.25],
[0.35, 0.35, 0.8],
[0, 0.55, 0.8],
[-0.35, 0.35, 0.8],
[-0.55, 0, 0.8],
[-0.35, -0.35, 0.8],
[0, -0.55, 0.8],
[0.35, -0.35, 0.8],
]
radius = 0.2
env = vamp.Environment()
for sphere in problem:
env.add_sphere(vamp.Sphere(sphere, radius))

Configuring OMPL

Now we wire everything together using OMPL's ompl::geometric::SimpleSetup:

from ompl import base as ob
from ompl import geometric as og
from vamp_state_space import VampMotionValidator, VampStateValidityChecker, VampStateSpace
# Select the VAMP robot module
robot = vamp.panda
dimension = robot.dimension()
# Create the state space from the VAMP robot
space = VampStateSpace(robot=robot)
# Set VAMP-based validators
motion_validator = VampMotionValidator(si=si, env=env, robot=robot)
state_validity_checker = VampStateValidityChecker(si=si, env=env, robot=robot)
si.setMotionValidator(motion_validator)
si.setStateValidityChecker(state_validity_checker)
# Choose a planner
planner = og.RRTConnect(si)
# Build SimpleSetup
ss.setPlanner(planner)
The base class for space information. This contains all the information about the space planning is d...
RRT-Connect (RRTConnect).
Definition RRTConnect.h:62
Create the set of classes typically needed to solve a geometric problem.
Definition SimpleSetup.h:63

Setting Start and Goal States

Define start and goal configurations as joint angles for the 7-DOF Panda arm:

# Start configuration
a = [0., -0.785, 0., -2.356, 0., 1.571, 0.785]
# Goal configuration
b = [2.35, 1., 0., -0.8, 0, 2.5, 0.785]
start = si.allocState()
start[0:dimension] = a
goal = si.allocState()
goal[0:dimension] = b
ss.setStartAndGoalStates(start, goal)

Solving

With everything configured, solving is a single call:

import time
planning_start = time.time()
result = ss.solve(5.0) # 5 second time limit
if result:
path = ss.getSolutionPath()
print(f"Planning time: {time.time() - planning_start}")
print(f"Generated path with {path.getStateCount()} states")

The planner will typically find a solution in milliseconds thanks to VAMP's fast collision checking.

Benchmarking Planners

OMPL's ompl::tools::Benchmark class makes it easy to compare multiple planners on the same problem. Here we benchmark RRTConnect, RRT, KPIECE1, and LBKPIECE1:

from ompl import tools as ot
# Set up SimpleSetup as before (without setting a specific planner)
start = si.allocState()
start[0:dimension] = a
goal = si.allocState()
goal[0:dimension] = b
ss.setStartAndGoalStates(start, goal)
# Add planners to benchmark
planners = [
og.RRT(si),
]
benchmark = ot.Benchmark(ss, "Vamp Cage Planning Benchmark")
for planner in planners:
benchmark.addPlanner(planner)
# Configure and run
req = ot.Request()
req.maxTime = 1.0 # 1 second per run
req.maxMem = 100.0 # 100 MB memory limit
req.runCount = 100 # 100 runs per planner
req.displayProgress = True
benchmark.benchmark(req)
# Save results
benchmark.saveResultsToFile("benchmark.log")
Kinematic Planning by Interior-Exterior Cell Exploration.
Definition KPIECE1.h:72
Lazy Bi-directional KPIECE with one level of discretization.
Definition LBKPIECE1.h:79
Rapidly-exploring Random Trees.
Definition RRT.h:66
Benchmark a set of planners on a problem instance.
Definition Benchmark.h:49

The log file can be visualized with Planner Arena (see the benchmarking tutorial for details). You can also convert it to a SQLite database for custom analysis:

db_path = "benchmark.db"
ot.readBenchmarkLog(db_path, ["benchmark.log"], moveitformat=False)

Running the Demo

The complete demo script supports both single planning and benchmarking via command-line flags:

# Single planning run
python3 vamp/vamp_planning.py
# With 3D visualization (opens at localhost:8080)
python3 vamp/vamp_planning.py --visualize
# Benchmark multiple planners
python3 vamp/vamp_planning.py --benchmark --n_trials 100

Extending to Other Robots

VAMP supports several robots out of the box. To plan for a different robot, simply change the robot module:

robot = vamp.ur5 # UR5 (6-DOF)
robot = vamp.fetch # Fetch (8-DOF)
robot = vamp.baxter # Baxter (7-DOF per arm)

The VampStateSpace, VampStateValidityChecker, and VampMotionValidator classes work with any VAMP robot module, since they read the dimension and bounds from the robot object.

For a more comprehensive example that loads problems from the MotionBenchMaker dataset and supports multiple robots, see the motion_benchmaker_demo.py script in the demos directory.