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