Bench-MR
A Motion Planning Benchmark for Wheeled Mobile Robots
PathEvaluation.hpp
Go to the documentation of this file.
1#pragma once
2
7#include <metrics/AOLMetric.h>
9
11
16#include "utils/Log.h"
17
19 private:
25 static void createEmptyEntry(const std::string &planner_name,
26 nlohmann::json &info) {
27 static PathStatistics empty_stats;
28 auto &j = info["plans"][planner_name];
29 j["path"] = {};
30 j["stats"] = nlohmann::json(empty_stats)["stats"];
31 j["trajectory"] = {};
32 j["intermediary_solutions"] = {};
33 j["params"] = {};
34 }
35
36 public:
41 static void computeCusps(PathStatistics &stats,
42 const std::vector<Point> path) {
43 std::vector<Point> &cusps = stats.cusps.value();
44
45 auto prev = path.begin();
46 auto current = prev;
47 auto next = prev;
48 while (next != path.end()) {
49 // advance until current point != prev point, i.e., skip duplicates
50 if (prev->distance(*current) <= 0) {
51 ++current;
52 ++next;
53 }
54 else if (current->distance(*next) <= 0) {
55 ++next;
56 }
57 else {
58 const double yaw_prev = PlannerUtils::slope(*prev, *current);
59 const double yaw_next = PlannerUtils::slope(*current, *next);
60
61 // compute angle difference in [0, pi)
62 // close to pi -> cusp; 0 -> straight line; inbetween -> curve
63 const double yaw_change =
64 std::abs(PlannerUtils::normalizeAngle(yaw_next - yaw_prev));
65
66 if (yaw_change > global::settings.cusp_angle_threshold) {
67 cusps.emplace_back(*current);
68 }
69 prev = current;
70 current = next;
71 ++next;
72 }
73 }
74 }
75
76 static bool evaluate(PathStatistics &stats,
77 const ompl::control::PathControl &path,
78 const AbstractPlanner *planner) {
79 stats.planning_time = planner->planningTime();
80 stats.collision_time = global::settings.environment->elapsedCollisionTime();
82 stats.planner = planner->name();
83 stats.planner_settings = planner->getSettings();
84 if (path.getStateCount() < 2) {
85 stats.path_found = false;
86 stats.exact_goal_path = false;
87 } else {
88 stats.path_found = true;
89 auto solution = path;
90 solution = PlannerUtils::interpolated(path);
91 stats.path_collides = !planner->isValid(solution, stats.collisions);
92 stats.exact_goal_path =
93 Point(solution.getStates().back())
94 .distance(global::settings.environment->goal()) <=
100 stats.aol = AOLMetric::evaluate(solution);
101 // This is not implemented in OMPL for ompl::control
102 // stats.smoothness = solution.smoothness();
103
104 if (global::settings.evaluate_clearing &&
105 global::settings.environment->distance(0., 0.) >= 0.) {
106 const auto clearings = ClearingMetric::clearingDistances(solution);
107 stats.mean_clearing_distance = stat::mean(clearings);
108 stats.median_clearing_distance = stat::median(clearings);
109 stats.min_clearing_distance = stat::min(clearings);
110 stats.max_clearing_distance = stat::max(clearings);
111 }
112 const auto p = Point::fromPath(solution);
113 computeCusps(stats, p);
114 }
115 return stats.path_found;
116 }
117
118 static bool evaluate(PathStatistics &stats,
119 const ompl::geometric::PathGeometric &path,
120 const AbstractPlanner *planner) {
121 stats.planning_time = planner->planningTime();
122 stats.collision_time = global::settings.environment->elapsedCollisionTime();
124 stats.planner = planner->name();
125 stats.planner_settings = planner->getSettings();
126 if (path.getStateCount() < 2) {
127 stats.path_found = false;
128 stats.exact_goal_path = false;
129 } else {
130 stats.path_found = true;
131 auto solution = path;
132
133 // assume if SBPL has found a solution, it does not collide and is exact
134 if (planner->name().rfind("SBPL", 0) == 0) {
135 // do not interpolate the path returned by SBPL (it uses its own steer
136 // function)
137 stats.path_collides = false;
138 stats.exact_goal_path = true;
139 } else {
140 solution = PlannerUtils::interpolated(path);
141 stats.path_collides = !planner->isValid(solution, stats.collisions);
142 stats.exact_goal_path =
143 Point(solution.getStates().back())
144 .distance(global::settings.environment->goal()) <=
146 }
147 stats.path_length = PathLengthMetric::evaluate(solution);
151 stats.aol = AOLMetric::evaluate(solution);
152 stats.smoothness = solution.smoothness();
153
154 if (global::settings.evaluate_clearing &&
155 global::settings.environment->distance(0., 0.) >= 0.) {
156 const auto clearings = ClearingMetric::clearingDistances(solution);
157 stats.mean_clearing_distance = stat::mean(clearings);
158 stats.median_clearing_distance = stat::median(clearings);
159 stats.min_clearing_distance = stat::min(clearings);
160 stats.max_clearing_distance = stat::max(clearings);
161 }
162
163 const auto p = Point::fromPath(solution);
164 computeCusps(stats, p);
165 }
166 return stats.path_found;
167 }
168
169 template <class PLANNER>
170 static bool evaluate(PLANNER &planner, nlohmann::json &info) {
171 PathStatistics stats(planner.name());
172 auto &j = info["plans"][planner.name()];
173 OMPL_INFORM(("Running " + planner.name() + "...").c_str());
174 bool success;
175 global::settings.environment->resetCollisionTimer();
177 try {
178 if (planner.run()) {
179 success = PathEvaluation::evaluate(stats, planner.solution(), &planner);
180 j["path"] = Log::serializeTrajectory(planner.solution(), false);
181 } else {
182 createEmptyEntry(planner.name(), info);
183 std::cout << "<stats> No solution was found. </stats>\n";
184 return false;
185 }
186 } catch (std::bad_alloc &ba) {
187 // we ran out of memory
188 OMPL_ERROR("<stats> Error </stats>\nPlanner %s ran out of memory: %s.",
189 planner.name().c_str(), ba.what());
190 createEmptyEntry(planner.name(), info);
191 return false;
192 } catch (ompl::Exception &ex) {
193 OMPL_ERROR("Unable to evaluate new planner %s.\n%s",
194 planner.name().c_str(), ex.what());
195 createEmptyEntry(planner.name(), info);
196 return false;
197 } catch (...) {
198 OMPL_ERROR(
199 "<stats> Error </stats>\nAn unknown exception occurred while running "
200 "planner %s.",
201 planner.name().c_str());
202 createEmptyEntry(planner.name(), info);
203 return false;
204 }
205
206 std::cout << stats << std::endl;
207 std::cout << "Steer function: "
208 << Steering::to_string(global::settings.steer.steering_type)
209 << std::endl;
210 // do not interpolate SBPL solutions since they do not use OMPL steer
211 // functions
212 if (planner.name().rfind("SBPL", 0) == 0) {
213 j["trajectory"] = Log::serializeTrajectory(planner.solution(), false);
214 } else {
215 j["trajectory"] = Log::serializeTrajectory(planner.solution());
216 }
217 j["stats"] = nlohmann::json(stats)["stats"];
218
219 // add intermediary solutions
220 std::vector<nlohmann::json> intermediaries;
221 for (const auto &is : planner.intermediarySolutions) {
222 PathStatistics is_stats;
223 evaluate(is_stats, is.solution, &planner);
224 nlohmann::json s{{"time", is.time},
225 {"collision_time", is_stats.collision_time},
226 {"steering_time", is_stats.steering_time},
227 {"cost", is.cost},
228 {"trajectory", Log::serializeTrajectory(is.solution)},
229 {"path", Log::serializeTrajectory(is.solution, false)},
230 {"stats", nlohmann::json(is_stats)["stats"]}};
231 intermediaries.emplace_back(s);
232 }
233 j["intermediary_solutions"] = intermediaries;
234 return success;
235 }
236
237 template <class PLANNER>
238 static bool evaluate(nlohmann::json &info) {
239 PLANNER *planner = nullptr;
240 try {
241 planner = new PLANNER;
243 } catch (std::bad_alloc &ba) {
244 // we ran out of memory
245 OMPL_ERROR(
246 "<stats> Error </stats>\nRan out of memory while creating planner "
247 "%s: %s.",
248 AbstractPlanner::LastCreatedPlannerName.c_str(), ba.what());
249 createEmptyEntry(AbstractPlanner::LastCreatedPlannerName, info);
250 delete planner;
251 return false;
252 } catch (ompl::Exception &ex) {
253 OMPL_ERROR("Unable to create new planner %s.\n%s",
254 AbstractPlanner::LastCreatedPlannerName.c_str(), ex.what());
255 createEmptyEntry(AbstractPlanner::LastCreatedPlannerName, info);
256 delete planner;
257 return false;
258 } catch (...) {
259 OMPL_ERROR(
260 "<stats> Error </stats>\nAn unknown exception occurred while "
261 "creating planner %s.",
263 createEmptyEntry(AbstractPlanner::LastCreatedPlannerName, info);
264 delete planner;
265 return false;
266 }
267 auto result = evaluate(*planner, info);
268 delete planner;
269 return result;
270 }
271
272 template <class PLANNER>
273 static bool evaluateSmoothers(nlohmann::json &info) {
274 PLANNER *planner = nullptr;
275 try {
276 planner = new PLANNER;
278 } catch (std::bad_alloc &ba) {
279 // we ran out of memory
280 OMPL_ERROR(
281 "<stats> Error </stats>\nRan out of memory while creating planner "
282 "%s: %s.",
283 AbstractPlanner::LastCreatedPlannerName.c_str(), ba.what());
284 createEmptyEntry(AbstractPlanner::LastCreatedPlannerName, info);
285 delete planner;
286 return false;
287 } catch (...) {
288 OMPL_ERROR(
289 "<stats> Error </stats>\nAn unknown exception occurred while "
290 "creating planner %s.",
292 createEmptyEntry(AbstractPlanner::LastCreatedPlannerName, info);
293 delete planner;
294 return false;
295 }
296 if (!evaluate<PLANNER>(*planner, info)) {
297 OMPL_WARN("Cannot evaluate smoothers since no solution could be found.");
298 delete planner;
299 return false;
300 }
301 auto &j = info["plans"][planner->name()]["smoothing"];
302 global::settings.environment->resetCollisionTimer();
304
305 if (global::settings.benchmark.smoothing.grips) {
306 const double cached_min_node_dist =
308 if (global::settings.steer.steering_type ==
310 // XXX increase min distance between vertices to ensure GRIPS can steer
311 // using CC Dubins
313 }
314 // GRIPS
315 og::PathGeometric grips(planner->solution());
316 GRIPS::smooth(grips);
317 PathStatistics grips_stats;
318 evaluate(grips_stats, grips, planner);
319 j["grips"] = {
320 {"time", GRIPS::smoothingTime},
321 {"collision_time",
322 global::settings.environment->elapsedCollisionTime()},
323 {"steering_time", global::settings.ompl.steering_timer.elapsed()},
324 {"name", "GRIPS"},
325 {"inserted_nodes", GRIPS::insertedNodes},
326 {"pruning_rounds", GRIPS::pruningRounds},
327 {"cost", grips.length()},
328 {"trajectory", Log::serializeTrajectory(grips)},
329 {"path", Log::serializeTrajectory(grips, false)},
330 {"stats", nlohmann::json(grips_stats)["stats"]},
331 {"round_stats", GRIPS::statsPerRound}};
332 global::settings.smoothing.grips.min_node_distance = cached_min_node_dist;
333 }
334 if (global::settings.benchmark.smoothing.chomp) {
335 global::settings.environment->resetCollisionTimer();
337 // CHOMP
338 CHOMP chomp;
339 chomp.run(planner->solution());
340 PathStatistics chomp_stats;
341 evaluate(chomp_stats, chomp.solution(), planner);
342 j["chomp"] = {
343 {"time", chomp.planningTime()},
344 {"collision_time",
345 global::settings.environment->elapsedCollisionTime()},
346 {"steering_time", global::settings.ompl.steering_timer.elapsed()},
347 {"name", "CHOMP"},
348 {"cost", chomp.solution().length()},
349 {"path", Log::serializeTrajectory(chomp.solution(), false)},
350 {"trajectory", chomp.solutionPath()},
351 {"stats", nlohmann::json(chomp_stats)["stats"]}};
352 }
353
354 // OMPL Smoothers
355 OmplSmoother smoother(planner->simpleSetup(), planner->solution());
356 if (global::settings.benchmark.smoothing.ompl_shortcut) {
357 global::settings.environment->resetCollisionTimer();
359 // Shortcut
360 PathStatistics stats;
361 TimedResult tr = smoother.shortcutPath();
362 evaluate(stats, tr.trajectory, planner);
363 j["ompl_shortcut"] = {
364 {"time", tr.elapsed()},
365 {"collision_time",
366 global::settings.environment->elapsedCollisionTime()},
367 {"steering_time", global::settings.ompl.steering_timer.elapsed()},
368 {"name", "Shortcut"},
369 {"cost", tr.trajectory.length()},
370 {"path", Log::serializeTrajectory(tr.trajectory, false)},
371 {"trajectory", Log::serializeTrajectory(tr.trajectory)},
372 {"stats", nlohmann::json(stats)["stats"]}};
373 }
374 if (global::settings.benchmark.smoothing.ompl_bspline) {
375 global::settings.environment->resetCollisionTimer();
377 // B-Spline
378 PathStatistics stats;
379 TimedResult tr = smoother.smoothBSpline();
380 evaluate(stats, tr.trajectory, planner);
381 j["ompl_bspline"] = {
382 {"time", tr.elapsed()},
383 {"collision_time",
384 global::settings.environment->elapsedCollisionTime()},
385 {"steering_time", global::settings.ompl.steering_timer.elapsed()},
386 {"name", "B-Spline"},
387 {"cost", tr.trajectory.length()},
388 {"path", Log::serializeTrajectory(tr.trajectory, false)},
389 {"trajectory", Log::serializeTrajectory(tr.trajectory)},
390 {"stats", nlohmann::json(stats)["stats"]}};
391 }
392 if (global::settings.benchmark.smoothing.ompl_simplify_max) {
393 global::settings.environment->resetCollisionTimer();
395 // Simplify Max
396 PathStatistics stats;
397 TimedResult tr = smoother.simplifyMax();
398 evaluate(stats, tr.trajectory, planner);
399 j["ompl_simplify_max"] = {
400 {"time", tr.elapsed()},
401 {"collision_time",
402 global::settings.environment->elapsedCollisionTime()},
403 {"steering_time", global::settings.ompl.steering_timer.elapsed()},
404 {"name", "SimplifyMax"},
405 {"cost", tr.trajectory.length()},
406 {"path", Log::serializeTrajectory(tr.trajectory, false)},
407 {"trajectory", Log::serializeTrajectory(tr.trajectory)},
408 {"stats", nlohmann::json(stats)["stats"]}};
409 }
410
411 delete planner;
412 return true;
413 }
414
420 // TODO remove? Python front-end adds this functionality
421 template <class PLANNER>
422 static bool evaluateAnytime(nlohmann::json &info) {
423 PLANNER planner;
424 const std::vector<double> times =
426 PathStatistics stats(planner.name());
427 auto &j = info["plans"][planner.name()];
428 std::vector<nlohmann::json> intermediaries;
429 bool success = false;
430 const double cached_time_limit = global::settings.max_planning_time;
431 for (double time : times) {
432 OMPL_INFORM(("Running " + planner.name() + " for " +
433 std::to_string(time) + "s...")
434 .c_str());
436 global::settings.environment->resetCollisionTimer();
437 if (planner.run()) {
438 success = PathEvaluation::evaluate(stats, planner.solution(), &planner);
439 j["path"] = Log::serializeTrajectory(planner.solution(), false);
440 } else {
441 j["path"] = {};
442 }
443 std::cout << stats << std::endl;
444 std::cout << "Steer function: "
445 << Steering::to_string(global::settings.steer.steering_type)
446 << std::endl;
447
448 // add intermediary solutions
449 nlohmann::json s{
450 {"time", planner.planningTime()},
451 {"collision_time",
452 global::settings.environment->elapsedCollisionTime()},
453 {"max_time", time},
454 {"cost", stats.path_length},
455 {"trajectory", Log::serializeTrajectory(planner.solution())},
456 {"path", Log::serializeTrajectory(planner.solution(), false)},
457 {"stats", nlohmann::json(stats)["stats"]}};
458 intermediaries.emplace_back(s);
459 }
460
461 j["intermediary_solutions"] = intermediaries;
462 if (planner.name().rfind("SBPL", 0) == 0) {
463 j["trajectory"] = Log::serializeTrajectory(planner.solution(), false);
464 } else {
465 j["trajectory"] = Log::serializeTrajectory(planner.solution());
466 }
467 j["stats"] = nlohmann::json(stats)["stats"];
468 // restore global time limit
469 global::settings.max_planning_time = cached_time_limit;
470 return success;
471 }
472
473 PathEvaluation() = delete;
474};
Definition: AbstractPlanner.h:29
bool isValid(const ob::State *state) const
Definition: AbstractPlanner.h:78
virtual nlohmann::json getSettings() const
Definition: AbstractPlanner.h:121
static std::string LastCreatedPlannerName
Stores the name of the planner created last.
Definition: AbstractPlanner.h:38
virtual double planningTime() const =0
virtual std::string name() const =0
Definition: CHOMP.h:13
ob::PlannerStatus run(const og::PathGeometric &path)
Definition: CHOMP.cpp:38
std::vector< Point > solutionPath() const
Definition: CHOMP.cpp:127
og::PathGeometric solution() const
Definition: CHOMP.cpp:121
double planningTime() const
Definition: CHOMP.cpp:129
static std::vector< double > clearingDistances(const ompl::geometric::PathGeometric &trajectory)
Definition: ClearingMetric.h:8
static int insertedNodes
Definition: GRIPS.h:74
static bool smooth(ompl::geometric::PathGeometric &path, const std::vector< Point > &originalPathIntermediaries)
Definition: GRIPS.cpp:17
static int pruningRounds
Definition: GRIPS.h:75
static std::vector< RoundStats > statsPerRound
Definition: GRIPS.h:77
static double smoothingTime
Definition: GRIPS.h:78
static std::vector< std::array< double, 3 > > serializeTrajectory(const ompl::geometric::PathGeometric &traj, bool interpolate=true)
Definition: Log.cpp:76
Definition: OmplSmoother.hpp:13
virtual TimedResult simplifyMax() const
Definition: OmplSmoother.hpp:62
virtual TimedResult shortcutPath() const
Definition: OmplSmoother.hpp:18
virtual TimedResult smoothBSpline() const
Definition: OmplSmoother.hpp:41
static void configure(PLANNER &planner)
Definition: PlannerConfigurator.hpp:17
static ompl::geometric::PathGeometric interpolated(ompl::geometric::PathGeometric path)
Interpolate path based on its associated state space.
Definition: PlannerUtils.hpp:261
static double slope(const N &x1, const N &y1, const N &x2, const N &y2)
Compute the angular slope between two points.
Definition: PlannerUtils.hpp:39
static double normalizeAngle(double angle)
Normalize angle in radians to the interval .
Definition: PlannerUtils.hpp:572
void reset()
Stops the timer and sets elapsed time to zero.
Definition: Stopwatch.hpp:23
double elapsed() const
Elapsed time in seconds.
Definition: Stopwatch.hpp:41
static double evaluate(const ompl::geometric::PathGeometric &trajectory, double dt=0.1)
Definition: TrajectoryMetric.h:14
Definition: TimedResult.hpp:9
ompl::geometric::PathGeometric trajectory
Definition: TimedResult.hpp:11
std::string to_string(Steering::SteeringType t)
Definition: Steering.h:17
@ STEER_TYPE_CC_DUBINS
Definition: Steering.h:12
std::string to_string(Method m)
Definition: PlannerSettings.h:32
double max(const std::vector< double > &values)
Definition: PathStatistics.hpp:79
double min(const std::vector< double > &values)
Definition: PathStatistics.hpp:73
double median(std::vector< double > values)
Definition: PathStatistics.hpp:57
double mean(const std::vector< double > &values)
Definition: PathStatistics.hpp:67
Definition: PathEvaluation.hpp:18
static bool evaluate(PathStatistics &stats, const ompl::control::PathControl &path, const AbstractPlanner *planner)
Definition: PathEvaluation.hpp:76
static void computeCusps(PathStatistics &stats, const std::vector< Point > path)
Identifies cusps in a solution path by comparing the yaw angles between every second state.
Definition: PathEvaluation.hpp:41
static bool evaluateAnytime(nlohmann::json &info)
Evaluates an anytime path planner by running the planner for each of the provided time intervals (in ...
Definition: PathEvaluation.hpp:422
static bool evaluate(nlohmann::json &info)
Definition: PathEvaluation.hpp:238
static bool evaluateSmoothers(nlohmann::json &info)
Definition: PathEvaluation.hpp:273
static bool evaluate(PLANNER &planner, nlohmann::json &info)
Definition: PathEvaluation.hpp:170
PathEvaluation()=delete
static bool evaluate(PathStatistics &stats, const ompl::geometric::PathGeometric &path, const AbstractPlanner *planner)
Definition: PathEvaluation.hpp:118
Definition: PathStatistics.hpp:16
Property< double > smoothness
Definition: PathStatistics.hpp:34
Property< std::vector< Point > > collisions
Definition: PathStatistics.hpp:48
Property< bool > path_collides
Definition: PathStatistics.hpp:24
Property< double > normalized_curvature
Definition: PathStatistics.hpp:30
Property< double > planning_time
Definition: PathStatistics.hpp:17
Property< std::vector< Point > > cusps
Definition: PathStatistics.hpp:47
Property< double > max_curvature
Definition: PathStatistics.hpp:28
Property< double > aol
Definition: PathStatistics.hpp:32
Property< double > steering_time
Definition: PathStatistics.hpp:21
Property< bool > path_found
Definition: PathStatistics.hpp:23
Property< double > min_clearing_distance
Definition: PathStatistics.hpp:41
Property< double > path_length
Definition: PathStatistics.hpp:26
Property< double > mean_clearing_distance
Definition: PathStatistics.hpp:36
Property< std::string > planner
Definition: PathStatistics.hpp:45
Property< bool > exact_goal_path
Definition: PathStatistics.hpp:25
Property< double > collision_time
Definition: PathStatistics.hpp:19
Property< double > median_clearing_distance
Definition: PathStatistics.hpp:38
Property< double > max_clearing_distance
Definition: PathStatistics.hpp:43
Property< nlohmann::json > planner_settings
Definition: PathStatistics.hpp:46
Property< std::vector< double > > anytime_intervals
Time intervals used for the anytime planner benchmark.
Definition: PlannerSettings.h:314
Stopwatch steering_timer
Definition: PlannerSettings.h:414
Property< double > min_node_distance
Definition: PlannerSettings.h:622
PlannerSettings::GlobalSettings::SmoothingSettings::ChompSettings chomp
PlannerSettings::GlobalSettings::SmoothingSettings::GripsSettings grips
Property< double > exact_goal_radius
Radius threshold to evaluate whether the exact goal has been found.
Definition: PlannerSettings.h:262
std::shared_ptr< Environment > environment
Environment used for planning.
Definition: PlannerSettings.h:73
PlannerSettings::GlobalSettings::OmplSettings ompl
PlannerSettings::GlobalSettings::SmoothingSettings smoothing
PlannerSettings::GlobalSettings::BenchmarkSettings benchmark
Property< double > max_planning_time
Definition: PlannerSettings.h:238
Definition: Primitives.h:42
double distance(const Point &p) const
Definition: Primitives.h:74
static std::vector< Point > fromPath(const ompl::geometric::PathGeometric &p, bool interpolate=false)
Definition: Primitives.cpp:25
static PlannerSettings::GlobalSettings settings
Definition: PlannerSettings.h:699