Skip to content

Commit 7b594b0

Browse files
Adding initial prototype for a MPPI trajectory validator (#5319)
* Adding initial prototype for a MPPI trajectory validator Signed-off-by: SteveMacenski <stevenmacenski@gmail.com> * Adding in control sequence and renaming optimal trajectory Signed-off-by: SteveMacenski <stevenmacenski@gmail.com> * Fix broken tests Signed-off-by: SteveMacenski <stevenmacenski@gmail.com> * Adding trajectory validation collision checks Signed-off-by: SteveMacenski <stevenmacenski@gmail.com> * Last stylistic change Signed-off-by: SteveMacenski <stevenmacenski@gmail.com> * Update optimal_trajectory_validator.hpp Signed-off-by: Steve Macenski <stevenmacenski@gmail.com> --------- Signed-off-by: SteveMacenski <stevenmacenski@gmail.com> Signed-off-by: Steve Macenski <stevenmacenski@gmail.com>
1 parent b9817db commit 7b594b0

File tree

13 files changed

+333
-36
lines changed

13 files changed

+333
-36
lines changed

nav2_bringup/params/nav2_params.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,11 @@ controller_server:
141141
TrajectoryVisualizer:
142142
trajectory_step: 5
143143
time_step: 3
144+
TrajectoryValidator:
145+
# The validator can be configured with additional parameters if needed.
146+
plugin: "mppi::DefaultOptimalTrajectoryValidator"
147+
collision_lookahead_time: 2.0 # Time to look ahead to check for collisions
148+
consider_footprint: false # Whether to consider the full robot's footprint (or circle) in trajectory validation collision checks
144149
AckermannConstraints:
145150
min_turning_r: 0.2
146151
critics:

nav2_mppi_controller/CMakeLists.txt

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,35 @@ target_link_libraries(mppi_critics PRIVATE
107107
pluginlib::pluginlib
108108
)
109109

110-
install(TARGETS mppi_controller mppi_critics
110+
add_library(mppi_trajectory_validators SHARED
111+
src/trajectory_validators/optimal_trajectory_validator.cpp
112+
)
113+
target_compile_options(mppi_trajectory_validators PUBLIC -O3)
114+
target_include_directories(mppi_trajectory_validators
115+
PUBLIC
116+
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
117+
"$<INSTALL_INTERFACE:include/${PROJECT_NAME}>"
118+
"$<BUILD_INTERFACE:${nav2_ros_common_INCLUDE_DIRS}>")
119+
target_link_libraries(mppi_trajectory_validators PUBLIC
120+
angles::angles
121+
${geometry_msgs_TARGETS}
122+
nav2_core::nav2_core
123+
nav2_costmap_2d::layers
124+
nav2_costmap_2d::nav2_costmap_2d_core
125+
${nav_msgs_TARGETS}
126+
rclcpp::rclcpp
127+
rclcpp_lifecycle::rclcpp_lifecycle
128+
${std_msgs_TARGETS}
129+
tf2::tf2
130+
tf2_geometry_msgs::tf2_geometry_msgs
131+
tf2_ros::tf2_ros
132+
${visualization_msgs_TARGETS}
133+
)
134+
target_link_libraries(mppi_trajectory_validators PRIVATE
135+
pluginlib::pluginlib
136+
)
137+
138+
install(TARGETS mppi_controller mppi_critics mppi_trajectory_validators
111139
EXPORT nav2_mppi_controller
112140
ARCHIVE DESTINATION lib
113141
LIBRARY DESTINATION lib
@@ -153,5 +181,6 @@ ament_export_targets(nav2_mppi_controller)
153181

154182
pluginlib_export_plugin_description_file(nav2_core mppic.xml)
155183
pluginlib_export_plugin_description_file(nav2_mppi_controller critics.xml)
184+
pluginlib_export_plugin_description_file(nav2_mppi_controller trajectory_validators.xml)
156185

157186
ament_package()

nav2_mppi_controller/benchmark/optimizer_benchmark.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include <nav2_costmap_2d/costmap_2d.hpp>
2929
#include <nav2_costmap_2d/costmap_2d_ros.hpp>
3030
#include <nav2_core/goal_checker.hpp>
31+
#include "tf2_ros/buffer.h"
3132

3233
#include "nav2_mppi_controller/optimizer.hpp"
3334
#include "nav2_mppi_controller/motion_models.hpp"
@@ -81,7 +82,8 @@ void prepareAndRunBenchmark(
8182
auto node = getDummyNode(optimizer_settings, critics);
8283
std::string name = "test";
8384
auto parameters_handler = std::make_unique<mppi::ParametersHandler>(node, name);
84-
auto optimizer = getDummyOptimizer(node, costmap_ros, parameters_handler.get());
85+
auto tf_buffer = std::make_shared<tf2_ros::Buffer>(node->get_clock());
86+
auto optimizer = getDummyOptimizer(node, costmap_ros, tf_buffer, parameters_handler.get());
8587

8688
// evalControl args
8789
auto pose = getDummyPointStamped(node, start_pose);
@@ -90,7 +92,8 @@ void prepareAndRunBenchmark(
9092
nav2_core::GoalChecker * dummy_goal_checker{nullptr};
9193

9294
for (auto _ : state) {
93-
optimizer->evalControl(pose, velocity, path, path.poses.back().pose, dummy_goal_checker);
95+
auto [cmd, trajectory] = optimizer->evalControl(pose, velocity, path, path.poses.back().pose,
96+
dummy_goal_checker);
9497
}
9598
}
9699

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
// Copyright (c) 2025 Open Navigation LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef NAV2_MPPI_CONTROLLER__OPTIMAL_TRAJECTORY_VALIDATOR_HPP_
16+
#define NAV2_MPPI_CONTROLLER__OPTIMAL_TRAJECTORY_VALIDATOR_HPP_
17+
18+
#include <Eigen/Core>
19+
#include <string>
20+
#include <memory>
21+
22+
#include "nav2_ros_common/lifecycle_node.hpp"
23+
#include "nav2_costmap_2d/costmap_2d_ros.hpp"
24+
#include "tf2_ros/buffer.h"
25+
#include "geometry_msgs/msg/twist.hpp"
26+
#include "geometry_msgs/msg/pose_stamped.hpp"
27+
#include "nav_msgs/msg/path.hpp"
28+
29+
#include "nav2_mppi_controller/tools/parameters_handler.hpp"
30+
#include "nav2_mppi_controller/models/control_sequence.hpp"
31+
#include "nav2_mppi_controller/models/optimizer_settings.hpp"
32+
33+
namespace mppi
34+
{
35+
36+
enum class ValidationResult
37+
{
38+
SUCCESS,
39+
SOFT_RESET,
40+
FAILURE,
41+
};
42+
43+
/**
44+
* @class mppi::OptimalTrajectoryValidator
45+
* @brief Abstract base class for validating optimal trajectories from MPPI optimization
46+
*/
47+
class OptimalTrajectoryValidator
48+
{
49+
public:
50+
using Ptr = std::shared_ptr<OptimalTrajectoryValidator>;
51+
52+
/**
53+
* @brief Constructor for mppi::OptimalTrajectoryValidator
54+
*/
55+
OptimalTrajectoryValidator() = default;
56+
57+
/**
58+
* @brief Destructor for mppi::OptimalTrajectoryValidator
59+
*/
60+
virtual ~OptimalTrajectoryValidator() = default;
61+
62+
/**
63+
* @brief Initialize the trajectory validator
64+
* @param node Weak pointer to the lifecycle node
65+
* @param name Name of the validator plugin
66+
* @param costmap Shared pointer to the costmap ROS wrapper
67+
* @param param_handler Pointer to the parameters handler
68+
* @param tf_buffer Shared pointer to the TF buffer
69+
* @param settings Settings for the MPPI optimizer
70+
*/
71+
virtual void initialize(
72+
const nav2::LifecycleNode::WeakPtr & parent,
73+
const std::string & name,
74+
const std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap,
75+
ParametersHandler * param_handler,
76+
const std::shared_ptr<tf2_ros::Buffer> tf_buffer,
77+
const models::OptimizerSettings & settings)
78+
{
79+
param_handler_ = param_handler;
80+
name_ = name;
81+
node_ = parent;
82+
costmap_ros_ = costmap;
83+
tf_buffer_ = tf_buffer;
84+
85+
auto node = node_.lock();
86+
auto getParam = param_handler_->getParamGetter(name_);
87+
getParam(collision_lookahead_time_, "collision_lookahead_time", 2.0f);
88+
traj_samples_to_evaluate_ = collision_lookahead_time_ / settings.model_dt;
89+
if (traj_samples_to_evaluate_ > settings.time_steps) {
90+
traj_samples_to_evaluate_ = settings.time_steps;
91+
RCLCPP_WARN(
92+
node->get_logger(),
93+
"Collision lookahead time is greater than the number of trajectory samples, "
94+
"setting it to the maximum number of samples (%u).",
95+
traj_samples_to_evaluate_);
96+
}
97+
98+
getParam(consider_footprint_, "consider_footprint", false);
99+
if (consider_footprint_) {
100+
collision_checker_ = std::make_unique<nav2_costmap_2d::FootprintCollisionChecker<
101+
nav2_costmap_2d::Costmap2D *>>(costmap_ros_->getCostmap());
102+
}
103+
}
104+
105+
/**
106+
* @brief Validate the optimal trajectory from MPPI optimization
107+
* Could be used to check for collisions, progress towards goal,
108+
* distance from path, min distance from obstacles, dynamic feasibility, etc.
109+
* @param optimal_trajectory The optimal trajectory to validate
110+
* @param control_sequence The control sequence to validate
111+
* @param robot_pose The current pose of the robot
112+
* @param robot_speed The current speed of the robot
113+
* @param plan The planned path for the robot
114+
* @param goal The goal pose for the robot
115+
* @return True if the trajectory is valid, false otherwise
116+
*/
117+
virtual ValidationResult validateTrajectory(
118+
const Eigen::ArrayXXf & optimal_trajectory,
119+
const models::ControlSequence & /*control_sequence*/,
120+
const geometry_msgs::msg::PoseStamped & /*robot_pose*/,
121+
const geometry_msgs::msg::Twist & /*robot_speed*/,
122+
const nav_msgs::msg::Path & /*plan*/,
123+
const geometry_msgs::msg::Pose & /*goal*/)
124+
{
125+
// The Optimizer automatically ensures that we are within Kinematic
126+
// and dynamic constraints, no need to check for those again.
127+
128+
// Check for collisions. This is highly unlikely to occur since the Obstacle/Cost Critics
129+
// penalize collisions severely, but it is still possible if those critics are not used or the
130+
// optimized trajectory is very near obstacles and the dynamic constraints cause invalidity.
131+
auto costmap = costmap_ros_->getCostmap();
132+
for (size_t i = 0; i < traj_samples_to_evaluate_; ++i) {
133+
const double x = static_cast<double>(optimal_trajectory(i, 0));
134+
const double y = static_cast<double>(optimal_trajectory(i, 1));
135+
136+
if (consider_footprint_) {
137+
const double theta = static_cast<double>(optimal_trajectory(i, 2));
138+
double footprint_cost = collision_checker_->footprintCostAtPose(
139+
x, y, theta, costmap_ros_->getRobotFootprint());
140+
if (footprint_cost == static_cast<double>(nav2_costmap_2d::LETHAL_OBSTACLE)) {
141+
return ValidationResult::SOFT_RESET;
142+
}
143+
} else {
144+
unsigned int x_i = 0u, y_i = 0u;
145+
if (!costmap->worldToMap(x, y, x_i, y_i)) {
146+
continue; // Out of bounds, skip this point
147+
}
148+
unsigned char cost = costmap->getCost(x_i, y_i);
149+
if (cost == nav2_costmap_2d::INSCRIBED_INFLATED_OBSTACLE ||
150+
cost == nav2_costmap_2d::LETHAL_OBSTACLE)
151+
{
152+
return ValidationResult::SOFT_RESET;
153+
}
154+
}
155+
}
156+
157+
return ValidationResult::SUCCESS;
158+
}
159+
160+
protected:
161+
nav2::LifecycleNode::WeakPtr node_;
162+
std::string name_;
163+
std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros_;
164+
ParametersHandler * param_handler_;
165+
std::shared_ptr<tf2_ros::Buffer> tf_buffer_;
166+
float collision_lookahead_time_{1.0f};
167+
unsigned int traj_samples_to_evaluate_{0u};
168+
bool consider_footprint_{false};
169+
std::unique_ptr<nav2_costmap_2d::FootprintCollisionChecker<nav2_costmap_2d::Costmap2D *>>
170+
collision_checker_;
171+
};
172+
173+
} // namespace mppi
174+
175+
#endif // NAV2_MPPI_CONTROLLER__OPTIMAL_TRAJECTORY_VALIDATOR_HPP_

nav2_mppi_controller/include/nav2_mppi_controller/optimizer.hpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,15 @@
1919

2020
#include <string>
2121
#include <memory>
22+
#include <tuple>
2223

2324
#include "rclcpp_lifecycle/lifecycle_node.hpp"
2425

2526
#include "nav2_costmap_2d/costmap_2d_ros.hpp"
2627
#include "nav2_core/goal_checker.hpp"
2728
#include "nav2_core/controller_exceptions.hpp"
29+
#include "tf2_ros/buffer.h"
30+
#include "pluginlib/class_loader.hpp"
2831

2932
#include "geometry_msgs/msg/twist.hpp"
3033
#include "geometry_msgs/msg/pose_stamped.hpp"
@@ -40,6 +43,7 @@
4043
#include "nav2_mppi_controller/tools/noise_generator.hpp"
4144
#include "nav2_mppi_controller/tools/parameters_handler.hpp"
4245
#include "nav2_mppi_controller/tools/utils.hpp"
46+
#include "nav2_mppi_controller/optimal_trajectory_validator.hpp"
4347

4448
namespace mppi
4549
{
@@ -68,10 +72,12 @@ class Optimizer
6872
* @param name Name of plugin
6973
* @param costmap_ros Costmap2DROS object of environment
7074
* @param dynamic_parameter_handler Parameter handler object
75+
* @param tf_buffer TF buffer for transformations
7176
*/
7277
void initialize(
7378
nav2::LifecycleNode::WeakPtr parent, const std::string & name,
7479
std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros,
80+
std::shared_ptr<tf2_ros::Buffer> tf_buffer,
7581
ParametersHandler * dynamic_parameters_handler);
7682

7783
/**
@@ -86,9 +92,9 @@ class Optimizer
8692
* @param plan Path plan to track
8793
* @param goal Given Goal pose to reach.
8894
* @param goal_checker Object to check if goal is completed
89-
* @return TwistStamped of the MPPI control
95+
* @return Tuple of [TwistStamped command, optimal trajectory]
9096
*/
91-
geometry_msgs::msg::TwistStamped evalControl(
97+
std::tuple<geometry_msgs::msg::TwistStamped, Eigen::ArrayXXf> evalControl(
9298
const geometry_msgs::msg::PoseStamped & robot_pose,
9399
const geometry_msgs::msg::Twist & robot_speed, const nav_msgs::msg::Path & plan,
94100
const geometry_msgs::msg::Pose & goal, nav2_core::GoalChecker * goal_checker);
@@ -254,13 +260,17 @@ class Optimizer
254260
std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros_;
255261
nav2_costmap_2d::Costmap2D * costmap_;
256262
std::string name_;
263+
std::shared_ptr<tf2_ros::Buffer> tf_buffer_;
257264

258265
std::shared_ptr<MotionModel> motion_model_;
259266

260267
ParametersHandler * parameters_handler_;
261268
CriticManager critic_manager_;
262269
NoiseGenerator noise_generator_;
263270

271+
std::unique_ptr<pluginlib::ClassLoader<OptimalTrajectoryValidator>> validator_loader_;
272+
OptimalTrajectoryValidator::Ptr trajectory_validator_;
273+
264274
models::OptimizerSettings settings_;
265275

266276
models::State state_;

nav2_mppi_controller/package.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
<build_type>ament_cmake</build_type>
4040
<nav2_core plugin="${prefix}/mppic.xml" />
4141
<nav2_mppi_controller plugin="${prefix}/critics.xml" />
42+
<nav2_mppi_controller plugin="${prefix}/trajectory_validators.xml" />
4243
</export>
4344

4445
</package>

nav2_mppi_controller/src/controller.cpp

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ void MPPIController::configure(
4141
getParam(publish_optimal_trajectory_, "publish_optimal_trajectory", false);
4242

4343
// Configure composed objects
44-
optimizer_.initialize(parent_, name_, costmap_ros_, parameters_handler_.get());
44+
optimizer_.initialize(parent_, name_, costmap_ros_, tf_buffer_, parameters_handler_.get());
4545
path_handler_.initialize(parent_, name_, costmap_ros_, tf_buffer_, parameters_handler_.get());
4646
trajectory_visualizer_.on_configure(
4747
parent_, name_,
@@ -106,7 +106,7 @@ geometry_msgs::msg::TwistStamped MPPIController::computeVelocityCommands(
106106
nav2_costmap_2d::Costmap2D * costmap = costmap_ros_->getCostmap();
107107
std::unique_lock<nav2_costmap_2d::Costmap2D::mutex_t> costmap_lock(*(costmap->getMutex()));
108108

109-
geometry_msgs::msg::TwistStamped cmd =
109+
auto [cmd, optimal_trajectory] =
110110
optimizer_.evalControl(robot_pose, robot_speed, transformed_plan, goal, goal_checker);
111111

112112
#ifdef BENCHMARK_TESTING
@@ -115,9 +115,7 @@ geometry_msgs::msg::TwistStamped MPPIController::computeVelocityCommands(
115115
RCLCPP_INFO(logger_, "Control loop execution time: %ld [ms]", duration);
116116
#endif
117117

118-
Eigen::ArrayXXf optimal_trajectory;
119118
if (publish_optimal_trajectory_ && opt_traj_pub_->get_subscription_count() > 0) {
120-
optimal_trajectory = optimizer_.getOptimizedTrajectory();
121119
auto trajectory_msg = utils::toTrajectoryMsg(
122120
optimal_trajectory,
123121
optimizer_.getOptimalControlSequence(),
@@ -139,12 +137,7 @@ void MPPIController::visualize(
139137
const Eigen::ArrayXXf & optimal_trajectory)
140138
{
141139
trajectory_visualizer_.add(optimizer_.getGeneratedTrajectories(), "Candidate Trajectories");
142-
if (optimal_trajectory.size() > 0) {
143-
trajectory_visualizer_.add(optimal_trajectory, "Optimal Trajectory", cmd_stamp);
144-
} else {
145-
trajectory_visualizer_.add(
146-
optimizer_.getOptimizedTrajectory(), "Optimal Trajectory", cmd_stamp);
147-
}
140+
trajectory_visualizer_.add(optimal_trajectory, "Optimal Trajectory", cmd_stamp);
148141
trajectory_visualizer_.visualize(std::move(transformed_plan));
149142
}
150143

0 commit comments

Comments
 (0)