moveit2
The MoveIt Motion Planning Framework for ROS 2.
stomp_moveit_planning_context.cpp
Go to the documentation of this file.
1 /*********************************************************************
2  * Software License Agreement (BSD License)
3  *
4  * Copyright (c) 2023, PickNik Inc.
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
9  * are met:
10  *
11  * * Redistributions of source code must retain the above copyright
12  * notice, this list of conditions and the following disclaimer.
13  * * Redistributions in binary form must reproduce the above
14  * copyright notice, this list of conditions and the following
15  * disclaimer in the documentation and/or other materials provided
16  * with the distribution.
17  * * Neither the name of PickNik Inc. nor the names of its
18  * contributors may be used to endorse or promote products derived
19  * from this software without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32  * POSSIBILITY OF SUCH DAMAGE.
33  *********************************************************************/
34 
39 #include <atomic>
40 #include <future>
41 
42 #include <stomp/stomp.h>
43 
50 #include <stomp_moveit_parameters.hpp>
51 
54 
55 namespace stomp_moveit
56 {
57 static const rclcpp::Logger LOGGER = rclcpp::get_logger("stomp_moveit");
58 
59 // @brief Run a planning attempt with STOMP, either providing start and goal states or an optional seed trajectory
60 bool solveWithStomp(const std::shared_ptr<stomp::Stomp>& stomp, const moveit::core::RobotState& start_state,
62  const robot_trajectory::RobotTrajectoryPtr& input_trajectory,
63  robot_trajectory::RobotTrajectoryPtr& output_trajectory)
64 {
65  Eigen::MatrixXd waypoints;
66  const auto& joints = group->getActiveJointModels();
67  bool success = false;
68  if (!input_trajectory || input_trajectory->empty())
69  {
70  success = stomp->solve(getPositions(start_state, joints), getPositions(goal_state, joints), waypoints);
71  }
72  else
73  {
74  auto input = robotTrajectoryToMatrix(*input_trajectory);
75  success = stomp->solve(input, waypoints);
76  }
77  if (success)
78  {
79  output_trajectory = std::make_shared<robot_trajectory::RobotTrajectory>(start_state.getRobotModel(), group);
80  fillRobotTrajectory(waypoints, start_state, *output_trajectory);
81  }
82 
83  return success;
84 }
85 
86 // @brief Extract a robot trajectory from the seed waypoints passed with a motion plan request
88  const moveit::core::RobotModelConstPtr& robot_model,
89  robot_trajectory::RobotTrajectoryPtr& seed)
90 {
91  if (req.trajectory_constraints.constraints.empty())
92  {
93  return false;
94  }
95 
96  const auto* joint_group = robot_model->getJointModelGroup(req.group_name);
97  const auto& names = joint_group->getActiveJointModelNames();
98  const auto dof = names.size();
99 
100  trajectory_msgs::msg::JointTrajectory seed_traj;
101  const auto& constraints = req.trajectory_constraints.constraints; // alias to keep names short
102  // Test the first point to ensure that it has all of the joints required
103  for (size_t i = 0; i < constraints.size(); ++i)
104  {
105  auto n = constraints[i].joint_constraints.size();
106  if (n != dof)
107  { // first test to ensure that dimensionality is correct
108  RCLCPP_WARN(LOGGER, "Seed trajectory index %lu does not have %lu constraints (has %lu instead).", i, dof, n);
109  return false;
110  }
111 
112  trajectory_msgs::msg::JointTrajectoryPoint joint_pt;
113 
114  for (size_t j = 0; j < constraints[i].joint_constraints.size(); ++j)
115  {
116  const auto& c = constraints[i].joint_constraints[j];
117  if (c.joint_name != names[j])
118  {
119  RCLCPP_WARN(LOGGER, "Seed trajectory (index %lu, joint %lu) joint name '%s' does not match expected name '%s'",
120  i, j, c.joint_name.c_str(), names[j].c_str());
121  return false;
122  }
123  joint_pt.positions.push_back(c.position);
124  }
125 
126  seed_traj.points.push_back(joint_pt);
127  }
128  seed_traj.joint_names = names;
129 
130  moveit::core::RobotState robot_state(robot_model);
131  moveit::core::robotStateMsgToRobotState(req.start_state, robot_state);
132  seed = std::make_shared<robot_trajectory::RobotTrajectory>(robot_model, joint_group);
133  seed->setRobotTrajectoryMsg(robot_state, seed_traj);
134 
135  return !seed->empty();
136 }
137 
138 // @brief Build a STOMP task that uses MoveIt callback types for planning in STOMP
139 stomp::TaskPtr createStompTask(const stomp::StompConfiguration& config, StompPlanningContext& context)
140 {
141  const size_t num_timesteps = config.num_timesteps;
142  const auto planning_scene = context.getPlanningScene();
143  const auto group = planning_scene->getRobotModel()->getJointModelGroup(context.getGroupName());
144 
145  // Check if we do have path constraints
146  const auto& req = context.getMotionPlanRequest();
148  constraints.add(req.path_constraints, planning_scene->getTransforms());
149 
150  // Create callback functions for STOMP task
151  // Cost, noise and filter functions are provided for planning.
152  // TODO(henningkayser): parameterize cost penalties
153  using namespace stomp_moveit;
154  CostFn cost_fn;
155  if (!constraints.empty())
156  {
157  cost_fn = costs::sum({ costs::getCollisionCostFunction(planning_scene, group, 1.0 /* collision penalty */),
159  1.0 /* constraint penalty */) });
160  }
161  else
162  {
163  cost_fn = costs::getCollisionCostFunction(planning_scene, group, 1.0 /* collision penalty */);
164  }
165 
166  // TODO(henningkayser): parameterize stddev
167  const std::vector<double> stddev(group->getActiveJointModels().size(), 0.1);
168  auto noise_generator_fn = noise::getNormalDistributionGenerator(num_timesteps, stddev);
169  auto filter_fn =
171  auto iteration_callback_fn =
173  auto done_callback_fn =
175 
176  // Initialize and return STOMP task
177  stomp::TaskPtr task =
178  std::make_shared<ComposableTask>(noise_generator_fn, cost_fn, filter_fn, iteration_callback_fn, done_callback_fn);
179  return task;
180 }
181 
182 // @brief Create a valid STOMP configuration from runtime parameters and dimensions provided by the planning request
183 stomp::StompConfiguration getStompConfig(const stomp_moveit::Params& params, size_t num_dimensions)
184 {
185  stomp::StompConfiguration config;
186  config.num_dimensions = num_dimensions; // Copied from joint count
187  // TODO(henningkayser): set from request or params
188  config.initialization_method = stomp::TrajectoryInitializations::LINEAR_INTERPOLATION;
189  config.num_iterations = params.num_iterations;
190  config.num_iterations_after_valid = params.num_iterations_after_valid;
191  config.num_timesteps = params.num_timesteps;
192  config.delta_t = params.delta_t;
193  config.exponentiated_cost_sensitivity = params.exponentiated_cost_sensitivity;
194  config.num_rollouts = params.num_rollouts;
195  config.max_rollouts = params.max_rollouts;
196  config.control_cost_weight = params.control_cost_weight;
197 
198  return config;
199 }
200 
201 StompPlanningContext::StompPlanningContext(const std::string& name, const std::string& group,
202  const stomp_moveit::Params& params)
203  : planning_interface::PlanningContext(name, group), params_(params)
204 {
205 }
206 
208 {
209  // Start time
210  auto time_start = std::chrono::steady_clock::now();
211 
212  res.planner_id = std::string("stomp");
213  // Default to happy path
214  res.error_code.val = moveit_msgs::msg::MoveItErrorCodes::SUCCESS;
215 
216  // Extract start and goal states
217  const auto& req = getMotionPlanRequest();
218  const moveit::core::RobotState start_state(*getPlanningScene()->getCurrentStateUpdated(req.start_state));
219  moveit::core::RobotState goal_state(start_state);
221  auto goal_sampler = sampler_manager.selectSampler(getPlanningScene(), getGroupName(), req.goal_constraints.at(0));
222  if (!goal_sampler || !goal_sampler->sample(goal_state))
223  {
224  res.error_code.val = moveit_msgs::msg::MoveItErrorCodes::INVALID_GOAL_CONSTRAINTS;
225  return false; // Can't plan without valid goal state
226  }
227 
228  // STOMP config, task, planner instance
229  const auto group = getPlanningScene()->getRobotModel()->getJointModelGroup(getGroupName());
230  auto config = getStompConfig(params_, group->getActiveJointModels().size() /* num_dimensions */);
231  robot_trajectory::RobotTrajectoryPtr input_trajectory;
232  if (extractSeedTrajectory(request_, getPlanningScene()->getRobotModel(), input_trajectory))
233  {
234  config.num_timesteps = input_trajectory->size();
235  }
236  const auto task = createStompTask(config, *this);
237  stomp_ = std::make_shared<stomp::Stomp>(config, task);
238 
239  std::condition_variable cv;
240  std::mutex cv_mutex;
241  bool finished = false;
242  auto timeout_future = std::async(std::launch::async, [&, stomp = stomp_]() {
243  std::unique_lock<std::mutex> lock(cv_mutex);
244  cv.wait_for(lock, std::chrono::duration<double>(req.allowed_planning_time), [&finished] { return finished; });
245  if (!finished)
246  {
247  stomp->cancel();
248  }
249  });
250 
251  // Solve
252  if (!solveWithStomp(stomp_, start_state, goal_state, group, input_trajectory, res.trajectory))
253  {
254  // We timed out if the timeout task has completed so that the timeout future is valid and ready
255  bool timed_out =
256  timeout_future.valid() && timeout_future.wait_for(std::chrono::nanoseconds(1)) == std::future_status::ready;
257  res.error_code.val =
258  timed_out ? moveit_msgs::msg::MoveItErrorCodes::TIMED_OUT : moveit_msgs::msg::MoveItErrorCodes::PLANNING_FAILED;
259  }
260  stomp_.reset();
261  {
262  std::unique_lock<std::mutex> lock(cv_mutex);
263  finished = true;
264  cv.notify_all();
265  }
266 
267  // Stop time
268  std::chrono::duration<double> elapsed_seconds = std::chrono::steady_clock::now() - time_start;
269  res.planning_time = elapsed_seconds.count();
270 
271  return res.error_code.val == moveit_msgs::msg::MoveItErrorCodes::SUCCESS;
272 }
273 
275 {
276  // TODO(#2168): implement this function
277  RCLCPP_ERROR(LOGGER,
278  "StompPlanningContext::solve(planning_interface::MotionPlanDetailedResponse&) is not implemented!");
279  return false;
280 }
281 
283 {
284  // Copy shared pointer to avoid race conditions
285  auto stomp = stomp_;
286  if (stomp)
287  {
288  return stomp->cancel();
289  }
290 
291  return true;
292 }
293 
295 {
296 }
297 
299  const std::shared_ptr<rclcpp::Publisher<visualization_msgs::msg::MarkerArray>>& path_publisher)
300 {
301  path_publisher_ = path_publisher;
302 }
303 
304 std::shared_ptr<rclcpp::Publisher<visualization_msgs::msg::MarkerArray>> StompPlanningContext::getPathPublisher()
305 {
306  return path_publisher_;
307 }
308 } // namespace stomp_moveit
This class assists in the generation of a ConstraintSampler for a particular group from a moveit_msgs...
ConstraintSamplerPtr selectSampler(const planning_scene::PlanningSceneConstPtr &scene, const std::string &group_name, const moveit_msgs::msg::Constraints &constr) const
Selects among the potential sampler allocators.
A class that contains many different constraints, and can check RobotState *versus the full set....
const moveit_msgs::msg::Constraints & getAllConstraints() const
Get all constraints in the set.
bool empty() const
Returns whether or not there are any constraints in the set.
bool add(const moveit_msgs::msg::Constraints &c, const moveit::core::Transforms &tf)
Add all known constraints.
Representation of a robot's state. This includes position, velocity, acceleration and effort.
Definition: robot_state.h:90
const RobotModelConstPtr & getRobotModel() const
Get the robot model this state is constructed for.
Definition: robot_state.h:104
Representation of a particular planning context – the planning scene and the request are known,...
MotionPlanRequest request_
The planning request for this context.
const std::string & getGroupName() const
Get the name of the group this planning context is for.
const MotionPlanRequest & getMotionPlanRequest() const
Get the motion plan request associated to this planning context.
const planning_scene::PlanningSceneConstPtr & getPlanningScene() const
Get the planning scene associated to this planning context.
void clear() override
Clear the data structures used by the planner.
std::shared_ptr< rclcpp::Publisher< visualization_msgs::msg::MarkerArray > > getPathPublisher()
void setPathPublisher(const std::shared_ptr< rclcpp::Publisher< visualization_msgs::msg::MarkerArray >> &path_publisher)
bool terminate() override
If solve() is running, terminate the computation. Return false if termination not possible....
StompPlanningContext(const std::string &name, const std::string &group_name, const stomp_moveit::Params &params)
bool solve(planning_interface::MotionPlanResponse &res) override
Solve the motion planning problem and store the result in res. This function should not clear data st...
MoveIt-based cost functions that can be passed to STOMP via a ComposableTask.
Filter functions that can be passed to STOMP via a ComposableTask.
bool robotStateMsgToRobotState(const Transforms &tf, const moveit_msgs::msg::RobotState &robot_state, RobotState &state, bool copy_attached_bodies=true)
Convert a robot state msg (with accompanying extra transforms) to a MoveIt robot state.
This namespace includes the base class for MoveIt planners.
moveit_msgs::msg::MotionPlanRequest MotionPlanRequest
This namespace includes the central class for representing planning contexts.
name
Definition: setup.py:7
CostFn getConstraintsCostFunction(const std::shared_ptr< const planning_scene::PlanningScene > &planning_scene, const moveit::core::JointModelGroup *group, const moveit_msgs::msg::Constraints &constraints_msg, double cost_scale)
CostFn sum(const std::vector< CostFn > &cost_functions)
CostFn getCollisionCostFunction(const std::shared_ptr< const planning_scene::PlanningScene > &planning_scene, const moveit::core::JointModelGroup *group, double collision_penalty)
FilterFn chain(const std::vector< FilterFn > &filter_functions)
FilterFn enforcePositionBounds(const moveit::core::JointModelGroup *group)
FilterFn simpleSmoothingMatrix(size_t num_timesteps)
NoiseGeneratorFn getNormalDistributionGenerator(size_t num_timesteps, const std::vector< double > &stddev)
PostIterationFn getIterationPathPublisher(const rclcpp::Publisher< visualization_msgs::msg::MarkerArray >::SharedPtr &marker_publisher, const std::shared_ptr< const planning_scene::PlanningScene > &planning_scene, const moveit::core::JointModelGroup *group)
Get post iteration function that publishes the EE path of the generated trajectory.
DoneFn getSuccessTrajectoryPublisher(const rclcpp::Publisher< visualization_msgs::msg::MarkerArray >::SharedPtr &marker_publisher, const std::shared_ptr< const planning_scene::PlanningScene > &planning_scene, const moveit::core::JointModelGroup *group)
Get Done function that publishes the EE path of the generated trajectory.
Eigen::MatrixXd robotTrajectoryToMatrix(const robot_trajectory::RobotTrajectory &trajectory)
void fillRobotTrajectory(const Eigen::MatrixXd &trajectory_values, const moveit::core::RobotState &reference_state, robot_trajectory::RobotTrajectory &trajectory)
std::vector< double > getPositions(const moveit::core::RobotState &state, const Joints &joints)
bool extractSeedTrajectory(const planning_interface::MotionPlanRequest &req, const moveit::core::RobotModelConstPtr &robot_model, robot_trajectory::RobotTrajectoryPtr &seed)
bool solveWithStomp(const std::shared_ptr< stomp::Stomp > &stomp, const moveit::core::RobotState &start_state, const moveit::core::RobotState &goal_state, const moveit::core::JointModelGroup *group, const robot_trajectory::RobotTrajectoryPtr &input_trajectory, robot_trajectory::RobotTrajectoryPtr &output_trajectory)
std::function< bool(const Eigen::MatrixXd &values, Eigen::VectorXd &costs, bool &validity)> CostFn
stomp::StompConfiguration getStompConfig(const stomp_moveit::Params &params, size_t num_dimensions)
stomp::TaskPtr createStompTask(const stomp::StompConfiguration &config, StompPlanningContext &context)
Noise generator functions for randomizing trajectories in STOMP via a ComposableTask.
KeyboardReader input
Planning Context implementation for STOMP.
A STOMP task definition that allows injecting custom functions for planning.
Response to a planning query.
moveit::core::MoveItErrorCode error_code
robot_trajectory::RobotTrajectoryPtr trajectory
: Helper functions for visualizing trajectory markers for STOMP planning iterations.