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