SPFASmoothing.cpp
Go to the documentation of this file.
1/**
2 * This file is part of ArmarX.
3 *
4 * ArmarX is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 *
8 * ArmarX is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#include "SPFASmoothing.h"
18
19#include <array>
20#include <cmath>
21#include <iomanip>
22#include <string>
23#include <vector>
24
25#include <Eigen/Geometry>
26
29
38
40{
41 namespace
42 {
44
45 core::GlobalTrajectory toGlobalTrajectory(const std::vector<CenterPoint>& traj)
46 {
47 core::GlobalTrajectory gtraj;
48 for (const auto& p : traj)
49 {
50 core::GlobalTrajectoryPoint gp;
51 gp.waypoint.pose =
52 Eigen::Translation3f{Eigen::Vector3f(static_cast<float>(p.x),
53 static_cast<float>(p.y),
54 0.0f)} *
55 Eigen::AngleAxisf{static_cast<float>(p.theta), Eigen::Vector3f::UnitZ()};
56 gp.velocity = static_cast<float>(p.v);
57 gtraj.mutablePoints().push_back(gp);
58 }
59 return gtraj;
60 }
61
62 std::vector<CenterPoint> toCenterTrajectory(const core::GlobalTrajectory& gtraj)
63 {
64 std::vector<CenterPoint> traj;
65 for (const auto& gp : gtraj.points())
66 {
68 p.x = gp.waypoint.pose.translation().x();
69 p.y = gp.waypoint.pose.translation().y();
70 const auto& R = gp.waypoint.pose.rotation();
71 p.theta = std::atan2(R(1, 0), R(0, 0));
72 p.v = gp.velocity;
73 traj.push_back(p);
74 }
75 return traj;
76 }
77
78 double queryDistance(const Costmap2DWrapper& wrapper, double x, double y)
79 {
80 double out;
81 wrapper(&x, &y, &out);
82 return out;
83 }
84
85 bool isInsideBounds(double x, double y, const algorithms::Costmap& costmap)
86 {
87 const auto local = costmap.origin().inverse() * Eigen::Vector2f{static_cast<float>(x),
88 static_cast<float>(y)};
89 const auto& bounds = costmap.getLocalSceneBounds();
90 return local.x() >= bounds.min.x() && local.x() <= bounds.max.x() &&
91 local.y() >= bounds.min.y() && local.y() <= bounds.max.y();
92 }
93
94 void pushWaypointsToSafeZone(std::vector<CenterPoint>& traj,
95 const Costmap2DWrapper& wrapper,
96 const algorithms::Costmap& costmap,
97 const std::vector<CenterPoint>& originalTargets,
98 double clearance,
99 double stepSize,
100 int maxIterations,
101 double maxDeviation)
102 {
103 const std::vector<std::pair<double, double>> neighbours = {
104 {0.0, 0.0},
105 {stepSize, 0.0},
106 {-stepSize, 0.0},
107 {0.0, stepSize},
108 {0.0, -stepSize},
109 {stepSize, stepSize},
110 {stepSize, -stepSize},
111 {-stepSize, stepSize},
112 {-stepSize, -stepSize},
113 };
114
115 for (int idx = 1; idx + 1 < static_cast<int>(traj.size()); ++idx)
116 {
117 CenterPoint& p = traj[idx];
118 const double origX = originalTargets[idx].x;
119 const double origY = originalTargets[idx].y;
120
121 auto withinDeviation = [&](double x, double y)
122 {
123 return std::hypot(x - origX, y - origY) <= maxDeviation + 1e-6;
124 };
125
126 double bestDist = queryDistance(wrapper, p.x, p.y);
127
128 if (bestDist >= clearance)
129 continue;
130
131 for (int iter = 0; iter < maxIterations; ++iter)
132 {
133 double bestX = p.x;
134 double bestY = p.y;
135 bool improved = false;
136
137 for (const auto& [dx, dy] : neighbours)
138 {
139 double candX = p.x + dx;
140 double candY = p.y + dy;
141 if (!isInsideBounds(candX, candY, costmap))
142 continue;
143 if (!withinDeviation(candX, candY))
144 continue;
145 double candDist = queryDistance(wrapper, candX, candY);
146 if (candDist > bestDist)
147 {
148 bestDist = candDist;
149 bestX = candX;
150 bestY = candY;
151 improved = true;
152 }
153 }
154
155 if (!improved)
156 break;
157
158 p.x = bestX;
159 p.y = bestY;
160
161 if (bestDist >= clearance)
162 break;
163 }
164
165 // Final safety projection: if still in collision, snap to the closest
166 // collision-free cell within the allowed deviation.
167 if (queryDistance(wrapper, p.x, p.y) <= 0.0)
168 {
169 const auto vertex = costmap.findClosestCollisionFreeVertex(
170 Eigen::Vector2f{static_cast<float>(origX), static_cast<float>(origY)},
171 static_cast<float>(maxDeviation));
172
173 if (vertex && withinDeviation(vertex->position.x(), vertex->position.y()))
174 {
175 p.x = vertex->position.x();
176 p.y = vertex->position.y();
177 }
178 }
179 }
180 }
181
182 bool validateTrajectory(const std::vector<CenterPoint>& traj, const std::string& label)
183 {
184 bool valid = true;
185 for (int i = 0; i < static_cast<int>(traj.size()); ++i)
186 {
187 if (!std::isfinite(traj[i].x) || !std::isfinite(traj[i].y) ||
188 !std::isfinite(traj[i].theta) || !std::isfinite(traj[i].v))
189 {
190 ARMARX_WARNING << "[" << label << "] NaN/Inf in traj[" << i
191 << "] : x=" << traj[i].x << " y=" << traj[i].y
192 << " theta=" << traj[i].theta << " v=" << traj[i].v;
193 valid = false;
194 }
195 }
196 return valid;
197 }
198
199 ceres::Solver::Options makeSolverOptions(const Params& params, int maxIterations)
200 {
201 ceres::Solver::Options options;
202 options.max_num_iterations = maxIterations;
203 options.linear_solver_type = ceres::SPARSE_NORMAL_CHOLESKY;
204 options.minimizer_progress_to_stdout = false;
205 options.num_threads = params.num_threads;
206 options.function_tolerance = 1e-6;
207 options.parameter_tolerance = 1e-8;
208 options.trust_region_strategy_type = ceres::LEVENBERG_MARQUARDT;
209 options.initial_trust_region_radius = params.initial_trust_region_radius;
210 options.max_trust_region_radius = params.max_trust_region_radius;
211 options.min_trust_region_radius = params.min_trust_region_radius;
212 options.min_relative_decrease = 1e-4;
213 options.use_nonmonotonic_steps = true;
214 return options;
215 }
216
217 struct PassWeights
218 {
219 double w_obs;
220 double w_pose_smooth;
221 double w_spacing;
222 double w_tracking;
223 double w_safe_tracking;
224 };
225
226 double evalObstacleCost(const Costmap2DWrapper& wrapper,
227 double x,
228 double y,
229 double max_dist,
230 double clearance)
231 {
232 double d_raw;
233 wrapper(&x, &y, &d_raw);
234 if (!std::isfinite(d_raw))
235 return 100.0;
236
237 double obs_dist = d_raw - clearance;
238 if (obs_dist > max_dist)
239 return 0.0;
240 if (obs_dist < 0.0)
241 {
242 double penetration = -obs_dist;
243 double max_pen = 3.0 * max_dist;
244 if (penetration > max_pen)
245 penetration = max_pen;
246 double ratio = penetration / max_dist;
247 return std::exp(2.0 * ratio) - 1.0 + 0.1 * ratio;
248 }
249 return (max_dist - obs_dist) / max_dist;
250 }
251
252 void logResidualBreakdown(const std::vector<CenterPoint>& traj,
253 const Costmap2DWrapper& wrapper,
254 const std::vector<CenterPoint>& originalTargets,
255 const std::vector<CenterPoint>& safeTargets,
256 const Params& params,
257 const std::string& label)
258 {
259 const int N = static_cast<int>(traj.size());
260 if (N < 4)
261 return;
262
263 double c_obs_wp = 0.0;
264 double c_obs_seg = 0.0;
265 double c_smooth = 0.0;
266 double c_tracking = 0.0;
267 double c_safe_tracking = 0.0;
268 double c_spacing = 0.0;
269
270 // Obstacle (waypoints)
271 for (int i = 0; i < N; ++i)
272 {
273 double r = evalObstacleCost(wrapper, traj[i].x, traj[i].y, params.obs_max_distance, params.clearance);
274 c_obs_wp += r * r;
275 }
276
277 // Obstacle (segment samples)
278 for (int i = 0; i < N - 1; ++i)
279 {
280 for (int s = 1; s <= params.segment_obstacle_samples; ++s)
281 {
282 double t = static_cast<double>(s) / (params.segment_obstacle_samples + 1);
283 double mx = (1.0 - t) * traj[i].x + t * traj[i + 1].x;
284 double my = (1.0 - t) * traj[i].y + t * traj[i + 1].y;
285 double r = evalObstacleCost(wrapper, mx, my, params.obs_max_distance, params.clearance);
286 c_obs_seg += r * r;
287 }
288 }
289
290 // Position smoothness
291 for (int i = 1; i <= N - 2; ++i)
292 {
293 double rx = traj[i - 1].x - 2.0 * traj[i].x + traj[i + 1].x;
294 double ry = traj[i - 1].y - 2.0 * traj[i].y + traj[i + 1].y;
295 c_smooth += rx * rx + ry * ry;
296 }
297
298 // Bounded tracking to safe path
299 for (int i = 1; i <= N - 2; ++i)
300 {
301 double dx = traj[i].x - safeTargets[i].x;
302 double dy = traj[i].y - safeTargets[i].y;
303 double d = std::hypot(dx, dy);
304 double excess = d - params.tracking_deadzone;
305 if (excess > 0)
306 {
307 if (excess <= params.tracking_max_deviation - params.tracking_deadzone)
308 {
309 double r = params.w_tracking_in_bounds * excess;
310 c_tracking += r * r;
311 }
312 else
313 {
314 double over = excess - (params.tracking_max_deviation - params.tracking_deadzone);
315 double r = params.w_tracking_in_bounds * (params.tracking_max_deviation - params.tracking_deadzone)
316 + params.w_tracking_hard * over;
317 c_tracking += r * r;
318 }
319 }
320 }
321
322 // Soft tracking to original path
323 for (int i = 1; i <= N - 2; ++i)
324 {
325 double rx = traj[i].x - originalTargets[i].x;
326 double ry = traj[i].y - originalTargets[i].y;
327 // Use pass3 weight for breakdown (representative)
328 double w = params.pass3_w_tracking;
329 c_safe_tracking += (w * rx) * (w * rx) + (w * ry) * (w * ry);
330 }
331
332 // Spacing
333 double d_avg = computeAverageSpacing(traj);
334 if (d_avg <= 1e-9)
335 d_avg = 0.1;
336 for (int i = 0; i < N - 1; ++i)
337 {
338 double dx = traj[i + 1].x - traj[i].x;
339 double dy = traj[i + 1].y - traj[i].y;
340 double dist_sq = dx * dx + dy * dy;
341 double r = params.pass3_w_spacing * (dist_sq - d_avg * d_avg);
342 c_spacing += r * r;
343 }
344
345 double total = c_obs_wp + c_obs_seg + c_smooth + c_tracking + c_safe_tracking + c_spacing;
346
347 ARMARX_INFO << "=== Residual breakdown [" << label << "] ===";
348 ARMARX_INFO << "Obstacle (waypoints): " << std::scientific << std::setprecision(3) << c_obs_wp;
349 ARMARX_INFO << "Obstacle (segments): " << std::scientific << std::setprecision(3) << c_obs_seg;
350 ARMARX_INFO << "Position smoothness: " << std::scientific << std::setprecision(3) << c_smooth;
351 ARMARX_INFO << "Bounded tracking: " << std::scientific << std::setprecision(3) << c_tracking;
352 ARMARX_INFO << "Soft tracking: " << std::scientific << std::setprecision(3) << c_safe_tracking;
353 ARMARX_INFO << "Spacing: " << std::scientific << std::setprecision(3) << c_spacing;
354 ARMARX_INFO << "TOTAL: " << std::scientific << std::setprecision(3) << total;
355 }
356
357 void runPositionPass(std::vector<CenterPoint>& traj,
358 const Costmap2DWrapper& wrapper,
359 const std::vector<CenterPoint>& originalTargets,
360 const std::vector<CenterPoint>& safeTargets,
361 const PassWeights& w,
362 const Params& params,
363 int maxIterations)
364 {
365 const int N = static_cast<int>(traj.size());
366 if (N < 4)
367 {
368 ARMARX_INFO << "[SPFA smoothing] need at least 4 points for position pass";
369 return;
370 }
371
372 normalizeTrajectoryAngles(traj);
373
374 // Avoid zero-length segments.
375 for (int i = 1; i < N - 1; ++i)
376 {
377 double dx = traj[i].x - traj[i - 1].x;
378 double dy = traj[i].y - traj[i - 1].y;
379 if (std::hypot(dx, dy) < 1.0)
380 {
381 double ndx = (i + 1 < N) ? (traj[i + 1].x - traj[i].x) : 1.0;
382 double ndy = (i + 1 < N) ? (traj[i + 1].y - traj[i].y) : 0.0;
383 double nlen = std::hypot(ndx, ndy);
384 if (nlen < 1e-6)
385 {
386 ndx = 1.0;
387 ndy = 0.0;
388 nlen = 1.0;
389 }
390 traj[i].x += 1.0 * ndx / nlen;
391 traj[i].y += 1.0 * ndy / nlen;
392 }
393 }
394
395 ceres::Problem problem;
396 for (int i = 0; i < N; ++i)
397 problem.AddParameterBlock(&traj[i].x, 4);
398
399 problem.SetParameterBlockConstant(&traj.front().x);
400 problem.SetParameterBlockConstant(&traj.back().x);
401
402 // Position pass: only x, y are free.
403 for (int i = 1; i < N - 1; ++i)
404 {
405 std::vector<int> constant_indices = {2, 3}; // theta, v
406 SET_CONSTRAINT(problem, &traj[i].x, new SubsetConstraint(4, constant_indices));
407 }
408
409 // 1) Position smoothness
410 for (int i = 1; i <= N - 2; ++i)
411 {
412 problem.AddResidualBlock(
413 new ceres::AutoDiffCostFunction<PositionSmoothResidual, 2, 4, 4, 4>(
414 new PositionSmoothResidual(w.w_pose_smooth)),
415 nullptr,
416 &traj[i - 1].x,
417 &traj[i].x,
418 &traj[i + 1].x);
419 }
420
421 // 2) Spacing
422 double d_avg = computeAverageSpacing(traj);
423 if (d_avg <= 1e-9)
424 d_avg = 0.1;
425 for (int i = 0; i < N - 1; ++i)
426 {
427 problem.AddResidualBlock(
428 new ceres::AutoDiffCostFunction<SpacingResidual, 1, 4, 4>(
429 new SpacingResidual(d_avg, w.w_spacing)),
430 nullptr,
431 &traj[i].x,
432 &traj[i + 1].x);
433 }
434
435 // 3) Obstacle residuals (waypoints)
436 for (int i = 0; i < N; ++i)
437 {
438 problem.AddResidualBlock(
439 new ceres::AutoDiffCostFunction<ObstacleResidual2D, 1, 4>(
440 new ObstacleResidual2D(wrapper, w.w_obs, params.obs_max_distance, params.clearance)),
441 nullptr,
442 &traj[i].x);
443 }
444
445 // 4) Obstacle residuals along segments (interior samples)
446 for (int i = 0; i < N - 1; ++i)
447 {
448 for (int s = 1; s <= params.segment_obstacle_samples; ++s)
449 {
450 double t = static_cast<double>(s) / (params.segment_obstacle_samples + 1);
451 problem.AddResidualBlock(
452 new ceres::AutoDiffCostFunction<SegmentObstacleResidual2D, 1, 4, 4>(
453 new SegmentObstacleResidual2D(wrapper, w.w_obs, params.obs_max_distance, params.clearance, t)),
454 nullptr,
455 &traj[i].x,
456 &traj[i + 1].x);
457 }
458 }
459
460 // 5) Bounded tracking to the safe (hill-climbed) path
461 for (int i = 1; i <= N - 2; ++i)
462 {
463 problem.AddResidualBlock(
464 new ceres::AutoDiffCostFunction<BoundedTrackingResidual, 1, 4>(
465 new BoundedTrackingResidual(safeTargets[i].x,
466 safeTargets[i].y,
467 params.tracking_deadzone,
468 params.tracking_max_deviation,
469 params.w_tracking_in_bounds,
470 params.w_tracking_hard)),
471 nullptr,
472 &traj[i].x);
473 }
474
475 // 6) Soft tracking to the original SPFA path (weak, keeps deviation small)
476 if (w.w_tracking > 0.0)
477 {
478 for (int i = 1; i <= N - 2; ++i)
479 {
480 problem.AddResidualBlock(
481 new ceres::AutoDiffCostFunction<SoftTrackingResidual, 2, 4>(
482 new SoftTrackingResidual(originalTargets[i].x, originalTargets[i].y, w.w_tracking)),
483 nullptr,
484 &traj[i].x);
485 }
486 }
487
488 ceres::Solver::Options options = makeSolverOptions(params, maxIterations);
489 ceres::Solver::Summary summary;
490 ceres::Solve(options, &problem, &summary);
491
492 normalizeTrajectoryAngles(traj);
493
494 ARMARX_INFO << summary.FullReport();
495 ARMARX_INFO << "[Position pass] Final cost: " << summary.final_cost
496 << " iterations: " << summary.num_successful_steps
497 << " termination: " << summary.termination_type;
498 }
499
500 bool repairWaypoints(std::vector<CenterPoint>& traj,
501 const algorithms::Costmap& costmap,
502 const Costmap2DWrapper& wrapper,
503 const std::vector<CenterPoint>& originalTargets,
504 const Params& params)
505 {
506 const int N = static_cast<int>(traj.size());
507
508 for (int i = 1; i < N - 1; ++i)
509 {
510 double d = queryDistance(wrapper, traj[i].x, traj[i].y);
511 if (d > params.clearance)
512 continue;
513
514 const auto vertex = costmap.findClosestCollisionFreeVertex(
515 Eigen::Vector2f{static_cast<float>(originalTargets[i].x),
516 static_cast<float>(originalTargets[i].y)},
517 static_cast<float>(params.tracking_max_deviation));
518
519 if (!vertex)
520 return false;
521
522 const double dx = vertex->position.x() - originalTargets[i].x;
523 const double dy = vertex->position.y() - originalTargets[i].y;
524 if (std::hypot(dx, dy) > params.tracking_max_deviation + 1e-3)
525 return false;
526
527 traj[i].x = vertex->position.x();
528 traj[i].y = vertex->position.y();
529 }
530
531 return true;
532 }
533
534 double computeMaxDeviation(const std::vector<CenterPoint>& traj,
535 const std::vector<CenterPoint>& originalTargets)
536 {
537 double maxDev = 0.0;
538 for (std::size_t i = 0; i < traj.size(); ++i)
539 {
540 double dx = traj[i].x - originalTargets[i].x;
541 double dy = traj[i].y - originalTargets[i].y;
542 maxDev = std::max(maxDev, std::hypot(dx, dy));
543 }
544 return maxDev;
545 }
546 } // namespace
547
549 const algorithms::Costmap& costmap,
550 const Params& params) :
551 trajectory(trajectory),
552 costmap(costmap),
553 params(params)
554 {
555 }
556
558 {
559 Costmap2DWrapper wrapper{costmap};
560
561 std::vector<CenterPoint> traj = toCenterTrajectory(trajectory);
562 const std::vector<CenterPoint> originalTargets = traj;
563
564 if (!validateTrajectory(traj, "pre-hill-climb"))
565 {
566 ARMARX_WARNING << "[SPFA smoothing] Invalid trajectory before pre-processing. Aborting.";
567 return {std::nullopt, std::nullopt, false, false, {}};
568 }
569
570 pushWaypointsToSafeZone(traj,
571 wrapper,
572 costmap,
573 originalTargets,
574 params.clearance,
575 params.hill_climb_step_size,
576 params.hill_climb_max_iterations,
577 params.tracking_max_deviation);
578
579 const std::vector<CenterPoint> safeTargets = traj;
580
581 if (!validateTrajectory(traj, "post-hill-climb"))
582 {
583 ARMARX_WARNING << "[SPFA smoothing] Invalid trajectory after pre-processing. Aborting.";
584 return {std::nullopt, std::nullopt, false, false, {}};
585 }
586
587 logResidualBreakdown(traj, wrapper, originalTargets, safeTargets, params, "after pre-processing");
588
589 // Multi-pass position optimization.
590 const std::array<PassWeights, 3> passes = {
591 PassWeights{params.pass1_w_obs,
592 params.pass1_w_pose_smooth,
593 params.pass1_w_spacing,
594 params.pass1_w_tracking,
595 params.pass1_w_safe_tracking},
596 PassWeights{params.pass2_w_obs,
597 params.pass2_w_pose_smooth,
598 params.pass2_w_spacing,
599 params.pass2_w_tracking,
600 params.pass2_w_safe_tracking},
601 PassWeights{params.pass3_w_obs,
602 params.pass3_w_pose_smooth,
603 params.pass3_w_spacing,
604 params.pass3_w_tracking,
605 params.pass3_w_safe_tracking}};
606
607 for (size_t pass = 0; pass < passes.size(); ++pass)
608 {
609 ARMARX_INFO << "[SPFA smoothing] Starting position pass " << (pass + 1);
610 runPositionPass(traj, wrapper, originalTargets, safeTargets, passes[pass], params, params.max_iterations);
611 logResidualBreakdown(traj, wrapper, originalTargets, safeTargets, params,
612 "after position pass " + std::to_string(pass + 1));
613
614 if (!validateTrajectory(traj, "post-position-pass-" + std::to_string(pass + 1)))
615 {
616 ARMARX_WARNING << "[SPFA smoothing] NaN/Inf after position pass " << (pass + 1)
617 << ". Reverting to pre-processed safe path.";
618 traj = safeTargets;
619 break;
620 }
621 }
622
623 normalizeTrajectoryAngles(traj);
624
625 // Collision repair if needed.
626 TrajectoryChecker2D checker(costmap, params.clearance);
627 core::GlobalTrajectory smoothedTrajectory = toGlobalTrajectory(traj);
628 bool collisionFree = checker.check(smoothedTrajectory);
629
630 if (!collisionFree)
631 {
632 ARMARX_WARNING << "[SPFA smoothing] Initial smoothing has collisions. Running repair pass.";
633
634 if (!repairWaypoints(traj, costmap, wrapper, originalTargets, params))
635 {
636 ARMARX_ERROR << "[SPFA smoothing] Repair failed: could not project colliding waypoints "
637 "to collision-free positions within max deviation.";
638 }
639 else
640 {
641 PassWeights repairWeights{params.repair_w_obs,
642 params.repair_w_pose_smooth,
643 params.repair_w_spacing,
644 params.repair_w_tracking,
645 params.repair_w_safe_tracking};
646 runPositionPass(traj, wrapper, originalTargets, safeTargets, repairWeights, params, params.repair_max_iterations);
647 logResidualBreakdown(traj, wrapper, originalTargets, safeTargets, params, "after repair pass");
648
649 smoothedTrajectory = toGlobalTrajectory(traj);
650 collisionFree = checker.check(smoothedTrajectory);
651 }
652 }
653
654 const double maxDeviation = computeMaxDeviation(traj, originalTargets);
655 const bool deviationOk = maxDeviation <= params.tracking_max_deviation + 1e-3;
656
657 if (collisionFree && deviationOk)
658 {
659 ARMARX_INFO << "[SPFA smoothing] Smoothed trajectory is collision-free. Max deviation: "
660 << maxDeviation << " mm";
661 }
662 else
663 {
664 if (!collisionFree)
665 ARMARX_WARNING << "[SPFA smoothing] Smoothed trajectory still has collisions after repair.";
666 if (!deviationOk)
667 ARMARX_WARNING << "[SPFA smoothing] Max deviation too large: " << maxDeviation
668 << " mm (limit: " << params.tracking_max_deviation << " mm).";
669 }
670
671 for (int i = 0; i < static_cast<int>(traj.size()); ++i)
672 {
673 ARMARX_DEBUG << std::fixed << std::setprecision(3) << "i=" << i << " : x=" << traj[i].x
674 << " y=" << traj[i].y << " theta=" << traj[i].theta << " v=" << traj[i].v;
675 }
676
677 // Geometry check, after the collision repair so it sees the path that would actually be
678 // handed on. A fold is invisible to every residual in this optimizer -- squared distances
679 // are sign-blind and the second difference vanishes for a point placed behind its
680 // predecessor -- and it is invisible to the collision check too, so this is the only
681 // place it can be caught.
682 std::vector<std::size_t> repairedWaypoints;
683 bool geometryValid = true;
684
685 const std::vector<std::size_t> violations = findGeometryViolations(smoothedTrajectory);
686
687 if (not violations.empty())
688 {
689 ARMARX_WARNING << "[nav-guard] path-fold-detected: the smoothed path reverses "
690 "direction at "
691 << violations.size() << " waypoint(s), first at index "
692 << violations.front()
693 << ". Repairing by removing them; a fold collapses the spline tangent "
694 "downstream and stalls the reparametrization there.";
695
696 const core::GlobalTrajectory candidate =
697 repairGeometry(smoothedTrajectory, repairedWaypoints);
698
699 // Removing a waypoint creates a new segment between its neighbours, and the straight
700 // line between them can cut a corner the original path went around.
701 const bool repairedIsCollisionFree = checker.check(candidate);
702 const bool repairedIsMonotone = findGeometryViolations(candidate).empty();
703
704 if (repairedIsCollisionFree and repairedIsMonotone)
705 {
706 ARMARX_WARNING << "[nav-guard] path-fold-repaired: removed "
707 << repairedWaypoints.size() << " waypoint(s), "
708 << smoothedTrajectory.points().size() << " -> "
709 << candidate.points().size()
710 << " points, still collision-free.";
711
712 smoothedTrajectory = candidate;
713 }
714 else
715 {
716 ARMARX_WARNING << "[nav-guard] path-fold-unrepairable: removing the folded "
717 "waypoint(s) would "
718 << (repairedIsCollisionFree ? "leave the path folded"
719 : "cut a corner into an obstacle")
720 << ". Rejecting the smoothed path; the caller falls back to the "
721 "unsmoothed one.";
722
723 repairedWaypoints.clear();
724 geometryValid = false;
725 }
726 }
727
728 const core::GlobalTrajectory preprocessedTrajectory = toGlobalTrajectory(safeTargets);
729 const bool success = collisionFree && deviationOk;
730 return {smoothedTrajectory, preprocessedTrajectory, success, geometryValid,
731 repairedWaypoints};
732 }
733
734} // namespace armarx::navigation::algorithms::spfa::smoothing
Differentiable wrapper around the standard 2-D distance-to-obstacle costmap.
SPFASmoothing(const core::GlobalTrajectory &trajectory, const algorithms::Costmap &costmap, const Params &params)
bool check(const core::GlobalTrajectory &trajectory, bool logDetails=false) const
const std::vector< GlobalTrajectoryPoint > & points() const
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:179
#define ARMARX_ERROR
The logging level for unexpected behaviour, that must be fixed.
Definition Logging.h:194
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
Definition Logging.h:182
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:191
double s(double t, double s0, double v0, double a0, double j)
Definition CtrlUtil.h:33
std::vector< CenterPoint > toCenterTrajectory(const armarx::navigation::core::GlobalTrajectory &gtraj)
Definition Smoothing.cpp:50
armarx::navigation::core::GlobalTrajectory toGlobalTrajectory(const std::vector< CenterPoint > &traj)
Definition Smoothing.cpp:32
std::vector< std::size_t > findGeometryViolations(const core::GlobalTrajectory &trajectory, const GeometryLimits &limits)
Indices of waypoints that make the path double back on itself.
core::GlobalTrajectory repairGeometry(const core::GlobalTrajectory &trajectory, std::vector< std::size_t > &removed, const GeometryLimits &limits)
Drop the offending waypoints, repeating until the path is monotone.
std::multimap< std::string, std::string > Params
Definition httplib.h:510
bool isfinite(const std::vector< T, Ts... > &v)
Definition algorithm.h:366
ceres::SubsetParameterization SubsetConstraint
This file is part of ArmarX.
Definition residuals.h:24
#define SET_CONSTRAINT(problem, param, constraint)
Definition residuals.h:25