moveit2
The MoveIt Motion Planning Framework for ROS 2.
best_seen_execution_time_policy.cpp
Go to the documentation of this file.
1 // Copyright 2024 Intrinsic Innovation LLC.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
24 #include <limits>
25 #include <memory>
26 #include <sstream>
27 #include <string>
28 
29 #include <warehouse_ros/message_collection.h>
31 
32 #include <moveit_msgs/msg/motion_plan_request.hpp>
33 #include <moveit_msgs/msg/robot_trajectory.hpp>
34 #include <moveit_msgs/srv/get_cartesian_path.hpp>
35 
41 
42 namespace moveit_ros
43 {
44 namespace trajectory_cache
45 {
46 
47 using ::warehouse_ros::MessageCollection;
48 using ::warehouse_ros::MessageWithMetadata;
49 using ::warehouse_ros::Metadata;
50 using ::warehouse_ros::Query;
51 
52 using ::moveit::core::MoveItErrorCode;
53 using ::moveit::planning_interface::MoveGroupInterface;
54 
56 using ::moveit_msgs::msg::RobotTrajectory;
57 using ::moveit_msgs::srv::GetCartesianPath;
58 
59 using ::moveit_ros::trajectory_cache::FeaturesInterface;
60 
61 namespace
62 {
63 
64 const std::string EXECUTION_TIME = "execution_time_s";
65 const std::string FRACTION = "fraction";
66 const std::string PLANNING_TIME = "planning_time_s";
67 
68 } // namespace
69 
70 // =================================================================================================
71 // BestSeenExecutionTimePolicy.
72 // =================================================================================================
73 // moveit_msgs::msg::MotionPlanRequest <=> moveit::planning_interface::MoveGroupInterface::Plan
74 
76  : name_("BestSeenExecutionTimePolicy"), best_seen_execution_time_(std::numeric_limits<double>::infinity())
77 {
78  exact_matching_supported_features_ = BestSeenExecutionTimePolicy::getSupportedFeatures(/*start_tolerance=*/0.0,
79  /*goal_tolerance=*/0.0);
80 }
81 
82 std::vector<std::unique_ptr<FeaturesInterface<MotionPlanRequest>>>
83 BestSeenExecutionTimePolicy::getSupportedFeatures(double start_tolerance, double goal_tolerance)
84 {
85  std::vector<std::unique_ptr<FeaturesInterface<MotionPlanRequest>>> out;
86  out.reserve(6);
87 
88  // Start.
89  out.push_back(std::make_unique<WorkspaceFeatures>());
90  out.push_back(std::make_unique<StartStateJointStateFeatures>(start_tolerance));
91 
92  // Goal.
93  out.push_back(std::make_unique<MaxSpeedAndAccelerationFeatures>());
94  out.push_back(std::make_unique<GoalConstraintsFeatures>(goal_tolerance));
95  out.push_back(std::make_unique<PathConstraintsFeatures>(goal_tolerance));
96  out.push_back(std::make_unique<TrajectoryConstraintsFeatures>(goal_tolerance));
97 
98  return out;
99 }
100 
102 {
103  return name_;
104 }
105 
106 MoveItErrorCode BestSeenExecutionTimePolicy::checkCacheInsertInputs(const MoveGroupInterface& move_group,
107  const MessageCollection<RobotTrajectory>& /*coll*/,
108  const MotionPlanRequest& key,
109  const MoveGroupInterface::Plan& value)
110 {
111  std::string workspace_frame_id = getWorkspaceFrameId(move_group, key.workspace_parameters);
112 
113  // Check key.
114  if (workspace_frame_id.empty())
115  {
116  return MoveItErrorCode(MoveItErrorCode::INVALID_MOTION_PLAN,
117  name_ + ": Skipping insert: Workspace frame ID cannot be empty.");
118  }
119  if (key.goal_constraints.empty())
120  {
121  return MoveItErrorCode(MoveItErrorCode::INVALID_MOTION_PLAN, name_ + ": Skipping insert: No goal.");
122  }
123 
124  // Check value.
125  if (value.trajectory.joint_trajectory.points.empty())
126  {
127  return MoveItErrorCode(MoveItErrorCode::INVALID_MOTION_PLAN, name_ + ": Empty joint trajectory points.");
128  }
129  if (value.trajectory.joint_trajectory.joint_names.empty())
130  {
131  return MoveItErrorCode(MoveItErrorCode::INVALID_MOTION_PLAN,
132  name_ + ": Skipping insert: Empty joint trajectory joint names.");
133  }
134  if (!value.trajectory.multi_dof_joint_trajectory.points.empty())
135  {
136  return MoveItErrorCode(MoveItErrorCode::INVALID_MOTION_PLAN,
137  name_ + ": Skipping insert: Multi-DOF trajectory plans are not supported.");
138  }
139  if (value.trajectory.joint_trajectory.header.frame_id.empty())
140  {
141  return MoveItErrorCode(MoveItErrorCode::INVALID_MOTION_PLAN,
142  name_ + ": Skipping insert: Trajectory frame ID cannot be empty.");
143  }
144  if (workspace_frame_id != value.trajectory.joint_trajectory.header.frame_id)
145  {
146  std::stringstream ss;
147  ss << "Skipping insert: Plan request frame (" << workspace_frame_id << ") does not match plan frame ("
148  << value.trajectory.joint_trajectory.header.frame_id << ").";
149  return MoveItErrorCode(MoveItErrorCode::INVALID_MOTION_PLAN, ss.str());
150  }
151 
152  return MoveItErrorCode::SUCCESS;
153 }
154 
155 std::vector<MessageWithMetadata<RobotTrajectory>::ConstPtr> BestSeenExecutionTimePolicy::fetchMatchingEntries(
156  const MoveGroupInterface& move_group, const MessageCollection<RobotTrajectory>& coll, const MotionPlanRequest& key,
157  const MoveGroupInterface::Plan& /*value*/, double exact_match_precision)
158 {
159  Query::Ptr query = coll.createQuery();
160  for (const auto& feature : exact_matching_supported_features_)
161  {
162  if (MoveItErrorCode ret = feature->appendFeaturesAsExactFetchQuery(*query, key, move_group, exact_match_precision);
163  !ret)
164  {
165  return {};
166  }
167  }
168 
169  std::vector<MessageWithMetadata<RobotTrajectory>::ConstPtr> out =
170  coll.queryList(query, /*metadata_only=*/true, /*sort_by=*/EXECUTION_TIME, /*ascending=*/true);
171  if (!out.empty())
172  {
173  best_seen_execution_time_ = out[0]->lookupDouble(EXECUTION_TIME);
174  }
175 
176  return out;
177 }
178 
180  const MoveGroupInterface& /*move_group*/, const MotionPlanRequest& /*key*/, const MoveGroupInterface::Plan& value,
181  const MessageWithMetadata<RobotTrajectory>::ConstPtr& matching_entry, std::string* reason)
182 {
183  double matching_entry_execution_time_s = matching_entry->lookupDouble(EXECUTION_TIME);
184  double candidate_execution_time_s = getExecutionTime(value.trajectory);
185 
186  if (matching_entry_execution_time_s >= candidate_execution_time_s)
187  {
188  if (reason != nullptr)
189  {
190  std::stringstream ss;
191  ss << "Matching trajectory execution_time_s `" << matching_entry_execution_time_s << "s` "
192  << "is worse than candidate trajectory's execution_time_s `" << candidate_execution_time_s << "s`";
193  *reason = ss.str();
194  }
195  return true;
196  }
197  else
198  {
199  if (reason != nullptr)
200  {
201  std::stringstream ss;
202  ss << "Matching trajectory execution_time_s `" << matching_entry_execution_time_s << "s` "
203  << "is better than candidate trajectory's execution_time_s `" << candidate_execution_time_s << "s`";
204  *reason = ss.str();
205  }
206  return false;
207  }
208 }
209 
210 bool BestSeenExecutionTimePolicy::shouldInsert(const MoveGroupInterface& /*move_group*/,
211  const MotionPlanRequest& /*key*/, const MoveGroupInterface::Plan& value,
212  std::string* reason)
213 {
214  double execution_time_s = getExecutionTime(value.trajectory);
215 
216  if (execution_time_s < best_seen_execution_time_)
217  {
218  if (reason != nullptr)
219  {
220  std::stringstream ss;
221  ss << "New trajectory execution_time_s `" << execution_time_s << "s` "
222  << "is better than best trajectory's execution_time_s `" << best_seen_execution_time_ << "s`";
223  *reason = ss.str();
224  }
225  return true;
226  }
227  else
228  {
229  if (reason != nullptr)
230  {
231  std::stringstream ss;
232  ss << "New trajectory execution_time `" << execution_time_s << "s` "
233  << "is worse than best trajectory's execution_time `" << best_seen_execution_time_ << "s`";
234  *reason = ss.str();
235  }
236  return false;
237  }
238 }
239 
240 MoveItErrorCode BestSeenExecutionTimePolicy::appendInsertMetadata(Metadata& metadata,
241  const MoveGroupInterface& move_group,
242  const MotionPlanRequest& key,
243  const MoveGroupInterface::Plan& value)
244 {
245  for (const auto& feature : exact_matching_supported_features_)
246  {
247  if (MoveItErrorCode ret = feature->appendFeaturesAsInsertMetadata(metadata, key, move_group); !ret)
248  {
249  return ret;
250  }
251  }
252 
253  // Append ValueT metadata.
254  metadata.append(EXECUTION_TIME, getExecutionTime(value.trajectory));
255  metadata.append(PLANNING_TIME, value.planning_time);
256 
257  return MoveItErrorCode::SUCCESS;
258 }
259 
261 {
262  best_seen_execution_time_ = std::numeric_limits<double>::infinity();
263  return;
264 }
265 
266 // =================================================================================================
267 // CartesianBestSeenExecutionTimePolicy.
268 // =================================================================================================
269 // moveit_msgs::srv::GetCartesianPath::Request <=> moveit_msgs::srv::GetCartesianPath::Response
270 
272  : name_("CartesianBestSeenExecutionTimePolicy"), best_seen_execution_time_(std::numeric_limits<double>::infinity())
273 {
274  exact_matching_supported_features_ =
276  /*goal_tolerance=*/0.0, /*min_fraction=*/0.0);
277 }
278 
279 std::vector<std::unique_ptr<FeaturesInterface<GetCartesianPath::Request>>>
280 CartesianBestSeenExecutionTimePolicy::getSupportedFeatures(double start_tolerance, double goal_tolerance,
281  double min_fraction)
282 {
283  std::vector<std::unique_ptr<FeaturesInterface<GetCartesianPath::Request>>> out;
284  out.reserve(7);
285 
286  // Start.
287  out.push_back(std::make_unique<CartesianWorkspaceFeatures>());
288  out.push_back(std::make_unique<CartesianStartStateJointStateFeatures>(start_tolerance));
289 
290  // Goal.
291  out.push_back(std::make_unique<CartesianMaxSpeedAndAccelerationFeatures>());
292  out.push_back(std::make_unique<CartesianMaxStepAndJumpThresholdFeatures>());
293  out.push_back(std::make_unique<CartesianWaypointsFeatures>(goal_tolerance));
294  out.push_back(std::make_unique<CartesianPathConstraintsFeatures>(goal_tolerance));
295 
296  // Min fraction.
297  out.push_back(std::make_unique<QueryOnlyGTEFeature<double, GetCartesianPath::Request>>(FRACTION, min_fraction));
298 
299  return out;
300 }
301 
303 {
304  return name_;
305 }
306 
308  const MoveGroupInterface& move_group, const MessageCollection<RobotTrajectory>& /*coll*/,
309  const GetCartesianPath::Request& key, const GetCartesianPath::Response& value)
310 {
311  std::string frame_id = getCartesianPathRequestFrameId(move_group, key);
312 
313  // Check key.
314  if (frame_id.empty())
315  {
316  return MoveItErrorCode(MoveItErrorCode::INVALID_MOTION_PLAN,
317  name_ + ": Skipping insert: Workspace frame ID cannot be empty.");
318  }
319  if (key.waypoints.empty())
320  {
321  return MoveItErrorCode(MoveItErrorCode::INVALID_MOTION_PLAN, name_ + ": Skipping insert: No waypoints.");
322  }
323 
324  // Check value.
325  if (value.solution.joint_trajectory.points.empty())
326  {
327  return MoveItErrorCode(MoveItErrorCode::INVALID_MOTION_PLAN, name_ + ": Empty joint trajectory points.");
328  }
329  if (value.solution.joint_trajectory.joint_names.empty())
330  {
331  return MoveItErrorCode(MoveItErrorCode::INVALID_MOTION_PLAN,
332  name_ + ": Skipping insert: Empty joint trajectory joint names.");
333  }
334  if (!value.solution.multi_dof_joint_trajectory.points.empty())
335  {
336  return MoveItErrorCode(MoveItErrorCode::INVALID_MOTION_PLAN,
337  name_ + ": Skipping insert: Multi-DOF trajectory plans are not supported.");
338  }
339  if (value.solution.joint_trajectory.header.frame_id.empty())
340  {
341  return MoveItErrorCode(MoveItErrorCode::INVALID_MOTION_PLAN,
342  name_ + ": Skipping insert: Trajectory frame ID cannot be empty.");
343  }
344  if (frame_id != value.solution.joint_trajectory.header.frame_id)
345  {
346  std::stringstream ss;
347  ss << "Skipping insert: Plan request frame `" << frame_id << "` does not match plan frame `"
348  << value.solution.joint_trajectory.header.frame_id << "`.";
349  return MoveItErrorCode(MoveItErrorCode::INVALID_MOTION_PLAN, ss.str());
350  }
351 
352  return MoveItErrorCode::SUCCESS;
353 }
354 
355 std::vector<MessageWithMetadata<RobotTrajectory>::ConstPtr> CartesianBestSeenExecutionTimePolicy::fetchMatchingEntries(
356  const MoveGroupInterface& move_group, const MessageCollection<RobotTrajectory>& coll,
357  const GetCartesianPath::Request& key, const GetCartesianPath::Response& /*value*/, double exact_match_precision)
358 {
359  Query::Ptr query = coll.createQuery();
360  for (const auto& feature : exact_matching_supported_features_)
361  {
362  if (MoveItErrorCode ret = feature->appendFeaturesAsExactFetchQuery(*query, key, move_group, exact_match_precision);
363  !ret)
364  {
365  return {};
366  }
367  }
368 
369  std::vector<MessageWithMetadata<RobotTrajectory>::ConstPtr> out =
370  coll.queryList(query, /*metadata_only=*/true, /*sort_by=*/EXECUTION_TIME, /*ascending=*/true);
371  if (!out.empty())
372  {
373  best_seen_execution_time_ = out[0]->lookupDouble(EXECUTION_TIME);
374  }
375 
376  return out;
377 }
378 
380  const MoveGroupInterface& /*move_group*/, const GetCartesianPath::Request& /*key*/,
381  const GetCartesianPath::Response& value, const MessageWithMetadata<RobotTrajectory>::ConstPtr& matching_entry,
382  std::string* reason)
383 {
384  double matching_entry_execution_time_s = matching_entry->lookupDouble(EXECUTION_TIME);
385  double candidate_execution_time_s = getExecutionTime(value.solution);
386 
387  if (matching_entry_execution_time_s >= candidate_execution_time_s)
388  {
389  if (reason != nullptr)
390  {
391  std::stringstream ss;
392  ss << "Matching trajectory execution_time_s `" << matching_entry_execution_time_s << "s` "
393  << "is worse than candidate trajectory's execution_time_s `" << candidate_execution_time_s << "s`";
394  *reason = ss.str();
395  }
396  return true;
397  }
398  else
399  {
400  if (reason != nullptr)
401  {
402  std::stringstream ss;
403  ss << "Matching trajectory execution_time_s `" << matching_entry_execution_time_s << "s` "
404  << "is better than candidate trajectory's execution_time_s `" << candidate_execution_time_s << "s`";
405  *reason = ss.str();
406  }
407  return false;
408  }
409 }
410 
411 bool CartesianBestSeenExecutionTimePolicy::shouldInsert(const MoveGroupInterface& /*move_group*/,
412  const GetCartesianPath::Request& /*key*/,
413  const GetCartesianPath::Response& value, std::string* reason)
414 {
415  double execution_time_s = getExecutionTime(value.solution);
416 
417  if (execution_time_s < best_seen_execution_time_)
418  {
419  if (reason != nullptr)
420  {
421  std::stringstream ss;
422  ss << "New cartesian trajectory execution_time_s `" << execution_time_s << "s` "
423  << "is better than best cartesian trajectory's execution_time_s `" << best_seen_execution_time_ << "s`";
424  *reason = ss.str();
425  }
426  return true;
427  }
428  else
429  {
430  if (reason != nullptr)
431  {
432  std::stringstream ss;
433  ss << "New cartesian trajectory execution_time `" << execution_time_s << "s` "
434  << "is worse than best cartesian trajectory's execution_time `" << best_seen_execution_time_ << "s`";
435  *reason = ss.str();
436  }
437  return false;
438  }
439 }
440 
442  const MoveGroupInterface& move_group,
443  const GetCartesianPath::Request& key,
444  const GetCartesianPath::Response& value)
445 {
446  for (const auto& feature : exact_matching_supported_features_)
447  {
448  if (MoveItErrorCode ret = feature->appendFeaturesAsInsertMetadata(metadata, key, move_group); !ret)
449  {
450  return ret;
451  }
452  }
453 
454  // Append ValueT metadata.
455  metadata.append(EXECUTION_TIME, getExecutionTime(value.solution));
456  metadata.append(FRACTION, value.fraction);
457 
458  return MoveItErrorCode::SUCCESS;
459 }
460 
462 {
463  best_seen_execution_time_ = std::numeric_limits<double>::infinity();
464  return;
465 }
466 
467 } // namespace trajectory_cache
468 } // namespace moveit_ros
A cache insertion policy that only decides to insert if the motion plan is the one with the shortest ...
std::string getName() const override
Gets the name of the cache insert policy.
moveit::core::MoveItErrorCode appendInsertMetadata(warehouse_ros::Metadata &metadata, const moveit::planning_interface::MoveGroupInterface &move_group, const moveit_msgs::msg::MotionPlanRequest &key, const moveit::planning_interface::MoveGroupInterface::Plan &value) override
Appends the insert metadata with the features supported by the policy.
moveit::core::MoveItErrorCode checkCacheInsertInputs(const moveit::planning_interface::MoveGroupInterface &move_group, const warehouse_ros::MessageCollection< moveit_msgs::msg::RobotTrajectory > &coll, const moveit_msgs::msg::MotionPlanRequest &key, const moveit::planning_interface::MoveGroupInterface::Plan &value) override
Checks inputs to the cache insert call to see if we should abort instead.
static std::vector< std::unique_ptr< FeaturesInterface< moveit_msgs::msg::MotionPlanRequest > > > getSupportedFeatures(double start_tolerance, double goal_tolerance)
Configures and returns a vector of feature extractors that can be used with this policy.
bool shouldInsert(const moveit::planning_interface::MoveGroupInterface &move_group, const moveit_msgs::msg::MotionPlanRequest &key, const moveit::planning_interface::MoveGroupInterface::Plan &value, std::string *reason=nullptr) override
Returns whether the insertion candidate should be inserted into the cache.
std::vector< warehouse_ros::MessageWithMetadata< moveit_msgs::msg::RobotTrajectory >::ConstPtr > fetchMatchingEntries(const moveit::planning_interface::MoveGroupInterface &move_group, const warehouse_ros::MessageCollection< moveit_msgs::msg::RobotTrajectory > &coll, const moveit_msgs::msg::MotionPlanRequest &key, const moveit::planning_interface::MoveGroupInterface::Plan &value, double exact_match_precision) override
Fetches all "matching" cache entries for comparison for pruning.
bool shouldPruneMatchingEntry(const moveit::planning_interface::MoveGroupInterface &move_group, const moveit_msgs::msg::MotionPlanRequest &key, const moveit::planning_interface::MoveGroupInterface::Plan &value, const warehouse_ros::MessageWithMetadata< moveit_msgs::msg::RobotTrajectory >::ConstPtr &matching_entry, std::string *reason=nullptr) override
moveit::core::MoveItErrorCode appendInsertMetadata(warehouse_ros::Metadata &metadata, const moveit::planning_interface::MoveGroupInterface &move_group, const moveit_msgs::srv::GetCartesianPath::Request &key, const moveit_msgs::srv::GetCartesianPath::Response &value) override
Appends the insert metadata with the features supported by the policy.
static std::vector< std::unique_ptr< FeaturesInterface< moveit_msgs::srv::GetCartesianPath::Request > > > getSupportedFeatures(double start_tolerance, double goal_tolerance, double min_fraction)
Configures and returns a vector of feature extractors that can be used with this policy.
moveit::core::MoveItErrorCode checkCacheInsertInputs(const moveit::planning_interface::MoveGroupInterface &move_group, const warehouse_ros::MessageCollection< moveit_msgs::msg::RobotTrajectory > &coll, const moveit_msgs::srv::GetCartesianPath::Request &key, const moveit_msgs::srv::GetCartesianPath::Response &value) override
Checks inputs to the cache insert call to see if we should abort instead.
std::vector< warehouse_ros::MessageWithMetadata< moveit_msgs::msg::RobotTrajectory >::ConstPtr > fetchMatchingEntries(const moveit::planning_interface::MoveGroupInterface &move_group, const warehouse_ros::MessageCollection< moveit_msgs::msg::RobotTrajectory > &coll, const moveit_msgs::srv::GetCartesianPath::Request &key, const moveit_msgs::srv::GetCartesianPath::Response &value, double exact_match_precision) override
Fetches all "matching" cache entries for comparison for pruning.
std::string getName() const override
Gets the name of the cache insert policy.
bool shouldInsert(const moveit::planning_interface::MoveGroupInterface &move_group, const moveit_msgs::srv::GetCartesianPath::Request &key, const moveit_msgs::srv::GetCartesianPath::Response &value, std::string *reason=nullptr) override
Returns whether the insertion candidate should be inserted into the cache.
bool shouldPruneMatchingEntry(const moveit::planning_interface::MoveGroupInterface &move_group, const moveit_msgs::srv::GetCartesianPath::Request &key, const moveit_msgs::srv::GetCartesianPath::Response &value, const warehouse_ros::MessageWithMetadata< moveit_msgs::msg::RobotTrajectory >::ConstPtr &matching_entry, std::string *reason=nullptr) override
User-specified constant features to key the trajectory cache on.
moveit_msgs::srv::GetCartesianPath::Request features to key the trajectory cache on.
moveit_msgs::msg::MotionPlanRequest features to key the trajectory cache on.
Utilities used by the trajectory_cache package.
std::string getWorkspaceFrameId(const moveit::planning_interface::MoveGroupInterface &move_group, const moveit_msgs::msg::WorkspaceParameters &workspace_parameters)
Gets workspace frame ID. If workspace_parameters has no frame ID, fetch it from move_group.
double getExecutionTime(const moveit_msgs::msg::RobotTrajectory &trajectory)
Returns the execution time of the trajectory in double seconds.
Definition: utils.cpp:146
std::string getCartesianPathRequestFrameId(const moveit::planning_interface::MoveGroupInterface &move_group, const moveit_msgs::srv::GetCartesianPath::Request &path_request)
Gets cartesian path request frame ID. If path_request has no frame ID, fetch it from move_group.
moveit_msgs::msg::MotionPlanRequest MotionPlanRequest