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/rclcpp.hpp>
43#include <tf2_eigen/tf2_eigen.hpp>
44
45// MoveIt
51
53#include <moveit_msgs/msg/display_trajectory.hpp>
55
58
59rclcpp::Logger getLogger()
60{
61 return moveit::getLogger("moveit.kinematics.test_kinematics_plugin");
62}
63const std::string ROBOT_DESCRIPTION_PARAM = "robot_description";
64const double DEFAULT_SEARCH_DISCRETIZATION = 0.01f;
65const double EXPECTED_SUCCESS_RATE = 0.8;
66static const std::string UNDEFINED = "<undefined>";
67
68// As loading of parameters is quite slow, we share them across all tests
70{
71 friend class KinematicsTest;
72 typedef pluginlib::ClassLoader<kinematics::KinematicsBase> KinematicsLoader;
73
74 moveit::core::RobotModelPtr robot_model_;
75 std::unique_ptr<KinematicsLoader> kinematics_loader_;
76 std::string root_link_;
77 std::string tip_link_;
78 std::string group_name_;
79 std::string ik_plugin_name_;
80 std::vector<std::string> joints_;
81 std::vector<double> seed_;
82 std::vector<double> consistency_limits_;
83 double timeout_;
84 double tolerance_;
85 int num_fk_tests_;
86 int num_ik_cb_tests_;
87 int num_ik_tests_;
88 int num_ik_multiple_tests_;
89 int num_nearest_ik_tests_;
90 bool publish_trajectory_;
91
92 SharedData(const SharedData&) = delete; // this is a singleton
94 {
95 initialize();
96 }
97
98 void initialize()
99 {
100 rclcpp::NodeOptions node_options;
101 node_options.automatically_declare_parameters_from_overrides(true);
102 node_ = rclcpp::Node::make_shared("moveit_kinematics_test", node_options);
103
104 RCLCPP_INFO_STREAM(getLogger(), "Loading robot model from " << node_->get_name() << '.' << ROBOT_DESCRIPTION_PARAM);
105 // load robot model
107 robot_model_ = std::make_shared<moveit::core::RobotModel>(rdf_loader.getURDF(), rdf_loader.getSRDF());
108 ASSERT_TRUE(bool(robot_model_)) << "Failed to load robot model";
109
110 // init ClassLoader
111 kinematics_loader_ = std::make_unique<KinematicsLoader>("moveit_core", "kinematics::KinematicsBase");
112 ASSERT_TRUE(bool(kinematics_loader_)) << "Failed to instantiate ClassLoader";
113
114 // load parameters
115 ASSERT_TRUE(node_->get_parameter("group", group_name_));
116 ASSERT_TRUE(node_->get_parameter("tip_link", tip_link_));
117 ASSERT_TRUE(node_->get_parameter("root_link", root_link_));
118 ASSERT_TRUE(node_->get_parameter("joint_names", joints_));
119 node_->get_parameter_or("seed", seed_, { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 });
120 ASSERT_TRUE(seed_.empty() || seed_.size() == joints_.size()) << "If set, 'seed' size must match 'joint_names' size";
121 node_->get_parameter_or("consistency_limits", consistency_limits_, consistency_limits_);
122 ASSERT_TRUE(consistency_limits_.empty() || consistency_limits_.size() == joints_.size())
123 << "If set, 'consistency_limits' size must match 'joint_names' size";
124 ASSERT_TRUE(node_->get_parameter("ik_timeout", timeout_));
125 ASSERT_TRUE(timeout_ > 0.0) << "'ik_timeout' must be more than 0.0 seconds";
126 ASSERT_TRUE(node_->get_parameter("tolerance", tolerance_));
127 ASSERT_TRUE(tolerance_ > 0.0) << "'tolerance' must be greater than 0.0";
128 ASSERT_TRUE(node_->get_parameter("num_fk_tests", num_fk_tests_));
129 ASSERT_TRUE(node_->get_parameter("num_ik_cb_tests", num_ik_cb_tests_));
130 ASSERT_TRUE(node_->get_parameter("num_ik_tests", num_ik_tests_));
131 ASSERT_TRUE(node_->get_parameter("num_ik_multiple_tests", num_ik_multiple_tests_));
132 ASSERT_TRUE(node_->get_parameter("num_nearest_ik_tests", num_nearest_ik_tests_));
133 ASSERT_TRUE(node_->get_parameter("ik_plugin_name", ik_plugin_name_));
134 node_->get_parameter_or("publish_trajectory", publish_trajectory_, false);
135
136 ASSERT_TRUE(robot_model_->hasJointModelGroup(group_name_));
137 ASSERT_TRUE(robot_model_->hasLinkModel(root_link_));
138 ASSERT_TRUE(robot_model_->hasLinkModel(tip_link_));
139 }
140
141public:
142 std::shared_ptr<rclcpp::Node> node_;
143
144 auto createUniqueInstance(const std::string& name) const
145 {
146 return kinematics_loader_->createUniqueInstance(name);
147 }
148
149 static const SharedData& instance()
150 {
151 static SharedData instance;
152 return instance;
153 }
154 static void release()
155 {
156 SharedData& shared = const_cast<SharedData&>(instance());
157 shared.kinematics_loader_.reset();
158 }
159};
160
161class KinematicsTest : public ::testing::Test
162{
163protected:
164 void operator=(const SharedData& data)
165 {
166 node_ = data.node_;
167 robot_model_ = data.robot_model_;
168 jmg_ = robot_model_->getJointModelGroup(data.group_name_);
169 root_link_ = data.root_link_;
170 tip_link_ = data.tip_link_;
171 group_name_ = data.group_name_;
172 ik_plugin_name_ = data.ik_plugin_name_;
173 joints_ = data.joints_;
174 seed_ = data.seed_;
175 consistency_limits_ = data.consistency_limits_;
176 timeout_ = data.timeout_;
177 tolerance_ = data.tolerance_;
178 num_fk_tests_ = data.num_fk_tests_;
179 num_ik_cb_tests_ = data.num_ik_cb_tests_;
180 num_ik_tests_ = data.num_ik_tests_;
181 num_ik_multiple_tests_ = data.num_ik_multiple_tests_;
182 num_nearest_ik_tests_ = data.num_nearest_ik_tests_;
183 publish_trajectory_ = data.publish_trajectory_;
184 }
185
186 void SetUp() override
187 {
188 *this = SharedData::instance();
189
190 RCLCPP_INFO_STREAM(getLogger(), "Loading " << ik_plugin_name_);
192 ASSERT_TRUE(bool(kinematics_solver_)) << "Failed to load plugin: " << ik_plugin_name_;
193
194 // initializing plugin
195 ASSERT_TRUE(kinematics_solver_->initialize(node_, *robot_model_, group_name_, root_link_, { tip_link_ },
197 << "Solver failed to initialize";
198 jmg_ = robot_model_->getJointModelGroup(kinematics_solver_->getGroupName());
199 ASSERT_TRUE(jmg_);
200 // Validate chain information
201 ASSERT_EQ(root_link_, kinematics_solver_->getBaseFrame());
202 ASSERT_FALSE(kinematics_solver_->getTipFrames().empty());
203 ASSERT_EQ(tip_link_, kinematics_solver_->getTipFrame());
204
205 ASSERT_EQ(joints_, kinematics_solver_->getJointNames());
206 }
207
208public:
209 testing::AssertionResult isNear(const char* expr1, const char* expr2, const char* /*abs_error_expr*/,
210 const geometry_msgs::msg::Point& val1, const geometry_msgs::msg::Point& val2,
211 double abs_error)
212 {
213 // clang-format off
214 if (std::fabs(val1.x - val2.x) <= abs_error &&
215 std::fabs(val1.y - val2.y) <= abs_error &&
216 std::fabs(val1.z - val2.z) <= abs_error)
217 return testing::AssertionSuccess();
218
219 return testing::AssertionFailure()
220 << std::setprecision(std::numeric_limits<double>::digits10 + 2)
221 << "Expected: " << expr1 << " [" << val1.x << ", " << val1.y << ", " << val1.z << "]\n"
222 << "Actual: " << expr2 << " [" << val2.x << ", " << val2.y << ", " << val2.z << ']';
223 // clang-format on
224 }
225 testing::AssertionResult isNear(const char* expr1, const char* expr2, const char* /*abs_error_expr*/,
226 const geometry_msgs::msg::Quaternion& val1,
227 const geometry_msgs::msg::Quaternion& val2, double abs_error)
228 {
229 if ((std::fabs(val1.x - val2.x) <= abs_error && std::fabs(val1.y - val2.y) <= abs_error &&
230 std::fabs(val1.z - val2.z) <= abs_error && std::fabs(val1.w - val2.w) <= abs_error) ||
231 (std::fabs(val1.x + val2.x) <= abs_error && std::fabs(val1.y + val2.y) <= abs_error &&
232 std::fabs(val1.z + val2.z) <= abs_error && std::fabs(val1.w + val2.w) <= abs_error))
233 return testing::AssertionSuccess();
234
235 // clang-format off
236 return testing::AssertionFailure()
237 << std::setprecision(std::numeric_limits<double>::digits10 + 2)
238 << "Expected: " << expr1 << " [" << val1.w << ", " << val1.x << ", " << val1.y << ", " << val1.z << "]\n"
239 << "Actual: " << expr2 << " [" << val2.w << ", " << val2.x << ", " << val2.y << ", " << val2.z << ']';
240 // clang-format on
241 }
242 testing::AssertionResult expectNearHelper(const char* expr1, const char* expr2, const char* abs_error_expr,
243 const std::vector<geometry_msgs::msg::Pose>& val1,
244 const std::vector<geometry_msgs::msg::Pose>& val2, double abs_error)
245 {
246 if (val1.size() != val2.size())
247 {
248 return testing::AssertionFailure() << "Different vector sizes"
249 << "\nExpected: " << expr1 << " (" << val1.size() << ')'
250 << "\nActual: " << expr2 << " (" << val2.size() << ')';
251 }
252
253 for (size_t i = 0; i < val1.size(); ++i)
254 {
255 ::std::stringstream ss;
256 ss << '[' << i << "].position";
257 GTEST_ASSERT_(isNear((expr1 + ss.str()).c_str(), (expr2 + ss.str()).c_str(), abs_error_expr, val1[i].position,
258 val2[i].position, abs_error),
259 GTEST_NONFATAL_FAILURE_);
260
261 ss.str("");
262 ss << '[' << i << "].orientation";
263 GTEST_ASSERT_(isNear((expr1 + ss.str()).c_str(), (expr2 + ss.str()).c_str(), abs_error_expr, val1[i].orientation,
264 val2[i].orientation, abs_error),
265 GTEST_NONFATAL_FAILURE_);
266 }
267 return testing::AssertionSuccess();
268 }
269
270 void searchIKCallback(const std::vector<double>& joint_state, moveit_msgs::msg::MoveItErrorCodes& error_code)
271 {
272 std::vector<std::string> link_names = { tip_link_ };
273 std::vector<geometry_msgs::msg::Pose> poses;
274 if (!kinematics_solver_->getPositionFK(link_names, joint_state, poses))
275 {
276 error_code.val = error_code.PLANNING_FAILED;
277 return;
278 }
279
280 EXPECT_GT(poses[0].position.z, 0.0f);
281 if (poses[0].position.z > 0.0)
282 {
283 error_code.val = error_code.SUCCESS;
284 }
285 else
286 {
287 error_code.val = error_code.PLANNING_FAILED;
288 }
289 }
290
291public:
292 rclcpp::Node::SharedPtr node_;
293 moveit::core::RobotModelPtr robot_model_;
295 kinematics::KinematicsBasePtr kinematics_solver_;
296 random_numbers::RandomNumberGenerator rng_{ 42 };
297 std::string root_link_;
298 std::string tip_link_;
299 std::string group_name_;
300 std::string ik_plugin_name_;
301 std::vector<std::string> joints_;
302 std::vector<double> seed_;
303 std::vector<double> consistency_limits_;
304 double timeout_;
306 unsigned int num_fk_tests_;
307 unsigned int num_ik_cb_tests_;
308 unsigned int num_ik_tests_;
312};
313
314#define EXPECT_NEAR_POSES(lhs, rhs, near) \
315 SCOPED_TRACE("EXPECT_NEAR_POSES(" #lhs ", " #rhs ")"); \
316 GTEST_ASSERT_(expectNearHelper(#lhs, #rhs, #near, lhs, rhs, near), GTEST_NONFATAL_FAILURE_);
317
319{
320 std::vector<double> joints(kinematics_solver_->getJointNames().size(), 0.0);
321 const std::vector<std::string>& tip_frames = kinematics_solver_->getTipFrames();
322 moveit::core::RobotState robot_state(robot_model_);
323 robot_state.setToDefaultValues();
324
325 for (unsigned int i = 0; i < num_fk_tests_; ++i)
326 {
327 robot_state.setToRandomPositions(jmg_, this->rng_);
328 robot_state.copyJointGroupPositions(jmg_, joints);
329 std::vector<geometry_msgs::msg::Pose> fk_poses;
330 EXPECT_TRUE(kinematics_solver_->getPositionFK(tip_frames, joints, fk_poses));
331
332 robot_state.updateLinkTransforms();
333 std::vector<geometry_msgs::msg::Pose> model_poses;
334 model_poses.reserve(tip_frames.size());
335 for (const auto& tip : tip_frames)
336 model_poses.emplace_back(tf2::toMsg(robot_state.getGlobalLinkTransform(tip)));
337 EXPECT_NEAR_POSES(model_poses, fk_poses, tolerance_);
338 }
339}
340
341// perform random walk in joint-space, reaching poses via IK
342TEST_F(KinematicsTest, randomWalkIK)
343{
344 std::vector<double> seed, goal, solution;
345 const std::vector<std::string>& tip_frames = kinematics_solver_->getTipFrames();
346 moveit::core::RobotState robot_state(robot_model_);
347 robot_state.setToDefaultValues();
348
349 if (!seed_.empty())
350 robot_state.setJointGroupPositions(jmg_, seed_);
351
352 moveit_msgs::msg::DisplayTrajectory msg;
353 msg.model_id = robot_model_->getName();
354 moveit::core::robotStateToRobotStateMsg(robot_state, msg.trajectory_start);
355 msg.trajectory.resize(1);
356 robot_trajectory::RobotTrajectory traj(robot_model_, jmg_);
357
358 unsigned int failures = 0;
359 static constexpr double NEAR_JOINT = 0.1;
360 const std::vector<double> consistency_limits(jmg_->getVariableCount(), 1.05 * NEAR_JOINT);
361 for (unsigned int i = 0; i < num_ik_tests_; ++i)
362 {
363 // remember previous pose
364 robot_state.copyJointGroupPositions(jmg_, seed);
365 // sample a new pose nearby
366 robot_state.setToRandomPositionsNearBy(jmg_, robot_state, NEAR_JOINT);
367 // get joints of new pose
368 robot_state.copyJointGroupPositions(jmg_, goal);
369 // compute target tip_frames
370 std::vector<geometry_msgs::msg::Pose> poses;
371 ASSERT_TRUE(kinematics_solver_->getPositionFK(tip_frames, goal, poses));
372
373 // compute IK
374 moveit_msgs::msg::MoveItErrorCodes error_code;
375 kinematics_solver_->searchPositionIK(poses[0], seed, 0.1, consistency_limits, solution, error_code);
376 if (error_code.val != error_code.SUCCESS)
377 {
378 ++failures;
379 continue;
380 }
381
382 // on success: validate reached poses
383 std::vector<geometry_msgs::msg::Pose> reached_poses;
384 kinematics_solver_->getPositionFK(tip_frames, solution, reached_poses);
385 EXPECT_NEAR_POSES(poses, reached_poses, tolerance_);
386
387 // validate closeness of solution pose to goal
388 auto diff = Eigen::Map<Eigen::ArrayXd>(solution.data(), solution.size()) -
389 Eigen::Map<Eigen::ArrayXd>(goal.data(), goal.size());
390 if (!diff.isZero(1.05 * NEAR_JOINT))
391 {
392 ++failures;
393 RCLCPP_WARN_STREAM(getLogger(), "jump in [" << i << "]: " << diff.transpose());
394 }
395
396 // update robot state to found pose
397 robot_state.setJointGroupPositions(jmg_, solution);
398 traj.addSuffixWayPoint(robot_state, 0.1);
399 }
400 EXPECT_LE(failures, (1.0 - EXPECTED_SUCCESS_RATE) * num_ik_tests_);
401 if (publish_trajectory_)
402 {
403 auto pub = node_->create_publisher<moveit_msgs::msg::DisplayTrajectory>("display_random_walk", 1);
404 traj.getRobotTrajectoryMsg(msg.trajectory[0]);
405 pub->publish(msg);
406 rclcpp::spin_some(node_);
407 }
408}
409
410static bool parsePose(const std::vector<double>& pose_values, Eigen::Isometry3d& goal)
411{
412 std::vector<double> vec;
413 Eigen::Quaterniond q;
414 if (pose_values.size() == 6)
415 {
416 q = Eigen::AngleAxisd(pose_values[3], Eigen::Vector3d::UnitX()) *
417 Eigen::AngleAxisd(pose_values[4], Eigen::Vector3d::UnitY()) *
418 Eigen::AngleAxisd(pose_values[5], Eigen::Vector3d::UnitZ());
419 }
420 else if (pose_values.size() == 7)
421 {
422 q = Eigen::Quaterniond(pose_values[3], pose_values[4], pose_values[5], pose_values[6]);
423 }
424 else
425 {
426 return false;
427 }
428
429 goal = q;
430 goal.translation() = Eigen::Vector3d(pose_values[0], pose_values[1], pose_values[2]);
431
432 return true;
433}
434
436{
437 static const std::string TEST_POSES_PARAM = "unit_test_poses";
438 size_t expected_test_poses = 0;
439 node_->get_parameter_or(TEST_POSES_PARAM + ".size", expected_test_poses, expected_test_poses);
440
441 std::vector<double> sol;
442 const std::vector<std::string>& tip_frames = kinematics_solver_->getTipFrames();
443 moveit::core::RobotState robot_state(robot_model_);
444 robot_state.setToDefaultValues();
445 robot_state.setJointGroupPositions(jmg_, seed_);
446
447 // compute initial end-effector pose
448 std::vector<geometry_msgs::msg::Pose> poses;
449 ASSERT_TRUE(kinematics_solver_->getPositionFK(tip_frames, seed_, poses));
450 Eigen::Isometry3d initial, goal;
451 tf2::fromMsg(poses[0], initial);
452
453 RCLCPP_DEBUG(getLogger(), "Initial: %f %f %f %f %f %f %f\n", poses[0].position.x, poses[0].position.y,
454 poses[0].position.z, poses[0].orientation.x, poses[0].orientation.y, poses[0].orientation.z,
455 poses[0].orientation.w);
456
457 auto validate_ik = [&](const geometry_msgs::msg::Pose& goal, std::vector<double>& truth) {
458 // compute IK
459 moveit_msgs::msg::MoveItErrorCodes error_code;
460
461 RCLCPP_DEBUG(getLogger(), "Goal %f %f %f %f %f %f %f\n", goal.position.x, goal.position.y, goal.position.z,
462 goal.orientation.x, goal.orientation.y, goal.orientation.z, goal.orientation.w);
463
464 kinematics_solver_->searchPositionIK(goal, seed_, timeout_,
465 const_cast<const std::vector<double>&>(consistency_limits_), sol, error_code);
466 ASSERT_EQ(error_code.val, error_code.SUCCESS);
467
468 // validate reached poses
469 std::vector<geometry_msgs::msg::Pose> reached_poses;
470 kinematics_solver_->getPositionFK(tip_frames, sol, reached_poses);
471 EXPECT_NEAR_POSES({ goal }, reached_poses, tolerance_);
472
473 // validate ground truth
474 if (!truth.empty())
475 {
476 ASSERT_EQ(truth.size(), sol.size()) << "Invalid size of ground truth joints vector";
477 Eigen::Map<Eigen::ArrayXd> solution(sol.data(), sol.size());
478 Eigen::Map<Eigen::ArrayXd> ground_truth(truth.data(), truth.size());
479 EXPECT_TRUE(solution.isApprox(ground_truth, 10 * tolerance_)) << solution.transpose() << '\n'
480 << ground_truth.transpose() << '\n';
481 }
482 };
483
484 std::vector<double> ground_truth, pose_values;
485 constexpr char pose_type_relative[] = "relative";
486 constexpr char pose_type_absolute[] = "absolute";
487
488 // process tests definitions on parameter server of the form
489 // pose_1:
490 // pose: [0.3, 0.2, 0.1, 0, 0, 0] // xyzrpy (position + euler angles)
491 // joints: [0, 0, 0, 0, 0, 0] // ground truth solution
492 // type: relative // pose applied relative to current pose
493 // pose_2:
494 // pose: [0.1, 0.2, 0.3, 0, 0, 0, 0] // xyzwxyz (position + quaternion)
495 // joints: [1, 2, 3, 4, 5, 6] // pose applied absolute in planning frame
496 // type: absolute
497
498 for (size_t i = 0; i < expected_test_poses; ++i) // NOLINT(modernize-loop-convert)
499 {
500 const std::string pose_name = "pose_" + std::to_string(i);
501 const std::string pose_param = TEST_POSES_PARAM + "." + pose_name; // NOLINT
502 goal = initial; // reset goal to initial
503 ground_truth.clear();
504
505 node_->get_parameter_or(pose_param + ".joints", ground_truth, ground_truth);
506 if (!ground_truth.empty())
507 {
508 ASSERT_EQ(ground_truth.size(), joints_.size())
509 << "Test pose '" << pose_name << "' has invalid 'joints' vector size";
510 }
511
512 pose_values.clear();
513 node_->get_parameter_or(pose_param + ".pose", pose_values, pose_values);
514 ASSERT_TRUE(pose_values.size() == 6 || pose_values.size() == 7)
515 << "Test pose '" << pose_name << "' has invalid 'pose' vector size";
516
517 Eigen::Isometry3d pose;
518 ASSERT_TRUE(parsePose(pose_values, pose)) << "Failed to parse 'pose' vector in: " << pose_name;
519 std::string pose_type = "pose_type_relative";
520 node_->get_parameter_or(pose_param + ".type", pose_type, pose_type);
521 if (pose_type == pose_type_relative)
522 {
523 goal = goal * pose;
524 }
525 else if (pose_type == pose_type_absolute)
526 {
527 goal = pose;
528 }
529 else
530 {
531 FAIL() << "Found invalid 'type' in " << pose_name << ": should be one of '" << pose_type_relative << "' or '"
532 << pose_type_absolute << '\'';
533 }
534
535 std::string desc;
536 {
537 SCOPED_TRACE(desc);
538 validate_ik(tf2::toMsg(goal), ground_truth);
539 }
540 }
541}
542
544{
545 std::vector<double> seed, fk_values, solution;
546 moveit_msgs::msg::MoveItErrorCodes error_code;
547 solution.resize(kinematics_solver_->getJointNames().size(), 0.0);
548 const std::vector<std::string>& fk_names = kinematics_solver_->getTipFrames();
549 moveit::core::RobotState robot_state(robot_model_);
550 robot_state.setToDefaultValues();
551
552 unsigned int success = 0;
553 for (unsigned int i = 0; i < num_ik_tests_; ++i)
554 {
555 seed.resize(kinematics_solver_->getJointNames().size(), 0.0);
556 fk_values.resize(kinematics_solver_->getJointNames().size(), 0.0);
557 robot_state.setToRandomPositions(jmg_, this->rng_);
558 robot_state.copyJointGroupPositions(jmg_, fk_values);
559 std::vector<geometry_msgs::msg::Pose> poses;
560 ASSERT_TRUE(kinematics_solver_->getPositionFK(fk_names, fk_values, poses));
561
562 kinematics_solver_->searchPositionIK(poses[0], seed, timeout_, solution, error_code);
563 if (error_code.val == error_code.SUCCESS)
564 {
565 success++;
566 }
567 else
568 {
569 continue;
570 }
571
572 std::vector<geometry_msgs::msg::Pose> reached_poses;
573 kinematics_solver_->getPositionFK(fk_names, solution, reached_poses);
574 EXPECT_NEAR_POSES(poses, reached_poses, tolerance_);
575 }
576
577 if (num_ik_cb_tests_ > 0)
578 {
579 RCLCPP_INFO_STREAM(getLogger(), "Success Rate: " << static_cast<double>(success) / num_ik_tests_);
580 }
581 EXPECT_GE(success, EXPECTED_SUCCESS_RATE * num_ik_tests_);
582}
583
584TEST_F(KinematicsTest, searchIKWithCallback)
585{
586 std::vector<double> seed, fk_values, solution;
587 moveit_msgs::msg::MoveItErrorCodes error_code;
588 solution.resize(kinematics_solver_->getJointNames().size(), 0.0);
589 const std::vector<std::string>& fk_names = kinematics_solver_->getTipFrames();
590 moveit::core::RobotState robot_state(robot_model_);
591 robot_state.setToDefaultValues();
592
593 unsigned int success = 0;
594 for (unsigned int i = 0; i < num_ik_cb_tests_; ++i)
595 {
596 seed.resize(kinematics_solver_->getJointNames().size(), 0.0);
597 fk_values.resize(kinematics_solver_->getJointNames().size(), 0.0);
598 robot_state.setToRandomPositions(jmg_, this->rng_);
599 robot_state.copyJointGroupPositions(jmg_, fk_values);
600 std::vector<geometry_msgs::msg::Pose> poses;
601 ASSERT_TRUE(kinematics_solver_->getPositionFK(fk_names, fk_values, poses));
602 if (poses[0].position.z <= 0.0f)
603 {
604 --i; // draw a new random state
605 continue;
606 }
607
608 kinematics_solver_->searchPositionIK(
609 poses[0], fk_values, timeout_, solution,
610 [this](const geometry_msgs::msg::Pose&, const std::vector<double>& joints,
611 moveit_msgs::msg::MoveItErrorCodes& error_code) { searchIKCallback(joints, error_code); },
612 error_code);
613 if (error_code.val == error_code.SUCCESS)
614 {
615 success++;
616 }
617 else
618 {
619 continue;
620 }
621
622 std::vector<geometry_msgs::msg::Pose> reached_poses;
623 kinematics_solver_->getPositionFK(fk_names, solution, reached_poses);
624 EXPECT_NEAR_POSES(poses, reached_poses, tolerance_);
625 }
626
627 if (num_ik_cb_tests_ > 0)
628 {
629 RCLCPP_INFO_STREAM(getLogger(), "Success Rate: " << static_cast<double>(success) / num_ik_cb_tests_);
630 }
631 EXPECT_GE(success, EXPECTED_SUCCESS_RATE * num_ik_cb_tests_);
632}
633
635{
636 std::vector<double> fk_values, solution;
637 moveit_msgs::msg::MoveItErrorCodes error_code;
638 solution.resize(kinematics_solver_->getJointNames().size(), 0.0);
639 const std::vector<std::string>& fk_names = kinematics_solver_->getTipFrames();
640 moveit::core::RobotState robot_state(robot_model_);
641 robot_state.setToDefaultValues();
642
643 for (unsigned int i = 0; i < num_ik_tests_; ++i)
644 {
645 fk_values.resize(kinematics_solver_->getJointNames().size(), 0.0);
646 robot_state.setToRandomPositions(jmg_, this->rng_);
647 robot_state.copyJointGroupPositions(jmg_, fk_values);
648 std::vector<geometry_msgs::msg::Pose> poses;
649
650 ASSERT_TRUE(kinematics_solver_->getPositionFK(fk_names, fk_values, poses));
651 kinematics_solver_->getPositionIK(poses[0], fk_values, solution, error_code);
652 // starting from the correct solution, should yield the same pose
653 EXPECT_EQ(error_code.val, error_code.SUCCESS);
654
655 Eigen::Map<Eigen::ArrayXd> sol(solution.data(), solution.size());
656 Eigen::Map<Eigen::ArrayXd> truth(fk_values.data(), fk_values.size());
657 EXPECT_TRUE(sol.isApprox(truth, tolerance_)) << sol.transpose() << '\n' << truth.transpose() << '\n';
658 }
659}
660
661TEST_F(KinematicsTest, getIKMultipleSolutions)
662{
663 std::vector<double> seed, fk_values;
664 std::vector<std::vector<double>> solutions;
667
668 const std::vector<std::string>& fk_names = kinematics_solver_->getTipFrames();
669 moveit::core::RobotState robot_state(robot_model_);
670 robot_state.setToDefaultValues();
671
672 unsigned int success = 0;
673 for (unsigned int i = 0; i < num_ik_multiple_tests_; ++i)
674 {
675 seed.resize(kinematics_solver_->getJointNames().size(), 0.0);
676 fk_values.resize(kinematics_solver_->getJointNames().size(), 0.0);
677 robot_state.setToRandomPositions(jmg_, this->rng_);
678 robot_state.copyJointGroupPositions(jmg_, fk_values);
679 std::vector<geometry_msgs::msg::Pose> poses;
680 ASSERT_TRUE(kinematics_solver_->getPositionFK(fk_names, fk_values, poses));
681
682 solutions.clear();
683 kinematics_solver_->getPositionIK(poses, fk_values, solutions, result, options);
684
686 {
687 success += solutions.empty() ? 0 : 1;
688 }
689 else
690 {
691 continue;
692 }
693
694 std::vector<geometry_msgs::msg::Pose> reached_poses;
695 for (const auto& s : solutions)
696 {
697 kinematics_solver_->getPositionFK(fk_names, s, reached_poses);
698 EXPECT_NEAR_POSES(poses, reached_poses, tolerance_);
699 }
700 }
701
702 if (num_ik_cb_tests_ > 0)
703 {
704 RCLCPP_INFO_STREAM(getLogger(), "Success Rate: " << static_cast<double>(success) / num_ik_multiple_tests_);
705 }
706 EXPECT_GE(success, EXPECTED_SUCCESS_RATE * num_ik_multiple_tests_);
707}
708
709// validate that getPositionIK() retrieves closest solution to seed
710TEST_F(KinematicsTest, getNearestIKSolution)
711{
712 std::vector<std::vector<double>> solutions;
715
716 std::vector<double> seed, fk_values, solution;
717 moveit_msgs::msg::MoveItErrorCodes error_code;
718 const std::vector<std::string>& fk_names = kinematics_solver_->getTipFrames();
719 moveit::core::RobotState robot_state(robot_model_);
720 robot_state.setToDefaultValues();
721
722 for (unsigned int i = 0; i < num_nearest_ik_tests_; ++i)
723 {
724 robot_state.setToRandomPositions(jmg_, this->rng_);
725 robot_state.copyJointGroupPositions(jmg_, fk_values);
726 std::vector<geometry_msgs::msg::Pose> poses;
727 ASSERT_TRUE(kinematics_solver_->getPositionFK(fk_names, fk_values, poses));
728
729 // sample seed vector
730 robot_state.setToRandomPositions(jmg_, this->rng_);
731 robot_state.copyJointGroupPositions(jmg_, seed);
732
733 // getPositionIK for single solution
734 kinematics_solver_->getPositionIK(poses[0], seed, solution, error_code);
735
736 // check if getPositionIK call for single solution returns solution
737 if (error_code.val != error_code.SUCCESS)
738 continue;
739
740 const Eigen::Map<const Eigen::VectorXd> seed_eigen(seed.data(), seed.size());
741 double error_get_ik =
742 (Eigen::Map<const Eigen::VectorXd>(solution.data(), solution.size()) - seed_eigen).array().abs().sum();
743
744 // getPositionIK for multiple solutions
745 solutions.clear();
746 kinematics_solver_->getPositionIK(poses, seed, solutions, result, options);
747
748 // check if getPositionIK call for multiple solutions returns solution
750 << "Multiple solution call failed, while single solution call succeeded";
752 continue;
753
754 double smallest_error_multiple_ik = std::numeric_limits<double>::max();
755 for (const auto& s : solutions)
756 {
757 double error_multiple_ik =
758 (Eigen::Map<const Eigen::VectorXd>(s.data(), s.size()) - seed_eigen).array().abs().sum();
759 if (error_multiple_ik <= smallest_error_multiple_ik)
760 smallest_error_multiple_ik = error_multiple_ik;
761 }
762 EXPECT_NEAR(smallest_error_multiple_ik, error_get_ik, tolerance_);
763 }
764}
765
766int main(int argc, char** argv)
767{
768 testing::InitGoogleTest(&argc, argv);
769 rclcpp::init(argc, argv);
770 int result = RUN_ALL_TESTS();
771 SharedData::release(); // avoid class_loader::LibraryUnloadException
772 return result;
773}
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.
Definition robot_state.h:90
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)