1 // NOLINT(legal/copyright)
2 // C-REPLACE "fmax" "casadi_fmax"
3 // C-REPLACE "nullptr" "0"
4
5 // SYMBOL "cvx_house"
6 /// Computes Householder vector
7 /// beta: scalar
8 /// v: vector of length nv
9 /// Returns 2-norm of v
10 ///
11 /// Ref: Golub & Van Loan Alg 5.1.1
12 template<typename T1>
casadi_cvx_house(T1 * v,T1 * beta,casadi_int nv)13 T1 casadi_cvx_house(T1* v, T1* beta, casadi_int nv) {
14 // Local variable
15 casadi_int i;
16 T1 v0, sigma, s, v02;
17 // Calculate norm
18 v0 = v[0]; // Save v0 (overwritten below)
19 sigma=0;
20 for (i=1; i<nv; ++i) sigma += v[i]*v[i];
21 s = sqrt(v0*v0 + sigma); // s = norm(v)
22 if (sigma==0) {
23 *beta = 0;
24 } else {
25 if (v0<=0) {
26 v0 -= s;
27 } else {
28 v0 = -sigma/(v0+s);
29 }
30 v02 = v0*v0;
31 *beta = 2*v02/(sigma+v02);
32 v[0] = 1;
33 for (i=1;i<nv;++i) v[i] /= v0;
34 }
35 return s;
36 }
37
38
39 // SYMBOL "cvx_house_apply_symm"
40 // Apply householder transform to a symmetric submatrix
41 // on dense A m-by-n matrix
42 //
43 // A is modified in-place
44 //
45 // s : stride
46 // normally equal to m
47 // when A is a submatrix of a bigger matrix, set equal to latter's number of rows
48 // v : compact housholder factorisation (length m)
49 // First element (always one) is used to store beta
50 // p : length n
51 //
52 // Reference: Golub & Van Loan, Alg. 8.3.1
53 template<typename T1>
casadi_cvx_house_apply_symm(casadi_int n,casadi_int k,T1 * A,T1 * p,T1 * v)54 void casadi_cvx_house_apply_symm(casadi_int n, casadi_int k, T1* A, T1* p, T1* v) {
55 casadi_int i, j, stride, N;
56 T1 *a;
57 T1 beta = v[0];
58 v[0] = 1;
59 stride = k+1;
60 A+= k+1+n*k;
61 N = n-k-1;
62
63 // p <- beta * A(k+1:n,k+1:n) v
64 casadi_clear(p, N);
65 a = A+n;
66
67 // Loop over columns
68 for (i=0;i<N;++i) {
69 p[i] += beta*(*a++)*v[i];
70 // Loop over rows
71 for (j=i+1;j<N;++j) {
72 p[j] += beta*(*a)*v[i];
73 p[i] += beta*(*a++)*v[j];
74 }
75 a += stride+i+1;
76 }
77
78 // p <- p - (beta p'v/2) v
79 casadi_axpy(N, -beta*casadi_dot(N, p, v)/2, v, p);
80
81 // Rank-2 update
82 a = A+n;
83 // Loop over columns
84 for (i=0;i<N;++i) {
85 *a++ -= 2*v[i]*p[i];
86 // Loop over rows
87 for (j=i+1;j<N;++j) {
88 *a++ -= v[i]*p[j]+v[j]*p[i];
89 }
90 a += stride+i+1;
91 }
92 v[0] = beta;
93 }
94
95
96 // SYMBOL "cvx_tri"
97 // Tri-diagonalize a symmetric matrix in-place
98 // Results are in lower-triangular part
99 //
100 // Upper triangular part contains compact housholder factorisations
101 //
102 // A: n-by-n dense
103 // p: work vector; length n
104 template<typename T1>
casadi_cvx_tri(T1 * A,casadi_int n,T1 * p)105 void casadi_cvx_tri(T1* A, casadi_int n, T1* p) {
106 T1 pp[1000];
107 casadi_int k, N;
108 T1 *A_base, *v;
109 T1 beta;
110 for (k=0;k<n-2;++k) {
111 A_base = A+k+1+n*k;
112 N = n-k-1;
113
114 v = A+N*n;
115
116 // Compute Householder transformation
117 casadi_copy(A_base, N, v);
118
119 // Assign 2-norm
120 *A_base = casadi_cvx_house(v, &beta, N);
121
122 v[0] = beta;
123 casadi_cvx_house_apply_symm(n, k, A, pp, v);
124
125 }
126 }
127
128
129 // SYMBOL "cvx_givens"
130 // Ref: Golub & Van Loan Alg. 5.1.3
131 template<typename T1>
casadi_cvx_givens(T1 a,T1 b,T1 * c,T1 * s)132 void casadi_cvx_givens(T1 a, T1 b, T1* c, T1* s) {
133 T1 r;
134 if (b==0) {
135 *c = 1;
136 *s = 0;
137 } else {
138 if (fabs(b)>fabs(a)) {
139 r = -a/b;
140 *s = 1/sqrt(1+r*r);
141 *c = (*s)*r;
142 } else {
143 r = -b/a;
144 *c = 1/sqrt(1+r*r);
145 *s = (*c)*r;
146 }
147 }
148 }
149
150
151 // SYMBOL "cvx_implicit_qr"
152 // Implicit Symmetric QR step with Wilkinson shift
153 //
154 // Tri-diagonal n-by-n matrix
155 // Diagonal: t_diag (length n)
156 // Off-diagonal: t_off (length n-1)
157 // cs: [c0 s0 c1 s1 ...] length 2*(n-1)
158 // Golub & Van Loan Alg. 8.3.2
159 template<typename T1>
casadi_cvx_implicit_qr(casadi_int n,T1 * t_diag,T1 * t_off,T1 * cs)160 void casadi_cvx_implicit_qr(casadi_int n, T1* t_diag, T1* t_off, T1* cs) {
161 T1 d, mu, to2, x, z, c, s, t1, t2, d0, d1, o0, o1, sd;
162 casadi_int i;
163 d = 0.5*(t_diag[n-2]-t_diag[n-1]);
164 to2 = t_off[n-2]*t_off[n-2];
165 sd = 1;
166 if (d<0) sd = -1;
167 mu = t_diag[n-1]-to2/(d+sd*sqrt(d*d+to2));
168 x = t_diag[0]-mu;
169 z = t_off[0];
170 for (i=0;i<n-1;++i) {
171 // Compute Givens transformation
172 casadi_cvx_givens(x, z, &c, &s);
173 // T = G'TG (worked out with scalars)
174 d0 = t_diag[i];
175 d1 = t_diag[i+1];
176 o0 = t_off[i];
177 o1 = t_off[i+1];
178 t1 = d0*c-o0*s;
179 t2 = o0*c-d1*s;
180 t_diag[i] = c*t1-s*t2;
181 t_off[i] = s*t1+c*t2;
182 t_diag[i+1] = d0*s*s+2*s*o0*c+d1*c*c;
183 t_off[i+1] *= c;
184 if (i>0) {
185 t_off[i-1] = t_off[i-1]*c-z*s;
186 }
187 x = t_off[i];
188 z = -s*o1;
189 if (cs) {
190 *cs++ = c;
191 *cs++ = s;
192 }
193 }
194 }
195
196
197 // SYMBOL "cvx_symm_schur"
198 // Eigen-decomposition Q'TQ = D
199 // T tri-diagonal, with:
200 // - t_diag the diagonal vector (length n)
201 // - t_off the off-diagonal vector (length n-1)
202 //
203 // Eigenvalues can be read from returned t_diag
204 //
205 // tolerance greater than machine precision
206 //
207 // trace_meta: length 1+3*n_iter
208 // trace: length 2*(n-1)*n_iter
209 //
210 /// Golub & Van Loan Alg. 8.3.3
211 template<typename T1>
casadi_cvx_symm_schur(casadi_int n,T1 * t_diag,T1 * t_off,T1 tol,casadi_int max_iter,casadi_int * trace_meta,T1 * trace)212 int casadi_cvx_symm_schur(casadi_int n, T1* t_diag, T1* t_off, T1 tol, casadi_int max_iter,
213 casadi_int* trace_meta, T1* trace) {
214 casadi_int i, p, q, sp, sq, trace_offset, nn;
215 casadi_int* n_iter;
216 n_iter = trace_meta++;
217
218 trace_offset = 0;
219 q = 0;
220 *n_iter = 0;
221
222 while (q<n) {
223 if (*n_iter==max_iter) return 1;
224 // Clip converged entries
225 for (i=0;i<n-1;++i) {
226 if (fabs(t_off[i])<=tol*(fabs(t_diag[i])+fabs(t_diag[i+1]))) {
227 t_off[i] = 0;
228 }
229 }
230
231 // Determine p, q
232 p = 0;
233 q = 0;
234 sp = 0;
235 sq = 0;
236 for (i=0;i<n-1;++i) {
237 if (t_off[n-i-2]==0 && sq==0) {
238 q++;
239 } else {
240 sq = 1;
241 }
242 if (t_off[i]==0 && sp==0) {
243 p++;
244 } else {
245 sp = 1;
246 }
247 if (q==n-1) {
248 q = n;
249 p = 0;
250 }
251 }
252
253 nn = n-q-p;
254 if (q<n) {
255 casadi_cvx_implicit_qr(nn, t_diag+p, t_off+p, trace ? trace+trace_offset : nullptr);
256 trace_offset += 2*(nn-1);
257
258 if (trace_meta) {
259 *trace_meta++ = nn;
260 *trace_meta++ = p;
261 *trace_meta++ = trace_offset;
262 }
263 (*n_iter)++;
264 }
265 }
266 return 0;
267 }
268
269
270
271 // SYMBOL "cvx_givens_apply"
272 template<typename T1>
casadi_cvx_givens_apply(casadi_int n,T1 * q,T1 c,T1 s,casadi_int p)273 void casadi_cvx_givens_apply(casadi_int n, T1* q, T1 c, T1 s, casadi_int p) {
274 T1 t1, t2, t3, t4, a, b;
275 casadi_int i;
276 // Update rows
277 T1 *m = q;
278 m += p;
279 for (i=0;i<p;++i) {
280 a = m[0];
281 b = m[1];
282 m[0] = c*a+s*b;
283 m[1] = c*b-s*a;
284 m+=n;
285 }
286 // Update central patch
287 t1 = c*m[0]+s*m[1];
288 t2 = c*m[1]+s*m[n+1];
289 t3 = c*m[1]-s*m[0];
290 t4 = c*m[n+1]-s*m[1];
291 m[0] = c*t1+s*t2;
292 m[1] = c*t2-s*t1;
293 m[n+1] = c*t4-s*t3;
294 // Update columns
295 m = q+n*p+p+2;
296 for (i=0;i<n-p-2;++i) {
297 a = m[0];
298 b = m[n];
299 m[0] = c*a+s*b;
300 m[n] = c*b-s*a;
301 m++;
302 }
303 }
304
305
306 // SYMBOL "cvx_house_apply"
307 /// Apply householder transform
308 /// on dense A m-by-n matrix
309 ///
310 /// A is modified in-place
311 ///
312 /// s : stride
313 /// normally equal to m
314 /// when A is a submatrix of a bigger matrix, set equal to latter's number of rows
315 /// v : compact housholder factorisation (length m)
316 /// First element (always one) is used to store beta
317 /// p : length n
318 ///
319 template<typename T1>
casadi_cvx_house_apply(casadi_int n,casadi_int m,casadi_int s,T1 * A,T1 * p,const T1 * v)320 void casadi_cvx_house_apply(casadi_int n, casadi_int m, casadi_int s, T1* A,
321 T1* p, const T1* v) {
322 casadi_int i, j;
323 T1 *a;
324 T1 beta;
325 beta = v[0];
326
327 // pi <- beta Aji vj
328 casadi_clear(p, n);
329 a = A;
330
331 // Loop over columns
332 for (i=0;i<n;++i) {
333 p[i] += beta*a[0];
334 // Loop over rows
335 for (j=1;j<m;++j) {
336 p[i] += beta*a[j]*v[j];
337 }
338 a += s;
339 }
340
341 a = A;
342 // Loop over columns
343 for (i=0;i<n;++i) {
344 a[0] -= p[i];
345 // Loop over rows
346 for (j=1;j<m;++j) {
347 a[j] -= v[j]*p[i];
348 }
349 a += s;
350 }
351 }
352
353 template<typename T1>
casadi_cvx_scalar(T1 epsilon,casadi_int reflect,T1 eig)354 T1 casadi_cvx_scalar(T1 epsilon, casadi_int reflect, T1 eig) {
355 return fmax(epsilon, reflect ? fabs(eig) : eig);
356 }
357
358
359 // SYMBOL "cvx"
360 // Convexify a dense symmetric Hessian
361 //
362 // w real work vector: length max(n,2*(n-1)*n_iter)
363 // iw integer work vector: 1+3*n_iter
364 //
365 // tol: tolerance for symmetric schur
366 // epsilon: minimum magnitude of eigenvalues
367 // reflect: when nonzero, reflect negative eigenvalues
368 template<typename T1>
casadi_cvx(casadi_int n,T1 * A,T1 epsilon,T1 tol,casadi_int reflect,casadi_int max_iter,T1 * w,casadi_int * iw)369 int casadi_cvx(casadi_int n, T1 *A, T1 epsilon, T1 tol, casadi_int reflect, casadi_int max_iter,
370 T1* w, casadi_int* iw) {
371 casadi_int i, j, k, n_iter, nn, p, trace_offset;
372 casadi_int *t_meta;
373 T1 c, s, t_off0;
374 T1 *cs, *t_diag, *t_off;
375
376 // Short-circuit for empty matrices
377 if (n==0) return 0;
378
379 // Short-circuit for scalar matrices
380 if (n==1) {
381 A[0] = casadi_cvx_scalar(epsilon, reflect, A[0]);
382 return 0;
383 }
384
385 casadi_cvx_tri(A, n, w);
386
387 for (i=0;i<n;++i) {
388 for (j=0;j<n;++j) {
389 if (i-j>=2) {
390 A[i+j*n] = 0;
391 }
392 }
393 }
394
395 // Represent tri-diagonal as vector pair (t_diag, t_off)
396 t_off0 = A[1];
397 t_diag = A;
398 t_off = A+n;
399 for (i=1;i<n;++i) {
400 t_diag[i] = A[i+n*i];
401 }
402 t_off[0] = t_off0;
403 for (i=1;i<n-1;++i) {
404 t_off[i] = A[i+1+n*i];
405 }
406
407 // Diagonalize matrix by Symmetric QR
408 if (casadi_cvx_symm_schur(n, t_diag, t_off, tol, max_iter, iw, w)) return 1;
409
410 // Retain diagonals (eigenvalues)
411 for (i=0;i<n;++i) {
412 A[i+n*i] = casadi_cvx_scalar(epsilon, reflect, t_diag[i]);
413 }
414
415 // Reset other elements
416 for (i=0;i<n;++i) {
417 for (j=i+1;j<n;++j) A[j+i*n] = 0;
418 }
419
420 // Undo Symmetric QR
421 n_iter = iw[0];
422 t_meta = iw+3*(n_iter-1)+1;
423
424 for (i=0;i<n_iter;++i) {
425 nn = *t_meta++;
426 p = *t_meta++;
427 trace_offset = *t_meta++;
428 cs = w+trace_offset;
429 t_meta-= 6;
430 for (j=0;j<nn-1;j++) {
431 s = *--cs;
432 c = *--cs;
433 casadi_cvx_givens_apply(n, A, c, s, p+nn-j-2);
434 }
435 }
436
437 // Undo triangularization
438 for (k = n-3; k>=0; --k) {
439 casadi_int N = n-k-1;
440 T1 *v = A+N*n;
441 casadi_cvx_house_apply_symm(n, k, A, w, v);
442 casadi_cvx_house_apply(k+1, N, n, A+k+1, w, v);
443 }
444
445 return 0;
446 }
447