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