moveit2
The MoveIt Motion Planning Framework for ROS 2.
Loading...
Searching...
No Matches
test_best_seen_execution_time_policy_with_move_group.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
18
19#include <memory>
20#include <utility>
21
22#include <gtest/gtest.h>
23#include <warehouse_ros/message_collection.h>
24
26#include <moveit_msgs/msg/motion_plan_request.hpp>
27#include <moveit_msgs/msg/robot_trajectory.hpp>
28#include <moveit_msgs/srv/get_cartesian_path.hpp>
29
33
35
36namespace
37{
38
39using ::warehouse_ros::MessageCollection;
40using ::warehouse_ros::MessageWithMetadata;
41using ::warehouse_ros::Metadata;
42using ::warehouse_ros::Query;
43
46
47using ::moveit_msgs::msg::MotionPlanRequest;
48using ::moveit_msgs::msg::RobotTrajectory;
49using ::moveit_msgs::srv::GetCartesianPath;
50
55
56// =================================================================================================
57// BestSeenExecutionTimePolicy.
58// =================================================================================================
59
60TEST_F(MoveGroupFixture, BestSeenExecutionTimePolicy)
61{
62 // Setup.
63 BestSeenExecutionTimePolicy policy;
64
65 MessageCollection<RobotTrajectory> coll = db_->openCollection<RobotTrajectory>("test_db", policy.getName());
66
67 MotionPlanRequest valid_msg;
68 MoveGroupInterface::Plan valid_plan;
69
70 // Valid case, as control.
71 {
72 valid_msg.workspace_parameters.header.frame_id = "panda_link0";
73 valid_msg.goal_constraints.emplace_back();
74
75 valid_plan.trajectory.joint_trajectory.header.frame_id = "panda_link0";
76 valid_plan.trajectory.joint_trajectory.joint_names.emplace_back();
77 valid_plan.trajectory.joint_trajectory.points.emplace_back();
78
79 MoveItErrorCode ret = policy.checkCacheInsertInputs(*move_group_, coll, valid_msg, valid_plan);
80 ASSERT_TRUE(ret) << ret.message;
81 }
82
83 // We can't test workspace ID frame empty.
84 // But it technically should be unreachable as long as the robot description is correct.
85
86 // No goal.
87 {
88 MotionPlanRequest msg = valid_msg;
89 msg.goal_constraints.clear();
90 EXPECT_FALSE(policy.checkCacheInsertInputs(*move_group_, coll, msg, valid_plan));
91 }
92
93 // Empty joint trajectory points.
94 {
95 MoveGroupInterface::Plan plan = valid_plan;
96 plan.trajectory.joint_trajectory.points.clear();
97 EXPECT_FALSE(policy.checkCacheInsertInputs(*move_group_, coll, valid_msg, plan));
98 }
99
100 // Empty joint trajectory names.
101 {
102 MoveGroupInterface::Plan plan = valid_plan;
103 plan.trajectory.joint_trajectory.joint_names.clear();
104 EXPECT_FALSE(policy.checkCacheInsertInputs(*move_group_, coll, valid_msg, plan));
105 }
106
107 // Multi-DOF trajectory plan.
108 {
109 MoveGroupInterface::Plan plan = valid_plan;
110 plan.trajectory.multi_dof_joint_trajectory.points.emplace_back();
111 EXPECT_FALSE(policy.checkCacheInsertInputs(*move_group_, coll, valid_msg, plan));
112 }
113
114 // Trajectory frame ID empty.
115 {
116 MoveGroupInterface::Plan plan = valid_plan;
117 plan.trajectory.joint_trajectory.header.frame_id = "";
118 EXPECT_FALSE(policy.checkCacheInsertInputs(*move_group_, coll, valid_msg, plan));
119 }
120
121 // Mismatched frames.
122 {
123 MotionPlanRequest msg = valid_msg;
124 MoveGroupInterface::Plan plan = valid_plan;
125 msg.workspace_parameters.header.frame_id = "panda_link0";
126 plan.trajectory.joint_trajectory.header.frame_id = "clearly_a_different_frame";
127 EXPECT_FALSE(policy.checkCacheInsertInputs(*move_group_, coll, msg, plan));
128 }
129}
130
131TEST_F(MoveGroupFixture, BestSeenExecutionTimePolicyWorks)
132{
133 BestSeenExecutionTimePolicy policy;
134
135 MessageCollection<RobotTrajectory> coll = db_->openCollection<RobotTrajectory>("test_db", policy.getName());
136 ASSERT_EQ(coll.count(), 0);
137
138 // Setup. Get valid entries to insert.
141 MoveItErrorCode ret = MoveItErrorCode::FAILURE;
142
143 MotionPlanRequest another_msg;
144 MoveGroupInterface::Plan another_plan;
145 MoveItErrorCode another_ret = MoveItErrorCode::FAILURE;
146
147 do
148 {
149 ASSERT_TRUE(move_group_->setPoseTarget(move_group_->getRandomPose()));
150 move_group_->constructMotionPlanRequest(msg);
151 ret = move_group_->plan(plan);
152 } while (!ret || plan.trajectory.joint_trajectory.points.empty()); // Sometimes the plan fails with the random pose.
153
154 do
155 {
156 ASSERT_TRUE(move_group_->setPoseTarget(move_group_->getRandomPose()));
157 move_group_->constructMotionPlanRequest(another_msg);
158 another_ret = move_group_->plan(another_plan);
159 } while (another_msg == msg || !another_ret ||
160 another_plan.trajectory.joint_trajectory.points
161 .empty()); // Also, sometimes the random pose happens to be the same.
162
163 // Ensure that the entries are valid.
164 {
165 MoveItErrorCode ret = policy.checkCacheInsertInputs(*move_group_, coll, msg, plan);
166 ASSERT_TRUE(ret) << ret.message;
167 }
168 {
169 MoveItErrorCode ret = policy.checkCacheInsertInputs(*move_group_, coll, another_msg, another_plan);
170 ASSERT_TRUE(ret) << ret.message;
171 }
172
173 // Core test. ====================================================================================
174 // NOTE: Be mindful that the policy is stateful.
175
176 // Set the execution times for the plans to be between the shorter and longer plan execution times
177 // later.
178 plan.trajectory.joint_trajectory.points.back().time_from_start.sec = 10;
179 plan.trajectory.joint_trajectory.points.back().time_from_start.nanosec = 0;
180 another_plan.trajectory.joint_trajectory.points.back().time_from_start.sec = 10;
181 another_plan.trajectory.joint_trajectory.points.back().time_from_start.nanosec = 0;
182
183 // Insert messages and check if policy-specific additional metadata are added.
184 size_t count = 0;
185 for (const auto& msg_plan_pair : { std::make_pair(msg, plan), std::make_pair(another_msg, another_plan) })
186 {
187 Metadata::Ptr metadata = coll.createMetadata();
188 EXPECT_TRUE(policy.appendInsertMetadata(*metadata, *move_group_, msg_plan_pair.first, msg_plan_pair.second));
189 EXPECT_TRUE(metadata->lookupField("execution_time_s"));
190 EXPECT_TRUE(metadata->lookupField("planning_time_s"));
191
192 // We add two to test the prune predicate, as appropriate.
193 coll.insert(msg_plan_pair.second.trajectory, metadata);
194 coll.insert(msg_plan_pair.second.trajectory, metadata);
195 count += 2;
196 ASSERT_EQ(coll.count(), count);
197 }
198
199 // Fetch with features from getSupportedFeatures and fetchMatchingEntries.
200 // In this case the results should also match with fetchMatchingEntries.
201 //
202 // We also test the predicates here.
203 std::vector<std::unique_ptr<FeaturesInterface<MotionPlanRequest>>> features =
204 BestSeenExecutionTimePolicy::getSupportedFeatures(/*start_tolerance=*/0.025, /*goal_tolerance=*/0.001);
205
206 for (const auto& msg_plan_pair : { std::make_pair(msg, plan), std::make_pair(another_msg, another_plan) })
207 {
208 Query::Ptr query = coll.createQuery();
209 for (const auto& feature : features)
210 {
211 ASSERT_TRUE(feature->appendFeaturesAsExactFetchQuery(*query, msg_plan_pair.first, *move_group_,
212 /*exact_match_precision=*/0.0001));
213 }
214
215 std::vector<MessageWithMetadata<RobotTrajectory>::ConstPtr> feature_fetch =
216 coll.queryList(query, /*metadata_only=*/true);
217 std::vector<MessageWithMetadata<RobotTrajectory>::ConstPtr> policy_fetch = policy.fetchMatchingEntries(
218 *move_group_, coll, msg_plan_pair.first, msg_plan_pair.second, /*exact_match_precision=*/0.0001);
219
220 ASSERT_EQ(feature_fetch.size(), 2);
221 ASSERT_EQ(policy_fetch.size(), 2);
222 for (size_t i = 0; i < feature_fetch.size(); ++i)
223 {
224 ASSERT_EQ(*feature_fetch[i], *policy_fetch[i]);
225
226 // This guarantees that the longer plan will have a larger time_from_start than the shorter plan.
227 auto longer_plan = msg_plan_pair.second;
228 longer_plan.trajectory.joint_trajectory.points.back().time_from_start.sec = 100;
229 longer_plan.trajectory.joint_trajectory.points.back().time_from_start.nanosec = 0;
230
231 auto shorter_plan = msg_plan_pair.second;
232 shorter_plan.trajectory.joint_trajectory.points.back().time_from_start.sec = 0;
233 shorter_plan.trajectory.joint_trajectory.points.back().time_from_start.nanosec = 100;
234
235 // Should prune matched plan if execution time is longer than candidate.
236 EXPECT_FALSE(policy.shouldPruneMatchingEntry(*move_group_, msg_plan_pair.first, longer_plan, policy_fetch[i]));
237 EXPECT_TRUE(policy.shouldPruneMatchingEntry(*move_group_, msg_plan_pair.first, shorter_plan, policy_fetch[i]));
238
239 // Should insert candidate plan if execution time is best seen.
240 EXPECT_FALSE(policy.shouldInsert(*move_group_, msg_plan_pair.first, longer_plan));
241 EXPECT_TRUE(policy.shouldInsert(*move_group_, msg_plan_pair.first, shorter_plan));
242 }
243
244 policy.reset();
245 }
246}
247
248// =================================================================================================
249// CartesianBestSeenExecutionTimePolicy.
250// =================================================================================================
251
252TEST_F(MoveGroupFixture, CartesianBestSeenExecutionTimePolicyChecks)
253{
254 // Setup.
255 CartesianBestSeenExecutionTimePolicy policy;
256
257 MessageCollection<RobotTrajectory> coll = db_->openCollection<RobotTrajectory>("test_db", policy.getName());
258
259 GetCartesianPath::Request valid_msg;
260 GetCartesianPath::Response valid_plan;
261 valid_plan.fraction = -1;
262
263 do
264 {
265 std::vector<geometry_msgs::msg::Pose> waypoints;
266 waypoints.push_back(move_group_->getCurrentPose().pose);
267 waypoints.push_back(move_group_->getRandomPose().pose);
268
269 valid_msg = constructGetCartesianPathRequest(*move_group_, waypoints, /*max_step=*/0.01, /*jump_threshold=*/0.0);
270// TODO: Swap this over to the new computeCartesianPath API.
271#pragma GCC diagnostic push
272#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
273 valid_plan.fraction = move_group_->computeCartesianPath(valid_msg.waypoints, valid_msg.max_step,
274 valid_msg.jump_threshold, valid_plan.solution);
275#pragma GCC diagnostic pop
276 } while (valid_plan.fraction <= 0 &&
277 valid_plan.solution.joint_trajectory.points.size() < 2); // Sometimes the plan fails with the random pose.
278
279 // Valid case, as control.
280 {
281 MoveItErrorCode ret = policy.checkCacheInsertInputs(*move_group_, coll, valid_msg, valid_plan);
282 ASSERT_TRUE(ret) << ret.message;
283 }
284
285 // We can't test workspace ID frame empty.
286 // But it technically should be unreachable as long as the robot description is correct.
287
288 // No waypoints.
289 {
290 GetCartesianPath::Request msg = valid_msg;
291 msg.waypoints.clear();
292 EXPECT_FALSE(policy.checkCacheInsertInputs(*move_group_, coll, msg, valid_plan));
293 }
294
295 // Empty joint trajectory points.
296 {
297 GetCartesianPath::Response plan = valid_plan;
298 plan.solution.joint_trajectory.points.clear();
299 EXPECT_FALSE(policy.checkCacheInsertInputs(*move_group_, coll, valid_msg, plan));
300 }
301
302 // Empty joint trajectory names.
303 {
304 GetCartesianPath::Response plan = valid_plan;
305 plan.solution.joint_trajectory.joint_names.clear();
306 EXPECT_FALSE(policy.checkCacheInsertInputs(*move_group_, coll, valid_msg, plan));
307 }
308
309 // Multi-DOF trajectory plan.
310 {
311 GetCartesianPath::Response plan = valid_plan;
312 plan.solution.multi_dof_joint_trajectory.points.emplace_back();
313 EXPECT_FALSE(policy.checkCacheInsertInputs(*move_group_, coll, valid_msg, plan));
314 }
315
316 // Trajectory frame ID empty.
317 {
318 GetCartesianPath::Response plan = valid_plan;
319 plan.solution.joint_trajectory.header.frame_id = "";
320 EXPECT_FALSE(policy.checkCacheInsertInputs(*move_group_, coll, valid_msg, plan));
321 }
322
323 // Mismatched frames.
324 {
325 GetCartesianPath::Request msg = valid_msg;
326 GetCartesianPath::Response plan = valid_plan;
327 msg.header.frame_id = "panda_link0";
328 plan.solution.joint_trajectory.header.frame_id = "clearly_a_different_frame";
329 EXPECT_FALSE(policy.checkCacheInsertInputs(*move_group_, coll, msg, plan));
330 }
331}
332
333TEST_F(MoveGroupFixture, CartesianBestSeenExecutionTimePolicyWorks)
334{
335 CartesianBestSeenExecutionTimePolicy policy;
336
337 MessageCollection<RobotTrajectory> coll = db_->openCollection<RobotTrajectory>("test_db", policy.getName());
338 ASSERT_EQ(coll.count(), 0);
339
340 // Setup. Get valid entries to insert.
341 GetCartesianPath::Request msg;
342 GetCartesianPath::Response plan;
343 plan.fraction = -1;
344
345 GetCartesianPath::Request another_msg;
346 GetCartesianPath::Response another_plan;
347 another_plan.fraction = -1;
348
349 do
350 {
351 std::vector<geometry_msgs::msg::Pose> waypoints;
352 waypoints.push_back(move_group_->getCurrentPose().pose);
353 waypoints.push_back(move_group_->getRandomPose().pose);
354
355 msg = constructGetCartesianPathRequest(*move_group_, waypoints, /*max_step=*/0.01, /*jump_threshold=*/0.0);
356// TODO: Swap this over to the new computeCartesianPath API.
357#pragma GCC diagnostic push
358#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
359 plan.fraction = move_group_->computeCartesianPath(msg.waypoints, msg.max_step, msg.jump_threshold, plan.solution);
360#pragma GCC diagnostic pop
361 } while (plan.fraction <= 0 &&
362 plan.solution.joint_trajectory.points.size() < 2); // Sometimes the plan fails with the random pose.
363
364 do
365 {
366 std::vector<geometry_msgs::msg::Pose> waypoints;
367 waypoints.push_back(move_group_->getCurrentPose().pose);
368 waypoints.push_back(move_group_->getRandomPose().pose);
369
370 another_msg = constructGetCartesianPathRequest(*move_group_, waypoints, /*max_step=*/0.01, /*jump_threshold=*/0.0);
371// TODO: Swap this over to the new computeCartesianPath API.
372#pragma GCC diagnostic push
373#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
374 another_plan.fraction = move_group_->computeCartesianPath(another_msg.waypoints, another_msg.max_step,
375 another_msg.jump_threshold, another_plan.solution);
376#pragma GCC diagnostic pop
377 } while (another_plan.fraction <= 0 &&
378 another_plan.solution.joint_trajectory.points.size() < 2); // Sometimes the plan fails with the random pose.
379
380 // Ensure that the entries are valid.
381 {
382 MoveItErrorCode ret = policy.checkCacheInsertInputs(*move_group_, coll, msg, plan);
383 ASSERT_TRUE(ret) << ret.message;
384 }
385 {
386 MoveItErrorCode ret = policy.checkCacheInsertInputs(*move_group_, coll, another_msg, another_plan);
387 ASSERT_TRUE(ret) << ret.message;
388 }
389
390 // Core test. ====================================================================================
391 // NOTE: Be mindful that the policy is stateful.
392
393 // Set the execution times for the plans to be between the shorter and longer plan execution times
394 // later.
395 plan.solution.joint_trajectory.points.back().time_from_start.sec = 10;
396 plan.solution.joint_trajectory.points.back().time_from_start.nanosec = 0;
397 another_plan.solution.joint_trajectory.points.back().time_from_start.sec = 10;
398 another_plan.solution.joint_trajectory.points.back().time_from_start.nanosec = 0;
399
400 // Insert messages and check if policy-specific additional metadata are added.
401 size_t count = 0;
402 for (const auto& msg_plan_pair : { std::make_pair(msg, plan), std::make_pair(another_msg, another_plan) })
403 {
404 Metadata::Ptr metadata = coll.createMetadata();
405 EXPECT_TRUE(policy.appendInsertMetadata(*metadata, *move_group_, msg_plan_pair.first, msg_plan_pair.second));
406 EXPECT_TRUE(metadata->lookupField("execution_time_s"));
407 EXPECT_TRUE(metadata->lookupField("fraction"));
408
409 // We add two to test the prune predicate, as appropriate.
410 coll.insert(msg_plan_pair.second.solution, metadata);
411 coll.insert(msg_plan_pair.second.solution, metadata);
412 count += 2;
413 ASSERT_EQ(coll.count(), count);
414 }
415
416 // Fetch with features from getSupportedFeatures and fetchMatchingEntries.
417 // In this case the results should also match with fetchMatchingEntries.
418 //
419 // We also test the predicates here.
420 std::vector<std::unique_ptr<FeaturesInterface<GetCartesianPath::Request>>> features =
421 CartesianBestSeenExecutionTimePolicy::getSupportedFeatures(/*start_tolerance=*/0.025,
422 /*goal_tolerance=*/0.001,
423 /*min_fraction=*/0.0);
424
425 for (const auto& msg_plan_pair : { std::make_pair(msg, plan), std::make_pair(another_msg, another_plan) })
426 {
427 Query::Ptr query = coll.createQuery();
428 for (const auto& feature : features)
429 {
430 ASSERT_TRUE(feature->appendFeaturesAsExactFetchQuery(*query, msg_plan_pair.first, *move_group_,
431 /*exact_match_precision=*/0.0001));
432 }
433
434 std::vector<MessageWithMetadata<RobotTrajectory>::ConstPtr> feature_fetch =
435 coll.queryList(query, /*metadata_only=*/true);
436 std::vector<MessageWithMetadata<RobotTrajectory>::ConstPtr> policy_fetch = policy.fetchMatchingEntries(
437 *move_group_, coll, msg_plan_pair.first, msg_plan_pair.second, /*exact_match_precision=*/0.0001);
438
439 ASSERT_EQ(feature_fetch.size(), 2);
440 ASSERT_EQ(policy_fetch.size(), 2);
441 for (size_t i = 0; i < feature_fetch.size(); ++i)
442 {
443 ASSERT_EQ(*feature_fetch[i], *policy_fetch[i]);
444
445 // This guarantees that the longer plan will have a larger time_from_start than the shorter plan.
446 auto longer_plan = msg_plan_pair.second;
447 longer_plan.solution.joint_trajectory.points.back().time_from_start.sec = 100;
448 longer_plan.solution.joint_trajectory.points.back().time_from_start.nanosec = 0;
449
450 auto shorter_plan = msg_plan_pair.second;
451 shorter_plan.solution.joint_trajectory.points.back().time_from_start.sec = 0;
452 shorter_plan.solution.joint_trajectory.points.back().time_from_start.nanosec = 100;
453
454 // Should prune matched plan if execution time is longer than candidate.
455 std::string longer_prune_reason;
456 std::string shorter_prune_reason;
457
458 EXPECT_FALSE(policy.shouldPruneMatchingEntry(*move_group_, msg_plan_pair.first, longer_plan, policy_fetch[i],
459 &longer_prune_reason));
460 EXPECT_TRUE(policy.shouldPruneMatchingEntry(*move_group_, msg_plan_pair.first, shorter_plan, policy_fetch[i],
461 &shorter_prune_reason));
462
463 EXPECT_FALSE(longer_prune_reason.empty());
464 EXPECT_FALSE(shorter_prune_reason.empty());
465
466 // Should insert candidate plan if execution time is best seen.
467 std::string longer_insert_reason;
468 std::string shorter_insert_reason;
469
470 EXPECT_FALSE(policy.shouldInsert(*move_group_, msg_plan_pair.first, longer_plan, &longer_insert_reason));
471 EXPECT_TRUE(policy.shouldInsert(*move_group_, msg_plan_pair.first, shorter_plan, &shorter_insert_reason));
472
473 EXPECT_FALSE(longer_insert_reason.empty());
474 EXPECT_FALSE(shorter_insert_reason.empty());
475 }
476
477 policy.reset();
478 }
479}
480
481} // namespace
482
483int main(int argc, char** argv)
484{
485 rclcpp::init(argc, argv);
486 ::testing::InitGoogleTest(&argc, argv);
487 int result = RUN_ALL_TESTS();
488 rclcpp::shutdown();
489 return result;
490}
A cache insertion policy that only decides to insert if the motion plan is the one with the shortest ...
Test fixture to spin up a node to start a move group with.
a wrapper around moveit_msgs::MoveItErrorCodes to make it easier to return an error code message from...
Client class to conveniently use the ROS interfaces provided by the move_group node.
A cache insertion policy that only decides to insert if the motion plan is the one with the shortest ...
A cache insertion policy that only decides to insert if the motion plan is the one with the shortest ...
Abstract template class for extracting features from some FeatureSourceT.
Utilities used by the trajectory_cache package.
planning_interface::MotionPlanResponse plan(std::shared_ptr< moveit_cpp::PlanningComponent > &planning_component, std::shared_ptr< moveit_cpp::PlanningComponent::PlanRequestParameters > &single_plan_parameters, std::shared_ptr< moveit_cpp::PlanningComponent::MultiPipelinePlanRequestParameters > &multi_plan_parameters, std::shared_ptr< planning_scene::PlanningScene > &planning_scene, std::optional< const moveit::planning_pipeline_interfaces::SolutionSelectionFunction > solution_selection_function, std::optional< moveit::planning_pipeline_interfaces::StoppingCriterionFunction > stopping_criterion_callback)
moveit_msgs::srv::GetCartesianPath::Request constructGetCartesianPathRequest(moveit::planning_interface::MoveGroupInterface &move_group, const std::vector< geometry_msgs::msg::Pose > &waypoints, double max_step, double jump_threshold, bool avoid_collisions=true)
Constructs a GetCartesianPath request.
Definition utils.cpp:161
moveit_msgs::msg::MotionPlanRequest MotionPlanRequest
The representation of a motion plan (as ROS messages).
moveit_msgs::msg::RobotTrajectory trajectory
The trajectory of the robot (may not contain joints that are the same as for the start_state_).
robot_trajectory::RobotTrajectoryPtr trajectory
TEST_F(BulletCollisionDetectionTester, DISABLED_ContinuousCollisionSelf)
Continuous self collision checks are not supported yet by the bullet integration.
moveit_msgs::srv::GetCartesianPath::Request constructGetCartesianPathRequest(moveit::planning_interface::MoveGroupInterface &move_group, const std::vector< geometry_msgs::msg::Pose > &waypoints, double max_step, double jump_threshold, bool avoid_collisions=true)
Constructs a GetCartesianPath request.
Definition utils.cpp:161