ilqr_solver.cpp
Go to the documentation of this file.
1 //
2 // Copyright (c) 2019, University of Edinburgh
3 // All rights reserved.
4 //
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions are met:
7 //
8 // * Redistributions of source code must retain the above copyright notice,
9 // this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above copyright
11 // notice, this list of conditions and the following disclaimer in the
12 // documentation and/or other materials provided with the distribution.
13 // * Neither the name of nor the names of its contributors may be used to
14 // endorse or promote products derived from this software without specific
15 // prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 // POSSIBILITY OF SUCH DAMAGE.
28 //
29 
31 
33 
34 namespace exotica
35 {
36 void ILQRSolver::SpecifyProblem(PlanningProblemPtr pointer)
37 {
38  if (pointer->type() != "exotica::DynamicTimeIndexedShootingProblem")
39  {
40  ThrowNamed("This ILQRSolver can't solve problem of type '" << pointer->type() << "'!");
41  }
42  MotionSolver::SpecifyProblem(pointer);
43  prob_ = std::static_pointer_cast<DynamicTimeIndexedShootingProblem>(pointer);
44  dynamics_solver_ = prob_->GetScene()->GetDynamicsSolver();
45 }
46 
47 void ILQRSolver::BackwardPass()
48 {
49  constexpr double min_clamp_ = -1e10;
50  constexpr double max_clamp_ = 1e10;
51  const int T = prob_->get_T();
52  const double dt = dynamics_solver_->get_dt();
53 
54  const Eigen::MatrixXd& Qf = prob_->get_Qf();
55  const Eigen::MatrixXd R = dt * prob_->get_R();
56  const Eigen::MatrixXd& X_star = prob_->get_X_star();
57 
58  // eq. 18
59  vk_gains_[T - 1] = Qf * dynamics_solver_->StateDelta(prob_->get_X(T - 1), X_star.col(T - 1));
60  vk_gains_[T - 1] = vk_gains_[T - 1].unaryExpr([min_clamp_, max_clamp_](double x) -> double {
61  return std::min(std::max(x, min_clamp_), max_clamp_);
62  });
63 
64  Eigen::MatrixXd Sk = Qf;
65  Sk = Sk.unaryExpr([min_clamp_, max_clamp_](double x) -> double {
66  return std::min(std::max(x, min_clamp_), max_clamp_);
67  });
68  vk_gains_[T - 1] = vk_gains_[T - 1].unaryExpr([min_clamp_, max_clamp_](double x) -> double {
69  return std::min(std::max(x, min_clamp_), max_clamp_);
70  });
71 
72  for (int t = T - 2; t >= 0; t--)
73  {
74  Eigen::VectorXd x = prob_->get_X(t), u = prob_->get_U(t);
75  dynamics_solver_->ComputeDerivatives(x, u);
76  Eigen::MatrixXd Ak = dynamics_solver_->get_Fx(), Bk = dynamics_solver_->get_Fu(),
77  Q = dt * prob_->get_Q(t);
78 
79  // this inverse is common for all factors
80  // TODO: use LLT
81  const Eigen::MatrixXd _inv =
82  (Eigen::MatrixXd::Identity(R.rows(), R.cols()) * parameters_.RegularizationRate + R + Bk.transpose() * Sk * Bk).inverse();
83 
84  Kv_gains_[t] = _inv * Bk.transpose();
85  K_gains_[t] = _inv * Bk.transpose() * Sk * Ak;
86  Ku_gains_[t] = _inv * R;
87  Sk = Ak.transpose() * Sk * (Ak - Bk * K_gains_[t]) + Q;
88 
89  vk_gains_[t] = ((Ak - Bk * K_gains_[t]).transpose() * vk_gains_[t + 1]) -
90  (K_gains_[t].transpose() * R * u) + (Q * x);
91 
92  // fix for large values
93  Sk = Sk.unaryExpr([min_clamp_, max_clamp_](double x) -> double {
94  return std::min(std::max(x, min_clamp_), max_clamp_);
95  });
96  vk_gains_[t] = vk_gains_[t].unaryExpr([min_clamp_, max_clamp_](double x) -> double {
97  return std::min(std::max(x, min_clamp_), max_clamp_);
98  });
99  }
100 }
101 
102 double ILQRSolver::ForwardPass(const double alpha, Eigen::MatrixXdRefConst ref_x, Eigen::MatrixXdRefConst ref_u)
103 {
104  double cost = 0;
105  const int T = prob_->get_T();
106  const Eigen::MatrixXd control_limits = dynamics_solver_->get_control_limits();
107  const double dt = dynamics_solver_->get_dt();
108 
109  Eigen::VectorXd delta_uk(dynamics_solver_->get_num_controls()), u(dynamics_solver_->get_num_controls());
110  for (int t = 0; t < T - 1; ++t)
111  {
112  u = ref_u.col(t);
113  // eq. 12
114  delta_uk = -Ku_gains_[t] * u - Kv_gains_[t] * vk_gains_[t + 1] -
115  K_gains_[t] * dynamics_solver_->StateDelta(prob_->get_X(t), ref_x.col(t));
116 
117  u.noalias() += alpha * delta_uk;
118  // clamp controls
119  u = u.cwiseMax(control_limits.col(0)).cwiseMin(control_limits.col(1));
120 
121  prob_->Update(u, t);
122  cost += dt * (prob_->GetControlCost(t) + prob_->GetStateCost(t));
123  }
124 
125  // add terminal cost
126  cost += prob_->GetStateCost(T - 1);
127  return cost;
128 }
129 
130 void ILQRSolver::Solve(Eigen::MatrixXd& solution)
131 {
132  if (!prob_) ThrowNamed("Solver has not been initialized!");
133  Timer planning_timer, backward_pass_timer, line_search_timer;
134 
135  const int T = prob_->get_T();
136  const int NU = prob_->GetScene()->get_num_controls();
137  const int NX = prob_->GetScene()->get_num_state();
138  const double dt = dynamics_solver_->get_dt();
139  prob_->ResetCostEvolution(GetNumberOfMaxIterations() + 1);
140  prob_->PreUpdate();
141 
142  double cost = 0;
143  for (int t = 0; t < T - 1; ++t)
144  {
145  prob_->Update(prob_->get_U(t), t);
146 
147  cost += dt * (prob_->GetControlCost(t) + prob_->GetStateCost(t));
148  }
149 
150  // add terminal cost
151  cost += prob_->GetStateCost(T - 1);
152  prob_->SetCostEvolution(0, cost);
153 
154  // initialize Gain matrices
155  K_gains_.assign(T, Eigen::MatrixXd(NU, NX));
156  Ku_gains_.assign(T, Eigen::MatrixXd(NU, NU));
157  Kv_gains_.assign(T, Eigen::MatrixXd(NU, NX));
158  vk_gains_.assign(T, Eigen::MatrixXd(NX, 1));
159 
160  // all of the below are not pointers, since we want to copy over
161  // solutions across iterations
162  Eigen::MatrixXd new_U = prob_->get_U();
163  solution.resize(T - 1, NU);
164 
165  if (debug_) HIGHLIGHT_NAMED("ILQRSolver", "Running ILQR solver for max " << GetNumberOfMaxIterations() << " iterations");
166 
167  double cost_prev = cost;
168  int last_best_iteration = 0;
169 
170  Eigen::VectorXd alpha_space = Eigen::VectorXd::LinSpaced(11, 0.0, -3.0);
171  for (int ai = 0; ai < alpha_space.size(); ++ai)
172  {
173  alpha_space(ai) = std::pow(10.0, alpha_space(ai));
174  }
175 
176  double time_taken_backward_pass = 0.0, time_taken_forward_pass = 0.0;
177  for (int iteration = 1; iteration <= GetNumberOfMaxIterations(); ++iteration)
178  {
179  // Check whether user interrupted (Ctrl+C)
180  if (Server::IsRos() && !ros::ok())
181  {
182  if (debug_) HIGHLIGHT("Solving cancelled by user");
183  prob_->termination_criterion = TerminationCriterion::UserDefined;
184  break;
185  }
186 
187  // Backwards pass computes the gains
188  backward_pass_timer.Reset();
189  BackwardPass();
190  time_taken_backward_pass = backward_pass_timer.GetDuration();
191 
192  // Forward pass to compute new control trajectory
193  line_search_timer.Reset();
194 
195  // Make a copy to compare against
196  Eigen::MatrixXd ref_x = prob_->get_X(),
197  ref_u = prob_->get_U();
198 
199  // Perform a backtracking line search to find the best rate
200  double best_alpha = 0;
201  for (int ai = 0; ai < alpha_space.size(); ++ai)
202  {
203  const double& alpha = alpha_space(ai);
204  double rollout_cost = ForwardPass(alpha, ref_x, ref_u);
205 
206  if (rollout_cost < cost_prev && !std::isnan(rollout_cost))
207  {
208  cost = rollout_cost;
209  new_U = prob_->get_U();
210  best_alpha = alpha;
211  break;
212  }
213  }
214 
215  // Finiteness checks
216  if (!new_U.allFinite() || !std::isfinite(cost))
217  {
218  prob_->termination_criterion = TerminationCriterion::Divergence;
219  WARNING_NAMED("ILQRSolver", "Diverged: Controls or cost are not finite.");
220  return;
221  }
222 
223  time_taken_forward_pass = line_search_timer.GetDuration();
224 
225  if (debug_)
226  {
227  HIGHLIGHT_NAMED("ILQRSolver", "Iteration " << iteration << std::setprecision(3) << ":\tBackward pass: " << time_taken_backward_pass << " s\tForward pass: " << time_taken_forward_pass << " s\tCost: " << cost << "\talpha: " << best_alpha);
228  }
229 
230  //
231  // Stopping criteria checks
232  //
233 
234  // Relative function tolerance
235  // (f_t-1 - f_t) <= functionTolerance * max(1, abs(f_t))
236  if ((cost_prev - cost) < parameters_.FunctionTolerance * std::max(1.0, std::abs(cost)))
237  {
238  // Function tolerance patience check
239  if (parameters_.FunctionTolerancePatience > 0)
240  {
241  if (iteration - last_best_iteration > parameters_.FunctionTolerancePatience)
242  {
243  if (debug_) HIGHLIGHT_NAMED("ILQRSolver", "Early stopping criterion reached (" << cost << " < " << cost_prev << "). Time: " << planning_timer.GetDuration());
244  prob_->termination_criterion = TerminationCriterion::FunctionTolerance;
245  break;
246  }
247  }
248  else
249  {
250  if (debug_) HIGHLIGHT_NAMED("ILQRSolver", "Function tolerance reached (" << cost << " < " << cost_prev << "). Time: " << planning_timer.GetDuration());
251  prob_->termination_criterion = TerminationCriterion::FunctionTolerance;
252  break;
253  }
254  }
255  else
256  {
257  // Reset function tolerance patience
258  last_best_iteration = iteration;
259  }
260 
261  // If better than previous iteration, copy solutions for next iteration
262  if (cost < cost_prev)
263  {
264  cost_prev = cost;
265  // last_best_iteration = iteration;
266  best_ref_x_ = ref_x;
267  best_ref_u_ = ref_u;
268  }
269 
270  // if (iteration - last_best_iteration > parameters_.FunctionTolerancePatience)
271  // {
272  // if (debug_) HIGHLIGHT_NAMED("ILQRSolver", "Early stopping criterion reached. Time: " << planning_timer.GetDuration());
273  // prob_->termination_criterion = TerminationCriterion::FunctionTolerance;
274  // break;
275  // }
276 
277  // if (last_cost - current_cost < parameters_.FunctionTolerance && last_cost - current_cost > 0)
278  // {
279  // if (debug_) HIGHLIGHT_NAMED("ILQRSolver", "Function tolerance reached. Time: " << planning_timer.GetDuration());
280  // prob_->termination_criterion = TerminationCriterion::FunctionTolerance;
281  // break;
282  // }
283 
284  if (debug_ && iteration == parameters_.MaxIterations)
285  {
286  HIGHLIGHT_NAMED("ILQRSolver", "Max iterations reached. Time: " << planning_timer.GetDuration());
287  prob_->termination_criterion = TerminationCriterion::IterationLimit;
288  }
289 
290  // Roll-out
291  for (int t = 0; t < T - 1; ++t)
292  prob_->Update(new_U.col(t), t);
293 
294  // Set cost evolution
295  prob_->SetCostEvolution(iteration, cost);
296  }
297 
298  // store the best solution found over all iterations
299  for (int t = 0; t < T - 1; ++t)
300  {
301  solution.row(t) = new_U.col(t).transpose();
302  prob_->Update(new_U.col(t), t);
303  }
304 
305  planning_time_ = planning_timer.GetDuration();
306 }
307 
308 Eigen::VectorXd ILQRSolver::GetFeedbackControl(Eigen::VectorXdRefConst x, int t) const
309 {
310  const Eigen::MatrixXd control_limits = dynamics_solver_->get_control_limits();
311 
312  Eigen::VectorXd delta_uk = -Ku_gains_[t] * best_ref_u_.col(t) - Kv_gains_[t] * vk_gains_[t + 1] -
313  K_gains_[t] * dynamics_solver_->StateDelta(x, best_ref_x_.col(t));
314 
315  Eigen::VectorXd u = best_ref_u_.col(t) + delta_uk;
316  return u.cwiseMax(control_limits.col(0)).cwiseMin(control_limits.col(1));
317 }
318 
319 } // namespace exotica
#define HIGHLIGHT(x)
#define WARNING_NAMED(name, x)
#define REGISTER_MOTIONSOLVER_TYPE(TYPE, DERIV)
const Eigen::Ref< const Eigen::MatrixXd > & MatrixXdRefConst
#define ThrowNamed(m)
ROSCPP_DECL bool ok()
const Eigen::Ref< const Eigen::VectorXd > & VectorXdRefConst
#define HIGHLIGHT_NAMED(name, x)
double GetDuration() const
std::shared_ptr< PlanningProblem > PlanningProblemPtr
ScenePtr GetScene() const
double x


exotica_ilqr_solver
Author(s): Traiko Dinev
autogenerated on Sat Apr 10 2021 02:36:33