Skip to content

Commit 895f2e2

Browse files
committed
Styling and ordering of files refining comments
Signed-off-by: silanus23 <berkantali23@outlook.com>
1 parent b008e25 commit 895f2e2

2 files changed

Lines changed: 103 additions & 92 deletions

File tree

nav2_mppi_controller/include/nav2_mppi_controller/critics/path_align_critic.hpp

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#ifndef NAV2_MPPI_CONTROLLER__CRITICS__PATH_ALIGN_CRITIC_HPP_
1616
#define NAV2_MPPI_CONTROLLER__CRITICS__PATH_ALIGN_CRITIC_HPP_
1717

18+
#include <algorithm>
1819
#include <vector>
1920

2021
#include "nav2_mppi_controller/critic_function.hpp"
@@ -48,26 +49,54 @@ class PathAlignCritic : public CriticFunction
4849

4950
protected:
5051
/**
51-
* @brief Score trajectories using arc-length matching
52+
* @brief Update cached path data when path changes
53+
*
54+
* Precomputes and caches path segment geometry including segment vectors,
55+
* lengths, and cumulative arc-length distances. Only recomputes when the path
56+
* actually changes (detected via size and key point checks).
57+
*
58+
* @param path Reference path to cache
59+
* @param path_segments_count Number of segments to consider from path start
5260
*/
53-
void scoreArcLength(CriticData & data, std::vector<bool> & path_pts_valid);
61+
void updatePathCache(const models::Path & path, size_t path_segments_count);
5462

5563
/**
56-
* @brief Score trajectories using geometric distance to path segments
64+
* @brief Score trajectories using arc-length matching along path
65+
*
66+
* Integrates distance traveled along each trajectory and matches it to equivalent
67+
* arc-length positions on the reference path. Penalizes Euclidean distance between
68+
* trajectory points and their corresponding path points. Optionally includes
69+
* orientation difference when use_path_orientations_ is enabled.
70+
*
71+
* @param data Critic data containing trajectories and path information
72+
* @param path_pts_valid Validity flags for each path point (collision-free or not)
5773
*/
58-
void scoreGeometric(CriticData & data, std::vector<bool> & path_pts_valid);
74+
void scoreArcLength(CriticData & data, std::vector<bool> & path_pts_valid);
5975

6076
/**
61-
* @brief Update cached path data when path changes
77+
* @brief Score trajectories using geometric distance to path segments
78+
*
79+
* Samples points along each trajectory and computes their perpendicular distance
80+
* to the nearest path segment. Uses a hint-based search within a configurable
81+
* window for efficiency. Only scores points within lookahead_distance_ of the
82+
* trajectory start.
83+
*
84+
* @param data Critic data containing trajectories and path information
85+
* @param path_pts_valid Validity flags for each path point (collision-free or not)
6286
*/
63-
void updatePathCache(const models::Path & path, size_t path_segments_count);
87+
void scoreGeometric(CriticData & data, std::vector<bool> & path_pts_valid);
6488

6589
/**
6690
* @brief Compute minimum distance from point to any path segment
91+
*
92+
* Uses a search window around the hint index to find the nearest segment,
93+
* exploiting path locality. The window is defined in arc-length distance
94+
* along the path. Updates the hint for subsequent calls.
95+
*
6796
* @param px Point x coordinate
6897
* @param py Point y coordinate
69-
* @param closest_seg_idx In/out: hint for search, updated to closest segment
70-
* @return Distance to closest segment
98+
* @param closest_seg_idx [in/out] Search hint, updated to closest segment found
99+
* @return Euclidean distance to closest segment
71100
*/
72101
float computeMinDistanceToPath(float px, float py, Eigen::Index & closest_seg_idx);
73102

nav2_mppi_controller/src/critics/path_align_critic.cpp

Lines changed: 66 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,13 @@ void PathAlignCritic::initialize()
4646

4747
RCLCPP_INFO(
4848
logger_,
49-
"PathAlignCritic instantiated with %d power, arc_length_weight: %f, geometric_weight: %f, mode: %s",
49+
"PathAlignCritic instantiated with %d power, arc_length_weight: %f, "
50+
"geometric_weight: %f, mode: %s",
5051
power_, arc_length_weight_, geometric_weight_, mode.c_str());
5152
}
5253

5354
void PathAlignCritic::score(CriticData & data)
5455
{
55-
// Skip scoring if disabled or path too short
5656
if (!enabled_ || data.state.local_path_length < threshold_to_consider_) {
5757
return;
5858
}
@@ -76,14 +76,12 @@ void PathAlignCritic::score(CriticData & data)
7676
if (invalid_ctr / static_cast<float>(path_segments_count) > max_path_occupancy_ratio_ &&
7777
invalid_ctr > 2.0f)
7878
{
79-
return; // Too many obstacles on path, don't score
79+
return;
8080
}
8181
}
8282

83-
// Cache path segment geometry for efficient distance calculations
8483
updatePathCache(data.path, path_segments_count);
8584

86-
// Apply selected scoring mode(s)
8785
if (use_geometric_alignment_) {
8886
scoreGeometric(data, path_pts_valid);
8987
}
@@ -92,6 +90,63 @@ void PathAlignCritic::score(CriticData & data)
9290
}
9391
}
9492

93+
void PathAlignCritic::updatePathCache(const models::Path & path, size_t path_segments_count)
94+
{
95+
if (path_segments_count == 0) {
96+
path_size_cache_ = 0;
97+
return;
98+
}
99+
100+
// path_segments_count is the number of segments, so number of points is segments + 1
101+
const size_t path_points_count = path_segments_count + 1;
102+
103+
// Detect path changes cheaply using size and strategic point comparisons
104+
const Eigen::Index path_points_idx = static_cast<Eigen::Index>(path_points_count);
105+
const Eigen::Index mid_idx = path_points_idx / 2;
106+
if (path_points_idx == static_cast<Eigen::Index>(path_size_cache_) &&
107+
path_x_cache_.size() == path_points_idx &&
108+
path.x(0) == path_x_cache_(0) &&
109+
path.y(0) == path_y_cache_(0) &&
110+
path.x(mid_idx) == path_x_cache_(mid_idx) &&
111+
path.y(mid_idx) == path_y_cache_(mid_idx))
112+
{
113+
return;
114+
}
115+
116+
path_size_cache_ = path_points_count;
117+
118+
if (static_cast<Eigen::Index>(path_x_cache_.size()) != path_points_idx) {
119+
path_x_cache_.resize(path_points_idx);
120+
path_y_cache_.resize(path_points_idx);
121+
}
122+
123+
path_x_cache_.head(path_points_idx) = path.x.head(path_points_idx);
124+
path_y_cache_.head(path_points_idx) = path.y.head(path_points_idx);
125+
126+
if (path_points_count < 2) {return;}
127+
128+
const Eigen::Index num_segments = path_points_idx - 1;
129+
130+
if (static_cast<Eigen::Index>(segment_lengths_.size()) != num_segments) {
131+
segment_lengths_.resize(num_segments);
132+
cumulative_distances_.resize(path_points_idx);
133+
segment_dx_.resize(num_segments);
134+
segment_dy_.resize(num_segments);
135+
segment_len_sq_.resize(num_segments);
136+
segment_inv_len_sq_.resize(num_segments);
137+
}
138+
139+
cumulative_distances_(0) = 0.0;
140+
for (Eigen::Index i = 0; i < num_segments; ++i) {
141+
segment_dx_(i) = path.x(i + 1) - path.x(i);
142+
segment_dy_(i) = path.y(i + 1) - path.y(i);
143+
segment_len_sq_(i) = segment_dx_(i) * segment_dx_(i) + segment_dy_(i) * segment_dy_(i);
144+
segment_lengths_(i) = std::sqrt(segment_len_sq_(i));
145+
cumulative_distances_(i + 1) = cumulative_distances_(i) + segment_lengths_(i);
146+
segment_inv_len_sq_(i) = (segment_len_sq_(i) > 1e-6f) ? (1.0f / segment_len_sq_(i)) : 0.0f;
147+
}
148+
}
149+
95150
void PathAlignCritic::scoreArcLength(CriticData & data, std::vector<bool> & path_pts_valid)
96151
{
97152
const size_t path_segments_count = *data.furthest_reached_path_point;
@@ -112,10 +167,10 @@ void PathAlignCritic::scoreArcLength(CriticData & data, std::vector<bool> & path
112167
unsigned int path_pt = 0u;
113168
float traj_integrated_distance = 0.0f;
114169

170+
// Sample trajectories at intervals to reduce computation while maintaining accuracy
115171
int strided_traj_rows = data.trajectories.x.rows();
116172
int strided_traj_cols = floor((data.trajectories.x.cols() - 1) / trajectory_point_step_) + 1;
117173
int outer_stride = strided_traj_rows * trajectory_point_step_;
118-
// Get strided trajectory information
119174
const auto T_x = Eigen::Map<const Eigen::ArrayXXf, 0,
120175
Eigen::Stride<-1, -1>>(
121176
data.trajectories.x.data(),
@@ -145,12 +200,12 @@ void PathAlignCritic::scoreArcLength(CriticData & data, std::vector<bool> & path
145200
Tx_m1 = Tx;
146201
Ty_m1 = Ty;
147202
traj_integrated_distance += sqrtf(dx * dx + dy * dy);
203+
// Find path point at equivalent arc-length distance
148204
path_pt = utils::findClosestPathPtBinary(
149205
cumulative_distances_.data(), path_segments_count,
150206
traj_integrated_distance);
151207

152-
// The nearest path point to align to needs to be not in collision, else
153-
// let the obstacle critic take over in this region due to dynamic obstacles
208+
// Skip invalid path points - let obstacle critic handle dynamic obstacles
154209
if (path_pts_valid[path_pt]) {
155210
const auto & pose = path[path_pt];
156211
dx = pose.x - Tx;
@@ -193,13 +248,12 @@ void PathAlignCritic::scoreGeometric(CriticData & data, std::vector<bool> & path
193248
Eigen::ArrayXi valid_sample_count(batch_size);
194249
valid_sample_count.setZero();
195250

196-
// Store closest segment index per trajectory for search efficiency
251+
// Initialize search hints from robot's current position for efficiency
197252
if (closest_indices_.size() != batch_size) {
198253
closest_indices_.resize(batch_size);
199254
closest_indices_.setZero();
200255
}
201256

202-
// Reset hints to robot's current position on path to avoid stale hints from previous cycle
203257
const float robot_x = data.state.pose.pose.position.x;
204258
const float robot_y = data.state.pose.pose.position.y;
205259
const size_t path_segments_count = *data.furthest_reached_path_point;
@@ -208,7 +262,6 @@ void PathAlignCritic::scoreGeometric(CriticData & data, std::vector<bool> & path
208262
Eigen::Index current_closest_seg = 0;
209263
float min_dist_sq = std::numeric_limits<float>::max();
210264

211-
// Find initial hint by locating robot position on path
212265
for (Eigen::Index i = 0;
213266
i < num_segments && i < static_cast<Eigen::Index>(path_segments_count);
214267
++i)
@@ -225,17 +278,14 @@ void PathAlignCritic::scoreGeometric(CriticData & data, std::vector<bool> & path
225278
const int effective_stride = std::max(1, std::min(trajectory_point_step_,
226279
static_cast<int>(traj_length)));
227280

228-
// Score each trajectory in the batch
229281
for (Eigen::Index traj_idx = 0; traj_idx < batch_size; ++traj_idx) {
230282
float accumulated_distance = 0.0f;
231283

232-
// Start from first trajectory point
233284
float prev_x = traj_x(traj_idx, 0);
234285
float prev_y = traj_y(traj_idx, 0);
235286

236287
int sample_idx = 0;
237288

238-
// Sample trajectory points at regular intervals
239289
while (true) {
240290
const Eigen::Index traj_col = sample_idx * effective_stride;
241291
if (traj_col >= traj_length) {break;}
@@ -249,15 +299,13 @@ void PathAlignCritic::scoreGeometric(CriticData & data, std::vector<bool> & path
249299
const float dy = py - prev_y;
250300
accumulated_distance += std::sqrt(dx * dx + dy * dy);
251301

252-
// Stop scoring beyond lookahead distance
253302
if (accumulated_distance > lookahead_distance_) {break;}
254303
}
255304

256305
// Use previous closest segment as hint for efficient search
257306
Eigen::Index closest_seg = closest_indices_(traj_idx);
258307
float min_dist = computeMinDistanceToPath(px, py, closest_seg);
259308

260-
// Only accumulate cost if the closest segment is valid
261309
if (closest_seg < static_cast<Eigen::Index>(path_segments_count) &&
262310
path_pts_valid[closest_seg])
263311
{
@@ -272,96 +320,32 @@ void PathAlignCritic::scoreGeometric(CriticData & data, std::vector<bool> & path
272320
}
273321
}
274322

275-
// Normalize by number of samples to get average deviation per sample
276323
for (Eigen::Index i = 0; i < batch_size; ++i) {
277324
if (valid_sample_count(i) > 0) {
278325
cost_array(i) /= static_cast<float>(valid_sample_count(i));
279326
}
280327
}
281328

282-
// Apply weight and power, then add to total costs
283329
if (power_ > 1u) {
284330
data.costs += (cost_array * geometric_weight_).pow(power_);
285331
} else {
286332
data.costs += cost_array * geometric_weight_;
287333
}
288334
}
289335

290-
void PathAlignCritic::updatePathCache(const models::Path & path, size_t path_segments_count)
291-
{
292-
// Handle empty path
293-
if (path_segments_count == 0) {
294-
path_size_cache_ = 0;
295-
return;
296-
}
297-
298-
// path_segments_count is the number of segments, so number of points is segments + 1
299-
const size_t path_points_count = path_segments_count + 1;
300-
301-
// Only update cache if path changed by checking size, first, and middle positions
302-
const Eigen::Index path_points_idx = static_cast<Eigen::Index>(path_points_count);
303-
const Eigen::Index mid_idx = path_points_idx / 2;
304-
if (path_points_idx == static_cast<Eigen::Index>(path_size_cache_) &&
305-
path_x_cache_.size() == path_points_idx &&
306-
path.x(0) == path_x_cache_(0) &&
307-
path.y(0) == path_y_cache_(0) &&
308-
path.x(mid_idx) == path_x_cache_(mid_idx) &&
309-
path.y(mid_idx) == path_y_cache_(mid_idx))
310-
{
311-
return;
312-
}
313-
314-
path_size_cache_ = path_points_count;
315-
316-
if (static_cast<Eigen::Index>(path_x_cache_.size()) != path_points_idx) {
317-
path_x_cache_.resize(path_points_idx);
318-
path_y_cache_.resize(path_points_idx);
319-
}
320-
321-
// Cache path points
322-
path_x_cache_.head(path_points_idx) = path.x.head(path_points_idx);
323-
path_y_cache_.head(path_points_idx) = path.y.head(path_points_idx);
324-
325-
if (path_points_count < 2) {return;}
326-
327-
const Eigen::Index num_segments = path_points_idx - 1;
328-
329-
if (static_cast<Eigen::Index>(segment_lengths_.size()) != num_segments) {
330-
segment_lengths_.resize(num_segments);
331-
cumulative_distances_.resize(path_points_idx);
332-
segment_dx_.resize(num_segments);
333-
segment_dy_.resize(num_segments);
334-
segment_len_sq_.resize(num_segments);
335-
segment_inv_len_sq_.resize(num_segments);
336-
}
337-
338-
// Precompute segment geometry for efficient distance calculations
339-
cumulative_distances_(0) = 0.0;
340-
for (Eigen::Index i = 0; i < num_segments; ++i) {
341-
segment_dx_(i) = path.x(i + 1) - path.x(i);
342-
segment_dy_(i) = path.y(i + 1) - path.y(i);
343-
segment_len_sq_(i) = segment_dx_(i) * segment_dx_(i) + segment_dy_(i) * segment_dy_(i);
344-
segment_lengths_(i) = std::sqrt(segment_len_sq_(i));
345-
cumulative_distances_(i + 1) = cumulative_distances_(i) + segment_lengths_(i);
346-
segment_inv_len_sq_(i) = (segment_len_sq_(i) > 1e-6f) ? (1.0f / segment_len_sq_(i)) : 0.0f;
347-
}
348-
}
349-
350336
// TODO(@silanus23): Not sure if this should be in util too
351337
float PathAlignCritic::computeMinDistanceToPath(float px, float py, Eigen::Index & closest_seg_idx)
352338
{
353339
const Eigen::Index num_segments = static_cast<Eigen::Index>(path_size_cache_) - 1;
354340
if (num_segments < 1) {return std::numeric_limits<float>::max();}
355341

356-
// Clamp hint index to valid range
357342
closest_seg_idx = std::clamp(closest_seg_idx, Eigen::Index(0), num_segments - 1);
358343

359-
// Define search window around hint using cumulative distances along path
344+
// Limit search to window around hint (exploits path locality)
360345
const float hint_distance = cumulative_distances_(closest_seg_idx);
361346
const float start_distance = std::max(0.0f, hint_distance - static_cast<float>(search_window_));
362347
const float end_distance = hint_distance + static_cast<float>(search_window_);
363348

364-
// Use binary search to find start segment for search window
365349
const Eigen::Index start_idx = static_cast<Eigen::Index>(
366350
utils::findClosestPathPtBinary(
367351
cumulative_distances_.data(),
@@ -371,10 +355,9 @@ float PathAlignCritic::computeMinDistanceToPath(float px, float py, Eigen::Index
371355
float min_dist_sq = std::numeric_limits<float>::max();
372356
Eigen::Index best_seg = closest_seg_idx;
373357

374-
// Search only within window for efficiency
375358
for (Eigen::Index seg_idx = start_idx; seg_idx < num_segments; ++seg_idx) {
376359
if (cumulative_distances_(seg_idx) > end_distance) {break;}
377-
if (segment_len_sq_(seg_idx) < 1e-6f) {continue;} // Skip degenerate segments
360+
if (segment_len_sq_(seg_idx) < 1e-6f) {continue;}
378361

379362
const float dist_sq = distSqToSegment(px, py, seg_idx);
380363

@@ -384,7 +367,6 @@ float PathAlignCritic::computeMinDistanceToPath(float px, float py, Eigen::Index
384367
}
385368
}
386369

387-
// Update hint for next call
388370
closest_seg_idx = best_seg;
389371
return std::sqrt(min_dist_sq);
390372
}

0 commit comments

Comments
 (0)