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
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
34#include "../fixtures/move_group_fixture.hpp"
35
36namespace
37{
38
39using ::warehouse_ros::MessageCollection;
40using ::warehouse_ros::MessageWithMetadata;
41using ::warehouse_ros::Metadata;
42using ::warehouse_ros::Query;
43
44using ::moveit::core::MoveItErrorCode;
45using ::moveit::planning_interface::MoveGroupInterface;
46
47using ::moveit_msgs::msg::MotionPlanRequest;
48using ::moveit_msgs::msg::RobotTrajectory;
49using ::moveit_msgs::srv::GetCartesianPath;
50
51using ::moveit_ros::trajectory_cache::BestSeenExecutionTimePolicy;
52using ::moveit_ros::trajectory_cache::CartesianBestSeenExecutionTimePolicy;
53using ::moveit_ros::trajectory_cache::constructGetCartesianPathRequest;
54using ::moveit_ros::trajectory_cache::FeaturesInterface;
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.
140 MoveGroupInterface::Plan plan;
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.
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.
moveit_msgs::msg::MotionPlanRequest MotionPlanRequest
robot_trajectory::RobotTrajectoryPtr trajectory
TEST_F(BulletCollisionDetectionTester, DISABLED_ContinuousCollisionSelf)
Continuous self collision checks are not supported yet by the bullet integration.