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