GCC Code Coverage Report


Directory: ./
File: src/path-optimization/spline-gradient-based/eiquadprog_2011.hpp
Date: 2024-08-10 11:29:48
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