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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/lib/scs/ffi.rb +2 -0
- data/lib/scs/solver.rb +15 -7
- data/lib/scs/version.rb +1 -1
- data/vendor/scs/CITATION.cff +2 -2
- data/vendor/scs/CMakeLists.txt +136 -6
- data/vendor/scs/Makefile +53 -3
- data/vendor/scs/README.md +1 -1
- data/vendor/scs/include/cones.h +47 -2
- data/vendor/scs/include/glbopts.h +1 -1
- data/vendor/scs/include/scs.h +29 -0
- data/vendor/scs/include/scs_blas.h +4 -0
- data/vendor/scs/include/scs_types.h +3 -1
- data/vendor/scs/include/util_spectral_cones.h +45 -0
- data/vendor/scs/linsys/cpu/direct/private.c +3 -3
- data/vendor/scs/linsys/cpu/direct/private.h +2 -1
- data/vendor/scs/linsys/csparse.c +1 -1
- data/vendor/scs/linsys/cudss/direct/private.c +279 -0
- data/vendor/scs/linsys/cudss/direct/private.h +63 -0
- data/vendor/scs/linsys/external/qdldl/qdldl_types.h +1 -1
- data/vendor/scs/linsys/gpu/indirect/private.c +14 -21
- data/vendor/scs/scs.mk +17 -2
- data/vendor/scs/src/aa.c +8 -12
- data/vendor/scs/src/cones.c +783 -12
- data/vendor/scs/src/rw.c +15 -1
- data/vendor/scs/src/scs.c +4 -0
- data/vendor/scs/src/spectral_cones/logdeterminant/log_cone_IPM.c +660 -0
- data/vendor/scs/src/spectral_cones/logdeterminant/log_cone_Newton.c +279 -0
- data/vendor/scs/src/spectral_cones/logdeterminant/log_cone_wrapper.c +205 -0
- data/vendor/scs/src/spectral_cones/logdeterminant/logdet_cone.c +143 -0
- data/vendor/scs/src/spectral_cones/nuclear/ell1_cone.c +221 -0
- data/vendor/scs/src/spectral_cones/nuclear/nuclear_cone.c +99 -0
- data/vendor/scs/src/spectral_cones/sum-largest/sum_largest_cone.c +196 -0
- data/vendor/scs/src/spectral_cones/sum-largest/sum_largest_eval_cone.c +140 -0
- data/vendor/scs/src/spectral_cones/util_spectral_cones.c +52 -0
- data/vendor/scs/test/problems/complex_PSD.h +83 -0
- data/vendor/scs/test/rng.h +4 -4
- data/vendor/scs/test/run_tests.c +25 -0
- data/vendor/scs/test/spectral_cones_problems/exp_design.h +141 -0
- data/vendor/scs/test/spectral_cones_problems/graph_partitioning.h +275 -0
- data/vendor/scs/test/spectral_cones_problems/robust_pca.h +253 -0
- data/vendor/scs/test/spectral_cones_problems/several_logdet_cones.h +222 -0
- data/vendor/scs/test/spectral_cones_problems/several_nuc_cone.h +285 -0
- data/vendor/scs/test/spectral_cones_problems/several_sum_largest.h +420 -0
- 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
|
+
}
|