OrientationOptimizer.cpp
Go to the documentation of this file.
2
3#include <math.h>
4
5#include <algorithm>
6#include <cmath>
7#include <cstddef>
8#include <vector>
9
10#include <range/v3/algorithm/for_each.hpp>
11#include <range/v3/range/conversion.hpp>
12#include <range/v3/view/transform.hpp>
13#include <range/v3/view/zip.hpp>
14
15#include <SimoxUtility/math/convert/mat4f_to_xyyaw.h>
16#include <SimoxUtility/math/convert/rpy_to_mat3f.h>
17#include <SimoxUtility/math/periodic/periodic_clamp.h>
18#include <VirtualRobot/Random.h>
19
22
25
26#include <ceres/ceres.h>
27#include <ceres/cost_function.h>
28
30{
32 const Params& params) :
33 trajectory(trajectory), params(params)
34 {
35 }
36
37 inline double
38 angleDiff(const double a, const double b)
39 {
40 double d = a - b;
41
42 // Wrap into (−π, π]
43 while (d <= -M_PI)
44 {
45 d += 2 * M_PI;
46 }
47 while (d > M_PI)
48 {
49 d -= 2 * M_PI;
50 }
51
52 return d;
53 }
54
57 {
58 namespace r = ::ranges;
59 namespace rv = ::ranges::views;
60
61 ARMARX_IMPORTANT << VAROUT(params.startGoalDistanceThreshold);
62
63 const auto toYaw = [](const core::GlobalTrajectoryPoint& pt) -> double
64 { return simox::math::mat4f_to_xyyaw(pt.waypoint.pose.matrix()).z(); };
65
66 //orientations vector contains variable values that are changed during optimization. these
67 // values are changed to fit the given optimization criteria as good as possible
68 std::vector<double> orientations =
69 trajectory.points() | ranges::views::transform(toYaw) | ranges::to_vector;
70
71 //the optimization tries to stay close to the movement direction, thus the original
72 // orientation is copied (it is expected that original orientation values are oriented
73 // so that they face along the trajectory)
74 const std::vector<double> inMovementDir = orientations;
75
76 //calculate angle increment necessary for initial equidistant scattering of orientations
77 const double startOrientation = orientations.front();
78 const double goalOrientation = orientations.back();
79
80 ARMARX_DEBUG << VAROUT(startOrientation);
81 ARMARX_DEBUG << VAROUT(goalOrientation);
82
83 const std::size_t nOrientationStartGoalInfluence = std::ceil(
84 (orientations.size() - 1) * params.startGoalDistanceThreshold / trajectory.length());
85 ARMARX_DEBUG << VAROUT(nOrientationStartGoalInfluence);
86
87 const double signedAngleDiff = angleDiff(goalOrientation, startOrientation);
88
89 ARMARX_DEBUG << VAROUT(signedAngleDiff);
90
91 const double signedAngleDiffDesired = [&]() -> double
92 {
93 switch (params.predefinedRotationDirection)
94 {
96 ARMARX_VERBOSE << "Clockwise";
97 if (signedAngleDiff <= 0)
98 {
99 return signedAngleDiff;
100 }
101 return -2 * M_PI + signedAngleDiff;
102
104 ARMARX_VERBOSE << "CounterClockwise";
105 if (signedAngleDiff >= 0)
106 {
107 return signedAngleDiff;
108 }
109 return 2 * M_PI + signedAngleDiff;
110 default:
111 ARMARX_VERBOSE << "Unspecified";
112 return signedAngleDiff;
113 }
114 }();
115
116
117 if (orientations.size() < 2 * nOrientationStartGoalInfluence)
118 {
119 // we just interpolate between start and end
120
121 const double angleIncrement = signedAngleDiffDesired / (orientations.size() - 1);
122
123 //equidistant orientations are used as starting value for the optimization of the
124 // orientations in the first iteration
125 const std::vector<double> equidistantOrientations = [&]
126 {
127 std::vector<double> vec{orientations.front()};
128 for (std::size_t i = 1; i < orientations.size() - 1; i++)
129 {
130 vec.push_back(startOrientation + angleIncrement * i);
131 }
132 vec.push_back(orientations.back());
133 return vec;
134 }();
135 orientations = equidistantOrientations;
136 }
137 else
138 {
139 // here, we ignore the rotation direction
140
141 // we obtain the first part in which we interpolate between the start and the inMovementDir(nOrientationStartGoalInfluence)
142 {
143 const double signedAngleDiff = angleDiff(
144 inMovementDir.at(nOrientationStartGoalInfluence - 1), startOrientation);
145
146 const double angleIncrement = signedAngleDiff / (nOrientationStartGoalInfluence);
147
148 for (std::size_t i = 1; i < nOrientationStartGoalInfluence; i++)
149 {
150 orientations.at(i) = startOrientation + angleIncrement * i;
151 }
152 }
153
154 // the intermediate points are initialized with inMovementDir
155 {
156 for (std::size_t i = nOrientationStartGoalInfluence;
157 i < orientations.size() - nOrientationStartGoalInfluence;
158 i++)
159 {
160 orientations.at(i) = inMovementDir.at(i);
161 }
162 }
163
164 // we obtain the last part in which we interpolate between the end and the inMovementDir(n-nOrientationStartGoalInfluence)
165 {
166 const double signedAngleDiff = angleDiff(
167 goalOrientation,
168 inMovementDir.at(orientations.size() - nOrientationStartGoalInfluence));
169
170 const double angleIncrement = signedAngleDiff / (nOrientationStartGoalInfluence);
171
172 for (std::size_t i = 1; i < nOrientationStartGoalInfluence; i++)
173 {
174 orientations.at(orientations.size() - nOrientationStartGoalInfluence + i) =
175 goalOrientation - angleIncrement * (nOrientationStartGoalInfluence - i);
176 }
177 }
178 }
179
180
181 ARMARX_DEBUG << "Equidistant";
182 for (const auto& ori : orientations)
183 {
184 ARMARX_DEBUG << ori;
185 }
186
187 /* //COMMENT IN FOR TEST CASE PLOTTING
188 std::vector<double> initial = orientations;
189 std::vector<double> prior = inMovementDir; */
190
191 const float movementDirWeightStep =
192 (params.movementDirWeightEnd - params.movementDirWeightStart) / (params.iterations - 1);
193
194 //iteratively optimize the orientations with increasing movementDirWeight, thus at first ignoring
195 // the criteria to look in the movement direction and slowly considering this more and more
196 // with each iteration
197 // for (int it = 0; it < params.iterations; it++)
198 {
199 const float movementDirWeight = params.movementDirWeightEnd;
200 // params.iterations == 1 ? params.movementDirWeightEnd
201 // : params.movementDirWeightStart + it * movementDirWeightStep;
202
203 //interpolate orientation in walking direction and current orientation at walkingDirAlpha.
204 // interpolated values are used as starting values for next iteration
205 for (std::size_t i = 1; i < orientations.size() - 1; i++)
206 {
207 const float walkingDirAlpha = 0.2;
208
209 const Eigen::Vector2f vMovementDir =
210 walkingDirAlpha *
211 (Eigen::Rotation2Df(inMovementDir.at(i)) * Eigen::Vector2f::UnitX());
212
213 const Eigen::Vector2f vStartGoal =
214 (1.0 - walkingDirAlpha) *
215 (Eigen::Rotation2Df(orientations.at(i)) * Eigen::Vector2f::UnitX());
216
217 const Eigen::Vector2f vCombined = [&]() -> Eigen::Vector2f
218 {
219 if (movementDirWeight > 0) // should rotate into movement direction?
220 {
221 return vMovementDir + vStartGoal;
222 }
223
224 return vStartGoal;
225 }();
226
227 // FIXME reconsider this
228 // orientations.at(i) = std::atan2(vCombined.y(), vCombined.x());
229
230 orientations.at(i) += VirtualRobot::RandomFloat(-0.05F, 0.05F);
231 }
232
233 /* //COMMENT IN FOR TEST CASE PLOTTING
234 initial = orientations;
235 prior = inMovementDir; */
236
237 const std::size_t nPoints = orientations.size();
238
239 ceres::Problem problem;
240
241 ARMARX_VERBOSE << orientations.size() - 2 << " orientations to optimize";
242
243
244 // Define Optimization Criteria
245 {
246 // TODO https://ceres-solver.googlesource.com/ceres-solver/+/master/examples/slam/pose_graph_2d/pose_graph_2d.cc
247 // ceres::LocalParameterization* angle_local_parameterization =
248 // ceres::AngleLocalParameterization::Create();
249
250 // keeps the new orientations somewhat close to the orientation in movement direction
251 if (movementDirWeight > 0.F)
252 {
253 ARMARX_VERBOSE << "Optimize: movement dir enabled.";
254 for (size_t i = 1; i < (orientations.size() - 1); i++)
255 {
256 float actualMovementDirWeight = movementDirWeight;
257 //the first and last four points should not consider it too much to look in the
258 // direction of the trajectory as the robot should not move too much when
259 // close to start or goal
260 if (i < 5)
261 {
262 actualMovementDirWeight *= i / 5.;
263 }
264 else if ((orientations.size() - i - 1) < 5)
265 {
266 actualMovementDirWeight *= (orientations.size() - i - 1) / 5.;
267 }
268
269 ceres::CostFunction* movementDirCostFunction =
270 new OrientationPriorCostFunctor(inMovementDir.at(i),
271 actualMovementDirWeight);
272
273 ARMARX_DEBUG << "Adding OrientationPriorCostFunctor to optimize " << i;
274 problem.AddResidualBlock(
275 movementDirCostFunction, nullptr, &orientations.at(i));
276 }
277 }
278
279 // smooth waypoint orientation, no start and goal nodes involved!
280 if (params.smoothnessWeight > 0.F)
281 {
282 ARMARX_VERBOSE << "Enabled SmoothOrientationCost";
283 for (size_t i = 2; i < (orientations.size() - 2); i++)
284 {
285 ceres::CostFunction* smoothCostFunction =
286 new SmoothOrientationCostFunctor(params.smoothnessWeight);
287
288 ARMARX_DEBUG << "Addding SmoothOrientationCostFunctor to optimize " << i - 1
289 << ", " << i << " and " << i + 1;
290 problem.AddResidualBlock(smoothCostFunction,
291 nullptr,
292 &orientations.at(i - 1),
293 &orientations.at(i),
294 &orientations.at(i + 1));
295 }
296 }
297
298 // within a certain range close to the start, the robot shouldn't change its orientation
299 if (params.priorStartWeight > 0.F)
300 {
301 ARMARX_DEBUG << "prior start enabled";
302 const auto connectedPointsInRangeStart =
303 trajectory.allConnectedPointsInRange(0, params.startGoalDistanceThreshold);
304
305 ARMARX_DEBUG << VAROUT(connectedPointsInRangeStart.size());
306
307 for (const size_t i : connectedPointsInRangeStart)
308 {
309 // skip the points that belong to the second half of the trajectory
310 if (i >= orientations.size() / 2)
311 {
312 continue;
313 }
314
315 ceres::CostFunction* priorCostFunction = new OrientationPriorCostFunctor(
316 inMovementDir.front(), params.priorStartWeight);
317
318 ARMARX_DEBUG << "Addding OrientationPriorCostFunctor(start) to optimize "
319 << i;
320 problem.AddResidualBlock(priorCostFunction, nullptr, &orientations.at(i));
321 }
322 }
323
324 // within a certain range close to the end, the robot shouldn't change its orientation
325 if (params.priorEndWeight > 0.F)
326 {
327 ARMARX_VERBOSE << "prior end enabled";
328
329 const auto connectedPointsInRangeGoal = trajectory.allConnectedPointsInRange(
330 trajectory.poses().size() - 1, params.startGoalDistanceThreshold);
331
332 ARMARX_DEBUG << VAROUT(connectedPointsInRangeGoal.size());
333
334 for (const size_t i : connectedPointsInRangeGoal)
335 {
336 // skip the points that belong to the first half of the trajectory
337 if (i < orientations.size() / 2)
338 {
339 continue;
340 }
341
342 ceres::CostFunction* priorCostFunction = new OrientationPriorCostFunctor(
343 inMovementDir.back(), params.priorEndWeight);
344
345 ARMARX_DEBUG << "Addding OrientationPriorCostFunctor to optimize " << i;
346 problem.AddResidualBlock(priorCostFunction, nullptr, &orientations.at(i));
347 }
348 }
349
350
351 // smooth waypoint orientation, involving start
352 if (params.smoothnessWeightStartGoal > 0.F and nPoints > 3)
353 {
354 ARMARX_VERBOSE << "Enabled SmoothOrientationFixedPreCost";
355
356 ceres::CostFunction* smoothCostFunction =
357 new SmoothOrientationFixedPreCostFunctor(orientations.front(),
358 params.smoothnessWeightStartGoal);
359
361 << "Addding SmoothOrientationFixedPreCostFunctor to optimize 1 and 2";
362 problem.AddResidualBlock(
363 smoothCostFunction, nullptr, &orientations.at(1), &orientations.at(2));
364 }
365
366 // smooth waypoint orientation, involving goal
367 if (params.smoothnessWeightStartGoal > 0.F and nPoints > 3)
368 {
369 ARMARX_VERBOSE << "Enabled SmoothOrientationFixedNextCost";
370
371 ceres::CostFunction* smoothCostFunction =
372 new SmoothOrientationFixedNextCostFunctor(orientations.back(),
373 params.smoothnessWeightStartGoal);
374
375 ARMARX_DEBUG << "Addding SmoothOrientationFixedPreCostFunctor to optimize "
376 << orientations.size() - 3 << " and " << orientations.size() - 2;
377 problem.AddResidualBlock(smoothCostFunction,
378 nullptr,
379 &orientations.at(orientations.size() - 3),
380 &orientations.at(orientations.size() - 2));
381 }
382 }
383
384
385 // Run the solver!
386 ceres::Solver::Options options;
387 options.linear_solver_type = ceres::DENSE_QR;
388 options.minimizer_progress_to_stdout = true;
389 options.max_num_iterations = 100;
390 options.function_tolerance = 0.01;
391 // options.check_gradients = false;
392 // options.trust_region_minimizer_iterations_to_dump = {0, 1, 2, 5, 10, 20};
393 // options.max_trust_region_radius = 0.05;
394 // options.initial_trust_region_radius = 0.01;
395
396 ceres::Solver::Summary summary;
397 Solve(options, &problem, &summary);
398
399 // orientations = inMovementDir;
400
401 ARMARX_VERBOSE << summary.FullReport() << "\n";
402
403 if (not summary.IsSolutionUsable())
404 {
405 ARMARX_ERROR << "Orientation optimization failed!";
406 // TODO write to file
407 }
408 }
409
410 // no matter what, we have to ensure that the start and goal are still as they were
411 // FIXME ARMARX_CHECK
412
413 // temporary fix: overwrite the values
414 orientations.front() = startOrientation;
415 orientations.back() = goalOrientation;
416
417 ARMARX_DEBUG << "Final";
418 for (const auto& ori : orientations)
419 {
420 ARMARX_DEBUG << ori;
421 }
422
423 const auto applyOrientation = [](const auto& p) -> core::GlobalTrajectoryPoint
424 {
425 core::GlobalTrajectoryPoint tp = p.first;
426 const float yaw = p.second;
427
428 tp.waypoint.pose.linear() =
429 Eigen::AngleAxisf(yaw, Eigen::Vector3f::UnitZ()).toRotationMatrix();
430
431 return tp;
432 };
433
434 // TODO(fabian.reister): could also be in-place
435 const auto modifiedTrajectory = rv::zip(trajectory.points(), orientations) |
436 rv::transform(applyOrientation) | r::to_vector;
437
439 .trajectory = modifiedTrajectory,
440
441 /* //COMMENT IN FOR TEST CASE PLOTTING
442 .initial = initial,
443 .prior = prior */
444
445 };
446 }
447
448
449} // namespace armarx::navigation::global_planning::optimization
#define M_PI
Definition MathTools.h:17
#define VAROUT(x)
OrientationOptimizer(const core::GlobalTrajectory &trajectory, const Params &params)
#define ARMARX_IMPORTANT
The logging level for always important information, but expected behaviour (in contrast to ARMARX_WAR...
Definition Logging.h:190
#define ARMARX_ERROR
The logging level for unexpected behaviour, that must be fixed.
Definition Logging.h:196
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
Definition Logging.h:184
#define ARMARX_VERBOSE
The logging level for verbose information.
Definition Logging.h:187
double angleDiff(const double a, const double b)