moveit2
The MoveIt Motion Planning Framework for ROS 2.
Loading...
Searching...
No Matches
test_kinematics_plugin.cpp
Go to the documentation of this file.
1/*********************************************************************
2 * Software License Agreement (BSD License)
3 *
4 * Copyright (c) 2015, Southwest Research Institute
5 * 2018, Bielefeld University
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * * Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * * Redistributions in binary form must reproduce the above
15 * copyright notice, this list of conditions and the following
16 * disclaimer in the documentation and/or other materials provided
17 * with the distribution.
18 * * Neither the name of Willow Garage nor the names of its
19 * contributors may be used to endorse or promote products derived
20 * from this software without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
26 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
27 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
28 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
32 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33 * POSSIBILITY OF SUCH DAMAGE.
34 *********************************************************************/
35
36/* Author: Jorge Nicho, Robert Haschke */
37
38#include <gtest/gtest.h>
39#include <memory>
40#include <functional>
41#include <pluginlib/class_loader.hpp>
42#include <rclcpp/executors/single_threaded_executor.hpp>
43#include <rclcpp/rclcpp.hpp>
44#include <tf2_eigen/tf2_eigen.hpp>
45
46// MoveIt
52
54#include <moveit_msgs/msg/display_trajectory.hpp>
56
59
60rclcpp::Logger getLogger()
61{
62 return moveit::getLogger("moveit.kinematics.test_kinematics_plugin");
63}
64const std::string ROBOT_DESCRIPTION_PARAM = "robot_description";
65const double DEFAULT_SEARCH_DISCRETIZATION = 0.01f;
66const double EXPECTED_SUCCESS_RATE = 0.8;
67static const std::string UNDEFINED = "<undefined>";
68
69// As loading of parameters is quite slow, we share them across all tests
71{
72 friend class KinematicsTest;
73 typedef pluginlib::ClassLoader<kinematics::KinematicsBase> KinematicsLoader;
74
75 moveit::core::RobotModelPtr robot_model_;
76 std::unique_ptr<KinematicsLoader> kinematics_loader_;
77 std::string root_link_;
78 std::string tip_link_;
79 std::string group_name_;
80 std::string ik_plugin_name_;
81 std::vector<std::string> joints_;
82 std::vector<double> seed_;
83 std::vector<double> consistency_limits_;
84 double timeout_;
85 double tolerance_;
86 int num_fk_tests_;
87 int num_ik_cb_tests_;
88 int num_ik_tests_;
89 int num_ik_multiple_tests_;
90 int num_nearest_ik_tests_;
91 bool publish_trajectory_;
92
93 SharedData(const SharedData&) = delete; // this is a singleton
95 {
96 initialize();
97 }
98
99 void initialize()
100 {
101 rclcpp::NodeOptions node_options;
102 node_options.automatically_declare_parameters_from_overrides(true);
103 node_ = rclcpp::Node::make_shared("moveit_kinematics_test", node_options);
104
105 RCLCPP_INFO_STREAM(getLogger(), "Loading robot model from " << node_->get_name() << '.' << ROBOT_DESCRIPTION_PARAM);
106 // load robot model
108 robot_model_ = std::make_shared<moveit::core::RobotModel>(rdf_loader.getURDF(), rdf_loader.getSRDF());
109 ASSERT_TRUE(bool(robot_model_)) << "Failed to load robot model";
110
111 // init ClassLoader
112 kinematics_loader_ = std::make_unique<KinematicsLoader>("moveit_core", "kinematics::KinematicsBase");
113 ASSERT_TRUE(bool(kinematics_loader_)) << "Failed to instantiate ClassLoader";
114
115 // load parameters
116 ASSERT_TRUE(node_->get_parameter("group", group_name_));
117 ASSERT_TRUE(node_->get_parameter("tip_link", tip_link_));
118 ASSERT_TRUE(node_->get_parameter("root_link", root_link_));
119 ASSERT_TRUE(node_->get_parameter("joint_names", joints_));
120 node_->get_parameter_or("seed", seed_, { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 });
121 ASSERT_TRUE(seed_.empty() || seed_.size() == joints_.size()) << "If set, 'seed' size must match 'joint_names' size";
122 node_->get_parameter_or("consistency_limits", consistency_limits_, consistency_limits_);
123 ASSERT_TRUE(consistency_limits_.empty() || consistency_limits_.size() == joints_.size())
124 << "If set, 'consistency_limits' size must match 'joint_names' size";
125 ASSERT_TRUE(node_->get_parameter("ik_timeout", timeout_));
126 ASSERT_TRUE(timeout_ > 0.0) << "'ik_timeout' must be more than 0.0 seconds";
127 ASSERT_TRUE(node_->get_parameter("tolerance", tolerance_));
128 ASSERT_TRUE(tolerance_ > 0.0) << "'tolerance' must be greater than 0.0";
129 ASSERT_TRUE(node_->get_parameter("num_fk_tests", num_fk_tests_));
130 ASSERT_TRUE(node_->get_parameter("num_ik_cb_tests", num_ik_cb_tests_));
131 ASSERT_TRUE(node_->get_parameter("num_ik_tests", num_ik_tests_));
132 ASSERT_TRUE(node_->get_parameter("num_ik_multiple_tests", num_ik_multiple_tests_));
133 ASSERT_TRUE(node_->get_parameter("num_nearest_ik_tests", num_nearest_ik_tests_));
134 ASSERT_TRUE(node_->get_parameter("ik_plugin_name", ik_plugin_name_));
135 node_->get_parameter_or("publish_trajectory", publish_trajectory_, false);
136
137 ASSERT_TRUE(robot_model_->hasJointModelGroup(group_name_));
138 ASSERT_TRUE(robot_model_->hasLinkModel(root_link_));
139 ASSERT_TRUE(robot_model_->hasLinkModel(tip_link_));
140 }
141
142public:
143 std::shared_ptr<rclcpp::Node> node_;
144
145 auto createUniqueInstance(const std::string& name) const
146 {
147 return kinematics_loader_->createUniqueInstance(name);
148 }
149
150 static const SharedData& instance()
151 {
152 static SharedData instance;
153 return instance;
154 }
155 static void release()
156 {
157 SharedData& shared = const_cast<SharedData&>(instance());
158 shared.kinematics_loader_.reset();
159 }
160};
161
162class KinematicsTest : public ::testing::Test
163{
164protected:
165 void operator=(const SharedData& data)
166 {
167 node_ = data.node_;
168 robot_model_ = data.robot_model_;
169 jmg_ = robot_model_->getJointModelGroup(data.group_name_);
170 root_link_ = data.root_link_;
171 tip_link_ = data.tip_link_;
172 group_name_ = data.group_name_;
173 ik_plugin_name_ = data.ik_plugin_name_;
174 joints_ = data.joints_;
175 seed_ = data.seed_;
176 consistency_limits_ = data.consistency_limits_;
177 timeout_ = data.timeout_;
178 tolerance_ = data.tolerance_;
179 num_fk_tests_ = data.num_fk_tests_;
180 num_ik_cb_tests_ = data.num_ik_cb_tests_;
181 num_ik_tests_ = data.num_ik_tests_;
182 num_ik_multiple_tests_ = data.num_ik_multiple_tests_;
183 num_nearest_ik_tests_ = data.num_nearest_ik_tests_;
184 publish_trajectory_ = data.publish_trajectory_;
185 }
186
187 void SetUp() override
188 {
189 *this = SharedData::instance();
190
191 RCLCPP_INFO_STREAM(getLogger(), "Loading " << ik_plugin_name_);
193 ASSERT_TRUE(bool(kinematics_solver_)) << "Failed to load plugin: " << ik_plugin_name_;
194
195 // initializing plugin
196 ASSERT_TRUE(kinematics_solver_->initialize(node_, *robot_model_, group_name_, root_link_, { tip_link_ },
198 << "Solver failed to initialize";
199 jmg_ = robot_model_->getJointModelGroup(kinematics_solver_->getGroupName());
200 ASSERT_TRUE(jmg_);
201 // Validate chain information
202 ASSERT_EQ(root_link_, kinematics_solver_->getBaseFrame());
203 ASSERT_FALSE(kinematics_solver_->getTipFrames().empty());
204 ASSERT_EQ(tip_link_, kinematics_solver_->getTipFrame());
205
206 ASSERT_EQ(joints_, kinematics_solver_->getJointNames());
207 }
208
209public:
210 testing::AssertionResult isNear(const char* expr1, const char* expr2, const char* /*abs_error_expr*/,
211 const geometry_msgs::msg::Point& val1, const geometry_msgs::msg::Point& val2,
212 double abs_error)
213 {
214 // clang-format off
215 if (std::fabs(val1.x - val2.x) <= abs_error &&
216 std::fabs(val1.y - val2.y) <= abs_error &&
217 std::fabs(val1.z - val2.z) <= abs_error)
218 return testing::AssertionSuccess();
219
220 return testing::AssertionFailure()
221 << std::setprecision(std::numeric_limits<double>::digits10 + 2)
222 << "Expected: " << expr1 << " [" << val1.x << ", " << val1.y << ", " << val1.z << "]\n"
223 << "Actual: " << expr2 << " [" << val2.x << ", " << val2.y << ", " << val2.z << ']';
224 // clang-format on
225 }
226 testing::AssertionResult isNear(const char* expr1, const char* expr2, const char* /*abs_error_expr*/,
227 const geometry_msgs::msg::Quaternion& val1,
228 const geometry_msgs::msg::Quaternion& val2, double abs_error)
229 {
230 if ((std::fabs(val1.x - val2.x) <= abs_error && std::fabs(val1.y - val2.y) <= abs_error &&
231 std::fabs(val1.z - val2.z) <= abs_error && std::fabs(val1.w - val2.w) <= abs_error) ||
232 (std::fabs(val1.x + val2.x) <= abs_error && std::fabs(val1.y + val2.y) <= abs_error &&
233 std::fabs(val1.z + val2.z) <= abs_error && std::fabs(val1.w + val2.w) <= abs_error))
234 return testing::AssertionSuccess();
235
236 // clang-format off
237 return testing::AssertionFailure()
238 << std::setprecision(std::numeric_limits<double>::digits10 + 2)
239 << "Expected: " << expr1 << " [" << val1.w << ", " << val1.x << ", " << val1.y << ", " << val1.z << "]\n"
240 << "Actual: " << expr2 << " [" << val2.w << ", " << val2.x << ", " << val2.y << ", " << val2.z << ']';
241 // clang-format on
242 }
243 testing::AssertionResult expectNearHelper(const char* expr1, const char* expr2, const char* abs_error_expr,
244 const std::vector<geometry_msgs::msg::Pose>& val1,
245 const std::vector<geometry_msgs::msg::Pose>& val2, double abs_error)
246 {
247 if (val1.size() != val2.size())
248 {
249 return testing::AssertionFailure() << "Different vector sizes"
250 << "\nExpected: " << expr1 << " (" << val1.size() << ')'
251 << "\nActual: " << expr2 << " (" << val2.size() << ')';
252 }
253
254 for (size_t i = 0; i < val1.size(); ++i)
255 {
256 ::std::stringstream ss;
257 ss << '[' << i << "].position";
258 GTEST_ASSERT_(isNear((expr1 + ss.str()).c_str(), (expr2 + ss.str()).c_str(), abs_error_expr, val1[i].position,
259 val2[i].position, abs_error),
260 GTEST_NONFATAL_FAILURE_);
261
262 ss.str("");
263 ss << '[' << i << "].orientation";
264 GTEST_ASSERT_(isNear((expr1 + ss.str()).c_str(), (expr2 + ss.str()).c_str(), abs_error_expr, val1[i].orientation,
265 val2[i].orientation, abs_error),
266 GTEST_NONFATAL_FAILURE_);
267 }
268 return testing::AssertionSuccess();
269 }
270
271 void searchIKCallback(const std::vector<double>& joint_state, moveit_msgs::msg::MoveItErrorCodes& error_code)
272 {
273 std::vector<std::string> link_names = { tip_link_ };
274 std::vector<geometry_msgs::msg::Pose> poses;
275 if (!kinematics_solver_->getPositionFK(link_names, joint_state, poses))
276 {
277 error_code.val = error_code.PLANNING_FAILED;
278 return;
279 }
280
281 EXPECT_GT(poses[0].position.z, 0.0f);
282 if (poses[0].position.z > 0.0)
283 {
284 error_code.val = error_code.SUCCESS;
285 }
286 else
287 {
288 error_code.val = error_code.PLANNING_FAILED;
289 }
290 }
291
292public:
293 rclcpp::Node::SharedPtr node_;
294 moveit::core::RobotModelPtr robot_model_;
296 kinematics::KinematicsBasePtr kinematics_solver_;
297 random_numbers::RandomNumberGenerator rng_{ 42 };
298 std::string root_link_;
299 std::string tip_link_;
300 std::string group_name_;
301 std::string ik_plugin_name_;
302 std::vector<std::string> joints_;
303 std::vector<double> seed_;
304 std::vector<double> consistency_limits_;
305 double timeout_;
307 unsigned int num_fk_tests_;
308 unsigned int num_ik_cb_tests_;
309 unsigned int num_ik_tests_;
313};
314
315#define EXPECT_NEAR_POSES(lhs, rhs, near) \
316 SCOPED_TRACE("EXPECT_NEAR_POSES(" #lhs ", " #rhs ")"); \
317 GTEST_ASSERT_(expectNearHelper(#lhs, #rhs, #near, lhs, rhs, near), GTEST_NONFATAL_FAILURE_);
318
320{
321 std::vector<double> joints(kinematics_solver_->getJointNames().size(), 0.0);
322 const std::vector<std::string>& tip_frames = kinematics_solver_->getTipFrames();
323 moveit::core::RobotState robot_state(robot_model_);
324 robot_state.setToDefaultValues();
325
326 for (unsigned int i = 0; i < num_fk_tests_; ++i)
327 {
328 robot_state.setToRandomPositions(jmg_, this->rng_);
329 robot_state.copyJointGroupPositions(jmg_, joints);
330 std::vector<geometry_msgs::msg::Pose> fk_poses;
331 EXPECT_TRUE(kinematics_solver_->getPositionFK(tip_frames, joints, fk_poses));
332
333 robot_state.updateLinkTransforms();
334 std::vector<geometry_msgs::msg::Pose> model_poses;
335 model_poses.reserve(tip_frames.size());
336 for (const auto& tip : tip_frames)
337 model_poses.emplace_back(tf2::toMsg(robot_state.getGlobalLinkTransform(tip)));
338 EXPECT_NEAR_POSES(model_poses, fk_poses, tolerance_);
339 }
340}
341
342// perform random walk in joint-space, reaching poses via IK
343TEST_F(KinematicsTest, randomWalkIK)
344{
345 std::vector<double> seed, goal, solution;
346 const std::vector<std::string>& tip_frames = kinematics_solver_->getTipFrames();
347 moveit::core::RobotState robot_state(robot_model_);
348 robot_state.setToDefaultValues();
349
350 if (!seed_.empty())
351 robot_state.setJointGroupPositions(jmg_, seed_);
352
353 moveit_msgs::msg::DisplayTrajectory msg;
354 msg.model_id = robot_model_->getName();
355 moveit::core::robotStateToRobotStateMsg(robot_state, msg.trajectory_start);
356 msg.trajectory.resize(1);
357 robot_trajectory::RobotTrajectory traj(robot_model_, jmg_);
358
359 unsigned int failures = 0;
360 static constexpr double NEAR_JOINT = 0.1;
361 const std::vector<double> consistency_limits(jmg_->getVariableCount(), 1.05 * NEAR_JOINT);
362 for (unsigned int i = 0; i < num_ik_tests_; ++i)
363 {
364 // remember previous pose
365 robot_state.copyJointGroupPositions(jmg_, seed);
366 // sample a new pose nearby
367 robot_state.setToRandomPositionsNearBy(jmg_, robot_state, NEAR_JOINT);
368 // get joints of new pose
369 robot_state.copyJointGroupPositions(jmg_, goal);
370 // compute target tip_frames
371 std::vector<geometry_msgs::msg::Pose> poses;
372 ASSERT_TRUE(kinematics_solver_->getPositionFK(tip_frames, goal, poses));
373
374 // compute IK
375 moveit_msgs::msg::MoveItErrorCodes error_code;
376 kinematics_solver_->searchPositionIK(poses[0], seed, 0.1, consistency_limits, solution, error_code);
377 if (error_code.val != error_code.SUCCESS)
378 {
379 ++failures;
380 continue;
381 }
382
383 // on success: validate reached poses
384 std::vector<geometry_msgs::msg::Pose> reached_poses;
385 kinematics_solver_->getPositionFK(tip_frames, solution, reached_poses);
386 EXPECT_NEAR_POSES(poses, reached_poses, tolerance_);
387
388 // validate closeness of solution pose to goal
389 auto diff = Eigen::Map<Eigen::ArrayXd>(solution.data(), solution.size()) -
390 Eigen::Map<Eigen::ArrayXd>(goal.data(), goal.size());
391 if (!diff.isZero(1.05 * NEAR_JOINT))
392 {
393 ++failures;
394 RCLCPP_WARN_STREAM(getLogger(), "jump in [" << i << "]: " << diff.transpose());
395 }
396
397 // update robot state to found pose
398 robot_state.setJointGroupPositions(jmg_, solution);
399 traj.addSuffixWayPoint(robot_state, 0.1);
400 }
401 EXPECT_LE(failures, (1.0 - EXPECTED_SUCCESS_RATE) * num_ik_tests_);
402 if (publish_trajectory_)
403 {
404 auto pub = node_->create_publisher<moveit_msgs::msg::DisplayTrajectory>("display_random_walk", 1);
405 traj.getRobotTrajectoryMsg(msg.trajectory[0]);
406 pub->publish(msg);
407 rclcpp::executors::SingleThreadedExecutor executor;
408 executor.add_node(node_);
409 executor.spin_some();
410 }
411}
412
413static bool parsePose(const std::vector<double>& pose_values, Eigen::Isometry3d& goal)
414{
415 std::vector<double> vec;
416 Eigen::Quaterniond q;
417 if (pose_values.size() == 6)
418 {
419 q = Eigen::AngleAxisd(pose_values[3], Eigen::Vector3d::UnitX()) *
420 Eigen::AngleAxisd(pose_values[4], Eigen::Vector3d::UnitY()) *
421 Eigen::AngleAxisd(pose_values[5], Eigen::Vector3d::UnitZ());
422 }
423 else if (pose_values.size() == 7)
424 {
425 q = Eigen::Quaterniond(pose_values[3], pose_values[4], pose_values[5], pose_values[6]);
426 }
427 else
428 {
429 return false;
430 }
431
432 goal = q;
433 goal.translation() = Eigen::Vector3d(pose_values[0], pose_values[1], pose_values[2]);
434
435 return true;
436}
437
439{
440 static const std::string TEST_POSES_PARAM = "unit_test_poses";
441 size_t expected_test_poses = 0;
442 node_->get_parameter_or(TEST_POSES_PARAM + ".size", expected_test_poses, expected_test_poses);
443
444 std::vector<double> sol;
445 const std::vector<std::string>& tip_frames = kinematics_solver_->getTipFrames();
446 moveit::core::RobotState robot_state(robot_model_);
447 robot_state.setToDefaultValues();
448 robot_state.setJointGroupPositions(jmg_, seed_);
449
450 // compute initial end-effector pose
451 std::vector<geometry_msgs::msg::Pose> poses;
452 ASSERT_TRUE(kinematics_solver_->getPositionFK(tip_frames, seed_, poses));
453 Eigen::Isometry3d initial, goal;
454 tf2::fromMsg(poses[0], initial);
455
456 RCLCPP_DEBUG(getLogger(), "Initial: %f %f %f %f %f %f %f\n", poses[0].position.x, poses[0].position.y,
457 poses[0].position.z, poses[0].orientation.x, poses[0].orientation.y, poses[0].orientation.z,
458 poses[0].orientation.w);
459
460 auto validate_ik = [&](const geometry_msgs::msg::Pose& goal, std::vector<double>& truth) {
461 // compute IK
462 moveit_msgs::msg::MoveItErrorCodes error_code;
463
464 RCLCPP_DEBUG(getLogger(), "Goal %f %f %f %f %f %f %f\n", goal.position.x, goal.position.y, goal.position.z,
465 goal.orientation.x, goal.orientation.y, goal.orientation.z, goal.orientation.w);
466
467 kinematics_solver_->searchPositionIK(goal, seed_, timeout_,
468 const_cast<const std::vector<double>&>(consistency_limits_), sol, error_code);
469 ASSERT_EQ(error_code.val, error_code.SUCCESS);
470
471 // validate reached poses
472 std::vector<geometry_msgs::msg::Pose> reached_poses;
473 kinematics_solver_->getPositionFK(tip_frames, sol, reached_poses);
474 EXPECT_NEAR_POSES({ goal }, reached_poses, tolerance_);
475
476 // validate ground truth
477 if (!truth.empty())
478 {
479 ASSERT_EQ(truth.size(), sol.size()) << "Invalid size of ground truth joints vector";
480 Eigen::Map<Eigen::ArrayXd> solution(sol.data(), sol.size());
481 Eigen::Map<Eigen::ArrayXd> ground_truth(truth.data(), truth.size());
482 EXPECT_TRUE(solution.isApprox(ground_truth, 10 * tolerance_)) << solution.transpose() << '\n'
483 << ground_truth.transpose() << '\n';
484 }
485 };
486
487 std::vector<double> ground_truth, pose_values;
488 constexpr char pose_type_relative[] = "relative";
489 constexpr char pose_type_absolute[] = "absolute";
490
491 // process tests definitions on parameter server of the form
492 // pose_1:
493 // pose: [0.3, 0.2, 0.1, 0, 0, 0] // xyzrpy (position + euler angles)
494 // joints: [0, 0, 0, 0, 0, 0] // ground truth solution
495 // type: relative // pose applied relative to current pose
496 // pose_2:
497 // pose: [0.1, 0.2, 0.3, 0, 0, 0, 0] // xyzwxyz (position + quaternion)
498 // joints: [1, 2, 3, 4, 5, 6] // pose applied absolute in planning frame
499 // type: absolute
500
501 for (size_t i = 0; i < expected_test_poses; ++i) // NOLINT(modernize-loop-convert)
502 {
503 const std::string pose_name = "pose_" + std::to_string(i);
504 const std::string pose_param = TEST_POSES_PARAM + "." + pose_name; // NOLINT
505 goal = initial; // reset goal to initial
506 ground_truth.clear();
507
508 node_->get_parameter_or(pose_param + ".joints", ground_truth, ground_truth);
509 if (!ground_truth.empty())
510 {
511 ASSERT_EQ(ground_truth.size(), joints_.size())
512 << "Test pose '" << pose_name << "' has invalid 'joints' vector size";
513 }
514
515 pose_values.clear();
516 node_->get_parameter_or(pose_param + ".pose", pose_values, pose_values);
517 ASSERT_TRUE(pose_values.size() == 6 || pose_values.size() == 7)
518 << "Test pose '" << pose_name << "' has invalid 'pose' vector size";
519
520 Eigen::Isometry3d pose;
521 ASSERT_TRUE(parsePose(pose_values, pose)) << "Failed to parse 'pose' vector in: " << pose_name;
522 std::string pose_type = "pose_type_relative";
523 node_->get_parameter_or(pose_param + ".type", pose_type, pose_type);
524 if (pose_type == pose_type_relative)
525 {
526 goal = goal * pose;
527 }
528 else if (pose_type == pose_type_absolute)
529 {
530 goal = pose;
531 }
532 else
533 {
534 FAIL() << "Found invalid 'type' in " << pose_name << ": should be one of '" << pose_type_relative << "' or '"
535 << pose_type_absolute << '\'';
536 }
537
538 std::string desc;
539 {
540 SCOPED_TRACE(desc);
541 validate_ik(tf2::toMsg(goal), ground_truth);
542 }
543 }
544}
545
547{
548 std::vector<double> seed, fk_values, solution;
549 moveit_msgs::msg::MoveItErrorCodes error_code;
550 solution.resize(kinematics_solver_->getJointNames().size(), 0.0);
551 const std::vector<std::string>& fk_names = kinematics_solver_->getTipFrames();
552 moveit::core::RobotState robot_state(robot_model_);
553 robot_state.setToDefaultValues();
554
555 unsigned int success = 0;
556 for (unsigned int i = 0; i < num_ik_tests_; ++i)
557 {
558 seed.resize(kinematics_solver_->getJointNames().size(), 0.0);
559 fk_values.resize(kinematics_solver_->getJointNames().size(), 0.0);
560 robot_state.setToRandomPositions(jmg_, this->rng_);
561 robot_state.copyJointGroupPositions(jmg_, fk_values);
562 std::vector<geometry_msgs::msg::Pose> poses;
563 ASSERT_TRUE(kinematics_solver_->getPositionFK(fk_names, fk_values, poses));
564
565 kinematics_solver_->searchPositionIK(poses[0], seed, timeout_, solution, error_code);
566 if (error_code.val == error_code.SUCCESS)
567 {
568 success++;
569 }
570 else
571 {
572 continue;
573 }
574
575 std::vector<geometry_msgs::msg::Pose> reached_poses;
576 kinematics_solver_->getPositionFK(fk_names, solution, reached_poses);
577 EXPECT_NEAR_POSES(poses, reached_poses, tolerance_);
578 }
579
580 if (num_ik_cb_tests_ > 0)
581 {
582 RCLCPP_INFO_STREAM(getLogger(), "Success Rate: " << static_cast<double>(success) / num_ik_tests_);
583 }
584 EXPECT_GE(success, EXPECTED_SUCCESS_RATE * num_ik_tests_);
585}
586
587TEST_F(KinematicsTest, searchIKWithCallback)
588{
589 std::vector<double> seed, fk_values, solution;
590 moveit_msgs::msg::MoveItErrorCodes error_code;
591 solution.resize(kinematics_solver_->getJointNames().size(), 0.0);
592 const std::vector<std::string>& fk_names = kinematics_solver_->getTipFrames();
593 moveit::core::RobotState robot_state(robot_model_);
594 robot_state.setToDefaultValues();
595
596 unsigned int success = 0;
597 for (unsigned int i = 0; i < num_ik_cb_tests_; ++i)
598 {
599 seed.resize(kinematics_solver_->getJointNames().size(), 0.0);
600 fk_values.resize(kinematics_solver_->getJointNames().size(), 0.0);
601 robot_state.setToRandomPositions(jmg_, this->rng_);
602 robot_state.copyJointGroupPositions(jmg_, fk_values);
603 std::vector<geometry_msgs::msg::Pose> poses;
604 ASSERT_TRUE(kinematics_solver_->getPositionFK(fk_names, fk_values, poses));
605 if (poses[0].position.z <= 0.0f)
606 {
607 --i; // draw a new random state
608 continue;
609 }
610
611 kinematics_solver_->searchPositionIK(
612 poses[0], fk_values, timeout_, solution,
613 [this](const geometry_msgs::msg::Pose&, const std::vector<double>& joints,
614 moveit_msgs::msg::MoveItErrorCodes& error_code) { searchIKCallback(joints, error_code); },
615 error_code);
616 if (error_code.val == error_code.SUCCESS)
617 {
618 success++;
619 }
620 else
621 {
622 continue;
623 }
624
625 std::vector<geometry_msgs::msg::Pose> reached_poses;
626 kinematics_solver_->getPositionFK(fk_names, solution, reached_poses);
627 EXPECT_NEAR_POSES(poses, reached_poses, tolerance_);
628 }
629
630 if (num_ik_cb_tests_ > 0)
631 {
632 RCLCPP_INFO_STREAM(getLogger(), "Success Rate: " << static_cast<double>(success) / num_ik_cb_tests_);
633 }
634 EXPECT_GE(success, EXPECTED_SUCCESS_RATE * num_ik_cb_tests_);
635}
636
638{
639 std::vector<double> fk_values, solution;
640 moveit_msgs::msg::MoveItErrorCodes error_code;
641 solution.resize(kinematics_solver_->getJointNames().size(), 0.0);
642 const std::vector<std::string>& fk_names = kinematics_solver_->getTipFrames();
643 moveit::core::RobotState robot_state(robot_model_);
644 robot_state.setToDefaultValues();
645
646 for (unsigned int i = 0; i < num_ik_tests_; ++i)
647 {
648 fk_values.resize(kinematics_solver_->getJointNames().size(), 0.0);
649 robot_state.setToRandomPositions(jmg_, this->rng_);
650 robot_state.copyJointGroupPositions(jmg_, fk_values);
651 std::vector<geometry_msgs::msg::Pose> poses;
652
653 ASSERT_TRUE(kinematics_solver_->getPositionFK(fk_names, fk_values, poses));
654 kinematics_solver_->getPositionIK(poses[0], fk_values, solution, error_code);
655 // starting from the correct solution, should yield the same pose
656 EXPECT_EQ(error_code.val, error_code.SUCCESS);
657
658 Eigen::Map<Eigen::ArrayXd> sol(solution.data(), solution.size());
659 Eigen::Map<Eigen::ArrayXd> truth(fk_values.data(), fk_values.size());
660 EXPECT_TRUE(sol.isApprox(truth, tolerance_)) << sol.transpose() << '\n' << truth.transpose() << '\n';
661 }
662}
663
664TEST_F(KinematicsTest, getIKMultipleSolutions)
665{
666 std::vector<double> seed, fk_values;
667 std::vector<std::vector<double>> solutions;
670
671 const std::vector<std::string>& fk_names = kinematics_solver_->getTipFrames();
672 moveit::core::RobotState robot_state(robot_model_);
673 robot_state.setToDefaultValues();
674
675 unsigned int success = 0;
676 for (unsigned int i = 0; i < num_ik_multiple_tests_; ++i)
677 {
678 seed.resize(kinematics_solver_->getJointNames().size(), 0.0);
679 fk_values.resize(kinematics_solver_->getJointNames().size(), 0.0);
680 robot_state.setToRandomPositions(jmg_, this->rng_);
681 robot_state.copyJointGroupPositions(jmg_, fk_values);
682 std::vector<geometry_msgs::msg::Pose> poses;
683 ASSERT_TRUE(kinematics_solver_->getPositionFK(fk_names, fk_values, poses));
684
685 solutions.clear();
686 kinematics_solver_->getPositionIK(poses, fk_values, solutions, result, options);
687
689 {
690 success += solutions.empty() ? 0 : 1;
691 }
692 else
693 {
694 continue;
695 }
696
697 std::vector<geometry_msgs::msg::Pose> reached_poses;
698 for (const auto& s : solutions)
699 {
700 kinematics_solver_->getPositionFK(fk_names, s, reached_poses);
701 EXPECT_NEAR_POSES(poses, reached_poses, tolerance_);
702 }
703 }
704
705 if (num_ik_cb_tests_ > 0)
706 {
707 RCLCPP_INFO_STREAM(getLogger(), "Success Rate: " << static_cast<double>(success) / num_ik_multiple_tests_);
708 }
709 EXPECT_GE(success, EXPECTED_SUCCESS_RATE * num_ik_multiple_tests_);
710}
711
712// validate that getPositionIK() retrieves closest solution to seed
713TEST_F(KinematicsTest, getNearestIKSolution)
714{
715 std::vector<std::vector<double>> solutions;
718
719 std::vector<double> seed, fk_values, solution;
720 moveit_msgs::msg::MoveItErrorCodes error_code;
721 const std::vector<std::string>& fk_names = kinematics_solver_->getTipFrames();
722 moveit::core::RobotState robot_state(robot_model_);
723 robot_state.setToDefaultValues();
724
725 for (unsigned int i = 0; i < num_nearest_ik_tests_; ++i)
726 {
727 robot_state.setToRandomPositions(jmg_, this->rng_);
728 robot_state.copyJointGroupPositions(jmg_, fk_values);
729 std::vector<geometry_msgs::msg::Pose> poses;
730 ASSERT_TRUE(kinematics_solver_->getPositionFK(fk_names, fk_values, poses));
731
732 // sample seed vector
733 robot_state.setToRandomPositions(jmg_, this->rng_);
734 robot_state.copyJointGroupPositions(jmg_, seed);
735
736 // getPositionIK for single solution
737 kinematics_solver_->getPositionIK(poses[0], seed, solution, error_code);
738
739 // check if getPositionIK call for single solution returns solution
740 if (error_code.val != error_code.SUCCESS)
741 continue;
742
743 const Eigen::Map<const Eigen::VectorXd> seed_eigen(seed.data(), seed.size());
744 double error_get_ik =
745 (Eigen::Map<const Eigen::VectorXd>(solution.data(), solution.size()) - seed_eigen).array().abs().sum();
746
747 // getPositionIK for multiple solutions
748 solutions.clear();
749 kinematics_solver_->getPositionIK(poses, seed, solutions, result, options);
750
751 // check if getPositionIK call for multiple solutions returns solution
753 << "Multiple solution call failed, while single solution call succeeded";
755 continue;
756
757 double smallest_error_multiple_ik = std::numeric_limits<double>::max();
758 for (const auto& s : solutions)
759 {
760 double error_multiple_ik =
761 (Eigen::Map<const Eigen::VectorXd>(s.data(), s.size()) - seed_eigen).array().abs().sum();
762 if (error_multiple_ik <= smallest_error_multiple_ik)
763 smallest_error_multiple_ik = error_multiple_ik;
764 }
765 EXPECT_NEAR(smallest_error_multiple_ik, error_get_ik, tolerance_);
766 }
767}
768
769int main(int argc, char** argv)
770{
771 testing::InitGoogleTest(&argc, argv);
772 rclcpp::init(argc, argv);
773 int result = RUN_ALL_TESTS();
774 SharedData::release(); // avoid class_loader::LibraryUnloadException
775 return result;
776}
testing::AssertionResult isNear(const char *expr1, const char *expr2, const char *, const geometry_msgs::msg::Point &val1, const geometry_msgs::msg::Point &val2, double abs_error)
std::vector< double > consistency_limits_
void searchIKCallback(const std::vector< double > &joint_state, moveit_msgs::msg::MoveItErrorCodes &error_code)
moveit::core::RobotModelPtr robot_model_
random_numbers::RandomNumberGenerator rng_
unsigned int num_ik_multiple_tests_
std::vector< double > seed_
kinematics::KinematicsBasePtr kinematics_solver_
void operator=(const SharedData &data)
moveit::core::JointModelGroup * jmg_
unsigned int num_nearest_ik_tests_
testing::AssertionResult expectNearHelper(const char *expr1, const char *expr2, const char *abs_error_expr, const std::vector< geometry_msgs::msg::Pose > &val1, const std::vector< geometry_msgs::msg::Pose > &val2, double abs_error)
std::vector< std::string > joints_
rclcpp::Node::SharedPtr node_
testing::AssertionResult isNear(const char *expr1, const char *expr2, const char *, const geometry_msgs::msg::Quaternion &val1, const geometry_msgs::msg::Quaternion &val2, double abs_error)
auto createUniqueInstance(const std::string &name) const
static const SharedData & instance()
std::shared_ptr< rclcpp::Node > node_
Representation of a robot's state. This includes position, velocity, acceleration and effort.
void setJointGroupPositions(const std::string &joint_group_name, const double *gstate)
Given positions for the variables that make up a group, in the order found in the group (including va...
void copyJointGroupPositions(const std::string &joint_group_name, std::vector< double > &gstate) const
For a given group, copy the position values of the variables that make up the group into another loca...
void setToRandomPositionsNearBy(const JointModelGroup *group, const RobotState &seed, double distance)
Set all joints in group to random values near the value in seed. distance is the maximum amount each ...
void updateLinkTransforms()
Update the reference frame transforms for links. This call is needed before using the transforms of l...
void setToRandomPositions()
Set all joints to random values. Values will be within default bounds.
void setToDefaultValues()
Set all joints to their default positions. The default position is 0, or if that is not within bounds...
const Eigen::Isometry3d & getGlobalLinkTransform(const std::string &link_name)
Get the link transform w.r.t. the root link (model frame) of the RobotModel. This is typically the ro...
Maintain a sequence of waypoints and the time durations between these waypoints.
RobotTrajectory & addSuffixWayPoint(const moveit::core::RobotState &state, double dt)
Add a point to the trajectory.
void getRobotTrajectoryMsg(moveit_msgs::msg::RobotTrajectory &trajectory, const std::vector< std::string > &joint_filter=std::vector< std::string >()) const
void robotStateToRobotStateMsg(const RobotState &state, moveit_msgs::msg::RobotState &robot_state, bool copy_attached_bodies=true)
Convert a MoveIt robot state to a robot state message.
rclcpp::Logger getLogger(const std::string &name)
Creates a namespaced logger.
Definition logger.cpp:79
A set of options for the kinematics solver.
int main(int argc, char **argv)
rclcpp::Logger getLogger()
const double EXPECTED_SUCCESS_RATE
const double DEFAULT_SEARCH_DISCRETIZATION
#define EXPECT_NEAR_POSES(lhs, rhs, near)
const std::string ROBOT_DESCRIPTION_PARAM
TEST_F(KinematicsTest, getFK)