moveit2
The MoveIt Motion Planning Framework for ROS 2.
cost_functions.hpp
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 
40 #pragma once
41 
42 #include <Eigen/Geometry>
43 
46 
49 
50 namespace stomp_moveit
51 {
52 // Validates a given state and produces a scalar cost penalty - example use cases are collision or constraint checking
53 using StateValidatorFn = std::function<double(const Eigen::VectorXd& state_positions)>;
54 
55 namespace costs
56 {
57 
58 // Interpolation step size for collision checking (joint space, L2 norm)
59 constexpr double COL_CHECK_DISTANCE = 0.05;
60 constexpr double CONSTRAINT_CHECK_DISTANCE = 0.05;
61 
75 CostFn getCostFunctionFromStateValidator(const StateValidatorFn& state_validator_fn, double interpolation_step_size)
76 {
77  CostFn cost_fn = [=](const Eigen::MatrixXd& values, Eigen::VectorXd& costs, bool& validity) {
78  costs.setZero(values.cols());
79 
80  validity = true;
81  std::vector<std::pair<long, long>> invalid_windows;
82  bool in_invalid_window = false;
83 
84  // Iterate over sample waypoint pairs and check for validity in each segment.
85  // If an invalid state is found, weighted penalty costs are applied to both waypoints.
86  // Subsequent invalid states are assumed to have the same cause, so we are keeping track
87  // of "invalid windows" which are used for smoothing out the costs per violation cause
88  // with a gaussian, penalizing neighboring valid states as well.
89  for (int timestep = 0; timestep < values.cols() - 1; ++timestep)
90  {
91  Eigen::VectorXd current = values.col(timestep);
92  Eigen::VectorXd next = values.col(timestep + 1);
93  const double segment_distance = (next - current).norm();
94  double interpolation_fraction = 0.0;
95  const double interpolation_step = std::min(0.5, interpolation_step_size / segment_distance);
96  bool found_invalid_state = false;
97  double penalty = 0.0;
98  while (!found_invalid_state && interpolation_fraction < 1.0)
99  {
100  Eigen::VectorXd sample_vec = (1 - interpolation_fraction) * current + interpolation_fraction * next;
101 
102  penalty = state_validator_fn(sample_vec);
103  found_invalid_state = penalty > 0.0;
104  interpolation_fraction += interpolation_step;
105  }
106 
107  if (found_invalid_state)
108  {
109  // Apply weighted penalties -> This trajectory is definitely invalid
110  costs(timestep) = (1 - interpolation_fraction) * penalty;
111  costs(timestep + 1) = interpolation_fraction * penalty;
112  validity = false;
113 
114  // Open new invalid window when this is the first detected invalid state in a group
115  if (!in_invalid_window)
116  {
117  invalid_windows.emplace_back(timestep, values.cols());
118  in_invalid_window = true;
119  }
120  }
121  else
122  {
123  // Close current invalid window if the current state is valid
124  if (in_invalid_window)
125  {
126  invalid_windows.back().second = timestep - 1;
127  in_invalid_window = false;
128  }
129  }
130  }
131 
132  // Smooth out cost of invalid segments using a gaussian
133  // The standard deviation is picked so that neighboring states
134  // before and after the violation are penalized as well.
135  for (const auto& [start, end] : invalid_windows)
136  {
137  const double window_size = static_cast<double>(end - start) + 1;
138  const double sigma = window_size / 5.0;
139  const double mu = 0.5 * (start + end);
140  // Iterate over waypoints in the range of +/-sigma (neighborhood)
141  // and add a weighted and continuous cost value for each waypoint
142  // based on a Gaussian distribution.
143  for (auto j = std::max(0l, start - static_cast<long>(sigma));
144  j <= std::min(values.cols() - 1, end + static_cast<long>(sigma)); ++j)
145  {
146  costs(j) +=
147  std::exp(-std::pow(j - mu, 2) / (2 * std::pow(sigma, 2))) / (sigma * std::sqrt(2 * mu)) * window_size;
148  }
149  }
150 
151  return true;
152  };
153 
154  return cost_fn;
155 }
156 
168 CostFn getCollisionCostFunction(const std::shared_ptr<const planning_scene::PlanningScene>& planning_scene,
169  const moveit::core::JointModelGroup* group, double collision_penalty)
170 {
171  const auto& joints = group ? group->getActiveJointModels() : planning_scene->getRobotModel()->getActiveJointModels();
172  const auto& group_name = group ? group->getName() : "";
173 
174  StateValidatorFn collision_validator_fn = [=](const Eigen::VectorXd& positions) {
175  static moveit::core::RobotState state(planning_scene->getCurrentState());
176 
177  // Update robot state values
178  setJointPositions(positions, joints, state);
179  state.update();
180 
181  return planning_scene->isStateColliding(state, group_name) ? collision_penalty : 0.0;
182  };
183 
184  return getCostFunctionFromStateValidator(collision_validator_fn, COL_CHECK_DISTANCE);
185 }
186 
199 CostFn getConstraintsCostFunction(const std::shared_ptr<const planning_scene::PlanningScene>& planning_scene,
201  const moveit_msgs::msg::Constraints& constraints_msg, double cost_scale)
202 {
203  const auto& joints = group ? group->getActiveJointModels() : planning_scene->getRobotModel()->getActiveJointModels();
204 
206  constraints.add(constraints_msg, planning_scene->getTransforms());
207 
208  StateValidatorFn constraints_validator_fn = [=](const Eigen::VectorXd& positions) {
209  static moveit::core::RobotState state(planning_scene->getCurrentState());
210 
211  // Update robot state values
212  setJointPositions(positions, joints, state);
213  state.update();
214 
215  return constraints.decide(state).distance * cost_scale;
216  };
217 
218  return getCostFunctionFromStateValidator(constraints_validator_fn, CONSTRAINT_CHECK_DISTANCE);
219 }
220 
228 CostFn sum(const std::vector<CostFn>& cost_functions)
229 {
230  return [=](const Eigen::MatrixXd& values, Eigen::VectorXd& overall_costs, bool& overall_validity) {
231  overall_validity = true;
232  overall_costs.setZero(values.cols());
233 
234  auto costs = overall_costs;
235  for (const auto& cost_fn : cost_functions)
236  {
237  bool valid = true;
238  cost_fn(values, costs, valid);
239 
240  // Sum results
241  overall_validity = overall_validity && valid;
242  overall_costs += costs;
243  }
244  return true;
245  };
246 }
247 } // namespace costs
248 } // namespace stomp_moveit
A class that contains many different constraints, and can check RobotState *versus the full set....
bool add(const moveit_msgs::msg::Constraints &c, const moveit::core::Transforms &tf)
Add all known constraints.
ConstraintEvaluationResult decide(const moveit::core::RobotState &state, bool verbose=false) const
Determines whether all constraints are satisfied by state, returning a single evaluation result.
Representation of a robot's state. This includes position, velocity, acceleration and effort.
Definition: robot_state.h:90
void update(bool force=false)
Update all transforms.
Helper functions for converting between MoveIt types and plain Eigen types.
This namespace includes the central class for representing planning contexts.
CostFn getCostFunctionFromStateValidator(const StateValidatorFn &state_validator_fn, double interpolation_step_size)
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)
constexpr double COL_CHECK_DISTANCE
CostFn sum(const std::vector< CostFn > &cost_functions)
constexpr double CONSTRAINT_CHECK_DISTANCE
CostFn getCollisionCostFunction(const std::shared_ptr< const planning_scene::PlanningScene > &planning_scene, const moveit::core::JointModelGroup *group, double collision_penalty)
void setJointPositions(const Eigen::VectorXd &values, const Joints &joints, moveit::core::RobotState &state)
std::function< bool(const Eigen::MatrixXd &values, Eigen::VectorXd &costs, bool &validity)> CostFn
std::function< double(const Eigen::VectorXd &state_positions)> StateValidatorFn
A STOMP task definition that allows injecting custom functions for planning.
double distance
The distance evaluation from the constraint or constraints.