residuals.h
Go to the documentation of this file.
1#include <algorithm>
2#include <cmath>
3#include <iostream>
4#include <istream>
5#include <vector>
6
8
11
12#include <ceres/ceres.h>
13
15{
16 // Used ChatGPT for initial draft
17 // Full CHOMP-like smoother using Ceres with spacing residual and correct angle handling.
18 //
19 // NOTE: This is a compact but complete example. Adapt the centerToRobot transform, SDF grid source,
20 // weights, and solver options to your codebase.
21
22
23 // -----------------------------
24 // Data structures
25 // -----------------------------
27 {
28 double x, y, theta, v;
29 };
30
31 // -----------------------------
32 // Utilities: angle diff + normalize
33 // -----------------------------
34 template <typename T>
35 T
36 angleDiff(const T& a, const T& b)
37 {
38 // returns normalized a - b in [-pi, pi]
39 T diff = a - b;
40 return ceres::atan2(ceres::sin(diff), ceres::cos(diff));
41 }
42
43 inline double
45 {
46 return std::atan2(std::sin(a), std::cos(a));
47 }
48
49 static void
50 normalizeTrajectoryAngles(std::vector<CenterPoint>& traj)
51 {
52 for (auto& p : traj)
53 p.theta = normalizeAngle(p.theta);
54 }
55
56 // -----------------------------
57 // Residuals
58 // -----------------------------
59
60 // Pose second-difference smoothness residual (x,y,theta)
62 {
63 PoseSmoothResidual(double pos_weight = 1.0, double orientation_weight = 1.0) :
64 pos_w(pos_weight), ori_w(orientation_weight)
65 {
66 }
67
68 template <typename T>
69 bool
70 operator()(const T* const c_prev,
71 const T* const c_i,
72 const T* const c_next,
73 T* residual) const
74 {
75 residual[0] = T(pos_w) * (c_prev[0] - T(2.0) * c_i[0] + c_next[0]); // x
76 residual[1] = T(pos_w) * (c_prev[1] - T(2.0) * c_i[1] + c_next[1]); // y
77 // theta second-diff using angleDiff to handle wrap
78 T dprev = angleDiff(c_prev[2], c_i[2]);
79 T dnext = angleDiff(c_i[2], c_next[2]);
80 residual[2] = T(ori_w) * (dprev - dnext);
81 return true;
82 }
83
84 double pos_w;
85 double ori_w;
86 };
87
88 // Residual to enforce smooth orientation transition at start and end of trajectory
90 {
92 {
93 }
94
95 template <typename T>
96 bool
97 operator()(const T* const theta0, const T* const theta1, T* residuals) const
98 {
99
100 T d = angleDiff(theta1[2], theta0[2]);
101 residuals[0] = T(w_) * d;
102 return true;
103 }
104
105 double w_;
106 };
107
108 // Velocity (v) second-difference smoothness
110 {
111 VelSmoothResidual(double weight = 1.0) : w(weight)
112 {
113 }
114
115 template <typename T>
116 bool
117 operator()(const T* const c_prev,
118 const T* const c_i,
119 const T* const c_next,
120 T* residual) const
121 {
122 residual[0] = T(w) * (c_prev[3] - T(2.0) * c_i[3] + c_next[3]); // v
123 return true;
124 }
125
126 double w;
127 };
128
129 // Jerk residual (optional) for tighter smoothness on pose (uses 4 consecutive centers)
131 {
132 PoseJerkResidual(double weight = 1.0) : w(weight)
133 {
134 }
135
136 template <typename T>
137 bool
138 operator()(const T* const c_m2,
139 const T* const c_m1,
140 const T* const c_i,
141 const T* const c_p1,
142 T* residual) const
143 {
144 residual[0] = T(w) * (c_m2[0] - T(3.0) * c_m1[0] + T(3.0) * c_i[0] - c_p1[0]);
145 residual[1] = T(w) * (c_m2[1] - T(3.0) * c_m1[1] + T(3.0) * c_i[1] - c_p1[1]);
146 // approximate angle jerk (small-angle assumption)
147 T a_m2 = c_m2[2], a_m1 = c_m1[2], a_i = c_i[2], a_p1 = c_p1[2];
148 T r = angleDiff(a_m2, a_i) - T(3.0) * angleDiff(a_m1, a_i) +
149 T(3.0) * angleDiff(a_i, a_i) - angleDiff(a_p1, a_i);
150 residual[2] = T(w) * r;
151 // T d1 = angleDiff(a_m1, a_i);
152 // T d2 = angleDiff(a_i, a_p1);
153 // T d3 = angleDiff(a_p1, a_p2);
154 //
155 // T jerk = d1 - T(2.0)*d2 + d3;
156 // residual[2] = T(w) * jerk;
157
158 return true;
159 }
160
161 double w;
162 };
163
164 // obstacle residual using Costmap3DWrapper; weighted
166 {
168 double weight,
169 double max_dist,
170 double collision_residual) :
171 wrapper_(wrapper),
172 w_(weight),
173 max_dist_(max_dist),
174 collision_residual_(collision_residual)
175 {
176 }
177
178 template <typename T>
179 T
180 obstacleCost(const T& distance_m) const
181 {
182 const T k = T(0.03); // decay factor
183 const T max_cost = T(collision_residual_);
184
185 if (distance_m < T(0))
186 {
187 // Inside obstacle → constant high cost, no gradient surprise
188 return max_cost;
189 }
190
191 if (distance_m > T(1.0))
192 {
193 // Beyond 1m → 0 influence
194 return T(0);
195 }
196
197 return max_cost * ceres::exp(-k * distance_m);
198 }
199
200 template <typename T>
201 bool
202 operator()(const T* const node, T* residual) const
203 {
204 T obs_dist;
205 wrapper_(&node[0], &node[1], &node[2], &obs_dist);
206 // residual[0] = obstacleCost(obs_dist);
207 // const T max_dist = T(max_dist_);
208 if (obs_dist > max_dist_)
209 {
210 residual[0] = T(0.0);
211 }
212 else if (obs_dist < T(0.0))
213 {
214 // residual[0] = T(collision_residual_);
215 const auto tmp = ((max_dist_ - obs_dist) / max_dist_);
216 // residual[0] = T(w_) * T(collision_residual_) * tmp;
217 residual[0] = T(w_) * T(w_) * (tmp + T(collision_residual_));
218 }
219 else
220 {
221 residual[0] = T(w_) * ((max_dist_ - obs_dist) / max_dist_);
222 }
223 //
224 return true;
225 }
226
228 double w_;
229 double max_dist_;
231 };
232
233 // Tracking residual to original center traj: (x,y,theta,v)
235 {
237 double yr,
238 double thetar,
239 double vr,
240 double wx,
241 double wy,
242 double wth,
243 double wv) :
244 xr_(xr), yr_(yr), thetar_(thetar), vr_(vr), wx_(wx), wy_(wy), wth_(wth), wv_(wv)
245 {
246 }
247
248 template <typename T>
249 bool
250 operator()(const T* const c_i, T* residual) const
251 {
252 residual[0] = T(wx_) * (c_i[0] - T(xr_));
253 residual[1] = T(wy_) * (c_i[1] - T(yr_));
254 residual[2] = T(wth_) * angleDiff(c_i[2], T(thetar_));
255 residual[3] = T(wv_) * (c_i[3] - T(vr_));
256 return true;
257 }
258
259 double xr_, yr_, thetar_, vr_;
260 double wx_, wy_, wth_, wv_;
261 };
262
263 // Velocity limit hinge residual: penalize v > vmax
265 {
266 VelLimitResidual(double vmax, double weight = 1.0) : vmax_(vmax), w(weight)
267 {
268 }
269
270 template <typename T>
271 bool
272 operator()(const T* const c_i, T* residual) const
273 {
274 T v = c_i[3];
275 T diff = v - T(vmax_);
276 if (diff > T(0))
277 residual[0] = T(w) * diff;
278 else
279 residual[0] = T(0.0);
280 return true;
281 }
282
283 double vmax_;
284 double w;
285 };
286
287 // Robot smoothness: second-diff applied to robot pose derived from center.
288 // The mapping center->robot is templated for AutoDiff.
290 {
291 RobotSmoothResidual(double weight = 1.0, double dx = -0.5, double dy = 0.0) :
292 w(weight), dx_(dx), dy_(dy)
293 {
294 }
295
296 template <typename T>
297 inline void
298 centerToRobotTemplated(const T* const c, T& rx, T& ry, T& rtheta) const
299 {
300 T cx = c[0], cy = c[1], cth = c[2];
301 T ccos = ceres::cos(cth), csin = ceres::sin(cth);
302 rx = cx + ccos * T(dx_) - csin * T(dy_);
303 ry = cy + csin * T(dx_) + ccos * T(dy_);
304 rtheta = cth; // robot orientation equals center orientation here; adapt if needed
305 }
306
307 template <typename T>
308 bool
309 operator()(const T* const c_prev,
310 const T* const c_i,
311 const T* const c_next,
312 T* residual) const
313 {
314 T rpx, rpy, rpth, rix, riy, rith, rnx, rny, rnth;
315 centerToRobotTemplated(c_prev, rpx, rpy, rpth);
316 centerToRobotTemplated(c_i, rix, riy, rith);
317 centerToRobotTemplated(c_next, rnx, rny, rnth);
318 residual[0] = T(w) * (rpx - T(2.0) * rix + rnx);
319 residual[1] = T(w) * (rpy - T(2.0) * riy + rny);
320 // Proper theta second-diff via angleDiff:
321 residual[2] = T(w) * (angleDiff(rpth, rith) - angleDiff(rnth, rith));
322 return true;
323 }
324
325 double w;
326 double dx_, dy_;
327 };
328
329 // Spacing residual: keeps |p_{i+1} - p_i| close to average spacing d_avg
331 {
332 SpacingResidual(double d_avg, double weight) : d_avg_(d_avg), w(weight)
333 {
334 }
335
336 template <typename T>
337 bool
338 operator()(const T* const c_i, const T* const c_ip1, T* residual) const
339 {
340 T dx = c_ip1[0] - c_i[0];
341 T dy = c_ip1[1] - c_i[1];
342 T dist = ceres::sqrt(dx * dx + dy * dy);
343 residual[0] = T(w) * (dist - T(d_avg_));
344 return true;
345 }
346
347 double d_avg_;
348 double w;
349 };
350
351 // -----------------------------
352 // Options & helpers
353 // -----------------------------
354 static double
355 segment_length(const CenterPoint& a, const CenterPoint& b)
356 {
357 return std::hypot(b.x - a.x, b.y - a.y);
358 }
359
360 static double
361 computeAverageSpacing(const std::vector<CenterPoint>& traj)
362 {
363 if (traj.size() < 2)
364 return 0.0;
365 double sum = 0.0;
366 for (size_t i = 0; i + 1 < traj.size(); ++i)
367 sum += segment_length(traj[i], traj[i + 1]);
368 return sum / double(traj.size() - 1);
369 }
370
371 // -----------------------------
372 // Main optimizer
373 // -----------------------------
374 void
375 optimizeTrajectoryCeres(std::vector<CenterPoint>& traj,
376 const Costmap3DWrapper& costmap_wrapper,
377 const std::vector<CenterPoint>& traj_original,
378 const io::SmoothingParams& opts)
379 {
380 const int N = static_cast<int>(traj.size());
381 if (N < 4)
382 {
383 std::cerr << "[optimizeTrajectoryCeres] need at least 4 points\n";
384 return;
385 }
386
387 // Normalize input angles
388 normalizeTrajectoryAngles(traj);
389
390 ceres::Problem problem;
391 // Add parameter blocks
392 for (int i = 0; i < N; ++i)
393 problem.AddParameterBlock(&traj[i].x, 4);
394
395 // Hard anchors: fix start and goal
396 problem.SetParameterBlockConstant(&traj.front().x);
397 problem.SetParameterBlockConstant(&traj.back().x);
398
399 // 1) Pose smoothness (i = 1 .. N-2)
400 for (int i = 1; i <= N - 2; ++i)
401 {
402 problem.AddResidualBlock(
403 new ceres::AutoDiffCostFunction<PoseSmoothResidual, 3, 4, 4, 4>(
405 nullptr,
406 &traj[i - 1].x,
407 &traj[i].x,
408 &traj[i + 1].x);
409 }
410
411 // Start and end rotation smoothness
412 {
413 // Start boundary
414 problem.AddResidualBlock(
415 new ceres::AutoDiffCostFunction<StartRotationResidual, 1, 4, 4>(
417 nullptr,
418 &traj[0].x,
419 &traj[1].x);
420
421 // End boundary
422 int N = traj.size();
423 problem.AddResidualBlock(
424 new ceres::AutoDiffCostFunction<StartRotationResidual, 1, 4, 4>(
426 nullptr,
427 &traj[N - 2].x,
428 &traj[N - 1].x);
429 }
430
431 // 2) Velocity smoothness
432 for (int i = 1; i <= N - 2; ++i)
433 {
434 problem.AddResidualBlock(new ceres::AutoDiffCostFunction<VelSmoothResidual, 1, 4, 4, 4>(
436 nullptr,
437 &traj[i - 1].x,
438 &traj[i].x,
439 &traj[i + 1].x);
440 }
441
442 // 3) Optional jerk residuals
443 if (opts.use_jerk && N >= 4)
444 {
445 for (int i = 2; i <= N - 2; ++i)
446 {
447 problem.AddResidualBlock(
448 new ceres::AutoDiffCostFunction<PoseJerkResidual, 3, 4, 4, 4, 4>(
449 new PoseJerkResidual(opts.w_pose_jerk)),
450 nullptr,
451 &traj[i - 2].x,
452 &traj[i - 1].x,
453 &traj[i].x,
454 &traj[i + 1].x);
455 }
456 }
457
458 // 4) Robot smoothness residuals (optional)
459 if (opts.use_robot_smooth)
460 {
461 for (int i = 1; i <= N - 2; ++i)
462 {
463 problem.AddResidualBlock(
464 new ceres::AutoDiffCostFunction<RobotSmoothResidual, 3, 4, 4, 4>(
465 new RobotSmoothResidual(opts.w_robot_smooth, -0.5, 0.0)),
466 nullptr,
467 &traj[i - 1].x,
468 &traj[i].x,
469 &traj[i + 1].x);
470 }
471 }
472
473 // 5) Spacing residuals to encourage equal spacing
474 double d_avg = computeAverageSpacing(traj);
475 if (d_avg <= 1e-9)
476 d_avg = 0.1;
477 for (int i = 0; i < N - 1; ++i)
478 {
479 problem.AddResidualBlock(new ceres::AutoDiffCostFunction<SpacingResidual, 1, 4, 4>(
480 new SpacingResidual(d_avg, opts.w_spacing)),
481 nullptr,
482 &traj[i].x,
483 &traj[i + 1].x);
484 }
485
486 // 6) Obstacle residuals with arc-length weighting
487 if (opts.use_obs)
488 {
489 for (int i = 0; i < N; ++i)
490 {
491 double s_local;
492 if (i == 0)
493 s_local = segment_length(traj[0], traj[1]);
494 else if (i == N - 1)
495 s_local = segment_length(traj[N - 2], traj[N - 1]);
496 else
497 s_local = 0.5 * (segment_length(traj[i], traj[i - 1]) +
498 segment_length(traj[i + 1], traj[i]));
499 double s_sqrt = std::sqrt(std::max(1e-6, s_local));
500 // problem.AddResidualBlock(new ceres::AutoDiffCostFunction<ObsResidual, 1, 4>(
501 // new ObsResidual(grid, opts.clearance, s_sqrt, opts.w_obs)),
502 // nullptr,
503 // &traj[i].x);
504 //
505
506 // NOTE: Arc-length weighting is currently not working (not updated at each step)
507 // Therefore, we don't even pass s_local to the ObsResidual
508
509 problem.AddResidualBlock(new ceres::AutoDiffCostFunction<ObstacleResidual, 1, 4>(
510 new ObstacleResidual(costmap_wrapper,
511 opts.w_obs,
512 opts.obsMaxDistance,
514 nullptr,
515 &traj[i].x);
516 }
517 }
518
519 // 7) Velocity limit hinge residuals
520 for (int i = 0; i < N; ++i)
521 {
522 problem.AddResidualBlock(new ceres::AutoDiffCostFunction<VelLimitResidual, 1, 4>(
523 new VelLimitResidual(opts.vmax, opts.w_vel_limit)),
524 nullptr,
525 &traj[i].x);
526 }
527
528 // 8) Tracking residuals to original (optional)
529 if (opts.use_tracking)
530 {
531 if (static_cast<int>(traj_original.size()) != N)
532 {
533 std::cerr << "[optimizeTrajectoryCeres] traj_original size mismatch; skipping "
534 "tracking.\n";
535 }
536 else
537 {
538 for (int i = 0; i < N; ++i)
539 {
540 double wx = opts.w_track;
541 double wy = opts.w_track;
542 double wth = opts.w_track * 300.0;
543 double wv = opts.w_track * 0.2;
544 problem.AddResidualBlock(
545 new ceres::AutoDiffCostFunction<TrackingResidual, 4, 4>(
546 new TrackingResidual(traj_original[i].x,
547 traj_original[i].y,
548 traj_original[i].theta,
549 traj_original[i].v,
550 wx,
551 wy,
552 wth,
553 wv)),
554 nullptr,
555 &traj[i].x);
556 }
557 }
558 }
559
560 // Solver options: limit iterations (we recommend a small number for real-time)
561 ceres::Solver::Options options;
562 options.max_num_iterations = opts.max_iterations;
563 options.linear_solver_type = ceres::SPARSE_NORMAL_CHOLESKY;
564 options.minimizer_progress_to_stdout = false;
565 options.num_threads = opts.num_threads;
566 options.function_tolerance = 1e-6;
567 options.parameter_tolerance = 1e-8;
568
569
570 // Use Levenberg-Marquardt (better for constrained problems)
571 options.trust_region_strategy_type = ceres::LEVENBERG_MARQUARDT;
572 // Start with small trust region
573 options.initial_trust_region_radius = 1e1; // Tune this: 1-100
574 options.max_trust_region_radius = 1e3;
575 options.min_trust_region_radius = 1e-3;
576
577 // Conservative updates
578 options.min_relative_decrease = 1e-3; // Require actual improvement
579
580 auto print_residuals = [&problem, N]()
581 {
582 std::vector<ceres::ResidualBlockId> to_eval;
583 problem.GetResidualBlocks(&to_eval);
584 ceres::Problem::EvaluateOptions options;
585 options.residual_blocks = to_eval;
586 double total_cost = 0.0;
587 std::vector<double> evaluated_residuals;
588 problem.Evaluate(options, &total_cost, &evaluated_residuals, nullptr, nullptr);
589 for (auto i = 0; i < evaluated_residuals.size(); i++)
590 {
591 ARMARX_DEBUG << i << ": " << evaluated_residuals[i];
592 }
593 };
594
595 ceres::Solver::Summary summary;
596 print_residuals();
597 ceres::Solve(options, &problem, &summary);
598 print_residuals();
599 // Normalize angles after optimization (wrapping to [-pi,pi])
600 normalizeTrajectoryAngles(traj);
601
602 ARMARX_INFO << summary.FullReport();
603
604 ARMARX_INFO << "[optimizeTrajectoryCeres] Done. Final cost: " << summary.final_cost
605 << " iterations: " << summary.num_successful_steps << "\n";
606 }
607
608} // namespace armarx::navigation::algorithms::orientation_aware::smoothing
constexpr T c
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:181
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
Definition Logging.h:184
void optimizeTrajectoryCeres(std::vector< CenterPoint > &traj, const Costmap3DWrapper &costmap_wrapper, const std::vector< CenterPoint > &traj_original, const io::SmoothingParams &opts)
Definition residuals.h:375
This file offers overloads of toIce() and fromIce() functions for STL container types.
ObstacleResidual(const Costmap3DWrapper &wrapper, double weight, double max_dist, double collision_residual)
Definition residuals.h:167
bool operator()(const T *const c_m2, const T *const c_m1, const T *const c_i, const T *const c_p1, T *residual) const
Definition residuals.h:138
PoseSmoothResidual(double pos_weight=1.0, double orientation_weight=1.0)
Definition residuals.h:63
bool operator()(const T *const c_prev, const T *const c_i, const T *const c_next, T *residual) const
Definition residuals.h:70
bool operator()(const T *const c_prev, const T *const c_i, const T *const c_next, T *residual) const
Definition residuals.h:309
RobotSmoothResidual(double weight=1.0, double dx=-0.5, double dy=0.0)
Definition residuals.h:291
void centerToRobotTemplated(const T *const c, T &rx, T &ry, T &rtheta) const
Definition residuals.h:298
bool operator()(const T *const c_i, const T *const c_ip1, T *residual) const
Definition residuals.h:338
bool operator()(const T *const theta0, const T *const theta1, T *residuals) const
Definition residuals.h:97
TrackingResidual(double xr, double yr, double thetar, double vr, double wx, double wy, double wth, double wv)
Definition residuals.h:236
bool operator()(const T *const c_prev, const T *const c_i, const T *const c_next, T *residual) const
Definition residuals.h:117