scs 0.5.6 → 0.6.0

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 (95) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -0
  3. data/LICENSE.txt +1 -1
  4. data/lib/scs/ffi.rb +18 -0
  5. data/lib/scs/version.rb +1 -1
  6. data/vendor/scs/CITATION.cff +2 -3
  7. data/vendor/scs/CLAUDE.md +115 -0
  8. data/vendor/scs/CMakeLists.txt +330 -201
  9. data/vendor/scs/CONTRIBUTING.md +49 -0
  10. data/vendor/scs/Makefile +90 -43
  11. data/vendor/scs/README.md +96 -2
  12. data/vendor/scs/include/aa.h +59 -11
  13. data/vendor/scs/include/aa_stats.h +47 -0
  14. data/vendor/scs/include/cones.h +16 -7
  15. data/vendor/scs/include/glbopts.h +113 -26
  16. data/vendor/scs/include/linalg.h +5 -1
  17. data/vendor/scs/include/linsys.h +1 -2
  18. data/vendor/scs/include/normalize.h +6 -2
  19. data/vendor/scs/include/rw.h +9 -3
  20. data/vendor/scs/include/scs.h +17 -1
  21. data/vendor/scs/include/scs_blas.h +8 -0
  22. data/vendor/scs/include/scs_types.h +2 -2
  23. data/vendor/scs/include/scs_work.h +22 -1
  24. data/vendor/scs/include/util.h +3 -6
  25. data/vendor/scs/include/util_spectral_cones.h +3 -3
  26. data/vendor/scs/linsys/accelerate/direct/private.c +126 -0
  27. data/vendor/scs/linsys/accelerate/direct/private.h +34 -0
  28. data/vendor/scs/linsys/cpu/dense/private.c +250 -0
  29. data/vendor/scs/linsys/cpu/dense/private.h +31 -0
  30. data/vendor/scs/linsys/cpu/direct/private.c +86 -44
  31. data/vendor/scs/linsys/cpu/indirect/private.c +647 -113
  32. data/vendor/scs/linsys/cpu/indirect/private.h +28 -0
  33. data/vendor/scs/linsys/csparse.c +9 -3
  34. data/vendor/scs/linsys/csparse.h +4 -2
  35. data/vendor/scs/linsys/cudss/direct/private.c +77 -43
  36. data/vendor/scs/linsys/cudss/direct/private.h +33 -7
  37. data/vendor/scs/linsys/gpu/indirect/private.c +6 -2
  38. data/vendor/scs/linsys/mkl/direct/private.c +63 -31
  39. data/vendor/scs/linsys/mkl/direct/private.h +0 -1
  40. data/vendor/scs/linsys/scs_matrix.c +291 -165
  41. data/vendor/scs/linsys/scs_matrix.h +7 -9
  42. data/vendor/scs/scs.mk +35 -13
  43. data/vendor/scs/src/aa.c +673 -166
  44. data/vendor/scs/src/cones.c +545 -223
  45. data/vendor/scs/src/ctrlc.c +59 -16
  46. data/vendor/scs/src/exp_cone.c +70 -50
  47. data/vendor/scs/src/linalg.c +21 -2
  48. data/vendor/scs/src/normalize.c +24 -26
  49. data/vendor/scs/src/rw.c +596 -124
  50. data/vendor/scs/src/scs.c +990 -513
  51. data/vendor/scs/src/spectral_cones/logdeterminant/log_cone_IPM.c +240 -187
  52. data/vendor/scs/src/spectral_cones/logdeterminant/log_cone_Newton.c +108 -85
  53. data/vendor/scs/src/spectral_cones/logdeterminant/log_cone_wrapper.c +62 -63
  54. data/vendor/scs/src/spectral_cones/logdeterminant/logdet_cone.c +85 -78
  55. data/vendor/scs/src/spectral_cones/nuclear/ell1_cone.c +92 -97
  56. data/vendor/scs/src/spectral_cones/nuclear/nuclear_cone.c +39 -28
  57. data/vendor/scs/src/spectral_cones/sum-largest/sum_largest_cone.c +59 -40
  58. data/vendor/scs/src/spectral_cones/sum-largest/sum_largest_eval_cone.c +37 -29
  59. data/vendor/scs/src/spectral_cones/util_spectral_cones.c +12 -6
  60. data/vendor/scs/src/util.c +37 -7
  61. data/vendor/scs/test/mkl_interface_mismatch.c +97 -0
  62. data/vendor/scs/test/packaging/CMakeLists.txt +21 -0
  63. data/vendor/scs/test/packaging/consume.c +50 -0
  64. data/vendor/scs/test/problem_utils.h +9 -2
  65. data/vendor/scs/test/problems/dense_qp.h +64 -0
  66. data/vendor/scs/test/problems/hs21_tiny_qp.h +6 -2
  67. data/vendor/scs/test/problems/hs21_tiny_qp_rw.h +10 -2
  68. data/vendor/scs/test/problems/infeasible_lp.h +66 -0
  69. data/vendor/scs/test/problems/infeasible_socp.h +75 -0
  70. data/vendor/scs/test/problems/lp_update.h +110 -0
  71. data/vendor/scs/test/problems/qafiro_tiny_qp.h +3 -1
  72. data/vendor/scs/test/problems/rob_gauss_cov_est.h +6 -3
  73. data/vendor/scs/test/problems/small_qp.h +5 -0
  74. data/vendor/scs/test/problems/test_box_cone.h +92 -0
  75. data/vendor/scs/test/problems/test_dual_exp_cone.h +96 -0
  76. data/vendor/scs/test/problems/test_inaccurate.h +212 -0
  77. data/vendor/scs/test/problems/test_mixed_cones.h +113 -0
  78. data/vendor/scs/test/problems/test_normalize_roundtrip.h +279 -0
  79. data/vendor/scs/test/problems/test_power_cone.h +414 -0
  80. data/vendor/scs/test/problems/test_psd_metric.h +109 -0
  81. data/vendor/scs/test/problems/test_psd_n1.h +84 -0
  82. data/vendor/scs/test/problems/test_root_plus.h +191 -0
  83. data/vendor/scs/test/problems/test_rw_settings.h +112 -0
  84. data/vendor/scs/test/problems/test_soc_sizes.h +393 -0
  85. data/vendor/scs/test/problems/test_solver_options.h +558 -0
  86. data/vendor/scs/test/problems/test_validation.h +263 -24
  87. data/vendor/scs/test/problems/test_zero_cone.h +81 -0
  88. data/vendor/scs/test/problems/unbounded_lp.h +66 -0
  89. data/vendor/scs/test/problems/unbounded_socp.h +71 -0
  90. data/vendor/scs/test/run_from_file.c +6 -0
  91. data/vendor/scs/test/run_tests.c +71 -0
  92. data/vendor/scs/test/rw_settings.c +7 -0
  93. data/vendor/scs/test/spectral_cones_problems/test_ell1_and_nuc.h +128 -0
  94. data/vendor/scs/test/spectral_cones_problems/test_ell1_cone.h +115 -0
  95. metadata +35 -3
@@ -1,47 +1,8 @@
1
- #include "private.h"
2
-
3
- const char *scs_get_lin_sys_method(void) {
4
- return "sparse-indirect-scs";
5
- }
6
-
7
- /* Not possible to do this on the fly due to M_ii += a_i' (R_y)^-1 a_i */
8
- /* set M = inv ( diag ( R_x + P + A' R_y^{-1} A ) ) */
9
- static void set_preconditioner(ScsLinSysWork *p) {
10
- scs_int i, k;
11
- scs_float *M = p->M;
12
- const ScsMatrix *A = p->A;
13
- const ScsMatrix *P = p->P;
1
+ /* ======================== Includes / Types ======================== */
14
2
 
15
- #if VERBOSITY > 0
16
- scs_printf("getting pre-conditioner\n");
17
- #endif
3
+ #include "private.h"
18
4
 
19
- /* M_ii = (R_x)_i + P_ii + a_i' (R_y)^-1 a_i */
20
- for (i = 0; i < A->n; ++i) { /* cols */
21
- /* M_ii = (R_x)_i */
22
- M[i] = p->diag_r[i];
23
- /* M_ii += a_i' (R_y)^-1 a_i */
24
- for (k = A->p[i]; k < A->p[i + 1]; ++k) {
25
- /* A->i[k] is row of entry k with value A->x[k] */
26
- M[i] += A->x[k] * A->x[k] / p->diag_r[A->n + A->i[k]];
27
- }
28
- if (P) {
29
- for (k = P->p[i]; k < P->p[i + 1]; k++) {
30
- /* diagonal element only */
31
- if (P->i[k] == i) { /* row == col */
32
- /* M_ii += P_ii */
33
- M[i] += P->x[k];
34
- break;
35
- }
36
- }
37
- }
38
- /* finally invert for pre-conditioner */
39
- M[i] = 1. / M[i];
40
- }
41
- #if VERBOSITY > 0
42
- scs_printf("finished getting pre-conditioner\n");
43
- #endif
44
- }
5
+ /* ======================== Matrix Helpers ======================== */
45
6
 
46
7
  static void transpose(const ScsMatrix *A, ScsLinSysWork *p) {
47
8
  scs_int *Ci = p->At->i;
@@ -84,30 +45,45 @@ static void transpose(const ScsMatrix *A, ScsLinSysWork *p) {
84
45
  #endif
85
46
  }
86
47
 
87
- void scs_free_lin_sys_work(ScsLinSysWork *p) {
88
- if (p) {
89
- scs_free(p->p);
90
- scs_free(p->r);
91
- scs_free(p->Gp);
92
- scs_free(p->tmp);
93
- if (p->At) {
94
- scs_free(p->At->i);
95
- scs_free(p->At->x);
96
- scs_free(p->At->p);
97
- scs_free(p->At);
48
+ /* Not possible to do this on the fly due to M_ii += a_i' (R_y)^-1 a_i */
49
+ /* set M = inv ( diag ( R_x + P + A' R_y^{-1} A ) ) */
50
+ static void set_preconditioner(ScsLinSysWork *p) {
51
+ scs_int i, k;
52
+ scs_float *M = p->M;
53
+ const ScsMatrix *A = p->A;
54
+ const ScsMatrix *P = p->P;
55
+
56
+ #if VERBOSITY > 0
57
+ scs_printf("getting pre-conditioner\n");
58
+ #endif
59
+
60
+ /* M_ii = (R_x)_i + P_ii + a_i' (R_y)^-1 a_i */
61
+ for (i = 0; i < A->n; ++i) { /* cols */
62
+ /* M_ii = (R_x)_i */
63
+ M[i] = p->diag_r[i];
64
+ /* M_ii += a_i' (R_y)^-1 a_i */
65
+ for (k = A->p[i]; k < A->p[i + 1]; ++k) {
66
+ /* A->i[k] is row of entry k with value A->x[k] */
67
+ M[i] += A->x[k] * A->x[k] / p->diag_r[A->n + A->i[k]];
98
68
  }
99
- scs_free(p->z);
100
- scs_free(p->M);
101
- scs_free(p);
69
+ if (P) {
70
+ for (k = P->p[i]; k < P->p[i + 1]; ++k) {
71
+ if (P->i[k] == i) {
72
+ M[i] += P->x[k];
73
+ }
74
+ }
75
+ }
76
+ /* finally invert for pre-conditioner */
77
+ M[i] = 1. / M[i];
102
78
  }
79
+ #if VERBOSITY > 0
80
+ scs_printf("finished getting pre-conditioner\n");
81
+ #endif
103
82
  }
104
83
 
105
- /* vec -> R_y^{-1} vec */
106
- static void scale_by_r_y_inv(scs_float *vec, ScsLinSysWork *p) {
107
- scs_int i;
108
- for (i = 0; i < p->m; ++i) {
109
- vec[i] /= p->diag_r[p->n + i];
110
- }
84
+ /* we use a different accum_by_a here for speed */
85
+ static void accum_by_a(ScsLinSysWork *p, const scs_float *x, scs_float *y) {
86
+ SCS(accum_by_atrans)(p->At, x, y);
111
87
  }
112
88
 
113
89
  /* y += R_x * x */
@@ -118,9 +94,12 @@ static void accum_by_r_x(scs_float *y, const scs_float *x, ScsLinSysWork *p) {
118
94
  }
119
95
  }
120
96
 
121
- /* we use a different accum_by_a here for speed */
122
- static void accum_by_a(ScsLinSysWork *p, const scs_float *x, scs_float *y) {
123
- SCS(accum_by_atrans)(p->At, x, y);
97
+ /* vec -> R_y^{-1} vec */
98
+ static void scale_by_r_y_inv(scs_float *vec, ScsLinSysWork *p) {
99
+ scs_int i;
100
+ for (i = 0; i < p->m; ++i) {
101
+ vec[i] /= p->diag_r[p->n + i];
102
+ }
124
103
  }
125
104
 
126
105
  /* y = (R_x + P + A' R_y^{-1} A) x */
@@ -148,49 +127,389 @@ static void apply_pre_conditioner(scs_float *z, scs_float *r, scs_int n,
148
127
  }
149
128
  }
150
129
 
151
- /* no need to update anything in this case */
152
- void scs_update_lin_sys_diag_r(ScsLinSysWork *p, const scs_float *diag_r) {
153
- p->diag_r = diag_r; /* this isn't needed but do it to be safe */
154
- set_preconditioner(p);
130
+ /* ======================== eigCG deflation ======================== */
131
+
132
+ #define EIGCG_WINDOW (60)
133
+
134
+ #ifdef USE_LAPACK
135
+ #include "scs_blas.h"
136
+
137
+ #ifdef __cplusplus
138
+ extern "C" {
139
+ #endif
140
+
141
+ void BLAS(syev)(const char *jobz, const char *uplo, blas_int *n, scs_float *a,
142
+ blas_int *lda, scs_float *w, scs_float *work, blas_int *lwork,
143
+ blas_int *info);
144
+ void BLAS(gemm)(const char *transa, const char *transb, blas_int *m,
145
+ blas_int *n, blas_int *k, scs_float *alpha, scs_float *a,
146
+ blas_int *lda, scs_float *b, blas_int *ldb, scs_float *beta,
147
+ scs_float *c, blas_int *ldc);
148
+
149
+ #ifdef __cplusplus
155
150
  }
151
+ #endif
156
152
 
157
- ScsLinSysWork *scs_init_lin_sys_work(const ScsMatrix *A, const ScsMatrix *P,
158
- const scs_float *diag_r) {
159
- ScsLinSysWork *p = (ScsLinSysWork *)scs_calloc(1, sizeof(ScsLinSysWork));
160
- p->A = A;
161
- p->P = P;
162
- p->m = A->m;
163
- p->n = A->n;
153
+ /* Cholesky of a k x k column-major matrix, lower triangle, in place.
154
+ * Returns nonzero on failure (also catches NaN via the negated compare). */
155
+ static scs_int eigcg_chol(scs_float *G, scs_int k) {
156
+ scs_int c, r, t;
157
+ for (c = 0; c < k; ++c) {
158
+ for (r = c; r < k; ++r) {
159
+ scs_float acc = G[c * k + r];
160
+ for (t = 0; t < c; ++t) {
161
+ acc -= G[t * k + r] * G[t * k + c];
162
+ }
163
+ G[c * k + r] = acc;
164
+ }
165
+ if (!(G[c * k + c] > 1e-300)) {
166
+ return 1;
167
+ }
168
+ G[c * k + c] = SQRTF(G[c * k + c]);
169
+ for (r = c + 1; r < k; ++r) {
170
+ G[c * k + r] /= G[c * k + c];
171
+ }
172
+ }
173
+ return 0;
174
+ }
164
175
 
165
- p->p = (scs_float *)scs_calloc((A->n), sizeof(scs_float));
166
- p->r = (scs_float *)scs_calloc((A->n), sizeof(scs_float));
167
- p->Gp = (scs_float *)scs_calloc((A->n), sizeof(scs_float));
168
- p->tmp = (scs_float *)scs_calloc((A->m), sizeof(scs_float));
176
+ /* solve L L' x = rhs in place given the factor from eigcg_chol */
177
+ static void eigcg_chol_solve(const scs_float *G, scs_float *x, scs_int k) {
178
+ scs_int c, t;
179
+ for (c = 0; c < k; ++c) {
180
+ for (t = 0; t < c; ++t) {
181
+ x[c] -= G[t * k + c] * x[t];
182
+ }
183
+ x[c] /= G[c * k + c];
184
+ }
185
+ for (c = k - 1; c >= 0; --c) {
186
+ for (t = c + 1; t < k; ++t) {
187
+ x[c] -= G[c * k + t] * x[t];
188
+ }
189
+ x[c] /= G[c * k + c];
190
+ }
191
+ }
169
192
 
170
- /* memory for A transpose */
171
- p->At = (ScsMatrix *)scs_calloc(1, sizeof(ScsMatrix));
172
- p->At->m = A->n;
173
- p->At->n = A->m;
174
- p->At->i = (scs_int *)scs_calloc((A->p[A->n]), sizeof(scs_int));
175
- p->At->p = (scs_int *)scs_calloc((A->m + 1), sizeof(scs_int));
176
- p->At->x = (scs_float *)scs_calloc((A->p[A->n]), sizeof(scs_float));
177
- transpose(A, p);
193
+ /* eigendecomposition of the leading j x j block of eig_T (lda eig_win):
194
+ * eigenvectors into E (lda j), ascending eigenvalues into w */
195
+ static scs_int eigcg_eig_leading(ScsLinSysWork *pr, scs_int j, scs_float *E,
196
+ scs_float *w) {
197
+ blas_int bn = (blas_int)j, lda = (blas_int)j,
198
+ lwork = (blas_int)pr->eig_lwork, info = 0;
199
+ scs_int a, b;
200
+ for (a = 0; a < j; ++a) {
201
+ for (b = 0; b < j; ++b) {
202
+ E[a * j + b] = pr->eig_T[a * pr->eig_win + b];
203
+ }
204
+ }
205
+ BLAS(syev)("V", "U", &bn, E, &lda, w, pr->eig_work, &lwork, &info);
206
+ return (scs_int)info;
207
+ }
178
208
 
179
- /* preconditioner memory */
180
- p->diag_r = diag_r;
181
- p->z = (scs_float *)scs_calloc(A->n, sizeof(scs_float));
182
- p->M = (scs_float *)scs_calloc(A->n, sizeof(scs_float));
183
- set_preconditioner(p);
209
+ /* modified Gram-Schmidt on the columns of S, in place with compaction;
210
+ * returns the numerical rank kept */
211
+ static scs_int eigcg_mgs(scs_float *S, scs_int rows, scs_int cols) {
212
+ scs_int c, k, t, pass, kept = 0;
213
+ for (c = 0; c < cols; ++c) {
214
+ scs_float *v = &(S[c * rows]);
215
+ scs_float nrm = 0.;
216
+ for (pass = 0; pass < 2; ++pass) {
217
+ for (k = 0; k < kept; ++k) {
218
+ const scs_float *qk = &(S[k * rows]);
219
+ scs_float d = 0.;
220
+ for (t = 0; t < rows; ++t) {
221
+ d += qk[t] * v[t];
222
+ }
223
+ for (t = 0; t < rows; ++t) {
224
+ v[t] -= d * qk[t];
225
+ }
226
+ }
227
+ }
228
+ for (t = 0; t < rows; ++t) {
229
+ nrm += v[t] * v[t];
230
+ }
231
+ nrm = SQRTF(nrm);
232
+ if (nrm > 1e-8) {
233
+ for (t = 0; t < rows; ++t) {
234
+ v[t] /= nrm;
235
+ }
236
+ if (kept != c) {
237
+ memcpy(&(S[kept * rows]), v, rows * sizeof(scs_float));
238
+ }
239
+ kept++;
240
+ }
241
+ }
242
+ return kept;
243
+ }
184
244
 
185
- p->tot_cg_its = 0;
186
- if (!p->p || !p->r || !p->Gp || !p->tmp || !p->At || !p->At->i || !p->At->p ||
187
- !p->At->x) {
188
- scs_free_lin_sys_work(p);
189
- return SCS_NULL;
245
+ /* Thick restart: compress the full window to the lowest-nev Ritz vectors of
246
+ * this window AND of the previous one (the eigCG doubling trick, which is
247
+ * what makes the Ritz pairs converge despite restarting), re-diagonalise,
248
+ * and leave the arrowhead border that couples the compressed basis to the
249
+ * next incoming Lanczos vector. The CG recurrence is never touched. */
250
+ static void eigcg_restart(ScsLinSysWork *pr, scs_float tlink) {
251
+ scs_int win = pr->eig_win, nev = pr->dfl_max, n = pr->n, q, a, t;
252
+ scs_float *E = pr->eig_E, *w = pr->eig_w, *S = pr->eig_S, *TS = pr->eig_TS,
253
+ *H = pr->eig_H, *G = pr->eig_G, *T = pr->eig_T;
254
+ scs_float one = 1., zero = 0.;
255
+ blas_int bm, bn2, bk, ld1, ld2, ld3, info = 0,
256
+ lwork = (blas_int)pr->eig_lwork;
257
+ /* lowest nev of T_win */
258
+ if (eigcg_eig_leading(pr, win, E, w)) {
259
+ pr->eig_dead = 1;
260
+ return;
190
261
  }
191
- return p;
262
+ for (a = 0; a < nev; ++a) {
263
+ memcpy(&(S[a * win]), &(E[a * win]), win * sizeof(scs_float));
264
+ }
265
+ /* lowest nev of the leading (win-1) block, zero-padded last row */
266
+ if (eigcg_eig_leading(pr, win - 1, E, w)) {
267
+ pr->eig_dead = 1;
268
+ return;
269
+ }
270
+ for (a = 0; a < nev; ++a) {
271
+ for (t = 0; t < win - 1; ++t) {
272
+ S[(nev + a) * win + t] = E[a * (win - 1) + t];
273
+ }
274
+ S[(nev + a) * win + win - 1] = 0.;
275
+ }
276
+ q = eigcg_mgs(S, win, 2 * nev);
277
+ if (q <= 0) {
278
+ pr->eig_dead = 1;
279
+ return;
280
+ }
281
+ /* H = S' T S */
282
+ bm = (blas_int)win;
283
+ bn2 = (blas_int)q;
284
+ bk = (blas_int)win;
285
+ ld1 = (blas_int)win;
286
+ BLAS(gemm)("N", "N", &bm, &bn2, &bk, &one, T, &ld1, S, &ld1, &zero, TS,
287
+ &ld1);
288
+ bm = (blas_int)q;
289
+ ld2 = (blas_int)q;
290
+ BLAS(gemm)("T", "N", &bm, &bn2, &bk, &one, S, &ld1, TS, &ld1, &zero, H,
291
+ &ld2);
292
+ BLAS(syev)("V", "U", &bm, H, &ld2, w, pr->eig_work, &lwork, &info);
293
+ if (info) {
294
+ pr->eig_dead = 1;
295
+ return;
296
+ }
297
+ /* G = S U; V(:, 0:q) = V(:, 0:win) G */
298
+ bm = (blas_int)win;
299
+ bk = (blas_int)q;
300
+ BLAS(gemm)("N", "N", &bm, &bn2, &bk, &one, S, &ld1, H, &ld2, &zero, G,
301
+ &ld1);
302
+ bm = (blas_int)n;
303
+ bk = (blas_int)win;
304
+ ld3 = (blas_int)n;
305
+ BLAS(gemm)("N", "N", &bm, &bn2, &bk, &one, pr->eig_V, &ld3, G, &ld1, &zero,
306
+ pr->eig_VS, &ld3);
307
+ memcpy(pr->eig_V, pr->eig_VS, (size_t)q * n * sizeof(scs_float));
308
+ /* T = diag(ritz values) + arrowhead border to the incoming vector */
309
+ memset(T, 0, (size_t)win * win * sizeof(scs_float));
310
+ for (a = 0; a < q; ++a) {
311
+ T[a * win + a] = w[a];
312
+ }
313
+ for (a = 0; a < q; ++a) {
314
+ scs_float sa = tlink * G[a * win + (win - 1)];
315
+ T[q * win + a] = sa;
316
+ T[a * win + q] = sa;
317
+ }
318
+ pr->eig_j = q;
319
+ pr->eig_jd = q;
320
+ }
321
+
322
+ /* the window is transient: live only between eigcg_start and
323
+ * eigcg_finish, i.e. during the one deep solve per metric change, so the
324
+ * persistent footprint of deflation is just W and AW (2 k n) */
325
+ static void eigcg_free_window(ScsLinSysWork *pr) {
326
+ scs_free(pr->eig_V);
327
+ scs_free(pr->eig_T);
328
+ scs_free(pr->eig_E);
329
+ scs_free(pr->eig_w);
330
+ scs_free(pr->eig_S);
331
+ scs_free(pr->eig_TS);
332
+ scs_free(pr->eig_H);
333
+ scs_free(pr->eig_G);
334
+ scs_free(pr->eig_VS);
335
+ scs_free(pr->eig_work);
336
+ pr->eig_V = pr->eig_T = pr->eig_E = pr->eig_w = pr->eig_S = pr->eig_TS =
337
+ pr->eig_H = pr->eig_G = pr->eig_VS = pr->eig_work = SCS_NULL;
338
+ }
339
+
340
+ static scs_int eigcg_alloc_window(ScsLinSysWork *pr) {
341
+ scs_int n = pr->n, win = pr->eig_win, nev2 = 2 * pr->dfl_max;
342
+ pr->eig_V = (scs_float *)scs_calloc((size_t)n * win, sizeof(scs_float));
343
+ pr->eig_T = (scs_float *)scs_calloc((size_t)win * win, sizeof(scs_float));
344
+ pr->eig_E = (scs_float *)scs_calloc((size_t)win * win, sizeof(scs_float));
345
+ pr->eig_w = (scs_float *)scs_calloc(win, sizeof(scs_float));
346
+ pr->eig_S = (scs_float *)scs_calloc((size_t)win * nev2, sizeof(scs_float));
347
+ pr->eig_TS = (scs_float *)scs_calloc((size_t)win * nev2, sizeof(scs_float));
348
+ pr->eig_H = (scs_float *)scs_calloc((size_t)nev2 * nev2, sizeof(scs_float));
349
+ pr->eig_G = (scs_float *)scs_calloc((size_t)win * nev2, sizeof(scs_float));
350
+ pr->eig_VS = (scs_float *)scs_calloc((size_t)n * nev2, sizeof(scs_float));
351
+ pr->eig_work =
352
+ (scs_float *)scs_calloc(MAX(pr->eig_lwork, 1), sizeof(scs_float));
353
+ if (!pr->eig_V || !pr->eig_T || !pr->eig_E || !pr->eig_w || !pr->eig_S ||
354
+ !pr->eig_TS || !pr->eig_H || !pr->eig_G || !pr->eig_VS ||
355
+ !pr->eig_work) {
356
+ eigcg_free_window(pr);
357
+ return 1;
358
+ }
359
+ return 0;
360
+ }
361
+
362
+ /* begin a harvest: column 0 is the first preconditioned residual */
363
+ static void eigcg_start(ScsLinSysWork *pr, const scs_float *z, scs_float ztr) {
364
+ scs_int t, n = pr->n;
365
+ scs_float isr;
366
+ if (!pr->eig_V && eigcg_alloc_window(pr)) {
367
+ pr->eig_dead = 1;
368
+ return;
369
+ }
370
+ pr->eig_j = 0;
371
+ pr->eig_jd = 0;
372
+ pr->eig_step = 0;
373
+ pr->eig_have_prev = 0;
374
+ pr->eig_dead = 0;
375
+ memset(pr->eig_T, 0, (size_t)pr->eig_win * pr->eig_win * sizeof(scs_float));
376
+ if (!(ztr > 0.)) {
377
+ pr->eig_dead = 1;
378
+ return;
379
+ }
380
+ isr = 1.0 / SQRTF(ztr);
381
+ for (t = 0; t < n; ++t) {
382
+ pr->eig_V[t] = isr * z[t];
383
+ }
384
+ pr->eig_j = 1;
385
+ pr->eig_step = 1;
386
+ }
387
+
388
+ /* One observation per CG iteration, at the point where alpha_i, beta_{i+1},
389
+ * z_{i+1} and ztr_{i+1} are all fresh. Completes the pending diagonal entry
390
+ * (1/alpha_i + beta_i/alpha_{i-1}), links the incoming Lanczos vector with
391
+ * sqrt(beta_{i+1})/alpha_i, and appends it with the (-1)^step sign that the
392
+ * positive-offdiagonal tridiagonal convention requires. */
393
+ static void eigcg_accum(ScsLinSysWork *pr, scs_float alpha, scs_float beta_next,
394
+ const scs_float *z, scs_float ztr) {
395
+ scs_int n = pr->n, win = pr->eig_win, cur, t;
396
+ scs_float d, tlink, sgn;
397
+ scs_float *T = pr->eig_T;
398
+ if (pr->eig_dead) {
399
+ return;
400
+ }
401
+ if (!(alpha > 0.) || !(beta_next >= 0.) || !(ztr > 0.)) {
402
+ pr->eig_dead = 1;
403
+ return;
404
+ }
405
+ cur = pr->eig_j - 1;
406
+ d = 1. / alpha;
407
+ if (pr->eig_have_prev) {
408
+ d += pr->eig_pb / pr->eig_pa;
409
+ }
410
+ T[cur * win + cur] = d;
411
+ pr->eig_jd = pr->eig_j;
412
+ tlink = SQRTF(beta_next) / alpha;
413
+ if (pr->eig_j == win) {
414
+ eigcg_restart(pr, tlink);
415
+ if (pr->eig_dead) {
416
+ return;
417
+ }
418
+ } else {
419
+ T[pr->eig_j * win + cur] = tlink;
420
+ T[cur * win + pr->eig_j] = tlink;
421
+ }
422
+ sgn = (pr->eig_step % 2) ? -1.0 / SQRTF(ztr) : 1.0 / SQRTF(ztr);
423
+ {
424
+ scs_float *col = &(pr->eig_V[pr->eig_j * n]);
425
+ for (t = 0; t < n; ++t) {
426
+ col[t] = sgn * z[t];
427
+ }
428
+ }
429
+ pr->eig_j++;
430
+ pr->eig_step++;
431
+ pr->eig_pa = alpha;
432
+ pr->eig_pb = beta_next;
433
+ pr->eig_have_prev = 1;
192
434
  }
193
435
 
436
+ /* After the deep solve: extract the lowest-kv Ritz vectors, form their true
437
+ * images through the operator (kv extra matvecs, counted by the caller),
438
+ * factor the Gram matrix. Returns the number of matvecs spent. */
439
+ static scs_int eigcg_extract(ScsLinSysWork *pr) {
440
+ scs_int n = pr->n, jd = pr->eig_jd, kv, a, b, probes;
441
+ scs_float *E = pr->eig_E, *w = pr->eig_w;
442
+ scs_float one = 1., zero = 0.;
443
+ blas_int bm, bn2, bk, ld1, ld2;
444
+ pr->dfl_count = 0;
445
+ kv = MIN(pr->dfl_max, jd);
446
+ if (kv <= 0 || !pr->eig_V) {
447
+ return 0;
448
+ }
449
+ if (eigcg_eig_leading(pr, jd, E, w)) {
450
+ return 0;
451
+ }
452
+ probes = kv;
453
+ bm = (blas_int)n;
454
+ bn2 = (blas_int)kv;
455
+ bk = (blas_int)jd;
456
+ ld1 = (blas_int)n;
457
+ ld2 = (blas_int)jd;
458
+ BLAS(gemm)("N", "N", &bm, &bn2, &bk, &one, pr->eig_V, &ld1, E, &ld2, &zero,
459
+ pr->dfl_p, &ld1);
460
+ {
461
+ /* Keep only columns whose eigenresidual A w = theta Mp w actually
462
+ * holds: deflation coefficients scale like 1/lambda, so a grossly
463
+ * wrong vector injects error amplified by the smallest eigenvalue.
464
+ * Exact vectors pass at machine precision; the tolerance is loose. */
465
+ scs_int kept = 0, t;
466
+ for (a = 0; a < kv; ++a) {
467
+ scs_float *wv = &(pr->dfl_p[a * n]);
468
+ scs_float *awv = &(pr->dfl_Ap[kept * n]);
469
+ scs_float rr = 0., mm = 0., th = w[a];
470
+ if (kept != a) {
471
+ memcpy(&(pr->dfl_p[kept * n]), wv, n * sizeof(scs_float));
472
+ wv = &(pr->dfl_p[kept * n]);
473
+ }
474
+ mat_vec(pr->A, pr->P, pr, wv, awv);
475
+ for (t = 0; t < n; ++t) {
476
+ scs_float mw = th * wv[t] / pr->M[t];
477
+ rr += (awv[t] - mw) * (awv[t] - mw);
478
+ mm += mw * mw;
479
+ }
480
+ if (mm > 0. && rr <= 0.25 * 0.25 * mm) {
481
+ w[kept] = th;
482
+ kept++;
483
+ }
484
+ }
485
+ kv = kept;
486
+ if (kv <= 0) {
487
+ return probes; /* matvecs were still spent probing */
488
+ }
489
+ }
490
+ for (a = 0; a < kv; ++a) {
491
+ for (b = 0; b < kv; ++b) {
492
+ pr->dfl_G[b * kv + a] =
493
+ SCS(dot)(&(pr->dfl_p[a * n]), &(pr->dfl_Ap[b * n]), n);
494
+ }
495
+ }
496
+ if (eigcg_chol(pr->dfl_G, kv)) {
497
+ return probes;
498
+ }
499
+ pr->dfl_count = kv;
500
+ return probes;
501
+ }
502
+
503
+ static scs_int eigcg_finish(ScsLinSysWork *pr) {
504
+ scs_int probes = eigcg_extract(pr);
505
+ eigcg_free_window(pr);
506
+ pr->eig_jd = 0;
507
+ return probes;
508
+ }
509
+ #endif /* USE_LAPACK */
510
+
511
+ /* ======================== Conjugate Gradient Solver ======================== */
512
+
194
513
  /* solves (R_x * I + P + A' R_y^{-1} A)x = b, s warm start, solution in b */
195
514
  static scs_int pcg(ScsLinSysWork *pr, const scs_float *s, scs_float *b,
196
515
  scs_int max_its, scs_float tol) {
@@ -221,6 +540,50 @@ static scs_int pcg(ScsLinSysWork *pr, const scs_float *s, scs_float *b,
221
540
  memcpy(b, s, n * sizeof(scs_float));
222
541
  }
223
542
 
543
+ /* Deflation: Galerkin-project the residual against the harvested
544
+ * eigenvector approximations, solve (W'AW) c = W'r via the stored
545
+ * Cholesky factor, and remove the correction. Because W spans a
546
+ * near-invariant subspace of the preconditioned operator, CG does not
547
+ * reintroduce the deflated components -- a single projection here
548
+ * suffices (init-CG). Costs dot products and axpys only. */
549
+ #ifdef USE_LAPACK
550
+ if (pr->dfl_count > 0) {
551
+ scs_int j, k, kd = pr->dfl_count;
552
+ scs_float nrm0 = CG_NORM(r, n), nrm1;
553
+ for (j = 0; j < kd; ++j) {
554
+ pr->dfl_c[j] = SCS(dot)(&(pr->dfl_p[j * n]), r, n);
555
+ }
556
+ eigcg_chol_solve(pr->dfl_G, pr->dfl_c, kd);
557
+ for (j = 0; j < kd; ++j) {
558
+ const scs_float cj = pr->dfl_c[j];
559
+ const scs_float *wj = &(pr->dfl_p[j * n]);
560
+ const scs_float *awj = &(pr->dfl_Ap[j * n]);
561
+ if (cj == 0.) {
562
+ continue;
563
+ }
564
+ for (k = 0; k < n; ++k) {
565
+ b[k] += cj * wj[k];
566
+ r[k] -= cj * awj[k];
567
+ }
568
+ }
569
+ /* For exact eigenvectors this projector is orthogonal and cannot
570
+ * grow the residual; growth means the Ritz error is being amplified
571
+ * by 1/lambda on this right-hand side -- take it back. */
572
+ nrm1 = CG_NORM(r, n);
573
+ if (!(nrm1 <= 1.5 * nrm0)) {
574
+ for (j = 0; j < kd; ++j) {
575
+ const scs_float cj = pr->dfl_c[j];
576
+ const scs_float *wj = &(pr->dfl_p[j * n]);
577
+ const scs_float *awj = &(pr->dfl_Ap[j * n]);
578
+ for (k = 0; k < n; ++k) {
579
+ b[k] -= cj * wj[k];
580
+ r[k] += cj * awj[k];
581
+ }
582
+ }
583
+ }
584
+ }
585
+ #endif
586
+
224
587
  /* check to see if we need to run CG at all */
225
588
  if (CG_NORM(r, n) < MAX(tol, 1e-12)) {
226
589
  return 0;
@@ -232,8 +595,16 @@ static scs_int pcg(ScsLinSysWork *pr, const scs_float *s, scs_float *b,
232
595
  ztr = SCS(dot)(z, r, n);
233
596
  /* p = z */
234
597
  memcpy(p, z, n * sizeof(scs_float));
598
+ #ifdef USE_LAPACK
599
+ if (pr->dfl_harvest) {
600
+ eigcg_start(pr, z, ztr);
601
+ }
602
+ #endif
235
603
 
236
604
  for (i = 0; i < max_its; ++i) {
605
+ scs_float norm_r, beta;
606
+ scs_int k;
607
+ scs_float *M = pr->M;
237
608
  /* Gp = Mat * p */
238
609
  mat_vec(A, P, pr, p, Gp);
239
610
  /* alpha = z'r / p'G p */
@@ -243,26 +614,137 @@ static scs_int pcg(ScsLinSysWork *pr, const scs_float *s, scs_float *b,
243
614
  /* r -= alpha * G p */
244
615
  SCS(add_scaled_array)(r, Gp, n, -alpha);
245
616
 
617
+ /* Fuse convergence norm, preconditioner apply, and z'r dot product into
618
+ * one pass over r, saving two separate vector scans per CG iteration. */
619
+ ztr_prev = ztr;
620
+ norm_r = 0.0;
621
+ ztr = 0.0;
622
+ for (k = 0; k < n; ++k) {
623
+ scs_float rk = r[k], zk = rk * M[k], ark = ABS(rk);
624
+ z[k] = zk;
625
+ ztr += zk * rk;
626
+ if (ark > norm_r) norm_r = ark;
627
+ }
246
628
  #if VERBOSITY > 3
247
- scs_printf("tol: %.4e, resid: %.4e, iters: %li\n", tol, CG_NORM(r, n),
629
+ scs_printf("tol: %.4e, resid: %.4e, iters: %li\n", tol, norm_r,
248
630
  (long)i + 1);
249
631
  #endif
250
- if (CG_NORM(r, n) < tol) {
632
+ if (norm_r < tol) {
251
633
  return i + 1;
252
634
  }
253
- /* z = M r (M is inverse preconditioner) */
254
- apply_pre_conditioner(z, r, n, pr);
255
- ztr_prev = ztr;
256
- /* ztr = z'r */
257
- ztr = SCS(dot)(z, r, n);
258
- /* p = beta * p, where beta = ztr / ztr_prev */
259
- SCS(scale_array)(p, ztr / ztr_prev, n);
260
- /* p = z + beta * p */
261
- SCS(add_scaled_array)(p, z, n, 1.);
635
+ if (ztr_prev == 0.) {
636
+ /* preconditioned residual is zero; further CG steps would divide by
637
+ * zero, declare convergence (r must be negligibly small) */
638
+ break;
639
+ }
640
+ /* p = z + beta * p fuse scale and axpy into one loop */
641
+ beta = ztr / ztr_prev;
642
+ #ifdef USE_LAPACK
643
+ if (pr->dfl_harvest) {
644
+ eigcg_accum(pr, alpha, beta, z, ztr);
645
+ }
646
+ #endif
647
+ for (k = 0; k < n; ++k) {
648
+ p[k] = z[k] + beta * p[k];
649
+ }
262
650
  }
263
651
  return i;
264
652
  }
265
653
 
654
+ /* ======================== Public API ======================== */
655
+
656
+ const char *scs_get_lin_sys_method(void) {
657
+ return "sparse-indirect-scs";
658
+ }
659
+
660
+ ScsLinSysWork *scs_init_lin_sys_work(const ScsMatrix *A, const ScsMatrix *P,
661
+ const scs_float *diag_r) {
662
+ ScsLinSysWork *p = (ScsLinSysWork *)scs_calloc(1, sizeof(ScsLinSysWork));
663
+ if (!p) {
664
+ return SCS_NULL;
665
+ }
666
+ p->A = A;
667
+ p->P = P;
668
+ p->m = A->m;
669
+ p->n = A->n;
670
+
671
+ p->p = (scs_float *)scs_calloc((A->n), sizeof(scs_float));
672
+ p->r = (scs_float *)scs_calloc((A->n), sizeof(scs_float));
673
+ p->Gp = (scs_float *)scs_calloc((A->n), sizeof(scs_float));
674
+ p->tmp = (scs_float *)scs_calloc((A->m), sizeof(scs_float));
675
+
676
+ /* memory for A transpose */
677
+ p->At = (ScsMatrix *)scs_calloc(1, sizeof(ScsMatrix));
678
+ if (!p->p || !p->r || !p->Gp || !p->tmp || !p->At) {
679
+ scs_free_lin_sys_work(p);
680
+ return SCS_NULL;
681
+ }
682
+ p->At->m = A->n;
683
+ p->At->n = A->m;
684
+ p->At->i = (scs_int *)scs_calloc((A->p[A->n]), sizeof(scs_int));
685
+ p->At->p = (scs_int *)scs_calloc((A->m + 1), sizeof(scs_int));
686
+ p->At->x = (scs_float *)scs_calloc((A->p[A->n]), sizeof(scs_float));
687
+ if (!p->At->i || !p->At->p || !p->At->x) {
688
+ scs_free_lin_sys_work(p);
689
+ return SCS_NULL;
690
+ }
691
+ transpose(A, p);
692
+
693
+ /* preconditioner memory */
694
+ p->diag_r = diag_r;
695
+ p->z = (scs_float *)scs_calloc(A->n, sizeof(scs_float));
696
+ p->M = (scs_float *)scs_calloc(A->n, sizeof(scs_float));
697
+ if (!p->z || !p->M) {
698
+ scs_free_lin_sys_work(p);
699
+ return SCS_NULL;
700
+ }
701
+ set_preconditioner(p);
702
+
703
+ p->tot_cg_its = 0;
704
+ {
705
+ const char *dm = getenv("SCS_DEFLATE");
706
+ char *wm = getenv("SCS_EIGCG_WIN");
707
+ p->dfl_max = dm ? (scs_int)atoi(dm) : DEFLATE_VECTORS;
708
+ p->dfl_max = MIN(p->dfl_max, p->n);
709
+ p->dfl_count = 0;
710
+ p->dfl_harvest = 0;
711
+ #ifndef USE_LAPACK
712
+ p->dfl_max = 0; /* eigCG needs syev */
713
+ #endif
714
+ #ifdef SFLOAT
715
+ /* the Lanczos harvest and the small Cholesky sit below the single-
716
+ * precision noise floor; deflation is double-precision only */
717
+ p->dfl_max = 0;
718
+ #endif
719
+ if (p->dfl_max > 0) {
720
+ scs_int win = wm ? (scs_int)atoi(wm) : EIGCG_WINDOW;
721
+ win = MAX(win, 3 * p->dfl_max); /* >= nev free slots per cycle */
722
+ win = MAX(win, 2 * p->dfl_max + 2);
723
+ p->eig_win = win;
724
+ /* only W, AW and the small factor persist; the window itself is
725
+ * allocated per harvest and freed with the deep solve */
726
+ p->dfl_p = (scs_float *)scs_calloc(p->n * p->dfl_max, sizeof(scs_float));
727
+ p->dfl_Ap = (scs_float *)scs_calloc(p->n * p->dfl_max, sizeof(scs_float));
728
+ p->dfl_G =
729
+ (scs_float *)scs_calloc(p->dfl_max * p->dfl_max, sizeof(scs_float));
730
+ p->dfl_c = (scs_float *)scs_calloc(p->dfl_max, sizeof(scs_float));
731
+ #ifdef USE_LAPACK
732
+ {
733
+ scs_float wkopt = 0., dummy = 0.;
734
+ blas_int bn = (blas_int)win, lda = (blas_int)win, lw = -1, info = 0;
735
+ BLAS(syev)("V", "U", &bn, &dummy, &lda, &wkopt, &wkopt, &lw, &info);
736
+ p->eig_lwork = info ? 0 : (scs_int)wkopt;
737
+ }
738
+ #endif
739
+ if (!p->dfl_p || !p->dfl_Ap || !p->dfl_G || !p->dfl_c ||
740
+ p->eig_lwork <= 0) {
741
+ p->dfl_max = 0;
742
+ }
743
+ }
744
+ }
745
+ return p;
746
+ }
747
+
266
748
  /* solves Mx = b, for x but stores result in b */
267
749
  /* s contains warm-start (if available) */
268
750
  /*
@@ -278,6 +760,8 @@ static scs_int pcg(ScsLinSysWork *pr, const scs_float *s, scs_float *b,
278
760
  scs_int scs_solve_lin_sys(ScsLinSysWork *p, scs_float *b, const scs_float *s,
279
761
  scs_float tol) {
280
762
  scs_int cg_its, max_iters;
763
+ /* the cold, high-accuracy solve is the one worth harvesting from */
764
+ p->dfl_harvest = (p->dfl_max > 0 && !s && p->dfl_count == 0);
281
765
 
282
766
  if (tol <= 0.) {
283
767
  scs_printf("Warning: tol = %4f <= 0, likely compiled without setting "
@@ -285,13 +769,12 @@ scs_int scs_solve_lin_sys(ScsLinSysWork *p, scs_float *b, const scs_float *s,
285
769
  tol);
286
770
  }
287
771
 
772
+ /* use p->tmp here, and in mat_vec, can do both since they don't overlap */
773
+ /* b = [rx; ry] */
288
774
  if (CG_NORM(b, p->n + p->m) <= 1e-12) {
289
775
  memset(b, 0, (p->n + p->m) * sizeof(scs_float));
290
776
  return 0;
291
777
  }
292
-
293
- /* use p->tmp here, and in mat_vec, can do both since they don't overlap */
294
- /* b = [rx; ry] */
295
778
  /* tmp = ry */
296
779
  memcpy(p->tmp, &(b[p->n]), p->m * sizeof(scs_float));
297
780
  /* tmp = R_y^{-1} * ry */
@@ -303,6 +786,14 @@ scs_int scs_solve_lin_sys(ScsLinSysWork *p, scs_float *b, const scs_float *s,
303
786
  /* solves (R_x + P + A' R_y^{-1} A)x = b, s warm start, solution stored in
304
787
  * b */
305
788
  cg_its = pcg(p, s, b, max_iters, tol); /* b[:n] = x */
789
+ #ifdef USE_LAPACK
790
+ if (p->dfl_harvest) {
791
+ /* extract the Ritz vectors; the kv image matvecs it spends are added
792
+ * to the iteration count so the matvec accounting stays honest */
793
+ cg_its += eigcg_finish(p);
794
+ p->dfl_harvest = 0;
795
+ }
796
+ #endif
306
797
 
307
798
  /* b[n:] = -ry */
308
799
  SCS(scale_array)(&(b[p->n]), -1., p->m);
@@ -317,3 +808,46 @@ scs_int scs_solve_lin_sys(ScsLinSysWork *p, scs_float *b, const scs_float *s,
317
808
  #endif
318
809
  return 0;
319
810
  }
811
+
812
+ /* no need to update anything in this case */
813
+ scs_int scs_update_lin_sys_diag_r(ScsLinSysWork *p, const scs_float *diag_r) {
814
+ p->diag_r = diag_r; /* this isn't needed but do it to be safe */
815
+ set_preconditioner(p);
816
+ /* the operator and preconditioner changed: the harvested eigenvectors
817
+ * live in the old preconditioned geometry, discard them; the next deep
818
+ * solve (triggered by this same metric change) re-harvests */
819
+ p->dfl_count = 0;
820
+ return 0;
821
+ }
822
+
823
+ void scs_free_lin_sys_work(ScsLinSysWork *p) {
824
+ if (p) {
825
+ scs_free(p->dfl_p);
826
+ scs_free(p->dfl_Ap);
827
+ scs_free(p->dfl_G);
828
+ scs_free(p->dfl_c);
829
+ scs_free(p->eig_V);
830
+ scs_free(p->eig_T);
831
+ scs_free(p->eig_E);
832
+ scs_free(p->eig_w);
833
+ scs_free(p->eig_S);
834
+ scs_free(p->eig_TS);
835
+ scs_free(p->eig_H);
836
+ scs_free(p->eig_G);
837
+ scs_free(p->eig_VS);
838
+ scs_free(p->eig_work);
839
+ scs_free(p->p);
840
+ scs_free(p->r);
841
+ scs_free(p->Gp);
842
+ scs_free(p->tmp);
843
+ if (p->At) {
844
+ scs_free(p->At->i);
845
+ scs_free(p->At->x);
846
+ scs_free(p->At->p);
847
+ scs_free(p->At);
848
+ }
849
+ scs_free(p->z);
850
+ scs_free(p->M);
851
+ scs_free(p);
852
+ }
853
+ }