residuals.h
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#pragma once
18
19#include <algorithm>
20#include <cmath>
21#include <vector>
22
24
27
28#include <ceres/ceres.h>
29
30#if CERES_VERSION_MAJOR >= 2 && CERES_VERSION_MINOR >= 1
31# include <ceres/manifold.h>
32 using SubsetConstraint = ceres::SubsetManifold;
33# define SET_CONSTRAINT(problem, param, constraint) \
34 (problem).SetManifold((param), (constraint))
35#else
36# include <ceres/local_parameterization.h>
37 using SubsetConstraint = ceres::SubsetParameterization;
38# define SET_CONSTRAINT(problem, param, constraint) \
39 (problem).SetParameterization((param), (constraint))
40#endif
41
43{
45 {
46 double x, y, theta, v;
47 };
48
49 inline double normalizeAngle(double a)
50 {
51 return std::atan2(std::sin(a), std::cos(a));
52 }
53
54 static void normalizeTrajectoryAngles(std::vector<CenterPoint>& traj)
55 {
56 for (auto& p : traj)
57 p.theta = normalizeAngle(p.theta);
58 }
59
60 // -----------------------------
61 // Position-only smoothness residual
62 // -----------------------------
64 {
65 explicit PositionSmoothResidual(double weight = 1.0) : w(weight) {}
66
67 template <typename T>
68 bool operator()(const T* const c_prev,
69 const T* const c_i,
70 const T* const c_next,
71 T* residual) const
72 {
73 residual[0] = T(w) * (c_prev[0] - T(2.0) * c_i[0] + c_next[0]); // x
74 residual[1] = T(w) * (c_prev[1] - T(2.0) * c_i[1] + c_next[1]); // y
75 return true;
76 }
77
78 double w;
79 };
80
81 /// Hermite smoothstep: 0 below edge0, 1 above edge1, C1 at both ends.
82 inline double smoothstep(double edge0, double edge1, double x)
83 {
84 if (x <= edge0)
85 {
86 return 0.0;
87 }
88 if (x >= edge1)
89 {
90 return 1.0;
91 }
92
93 const double t = (x - edge0) / (edge1 - edge0);
94 return t * t * (3.0 - 2.0 * t);
95 }
96
97 /**
98 * Scale for the tracking weight, from the clearance at a waypoint's reference position.
99 *
100 * 1 where clearance is scarce, fading to 0 where it is plentiful, so the leash to the
101 * planner's route tightens exactly where wandering is expensive and lets go where the
102 * smoothness term should be free to straighten.
103 *
104 * This is the soft form of pinning a tight waypoint outright. Pinning makes both adjoining
105 * segments half-constrained, which is the structure behind this smoother's rejections at
106 * its commanded endpoints; a weight cannot do that, and it degrades gracefully instead of
107 * removing a degree of freedom.
108 */
109 inline double trackingScaleFromClearance(double clearance,
110 double fadeStartClearance,
111 double fadeEndClearance)
112 {
113 return 1.0 - smoothstep(fadeStartClearance, fadeEndClearance, clearance);
114 }
115
116 // -----------------------------
117 // Obstacle cost
118 // -----------------------------
119
120 /**
121 * Obstacle cost: a single parabola in the distance to the nearest obstacle.
122 *
123 * t = (d - clearance) / max_dist
124 * r = (t >= 1) ? 0 : weight * (1 - t)^2
125 *
126 * Three properties matter, and the form this replaced broke the first two.
127 *
128 * *No step.* The previous form branched at the clearance boundary into two pieces that
129 * did not meet: the inside piece tended to 0 there while the outside piece tended to
130 * `weight`. A trust-region solver compares predicted against actual reduction, which is
131 * meaningless across a discontinuity, so steps were rejected at a boundary the optimizer
132 * crosses constantly.
133 *
134 * *Monotone in distance.* Because of that step the collision side of the boundary was
135 * strictly cheaper than the safe side, so a waypoint descending out of an obstacle was
136 * rewarded for stopping just inside it. Here the cost never increases as an obstacle gets
137 * further away, so there is never anything to gain by moving toward one.
138 *
139 * *A standoff preference that ends cleanly.* The cost reaches exactly zero at
140 * `clearance + max_dist`, with zero slope, so obstacles beyond the band exert no pull and
141 * cannot couple waypoints that are already comfortably clear. Within the band there is a
142 * mild preference for more room, which is what keeps paths off the furniture rather than
143 * merely legal.
144 *
145 * That last property is why this is a band and not a hinge at the clearance. An earlier
146 * revision made the cost zero everywhere outside `clearance`, which removed the step but
147 * also removed the only term expressing "prefer to stay away". Measured over 600 requests
148 * in R007 that cost 88 mm of median clearance and put 81 more paths within 200 mm of
149 * furniture, for no improvement in the acceptance rate.
150 *
151 * Inside the clearance the same parabola keeps growing, so `max_dist` also sets how
152 * sharply penetration is punished. It is not a distance the optimizer is pushed to reach.
153 */
154 template <typename T>
155 T obstacleBarrier(const T& d_raw,
156 const double weight,
157 const double max_dist,
158 const double clearance)
159 {
160 // Defensive guard: if the wrapper ever returns non-finite, treat as deep obstacle so
161 // the residual stays finite and Ceres does not abort.
162 if (!ceres::isfinite(d_raw))
163 {
164 return T(weight) * T(100.0);
165 }
166
167 // Keep deeply negative distances bounded, so a waypoint far inside an obstacle cannot
168 // produce an arbitrarily large residual.
169 T d = d_raw;
170 const T min_allowed = T(-3.0 * max_dist);
171 if (d < min_allowed)
172 {
173 d = min_allowed;
174 }
175
176 const T t = (d - T(clearance)) / T(max_dist);
177
178 if (t >= T(1.0))
179 {
180 return T(0.0);
181 }
182
183 const T u = T(1.0) - t;
184 return T(weight) * u * u;
185 }
186
187 // -----------------------------
188 // Obstacle residuals (2-D)
189 // -----------------------------
191 {
193 double weight,
194 double max_dist,
195 double clearance) :
196 wrapper_(wrapper),
197 w_(weight),
198 max_dist_(max_dist),
199 clearance_(clearance)
200 {
201 }
202
203 template <typename T>
204 bool operator()(const T* const node, T* residual) const
205 {
206 T d_raw;
207 wrapper_(&node[0], &node[1], &d_raw);
208
209 residual[0] = obstacleBarrier(d_raw, w_, max_dist_, clearance_);
210 return true;
211 }
212
214 double w_;
215 double max_dist_;
217 };
218
220 {
222 double weight,
223 double max_dist,
224 double clearance) :
225 wrapper_(wrapper),
226 w_(weight),
227 max_dist_(max_dist),
228 clearance_(clearance)
229 {
230 }
231
232 template <typename T>
233 bool operator()(const T* const c_i, const T* const c_ip1, T* residual) const
234 {
235 T mx = (c_i[0] + c_ip1[0]) / T(2.0);
236 T my = (c_i[1] + c_ip1[1]) / T(2.0);
237
238 T d_raw;
239 wrapper_(&mx, &my, &d_raw);
240
241 residual[0] = obstacleBarrier(d_raw, w_, max_dist_, clearance_);
242 return true;
243 }
244
246 double w_;
247 double max_dist_;
249 };
250
251 // Obstacle residual sampled at an arbitrary position along the segment.
252 // t=0 evaluates at c_i, t=1 at c_ip1; midpoints use t=0.5.
254 {
256 double weight,
257 double max_dist,
258 double clearance,
259 double t) :
260 wrapper_(wrapper),
261 w_(weight),
262 max_dist_(max_dist),
263 clearance_(clearance),
264 t_(t)
265 {
266 }
267
268 template <typename T>
269 bool operator()(const T* const c_i, const T* const c_ip1, T* residual) const
270 {
271 T sx = (T(1.0) - T(t_)) * c_i[0] + T(t_) * c_ip1[0];
272 T sy = (T(1.0) - T(t_)) * c_i[1] + T(t_) * c_ip1[1];
273
274 T d_raw;
275 wrapper_(&sx, &sy, &d_raw);
276
277 residual[0] = obstacleBarrier(d_raw, w_, max_dist_, clearance_);
278 return true;
279 }
280
282 double w_;
283 double max_dist_;
285 double t_;
286 };
287
288 // -----------------------------
289 // Tracking residuals
290 // -----------------------------
291
292 /**
293 * Bounded tracking residual.
294 *
295 * - Deviation <= deadzone: 0 cost.
296 * - deadzone < deviation <= max_deviation: quadratic growth.
297 * - deviation > max_deviation: very steep quadratic growth (hard limit).
298 */
300 {
302 double yr,
303 double deadzone,
304 double max_deviation,
305 double w_in_bounds,
306 double w_hard) :
307 xr_(xr),
308 yr_(yr),
309 deadzone_(deadzone),
310 max_deviation_(max_deviation),
311 w_in_bounds_(w_in_bounds),
312 w_hard_(w_hard)
313 {
314 }
315
316 template <typename T>
317 bool operator()(const T* const c_i, T* residual) const
318 {
319 T dx = c_i[0] - T(xr_);
320 T dy = c_i[1] - T(yr_);
321 T d = ceres::sqrt(dx * dx + dy * dy + T(1e-6));
322
323 T excess = d - T(deadzone_);
324 if (excess <= T(0))
325 {
326 residual[0] = T(0);
327 }
328 else if (excess <= T(max_deviation_ - deadzone_))
329 {
330 residual[0] = T(w_in_bounds_) * excess;
331 }
332 else
333 {
334 T over = excess - T(max_deviation_ - deadzone_);
335 residual[0] = T(w_in_bounds_) * T(max_deviation_ - deadzone_) + T(w_hard_) * over;
336 }
337 return true;
338 }
339
340 double xr_, yr_;
341 double deadzone_;
344 double w_hard_;
345 };
346
347 // Soft tracking to the hill-climbed safe path (weak weight).
349 {
350 SoftTrackingResidual(double xr, double yr, double weight) :
351 xr_(xr), yr_(yr), w_(weight)
352 {
353 }
354
355 template <typename T>
356 bool operator()(const T* const c_i, T* residual) const
357 {
358 residual[0] = T(w_) * (c_i[0] - T(xr_));
359 residual[1] = T(w_) * (c_i[1] - T(yr_));
360 return true;
361 }
362
363 double xr_, yr_;
364 double w_;
365 };
366
367 // -----------------------------
368 // Spacing residual
369 // -----------------------------
371 {
372 SpacingResidual(double d_avg, double weight) : d_avg_(d_avg), w(weight) {}
373
374 template <typename T>
375 bool operator()(const T* const c_i, const T* const c_ip1, T* residual) const
376 {
377 T dx = c_ip1[0] - c_i[0];
378 T dy = c_ip1[1] - c_i[1];
379 T dist_sq = dx * dx + dy * dy;
380 T target_sq = T(d_avg_ * d_avg_);
381 residual[0] = T(w) * (dist_sq - target_sq);
382 return true;
383 }
384
385 double d_avg_;
386 double w;
387 };
388
389 // -----------------------------
390 // Helpers
391 // -----------------------------
392 static double segment_length(const CenterPoint& a, const CenterPoint& b)
393 {
394 return std::hypot(b.x - a.x, b.y - a.y);
395 }
396
397 static double computeAverageSpacing(const std::vector<CenterPoint>& traj)
398 {
399 if (traj.size() < 2)
400 return 0.0;
401 double sum = 0.0;
402 for (size_t i = 0; i + 1 < traj.size(); ++i)
403 sum += segment_length(traj[i], traj[i + 1]);
404 return sum / double(traj.size() - 1);
405 }
406
407} // namespace armarx::navigation::algorithms::spfa::smoothing
Differentiable wrapper around the standard 2-D distance-to-obstacle costmap.
double smoothstep(double edge0, double edge1, double x)
Hermite smoothstep: 0 below edge0, 1 above edge1, C1 at both ends.
Definition residuals.h:82
T obstacleBarrier(const T &d_raw, const double weight, const double max_dist, const double clearance)
Obstacle cost: a single parabola in the distance to the nearest obstacle.
Definition residuals.h:155
double trackingScaleFromClearance(double clearance, double fadeStartClearance, double fadeEndClearance)
Scale for the tracking weight, from the clearance at a waypoint's reference position.
Definition residuals.h:109
This file offers overloads of toIce() and fromIce() functions for STL container types.
ceres::SubsetParameterization SubsetConstraint
This file is part of ArmarX.
Definition residuals.h:24
BoundedTrackingResidual(double xr, double yr, double deadzone, double max_deviation, double w_in_bounds, double w_hard)
Definition residuals.h:301
MidpointObstacleResidual2D(const Costmap2DWrapper &wrapper, double weight, double max_dist, double clearance)
Definition residuals.h:221
bool operator()(const T *const c_i, const T *const c_ip1, T *residual) const
Definition residuals.h:233
bool operator()(const T *const node, T *residual) const
Definition residuals.h:204
ObstacleResidual2D(const Costmap2DWrapper &wrapper, double weight, double max_dist, double clearance)
Definition residuals.h:192
bool operator()(const T *const c_prev, const T *const c_i, const T *const c_next, T *residual) const
Definition residuals.h:68
bool operator()(const T *const c_i, const T *const c_ip1, T *residual) const
Definition residuals.h:269
SegmentObstacleResidual2D(const Costmap2DWrapper &wrapper, double weight, double max_dist, double clearance, double t)
Definition residuals.h:255
bool operator()(const T *const c_i, T *residual) const
Definition residuals.h:356
bool operator()(const T *const c_i, const T *const c_ip1, T *residual) const
Definition residuals.h:375