moveit2
The MoveIt Motion Planning Framework for ROS 2.
Loading...
Searching...
No Matches
BenchmarkExecutor.cpp
Go to the documentation of this file.
1/*********************************************************************
2 * Software License Agreement (BSD License)
3 *
4 * Copyright (c) 2015, Rice University
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 Rice University 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/* Author: Ryan Luna */
36
42#include <moveit/version.hpp>
43#include <tf2_eigen/tf2_eigen.hpp>
45
46#include <regex>
47
48#if __has_include(<boost/timer/progress_display.hpp>)
49#include <boost/timer/progress_display.hpp>
50using boost_progress_display = boost::timer::progress_display;
51#else
52// boost < 1.72
53#define BOOST_TIMER_ENABLE_DEPRECATED 1
54#include <boost/progress.hpp>
55#undef BOOST_TIMER_ENABLE_DEPRECATED
56using boost_progress_display = boost::progress_display;
57#endif
58
59#include <boost/math/constants/constants.hpp>
60#include <boost/date_time/posix_time/posix_time.hpp>
61#include <math.h>
62#include <limits>
63#include <filesystem>
64#ifndef _WIN32
65#include <unistd.h>
66#else
67#include <winsock2.h>
68#endif
69
70#undef max
71
72using namespace moveit_ros_benchmarks;
73
74namespace
75{
76rclcpp::Logger getLogger()
77{
78 return moveit::getLogger("moveit.benchmarks.executor");
79}
80} // namespace
81
82template <class Clock, class Duration>
83boost::posix_time::ptime toBoost(const std::chrono::time_point<Clock, Duration>& from)
84{
85 typedef std::chrono::nanoseconds duration_t;
86 typedef long rep_t;
87 rep_t d = std::chrono::duration_cast<duration_t>(from.time_since_epoch()).count();
88 rep_t sec = d / 1000000000;
89 rep_t nsec = d % 1000000000;
90 namespace pt = boost::posix_time;
91#ifdef BOOST_DATE_TIME_HAS_NANOSECONDS
92 return pt::from_time_t(sec) + pt::nanoseconds(nsec)
93#else
94 return pt::from_time_t(sec) + pt::microseconds(nsec / 1000);
95#endif
96}
97
98BenchmarkExecutor::BenchmarkExecutor(const rclcpp::Node::SharedPtr& node, const std::string& robot_descriptionparam)
99 : planning_scene_monitor_{ std::make_shared<planning_scene_monitor::PlanningSceneMonitor>(node,
100 robot_descriptionparam) }
101 , planning_scene_storage_{ nullptr }
103 , robot_state_storage_{ nullptr }
104 , constraints_storage_{ nullptr }
106 , node_{ node }
107 , db_loader_{ node }
108{
109 planning_scene_ = planning_scene_monitor_->getPlanningScene();
110}
111
115
116[[nodiscard]] bool BenchmarkExecutor::initialize(const std::vector<std::string>& planning_pipeline_names)
117{
118 // Initialize moveit_cpp
119 moveit_cpp_ = std::make_shared<moveit_cpp::MoveItCpp>(node_);
120
121 for (const std::string& planning_pipeline_name : planning_pipeline_names)
122 {
123 if (moveit_cpp_->getPlanningPipelines().find(planning_pipeline_name) == moveit_cpp_->getPlanningPipelines().end())
124 {
125 RCLCPP_ERROR(getLogger(), "Cannot find pipeline '%s'", planning_pipeline_name.c_str());
126 return false;
127 }
128
129 const auto& pipeline = moveit_cpp_->getPlanningPipelines().at(planning_pipeline_name);
130 // Verify the pipeline has successfully initialized a planner
131 if (!pipeline)
132 {
133 RCLCPP_ERROR(getLogger(), "Failed to initialize planning pipeline '%s'", planning_pipeline_name.c_str());
134 continue;
135 }
136 }
137
138 // Error check
139 if (moveit_cpp_->getPlanningPipelines().empty())
140 {
141 RCLCPP_ERROR(getLogger(), "No planning pipelines have been loaded. Nothing to do for the benchmarking service.");
142 }
143 else
144 {
145 RCLCPP_INFO(getLogger(), "Available planning pipelines:");
146 for (const std::pair<const std::string, planning_pipeline::PlanningPipelinePtr>& entry :
147 moveit_cpp_->getPlanningPipelines())
148 {
149 RCLCPP_INFO_STREAM(getLogger(), entry.first);
150 }
151 }
152 return true;
153}
154
156{
158 {
160 }
162 {
164 }
166 {
167 robot_state_storage_.reset();
168 }
170 {
171 constraints_storage_.reset();
172 }
174 {
176 }
177
178 benchmark_data_.clear();
179 pre_event_functions_.clear();
180 post_event_functions_.clear();
184 query_end_functions_.clear();
185}
186
188{
189 pre_event_functions_.push_back(func);
190}
191
196
201
206
211
216
218{
219 if (moveit_cpp_->getPlanningPipelines().empty())
220 {
221 RCLCPP_ERROR(getLogger(), "No planning pipelines configured. Did you call BenchmarkExecutor::initialize?");
222 return false;
223 }
224
225 std::vector<BenchmarkRequest> queries;
226 moveit_msgs::msg::PlanningScene scene_msg;
227
228 if (initializeBenchmarks(options, scene_msg, queries))
229 {
230 for (std::size_t i = 0; i < queries.size(); ++i)
231 {
232 // Configure planning scene
233 if (scene_msg.robot_model_name != planning_scene_->getRobotModel()->getName())
234 {
235 // Clear all geometry from the scene
236 planning_scene_->getWorldNonConst()->clearObjects();
237 planning_scene_->getCurrentStateNonConst().clearAttachedBodies();
238 planning_scene_->getCurrentStateNonConst().setToDefaultValues();
239
240 planning_scene_->processPlanningSceneWorldMsg(scene_msg.world);
241 }
242 else
243 {
244 planning_scene_->usePlanningSceneMsg(scene_msg);
245 }
246
247 // Calling query start events
248 for (QueryStartEventFunction& query_start_fn : query_start_functions_)
249 {
250 query_start_fn(queries[i].request, planning_scene_);
251 }
252
253 RCLCPP_INFO(getLogger(), "Benchmarking query '%s' (%lu of %lu)", queries[i].name.c_str(), i + 1, queries.size());
254 std::chrono::system_clock::time_point start_time = std::chrono::system_clock::now();
255 runBenchmark(queries[i].request, options);
256 std::chrono::duration<double> dt = std::chrono::system_clock::now() - start_time;
257 double duration = dt.count();
258
260 {
261 query_end_fn(queries[i].request, planning_scene_);
262 }
263
264 writeOutput(queries[i], boost::posix_time::to_iso_extended_string(toBoost(start_time)), duration, options);
265 }
266
267 return true;
268 }
269 return false;
270}
271
273 moveit_msgs::msg::PlanningScene& scene_msg,
274 std::vector<BenchmarkRequest>& requests)
275{
276 if (!pipelinesExist(options.planning_pipelines))
277 {
278 return false;
279 }
280
281 std::vector<StartState> start_states;
282 std::vector<PathConstraints> path_constraints;
283 std::vector<PathConstraints> goal_constraints;
284 std::vector<TrajectoryConstraints> traj_constraints;
285 std::vector<BenchmarkRequest> queries;
286
287 if (!loadBenchmarkQueryData(options, scene_msg, start_states, path_constraints, goal_constraints, traj_constraints,
288 queries))
289 {
290 RCLCPP_ERROR(getLogger(), "Failed to load benchmark query data");
291 return false;
292 }
293
294 RCLCPP_INFO(
295 getLogger(),
296 "Benchmark loaded %lu starts, %lu goals, %lu path constraints, %lu trajectory constraints, and %lu queries",
297 start_states.size(), goal_constraints.size(), path_constraints.size(), traj_constraints.size(), queries.size());
298
299 moveit_msgs::msg::WorkspaceParameters workspace_parameters = options.workspace;
300 // Make sure that workspace_parameters are set
301 if (workspace_parameters.min_corner.x == workspace_parameters.max_corner.x &&
302 workspace_parameters.min_corner.x == 0.0 &&
303 workspace_parameters.min_corner.y == workspace_parameters.max_corner.y &&
304 workspace_parameters.min_corner.y == 0.0 &&
305 workspace_parameters.min_corner.z == workspace_parameters.max_corner.z &&
306 workspace_parameters.min_corner.z == 0.0)
307 {
308 workspace_parameters.min_corner.x = workspace_parameters.min_corner.y = workspace_parameters.min_corner.z = -5.0;
309
310 workspace_parameters.max_corner.x = workspace_parameters.max_corner.y = workspace_parameters.max_corner.z = 5.0;
311 }
312
313 // Create the combinations of BenchmarkRequests
314
315 // 1) Create requests for combinations of start states,
316 // goal constraints, and path constraints
317 for (PathConstraints& goal_constraint : goal_constraints)
318 {
319 // Common benchmark request properties
320 BenchmarkRequest benchmark_request;
321 benchmark_request.name = goal_constraint.name;
322 benchmark_request.request.workspace_parameters = workspace_parameters;
323 benchmark_request.request.goal_constraints = goal_constraint.constraints;
324 benchmark_request.request.group_name = options.group_name;
325 benchmark_request.request.allowed_planning_time = options.timeout;
326 benchmark_request.request.num_planning_attempts = 1;
327
328 if (benchmark_request.request.goal_constraints.size() == 1 &&
329 benchmark_request.request.goal_constraints.at(0).position_constraints.size() == 1 &&
330 benchmark_request.request.goal_constraints.at(0).orientation_constraints.size() == 1 &&
331 benchmark_request.request.goal_constraints.at(0).visibility_constraints.empty() &&
332 benchmark_request.request.goal_constraints.at(0).joint_constraints.empty())
333 {
334 shiftConstraintsByOffset(benchmark_request.request.goal_constraints.at(0), options.goal_offsets);
335 }
336
337 std::vector<BenchmarkRequest> request_combos;
338 createRequestCombinations(benchmark_request, start_states, path_constraints, request_combos);
339 requests.insert(requests.end(), request_combos.begin(), request_combos.end());
340 }
341
342 // 2) Existing queries are treated like goal constraints.
343 // Create all combos of query, start states, and path constraints
344 for (BenchmarkRequest& query : queries)
345 {
346 // Common benchmark request properties
347 BenchmarkRequest benchmark_request;
348 benchmark_request.name = query.name;
349 benchmark_request.request = query.request;
350 benchmark_request.request.group_name = options.group_name;
351 benchmark_request.request.allowed_planning_time = options.timeout;
352 benchmark_request.request.num_planning_attempts = 1;
353
354 // Make sure that workspace_parameters are set
355 if (benchmark_request.request.workspace_parameters.min_corner.x ==
356 benchmark_request.request.workspace_parameters.max_corner.x &&
357 benchmark_request.request.workspace_parameters.min_corner.x == 0.0 &&
358 benchmark_request.request.workspace_parameters.min_corner.y ==
359 benchmark_request.request.workspace_parameters.max_corner.y &&
360 benchmark_request.request.workspace_parameters.min_corner.y == 0.0 &&
361 benchmark_request.request.workspace_parameters.min_corner.z ==
362 benchmark_request.request.workspace_parameters.max_corner.z &&
363 benchmark_request.request.workspace_parameters.min_corner.z == 0.0)
364 {
365 // ROS_WARN("Workspace parameters are not set for request %s. Setting defaults", queries[i].name.c_str());
366 benchmark_request.request.workspace_parameters = workspace_parameters;
367 }
368
369 // Create all combinations of start states and path constraints
370 std::vector<BenchmarkRequest> request_combos;
371 createRequestCombinations(benchmark_request, start_states, path_constraints, request_combos);
372 requests.insert(requests.end(), request_combos.begin(), request_combos.end());
373 }
374
375 // 3) Trajectory constraints are also treated like goal constraints
376 for (TrajectoryConstraints& traj_constraint : traj_constraints)
377 {
378 // Common benchmark request properties
379 BenchmarkRequest benchmark_request;
380 benchmark_request.name = traj_constraint.name;
381 benchmark_request.request.trajectory_constraints = traj_constraint.constraints;
382 benchmark_request.request.group_name = options.group_name;
383 benchmark_request.request.allowed_planning_time = options.timeout;
384 benchmark_request.request.num_planning_attempts = 1;
385
386 if (benchmark_request.request.trajectory_constraints.constraints.size() == 1 &&
387 benchmark_request.request.trajectory_constraints.constraints.at(0).position_constraints.size() == 1 &&
388 benchmark_request.request.trajectory_constraints.constraints.at(0).orientation_constraints.size() == 1 &&
389 benchmark_request.request.trajectory_constraints.constraints.at(0).visibility_constraints.empty() &&
390 benchmark_request.request.trajectory_constraints.constraints.at(0).joint_constraints.empty())
391 {
392 shiftConstraintsByOffset(benchmark_request.request.trajectory_constraints.constraints.at(0), options.goal_offsets);
393 }
394
395 std::vector<BenchmarkRequest> request_combos;
396 std::vector<PathConstraints> no_path_constraints;
397 createRequestCombinations(benchmark_request, start_states, no_path_constraints, request_combos);
398 requests.insert(requests.end(), request_combos.begin(), request_combos.end());
399 }
400 return true;
401}
402
404 const BenchmarkOptions& options, moveit_msgs::msg::PlanningScene& scene_msg, std::vector<StartState>& start_states,
405 std::vector<PathConstraints>& path_constraints, std::vector<PathConstraints>& goal_constraints,
406 std::vector<TrajectoryConstraints>& traj_constraints, std::vector<BenchmarkRequest>& queries)
407{
408 try
409 {
410 warehouse_ros::DatabaseConnection::Ptr warehouse_connection = db_loader_.loadDatabase();
411 warehouse_connection->setParams(options.hostname, options.port, 20);
412 if (warehouse_connection->connect())
413 {
414 planning_scene_storage_ = std::make_shared<moveit_warehouse::PlanningSceneStorage>(warehouse_connection);
416 std::make_shared<moveit_warehouse::PlanningSceneWorldStorage>(warehouse_connection);
417 robot_state_storage_ = std::make_shared<moveit_warehouse::RobotStateStorage>(warehouse_connection);
418 constraints_storage_ = std::make_shared<moveit_warehouse::ConstraintsStorage>(warehouse_connection);
420 std::make_shared<moveit_warehouse::TrajectoryConstraintsStorage>(warehouse_connection);
421 RCLCPP_INFO(getLogger(), "Connected to DB");
422 }
423 else
424 {
425 RCLCPP_ERROR(getLogger(), "Failed to connect to DB");
426 return false;
427 }
428 }
429 catch (std::exception& e)
430 {
431 RCLCPP_ERROR(getLogger(), "Failed to initialize benchmark server: '%s'", e.what());
432 return false;
433 }
434
435 if (!loadPlanningScene(options.scene_name, scene_msg))
436 {
437 RCLCPP_ERROR(getLogger(), "Failed to load the planning scene");
438 return false;
439 }
440 if (!loadStates(options.start_state_regex, start_states))
441 {
442 RCLCPP_ERROR(getLogger(), "Failed to load the states");
443 return false;
444 }
445 if (!loadPathConstraints(options.goal_constraint_regex, goal_constraints))
446 {
447 RCLCPP_ERROR(getLogger(), "Failed to load the goal constraints");
448 }
449 if (!loadPathConstraints(options.path_constraint_regex, path_constraints))
450 {
451 RCLCPP_ERROR(getLogger(), "Failed to load the path constraints");
452 }
453 if (!loadTrajectoryConstraints(options.trajectory_constraint_regex, traj_constraints))
454 {
455 RCLCPP_ERROR(getLogger(), "Failed to load the trajectory constraints");
456 }
457 if (!loadQueries(options.query_regex, options.scene_name, queries))
458 {
459 RCLCPP_ERROR(getLogger(), "Failed to get a query regex");
460 }
461 return true;
462}
463
464void BenchmarkExecutor::shiftConstraintsByOffset(moveit_msgs::msg::Constraints& constraints,
465 const std::vector<double>& offset)
466{
467 Eigen::Isometry3d offset_tf(Eigen::AngleAxis<double>(offset.at(3), Eigen::Vector3d::UnitX()) *
468 Eigen::AngleAxis<double>(offset.at(4), Eigen::Vector3d::UnitY()) *
469 Eigen::AngleAxis<double>(offset.at(5), Eigen::Vector3d::UnitZ()));
470 offset_tf.translation() = Eigen::Vector3d(offset.at(0), offset.at(1), offset.at(2));
471
472 geometry_msgs::msg::Pose constraint_pose_msg;
473 constraint_pose_msg.position =
474 constraints.position_constraints.at(0).constraint_region.primitive_poses.at(0).position;
475 constraint_pose_msg.orientation = constraints.orientation_constraints.at(0).orientation;
476 Eigen::Isometry3d constraint_pose;
477 tf2::fromMsg(constraint_pose_msg, constraint_pose);
478
479 Eigen::Isometry3d new_pose = constraint_pose * offset_tf;
480 geometry_msgs::msg::Pose new_pose_msg;
481 new_pose_msg = tf2::toMsg(new_pose);
482
483 constraints.position_constraints.at(0).constraint_region.primitive_poses.at(0).position = new_pose_msg.position;
484 constraints.orientation_constraints.at(0).orientation = new_pose_msg.orientation;
485}
486
488 const std::vector<StartState>& start_states,
489 const std::vector<PathConstraints>& path_constraints,
490 std::vector<BenchmarkRequest>& requests)
491{
492 // Use default start state
493 if (start_states.empty())
494 {
495 // Adding path constraints
496 for (const PathConstraints& path_constraint : path_constraints)
497 {
498 BenchmarkRequest new_benchmark_request = benchmark_request;
499 new_benchmark_request.request.path_constraints = path_constraint.constraints.at(0);
500 new_benchmark_request.name = benchmark_request.name + "_" + path_constraint.name;
501 requests.push_back(new_benchmark_request);
502 }
503
504 if (path_constraints.empty())
505 {
506 requests.push_back(benchmark_request);
507 }
508 }
509 else // Create a request for each start state specified
510 {
511 for (const StartState& start_state : start_states)
512 {
513 // Skip start states that have the same name as the goal
514 if (start_state.name == benchmark_request.name)
515 continue;
516
517 BenchmarkRequest new_benchmark_request = benchmark_request;
518 new_benchmark_request.request.start_state = start_state.state;
519
520 // Duplicate the request for each of the path constraints
521 for (const PathConstraints& path_constraint : path_constraints)
522 {
523 new_benchmark_request.request.path_constraints = path_constraint.constraints.at(0);
524 new_benchmark_request.name = start_state.name + "_" + new_benchmark_request.name + "_" + path_constraint.name;
525 requests.push_back(new_benchmark_request);
526 }
527
528 if (path_constraints.empty())
529 {
530 new_benchmark_request.name = start_state.name + "_" + benchmark_request.name;
531 requests.push_back(new_benchmark_request);
532 }
533 }
534 }
535}
536
537bool BenchmarkExecutor::pipelinesExist(const std::map<std::string, std::vector<std::string>>& pipeline_configurations)
538{
539 // Make sure planner plugins exist
540 for (const std::pair<const std::string, std::vector<std::string>>& pipeline_config_entry : pipeline_configurations)
541 {
542 bool pipeline_exists = false;
543 for (const std::pair<const std::string, planning_pipeline::PlanningPipelinePtr>& pipeline_entry :
544 moveit_cpp_->getPlanningPipelines())
545 {
546 pipeline_exists = pipeline_entry.first == pipeline_config_entry.first;
547 if (pipeline_exists)
548 break;
549 }
550
551 if (!pipeline_exists)
552 {
553 RCLCPP_ERROR(getLogger(), "Planning pipeline '%s' does NOT exist", pipeline_config_entry.first.c_str());
554 return false;
555 }
556 }
557 return true;
558}
559
560bool BenchmarkExecutor::loadPlanningScene(const std::string& scene_name, moveit_msgs::msg::PlanningScene& scene_msg)
561{
562 try
563 {
564 if (planning_scene_storage_->hasPlanningScene(scene_name)) // whole planning scene
565 {
566 moveit_warehouse::PlanningSceneWithMetadata planning_scene_w_metadata;
567
568 if (!planning_scene_storage_->getPlanningScene(planning_scene_w_metadata, scene_name))
569 {
570 RCLCPP_ERROR(getLogger(), "Failed to load planning scene '%s'", scene_name.c_str());
571 return false;
572 }
573 scene_msg = static_cast<moveit_msgs::msg::PlanningScene>(*planning_scene_w_metadata);
574 }
575 else if (planning_scene_world_storage_->hasPlanningSceneWorld(scene_name)) // Just the world (no robot)
576 {
578 if (!planning_scene_world_storage_->getPlanningSceneWorld(pswwm, scene_name))
579 {
580 RCLCPP_ERROR(getLogger(), "Failed to load planning scene world '%s'", scene_name.c_str());
581 return false;
582 }
583 scene_msg.world = static_cast<moveit_msgs::msg::PlanningSceneWorld>(*pswwm);
584 scene_msg.robot_model_name =
585 "NO ROBOT INFORMATION. ONLY WORLD GEOMETRY"; // this will be fixed when running benchmark
586 }
587 else
588 {
589 RCLCPP_ERROR(getLogger(), "Failed to find planning scene '%s'", scene_name.c_str());
590 return false;
591 }
592 }
593 catch (std::exception& ex)
594 {
595 RCLCPP_ERROR(getLogger(), "Error loading planning scene: %s", ex.what());
596 return false;
597 }
598 RCLCPP_INFO(getLogger(), "Loaded planning scene successfully");
599 return true;
600}
601
602bool BenchmarkExecutor::loadQueries(const std::string& regex, const std::string& scene_name,
603 std::vector<BenchmarkRequest>& queries)
604{
605 if (regex.empty())
606 {
607 RCLCPP_WARN(getLogger(), "No query regex provided, don't load any queries from the database");
608 return true;
609 }
610
611 std::vector<std::string> query_names;
612 try
613 {
614 planning_scene_storage_->getPlanningQueriesNames(regex, query_names, scene_name);
615 }
616 catch (std::exception& ex)
617 {
618 RCLCPP_ERROR(getLogger(), "Error loading motion planning queries: %s", ex.what());
619 return false;
620 }
621
622 if (query_names.empty())
623 {
624 RCLCPP_ERROR(getLogger(), "Scene '%s' has no associated queries", scene_name.c_str());
625 return false;
626 }
627
628 for (const std::string& query_name : query_names)
629 {
631 try
632 {
633 planning_scene_storage_->getPlanningQuery(planning_query, scene_name, query_name);
634 }
635 catch (std::exception& ex)
636 {
637 RCLCPP_ERROR(getLogger(), "Error loading motion planning query '%s': %s", query_name.c_str(), ex.what());
638 continue;
639 }
640
641 BenchmarkRequest query;
642 query.name = query_name;
643 query.request = static_cast<moveit_msgs::msg::MotionPlanRequest>(*planning_query);
644 queries.push_back(query);
645 }
646 RCLCPP_INFO(getLogger(), "Loaded queries successfully");
647 return true;
648}
649
650bool BenchmarkExecutor::loadStates(const std::string& regex, std::vector<StartState>& start_states)
651{
652 if (!regex.empty())
653 {
654 std::regex start_regex(regex);
655 std::vector<std::string> state_names;
656 robot_state_storage_->getKnownRobotStates(state_names);
657
658 if (state_names.empty())
659 {
660 RCLCPP_WARN(getLogger(), "Database does not contain any named states");
661 }
662
663 for (const std::string& state_name : state_names)
664 {
665 std::smatch match;
666 if (std::regex_match(state_name, match, start_regex))
667 {
669 try
670 {
671 if (robot_state_storage_->getRobotState(robot_state, state_name))
672 {
673 StartState start_state;
674 start_state.state = moveit_msgs::msg::RobotState(*robot_state);
675 start_state.name = state_name;
676 start_states.push_back(start_state);
677 }
678 }
679 catch (std::exception& ex)
680 {
681 RCLCPP_ERROR(getLogger(), "Runtime error when loading state '%s': %s", state_name.c_str(), ex.what());
682 continue;
683 }
684 }
685 }
686
687 if (start_states.empty())
688 {
689 RCLCPP_WARN(getLogger(), "No stored states matched the provided start state regex: '%s'", regex.c_str());
690 }
691 }
692 RCLCPP_INFO(getLogger(), "Loaded states successfully");
693 return true;
694}
695
696bool BenchmarkExecutor::loadPathConstraints(const std::string& regex, std::vector<PathConstraints>& constraints)
697{
698 if (!regex.empty())
699 {
700 std::vector<std::string> cnames;
701 constraints_storage_->getKnownConstraints(regex, cnames);
702
703 for (const std::string& cname : cnames)
704 {
706 try
707 {
708 if (constraints_storage_->getConstraints(constr, cname))
709 {
710 PathConstraints constraint;
711 constraint.constraints.push_back(*constr);
712 constraint.name = cname;
713 constraints.push_back(constraint);
714 }
715 }
716 catch (std::exception& ex)
717 {
718 RCLCPP_ERROR(getLogger(), "Runtime error when loading path constraint '%s': %s", cname.c_str(), ex.what());
719 continue;
720 }
721 }
722
723 if (constraints.empty())
724 {
725 RCLCPP_WARN(getLogger(), "No path constraints found that match regex: '%s'", regex.c_str());
726 }
727 else
728 {
729 RCLCPP_INFO(getLogger(), "Loaded path constraints successfully");
730 }
731 }
732 return true;
733}
734
736 std::vector<TrajectoryConstraints>& constraints)
737{
738 if (!regex.empty())
739 {
740 std::vector<std::string> cnames;
741 trajectory_constraints_storage_->getKnownTrajectoryConstraints(regex, cnames);
742
743 for (const std::string& cname : cnames)
744 {
746 try
747 {
748 if (trajectory_constraints_storage_->getTrajectoryConstraints(constr, cname))
749 {
750 TrajectoryConstraints constraint;
751 constraint.constraints = *constr;
752 constraint.name = cname;
753 constraints.push_back(constraint);
754 }
755 }
756 catch (std::exception& ex)
757 {
758 RCLCPP_ERROR(getLogger(), "Runtime error when loading trajectory constraint '%s': %s", cname.c_str(), ex.what());
759 continue;
760 }
761 }
762
763 if (constraints.empty())
764 {
765 RCLCPP_WARN(getLogger(), "No trajectory constraints found that match regex: '%s'", regex.c_str());
766 }
767 else
768 {
769 RCLCPP_INFO(getLogger(), "Loaded trajectory constraints successfully");
770 }
771 }
772 return true;
773}
774
775void BenchmarkExecutor::runBenchmark(moveit_msgs::msg::MotionPlanRequest request, const BenchmarkOptions& options)
776{
777 benchmark_data_.clear();
778
779 auto num_planners = 0;
780 for (const std::pair<const std::string, std::vector<std::string>>& pipeline_entry : options.planning_pipelines)
781 {
782 num_planners += pipeline_entry.second.size();
783 }
784 num_planners += options.parallel_planning_pipelines.size();
785
786 boost_progress_display progress(num_planners * options.runs, std::cout);
787
788 // Iterate through all planning pipelines
789 auto planning_pipelines = moveit_cpp_->getPlanningPipelines();
790 for (const std::pair<const std::string, std::vector<std::string>>& pipeline_entry : options.planning_pipelines)
791 {
792 // Iterate through all planners configured for the pipeline
793 for (const std::string& planner_id : pipeline_entry.second)
794 {
795 // This container stores all of the benchmark data for this planner
796 PlannerBenchmarkData planner_data(options.runs);
797 // This vector stores all motion plan results for further evaluation
798 std::vector<planning_interface::MotionPlanDetailedResponse> responses(options.runs);
799 std::vector<bool> solved(options.runs);
800
801 request.planner_id = planner_id;
802
803 // Planner start events
804 for (PlannerStartEventFunction& planner_start_function : planner_start_functions_)
805 {
806 planner_start_function(request, planner_data);
807 }
808
810 .planner_id = planner_id,
811 .planning_pipeline = pipeline_entry.first,
812 .planning_attempts = request.num_planning_attempts,
813 .planning_time = request.allowed_planning_time,
814 .max_velocity_scaling_factor = request.max_velocity_scaling_factor,
815 .max_acceleration_scaling_factor = request.max_acceleration_scaling_factor
816 };
817
818 // Iterate runs
819 for (int j = 0; j < options.runs; ++j)
820 {
821 // Pre-run events
822 for (PreRunEventFunction& pre_event_function : pre_event_functions_)
823 pre_event_function(request);
824
825 // Create planning component
826 auto planning_component = std::make_shared<moveit_cpp::PlanningComponent>(request.group_name, moveit_cpp_);
827 moveit::core::RobotState start_state(planning_scene_monitor_->getRobotModel());
828 moveit::core::robotStateMsgToRobotState(request.start_state, start_state);
829
830 planning_component->setStartState(start_state);
831 planning_component->setGoal(request.goal_constraints);
832 planning_component->setPathConstraints(request.path_constraints);
833 planning_component->setTrajectoryConstraints(request.trajectory_constraints);
834
835 // Solve problem
836 std::chrono::system_clock::time_point start = std::chrono::system_clock::now();
837
838 // Planning pipeline benchmark
839 const auto response = planning_component->plan(plan_req_params, planning_scene_);
840
841 solved[j] = bool(response.error_code);
842
843 responses[j].error_code = response.error_code;
844 if (response.trajectory)
845 {
846 responses[j].description.push_back("plan");
847 responses[j].trajectory.push_back(response.trajectory);
848 responses[j].processing_time.push_back(response.planning_time);
849 }
850
851 std::chrono::duration<double> dt = std::chrono::system_clock::now() - start;
852 double total_time = dt.count();
853
854 // Collect data
855 start = std::chrono::system_clock::now();
856
857 // Post-run events
858 for (PostRunEventFunction& post_event_fn : post_event_functions_)
859 {
860 post_event_fn(request, responses[j], planner_data[j]);
861 }
862 collectMetrics(planner_data[j], responses[j], solved[j], total_time);
863 dt = std::chrono::system_clock::now() - start;
864 double metriconstraints_storage_time = dt.count();
865 RCLCPP_DEBUG(getLogger(), "Spent %lf seconds collecting metrics", metriconstraints_storage_time);
866
867 ++progress;
868 }
869
870 computeAveragePathSimilarities(planner_data, responses, solved);
871
872 // Planner completion events
874 {
875 planner_completion_fn(request, planner_data);
876 }
877
878 benchmark_data_.push_back(planner_data);
879 }
880 }
881
882 if (!options.parallel_planning_pipelines.empty())
883 {
884 // Iterate through all parallel pipelines
885 for (const std::pair<const std::string, std::vector<std::pair<std::string, std::string>>>& parallel_pipeline_entry :
886 options.parallel_planning_pipelines)
887 {
888 // This container stores all of the benchmark data for this planner
889 PlannerBenchmarkData planner_data(options.runs);
890 // This vector stores all motion plan results for further evaluation
891 std::vector<planning_interface::MotionPlanDetailedResponse> responses(options.runs);
892 std::vector<bool> solved(options.runs);
893
894 // Planner start events
895 for (PlannerStartEventFunction& planner_start_function : planner_start_functions_)
896 {
897 planner_start_function(request, planner_data);
898 }
899
900 // Create multi-pipeline request
902 for (const auto& pipeline_planner_id_pair : parallel_pipeline_entry.second)
903 {
905 .planner_id = pipeline_planner_id_pair.second,
906 .planning_pipeline = pipeline_planner_id_pair.first,
907 .planning_attempts = request.num_planning_attempts,
908 .planning_time = request.allowed_planning_time,
909 .max_velocity_scaling_factor = request.max_velocity_scaling_factor,
910 .max_acceleration_scaling_factor = request.max_acceleration_scaling_factor
911 };
912 multi_pipeline_plan_request.plan_request_parameter_vector.push_back(plan_req_params);
913 }
914
915 // Iterate runs
916 for (int j = 0; j < options.runs; ++j)
917 {
918 // Pre-run events
919 for (PreRunEventFunction& pre_event_function : pre_event_functions_)
920 {
921 pre_event_function(request);
922 }
923
924 // Create planning component
925 auto planning_component = std::make_shared<moveit_cpp::PlanningComponent>(request.group_name, moveit_cpp_);
926 moveit::core::RobotState start_state(planning_scene_monitor_->getRobotModel());
927 moveit::core::robotStateMsgToRobotState(request.start_state, start_state);
928
929 planning_component->setStartState(start_state);
930 planning_component->setGoal(request.goal_constraints);
931 planning_component->setPathConstraints(request.path_constraints);
932 planning_component->setTrajectoryConstraints(request.trajectory_constraints);
933
934 // Solve problem
935 std::chrono::system_clock::time_point start = std::chrono::system_clock::now();
936
937 const auto t1 = std::chrono::system_clock::now();
938 const auto response = planning_component->plan(multi_pipeline_plan_request,
940 nullptr, planning_scene_);
941 const auto t2 = std::chrono::system_clock::now();
942
943 solved[j] = bool(response.error_code);
944
945 responses[j].error_code = response.error_code;
946 if (response.trajectory)
947 {
948 responses[j].description.push_back("plan");
949 responses[j].trajectory.push_back(response.trajectory);
950 responses[j].processing_time.push_back(std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count());
951 }
952
953 std::chrono::duration<double> dt = std::chrono::system_clock::now() - start;
954 double total_time = dt.count();
955
956 // Collect data
957 start = std::chrono::system_clock::now();
958 // Post-run events
959 for (PostRunEventFunction& post_event_fn : post_event_functions_)
960 {
961 post_event_fn(request, responses[j], planner_data[j]);
962 }
963
964 collectMetrics(planner_data[j], responses[j], solved[j], total_time);
965 dt = std::chrono::system_clock::now() - start;
966 double metriconstraints_storage_time = dt.count();
967 RCLCPP_DEBUG(getLogger(), "Spent %lf seconds collecting metrics", metriconstraints_storage_time);
968
969 ++progress;
970 }
971
972 computeAveragePathSimilarities(planner_data, responses, solved);
973
974 // Planner completion events
976 {
977 planner_completion_fn(request, planner_data);
978 }
979
980 benchmark_data_.push_back(planner_data);
981 }
982 }
983}
984
986 const planning_interface::MotionPlanDetailedResponse& motion_plan_response,
987 bool solved, double total_time)
988{
989 metrics["time REAL"] = moveit::core::toString(total_time);
990 metrics["solved BOOLEAN"] = solved ? "true" : "false";
991
992 if (solved)
993 {
994 // Analyzing the trajectory(ies) geometrically
995 double traj_len = 0.0; // trajectory length
996 double clearance = 0.0; // trajectory clearance (average)
997 bool correct = true; // entire trajectory collision free and in bounds
998
999 double process_time = total_time;
1000 for (std::size_t j = 0; j < motion_plan_response.trajectory.size(); ++j)
1001 {
1002 correct = true;
1003 traj_len = 0.0;
1004 clearance = 0.0;
1005 const robot_trajectory::RobotTrajectory& p = *motion_plan_response.trajectory[j];
1006
1007 // compute path length
1008 traj_len = robot_trajectory::pathLength(p);
1009
1010 // compute correctness and clearance
1012 req.pad_environment_collisions = false;
1013 for (std::size_t k = 0; k < p.getWayPointCount(); ++k)
1014 {
1016 planning_scene_->checkCollision(req, res, p.getWayPoint(k));
1017 if (res.collision)
1018 correct = false;
1019 if (!p.getWayPoint(k).satisfiesBounds())
1020 correct = false;
1021 double d = planning_scene_->distanceToCollisionUnpadded(p.getWayPoint(k));
1022 if (d > 0.0) // in case of collision, distance is negative
1023 clearance += d;
1024 }
1025 clearance /= static_cast<double>(p.getWayPointCount());
1026
1027 // compute smoothness
1028 const auto smoothness = [&]() {
1029 const auto s = robot_trajectory::smoothness(p);
1030 return s.has_value() ? s.value() : 0.0;
1031 }();
1032
1033 metrics["path_" + motion_plan_response.description[j] + "_correct BOOLEAN"] = correct ? "true" : "false";
1034 metrics["path_" + motion_plan_response.description[j] + "_length REAL"] = moveit::core::toString(traj_len);
1035 metrics["path_" + motion_plan_response.description[j] + "_clearance REAL"] = moveit::core::toString(clearance);
1036 metrics["path_" + motion_plan_response.description[j] + "_smoothness REAL"] = moveit::core::toString(smoothness);
1037 metrics["path_" + motion_plan_response.description[j] + "_time REAL"] =
1038 moveit::core::toString(motion_plan_response.processing_time[j]);
1039
1040 if (j == motion_plan_response.trajectory.size() - 1)
1041 {
1042 metrics["final_path_correct BOOLEAN"] = correct ? "true" : "false";
1043 metrics["final_path_length REAL"] = moveit::core::toString(traj_len);
1044 metrics["final_path_clearance REAL"] = moveit::core::toString(clearance);
1045 metrics["final_path_smoothness REAL"] = moveit::core::toString(smoothness);
1046 metrics["final_path_time REAL"] = moveit::core::toString(motion_plan_response.processing_time[j]);
1047 }
1048 process_time -= motion_plan_response.processing_time[j];
1049 }
1050 if (process_time <= 0.0)
1051 process_time = 0.0;
1052 metrics["process_time REAL"] = moveit::core::toString(process_time);
1053 }
1054}
1055
1057 PlannerBenchmarkData& planner_data, const std::vector<planning_interface::MotionPlanDetailedResponse>& responses,
1058 const std::vector<bool>& solved)
1059{
1060 RCLCPP_INFO(getLogger(), "Computing result path similarity");
1061 const size_t result_count = planner_data.size();
1062 size_t unsolved = std::count_if(solved.begin(), solved.end(), [](bool s) { return !s; });
1063 std::vector<double> average_distances(responses.size());
1064 for (size_t first_traj_i = 0; first_traj_i < result_count; ++first_traj_i)
1065 {
1066 // If trajectory was not solved there is no valid average distance so it's set to max double only
1067 if (!solved[first_traj_i])
1068 {
1069 average_distances[first_traj_i] = std::numeric_limits<double>::max();
1070 continue;
1071 }
1072 // Iterate all result trajectories that haven't been compared yet
1073 for (size_t second_traj_i = first_traj_i + 1; second_traj_i < result_count; ++second_traj_i)
1074 {
1075 // Ignore if other result has not been solved
1076 if (!solved[second_traj_i])
1077 continue;
1078
1079 // Get final trajectories
1080 const robot_trajectory::RobotTrajectory& traj_first = *responses[first_traj_i].trajectory.back();
1081 const robot_trajectory::RobotTrajectory& traj_second = *responses[second_traj_i].trajectory.back();
1082
1083 // Compute trajectory distance
1084 double trajectory_distance;
1085 if (!computeTrajectoryDistance(traj_first, traj_second, trajectory_distance))
1086 continue;
1087
1088 // Add average distance to counters of both trajectories
1089 average_distances[first_traj_i] += trajectory_distance;
1090 average_distances[second_traj_i] += trajectory_distance;
1091 }
1092 // Normalize average distance by number of actual comparisons
1093 average_distances[first_traj_i] /= result_count - unsolved - 1;
1094 }
1095
1096 // Store results in planner_data
1097 for (size_t i = 0; i < result_count; ++i)
1098 planner_data[i]["average_waypoint_distance REAL"] = moveit::core::toString(average_distances[i]);
1099}
1100
1102 const robot_trajectory::RobotTrajectory& traj_second,
1103 double& result_distance)
1104{
1105 // Abort if trajectories are empty
1106 if (traj_first.empty() || traj_second.empty())
1107 return false;
1108
1109 // Waypoint counter
1110 size_t pos_first = 0;
1111 size_t pos_second = 0;
1112 const size_t max_pos_first = traj_first.getWayPointCount() - 1;
1113 const size_t max_pos_second = traj_second.getWayPointCount() - 1;
1114
1115 // Compute total distance between pairwise waypoints of both trajectories.
1116 // The selection of waypoint pairs is based on what steps results in the minimal distance between the next pair of
1117 // waypoints. We first check what steps are still possible or if we reached the end of the trajectories. Then we
1118 // compute the pairwise waypoint distances of the pairs from increasing both, the first, or the second trajectory.
1119 // Finally we select the pair that results in the minimal distance, summarize the total distance and iterate
1120 // accordingly. After that we compute the average trajectory distance by normalizing over the number of steps.
1121 double total_distance = 0;
1122 size_t steps = 0;
1123 double current_distance = traj_first.getWayPoint(pos_first).distance(traj_second.getWayPoint(pos_second));
1124 while (true)
1125 {
1126 // Keep track of total distance and number of comparisons
1127 total_distance += current_distance;
1128 ++steps;
1129 if (pos_first == max_pos_first && pos_second == max_pos_second) // end reached
1130 break;
1131
1132 // Determine what steps are still possible
1133 bool can_up_first = pos_first < max_pos_first;
1134 bool can_up_second = pos_second < max_pos_second;
1135 bool can_up_both = can_up_first && can_up_second;
1136
1137 // Compute pair-wise waypoint distances (increasing both, first, or second trajectories).
1138 double up_both = std::numeric_limits<double>::max();
1139 double up_first = std::numeric_limits<double>::max();
1140 double up_second = std::numeric_limits<double>::max();
1141 if (can_up_both)
1142 up_both = traj_first.getWayPoint(pos_first + 1).distance(traj_second.getWayPoint(pos_second + 1));
1143 if (can_up_first)
1144 up_first = traj_first.getWayPoint(pos_first + 1).distance(traj_second.getWayPoint(pos_second));
1145 if (can_up_second)
1146 up_second = traj_first.getWayPoint(pos_first).distance(traj_second.getWayPoint(pos_second + 1));
1147
1148 // Select actual step, store new distance value and iterate trajectory positions
1149 if (can_up_both && up_both < up_first && up_both < up_second)
1150 {
1151 ++pos_first;
1152 ++pos_second;
1153 current_distance = up_both;
1154 }
1155 else if ((can_up_first && up_first < up_second) || !can_up_second)
1156 {
1157 ++pos_first;
1158 current_distance = up_first;
1159 }
1160 else if (can_up_second)
1161 {
1162 ++pos_second;
1163 current_distance = up_second;
1164 }
1165 }
1166 // Normalize trajectory distance by number of comparison steps
1167 result_distance = total_distance / static_cast<double>(steps);
1168 return true;
1169}
1170
1171void BenchmarkExecutor::writeOutput(const BenchmarkRequest& benchmark_request, const std::string& start_time,
1172 double benchmark_duration, const BenchmarkOptions& options)
1173{
1174 // Count number of benchmarked planners
1175 size_t num_planners = 0;
1176 for (const std::pair<const std::string, std::vector<std::string>>& pipeline : options.planning_pipelines)
1177 {
1178 num_planners += pipeline.second.size();
1179 }
1180 num_planners += options.parallel_planning_pipelines.size();
1181
1182 std::string hostname = [&]() {
1183 static const int BUF_SIZE = 1024;
1184 char buffer[BUF_SIZE];
1185 int err = gethostname(buffer, sizeof(buffer));
1186 if (err != 0)
1187 {
1188 return std::string();
1189 }
1190 else
1191 {
1192 buffer[BUF_SIZE - 1] = '\0';
1193 return std::string(buffer);
1194 }
1195 }();
1196 if (hostname.empty())
1197 {
1198 hostname = "UNKNOWN";
1199 }
1200
1201 // Set output directory name
1202 std::string filename = options.output_directory;
1203 if (!filename.empty() && filename[filename.size() - 1] != '/')
1204 {
1205 filename.append("/");
1206 }
1207
1208 // Ensure directories exist
1209 std::filesystem::create_directories(filename);
1210
1211 // Create output log file name
1212 filename += (options.benchmark_name.empty() ? "" : options.benchmark_name + "_") + benchmark_request.name + "_" +
1213 hostname + "_" + start_time + ".log";
1214
1215 // Write benchmark results to file
1216 std::ofstream out(filename.c_str());
1217 if (!out)
1218 {
1219 RCLCPP_ERROR(getLogger(), "Failed to open '%s' for benchmark output", filename.c_str());
1220 return;
1221 }
1222
1223 // General data
1224 out << "MoveIt version " << MOVEIT_VERSION_STR << '\n';
1225 out << "Experiment " << benchmark_request.name << '\n';
1226 out << "Running on " << hostname << '\n';
1227 out << "Starting at " << start_time << '\n';
1228
1229 // Experiment setup
1230 moveit_msgs::msg::PlanningScene scene_msg;
1231 planning_scene_->getPlanningSceneMsg(scene_msg);
1232 out << "<<<|" << '\n';
1233 out << "Motion plan request:" << '\n'
1234 << " planner_id: " << benchmark_request.request.planner_id << '\n'
1235 << " group_name: " << benchmark_request.request.group_name << '\n'
1236 << " num_planning_attempts: " << benchmark_request.request.num_planning_attempts << '\n'
1237 << " allowed_planning_time: " << benchmark_request.request.allowed_planning_time << '\n';
1238 out << "Planning scene:" << '\n'
1239 << " scene_name: " << scene_msg.name << '\n'
1240 << " robot_model_name: " << scene_msg.robot_model_name << '\n'
1241 << "|>>>" << '\n';
1242
1243 // The real random seed is unknown. Writing a fake value
1244 out << "0 is the random seed" << '\n';
1245 out << benchmark_request.request.allowed_planning_time << " seconds per run" << '\n';
1246 // There is no memory cap
1247 out << "-1 MB per run" << '\n';
1248 out << options.runs << " runs per planner" << '\n';
1249 out << benchmark_duration << " seconds spent to collect the data" << '\n';
1250
1251 // No enum types
1252 out << "0 enum types" << '\n';
1253
1254 out << num_planners << " planners" << '\n';
1255
1256 // Index for benchmark data of one planner
1257 size_t run_id = 0;
1258
1259 // Write data for individual planners to the output file
1260 for (const std::pair<const std::string, std::vector<std::string>>& pipeline : options.planning_pipelines)
1261 {
1262 for (std::size_t i = 0; i < pipeline.second.size(); ++i, ++run_id)
1263 {
1264 // Write the name of the planner and the used pipeline
1265 out << pipeline.second[i] << " (" << pipeline.first << ')' << '\n';
1266
1267 // in general, we could have properties specific for a planner;
1268 // right now, we do not include such properties
1269 out << "0 common properties" << '\n';
1270
1271 // Create a list of the benchmark properties for this planner
1272 std::set<std::string> properties_set;
1273 for (PlannerRunData& planner_run_data : benchmark_data_[run_id])
1274 { // each run of this planner
1275 for (PlannerRunData::const_iterator pit = planner_run_data.begin(); pit != planner_run_data.end();
1276 ++pit) // each benchmark property of the given run
1277 properties_set.insert(pit->first);
1278 }
1279
1280 // Writing property list
1281 out << properties_set.size() << " properties for each run" << '\n';
1282 for (const std::string& property : properties_set)
1283 out << property << '\n';
1284
1285 // Number of runs
1286 out << benchmark_data_[run_id].size() << " runs" << '\n';
1287
1288 // And the benchmark properties
1289 for (PlannerRunData& planner_run_data : benchmark_data_[run_id]) // each run of this planner
1290 {
1291 // Write out properties in the order we listed them above
1292 for (const std::string& property : properties_set)
1293 {
1294 // Make sure this run has this property
1295 PlannerRunData::const_iterator runit = planner_run_data.find(property);
1296 if (runit != planner_run_data.end())
1297 out << runit->second;
1298 out << "; ";
1299 }
1300 out << '\n'; // end of the run
1301 }
1302 out << '.' << '\n'; // end the planner
1303 }
1304 }
1305
1306 // Write results for parallel planning pipelines to output file
1307 for (const std::pair<const std::string, std::vector<std::pair<std::string, std::string>>>& parallel_pipeline :
1308 options.parallel_planning_pipelines)
1309 {
1310 // Write the name of the planner and the used pipeline
1311 out << parallel_pipeline.first << " (" << parallel_pipeline.first << ")" << '\n';
1312
1313 // in general, we could have properties specific for a planner;
1314 // right now, we do not include such properties
1315 out << "0 common properties" << '\n';
1316
1317 // Create a list of the benchmark properties for this planner
1318 std::set<std::string> properties_set;
1319 // each run of this planner
1320 for (PlannerRunData& planner_run_data : benchmark_data_[run_id])
1321 {
1322 for (PlannerRunData::const_iterator pit = planner_run_data.begin(); pit != planner_run_data.end(); ++pit)
1323 {
1324 properties_set.insert(pit->first);
1325 }
1326 }
1327
1328 // Writing property list
1329 out << properties_set.size() << " properties for each run" << '\n';
1330 for (const std::string& property : properties_set)
1331 out << property << '\n';
1332
1333 // Number of runs
1334 out << benchmark_data_[run_id].size() << " runs" << '\n';
1335
1336 // And the benchmark properties
1337 for (PlannerRunData& planner_run_data : benchmark_data_[run_id]) // each run of this planner
1338 {
1339 // Write out properties in the order we listed them above
1340 for (const std::string& property : properties_set)
1341 {
1342 // Make sure this run has this property
1343 PlannerRunData::const_iterator runit = planner_run_data.find(property);
1344 if (runit != planner_run_data.end())
1345 out << runit->second;
1346 out << "; ";
1347 }
1348 out << '\n'; // end of the run
1349 }
1350 out << "." << '\n'; // end the planner
1351
1352 // Increase index
1353 run_id += 1;
1354 }
1355
1356 out.close();
1357 RCLCPP_INFO(getLogger(), "Benchmark results saved to '%s'", filename.c_str());
1358}
boost::progress_display boost_progress_display
boost::posix_time::ptime toBoost(const std::chrono::time_point< Clock, Duration > &from)
Representation of a robot's state. This includes position, velocity, acceleration and effort.
double distance(const RobotState &other) const
Return the sum of joint distances to "other" state. An L1 norm. Only considers active joints.
bool satisfiesBounds(double margin=0.0) const
bool loadPlanningScene(const std::string &scene_name, moveit_msgs::msg::PlanningScene &scene_msg)
Load the planning scene with the given name from the warehouse.
void computeAveragePathSimilarities(PlannerBenchmarkData &planner_data, const std::vector< planning_interface::MotionPlanDetailedResponse > &responses, const std::vector< bool > &solved)
bool pipelinesExist(const std::map< std::string, std::vector< std::string > > &planners)
Check that the desired planning pipelines exist.
bool loadQueries(const std::string &regex, const std::string &scene_name, std::vector< BenchmarkRequest > &queries)
Load all motion plan requests matching the given regular expression from the warehouse.
void createRequestCombinations(const BenchmarkRequest &benchmark_request, const std::vector< StartState > &start_states, const std::vector< PathConstraints > &path_constraints, std::vector< BenchmarkRequest > &combos)
Duplicate the given benchmark request for all combinations of start states and path constraints.
std::vector< PlannerRunData > PlannerBenchmarkData
Structure to hold information for a single planner's benchmark data.
planning_scene::PlanningScenePtr planning_scene_
std::vector< PlannerStartEventFunction > planner_start_functions_
bool computeTrajectoryDistance(const robot_trajectory::RobotTrajectory &traj_first, const robot_trajectory::RobotTrajectory &traj_second, double &result_distance)
void addQueryStartEvent(const QueryStartEventFunction &func)
std::shared_ptr< moveit_warehouse::ConstraintsStorage > constraints_storage_
std::shared_ptr< moveit_warehouse::PlanningSceneStorage > planning_scene_storage_
std::function< void(const moveit_msgs::msg::MotionPlanRequest &request, PlannerBenchmarkData &benchmark_data)> PlannerCompletionEventFunction
std::vector< QueryCompletionEventFunction > query_end_functions_
std::shared_ptr< moveit_warehouse::TrajectoryConstraintsStorage > trajectory_constraints_storage_
std::function< void(const moveit_msgs::msg::MotionPlanRequest &request, planning_scene::PlanningScenePtr)> QueryCompletionEventFunction
Definition of a query-end benchmark event function. Invoked after a query has finished benchmarking.
std::shared_ptr< moveit_cpp::MoveItCpp > moveit_cpp_
virtual void collectMetrics(PlannerRunData &metrics, const planning_interface::MotionPlanDetailedResponse &motion_plan_response, bool solved, double total_time)
std::vector< QueryStartEventFunction > query_start_functions_
bool loadPathConstraints(const std::string &regex, std::vector< PathConstraints > &constraints)
Load all constraints matching the given regular expression from the warehouse.
std::vector< PreRunEventFunction > pre_event_functions_
void runBenchmark(moveit_msgs::msg::MotionPlanRequest request, const BenchmarkOptions &options)
Execute the given motion plan request on the set of planners for the set number of runs.
void addPreRunEvent(const PreRunEventFunction &func)
BenchmarkExecutor(const rclcpp::Node::SharedPtr &node, const std::string &robot_description_param="robot_description")
virtual bool runBenchmarks(const BenchmarkOptions &options)
std::vector< PlannerBenchmarkData > benchmark_data_
virtual bool loadBenchmarkQueryData(const BenchmarkOptions &options, moveit_msgs::msg::PlanningScene &scene_msg, std::vector< StartState > &start_states, std::vector< PathConstraints > &path_constraints, std::vector< PathConstraints > &goal_constraints, std::vector< TrajectoryConstraints > &traj_constraints, std::vector< BenchmarkRequest > &queries)
Initialize benchmark query data from start states and constraints.
std::vector< PostRunEventFunction > post_event_functions_
virtual bool initializeBenchmarks(const BenchmarkOptions &options, moveit_msgs::msg::PlanningScene &scene_msg, std::vector< BenchmarkRequest > &queries)
void addPlannerCompletionEvent(const PlannerCompletionEventFunction &func)
std::shared_ptr< planning_scene_monitor::PlanningSceneMonitor > planning_scene_monitor_
void addPlannerStartEvent(const PlannerStartEventFunction &func)
void addQueryCompletionEvent(const QueryCompletionEventFunction &func)
bool loadStates(const std::string &regex, std::vector< StartState > &start_states)
Load all states matching the given regular expression from the warehouse.
std::function< void(const moveit_msgs::msg::MotionPlanRequest &request, const planning_interface::MotionPlanDetailedResponse &response, PlannerRunData &run_data)> PostRunEventFunction
Definition of a post-run benchmark event function. Invoked immediately after each planner calls solve...
std::function< void(moveit_msgs::msg::MotionPlanRequest &request)> PreRunEventFunction
Definition of a pre-run benchmark event function. Invoked immediately before each planner calls solve...
std::vector< PlannerCompletionEventFunction > planner_completion_functions_
std::shared_ptr< moveit_warehouse::RobotStateStorage > robot_state_storage_
std::function< void(const moveit_msgs::msg::MotionPlanRequest &request, planning_scene::PlanningScenePtr)> QueryStartEventFunction
Definition of a query-start benchmark event function. Invoked before a new query is benchmarked.
virtual void writeOutput(const BenchmarkRequest &benchmark_request, const std::string &start_time, double benchmark_duration, const BenchmarkOptions &options)
bool initialize(const std::vector< std::string > &plugin_classes)
std::shared_ptr< moveit_warehouse::PlanningSceneWorldStorage > planning_scene_world_storage_
std::function< void(const moveit_msgs::msg::MotionPlanRequest &request, PlannerBenchmarkData &benchmark_data)> PlannerStartEventFunction
bool loadTrajectoryConstraints(const std::string &regex, std::vector< TrajectoryConstraints > &constraints)
Load all trajectory constraints from the warehouse that match the given regular expression.
void addPostRunEvent(const PostRunEventFunction &func)
void shiftConstraintsByOffset(moveit_msgs::msg::Constraints &constraints, const std::vector< double > &offset)
std::map< std::string, std::string > PlannerRunData
Structure to hold information for a single run of a planner.
Maintain a sequence of waypoints and the time durations between these waypoints.
const moveit::core::RobotState & getWayPoint(std::size_t index) const
std::string toString(double d)
Convert a double to std::string using the classic C locale.
bool robotStateMsgToRobotState(const Transforms &tf, const moveit_msgs::msg::RobotState &robot_state, RobotState &state, bool copy_attached_bodies=true)
Convert a robot state msg (with accompanying extra transforms) to a MoveIt robot state.
::planning_interface::MotionPlanResponse getShortestSolution(const std::vector<::planning_interface::MotionPlanResponse > &solutions)
Function that returns the shortest solution out of a vector of solutions based on robot_trajectory::p...
warehouse_ros::MessageWithMetadata< moveit_msgs::msg::RobotState >::ConstPtr RobotStateWithMetadata
warehouse_ros::MessageWithMetadata< moveit_msgs::msg::PlanningScene >::ConstPtr PlanningSceneWithMetadata
warehouse_ros::MessageWithMetadata< moveit_msgs::msg::TrajectoryConstraints >::ConstPtr TrajectoryConstraintsWithMetadata
warehouse_ros::MessageWithMetadata< moveit_msgs::msg::PlanningSceneWorld >::ConstPtr PlanningSceneWorldWithMetadata
warehouse_ros::MessageWithMetadata< moveit_msgs::msg::MotionPlanRequest >::ConstPtr MotionPlanRequestWithMetadata
warehouse_ros::MessageWithMetadata< moveit_msgs::msg::Constraints >::ConstPtr ConstraintsWithMetadata
rclcpp::Logger getLogger(const std::string &name)
Creates a namespaced logger.
Definition logger.cpp:79
std::optional< double > smoothness(const RobotTrajectory &trajectory)
Calculate the smoothness of a given trajectory.
double pathLength(const RobotTrajectory &trajectory)
Calculate the path length of a given trajectory based on the accumulated robot state distances....
Representation of a collision checking request.
bool pad_environment_collisions
If true, use padded collision environment.
Representation of a collision checking result.
bool collision
True if collision was found, false otherwise.
Planner parameters provided with the MotionPlanRequest.
Planner parameters provided with the MotionPlanRequest.
std::vector< moveit_msgs::msg::Constraints > constraints
Options to configure a benchmark experiment. The configuration is provided via ROS2 parameters.
std::vector< robot_trajectory::RobotTrajectoryPtr > trajectory