OrientationOptimizer.cpp
Go to the documentation of this file.
1 #include "OrientationOptimizer.h"
2 
3 #include <algorithm>
4 #include <cmath>
5 
6 #include <SimoxUtility/math/convert/mat4f_to_xyyaw.h>
7 #include <SimoxUtility/math/convert/rpy_to_mat3f.h>
8 #include <SimoxUtility/math/periodic/periodic_clamp.h>
9 #include <SimoxUtility/math/periodic/periodic_diff.h>
10 #include <VirtualRobot/MathTools.h>
11 #include <VirtualRobot/Random.h>
12 
15 
17 
20 
21 #include <ceres/ceres.h>
22 #include <ceres/cost_function.h>
23 #include <ceres/jet.h>
24 #include <ceres/rotation.h>
25 #include <ceres/sized_cost_function.h>
26 #include <range/v3/range/conversion.hpp>
27 #include <range/v3/view/drop.hpp>
28 #include <range/v3/view/drop_last.hpp>
29 #include <range/v3/view/transform.hpp>
30 #include <range/v3/view/zip.hpp>
31 
33 {
35  const Params& params) :
36  trajectory(trajectory), params(params)
37  {
38  }
39 
42  {
43  namespace r = ::ranges;
44  namespace rv = ::ranges::views;
45 
46  const auto toYaw = [](const core::GlobalTrajectoryPoint& pt) -> double
47  { return simox::math::mat4f_to_xyyaw(pt.waypoint.pose.matrix()).z(); };
48 
49  //orientations vector contains variable values that are changed during optimization. these
50  // values are changed to fit the given optimization criteria as good as possible
51  std::vector<double> orientations =
52  trajectory.points() | ranges::views::transform(toYaw) | ranges::to_vector;
53 
54  //the optimization tries to stay close to the movement direction, thus the original
55  // orientation is copied (it is expected that original orientation values are oriented
56  // so that they face along the trajectory)
57  const std::vector<double> inMovementDir = orientations;
58 
59  //calculate angle increment necessary for initial equidistant scattering of orientations
60  const double& startOrientation = orientations.front();
61  const double& goalOrientation = orientations.back();
62  const double signedAngleDiff =
63  std::fmod<double>(goalOrientation - startOrientation + 3 * M_PI, 2 * M_PI) - M_PI;
64  const double signedAngleDiffDesired = [&]() -> double
65  {
66  if (params.predefinedRotationDirection)
67  {
68  switch (params.predefinedRotationDirection.value())
69  {
71  if (signedAngleDiff <= 0)
72  {
73  return signedAngleDiff;
74  }
75  return -2 * M_PI + signedAngleDiff;
76 
78  if (signedAngleDiff >= 0)
79  {
80  return signedAngleDiff;
81  }
82  return 2 * M_PI + signedAngleDiff;
83  }
84  }
85  return signedAngleDiff;
86  }();
87  const double angleIncrement = signedAngleDiffDesired / (orientations.size() - 1);
88 
89  //equidistant orientations are used as starting value for the optimization of the
90  // orientations in the first iteration
91  const std::vector<double> equidistantOrientations = [&]
92  {
93  std::vector<double> vec{orientations.front()};
94  for (std::size_t i = 1; i < orientations.size() - 1; i++)
95  {
96  vec.push_back(startOrientation + angleIncrement * i);
97  }
98  vec.push_back(orientations.back());
99  return vec;
100  }();
101  orientations = equidistantOrientations;
102 
103  /* //COMMENT IN FOR TEST CASE PLOTTING
104  std::vector<double> initial = orientations;
105  std::vector<double> prior = inMovementDir; */
106 
107  const float movementDirWeightStep =
108  (params.movementDirWeightEnd - params.movementDirWeightStart) / (params.iterations - 1);
109 
110  //iteratively optimize the orientations with increasing movementDirWeight, thus at first ignoring
111  // the criteria to look in the movement direction and slowly considering this more and more
112  // with each iteration
113  for (int it = 0; it < params.iterations; it++)
114  {
115  const float movementDirWeight =
116  params.iterations == 1 ? params.movementDirWeightEnd
117  : params.movementDirWeightStart + it * movementDirWeightStep;
118 
119  //interpolate orientation in walking direction and current orientation at walkingDirAlpha.
120  // interpolated values are used as starting values for next iteration
121  for (std::size_t i = 1; i < orientations.size() - 1; i++)
122  {
123  const float walkingDirAlpha = 0.2;
124 
125  const Eigen::Vector2f vMovementDir =
126  walkingDirAlpha *
127  (Eigen::Rotation2Df(inMovementDir.at(i)) * Eigen::Vector2f::UnitX());
128 
129  const Eigen::Vector2f vStartGoal =
130  (1.0 - walkingDirAlpha) *
131  (Eigen::Rotation2Df(orientations.at(i)) * Eigen::Vector2f::UnitX());
132 
133  const Eigen::Vector2f vCombined = [&]() -> Eigen::Vector2f
134  {
135  if (movementDirWeight > 0) // should rotate into movement direction?
136  {
137  return vMovementDir + vStartGoal;
138  }
139 
140  return vStartGoal;
141  }();
142  orientations.at(i) = std::atan2(vCombined.y(), vCombined.x());
143  }
144 
145  /* //COMMENT IN FOR TEST CASE PLOTTING
146  initial = orientations;
147  prior = inMovementDir; */
148 
149  const std::size_t nPoints = orientations.size();
150 
151  ceres::Problem problem;
152 
153  ARMARX_VERBOSE << orientations.size() - 2 << " orientations to optimize";
154 
155 
156  //--------------------------------------------------------------------------------------
157  // Start of Optimization Criteria
158 
159  // TODO https://ceres-solver.googlesource.com/ceres-solver/+/master/examples/slam/pose_graph_2d/pose_graph_2d.cc
160  // ceres::LocalParameterization* angle_local_parameterization =
161  // ceres::AngleLocalParameterization::Create();
162 
163  // keeps the new orientations somewhat close to the orientation in movement direction
164  if (movementDirWeight > 0.F)
165  {
166  ARMARX_VERBOSE << "Optimize: movement dir enabled.";
167  for (size_t i = 1; i < (orientations.size() - 1); i++)
168  {
169  float actualMovementDirWeight = movementDirWeight;
170  //the first and last four points should not consider it too much to look in the
171  // direction of the trajectory as the robot should not move too much when
172  // close to start or goal
173  if (i < 5)
174  {
175  actualMovementDirWeight *= i / 5.;
176  }
177  else if ((orientations.size() - i - 1) < 5)
178  {
179  actualMovementDirWeight *= (orientations.size() - i - 1) / 5.;
180  }
181 
182  ceres::CostFunction* movementDirCostFunction = new OrientationPriorCostFunctor(
183  inMovementDir.at(i), actualMovementDirWeight);
184 
185  ARMARX_DEBUG << "Adding OrientationPriorCostFunctor to optimize " << i;
186  problem.AddResidualBlock(movementDirCostFunction, nullptr, &orientations.at(i));
187  }
188  }
189 
190  // smooth waypoint orientation, no start and goal nodes involved!
191  if (params.smoothnessWeight > 0.F)
192  {
193  ARMARX_VERBOSE << "Enabled SmoothOrientationCost";
194  for (size_t i = 2; i < (orientations.size() - 2); i++)
195  {
196  ceres::CostFunction* smoothCostFunction =
198 
199  ARMARX_DEBUG << "Addding SmoothOrientationCostFunctor to optimize " << i - 1
200  << ", " << i << " and " << i + 1;
201  problem.AddResidualBlock(smoothCostFunction,
202  nullptr,
203  &orientations.at(i - 1),
204  &orientations.at(i),
205  &orientations.at(i + 1));
206  }
207  }
208 
209  // within a certain range close to the start, the robot shouldn't change its orientation
210  if (params.priorStartWeight > 0.F)
211  {
212  ARMARX_VERBOSE << "prior start enabled";
213  const auto connectedPointsInRangeStart =
215 
216  for (const size_t i : connectedPointsInRangeStart)
217  {
218  // skip the points that belong to the second half of the trajectory
219  if (i >= orientations.size() / 2)
220  {
221  continue;
222  }
223 
224  ceres::CostFunction* priorCostFunction = new OrientationPriorCostFunctor(
225  inMovementDir.front(), params.priorStartWeight);
226 
227  ARMARX_DEBUG << "Addding OrientationPriorCostFunctor(start) to optimize " << i;
228  problem.AddResidualBlock(priorCostFunction, nullptr, &orientations.at(i));
229  }
230  }
231 
232  // within a certain range close to the end, the robot shouldn't change its orientation
233  if (params.priorEndWeight > 0.F)
234  {
235  ARMARX_VERBOSE << "prior end enabled";
236 
237  const auto connectedPointsInRangeGoal = trajectory.allConnectedPointsInRange(
238  trajectory.poses().size() - 1, params.startGoalDistanceThreshold);
239 
240  for (const size_t i : connectedPointsInRangeGoal)
241  {
242  // skip the points that belong to the first half of the trajectory
243  if (i < orientations.size() / 2)
244  {
245  continue;
246  }
247 
248  ceres::CostFunction* priorCostFunction = new OrientationPriorCostFunctor(
249  inMovementDir.back(), params.priorEndWeight);
250 
251  ARMARX_DEBUG << "Addding OrientationPriorCostFunctor to optimize " << i;
252  problem.AddResidualBlock(priorCostFunction, nullptr, &orientations.at(i));
253  }
254  }
255 
256 
257  // smooth waypoint orientation, involving start
258  if (params.smoothnessWeightStartGoal > 0.F and nPoints > 3)
259  {
260  ARMARX_VERBOSE << "Enabled SmoothOrientationFixedPreCost";
261 
262  ceres::CostFunction* smoothCostFunction = new SmoothOrientationFixedPreCostFunctor(
263  orientations.front(), params.smoothnessWeightStartGoal);
264 
265  ARMARX_DEBUG << "Addding SmoothOrientationFixedPreCostFunctor to optimize 1 and 2";
266  problem.AddResidualBlock(
267  smoothCostFunction, nullptr, &orientations.at(1), &orientations.at(2));
268  }
269 
270  // smooth waypoint orientation, involving goal
271  if (params.smoothnessWeightStartGoal > 0.F and nPoints > 3)
272  {
273  ARMARX_VERBOSE << "Enabled SmoothOrientationFixedNextCost";
274 
275  ceres::CostFunction* smoothCostFunction = new SmoothOrientationFixedNextCostFunctor(
276  orientations.back(), params.smoothnessWeightStartGoal);
277 
278  ARMARX_DEBUG << "Addding SmoothOrientationFixedPreCostFunctor to optimize "
279  << orientations.size() - 3 << " and " << orientations.size() - 2;
280  problem.AddResidualBlock(smoothCostFunction,
281  nullptr,
282  &orientations.at(orientations.size() - 3),
283  &orientations.at(orientations.size() - 2));
284  }
285 
286  // End of Optimization Criteria
287  //--------------------------------------------------------------------------------------
288 
289  // Run the solver!
290  ceres::Solver::Options options;
291  options.linear_solver_type = ceres::DENSE_QR;
292  options.minimizer_progress_to_stdout = false;
293  options.max_num_iterations = 100;
294  options.function_tolerance = 0.01;
295  // options.check_gradients = false;
296  // options.trust_region_minimizer_iterations_to_dump = {0, 1, 2, 5, 10, 20};
297  // options.max_trust_region_radius = 0.05;
298  // options.initial_trust_region_radius = 0.01;
299 
300  ceres::Solver::Summary summary;
301  Solve(options, &problem, &summary);
302 
303  ARMARX_VERBOSE << summary.FullReport() << "\n";
304 
305  const auto clampInPlace = [](auto& val)
306  { val = simox::math::periodic_clamp(val, -M_PI, M_PI); };
307 
308  std::for_each(orientations.begin(), orientations.end(), clampInPlace);
309 
310  if (not summary.IsSolutionUsable())
311  {
312  ARMARX_ERROR << "Orientation optimization failed!";
313  // TODO write to file
314  }
315  }
316 
317  const auto applyOrientation = [](const auto& p) -> core::GlobalTrajectoryPoint
318  {
319  core::GlobalTrajectoryPoint tp = p.first;
320  const float yaw = p.second;
321 
322  tp.waypoint.pose.linear() = simox::math::rpy_to_mat3f(0.F, 0.F, yaw);
323 
324  return tp;
325  };
326 
327  // TODO(fabian.reister): could also be in-place
328  const auto modifiedTrajectory = rv::zip(trajectory.points(), orientations) |
329  rv::transform(applyOrientation) | r::to_vector;
330 
332  .trajectory = modifiedTrajectory,
333 
334  /* //COMMENT IN FOR TEST CASE PLOTTING
335  .initial = initial,
336  .prior = prior */
337 
338  };
339  }
340 
341 
342 } // namespace armarx::navigation::global_planning::optimization
armarx::navigation::core::GlobalTrajectory
Definition: Trajectory.h:68
armarx::navigation::global_planning::optimization
This file is part of ArmarX.
Definition: aron_conversions.h:12
armarx::navigation::core::Waypoint::pose
Pose pose
Definition: basic_types.h:50
ARMARX_VERBOSE
#define ARMARX_VERBOSE
Definition: Logging.h:180
armarx::navigation::core::GlobalTrajectoryPoint::waypoint
Waypoint waypoint
Definition: Trajectory.h:37
armarx::navigation::global_planning::optimization::OrientationOptimizerParams::priorStartWeight
float priorStartWeight
Definition: OrientationOptimizer.h:46
OrientationOptimizer.h
armarx::navigation::global_planning::optimization::OrientationOptimizer::optimize
OptimizationResult optimize()
Definition: OrientationOptimizer.cpp:41
armarx::navigation::global_planning::optimization::OrientationOptimizerParams::movementDirWeightEnd
float movementDirWeightEnd
Definition: OrientationOptimizer.h:41
cost_functions.h
armarx::navigation::global_planning::optimization::OrientationOptimizerParams::startGoalDistanceThreshold
float startGoalDistanceThreshold
Definition: OrientationOptimizer.h:49
armarx::navigation::core::GlobalTrajectoryPoint
Definition: Trajectory.h:35
armarx::navigation::global_planning::optimization::OrientationOptimizerParams
Definition: OrientationOptimizer.h:37
BasicControllers.h
armarx::navigation::global_planning::optimization::OrientationOptimizer::OptimizationResult::trajectory
std::optional< core::GlobalTrajectory > trajectory
Definition: OrientationOptimizer.h:63
armarx::navigation::global_planning::optimization::OrientationOptimizerParams::iterations
int iterations
Definition: OrientationOptimizer.h:39
armarx::navigation::global_planning::optimization::RotationDirection::CounterClockwise
@ CounterClockwise
M_PI
#define M_PI
Definition: MathTools.h:17
armarx::navigation::global_planning::optimization::OrientationOptimizerParams::smoothnessWeightStartGoal
float smoothnessWeightStartGoal
Definition: OrientationOptimizer.h:44
ARMARX_DEBUG
#define ARMARX_DEBUG
Definition: Logging.h:177
armarx::navigation::global_planning::optimization::OrientationOptimizer::OrientationOptimizer
OrientationOptimizer(const core::GlobalTrajectory &trajectory, const Params &params)
Definition: OrientationOptimizer.cpp:34
armarx::navigation::global_planning::optimization::OrientationOptimizerParams::movementDirWeightStart
float movementDirWeightStart
Definition: OrientationOptimizer.h:40
armarx::navigation::global_planning::optimization::SmoothOrientationCostFunctor
Definition: cost_functions.h:191
armarx::navigation::core::GlobalTrajectory::poses
std::vector< Pose > poses() const noexcept
Definition: Trajectory.cpp:344
ARMARX_ERROR
#define ARMARX_ERROR
Definition: Logging.h:189
ExpressionException.h
armarx::navigation::global_planning::optimization::SmoothOrientationFixedNextCostFunctor
Definition: cost_functions.h:92
armarx::navigation::global_planning::optimization::SmoothOrientationFixedPreCostFunctor
Definition: cost_functions.h:31
armarx::navigation::global_planning::optimization::OrientationOptimizerParams::predefinedRotationDirection
std::optional< RotationDirection > predefinedRotationDirection
Definition: OrientationOptimizer.h:51
armarx::navigation::global_planning::optimization::OrientationOptimizerParams::priorEndWeight
float priorEndWeight
Definition: OrientationOptimizer.h:47
armarx::transform
auto transform(const Container< InputT, Alloc > &in, OutputT(*func)(InputT const &)) -> Container< OutputT, typename std::allocator_traits< Alloc >::template rebind_alloc< OutputT > >
Convenience function (with less typing) to transform a container of type InputT into the same contain...
Definition: algorithm.h:315
armarx::navigation::global_planning::optimization::OrientationPriorCostFunctor
Definition: cost_functions.h:151
F
Definition: ExportDialogControllerTest.cpp:16
armarx::navigation::core::GlobalTrajectory::allConnectedPointsInRange
Indices allConnectedPointsInRange(std::size_t idx, float radius) const
get all points within a certain radius that are directly connected to idx
Definition: Trajectory.cpp:788
armarx::navigation::global_planning::optimization::RotationDirection::Clockwise
@ Clockwise
Logging.h
Trajectory.h
armarx::navigation::global_planning::optimization::OrientationOptimizerParams::smoothnessWeight
float smoothnessWeight
Definition: OrientationOptimizer.h:43
armarx::navigation::core::GlobalTrajectory::points
const std::vector< GlobalTrajectoryPoint > & points() const
Definition: Trajectory.cpp:732
armarx::navigation::global_planning::optimization::OrientationOptimizer::OptimizationResult
Definition: OrientationOptimizer.h:61