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,9 +1,21 @@
1
+ /*
2
+ * Cone projection implementation.
3
+ *
4
+ * Handles projection onto the dual cone for all supported cone types:
5
+ * zero/free, non-negative, box, second-order, semidefinite, complex
6
+ * semidefinite, exponential, power, and (optionally) spectral cones.
7
+ * Uses Moreau decomposition: proj_K*(x) = x + proj_K(-x).
8
+ */
9
+
1
10
  #include "cones.h"
2
11
  #include "linalg.h"
3
12
  #include "scs.h"
4
13
  #include "scs_blas.h" /* contains BLAS(X) macros and type info */
5
14
  #include "util.h"
6
15
 
16
+ #include <stdio.h>
17
+ #include <string.h>
18
+
7
19
  /*
8
20
  * Cross-platform Complex Type Handling
9
21
  * MSVC uses struct layout; GCC/Clang uses C99 _Complex
@@ -105,9 +117,7 @@ scs_int SCS(proj_sum_largest_evals)(scs_float *tX, scs_int n, scs_int k,
105
117
  /* Forward declare exponential cone projection (exp_cone.c) */
106
118
  scs_float SCS(proj_pd_exp_cone)(scs_float *v0, scs_int primal);
107
119
 
108
- /*
109
- * Memory Management
110
- */
120
+ /* ======================== Memory Management ======================== */
111
121
 
112
122
  void SCS(free_cone)(ScsCone *k) {
113
123
  if (k) {
@@ -141,13 +151,31 @@ void SCS(free_cone)(ScsCone *k) {
141
151
  }
142
152
  }
143
153
 
144
- void SCS(deep_copy_cone)(ScsCone *dest, const ScsCone *src) {
145
- memcpy(dest, src, sizeof(ScsCone));
154
+ scs_int SCS(deep_copy_cone)(ScsCone *dest, const ScsCone *src) {
155
+ memset(dest, 0, sizeof(*dest));
156
+ dest->z = src->z;
157
+ dest->l = src->l;
158
+ dest->bsize = src->bsize;
159
+ dest->qsize = src->qsize;
160
+ dest->ssize = src->ssize;
161
+ dest->cssize = src->cssize;
162
+ dest->ep = src->ep;
163
+ dest->ed = src->ed;
164
+ dest->psize = src->psize;
165
+ #ifdef USE_SPECTRAL_CONES
166
+ dest->dsize = src->dsize;
167
+ dest->nucsize = src->nucsize;
168
+ dest->ell1_size = src->ell1_size;
169
+ dest->sl_size = src->sl_size;
170
+ #endif
146
171
 
147
172
  /* Box cone */
148
173
  if (src->bsize > 1) {
149
174
  dest->bu = (scs_float *)scs_calloc(src->bsize - 1, sizeof(scs_float));
150
175
  dest->bl = (scs_float *)scs_calloc(src->bsize - 1, sizeof(scs_float));
176
+ if (!dest->bu || !dest->bl) {
177
+ return 0;
178
+ }
151
179
  memcpy(dest->bu, src->bu, (src->bsize - 1) * sizeof(scs_float));
152
180
  memcpy(dest->bl, src->bl, (src->bsize - 1) * sizeof(scs_float));
153
181
  } else {
@@ -158,6 +186,9 @@ void SCS(deep_copy_cone)(ScsCone *dest, const ScsCone *src) {
158
186
  /* SOC */
159
187
  if (src->qsize > 0) {
160
188
  dest->q = (scs_int *)scs_calloc(src->qsize, sizeof(scs_int));
189
+ if (!dest->q) {
190
+ return 0;
191
+ }
161
192
  memcpy(dest->q, src->q, src->qsize * sizeof(scs_int));
162
193
  } else {
163
194
  dest->q = SCS_NULL;
@@ -166,6 +197,9 @@ void SCS(deep_copy_cone)(ScsCone *dest, const ScsCone *src) {
166
197
  /* PSD */
167
198
  if (src->ssize > 0) {
168
199
  dest->s = (scs_int *)scs_calloc(src->ssize, sizeof(scs_int));
200
+ if (!dest->s) {
201
+ return 0;
202
+ }
169
203
  memcpy(dest->s, src->s, src->ssize * sizeof(scs_int));
170
204
  } else {
171
205
  dest->s = SCS_NULL;
@@ -174,6 +208,9 @@ void SCS(deep_copy_cone)(ScsCone *dest, const ScsCone *src) {
174
208
  /* Complex PSD */
175
209
  if (src->cssize > 0) {
176
210
  dest->cs = (scs_int *)scs_calloc(src->cssize, sizeof(scs_int));
211
+ if (!dest->cs) {
212
+ return 0;
213
+ }
177
214
  memcpy(dest->cs, src->cs, src->cssize * sizeof(scs_int));
178
215
  } else {
179
216
  dest->cs = SCS_NULL;
@@ -182,6 +219,9 @@ void SCS(deep_copy_cone)(ScsCone *dest, const ScsCone *src) {
182
219
  /* Power */
183
220
  if (src->psize > 0) {
184
221
  dest->p = (scs_float *)scs_calloc(src->psize, sizeof(scs_float));
222
+ if (!dest->p) {
223
+ return 0;
224
+ }
185
225
  memcpy(dest->p, src->p, src->psize * sizeof(scs_float));
186
226
  } else {
187
227
  dest->p = SCS_NULL;
@@ -191,6 +231,9 @@ void SCS(deep_copy_cone)(ScsCone *dest, const ScsCone *src) {
191
231
  /* Logdet */
192
232
  if (src->dsize > 0) {
193
233
  dest->d = (scs_int *)scs_calloc(src->dsize, sizeof(scs_int));
234
+ if (!dest->d) {
235
+ return 0;
236
+ }
194
237
  memcpy(dest->d, src->d, src->dsize * sizeof(scs_int));
195
238
  } else {
196
239
  dest->d = SCS_NULL;
@@ -200,6 +243,9 @@ void SCS(deep_copy_cone)(ScsCone *dest, const ScsCone *src) {
200
243
  if (src->nucsize > 0) {
201
244
  dest->nuc_m = (scs_int *)scs_calloc(src->nucsize, sizeof(scs_int));
202
245
  dest->nuc_n = (scs_int *)scs_calloc(src->nucsize, sizeof(scs_int));
246
+ if (!dest->nuc_m || !dest->nuc_n) {
247
+ return 0;
248
+ }
203
249
  memcpy(dest->nuc_m, src->nuc_m, src->nucsize * sizeof(scs_int));
204
250
  memcpy(dest->nuc_n, src->nuc_n, src->nucsize * sizeof(scs_int));
205
251
  } else {
@@ -210,6 +256,9 @@ void SCS(deep_copy_cone)(ScsCone *dest, const ScsCone *src) {
210
256
  /* Ell1 */
211
257
  if (src->ell1_size > 0) {
212
258
  dest->ell1 = (scs_int *)scs_calloc(src->ell1_size, sizeof(scs_int));
259
+ if (!dest->ell1) {
260
+ return 0;
261
+ }
213
262
  memcpy(dest->ell1, src->ell1, src->ell1_size * sizeof(scs_int));
214
263
  } else {
215
264
  dest->ell1 = SCS_NULL;
@@ -219,6 +268,9 @@ void SCS(deep_copy_cone)(ScsCone *dest, const ScsCone *src) {
219
268
  if (src->sl_size > 0) {
220
269
  dest->sl_n = (scs_int *)scs_calloc(src->sl_size, sizeof(scs_int));
221
270
  dest->sl_k = (scs_int *)scs_calloc(src->sl_size, sizeof(scs_int));
271
+ if (!dest->sl_n || !dest->sl_k) {
272
+ return 0;
273
+ }
222
274
  memcpy(dest->sl_n, src->sl_n, src->sl_size * sizeof(scs_int));
223
275
  memcpy(dest->sl_k, src->sl_k, src->sl_size * sizeof(scs_int));
224
276
  } else {
@@ -226,11 +278,70 @@ void SCS(deep_copy_cone)(ScsCone *dest, const ScsCone *src) {
226
278
  dest->sl_k = SCS_NULL;
227
279
  }
228
280
  #endif
281
+ return 1;
229
282
  }
230
283
 
231
- /*
232
- * Helper Functions
233
- */
284
+ void SCS(finish_cone)(ScsConeWork *c) {
285
+ if (!c)
286
+ return;
287
+ #ifdef USE_LAPACK
288
+ if (c->Xs)
289
+ scs_free(c->Xs);
290
+ if (c->cXs)
291
+ scs_free(c->cXs);
292
+ if (c->Z)
293
+ scs_free(c->Z);
294
+ if (c->cZ)
295
+ scs_free(c->cZ);
296
+ if (c->e)
297
+ scs_free(c->e);
298
+ if (c->isuppz)
299
+ scs_free(c->isuppz);
300
+ if (c->work)
301
+ scs_free(c->work);
302
+ if (c->iwork)
303
+ scs_free(c->iwork);
304
+ if (c->cwork)
305
+ scs_free(c->cwork);
306
+ /* c->rwork is aliased to c->work in setup, no free needed */
307
+ #endif
308
+ if (c->cone_boundaries)
309
+ scs_free(c->cone_boundaries);
310
+ if (c->cone_boundaries_psd_n)
311
+ scs_free(c->cone_boundaries_psd_n);
312
+ if (c->psd_gamma)
313
+ scs_free(c->psd_gamma);
314
+ if (c->r_box_inv)
315
+ scs_free(c->r_box_inv);
316
+ if (c->s)
317
+ scs_free(c->s);
318
+
319
+ #ifdef USE_SPECTRAL_CONES
320
+ if (c->work_logdet)
321
+ scs_free(c->work_logdet);
322
+ if (c->saved_log_projs)
323
+ scs_free(c->saved_log_projs);
324
+ if (c->s_nuc)
325
+ scs_free(c->s_nuc);
326
+ if (c->u_nuc)
327
+ scs_free(c->u_nuc);
328
+ if (c->vt_nuc)
329
+ scs_free(c->vt_nuc);
330
+ if (c->work_nuc)
331
+ scs_free(c->work_nuc);
332
+ if (c->work_sum_of_largest)
333
+ scs_free(c->work_sum_of_largest);
334
+ if (c->log_cone_warmstarts)
335
+ scs_free(c->log_cone_warmstarts);
336
+ if (c->work_ell1)
337
+ scs_free(c->work_ell1);
338
+ if (c->work_ell1_proj)
339
+ scs_free(c->work_ell1_proj);
340
+ #endif
341
+ scs_free(c);
342
+ }
343
+
344
+ /* ========================= Cone Utilities ============================ */
234
345
 
235
346
  static inline scs_int get_sd_cone_size(scs_int s) {
236
347
  return (s * (s + 1)) / 2;
@@ -255,17 +366,102 @@ void SCS(set_r_y)(const ScsConeWork *c, scs_float scale, scs_float *r_y) {
255
366
  }
256
367
  }
257
368
 
258
- /* The function f aggregates the entries within each cone */
369
+ /* svec packed index of entry (i, j), i >= j, in an n x n lower
370
+ * triangle stored column-major. */
371
+ static scs_int svec_idx(scs_int i, scs_int j, scs_int n) {
372
+ return j * n - (j * (j - 1)) / 2 + (i - j);
373
+ }
374
+
375
+ /* Fit the rank-one form w_ij = delta_i * delta_j to a positive profile
376
+ * on one PSD block (svec packed, length n(n+1)/2) by least squares in
377
+ * log space, and overwrite the block with the fitted values. This is
378
+ * the richest svec-diagonal metric that maps the PSD cone to itself
379
+ * (the diagonal congruence X -> diag(delta) X diag(delta)), so unlike
380
+ * generic cones the block need not collapse to a single scalar.
381
+ *
382
+ * Normal equations: minimizing sum_{i<=j} (l_i + l_j - y_ij)^2 gives
383
+ * A'A = (n+2) I + 11', inverted in closed form by Sherman-Morrison.
384
+ * The fitted delta_i are clamped so every product w_ij stays within
385
+ * [DIAG_SCALE_MULT_MIN, DIAG_SCALE_MULT_MAX], preserving rank-one
386
+ * structure (clamping products directly would break it). */
387
+ static void fit_psd_block_weights(scs_float *vec, scs_int n) {
388
+ scs_int i, j;
389
+ scs_float alpha = (scs_float)(n + 2);
390
+ scs_float rhs_sum = 0., corr, li;
391
+ scs_float lo = SQRTF(DIAG_SCALE_MULT_MIN), hi = SQRTF(DIAG_SCALE_MULT_MAX);
392
+ scs_float *ell = (scs_float *)scs_calloc(n, sizeof(scs_float));
393
+ if (!ell) {
394
+ return; /* leave profile as-is; caller falls back to scalar */
395
+ }
396
+ /* Floor entries relative to the block max: (near-)zero rows carry no
397
+ * information about the block's scale structure (e.g. unconstrained
398
+ * off-diagonal svec rows are exactly zero in the equilibration
399
+ * profile) and an absolute floor lets them dominate the fit. */
400
+ {
401
+ scs_float vmax = 0., vfloor;
402
+ scs_int sz = (n * (n + 1)) / 2;
403
+ for (i = 0; i < sz; ++i) {
404
+ vmax = MAX(vmax, vec[i]);
405
+ }
406
+ vfloor = MAX(1e-3 * vmax, 1e-12);
407
+ for (i = 0; i < sz; ++i) {
408
+ vec[i] = MAX(vec[i], vfloor);
409
+ }
410
+ }
411
+
412
+ /* rhs_k = 2 y_kk + sum_{j != k} y_kj */
413
+ for (j = 0; j < n; ++j) {
414
+ for (i = j; i < n; ++i) {
415
+ scs_float y = log(vec[svec_idx(i, j, n)]);
416
+ if (i == j) {
417
+ ell[i] += 2. * y;
418
+ } else {
419
+ ell[i] += y;
420
+ ell[j] += y;
421
+ }
422
+ }
423
+ }
424
+ for (i = 0; i < n; ++i) {
425
+ rhs_sum += ell[i];
426
+ }
427
+ /* (alpha I + 11')^{-1} rhs = rhs/alpha - 1 (1'rhs) / (alpha(alpha+n)) */
428
+ corr = rhs_sum / (alpha * (alpha + (scs_float)n));
429
+ for (i = 0; i < n; ++i) {
430
+ li = exp(ell[i] / alpha - corr);
431
+ ell[i] = MIN(MAX(li, lo), hi);
432
+ }
433
+ for (j = 0; j < n; ++j) {
434
+ for (i = j; i < n; ++i) {
435
+ vec[svec_idx(i, j, n)] = ell[i] * ell[j];
436
+ }
437
+ }
438
+ scs_free(ell);
439
+ }
440
+
441
+ /* The function f aggregates the entries within each cone. With
442
+ * psd_rank1 set, real PSD blocks instead get the rank-one (diagonal
443
+ * congruence) fit, which is cone-invariant and strictly more
444
+ * expressive than a single scalar. The fit is only safe for the
445
+ * one-shot dynamic metric update: applied inside the iterated Ruiz
446
+ * equilibration loop it compounds pass over pass instead of
447
+ * contracting and destroys the scaling (mcp100 diverges), so the
448
+ * equilibration call sites keep the scalar collapse. */
259
449
  void SCS(enforce_cone_boundaries)(const ScsConeWork *c, scs_float *vec,
260
- scs_float (*f)(const scs_float *, scs_int)) {
450
+ scs_float (*f)(const scs_float *, scs_int),
451
+ scs_int psd_rank1) {
261
452
  scs_int i, j, delta;
262
453
  scs_int count = c->cone_boundaries[0];
263
454
  scs_float wrk;
264
455
  for (i = 1; i < c->cone_boundaries_len; ++i) {
265
456
  delta = c->cone_boundaries[i];
266
- wrk = f(&(vec[count]), delta);
267
- for (j = count; j < count + delta; ++j) {
268
- vec[j] = wrk;
457
+ if (psd_rank1 && c->cone_boundaries_psd_n &&
458
+ c->cone_boundaries_psd_n[i] > 1) {
459
+ fit_psd_block_weights(&(vec[count]), c->cone_boundaries_psd_n[i]);
460
+ } else {
461
+ wrk = f(&(vec[count]), delta);
462
+ for (j = count; j < count + delta; ++j) {
463
+ vec[j] = wrk;
464
+ }
269
465
  }
270
466
  count += delta;
271
467
  }
@@ -287,13 +483,16 @@ void set_cone_boundaries(const ScsCone *k, ScsConeWork *c) {
287
483
  k->qsize + k->ssize + k->cssize + k->ed + k->ep + k->psize;
288
484
  #endif
289
485
  scs_int *b = (scs_int *)scs_calloc(total_cones + 1, sizeof(scs_int));
486
+ scs_int *psd_n = (scs_int *)scs_calloc(total_cones + 1, sizeof(scs_int));
290
487
 
291
488
  /* Cones that can be scaled independently */
292
489
  b[count++] = k->z + k->l + k->bsize;
293
490
  for (i = 0; i < k->qsize; ++i)
294
491
  b[count++] = k->q[i];
295
- for (i = 0; i < k->ssize; ++i)
492
+ for (i = 0; i < k->ssize; ++i) {
493
+ psd_n[count] = k->s[i];
296
494
  b[count++] = get_sd_cone_size(k->s[i]);
495
+ }
297
496
  for (i = 0; i < k->cssize; ++i)
298
497
  b[count++] = get_csd_cone_size(k->cs[i]);
299
498
  for (i = 0; i < k->ep + k->ed; ++i)
@@ -314,6 +513,7 @@ void set_cone_boundaries(const ScsCone *k, ScsConeWork *c) {
314
513
 
315
514
  c->cone_boundaries = b;
316
515
  c->cone_boundaries_len = total_cones + 1;
516
+ c->cone_boundaries_psd_n = psd_n;
317
517
  }
318
518
 
319
519
  static scs_int get_full_cone_dims(const ScsCone *k) {
@@ -338,36 +538,183 @@ static scs_int get_full_cone_dims(const ScsCone *k) {
338
538
  return dims;
339
539
  }
340
540
 
541
+ /* If buf is NULL, only accumulate the length that would be written.
542
+ * Otherwise append chunk, including its trailing '\0', at the current offset.
543
+ */
544
+ static void append_to_header(char *buf, size_t *len, const char *chunk) {
545
+ size_t chunk_len = strlen(chunk);
546
+ if (buf) {
547
+ memcpy(buf + *len, chunk, chunk_len + 1);
548
+ }
549
+ *len += chunk_len;
550
+ }
551
+
552
+ /* Format the cone summary in one shared code path.
553
+ *
554
+ * When buf is NULL, this function performs a sizing pass only and updates
555
+ * *len with the number of characters required for the final string.
556
+ * When buf is non-NULL, it writes the same formatted output into buf and
557
+ * advances *len as it appends each line.
558
+ *
559
+ * This supports a two-pass implementation in get_cone_header:
560
+ * 1. call with buf == NULL to compute the exact allocation size
561
+ * 2. allocate once
562
+ * 3. call again with buf != NULL to render the final string
563
+ */
564
+ static void format_cone_header(const ScsCone *k, char *buf, size_t *len) {
565
+ char line[128];
566
+ scs_int i, count;
567
+ #ifdef USE_SPECTRAL_CONES
568
+ scs_int ell1_vars, log_vars, nuc_vars, sl_vars;
569
+ #endif
570
+
571
+ append_to_header(buf, len, "cones: ");
572
+ if (k->z) {
573
+ sprintf(line, "\t z: primal zero / dual free vars: %li\n", (long)k->z);
574
+ append_to_header(buf, len, line);
575
+ }
576
+ if (k->l) {
577
+ sprintf(line, "\t l: linear vars: %li\n", (long)k->l);
578
+ append_to_header(buf, len, line);
579
+ }
580
+ if (k->bsize) {
581
+ sprintf(line, "\t b: box cone vars: %li\n", (long)k->bsize);
582
+ append_to_header(buf, len, line);
583
+ }
584
+ if (k->qsize) {
585
+ count = 0;
586
+ for (i = 0; i < k->qsize; ++i)
587
+ count += k->q[i];
588
+ sprintf(line, "\t q: soc vars: %li, qsize: %li\n", (long)count,
589
+ (long)k->qsize);
590
+ append_to_header(buf, len, line);
591
+ }
592
+ if (k->ssize) {
593
+ count = 0;
594
+ for (i = 0; i < k->ssize; ++i)
595
+ count += get_sd_cone_size(k->s[i]);
596
+ sprintf(line, "\t s: psd vars: %li, ssize: %li\n", (long)count,
597
+ (long)k->ssize);
598
+ append_to_header(buf, len, line);
599
+ }
600
+ if (k->cssize) {
601
+ count = 0;
602
+ for (i = 0; i < k->cssize; ++i)
603
+ count += get_csd_cone_size(k->cs[i]);
604
+ sprintf(line, "\t cs: complex psd vars: %li, cssize: %li\n", (long)count,
605
+ (long)k->cssize);
606
+ append_to_header(buf, len, line);
607
+ }
608
+ if (k->ep || k->ed) {
609
+ sprintf(line, "\t e: exp vars: %li, dual exp vars: %li\n",
610
+ (long)(3 * k->ep), (long)(3 * k->ed));
611
+ append_to_header(buf, len, line);
612
+ }
613
+ if (k->psize) {
614
+ sprintf(line, "\t p: primal + dual power vars: %li\n",
615
+ (long)(3 * k->psize));
616
+ append_to_header(buf, len, line);
617
+ }
618
+ #ifdef USE_SPECTRAL_CONES
619
+ log_vars = 0;
620
+ if (k->dsize && k->d) {
621
+ for (i = 0; i < k->dsize; i++) {
622
+ log_vars += get_sd_cone_size(k->d[i]) + 2;
623
+ }
624
+ sprintf(line, "\t d: logdet vars: %li, dsize: %li\n", (long)log_vars,
625
+ (long)k->dsize);
626
+ append_to_header(buf, len, line);
627
+ }
628
+ nuc_vars = 0;
629
+ if (k->nucsize && k->nuc_m && k->nuc_n) {
630
+ for (i = 0; i < k->nucsize; i++) {
631
+ nuc_vars += k->nuc_m[i] * k->nuc_n[i] + 1;
632
+ }
633
+ sprintf(line, "\t nuc: nuclear vars: %li, nucsize: %li\n",
634
+ (long)nuc_vars, (long)k->nucsize);
635
+ append_to_header(buf, len, line);
636
+ }
637
+ ell1_vars = 0;
638
+ if (k->ell1_size && k->ell1) {
639
+ for (i = 0; i < k->ell1_size; ++i) {
640
+ ell1_vars += k->ell1[i] + 1;
641
+ }
642
+ sprintf(line, "\t ell1: ell1 vars: %li, ell1_size: %li\n",
643
+ (long)ell1_vars, (long)k->ell1_size);
644
+ append_to_header(buf, len, line);
645
+ }
646
+ sl_vars = 0;
647
+ if (k->sl_size && k->sl_n) {
648
+ for (i = 0; i < k->sl_size; ++i) {
649
+ sl_vars += get_sd_cone_size(k->sl_n[i]) + 1;
650
+ }
651
+ sprintf(line, "\t sl: sl vars: %li, sl_size: %li\n", (long)sl_vars,
652
+ (long)k->sl_size);
653
+ append_to_header(buf, len, line);
654
+ }
655
+ #endif
656
+ }
657
+
658
+ char *SCS(get_cone_header)(const ScsCone *k) {
659
+ char *tmp;
660
+ size_t len = 0;
661
+
662
+ format_cone_header(k, SCS_NULL, &len);
663
+
664
+ tmp = (char *)scs_malloc(len + 1);
665
+ if (!tmp) {
666
+ return SCS_NULL;
667
+ }
668
+
669
+ len = 0;
670
+ format_cone_header(k, tmp, &len);
671
+ return tmp;
672
+ }
673
+
674
+ /* ========================== Validation =============================== */
675
+
341
676
  scs_int SCS(validate_cones)(const ScsData *d, const ScsCone *k) {
342
677
  scs_int i;
343
- if (get_full_cone_dims(k) != d->m) {
344
- scs_printf("Error: Cone dims %li != rows in A %li\n",
345
- (long)get_full_cone_dims(k), (long)d->m);
346
- return -1;
347
- }
348
- if (k->z && k->z < 0) {
678
+ scs_int cone_dims;
679
+ if (k->z < 0) {
349
680
  scs_printf("free cone dimension error\n");
350
681
  return -1;
351
682
  }
352
- if (k->l && k->l < 0) {
683
+ if (k->l < 0) {
353
684
  scs_printf("lp cone dimension error\n");
354
685
  return -1;
355
686
  }
356
- if (k->bsize) {
357
- if (k->bsize < 0) {
358
- scs_printf("box cone dimension error\n");
687
+ if (k->bsize < 0) {
688
+ scs_printf("box cone dimension error\n");
689
+ return -1;
690
+ }
691
+ if (k->bsize > 1) {
692
+ if (!k->bl || !k->bu) {
693
+ scs_printf("box cone bounds missing\n");
359
694
  return -1;
360
695
  }
361
696
  for (i = 0; i < k->bsize - 1; ++i) {
697
+ if (isnan(k->bl[i]) || isnan(k->bu[i])) {
698
+ scs_printf("box cone bounds cannot be NaN\n");
699
+ return -1;
700
+ }
701
+ if (k->bl[i] == INFINITY || k->bu[i] == -INFINITY) {
702
+ scs_printf("box cone bounds use invalid infinity direction\n");
703
+ return -1;
704
+ }
362
705
  if (k->bl[i] > k->bu[i]) {
363
706
  scs_printf("infeasible: box lower bound larger than upper bound\n");
364
707
  return -1;
365
708
  }
366
709
  }
367
710
  }
368
- if (k->qsize && k->q) {
369
- if (k->qsize < 0) {
370
- scs_printf("soc cone dimension error\n");
711
+ if (k->qsize < 0) {
712
+ scs_printf("soc cone dimension error\n");
713
+ return -1;
714
+ }
715
+ if (k->qsize > 0) {
716
+ if (!k->q) {
717
+ scs_printf("soc cone array missing\n");
371
718
  return -1;
372
719
  }
373
720
  for (i = 0; i < k->qsize; ++i) {
@@ -377,9 +724,13 @@ scs_int SCS(validate_cones)(const ScsData *d, const ScsCone *k) {
377
724
  }
378
725
  }
379
726
  }
380
- if (k->ssize && k->s) {
381
- if (k->ssize < 0) {
382
- scs_printf("sd cone dimension error\n");
727
+ if (k->ssize < 0) {
728
+ scs_printf("sd cone dimension error\n");
729
+ return -1;
730
+ }
731
+ if (k->ssize > 0) {
732
+ if (!k->s) {
733
+ scs_printf("sd cone array missing\n");
383
734
  return -1;
384
735
  }
385
736
  for (i = 0; i < k->ssize; ++i) {
@@ -389,9 +740,13 @@ scs_int SCS(validate_cones)(const ScsData *d, const ScsCone *k) {
389
740
  }
390
741
  }
391
742
  }
392
- if (k->cssize && k->cs) {
393
- if (k->cssize < 0) {
394
- scs_printf("complex psd cone dimension error\n");
743
+ if (k->cssize < 0) {
744
+ scs_printf("complex psd cone dimension error\n");
745
+ return -1;
746
+ }
747
+ if (k->cssize > 0) {
748
+ if (!k->cs) {
749
+ scs_printf("complex psd cone array missing\n");
395
750
  return -1;
396
751
  }
397
752
  for (i = 0; i < k->cssize; ++i) {
@@ -401,30 +756,38 @@ scs_int SCS(validate_cones)(const ScsData *d, const ScsCone *k) {
401
756
  }
402
757
  }
403
758
  }
404
- if (k->ed && k->ed < 0) {
759
+ if (k->ed < 0) {
760
+ scs_printf("ed cone dimension error\n");
761
+ return -1;
762
+ }
763
+ if (k->ep < 0) {
405
764
  scs_printf("ep cone dimension error\n");
406
765
  return -1;
407
766
  }
408
- if (k->ep && k->ep < 0) {
409
- scs_printf("ed cone dimension error\n");
767
+ if (k->psize < 0) {
768
+ scs_printf("power cone dimension error\n");
410
769
  return -1;
411
770
  }
412
- if (k->psize && k->p) {
413
- if (k->psize < 0) {
414
- scs_printf("power cone dimension error\n");
771
+ if (k->psize > 0) {
772
+ if (!k->p) {
773
+ scs_printf("power cone array missing\n");
415
774
  return -1;
416
775
  }
417
776
  for (i = 0; i < k->psize; ++i) {
418
- if (k->p[i] < -1 || k->p[i] > 1) {
419
- scs_printf("power cone error, values must be in [-1,1]\n");
777
+ if (!isfinite(k->p[i]) || k->p[i] < -1 || k->p[i] > 1) {
778
+ scs_printf("power cone error, values must be finite and in [-1,1]\n");
420
779
  return -1;
421
780
  }
422
781
  }
423
782
  }
424
783
  #ifdef USE_SPECTRAL_CONES
425
- if (k->dsize && k->d) {
426
- if (k->dsize < 0) {
427
- scs_printf("logdet cone dimension error\n");
784
+ if (k->dsize < 0) {
785
+ scs_printf("logdet cone dimension error\n");
786
+ return -1;
787
+ }
788
+ if (k->dsize > 0) {
789
+ if (!k->d) {
790
+ scs_printf("logdet cone array missing\n");
428
791
  return -1;
429
792
  }
430
793
  for (i = 0; i < k->dsize; ++i) {
@@ -434,9 +797,13 @@ scs_int SCS(validate_cones)(const ScsData *d, const ScsCone *k) {
434
797
  }
435
798
  }
436
799
  }
437
- if (k->nucsize && k->nuc_m && k->nuc_n) {
438
- if (k->nucsize < 0) {
439
- scs_printf("nuclear cone dimension error\n");
800
+ if (k->nucsize < 0) {
801
+ scs_printf("nuclear cone dimension error\n");
802
+ return -1;
803
+ }
804
+ if (k->nucsize > 0) {
805
+ if (!k->nuc_m || !k->nuc_n) {
806
+ scs_printf("nuclear cone arrays missing\n");
440
807
  return -1;
441
808
  }
442
809
  for (i = 0; i < k->nucsize; ++i) {
@@ -446,9 +813,13 @@ scs_int SCS(validate_cones)(const ScsData *d, const ScsCone *k) {
446
813
  }
447
814
  }
448
815
  }
449
- if (k->ell1_size && k->ell1) {
450
- if (k->ell1_size < 0) {
451
- scs_printf("ell1 cone dimension error\n");
816
+ if (k->ell1_size < 0) {
817
+ scs_printf("ell1 cone dimension error\n");
818
+ return -1;
819
+ }
820
+ if (k->ell1_size > 0) {
821
+ if (!k->ell1) {
822
+ scs_printf("ell1 cone array missing\n");
452
823
  return -1;
453
824
  }
454
825
  for (i = 0; i < k->ell1_size; ++i) {
@@ -458,7 +829,15 @@ scs_int SCS(validate_cones)(const ScsData *d, const ScsCone *k) {
458
829
  }
459
830
  }
460
831
  }
461
- if (k->sl_size && k->sl_n && k->sl_k) {
832
+ if (k->sl_size < 0) {
833
+ scs_printf("sum-of-largest-eigenvalues cone dimension error\n");
834
+ return -1;
835
+ }
836
+ if (k->sl_size > 0) {
837
+ if (!k->sl_n || !k->sl_k) {
838
+ scs_printf("sum-of-largest-eigenvalues cone arrays missing\n");
839
+ return -1;
840
+ }
462
841
  for (i = 0; i < k->sl_size; ++i) {
463
842
  if ((k->sl_k[i] >= k->sl_n[i]) || k->sl_k[i] <= 0) {
464
843
  scs_printf("sum-of-largest-eigenvalues cone dimension error\n");
@@ -467,146 +846,18 @@ scs_int SCS(validate_cones)(const ScsData *d, const ScsCone *k) {
467
846
  }
468
847
  }
469
848
  #endif
849
+ cone_dims = get_full_cone_dims(k);
850
+ if (cone_dims != d->m) {
851
+ scs_printf("Error: Cone dims %li != rows in A %li\n", (long)cone_dims,
852
+ (long)d->m);
853
+ return -1;
854
+ }
470
855
  return 0;
471
856
  }
472
857
 
473
- void SCS(finish_cone)(ScsConeWork *c) {
474
- if (!c)
475
- return;
476
- #ifdef USE_LAPACK
477
- if (c->Xs)
478
- scs_free(c->Xs);
479
- if (c->cXs)
480
- scs_free(c->cXs);
481
- if (c->Z)
482
- scs_free(c->Z);
483
- if (c->cZ)
484
- scs_free(c->cZ);
485
- if (c->e)
486
- scs_free(c->e);
487
- if (c->isuppz)
488
- scs_free(c->isuppz);
489
- if (c->work)
490
- scs_free(c->work);
491
- if (c->iwork)
492
- scs_free(c->iwork);
493
- if (c->cwork)
494
- scs_free(c->cwork);
495
- /* c->rwork is aliased to c->work in setup, no free needed */
496
- #endif
497
- if (c->cone_boundaries)
498
- scs_free(c->cone_boundaries);
499
- if (c->s)
500
- scs_free(c->s);
501
-
502
- #ifdef USE_SPECTRAL_CONES
503
- if (c->work_logdet)
504
- scs_free(c->work_logdet);
505
- if (c->saved_log_projs)
506
- scs_free(c->saved_log_projs);
507
- if (c->s_nuc)
508
- scs_free(c->s_nuc);
509
- if (c->u_nuc)
510
- scs_free(c->u_nuc);
511
- if (c->vt_nuc)
512
- scs_free(c->vt_nuc);
513
- if (c->work_nuc)
514
- scs_free(c->work_nuc);
515
- if (c->work_sum_of_largest)
516
- scs_free(c->work_sum_of_largest);
517
- if (c->log_cone_warmstarts)
518
- scs_free(c->log_cone_warmstarts);
519
- if (c->work_ell1)
520
- scs_free(c->work_ell1);
521
- if (c->work_ell1_proj)
522
- scs_free(c->work_ell1_proj);
523
- #endif
524
- scs_free(c);
525
- }
526
-
527
- char *SCS(get_cone_header)(const ScsCone *k) {
528
- char *tmp = (char *)scs_malloc(512);
529
- scs_int i, count;
530
-
531
- sprintf(tmp, "cones: ");
532
- if (k->z)
533
- sprintf(tmp + strlen(tmp), "\t z: primal zero / dual free vars: %li\n",
534
- (long)k->z);
535
- if (k->l)
536
- sprintf(tmp + strlen(tmp), "\t l: linear vars: %li\n", (long)k->l);
537
- if (k->bsize)
538
- sprintf(tmp + strlen(tmp), "\t b: box cone vars: %li\n", (long)k->bsize);
539
-
540
- if (k->qsize) {
541
- count = 0;
542
- for (i = 0; i < k->qsize; ++i)
543
- count += k->q[i];
544
- sprintf(tmp + strlen(tmp), "\t q: soc vars: %li, qsize: %li\n",
545
- (long)count, (long)k->qsize);
546
- }
547
- if (k->ssize) {
548
- count = 0;
549
- for (i = 0; i < k->ssize; ++i)
550
- count += get_sd_cone_size(k->s[i]);
551
- sprintf(tmp + strlen(tmp), "\t s: psd vars: %li, ssize: %li\n",
552
- (long)count, (long)k->ssize);
553
- }
554
- if (k->cssize) {
555
- count = 0;
556
- for (i = 0; i < k->cssize; ++i)
557
- count += get_csd_cone_size(k->cs[i]);
558
- sprintf(tmp + strlen(tmp), "\t cs: complex psd vars: %li, cssize: %li\n",
559
- (long)count, (long)k->cssize);
560
- }
561
- if (k->ep || k->ed) {
562
- sprintf(tmp + strlen(tmp), "\t e: exp vars: %li, dual exp vars: %li\n",
563
- (long)(3 * k->ep), (long)(3 * k->ed));
564
- }
565
- if (k->psize) {
566
- sprintf(tmp + strlen(tmp), "\t p: primal + dual power vars: %li\n",
567
- (long)(3 * k->psize));
568
- }
569
- #ifdef USE_SPECTRAL_CONES
570
- scs_int ell1_vars, log_vars, nuc_vars, sl_vars;
571
- log_vars = 0;
572
- if (k->dsize && k->d) {
573
- for (i = 0; i < k->dsize; i++) {
574
- log_vars += get_sd_cone_size(k->d[i]) + 2;
575
- }
576
- sprintf(tmp + strlen(tmp), "\t d: logdet vars: %li, dsize: %li\n",
577
- (long)log_vars, (long)k->dsize);
578
- }
579
- nuc_vars = 0;
580
- if (k->nucsize && k->nuc_m && k->nuc_n) {
581
- for (i = 0; i < k->nucsize; i++) {
582
- nuc_vars += k->nuc_m[i] * k->nuc_n[i] + 1;
583
- }
584
- sprintf(tmp + strlen(tmp), "\t nuc: nuclear vars: %li, nucsize: %li\n",
585
- (long)nuc_vars, (long)k->nucsize);
586
- }
587
- ell1_vars = 0;
588
- if (k->ell1_size && k->ell1) {
589
- for (i = 0; i < k->ell1_size; ++i) {
590
- ell1_vars += k->ell1[i];
591
- }
592
- sprintf(tmp + strlen(tmp), "\t ell1: ell1 vars: %li, ell1_size: %li\n",
593
- (long)ell1_vars, (long)k->ell1_size);
594
- }
595
-
596
- sl_vars = 0;
597
- if (k->sl_size && k->sl_n) {
598
- for (i = 0; i < k->sl_size; ++i) {
599
- sl_vars += get_sd_cone_size(k->sl_n[i]) + 1;
600
- }
601
- sprintf(tmp + strlen(tmp), "\t sl: sl vars: %li, sl_size: %li\n",
602
- (long)sl_vars, (long)k->sl_size);
603
- }
604
- #endif
605
- return tmp;
606
- }
858
+ /* ======================== Workspace Setup ============================= */
607
859
 
608
860
  /*
609
- * Workspace Setup
610
861
  * Consolidated setup for Real PSD, Complex PSD, and Spectral cones.
611
862
  */
612
863
  static scs_int set_up_cone_work_spaces(ScsConeWork *c, const ScsCone *k) {
@@ -684,6 +935,7 @@ static scs_int set_up_cone_work_spaces(ScsConeWork *c, const ScsCone *k) {
684
935
  * 'e' stores eigenvalues, 'isuppz' supports them. Shared by Real/Complex.
685
936
  */
686
937
  c->e = (scs_float *)scs_calloc(n_max, sizeof(scs_float));
938
+ c->psd_gamma = (scs_float *)scs_calloc(n_max, sizeof(scs_float));
687
939
  c->isuppz = (blas_int *)scs_calloc(MAX(2, 2 * n_max), sizeof(blas_int));
688
940
  if (!c->e || !c->isuppz)
689
941
  return -1;
@@ -833,11 +1085,23 @@ static scs_int set_up_ell1_cone_work_space(ScsConeWork *c, const ScsCone *k) {
833
1085
  }
834
1086
  #endif
835
1087
 
1088
+ /* ====================== Cone Projections ============================= */
1089
+
836
1090
  /*
837
1091
  * Projection: Real Semi-Definite Cone
838
1092
  */
1093
+ /* Project one svec-packed block onto the PSD cone. When rho (the
1094
+ * block's slice of the diagonal metric R) is non-uniform it must have
1095
+ * the rank-one structure w_ij = delta_i * delta_j (enforced by
1096
+ * fit_psd_block_weights). The projection under the induced R^{-1}
1097
+ * metric is then a diagonal congruence sandwich: with gamma_i =
1098
+ * w_ii^{-1/4}, project Gamma X Gamma and unscale. Positive homogeneity
1099
+ * of the cone makes any uniform factor in rho drop out, so the
1100
+ * historical scalar-per-block case reduces to the unscaled path. */
839
1101
  static scs_int proj_semi_definite_cone(scs_float *X, const scs_int n,
840
- ScsConeWork *c) {
1102
+ ScsConeWork *c,
1103
+ const scs_float *rho) {
1104
+ scs_int scaled = 0;
841
1105
  if (n == 0)
842
1106
  return 0;
843
1107
  if (n == 1) {
@@ -855,6 +1119,31 @@ static scs_int proj_semi_definite_cone(scs_float *X, const scs_int n,
855
1119
  blas_int m = 0, d_i = 0;
856
1120
  scs_int first_idx = -1;
857
1121
 
1122
+ /* Diagonal-congruence pre-scale (skip when the metric is uniform on
1123
+ * this block so the historical path stays bit-identical). */
1124
+ if (rho && c->psd_gamma) {
1125
+ scs_float wmin = rho[0], wmax = rho[0];
1126
+ for (i = 1; i < n; ++i) {
1127
+ scs_float wii = rho[i * n - ((i - 1) * i) / 2];
1128
+ wmin = MIN(wmin, wii);
1129
+ wmax = MAX(wmax, wii);
1130
+ }
1131
+ if (wmax > wmin * (1. + 1e-12)) {
1132
+ scs_int jj, ii;
1133
+ scaled = 1;
1134
+ for (i = 0; i < n; ++i) {
1135
+ c->psd_gamma[i] = POWF(rho[i * n - ((i - 1) * i) / 2], -0.25);
1136
+ }
1137
+ for (jj = 0; jj < n; ++jj) {
1138
+ scs_float gj = c->psd_gamma[jj];
1139
+ scs_float *col = &(X[jj * n - ((jj - 1) * jj) / 2]);
1140
+ for (ii = jj; ii < n; ++ii) {
1141
+ col[ii - jj] *= c->psd_gamma[ii] * gj;
1142
+ }
1143
+ }
1144
+ }
1145
+ }
1146
+
858
1147
  /* Copy lower triangular part to full matrix buffer Xs */
859
1148
  for (i = 0; i < n; ++i) {
860
1149
  memcpy(&(c->Xs[i * (n + 1)]), &(X[i * n - ((i - 1) * i) / 2]),
@@ -900,6 +1189,18 @@ static scs_int proj_semi_definite_cone(scs_float *X, const scs_int n,
900
1189
  memcpy(&(X[i * n - ((i - 1) * i) / 2]), &(c->Xs[i * (n + 1)]),
901
1190
  (n - i) * sizeof(scs_float));
902
1191
  }
1192
+
1193
+ /* Undo the congruence pre-scale */
1194
+ if (scaled) {
1195
+ scs_int jj, ii;
1196
+ for (jj = 0; jj < n; ++jj) {
1197
+ scs_float gj = c->psd_gamma[jj];
1198
+ scs_float *col = &(X[jj * n - ((jj - 1) * jj) / 2]);
1199
+ for (ii = jj; ii < n; ++ii) {
1200
+ col[ii - jj] /= c->psd_gamma[ii] * gj;
1201
+ }
1202
+ }
1203
+ }
903
1204
  return 0;
904
1205
  #else
905
1206
  return -1;
@@ -1021,7 +1322,8 @@ static void normalize_box_cone(ScsCone *k, scs_float *D, scs_int bsize) {
1021
1322
  */
1022
1323
  static scs_float proj_box_cone(scs_float *tx, const scs_float *bl,
1023
1324
  const scs_float *bu, scs_int bsize,
1024
- scs_float t_wm, scs_float *r_box) {
1325
+ scs_float t_wm, scs_float *r_box,
1326
+ scs_float *r_box_inv) {
1025
1327
  scs_float *x = &(tx[1]);
1026
1328
  scs_float gt, ht, t = t_wm, t_prev, r;
1027
1329
  scs_float rho_t = 1.0;
@@ -1035,6 +1337,12 @@ static scs_float proj_box_cone(scs_float *tx, const scs_float *bl,
1035
1337
  if (r_box) {
1036
1338
  rho_t = 1.0 / r_box[0];
1037
1339
  rho = &(r_box[1]);
1340
+ /* Precompute reciprocals once, used across all Newton iterations */
1341
+ if (r_box_inv) {
1342
+ for (j = 0; j < bsize - 1; j++) {
1343
+ r_box_inv[j] = 1.0 / rho[j];
1344
+ }
1345
+ }
1038
1346
  }
1039
1347
 
1040
1348
  /* Newton's method for t */
@@ -1044,7 +1352,7 @@ static scs_float proj_box_cone(scs_float *tx, const scs_float *bl,
1044
1352
  ht = rho_t;
1045
1353
 
1046
1354
  for (j = 0; j < bsize - 1; j++) {
1047
- r = rho ? 1.0 / rho[j] : 1.0;
1355
+ r = (rho && r_box_inv) ? r_box_inv[j] : 1.0;
1048
1356
  if (x[j] > t * bu[j]) {
1049
1357
  gt += r * (t * bu[j] - x[j]) * bu[j];
1050
1358
  ht += r * bu[j] * bu[j];
@@ -1081,6 +1389,7 @@ static scs_float proj_box_cone(scs_float *tx, const scs_float *bl,
1081
1389
  * Projection: Second Order Cone
1082
1390
  */
1083
1391
  static void proj_soc(scs_float *x, scs_int q) {
1392
+ scs_float v1, s, alpha;
1084
1393
  if (q <= 0)
1085
1394
  return;
1086
1395
  if (q == 1) {
@@ -1088,9 +1397,17 @@ static void proj_soc(scs_float *x, scs_int q) {
1088
1397
  return;
1089
1398
  }
1090
1399
 
1091
- scs_float v1 = x[0];
1092
- scs_float s = SCS(norm_2)(&(x[1]), q - 1);
1093
- scs_float alpha = (s + v1) / 2.0;
1400
+ v1 = x[0];
1401
+ /* Fast paths for the two most common small SOC sizes avoid BLAS call
1402
+ * overhead (function pointer dispatch + Fortran ABI arguments). */
1403
+ if (q == 2) {
1404
+ s = ABS(x[1]);
1405
+ } else if (q == 3) {
1406
+ s = SQRTF(x[1] * x[1] + x[2] * x[2]);
1407
+ } else {
1408
+ s = SCS(norm_2)(&(x[1]), q - 1);
1409
+ }
1410
+ alpha = (s + v1) / 2.0;
1094
1411
 
1095
1412
  if (s <= v1)
1096
1413
  return; /* Inside cone */
@@ -1111,12 +1428,6 @@ static scs_float pow_calc_x(scs_float r, scs_float xh, scs_float rh,
1111
1428
  return MAX(x, 1e-12);
1112
1429
  }
1113
1430
 
1114
- static scs_float pow_calc_fp(scs_float x, scs_float y, scs_float dxdr,
1115
- scs_float dydr, scs_float a) {
1116
- return POWF(x, a) * POWF(y, (1 - a)) * (a * dxdr / x + (1 - a) * dydr / y) -
1117
- 1;
1118
- }
1119
-
1120
1431
  static void proj_power_cone(scs_float *v, scs_float a) {
1121
1432
  scs_float xh = v[0], yh = v[1], rh = ABS(v[2]);
1122
1433
  scs_float x = 0.0, y = 0.0, r;
@@ -1137,17 +1448,21 @@ static void proj_power_cone(scs_float *v, scs_float a) {
1137
1448
 
1138
1449
  r = rh / 2;
1139
1450
  for (i = 0; i < POW_CONE_MAX_ITERS; ++i) {
1140
- scs_float f, fp, dxdr, dydr;
1451
+ scs_float f, fp, dxdr, dydr, xa, y1a;
1141
1452
  x = pow_calc_x(r, xh, rh, a);
1142
1453
  y = pow_calc_x(r, yh, rh, 1 - a);
1143
1454
 
1144
- f = POWF(x, a) * POWF(y, (1 - a)) - r;
1455
+ /* Cache POWF(x,a) and POWF(y,1-a): both are needed for f and fp,
1456
+ * so computing them once saves two POWF calls per Newton iteration. */
1457
+ xa = POWF(x, a);
1458
+ y1a = POWF(y, (1 - a));
1459
+ f = xa * y1a - r;
1145
1460
  if (ABS(f) < POW_CONE_TOL)
1146
1461
  break;
1147
1462
 
1148
1463
  dxdr = a * (rh - 2 * r) / (2 * x - xh);
1149
1464
  dydr = (1 - a) * (rh - 2 * r) / (2 * y - yh);
1150
- fp = pow_calc_fp(x, y, dxdr, dydr, a);
1465
+ fp = xa * y1a * (a * dxdr / x + (1 - a) * dydr / y) - 1;
1151
1466
 
1152
1467
  r = MAX(r - f / fp, 0);
1153
1468
  r = MIN(r, rh);
@@ -1157,6 +1472,8 @@ static void proj_power_cone(scs_float *v, scs_float a) {
1157
1472
  v[2] = (v[2] < 0) ? -r : r;
1158
1473
  }
1159
1474
 
1475
+ /* ================ Main Cone Projection Dispatch ====================== */
1476
+
1160
1477
  /* Project onto the primal K cone in the paper */
1161
1478
  /* The r_y vector determines the INVERSE metric, ie, project under the
1162
1479
  * diag(r_y)^-1 norm.
@@ -1187,7 +1504,8 @@ static scs_int proj_cone(scs_float *x, const ScsCone *k, ScsConeWork *c,
1187
1504
  if (r_y)
1188
1505
  r_box = &(r_y[count]);
1189
1506
  c->box_t_warm_start = proj_box_cone(&(x[count]), k->bl, k->bu, k->bsize,
1190
- c->box_t_warm_start, r_box);
1507
+ c->box_t_warm_start, r_box,
1508
+ c->r_box_inv);
1191
1509
  count += k->bsize;
1192
1510
  }
1193
1511
 
@@ -1205,7 +1523,8 @@ static scs_int proj_cone(scs_float *x, const ScsCone *k, ScsConeWork *c,
1205
1523
  #ifdef USE_SPECTRAL_CONES
1206
1524
  SPECTRAL_TIMING(SCS(tic)(&spec_mat_proj_timer);)
1207
1525
  #endif
1208
- status = proj_semi_definite_cone(&(x[count]), k->s[i], c);
1526
+ status = proj_semi_definite_cone(&(x[count]), k->s[i], c,
1527
+ r_y ? &(r_y[count]) : SCS_NULL);
1209
1528
  #ifdef USE_SPECTRAL_CONES
1210
1529
  SPECTRAL_TIMING(c->tot_time_mat_cone_proj +=
1211
1530
  SCS(tocq)(&spec_mat_proj_timer);)
@@ -1316,9 +1635,7 @@ static scs_int proj_cone(scs_float *x, const ScsCone *k, ScsConeWork *c,
1316
1635
  return 0;
1317
1636
  }
1318
1637
 
1319
- /*
1320
- * Public API
1321
- */
1638
+ /* =========================== Public API ============================== */
1322
1639
 
1323
1640
  ScsConeWork *SCS(init_cone)(ScsCone *k, scs_int m) {
1324
1641
  ScsConeWork *c = (ScsConeWork *)scs_calloc(1, sizeof(ScsConeWork));
@@ -1332,6 +1649,12 @@ ScsConeWork *SCS(init_cone)(ScsCone *k, scs_int m) {
1332
1649
  set_cone_boundaries(k, c);
1333
1650
  c->s = (scs_float *)scs_calloc(m, sizeof(scs_float));
1334
1651
 
1652
+ /* Preallocate workspace for box cone reciprocal metric */
1653
+ if (k->bsize > 1) {
1654
+ c->r_box_inv =
1655
+ (scs_float *)scs_calloc(k->bsize - 1, sizeof(scs_float));
1656
+ }
1657
+
1335
1658
  /* Set up workspaces if matrix cones are present */
1336
1659
  if ((k->ssize && k->s) || (k->cssize && k->cs)
1337
1660
  #ifdef USE_SPECTRAL_CONES
@@ -1356,16 +1679,6 @@ ScsConeWork *SCS(init_cone)(ScsCone *k, scs_int m) {
1356
1679
  return c;
1357
1680
  }
1358
1681
 
1359
- void scale_box_cone(ScsCone *k, ScsConeWork *c, const ScsScaling *scal) {
1360
- if (k->bsize && k->bu && k->bl) {
1361
- c->box_t_warm_start = 1.;
1362
- if (scal) {
1363
- /* also does some sanitizing */
1364
- normalize_box_cone(k, &(scal->D[k->z + k->l]), k->bsize);
1365
- }
1366
- }
1367
- }
1368
-
1369
1682
  /* Outward facing cone projection routine, performs projection in-place.
1370
1683
  If normalize > 0 then will use normalized (equilibrated) cones if applicable.
1371
1684
 
@@ -1396,20 +1709,29 @@ scs_int SCS(proj_dual_cone)(scs_float *x, ScsConeWork *c,
1396
1709
  /* Copy s = x */
1397
1710
  memcpy(c->s, x, c->m * sizeof(scs_float));
1398
1711
 
1399
- /* x -> - Rx */
1400
- for (i = 0; i < c->m; ++i) {
1401
- x[i] *= r_y ? -r_y[i] : -1.0;
1712
+ /* x -> - Rx; hoist the r_y != NULL check outside the loop */
1713
+ if (r_y) {
1714
+ for (i = 0; i < c->m; ++i) {
1715
+ x[i] *= -r_y[i];
1716
+ }
1717
+ } else {
1718
+ for (i = 0; i < c->m; ++i) {
1719
+ x[i] = -x[i];
1720
+ }
1402
1721
  }
1403
1722
 
1404
1723
  /* Project -x onto cone, x -> \Pi_{C^*}^{R^{-1}}(-x) under r_y metric */
1405
1724
  status = proj_cone(x, k, c, scal ? 1 : 0, r_y);
1406
1725
 
1407
- /* Return x + R^{-1} \Pi_{C^*}^{R^{-1}} ( -x ) */
1408
- for (i = 0; i < c->m; ++i) {
1409
- if (r_y)
1726
+ /* Return x + R^{-1} \Pi_{C^*}^{R^{-1}} ( -x ); hoist r_y check */
1727
+ if (r_y) {
1728
+ for (i = 0; i < c->m; ++i) {
1410
1729
  x[i] = x[i] / r_y[i] + c->s[i];
1411
- else
1730
+ }
1731
+ } else {
1732
+ for (i = 0; i < c->m; ++i) {
1412
1733
  x[i] += c->s[i];
1734
+ }
1413
1735
  }
1414
1736
 
1415
1737
  return status;