moveit2
The MoveIt Motion Planning Framework for ROS 2.
ruckig_traj_smoothing.cpp
Go to the documentation of this file.
1 /*******************************************************************************
2  * BSD 3-Clause License
3  *
4  * Copyright (c) 2021, PickNik Robotics
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions are met:
9  *
10  * * Redistributions of source code must retain the above copyright notice, this
11  * list of conditions and the following disclaimer.
12  *
13  * * Redistributions in binary form must reproduce the above copyright notice,
14  * this list of conditions and the following disclaimer in the documentation
15  * and/or other materials provided with the distribution.
16  *
17  * * Neither the name of the copyright holder nor the names of its
18  * contributors may be used to endorse or promote products derived from
19  * this software without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE
25  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
28  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32  *******************************************************************************/
33 
34 /* Author: Jack Center, Wyatt Rees, Andy Zelenak, Stephanie Eng */
35 
36 #include <rclcpp/logger.hpp>
37 #include <rclcpp/logging.hpp>
38 #include <algorithm>
39 #include <cmath>
40 #include <Eigen/Geometry>
41 #include <limits>
43 #include <vector>
44 
45 namespace trajectory_processing
46 {
47 namespace
48 {
49 const rclcpp::Logger LOGGER = rclcpp::get_logger("moveit_trajectory_processing.ruckig_traj_smoothing");
50 constexpr double DEFAULT_MAX_VELOCITY = 5; // rad/s
51 constexpr double DEFAULT_MAX_ACCELERATION = 10; // rad/s^2
52 constexpr double DEFAULT_MAX_JERK = 1000; // rad/s^3
53 constexpr double MAX_DURATION_EXTENSION_FACTOR = 10.0;
54 constexpr double DURATION_EXTENSION_FRACTION = 1.1;
55 } // namespace
56 
58  const double max_velocity_scaling_factor,
59  const double max_acceleration_scaling_factor)
60 {
61  if (!validateGroup(trajectory))
62  {
63  return false;
64  }
65 
66  const size_t num_waypoints = trajectory.getWayPointCount();
67  if (num_waypoints < 2)
68  {
69  RCLCPP_WARN(LOGGER,
70  "Trajectory does not have enough points to smooth with Ruckig. Returning an unmodified trajectory.");
71  return true;
72  }
73 
74  // Kinematic limits (vels/accels/jerks) from RobotModel
75  moveit::core::JointModelGroup const* const group = trajectory.getGroup();
76  const size_t num_dof = group->getVariableCount();
77  ruckig::InputParameter<ruckig::DynamicDOFs> ruckig_input{ num_dof };
78  if (!getRobotModelBounds(max_velocity_scaling_factor, max_acceleration_scaling_factor, group, ruckig_input))
79  {
80  RCLCPP_ERROR(LOGGER, "Error while retrieving kinematic limits (vel/accel/jerk) from RobotModel.");
81  return false;
82  }
83 
84  return runRuckig(trajectory, ruckig_input);
85 }
86 
88  const std::unordered_map<std::string, double>& velocity_limits,
89  const std::unordered_map<std::string, double>& acceleration_limits,
90  const std::unordered_map<std::string, double>& jerk_limits)
91 {
92  if (!validateGroup(trajectory))
93  {
94  return false;
95  }
96 
97  const size_t num_waypoints = trajectory.getWayPointCount();
98  if (num_waypoints < 2)
99  {
100  RCLCPP_WARN(LOGGER,
101  "Trajectory does not have enough points to smooth with Ruckig. Returning an unmodified trajectory.");
102  return true;
103  }
104 
105  // Set default kinematic limits (vels/accels/jerks)
106  moveit::core::JointModelGroup const* const group = trajectory.getGroup();
107  const size_t num_dof = group->getVariableCount();
108  ruckig::InputParameter<ruckig::DynamicDOFs> ruckig_input{ num_dof };
109  double max_velocity_scaling_factor = 1.0;
110  double max_acceleration_scaling_factor = 1.0;
111  if (!getRobotModelBounds(max_velocity_scaling_factor, max_acceleration_scaling_factor, group, ruckig_input))
112  {
113  RCLCPP_ERROR(LOGGER, "Error while retrieving kinematic limits (vel/accel/jerk) from RobotModel.");
114  return false;
115  }
116 
117  // Check if custom limits were supplied as arguments to overwrite the defaults
118  const std::vector<std::string>& vars = group->getVariableNames();
119  const unsigned num_joints = group->getVariableCount();
120  for (size_t j = 0; j < num_joints; ++j)
121  {
122  // Velocity
123  auto it = velocity_limits.find(vars[j]);
124  if (it != velocity_limits.end())
125  {
126  ruckig_input.max_velocity.at(j) = it->second;
127  }
128  // Acceleration
129  it = acceleration_limits.find(vars[j]);
130  if (it != acceleration_limits.end())
131  {
132  ruckig_input.max_acceleration.at(j) = it->second;
133  }
134  // Jerk
135  it = jerk_limits.find(vars[j]);
136  if (it != jerk_limits.end())
137  {
138  ruckig_input.max_jerk.at(j) = it->second;
139  }
140  }
141 
142  return runRuckig(trajectory, ruckig_input);
143 }
144 
145 bool RuckigSmoothing::validateGroup(const robot_trajectory::RobotTrajectory& trajectory)
146 {
147  moveit::core::JointModelGroup const* const group = trajectory.getGroup();
148  if (!group)
149  {
150  RCLCPP_ERROR(LOGGER, "The planner did not set the group the plan was computed for");
151  return false;
152  }
153  return true;
154 }
155 
156 bool RuckigSmoothing::getRobotModelBounds(const double max_velocity_scaling_factor,
157  const double max_acceleration_scaling_factor,
159  ruckig::InputParameter<ruckig::DynamicDOFs>& ruckig_input)
160 {
161  const size_t num_dof = group->getVariableCount();
162  const std::vector<std::string>& vars = group->getVariableNames();
163  const moveit::core::RobotModel& rmodel = group->getParentModel();
164  for (size_t i = 0; i < num_dof; ++i)
165  {
166  const moveit::core::VariableBounds& bounds = rmodel.getVariableBounds(vars.at(i));
167 
168  // This assumes min/max bounds are symmetric
169  if (bounds.velocity_bounded_)
170  {
171  ruckig_input.max_velocity.at(i) = max_velocity_scaling_factor * bounds.max_velocity_;
172  }
173  else
174  {
175  RCLCPP_WARN_STREAM_ONCE(LOGGER,
176  "Joint velocity limits are not defined. Using the default "
177  << DEFAULT_MAX_VELOCITY
178  << " rad/s. You can define velocity limits in the URDF or joint_limits.yaml.");
179  ruckig_input.max_velocity.at(i) = max_velocity_scaling_factor * DEFAULT_MAX_VELOCITY;
180  }
181  if (bounds.acceleration_bounded_)
182  {
183  ruckig_input.max_acceleration.at(i) = max_acceleration_scaling_factor * bounds.max_acceleration_;
184  }
185  else
186  {
187  RCLCPP_WARN_STREAM_ONCE(LOGGER,
188  "Joint acceleration limits are not defined. Using the default "
189  << DEFAULT_MAX_ACCELERATION
190  << " rad/s^2. You can define acceleration limits in the URDF or joint_limits.yaml.");
191  ruckig_input.max_acceleration.at(i) = max_acceleration_scaling_factor * DEFAULT_MAX_ACCELERATION;
192  }
193  ruckig_input.max_jerk.at(i) = bounds.jerk_bounded_ ? bounds.max_jerk_ : DEFAULT_MAX_JERK;
194  if (bounds.jerk_bounded_)
195  {
196  ruckig_input.max_jerk.at(i) = bounds.max_jerk_;
197  }
198  else
199  {
200  RCLCPP_WARN_STREAM_ONCE(LOGGER, "Joint jerk limits are not defined. Using the default "
201  << DEFAULT_MAX_JERK
202  << " rad/s^3. You can define jerk limits in joint_limits.yaml.");
203  ruckig_input.max_jerk.at(i) = DEFAULT_MAX_JERK;
204  }
205  }
206 
207  return true;
208 }
209 
210 bool RuckigSmoothing::runRuckig(robot_trajectory::RobotTrajectory& trajectory,
211  ruckig::InputParameter<ruckig::DynamicDOFs>& ruckig_input)
212 {
213  const size_t num_waypoints = trajectory.getWayPointCount();
214  moveit::core::JointModelGroup const* const group = trajectory.getGroup();
215  const size_t num_dof = group->getVariableCount();
216  ruckig::OutputParameter<ruckig::DynamicDOFs> ruckig_output{ num_dof };
217  const std::vector<int>& move_group_idx = group->getVariableIndexList();
218 
219  // This lib does not work properly when angles wrap, so we need to unwind the path first
220  trajectory.unwind();
221 
222  // Initialize the smoother
223  double timestep = trajectory.getAverageSegmentDuration();
224  std::unique_ptr<ruckig::Ruckig<ruckig::DynamicDOFs>> ruckig_ptr;
225  ruckig_ptr = std::make_unique<ruckig::Ruckig<ruckig::DynamicDOFs>>(num_dof, timestep);
226  initializeRuckigState(*trajectory.getFirstWayPointPtr(), group, ruckig_input, ruckig_output);
227 
228  // Cache the trajectory in case we need to reset it
229  robot_trajectory::RobotTrajectory original_trajectory =
230  robot_trajectory::RobotTrajectory(trajectory, true /* deep copy */);
231 
232  ruckig::Result ruckig_result;
233  double duration_extension_factor = 1;
234  bool smoothing_complete = false;
235  while ((duration_extension_factor < MAX_DURATION_EXTENSION_FACTOR) && !smoothing_complete)
236  {
237  for (size_t waypoint_idx = 0; waypoint_idx < num_waypoints - 1; ++waypoint_idx)
238  {
239  moveit::core::RobotStatePtr next_waypoint = trajectory.getWayPointPtr(waypoint_idx + 1);
240 
241  getNextRuckigInput(trajectory.getWayPointPtr(waypoint_idx), next_waypoint, group, ruckig_input);
242 
243  // Run Ruckig
244  ruckig_result = ruckig_ptr->update(ruckig_input, ruckig_output);
245 
246  if ((waypoint_idx == num_waypoints - 2) && ruckig_result == ruckig::Result::Finished)
247  {
248  smoothing_complete = true;
249  break;
250  }
251 
252  // Extend the trajectory duration if Ruckig could not reach the waypoint successfully
253  if (ruckig_result != ruckig::Result::Finished)
254  {
255  duration_extension_factor *= DURATION_EXTENSION_FRACTION;
256  // Reset the trajectory
257  trajectory = robot_trajectory::RobotTrajectory(original_trajectory, true /* deep copy */);
258  for (size_t time_stretch_idx = 1; time_stretch_idx < num_waypoints; ++time_stretch_idx)
259  {
261  time_stretch_idx,
262  duration_extension_factor * original_trajectory.getWayPointDurationFromPrevious(time_stretch_idx));
263  // re-calculate waypoint velocity and acceleration
264  auto target_state = trajectory.getWayPointPtr(time_stretch_idx);
265  const auto prev_state = trajectory.getWayPointPtr(time_stretch_idx - 1);
266  timestep = trajectory.getAverageSegmentDuration();
267  for (size_t joint = 0; joint < num_dof; ++joint)
268  {
269  target_state->setVariableVelocity(move_group_idx.at(joint),
270  (1 / duration_extension_factor) *
271  target_state->getVariableVelocity(move_group_idx.at(joint)));
272 
273  double prev_velocity = prev_state->getVariableVelocity(move_group_idx.at(joint));
274  double curr_velocity = target_state->getVariableVelocity(move_group_idx.at(joint));
275  target_state->setVariableAcceleration(move_group_idx.at(joint), (curr_velocity - prev_velocity) / timestep);
276  }
277  target_state->update();
278  }
279  ruckig_ptr = std::make_unique<ruckig::Ruckig<ruckig::DynamicDOFs>>(num_dof, timestep);
280  initializeRuckigState(*trajectory.getFirstWayPointPtr(), group, ruckig_input, ruckig_output);
281  // Begin the while() loop again
282  break;
283  }
284  }
285  }
286 
287  if (ruckig_result != ruckig::Result::Finished)
288  {
289  RCLCPP_ERROR_STREAM(LOGGER, "Ruckig trajectory smoothing failed. Ruckig error: " << ruckig_result);
290  return false;
291  }
292 
293  return true;
294 }
295 
296 void RuckigSmoothing::initializeRuckigState(const moveit::core::RobotState& first_waypoint,
297  const moveit::core::JointModelGroup* joint_group,
298  ruckig::InputParameter<ruckig::DynamicDOFs>& ruckig_input,
299  ruckig::OutputParameter<ruckig::DynamicDOFs>& ruckig_output)
300 {
301  const size_t num_dof = joint_group->getVariableCount();
302  const std::vector<int>& idx = joint_group->getVariableIndexList();
303 
304  std::vector<double> current_positions_vector(num_dof);
305  std::vector<double> current_velocities_vector(num_dof);
306  std::vector<double> current_accelerations_vector(num_dof);
307 
308  for (size_t i = 0; i < num_dof; ++i)
309  {
310  current_positions_vector.at(i) = first_waypoint.getVariablePosition(idx.at(i));
311  current_velocities_vector.at(i) = first_waypoint.getVariableVelocity(idx.at(i));
312  current_accelerations_vector.at(i) = first_waypoint.getVariableAcceleration(idx.at(i));
313  // Clamp velocities/accelerations in case they exceed the limit due to small numerical errors
314  current_velocities_vector.at(i) =
315  std::clamp(current_velocities_vector.at(i), -ruckig_input.max_velocity.at(i), ruckig_input.max_velocity.at(i));
316  current_accelerations_vector.at(i) = std::clamp(
317  current_accelerations_vector.at(i), -ruckig_input.max_acceleration.at(i), ruckig_input.max_acceleration.at(i));
318  }
319  std::copy_n(current_positions_vector.begin(), num_dof, ruckig_input.current_position.begin());
320  std::copy_n(current_velocities_vector.begin(), num_dof, ruckig_input.current_velocity.begin());
321  std::copy_n(current_accelerations_vector.begin(), num_dof, ruckig_input.current_acceleration.begin());
322  // Initialize output data struct
323  ruckig_output.new_position = ruckig_input.current_position;
324  ruckig_output.new_velocity = ruckig_input.current_velocity;
325  ruckig_output.new_acceleration = ruckig_input.current_acceleration;
326 }
327 
328 void RuckigSmoothing::getNextRuckigInput(const moveit::core::RobotStatePtr& current_waypoint,
329  const moveit::core::RobotStatePtr& next_waypoint,
330  const moveit::core::JointModelGroup* joint_group,
331  ruckig::InputParameter<ruckig::DynamicDOFs>& ruckig_input)
332 {
333  const size_t num_dof = joint_group->getVariableCount();
334  const std::vector<int>& idx = joint_group->getVariableIndexList();
335 
336  for (size_t joint = 0; joint < num_dof; ++joint)
337  {
338  ruckig_input.current_position.at(joint) = current_waypoint->getVariablePosition(idx.at(joint));
339  ruckig_input.current_velocity.at(joint) = current_waypoint->getVariableVelocity(idx.at(joint));
340  ruckig_input.current_acceleration.at(joint) = current_waypoint->getVariableAcceleration(idx.at(joint));
341 
342  // Target state is the next waypoint
343  ruckig_input.target_position.at(joint) = next_waypoint->getVariablePosition(idx.at(joint));
344  ruckig_input.target_velocity.at(joint) = next_waypoint->getVariableVelocity(idx.at(joint));
345  ruckig_input.target_acceleration.at(joint) = next_waypoint->getVariableAcceleration(idx.at(joint));
346 
347  // Clamp velocities/accelerations in case they exceed the limit due to small numerical errors
348  ruckig_input.current_velocity.at(joint) =
349  std::clamp(ruckig_input.current_velocity.at(joint), -ruckig_input.max_velocity.at(joint),
350  ruckig_input.max_velocity.at(joint));
351  ruckig_input.current_acceleration.at(joint) =
352  std::clamp(ruckig_input.current_acceleration.at(joint), -ruckig_input.max_acceleration.at(joint),
353  ruckig_input.max_acceleration.at(joint));
354  ruckig_input.target_velocity.at(joint) =
355  std::clamp(ruckig_input.target_velocity.at(joint), -ruckig_input.max_velocity.at(joint),
356  ruckig_input.max_velocity.at(joint));
357  ruckig_input.target_acceleration.at(joint) =
358  std::clamp(ruckig_input.target_acceleration.at(joint), -ruckig_input.max_acceleration.at(joint),
359  ruckig_input.max_acceleration.at(joint));
360  }
361 }
362 } // namespace trajectory_processing
unsigned int getVariableCount() const
Get the number of variables that describe this joint group. This includes variables necessary for mim...
const std::vector< int > & getVariableIndexList() const
Get the index locations in the complete robot state for all the variables in this group.
Definition of a kinematic model. This class is not thread safe, however multiple instances can be cre...
Definition: robot_model.h:76
const VariableBounds & getVariableBounds(const std::string &variable) const
Get the bounds for a specific variable. Throw an exception of variable is not found.
Definition: robot_model.h:433
Representation of a robot's state. This includes position, velocity, acceleration and effort.
Definition: robot_state.h:90
double getVariableAcceleration(const std::string &variable) const
Get the acceleration of a particular variable. An exception is thrown if the variable is not known.
Definition: robot_state.h:395
double getVariableVelocity(const std::string &variable) const
Get the velocity of a particular variable. An exception is thrown if the variable is not known.
Definition: robot_state.h:296
double getVariablePosition(const std::string &variable) const
Get the position of a particular variable. An exception is thrown if the variable is not known.
Definition: robot_state.h:207
Maintain a sequence of waypoints and the time durations between these waypoints.
RobotTrajectory & setWayPointDurationFromPrevious(std::size_t index, double value)
const moveit::core::JointModelGroup * getGroup() const
moveit::core::RobotStatePtr & getWayPointPtr(std::size_t index)
double getWayPointDurationFromPrevious(std::size_t index) const
moveit::core::RobotStatePtr & getFirstWayPointPtr()
static bool applySmoothing(robot_trajectory::RobotTrajectory &trajectory, const double max_velocity_scaling_factor=1.0, const double max_acceleration_scaling_factor=1.0)
const rclcpp::Logger LOGGER
Definition: async_test.h:31