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