10namespace quadratic_programming {
23 eigen_assert(
nx>0 &&
"AT LEAST ONE DECISION VARIABLE IS REQUIRED");
24 eigen_assert(
nr>0 &&
"AT LEAST ONE OBJECTIVE ROW IS REQUIRED");
25 Qy =
Q = MatrixXs::Zero(
nr,
nx);
26 ry =
r = VectorXs::Zero(
nr);
27 Cy = MatrixXs::Zero(0,
nx);
28 dy = VectorXs::Zero(0);
29 Z = MatrixXs::Identity(
nx,
nx);
36template<
class D1,
class D2>
38 const Eigen::EigenBase<D1>& Q,
39 const Eigen::EigenBase<D2>& r,
40 const Scalar& tolerance
42:
Solver<Scalar>(Q.cols(), Q.rows(), tolerance)
50template<
class D1,
class D2>
52 const Eigen::EigenBase<D1>& Q,
53 const Eigen::EigenBase<D2>& r
57 eigen_assert(Q.rows()==nr &&
"Q MATRIX HAS WRONG NUMBER OF ROWS");
58 eigen_assert(Q.cols()==nx &&
"Q MATRIX HAS WRONG NUMBER OF COLUMNS");
59 eigen_assert(r.rows()==nr &&
"R VECTOR HAS WRONG NUMBER OF ROWS");
62 EigenOpt_QUADPROG_DBG(
"Q=" << std::endl << this->Q << std::endl <<
"and vector r=" << std::endl << this->r);
68 ry = this->r - this->Q * xeq;
71 Qy = MatrixXs::Zero(nr, 0);
72 ry = VectorXs::Zero(nr);
83 #ifdef USE_QR_INSTEAD_OF_SVD
84 yu = Qy.fullPivHouseholderQr().solve(ry);
87 yu = Qy.jacobiSvd(Eigen::ComputeThinU|Eigen::ComputeThinV).solve(ry);
91 yu = VectorXs::Zero(0);
93 EigenOpt_QUADPROG_DBG(
"Unconstrained minimum:" << std::endl <<
"y: " << yu.transpose() << std::endl <<
"x: " << (xeq+Z*yu).transpose());
106 for(
int i=0; i<mi; i++)
111template<
class Scalar>
114 Z = MatrixXs::Identity(nx, nx);
115 xeq = VectorXs::Zero(nx);
116 Cy = MatrixXs::Zero(0,ny);
117 dy = VectorXs::Zero(0);
122 updateObjective(Q, r);
126template<
class Scalar>
127template<
class D1,
class D2>
129 const Eigen::EigenBase<D1>& C,
130 const Eigen::EigenBase<D2>& d
133 return setConstraints(MatrixXs(0, nx), VectorXs(0), C, d);
137template<
class Scalar>
138template<
class D1,
class D2,
class D3,
class D4>
140 const Eigen::MatrixBase<D1>& A,
141 const Eigen::MatrixBase<D2>& b,
142 const Eigen::EigenBase<D3>& C,
143 const Eigen::EigenBase<D4>& d
147 eigen_assert(A.cols()==nx &&
"A MATRIX HAS WRONG NUMBER OF COLUMNS");
148 eigen_assert(A.rows()==b.rows() &&
"A MATRIX AND b VECTOR HAVE DIFFERENT NUMBER OF ROWS");
153 Z = MatrixXs::Identity(nx, nx);
154 xeq = VectorXs::Zero(nx);
158 updateObjective(Q, r);
165 #ifdef USE_QR_INSTEAD_OF_SVD
174 if(!(A*xeq-b).isZero(tol)) {
186 updateObjective(Q, r);
191 return updateInequalities(C, d);
195template<
class Scalar>
196template<
class D1,
class D2>
198 const Eigen::EigenBase<D1>& C,
199 const Eigen::EigenBase<D2>& d
203 eigen_assert(C.cols()==nx &&
"C MATRIX HAS WRONG NUMBER OF COLUMNS");
204 eigen_assert(C.rows()==d.rows() &&
"C MATRIX AND D VECTOR HAVE DIFFERENT NUMBER OF ROWS");
208 EigenOpt_QUADPROG_DBG(
"The constraints are C=" << std::endl << Cy << std::endl <<
"and d=" << std::endl << dy);
219 Cy = MatrixXs::Zero(C.rows(), 0);
220 dy = VectorXs::Zero(C.rows());
224 Cy = MatrixXs::Zero(0, ny);
225 dy = VectorXs::Zero(0);
227 EigenOpt_QUADPROG_DBG(
"The constraints in y are Cy=" << std::endl << Cy << std::endl <<
"and dy=" << std::endl << dy);
237 std::string simplex_error;
243 if((Cy*yk-dy).maxCoeff() > tol) {
254 if((MatrixXs(C)*xeq-VectorXs(d)).maxCoeff() > tol) {
255 EigenOpt_QUADPROG_DBG(
"Equalities fully constrain the decision vector, but xeq is not feasible for the inequalities: C*xeq-d = " << (MatrixXs(C)*xeq-VectorXs(d)).transpose());
271template<
class Scalar>
287 x = Z * VectorXs(x) + xeq;
295template<
class Scalar>
303 for(
unsigned int i=0; i<active.size(); i++) {
304 Ca.row(i) = Cy.row(active[i]);
305 da(i) = dy(active[i]);
307 #ifdef USE_QR_INSTEAD_OF_SVD
308 VectorXd ya = Ca.fullPivHouseholderQr().solve(da);
311 VectorXs ya = Ca.jacobiSvd(Eigen::ComputeThinU|Eigen::ComputeThinV).solve(da);
313 if((Cy*ya - dy).maxCoeff() <= 0) {
324 if((Cy*yk - dy).maxCoeff() <= 0) {
326 return initActiveSet();
330 if(y.rows() == ny && (Cy*y - dy).maxCoeff() <= 0) {
333 return initActiveSet();
338 std::string simplex_error;
346 return initActiveSet();
350template<
class Scalar>
352 eigen_assert(active.size() == 0 &&
"IF YOU CALLED initActiveSet(), THE ACTIVE SET SHOULD'VE BEEN EMPTY");
354 VectorXs e = Cy*yk - dy;
358 inactive.reserve(ny);
361 for(
unsigned int i=0; i<e.rows(); i++) {
366 else if(e(i) <= -tol) {
368 inactive.push_back(i);
377 Ca.resize(active.size(), ny);
378 da.resize(active.size());
379 for(
unsigned int i=0; i<active.size(); i++) {
380 Ca.row(i) = Cy.row(active[i]);
381 da(i) = dy(active[i]);
387template<
class Scalar>
400 yk = VectorXs::Zero(ny);
410 int na = active.size();
413#ifdef USE_QR_INSTEAD_OF_SVD
420 Eigen::ColPivHouseholderQR<MatrixXs> CaT_qr(nx, na);
424 Eigen::JacobiSVD<MatrixXs> Ca_svd(na, nx, Eigen::ComputeFullV);
426 Eigen::JacobiSVD<MatrixXs> CaT_svd(nx, na, Eigen::ComputeThinU|Eigen::ComputeThinV);
433 for(
unsigned int iter=0; iter<1e6; iter++) {
451 #ifdef USE_QR_INSTEAD_OF_SVD
455 p = VectorXs::Zero(ny);
458 CaT_qr.compute(Ca.transpose());
459 auto W = MatrixXs(CaT_qr.householderQ()).rightCols(ny-na);
460 p = W * (Qy*W).colPivHouseholderQr().solve(ry-Qy*yk);
461 EigenOpt_QUADPROG_DBG(
"Computed step p; the constraint equation is s.t. Ca*p = " << (Ca*p).transpose() <<
" (expecting zeros everywhere)");
465 if(Ca_svd.cols() == Ca_svd.rank()) {
468 p = VectorXs::Zero(ny);
476 auto W = Ca_svd.matrixV().rightCols(Ca_svd.cols()-Ca_svd.rank());
477 p = W * (Qy*W).bdcSvd(Eigen::ComputeThinU|Eigen::ComputeThinV).solve(ry-Qy*yk);
478 EigenOpt_QUADPROG_DBG(
"Computed step p; the constraint equation is s.t. Ca*p = " << (Ca*p).transpose() <<
" (expecting zeros everywhere)");
506 for(
int i=0; i<inactive.size(); i++) {
507 auto& idx = inactive[i];
508 auto ci = Cy.row(idx);
509 double cp = p.dot(ci);
511 double ai = (dy(idx)-yk.dot(ci))/cp;
524 yk.noalias() += alpha*p;
526 Ca.conservativeResize(na+1,Cy.cols());
527 Ca.row(na) = Cy.row(inactive[idxact]);
528 da.conservativeResize(na+1);
529 da(na) = dy(inactive[idxact]);
532 active.push_back(inactive[idxact]);
533 inactive.erase(inactive.begin()+idxact);
549 #ifdef USE_QR_INSTEAD_OF_SVD
550 CaT_qr.compute(Ca.transpose());
551 VectorXs half_mu = CaT_qr.solve(Qy.transpose() * (ry-Qy*yk));
553 CaT_svd.compute(Ca.transpose());
554 VectorXs half_mu = CaT_svd.solve(Qy.transpose() * (ry-Qy*yk));
559 for(
unsigned i=0; i<na; i++) {
560 if(half_mu(i) < mumin) {
576 Ca.block(idx,0,nc,Ca.cols()) = Ca.bottomRows(nc).eval();
577 da.segment(idx,nc) = da.tail(nc).eval();
582 Ca.conservativeResize(na,Ca.cols());
583 da.conservativeResize(na);
585 inactive.push_back(active[idx]);
586 active.erase(active.begin()+idx);
591 EigenOpt_QUADPROG_DBG(
"Active constraints violations (positive = violated):" << std::endl << (Ca*yk-da).transpose());
592 EigenOpt_QUADPROG_DBG(
"All constraints violations (positive = violated):" << std::endl << (Cy*yk-dy).transpose());
599 EigenOpt_QUADPROG_DBG(
"Active constraints violations (positive = violated):" << std::endl << (Ca*yk-da).transpose());
600 EigenOpt_QUADPROG_DBG(
"All constraints violations (positive = violated):" << std::endl << (Cy*yk-dy).transpose());
604 throw std::runtime_error(
"Could not find a solution within maximum number of iterations");
613#undef EigenOpt_QUADPROG_DBG
614#undef EigenOpt_QUADPROG_BREAK
Quadratic Programming solver using active set and null-space projections.
bool solve(Eigen::MatrixBase< D > &x)
Solve the optimization problem.
MatrixXs Qy
Modified matrix of coefficients for the objective function.
bool guess(Eigen::MatrixBase< D > &y)
Find an initial solution to start the active-set algorithm.
const int nr
Number of rows in the objective.
VectorXs xeq
A particular solution to the equality constraints.
VectorXs ry
Modified vector of coefficients for the objective function.
VectorXs r
Vector of coefficients for the objective function.
VectorXs yu
Unconstrained minimum of the objective.
MatrixXs Cy
Modified inequality constraints matrix.
bool updateInequalities(const Eigen::EigenBase< D1 > &C, const Eigen::EigenBase< D2 > &d)
Update inequality constraints to the problem.
void resetActiveSet()
Clear the current active set, preventing warm starts.
VectorXs dy
Modified inequality constraints vector.
VectorXs yk
Current guess of the decision variables.
MatrixXs Z
Matrix that projects into the kernel of the equality constraints matrix.
const int nx
Number of decision variables.
void clearConstraints()
Removes constraints and clear the active set.
bool setConstraints(const Eigen::EigenBase< D1 > &C, const Eigen::EigenBase< D2 > &d)
Add inequality constraints to the problem.
Solver(int xdim, int rdim, const Scalar &tolerance)
Give dimensions for x, Q and r explicitly.
bool solveY(Eigen::MatrixBase< D > &y)
Solve the problem in the y variable.
bool initActiveSet()
Initialize the active-set from the current solution.
void updateObjective(const Eigen::EigenBase< D1 > &Q, const Eigen::EigenBase< D2 > &r)
Updates the objective matrix.
MatrixXs Q
Matrix of coefficients for the objective function.
bool minimize(const Eigen::MatrixBase< D1 > &f, const Eigen::MatrixBase< D2 > &A, const Eigen::MatrixBase< D3 > &b, const Eigen::MatrixBase< D4 > &C, const Eigen::MatrixBase< D5 > &d, Eigen::DenseBase< D6 > &x, std::string &halt_reason, const Scalar &small_number, const Scalar &large_number=-1)
Solve a constrained linear optimization problem.
Main namespace of this project, containing all utilities.
void svd_projection(const Eigen::MatrixBase< D1 > &A, const Eigen::MatrixBase< D2 > &b, Eigen::Matrix< Scalar, Eigen::Dynamic, Eigen::Dynamic > &Z, Eigen::Matrix< Scalar, Eigen::Dynamic, 1 > &xeq)
Return all solutions to a linear system, using a kernel-based parameterization.
void qr_projection(const Eigen::MatrixBase< D1 > &A, const Eigen::MatrixBase< D2 > &b, Eigen::Matrix< Scalar, Eigen::Dynamic, Eigen::Dynamic > &Z, Eigen::Matrix< Scalar, Eigen::Dynamic, 1 > &xeq)
Return all solutions to a linear system, using a kernel-based parameterization.
#define EigenOpt_QUADPROG_DBG(x)
#define EigenOpt_QUADPROG_BREAK
#define EigenOpt_SIMPLEX_DBG(x)