GCC Code Coverage Report


Directory: ./
File: src/path-optimization/spline-gradient-based/eiquadprog_2011.hpp
Date: 2025-03-10 11:18:21
Exec Total Coverage
Lines: 0 206 0.0%
Branches: 0 338 0.0%

Line Branch Exec Source
1 #ifndef _EIGEN_QUADSOLVE_HPP_
2 #define _EIGEN_QUADSOLVE_HPP_
3
4 /*
5 FILE eiquadprog.hh
6
7 NOTE: this is a modified of uQuadProg++ package, working with Eigen data
8 structures. uQuadProg++ is itself a port made by Angelo Furfaro of QuadProg++
9 originally developed by Luca Di Gaspero, working with ublas data structures.
10
11 The quadprog_solve() function implements the algorithm of Goldfarb and Idnani
12 for the solution of a (convex) Quadratic Programming problem
13 by means of a dual method.
14
15 The problem is in the form:
16
17 min 0.5 * x G x + g0 x
18 s.t.
19 CE^T x + ce0 = 0
20 CI^T x + ci0 >= 0
21
22 The matrix and vectors dimensions are as follows:
23 G: n * n
24 g0: n
25
26 CE: n * p
27 ce0: p
28
29 CI: n * m
30 ci0: m
31
32 x: n
33
34 The function will return the cost of the solution written in the x vector or
35 std::numeric_limits::infinity() if the problem is infeasible. In the latter
36 case the value of the x vector is not correct.
37
38 References: D. Goldfarb, A. Idnani. A numerically stable dual method for
39 solving strictly convex quadratic programs. Mathematical Programming 27 (1983)
40 pp. 1-33.
41
42 Notes:
43 1. pay attention in setting up the vectors ce0 and ci0.
44 If the constraints of your problem are specified in the form
45 A^T x = b and C^T x >= d, then you should set ce0 = -b and ci0 = -d.
46 2. The matrix G is modified within the function since it is used to compute
47 the G = L^T L cholesky factorization for further computations inside the
48 function. If you need the original matrix G you should make a copy of it and
49 pass the copy to the function.
50
51
52 The author will be grateful if the researchers using this software will
53 acknowledge the contribution of this modified function and of Di Gaspero's
54 original version in their research papers.
55
56
57 LICENSE
58
59 Copyright (2011) Benjamin Stephens
60 Copyright (2010) Gael Guennebaud
61 Copyright (2008) Angelo Furfaro
62 Copyright (2006) Luca Di Gaspero
63
64
65 This file is a porting of QuadProg++ routine, originally developed
66 by Luca Di Gaspero, exploiting uBlas data structures for vectors and
67 matrices instead of native C++ array.
68
69 uquadprog is free software; you can redistribute it and/or modify
70 it under the terms of the GNU General Public License as published by
71 the Free Software Foundation; either version 2 of the License, or
72 (at your option) any later version.
73
74 uquadprog is distributed in the hope that it will be useful,
75 but WITHOUT ANY WARRANTY; without even the implied warranty of
76 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
77 GNU General Public License for more details.
78
79 You should have received a copy of the GNU General Public License
80 along with uquadprog; if not, write to the Free Software
81 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
82
83 */
84
85 #include <Eigen/Cholesky>
86 #include <Eigen/Core>
87 #include <iostream>
88
89 namespace Eigen {
90 enum QuadProgStatus { CONVERGED, CONSTRAINT_LINEARLY_DEPENDENT, UNBOUNDED };
91
92 // namespace internal {
93
94 template <typename Scalar>
95 ✗ inline Scalar distance(Scalar a, Scalar b) {
96 Scalar a1, b1, t;
97 ✗ a1 = std::abs(a);
98 ✗ b1 = std::abs(b);
99 ✗ if (a1 > b1) {
100 ✗ t = (b1 / a1);
101 ✗ return a1 * std::sqrt(1.0 + t * t);
102 ✗ } else if (b1 > a1) {
103 ✗ t = (a1 / b1);
104 ✗ return b1 * std::sqrt(1.0 + t * t);
105 }
106 ✗ return a1 * std::sqrt(2.0);
107 }
108
109 // }
110
111 ✗ inline void compute_d(VectorXd& d, const MatrixXd& J, const VectorXd& np) {
112 ✗ d = J.adjoint() * np;
113 }
114
115 ✗ inline void update_z(VectorXd& z, const MatrixXd& J, const VectorXd& d,
116 int iq) {
117 ✗ z = J.rightCols(z.size() - iq) * d.tail(d.size() - iq);
118 }
119
120 ✗ inline void update_r(const MatrixXd& R, VectorXd& r, const VectorXd& d,
121 int iq) {
122 ✗ r.head(iq) =
123 ✗ R.topLeftCorner(iq, iq).triangularView<Upper>().solve(d.head(iq));
124 }
125
126 bool add_constraint(MatrixXd& R, MatrixXd& J, VectorXd& d, int& iq,
127 double& R_norm);
128 void delete_constraint(MatrixXd& R, MatrixXd& J, VectorXi& A, VectorXd& u,
129 int p, int& iq, int l);
130
131 /* solve_quadprog2 is used when the Cholesky decomposition of the G matrix is
132 * precomputed */
133 double solve_quadprog2(const LLT<MatrixXd, Lower>& chol, double c1,
134 const VectorXd& g0, const MatrixXd& CE,
135 const VectorXd& ce0, const MatrixXd& CI,
136 const VectorXd& ci0, VectorXd& x, VectorXi& A, int& q,
137 QuadProgStatus& status);
138
139 /* solve_quadprog is used for on-demand QP solving */
140 inline double solve_quadprog(MatrixXd& G, VectorXd& g0, const MatrixXd& CE,
141 const VectorXd& ce0, const MatrixXd& CI,
142 const VectorXd& ci0, VectorXd& x,
143 VectorXi& activeSet, int& activeSetSize,
144 QuadProgStatus& status) {
145 LLT<MatrixXd, Lower> chol(G.cols());
146 double c1;
147
148 /* compute the trace of the original matrix G */
149 c1 = G.trace();
150
151 /* decompose the matrix G in the form LL^T */
152 chol.compute(G);
153
154 return solve_quadprog2(chol, c1, g0, CE, ce0, CI, ci0, x, activeSet,
155 activeSetSize, status);
156 }
157
158 /* solve_quadprog2 is used for when the Cholesky decomposition of G is
159 * pre-computed
160 * @param A Output vector containing the indexes of the active constraints.
161 * @param q Output value representing the size of the active set.
162 */
163 ✗ inline double solve_quadprog2(const LLT<MatrixXd, Lower>& chol, double c1,
164 const VectorXd& g0, const MatrixXd& CE,
165 const VectorXd& ce0, const MatrixXd& CI,
166 const VectorXd& ci0, VectorXd& x, VectorXi& A,
167 int& q, QuadProgStatus& status) {
168 int i, j, k, l; /* indices */
169 MatrixXd::Index ip, me, mi;
170 ✗ VectorXd::Index n = g0.size();
171 ✗ MatrixXd::Index p = CE.cols();
172 ✗ MatrixXd::Index m = CI.cols();
173 ✗ MatrixXd R(g0.size(), g0.size()), J(g0.size(), g0.size());
174
175 ✗ VectorXd s(m + p), z(n), r(m + p), d(n), np(n), u(m + p);
176 ✗ VectorXd x_old(n), u_old(m + p);
177 double f_value, psi, c2, sum, ss, R_norm;
178 ✗ const double inf = std::numeric_limits<double>::infinity();
179 double t, t1, t2; /* t is the step length, which is the minimum of the partial
180 * step length t1 and the full step length t2 */
181 // VectorXi A(m + p); // Del Prete: active set is now an output
182 // parameter
183 ✗ if (A.size() != m + p) A.resize(m + p);
184 ✗ VectorXi A_old(m + p), iai(m + p), iaexcl(m + p);
185 // int q;
186 ✗ int iq, iter = 0;
187
188 ✗ me = p; /* number of equality constraints */
189 ✗ mi = m; /* number of inequality constraints */
190 ✗ q = 0; /* size of the active set A (containing the indices of the active
191 constraints) */
192
193 /*
194 * Preprocessing phase
195 */
196
197 /* initialize the matrix R */
198 ✗ d.setZero();
199 ✗ R.setZero();
200 ✗ R_norm = 1.0; /* this variable will hold the norm of the matrix R */
201
202 /* compute the inverse of the factorized matrix G^-1, this is the initial
203 * value for H */
204 // J = L^-T
205 ✗ J.setIdentity();
206 ✗ J = chol.matrixU().solve(J);
207 ✗ c2 = J.trace();
208 #ifdef TRACE_SOLVER
209 print_matrix("J", J, n);
210 #endif
211
212 /* c1 * c2 is an estimate for cond(G) */
213
214 /*
215 * Find the unconstrained minimizer of the quadratic form 0.5 * x G x + g0 x
216 * this is a feasible point in the dual space
217 * x = G^-1 * g0
218 */
219 ✗ x = chol.solve(g0);
220 ✗ x = -x;
221 /* and compute the current solution value */
222 ✗ f_value = 0.5 * g0.dot(x);
223 #ifdef TRACE_SOLVER
224 std::cerr << "Unconstrained solution: " << f_value << std::endl;
225 print_vector("x", x, n);
226 #endif
227
228 /* Add equality constraints to the working set A */
229 ✗ iq = 0;
230 ✗ for (i = 0; i < me; i++) {
231 ✗ np = CE.col(i);
232 ✗ compute_d(d, J, np);
233 ✗ update_z(z, J, d, iq);
234 ✗ update_r(R, r, d, iq);
235 #ifdef TRACE_SOLVER
236 print_matrix("R", R, iq);
237 print_vector("z", z, n);
238 print_vector("r", r, iq);
239 print_vector("d", d, n);
240 #endif
241
242 /* compute full step length t2: i.e., the minimum step in primal space s.t.
243 the contraint becomes feasible */
244 ✗ t2 = 0.0;
245 ✗ if (std::abs(z.dot(z)) >
246 ✗ std::numeric_limits<double>::epsilon()) // i.e. z != 0
247 ✗ t2 = (-np.dot(x) - ce0(i)) / z.dot(np);
248
249 ✗ x += t2 * z;
250
251 /* set u = u+ */
252 ✗ u(iq) = t2;
253 ✗ u.head(iq) -= t2 * r.head(iq);
254
255 /* compute the new solution value */
256 ✗ f_value += 0.5 * (t2 * t2) * z.dot(np);
257 ✗ A(i) = -i - 1;
258
259 ✗ if (!add_constraint(R, J, d, iq, R_norm)) {
260 // FIXME: it should raise an error
261 // Equality constraints are linearly dependent
262 ✗ status = CONSTRAINT_LINEARLY_DEPENDENT;
263 ✗ return f_value;
264 }
265 }
266
267 /* set iai = K \ A */
268 ✗ for (i = 0; i < mi; i++) iai(i) = i;
269
270 ✗ l1:
271 ✗ iter++;
272 #ifdef TRACE_SOLVER
273 print_vector("x", x, n);
274 #endif
275 /* step 1: choose a violated constraint */
276 ✗ for (i = me; i < iq; i++) {
277 ✗ ip = A(i);
278 ✗ iai(ip) = -1;
279 }
280
281 /* compute s(x) = ci^T * x + ci0 for all elements of K \ A */
282 ✗ ss = 0.0;
283 ✗ psi = 0.0; /* this value will contain the sum of all infeasibilities */
284 ✗ ip = 0; /* ip will be the index of the chosen violated constraint */
285 ✗ for (i = 0; i < mi; i++) {
286 ✗ iaexcl(i) = 1;
287 ✗ sum = CI.col(i).dot(x) + ci0(i);
288 ✗ s(i) = sum;
289 ✗ psi += std::min(0.0, sum);
290 }
291 #ifdef TRACE_SOLVER
292 print_vector("s", s, mi);
293 #endif
294
295 ✗ if (std::abs(psi) <=
296 ✗ mi * std::numeric_limits<double>::epsilon() * c1 * c2 * 100.0) {
297 /* numerically there are not infeasibilities anymore */
298 ✗ q = iq;
299 ✗ status = CONVERGED;
300 ✗ return f_value;
301 }
302
303 /* save old values for u, x and A */
304 ✗ u_old.head(iq) = u.head(iq);
305 ✗ A_old.head(iq) = A.head(iq);
306 ✗ x_old = x;
307
308 ✗ l2: /* Step 2: check for feasibility and determine a new S-pair */
309 ✗ for (i = 0; i < mi; i++) {
310 ✗ if (s(i) < ss && iai(i) != -1 && iaexcl(i)) {
311 ✗ ss = s(i);
312 ✗ ip = i;
313 }
314 }
315 ✗ if (ss >= 0.0) {
316 ✗ q = iq;
317 ✗ status = CONVERGED;
318 ✗ return f_value;
319 }
320
321 /* set np = n(ip) */
322 ✗ np = CI.col(ip);
323 /* set u = (u 0)^T */
324 ✗ u(iq) = 0.0;
325 /* add ip to the active set A */
326 ✗ A(iq) = ip;
327
328 #ifdef TRACE_SOLVER
329 std::cerr << "Trying with constraint " << ip << std::endl;
330 print_vector("np", np, n);
331 #endif
332
333 ✗ l2a: /* Step 2a: determine step direction */
334 /* compute z = H np: the step direction in the primal space (through J, see
335 * the paper) */
336 ✗ compute_d(d, J, np);
337 ✗ update_z(z, J, d, iq);
338 /* compute N* np (if q > 0): the negative of the step direction in the dual
339 * space */
340 ✗ update_r(R, r, d, iq);
341 #ifdef TRACE_SOLVER
342 std::cerr << "Step direction z" << std::endl;
343 print_vector("z", z, n);
344 print_vector("r", r, iq + 1);
345 print_vector("u", u, iq + 1);
346 print_vector("d", d, n);
347 print_ivector("A", A, iq + 1);
348 #endif
349
350 /* Step 2b: compute step length */
351 ✗ l = 0;
352 /* Compute t1: partial step length (maximum step in dual space without
353 * violating dual feasibility */
354 ✗ t1 = inf; /* +inf */
355 /* find the index l s.t. it reaches the minimum of u+(x) / r */
356 ✗ for (k = me; k < iq; k++) {
357 double tmp;
358 ✗ if (r(k) > 0.0 && ((tmp = u(k) / r(k)) < t1)) {
359 ✗ t1 = tmp;
360 ✗ l = A(k);
361 }
362 }
363 /* Compute t2: full step length (minimum step in primal space such that the
364 * constraint ip becomes feasible */
365 ✗ if (std::abs(z.dot(z)) >
366 ✗ std::numeric_limits<double>::epsilon()) // i.e. z != 0
367 ✗ t2 = -s(ip) / z.dot(np);
368 else
369 ✗ t2 = inf; /* +inf */
370
371 /* the step is chosen as the minimum of t1 and t2 */
372 ✗ t = std::min(t1, t2);
373 #ifdef TRACE_SOLVER
374 std::cerr << "Step sizes: " << t << " (t1 = " << t1 << ", t2 = " << t2
375 << ") ";
376 #endif
377
378 /* Step 2c: determine new S-pair and take step: */
379
380 /* case (i): no step in primal or dual space */
381 ✗ if (t >= inf) {
382 /* QPP is infeasible */
383 // FIXME: unbounded to raise
384 ✗ q = iq;
385 #ifdef TRACE_SOLVER
386 std::cerr << "QP unbounded\n";
387 #endif
388 ✗ status = UNBOUNDED;
389 ✗ return inf;
390 }
391 /* case (ii): step in dual space */
392 ✗ if (t2 >= inf) {
393 /* set u = u + t * [-r 1) and drop constraint l from the active set A */
394 ✗ u.head(iq) -= t * r.head(iq);
395 ✗ u(iq) += t;
396 ✗ iai(l) = l;
397 ✗ delete_constraint(R, J, A, u, p, iq, l);
398 #ifdef TRACE_SOLVER
399 std::cerr << " in dual space: " << f_value << std::endl;
400 print_vector("x", x, n);
401 print_vector("z", z, n);
402 print_ivector("A", A, iq + 1);
403 #endif
404 ✗ goto l2a;
405 }
406
407 /* case (iii): step in primal and dual space */
408
409 ✗ x += t * z;
410 /* update the solution value */
411 ✗ f_value += t * z.dot(np) * (0.5 * t + u(iq));
412
413 ✗ u.head(iq) -= t * r.head(iq);
414 ✗ u(iq) += t;
415 #ifdef TRACE_SOLVER
416 std::cerr << " in both spaces: " << f_value << std::endl;
417 print_vector("x", x, n);
418 print_vector("u", u, iq + 1);
419 print_vector("r", r, iq + 1);
420 print_ivector("A", A, iq + 1);
421 #endif
422
423 ✗ if (t == t2) {
424 #ifdef TRACE_SOLVER
425 std::cerr << "Full step has taken " << t << std::endl;
426 print_vector("x", x, n);
427 #endif
428 /* full step has taken */
429 /* add constraint ip to the active set*/
430 ✗ if (!add_constraint(R, J, d, iq, R_norm)) {
431 ✗ iaexcl(ip) = 0;
432 ✗ delete_constraint(R, J, A, u, p, iq, ip);
433 #ifdef TRACE_SOLVER
434 print_matrix("R", R, n);
435 print_ivector("A", A, iq);
436 #endif
437 ✗ for (i = 0; i < m; i++) iai(i) = i;
438 ✗ for (i = 0; i < iq; i++) {
439 ✗ A(i) = A_old(i);
440 ✗ iai(A(i)) = -1;
441 ✗ u(i) = u_old(i);
442 }
443 ✗ x = x_old;
444 ✗ goto l2; /* go to step 2 */
445 } else
446 ✗ iai(ip) = -1;
447 #ifdef TRACE_SOLVER
448 print_matrix("R", R, n);
449 print_ivector("A", A, iq);
450 #endif
451 ✗ goto l1;
452 }
453
454 /* a patial step has taken */
455 #ifdef TRACE_SOLVER
456 std::cerr << "Partial step has taken " << t << std::endl;
457 print_vector("x", x, n);
458 #endif
459 /* drop constraint l */
460 ✗ iai(l) = l;
461 ✗ delete_constraint(R, J, A, u, p, iq, l);
462 #ifdef TRACE_SOLVER
463 print_matrix("R", R, n);
464 print_ivector("A", A, iq);
465 #endif
466
467 ✗ s(ip) = CI.col(ip).dot(x) + ci0(ip);
468
469 #ifdef TRACE_SOLVER
470 print_vector("s", s, mi);
471 #endif
472 ✗ goto l2a;
473 }
474
475 ✗ inline bool add_constraint(MatrixXd& R, MatrixXd& J, VectorXd& d, int& iq,
476 double& R_norm) {
477 ✗ MatrixXd::Index n = J.rows();
478 #ifdef TRACE_SOLVER
479 std::cerr << "Add constraint " << iq << '/';
480 #endif
481 int i, j, k;
482 double cc, ss, h, t1, t2, xny;
483
484 /* we have to find the Givens rotation which will reduce the element
485 d(j) to zero.
486 if it is already zero we don't have to do anything, except of
487 decreasing j */
488 ✗ for (j = n - 1; j >= iq + 1; j--) {
489 /* The Givens rotation is done with the matrix (cc cs, cs -cc).
490 If cc is one, then element (j) of d is zero compared with
491 element (j - 1). Hence we don't have to do anything. If cc is zero, then
492 we just have to switch column (j) and column (j - 1) of J. Since we only
493 switch columns in J, we have to be careful how we update d depending on
494 the sign of gs. Otherwise we have to apply the Givens rotation to these
495 columns. The i - 1 element of d has to be updated to h. */
496 ✗ cc = d(j - 1);
497 ✗ ss = d(j);
498 ✗ h = distance(cc, ss);
499 ✗ if (h == 0.0) continue;
500 ✗ d(j) = 0.0;
501 ✗ ss = ss / h;
502 ✗ cc = cc / h;
503 ✗ if (cc < 0.0) {
504 ✗ cc = -cc;
505 ✗ ss = -ss;
506 ✗ d(j - 1) = -h;
507 } else
508 ✗ d(j - 1) = h;
509 ✗ xny = ss / (1.0 + cc);
510 ✗ for (k = 0; k < n; k++) {
511 ✗ t1 = J(k, j - 1);
512 ✗ t2 = J(k, j);
513 ✗ J(k, j - 1) = t1 * cc + t2 * ss;
514 ✗ J(k, j) = xny * (t1 + J(k, j - 1)) - t2;
515 }
516 }
517 /* update the number of constraints added*/
518 ✗ iq++;
519 /* To update R we have to put the iq components of the d vector
520 into column iq - 1 of R
521 */
522 ✗ R.col(iq - 1).head(iq) = d.head(iq);
523 #ifdef TRACE_SOLVER
524 std::cerr << iq << std::endl;
525 #endif
526
527 ✗ if (std::abs(d(iq - 1)) <= std::numeric_limits<double>::epsilon() * R_norm)
528 // problem degenerate
529 ✗ return false;
530 ✗ R_norm = std::max<double>(R_norm, std::abs(d(iq - 1)));
531 ✗ return true;
532 }
533
534 ✗ inline void delete_constraint(MatrixXd& R, MatrixXd& J, VectorXi& A,
535 VectorXd& u, int p, int& iq, int l) {
536 ✗ MatrixXd::Index n = R.rows();
537 #ifdef TRACE_SOLVER
538 std::cerr << "Delete constraint " << l << ' ' << iq;
539 #endif
540 int i, j, k, qq;
541 double cc, ss, h, xny, t1, t2;
542
543 /* Find the index qq for active constraint l to be removed */
544 ✗ for (i = p; i < iq; i++)
545 ✗ if (A(i) == l) {
546 ✗ qq = i;
547 ✗ break;
548 }
549
550 /* remove the constraint from the active set and the duals */
551 ✗ for (i = qq; i < iq - 1; i++) {
552 ✗ A(i) = A(i + 1);
553 ✗ u(i) = u(i + 1);
554 ✗ R.col(i) = R.col(i + 1);
555 }
556
557 ✗ A(iq - 1) = A(iq);
558 ✗ u(iq - 1) = u(iq);
559 ✗ A(iq) = 0;
560 ✗ u(iq) = 0.0;
561 ✗ for (j = 0; j < iq; j++) R(j, iq - 1) = 0.0;
562 /* constraint has been fully removed */
563 ✗ iq--;
564 #ifdef TRACE_SOLVER
565 std::cerr << '/' << iq << std::endl;
566 #endif
567
568 ✗ if (iq == 0) return;
569
570 ✗ for (j = qq; j < iq; j++) {
571 ✗ cc = R(j, j);
572 ✗ ss = R(j + 1, j);
573 ✗ h = distance(cc, ss);
574 ✗ if (h == 0.0) continue;
575 ✗ cc = cc / h;
576 ✗ ss = ss / h;
577 ✗ R(j + 1, j) = 0.0;
578 ✗ if (cc < 0.0) {
579 ✗ R(j, j) = -h;
580 ✗ cc = -cc;
581 ✗ ss = -ss;
582 } else
583 ✗ R(j, j) = h;
584
585 ✗ xny = ss / (1.0 + cc);
586 ✗ for (k = j + 1; k < iq; k++) {
587 ✗ t1 = R(j, k);
588 ✗ t2 = R(j + 1, k);
589 ✗ R(j, k) = t1 * cc + t2 * ss;
590 ✗ R(j + 1, k) = xny * (t1 + R(j, k)) - t2;
591 }
592 ✗ for (k = 0; k < n; k++) {
593 ✗ t1 = J(k, j);
594 ✗ t2 = J(k, j + 1);
595 ✗ J(k, j) = t1 * cc + t2 * ss;
596 ✗ J(k, j + 1) = xny * (J(k, j) + t1) - t2;
597 }
598 }
599 }
600
601 } // namespace Eigen
602
603 #endif
604