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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +1 -1
- data/lib/scs/ffi.rb +18 -0
- data/lib/scs/version.rb +1 -1
- data/vendor/scs/CITATION.cff +2 -3
- data/vendor/scs/CLAUDE.md +115 -0
- data/vendor/scs/CMakeLists.txt +330 -201
- data/vendor/scs/CONTRIBUTING.md +49 -0
- data/vendor/scs/Makefile +90 -43
- data/vendor/scs/README.md +96 -2
- data/vendor/scs/include/aa.h +59 -11
- data/vendor/scs/include/aa_stats.h +47 -0
- data/vendor/scs/include/cones.h +16 -7
- data/vendor/scs/include/glbopts.h +113 -26
- data/vendor/scs/include/linalg.h +5 -1
- data/vendor/scs/include/linsys.h +1 -2
- data/vendor/scs/include/normalize.h +6 -2
- data/vendor/scs/include/rw.h +9 -3
- data/vendor/scs/include/scs.h +17 -1
- data/vendor/scs/include/scs_blas.h +8 -0
- data/vendor/scs/include/scs_types.h +2 -2
- data/vendor/scs/include/scs_work.h +22 -1
- data/vendor/scs/include/util.h +3 -6
- data/vendor/scs/include/util_spectral_cones.h +3 -3
- data/vendor/scs/linsys/accelerate/direct/private.c +126 -0
- data/vendor/scs/linsys/accelerate/direct/private.h +34 -0
- data/vendor/scs/linsys/cpu/dense/private.c +250 -0
- data/vendor/scs/linsys/cpu/dense/private.h +31 -0
- data/vendor/scs/linsys/cpu/direct/private.c +86 -44
- data/vendor/scs/linsys/cpu/indirect/private.c +647 -113
- data/vendor/scs/linsys/cpu/indirect/private.h +28 -0
- data/vendor/scs/linsys/csparse.c +9 -3
- data/vendor/scs/linsys/csparse.h +4 -2
- data/vendor/scs/linsys/cudss/direct/private.c +77 -43
- data/vendor/scs/linsys/cudss/direct/private.h +33 -7
- data/vendor/scs/linsys/gpu/indirect/private.c +6 -2
- data/vendor/scs/linsys/mkl/direct/private.c +63 -31
- data/vendor/scs/linsys/mkl/direct/private.h +0 -1
- data/vendor/scs/linsys/scs_matrix.c +291 -165
- data/vendor/scs/linsys/scs_matrix.h +7 -9
- data/vendor/scs/scs.mk +35 -13
- data/vendor/scs/src/aa.c +673 -166
- data/vendor/scs/src/cones.c +545 -223
- data/vendor/scs/src/ctrlc.c +59 -16
- data/vendor/scs/src/exp_cone.c +70 -50
- data/vendor/scs/src/linalg.c +21 -2
- data/vendor/scs/src/normalize.c +24 -26
- data/vendor/scs/src/rw.c +596 -124
- data/vendor/scs/src/scs.c +990 -513
- data/vendor/scs/src/spectral_cones/logdeterminant/log_cone_IPM.c +240 -187
- data/vendor/scs/src/spectral_cones/logdeterminant/log_cone_Newton.c +108 -85
- data/vendor/scs/src/spectral_cones/logdeterminant/log_cone_wrapper.c +62 -63
- data/vendor/scs/src/spectral_cones/logdeterminant/logdet_cone.c +85 -78
- data/vendor/scs/src/spectral_cones/nuclear/ell1_cone.c +92 -97
- data/vendor/scs/src/spectral_cones/nuclear/nuclear_cone.c +39 -28
- data/vendor/scs/src/spectral_cones/sum-largest/sum_largest_cone.c +59 -40
- data/vendor/scs/src/spectral_cones/sum-largest/sum_largest_eval_cone.c +37 -29
- data/vendor/scs/src/spectral_cones/util_spectral_cones.c +12 -6
- data/vendor/scs/src/util.c +37 -7
- data/vendor/scs/test/mkl_interface_mismatch.c +97 -0
- data/vendor/scs/test/packaging/CMakeLists.txt +21 -0
- data/vendor/scs/test/packaging/consume.c +50 -0
- data/vendor/scs/test/problem_utils.h +9 -2
- data/vendor/scs/test/problems/dense_qp.h +64 -0
- data/vendor/scs/test/problems/hs21_tiny_qp.h +6 -2
- data/vendor/scs/test/problems/hs21_tiny_qp_rw.h +10 -2
- data/vendor/scs/test/problems/infeasible_lp.h +66 -0
- data/vendor/scs/test/problems/infeasible_socp.h +75 -0
- data/vendor/scs/test/problems/lp_update.h +110 -0
- data/vendor/scs/test/problems/qafiro_tiny_qp.h +3 -1
- data/vendor/scs/test/problems/rob_gauss_cov_est.h +6 -3
- data/vendor/scs/test/problems/small_qp.h +5 -0
- data/vendor/scs/test/problems/test_box_cone.h +92 -0
- data/vendor/scs/test/problems/test_dual_exp_cone.h +96 -0
- data/vendor/scs/test/problems/test_inaccurate.h +212 -0
- data/vendor/scs/test/problems/test_mixed_cones.h +113 -0
- data/vendor/scs/test/problems/test_normalize_roundtrip.h +279 -0
- data/vendor/scs/test/problems/test_power_cone.h +414 -0
- data/vendor/scs/test/problems/test_psd_metric.h +109 -0
- data/vendor/scs/test/problems/test_psd_n1.h +84 -0
- data/vendor/scs/test/problems/test_root_plus.h +191 -0
- data/vendor/scs/test/problems/test_rw_settings.h +112 -0
- data/vendor/scs/test/problems/test_soc_sizes.h +393 -0
- data/vendor/scs/test/problems/test_solver_options.h +558 -0
- data/vendor/scs/test/problems/test_validation.h +263 -24
- data/vendor/scs/test/problems/test_zero_cone.h +81 -0
- data/vendor/scs/test/problems/unbounded_lp.h +66 -0
- data/vendor/scs/test/problems/unbounded_socp.h +71 -0
- data/vendor/scs/test/run_from_file.c +6 -0
- data/vendor/scs/test/run_tests.c +71 -0
- data/vendor/scs/test/rw_settings.c +7 -0
- data/vendor/scs/test/spectral_cones_problems/test_ell1_and_nuc.h +128 -0
- data/vendor/scs/test/spectral_cones_problems/test_ell1_cone.h +115 -0
- metadata +35 -3
data/vendor/scs/src/aa.c
CHANGED
|
@@ -20,22 +20,40 @@
|
|
|
20
20
|
* Type-II:
|
|
21
21
|
* return f = f - (S - Y) * ( Y'Y + r I)^{-1} ( Y'g )
|
|
22
22
|
*
|
|
23
|
+
* Both types reduce to the same regularized least-squares augmentation
|
|
24
|
+
* (A'B + r I) γ = A' g
|
|
25
|
+
* ⇔ [A; √r I]' [B; √r I] γ = [A; √r I]' [g; 0],
|
|
26
|
+
* where A = S (type-I) or A = Y (type-II), and B = Y. We solve via a thin
|
|
27
|
+
* QR factorization of the augmented A, which keeps the conditioning at
|
|
28
|
+
* κ(A_aug) rather than the κ(A_aug)² that a normal-equations solve would
|
|
29
|
+
* incur — critical near the optimum where Y rows are tiny and the Gram
|
|
30
|
+
* matrix becomes numerically singular.
|
|
23
31
|
*/
|
|
24
32
|
|
|
33
|
+
#include <float.h>
|
|
34
|
+
#include <math.h>
|
|
35
|
+
#include <string.h>
|
|
36
|
+
|
|
25
37
|
#include "aa.h"
|
|
26
38
|
#include "scs_blas.h"
|
|
27
39
|
|
|
40
|
+
#ifndef SFLOAT
|
|
41
|
+
#define AA_EPS DBL_EPSILON
|
|
42
|
+
#else
|
|
43
|
+
#define AA_EPS FLT_EPSILON
|
|
44
|
+
#endif
|
|
45
|
+
|
|
28
46
|
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
|
|
29
47
|
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
|
|
30
|
-
#define FILL_MEMORY_BEFORE_SOLVE (1)
|
|
31
48
|
|
|
32
49
|
#ifndef USE_LAPACK
|
|
33
50
|
|
|
34
51
|
typedef void *ACCEL_WORK;
|
|
35
52
|
|
|
36
|
-
AaWork *aa_init(aa_int dim, aa_int mem, aa_int
|
|
37
|
-
aa_float
|
|
38
|
-
aa_float
|
|
53
|
+
AaWork *aa_init(aa_int dim, aa_int mem, aa_int min_len, aa_int type1,
|
|
54
|
+
aa_float regularization, aa_float relaxation,
|
|
55
|
+
aa_float safeguard_factor, aa_float max_weight_norm,
|
|
56
|
+
aa_int ir_max_steps, aa_int verbosity) {
|
|
39
57
|
return SCS_NULL;
|
|
40
58
|
}
|
|
41
59
|
aa_float aa_apply(aa_float *f, const aa_float *x, AaWork *a) {
|
|
@@ -48,6 +66,12 @@ void aa_finish(AaWork *a) {
|
|
|
48
66
|
}
|
|
49
67
|
void aa_reset(AaWork *a) {
|
|
50
68
|
}
|
|
69
|
+
AaStats aa_get_stats(const AaWork *a) {
|
|
70
|
+
AaStats s;
|
|
71
|
+
memset(&s, 0, sizeof(AaStats));
|
|
72
|
+
s.last_aa_norm = NAN;
|
|
73
|
+
return s;
|
|
74
|
+
}
|
|
51
75
|
|
|
52
76
|
#else
|
|
53
77
|
|
|
@@ -58,6 +82,25 @@ void aa_reset(AaWork *a) {
|
|
|
58
82
|
tic(&__t);
|
|
59
83
|
#define TIME_TOC toc(__func__, &__t);
|
|
60
84
|
|
|
85
|
+
#ifdef _WIN32
|
|
86
|
+
#include <windows.h>
|
|
87
|
+
typedef struct timer {
|
|
88
|
+
LARGE_INTEGER tic;
|
|
89
|
+
LARGE_INTEGER toc;
|
|
90
|
+
} timer;
|
|
91
|
+
|
|
92
|
+
void tic(timer *t) {
|
|
93
|
+
QueryPerformanceCounter(&t->tic);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
aa_float tocq(timer *t) {
|
|
97
|
+
LARGE_INTEGER freq;
|
|
98
|
+
QueryPerformanceFrequency(&freq);
|
|
99
|
+
QueryPerformanceCounter(&t->toc);
|
|
100
|
+
return (aa_float)(t->toc.QuadPart - t->tic.QuadPart) /
|
|
101
|
+
(aa_float)freq.QuadPart * 1e3;
|
|
102
|
+
}
|
|
103
|
+
#else
|
|
61
104
|
#include <time.h>
|
|
62
105
|
typedef struct timer {
|
|
63
106
|
struct timespec tic;
|
|
@@ -82,10 +125,11 @@ aa_float tocq(timer *t) {
|
|
|
82
125
|
}
|
|
83
126
|
return (aa_float)temp.tv_sec * 1e3 + (aa_float)temp.tv_nsec / 1e6;
|
|
84
127
|
}
|
|
128
|
+
#endif
|
|
85
129
|
|
|
86
130
|
aa_float toc(const char *str, timer *t) {
|
|
87
131
|
aa_float time = tocq(t);
|
|
88
|
-
|
|
132
|
+
scs_printf("%s - time: %8.4f milli-seconds.\n", str, time);
|
|
89
133
|
return time;
|
|
90
134
|
}
|
|
91
135
|
|
|
@@ -100,7 +144,7 @@ aa_float toc(const char *str, timer *t) {
|
|
|
100
144
|
extern "C" {
|
|
101
145
|
#endif
|
|
102
146
|
|
|
103
|
-
/* BLAS functions used */
|
|
147
|
+
/* BLAS / LAPACK functions used */
|
|
104
148
|
aa_float BLAS(nrm2)(blas_int *n, aa_float *x, blas_int *incx);
|
|
105
149
|
void BLAS(axpy)(blas_int *n, aa_float *a, const aa_float *x, blas_int *incx,
|
|
106
150
|
aa_float *y, blas_int *incy);
|
|
@@ -110,12 +154,25 @@ void BLAS(gemv)(const char *trans, const blas_int *m, const blas_int *n,
|
|
|
110
154
|
aa_float *y, const blas_int *incy);
|
|
111
155
|
void BLAS(gesv)(blas_int *n, blas_int *nrhs, aa_float *a, blas_int *lda,
|
|
112
156
|
blas_int *ipiv, aa_float *b, blas_int *ldb, blas_int *info);
|
|
113
|
-
void BLAS(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
aa_float *c, blas_int *ldc);
|
|
157
|
+
void BLAS(getrs)(const char *trans, const blas_int *n, const blas_int *nrhs,
|
|
158
|
+
const aa_float *a, const blas_int *lda, const blas_int *ipiv,
|
|
159
|
+
aa_float *b, const blas_int *ldb, blas_int *info);
|
|
117
160
|
void BLAS(scal)(const blas_int *n, const aa_float *a, aa_float *x,
|
|
118
161
|
const blas_int *incx);
|
|
162
|
+
void BLAS(trsv)(const char *uplo, const char *trans, const char *diag,
|
|
163
|
+
const blas_int *n, const aa_float *a, const blas_int *lda,
|
|
164
|
+
aa_float *x, const blas_int *incx);
|
|
165
|
+
void BLAS(trmv)(const char *uplo, const char *trans, const char *diag,
|
|
166
|
+
const blas_int *n, const aa_float *a, const blas_int *lda,
|
|
167
|
+
aa_float *x, const blas_int *incx);
|
|
168
|
+
void BLAS(geqp3)(const blas_int *m, const blas_int *n, aa_float *a,
|
|
169
|
+
const blas_int *lda, blas_int *jpvt, aa_float *tau,
|
|
170
|
+
aa_float *work, const blas_int *lwork, blas_int *info);
|
|
171
|
+
void BLAS(ormqr)(const char *side, const char *trans, const blas_int *m,
|
|
172
|
+
const blas_int *n, const blas_int *k, const aa_float *a,
|
|
173
|
+
const blas_int *lda, const aa_float *tau, aa_float *c,
|
|
174
|
+
const blas_int *ldc, aa_float *work, const blas_int *lwork,
|
|
175
|
+
blas_int *info);
|
|
119
176
|
|
|
120
177
|
#ifdef __cplusplus
|
|
121
178
|
}
|
|
@@ -123,18 +180,21 @@ void BLAS(scal)(const blas_int *n, const aa_float *a, aa_float *x,
|
|
|
123
180
|
|
|
124
181
|
/* This file uses Anderson acceleration to improve the convergence of
|
|
125
182
|
* a fixed point mapping.
|
|
126
|
-
* At each iteration we
|
|
127
|
-
*
|
|
183
|
+
* At each iteration we solve a (small) regularized least-squares
|
|
184
|
+
* problem via a pivoted QR factorization of an augmented matrix,
|
|
185
|
+
* followed by iterative refinement on the reduced system.
|
|
128
186
|
*/
|
|
129
187
|
|
|
130
188
|
/* contains the necessary parameters to perform aa at each step */
|
|
131
189
|
struct ACCEL_WORK {
|
|
132
|
-
aa_int type1;
|
|
133
|
-
aa_int mem;
|
|
134
|
-
aa_int
|
|
135
|
-
aa_int
|
|
136
|
-
aa_int
|
|
137
|
-
aa_int
|
|
190
|
+
aa_int type1; /* bool, if true type 1 aa otherwise type 2 */
|
|
191
|
+
aa_int mem; /* aa memory */
|
|
192
|
+
aa_int min_len; /* min iterates before solve starts (1..mem) */
|
|
193
|
+
aa_int dim; /* variable dimension */
|
|
194
|
+
aa_int iter; /* current iteration */
|
|
195
|
+
aa_int verbosity; /* verbosity level, 0 is no printing */
|
|
196
|
+
aa_int success; /* was the last AA step successful or not */
|
|
197
|
+
aa_int ir_max_steps; /* max iterative refinement passes, 0 disables */
|
|
138
198
|
|
|
139
199
|
aa_float relaxation; /* relaxation x and f, beta in some papers */
|
|
140
200
|
aa_float regularization; /* regularization */
|
|
@@ -149,55 +209,121 @@ struct ACCEL_WORK {
|
|
|
149
209
|
/* from previous iteration */
|
|
150
210
|
aa_float *g_prev; /* x_prev - f(x_prev) */
|
|
151
211
|
|
|
152
|
-
aa_float *y; /* g - g_prev */
|
|
153
|
-
aa_float *s; /* x - x_prev */
|
|
154
|
-
aa_float *d; /* f - f_prev */
|
|
155
|
-
|
|
156
212
|
aa_float *Y; /* matrix of stacked y values */
|
|
157
213
|
aa_float *S; /* matrix of stacked s values */
|
|
158
214
|
aa_float *D; /* matrix of stacked d values = (S-Y) */
|
|
159
|
-
aa_float *M; /* S'Y or Y'Y depending on type of aa */
|
|
160
215
|
|
|
161
|
-
/*
|
|
162
|
-
|
|
163
|
-
|
|
216
|
+
/* Per-column cached L2 norms of S and Y (length mem). Each slot is
|
|
217
|
+
* rewritten when its column is rewritten (update_accel_params), and
|
|
218
|
+
* compute_regularization reduces these with a max-scaled sum to form
|
|
219
|
+
* ||S||_F / ||Y||_F in O(mem) — versus the original O(dim·mem) nrm2
|
|
220
|
+
* over the whole matrix. Storing per-column (not incremental "subtract
|
|
221
|
+
* old / add new") avoids drift: near convergence Y columns can shrink
|
|
222
|
+
* by many orders of magnitude and add/subtract updates fall below the
|
|
223
|
+
* running sum's rounding floor, pegging the total above the true
|
|
224
|
+
* Frobenius norm (observed on the κ=1e10 stress test). Storing the
|
|
225
|
+
* norm (not its square) preserves nrm2's overflow/underflow safety —
|
|
226
|
+
* squaring a 1e200 or 1e-200 column norm would hit ±inf / 0 even
|
|
227
|
+
* though nrm2 on the whole matrix would produce a finite answer. */
|
|
228
|
+
aa_float *nrm_s_col;
|
|
229
|
+
aa_float *nrm_y_col;
|
|
230
|
+
|
|
231
|
+
/* QR workspaces, sized for the augmented problem. */
|
|
232
|
+
aa_float *A_aug; /* (dim + mem) x mem -- [A; √r I]; factored in place */
|
|
233
|
+
aa_float *B_aug; /* (dim + mem) x mem -- [Y; √r I] (type-I only) */
|
|
234
|
+
aa_float *c_aug; /* (dim + mem) -- [g; 0], overwritten by Q' c */
|
|
235
|
+
aa_float *tau; /* mem -- Householder scalars */
|
|
236
|
+
aa_float *qr_work; /* lwork -- LAPACK scratch for geqp3/ormqr */
|
|
237
|
+
blas_int qr_lwork; /* size of qr_work, chosen via workspace query at init */
|
|
238
|
+
blas_int *jpvt; /* mem -- column permutation from geqp3 */
|
|
239
|
+
|
|
240
|
+
aa_float *W; /* mem x mem scratch: Q' B_aug top block (type-I gesv) */
|
|
241
|
+
aa_float *W_orig; /* mem x mem: W copy preserved for iterative refinement */
|
|
242
|
+
blas_int *ipiv; /* gesv permutation (type-I) */
|
|
243
|
+
|
|
244
|
+
/* Iterative refinement scratches (size mem each). Separate from `work`
|
|
245
|
+
* so the IR dance doesn't clobber γ or the safeguard scratch. */
|
|
246
|
+
aa_float *gamma_red; /* permuted/truncated γ (length `rank`) */
|
|
247
|
+
aa_float *c_top_save; /* original RHS preserved across the solve */
|
|
248
|
+
aa_float *ir_res; /* residual/correction vector */
|
|
249
|
+
|
|
250
|
+
/* Dual-use scratch: dim-sized buffer for aa_safeguard's x_new - f_new
|
|
251
|
+
* diff, and also the natural-order γ home (len ≤ mem entries) inside
|
|
252
|
+
* solve(). Allocated as max(mem, dim) so both uses fit. */
|
|
253
|
+
aa_float *work;
|
|
164
254
|
|
|
165
255
|
aa_float *x_work; /* workspace (= x) for when relaxation != 1.0 */
|
|
256
|
+
|
|
257
|
+
/* Lifetime diagnostics (see AaStats in aa_stats.h). NOT cleared by
|
|
258
|
+
* aa_reset — the internal reset path fires on safeguard rejection,
|
|
259
|
+
* and you want the rejection to stay visible in the counters. */
|
|
260
|
+
aa_int n_accept;
|
|
261
|
+
aa_int n_reject_lapack;
|
|
262
|
+
aa_int n_reject_rank0;
|
|
263
|
+
aa_int n_reject_nonfinite;
|
|
264
|
+
aa_int n_reject_weight_cap;
|
|
265
|
+
aa_int n_safeguard_reject;
|
|
266
|
+
aa_int last_rank;
|
|
267
|
+
aa_float last_aa_norm;
|
|
268
|
+
aa_float last_regularization;
|
|
166
269
|
};
|
|
167
270
|
|
|
168
|
-
/*
|
|
169
|
-
|
|
170
|
-
|
|
271
|
+
/* Reduce a length-`mem` vector of nonnegative column norms to a single
|
|
272
|
+
* Frobenius norm ||A||_F = sqrt(Σ nrm_col_i²), using the classic
|
|
273
|
+
* max-scaled sum of squares so we don't square-then-overflow on a
|
|
274
|
+
* large column or square-then-underflow on a tiny one. This mirrors
|
|
275
|
+
* what nrm2 does internally across elements, but here across column
|
|
276
|
+
* norms — each nrm_col_i is already nrm2-safe per column. */
|
|
277
|
+
static aa_float frob_from_col_norms(const aa_float *nrm_col, aa_int mem) {
|
|
278
|
+
aa_int i;
|
|
279
|
+
aa_float m = 0;
|
|
280
|
+
for (i = 0; i < mem; ++i) {
|
|
281
|
+
if (nrm_col[i] > m) m = nrm_col[i];
|
|
282
|
+
}
|
|
283
|
+
if (m == 0) return 0;
|
|
284
|
+
aa_float sumsq = 0;
|
|
285
|
+
for (i = 0; i < mem; ++i) {
|
|
286
|
+
aa_float t = nrm_col[i] / m;
|
|
287
|
+
sumsq += t * t;
|
|
288
|
+
}
|
|
289
|
+
return m * sqrt(sumsq);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/* Tikhonov regularization scaled with the problem. Matches the prior
|
|
293
|
+
* behavior's intent (r grows with the magnitude of A'B so `regularization`
|
|
294
|
+
* stays unitless), but uses the cheap Frobenius-norm upper bound
|
|
295
|
+
* ||A'B||_F ≤ ||A||_F · ||B||_F
|
|
296
|
+
* instead of maintaining a Gram matrix. For type-II A == B so this is
|
|
297
|
+
* ||Y||_F², the same scale as the previous ||Y'Y||_F up to a factor
|
|
298
|
+
* ≤ √mem. Reduces the per-column cached norms (O(mem)) rather than a
|
|
299
|
+
* fresh nrm2 over dim·mem entries. */
|
|
300
|
+
static aa_float compute_regularization(AaWork *a) {
|
|
171
301
|
TIME_TIC
|
|
172
|
-
aa_float
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
r = a->regularization * nrm_m;
|
|
302
|
+
aa_float nrm_y = frob_from_col_norms(a->nrm_y_col, a->mem);
|
|
303
|
+
aa_float nrm_a = a->type1 ? frob_from_col_norms(a->nrm_s_col, a->mem) : nrm_y;
|
|
304
|
+
aa_float r = a->regularization * nrm_a * nrm_y;
|
|
176
305
|
if (a->verbosity > 2) {
|
|
177
|
-
|
|
306
|
+
scs_printf("iter: %i, ||A||_F %.2e, ||Y||_F %.2e, r: %.2e\n",
|
|
307
|
+
(int)a->iter, nrm_a, nrm_y, r);
|
|
178
308
|
}
|
|
179
309
|
TIME_TOC
|
|
180
310
|
return r;
|
|
181
311
|
}
|
|
182
312
|
|
|
183
|
-
/*
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
313
|
+
/* Build [M; √r I_len] column-major into `dst` with fixed leading dim
|
|
314
|
+
* (dim + mem). When len < mem we zero-pad the unused trailing rows so
|
|
315
|
+
* the QR factorization still operates on a well-defined (dim+mem) x len
|
|
316
|
+
* block; the zero rows don't change the solve. */
|
|
317
|
+
static void build_augmented(aa_float *dst, const aa_float *src, aa_int dim,
|
|
318
|
+
aa_int mem, aa_int len, aa_float sqrt_r) {
|
|
187
319
|
aa_int i;
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if (a->regularization > 0) {
|
|
195
|
-
r = compute_regularization(a, len);
|
|
196
|
-
for (i = 0; i < len; ++i) {
|
|
197
|
-
a->M[i + len * i] += r;
|
|
198
|
-
}
|
|
320
|
+
aa_int aug_rows = dim + mem;
|
|
321
|
+
for (i = 0; i < len; ++i) {
|
|
322
|
+
aa_float *col = &dst[i * aug_rows];
|
|
323
|
+
memcpy(col, &src[i * dim], dim * sizeof(aa_float));
|
|
324
|
+
memset(&col[dim], 0, mem * sizeof(aa_float));
|
|
325
|
+
col[dim + i] = sqrt_r;
|
|
199
326
|
}
|
|
200
|
-
TIME_TOC
|
|
201
327
|
}
|
|
202
328
|
|
|
203
329
|
/* initialize accel params, in particular x_prev, f_prev, g_prev */
|
|
@@ -217,62 +343,72 @@ static void init_accel_params(const aa_float *x, const aa_float *f, AaWork *a) {
|
|
|
217
343
|
TIME_TOC
|
|
218
344
|
}
|
|
219
345
|
|
|
220
|
-
/* updates the workspace parameters for aa for this iteration
|
|
346
|
+
/* updates the workspace parameters for aa for this iteration
|
|
347
|
+
*
|
|
348
|
+
* Writes this iteration's s, d, y columns directly into S, D, Y at slot
|
|
349
|
+
* `idx` — no intermediate scratch. Numerically sensitive because:
|
|
350
|
+
*
|
|
351
|
+
* - y is computed as g - g_prev (ONE rounding into a cancellation-prone
|
|
352
|
+
* quantity). Deriving y from s - d would add two extra roundings and
|
|
353
|
+
* make y noticeably worse near convergence where g and g_prev are
|
|
354
|
+
* tiny and nearly equal.
|
|
355
|
+
*
|
|
356
|
+
* - The reads of a->x, a->f, a->g_prev all require the PREVIOUS
|
|
357
|
+
* iteration's values, so state advance (x_prev <- x, f_prev <- f,
|
|
358
|
+
* g_prev <- g) must happen AFTER everything that reads them. s uses
|
|
359
|
+
* old a->x; d uses old a->f; y uses old a->g_prev. */
|
|
221
360
|
static void update_accel_params(const aa_float *x, const aa_float *f, AaWork *a,
|
|
222
361
|
aa_int len) {
|
|
223
|
-
/*
|
|
362
|
+
/* Entry invariant: a->x == x_prev, a->f == f_prev, a->g_prev == g_prev. */
|
|
224
363
|
TIME_TIC
|
|
225
364
|
aa_int idx = (a->iter - 1) % a->mem;
|
|
226
365
|
blas_int one = 1;
|
|
227
366
|
blas_int bdim = (blas_int)a->dim;
|
|
228
367
|
aa_float neg_onef = -1.0;
|
|
368
|
+
aa_float *s_col = &(a->S[idx * a->dim]);
|
|
369
|
+
aa_float *d_col = &(a->D[idx * a->dim]);
|
|
370
|
+
aa_float *y_col = &(a->Y[idx * a->dim]);
|
|
229
371
|
|
|
230
|
-
/*
|
|
231
|
-
memcpy(
|
|
232
|
-
|
|
233
|
-
memcpy(a->s, x, sizeof(aa_float) * a->dim);
|
|
234
|
-
/* d = f */
|
|
235
|
-
memcpy(a->d, f, sizeof(aa_float) * a->dim);
|
|
236
|
-
/* g = x - f */
|
|
237
|
-
BLAS(axpy)(&bdim, &neg_onef, f, &one, a->g, &one);
|
|
238
|
-
/* s = x - x_prev */
|
|
239
|
-
BLAS(axpy)(&bdim, &neg_onef, a->x, &one, a->s, &one);
|
|
240
|
-
/* d = f - f_prev */
|
|
241
|
-
BLAS(axpy)(&bdim, &neg_onef, a->f, &one, a->d, &one);
|
|
242
|
-
|
|
243
|
-
/* g, s, d correct here */
|
|
372
|
+
/* S[:, idx] = x - x_prev (reads old a->x). */
|
|
373
|
+
memcpy(s_col, x, sizeof(aa_float) * a->dim);
|
|
374
|
+
BLAS(axpy)(&bdim, &neg_onef, a->x, &one, s_col, &one);
|
|
244
375
|
|
|
245
|
-
/*
|
|
246
|
-
memcpy(
|
|
247
|
-
|
|
248
|
-
BLAS(axpy)(&bdim, &neg_onef, a->g_prev, &one, a->y, &one);
|
|
376
|
+
/* D[:, idx] = f - f_prev (reads old a->f). */
|
|
377
|
+
memcpy(d_col, f, sizeof(aa_float) * a->dim);
|
|
378
|
+
BLAS(axpy)(&bdim, &neg_onef, a->f, &one, d_col, &one);
|
|
249
379
|
|
|
250
|
-
/*
|
|
380
|
+
/* g = x - f (this iteration's residual; needed for the solve RHS). */
|
|
381
|
+
memcpy(a->g, x, sizeof(aa_float) * a->dim);
|
|
382
|
+
BLAS(axpy)(&bdim, &neg_onef, f, &one, a->g, &one);
|
|
251
383
|
|
|
252
|
-
/*
|
|
253
|
-
memcpy(
|
|
254
|
-
|
|
255
|
-
memcpy(&(a->S[idx * a->dim]), a->s, sizeof(aa_float) * a->dim);
|
|
256
|
-
/* copy d into idx col of D */
|
|
257
|
-
memcpy(&(a->D[idx * a->dim]), a->d, sizeof(aa_float) * a->dim);
|
|
384
|
+
/* Y[:, idx] = g - g_prev (reads old a->g_prev; single-rounding y). */
|
|
385
|
+
memcpy(y_col, a->g, sizeof(aa_float) * a->dim);
|
|
386
|
+
BLAS(axpy)(&bdim, &neg_onef, a->g_prev, &one, y_col, &one);
|
|
258
387
|
|
|
259
|
-
/*
|
|
388
|
+
/* Update the per-column cached norms only when the scaled
|
|
389
|
+
* regularization path will read them. Pinned/no regularization skips
|
|
390
|
+
* compute_regularization entirely, and Type-II uses Y for both A and B,
|
|
391
|
+
* so S norms are unused there. Store the norm itself (not its square) to
|
|
392
|
+
* preserve nrm2's overflow/underflow safety. */
|
|
393
|
+
if (a->regularization > 0) {
|
|
394
|
+
if (a->type1) {
|
|
395
|
+
a->nrm_s_col[idx] = BLAS(nrm2)(&bdim, s_col, &one);
|
|
396
|
+
}
|
|
397
|
+
a->nrm_y_col[idx] = BLAS(nrm2)(&bdim, y_col, &one);
|
|
398
|
+
}
|
|
260
399
|
|
|
261
|
-
/*
|
|
262
|
-
|
|
400
|
+
/* State advance for next iter: (x_prev, f_prev, g_prev) <- (x, f, g).
|
|
401
|
+
* Must follow all the reads above. */
|
|
263
402
|
memcpy(a->x, x, sizeof(aa_float) * a->dim);
|
|
403
|
+
memcpy(a->f, f, sizeof(aa_float) * a->dim);
|
|
404
|
+
memcpy(a->g_prev, a->g, sizeof(aa_float) * a->dim);
|
|
264
405
|
|
|
265
|
-
/*
|
|
406
|
+
/* Relaxation scratch (mirror of x); only present when relaxation != 1.0. */
|
|
266
407
|
if (a->x_work) {
|
|
267
408
|
memcpy(a->x_work, x, sizeof(aa_float) * a->dim);
|
|
268
409
|
}
|
|
269
410
|
|
|
270
|
-
/* x
|
|
271
|
-
|
|
272
|
-
memcpy(a->g_prev, a->g, sizeof(aa_float) * a->dim);
|
|
273
|
-
/* g_prev set for next iter here */
|
|
274
|
-
|
|
275
|
-
/* compute ||g|| = ||f - x|| */
|
|
411
|
+
/* ||g|| = ||x - f|| (current residual norm, used by the safeguard). */
|
|
276
412
|
a->norm_g = BLAS(nrm2)(&bdim, a->g, &one);
|
|
277
413
|
|
|
278
414
|
TIME_TOC
|
|
@@ -286,8 +422,9 @@ static void relax(aa_float *f, AaWork *a, aa_int len) {
|
|
|
286
422
|
aa_float onef = 1.0, neg_onef = -1.0;
|
|
287
423
|
aa_float one_m_relaxation = 1. - a->relaxation;
|
|
288
424
|
/* x_work = x - S * work */
|
|
289
|
-
BLAS(gemv)
|
|
290
|
-
|
|
425
|
+
BLAS(gemv)
|
|
426
|
+
("NoTrans", &bdim, &blen, &neg_onef, a->S, &bdim, a->work, &one, &onef,
|
|
427
|
+
a->x_work, &one);
|
|
291
428
|
/* f = relaxation * f */
|
|
292
429
|
BLAS(scal)(&bdim, &a->relaxation, f, &one);
|
|
293
430
|
/* f += (1 - relaxation) * x_work */
|
|
@@ -295,52 +432,246 @@ static void relax(aa_float *f, AaWork *a, aa_int len) {
|
|
|
295
432
|
TIME_TOC
|
|
296
433
|
}
|
|
297
434
|
|
|
298
|
-
/*
|
|
299
|
-
*
|
|
300
|
-
|
|
435
|
+
/* Solve the regularized normal equations (A'B + rI) γ = A'g via a
|
|
436
|
+
* pivoted QR (geqp3) of the augmented matrix [A; √r I]. Column pivoting
|
|
437
|
+
* exposes the numerical rank directly in the diagonal of R: we truncate
|
|
438
|
+
* at the first diagonal whose magnitude falls below len·ε·|R_11|
|
|
439
|
+
* (dim-independent: the inner LS is a len-column problem, so its noise
|
|
440
|
+
* floor scales with column count, not the caller's state dim) and solve
|
|
441
|
+
* the smaller, well-conditioned system (graceful degradation instead of
|
|
442
|
+
* hard reset on near-rank-deficiency). Iterative refinement on the
|
|
443
|
+
* reduced system recovers digits lost to gesv/trsv rounding; the loop
|
|
444
|
+
* auto-stops when the correction no longer contracts and is capped at
|
|
445
|
+
* ir_max_steps (see aa_init). γ is then validated against
|
|
446
|
+
* max_weight_norm in the L2 sense. */
|
|
301
447
|
static aa_float solve(aa_float *f, AaWork *a, aa_int len) {
|
|
302
448
|
TIME_TIC
|
|
303
449
|
blas_int info = -1, bdim = (blas_int)(a->dim), one = 1, blen = (blas_int)len;
|
|
304
|
-
|
|
450
|
+
/* Leading dim is fixed to dim+mem regardless of len so the buffers
|
|
451
|
+
* allocated in aa_init match the strides used here. Unused trailing
|
|
452
|
+
* rows are kept zero by build_augmented. */
|
|
453
|
+
blas_int aug_rows = bdim + (blas_int)a->mem;
|
|
454
|
+
blas_int bmem = (blas_int)a->mem;
|
|
455
|
+
aa_float onef = 1.0, neg_onef = -1.0, aa_norm;
|
|
456
|
+
aa_float *A_src = a->type1 ? a->S : a->Y;
|
|
457
|
+
aa_float *gamma = a->work; /* natural-order γ, len entries used by gemv below */
|
|
458
|
+
aa_int i;
|
|
459
|
+
aa_int rank = 0;
|
|
460
|
+
blas_int brank;
|
|
461
|
+
|
|
462
|
+
/* Three regularization modes:
|
|
463
|
+
* regularization > 0 : problem-scaled r = regularization * ||A||_F ||Y||_F
|
|
464
|
+
* regularization < 0 : pinned absolute r = -regularization (Frobenius skipped)
|
|
465
|
+
* regularization == 0 : unregularized r = 0
|
|
466
|
+
* Pinned mode gives a knob for applications where the problem scale is
|
|
467
|
+
* known and the caller wants a stable, scale-free regularizer. */
|
|
468
|
+
aa_float r;
|
|
469
|
+
if (a->regularization > 0) {
|
|
470
|
+
r = compute_regularization(a);
|
|
471
|
+
} else if (a->regularization < 0) {
|
|
472
|
+
r = -a->regularization;
|
|
473
|
+
} else {
|
|
474
|
+
r = 0.0;
|
|
475
|
+
}
|
|
476
|
+
aa_float sqrt_r = (r > 0) ? sqrt(r) : 0.0;
|
|
477
|
+
|
|
478
|
+
/* 1. Build A_aug = [A; √r I_len]; factor with column pivoting. geqp3
|
|
479
|
+
* requires jpvt zeroed on entry so it is free to choose the pivot
|
|
480
|
+
* order (nonzero entries would be treated as user-forced pivots). */
|
|
481
|
+
build_augmented(a->A_aug, A_src, a->dim, a->mem, len, sqrt_r);
|
|
482
|
+
for (i = 0; i < len; ++i) a->jpvt[i] = 0;
|
|
483
|
+
BLAS(geqp3)(&aug_rows, &blen, a->A_aug, &aug_rows, a->jpvt, a->tau,
|
|
484
|
+
a->qr_work, &a->qr_lwork, &info);
|
|
485
|
+
/* Capture geqp3's info before the rank-0 path below overwrites it; the
|
|
486
|
+
* reject-cause attribution needs to distinguish a genuine LAPACK failure
|
|
487
|
+
* from "the matrix went numerically to zero." */
|
|
488
|
+
blas_int lapack_info = info;
|
|
489
|
+
|
|
490
|
+
/* 2. Rank estimation. geqp3 sorts |R_ii| non-increasingly; find the
|
|
491
|
+
* largest `rank` with |R_rank-1,rank-1| ≥ tol. A rank of zero means
|
|
492
|
+
* the whole à is numerically zero — hand off to aa_reset below. */
|
|
493
|
+
if (info == 0) {
|
|
494
|
+
aa_float r11 = fabs(a->A_aug[0]);
|
|
495
|
+
if (r11 > 0) {
|
|
496
|
+
/* Column-count-based rank tolerance: the effective LS problem has
|
|
497
|
+
* `len` columns, so the rounding floor for rank determination
|
|
498
|
+
* scales with `len`, not (dim + mem). Decoupling from `dim` avoids
|
|
499
|
+
* falsely dropping columns that are healthy relative to the
|
|
500
|
+
* regularizer at large state dimensions. */
|
|
501
|
+
aa_float tol = r11 * (aa_float)len * AA_EPS;
|
|
502
|
+
for (rank = 0; rank < len; ++rank) {
|
|
503
|
+
if (fabs(a->A_aug[rank * aug_rows + rank]) < tol) break;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
if (rank == 0) info = 1;
|
|
507
|
+
}
|
|
508
|
+
brank = (blas_int)rank;
|
|
509
|
+
|
|
510
|
+
/* 3. c_aug = [g; 0]; overwrite with Q' c_aug. We only need the first
|
|
511
|
+
* `rank` entries (= Q_rank' c̃); pass `rank` to ormqr so it applies
|
|
512
|
+
* only the reflectors we care about. */
|
|
513
|
+
if (info == 0) {
|
|
514
|
+
memcpy(a->c_aug, a->g, a->dim * sizeof(aa_float));
|
|
515
|
+
memset(&a->c_aug[a->dim], 0, a->mem * sizeof(aa_float));
|
|
516
|
+
BLAS(ormqr)
|
|
517
|
+
("Left", "Trans", &aug_rows, &one, &brank, a->A_aug, &aug_rows, a->tau,
|
|
518
|
+
a->c_aug, &aug_rows, a->qr_work, &a->qr_lwork, &info);
|
|
519
|
+
}
|
|
305
520
|
|
|
306
|
-
/*
|
|
307
|
-
|
|
308
|
-
|
|
521
|
+
/* 4. Solve the reduced rank×rank system for γ_red (pivoted order),
|
|
522
|
+
* then un-permute into the natural-order γ consumed by `f -= D γ`. */
|
|
523
|
+
if (info == 0) {
|
|
524
|
+
/* Preserve the RHS for iterative refinement below. */
|
|
525
|
+
memcpy(a->c_top_save, a->c_aug, rank * sizeof(aa_float));
|
|
526
|
+
|
|
527
|
+
if (a->type1) {
|
|
528
|
+
/* Type-I: build B_aug with the pivoted Y columns (first `rank` only),
|
|
529
|
+
* apply Q', extract top-left rank×rank block into W, solve
|
|
530
|
+
* W γ_red = c_top. The √rI block is reshuffled too — column i of
|
|
531
|
+
* the permuted B̃ has √r at row (jpvt[i]-1). */
|
|
532
|
+
for (i = 0; i < rank; ++i) {
|
|
533
|
+
aa_int piv = a->jpvt[i] - 1; /* LAPACK jpvt is 1-indexed */
|
|
534
|
+
aa_float *col = &a->B_aug[i * aug_rows];
|
|
535
|
+
memcpy(col, &a->Y[piv * a->dim], a->dim * sizeof(aa_float));
|
|
536
|
+
memset(&col[a->dim], 0, a->mem * sizeof(aa_float));
|
|
537
|
+
col[a->dim + piv] = sqrt_r;
|
|
538
|
+
}
|
|
539
|
+
BLAS(ormqr)
|
|
540
|
+
("Left", "Trans", &aug_rows, &brank, &brank, a->A_aug, &aug_rows,
|
|
541
|
+
a->tau, a->B_aug, &aug_rows, a->qr_work, &a->qr_lwork, &info);
|
|
542
|
+
if (info == 0) {
|
|
543
|
+
/* W (mem×mem, LDA=mem) holds the rank×rank top-left block. */
|
|
544
|
+
for (i = 0; i < rank; ++i) {
|
|
545
|
+
memcpy(&a->W[i * a->mem], &a->B_aug[i * aug_rows],
|
|
546
|
+
rank * sizeof(aa_float));
|
|
547
|
+
memcpy(&a->W_orig[i * a->mem], &a->W[i * a->mem],
|
|
548
|
+
rank * sizeof(aa_float));
|
|
549
|
+
}
|
|
550
|
+
memcpy(a->gamma_red, a->c_top_save, rank * sizeof(aa_float));
|
|
551
|
+
BLAS(gesv)
|
|
552
|
+
(&brank, &one, a->W, &bmem, a->ipiv, a->gamma_red, &brank, &info);
|
|
553
|
+
if (info == 0) {
|
|
554
|
+
/* Iterative refinement: repeat while δ is still contracting,
|
|
555
|
+
* capped at ir_max_steps. Each step: ρ = c_top - W_orig γ_red,
|
|
556
|
+
* solve W δ = ρ with the LU already in W, γ_red += δ. Stop when
|
|
557
|
+
* ‖δ_k‖ ≥ 0.5·‖δ_{k-1}‖ (we've hit the working-precision floor
|
|
558
|
+
* and further steps won't help). */
|
|
559
|
+
aa_float prev_dnorm = 0.0;
|
|
560
|
+
aa_int k;
|
|
561
|
+
for (k = 0; k < a->ir_max_steps; ++k) {
|
|
562
|
+
aa_float dnorm;
|
|
563
|
+
memcpy(a->ir_res, a->c_top_save, rank * sizeof(aa_float));
|
|
564
|
+
BLAS(gemv)
|
|
565
|
+
("NoTrans", &brank, &brank, &neg_onef, a->W_orig, &bmem,
|
|
566
|
+
a->gamma_red, &one, &onef, a->ir_res, &one);
|
|
567
|
+
BLAS(getrs)
|
|
568
|
+
("NoTrans", &brank, &one, a->W, &bmem, a->ipiv, a->ir_res,
|
|
569
|
+
&brank, &info);
|
|
570
|
+
if (info != 0) break;
|
|
571
|
+
dnorm = BLAS(nrm2)(&brank, a->ir_res, &one);
|
|
572
|
+
BLAS(axpy)(&brank, &onef, a->ir_res, &one, a->gamma_red, &one);
|
|
573
|
+
if (k > 0 && dnorm >= 0.5 * prev_dnorm) break;
|
|
574
|
+
prev_dnorm = dnorm;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
} else {
|
|
579
|
+
/* Type-II: B̃ = Ã, so Q' B̃ = R. Solve R u = c_top for the permuted
|
|
580
|
+
* solution u; the rank×rank leading block of R lives in the upper
|
|
581
|
+
* triangle of A_aug. Rank truncation above already guaranteed the
|
|
582
|
+
* diagonal is nonzero through index rank-1. */
|
|
583
|
+
memcpy(a->gamma_red, a->c_top_save, rank * sizeof(aa_float));
|
|
584
|
+
BLAS(trsv)("Upper", "NoTrans", "NonUnit", &brank, a->A_aug,
|
|
585
|
+
&aug_rows, a->gamma_red, &one);
|
|
586
|
+
/* Iterative refinement, capped at ir_max_steps with early stop when
|
|
587
|
+
* δ stops contracting. ρ = c_top - R u via trmv + subtract; solve
|
|
588
|
+
* R δ = ρ via trsv; u += δ. */
|
|
589
|
+
{
|
|
590
|
+
aa_float prev_dnorm = 0.0;
|
|
591
|
+
aa_int k;
|
|
592
|
+
for (k = 0; k < a->ir_max_steps; ++k) {
|
|
593
|
+
aa_float dnorm;
|
|
594
|
+
memcpy(a->ir_res, a->gamma_red, rank * sizeof(aa_float));
|
|
595
|
+
BLAS(trmv)("Upper", "NoTrans", "NonUnit", &brank, a->A_aug,
|
|
596
|
+
&aug_rows, a->ir_res, &one);
|
|
597
|
+
for (i = 0; i < rank; ++i) {
|
|
598
|
+
a->ir_res[i] = a->c_top_save[i] - a->ir_res[i];
|
|
599
|
+
}
|
|
600
|
+
BLAS(trsv)("Upper", "NoTrans", "NonUnit", &brank, a->A_aug,
|
|
601
|
+
&aug_rows, a->ir_res, &one);
|
|
602
|
+
dnorm = BLAS(nrm2)(&brank, a->ir_res, &one);
|
|
603
|
+
BLAS(axpy)(&brank, &onef, a->ir_res, &one, a->gamma_red, &one);
|
|
604
|
+
if (k > 0 && dnorm >= 0.5 * prev_dnorm) break;
|
|
605
|
+
prev_dnorm = dnorm;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/* Un-permute γ_red into γ (natural column order); zero the rest so
|
|
611
|
+
* the `f -= D γ` gemv below sees a well-defined full-length vector. */
|
|
612
|
+
if (info == 0) {
|
|
613
|
+
memset(gamma, 0, len * sizeof(aa_float));
|
|
614
|
+
for (i = 0; i < rank; ++i) {
|
|
615
|
+
gamma[a->jpvt[i] - 1] = a->gamma_red[i];
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/* 5. Validate γ via ‖γ‖₂ against max_weight_norm. */
|
|
621
|
+
aa_norm = (info == 0) ? BLAS(nrm2)(&blen, gamma, &one) : -1.0;
|
|
622
|
+
|
|
623
|
+
/* Record diagnostics for this solve, regardless of accept/reject.
|
|
624
|
+
* NaN last_aa_norm signals "no valid norm this solve" — distinguishing
|
|
625
|
+
* the genuine-zero case (rank collapse gives aa_norm = 0 legitimately)
|
|
626
|
+
* from a failed/rejected solve. */
|
|
627
|
+
a->last_rank = rank;
|
|
628
|
+
a->last_regularization = r;
|
|
629
|
+
a->last_aa_norm = (info == 0 && isfinite(aa_norm)) ? aa_norm : NAN;
|
|
309
630
|
|
|
310
|
-
/* work = M \ work, where update_accel_params has set M = S'Y or M = Y'Y */
|
|
311
|
-
BLAS(gesv)(&blen, &one, a->M, &blen, a->ipiv, a->work, &blen, &info);
|
|
312
|
-
aa_norm = BLAS(nrm2)(&blen, a->work, &one);
|
|
313
631
|
if (a->verbosity > 1) {
|
|
314
|
-
|
|
315
|
-
|
|
632
|
+
scs_printf("AA type %i, iter: %i, len %i, rank %i, info: %i, aa_norm %.2e\n",
|
|
633
|
+
a->type1 ? 1 : 2, (int)a->iter, (int)len, (int)rank, (int)info,
|
|
634
|
+
aa_norm);
|
|
316
635
|
}
|
|
317
636
|
|
|
318
|
-
|
|
319
|
-
if (info != 0 || aa_norm >= a->max_weight_norm) {
|
|
637
|
+
if (info != 0 || !isfinite(aa_norm) || aa_norm >= a->max_weight_norm) {
|
|
320
638
|
if (a->verbosity > 0) {
|
|
321
|
-
|
|
322
|
-
|
|
639
|
+
scs_printf("Error in AA type %i, iter: %i, len %i, rank %i, info: %i, "
|
|
640
|
+
"aa_norm %.2e\n",
|
|
641
|
+
a->type1 ? 1 : 2, (int)a->iter, (int)len, (int)rank, (int)info,
|
|
642
|
+
aa_norm);
|
|
643
|
+
}
|
|
644
|
+
/* Attribute the rejection to exactly one cause, in priority order.
|
|
645
|
+
* lapack_info is the original geqp3 return; the rank-0 path above may
|
|
646
|
+
* have set info=1 but that is the bookkeeping trick, not a LAPACK
|
|
647
|
+
* failure. Without this ordering, rank-0 would be miscounted as
|
|
648
|
+
* "lapack" via info. */
|
|
649
|
+
if (lapack_info != 0) {
|
|
650
|
+
a->n_reject_lapack++;
|
|
651
|
+
} else if (rank == 0) {
|
|
652
|
+
a->n_reject_rank0++;
|
|
653
|
+
} else if (!isfinite(aa_norm)) {
|
|
654
|
+
a->n_reject_nonfinite++;
|
|
655
|
+
} else {
|
|
656
|
+
a->n_reject_weight_cap++;
|
|
323
657
|
}
|
|
324
658
|
a->success = 0;
|
|
325
|
-
/* reset aa for stability */
|
|
326
659
|
aa_reset(a);
|
|
327
660
|
TIME_TOC
|
|
328
|
-
|
|
661
|
+
if (!isfinite(aa_norm)) aa_norm = -1.0;
|
|
662
|
+
return (aa_norm < 0) ? aa_norm : -aa_norm;
|
|
329
663
|
}
|
|
330
664
|
|
|
331
|
-
/*
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
BLAS(gemv)("NoTrans", &bdim, &blen, &neg_onef, a->D, &bdim, a->work, &one,
|
|
336
|
-
&onef, f, &one);
|
|
665
|
+
/* f -= D γ */
|
|
666
|
+
BLAS(gemv)
|
|
667
|
+
("NoTrans", &bdim, &blen, &neg_onef, a->D, &bdim, gamma, &one, &onef, f,
|
|
668
|
+
&one);
|
|
337
669
|
|
|
338
|
-
/* if relaxation is not 1 then need to incorporate */
|
|
339
670
|
if (a->relaxation != 1.0) {
|
|
340
671
|
relax(f, a, len);
|
|
341
672
|
}
|
|
342
673
|
|
|
343
|
-
a->success = 1;
|
|
674
|
+
a->success = 1;
|
|
344
675
|
TIME_TOC
|
|
345
676
|
return aa_norm;
|
|
346
677
|
}
|
|
@@ -348,51 +679,167 @@ static aa_float solve(aa_float *f, AaWork *a, aa_int len) {
|
|
|
348
679
|
/*
|
|
349
680
|
* API functions below this line, see aa.h for descriptions.
|
|
350
681
|
*/
|
|
351
|
-
AaWork *aa_init(aa_int dim, aa_int mem, aa_int
|
|
352
|
-
aa_float
|
|
353
|
-
aa_float
|
|
682
|
+
AaWork *aa_init(aa_int dim, aa_int mem, aa_int min_len, aa_int type1,
|
|
683
|
+
aa_float regularization, aa_float relaxation,
|
|
684
|
+
aa_float safeguard_factor, aa_float max_weight_norm,
|
|
685
|
+
aa_int ir_max_steps, aa_int verbosity) {
|
|
354
686
|
TIME_TIC
|
|
355
|
-
AaWork *a
|
|
687
|
+
AaWork *a;
|
|
688
|
+
aa_int mem_clamped = MIN(mem, dim);
|
|
689
|
+
/* `regularization` is accepted with either sign: positive = scaled by
|
|
690
|
+
* ||A||_F ||Y||_F; negative = pinned absolute |regularization|; zero = off.
|
|
691
|
+
* Only NaN / non-finite values are rejected (via the !isfinite check).
|
|
692
|
+
* min_len < 1 is rejected when mem > 0; min_len > mem_clamped is
|
|
693
|
+
* silently clamped down — same treatment the `mem` argument already
|
|
694
|
+
* gets against `dim`, so callers can pass `min_len = mem` without
|
|
695
|
+
* caring whether mem exceeded dim. When mem == 0 (AA off), min_len
|
|
696
|
+
* is ignored entirely. */
|
|
697
|
+
if (dim <= 0 || mem < 0 || !isfinite(regularization) ||
|
|
698
|
+
!isfinite(relaxation) || relaxation < 0 || relaxation > 2 ||
|
|
699
|
+
!isfinite(safeguard_factor) || safeguard_factor < 0 ||
|
|
700
|
+
!isfinite(max_weight_norm) || max_weight_norm <= 0 ||
|
|
701
|
+
ir_max_steps < 0 ||
|
|
702
|
+
(mem_clamped > 0 && min_len < 1)) {
|
|
703
|
+
scs_printf("Invalid AA parameters.\n");
|
|
704
|
+
return SCS_NULL;
|
|
705
|
+
}
|
|
706
|
+
a = (AaWork *)scs_calloc(1, sizeof(AaWork));
|
|
356
707
|
if (!a) {
|
|
357
|
-
|
|
358
|
-
return
|
|
708
|
+
scs_printf("Failed to allocate memory for AA.\n");
|
|
709
|
+
return SCS_NULL;
|
|
359
710
|
}
|
|
360
711
|
a->type1 = type1;
|
|
361
712
|
a->iter = 0;
|
|
362
713
|
a->dim = dim;
|
|
363
|
-
a->mem =
|
|
714
|
+
a->mem = mem_clamped; /* clamped to dim for rank stability */
|
|
715
|
+
if (mem > dim && verbosity > 0) {
|
|
716
|
+
scs_printf("AA: mem (%d) > dim (%d); clamping mem to dim.\n",
|
|
717
|
+
(int)mem, (int)dim);
|
|
718
|
+
}
|
|
719
|
+
a->min_len = mem_clamped > 0 ? MIN(min_len, mem_clamped) : 0;
|
|
364
720
|
a->regularization = regularization;
|
|
365
721
|
a->relaxation = relaxation;
|
|
366
722
|
a->safeguard_factor = safeguard_factor;
|
|
367
723
|
a->max_weight_norm = max_weight_norm;
|
|
724
|
+
a->ir_max_steps = ir_max_steps;
|
|
368
725
|
a->success = 0;
|
|
369
726
|
a->verbosity = verbosity;
|
|
727
|
+
/* Counters are already zero from calloc; only last_aa_norm needs an
|
|
728
|
+
* explicit sentinel so callers can distinguish "never solved" from a
|
|
729
|
+
* legitimate zero norm (which never happens on a successful solve, but
|
|
730
|
+
* 0 is a bad signal either way). */
|
|
731
|
+
a->last_aa_norm = NAN;
|
|
370
732
|
if (a->mem <= 0) {
|
|
371
733
|
return a;
|
|
372
734
|
}
|
|
373
735
|
|
|
374
|
-
a->x = (aa_float *)
|
|
375
|
-
a->f = (aa_float *)
|
|
376
|
-
a->g = (aa_float *)
|
|
377
|
-
|
|
378
|
-
a->g_prev = (aa_float *)
|
|
379
|
-
|
|
380
|
-
a->
|
|
381
|
-
a->
|
|
382
|
-
a->
|
|
736
|
+
a->x = (aa_float *)scs_calloc(a->dim, sizeof(aa_float));
|
|
737
|
+
a->f = (aa_float *)scs_calloc(a->dim, sizeof(aa_float));
|
|
738
|
+
a->g = (aa_float *)scs_calloc(a->dim, sizeof(aa_float));
|
|
739
|
+
|
|
740
|
+
a->g_prev = (aa_float *)scs_calloc(a->dim, sizeof(aa_float));
|
|
741
|
+
|
|
742
|
+
a->Y = (aa_float *)scs_calloc(a->dim * a->mem, sizeof(aa_float));
|
|
743
|
+
a->S = (aa_float *)scs_calloc(a->dim * a->mem, sizeof(aa_float));
|
|
744
|
+
a->D = (aa_float *)scs_calloc(a->dim * a->mem, sizeof(aa_float));
|
|
745
|
+
|
|
746
|
+
{
|
|
747
|
+
aa_int aug_rows = a->dim + a->mem;
|
|
748
|
+
a->A_aug = (aa_float *)scs_calloc((size_t)aug_rows * a->mem, sizeof(aa_float));
|
|
749
|
+
a->c_aug = (aa_float *)scs_calloc((size_t)aug_rows, sizeof(aa_float));
|
|
750
|
+
a->tau = (aa_float *)scs_calloc(a->mem, sizeof(aa_float));
|
|
751
|
+
a->jpvt = (blas_int *)scs_calloc(a->mem, sizeof(blas_int));
|
|
752
|
+
|
|
753
|
+
/* Scratches for iterative refinement (both types). */
|
|
754
|
+
a->gamma_red = (aa_float *)scs_calloc(a->mem, sizeof(aa_float));
|
|
755
|
+
a->c_top_save = (aa_float *)scs_calloc(a->mem, sizeof(aa_float));
|
|
756
|
+
a->ir_res = (aa_float *)scs_calloc(a->mem, sizeof(aa_float));
|
|
757
|
+
|
|
758
|
+
/* Per-column cached norms used by compute_regularization. */
|
|
759
|
+
a->nrm_s_col = (aa_float *)scs_calloc(a->mem, sizeof(aa_float));
|
|
760
|
+
a->nrm_y_col = (aa_float *)scs_calloc(a->mem, sizeof(aa_float));
|
|
761
|
+
|
|
762
|
+
/* type-I needs a second augmented buffer and mem×mem gesv scratches;
|
|
763
|
+
* W_orig preserves W across gesv so iterative refinement can form
|
|
764
|
+
* the residual c_top − W γ. */
|
|
765
|
+
if (type1) {
|
|
766
|
+
a->B_aug = (aa_float *)scs_calloc((size_t)aug_rows * a->mem, sizeof(aa_float));
|
|
767
|
+
a->W = (aa_float *)scs_calloc((size_t)a->mem * a->mem, sizeof(aa_float));
|
|
768
|
+
a->W_orig = (aa_float *)scs_calloc((size_t)a->mem * a->mem, sizeof(aa_float));
|
|
769
|
+
a->ipiv = (blas_int *)scs_calloc(a->mem, sizeof(blas_int));
|
|
770
|
+
} else {
|
|
771
|
+
a->B_aug = SCS_NULL;
|
|
772
|
+
a->W = SCS_NULL;
|
|
773
|
+
a->W_orig = SCS_NULL;
|
|
774
|
+
a->ipiv = SCS_NULL;
|
|
775
|
+
}
|
|
383
776
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
777
|
+
a->work = (aa_float *)scs_calloc(MAX(a->mem, a->dim), sizeof(aa_float));
|
|
778
|
+
if (relaxation != 1.0) {
|
|
779
|
+
a->x_work = (aa_float *)scs_calloc(a->dim, sizeof(aa_float));
|
|
780
|
+
} else {
|
|
781
|
+
a->x_work = SCS_NULL;
|
|
782
|
+
}
|
|
387
783
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
784
|
+
/* Check every allocation before the LAPACK workspace query below. The
|
|
785
|
+
* query passes A_aug/jpvt/tau/c_aug/B_aug into geqp3/ormqr; if any of
|
|
786
|
+
* them is NULL we'd dereference inside LAPACK instead of returning
|
|
787
|
+
* cleanly. qr_work is still NULL here — it's allocated after the query. */
|
|
788
|
+
if (!a->x || !a->f || !a->g || !a->g_prev ||
|
|
789
|
+
!a->Y || !a->S || !a->D ||
|
|
790
|
+
!a->A_aug || !a->c_aug || !a->tau || !a->jpvt ||
|
|
791
|
+
!a->gamma_red || !a->c_top_save || !a->ir_res ||
|
|
792
|
+
!a->nrm_s_col || !a->nrm_y_col ||
|
|
793
|
+
(type1 && (!a->B_aug || !a->W || !a->W_orig || !a->ipiv)) ||
|
|
794
|
+
!a->work ||
|
|
795
|
+
(relaxation != 1.0 && !a->x_work)) {
|
|
796
|
+
scs_printf("Failed to allocate memory for AA.\n");
|
|
797
|
+
aa_finish(a);
|
|
798
|
+
return SCS_NULL;
|
|
799
|
+
}
|
|
391
800
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
801
|
+
/* LAPACK workspace query: ask geqp3 and ormqr for their preferred lwork,
|
|
802
|
+
* then take the max. lwork = -1 makes the routine write the optimal
|
|
803
|
+
* size into work[0] without doing any factoring. geqp3 typically wants
|
|
804
|
+
* more scratch than geqrf because it also maintains column norms. The
|
|
805
|
+
* optimal size is returned in an aa_float slot; round up with ceil
|
|
806
|
+
* before casting so a value like 255.9999 doesn't truncate to 255 and
|
|
807
|
+
* under-allocate. */
|
|
808
|
+
{
|
|
809
|
+
blas_int b_aug = (blas_int)aug_rows;
|
|
810
|
+
blas_int b_mem = (blas_int)a->mem;
|
|
811
|
+
blas_int b_neg_one = -1;
|
|
812
|
+
blas_int info_q = 0;
|
|
813
|
+
aa_float q_geqp3 = 0.0, q_ormqr_c = 0.0, q_ormqr_b = 0.0;
|
|
814
|
+
BLAS(geqp3)(&b_aug, &b_mem, a->A_aug, &b_aug, a->jpvt, a->tau,
|
|
815
|
+
&q_geqp3, &b_neg_one, &info_q);
|
|
816
|
+
{
|
|
817
|
+
blas_int b_one = 1;
|
|
818
|
+
BLAS(ormqr)
|
|
819
|
+
("Left", "Trans", &b_aug, &b_one, &b_mem, a->A_aug, &b_aug, a->tau,
|
|
820
|
+
a->c_aug, &b_aug, &q_ormqr_c, &b_neg_one, &info_q);
|
|
821
|
+
}
|
|
822
|
+
if (type1) {
|
|
823
|
+
BLAS(ormqr)
|
|
824
|
+
("Left", "Trans", &b_aug, &b_mem, &b_mem, a->A_aug, &b_aug, a->tau,
|
|
825
|
+
a->B_aug, &b_aug, &q_ormqr_b, &b_neg_one, &info_q);
|
|
826
|
+
}
|
|
827
|
+
{
|
|
828
|
+
aa_float lwork_f = q_geqp3;
|
|
829
|
+
if (q_ormqr_c > lwork_f) lwork_f = q_ormqr_c;
|
|
830
|
+
if (q_ormqr_b > lwork_f) lwork_f = q_ormqr_b;
|
|
831
|
+
/* Floor at mem — some LAPACK builds return modest sizes; keep
|
|
832
|
+
* a sane minimum. calloc of zero is implementation-defined. */
|
|
833
|
+
if (lwork_f < (aa_float)a->mem) lwork_f = (aa_float)a->mem;
|
|
834
|
+
a->qr_lwork = (blas_int)ceil(lwork_f);
|
|
835
|
+
a->qr_work = (aa_float *)scs_calloc((size_t)a->qr_lwork, sizeof(aa_float));
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
if (!a->qr_work) {
|
|
839
|
+
scs_printf("Failed to allocate memory for AA.\n");
|
|
840
|
+
aa_finish(a);
|
|
841
|
+
return SCS_NULL;
|
|
842
|
+
}
|
|
396
843
|
}
|
|
397
844
|
TIME_TOC
|
|
398
845
|
return a;
|
|
@@ -417,12 +864,15 @@ aa_float aa_apply(aa_float *f, const aa_float *x, AaWork *a) {
|
|
|
417
864
|
/* set various accel quantities */
|
|
418
865
|
update_accel_params(x, f, a, len);
|
|
419
866
|
|
|
420
|
-
/*
|
|
421
|
-
if (
|
|
422
|
-
/*
|
|
423
|
-
|
|
424
|
-
|
|
867
|
+
/* Hold off the solve until we have min_len residual pairs buffered. */
|
|
868
|
+
if (a->iter >= a->min_len) {
|
|
869
|
+
/* solve linear system, new point overwrites f if successful.
|
|
870
|
+
* Rejection causes are counted inside solve() where the specific
|
|
871
|
+
* failure mode is known; here we only count acceptances. */
|
|
425
872
|
aa_norm = solve(f, a, len);
|
|
873
|
+
if (aa_norm > 0) {
|
|
874
|
+
a->n_accept++;
|
|
875
|
+
}
|
|
426
876
|
}
|
|
427
877
|
a->iter++;
|
|
428
878
|
TIME_TOC
|
|
@@ -435,6 +885,11 @@ aa_int aa_safeguard(aa_float *f_new, aa_float *x_new, AaWork *a) {
|
|
|
435
885
|
blas_int one = 1;
|
|
436
886
|
aa_float neg_onef = -1.0;
|
|
437
887
|
aa_float norm_diff;
|
|
888
|
+
if (a->mem <= 0) {
|
|
889
|
+
/* degenerate workspace, nothing to safeguard against */
|
|
890
|
+
TIME_TOC
|
|
891
|
+
return 0;
|
|
892
|
+
}
|
|
438
893
|
if (!a->success) {
|
|
439
894
|
/* last AA update was not successful, no need for safeguarding */
|
|
440
895
|
TIME_TOC
|
|
@@ -444,6 +899,9 @@ aa_int aa_safeguard(aa_float *f_new, aa_float *x_new, AaWork *a) {
|
|
|
444
899
|
/* reset success indicator in case safeguarding called multiple times */
|
|
445
900
|
a->success = 0;
|
|
446
901
|
|
|
902
|
+
/* NB: a->work is used here as a dim-sized scratch, but elsewhere (in solve)
|
|
903
|
+
* only as a len-sized (<=mem) scratch. This is why it is allocated with
|
|
904
|
+
* MAX(mem, dim) in aa_init — do not shrink it to mem. */
|
|
447
905
|
/* work = x_new */
|
|
448
906
|
memcpy(a->work, x_new, a->dim * sizeof(aa_float));
|
|
449
907
|
/* work = x_new - f_new */
|
|
@@ -456,9 +914,10 @@ aa_int aa_safeguard(aa_float *f_new, aa_float *x_new, AaWork *a) {
|
|
|
456
914
|
memcpy(f_new, a->f, a->dim * sizeof(aa_float));
|
|
457
915
|
memcpy(x_new, a->x, a->dim * sizeof(aa_float));
|
|
458
916
|
if (a->verbosity > 0) {
|
|
459
|
-
|
|
460
|
-
|
|
917
|
+
scs_printf("AA rejection, iter: %i, norm_diff %.4e, prev_norm_diff %.4e\n",
|
|
918
|
+
(int)a->iter, norm_diff, a->norm_g);
|
|
461
919
|
}
|
|
920
|
+
a->n_safeguard_reject++;
|
|
462
921
|
aa_reset(a);
|
|
463
922
|
TIME_TOC
|
|
464
923
|
return -1;
|
|
@@ -469,32 +928,80 @@ aa_int aa_safeguard(aa_float *f_new, aa_float *x_new, AaWork *a) {
|
|
|
469
928
|
|
|
470
929
|
void aa_finish(AaWork *a) {
|
|
471
930
|
if (a) {
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
931
|
+
scs_free(a->x);
|
|
932
|
+
scs_free(a->f);
|
|
933
|
+
scs_free(a->g);
|
|
934
|
+
scs_free(a->g_prev);
|
|
935
|
+
scs_free(a->Y);
|
|
936
|
+
scs_free(a->S);
|
|
937
|
+
scs_free(a->D);
|
|
938
|
+
scs_free(a->A_aug);
|
|
939
|
+
scs_free(a->B_aug);
|
|
940
|
+
scs_free(a->c_aug);
|
|
941
|
+
scs_free(a->tau);
|
|
942
|
+
scs_free(a->qr_work);
|
|
943
|
+
scs_free(a->jpvt);
|
|
944
|
+
scs_free(a->W);
|
|
945
|
+
scs_free(a->W_orig);
|
|
946
|
+
scs_free(a->ipiv);
|
|
947
|
+
scs_free(a->gamma_red);
|
|
948
|
+
scs_free(a->c_top_save);
|
|
949
|
+
scs_free(a->ir_res);
|
|
950
|
+
scs_free(a->nrm_s_col);
|
|
951
|
+
scs_free(a->nrm_y_col);
|
|
952
|
+
scs_free(a->work);
|
|
485
953
|
if (a->x_work) {
|
|
486
|
-
|
|
954
|
+
scs_free(a->x_work);
|
|
487
955
|
}
|
|
488
|
-
|
|
956
|
+
scs_free(a);
|
|
489
957
|
}
|
|
490
958
|
}
|
|
491
959
|
|
|
492
960
|
void aa_reset(AaWork *a) {
|
|
493
|
-
/*
|
|
961
|
+
/* Restore the logical state of a freshly calloc'd workspace.
|
|
962
|
+
*
|
|
963
|
+
* Most internal buffers are fully overwritten before they are read:
|
|
964
|
+
* - x, f, g_prev are re-seeded by init_accel_params on the next
|
|
965
|
+
* aa_apply call (which runs when iter == 0).
|
|
966
|
+
* - g, S/Y/D columns, A_aug/B_aug/c_aug, tau, jpvt, qr_work, W/W_orig,
|
|
967
|
+
* gamma_red, c_top_save, ir_res, work, ipiv, x_work are all
|
|
968
|
+
* rewritten inside update_accel_params / solve / relax each
|
|
969
|
+
* iteration before any read.
|
|
970
|
+
*
|
|
971
|
+
* The only buffers that require zeroing are nrm_{s,y}_col: they are
|
|
972
|
+
* reduced over all `mem` slots in compute_regularization, and stale
|
|
973
|
+
* entries from an earlier run would contaminate the Frobenius-norm
|
|
974
|
+
* scale until every slot has been rewritten. */
|
|
975
|
+
if (!a) {
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
494
978
|
if (a->verbosity > 0) {
|
|
495
|
-
|
|
979
|
+
scs_printf("AA reset.\n");
|
|
496
980
|
}
|
|
497
981
|
a->iter = 0;
|
|
982
|
+
a->success = 0;
|
|
983
|
+
a->norm_g = 0;
|
|
984
|
+
if (a->nrm_s_col) {
|
|
985
|
+
memset(a->nrm_s_col, 0, sizeof(aa_float) * a->mem);
|
|
986
|
+
}
|
|
987
|
+
if (a->nrm_y_col) {
|
|
988
|
+
memset(a->nrm_y_col, 0, sizeof(aa_float) * a->mem);
|
|
989
|
+
}
|
|
498
990
|
}
|
|
499
991
|
|
|
500
|
-
|
|
992
|
+
AaStats aa_get_stats(const AaWork *a) {
|
|
993
|
+
AaStats s;
|
|
994
|
+
s.iter = a->iter;
|
|
995
|
+
s.n_accept = a->n_accept;
|
|
996
|
+
s.n_reject_lapack = a->n_reject_lapack;
|
|
997
|
+
s.n_reject_rank0 = a->n_reject_rank0;
|
|
998
|
+
s.n_reject_nonfinite = a->n_reject_nonfinite;
|
|
999
|
+
s.n_reject_weight_cap = a->n_reject_weight_cap;
|
|
1000
|
+
s.n_safeguard_reject = a->n_safeguard_reject;
|
|
1001
|
+
s.last_rank = a->last_rank;
|
|
1002
|
+
s.last_aa_norm = a->last_aa_norm;
|
|
1003
|
+
s.last_regularization = a->last_regularization;
|
|
1004
|
+
return s;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
#endif /* USE_LAPACK */
|