Line |
Branch |
Exec |
Source |
1 |
|
|
/////////////////////////////////////////////////////////////////////////////// |
2 |
|
|
// BSD 3-Clause License |
3 |
|
|
// |
4 |
|
|
// Copyright (C) 2019-2022, University of Edinburgh, Heriot-Watt University |
5 |
|
|
// Copyright note valid unless otherwise stated in individual files. |
6 |
|
|
// All rights reserved. |
7 |
|
|
/////////////////////////////////////////////////////////////////////////////// |
8 |
|
|
|
9 |
|
|
#include "crocoddyl/core/solvers/box-qp.hpp" |
10 |
|
|
|
11 |
|
|
namespace crocoddyl { |
12 |
|
|
|
13 |
|
✗ |
BoxQP::BoxQP(const std::size_t nx, const std::size_t maxiter, |
14 |
|
✗ |
const double th_acceptstep, const double th_grad, const double reg) |
15 |
|
✗ |
: nx_(nx), |
16 |
|
✗ |
maxiter_(maxiter), |
17 |
|
✗ |
th_acceptstep_(th_acceptstep), |
18 |
|
✗ |
th_grad_(th_grad), |
19 |
|
✗ |
reg_(reg), |
20 |
|
✗ |
fold_(0.), |
21 |
|
✗ |
fnew_(0.), |
22 |
|
✗ |
x_(nx), |
23 |
|
✗ |
xnew_(nx), |
24 |
|
✗ |
g_(nx), |
25 |
|
✗ |
dx_(nx), |
26 |
|
✗ |
xo_(nx), |
27 |
|
✗ |
dxo_(nx), |
28 |
|
✗ |
qo_(nx), |
29 |
|
✗ |
Ho_(nx, nx) { |
30 |
|
|
// Check if values have a proper range |
31 |
|
✗ |
if (0. >= th_acceptstep && th_acceptstep >= 0.5) { |
32 |
|
✗ |
std::cerr << "Warning: th_acceptstep value should between 0 and 0.5" |
33 |
|
✗ |
<< std::endl; |
34 |
|
|
} |
35 |
|
✗ |
if (0. > th_grad) { |
36 |
|
✗ |
std::cerr << "Warning: th_grad value has to be positive." << std::endl; |
37 |
|
|
} |
38 |
|
✗ |
if (0. > reg) { |
39 |
|
✗ |
std::cerr << "Warning: reg value has to be positive." << std::endl; |
40 |
|
|
} |
41 |
|
|
|
42 |
|
|
// Initialized the values of vectors |
43 |
|
✗ |
x_.setZero(); |
44 |
|
✗ |
xnew_.setZero(); |
45 |
|
✗ |
g_.setZero(); |
46 |
|
✗ |
dx_.setZero(); |
47 |
|
✗ |
xo_.setZero(); |
48 |
|
✗ |
dxo_.setZero(); |
49 |
|
✗ |
qo_.setZero(); |
50 |
|
✗ |
Ho_.setZero(); |
51 |
|
|
|
52 |
|
|
// Reserve the space and compute alphas |
53 |
|
✗ |
solution_.x = Eigen::VectorXd::Zero(nx); |
54 |
|
✗ |
solution_.clamped_idx.reserve(nx_); |
55 |
|
✗ |
solution_.free_idx.reserve(nx_); |
56 |
|
✗ |
const std::size_t n_alphas_ = 10; |
57 |
|
✗ |
alphas_.resize(n_alphas_); |
58 |
|
✗ |
for (std::size_t n = 0; n < n_alphas_; ++n) { |
59 |
|
✗ |
alphas_[n] = 1. / pow(2., static_cast<double>(n)); |
60 |
|
|
} |
61 |
|
|
} |
62 |
|
|
|
63 |
|
✗ |
BoxQP::~BoxQP() {} |
64 |
|
|
|
65 |
|
✗ |
const BoxQPSolution& BoxQP::solve(const Eigen::MatrixXd& H, |
66 |
|
|
const Eigen::VectorXd& q, |
67 |
|
|
const Eigen::VectorXd& lb, |
68 |
|
|
const Eigen::VectorXd& ub, |
69 |
|
|
const Eigen::VectorXd& xinit) { |
70 |
|
✗ |
if (static_cast<std::size_t>(H.rows()) != nx_ || |
71 |
|
✗ |
static_cast<std::size_t>(H.cols()) != nx_) { |
72 |
|
✗ |
throw_pretty("Invalid argument: " |
73 |
|
|
<< "H has wrong dimension (it should be " + |
74 |
|
|
std::to_string(nx_) + "," + std::to_string(nx_) + ")"); |
75 |
|
|
} |
76 |
|
✗ |
if (static_cast<std::size_t>(q.size()) != nx_) { |
77 |
|
✗ |
throw_pretty( |
78 |
|
|
"Invalid argument: " << "q has wrong dimension (it should be " + |
79 |
|
|
std::to_string(nx_) + ")"); |
80 |
|
|
} |
81 |
|
✗ |
if (static_cast<std::size_t>(lb.size()) != nx_) { |
82 |
|
✗ |
throw_pretty( |
83 |
|
|
"Invalid argument: " << "lb has wrong dimension (it should be " + |
84 |
|
|
std::to_string(nx_) + ")"); |
85 |
|
|
} |
86 |
|
✗ |
if (static_cast<std::size_t>(ub.size()) != nx_) { |
87 |
|
✗ |
throw_pretty( |
88 |
|
|
"Invalid argument: " << "ub has wrong dimension (it should be " + |
89 |
|
|
std::to_string(nx_) + ")"); |
90 |
|
|
} |
91 |
|
✗ |
if (static_cast<std::size_t>(xinit.size()) != nx_) { |
92 |
|
✗ |
throw_pretty( |
93 |
|
|
"Invalid argument: " << "xinit has wrong dimension (it should be " + |
94 |
|
|
std::to_string(nx_) + ")"); |
95 |
|
|
} |
96 |
|
|
|
97 |
|
|
// We need to enforce feasible warm-starting of the algorithm |
98 |
|
✗ |
for (std::size_t i = 0; i < nx_; ++i) { |
99 |
|
✗ |
x_(i) = std::max(std::min(xinit(i), ub(i)), lb(i)); |
100 |
|
|
} |
101 |
|
|
|
102 |
|
|
// Start the numerical iterations |
103 |
|
✗ |
for (std::size_t k = 0; k < maxiter_; ++k) { |
104 |
|
✗ |
solution_.clamped_idx.clear(); |
105 |
|
✗ |
solution_.free_idx.clear(); |
106 |
|
|
// Compute the Cauchy point and active set |
107 |
|
✗ |
g_ = q; |
108 |
|
✗ |
g_.noalias() += H * x_; |
109 |
|
✗ |
for (std::size_t j = 0; j < nx_; ++j) { |
110 |
|
✗ |
const double gj = g_(j); |
111 |
|
✗ |
const double xj = x_(j); |
112 |
|
✗ |
const double lbj = lb(j); |
113 |
|
✗ |
const double ubj = ub(j); |
114 |
|
✗ |
if ((xj == lbj && gj > 0.) || (xj == ubj && gj < 0.)) { |
115 |
|
✗ |
solution_.clamped_idx.push_back(j); |
116 |
|
|
} else { |
117 |
|
✗ |
solution_.free_idx.push_back(j); |
118 |
|
|
} |
119 |
|
|
} |
120 |
|
|
|
121 |
|
|
// Compute the search direction as Newton step along the free space |
122 |
|
✗ |
nf_ = solution_.free_idx.size(); |
123 |
|
✗ |
nc_ = solution_.clamped_idx.size(); |
124 |
|
✗ |
Eigen::VectorBlock<Eigen::VectorXd> xf = xo_.head(nf_); |
125 |
|
✗ |
Eigen::VectorBlock<Eigen::VectorXd> xc = xo_.tail(nc_); |
126 |
|
✗ |
Eigen::VectorBlock<Eigen::VectorXd> dxf = dxo_.head(nf_); |
127 |
|
✗ |
Eigen::VectorBlock<Eigen::VectorXd> qf = qo_.head(nf_); |
128 |
|
✗ |
Eigen::Block<Eigen::MatrixXd> Hff = Ho_.topLeftCorner(nf_, nf_); |
129 |
|
✗ |
Eigen::Block<Eigen::MatrixXd> Hfc = Ho_.topRightCorner(nf_, nc_); |
130 |
|
✗ |
for (std::size_t i = 0; i < nf_; ++i) { |
131 |
|
✗ |
const std::size_t fi = solution_.free_idx[i]; |
132 |
|
✗ |
qf(i) = q(fi); |
133 |
|
✗ |
xf(i) = x_(fi); |
134 |
|
✗ |
for (std::size_t j = 0; j < nf_; ++j) { |
135 |
|
✗ |
Hff(i, j) = H(fi, solution_.free_idx[j]); |
136 |
|
|
} |
137 |
|
✗ |
for (std::size_t j = 0; j < nc_; ++j) { |
138 |
|
✗ |
const std::size_t cj = solution_.clamped_idx[j]; |
139 |
|
✗ |
xc(j) = x_(cj); |
140 |
|
✗ |
Hfc(i, j) = H(fi, cj); |
141 |
|
|
} |
142 |
|
|
} |
143 |
|
✗ |
if (reg_ != 0.) { |
144 |
|
✗ |
Hff.diagonal().array() += reg_; |
145 |
|
|
} |
146 |
|
✗ |
Hff_inv_llt_.compute(Hff); |
147 |
|
✗ |
const Eigen::ComputationInfo& info = Hff_inv_llt_.info(); |
148 |
|
✗ |
if (info != Eigen::Success) { |
149 |
|
✗ |
throw_pretty("backward_error"); |
150 |
|
|
} |
151 |
|
✗ |
solution_.Hff_inv.setIdentity(nf_, nf_); |
152 |
|
✗ |
Hff_inv_llt_.solveInPlace(solution_.Hff_inv); |
153 |
|
✗ |
qf.noalias() += Hff * xf; |
154 |
|
✗ |
if (nc_ != 0) { |
155 |
|
✗ |
qf.noalias() += Hfc * xc; |
156 |
|
|
} |
157 |
|
✗ |
dxf = -qf; |
158 |
|
✗ |
Hff_inv_llt_.solveInPlace(dxf); |
159 |
|
✗ |
dx_.setZero(); |
160 |
|
✗ |
for (std::size_t i = 0; i < nf_; ++i) { |
161 |
|
✗ |
dx_(solution_.free_idx[i]) = dxf(i); |
162 |
|
✗ |
g_(solution_.free_idx[i]) = -qf(i); |
163 |
|
|
} |
164 |
|
|
|
165 |
|
|
// Try different step lengths |
166 |
|
✗ |
fold_ = 0.5 * x_.dot(H * x_) + q.dot(x_); |
167 |
|
✗ |
for (std::vector<double>::const_iterator it = alphas_.begin(); |
168 |
|
✗ |
it != alphas_.end(); ++it) { |
169 |
|
✗ |
double steplength = *it; |
170 |
|
✗ |
for (std::size_t i = 0; i < nx_; ++i) { |
171 |
|
✗ |
xnew_(i) = |
172 |
|
✗ |
std::max(std::min(x_(i) + steplength * dx_(i), ub(i)), lb(i)); |
173 |
|
|
} |
174 |
|
✗ |
fnew_ = 0.5 * xnew_.dot(H * xnew_) + q.dot(xnew_); |
175 |
|
✗ |
if (fold_ - fnew_ > th_acceptstep_ * g_.dot(x_ - xnew_)) { |
176 |
|
✗ |
x_ = xnew_; |
177 |
|
✗ |
break; |
178 |
|
|
} |
179 |
|
|
} |
180 |
|
|
|
181 |
|
|
// Check convergence |
182 |
|
✗ |
if (qf.lpNorm<Eigen::Infinity>() <= th_grad_) { |
183 |
|
✗ |
solution_.x = x_; |
184 |
|
✗ |
return solution_; |
185 |
|
|
} |
186 |
|
|
} |
187 |
|
✗ |
solution_.x = x_; |
188 |
|
✗ |
return solution_; |
189 |
|
|
} |
190 |
|
|
|
191 |
|
✗ |
const BoxQPSolution& BoxQP::get_solution() const { return solution_; } |
192 |
|
|
|
193 |
|
✗ |
std::size_t BoxQP::get_nx() const { return nx_; } |
194 |
|
|
|
195 |
|
✗ |
std::size_t BoxQP::get_maxiter() const { return maxiter_; } |
196 |
|
|
|
197 |
|
✗ |
double BoxQP::get_th_acceptstep() const { return th_acceptstep_; } |
198 |
|
|
|
199 |
|
✗ |
double BoxQP::get_th_grad() const { return th_grad_; } |
200 |
|
|
|
201 |
|
✗ |
double BoxQP::get_reg() const { return reg_; } |
202 |
|
|
|
203 |
|
✗ |
const std::vector<double>& BoxQP::get_alphas() const { return alphas_; } |
204 |
|
|
|
205 |
|
✗ |
void BoxQP::set_nx(const std::size_t nx) { |
206 |
|
✗ |
nx_ = nx; |
207 |
|
✗ |
x_.conservativeResize(nx); |
208 |
|
✗ |
xnew_.conservativeResize(nx); |
209 |
|
✗ |
g_.conservativeResize(nx); |
210 |
|
✗ |
dx_.conservativeResize(nx); |
211 |
|
✗ |
xo_.conservativeResize(nx); |
212 |
|
✗ |
dxo_.conservativeResize(nx); |
213 |
|
✗ |
qo_.conservativeResize(nx); |
214 |
|
✗ |
Ho_.conservativeResize(nx, nx); |
215 |
|
|
} |
216 |
|
|
|
217 |
|
✗ |
void BoxQP::set_maxiter(const std::size_t maxiter) { maxiter_ = maxiter; } |
218 |
|
|
|
219 |
|
✗ |
void BoxQP::set_th_acceptstep(const double th_acceptstep) { |
220 |
|
✗ |
if (0. >= th_acceptstep && th_acceptstep >= 0.5) { |
221 |
|
✗ |
throw_pretty( |
222 |
|
|
"Invalid argument: " << "th_acceptstep value should between 0 and 0.5"); |
223 |
|
|
} |
224 |
|
✗ |
th_acceptstep_ = th_acceptstep; |
225 |
|
|
} |
226 |
|
|
|
227 |
|
✗ |
void BoxQP::set_th_grad(const double th_grad) { |
228 |
|
✗ |
if (0. > th_grad) { |
229 |
|
✗ |
throw_pretty("Invalid argument: " << "th_grad value has to be positive."); |
230 |
|
|
} |
231 |
|
✗ |
th_grad_ = th_grad; |
232 |
|
|
} |
233 |
|
|
|
234 |
|
✗ |
void BoxQP::set_reg(const double reg) { |
235 |
|
✗ |
if (0. > reg) { |
236 |
|
✗ |
throw_pretty("Invalid argument: " << "reg value has to be positive."); |
237 |
|
|
} |
238 |
|
✗ |
reg_ = reg; |
239 |
|
|
} |
240 |
|
|
|
241 |
|
✗ |
void BoxQP::set_alphas(const std::vector<double>& alphas) { |
242 |
|
✗ |
double prev_alpha = alphas[0]; |
243 |
|
✗ |
if (prev_alpha != 1.) { |
244 |
|
✗ |
std::cerr << "Warning: alpha[0] should be 1" << std::endl; |
245 |
|
|
} |
246 |
|
✗ |
for (std::size_t i = 1; i < alphas.size(); ++i) { |
247 |
|
✗ |
double alpha = alphas[i]; |
248 |
|
✗ |
if (0. >= alpha) { |
249 |
|
✗ |
throw_pretty("Invalid argument: " << "alpha values has to be positive."); |
250 |
|
|
} |
251 |
|
✗ |
if (alpha >= prev_alpha) { |
252 |
|
✗ |
throw_pretty( |
253 |
|
|
"Invalid argument: " << "alpha values are monotonously decreasing."); |
254 |
|
|
} |
255 |
|
✗ |
prev_alpha = alpha; |
256 |
|
|
} |
257 |
|
✗ |
alphas_ = alphas; |
258 |
|
|
} |
259 |
|
|
|
260 |
|
|
} // namespace crocoddyl |
261 |
|
|
|