Crocoddyl
 
Loading...
Searching...
No Matches
ddp.cpp
1
2// BSD 3-Clause License
3//
4// Copyright (C) 2019-2022, LAAS-CNRS, University of Edinburgh,
5// University of Oxford, Heriot-Watt University
6// Copyright note valid unless otherwise stated in individual files.
7// All rights reserved.
9
10#include "crocoddyl/core/solvers/ddp.hpp"
11
12#include <iostream>
13
14#include "crocoddyl/core/utils/exception.hpp"
15
16namespace crocoddyl {
17
18SolverDDP::SolverDDP(std::shared_ptr<ShootingProblem> problem)
19 : SolverAbstract(problem),
20 reg_incfactor_(10.),
21 reg_decfactor_(10.),
22 reg_min_(1e-9),
23 reg_max_(1e9),
24 cost_try_(0.),
25 th_grad_(1e-12),
26 th_stepdec_(0.5),
27 th_stepinc_(0.01) {
28 allocateData();
29
30 const std::size_t n_alphas = 10;
31 alphas_.resize(n_alphas);
32 for (std::size_t n = 0; n < n_alphas; ++n) {
33 alphas_[n] = 1. / pow(2., static_cast<double>(n));
34 }
35 if (th_stepinc_ < alphas_[n_alphas - 1]) {
36 th_stepinc_ = alphas_[n_alphas - 1];
37 std::cerr << "Warning: th_stepinc has higher value than lowest alpha "
38 "value, set to "
39 << std::to_string(alphas_[n_alphas - 1]) << std::endl;
40 }
41}
42
43SolverDDP::~SolverDDP() {}
44
45bool SolverDDP::solve(const std::vector<Eigen::VectorXd>& init_xs,
46 const std::vector<Eigen::VectorXd>& init_us,
47 const std::size_t maxiter, const bool is_feasible,
48 const double init_reg) {
49 START_PROFILER("SolverDDP::solve");
50 if (problem_->is_updated()) {
51 resizeData();
52 }
53 xs_try_[0] =
54 problem_->get_x0(); // it is needed in case that init_xs[0] is infeasible
55 setCandidate(init_xs, init_us, is_feasible);
56
57 if (std::isnan(init_reg)) {
58 preg_ = reg_min_;
59 dreg_ = reg_min_;
60 } else {
61 preg_ = init_reg;
62 dreg_ = init_reg;
63 }
64 was_feasible_ = false;
65
66 bool recalcDiff = true;
67 for (iter_ = 0; iter_ < maxiter; ++iter_) {
68 while (true) {
69 try {
70 computeDirection(recalcDiff);
71 } catch (std::exception& e) {
72 recalcDiff = false;
73 increaseRegularization();
74 if (preg_ == reg_max_) {
75 return false;
76 } else {
77 continue;
78 }
79 }
80 break;
81 }
82 expectedImprovement();
83
84 // We need to recalculate the derivatives when the step length passes
85 recalcDiff = false;
86 for (std::vector<double>::const_iterator it = alphas_.begin();
87 it != alphas_.end(); ++it) {
88 steplength_ = *it;
89
90 try {
91 dV_ = tryStep(steplength_);
92 } catch (std::exception& e) {
93 continue;
94 }
95 dVexp_ = steplength_ * (d_[0] + 0.5 * steplength_ * d_[1]);
96
97 if (dVexp_ >= 0) { // descend direction
98 if (std::abs(d_[0]) < th_grad_ || !is_feasible_ ||
99 dV_ > th_acceptstep_ * dVexp_) {
100 was_feasible_ = is_feasible_;
101 setCandidate(xs_try_, us_try_, true);
102 cost_ = cost_try_;
103 recalcDiff = true;
104 break;
105 }
106 }
107 }
108
109 if (steplength_ > th_stepdec_) {
110 decreaseRegularization();
111 }
112 if (steplength_ <= th_stepinc_) {
113 increaseRegularization();
114 if (preg_ == reg_max_) {
115 STOP_PROFILER("SolverDDP::solve");
116 return false;
117 }
118 }
119 stoppingCriteria();
120
121 const std::size_t n_callbacks = callbacks_.size();
122 for (std::size_t c = 0; c < n_callbacks; ++c) {
123 CallbackAbstract& callback = *callbacks_[c];
124 callback(*this);
125 }
126
127 if (was_feasible_ && stop_ < th_stop_) {
128 STOP_PROFILER("SolverDDP::solve");
129 return true;
130 }
131 }
132 STOP_PROFILER("SolverDDP::solve");
133 return false;
134}
135
136void SolverDDP::computeDirection(const bool recalcDiff) {
137 START_PROFILER("SolverDDP::computeDirection");
138 if (recalcDiff) {
139 calcDiff();
140 }
141 backwardPass();
142 STOP_PROFILER("SolverDDP::computeDirection");
143}
144
145double SolverDDP::tryStep(const double steplength) {
146 START_PROFILER("SolverDDP::tryStep");
147 forwardPass(steplength);
148 STOP_PROFILER("SolverDDP::tryStep");
149 return cost_ - cost_try_;
150}
151
152double SolverDDP::stoppingCriteria() {
153 // This stopping criteria represents the expected reduction in the value
154 // function. If this reduction is less than a certain threshold, then the
155 // algorithm reaches the local minimum. For more details, see C. Mastalli et
156 // al. "Inverse-dynamics MPC via Nullspace Resolution".
157 stop_ = std::abs(d_[0] + 0.5 * d_[1]);
158 return stop_;
159}
160
161const Eigen::Vector2d& SolverDDP::expectedImprovement() {
162 d_.fill(0);
163 const std::size_t T = this->problem_->get_T();
164 const std::vector<std::shared_ptr<ActionModelAbstract> >& models =
165 problem_->get_runningModels();
166 for (std::size_t t = 0; t < T; ++t) {
167 const std::size_t nu = models[t]->get_nu();
168 if (nu != 0) {
169 d_[0] += Qu_[t].dot(k_[t]);
170 d_[1] -= k_[t].dot(Quuk_[t]);
171 }
172 }
173 return d_;
174}
175
176void SolverDDP::resizeData() {
177 START_PROFILER("SolverDDP::resizeData");
178 SolverAbstract::resizeData();
179
180 const std::size_t T = problem_->get_T();
181 const std::size_t ndx = problem_->get_ndx();
182 const std::vector<std::shared_ptr<ActionModelAbstract> >& models =
183 problem_->get_runningModels();
184 for (std::size_t t = 0; t < T; ++t) {
185 const std::shared_ptr<ActionModelAbstract>& model = models[t];
186 const std::size_t nu = model->get_nu();
187 Qxu_[t].conservativeResize(ndx, nu);
188 Quu_[t].conservativeResize(nu, nu);
189 Qu_[t].conservativeResize(nu);
190 K_[t].conservativeResize(nu, ndx);
191 k_[t].conservativeResize(nu);
192 us_try_[t].conservativeResize(nu);
193 FuTVxx_p_[t].conservativeResize(nu, ndx);
194 Quuk_[t].conservativeResize(nu);
195 if (nu != 0) {
196 FuTVxx_p_[t].setZero();
197 }
198 }
199 STOP_PROFILER("SolverDDP::resizeData");
200}
201
202double SolverDDP::calcDiff() {
203 START_PROFILER("SolverDDP::calcDiff");
204 if (iter_ == 0) {
205 problem_->calc(xs_, us_);
206 }
207 cost_ = problem_->calcDiff(xs_, us_);
208
209 ffeas_ = computeDynamicFeasibility();
210 gfeas_ = computeInequalityFeasibility();
211 hfeas_ = computeEqualityFeasibility();
212 STOP_PROFILER("SolverDDP::calcDiff");
213 return cost_;
214}
215
216void SolverDDP::backwardPass() {
217 START_PROFILER("SolverDDP::backwardPass");
218 const std::shared_ptr<ActionDataAbstract>& d_T = problem_->get_terminalData();
219 Vxx_.back() = d_T->Lxx;
220 Vx_.back() = d_T->Lx;
221
222 if (!std::isnan(preg_)) {
223 Vxx_.back().diagonal().array() += preg_;
224 }
225
226 if (!is_feasible_) {
227 Vx_.back().noalias() += Vxx_.back() * fs_.back();
228 }
229 const std::vector<std::shared_ptr<ActionModelAbstract> >& models =
230 problem_->get_runningModels();
231 const std::vector<std::shared_ptr<ActionDataAbstract> >& datas =
232 problem_->get_runningDatas();
233 for (int t = static_cast<int>(problem_->get_T()) - 1; t >= 0; --t) {
234 const std::shared_ptr<ActionModelAbstract>& m = models[t];
235 const std::shared_ptr<ActionDataAbstract>& d = datas[t];
236
237 // Compute the linear-quadratic approximation of the control Hamiltonian
238 // function
239 computeActionValueFunction(t, m, d);
240
241 // Compute the feedforward and feedback gains
242 computeGains(t);
243
244 // Compute the linear-quadratic approximation of the Value function
245 computeValueFunction(t, m);
246
247 if (raiseIfNaN(Vx_[t].lpNorm<Eigen::Infinity>())) {
248 throw_pretty("backward_error");
249 }
250 if (raiseIfNaN(Vxx_[t].lpNorm<Eigen::Infinity>())) {
251 throw_pretty("backward_error");
252 }
253 }
254 STOP_PROFILER("SolverDDP::backwardPass");
255}
256
257void SolverDDP::forwardPass(const double steplength) {
258 if (steplength > 1. || steplength < 0.) {
259 throw_pretty("Invalid argument: "
260 << "invalid step length, value is between 0. to 1.");
261 }
262 START_PROFILER("SolverDDP::forwardPass");
263 cost_try_ = 0.;
264 const std::size_t T = problem_->get_T();
265 const std::vector<std::shared_ptr<ActionModelAbstract> >& models =
266 problem_->get_runningModels();
267 const std::vector<std::shared_ptr<ActionDataAbstract> >& datas =
268 problem_->get_runningDatas();
269 for (std::size_t t = 0; t < T; ++t) {
270 const std::shared_ptr<ActionModelAbstract>& m = models[t];
271 const std::shared_ptr<ActionDataAbstract>& d = datas[t];
272
273 m->get_state()->diff(xs_[t], xs_try_[t], dx_[t]);
274 if (m->get_nu() != 0) {
275 us_try_[t].noalias() = us_[t];
276 us_try_[t].noalias() -= k_[t] * steplength;
277 us_try_[t].noalias() -= K_[t] * dx_[t];
278 m->calc(d, xs_try_[t], us_try_[t]);
279 } else {
280 m->calc(d, xs_try_[t]);
281 }
282 xs_try_[t + 1] = d->xnext;
283 cost_try_ += d->cost;
284
285 if (raiseIfNaN(cost_try_)) {
286 STOP_PROFILER("SolverDDP::forwardPass");
287 throw_pretty("forward_error");
288 }
289 if (raiseIfNaN(xs_try_[t + 1].lpNorm<Eigen::Infinity>())) {
290 STOP_PROFILER("SolverDDP::forwardPass");
291 throw_pretty("forward_error");
292 }
293 }
294
295 const std::shared_ptr<ActionModelAbstract>& m = problem_->get_terminalModel();
296 const std::shared_ptr<ActionDataAbstract>& d = problem_->get_terminalData();
297 m->calc(d, xs_try_.back());
298 cost_try_ += d->cost;
299
300 if (raiseIfNaN(cost_try_)) {
301 STOP_PROFILER("SolverDDP::forwardPass");
302 throw_pretty("forward_error");
303 }
304 STOP_PROFILER("SolverDDP::forwardPass");
305}
306
307void SolverDDP::computeActionValueFunction(
308 const std::size_t t, const std::shared_ptr<ActionModelAbstract>& model,
309 const std::shared_ptr<ActionDataAbstract>& data) {
310 assert_pretty(t < problem_->get_T(),
311 "Invalid argument: t should be between 0 and " +
312 std::to_string(problem_->get_T()););
313 const std::size_t nu = model->get_nu();
314 const Eigen::MatrixXd& Vxx_p = Vxx_[t + 1];
315 const Eigen::VectorXd& Vx_p = Vx_[t + 1];
316
317 FxTVxx_p_.noalias() = data->Fx.transpose() * Vxx_p;
318 START_PROFILER("SolverDDP::Qx");
319 Qx_[t] = data->Lx;
320 Qx_[t].noalias() += data->Fx.transpose() * Vx_p;
321 STOP_PROFILER("SolverDDP::Qx");
322 START_PROFILER("SolverDDP::Qxx");
323 Qxx_[t] = data->Lxx;
324 Qxx_[t].noalias() += FxTVxx_p_ * data->Fx;
325 STOP_PROFILER("SolverDDP::Qxx");
326 if (nu != 0) {
327 FuTVxx_p_[t].noalias() = data->Fu.transpose() * Vxx_p;
328 START_PROFILER("SolverDDP::Qu");
329 Qu_[t] = data->Lu;
330 Qu_[t].noalias() += data->Fu.transpose() * Vx_p;
331 STOP_PROFILER("SolverDDP::Qu");
332 START_PROFILER("SolverDDP::Quu");
333 Quu_[t] = data->Luu;
334 Quu_[t].noalias() += FuTVxx_p_[t] * data->Fu;
335 STOP_PROFILER("SolverDDP::Quu");
336 START_PROFILER("SolverDDP::Qxu");
337 Qxu_[t] = data->Lxu;
338 Qxu_[t].noalias() += FxTVxx_p_ * data->Fu;
339 STOP_PROFILER("SolverDDP::Qxu");
340 if (!std::isnan(preg_)) {
341 Quu_[t].diagonal().array() += preg_;
342 }
343 }
344}
345
346void SolverDDP::computeValueFunction(
347 const std::size_t t, const std::shared_ptr<ActionModelAbstract>& model) {
348 assert_pretty(t < problem_->get_T(),
349 "Invalid argument: t should be between 0 and " +
350 std::to_string(problem_->get_T()););
351 const std::size_t nu = model->get_nu();
352 Vx_[t] = Qx_[t];
353 Vxx_[t] = Qxx_[t];
354 if (nu != 0) {
355 START_PROFILER("SolverDDP::Vx");
356 Quuk_[t].noalias() = Quu_[t] * k_[t];
357 Vx_[t].noalias() -= K_[t].transpose() * Qu_[t];
358 STOP_PROFILER("SolverDDP::Vx");
359 START_PROFILER("SolverDDP::Vxx");
360 Vxx_[t].noalias() -= Qxu_[t] * K_[t];
361 STOP_PROFILER("SolverDDP::Vxx");
362 }
363 Vxx_tmp_ = 0.5 * (Vxx_[t] + Vxx_[t].transpose());
364 Vxx_[t] = Vxx_tmp_;
365
366 if (!std::isnan(preg_)) {
367 Vxx_[t].diagonal().array() += preg_;
368 }
369
370 // Compute and store the Vx gradient at end of the interval (rollout state)
371 if (!is_feasible_) {
372 Vx_[t].noalias() += Vxx_[t] * fs_[t];
373 }
374}
375
376void SolverDDP::computeGains(const std::size_t t) {
377 assert_pretty(t < problem_->get_T(),
378 "Invalid argument: t should be between 0 and " +
379 std::to_string(problem_->get_T()));
380 START_PROFILER("SolverDDP::computeGains");
381 const std::size_t nu = problem_->get_runningModels()[t]->get_nu();
382 if (nu > 0) {
383 START_PROFILER("SolverDDP::Quu_inv");
384 Quu_llt_[t].compute(Quu_[t]);
385 STOP_PROFILER("SolverDDP::Quu_inv");
386 const Eigen::ComputationInfo& info = Quu_llt_[t].info();
387 if (info != Eigen::Success) {
388 STOP_PROFILER("SolverDDP::computeGains");
389 throw_pretty("backward_error");
390 }
391 K_[t] = Qxu_[t].transpose();
392
393 START_PROFILER("SolverDDP::Quu_inv_Qux");
394 Quu_llt_[t].solveInPlace(K_[t]);
395 STOP_PROFILER("SolverDDP::Quu_inv_Qux");
396 k_[t] = Qu_[t];
397 Quu_llt_[t].solveInPlace(k_[t]);
398 }
399 STOP_PROFILER("SolverDDP::computeGains");
400}
401
402void SolverDDP::increaseRegularization() {
403 preg_ *= reg_incfactor_;
404 if (preg_ > reg_max_) {
405 preg_ = reg_max_;
406 }
407 dreg_ = preg_;
408}
409
410void SolverDDP::decreaseRegularization() {
411 preg_ /= reg_decfactor_;
412 if (preg_ < reg_min_) {
413 preg_ = reg_min_;
414 }
415 dreg_ = preg_;
416}
417
418void SolverDDP::allocateData() {
419 const std::size_t T = problem_->get_T();
420 Vxx_.resize(T + 1);
421 Vx_.resize(T + 1);
422 Qxx_.resize(T);
423 Qxu_.resize(T);
424 Quu_.resize(T);
425 Qx_.resize(T);
426 Qu_.resize(T);
427 K_.resize(T);
428 k_.resize(T);
429
430 xs_try_.resize(T + 1);
431 us_try_.resize(T);
432 dx_.resize(T);
433
434 FuTVxx_p_.resize(T);
435 Quu_llt_.resize(T);
436 Quuk_.resize(T);
437
438 const std::size_t ndx = problem_->get_ndx();
439 const std::vector<std::shared_ptr<ActionModelAbstract> >& models =
440 problem_->get_runningModels();
441 for (std::size_t t = 0; t < T; ++t) {
442 const std::shared_ptr<ActionModelAbstract>& model = models[t];
443 const std::size_t nu = model->get_nu();
444 Vxx_[t] = Eigen::MatrixXd::Zero(ndx, ndx);
445 Vx_[t] = Eigen::VectorXd::Zero(ndx);
446 Qxx_[t] = Eigen::MatrixXd::Zero(ndx, ndx);
447 Qxu_[t] = Eigen::MatrixXd::Zero(ndx, nu);
448 Quu_[t] = Eigen::MatrixXd::Zero(nu, nu);
449 Qx_[t] = Eigen::VectorXd::Zero(ndx);
450 Qu_[t] = Eigen::VectorXd::Zero(nu);
451 K_[t] = MatrixXdRowMajor::Zero(nu, ndx);
452 k_[t] = Eigen::VectorXd::Zero(nu);
453
454 if (t == 0) {
455 xs_try_[t] = problem_->get_x0();
456 } else {
457 xs_try_[t] = model->get_state()->zero();
458 }
459 us_try_[t] = Eigen::VectorXd::Zero(nu);
460 dx_[t] = Eigen::VectorXd::Zero(ndx);
461
462 FuTVxx_p_[t] = MatrixXdRowMajor::Zero(nu, ndx);
463 Quu_llt_[t] = Eigen::LLT<Eigen::MatrixXd>(nu);
464 Quuk_[t] = Eigen::VectorXd(nu);
465 }
466 Vxx_.back() = Eigen::MatrixXd::Zero(ndx, ndx);
467 Vxx_tmp_ = Eigen::MatrixXd::Zero(ndx, ndx);
468 Vx_.back() = Eigen::VectorXd::Zero(ndx);
469 xs_try_.back() = problem_->get_terminalModel()->get_state()->zero();
470
471 FxTVxx_p_ = MatrixXdRowMajor::Zero(ndx, ndx);
472 fTVxx_p_ = Eigen::VectorXd::Zero(ndx);
473}
474
475double SolverDDP::get_reg_incfactor() const { return reg_incfactor_; }
476
477double SolverDDP::get_reg_decfactor() const { return reg_decfactor_; }
478
479double SolverDDP::get_regfactor() const { return reg_incfactor_; }
480
481double SolverDDP::get_reg_min() const { return reg_min_; }
482
483double SolverDDP::get_regmin() const { return reg_min_; }
484
485double SolverDDP::get_reg_max() const { return reg_max_; }
486
487double SolverDDP::get_regmax() const { return reg_max_; }
488
489const std::vector<double>& SolverDDP::get_alphas() const { return alphas_; }
490
491double SolverDDP::get_th_stepdec() const { return th_stepdec_; }
492
493double SolverDDP::get_th_stepinc() const { return th_stepinc_; }
494
495double SolverDDP::get_th_grad() const { return th_grad_; }
496
497const std::vector<Eigen::MatrixXd>& SolverDDP::get_Vxx() const { return Vxx_; }
498
499const std::vector<Eigen::VectorXd>& SolverDDP::get_Vx() const { return Vx_; }
500
501const std::vector<Eigen::MatrixXd>& SolverDDP::get_Qxx() const { return Qxx_; }
502
503const std::vector<Eigen::MatrixXd>& SolverDDP::get_Qxu() const { return Qxu_; }
504
505const std::vector<Eigen::MatrixXd>& SolverDDP::get_Quu() const { return Quu_; }
506
507const std::vector<Eigen::VectorXd>& SolverDDP::get_Qx() const { return Qx_; }
508
509const std::vector<Eigen::VectorXd>& SolverDDP::get_Qu() const { return Qu_; }
510
511const std::vector<typename MathBaseTpl<double>::MatrixXsRowMajor>&
512SolverDDP::get_K() const {
513 return K_;
514}
515
516const std::vector<Eigen::VectorXd>& SolverDDP::get_k() const { return k_; }
517
518void SolverDDP::set_reg_incfactor(const double regfactor) {
519 if (regfactor <= 1.) {
520 throw_pretty(
521 "Invalid argument: " << "reg_incfactor value is higher than 1.");
522 }
523 reg_incfactor_ = regfactor;
524}
525
526void SolverDDP::set_reg_decfactor(const double regfactor) {
527 if (regfactor <= 1.) {
528 throw_pretty(
529 "Invalid argument: " << "reg_decfactor value is higher than 1.");
530 }
531 reg_decfactor_ = regfactor;
532}
533
534void SolverDDP::set_regfactor(const double regfactor) {
535 if (regfactor <= 1.) {
536 throw_pretty("Invalid argument: " << "regfactor value is higher than 1.");
537 }
538 set_reg_incfactor(regfactor);
539 set_reg_decfactor(regfactor);
540}
541
542void SolverDDP::set_reg_min(const double regmin) {
543 if (0. > regmin) {
544 throw_pretty("Invalid argument: " << "regmin value has to be positive.");
545 }
546 reg_min_ = regmin;
547}
548
549void SolverDDP::set_regmin(const double regmin) {
550 if (0. > regmin) {
551 throw_pretty("Invalid argument: " << "regmin value has to be positive.");
552 }
553 reg_min_ = regmin;
554}
555
556void SolverDDP::set_reg_max(const double regmax) {
557 if (0. > regmax) {
558 throw_pretty("Invalid argument: " << "regmax value has to be positive.");
559 }
560 reg_max_ = regmax;
561}
562
563void SolverDDP::set_regmax(const double regmax) {
564 if (0. > regmax) {
565 throw_pretty("Invalid argument: " << "regmax value has to be positive.");
566 }
567 reg_max_ = regmax;
568}
569
570void SolverDDP::set_alphas(const std::vector<double>& alphas) {
571 double prev_alpha = alphas[0];
572 if (prev_alpha != 1.) {
573 std::cerr << "Warning: alpha[0] should be 1" << std::endl;
574 }
575 for (std::size_t i = 1; i < alphas.size(); ++i) {
576 double alpha = alphas[i];
577 if (0. >= alpha) {
578 throw_pretty("Invalid argument: " << "alpha values has to be positive.");
579 }
580 if (alpha >= prev_alpha) {
581 throw_pretty(
582 "Invalid argument: " << "alpha values are monotonously decreasing.");
583 }
584 prev_alpha = alpha;
585 }
586 alphas_ = alphas;
587}
588
589void SolverDDP::set_th_stepdec(const double th_stepdec) {
590 if (0. >= th_stepdec || th_stepdec > 1.) {
591 throw_pretty(
592 "Invalid argument: " << "th_stepdec value should between 0 and 1.");
593 }
594 th_stepdec_ = th_stepdec;
595}
596
597void SolverDDP::set_th_stepinc(const double th_stepinc) {
598 if (0. >= th_stepinc || th_stepinc > 1.) {
599 throw_pretty(
600 "Invalid argument: " << "th_stepinc value should between 0 and 1.");
601 }
602 th_stepinc_ = th_stepinc;
603}
604
605void SolverDDP::set_th_grad(const double th_grad) {
606 if (0. > th_grad) {
607 throw_pretty("Invalid argument: " << "th_grad value has to be positive.");
608 }
609 th_grad_ = th_grad;
610}
611
612} // namespace crocoddyl