12 double calcDuration(
const std::chrono::time_point<Clock> & start_time,
const std::chrono::time_point<Clock> & end_time)
14 return 1e3 * std::chrono::duration_cast<std::chrono::duration<double>>(end_time - start_time).count();
20 template<
int StateDim,
int InputDim>
26 template<
int StateDim,
int InputDim>
29 const std::vector<InputDimVector> & initial_u_list)
33 auto start_time = std::chrono::system_clock::now();
36 current_t_ = current_t;
37 lambda_ = config_.initial_lambda;
38 dlambda_ = config_.initial_dlambda;
41 if(
static_cast<int>(initial_u_list.size()) != config_.horizon_steps)
43 throw std::invalid_argument(
"initial_u_list length should be " + std::to_string(config_.horizon_steps) +
" but "
44 + std::to_string(initial_u_list.size()) +
".");
46 if constexpr(InputDim == Eigen::Dynamic)
48 for(
int i = 0; i < config_.horizon_steps; i++)
50 double t = current_t_ + i * problem_->dt();
51 if(initial_u_list[i].size() != problem_->inputDim(t))
53 throw std::runtime_error(
"initial_u dimension should be " + std::to_string(problem_->inputDim(t)) +
" but "
54 + std::to_string(initial_u_list[i].size()) +
". i: " + std::to_string(i)
55 +
", time: " + std::to_string(t));
61 candidate_control_data_.x_list.resize(config_.horizon_steps + 1);
62 candidate_control_data_.u_list.resize(config_.horizon_steps);
63 candidate_control_data_.cost_list.resize(config_.horizon_steps + 1);
64 int outer_dim = config_.use_state_eq_second_derivative ? problem_->stateDim() : 0;
65 if constexpr(InputDim == Eigen::Dynamic)
67 derivative_list_.clear();
68 for(
int i = 0; i < config_.horizon_steps; i++)
70 double t = current_t_ + i * problem_->dt();
71 derivative_list_.push_back(
Derivative(problem_->stateDim(), problem_->inputDim(t), outer_dim));
77 derivative_list_.resize(config_.horizon_steps,
Derivative(problem_->stateDim(), problem_->inputDim(), outer_dim));
79 k_list_.resize(config_.horizon_steps);
80 K_list_.resize(config_.horizon_steps);
83 control_data_.u_list = initial_u_list;
84 if(config_.with_input_constraint)
87 for(
int i = 0; i < config_.horizon_steps; ++i)
89 const double t = current_t_ + i * problem_->dt();
90 const auto & u_limits = input_limits_func_(t);
91 control_data_.u_list[i] = control_data_.u_list[i].cwiseMax(u_limits[0]).cwiseMin(u_limits[1]);
94 control_data_.x_list.resize(config_.horizon_steps + 1);
95 control_data_.cost_list.resize(config_.horizon_steps + 1);
96 control_data_.x_list[0] = current_x;
97 for(
int i = 0; i < config_.horizon_steps; i++)
99 double t = current_t_ + i * problem_->dt();
100 control_data_.x_list[i + 1] = problem_->stateEq(t, control_data_.x_list[i], control_data_.u_list[i]);
101 control_data_.cost_list[i] = problem_->runningCost(t, control_data_.x_list[i], control_data_.u_list[i]);
103 double terminal_t = current_t_ + config_.horizon_steps * problem_->dt();
104 control_data_.cost_list[config_.horizon_steps] =
105 problem_->terminalCost(terminal_t, control_data_.x_list[config_.horizon_steps]);
108 trace_data_list_.clear();
110 initial_trace_data.
iter = 0;
111 initial_trace_data.
cost = control_data_.cost_list.sum();
112 initial_trace_data.
lambda = lambda_;
113 initial_trace_data.
dlambda = dlambda_;
114 trace_data_list_.push_back(initial_trace_data);
116 if(config_.print_level >= 3)
118 std::cout <<
"[DDP] Initial cost: " << control_data_.cost_list.sum() << std::endl;
121 auto setup_time = std::chrono::system_clock::now();
122 computation_duration_.setup = calcDuration(start_time, setup_time);
126 for(
int iter = 1; iter <= config_.max_iter; iter++)
128 retval = procOnce(iter);
135 if(config_.print_level >= 3)
137 std::cout <<
"[DDP] Final cost: " << control_data_.cost_list.sum() << std::endl;
140 auto end_time = std::chrono::system_clock::now();
141 computation_duration_.opt = calcDuration(setup_time, end_time);
142 computation_duration_.solve = calcDuration(start_time, end_time);
144 if(config_.print_level >= 3)
146 std::cout <<
"[DDP] Setup duration: " << computation_duration_.setup
147 <<
" [ms], optimization duration: " << computation_duration_.opt <<
" [ms]." << std::endl;
153 template<
int StateDim,
int InputDim>
156 if(config_.print_level >= 3)
158 std::cout <<
"[DDP] Start iteration " << iter << std::endl;
163 auto & trace_data = trace_data_list_.back();
164 trace_data.iter = iter;
168 auto start_time = std::chrono::system_clock::now();
170 for(
int i = 0; i < config_.horizon_steps; i++)
172 auto & derivative = derivative_list_[i];
174 double t = current_t_ + i * problem_->dt();
177 if(config_.use_state_eq_second_derivative)
179 problem_->calcStateEqDeriv(t, x, u, derivative.Fx, derivative.Fu, derivative.Fxx, derivative.Fuu,
184 problem_->calcStateEqDeriv(t, x, u, derivative.Fx, derivative.Fu);
186 problem_->calcRunningCostDeriv(t, x, u, derivative.Lx, derivative.Lu, derivative.Lxx, derivative.Luu,
189 double terminal_t = current_t_ + config_.horizon_steps * problem_->dt();
190 problem_->calcTerminalCostDeriv(terminal_t, control_data_.x_list[config_.horizon_steps], last_Vx_, last_Vxx_);
192 double duration_derivative = calcDuration(start_time, std::chrono::system_clock::now());
193 trace_data.duration_derivative = duration_derivative;
194 computation_duration_.derivative += duration_derivative;
199 auto start_time = std::chrono::system_clock::now();
201 while(!backwardPass())
204 dlambda_ = std::max(dlambda_ * config_.lambda_factor, config_.lambda_factor);
205 lambda_ = std::max(lambda_ * dlambda_, config_.lambda_min);
206 if(lambda_ > config_.lambda_max)
208 if(config_.print_level >= 1)
210 std::cout <<
"[DDP/Backward] Failure due to large lambda. (time: " << current_t_ <<
", iter: " << iter <<
")"
215 if(config_.print_level >= 3)
217 std::cout <<
"[DDP/Backward] Increase lambda to " << lambda_ << std::endl;
221 double duration_backward = calcDuration(start_time, std::chrono::system_clock::now());
222 trace_data.duration_backward = duration_backward;
223 computation_duration_.backward += duration_backward;
227 double k_rel_norm = 0;
228 for(
int i = 0; i < config_.horizon_steps; i++)
230 k_rel_norm = std::max(k_rel_norm, k_list_[i].norm() / (control_data_.u_list[i].norm() + 1.0));
232 trace_data.k_rel_norm = k_rel_norm;
233 if(k_rel_norm < config_.k_rel_norm_thre && lambda_ < config_.lambda_thre)
235 if(config_.print_level >= 2)
237 std::cout <<
"[DDP] Terminate due to small gradient. (time: " << current_t_ <<
", iter: " << iter <<
")"
244 bool forward_pass_success =
false;
245 double cost_update_actual = 0;
247 auto start_time = std::chrono::system_clock::now();
250 double cost_update_expected = 0;
251 double cost_update_ratio = 0;
252 for(
int i = 0; i < config_.alpha_list.size(); i++)
254 alpha = config_.alpha_list[i];
258 cost_update_actual = control_data_.cost_list.sum() - candidate_control_data_.cost_list.sum();
259 cost_update_expected = -1 * alpha * (dV_[0] + alpha * dV_[1]);
261 constexpr
double update_eps = 1e-12;
262 if(std::abs(cost_update_expected) < update_eps)
265 if(std::abs(cost_update_actual) < update_eps)
268 cost_update_ratio = 1.0;
269 forward_pass_success =
true;
276 cost_update_ratio = (cost_update_actual > 0) ? 1 : -1;
281 cost_update_ratio = cost_update_actual / cost_update_expected;
282 if(cost_update_expected < 0)
284 if((!config_.with_input_constraint && config_.print_level >= 0)
285 || (config_.with_input_constraint && config_.print_level >= 2))
287 std::cout <<
"[DDP/Forward] Value is not expected to decrease." << std::endl;
289 cost_update_ratio = (cost_update_actual >= 0 ? 1 : -1);
291 if(cost_update_ratio > config_.cost_update_ratio_thre)
293 forward_pass_success =
true;
298 trace_data.alpha = alpha;
299 trace_data.cost_update_actual = cost_update_actual;
300 trace_data.cost_update_expected = cost_update_expected;
301 trace_data.cost_update_ratio = cost_update_ratio;
303 double duration_forward = calcDuration(start_time, std::chrono::system_clock::now());
304 trace_data.duration_forward = duration_forward;
305 computation_duration_.forward += duration_forward;
307 if(!forward_pass_success && config_.print_level >= 3)
309 std::cout <<
"[DDP] Forward pass failed." << std::endl;
314 if(forward_pass_success)
317 control_data_.x_list = candidate_control_data_.x_list;
318 control_data_.u_list = candidate_control_data_.u_list;
319 control_data_.cost_list = candidate_control_data_.cost_list;
322 if(cost_update_actual < config_.cost_update_thre)
324 if(config_.print_level >= 2)
326 std::cout <<
"[DDP] Terminate due to small cost update. (time: " << current_t_ <<
", iter: " << iter <<
")"
333 dlambda_ = std::min(dlambda_ / config_.lambda_factor, 1 / config_.lambda_factor);
334 if(lambda_ >= config_.lambda_min)
342 if(config_.print_level >= 3)
344 std::cout <<
"[DDP/Forward] Decrease lambda to " << lambda_ << std::endl;
350 dlambda_ = std::max(dlambda_ * config_.lambda_factor, config_.lambda_factor);
351 lambda_ = std::max(lambda_ * dlambda_, config_.lambda_min);
352 if(lambda_ > config_.lambda_max)
354 if(config_.print_level >= 1)
356 std::cout <<
"[DDP/Forward] Failure due to large lambda. (time: " << current_t_ <<
", iter: " << iter <<
")"
361 if(config_.print_level >= 3)
363 std::cout <<
"[DDP/Forward] Increase lambda to " << lambda_ << std::endl;
367 trace_data.cost = control_data_.cost_list.sum();
368 trace_data.lambda = lambda_;
369 trace_data.dlambda = dlambda_;
374 template<
int StateDim,
int InputDim>
399 for(
int i = config_.horizon_steps - 1; i >= 0; i--)
402 double t = current_t_ + i * problem_->dt();
413 int input_dim =
static_cast<int>(Fu.cols());
416 auto start_time_Q = std::chrono::system_clock::now();
418 Qu.noalias() = Lu + Fu.transpose() * Vx;
420 Qx.noalias() = Lx + Fx.transpose() * Vx;
422 Qux.noalias() = Lxu.transpose() + Fu.transpose() * Vxx * Fx;
423 if(config_.use_state_eq_second_derivative)
425 throw std::runtime_error(
"Vector-tensor product is not implemented yet.");
431 Quu.noalias() = Luu + Fu.transpose() * Vxx * Fu;
432 if(config_.use_state_eq_second_derivative)
434 throw std::runtime_error(
"Vector-tensor product is not implemented yet.");
440 Qxx.noalias() = Lxx + Fx.transpose() * Vxx * Fx;
441 if(config_.use_state_eq_second_derivative)
443 throw std::runtime_error(
"Vector-tensor product is not implemented yet.");
448 computation_duration_.Q += calcDuration(start_time_Q, std::chrono::system_clock::now());
451 auto start_time_reg = std::chrono::system_clock::now();
454 if(config_.reg_type == 2)
456 Vxx_reg.diagonal().array() += lambda_;
459 Qux_reg.noalias() = Lxu.transpose() + Fu.transpose() * Vxx_reg * Fx;
460 if(config_.use_state_eq_second_derivative)
465 Quu_F.noalias() = Luu + Fu.transpose() * Vxx_reg * Fu;
466 if(config_.use_state_eq_second_derivative)
470 if(config_.reg_type == 1)
472 Quu_F.diagonal().array() += lambda_;
475 computation_duration_.reg += calcDuration(start_time_reg, std::chrono::system_clock::now());
478 auto start_time_gain = std::chrono::system_clock::now();
482 if(config_.with_input_constraint)
485 if(i == config_.horizon_steps - 1)
487 initial_k.setZero(input_dim);
491 if(k_list_[i + 1].size() == input_dim)
493 initial_k = k_list_[i + 1];
497 initial_k.setZero(input_dim);
502 const auto & u_limits = input_limits_func_(t);
503 k = qp.
solve(Quu_F, Qu, u_limits[0] - control_data_.u_list[i], u_limits[1] - control_data_.u_list[i],
507 if(config_.print_level >= 1)
509 std::cout <<
"[DDP/Backward] Failed BoxQP: " << qp.
retstr_.at(qp.
retval_) << std::endl;
515 K.setZero(input_dim, problem_->stateDim());
516 if(free_idxs.size() > 0)
518 Eigen::MatrixXd Qux_reg_free(free_idxs.size(), problem_->stateDim());
519 for(
size_t j = 0; j < free_idxs.size(); j++)
521 Qux_reg_free.row(j) = Qux_reg.row(free_idxs[j]);
523 Eigen::MatrixXd K_free = -1 * qp.
llt_free_->solve(Qux_reg_free);
524 for(
size_t j = 0; j < free_idxs.size(); j++)
526 K.row(free_idxs[j]) = K_free.row(j);
532 Eigen::LLT<InputInputDimMatrix> llt_Quu_F(Quu_F);
533 if(llt_Quu_F.info() == Eigen::NumericalIssue)
535 if(config_.print_level >= 1)
537 std::cout <<
"[DDP/Backward] Quu_F is not positive definite in Cholesky decomposition (LLT)." << std::endl;
541 k = -1 * llt_Quu_F.solve(Qu);
542 K = -1 * llt_Quu_F.solve(Qux_reg);
548 K.setZero(0, problem_->stateDim());
551 computation_duration_.gain += calcDuration(start_time_gain, std::chrono::system_clock::now());
554 dV_ += Eigen::Vector2d(k.dot(Qu), 0.5 * k.dot(Quu * k));
555 Vx.noalias() = Qx + K.transpose() * Quu * k + K.transpose() * Qu + Qux.transpose() * k;
556 Vxx.noalias() = Qxx + K.transpose() * Quu * K + K.transpose() * Qux + Qux.transpose() * K;
557 Vxx_symmetric = 0.5 * (Vxx + Vxx.transpose());
568 template<
int StateDim,
int InputDim>
572 candidate_control_data_.x_list[0] = control_data_.x_list[0];
574 for(
int i = 0; i < config_.horizon_steps; i++)
577 candidate_control_data_.u_list[i] = control_data_.u_list[i] + alpha * k_list_[i]
578 + K_list_[i] * (candidate_control_data_.x_list[i] - control_data_.x_list[i]);
581 const double t = current_t_ + i * problem_->dt();
582 if(config_.with_input_constraint)
584 const auto & u_limits = input_limits_func_(t);
585 candidate_control_data_.u_list[i] = candidate_control_data_.u_list[i].cwiseMax(u_limits[0]).cwiseMin(u_limits[1]);
589 candidate_control_data_.x_list[i + 1] =
590 problem_->stateEq(t, candidate_control_data_.x_list[i], candidate_control_data_.u_list[i]);
591 candidate_control_data_.cost_list[i] =
592 problem_->runningCost(t, candidate_control_data_.x_list[i], candidate_control_data_.u_list[i]);
594 double terminal_t = current_t_ + config_.horizon_steps * problem_->dt();
595 candidate_control_data_.cost_list[config_.horizon_steps] =
596 problem_->terminalCost(terminal_t, candidate_control_data_.x_list[config_.horizon_steps]);
599 template<
int StateDim,
int InputDim>
602 std::ofstream ofs(file_path);
610 <<
"cost_update_actual "
611 <<
"cost_update_expected "
612 <<
"cost_update_ratio "
613 <<
"duration_derivative "
614 <<
"duration_backward "
615 <<
"duration_forward" << std::endl;
617 for(
const auto & trace_data : trace_data_list_)
620 ofs << trace_data.iter <<
" "
621 << trace_data.cost <<
" "
622 << trace_data.lambda <<
" "
623 << trace_data.dlambda <<
" "
624 << trace_data.alpha <<
" "
625 << trace_data.k_rel_norm <<
" "
626 << trace_data.cost_update_actual <<
" "
627 << trace_data.cost_update_expected <<
" "
628 << trace_data.cost_update_ratio <<
" "
629 << trace_data.duration_derivative <<
" "
630 << trace_data.duration_backward <<
" "
631 << trace_data.duration_forward
Solver for quadratic programming problems with box constraints (i.e., only upper and lower bounds).
std::unique_ptr< Eigen::LLT< Eigen::MatrixXd > > llt_free_
Cholesky decomposition (LLT) of free block of objective Hessian matrix.
const std::unordered_map< int, std::string > retstr_
Return string.
std::vector< int > free_idxs_
Indices of free dimensions in decision variables.
VarDimVector solve(const VarVarDimMatrix &H, const VarDimVector &g, const VarDimVector &lower, const VarDimVector &upper)
Solve optimization.
typename DDPProblem< StateDim, InputDim >::StateStateDimMatrix StateStateDimMatrix
Type of matrix of state x state dimension.
void forwardPass(double alpha)
Process forward pass.
bool backwardPass()
Process backward pass.
typename DDPProblem< StateDim, InputDim >::InputInputDimMatrix InputInputDimMatrix
Type of matrix of input x input dimension.
typename DDPProblem< StateDim, InputDim >::StateInputDimMatrix StateInputDimMatrix
Type of matrix of state x input dimension.
typename DDPProblem< StateDim, InputDim >::StateDimVector StateDimVector
Type of vector of state dimension.
void dumpTraceDataList(const std::string &file_path) const
Dump trace data list.
typename DDPProblem< StateDim, InputDim >::InputStateDimMatrix InputStateDimMatrix
Type of matrix of input x state dimension.
int procOnce(int iter)
Process one iteration.
typename DDPProblem< StateDim, InputDim >::InputDimVector InputDimVector
Type of vector of input dimension.
bool solve(double current_t, const StateDimVector ¤t_x, const std::vector< InputDimVector > &initial_u_list)
Solve optimization.
EIGEN_MAKE_ALIGNED_OPERATOR_NEW DDPSolver(const std::shared_ptr< DDPProblem< StateDim, InputDim >> &problem)
Constructor.
Data of computation duration.
Derivatives of DDP problem.
Data to trace optimization loop.
double lambda
Regularization coefficient.
int iter
Iteration of optimization loop.
double dlambda
Scaling factor of regularization coefficient.