Loading...
Searching...
No Matches
motion_benchmaker_demo.py
1import pickle
2import time
3from tabulate import tabulate
4from tqdm import tqdm
5from pathlib import Path
6import pandas as pd
7from typing import Union, List
8from functools import partial
9import sys
10from datetime import datetime
11import subprocess
12import numpy as np
13
14from fire import Fire
15import vamp
16from vamp import pointcloud as vpc
17from ompl import base as ob
18from ompl import geometric as og
19from ompl import tools as ot
20
21from vamp_state_space import (
22 VampStateSpace,
23 VampMotionValidator,
24 VampStateValidityChecker,
25)
26
27sys.path.insert(0, str(Path(__file__).parent.parent))
28from viser_visualizer import ViserVisualizer
29
30
31def main(
32 robot: str = "panda", # Robot to plan for
33 planner: str = "rrtc", # Planner name to use (e.g., 'rrtc', 'prm', 'rrt')
34 dataset: str = "problems.pkl", # Pickled dataset to use
35 problem: Union[str, List[str]] = [], # Problem name or list of problems to evaluate
36 trials: int = 1, # Number of trials to evaluate each instance
37 print_failures: bool = False, # Print out failures and invalid problems
38 visualize: bool = False, # Visualize solutions using Viser (any key=next, q=disable, w=skip problem set)
39 pointcloud: bool = False, # Use pointcloud rather than primitive geometry
40 samples_per_object: int = 10000, # If pointcloud, samples per object to use
41 filter_radius: float = 0.02, # Filter radius for pointcloud filtering
42 filter_cull: bool = True, # Cull pointcloud around robot by maximum distance
43 planning_time: float = 1.0, # Planning time limit in seconds
44 benchmark: int = 0, # Run OMPL benchmark for n trials instead of 700 problems
45 n_trials: int = 100, # Number of trials for benchmarking
46 **kwargs,
47):
48 if robot not in vamp.robots:
49 raise RuntimeError(f"Robot {robot} does not exist in VAMP!")
50
51 # Get robot module
52 robot_module = getattr(vamp, robot)
53 dimension = robot_module.dimension()
54
55 # Setup OMPL state space
56 space = ob.RealVectorStateSpace(dimension)
57 bounds = ob.RealVectorBounds(dimension)
58 upper_bounds = robot_module.upper_bounds()
59 lower_bounds = robot_module.lower_bounds()
60 for i in range(dimension):
61 bounds.setLow(i, lower_bounds[i])
62 bounds.setHigh(i, upper_bounds[i])
63 space.setBounds(bounds)
64
65 # Map planner names to OMPL planners
66 planner_map = {
67 "rrtc": og.RRTConnect,
68 "rrt": og.RRT,
69 "prm": og.PRM,
70 "rrtstar": og.RRTstar,
71 "bitstar": og.BITstar,
72 }
73
74 if planner.lower() not in planner_map:
75 raise RuntimeError(
76 f"Planner {planner} not recognized. Available: {list(planner_map.keys())}"
77 )
78
79 planner_class = planner_map[planner.lower()]
80 vamp_folder = Path(__file__).parent.parent.parent / "external" / "vamp"
81 problems_dir = vamp_folder / "resources" / robot
82 pickle_path = problems_dir / dataset
83
84 # Check if pickle file exists, generate if not
85 if not pickle_path.exists():
86 print(f"Pickle file not found at {pickle_path}, generating from tar.bz2...")
87 script_path = vamp_folder / "resources" / "problem_tar_to_pkl_json.py"
88 result = subprocess.run(
89 [sys.executable, str(script_path), f"--robot={robot}"], check=True
90 )
91 if result.returncode != 0:
92 raise RuntimeError(f"Failed to generate pickle file using {script_path}")
93 print(f"Successfully generated pickle file")
94
95 with open(pickle_path, "rb") as f:
96 problems = pickle.load(f)
97
98 problem_names = list(problems["problems"].keys())
99 if isinstance(problem, str):
100 problem = [problem]
101
102 if not problem:
103 problem = problem_names
104 else:
105 for problem_name in problem:
106 if problem_name not in problem_names:
107 raise RuntimeError(
108 f"Problem `{problem_name}` not available! Available problems: {problem_names}"
109 )
110
111 total_problems = 0
112 valid_problems = 0
113 failed_problems = 0
114
115 # Initialize visualizer if requested
116 vis = None
117 visualize_enabled = visualize
118 skip_to_next_problem_set = False
119 if visualize:
120 vis = ViserVisualizer(robot_name=robot, robot_dimension=dimension)
121 # vis.add_grid()
122
123 if benchmark:
124 # Create benchmark output directory with timestamp
125 timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
126 benchmark_dir = Path("benchmarks") / f"benchmark_{timestamp}"
127 benchmark_dir.mkdir(parents=True, exist_ok=True)
128
129 print(
130 f"Benchmarking {robot} on first problem of each type with {n_trials} trials..."
131 )
132 print(f"Results will be saved to: {benchmark_dir}")
133
134 # Set this to x plans to do
135 planner_constructors = [
137 og.PRM,
140 og.RRT,
141 ]
142
143 tick = time.perf_counter()
144 results = []
145
146 print(
147 f"Evaluating {planner_class.__name__} on problems: {problems['problems'].keys()} with robot {robot}..."
148 )
149
150 for name, pset in problems["problems"].items():
151 if name not in problem:
152 continue
153
154 # Reset skip flag for new problem set
155 skip_to_next_problem_set = False
156
157 failures = []
158 invalids = []
159 print(f"Evaluating {robot} on {name}: ")
160 progress_bar = tqdm(total=len(pset), desc=f"Processing {name}")
161 for i, data in enumerate(pset):
162 total_problems += 1
163 skip_pset = False
164 progress_bar.update(1)
165
166 if not data["valid"]:
167 invalids.append(i)
168 continue
169
170 valid_problems += 1
171
172 if pointcloud:
173 r_min, r_max = robot_module.min_max_radii()
174 (env, original_pc, filtered_pc, filter_time, build_time) = (
175 vpc.problem_dict_to_pointcloud(
176 robot,
177 r_min,
178 r_max,
179 data,
180 samples_per_object,
181 filter_radius,
182 filter_cull,
183 )
184 )
185
186 pointcloud_results = {
187 "original_pointcloud_size": len(original_pc),
188 "filtered_pointcloud_size": len(filtered_pc),
189 "filter_time": pd.Timedelta(nanoseconds=filter_time),
190 "capt_build_time": pd.Timedelta(nanoseconds=build_time),
191 }
192 else:
193 env = vamp.problem_dict_to_vamp(data)
194
195 for trial in range(trials):
196 # Setup OMPL problem
197 dimension = robot_module.dimension()
198 space = VampStateSpace(robot=robot_module)
199 si = ob.SpaceInformation(space)
200
201 motion_validator = VampMotionValidator(
202 si=si, env=env, robot=robot_module
203 )
204 state_validity_checker = VampStateValidityChecker(
205 si=si, env=env, robot=robot_module
206 )
207
208 # Set validators
209 si.setMotionValidator(motion_validator)
210 si.setStateValidityChecker(state_validity_checker)
211
212 ss = og.SimpleSetup(si)
213 ompl_planner = planner_class(si)
214 ss.setPlanner(ompl_planner)
215
216 # Set start state
217 start = si.allocState()
218 start[0:dimension] = data["start"]
219
220 # Set goal state (use first goal)
221 goal = si.allocState()
222 goal[0:dimension] = data["goals"][0]
223
224 ss.setStartAndGoalStates(start, goal)
225
226 # Benchmark
227 if benchmark:
228 ompl_benchmark = ot.Benchmark(ss, "motion_benchmaker")
229 representation = "pointcloud" if pointcloud else "mesh"
230 ompl_benchmark.addExperimentParameter("robot", "TEXT", robot)
231 ompl_benchmark.addExperimentParameter("environment", "TEXT", name)
232 ompl_benchmark.addExperimentParameter(
233 "representation", "TEXT", representation
234 )
235
236 for planner_constructor in planner_constructors:
237 ompl_benchmark.addPlanner(planner_constructor(si))
238
239 # Configure benchmark request
240 req = ot.Request()
241 req.maxTime = planning_time
242 req.maxMem = 1000.0
243 req.runCount = n_trials
244 req.displayProgress = True
245
246 print(f" Running benchmark with {n_trials} trials...")
247 ompl_benchmark.benchmark(req)
248
249 # Save results
250 file = str(f"{robot}_{name}_{representation}_benchmark")
251 log_file = str(benchmark_dir / (file + ".log"))
252 db_file = str(benchmark_dir / (file + ".db"))
253
254 ompl_benchmark.saveResultsToFile(log_file)
255 print(f" Saved log to: {log_file}")
256
257 # Convert log to database
258 ot.readBenchmarkLog(db_file, [log_file], moveitformat=False)
259 print(f" Saved database to: {db_file}")
260 # only run one per problem
261 skip_pset = True
262 break
263 # Single Plan
264 planning_start = time.perf_counter()
265 result = ss.solve(planning_time)
266 planning_elapsed = time.perf_counter() - planning_start
267
268 if not ss.haveExactSolutionPath():
269 failures.append(i)
270 print(f"Failed to find exact solution for problem {i}")
271 break
272
273 # Get path
274 path = ss.getSolutionPath()
275 initial_cost = path.length()
276
277 # Simplify
278 simplify_start = time.perf_counter()
279 ss.simplifySolution()
280 simplify_elapsed = time.perf_counter() - simplify_start
281
282 simplified_path = ss.getSolutionPath()
283 simplified_cost = simplified_path.length()
284
285 trial_result = {
286 "planning_time": pd.Timedelta(microseconds=planning_elapsed * 1e6),
287 "simplification_time": pd.Timedelta(
288 microseconds=simplify_elapsed * 1e6
289 ),
290 "total_time": pd.Timedelta(
291 microseconds=(planning_elapsed + simplify_elapsed) * 1e6
292 ),
293 "initial_path_cost": initial_cost,
294 "simplified_path_cost": simplified_cost,
295 "planning_iterations": 0, # OMPL doesn't directly expose this
296 "avg_time_per_iteration": 0.0,
297 }
298
299 if pointcloud:
300 trial_result.update(pointcloud_results)
301
302 results.append(trial_result)
303
304 # Visualization
305 if visualize_enabled and vis is not None:
306 # Check if we should skip to next problem set
307 if skip_to_next_problem_set:
308 continue
309 # Clear previous visualization
310 vis.reset()
311 # vis.add_grid()
312
313 # Load environment obstacles
314 if pointcloud:
315 original_pc = np.array(original_pc)
316 z_values = original_pc[:, 2]
317 z_min, z_max = z_values.min(), z_values.max()
318 z_range = z_max - z_min
319
320 # Clamp and normalize z values to [0, 1] within the color range
321 z_normalized = np.clip(
322 (z_values - z_min) / (z_max - z_min), 0, 1
323 )
324
325 # Smooth rainbow colormap: red -> yellow -> green -> cyan -> blue
326 colors = np.zeros((len(original_pc), 3))
327 colors[:, 0] = np.maximum(
328 0, 1 - 2 * np.abs(z_normalized - 0.25)
329 ) # Red
330 colors[:, 1] = np.maximum(
331 0, 1 - 2 * np.abs(z_normalized - 0.5)
332 ) # Green
333 colors[:, 2] = np.maximum(
334 0, 1 - 2 * np.abs(z_normalized - 0.75)
335 ) # Blue
336 vis.add_point_cloud(original_pc, color=colors, point_size=0.01)
337 else:
338 vis.load_mbm_environment(
339 data, padding=0.0, color=(0.8, 0.4, 0.2, 0.75)
340 )
341
342 # Convert path to numpy array
343 simplified_path.interpolate(
344 int(simplified_path.getStateCount() * 15)
345 )
346 states = simplified_path.getStates()
347 trajectory = np.array(
348 [list(state[0:dimension]) for state in states]
349 )
350 # interpolate
351
352 vis.visualize_trajectory(trajectory)
353
354 print(
355 f"\nVisualizing problem {name} [{i}] - Press any key to continue, 'q' to disable viz, 'w' to skip to next problem set"
356 )
357 key = vis.play_until_key_pressed(key="any", dt=1 / 60)
358
359 if key == "q":
360 print("Visualization disabled for remaining problems")
361 visualize_enabled = False
362 elif key == "w":
363 print(f"Skipping to next problem set...")
364 skip_to_next_problem_set = True
365 break
366
367 if skip_pset:
368 break
369
370 progress_bar.close()
371
372 failed_problems += len(failures)
373
374 if print_failures:
375 if invalids:
376 print(f" Invalid problems: {invalids}")
377
378 if failures:
379 print(f" Failed on {failures}")
380
381 if benchmark > 0:
382 print(f"All benchmarks saved in {benchmark_dir}")
383 exit(0)
384
385 tock = time.perf_counter()
386
387 df = pd.DataFrame.from_dict(results)
388
389 # Convert to microseconds
390 df["planning_time"] = df["planning_time"].dt.microseconds
391 df["simplification_time"] = df["simplification_time"].dt.microseconds
392 df["avg_time_per_iteration"] = df["planning_iterations"] / df["planning_time"]
393
394 # Pointcloud data
395 if pointcloud:
396 df["total_build_and_plan_time"] = (
397 df["total_time"] + df["filter_time"] + df["capt_build_time"]
398 )
399 df["filter_time"] = df["filter_time"].dt.microseconds / 1e3
400 df["capt_build_time"] = df["capt_build_time"].dt.microseconds / 1e3
401 df["total_build_and_plan_time"] = (
402 df["total_build_and_plan_time"].dt.microseconds / 1e3
403 )
404
405 df["total_time"] = df["total_time"].dt.microseconds
406
407 # Get summary statistics
408 time_stats = df[
409 [
410 "planning_time",
411 "simplification_time",
412 "total_time",
413 "planning_iterations",
414 "avg_time_per_iteration",
415 ]
416 ].describe(percentiles=[0.25, 0.5, 0.75, 0.95])
417 time_stats.drop(index=["count"], inplace=True)
418
419 cost_stats = df[
420 [
421 "initial_path_cost",
422 "simplified_path_cost",
423 ]
424 ].describe(percentiles=[0.25, 0.5, 0.75, 0.95])
425 cost_stats.drop(index=["count"], inplace=True)
426
427 if pointcloud:
428 pointcloud_stats = df[
429 [
430 "filter_time",
431 "capt_build_time",
432 "total_build_and_plan_time",
433 ]
434 ].describe(percentiles=[0.25, 0.5, 0.75, 0.95])
435 pointcloud_stats.drop(index=["count"], inplace=True)
436
437 print()
438 print(
439 tabulate(
440 time_stats,
441 headers=[
442 "Planning Time (μs)",
443 "Simplification Time (μs)",
444 "Total Time (μs)",
445 "Planning Iters.",
446 "Time per Iter. (μs)",
447 ],
448 tablefmt="github",
449 )
450 )
451
452 print(
453 tabulate(
454 cost_stats,
455 headers=[
456 " Initial Cost (L2)",
457 " Simplified Cost (L2)",
458 ],
459 tablefmt="github",
460 )
461 )
462
463 if pointcloud:
464 print(
465 tabulate(
466 pointcloud_stats,
467 headers=[
468 " Filter Time (ms)",
469 " CAPT Build Time (ms)",
470 "Total Time (ms)",
471 ],
472 tablefmt="github",
473 )
474 )
475
476 print(
477 f"Solved / Valid / Total # Problems: {valid_problems - failed_problems} / {valid_problems} / {total_problems}"
478 )
479 print(f"Completed all problems in {df['total_time'].sum() / 1000:.3f} milliseconds")
480 print(
481 f"Total time including Python overhead: {(tock - tick) * 1000:.3f} milliseconds"
482 )
483
484
485if __name__ == "__main__":
486 Fire(main)
The lower and upper bounds for an Rn space.
A state space representing Rn. The distance function is the L2 norm.
The base class for space information. This contains all the information about the space planning is d...
Batch Informed Trees (BIT*).
Definition BITstar.h:93
Kinematic Planning by Interior-Exterior Cell Exploration.
Definition KPIECE1.h:72
Lazy Bi-directional KPIECE with one level of discretization.
Definition LBKPIECE1.h:79
Probabilistic RoadMap planner.
Definition PRM.h:81
RRT-Connect (RRTConnect).
Definition RRTConnect.h:62
Rapidly-exploring Random Trees.
Definition RRT.h:66
Optimal Rapidly-exploring Random Trees.
Definition RRTstar.h:75
Create the set of classes typically needed to solve a geometric problem.
Definition SimpleSetup.h:63
Benchmark a set of planners on a problem instance.
Definition Benchmark.h:49