Skip to content
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

Make planning pipeline respect the allowed_planning_time #2692

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
41 changes: 38 additions & 3 deletions moveit_ros/planning/planning_pipeline/src/planning_pipeline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@
#include <moveit/planning_pipeline/planning_pipeline.h>
#include <fmt/format.h>
#include <moveit/utils/logger.hpp>
#include <chrono>
#include <ratio>
#include <stdexcept>

namespace
{
Expand Down Expand Up @@ -267,19 +270,32 @@ bool PlanningPipeline::generatePlan(const planning_scene::PlanningSceneConstPtr&
// ---------------------------------
// Solve the motion planning problem
// ---------------------------------

planning_interface::MotionPlanRequest mutable_request = req;
try
{
using clock = std::chrono::system_clock;
const auto plan_start_time = clock::now();
const double allowed_planning_time = req.allowed_planning_time;

// Call plan request adapter chain
for (const auto& req_adapter : planning_request_adapter_vector_)
{
assert(req_adapter);
RCLCPP_INFO(node_->get_logger(), "Calling PlanningRequestAdapter '%s'", req_adapter->getDescription().c_str());
const auto status = req_adapter->adapt(planning_scene, mutable_request);
res.error_code = status.val;
std::string message = status.message;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
std::string message = status.message;

// Publish progress
publishPipelineState(mutable_request, res, req_adapter->getDescription());

// check for timeout (mainly for sanity check since adapters are fast)
if (std::chrono::duration<double>(clock::now() - plan_start_time).count() >= allowed_planning_time)
{
message = "failed to finish within " + std::to_string(allowed_planning_time) + "s";
TomCC7 marked this conversation as resolved.
Show resolved Hide resolved
res.error_code = moveit::core::MoveItErrorCode::TIMED_OUT;
}

// If adapter does not succeed, break chain and return false
if (!res.error_code)
{
Expand All @@ -291,9 +307,15 @@ bool PlanningPipeline::generatePlan(const planning_scene::PlanningSceneConstPtr&
}

// Call planners
for (const auto& planner_name : pipeline_parameters_.planning_plugins)
for (size_t i = 0; i < pipeline_parameters_.planning_plugins.size(); i++)
{
const auto& planner = planner_map_.at(planner_name);
// modify planner request to notice plugins of their corresponding max_allowed_time
// NOTE: currently just evenly distributing the remaining time among the remaining planners
double max_single_planner_time = std::chrono::duration<double>(clock::now() - plan_start_time).count() /
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this time calculation. Can you explain it? Why are we giving the planner the time since the planning started as time budget? Maybe I am missing something. Also can you make this const?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry made a mistake, I changed it to

mutable_request.allowed_planning_time =
          (allowed_planning_time - std::chrono::duration<double>(clock::now() - plan_start_time).count()) /
          (pipeline_parameters_.planning_plugins.size() - i);

(pipeline_parameters_.planning_plugins.size() - i);
mutable_request.allowed_planning_time = max_single_planner_time;
TomCC7 marked this conversation as resolved.
Show resolved Hide resolved

const auto& planner = planner_map_.at(pipeline_parameters_.planning_plugins[i]);
TomCC7 marked this conversation as resolved.
Show resolved Hide resolved
// Update reference trajectory with latest solution (if available)
if (res.trajectory)
{
Expand All @@ -317,6 +339,12 @@ bool PlanningPipeline::generatePlan(const planning_scene::PlanningSceneConstPtr&
context->solve(res);
publishPipelineState(mutable_request, res, planner->getDescription());

// check for overall timeout
if (std::chrono::duration<double>(clock::now() - plan_start_time).count() >= allowed_planning_time)
{
res.error_code = moveit::core::MoveItErrorCode::TIMED_OUT;
}

// If planner does not succeed, break chain and return false
if (!res.error_code)
{
Expand All @@ -335,6 +363,13 @@ bool PlanningPipeline::generatePlan(const planning_scene::PlanningSceneConstPtr&
RCLCPP_INFO(node_->get_logger(), "Calling PlanningResponseAdapter '%s'", res_adapter->getDescription().c_str());
res_adapter->adapt(planning_scene, mutable_request, res);
publishPipelineState(mutable_request, res, res_adapter->getDescription());

// check for timeout
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I'm missing it, but it looks like we will hit a timeout here if the planners use up the allowed planning time. Optimizing planners usually do that, so I think we should either ignore response adapters or use a separate budget

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think the ultimate solution to this is to give user more control over the timeout. Maybe let them decide timeout of each section (and each planner) separately? But that would require changing the MotionPlanRequest message so I'm not sure. What's your suggestion on this? Should we make the PR more complex or just ignore the response adapters for now?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we ensure for now that a fraction of the time will be reserved for the planners? Something like 100ms in any case?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But in that case the planner might still use up all the reserved time. Or are you saying that giving the request adapter at least 100ms?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the response adapter should have a reserved time budget which is deducted from the planner's allowed planning time.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hi, I add an extra check to ensure that the response adapter has 10ms left in the latest commit

if (std::chrono::duration<double>(clock::now() - plan_start_time).count() >= allowed_planning_time)
{
res.error_code = moveit::core::MoveItErrorCode::TIMED_OUT;
}

// If adapter does not succeed, break chain and return false
if (!res.error_code)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#include <moveit/planning_interface/planning_interface.h>
#include <moveit/planning_interface/planning_request_adapter.h>
#include <moveit/planning_interface/planning_response_adapter.h>
#include <moveit/utils/moveit_error_code.h>
TomCC7 marked this conversation as resolved.
Show resolved Hide resolved
#include <class_loader/class_loader.hpp>

namespace planning_pipeline_test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#include <gtest/gtest.h>

#include <moveit/planning_pipeline/planning_pipeline.h>
#include <moveit/utils/moveit_error_code.h>
#include <moveit/utils/robot_model_test_utils.h>

namespace
Expand Down Expand Up @@ -92,6 +93,7 @@ TEST_F(TestPlanningPipeline, HappyPath)
// THEN A successful response is created
planning_interface::MotionPlanResponse motion_plan_response;
planning_interface::MotionPlanRequest motion_plan_request;
motion_plan_request.allowed_planning_time = 10;
const auto planning_scene_ptr = std::make_shared<planning_scene::PlanningScene>(robot_model_);
EXPECT_TRUE(pipeline_ptr_->generatePlan(planning_scene_ptr, motion_plan_request, motion_plan_response));
EXPECT_TRUE(motion_plan_response.error_code);
Expand All @@ -115,6 +117,34 @@ TEST_F(TestPlanningPipeline, NoPlannerPluginConfigured)
std::runtime_error);
}

TEST_F(TestPlanningPipeline, TestTimeout)
TomCC7 marked this conversation as resolved.
Show resolved Hide resolved
{
// construct pipline
EXPECT_NO_THROW(pipeline_ptr_ = std::make_shared<planning_pipeline::PlanningPipeline>(
robot_model_, node_, "", PLANNER_PLUGINS, REQUEST_ADAPTERS, RESPONSE_ADAPTERS));

// WHEN generatePlan is called
// THEN A successful response is created
planning_interface::MotionPlanResponse motion_plan_response;
planning_interface::MotionPlanRequest motion_plan_request;
const auto planning_scene_ptr = std::make_shared<planning_scene::PlanningScene>(robot_model_);
TomCC7 marked this conversation as resolved.
Show resolved Hide resolved

// timeout by request adapter
TomCC7 marked this conversation as resolved.
Show resolved Hide resolved
motion_plan_request.allowed_planning_time = 0.1;
EXPECT_FALSE(pipeline_ptr_->generatePlan(planning_scene_ptr, motion_plan_request, motion_plan_response));
EXPECT_EQ(motion_plan_response.error_code, moveit::core::MoveItErrorCode::TIMED_OUT);

// timeout by planner
motion_plan_request.allowed_planning_time = 1;
TomCC7 marked this conversation as resolved.
Show resolved Hide resolved
EXPECT_FALSE(pipeline_ptr_->generatePlan(planning_scene_ptr, motion_plan_request, motion_plan_response));
EXPECT_EQ(motion_plan_response.error_code, moveit::core::MoveItErrorCode::TIMED_OUT);

// passes
motion_plan_request.allowed_planning_time = 10;
EXPECT_TRUE(pipeline_ptr_->generatePlan(planning_scene_ptr, motion_plan_request, motion_plan_response));
EXPECT_EQ(motion_plan_response.error_code, moveit::core::MoveItErrorCode::SUCCESS);
TomCC7 marked this conversation as resolved.
Show resolved Hide resolved
}

int main(int argc, char** argv)
{
rclcpp::init(argc, argv);
Expand Down
Loading