nmpc_ddp
BoxQP.h
Go to the documentation of this file.
1 /* Author: Masaki Murooka */
2 
3 #pragma once
4 
5 #include <vector>
6 
7 #include <Eigen/Dense>
8 
9 namespace nmpc_ddp
10 {
18 template<int VarDim>
19 class BoxQP
20 {
21 public:
23  using VarDimVector = Eigen::Matrix<double, VarDim, 1>;
24 
26  using VarVarDimMatrix = Eigen::Matrix<double, VarDim, VarDim>;
27 
29  using VarDimArray = Eigen::Array<bool, VarDim, 1>;
30 
31 public:
34  {
36  int print_level = 1;
37 
39  int max_iter = 500;
40 
42  double grad_thre = 1e-8;
43 
45  double rel_improve_thre = 1e-8;
46 
48  double step_factor = 0.6;
49 
51  double min_step = 1e-22;
52 
54  double armijo_param = 0.1;
55  };
56 
58  struct TraceData
59  {
60  EIGEN_MAKE_ALIGNED_OPERATOR_NEW
61 
63  int iter = 0;
64 
67 
69  double obj = 0;
70 
73 
76 
79 
81  int step_num = 0;
82 
86  TraceData(int var_dim)
87  {
88  x.setZero(var_dim);
89  search_dir.setZero(var_dim);
90  clamped_flag.setZero(var_dim);
91  }
92  };
93 
94 public:
95  EIGEN_MAKE_ALIGNED_OPERATOR_NEW
96 
101  BoxQP(int var_dim = VarDim) : var_dim_(var_dim)
102  {
103  // Check dimension is positive
104  if(var_dim_ <= 0)
105  {
106  throw std::runtime_error("var_dim must be positive: " + std::to_string(var_dim_) + " <= 0");
107  }
108 
109  // Check dimension consistency
110  if constexpr(VarDim != Eigen::Dynamic)
111  {
112  if(var_dim_ != VarDim)
113  {
114  throw std::runtime_error("var_dim is inconsistent with template parameter: " + std::to_string(var_dim_)
115  + " != " + std::to_string(VarDim));
116  }
117  }
118  }
119 
127  const VarDimVector & g,
128  const VarDimVector & lower,
129  const VarDimVector & upper)
130  {
131  return solve(H, g, lower, upper, VarDimVector::Zero(var_dim_));
132  }
133 
142  const VarDimVector & g,
143  const VarDimVector & lower,
144  const VarDimVector & upper,
145  const VarDimVector & initial_x)
146  {
147  // Initialize objective value
148  VarDimVector x = initial_x.cwiseMin(upper).cwiseMax(lower);
149  double obj = x.dot(g) + 0.5 * x.dot(H * x);
150  double old_obj = obj;
151 
152  // Initialize trace data
153  trace_data_list_.clear();
154  TraceData initial_trace_data(var_dim_);
155  initial_trace_data.iter = 0;
156  initial_trace_data.x = x;
157  initial_trace_data.obj = obj;
158  trace_data_list_.push_back(initial_trace_data);
159 
160  // Main loop
161  retval_ = 0;
162  int factorization_num = 0;
163  VarDimVector grad = VarDimVector::Zero(var_dim_);
164  VarDimArray clamped_flag = VarDimArray::Zero(var_dim_);
165  VarDimArray old_clamped_flag = clamped_flag;
166  int iter = 1;
167  free_idxs_.clear();
168  for(;; iter++)
169  {
170  // Append trace data
171  trace_data_list_.push_back(TraceData(var_dim_));
172  auto & trace_data = trace_data_list_.back();
173  trace_data.iter = iter;
174 
175  // Check relative improvement
176  if(iter > 1 && (old_obj - obj) < config_.rel_improve_thre * std::abs(old_obj))
177  {
178  retval_ = 4;
179  break;
180  }
181  old_obj = obj;
182 
183  // Calculate gradient
184  grad = g + H * x;
185 
186  // Find clamped dimensions
187  old_clamped_flag = clamped_flag;
188  clamped_flag.setConstant(false);
189  clamped_flag =
190  ((x.array() == lower.array() && grad.array() > 0) || (x.array() == upper.array() && grad.array() < 0))
191  .select(true, clamped_flag);
192 
193  // Set clamped and free indices
194  std::vector<int> clamped_idxs;
195  free_idxs_.clear();
196  for(int i = 0; i < clamped_flag.size(); i++)
197  {
198  if(clamped_flag[i])
199  {
200  clamped_idxs.push_back(i);
201  }
202  else
203  {
204  free_idxs_.push_back(i);
205  }
206  }
207 
208  // Check for all clamped
209  if(clamped_flag.all())
210  {
211  retval_ = 6;
212  break;
213  }
214 
215  // Factorize if clamped has changed
216  if(iter == 1 || (clamped_flag != old_clamped_flag).any())
217  {
218  // Set H_free
219  Eigen::MatrixXd H_free(free_idxs_.size(), free_idxs_.size());
220  for(size_t i = 0; i < free_idxs_.size(); i++)
221  {
222  for(size_t j = 0; j < free_idxs_.size(); j++)
223  {
224  H_free(i, j) = H(free_idxs_[i], free_idxs_[j]);
225  }
226  }
227 
228  // Cholesky decomposition
229  llt_free_ = std::make_unique<Eigen::LLT<Eigen::MatrixXd>>(H_free);
230  if(llt_free_->info() == Eigen::NumericalIssue)
231  {
232  if(config_.print_level >= 1)
233  {
234  std::cout << "[BoxQP] H_free is not positive definite in Cholesky decomposition (LLT)." << std::endl;
235  }
236  retval_ = -1;
237  break;
238  }
239 
240  factorization_num++;
241  }
242 
243  // Check gradient norm
244  double grad_norm = 0;
245  for(size_t i = 0; i < free_idxs_.size(); i++)
246  {
247  grad_norm += std::pow(grad[free_idxs_[i]], 2);
248  }
249  if(grad_norm < std::pow(config_.grad_thre, 2))
250  {
251  retval_ = 5;
252  break;
253  }
254 
255  // Calculate search direction
256  Eigen::VectorXd x_clamped(clamped_idxs.size());
257  Eigen::VectorXd x_free(free_idxs_.size());
258  Eigen::VectorXd g_free(free_idxs_.size());
259  Eigen::MatrixXd H_free_clamped(free_idxs_.size(), clamped_idxs.size());
260  for(size_t i = 0; i < clamped_idxs.size(); i++)
261  {
262  x_clamped[i] = x[clamped_idxs[i]];
263  }
264  for(size_t i = 0; i < free_idxs_.size(); i++)
265  {
266  x_free[i] = x[free_idxs_[i]];
267  g_free[i] = g[free_idxs_[i]];
268  for(size_t j = 0; j < clamped_idxs.size(); j++)
269  {
270  H_free_clamped(i, j) = H(free_idxs_[i], clamped_idxs[j]);
271  }
272  }
273  Eigen::VectorXd grad_free_clamped = g_free + H_free_clamped * x_clamped;
274  Eigen::VectorXd search_dir_free = -1 * llt_free_->solve(grad_free_clamped) - x_free;
275  VarDimVector search_dir = VarDimVector::Zero(var_dim_);
276  for(size_t i = 0; i < free_idxs_.size(); i++)
277  {
278  search_dir[free_idxs_[i]] = search_dir_free[i];
279  }
280 
281  // Check for descent direction
282  double search_dir_grad = search_dir.dot(grad);
283  if(search_dir_grad > 1e-10) // This should not happen
284  {
285  if(config_.print_level >= 1)
286  {
287  std::cout << "[BoxQP] search_dir_grad is positive: " << search_dir_grad << std::endl;
288  }
289  retval_ = -2;
290  break;
291  }
292 
293  // Armijo linesearch
294  double step = 1;
295  int step_num = 0;
296  VarDimVector x_candidate = (x + step * search_dir).cwiseMin(upper).cwiseMax(lower);
297  double obj_candidate = x_candidate.dot(g) + 0.5 * x_candidate.dot(H * x_candidate);
298  while((obj_candidate - old_obj) / (step * search_dir_grad) < config_.armijo_param)
299  {
300  step = step * config_.step_factor;
301  step_num++;
302  x_candidate = (x + step * search_dir).cwiseMin(upper).cwiseMax(lower);
303  obj_candidate = x_candidate.dot(g) + 0.5 * x_candidate.dot(H * x_candidate);
304  if(step < config_.min_step)
305  {
306  retval_ = 2;
307  break;
308  }
309  }
310 
311  // Print
312  if(config_.print_level >= 3)
313  {
314  std::cout << "[BoxQP] iter: " << iter << ", obj: " << obj << ", grad_norm: " << grad_norm
315  << ", obj_update, : " << old_obj - obj_candidate << ", step: " << step
316  << ", clamped_flag_num: " << clamped_idxs.size() << std::endl;
317  }
318 
319  // Set trace data
320  trace_data.x = x;
321  trace_data.obj = obj;
322  trace_data.search_dir = search_dir;
323  trace_data.clamped_flag = clamped_flag;
324  trace_data.factorization_num = factorization_num;
325  trace_data.step_num = step_num;
326 
327  // Accept candidate
328  x = x_candidate;
329  obj = obj_candidate;
330 
331  // Check loop termination
332  if(iter == config_.max_iter)
333  {
334  retval_ = 1;
335  break;
336  }
337  }
338 
339  // Print
340  if(config_.print_level >= 2)
341  {
342  std::cout << "[BoxQP] result: " << retval_ << " (" << retstr_.at(retval_) << "), iter: " << iter
343  << ", obj: " << obj << ", factorization_num: " << factorization_num << std::endl;
344  }
345 
346  return x;
347  }
348 
351  {
352  return config_;
353  }
354 
356  inline const Configuration & config() const
357  {
358  return config_;
359  }
360 
362  inline const std::vector<TraceData> & traceDataList() const
363  {
364  return trace_data_list_;
365  }
366 
367 public:
369  const int var_dim_ = 0;
370 
372  int retval_ = 0;
373 
375  const std::unordered_map<int, std::string> retstr_ = {{-2, "Gradient of search direction is positive"},
376  {-1, "Hessian is not positive definite"},
377  {0, "Computation is not finished"},
378  {1, "Maximum main iterations exceeded"},
379  {2, "Maximum line-search iterations exceeded"},
380  {3, "No bounds, returning Newton point"},
381  {4, "Improvement smaller than tolerance"},
382  {5, "Gradient norm smaller than tolerance"},
383  {6, "All dimensions are clamped"}};
384 
386  std::unique_ptr<Eigen::LLT<Eigen::MatrixXd>> llt_free_;
387 
389  std::vector<int> free_idxs_;
390 
391 protected:
394 
396  std::vector<TraceData> trace_data_list_;
397 };
398 } // namespace nmpc_ddp
nmpc_ddp::BoxQP::config
Configuration & config()
Accessor to configuration.
Definition: BoxQP.h:350
nmpc_ddp::BoxQP::traceDataList
const std::vector< TraceData > & traceDataList() const
Const accessor to trace data list.
Definition: BoxQP.h:362
nmpc_ddp::BoxQP::Configuration::print_level
int print_level
Print level (0: no print, 1: print only important, 2: print verbose, 3: print very verbose)
Definition: BoxQP.h:36
nmpc_ddp::BoxQP::TraceData
Data to trace optimization loop.
Definition: BoxQP.h:58
nmpc_ddp::BoxQP::Configuration::armijo_param
double armijo_param
Armijo parameter (fraction of linear improvement required)
Definition: BoxQP.h:54
nmpc_ddp::BoxQP::VarDimArray
Eigen::Array< bool, VarDim, 1 > VarDimArray
Type of boolean array of variables dimension.
Definition: BoxQP.h:29
nmpc_ddp::BoxQP::TraceData::search_dir
VarDimVector search_dir
Search direction.
Definition: BoxQP.h:72
nmpc_ddp::BoxQP::retstr_
const std::unordered_map< int, std::string > retstr_
Return string.
Definition: BoxQP.h:375
nmpc_ddp::BoxQP::Configuration::rel_improve_thre
double rel_improve_thre
Termination threshold of relative improvement.
Definition: BoxQP.h:45
nmpc_ddp::BoxQP::TraceData::step_num
int step_num
Number of linesearch step.
Definition: BoxQP.h:81
nmpc_ddp::BoxQP::free_idxs_
std::vector< int > free_idxs_
Indices of free dimensions in decision variables.
Definition: BoxQP.h:389
nmpc_ddp::BoxQP::Configuration::grad_thre
double grad_thre
Termination threshold of non-fixed gradient.
Definition: BoxQP.h:42
nmpc_ddp::BoxQP::Configuration
Configuration.
Definition: BoxQP.h:33
nmpc_ddp::BoxQP::Configuration::max_iter
int max_iter
Maximum iteration of optimization loop.
Definition: BoxQP.h:39
nmpc_ddp::BoxQP::Configuration::step_factor
double step_factor
Factor for decreasing stepsize.
Definition: BoxQP.h:48
nmpc_ddp::BoxQP
Solver for quadratic programming problems with box constraints (i.e., only upper and lower bounds).
Definition: BoxQP.h:19
nmpc_ddp::BoxQP::TraceData::clamped_flag
VarDimArray clamped_flag
Flag of clamped variables dimensions.
Definition: BoxQP.h:75
nmpc_ddp
Definition: BoxQP.h:9
nmpc_ddp::BoxQP::retval_
int retval_
Return value.
Definition: BoxQP.h:372
nmpc_ddp::BoxQP::VarVarDimMatrix
Eigen::Matrix< double, VarDim, VarDim > VarVarDimMatrix
Type of matrix of variables x variables dimension.
Definition: BoxQP.h:26
nmpc_ddp::BoxQP::solve
VarDimVector solve(const VarVarDimMatrix &H, const VarDimVector &g, const VarDimVector &lower, const VarDimVector &upper, const VarDimVector &initial_x)
Solve optimization.
Definition: BoxQP.h:141
nmpc_ddp::BoxQP::TraceData::factorization_num
int factorization_num
Number of factorization.
Definition: BoxQP.h:78
nmpc_ddp::BoxQP::trace_data_list_
std::vector< TraceData > trace_data_list_
Sequence of trace data.
Definition: BoxQP.h:396
nmpc_ddp::BoxQP::TraceData::iter
EIGEN_MAKE_ALIGNED_OPERATOR_NEW int iter
Iteration of optimization loop.
Definition: BoxQP.h:63
nmpc_ddp::BoxQP::TraceData::x
VarDimVector x
Decision variables.
Definition: BoxQP.h:66
nmpc_ddp::BoxQP::TraceData::obj
double obj
Objective value.
Definition: BoxQP.h:69
nmpc_ddp::BoxQP::VarDimVector
Eigen::Matrix< double, VarDim, 1 > VarDimVector
Type of vector of variables dimension.
Definition: BoxQP.h:23
nmpc_ddp::BoxQP::BoxQP
EIGEN_MAKE_ALIGNED_OPERATOR_NEW BoxQP(int var_dim=VarDim)
Constructor.
Definition: BoxQP.h:101
nmpc_ddp::BoxQP::TraceData::TraceData
TraceData(int var_dim)
Constructor.
Definition: BoxQP.h:86
nmpc_ddp::BoxQP::config
const Configuration & config() const
Const accessor to configuration.
Definition: BoxQP.h:356
nmpc_ddp::BoxQP::llt_free_
std::unique_ptr< Eigen::LLT< Eigen::MatrixXd > > llt_free_
Cholesky decomposition (LLT) of free block of objective Hessian matrix.
Definition: BoxQP.h:386
nmpc_ddp::BoxQP::solve
VarDimVector solve(const VarVarDimMatrix &H, const VarDimVector &g, const VarDimVector &lower, const VarDimVector &upper)
Solve optimization.
Definition: BoxQP.h:126
nmpc_ddp::BoxQP::config_
Configuration config_
Configuration.
Definition: BoxQP.h:393
nmpc_ddp::BoxQP::Configuration::min_step
double min_step
Minimum stepsize for linesearch.
Definition: BoxQP.h:51
nmpc_ddp::BoxQP::var_dim_
const int var_dim_
Dimension of decision variables.
Definition: BoxQP.h:369