@@ -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
5354void 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+
95150void 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
351337float 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