QPALM 1.0.0
Proximal Augmented Lagrangian method for Quadratic Programs
nonconvex.c
Go to the documentation of this file.
1/**
2 * @file nonconvex.c
3 * @author Ben Hermans
4 * @brief Routines to deal with nonconvex QPs.
5 * @details The functions in this file serve to set up QPALM for a nonconvex QP. The main routine in
6 * this file computes the minimum eigenvalue of a square matrix, based on lobpcg \cite knyazev2001toward. Furthermore,
7 * some setting updates are performed. In addition, the spectrum of a matrix can be upper bounded using
8 * Gershgorin's circle theorem, which is used in the gamma_boost routine in iteration.c.
9 */
10
11#include "nonconvex.h"
12#include "types.h"
13#include "constants.h"
14#include "global_opts.h"
15#include "lin_alg.h"
16#include "util.h"
17
18#ifdef COMPILE_NONCONVEX
19#include <math.h>
20
21/*TODO: make this a setting */
22#define LOBPCG_TOL 1e-5 /**< Tolerance on the infinity norm of the residual in lobpcg. */
23
24#ifndef M_PI
25 #define M_PI 3.14159265358979323846
26#endif
27
28#define RREF_TOL 1e-8
29
30static c_float min_root_third_order(c_float a, c_float b, c_float c, c_float d)
31{
32 c_float r[3] = {0};
33 c_float di, di_sqrt;
34 if (a == 0)
35 {
36 // Not a cubic polynomial, should not happen
37 # ifdef PRINTING
38 qpalm_eprint("Not a cubic polynomial.");
39 # endif /* ifdef PRINTING */
40 }
41 else if (d == 0)
42 {
43 di = b*b - 4*a*c;
44 if (di < 0)
45 {
46 # ifdef PRINTING
47 qpalm_eprint("Imaginary roots. This should not happen.");
48 # endif /* ifdef PRINTING */
49 }
50 di_sqrt = c_sqrt(di);
51 r[0] = (-b-di_sqrt)/(2*a);
52 r[1] = (-b+di_sqrt)/(2*a);
53 }
54 else
55 {
56 c_float temp, q, p, re, an, r13;
57 temp = 1/a;
58 b = b*temp;
59 c = c*temp;
60 d = d*temp;
61 q = (3.0*c - (b*b))/9.0;
62 p = (-(27.0*d) + b*(9.0*c - 2.0*(b*b)))/54.0;
63 di = q*q*q + p*p;
64 re = b/3.0;
65 if (di > 0)
66 {
67 # ifdef PRINTING
68 qpalm_eprint("Imaginary roots. This should not happen.");
69 # endif /* ifdef PRINTING */
70 }
71 else
72 {
73 q = -q;
74 an = q*q*q;
75 an = c_acos(p/c_sqrt(an));
76 r13 = 2.0*c_sqrt(q);
77 r[0] = -re + r13*c_cos(an/3.0);
78 r[1] = -re + r13*c_cos((an + 2.0*M_PI)/3.0);
79 r[2] = -re + r13*c_cos((an + 4.0*M_PI)/3.0);
80 }
81 }
82
83 if (r[0] <= r[1] && r[0] <= r[2]) return r[0];
84 else return c_min(r[1], r[2]);
85}
86
87
88static int custom_rref(c_float D[3][3])
89{
90 c_float p, temp[3], a[3];
91
92 // First column
93 a[0] = c_absval(D[0][0]); a[1] = c_absval(D[0][1]); a[2] = c_absval(D[0][2]);
94 if (a[0] < a[1] || a[0] < a[2])
95 {
96 if (a[1] > a[2])
97 {
98 if (a[1] < RREF_TOL) return 0;
99 // swap row 0 and 1
100 temp[0] = D[0][0]; temp[1] = D[0][1]; temp[2] = D[0][2];
101 D[0][0] = D[1][0]; D[0][1] = D[1][1]; D[0][2] = D[1][2];
102 D[1][0] = temp[0]; D[1][1] = temp[1]; D[1][2] = temp[2];
103 }
104 else
105 {
106 if (a[2] < RREF_TOL) return 0;
107 // swap row 0 and 2
108 temp[0] = D[0][0]; temp[1] = D[0][1]; temp[2] = D[0][2];
109 D[0][0] = D[2][0]; D[0][1] = D[2][1]; D[0][2] = D[2][2];
110 D[2][0] = temp[0]; D[2][1] = temp[1]; D[2][2] = temp[2];
111 }
112 }
113 else
114 {
115 if (a[0] < RREF_TOL) return 0;
116 }
117
118 p = 1.0/D[0][0];
119 D[0][1] *= p; D[0][2] *= p; D[0][0] = 1.0;
120 D[1][1] -= D[1][0]*D[0][1]; D[1][2] -= D[1][0]*D[0][2]; D[1][0] = 0;
121 D[2][1] -= D[2][0]*D[0][1]; D[2][2] -= D[2][0]*D[0][2]; D[2][0] = 0;
122
123 // Second column
124 a[1] = c_absval(D[1][1]); a[2] = c_absval(D[2][1]);
125 if (a[1] < a[2])
126 {
127 if (a[2] < RREF_TOL) return 1;
128 temp[2] = D[1][2];
129 D[1][1] = D[2][1]; D[1][2] = D[2][2];
130 D[2][2] = temp[2]; D[2][1] = 0;
131 }
132 else
133 {
134 if (a[1] < RREF_TOL) return 1;
135 }
136
137 p = 1.0/D[1][1];
138 D[1][2] *= p; D[1][1] = 1.0;
139 D[0][2] -= D[0][1]*D[1][2]; D[0][1] = 0;
140 D[2][2] -= D[2][1]*D[1][2]; D[2][1] = 0;
141
142 return 2;
143}
144
145static c_float custom_eig(const c_float B[3][3], const c_float C[3][3], c_float x[3])
146{
147 c_float a, b, c, d;
148 c_float xqx = B[0][0], xqw = B[0][1], xqp = B[0][2], wqw = B[1][1], wqp = B[1][2], pqp = B[2][2], xp = C[0][2], wp = C[1][2];
149 a = wp*wp + xp*xp - 1;
150 b = (-xqx*wp*wp + 2*xqw*wp*xp - 2*wqp*wp - wqw*xp*xp - 2*xqp*xp + pqp + wqw + xqx);
151 c = (wqp*wqp - 2*xp*wqp*xqw + 2*wp*xqx*wqp + xqp*xqp - 2*wp*xqp*xqw + 2*wqw*xp*xqp + xqw*xqw - pqp*wqw - pqp*xqx - wqw*xqx);
152 d = - xqx*wqp*wqp + 2*wqp*xqp*xqw - wqw*xqp*xqp - pqp*xqw*xqw + pqp*wqw*xqx;
153 c_float lam = min_root_third_order(a, b, c, d);
154
155 /* D = B - lam*C */
156 c_float D[3][3];
157 D[0][0] = B[0][0] - lam*C[0][0];
158 D[0][1] = B[0][1];
159 D[0][2] = B[0][2] - lam*C[0][2];
160 D[1][0] = B[1][0];
161 D[1][1] = B[1][1] - lam*C[1][1];
162 D[1][2] = B[1][2] - lam*C[1][2];
163 D[2][0] = B[2][0] - lam*C[2][0];
164 D[2][1] = B[2][1] - lam*C[2][1];
165 D[2][2] = B[2][2] - lam*C[2][2];
166
167 int ind = custom_rref(D);
168
169 if (ind == 0)
170 {
171 x[0] = 1; x[1] = 0; x[2] = 0;
172 }
173 else
174 if (ind == 1)
175 {
176 x[0] = 0; x[1] = 1; x[2] = 0;
177 }
178 else
179 {
180 c_float temp = 1/c_sqrt(1 + D[0][2]*D[0][2] - 2*D[0][2]*C[0][2] + D[1][2]*D[1][2] - 2*D[1][2]*C[1][2]);
181
182 x[0] = -D[0][2]*temp;
183 x[1] = -D[1][2]*temp;
184 x[2] = temp;
185 }
186
187 return lam;
188
189}
190
191static c_float lobpcg(QPALMWorkspace *work, c_float *x, solver_common *c) {
192 c_float lambda, norm_w;
193 size_t i;
194
195 size_t n = work->data->n;
196 solver_sparse* A = work->data->Q;
197
198 /*Current guess of the eigenvector */
199 if (x == NULL) {
200 x = work->d;
201 /* Initialize eigenvector randomly. */
202 for (i = 0; i < n; i++) {
203 x[i] = (c_float) rand()/RAND_MAX;
204 }
205 vec_self_mult_scalar(x, 1.0/vec_norm_two(x, n), n);
206 } else {
207 /* NB: Assume x is already normalized */
208 prea_vec_copy(x, work->d, n);
209 x = work->d;
210 }
211 solver_dense *x_chol = work->solver->d;
212
213 c_float *Ax = work->Qd;
214 solver_dense * Ax_chol = work->solver->Qd;
215 mat_vec(A, x_chol , Ax_chol, c);
216 lambda = vec_prod(x, Ax, n);
217
218 /*Current residual, Ax - lambda*x */
219 c_float *w = work->neg_dphi;
220 solver_dense * w_chol = work->solver->neg_dphi;
221 c_float *Aw = work->Atyh;
222 solver_dense * Aw_chol = work->solver->Atyh;
223
224 /* Conjugate gradient direction */
225 c_float *p = work->temp_n;
226 c_float *Ap = work->xx0;
227 c_float p_norm_inv;
228
229 /* Compressed system B = [x, w, p]'*Q*[x, w, p] */
230 c_float B[3][3];
231 c_float C[3][3] = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}; /* C = [1 0 xp; 0 1 wp; xp wp 1] Takes into account that p is not orthonormal with x, w */
232 c_float y[3]; /* Eigenvector corresponding to min(lambda_B) */
233 c_float xAw, wAw, xAp, wAp, pAp, xp, wp;
234
235 /* Compute residual and make it orthonormal wrt x */
236 vec_add_scaled(Ax, x, w, -lambda, n);
237 vec_add_scaled(w, x, w, -vec_prod(x, w, n), n);
238 vec_self_mult_scalar(w, 1.0/vec_norm_two(w, n), n);
239 mat_vec(A, w_chol, Aw_chol, c);
240 xAw = vec_prod(Aw, x, n);
241 wAw = vec_prod(Aw, w, n);
242
243 /* In the first compressed system, there is no p yet, so it is 2 by 2 */
244 c_float B_init[2][2] = {{lambda, xAw}, {xAw, wAw}};
245
246 /* Solve 2x2 eigenvalue system */
247 c_float b, cc, di;
248 b = -(lambda + wAw);
249 cc = lambda*wAw - xAw*xAw;
250 di = b*b - 4*cc;
251 lambda = (-b-c_sqrt(di))/2;
252 B_init[0][0] -= lambda;
253 B_init[1][1] -= lambda;
254 if (c_absval(B_init[0][0]) < RREF_TOL)
255 {
256 y[0] = 1; y[1] = 0;
257 }
258 else
259 {
260 B_init[0][1] /= B_init[0][0];
261 b = 1/c_sqrt(1 + B_init[0][1]*B_init[0][1]);
262 y[0] = -B_init[0][1]*b;
263 y[1] = b;
264 }
265
266 /* Compute first p */
267 vec_mult_scalar(w, y[1], p, n);
268 vec_mult_scalar(Aw, y[1], Ap, n);
269 vec_add_scaled(p, x, x, y[0], n);
270 vec_add_scaled(Ap, Ax, Ax, y[0], n);
271
272 size_t max_iter = 10000; /*TODO: make this a setting */
273 for (i = 0; i < max_iter; i++) {
274
275 /* Update w */
276 vec_add_scaled(Ax, x, w, -lambda, n);
277 /* Note: check inf norm because it is cheaper */
278 if (vec_norm_inf(w, n) < LOBPCG_TOL) {
279 norm_w = vec_norm_two(w, n);
280 lambda -= c_sqrt(2)*norm_w + 1e-6; /* Theoretical bound on the eigenvalue */
281 if (n <= 3) lambda -= 1e-6; /* If n <= 3, we should have the exact eigenvalue, hence we subtract a small value */
282 break;
283 }
284 vec_add_scaled(w, x, w, -vec_prod(x, w, n), n);
285 vec_self_mult_scalar(w, 1.0/vec_norm_two(w, n), n);
286 mat_vec(A, w_chol, Aw_chol, c);
287 xAw = vec_prod(Ax, w, n);
288 wAw = vec_prod(w, Aw, n);
289
290 /* Normalize p */
291 p_norm_inv = 1.0/vec_norm_two(p, n);
292 vec_self_mult_scalar(p, p_norm_inv, n);
293 vec_self_mult_scalar(Ap, p_norm_inv, n);
294
295 /* Compress the system */
296 xAp = vec_prod(Ax, p, n);
297 wAp = vec_prod(Aw, p, n);
298 pAp = vec_prod(Ap, p, n);
299 xp = vec_prod(x, p, n);
300 wp = vec_prod(w, p, n);
301
302 B[0][0] = lambda; B[0][1] = xAw; B[0][2] = xAp;
303 B[1][0] = xAw; B[1][1] = wAw; B[1][2] = wAp;
304 B[2][0] = xAp; B[2][1] = wAp; B[2][2] = pAp;
305
306 C[0][2] = xp; C[1][2] = wp; C[2][0] = xp; C[2][1] = wp;
307
308 /* Solve 3x3 eigenvalue system By = lambda*Cy */
309 lambda = custom_eig(B, C, y);
310
311 /* Update p and x */
312 vec_mult_add_scaled(p, w, y[2], y[1], n);
313 vec_mult_add_scaled(Ap, Aw, y[2], y[1], n);
314 vec_mult_add_scaled(x, p, y[0], 1, n);
315 vec_mult_add_scaled(Ax, Ap, y[0], 1, n);
316
317 /* Restore consistency between x and lambda once every so often */
318 if (mod(i, 50) == 0)
319 {
320 lambda = vec_prod(x, Ax, n);
321 }
322 }
323
324 //TODO: Implement error handling here
325 return lambda;
326
327}
328#endif /*COMPILE_NONCONVEX*/
329
330
332 #ifdef COMPILE_NONCONVEX
333 c_float lambda;
334 lambda = lobpcg(work, NULL, c);
335 if (lambda < 0) {
336 work->settings->proximal = TRUE;
337 work->settings->gamma_init = 1/c_absval(lambda);
338 work->gamma = work->settings->gamma_init;
339 work->settings->gamma_max = work->settings->gamma_init;
340 work->gamma_maxed = TRUE;
341 } else
342 {
343 work->settings->nonconvex = FALSE;
344 }
345 #else
346 #ifdef PRINTING
347 qpalm_print("Warning: nonconvex is not supported in this version of QPALM. Setting it to false.\n");
348 work->settings->nonconvex = FALSE;
349 #endif
350 #endif /*COMPILE_NONCONVEX*/
351}
352
354 /* NB: Assume M is symmetric, so Gershgorin may be performed along the columns as well. */
355 c_float ub_eig = 0.0;
356 c_float *Mx = M->x; c_int *Mi = M->i; c_int *Mp = M->p;
357 c_int row, i, j, ncol = (c_int)M->ncol;
358
359 for (i=0; i < ncol; i++) {
360 center[i] = 0.0;
361 radius[i] = 0.0;
362 for (j = Mp[i]; j < Mp[i+1]; j++) {
363 row = Mi[j];
364 if (row == i) {
365 center[i] = Mx[j];
366 } else {
367 radius[i] += c_absval(Mx[j]);
368 }
369 }
370 if (i==0) {
371 ub_eig = center[i] + radius[i];
372 } else {
373 ub_eig = c_max(ub_eig, (center[i] + radius[i]));
374 }
375 }
376
377 return ub_eig;
378}
379
Constants used in QPALM.
#define TRUE
Definition: constants.h:18
#define FALSE
Definition: constants.h:19
Custom memory allocation, print and utility functions, and data types for floats and ints.
#define c_cos
cosine
Definition: global_opts.h:111
#define c_sqrt
square root
Definition: global_opts.h:109
ladel_int c_int
type for integer numbers
Definition: global_opts.h:42
#define qpalm_print
Definition: global_opts.h:75
#define mod(a, b)
modulo operation (positive result for all values)
Definition: global_opts.h:100
ladel_double c_float
type for floating point numbers
Definition: global_opts.h:41
#define c_max(a, b)
maximum of two values
Definition: global_opts.h:92
#define qpalm_eprint(...)
Definition: global_opts.h:78
#define c_absval(x)
absolute value
Definition: global_opts.h:88
#define c_acos
arc cosine
Definition: global_opts.h:110
#define c_min(a, b)
minimum of two values
Definition: global_opts.h:96
Linear algebra with vectors.
void vec_add_scaled(const c_float *a, const c_float *b, c_float *c, c_float sc, size_t n)
Scaled addition of one vector to another vector, .
Definition: lin_alg.c:110
void vec_mult_scalar(const c_float *a, c_float sc, c_float *b, size_t n)
Mulitply vector with a constant scale factor and store in a different vector.
Definition: lin_alg.c:64
void vec_mult_add_scaled(c_float *a, const c_float *b, c_float sc1, c_float sc2, size_t n)
Scaled addition of one vector to another vector, both being scaled, .
Definition: lin_alg.c:118
void vec_self_mult_scalar(c_float *a, c_float sc, size_t n)
Mulitply vector with a constant scale factor.
Definition: lin_alg.c:56
c_float vec_prod(const c_float *a, const c_float *b, size_t n)
Inner product between two vectors, .
Definition: lin_alg.c:72
c_float vec_norm_two(const c_float *a, size_t n)
2-norm of a vector, .
Definition: lin_alg.c:88
void prea_vec_copy(const c_float *a, c_float *b, size_t n)
Copy vector a into preallocated vector b.
Definition: lin_alg.c:24
c_float vec_norm_inf(const c_float *a, size_t n)
Infinity norm of a vector, .
Definition: lin_alg.c:126
#define RREF_TOL
Definition: nonconvex.c:28
void set_settings_nonconvex(QPALMWorkspace *work, solver_common *c)
Set the proximal parameters for nonconvex QPs.
Definition: nonconvex.c:331
c_float gershgorin_max(solver_sparse *M, c_float *center, c_float *radius)
Calculate the Gershgorin upper bound for the eigenvalues of a symmetric matrix.
Definition: nonconvex.c:353
#define LOBPCG_TOL
Tolerance on the infinity norm of the residual in lobpcg.
Definition: nonconvex.c:22
#define M_PI
Definition: nonconvex.c:25
Routines to deal with nonconvex QPs.
void mat_vec(solver_sparse *A, solver_dense *x, solver_dense *y, solver_common *c)
Matrix-vector multiplication.
size_t n
number of variables n
Definition: types.h:103
solver_sparse * Q
sparse quadratic part of the cost Q (size n x n)
Definition: types.h:105
c_float gamma_max
proximal penalty parameter cap
Definition: types.h:134
c_int proximal
boolean, use proximal method of multipliers or not
Definition: types.h:131
c_float gamma_init
initial proximal penalty parameter
Definition: types.h:132
c_int nonconvex
boolean, indicates whether the QP is nonconvex
Definition: types.h:136
solver_dense * neg_dphi
-gradient of the Lagrangian
Definition: types.h:168
solver_dense * d
primal update step
Definition: types.h:171
solver_dense * Atyh
A' * yh.
Definition: types.h:175
solver_dense * Qd
Q * d.
Definition: types.h:173
QPALM Workspace.
Definition: types.h:197
c_float * neg_dphi
-dphi, required as the rhs in SCHUR
Definition: types.h:235
c_int gamma_maxed
flag to indicate whether gamma has been maximized when the primal residual was low
Definition: types.h:224
QPALMSettings * settings
problem settings
Definition: types.h:305
c_float gamma
proximal penalty factor
Definition: types.h:223
QPALMSolver * solver
linsys variables
Definition: types.h:304
c_float * xx0
x - x0
Definition: types.h:233
QPALMData * data
problem data to work on (possibly scaled)
Definition: types.h:198
c_float * Qd
Q * d.
Definition: types.h:246
c_float * temp_n
placeholder for vector of size n
Definition: types.h:218
c_float * d
primal update step
Definition: types.h:237
c_float * Atyh
A' * yh.
Definition: types.h:230
Internal data structures used in QPALM.
ladel_work solver_common
Definition: types.h:18
ladel_double solver_dense
Definition: types.h:20
ladel_sparse_matrix solver_sparse
Definition: types.h:19
Utility functions.