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 control_data_.x_list.resize(config_.horizon_steps + 1);
85 control_data_.cost_list.resize(config_.horizon_steps + 1);
86 control_data_.x_list[0] = current_x;
87 for(
int i = 0; i < config_.horizon_steps; i++)
89 double t = current_t_ + i * problem_->dt();
90 control_data_.x_list[i + 1] = problem_->stateEq(t, control_data_.x_list[i], control_data_.u_list[i]);
91 control_data_.cost_list[i] = problem_->runningCost(t, control_data_.x_list[i], control_data_.u_list[i]);
93 double terminal_t = current_t_ + config_.horizon_steps * problem_->dt();
94 control_data_.cost_list[config_.horizon_steps] =
95 problem_->terminalCost(terminal_t, control_data_.x_list[config_.horizon_steps]);
98 trace_data_list_.clear();
100 initial_trace_data.
iter = 0;
101 initial_trace_data.
cost = control_data_.cost_list.sum();
102 initial_trace_data.
lambda = lambda_;
103 initial_trace_data.
dlambda = dlambda_;
104 trace_data_list_.push_back(initial_trace_data);
106 if(config_.print_level >= 3)
108 std::cout <<
"[DDP] Initial cost: " << control_data_.cost_list.sum() << std::endl;
111 auto setup_time = std::chrono::system_clock::now();
112 computation_duration_.setup = calcDuration(start_time, setup_time);
116 for(
int iter = 1; iter <= config_.max_iter; iter++)
118 retval = procOnce(iter);
125 if(config_.print_level >= 3)
127 std::cout <<
"[DDP] Final cost: " << control_data_.cost_list.sum() << std::endl;
130 auto end_time = std::chrono::system_clock::now();
131 computation_duration_.opt = calcDuration(setup_time, end_time);
132 computation_duration_.solve = calcDuration(start_time, end_time);
134 if(config_.print_level >= 3)
136 std::cout <<
"[DDP] Setup duration: " << computation_duration_.setup
137 <<
" [ms], optimization duration: " << computation_duration_.opt <<
" [ms]." << std::endl;
143 template<
int StateDim,
int InputDim>
146 if(config_.print_level >= 3)
148 std::cout <<
"[DDP] Start iteration " << iter << std::endl;
153 auto & trace_data = trace_data_list_.back();
154 trace_data.iter = iter;
158 auto start_time = std::chrono::system_clock::now();
160 for(
int i = 0; i < config_.horizon_steps; i++)
162 auto & derivative = derivative_list_[i];
164 double t = current_t_ + i * problem_->dt();
167 if(config_.use_state_eq_second_derivative)
169 problem_->calcStateEqDeriv(t, x, u, derivative.Fx, derivative.Fu, derivative.Fxx, derivative.Fuu,
174 problem_->calcStateEqDeriv(t, x, u, derivative.Fx, derivative.Fu);
176 problem_->calcRunningCostDeriv(t, x, u, derivative.Lx, derivative.Lu, derivative.Lxx, derivative.Luu,
179 double terminal_t = current_t_ + config_.horizon_steps * problem_->dt();
180 problem_->calcTerminalCostDeriv(terminal_t, control_data_.x_list[config_.horizon_steps], last_Vx_, last_Vxx_);
182 double duration_derivative = calcDuration(start_time, std::chrono::system_clock::now());
183 trace_data.duration_derivative = duration_derivative;
184 computation_duration_.derivative += duration_derivative;
189 auto start_time = std::chrono::system_clock::now();
191 while(!backwardPass())
194 dlambda_ = std::max(dlambda_ * config_.lambda_factor, config_.lambda_factor);
195 lambda_ = std::max(lambda_ * dlambda_, config_.lambda_min);
196 if(lambda_ > config_.lambda_max)
198 if(config_.print_level >= 1)
200 std::cout <<
"[DDP/Backward] Failure due to large lambda. (time: " << current_t_ <<
", iter: " << iter <<
")"
205 if(config_.print_level >= 3)
207 std::cout <<
"[DDP/Backward] Increase lambda to " << lambda_ << std::endl;
211 double duration_backward = calcDuration(start_time, std::chrono::system_clock::now());
212 trace_data.duration_backward = duration_backward;
213 computation_duration_.backward += duration_backward;
217 double k_rel_norm = 0;
218 for(
int i = 0; i < config_.horizon_steps; i++)
220 k_rel_norm = std::max(k_rel_norm, k_list_[i].norm() / (control_data_.u_list[i].norm() + 1.0));
222 trace_data.k_rel_norm = k_rel_norm;
223 if(k_rel_norm < config_.k_rel_norm_thre && lambda_ < config_.lambda_thre)
225 if(config_.print_level >= 2)
227 std::cout <<
"[DDP] Terminate due to small gradient. (time: " << current_t_ <<
", iter: " << iter <<
")"
234 bool forward_pass_success =
false;
235 double cost_update_actual = 0;
237 auto start_time = std::chrono::system_clock::now();
240 double cost_update_expected = 0;
241 double cost_update_ratio = 0;
242 for(
int i = 0; i < config_.alpha_list.size(); i++)
244 alpha = config_.alpha_list[i];
248 cost_update_actual = control_data_.cost_list.sum() - candidate_control_data_.cost_list.sum();
249 cost_update_expected = -1 * alpha * (dV_[0] + alpha * dV_[1]);
250 cost_update_ratio = cost_update_actual / cost_update_expected;
251 if(cost_update_expected < 0)
253 if((!config_.with_input_constraint && config_.print_level >= 0)
254 || (config_.with_input_constraint && config_.print_level >= 2))
256 std::cout <<
"[DDP/Forward] Value is not expected to decrease." << std::endl;
258 cost_update_ratio = (cost_update_actual >= 0 ? 1 : -1);
260 if(cost_update_ratio > config_.cost_update_ratio_thre)
262 forward_pass_success =
true;
266 trace_data.alpha = alpha;
267 trace_data.cost_update_actual = cost_update_actual;
268 trace_data.cost_update_expected = cost_update_expected;
269 trace_data.cost_update_ratio = cost_update_ratio;
271 double duration_forward = calcDuration(start_time, std::chrono::system_clock::now());
272 trace_data.duration_forward = duration_forward;
273 computation_duration_.forward += duration_forward;
275 if(!forward_pass_success && config_.print_level >= 3)
277 std::cout <<
"[DDP] Forward pass failed." << std::endl;
282 if(forward_pass_success)
285 control_data_.x_list = candidate_control_data_.x_list;
286 control_data_.u_list = candidate_control_data_.u_list;
287 control_data_.cost_list = candidate_control_data_.cost_list;
290 if(cost_update_actual < config_.cost_update_thre)
292 if(config_.print_level >= 2)
294 std::cout <<
"[DDP] Terminate due to small cost update. (time: " << current_t_ <<
", iter: " << iter <<
")"
301 dlambda_ = std::min(dlambda_ / config_.lambda_factor, 1 / config_.lambda_factor);
302 if(lambda_ >= config_.lambda_min)
310 if(config_.print_level >= 3)
312 std::cout <<
"[DDP/Forward] Decrease lambda to " << lambda_ << std::endl;
318 dlambda_ = std::max(dlambda_ * config_.lambda_factor, config_.lambda_factor);
319 lambda_ = std::max(lambda_ * dlambda_, config_.lambda_min);
320 if(lambda_ > config_.lambda_max)
322 if(config_.print_level >= 1)
324 std::cout <<
"[DDP/Forward] Failure due to large lambda. (time: " << current_t_ <<
", iter: " << iter <<
")"
329 if(config_.print_level >= 3)
331 std::cout <<
"[DDP/Forward] Increase lambda to " << lambda_ << std::endl;
335 trace_data.cost = control_data_.cost_list.sum();
336 trace_data.lambda = lambda_;
337 trace_data.dlambda = dlambda_;
342 template<
int StateDim,
int InputDim>
367 for(
int i = config_.horizon_steps - 1; i >= 0; i--)
370 double t = current_t_ + i * problem_->dt();
381 int input_dim =
static_cast<int>(Fu.cols());
384 auto start_time_Q = std::chrono::system_clock::now();
386 Qu.noalias() = Lu + Fu.transpose() * Vx;
388 Qx.noalias() = Lx + Fx.transpose() * Vx;
390 Qux.noalias() = Lxu.transpose() + Fu.transpose() * Vxx * Fx;
391 if(config_.use_state_eq_second_derivative)
393 throw std::runtime_error(
"Vector-tensor product is not implemented yet.");
399 Quu.noalias() = Luu + Fu.transpose() * Vxx * Fu;
400 if(config_.use_state_eq_second_derivative)
402 throw std::runtime_error(
"Vector-tensor product is not implemented yet.");
408 Qxx.noalias() = Lxx + Fx.transpose() * Vxx * Fx;
409 if(config_.use_state_eq_second_derivative)
411 throw std::runtime_error(
"Vector-tensor product is not implemented yet.");
416 computation_duration_.Q += calcDuration(start_time_Q, std::chrono::system_clock::now());
419 auto start_time_reg = std::chrono::system_clock::now();
422 if(config_.reg_type == 2)
424 Vxx_reg.diagonal().array() += lambda_;
427 Qux_reg.noalias() = Lxu.transpose() + Fu.transpose() * Vxx_reg * Fx;
428 if(config_.use_state_eq_second_derivative)
433 Quu_F.noalias() = Luu + Fu.transpose() * Vxx_reg * Fu;
434 if(config_.use_state_eq_second_derivative)
438 if(config_.reg_type == 1)
440 Quu_F.diagonal().array() += lambda_;
443 computation_duration_.reg += calcDuration(start_time_reg, std::chrono::system_clock::now());
446 auto start_time_gain = std::chrono::system_clock::now();
450 if(config_.with_input_constraint)
453 if(i == config_.horizon_steps - 1)
455 initial_k.setZero(input_dim);
459 if(k_list_[i + 1].size() == input_dim)
461 initial_k = k_list_[i + 1];
465 initial_k.setZero(input_dim);
470 const auto & u_limits = input_limits_func_(t);
471 k = qp.
solve(Quu_F, Qu, u_limits[0] - control_data_.u_list[i], u_limits[1] - control_data_.u_list[i],
475 if(config_.print_level >= 1)
477 std::cout <<
"[DDP/Backward] Failed BoxQP: " << qp.
retstr_.at(qp.
retval_) << std::endl;
483 K.setZero(input_dim, problem_->stateDim());
484 if(free_idxs.size() > 0)
486 Eigen::MatrixXd Qux_reg_free(free_idxs.size(), problem_->stateDim());
487 for(
size_t j = 0; j < free_idxs.size(); j++)
489 Qux_reg_free.row(j) = Qux_reg.row(free_idxs[j]);
491 Eigen::MatrixXd K_free = -1 * qp.
llt_free_->solve(Qux_reg_free);
492 for(
size_t j = 0; j < free_idxs.size(); j++)
494 K.row(free_idxs[j]) = K_free.row(j);
500 Eigen::LLT<InputInputDimMatrix> llt_Quu_F(Quu_F);
501 if(llt_Quu_F.info() == Eigen::NumericalIssue)
503 if(config_.print_level >= 1)
505 std::cout <<
"[DDP/Backward] Quu_F is not positive definite in Cholesky decomposition (LLT)." << std::endl;
509 k = -1 * llt_Quu_F.solve(Qu);
510 K = -1 * llt_Quu_F.solve(Qux_reg);
516 K.setZero(0, problem_->stateDim());
519 computation_duration_.gain += calcDuration(start_time_gain, std::chrono::system_clock::now());
522 dV_ += Eigen::Vector2d(k.dot(Qu), 0.5 * k.dot(Quu * k));
523 Vx.noalias() = Qx + K.transpose() * Quu * k + K.transpose() * Qu + Qux.transpose() * k;
524 Vxx.noalias() = Qxx + K.transpose() * Quu * K + K.transpose() * Qux + Qux.transpose() * K;
525 Vxx_symmetric = 0.5 * (Vxx + Vxx.transpose());
536 template<
int StateDim,
int InputDim>
540 candidate_control_data_.x_list[0] = control_data_.x_list[0];
542 for(
int i = 0; i < config_.horizon_steps; i++)
545 candidate_control_data_.u_list[i] = control_data_.u_list[i] + alpha * k_list_[i]
546 + K_list_[i] * (candidate_control_data_.x_list[i] - control_data_.x_list[i]);
551 double t = current_t_ + i * problem_->dt();
552 candidate_control_data_.x_list[i + 1] =
553 problem_->stateEq(t, candidate_control_data_.x_list[i], candidate_control_data_.u_list[i]);
554 candidate_control_data_.cost_list[i] =
555 problem_->runningCost(t, candidate_control_data_.x_list[i], candidate_control_data_.u_list[i]);
557 double terminal_t = current_t_ + config_.horizon_steps * problem_->dt();
558 candidate_control_data_.cost_list[config_.horizon_steps] =
559 problem_->terminalCost(terminal_t, candidate_control_data_.x_list[config_.horizon_steps]);
562 template<
int StateDim,
int InputDim>
565 std::ofstream ofs(file_path);
573 <<
"cost_update_actual "
574 <<
"cost_update_expected "
575 <<
"cost_update_ratio "
576 <<
"duration_derivative "
577 <<
"duration_backward "
578 <<
"duration_forward" << std::endl;
580 for(
const auto & trace_data : trace_data_list_)
583 ofs << trace_data.iter <<
" "
584 << trace_data.cost <<
" "
585 << trace_data.lambda <<
" "
586 << trace_data.dlambda <<
" "
587 << trace_data.alpha <<
" "
588 << trace_data.k_rel_norm <<
" "
589 << trace_data.cost_update_actual <<
" "
590 << trace_data.cost_update_expected <<
" "
591 << trace_data.cost_update_ratio <<
" "
592 << trace_data.duration_derivative <<
" "
593 << trace_data.duration_backward <<
" "
594 << trace_data.duration_forward