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;
67
68// As loading of parameters is quite slow, we share them across all tests
69class SharedData
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
93 SharedData()
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::executors::SingleThreadedExecutor executor;
407 executor.add_node(node_);
408 executor.spin_some();
409 }
410}
411
412static bool parsePose(const std::vector<double>& pose_values, Eigen::Isometry3d& goal)
413{
414 std::vector<double> vec;
415 Eigen::Quaterniond q;
416 if (pose_values.size() == 6)
417 {
418 q = Eigen::AngleAxisd(pose_values[3], Eigen::Vector3d::UnitX()) *
419 Eigen::AngleAxisd(pose_values[4], Eigen::Vector3d::UnitY()) *
420 Eigen::AngleAxisd(pose_values[5], Eigen::Vector3d::UnitZ());
421 }
422 else if (pose_values.size() == 7)
423 {
424 q = Eigen::Quaterniond(pose_values[3], pose_values[4], pose_values[5], pose_values[6]);
425 }
426 else
427 {
428 return false;
429 }
430
431 goal = q;
432 goal.translation() = Eigen::Vector3d(pose_values[0], pose_values[1], pose_values[2]);
433
434 return true;
435}
436
438{
439 static const std::string TEST_POSES_PARAM = "unit_test_poses";
440 size_t expected_test_poses = 0;
441 node_->get_parameter_or(TEST_POSES_PARAM + ".size", expected_test_poses, expected_test_poses);
442
443 std::vector<double> sol;
444 const std::vector<std::string>& tip_frames = kinematics_solver_->getTipFrames();
445 moveit::core::RobotState robot_state(robot_model_);
446 robot_state.setToDefaultValues();
447 robot_state.setJointGroupPositions(jmg_, seed_);
448
449 // compute initial end-effector pose
450 std::vector<geometry_msgs::msg::Pose> poses;
451 ASSERT_TRUE(kinematics_solver_->getPositionFK(tip_frames, seed_, poses));
452 Eigen::Isometry3d initial, goal;
453 tf2::fromMsg(poses[0], initial);
454
455 RCLCPP_DEBUG(getLogger(), "Initial: %f %f %f %f %f %f %f\n", poses[0].position.x, poses[0].position.y,
456 poses[0].position.z, poses[0].orientation.x, poses[0].orientation.y, poses[0].orientation.z,
457 poses[0].orientation.w);
458
459 auto validate_ik = [&](const geometry_msgs::msg::Pose& goal, std::vector<double>& truth) {
460 // compute IK
461 moveit_msgs::msg::MoveItErrorCodes error_code;
462
463 RCLCPP_DEBUG(getLogger(), "Goal %f %f %f %f %f %f %f\n", goal.position.x, goal.position.y, goal.position.z,
464 goal.orientation.x, goal.orientation.y, goal.orientation.z, goal.orientation.w);
465
466 kinematics_solver_->searchPositionIK(goal, seed_, timeout_,
467 const_cast<const std::vector<double>&>(consistency_limits_), sol, error_code);
468 ASSERT_EQ(error_code.val, error_code.SUCCESS);
469
470 // validate reached poses
471 std::vector<geometry_msgs::msg::Pose> reached_poses;
472 kinematics_solver_->getPositionFK(tip_frames, sol, reached_poses);
473 EXPECT_NEAR_POSES({ goal }, reached_poses, tolerance_);
474
475 // validate ground truth
476 if (!truth.empty())
477 {
478 ASSERT_EQ(truth.size(), sol.size()) << "Invalid size of ground truth joints vector";
479 Eigen::Map<Eigen::ArrayXd> solution(sol.data(), sol.size());
480 Eigen::Map<Eigen::ArrayXd> ground_truth(truth.data(), truth.size());
481 EXPECT_TRUE(solution.isApprox(ground_truth, 10 * tolerance_)) << solution.transpose() << '\n'
482 << ground_truth.transpose() << '\n';
483 }
484 };
485
486 std::vector<double> ground_truth, pose_values;
487 constexpr char pose_type_relative[] = "relative";
488 constexpr char pose_type_absolute[] = "absolute";
489
490 // process tests definitions on parameter server of the form
491 // pose_1:
492 // pose: [0.3, 0.2, 0.1, 0, 0, 0] // xyzrpy (position + euler angles)
493 // joints: [0, 0, 0, 0, 0, 0] // ground truth solution
494 // type: relative // pose applied relative to current pose
495 // pose_2:
496 // pose: [0.1, 0.2, 0.3, 0, 0, 0, 0] // xyzwxyz (position + quaternion)
497 // joints: [1, 2, 3, 4, 5, 6] // pose applied absolute in planning frame
498 // type: absolute
499
500 for (size_t i = 0; i < expected_test_poses; ++i) // NOLINT(modernize-loop-convert)
501 {
502 const std::string pose_name = "pose_" + std::to_string(i);
503 const std::string pose_param = TEST_POSES_PARAM + "." + pose_name; // NOLINT
504 goal = initial; // reset goal to initial
505 ground_truth.clear();
506
507 node_->get_parameter_or(pose_param + ".joints", ground_truth, ground_truth);
508 if (!ground_truth.empty())
509 {
510 ASSERT_EQ(ground_truth.size(), joints_.size())
511 << "Test pose '" << pose_name << "' has invalid 'joints' vector size";
512 }
513
514 pose_values.clear();
515 node_->get_parameter_or(pose_param + ".pose", pose_values, pose_values);
516 ASSERT_TRUE(pose_values.size() == 6 || pose_values.size() == 7)
517 << "Test pose '" << pose_name << "' has invalid 'pose' vector size";
518
519 Eigen::Isometry3d pose;
520 ASSERT_TRUE(parsePose(pose_values, pose)) << "Failed to parse 'pose' vector in: " << pose_name;
521 std::string pose_type = "pose_type_relative";
522 node_->get_parameter_or(pose_param + ".type", pose_type, pose_type);
523 if (pose_type == pose_type_relative)
524 {
525 goal = goal * pose;
526 }
527 else if (pose_type == pose_type_absolute)
528 {
529 goal = pose;
530 }
531 else
532 {
533 FAIL() << "Found invalid 'type' in " << pose_name << ": should be one of '" << pose_type_relative << "' or '"
534 << pose_type_absolute << '\'';
535 }
536
537 std::string desc;
538 {
539 SCOPED_TRACE(desc);
540 validate_ik(tf2::toMsg(goal), ground_truth);
541 }
542 }
543}
544
546{
547 std::vector<double> seed, fk_values, solution;
548 moveit_msgs::msg::MoveItErrorCodes error_code;
549 solution.resize(kinematics_solver_->getJointNames().size(), 0.0);
550 const std::vector<std::string>& fk_names = kinematics_solver_->getTipFrames();
551 moveit::core::RobotState robot_state(robot_model_);
552 robot_state.setToDefaultValues();
553
554 unsigned int success = 0;
555 for (unsigned int i = 0; i < num_ik_tests_; ++i)
556 {
557 seed.resize(kinematics_solver_->getJointNames().size(), 0.0);
558 fk_values.resize(kinematics_solver_->getJointNames().size(), 0.0);
559 robot_state.setToRandomPositions(jmg_, this->rng_);
560 robot_state.copyJointGroupPositions(jmg_, fk_values);
561 std::vector<geometry_msgs::msg::Pose> poses;
562 ASSERT_TRUE(kinematics_solver_->getPositionFK(fk_names, fk_values, poses));
563
564 kinematics_solver_->searchPositionIK(poses[0], seed, timeout_, solution, error_code);
565 if (error_code.val == error_code.SUCCESS)
566 {
567 success++;
568 }
569 else
570 {
571 continue;
572 }
573
574 std::vector<geometry_msgs::msg::Pose> reached_poses;
575 kinematics_solver_->getPositionFK(fk_names, solution, reached_poses);
576 EXPECT_NEAR_POSES(poses, reached_poses, tolerance_);
577 }
578
579 if (num_ik_cb_tests_ > 0)
580 {
581 RCLCPP_INFO_STREAM(getLogger(), "Success Rate: " << static_cast<double>(success) / num_ik_tests_);
582 }
583 EXPECT_GE(success, EXPECTED_SUCCESS_RATE * num_ik_tests_);
584}
585
586TEST_F(KinematicsTest, searchIKWithCallback)
587{
588 std::vector<double> seed, fk_values, solution;
589 moveit_msgs::msg::MoveItErrorCodes error_code;
590 solution.resize(kinematics_solver_->getJointNames().size(), 0.0);
591 const std::vector<std::string>& fk_names = kinematics_solver_->getTipFrames();
592 moveit::core::RobotState robot_state(robot_model_);
593 robot_state.setToDefaultValues();
594
595 unsigned int success = 0;
596 for (unsigned int i = 0; i < num_ik_cb_tests_; ++i)
597 {
598 seed.resize(kinematics_solver_->getJointNames().size(), 0.0);
599 fk_values.resize(kinematics_solver_->getJointNames().size(), 0.0);
600 robot_state.setToRandomPositions(jmg_, this->rng_);
601 robot_state.copyJointGroupPositions(jmg_, fk_values);
602 std::vector<geometry_msgs::msg::Pose> poses;
603 ASSERT_TRUE(kinematics_solver_->getPositionFK(fk_names, fk_values, poses));
604 if (poses[0].position.z <= 0.0f)
605 {
606 --i; // draw a new random state
607 continue;
608 }
609
610 kinematics_solver_->searchPositionIK(
611 poses[0], fk_values, timeout_, solution,
612 [this](const geometry_msgs::msg::Pose&, const std::vector<double>& joints,
613 moveit_msgs::msg::MoveItErrorCodes& error_code) { searchIKCallback(joints, error_code); },
614 error_code);
615 if (error_code.val == error_code.SUCCESS)
616 {
617 success++;
618 }
619 else
620 {
621 continue;
622 }
623
624 std::vector<geometry_msgs::msg::Pose> reached_poses;
625 kinematics_solver_->getPositionFK(fk_names, solution, reached_poses);
626 EXPECT_NEAR_POSES(poses, reached_poses, tolerance_);
627 }
628
629 if (num_ik_cb_tests_ > 0)
630 {
631 RCLCPP_INFO_STREAM(getLogger(), "Success Rate: " << static_cast<double>(success) / num_ik_cb_tests_);
632 }
633 EXPECT_GE(success, EXPECTED_SUCCESS_RATE * num_ik_cb_tests_);
634}
635
637{
638 std::vector<double> fk_values, solution;
639 moveit_msgs::msg::MoveItErrorCodes error_code;
640 solution.resize(kinematics_solver_->getJointNames().size(), 0.0);
641 const std::vector<std::string>& fk_names = kinematics_solver_->getTipFrames();
642 moveit::core::RobotState robot_state(robot_model_);
643 robot_state.setToDefaultValues();
644
645 for (unsigned int i = 0; i < num_ik_tests_; ++i)
646 {
647 fk_values.resize(kinematics_solver_->getJointNames().size(), 0.0);
648 robot_state.setToRandomPositions(jmg_, this->rng_);
649 robot_state.copyJointGroupPositions(jmg_, fk_values);
650 std::vector<geometry_msgs::msg::Pose> poses;
651
652 ASSERT_TRUE(kinematics_solver_->getPositionFK(fk_names, fk_values, poses));
653 kinematics_solver_->getPositionIK(poses[0], fk_values, solution, error_code);
654 // starting from the correct solution, should yield the same pose
655 EXPECT_EQ(error_code.val, error_code.SUCCESS);
656
657 Eigen::Map<Eigen::ArrayXd> sol(solution.data(), solution.size());
658 Eigen::Map<Eigen::ArrayXd> truth(fk_values.data(), fk_values.size());
659 EXPECT_TRUE(sol.isApprox(truth, tolerance_)) << sol.transpose() << '\n' << truth.transpose() << '\n';
660 }
661}
662
663TEST_F(KinematicsTest, getIKMultipleSolutions)
664{
665 std::vector<double> seed, fk_values;
666 std::vector<std::vector<double>> solutions;
669
670 const std::vector<std::string>& fk_names = kinematics_solver_->getTipFrames();
671 moveit::core::RobotState robot_state(robot_model_);
672 robot_state.setToDefaultValues();
673
674 unsigned int success = 0;
675 for (unsigned int i = 0; i < num_ik_multiple_tests_; ++i)
676 {
677 seed.resize(kinematics_solver_->getJointNames().size(), 0.0);
678 fk_values.resize(kinematics_solver_->getJointNames().size(), 0.0);
679 robot_state.setToRandomPositions(jmg_, this->rng_);
680 robot_state.copyJointGroupPositions(jmg_, fk_values);
681 std::vector<geometry_msgs::msg::Pose> poses;
682 ASSERT_TRUE(kinematics_solver_->getPositionFK(fk_names, fk_values, poses));
683
684 solutions.clear();
685 kinematics_solver_->getPositionIK(poses, fk_values, solutions, result, options);
686
688 {
689 success += solutions.empty() ? 0 : 1;
690 }
691 else
692 {
693 continue;
694 }
695
696 std::vector<geometry_msgs::msg::Pose> reached_poses;
697 for (const auto& s : solutions)
698 {
699 kinematics_solver_->getPositionFK(fk_names, s, reached_poses);
700 EXPECT_NEAR_POSES(poses, reached_poses, tolerance_);
701 }
702 }
703
704 if (num_ik_cb_tests_ > 0)
705 {
706 RCLCPP_INFO_STREAM(getLogger(), "Success Rate: " << static_cast<double>(success) / num_ik_multiple_tests_);
707 }
708 EXPECT_GE(success, EXPECTED_SUCCESS_RATE * num_ik_multiple_tests_);
709}
710
711// validate that getPositionIK() retrieves closest solution to seed
712TEST_F(KinematicsTest, getNearestIKSolution)
713{
714 std::vector<std::vector<double>> solutions;
717
718 std::vector<double> seed, fk_values, solution;
719 moveit_msgs::msg::MoveItErrorCodes error_code;
720 const std::vector<std::string>& fk_names = kinematics_solver_->getTipFrames();
721 moveit::core::RobotState robot_state(robot_model_);
722 robot_state.setToDefaultValues();
723
724 for (unsigned int i = 0; i < num_nearest_ik_tests_; ++i)
725 {
726 robot_state.setToRandomPositions(jmg_, this->rng_);
727 robot_state.copyJointGroupPositions(jmg_, fk_values);
728 std::vector<geometry_msgs::msg::Pose> poses;
729 ASSERT_TRUE(kinematics_solver_->getPositionFK(fk_names, fk_values, poses));
730
731 // sample seed vector
732 robot_state.setToRandomPositions(jmg_, this->rng_);
733 robot_state.copyJointGroupPositions(jmg_, seed);
734
735 // getPositionIK for single solution
736 kinematics_solver_->getPositionIK(poses[0], seed, solution, error_code);
737
738 // check if getPositionIK call for single solution returns solution
739 if (error_code.val != error_code.SUCCESS)
740 continue;
741
742 const Eigen::Map<const Eigen::VectorXd> seed_eigen(seed.data(), seed.size());
743 double error_get_ik =
744 (Eigen::Map<const Eigen::VectorXd>(solution.data(), solution.size()) - seed_eigen).array().abs().sum();
745
746 // getPositionIK for multiple solutions
747 solutions.clear();
748 kinematics_solver_->getPositionIK(poses, seed, solutions, result, options);
749
750 // check if getPositionIK call for multiple solutions returns solution
752 << "Multiple solution call failed, while single solution call succeeded";
754 continue;
755
756 double smallest_error_multiple_ik = std::numeric_limits<double>::max();
757 for (const auto& s : solutions)
758 {
759 double error_multiple_ik =
760 (Eigen::Map<const Eigen::VectorXd>(s.data(), s.size()) - seed_eigen).array().abs().sum();
761 if (error_multiple_ik <= smallest_error_multiple_ik)
762 smallest_error_multiple_ik = error_multiple_ik;
763 }
764 EXPECT_NEAR(smallest_error_multiple_ik, error_get_ik, tolerance_);
765 }
766}
767
768int main(int argc, char** argv)
769{
770 testing::InitGoogleTest(&argc, argv);
771 rclcpp::init(argc, argv);
772 int result = RUN_ALL_TESTS();
773 SharedData::release(); // avoid class_loader::LibraryUnloadException
774 return result;
775}
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_
friend class KinematicsTest
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:106
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)