Skip to content

Commit 627a8b6

Browse files
authored
Add debug topic to visualize whether MPPI critic has an effect on costs (#5485)
* Publish criticsStats Signed-off-by: Tony Najjar <[email protected]> * linting Signed-off-by: Tony Najjar <[email protected]> * change header to stamp Signed-off-by: Tony Najjar <[email protected]> * make unique_pointer Signed-off-by: Tony Najjar <[email protected]> * typo Signed-off-by: Tony Najjar <[email protected]> * Add readme Signed-off-by: Tony Najjar <[email protected]> * add to readme Signed-off-by: Tony Najjar <[email protected]> * fixes Signed-off-by: Tony Najjar <[email protected]> --------- Signed-off-by: Tony Najjar <[email protected]>
1 parent f040318 commit 627a8b6

File tree

9 files changed

+79
-2
lines changed

9 files changed

+79
-2
lines changed

nav2_mppi_controller/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ find_package(geometry_msgs REQUIRED)
88
find_package(nav2_common REQUIRED)
99
find_package(nav2_core REQUIRED)
1010
find_package(nav2_costmap_2d REQUIRED)
11+
find_package(nav2_msgs REQUIRED)
1112
find_package(nav2_util REQUIRED)
1213
find_package(nav_msgs REQUIRED)
1314
find_package(pluginlib REQUIRED)
@@ -59,6 +60,7 @@ target_link_libraries(mppi_controller PUBLIC
5960
nav2_ros_common::nav2_ros_common
6061
nav2_costmap_2d::layers
6162
nav2_costmap_2d::nav2_costmap_2d_core
63+
${nav2_msgs_TARGETS}
6264
${nav_msgs_TARGETS}
6365
pluginlib::pluginlib
6466
rclcpp::rclcpp

nav2_mppi_controller/README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ This process is then repeated a number of times and returns a converged solution
5959
| retry_attempt_limit | int | Default 1. Number of attempts to find feasible trajectory on failure for soft-resets before reporting failure. |
6060
| regenerate_noises | bool | Default false. Whether to regenerate noises each iteration or use single noise distribution computed on initialization and reset. Practically, this is found to work fine since the trajectories are being sampled stochastically from a normal distribution and reduces compute jittering at run-time due to thread wake-ups to resample normal distribution. |
6161
| publish_optimal_trajectory | bool | Publishes the full optimal trajectory sequence each control iteration for downstream control systems, collision checkers, etc to have context beyond the next timestep. |
62+
| publish_critics_stats | bool | Default false. Whether to publish statistics about each critic's performance. When enabled, publishes a `nav2_msgs::msg::CriticsStats` message containing critic names, whether they changed costs, and the sum of costs added by each critic. Useful for debugging and tuning critic behavior. |
6263

6364

6465
#### Trajectory Visualizer
@@ -293,6 +294,7 @@ controller_server:
293294
|---------------------------|----------------------------------|-----------------------------------------------------------------------|
294295
| `trajectories` | `visualization_msgs/MarkerArray` | Randomly generated trajectories, including resulting control sequence |
295296
| `transformed_global_plan` | `nav_msgs/Path` | Part of global plan considered by local planner |
297+
| `critics_stats` | `nav2_msgs/CriticsStats` | Statistics about each critic's performance (published when `publish_critics_stats` is enabled) |
296298

297299
## Notes to Users
298300

@@ -328,6 +330,26 @@ As you increase or decrease your weights on the Obstacle, you may notice the afo
328330

329331
Once you have your obstacle avoidance behavior tuned and matched with an appropriate path following penalty, tune the Path Align critic to align with the path. If you design exact-path-alignment behavior, its possible to skip the obstacle critic step as highly tuning the system to follow the path will give it less ability to deviate to avoid obstacles (though it'll slow and stop). Tuning the critic weight for the Obstacle critic high will do the job to avoid near-collisions but the repulsion weight is largely unnecessary to you. For others wanting more dynamic behavior, it _can_ be beneficial to slowly lower the weight on the obstacle critic to give the path alignment critic some more room to work. If your path was generated with a cost-aware planner (like all provided by Nav2) and providing paths sufficiently far from obstacles for your satisfaction, the impact of a slightly reduced Obstacle critic with a Path Alignment critic will do you well. Not over-weighting the path align critic will allow the robot to deviate from the path to get around dynamic obstacles in the scene or other obstacles not previous considered during path planning. It is subjective as to the best behavior for your application, but it has been shown that MPPI can be an exact path tracker and/or avoid dynamic obstacles very fluidly and everywhere in between. The defaults provided are in the generally right regime for a balanced initial trade-off.
330332

333+
### Critic costs debugging
334+
335+
The `publish_critics_stats` parameter enables publishing of statistics about each critic's performance, which can be visualized using tools like PlotJuggler or Foxglove to analyze and debug critic behavior.
336+
337+
The published `nav2_msgs::msg::CriticsStats` message contains the following fields:
338+
339+
- **stamp**: Timestamp of when the statistics were computed
340+
- **critics**: Array of critic names that were evaluated (e.g., "ConstraintCritic", "GoalCritic", "ObstaclesCritic")
341+
- **changed**: Boolean array indicating whether each critic modified the trajectory costs. `true` means the critic added non-zero costs, `false` means it had no effect
342+
- **costs_sum**: Array of the total cost contribution from each critic. This represents the sum of all costs added by that specific critic across all trajectory candidates
343+
344+
This data is invaluable for understanding:
345+
- Which critics are actively influencing trajectory selection
346+
- The relative impact of each critic on the final trajectory choice
347+
- Whether critics are working as expected or if parameter tuning is needed (e.g. `threshold_to_consider`)
348+
349+
More detailed statistics could be added in the future.
350+
351+
![critics_stats](media/critics_stats.png)
352+
331353
### MFMA and AVX2 Optimizations
332354

333355
This MPPI is made possible to run on CPU-only by using a very well optimized implementation that rely on CPU vectorization through AVX2 and MFMA. All even remotely modern computers support this (2013+), but if using a very old computer you may not be able to use the plugin. Note that MPC is computationally heavy to begin with, so computers circa-2013 even if it were to have those compiler flags available probably wouldn't run it at a satisfactory rate anyway.

nav2_mppi_controller/include/nav2_mppi_controller/critic_manager.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
#include "geometry_msgs/msg/twist.hpp"
2424
#include "geometry_msgs/msg/twist_stamped.hpp"
25+
#include "nav2_msgs/msg/critics_stats.hpp"
2526

2627
#include "nav2_costmap_2d/costmap_2d_ros.hpp"
2728
#include "rclcpp_lifecycle/lifecycle_node.hpp"
@@ -96,6 +97,9 @@ class CriticManager
9697
std::unique_ptr<pluginlib::ClassLoader<critics::CriticFunction>> loader_;
9798
Critics critics_;
9899

100+
nav2::Publisher<nav2_msgs::msg::CriticsStats>::SharedPtr critics_effect_pub_;
101+
bool publish_critics_stats_;
102+
99103
rclcpp::Logger logger_{rclcpp::get_logger("MPPIController")};
100104
};
101105

117 KB
Loading

nav2_mppi_controller/package.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
<depend>nav2_common</depend>
1818
<depend>nav2_core</depend>
1919
<depend>nav2_costmap_2d</depend>
20+
<depend>nav2_msgs</depend>
2021
<depend>nav2_util</depend>
2122
<depend>nav_msgs</depend>
2223
<depend>pluginlib</depend>

nav2_mppi_controller/src/critic_manager.cpp

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ void CriticManager::getParams()
3737
auto node = parent_.lock();
3838
auto getParam = parameters_handler_->getParamGetter(name_);
3939
getParam(critic_names_, "critics", std::vector<std::string>{}, ParameterType::Static);
40+
getParam(publish_critics_stats_, "publish_critics_stats", false, ParameterType::Static);
4041
}
4142

4243
void CriticManager::loadCritics()
@@ -46,6 +47,13 @@ void CriticManager::loadCritics()
4647
"nav2_mppi_controller", "mppi::critics::CriticFunction");
4748
}
4849

50+
auto node = parent_.lock();
51+
if (publish_critics_stats_) {
52+
critics_effect_pub_ = node->create_publisher<nav2_msgs::msg::CriticsStats>(
53+
"~/critics_stats");
54+
critics_effect_pub_->on_activate();
55+
}
56+
4957
critics_.clear();
5058
for (auto name : critic_names_) {
5159
std::string fullname = getFullName(name);
@@ -67,11 +75,44 @@ std::string CriticManager::getFullName(const std::string & name)
6775
void CriticManager::evalTrajectoriesScores(
6876
CriticData & data) const
6977
{
70-
for (const auto & critic : critics_) {
78+
std::unique_ptr<nav2_msgs::msg::CriticsStats> stats_msg;
79+
if (publish_critics_stats_) {
80+
stats_msg = std::make_unique<nav2_msgs::msg::CriticsStats>();
81+
stats_msg->critics.reserve(critics_.size());
82+
stats_msg->changed.reserve(critics_.size());
83+
stats_msg->costs_sum.reserve(critics_.size());
84+
}
85+
86+
for (size_t i = 0; i < critics_.size(); ++i) {
7187
if (data.fail_flag) {
7288
break;
7389
}
74-
critic->score(data);
90+
91+
// Store costs before critic evaluation
92+
Eigen::ArrayXf costs_before;
93+
if (publish_critics_stats_) {
94+
costs_before = data.costs;
95+
}
96+
97+
critics_[i]->score(data);
98+
99+
// Calculate statistics if publishing is enabled
100+
if (publish_critics_stats_) {
101+
stats_msg->critics.push_back(critic_names_[i]);
102+
103+
// Calculate sum of costs added by this individual critic
104+
Eigen::ArrayXf cost_diff = data.costs - costs_before;
105+
float costs_sum = cost_diff.sum();
106+
stats_msg->costs_sum.push_back(costs_sum);
107+
stats_msg->changed.push_back(costs_sum != 0.0f);
108+
}
109+
}
110+
111+
// Publish statistics if enabled
112+
if (critics_effect_pub_) {
113+
auto node = parent_.lock();
114+
stats_msg->stamp = node->get_clock()->now();
115+
critics_effect_pub_->publish(std::move(stats_msg));
75116
}
76117
}
77118

nav2_msgs/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ rosidl_generate_interfaces(${PROJECT_NAME}
3030
"msg/VoxelGrid.msg"
3131
"msg/BehaviorTreeStatusChange.msg"
3232
"msg/BehaviorTreeLog.msg"
33+
"msg/CriticsStats.msg"
3334
"msg/Particle.msg"
3435
"msg/ParticleCloud.msg"
3536
"msg/WaypointStatus.msg"

nav2_msgs/msg/CriticsStats.msg

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Critics statistics message
2+
builtin_interfaces/Time stamp
3+
string[] critics
4+
bool[] changed
5+
float32[] costs_sum

nav2_system_tests/src/gps_navigation/nav2_no_map_params.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ controller_server:
7676
temperature: 0.3
7777
gamma: 0.015
7878
motion_model: "DiffDrive"
79+
publish_critics_stats: true
7980
visualize: true
8081
regenerate_noises: true
8182
TrajectoryVisualizer:

0 commit comments

Comments
 (0)