moveit2
The MoveIt Motion Planning Framework for ROS 2.
Loading...
Searching...
No Matches
test_trajectory_cache.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 <rclcpp/executors/single_threaded_executor.hpp>
20#include <rclcpp/rclcpp.hpp>
21
26
27#include <atomic>
28#include <thread>
29
33
34const std::string ROBOT_NAME = "panda";
35const std::string ROBOT_FRAME = "world";
36
37// UTILS =======================================================================
38// Utility function to emit a pass or fail statement.
39void checkAndEmit(bool predicate, const std::string& test_case, const std::string& label)
40{
41 if (predicate)
42 {
43 std::cout << "[PASS] " << test_case << ": " << label << "\n";
44 }
45 else
46 {
47 std::cout << "[FAIL] " << test_case << ": " << label << "\n";
48 }
49}
50
51moveit_msgs::msg::RobotTrajectory getDummyPandaTraj()
52{
53 static moveit_msgs::msg::RobotTrajectory out = []() {
54 moveit_msgs::msg::RobotTrajectory traj;
55
56 auto trajectory = &traj.joint_trajectory;
57 trajectory->header.frame_id = ROBOT_FRAME;
58
59 trajectory->joint_names.push_back(ROBOT_NAME + "_joint1");
60 trajectory->joint_names.push_back(ROBOT_NAME + "_joint2");
61 trajectory->joint_names.push_back(ROBOT_NAME + "_joint3");
62 trajectory->joint_names.push_back(ROBOT_NAME + "_joint4");
63 trajectory->joint_names.push_back(ROBOT_NAME + "_joint5");
64 trajectory->joint_names.push_back(ROBOT_NAME + "_joint6");
65 trajectory->joint_names.push_back(ROBOT_NAME + "_joint7");
66
67 trajectory->points.emplace_back();
68 trajectory->points.at(0).positions = { 0, 0, 0, 0, 0, 0 };
69 trajectory->points.at(0).velocities = { 0, 0, 0, 0, 0, 0 };
70 trajectory->points.at(0).accelerations = { 0, 0, 0, 0, 0, 0 };
71 trajectory->points.at(0).time_from_start.sec = 999999;
72
73 return traj;
74 }();
75 return out;
76}
77
78std::vector<geometry_msgs::msg::Pose> getDummyWaypoints()
79{
80 static std::vector<geometry_msgs::msg::Pose> out = []() {
81 std::vector<geometry_msgs::msg::Pose> waypoints;
82 for (size_t i = 0; i < 3; i++)
83 {
84 waypoints.emplace_back();
85 waypoints.at(i).position.x = i;
86 waypoints.at(i).position.y = i;
87 waypoints.at(i).position.z = i;
88 waypoints.at(i).orientation.w = i;
89 waypoints.at(i).orientation.x = i + 0.1;
90 waypoints.at(i).orientation.y = i + 0.1;
91 waypoints.at(i).orientation.z = i + 0.1;
92 }
93 return waypoints;
94 }();
95 return out;
96}
97
98void testGettersAndSetters(const std::shared_ptr<TrajectoryCache>& cache)
99{
100 std::string test_case = "getters_and_setters";
101
102 checkAndEmit(cache->getDbPath() == ":memory:", test_case, "getDbPath");
103 checkAndEmit(cache->getDbPort() == 0, test_case, "getDbPort");
104
105 checkAndEmit(cache->getExactMatchPrecision() == 10, test_case, "getExactMatchPrecision");
106 cache->setExactMatchPrecision(1);
107 checkAndEmit(cache->getExactMatchPrecision() == 1, test_case, "getExactMatchPrecisionAfterSet");
108
109 checkAndEmit(cache->getNumAdditionalTrajectoriesToPreserveWhenPruningWorse() == 10, test_case,
110 "getNumAdditionalTrajectoriesToPreserveWhenPruningWorse");
111 cache->setNumAdditionalTrajectoriesToPreserveWhenPruningWorse(1);
112 checkAndEmit(cache->getNumAdditionalTrajectoriesToPreserveWhenPruningWorse() == 1, test_case,
113 "getNumAdditionalTrajectoriesToPreserveWhenPruningWorseAfterSet");
114}
115
116void testMotionTrajectories(const std::shared_ptr<MoveGroupInterface>& move_group,
117 const std::shared_ptr<TrajectoryCache>& cache)
118{
119 // Setup =====================================================================
120 // All variants are modified copies of `plan_req`.
121
123
124 // Plain start
125 moveit_msgs::msg::MotionPlanRequest plan_req;
126 move_group->constructMotionPlanRequest(plan_req);
127 plan_req.workspace_parameters.header.frame_id = ROBOT_FRAME;
128 plan_req.workspace_parameters.max_corner.x = 10;
129 plan_req.workspace_parameters.max_corner.y = 10;
130 plan_req.workspace_parameters.max_corner.z = 10;
131 plan_req.workspace_parameters.min_corner.x = -10;
132 plan_req.workspace_parameters.min_corner.y = -10;
133 plan_req.workspace_parameters.min_corner.z = -10;
134 plan_req.start_state.multi_dof_joint_state.joint_names.clear();
135 plan_req.start_state.multi_dof_joint_state.transforms.clear();
136 plan_req.start_state.multi_dof_joint_state.twist.clear();
137 plan_req.start_state.multi_dof_joint_state.wrench.clear();
138
139 // Empty frame start
140 moveit_msgs::msg::MotionPlanRequest empty_frame_plan_req = plan_req;
141 empty_frame_plan_req.workspace_parameters.header.frame_id = "";
142
143 // is_diff = true
144 auto is_diff_plan_req = plan_req;
145 is_diff_plan_req.start_state.is_diff = true;
146 is_diff_plan_req.start_state.joint_state.header.frame_id = "";
147 is_diff_plan_req.start_state.joint_state.name.clear();
148 is_diff_plan_req.start_state.joint_state.position.clear();
149 is_diff_plan_req.start_state.joint_state.velocity.clear();
150 is_diff_plan_req.start_state.joint_state.effort.clear();
151
152 // Something close enough (mod 0.1 away)
153 auto close_matching_plan_req = plan_req;
154 close_matching_plan_req.workspace_parameters.max_corner.x += 0.05;
155 close_matching_plan_req.workspace_parameters.min_corner.x -= 0.05;
156 close_matching_plan_req.start_state.joint_state.position.at(0) -= 0.05;
157 close_matching_plan_req.start_state.joint_state.position.at(1) += 0.05;
158 close_matching_plan_req.start_state.joint_state.position.at(2) -= 0.05;
159 close_matching_plan_req.goal_constraints.at(0).joint_constraints.at(0).position -= 0.05;
160 close_matching_plan_req.goal_constraints.at(0).joint_constraints.at(1).position += 0.05;
161 close_matching_plan_req.goal_constraints.at(0).joint_constraints.at(2).position -= 0.05;
162
163 // Close with swapped constraints (mod 0.1 away)
164 auto swapped_close_matching_plan_req = close_matching_plan_req;
165 std::swap(swapped_close_matching_plan_req.goal_constraints.at(0).joint_constraints.at(0),
166 swapped_close_matching_plan_req.goal_constraints.at(0).joint_constraints.at(1));
167
168 // Smaller workspace start
169 auto smaller_workspace_plan_req = plan_req;
170 smaller_workspace_plan_req.workspace_parameters.max_corner.x = 1;
171 smaller_workspace_plan_req.workspace_parameters.max_corner.y = 1;
172 smaller_workspace_plan_req.workspace_parameters.max_corner.z = 1;
173 smaller_workspace_plan_req.workspace_parameters.min_corner.x = -1;
174 smaller_workspace_plan_req.workspace_parameters.min_corner.y = -1;
175 smaller_workspace_plan_req.workspace_parameters.min_corner.z = -1;
176
177 // Larger workspace start
178 auto larger_workspace_plan_req = plan_req;
179 larger_workspace_plan_req.workspace_parameters.max_corner.x = 50;
180 larger_workspace_plan_req.workspace_parameters.max_corner.y = 50;
181 larger_workspace_plan_req.workspace_parameters.max_corner.z = 50;
182 larger_workspace_plan_req.workspace_parameters.min_corner.x = -50;
183 larger_workspace_plan_req.workspace_parameters.min_corner.y = -50;
184 larger_workspace_plan_req.workspace_parameters.min_corner.z = -50;
185
186 // Different
187 auto different_plan_req = plan_req;
188 different_plan_req.workspace_parameters.max_corner.x += 1.05;
189 different_plan_req.workspace_parameters.min_corner.x -= 2.05;
190 different_plan_req.start_state.joint_state.position.at(0) -= 3.05;
191 different_plan_req.start_state.joint_state.position.at(1) += 4.05;
192 different_plan_req.start_state.joint_state.position.at(2) -= 5.05;
193 different_plan_req.goal_constraints.at(0).joint_constraints.at(0).position -= 6.05;
194 different_plan_req.goal_constraints.at(0).joint_constraints.at(1).position += 7.05;
195 different_plan_req.goal_constraints.at(0).joint_constraints.at(2).position -= 8.05;
196
198
199 // Trajectory
200 auto traj = getDummyPandaTraj();
201
202 // Trajectory with no frame_id in its trajectory header
203 auto empty_frame_traj = traj;
204 empty_frame_traj.joint_trajectory.header.frame_id = "";
205
206 auto different_traj = traj;
207 different_traj.joint_trajectory.points.at(0).positions.at(0) = 999;
208 different_traj.joint_trajectory.points.at(0).positions.at(1) = 999;
209 different_traj.joint_trajectory.points.at(0).positions.at(2) = 999;
210
211 // Test Utils
212
213 std::string test_case;
214
215 // Checks ====================================================================
216
217 // Initially empty
218 test_case = "testMotionTrajectories.empty";
219
220 checkAndEmit(cache->countTrajectories(ROBOT_NAME) == 0, test_case, "Trajectory cache initially empty");
221
222 checkAndEmit(cache->fetchAllMatchingTrajectories(*move_group, ROBOT_NAME, plan_req, 999, 999).empty(), test_case,
223 "Fetch all trajectories on empty cache returns empty");
224
225 checkAndEmit(cache->fetchBestMatchingTrajectory(*move_group, ROBOT_NAME, plan_req, 999, 999) == nullptr, test_case,
226 "Fetch best trajectory on empty cache returns nullptr");
227
228 // Put trajectory empty frame
229 //
230 // Trajectory must have frame in joint trajectory, expect put fail
231 test_case = "testMotionTrajectories.insertTrajectory_empty_frame";
232 double execution_time = 999;
233 double planning_time = 999;
234
235 checkAndEmit(!cache->insertTrajectory(*move_group, ROBOT_NAME, plan_req, empty_frame_traj, execution_time,
236 planning_time, false),
237 test_case, "Put empty frame trajectory, no prune_worse_trajectories, not ok");
238
239 checkAndEmit(cache->countTrajectories(ROBOT_NAME) == 0, test_case, "No trajectories in cache");
240
241 // Put trajectory req empty frame
242 //
243 // Trajectory request having no frame in workspace should default to robot frame
244 test_case = "testMotionTrajectories.insertTrajectory_req_empty_frame";
245 execution_time = 1000;
246 planning_time = 1000;
247
248 checkAndEmit(cache->insertTrajectory(*move_group, ROBOT_NAME, empty_frame_plan_req, traj, execution_time,
249 planning_time, false),
250 test_case, "Put empty frame req trajectory, no prune_worse_trajectories, ok");
251
252 checkAndEmit(cache->countTrajectories(ROBOT_NAME) == 1, test_case, "One trajectory in cache");
253
254 // Put second, no prune_worse_trajectories
255 test_case = "testMotionTrajectories.put_second";
256 execution_time = 999;
257 planning_time = 999;
258
259 checkAndEmit(cache->insertTrajectory(*move_group, ROBOT_NAME, plan_req, traj, execution_time, planning_time, false),
260 test_case, "Put second valid trajectory, no prune_worse_trajectories, ok");
261
262 checkAndEmit(cache->countTrajectories(ROBOT_NAME) == 2, test_case, "Two trajectories in cache");
263
264 // Fetch matching, no tolerance
265 //
266 // Exact key match should have cache hit
267 test_case = "testMotionTrajectories.put_second.fetch_matching_no_tolerance";
268
269 auto fetched_trajectories = cache->fetchAllMatchingTrajectories(*move_group, ROBOT_NAME, plan_req, 0, 0);
270
271 auto fetched_traj = cache->fetchBestMatchingTrajectory(*move_group, ROBOT_NAME, plan_req, 0, 0);
272
273 checkAndEmit(fetched_trajectories.size() == 2, test_case, "Fetch all returns two");
274 checkAndEmit(fetched_traj != nullptr, test_case, "Fetch best trajectory is not nullptr");
275
276 checkAndEmit(*fetched_trajectories.at(0) == *fetched_traj, test_case, "Fetched trajectory on both fetches match");
277
278 checkAndEmit(*fetched_traj == traj, test_case, "Fetched trajectory matches original");
279
280 checkAndEmit(fetched_traj->lookupDouble("execution_time_s") == execution_time, test_case,
281 "Fetched trajectory has correct execution time");
282
283 checkAndEmit(fetched_traj->lookupDouble("planning_time_s") == planning_time, test_case,
284 "Fetched trajectory has correct planning time");
285
286 // Fetch with is_diff
287 //
288 // is_diff key should be equivalent to exact match if robot state did not
289 // change, hence should have cache hit
290 test_case = "testMotionTrajectories.put_second.fetch_is_diff_no_tolerance";
291
292 fetched_trajectories = cache->fetchAllMatchingTrajectories(*move_group, ROBOT_NAME, is_diff_plan_req, 0, 0);
293
294 fetched_traj = cache->fetchBestMatchingTrajectory(*move_group, ROBOT_NAME, is_diff_plan_req, 0, 0);
295
296 checkAndEmit(fetched_trajectories.size() == 2, test_case, "Fetch all returns two");
297 checkAndEmit(fetched_traj != nullptr, test_case, "Fetch best trajectory is not nullptr");
298
299 checkAndEmit(*fetched_trajectories.at(0) == *fetched_traj, test_case, "Fetched trajectory on both fetches match");
300
301 checkAndEmit(*fetched_traj == traj, test_case, "Fetched trajectory matches original");
302
303 checkAndEmit(fetched_traj->lookupDouble("execution_time_s") == execution_time, test_case,
304 "Fetched trajectory has correct execution time");
305
306 checkAndEmit(fetched_traj->lookupDouble("planning_time_s") == planning_time, test_case,
307 "Fetched trajectory has correct planning time");
308
309 // Fetch non-matching, out of tolerance
310 //
311 // Non-matching key should not have cache hit
312 test_case = "testMotionTrajectories.put_second.fetch_non_matching_out_of_tolerance";
313
314 fetched_trajectories = cache->fetchAllMatchingTrajectories(*move_group, ROBOT_NAME, close_matching_plan_req, 0, 0);
315
316 fetched_traj = cache->fetchBestMatchingTrajectory(*move_group, ROBOT_NAME, close_matching_plan_req, 0, 0);
317
318 checkAndEmit(fetched_trajectories.empty(), test_case, "Fetch all returns empty");
319 checkAndEmit(fetched_traj == nullptr, test_case, "Fetch best trajectory is nullptr");
320
321 // Fetch non-matching, only start in tolerance (but not goal)
322 //
323 // Non-matching key should not have cache hit
324 test_case = "testMotionTrajectories.put_second.fetch_non_matching_only_start_in_tolerance";
325
326 fetched_trajectories = cache->fetchAllMatchingTrajectories(*move_group, ROBOT_NAME, close_matching_plan_req, 999, 0);
327
328 fetched_traj = cache->fetchBestMatchingTrajectory(*move_group, ROBOT_NAME, close_matching_plan_req, 999, 0);
329
330 checkAndEmit(fetched_trajectories.empty(), test_case, "Fetch all returns empty");
331 checkAndEmit(fetched_traj == nullptr, test_case, "Fetch best trajectory is nullptr");
332
333 // Fetch non-matching, only goal in tolerance (but not start)
334 //
335 // Non-matching key should not have cache hit
336 test_case = "testMotionTrajectories.put_second.fetch_non_matching_only_goal_in_tolerance";
337
338 fetched_trajectories = cache->fetchAllMatchingTrajectories(*move_group, ROBOT_NAME, close_matching_plan_req, 0, 999);
339
340 fetched_traj = cache->fetchBestMatchingTrajectory(*move_group, ROBOT_NAME, close_matching_plan_req, 0, 999);
341
342 checkAndEmit(fetched_trajectories.empty(), test_case, "Fetch all returns empty");
343 checkAndEmit(fetched_traj == nullptr, test_case, "Fetch best trajectory is nullptr");
344
345 // Fetch non-matching, in tolerance
346 //
347 // Close key within tolerance limit should have cache hit
348 test_case = "testMotionTrajectories.put_second.fetch_non_matching_in_tolerance";
349
350 fetched_trajectories =
351 cache->fetchAllMatchingTrajectories(*move_group, ROBOT_NAME, close_matching_plan_req, 0.1, 0.1);
352
353 fetched_traj = cache->fetchBestMatchingTrajectory(*move_group, ROBOT_NAME, close_matching_plan_req, 0.1, 0.1);
354
355 checkAndEmit(fetched_trajectories.size() == 2, test_case, "Fetch all returns two");
356 checkAndEmit(fetched_traj != nullptr, test_case, "Fetch best trajectory is not nullptr");
357
358 checkAndEmit(*fetched_trajectories.at(0) == *fetched_traj, test_case, "Fetched trajectory on both fetches match");
359
360 checkAndEmit(*fetched_traj == traj, test_case, "Fetched trajectory matches original");
361
362 checkAndEmit(fetched_traj->lookupDouble("execution_time_s") == execution_time, test_case,
363 "Fetched trajectory has correct execution time");
364
365 checkAndEmit(fetched_traj->lookupDouble("planning_time_s") == planning_time, test_case,
366 "Fetched trajectory has correct planning time");
367
368 // Fetch swapped
369 //
370 // Matches should be mostly invariant to constraint ordering
371 test_case = "testMotionTrajectories.put_second.fetch_swapped";
372
373 fetched_trajectories =
374 cache->fetchAllMatchingTrajectories(*move_group, ROBOT_NAME, swapped_close_matching_plan_req, 0.1, 0.1);
375
376 fetched_traj = cache->fetchBestMatchingTrajectory(*move_group, ROBOT_NAME, swapped_close_matching_plan_req, 0.1, 0.1);
377
378 checkAndEmit(fetched_trajectories.size() == 2, test_case, "Fetch all returns two");
379 checkAndEmit(fetched_traj != nullptr, test_case, "Fetch best trajectory is not nullptr");
380
381 checkAndEmit(*fetched_trajectories.at(0) == *fetched_traj, test_case, "Fetched trajectory on both fetches match");
382
383 checkAndEmit(*fetched_traj == traj, test_case, "Fetched trajectory matches original");
384
385 checkAndEmit(fetched_traj->lookupDouble("execution_time_s") == execution_time, test_case,
386 "Fetched trajectory has correct execution time");
387
388 checkAndEmit(fetched_traj->lookupDouble("planning_time_s") == planning_time, test_case,
389 "Fetched trajectory has correct planning time");
390
391 // Fetch with smaller workspace
392 //
393 // Matching trajectories with more restrictive workspace requirements should not
394 // pull up trajectories cached for less restrictive workspace requirements
395 test_case = "testMotionTrajectories.put_second.fetch_smaller_workspace";
396
397 fetched_trajectories =
398 cache->fetchAllMatchingTrajectories(*move_group, ROBOT_NAME, smaller_workspace_plan_req, 999, 999);
399
400 fetched_traj = cache->fetchBestMatchingTrajectory(*move_group, ROBOT_NAME, smaller_workspace_plan_req, 999, 999);
401
402 checkAndEmit(fetched_trajectories.empty(), test_case, "Fetch all returns empty");
403 checkAndEmit(fetched_traj == nullptr, test_case, "Fetch best trajectory is nullptr");
404
405 // Fetch with larger workspace
406 //
407 // Matching trajectories with less restrictive workspace requirements should pull up
408 // trajectories cached for more restrictive workspace requirements
409 test_case = "testMotionTrajectories.put_second.fetch_larger_workspace";
410
411 fetched_trajectories =
412 cache->fetchAllMatchingTrajectories(*move_group, ROBOT_NAME, larger_workspace_plan_req, 999, 999);
413
414 fetched_traj = cache->fetchBestMatchingTrajectory(*move_group, ROBOT_NAME, larger_workspace_plan_req, 999, 999);
415
416 checkAndEmit(fetched_trajectories.size() == 2, test_case, "Fetch all returns two");
417 checkAndEmit(fetched_traj != nullptr, test_case, "Fetch best trajectory is not nullptr");
418
419 checkAndEmit(*fetched_trajectories.at(0) == *fetched_traj, test_case, "Fetched trajectory on both fetches match");
420
421 checkAndEmit(*fetched_traj == traj, test_case, "Fetched trajectory matches original");
422
423 checkAndEmit(fetched_traj->lookupDouble("execution_time_s") == execution_time, test_case,
424 "Fetched trajectory has correct execution time");
425
426 checkAndEmit(fetched_traj->lookupDouble("planning_time_s") == planning_time, test_case,
427 "Fetched trajectory has correct planning time");
428
429 checkAndEmit(fetched_traj->lookupDouble("workspace_parameters.max_corner.x") <=
430 larger_workspace_plan_req.workspace_parameters.max_corner.x,
431 test_case, "Fetched trajectory has more restrictive workspace max_corner");
432
433 checkAndEmit(fetched_traj->lookupDouble("workspace_parameters.max_corner.x") >=
434 larger_workspace_plan_req.workspace_parameters.min_corner.x,
435 test_case, "Fetched trajectory has more restrictive workspace min_corner");
436
437 // Put worse, no prune_worse_trajectories
438 //
439 // Worse trajectories should not be inserted
440 test_case = "testMotionTrajectories.put_worse";
441 double worse_execution_time = execution_time + 100;
442
443 checkAndEmit(!cache->insertTrajectory(*move_group, ROBOT_NAME, plan_req, traj, worse_execution_time, planning_time,
444 false),
445 test_case, "Put worse trajectory, no prune_worse_trajectories, not ok");
446
447 checkAndEmit(cache->countTrajectories(ROBOT_NAME) == 2, test_case, "Two trajectories in cache");
448
449 // Put better, no prune_worse_trajectories
450 //
451 // Better trajectories should be inserted
452 test_case = "testMotionTrajectories.put_better";
453 double better_execution_time = execution_time - 100;
454
455 checkAndEmit(cache->insertTrajectory(*move_group, ROBOT_NAME, plan_req, traj, better_execution_time, planning_time,
456 false),
457 test_case, "Put better trajectory, no prune_worse_trajectories, ok");
458
459 checkAndEmit(cache->countTrajectories(ROBOT_NAME) == 3, test_case, "Three trajectories in cache");
460
461 // Fetch sorted
462 //
463 // With multiple trajectories in cache, fetches should be sorted accordingly
464 test_case = "testMotionTrajectories.put_better.fetch_sorted";
465
466 fetched_trajectories = cache->fetchAllMatchingTrajectories(*move_group, ROBOT_NAME, plan_req, 0, 0);
467
468 fetched_traj = cache->fetchBestMatchingTrajectory(*move_group, ROBOT_NAME, plan_req, 0, 0);
469
470 checkAndEmit(fetched_trajectories.size() == 3, test_case, "Fetch all returns three");
471 checkAndEmit(fetched_traj != nullptr, test_case, "Fetch best trajectory is not nullptr");
472
473 checkAndEmit(*fetched_trajectories.at(0) == *fetched_traj, test_case, "Fetched trajectory on both fetches match");
474
475 checkAndEmit(*fetched_traj == traj, test_case, "Fetched trajectory matches original");
476
477 checkAndEmit(fetched_traj->lookupDouble("execution_time_s") == better_execution_time, test_case,
478 "Fetched trajectory has correct execution time");
479
480 checkAndEmit(fetched_traj->lookupDouble("planning_time_s") == planning_time, test_case,
481 "Fetched trajectory has correct planning time");
482
483 checkAndEmit(fetched_trajectories.at(0)->lookupDouble("execution_time_s") == better_execution_time &&
484 fetched_trajectories.at(1)->lookupDouble("execution_time_s") == execution_time,
485 test_case, "Fetched trajectories are sorted correctly");
486
487 // Put better, prune_worse_trajectories
488 //
489 // Better, different, trajectories should be inserted
490 test_case = "testMotionTrajectories.put_better_prune_worse_trajectories";
491 double even_better_execution_time = better_execution_time - 100;
492
493 checkAndEmit(cache->insertTrajectory(*move_group, ROBOT_NAME, plan_req, different_traj, even_better_execution_time,
494 planning_time, true),
495 test_case, "Put better trajectory, prune_worse_trajectories, ok");
496
497 checkAndEmit(cache->countTrajectories(ROBOT_NAME) == 1, test_case, "One trajectory in cache");
498
499 // Fetch better plan
500 test_case = "testMotionTrajectories.put_better_prune_worse_trajectories.fetch";
501
502 fetched_trajectories = cache->fetchAllMatchingTrajectories(*move_group, ROBOT_NAME, plan_req, 0, 0);
503
504 fetched_traj = cache->fetchBestMatchingTrajectory(*move_group, ROBOT_NAME, plan_req, 0, 0);
505
506 checkAndEmit(fetched_trajectories.size() == 1, test_case, "Fetch all returns one");
507 checkAndEmit(fetched_traj != nullptr, test_case, "Fetch best trajectory is not nullptr");
508
509 checkAndEmit(*fetched_trajectories.at(0) == *fetched_traj, test_case, "Fetched trajectory on both fetches match");
510
511 checkAndEmit(*fetched_traj == different_traj, test_case, "Fetched trajectory matches original");
512
513 checkAndEmit(fetched_traj->lookupDouble("execution_time_s") == even_better_execution_time, test_case,
514 "Fetched trajectory has correct execution time");
515
516 checkAndEmit(fetched_traj->lookupDouble("planning_time_s") == planning_time, test_case,
517 "Fetched trajectory has correct planning time");
518
519 // Put different req, prune_worse_trajectories
520 //
521 // Unrelated trajectory requests should live alongside pre-existing plans
522 test_case = "testMotionTrajectories.put_different_req";
523
524 checkAndEmit(cache->insertTrajectory(*move_group, ROBOT_NAME, different_plan_req, different_traj,
525 better_execution_time, planning_time, true),
526 test_case, "Put different trajectory req, prune_worse_trajectories, ok");
527
528 checkAndEmit(cache->countTrajectories(ROBOT_NAME) == 2, test_case, "Two trajectories in cache");
529
530 // Fetch with different trajectory req
531 //
532 // With multiple trajectories in cache, fetches should be sorted accordingly
533 test_case = "testMotionTrajectories.put_different_req.fetch";
534
535 fetched_trajectories = cache->fetchAllMatchingTrajectories(*move_group, ROBOT_NAME, different_plan_req, 0, 0);
536
537 fetched_traj = cache->fetchBestMatchingTrajectory(*move_group, ROBOT_NAME, different_plan_req, 0, 0);
538
539 checkAndEmit(fetched_trajectories.size() == 1, test_case, "Fetch all returns one");
540 checkAndEmit(fetched_traj != nullptr, test_case, "Fetch best trajectory is not nullptr");
541
542 checkAndEmit(*fetched_trajectories.at(0) == *fetched_traj, test_case, "Fetched trajectory on both fetches match");
543
544 checkAndEmit(*fetched_traj == different_traj, test_case, "Fetched trajectory matches original");
545
546 checkAndEmit(fetched_traj->lookupDouble("execution_time_s") == better_execution_time, test_case,
547 "Fetched trajectory has correct execution time");
548
549 checkAndEmit(fetched_traj->lookupDouble("planning_time_s") == planning_time, test_case,
550 "Fetched trajectory has correct planning time");
551
552 // Fetch different robot
553 //
554 // Since we didn't populate anything, we should expect empty
555 test_case = "testMotionTrajectories.different_robot.empty";
556 std::string different_robot_name = "different_robot";
557
558 checkAndEmit(cache->countCartesianTrajectories(different_robot_name) == 0, test_case, "No trajectories in cache");
559
560 // Put first for different robot, prune_worse_trajectories
561 //
562 // A different robot's cache should not interact with the original cache
563 test_case = "testMotionTrajectories.different_robot.put_first";
564 checkAndEmit(cache->insertTrajectory(*move_group, different_robot_name, different_plan_req, different_traj,
565 better_execution_time, planning_time, true),
566 test_case, "Put different trajectory req, prune_worse_trajectories, ok");
567
568 checkAndEmit(cache->countTrajectories(different_robot_name) == 1, test_case, "One trajectory in cache");
569
570 checkAndEmit(cache->countTrajectories(ROBOT_NAME) == 2, test_case, "Two trajectories in original robot's cache");
571
572 fetched_trajectories = cache->fetchAllMatchingTrajectories(*move_group, ROBOT_NAME, different_plan_req, 0, 0);
573
574 checkAndEmit(fetched_trajectories.size() == 1, test_case, "Fetch all on original still returns one");
575}
576
577void testCartesianTrajectories(const std::shared_ptr<MoveGroupInterface>& move_group,
578 const std::shared_ptr<TrajectoryCache>& cache)
579{
580 std::string test_case;
581
583
584 // Construct get cartesian trajectory request
585 test_case = "testCartesianTrajectories.constructGetCartesianPathRequest";
586
587 int test_step = 1;
588 int test_jump = 2;
589 auto test_waypoints = getDummyWaypoints();
590 auto cartesian_plan_req_under_test =
591 constructGetCartesianPathRequest(*move_group, test_waypoints, test_step, test_jump, false);
592
593 checkAndEmit(cartesian_plan_req_under_test.waypoints == test_waypoints &&
594 static_cast<int>(cartesian_plan_req_under_test.max_step) == test_step &&
595 static_cast<int>(cartesian_plan_req_under_test.jump_threshold) == test_jump &&
596 !cartesian_plan_req_under_test.avoid_collisions,
597 test_case, "Ok");
598
599 // Setup =====================================================================
600 // All variants are modified copies of `cartesian_plan_req`.
601
603
604 // Plain start
605 auto waypoints = getDummyWaypoints();
606 auto cartesian_plan_req = constructGetCartesianPathRequest(*move_group, waypoints, 1, 1, false);
607 cartesian_plan_req.start_state.multi_dof_joint_state.joint_names.clear();
608 cartesian_plan_req.start_state.multi_dof_joint_state.transforms.clear();
609 cartesian_plan_req.start_state.multi_dof_joint_state.twist.clear();
610 cartesian_plan_req.start_state.multi_dof_joint_state.wrench.clear();
611 cartesian_plan_req.path_constraints.joint_constraints.clear();
612 cartesian_plan_req.path_constraints.position_constraints.clear();
613 cartesian_plan_req.path_constraints.orientation_constraints.clear();
614 cartesian_plan_req.path_constraints.visibility_constraints.clear();
615
616 // Empty frame start
617 auto empty_frame_cartesian_plan_req = cartesian_plan_req;
618 empty_frame_cartesian_plan_req.header.frame_id = "";
619
620 // is_diff = true
621 auto is_diff_cartesian_plan_req = cartesian_plan_req;
622 is_diff_cartesian_plan_req.start_state.is_diff = true;
623 is_diff_cartesian_plan_req.start_state.joint_state.header.frame_id = "";
624 is_diff_cartesian_plan_req.start_state.joint_state.name.clear();
625 is_diff_cartesian_plan_req.start_state.joint_state.position.clear();
626 is_diff_cartesian_plan_req.start_state.joint_state.velocity.clear();
627 is_diff_cartesian_plan_req.start_state.joint_state.effort.clear();
628
629 // Something close enough (mod 0.1 away)
630 auto close_matching_cartesian_plan_req = cartesian_plan_req;
631 close_matching_cartesian_plan_req.start_state.joint_state.position.at(0) -= 0.05;
632 close_matching_cartesian_plan_req.start_state.joint_state.position.at(1) += 0.05;
633 close_matching_cartesian_plan_req.start_state.joint_state.position.at(2) -= 0.05;
634 close_matching_cartesian_plan_req.waypoints.at(0).position.x -= 0.05;
635 close_matching_cartesian_plan_req.waypoints.at(1).position.x += 0.05;
636 close_matching_cartesian_plan_req.waypoints.at(2).position.x -= 0.05;
637
638 // Different
639 auto different_cartesian_plan_req = cartesian_plan_req;
640 different_cartesian_plan_req.start_state.joint_state.position.at(0) -= 1.05;
641 different_cartesian_plan_req.start_state.joint_state.position.at(1) += 2.05;
642 different_cartesian_plan_req.start_state.joint_state.position.at(2) -= 3.05;
643 different_cartesian_plan_req.waypoints.at(0).position.x -= 1.05;
644 different_cartesian_plan_req.waypoints.at(1).position.x += 2.05;
645 different_cartesian_plan_req.waypoints.at(2).position.x -= 3.05;
646
648
649 // Trajectory
650 auto traj = getDummyPandaTraj();
651
652 // Trajectory with no frame_id in its trajectory header
653 auto empty_frame_traj = traj;
654 empty_frame_traj.joint_trajectory.header.frame_id = "";
655
656 auto different_traj = traj;
657 different_traj.joint_trajectory.points.at(0).positions.at(0) = 999;
658 different_traj.joint_trajectory.points.at(0).positions.at(1) = 999;
659 different_traj.joint_trajectory.points.at(0).positions.at(2) = 999;
660
661 // Checks ====================================================================
662
663 // Initially empty
664 test_case = "testCartesianTrajectories.empty";
665
666 checkAndEmit(cache->countCartesianTrajectories(ROBOT_NAME) == 0, test_case, "Trajectory cache initially empty");
667
669 cache->fetchAllMatchingCartesianTrajectories(*move_group, ROBOT_NAME, cartesian_plan_req, 0, 999, 999).empty(),
670 test_case, "Fetch all trajectories on empty cache returns empty");
671
672 checkAndEmit(cache->fetchBestMatchingCartesianTrajectory(*move_group, ROBOT_NAME, cartesian_plan_req, 0, 999, 999) ==
673 nullptr,
674 test_case, "Fetch best trajectory on empty cache returns nullptr");
675
676 // Put trajectory empty frame
677 //
678 // Trajectory must have frame in joint trajectory, expect put fail
679 test_case = "testCartesianTrajectories.insertTrajectory_empty_frame";
680 double execution_time = 999;
681 double planning_time = 999;
682 double fraction = 0.5;
683
684 checkAndEmit(!cache->insertCartesianTrajectory(*move_group, ROBOT_NAME, cartesian_plan_req, empty_frame_traj,
685 execution_time, planning_time, fraction, false),
686 test_case, "Put empty frame trajectory, no prune_worse_trajectories, not ok");
687
688 checkAndEmit(cache->countCartesianTrajectories(ROBOT_NAME) == 0, test_case, "No trajectories in cache");
689
690 // Put trajectory req empty frame
691 //
692 // Trajectory request having no frame in workspace should default to robot frame
693 test_case = "testCartesianTrajectories.insertTrajectory_req_empty_frame";
694 execution_time = 1000;
695 planning_time = 1000;
696
697 checkAndEmit(cache->insertCartesianTrajectory(*move_group, ROBOT_NAME, empty_frame_cartesian_plan_req, traj,
698 execution_time, planning_time, fraction, false),
699 test_case, "Put empty frame req trajectory, no prune_worse_trajectories, ok");
700
701 checkAndEmit(cache->countCartesianTrajectories(ROBOT_NAME) == 1, test_case, "One trajectory in cache");
702
703 // Put second, no prune_worse_trajectories
704 test_case = "testCartesianTrajectories.put_second";
705 execution_time = 999;
706 planning_time = 999;
707
708 checkAndEmit(cache->insertCartesianTrajectory(*move_group, ROBOT_NAME, cartesian_plan_req, traj, execution_time,
709 planning_time, fraction, false),
710 test_case, "Put second valid trajectory, no prune_worse_trajectories, ok");
711
712 checkAndEmit(cache->countCartesianTrajectories(ROBOT_NAME) == 2, test_case, "Two trajectories in cache");
713
714 // Fetch matching, no tolerance
715 //
716 // Exact key match should have cache hit
717 test_case = "testCartesianTrajectories.put_second.fetch_matching_no_tolerance";
718
719 auto fetched_trajectories =
720 cache->fetchAllMatchingCartesianTrajectories(*move_group, ROBOT_NAME, cartesian_plan_req, fraction, 0, 0);
721
722 auto fetched_traj =
723 cache->fetchBestMatchingCartesianTrajectory(*move_group, ROBOT_NAME, cartesian_plan_req, fraction, 0, 0);
724
725 checkAndEmit(fetched_trajectories.size() == 2, test_case, "Fetch all returns two");
726 checkAndEmit(fetched_traj != nullptr, test_case, "Fetch best trajectory is not nullptr");
727
728 checkAndEmit(*fetched_trajectories.at(0) == *fetched_traj, test_case, "Fetched trajectory on both fetches match");
729
730 checkAndEmit(*fetched_traj == traj, test_case, "Fetched trajectory matches original");
731
732 checkAndEmit(fetched_traj->lookupDouble("execution_time_s") == execution_time, test_case,
733 "Fetched trajectory has correct execution time");
734
735 checkAndEmit(fetched_traj->lookupDouble("planning_time_s") == planning_time, test_case,
736 "Fetched trajectory has correct planning time");
737
738 checkAndEmit(fetched_traj->lookupDouble("fraction") == fraction, test_case, "Fetched trajectory has correct fraction");
739
740 // Fetch with is_diff
741 //
742 // is_diff key should be equivalent to exact match if robot state did not
743 // change, hence should have cache hit
744 test_case = "testCartesianTrajectories.put_second.fetch_is_diff_no_tolerance";
745
746 fetched_trajectories =
747 cache->fetchAllMatchingCartesianTrajectories(*move_group, ROBOT_NAME, is_diff_cartesian_plan_req, fraction, 0, 0);
748
749 fetched_traj =
750 cache->fetchBestMatchingCartesianTrajectory(*move_group, ROBOT_NAME, is_diff_cartesian_plan_req, fraction, 0, 0);
751
752 checkAndEmit(fetched_trajectories.size() == 2, test_case, "Fetch all returns two");
753 checkAndEmit(fetched_traj != nullptr, test_case, "Fetch best trajectory is not nullptr");
754
755 checkAndEmit(*fetched_trajectories.at(0) == *fetched_traj, test_case, "Fetched trajectory on both fetches match");
756
757 checkAndEmit(*fetched_traj == traj, test_case, "Fetched trajectory matches original");
758
759 checkAndEmit(fetched_traj->lookupDouble("execution_time_s") == execution_time, test_case,
760 "Fetched trajectory has correct execution time");
761
762 checkAndEmit(fetched_traj->lookupDouble("planning_time_s") == planning_time, test_case,
763 "Fetched trajectory has correct planning time");
764
765 checkAndEmit(fetched_traj->lookupDouble("fraction") == fraction, test_case, "Fetched trajectory has correct fraction");
766
767 // Fetch non-matching, out of tolerance
768 //
769 // Non-matching key should not have cache hit
770 test_case = "testCartesianTrajectories.put_second.fetch_non_matching_out_of_tolerance";
771
772 fetched_trajectories = cache->fetchAllMatchingCartesianTrajectories(
773 *move_group, ROBOT_NAME, close_matching_cartesian_plan_req, fraction, 0, 0);
774
775 fetched_traj = cache->fetchBestMatchingCartesianTrajectory(*move_group, ROBOT_NAME, close_matching_cartesian_plan_req,
776 fraction, 0, 0);
777
778 checkAndEmit(fetched_trajectories.empty(), test_case, "Fetch all returns empty");
779 checkAndEmit(fetched_traj == nullptr, test_case, "Fetch best trajectory is nullptr");
780
781 // Fetch non-matching, only start in tolerance (but not goal)
782 //
783 // Non-matching key should not have cache hit
784 test_case = "testMotionTrajectories.put_second.fetch_non_matching_only_start_in_tolerance";
785
786 fetched_trajectories = cache->fetchAllMatchingCartesianTrajectories(
787 *move_group, ROBOT_NAME, close_matching_cartesian_plan_req, fraction, 999, 0);
788
789 fetched_traj = cache->fetchBestMatchingCartesianTrajectory(*move_group, ROBOT_NAME, close_matching_cartesian_plan_req,
790 fraction, 999, 0);
791
792 checkAndEmit(fetched_trajectories.empty(), test_case, "Fetch all returns empty");
793 checkAndEmit(fetched_traj == nullptr, test_case, "Fetch best trajectory is nullptr");
794
795 // Fetch non-matching, only goal in tolerance (but not start)
796 //
797 // Non-matching key should not have cache hit
798 test_case = "testMotionTrajectories.put_second.fetch_non_matching_only_goal_in_tolerance";
799
800 fetched_trajectories = cache->fetchAllMatchingCartesianTrajectories(
801 *move_group, ROBOT_NAME, close_matching_cartesian_plan_req, fraction, 0, 999);
802
803 fetched_traj = cache->fetchBestMatchingCartesianTrajectory(*move_group, ROBOT_NAME, close_matching_cartesian_plan_req,
804 fraction, 0, 999);
805
806 checkAndEmit(fetched_trajectories.empty(), test_case, "Fetch all returns empty");
807 checkAndEmit(fetched_traj == nullptr, test_case, "Fetch best trajectory is nullptr");
808
809 // Fetch non-matching, in tolerance
810 //
811 // Close key within tolerance limit should have cache hit
812 test_case = "testCartesianTrajectories.put_second.fetch_non_matching_in_tolerance";
813
814 fetched_trajectories = cache->fetchAllMatchingCartesianTrajectories(
815 *move_group, ROBOT_NAME, close_matching_cartesian_plan_req, fraction, 0.1, 0.1);
816
817 fetched_traj = cache->fetchBestMatchingCartesianTrajectory(*move_group, ROBOT_NAME, close_matching_cartesian_plan_req,
818 fraction, 0.1, 0.1);
819
820 checkAndEmit(fetched_trajectories.size() == 2, test_case, "Fetch all returns two");
821 checkAndEmit(fetched_traj != nullptr, test_case, "Fetch best trajectory is not nullptr");
822
823 checkAndEmit(*fetched_trajectories.at(0) == *fetched_traj, test_case, "Fetched trajectory on both fetches match");
824
825 checkAndEmit(*fetched_traj == traj, test_case, "Fetched trajectory matches original");
826
827 checkAndEmit(fetched_traj->lookupDouble("execution_time_s") == execution_time, test_case,
828 "Fetched trajectory has correct execution time");
829
830 checkAndEmit(fetched_traj->lookupDouble("planning_time_s") == planning_time, test_case,
831 "Fetched trajectory has correct planning time");
832
833 checkAndEmit(fetched_traj->lookupDouble("fraction") == fraction, test_case, "Fetched trajectory has correct fraction");
834
835 // Fetch with higher fraction
836 //
837 // Matching trajectories with more restrictive fraction requirements should not
838 // pull up trajectories cached for less restrictive fraction requirements
839 test_case = "testCartesianTrajectories.put_second.fetch_higher_fraction";
840
841 fetched_trajectories =
842 cache->fetchAllMatchingCartesianTrajectories(*move_group, ROBOT_NAME, cartesian_plan_req, 1, 999, 999);
843
844 fetched_traj = cache->fetchBestMatchingCartesianTrajectory(*move_group, ROBOT_NAME, cartesian_plan_req, 1, 999, 999);
845
846 checkAndEmit(fetched_trajectories.empty(), test_case, "Fetch all returns empty");
847 checkAndEmit(fetched_traj == nullptr, test_case, "Fetch best trajectory is nullptr");
848
849 // Fetch with lower fraction
850 //
851 // Matching trajectories with less restrictive fraction requirements should pull up
852 // trajectories cached for more restrictive fraction requirements
853 test_case = "testCartesianTrajectories.put_second.fetch_lower_fraction";
854
855 fetched_trajectories =
856 cache->fetchAllMatchingCartesianTrajectories(*move_group, ROBOT_NAME, cartesian_plan_req, 0, 999, 999);
857
858 fetched_traj = cache->fetchBestMatchingCartesianTrajectory(*move_group, ROBOT_NAME, cartesian_plan_req, 0, 999, 999);
859
860 checkAndEmit(fetched_trajectories.size() == 2, test_case, "Fetch all returns two");
861 checkAndEmit(fetched_traj != nullptr, test_case, "Fetch best trajectory is not nullptr");
862
863 checkAndEmit(*fetched_trajectories.at(0) == *fetched_traj, test_case, "Fetched trajectory on both fetches match");
864
865 checkAndEmit(*fetched_traj == traj, test_case, "Fetched trajectory matches original");
866
867 checkAndEmit(fetched_traj->lookupDouble("execution_time_s") == execution_time, test_case,
868 "Fetched trajectory has correct execution time");
869
870 checkAndEmit(fetched_traj->lookupDouble("planning_time_s") == planning_time, test_case,
871 "Fetched trajectory has correct planning time");
872
873 checkAndEmit(fetched_traj->lookupDouble("fraction") == fraction, test_case, "Fetched trajectory has correct fraction");
874
875 // Put worse, no prune_worse_trajectories
876 //
877 // Worse trajectories should not be inserted
878 test_case = "testCartesianTrajectories.put_worse";
879 double worse_execution_time = execution_time + 100;
880
881 checkAndEmit(!cache->insertCartesianTrajectory(*move_group, ROBOT_NAME, cartesian_plan_req, traj,
882 worse_execution_time, planning_time, fraction, false),
883 test_case, "Put worse trajectory, no prune_worse_trajectories, not ok");
884
885 checkAndEmit(cache->countCartesianTrajectories(ROBOT_NAME) == 2, test_case, "Two trajectories in cache");
886
887 // Put better, no prune_worse_trajectories
888 //
889 // Better trajectories should be inserted
890 test_case = "testCartesianTrajectories.put_better";
891 double better_execution_time = execution_time - 100;
892
893 checkAndEmit(cache->insertCartesianTrajectory(*move_group, ROBOT_NAME, cartesian_plan_req, traj,
894 better_execution_time, planning_time, fraction, false),
895 test_case, "Put better trajectory, no prune_worse_trajectories, ok");
896
897 checkAndEmit(cache->countCartesianTrajectories(ROBOT_NAME) == 3, test_case, "Three trajectories in cache");
898
899 // Fetch sorted
900 //
901 // With multiple trajectories in cache, fetches should be sorted accordingly
902 test_case = "testCartesianTrajectories.put_better.fetch_sorted";
903
904 fetched_trajectories =
905 cache->fetchAllMatchingCartesianTrajectories(*move_group, ROBOT_NAME, cartesian_plan_req, fraction, 0, 0);
906
907 fetched_traj =
908 cache->fetchBestMatchingCartesianTrajectory(*move_group, ROBOT_NAME, cartesian_plan_req, fraction, 0, 0);
909
910 checkAndEmit(fetched_trajectories.size() == 3, test_case, "Fetch all returns three");
911 checkAndEmit(fetched_traj != nullptr, test_case, "Fetch best trajectory is not nullptr");
912
913 checkAndEmit(*fetched_trajectories.at(0) == *fetched_traj, test_case, "Fetched trajectory on both fetches match");
914
915 checkAndEmit(*fetched_traj == traj, test_case, "Fetched trajectory matches original");
916
917 checkAndEmit(fetched_traj->lookupDouble("execution_time_s") == better_execution_time, test_case,
918 "Fetched trajectory has correct execution time");
919
920 checkAndEmit(fetched_traj->lookupDouble("planning_time_s") == planning_time, test_case,
921 "Fetched trajectory has correct planning time");
922
923 checkAndEmit(fetched_traj->lookupDouble("fraction") == fraction, test_case, "Fetched trajectory has correct fraction");
924
925 checkAndEmit(fetched_trajectories.at(0)->lookupDouble("execution_time_s") == better_execution_time &&
926 fetched_trajectories.at(1)->lookupDouble("execution_time_s") == execution_time,
927 test_case, "Fetched trajectories are sorted correctly");
928
929 // Put better, prune_worse_trajectories
930 //
931 // Better, different, trajectories should be inserted
932 test_case = "testCartesianTrajectories.put_better_prune_worse_trajectories";
933 double even_better_execution_time = better_execution_time - 100;
934
935 checkAndEmit(cache->insertCartesianTrajectory(*move_group, ROBOT_NAME, cartesian_plan_req, different_traj,
936 even_better_execution_time, planning_time, fraction, true),
937 test_case, "Put better trajectory, prune_worse_trajectories, ok");
938
939 checkAndEmit(cache->countCartesianTrajectories(ROBOT_NAME) == 1, test_case, "One trajectory in cache");
940
941 // Fetch better plan
942 test_case = "testCartesianTrajectories.put_better_prune_worse_trajectories.fetch";
943
944 fetched_trajectories =
945 cache->fetchAllMatchingCartesianTrajectories(*move_group, ROBOT_NAME, cartesian_plan_req, fraction, 0, 0);
946
947 fetched_traj =
948 cache->fetchBestMatchingCartesianTrajectory(*move_group, ROBOT_NAME, cartesian_plan_req, fraction, 0, 0);
949
950 checkAndEmit(fetched_trajectories.size() == 1, test_case, "Fetch all returns one");
951 checkAndEmit(fetched_traj != nullptr, test_case, "Fetch best trajectory is not nullptr");
952
953 checkAndEmit(*fetched_trajectories.at(0) == *fetched_traj, test_case, "Fetched trajectory on both fetches match");
954
955 checkAndEmit(*fetched_traj == different_traj, test_case, "Fetched trajectory matches original");
956
957 checkAndEmit(fetched_traj->lookupDouble("execution_time_s") == even_better_execution_time, test_case,
958 "Fetched trajectory has correct execution time");
959
960 checkAndEmit(fetched_traj->lookupDouble("planning_time_s") == planning_time, test_case,
961 "Fetched trajectory has correct planning time");
962
963 checkAndEmit(fetched_traj->lookupDouble("fraction") == fraction, test_case, "Fetched trajectory has correct fraction");
964
965 // Put different req, prune_worse_trajectories
966 //
967 // Unrelated trajectory requests should live alongside pre-existing plans
968 test_case = "testCartesianTrajectories.put_different_req";
969
970 checkAndEmit(cache->insertCartesianTrajectory(*move_group, ROBOT_NAME, different_cartesian_plan_req, different_traj,
971 better_execution_time, planning_time, fraction, true),
972 test_case, "Put different trajectory req, prune_worse_trajectories, ok");
973
974 checkAndEmit(cache->countCartesianTrajectories(ROBOT_NAME) == 2, test_case, "Two trajectories in cache");
975
976 // Fetch with different trajectory req
977 //
978 // With multiple trajectories in cache, fetches should be sorted accordingly
979 test_case = "testCartesianTrajectories.put_different_req.fetch";
980
981 fetched_trajectories = cache->fetchAllMatchingCartesianTrajectories(*move_group, ROBOT_NAME,
982 different_cartesian_plan_req, fraction, 0, 0);
983
984 fetched_traj = cache->fetchBestMatchingCartesianTrajectory(*move_group, ROBOT_NAME, different_cartesian_plan_req,
985 fraction, 0, 0);
986
987 checkAndEmit(fetched_trajectories.size() == 1, test_case, "Fetch all returns one");
988 checkAndEmit(fetched_traj != nullptr, test_case, "Fetch best trajectory is not nullptr");
989
990 checkAndEmit(*fetched_trajectories.at(0) == *fetched_traj, test_case, "Fetched trajectory on both fetches match");
991
992 checkAndEmit(*fetched_traj == different_traj, test_case, "Fetched trajectory matches original");
993
994 checkAndEmit(fetched_traj->lookupDouble("execution_time_s") == better_execution_time, test_case,
995 "Fetched trajectory has correct execution time");
996
997 checkAndEmit(fetched_traj->lookupDouble("planning_time_s") == planning_time, test_case,
998 "Fetched trajectory has correct planning time");
999
1000 checkAndEmit(fetched_traj->lookupDouble("fraction") == fraction, test_case, "Fetched trajectory has correct fraction");
1001
1002 // Fetch different robot
1003 //
1004 // Since we didn't populate anything, we should expect empty
1005 test_case = "testCartesianTrajectories.different_robot.empty";
1006 std::string different_robot_name = "different_robot";
1007
1008 checkAndEmit(cache->countCartesianTrajectories(different_robot_name) == 0, test_case, "No trajectories in cache");
1009
1010 // Put first for different robot, prune_worse_trajectories
1011 //
1012 // A different robot's cache should not interact with the original cache
1013 test_case = "testCartesianTrajectories.different_robot.put_first";
1014 checkAndEmit(cache->insertCartesianTrajectory(*move_group, different_robot_name, different_cartesian_plan_req,
1015 different_traj, better_execution_time, planning_time, fraction, true),
1016 test_case, "Put different trajectory req, prune_worse_trajectories, ok");
1017
1018 checkAndEmit(cache->countCartesianTrajectories(different_robot_name) == 1, test_case, "One trajectory in cache");
1019
1020 checkAndEmit(cache->countCartesianTrajectories(ROBOT_NAME) == 2, test_case,
1021 "Two trajectories in original robot's cache");
1022
1023 fetched_trajectories = cache->fetchAllMatchingCartesianTrajectories(*move_group, ROBOT_NAME,
1024 different_cartesian_plan_req, fraction, 0, 0);
1025
1026 checkAndEmit(fetched_trajectories.size() == 1, test_case, "Fetch all on original still returns one");
1027}
1028
1029int main(int argc, char** argv)
1030{
1031 // Setup
1032 rclcpp::init(argc, argv);
1033
1034 rclcpp::NodeOptions test_node_options;
1035 test_node_options.automatically_declare_parameters_from_overrides(true);
1036 test_node_options.arguments({ "--ros-args", "-r", "__node:=test_node" });
1037
1038 rclcpp::NodeOptions move_group_node_options;
1039 move_group_node_options.automatically_declare_parameters_from_overrides(true);
1040 move_group_node_options.arguments({ "--ros-args", "-r", "__node:=move_group_node" });
1041
1042 auto test_node = rclcpp::Node::make_shared("test_node", test_node_options);
1043 auto move_group_node = rclcpp::Node::make_shared("move_group_node", move_group_node_options);
1044
1045 std::atomic<bool> running = true;
1046
1047 std::thread spin_thread([&]() {
1048 rclcpp::executors::SingleThreadedExecutor executor;
1049 executor.add_node(test_node);
1050 executor.add_node(move_group_node);
1051 while (rclcpp::ok() && running)
1052 {
1053 executor.spin_some();
1054 }
1055 });
1056
1057 // This is necessary
1058 test_node->declare_parameter<std::string>("warehouse_plugin", "warehouse_ros_sqlite::DatabaseConnection");
1059
1060 {
1061 // Init.
1062
1063 auto move_group = std::make_shared<MoveGroupInterface>(move_group_node, "panda_arm");
1064 auto curr_state = move_group->getCurrentState(60);
1065 move_group->setStartState(*curr_state);
1066
1067 auto cache = std::make_shared<TrajectoryCache>(test_node);
1068
1069 TrajectoryCache::Options options;
1070 options.db_path = ":memory:";
1071 options.db_port = 0;
1072 options.exact_match_precision = 10;
1073 options.num_additional_trajectories_to_preserve_when_pruning_worse = 10;
1074
1075 // Tests.
1076
1077 checkAndEmit(cache->init(options), "init", "Cache init");
1078
1079 testGettersAndSetters(cache);
1080
1081 cache->setExactMatchPrecision(1e-4);
1082 cache->setNumAdditionalTrajectoriesToPreserveWhenPruningWorse(0);
1083
1086 }
1087
1088 running = false;
1089 spin_thread.join();
1090
1091 test_node.reset();
1092 move_group_node.reset();
1093
1094 rclcpp::shutdown();
1095 return 0;
1096}
Client class to conveniently use the ROS interfaces provided by the move_group node.
Trajectory Cache manager for MoveIt.
Utilities used by the trajectory_cache package.
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.
const std::string ROBOT_NAME
int main(int argc, char **argv)
void testGettersAndSetters(const std::shared_ptr< TrajectoryCache > &cache)
void testMotionTrajectories(const std::shared_ptr< MoveGroupInterface > &move_group, const std::shared_ptr< TrajectoryCache > &cache)
const std::string ROBOT_FRAME
std::vector< geometry_msgs::msg::Pose > getDummyWaypoints()
void testCartesianTrajectories(const std::shared_ptr< MoveGroupInterface > &move_group, const std::shared_ptr< TrajectoryCache > &cache)
void checkAndEmit(bool predicate, const std::string &test_case, const std::string &label)
moveit_msgs::msg::RobotTrajectory getDummyPandaTraj()
Fuzzy-Matching Trajectory Cache.