-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Calculate next path point using lookahead for Graceful Controller #5003
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
base: main
Are you sure you want to change the base?
Conversation
@SakshayMahna, your PR has failed to build. Please check CI outputs and resolve issues. |
@SakshayMahna, your PR has failed to build. Please check CI outputs and resolve issues. |
1 similar comment
@SakshayMahna, your PR has failed to build. Please check CI outputs and resolve issues. |
Codecov ReportAttention: Patch coverage is
... and 4 files with indirect coverage changes 🚀 New features to boost your workflow:
|
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.
Thinking aloud a bit:
This bit of code looks like we iterate through the path, try to find the max_lookahead
distance away and back up closer and closer to the robot until we find a solution that is collision free.
Is that case, we should attempt to find the interpolated lookahead point for the max_lookahead
away to start off with, that gives us the best and smoothest value.
After that point, its a bit arbitrary the distance selected, isn't it? Selecting a lookahead_resolution
versus marching through the path points I don't think improves the smoothness, does it? Either way there's a discontinuity now on the distance away to use based on the collision state (unless we set the search resolution to be very fine, which is probably not computationally worthwhile).
For example, the resolution you set was 0.1, but a normal path from a planning plugin probably does it closer to 0.05 for the costmap resolution when that resolution is 5cm. So wouldn't this case actually be more coarse?
Did you test this without the lookahead_resolution
feature and generate those charts using the normal path point iteration? I suspect the improvement in smoothness you see is when the max_lookahead is used due to lack of collision requiring backing up the path to finding the first valid trajectory. My intuition says that when we do the backing-up process, we own that discontinuity either way, given we're backing up by 5cm or 10cm, respectively.
nav2_graceful_controller/params/graceful_controller_params.yaml
Outdated
Show resolved
Hide resolved
nav2_regulated_pure_pursuit_controller/params/regulated_pure_pursuit_params.yaml
Outdated
Show resolved
Hide resolved
nav2_graceful_controller/include/nav2_graceful_controller/graceful_controller.hpp
Outdated
Show resolved
Hide resolved
Do you see an improvement using the By default you have it set to 0.1, but the path resolution is ~0.05. If you lower that to 0.05 or even lower like 0.02, is it smoother than before? If so, I think that's a neat feature and we can keep it, users can tune to their liking and we can set it default to the costmap resolution. If not, I think maybe we should revert that bit and only use the interpolation when using the full lookahead distance. Else, just use the path points since there's a jump anyway. |
No, I don't see any improvement while using a smaller lookahead resolution. The plots for the 3 cases (smaller resolution, larger resolution, using points) all look similar. |
@@ -195,21 +195,19 @@ geometry_msgs::msg::TwistStamped GracefulController::computeVelocityCommands( | |||
// Else, fall through and see if we should follow control law longer | |||
} | |||
|
|||
// Precompute distance to candidate poses | |||
std::vector<double> target_distances; | |||
computeDistanceAlongPath(transformed_plan.poses, target_distances); |
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 believe this will cause a minor regression - the getLookaheadPoint() function simply looks at the distance between the target point and the robot - not the distance ALONG the path. In some instances, this can lead to the controller massively shortcutting the desired path.
I would suggest making getLookaheadPoint use distance along path instead of hypot().
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.
Right!
I can make this change in this PR.
Or another issue/PR would be appropriate for this?
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 having it done here is important since that is a regression from the current behavior in this branch 😄
Also, it is a nice feature which RPP would also share in since they share the implementation! I know that's actually been a topic of conversion in other threads / tickets to add that feature, so I think that's wonderful to add in
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.
@SakshayMahna I believe this item is resolved, correct? @mikeferguson can you take another look?
@@ -195,21 +195,19 @@ geometry_msgs::msg::TwistStamped GracefulController::computeVelocityCommands( | |||
// Else, fall through and see if we should follow control law longer | |||
} | |||
|
|||
// Precompute distance to candidate poses | |||
std::vector<double> target_distances; | |||
computeDistanceAlongPath(transformed_plan.poses, target_distances); |
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 having it done here is important since that is a regression from the current behavior in this branch 😄
Also, it is a nice feature which RPP would also share in since they share the implementation! I know that's actually been a topic of conversion in other threads / tickets to add that feature, so I think that's wonderful to add in
@@ -195,21 +195,19 @@ geometry_msgs::msg::TwistStamped GracefulController::computeVelocityCommands( | |||
// Else, fall through and see if we should follow control law longer | |||
} | |||
|
|||
// Precompute distance to candidate poses | |||
std::vector<double> target_distances; | |||
computeDistanceAlongPath(transformed_plan.poses, target_distances); |
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.
@SakshayMahna I believe this item is resolved, correct? @mikeferguson can you take another look?
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.
There's one superficial refactor that would in my opinion make this easier to read / understand:
// Calculate target pose through lookahead interpolation
double dist_to_target = params_->max_lookahead;
geometry_msgs::msg::PoseStamped target_pose = nav2_util::getLookAheadPoint(
dist_to_target, transformed_plan, params_->interpolate_after_goal);
bool valid_target_pose_found = validateTargetPose(target_pose, dist_to_target, dist_to_goal, local_plan, costmap_transform, cmd_vel);
if (!valid_target_pose_found) {
...
...
valid_target_pose_found = validateTargetPose(target_pose, dist_to_target, dist_to_goal, local_plan, costmap_transform, cmd_vel);
if (valid_target_pose_found) {
break;
}
}
This would remove the else
statement and explicitly setting the valid_target_pose_found instead of just capturing the validate method's return.
@SakshayMahna can you work on these last couple of updates? |
Hello Steve, |
This pull request is in conflict. Could you fix it @SakshayMahna? |
Hi @SteveMacenski What I understand it is because of the fallback code that was added, that in case Lookahead Point is not found, fallback to default behaviour of checking the poses one by one. The only way I think this can be tested is by adding an obstacle in the cost map. That way, the Lookahead Point will become invalid because of collision check by What do you think about this? Any ideas to improve the code coverage? |
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 some of these changes in refactoring will resolve the testing gap! :-)
nav2_util/src/controller_utils.cpp
Outdated
|
||
d += std::hypot(curr_pose.x - prev_pose.x, curr_pose.y - prev_pose.y); | ||
if (d >= lookahead_dist) { | ||
goal_pose_it = poses.begin() + i; |
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.
Simplify: I think you can just set goal_pose_it = poses.begin() + i;
each iteration and when d >= lookahead_dist
only do the break
. That would let you remove the pose_found
logic and the later goal_pose_it = poses.end();
.
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.
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 gave the suggested implementation quite some tries. But for the suggested one, the robot gets assigned a goal pose beyond the end point automatically (even with interpolate_after_goal=false
). And the behaviour just doesn't feel right.
However, with the current implementation everything seems perfectly fine.
I don't understand the reason, but I'm sure I maybe missing something.
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.
Can you make a branch off this one and send a link here? I'll take a look
geometry_msgs::msg::PoseStamped target_pose = nav2_util::getLookAheadPoint( | ||
dist_to_target, transformed_plan, params_->interpolate_after_goal); | ||
|
||
valid_target_pose_found = validateTargetPose( |
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 if getLookAheadPoint
returns transformed_plan.poses.back()
because !pose_found
? Then dist_to_target
may not be params_->max_lookahead
for the input
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 loop in getLookAheadPoint
iterates from each pose calculating distance along path. The loop for it only breaks when distance found is greater than max_lookahead
. The only case, we get !pose_found
is when the distance is not able to add up to max_lookahead
.
This is fine, because the logic inside validateTargetPose
only rejects the point if it is at a distance greater than max_lookahead
or less than min_lookahead
. Otherwise, the code logic continues to run the same.
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'm saying that you ahve this block of code:
dist_to_target = params_->max_lookahead;
target_pose = nav2_util::getLookAheadPoint(
dist_to_target, transformed_plan, params_->interpolate_after_goal);
If the target_pose
is not exactly max_lookahead
away because !pose_found
, then the inputs to validateTargetPose
aren't consistent. The target_pose
is not actually dist_to_target
away. dist_to_target
is not updated based on the actual return of getLookAheadPoint
. dist_to_target == params_->max_lookahead
always in this case even when target_pose
is the last pose instead.
I think something like the following is necessary:
if (target_pose == transformed_plan.poses.back() {
// update dist_to_target
}
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.
Yes, understood now. It makes sense to change the lookahead distance if the path is clipped to end at the last pose.
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.
@SakshayMahna OK! Let me know when its implemented :-)
This pull request is in conflict. Could you fix it @SakshayMahna? |
@SakshayMahna, your PR has failed to build. Please check CI outputs and resolve issues. |
This pull request is in conflict. Could you fix it @SakshayMahna? |
Signed-off-by: Sakshay Mahna <sakshum19@gmail.com>
Hi @SteveMacenski Sorry for the mess. |
Signed-off-by: Sakshay Mahna <sakshum19@gmail.com>
Signed-off-by: Sakshay Mahna <sakshum19@gmail.com>
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.
You have a few open comments above, but overall doesn't this look simpler and cleaner! Great work 😄
nav2_util/src/controller_utils.cpp
Outdated
|
||
d += std::hypot(curr_pose.x - prev_pose.x, curr_pose.y - prev_pose.y); | ||
if (d >= lookahead_dist) { | ||
goal_pose_it = poses.begin() + i; |
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.
} else if (dist_to_target < params_->min_lookahead) { | ||
// Make sure target is far enough away to avoid instability | ||
break; | ||
for (int i = transformed_plan.poses.size(); i >= 0; --i) { |
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.
If you make this an unsigned int
instead of an int
, then you don't need the static_cast
below and nothing else needs to change
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.
On making it unsigned int, i>=0
condition will always remain valid.
And I am getting a compiler error for the same.
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.
@SakshayMahna unsigned int i = transformed_plan.poses.size(); i >= 0; --i
then if (i == transformed_plan.poses.size())
Signed-off-by: Sakshay Mahna <sakshum19@gmail.com>
Signed-off-by: Sakshay Mahna <sakshum19@gmail.com>
Signed-off-by: Sakshay Mahna <sakshum19@gmail.com>
@SakshayMahna, your PR has failed to build. Please check CI outputs and resolve issues. |
@SakshayMahna please rebase on main to make CI turn over. There was a message filter API change that we resolved in |
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.
Super close now! Take a look at the remaining open items from previous reviews (should be 3x) :-) Thanks for the iterations - this is much improved!
@@ -35,6 +35,7 @@ struct Parameters | |||
double transform_tolerance; | |||
double min_lookahead; | |||
double max_lookahead; | |||
bool interpolate_after_goal; |
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.
Remove since now unused :-)
} else if (dist_to_target < params_->min_lookahead) { | ||
// Make sure target is far enough away to avoid instability | ||
break; | ||
for (int i = transformed_plan.poses.size(); i >= 0; --i) { |
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.
@SakshayMahna unsigned int i = transformed_plan.poses.size(); i >= 0; --i
then if (i == transformed_plan.poses.size())
nav2_util/src/controller_utils.cpp
Outdated
|
||
d += std::hypot(curr_pose.x - prev_pose.x, curr_pose.y - prev_pose.y); | ||
if (d >= lookahead_dist) { | ||
goal_pose_it = poses.begin() + i; |
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.
Can you make a branch off this one and send a link here? I'll take a look
geometry_msgs::msg::PoseStamped target_pose = nav2_util::getLookAheadPoint( | ||
dist_to_target, transformed_plan, params_->interpolate_after_goal); | ||
|
||
valid_target_pose_found = validateTargetPose( |
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.
@SakshayMahna OK! Let me know when its implemented :-)
Basic Info
Description of contribution in a few bullet points
lookahead_resolution
andinterpolate_after_goal
)Additional Information
lookahead_resolution
.interpolate_after_goal
.Description of documentation updates required from your changes
Description of how this change was tested
Future work that may be required in bullet points
For Maintainers: