scs 0.5.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/lib/scs/ffi.rb +2 -0
  4. data/lib/scs/solver.rb +15 -7
  5. data/lib/scs/version.rb +1 -1
  6. data/vendor/scs/CITATION.cff +2 -2
  7. data/vendor/scs/CMakeLists.txt +136 -6
  8. data/vendor/scs/Makefile +53 -3
  9. data/vendor/scs/README.md +1 -1
  10. data/vendor/scs/include/cones.h +47 -2
  11. data/vendor/scs/include/glbopts.h +1 -1
  12. data/vendor/scs/include/scs.h +29 -0
  13. data/vendor/scs/include/scs_blas.h +4 -0
  14. data/vendor/scs/include/scs_types.h +3 -1
  15. data/vendor/scs/include/util_spectral_cones.h +45 -0
  16. data/vendor/scs/linsys/cpu/direct/private.c +3 -3
  17. data/vendor/scs/linsys/cpu/direct/private.h +2 -1
  18. data/vendor/scs/linsys/csparse.c +1 -1
  19. data/vendor/scs/linsys/cudss/direct/private.c +279 -0
  20. data/vendor/scs/linsys/cudss/direct/private.h +63 -0
  21. data/vendor/scs/linsys/external/qdldl/qdldl_types.h +1 -1
  22. data/vendor/scs/linsys/gpu/indirect/private.c +14 -21
  23. data/vendor/scs/scs.mk +17 -2
  24. data/vendor/scs/src/aa.c +8 -12
  25. data/vendor/scs/src/cones.c +783 -12
  26. data/vendor/scs/src/rw.c +15 -1
  27. data/vendor/scs/src/scs.c +4 -0
  28. data/vendor/scs/src/spectral_cones/logdeterminant/log_cone_IPM.c +660 -0
  29. data/vendor/scs/src/spectral_cones/logdeterminant/log_cone_Newton.c +279 -0
  30. data/vendor/scs/src/spectral_cones/logdeterminant/log_cone_wrapper.c +205 -0
  31. data/vendor/scs/src/spectral_cones/logdeterminant/logdet_cone.c +143 -0
  32. data/vendor/scs/src/spectral_cones/nuclear/ell1_cone.c +221 -0
  33. data/vendor/scs/src/spectral_cones/nuclear/nuclear_cone.c +99 -0
  34. data/vendor/scs/src/spectral_cones/sum-largest/sum_largest_cone.c +196 -0
  35. data/vendor/scs/src/spectral_cones/sum-largest/sum_largest_eval_cone.c +140 -0
  36. data/vendor/scs/src/spectral_cones/util_spectral_cones.c +52 -0
  37. data/vendor/scs/test/problems/complex_PSD.h +83 -0
  38. data/vendor/scs/test/rng.h +4 -4
  39. data/vendor/scs/test/run_tests.c +25 -0
  40. data/vendor/scs/test/spectral_cones_problems/exp_design.h +141 -0
  41. data/vendor/scs/test/spectral_cones_problems/graph_partitioning.h +275 -0
  42. data/vendor/scs/test/spectral_cones_problems/robust_pca.h +253 -0
  43. data/vendor/scs/test/spectral_cones_problems/several_logdet_cones.h +222 -0
  44. data/vendor/scs/test/spectral_cones_problems/several_nuc_cone.h +285 -0
  45. data/vendor/scs/test/spectral_cones_problems/several_sum_largest.h +420 -0
  46. metadata +22 -7
@@ -0,0 +1,279 @@
1
+ #include "glbopts.h" // for scs_printf
2
+ #include "linalg.h"
3
+ #include "scs_blas.h"
4
+ #include "util_spectral_cones.h"
5
+ #include <string.h> // for memcpy
6
+
7
+ /*
8
+ * Spectral matrix cone projections, from "Projection onto Spectral Matrix
9
+ * Cones" by Daniel Cederberg and Stephen Boyd, 2024.
10
+ *
11
+ * If you have any questions on the code, please reach out to the code author
12
+ * Daniel Cederberg.
13
+ *
14
+ * This file implements Newton's method for projecting onto the logarithmic
15
+ * cone.
16
+ *
17
+ * Last modified: 25 August 2024.
18
+ */
19
+
20
+ #define LINESEARCH_RELATIVE_TOL 1e-14
21
+ #define MIN_INIT_LOG_CONE 1
22
+ #define MIN_DENOMINATOR 1e-14
23
+ #define MIN_X 1e-17
24
+ #define MIN_FLOAT MIN_X / 2
25
+ #define MIN_V 1e-14
26
+
27
+ #define MAX_ITER_NEWTON 100
28
+ #define ALPHA_NEWTON 0.01
29
+ #define BETA_NEWTON 0.8
30
+ #define TOL_NEWTON 1e-12
31
+ #define MAX_GRAD_STEPS 5
32
+
33
+ #define TERMINATE_DUE_TO_ZEROS -5
34
+ #define MAX_GRAD_STEPS_REACHED -6
35
+
36
+ // the CALLER of this function must make sure that the arguments belong to the
37
+ // domain
38
+ static scs_float obj_val(const scs_float *u, scs_float t0, scs_float v0,
39
+ const scs_float *x0, scs_int n) {
40
+ scs_float v = u[0];
41
+ const scs_float *x = u + 1;
42
+
43
+ assert(v > 0 && min_vec(x, n) > 0);
44
+
45
+ scs_float sx = -(v * sum_log(x, n) - n * v * log(v));
46
+ scs_float obj = 0.5 * (sx - t0) * (sx - t0) + 0.5 * (v - v0) * (v - v0);
47
+ for (scs_int i = 0; i < n; ++i) {
48
+ obj += 0.5 * (x[i] - x0[i]) * (x[i] - x0[i]);
49
+ }
50
+ return obj;
51
+ }
52
+
53
+ scs_int log_cone_Newton(scs_float t0, scs_float v0, const scs_float *x0,
54
+ scs_float *u, scs_int n, scs_float *workspace,
55
+ Newton_stats *stats, bool *warm_start) {
56
+ scs_float *t = u;
57
+ scs_float *v = u + 1;
58
+ scs_float *x = u + 2;
59
+ scs_int n_plus_one = n + 1;
60
+
61
+ // -------------------------------------------------------------------------
62
+ // check cone membership and special cases with analytic projections
63
+ // -------------------------------------------------------------------------
64
+ bool ix_x0_non_neg = true;
65
+ bool is_x0_pos = true;
66
+ scs_int i = 0;
67
+ while (ix_x0_non_neg || is_x0_pos) {
68
+ if (x[i] < 0) {
69
+ ix_x0_non_neg = false;
70
+ is_x0_pos = false;
71
+ } else if (x[i] == 0) {
72
+ is_x0_pos = false;
73
+ }
74
+
75
+ if (i == n - 1) {
76
+ break;
77
+ }
78
+ ++i;
79
+ }
80
+
81
+ // if (t0, v0, x0) belongs to cone
82
+ if ((v0 > 0 && is_x0_pos && -v0 * (sum_log(x0, n) - n * log(v0)) <= t0) ||
83
+ (v0 == 0 && ix_x0_non_neg && t0 >= 0)) {
84
+ *t = t0;
85
+ *v = v0;
86
+ memcpy(x, x0, sizeof(*x0) * n);
87
+ stats->iter = IN_CONE;
88
+ *warm_start = true;
89
+ return 0;
90
+ }
91
+
92
+ // if (t0, v0, x0) belongs to negative dual cone
93
+ if (t0 < 0 && is_negative(x0, n)) {
94
+ scs_float sum = -n;
95
+ for (i = 0; i < n; ++i) {
96
+ sum -= log(x0[i] / t0);
97
+ }
98
+ sum *= t0;
99
+
100
+ if (v0 <= sum) {
101
+ memset(u, 0, (n + 2) * sizeof(*x0));
102
+ stats->iter = IN_NEGATIVE_DUAL_CONE;
103
+ // if 0 is the solution we should not use it to warmstart the next
104
+ // iteration
105
+ *warm_start = false;
106
+ return 0;
107
+ }
108
+ }
109
+
110
+ // special case with analytic solution
111
+ if (v0 <= 0 && t0 >= 0) {
112
+ *t = t0;
113
+ *v = 0;
114
+ non_neg_proj(x0, x, n);
115
+ stats->iter = ANALYTICAL_SOL;
116
+ *warm_start = true;
117
+ return 0;
118
+ }
119
+
120
+ // ----------------------------------------------------------------------
121
+ // if 'warm_start' is false we initialize in the point
122
+ // (v, x) = (max(v0, MIN_INIT_LOG_CONE), max(x0, MIN_INIT_LOG_CONE)),
123
+ // otherwise it is assumed that 'proj' has already been
124
+ // initialized / warmstarted.
125
+ // ----------------------------------------------------------------------
126
+ if (!(*warm_start)) {
127
+ *v = (v0 > MIN_INIT_LOG_CONE) ? v0 : MIN_INIT_LOG_CONE;
128
+
129
+ for (scs_int i = 0; i < n; ++i) {
130
+ x[i] = (x0[i] > MIN_INIT_LOG_CONE) ? x0[i] : MIN_INIT_LOG_CONE;
131
+ }
132
+ }
133
+
134
+ scs_float obj_old = obj_val(u + 1, t0, v0, x0, n);
135
+
136
+ // -----------------------------
137
+ // parse workspace
138
+ // -----------------------------
139
+ scs_float *grad = workspace;
140
+ scs_float *d = grad + n_plus_one;
141
+ scs_float *w = d + n_plus_one;
142
+ scs_float *du = w + n_plus_one;
143
+ scs_float *temp1 = du + n_plus_one;
144
+ scs_float *u_new = du + n_plus_one + 1;
145
+
146
+ size_t num_grad_steps = 0;
147
+ scs_int iter = 0;
148
+ scs_float newton_decrement = 0;
149
+ for (iter = 1; iter <= MAX_ITER_NEWTON; ++iter) {
150
+ // A small value on v indicates that Newton's method converges to the
151
+ // origin. In this case we should abort and apply an IPM.
152
+ if (*v < MIN_V) {
153
+ stats->iter = TERMINATE_DUE_TO_ZEROS;
154
+ return -1;
155
+ }
156
+
157
+ // -------------------------------------------------------------------
158
+ // To avoid pathological cases where some components of
159
+ // x approach 0 we use a minimum threshold.
160
+ // -------------------------------------------------------------------
161
+ for (scs_int i = 0; i < n; i++) {
162
+ x[i] = MAX(x[i], MIN_X);
163
+ }
164
+
165
+ // ----------------------------------------------------------------
166
+ // compute gradient and Hessian
167
+ // ----------------------------------------------------------------
168
+ assert(*v > MIN_FLOAT && min_vec(x, n) > MIN_FLOAT);
169
+ scs_float temp0 = -sum_log(x, n) + n * log(*v);
170
+ scs_float a = (*v) * temp0 - t0;
171
+ scs_float c = temp0 + n;
172
+
173
+ grad[0] = a * c + (*v) - v0;
174
+ scs_float v_inv = 1 / (*v);
175
+ d[0] = 1 + a * (-a * (v_inv * v_inv) + n * v_inv - 2 * c * v_inv);
176
+ w[0] = -(a + (*v) * c) * v_inv;
177
+ scs_float av = a * (*v);
178
+
179
+ for (i = 1; i < n + 1; ++i) {
180
+ assert(x[i - 1] > 0);
181
+ scs_float x_inv = 1 / x[i - 1];
182
+ grad[i] = -av * x_inv + x[i - 1] - x0[i - 1];
183
+ d[i] = 1 + av * (x_inv * x_inv);
184
+ w[i] = (*v) * x_inv;
185
+ }
186
+
187
+ // ----------------------------------------------------------------------
188
+ // Solve for Newton step. I have seen a scenario when the variable
189
+ // 'denominator' becomes 0. This occurred when Newton's method
190
+ // converged to the origin and the IPM was necessary
191
+ // to converge to the correct point. We therefore abort when the
192
+ // denominator becomes very close to 0, since this may indicate
193
+ // that Newton's method is converging to the wrong point and in this
194
+ // case it is necessary to apply an IPM.
195
+ // ----------------------------------------------------------------------
196
+ scs_float nominator = 0;
197
+ scs_float denominator = 1;
198
+ for (i = 0; i < n + 1; ++i) {
199
+ assert(fabs(d[i]) > MIN_FLOAT);
200
+ du[i] = -grad[i] / d[i];
201
+ nominator += w[i] * du[i];
202
+ temp1[i] = w[i] / d[i];
203
+ denominator += w[i] * temp1[i];
204
+ }
205
+
206
+ if (fabs(denominator) < MIN_DENOMINATOR) {
207
+ stats->iter = TERMINATE_DUE_TO_ZEROS;
208
+ return -1;
209
+ }
210
+
211
+ scs_float ratio = -nominator / denominator;
212
+ SCS(add_scaled_array)(du, temp1, n_plus_one, ratio);
213
+
214
+ // --------------------------------------------------------------------
215
+ // if the Newton direction is not descent we use the negative gradient
216
+ // as the search direction, provided that it hasn't been used many times
217
+ // before.
218
+ // --------------------------------------------------------------------
219
+ scs_float dirDer = SCS(dot)(grad, du, n_plus_one);
220
+ if (dirDer > 0) {
221
+ if (num_grad_steps >= MAX_GRAD_STEPS) {
222
+ stats->iter = MAX_GRAD_STEPS_REACHED;
223
+ return -1;
224
+ }
225
+
226
+ num_grad_steps += 1;
227
+ dirDer = 0;
228
+
229
+ for (i = 0; i < n + 1; ++i) {
230
+ du[i] = -grad[i];
231
+ dirDer -= grad[i] * grad[i];
232
+ }
233
+ }
234
+
235
+ // --------------------------------------------------------
236
+ // compute Newton decrement and check termination criteria
237
+ // -------------------------------------------------------
238
+ newton_decrement = -dirDer;
239
+ if (newton_decrement <= 2 * TOL_NEWTON) {
240
+ break;
241
+ }
242
+
243
+ // --------------------------------------------------
244
+ // find largest step size with respect to the domain
245
+ // --------------------------------------------------
246
+ scs_float step_size = 1;
247
+ for (i = 0; i < n + 1; i++) {
248
+ if (du[i] < 0) {
249
+ scs_float max_step = -0.99 * u[i + 1] / du[i];
250
+ step_size = MIN(step_size, max_step);
251
+ }
252
+ }
253
+
254
+ // -------------------------------------------------
255
+ // backtracking line search. First two lines do
256
+ // u_new = u + t * du;
257
+ // -------------------------------------------------
258
+ memcpy(u_new + 1, u + 1, n_plus_one * sizeof(*u));
259
+ SCS(add_scaled_array)(u_new + 1, du, n_plus_one, step_size);
260
+ scs_float new_obj = obj_val(u_new + 1, t0, v0, x0, n);
261
+ while ((1 - LINESEARCH_RELATIVE_TOL) * new_obj >
262
+ obj_old + ALPHA_NEWTON * step_size * dirDer) {
263
+ step_size *= BETA_NEWTON;
264
+ memcpy(u_new + 1, u + 1, n_plus_one * sizeof(*u));
265
+ SCS(add_scaled_array)(u_new + 1, du, n_plus_one, step_size);
266
+ new_obj = obj_val(u_new + 1, t0, v0, x0, n);
267
+ }
268
+
269
+ obj_old = new_obj;
270
+ memcpy(u + 1, u_new + 1, n_plus_one * sizeof(*u));
271
+ }
272
+
273
+ assert(min_vec(x, n) > MIN_FLOAT && *v > MIN_FLOAT);
274
+ *t = -(*v) * (sum_log(x, n) - n * log(*v));
275
+
276
+ *warm_start = true;
277
+ stats->iter = iter;
278
+ return 0;
279
+ }
@@ -0,0 +1,205 @@
1
+ #include "cones.h"
2
+ #include "glbopts.h"
3
+ #include "linalg.h"
4
+ #include "scs.h"
5
+ #include "scs_blas.h"
6
+ #include "util_spectral_cones.h"
7
+
8
+ #define DUAL_FEAS_TOL 1e-2
9
+ #define PRI_FEAS_TOL 1e-2
10
+ #define COMP_TOL 1e-2
11
+ #define DUAL_T_THRESHOLD 1e-8
12
+ #define DUAL_X_THRESHOLD 1e-8
13
+
14
+ #define NEWTON_SUCCESS 1
15
+ #define IPM_VARIANT_0_SUCCESS 2
16
+ #define IPM_VARIANT_1_SUCCESS 3
17
+
18
+ /*
19
+ * Spectral matrix cone projections, from "Projection onto Spectral Matrix
20
+ * Cones" by Daniel Cederberg and Stephen Boyd, 2024.
21
+ *
22
+ * If you have any questions on the code, please reach out to the code author
23
+ * Daniel Cederberg.
24
+ *
25
+ * This file implements a wrapper to the code that projects onto the logarithmic
26
+ * cone.
27
+ *
28
+ * Last modified: 25 August 2024.
29
+ */
30
+
31
+ // forward declare from log_cone_Newton.c
32
+ scs_int log_cone_Newton(scs_float t0, scs_float v0, const scs_float *x0,
33
+ scs_float *proj, scs_int n, scs_float *workspace,
34
+ Newton_stats *stats, bool *warm_start);
35
+
36
+ // forward declare form log_cone_IPM.c
37
+ scs_int log_cone_IPM(scs_float t0, scs_float v0, scs_float *x0, scs_float *u1,
38
+ scs_int n, scs_float *workspace, Newton_stats *stats,
39
+ scs_int variant);
40
+
41
+ // forward declare from this file
42
+ static void check_opt_cond_log_cone(const scs_float *tvx, scs_float t0,
43
+ scs_float v0, const scs_float *x0,
44
+ scs_int n, scs_float residuals[3],
45
+ scs_float *dualx);
46
+
47
+ scs_int log_cone_proj_wrapper(scs_float t0, scs_float v0, scs_float *x0,
48
+ scs_float *proj, scs_int n, scs_float *workspace,
49
+ Newton_stats *stats, bool *warm_start) {
50
+ scs_int status;
51
+ // -----------------------------------------------------------------------
52
+ // 1. Solve problem with Newton's method. From Lagrange multiplier theory
53
+ // we know that the t-component of the projection must always be greater
54
+ // than t0, so if proj[0] < t0 it means that Newton's method has converged
55
+ // to wrong point. We return if proj[0] >= t0 and the residuals suggest
56
+ // that we converged to the correct point.
57
+ // 2. We warmstart Newton using the solution of the previous iteration
58
+ // except for the first iteration.
59
+ // 3. The current implementation of the warmstart assumes that there is only
60
+ // one spectral matrix cone so 'proj' isn't overwritten.
61
+ // ------------------------------------------------------------------------
62
+ status = log_cone_Newton(t0, v0, x0, proj, n, workspace, stats, warm_start);
63
+
64
+ if (proj[0] >= t0 - 0.1 * fabs(t0)) {
65
+ check_opt_cond_log_cone(proj, t0, v0, x0, n, stats->residuals, workspace);
66
+ } else {
67
+ status = -1;
68
+ }
69
+
70
+ if (status == 0 && stats->residuals[0] < DUAL_FEAS_TOL &&
71
+ stats->residuals[1] < PRI_FEAS_TOL &&
72
+ fabs(stats->residuals[2]) < COMP_TOL) {
73
+ stats->newton_success = NEWTON_SUCCESS;
74
+ return 0;
75
+ }
76
+
77
+ // ------------------------------------------------------------------------
78
+ // Solve problem with primal-dual IPM incorporating Mehrotra's correction
79
+ // etc.
80
+ // ------------------------------------------------------------------------
81
+ status = log_cone_IPM(t0, v0, x0, proj, n, workspace, stats, 0);
82
+ check_opt_cond_log_cone(proj, t0, v0, x0, n, stats->residuals, workspace);
83
+
84
+ *warm_start = true; // next iteration Newton should be warmstarted
85
+
86
+ if (status == 0 && stats->residuals[0] < DUAL_FEAS_TOL &&
87
+ stats->residuals[1] < PRI_FEAS_TOL &&
88
+ fabs(stats->residuals[2]) < COMP_TOL) {
89
+ stats->newton_success = IPM_VARIANT_0_SUCCESS;
90
+ return 0;
91
+ }
92
+
93
+ // ------------------------------------------------------------------------
94
+ // Solve problem with primal-dual IPM without Mehrotra's correction
95
+ // etc. (In all experiments in the paper by Cederberg and Boyd,
96
+ // the IPM above solves the problem correctly so the code below never
97
+ // runs. However, during development I ran into a (possibly pathological
98
+ // case) where the first IPM fails. If this happens we run below another
99
+ // version. This version of the IPM solves the problem that the first version
100
+ // failed on.)
101
+ // ------------------------------------------------------------------------
102
+ status = log_cone_IPM(t0, v0, x0, proj, n, workspace, stats, 1);
103
+ check_opt_cond_log_cone(proj, t0, v0, x0, n, stats->residuals, workspace);
104
+
105
+ if (status == 0 && stats->residuals[0] < DUAL_FEAS_TOL &&
106
+ stats->residuals[1] < PRI_FEAS_TOL &&
107
+ fabs(stats->residuals[2]) < COMP_TOL) {
108
+ stats->newton_success = IPM_VARIANT_1_SUCCESS;
109
+ return 0;
110
+ }
111
+
112
+ #ifdef SPECTRAL_DEBUG
113
+ scs_printf("FAILURE: logarithmic cone projection");
114
+ scs_printf("dual_res / pri_res / comp / iter: %.3e, %.3e, %.3e, %d\n",
115
+ stats->residuals[0], stats->residuals[1], stats->residuals[2],
116
+ stats->iter);
117
+
118
+ scs_printf("Projecting the following point:\n %.10e, %.10e", t0, v0);
119
+ for (scs_int i = 0; i < n; i++) {
120
+ scs_printf(" %.10e", x0[i]);
121
+ }
122
+ scs_printf("\n");
123
+ #endif
124
+
125
+ return -1;
126
+ }
127
+
128
+ // tvx = [t, v, x].
129
+ static void check_opt_cond_log_cone(const scs_float *tvx, scs_float t0,
130
+ scs_float v0, const scs_float *x0,
131
+ scs_int n, scs_float residuals[3],
132
+ scs_float *dualx) {
133
+ scs_float pri_res, dual_res, complementarity;
134
+
135
+ // -------------------------------------------------------
136
+ // Compute Lagrange multiplier
137
+ // -------------------------------------------------------
138
+ scs_float dualt = tvx[0] - t0;
139
+ if (fabs(dualt) < DUAL_T_THRESHOLD) {
140
+ dualt = DUAL_T_THRESHOLD;
141
+ }
142
+ scs_float dualv = tvx[1] - v0;
143
+ for (scs_int i = 0; i < n; ++i) {
144
+ dualx[i] = tvx[i + 2] - x0[i];
145
+ if (fabs(dualx[i]) < DUAL_X_THRESHOLD) {
146
+ dualx[i] = DUAL_X_THRESHOLD;
147
+ }
148
+ }
149
+
150
+ // ---------------------------------------------------------------------
151
+ // Compute complementarity measure
152
+ // ---------------------------------------------------------------------
153
+ complementarity =
154
+ tvx[0] * dualt + tvx[1] * dualv + SCS(dot)(dualx, tvx + 2, n);
155
+
156
+ // ----------------------------------------------------------------------
157
+ // Compute primal feasibility measure
158
+ // ----------------------------------------------------------------------
159
+ if (tvx[1] > 0 && is_pos(tvx + 2, n)) {
160
+ pri_res = -tvx[1] * (sum_log(tvx + 2, n) - n * log(tvx[1])) - tvx[0];
161
+ } else {
162
+ pri_res = tvx[1] * tvx[1];
163
+
164
+ if (tvx[0] < 0) {
165
+ pri_res += tvx[0] * tvx[0];
166
+ }
167
+
168
+ for (const scs_float *xi = tvx + 2; xi < tvx + 2 + n; ++xi) {
169
+ if (*xi < 0) {
170
+ pri_res += (*xi) * (*xi);
171
+ }
172
+ }
173
+ }
174
+
175
+ // ----------------------------------------------------------------------
176
+ // Compute dual feasibility measure
177
+ // ----------------------------------------------------------------------
178
+ if (dualt > 0 && is_pos(dualx, n)) {
179
+ dual_res = dualt * (n * log(dualt) - n - sum_log(dualx, n)) - dualv;
180
+ } else {
181
+ dual_res = dualt * dualt;
182
+
183
+ if (dualv < 0) {
184
+ dual_res += dualv * dualv;
185
+ }
186
+
187
+ for (scs_int i = 0; i < n; i++) {
188
+ if (dualx[i] < 0) {
189
+ dual_res += dualx[i] * dualx[i];
190
+ }
191
+ }
192
+ }
193
+
194
+ // --------------------------------------------------------
195
+ // Normalize the residuals and assign the result
196
+ // --------------------------------------------------------
197
+ scs_float dual_norm =
198
+ sqrt(dualt * dualt + dualv * dualv + SCS(norm_sq)(dualx, n));
199
+ scs_float pri_norm = SCS(norm_2)(tvx, n + 2);
200
+ residuals[0] = dual_res / MAX(dual_norm, 1.0);
201
+ residuals[1] = pri_res / MAX(pri_norm, 1.0);
202
+ double scale = MAX(pri_norm, 1.0);
203
+ residuals[2] = complementarity / MAX(scale, dual_norm);
204
+ }
205
+
@@ -0,0 +1,143 @@
1
+ #include "cones.h"
2
+ #include "glbopts.h"
3
+ #include "linalg.h"
4
+ #include "scs.h"
5
+ #include "scs_blas.h"
6
+ #include "util.h"
7
+ #include "util_spectral_cones.h"
8
+
9
+ /*
10
+ * Spectral matrix cone projections, from "Projection onto Spectral Matrix
11
+ * Cones" by Daniel Cederberg and Stephen Boyd, 2024.
12
+ *
13
+ * If you have any questions on the code, please reach out to the code author
14
+ * Daniel Cederberg.
15
+ *
16
+ * This file implements the projection onto the log-determinant cone.
17
+ *
18
+ * Last modified: 25 August 2024.
19
+ */
20
+
21
+ #ifdef __cplusplus
22
+ extern "C" {
23
+ #endif
24
+
25
+ void BLAS(syev)(const char *jobz, const char *uplo, blas_int *n, scs_float *a,
26
+ blas_int *lda, scs_float *w, scs_float *work, blas_int *lwork,
27
+ blas_int *info);
28
+ blas_int BLAS(syrk)(const char *uplo, const char *trans, const blas_int *n,
29
+ const blas_int *k, const scs_float *alpha,
30
+ const scs_float *a, const blas_int *lda,
31
+ const scs_float *beta, scs_float *c, const blas_int *ldc);
32
+ void BLAS(scal)(const blas_int *n, const scs_float *sa, scs_float *sx,
33
+ const blas_int *incx);
34
+
35
+ #ifdef __cplusplus
36
+ }
37
+ #endif
38
+
39
+ // forward declare from log_cone_wrapper.c
40
+ scs_int log_cone_proj_wrapper(scs_float t0, scs_float v0, scs_float *x0,
41
+ scs_float *proj, scs_int n, scs_float *workspace,
42
+ Newton_stats *stats, bool *warm_start);
43
+
44
+ scs_int SCS(proj_logdet_cone)(scs_float *tvX, scs_int n, ScsConeWork *c,
45
+ scs_int offset, bool *warmstart) {
46
+ Newton_stats *stats = &(c->newton_stats);
47
+
48
+ // tvX = [t, v, X], where X represents the lower triangular part of a matrix
49
+ // stored in a compact form and off-diagonal elements have been scaled by
50
+ // sqrt(2)
51
+ scs_float *X = tvX + 2;
52
+
53
+ #ifndef USE_LAPACK
54
+ scs_printf("FAILURE: solving SDP but no blas/lapack libraries were found!\n");
55
+ scs_printf("SCS will return nonsense!\n");
56
+ SCS(scale_array)(X, NAN, n);
57
+ return -1;
58
+ #endif
59
+
60
+ // ----------------------------------------------------------------------
61
+ // compute eigendecomposition
62
+ // ----------------------------------------------------------------------
63
+ scs_int i;
64
+ blas_int nb = (blas_int)n;
65
+ blas_int nb_plus_one = (blas_int)(n + 1);
66
+ blas_int one_int = 1;
67
+ scs_float zero = 0., one = 1.;
68
+ scs_float sqrt2 = SQRTF(2.0);
69
+ scs_float sqrt2_inv = 1.0 / sqrt2;
70
+ scs_float *Xs = c->Xs;
71
+ scs_float *Z = c->Z;
72
+ scs_float *e = c->e;
73
+ scs_float *work = c->work;
74
+ blas_int lwork = c->lwork;
75
+ blas_int info = 0;
76
+ scs_float sq_eig_pos;
77
+
78
+ // copy lower triangular matrix into full matrix
79
+ for (i = 0; i < n; ++i) {
80
+ memcpy(&(Xs[i * (n + 1)]), &(X[i * n - ((i - 1) * i) / 2]),
81
+ (n - i) * sizeof(scs_float));
82
+ }
83
+
84
+ // rescale diags by sqrt(2)
85
+ BLAS(scal)(&nb, &sqrt2, Xs, &nb_plus_one);
86
+
87
+ // Eigendecomposition. On exit, the lower triangular part of Xs stores
88
+ // the eigenvectors. The vector e stores the eigenvalues in ascending
89
+ // order (smallest eigenvalue first) */
90
+ BLAS(syev)("Vectors", "Lower", &nb, Xs, &nb, e, work, &lwork, &info);
91
+ if (info != 0) {
92
+ scs_printf("WARN: LAPACK syev error, info = %i\n", (int)info);
93
+ if (info < 0) {
94
+ scs_printf("entering LAPACK stuff!\n");
95
+ return info;
96
+ }
97
+ }
98
+
99
+ // ----------------------------------------------------------------------
100
+ // Project onto spectral *vector* cone. Note that e is sqrt(2) times
101
+ // the eigenvalue vector we want to project. We therefore multiply
102
+ // tvX[0] and tvX[1] by sqrt(2). The projection of sqrt(2) * (t0, v0,
103
+ // evals) is stored in current_log_proj
104
+ // (or equivalently, in c->saved_log_projs + offset)
105
+ // ----------------------------------------------------------------------
106
+ scs_float *current_log_proj = c->saved_log_projs + offset;
107
+ SPECTRAL_TIMING(SCS(timer) _timer; SCS(tic)(&_timer);)
108
+ scs_int status =
109
+ log_cone_proj_wrapper(sqrt2 * tvX[0], sqrt2 * tvX[1], e, current_log_proj,
110
+ n, c->work_logdet, stats, warmstart);
111
+ SPECTRAL_TIMING(c->tot_time_vec_cone_proj += SCS(tocq)(&_timer);)
112
+
113
+ if (status < 0) {
114
+ return status;
115
+ }
116
+ // return immediately if the origin is the solution
117
+ else if (status == IN_NEGATIVE_DUAL_CONE) {
118
+ memset(tvX, 0, sizeof(scs_float) * ((n * (n + 1)) / 2 + 2));
119
+ return 0;
120
+ }
121
+
122
+ // ----------------------------------------------------------------------
123
+ // recover projection onto spectral *matrix* cone
124
+ // ----------------------------------------------------------------------
125
+ scs_float *evals_proj = current_log_proj + 2;
126
+ for (i = 0; i < n; ++i) {
127
+ assert(evals_proj[i] >= 0);
128
+ sq_eig_pos = SQRTF(evals_proj[i]);
129
+ BLAS(scal)(&nb, &sq_eig_pos, &Xs[i * n], &one_int);
130
+ }
131
+
132
+ BLAS(syrk)("Lower", "NoTrans", &nb, &nb, &one, Xs, &nb, &zero, Z, &nb);
133
+ BLAS(scal)(&nb, &sqrt2_inv, Z, &nb_plus_one);
134
+
135
+ for (i = 0; i < n; ++i) {
136
+ memcpy(&(X[i * n - ((i - 1) * i) / 2]), &(Z[i * (n + 1)]),
137
+ (n - i) * sizeof(scs_float));
138
+ }
139
+ tvX[0] = sqrt2_inv * current_log_proj[0];
140
+ tvX[1] = sqrt2_inv * current_log_proj[1];
141
+
142
+ return 0;
143
+ }