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