-
Notifications
You must be signed in to change notification settings - Fork 532
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
State cost function interface #2153
base: main
Are you sure you want to change the base?
Conversation
moveit_core/cost_functions/include/moveit/cost_functions/cost_functions.hpp
Outdated
Show resolved
Hide resolved
[[nodiscard]] planning_interface::StateCostFn getClearnaceCostFn(); | ||
|
||
[[nodiscard]] planning_interface::StateCostFn | ||
getWeightedCostFnSum(std::vector<std::pair<double, planning_interface::StateCostFn>> weight_cost_vector); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As convenience functions you can define operator*(double, planning_interface::StateCostFn)
and operator+(planning_interface::StateCostFn,planning_interface::StateCostFn)
so that you can write weighted sums simply as:
planning_interface::StateCostFn fn1 = ...;
planning_interface::StateCostFn fn2 = ...;
planning_interface::StateCostFn fn3 = ...;
planning_interface::StateCostFn fn4 = 2. * fn1 + 3. * fn2 + 4. * fn3;
moveit_core/planning_interface/include/moveit/planning_interface/planning_interface.h
Outdated
Show resolved
Hide resolved
@@ -144,6 +150,9 @@ class PlanningContext | |||
|
|||
/// The planning request for this context | |||
MotionPlanRequest request_; | |||
|
|||
// Cost function for individual states | |||
StateCostFn state_cost_function_ = StateCostFn(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe a more reasonable default value is a function that always returns 1.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point! I'd like to have a way to check for an "unset" cost function, so I think this should be the default, but specific implementations should default to cost = 1
c8387e2
to
f982f86
Compare
This pull request is in conflict. Could you fix it @sjahr? |
1f22372
to
9cdde7d
Compare
moveit_core/planning_interface/include/moveit/planning_interface/planning_interface.h
Outdated
Show resolved
Hide resolved
e90e60f
to
20c9e39
Compare
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #2153 +/- ##
==========================================
+ Coverage 50.73% 50.74% +0.01%
==========================================
Files 386 388 +2
Lines 32087 32092 +5
==========================================
+ Hits 16276 16281 +5
Misses 15811 15811 ☔ View full report in Codecov by Sentry. |
This pull request is in conflict. Could you fix it @sjahr? |
cd5ed0b
to
e9e811f
Compare
a22d090
to
49d5e08
Compare
This pull request is in conflict. Could you fix it @sjahr? |
0a1cdae
to
78bdf82
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Finally trying to come to a conclusion on this, testing right now...
CostFn get_cost_function_from_moveit_state_cost_fn(const ::planning_interface::StateCostFn& state_cost_function) | ||
{ | ||
CostFn cost_fn = [&](const Eigen::MatrixXd& values, Eigen::VectorXd& costs, bool& validity) { | ||
validity = true; // TODO(sjahr): Discuss if that is always the case |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just found this... No, it's not always the case at all. STOMP will only plan until validity, and only continue optimizing if explicitly configured that way. For general usage, we would need a validity threshold for any cost we want to optimize.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤔 How would this validity threshold look like? Is that fixed with #2418?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The threshold needs to be provided by the cost function implementation. Unless we normalize all costs (which is not easy to do) there is no way to define a general threshold. The linked PR only changed the cost metric to use actual distance values, it doesn't really deal with validity.
// The closer the collision object the higher the cost | ||
return 1.0 / shortest_distance_to_collision; | ||
} | ||
return std::numeric_limits<double>::infinity(); // Return a max cost cost by default |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't there a max distance associated with the distance check? We don't want to return infinity if the returned distance is larger than that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question, I have not found anything about this in the documentation/ comments 😅 On the other hand, it only returns infinity, if the distance check returns a negative distance (penetration of the collision object). For simplicity sake I decided that this is always associated with the highest possible cost
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still think that infinity will basically break optimization since there is no way to approximate a gradient based in this value. IMO, a better cost function would take a specific clearance target as 0 (or max_distance if set), and then returning a linear cost for distances smaller than that, including negative distances. It's very useful to also consider negative distances since they also produce a gradient and STOMP can optimize out collisions. The cost function could looks like this max(0, target_clearance - distance)
moveit_core/cost_functions/include/moveit/cost_functions/cost_functions.hpp
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good!
I have a few minor comments and optional changes, but I'm probably missing context, so feel free to ignore!
I also have a question about how to add support for cost functions on the ROS message, if that's planned.
Other than that, with some additional documentation and testing I think this should be good to merge.
moveit_core/cost_functions/include/moveit/cost_functions/cost_functions.hpp
Outdated
Show resolved
Hide resolved
const planning_scene::PlanningSceneConstPtr& planning_scene) | ||
{ | ||
// Create cost function | ||
return [robot_state, group_name, planning_scene](const Eigen::VectorXd& state_vector) mutable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An alternative for additional transparency and simplicity would be to define the actual cost functions in the header, and then std::bind them directly from where they are needed, e.g.:
// This is a transparent cost function declaration. Takes what it needs, returns what it computes.
// Can be used in other contexts without the planning_interface dependency.
double computeClearanceCost(moveit::core::RobotState& robot_state, const std::string& group_name,
const planning_scene::PlanningSceneConstPtr& planning_scene, const Eigen::VectorXd& state_vector) {
... // what you have in the return lambda.
}
// And where you need to create a StateCostFn, you can use std::bind to make the wrapper, e.g.:
StateCostFn clearance_cost_fn = std::bind(computeClearanceCost, robot_state, group_name, planning_scene, std::placeholders::_1);
In C++20 there's also std::bind_front
to get rid of the std::placeholders::_1
.
But maybe not super important at this point, so as you prefer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good thought, thanks! I found multiple sources that discourage using std::bind (like abseil and turner) because of the compile and runtime overhead it causes. Maybe C++20's std::bind_front fixes that but since we're not using it at the moment I'd prefer the lambdas for now. I guess if I'd like to avoid the dependency on the planning_interface I could also just abstain from using the typedef and use std::function<double(const Eigen::VectorXd& state_vector)>
instead of StateCostFn
. So the dependency is not mandatory.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I mean with avoiding the dependency is that if you just have the function that computes the cost here, you can turn that into a StateCostFn or any other function signature at the caller side (e.g. stomp or other libraries may have their own cost function signature, but they could still use these cost computation functions and transform them into what they need via bind or lambdas. Of course this doesn't matter if we think that double(Eigen::VectorXd)
is a general enough cost-definition function, which it probably is. I'm okay with leaving this as is for now and iterate later if needed, but see my comment below for the lambda captures.
On a separate note, I wish we had the absl dependency to be able to use absl::bind_front, StatusOr and others, and to follow their TotW and style guide.
moveit_core/planning_interface/include/moveit/planning_interface/planning_request.h
Show resolved
Hide resolved
moveit_ros/planning/moveit_cpp/include/moveit/moveit_cpp/planning_component.h
Outdated
Show resolved
Hide resolved
moveit_ros/planning/planning_pipeline/src/planning_pipeline.cpp
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this looks good to start experimenting and iterating on what cost functions work and make sense.
Longer term, if we want to add cost functions to the PlanningRequest msg as enums, I would maybe update the PlanningRequest C++ type to reflect that (i.e. an enum instead of a StateCostFn, or go back to use the original msg), and handle the enum inside stomp. That would also avoid the double type-erasure with the lambda functions which I find hard to read (i.e. cost-function -> StateCostFn -> CostFn), since stomp could wrap directly MoveIt cost functions into CostFn. But I would leave all this for the future if needed.
[[nodiscard]] ::planning_interface::StateCostFn | ||
createMinJointDisplacementCostFn(moveit::core::RobotState& robot_state, const std::string& group_name, | ||
const planning_scene::PlanningSceneConstPtr& planning_scene); | ||
|
||
[[nodiscard]] ::planning_interface::StateCostFn | ||
createClearanceCostFn(moveit::core::RobotState& robot_state, const std::string& group_name, | ||
const planning_scene::PlanningSceneConstPtr& planning_scene); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We will need comments on what these do, and tests for them.
moveit_core/planning_interface/include/moveit/planning_interface/planning_request.h
Outdated
Show resolved
Hide resolved
This pull request is in conflict. Could you fix it @sjahr? |
This PR is stale because it has been open for 45 days with no activity. Please tag a maintainer for help on completing this PR, or close it if you think it has become obsolete. |
d95e6e2
to
7870bc9
Compare
@sjahr I think this looks good for merging. Can you please add the comments that @marioprats requested, and track my suggestions about the cost functions in a follow-up issue? |
This pull request is in conflict. Could you fix it @sjahr? |
This PR is stale because it has been open for 45 days with no activity. Please tag a maintainer for help on completing this PR, or close it if you think it has become obsolete. |
Description
This PR enhances the API to use a user-defined state cost function in the planners.
Testing can be done with moveit/moveit2_tutorials#679
Checklist