Dist m4ri 0.16.1
Computing distance of a classical or quantum CSS code
Loading...
Searching...
No Matches
dist_m4ri.c
Go to the documentation of this file.
1
66
67#define _GNU_SOURCE
68#include <inttypes.h>
69#include <strings.h>
70#include <stdlib.h>
71#include <stdio.h>
72#include <stdbool.h>
73#include <stdatomic.h>
74#include <time.h>
75#include <unistd.h>
76#include <pthread.h>
77#include <sched.h>
78#include <math.h>
79#include <limits.h>
80#include <m4ri/m4ri.h>
81
82#include "mmio.h"
83#include "uthash.h"
84#include "util_hash.h"
85#include "util_m4ri.h"
86#include "util_io.h"
87#include "dist_m4ri.h"
88#include "dist_cc.h"
89
90/* Mutex protecting M4RI's internal non-thread-safe MMC memory cache */
91static pthread_mutex_t m4ri_mem_mutex = PTHREAD_MUTEX_INITIALIZER;
92
93static inline mzd_t *safe_mzd_from_csr(mzd_t *dst, const csr_t *p) {
94 pthread_mutex_lock(&m4ri_mem_mutex);
95 mzd_t *res = mzd_from_csr(dst, p);
96 pthread_mutex_unlock(&m4ri_mem_mutex);
97 return res;
98}
99
100static inline mzd_t *safe_mzd_init(rci_t r, rci_t c) {
101 pthread_mutex_lock(&m4ri_mem_mutex);
102 mzd_t *res = mzd_init(r, c);
103 pthread_mutex_unlock(&m4ri_mem_mutex);
104 return res;
105}
106
107static inline void safe_mzd_free(mzd_t *M) {
108 if (!M) return;
109 pthread_mutex_lock(&m4ri_mem_mutex);
110 mzd_free(M);
111 pthread_mutex_unlock(&m4ri_mem_mutex);
112}
113
114static inline mzp_t *safe_mzp_init(rci_t length) {
115 pthread_mutex_lock(&m4ri_mem_mutex);
116 mzp_t *res = mzp_init(length);
117 pthread_mutex_unlock(&m4ri_mem_mutex);
118 return res;
119}
120
121static inline void safe_mzp_free(mzp_t *P) {
122 if (!P) return;
123 pthread_mutex_lock(&m4ri_mem_mutex);
124 mzp_free(P);
125 pthread_mutex_unlock(&m4ri_mem_mutex);
126}
127
128static inline double get_time_sec(void) {
129 struct timespec ts;
130 clock_gettime(CLOCK_MONOTONIC, &ts);
131 return (double)ts.tv_sec + (double)ts.tv_nsec * 1e-9;
132}
133
134/* The splitmix64 output function: a bijective 64-bit hash */
135static inline uint64_t mix64(uint64_t z) {
136 z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9ULL;
137 z = (z ^ (z >> 27)) * 0x94d049bb133111ebULL;
138 return z ^ (z >> 31);
139}
140
141static inline uint64_t splitmix64(uint64_t *state) {
142 return mix64(*state += 0x9e3779b97f4a7c15ULL);
143}
144
145static inline int rand_uniform_thread(int max, uint64_t *state) {
146 if (max <= 1) return 0;
147 return (int)(splitmix64(state) % (uint64_t)max);
148}
149
150static inline mzp_t * mzp_rand_thread(mzp_t *q, rci_t length, uint64_t *state) {
151 if (q == NULL) return NULL;
152 for (int i = 0; i <= (int)length - 2; i++) {
153 q->values[i] = i + rand_uniform_thread(length - i, state);
154 }
155 for (int i = length - 1; i < (int)q->length; i++) {
156 q->values[i] = i;
157 }
158 return q;
159}
160
161#define CC_SPLIT_MAX_LEVELS 3 /* CC work units: at most 3 fixed levels, B^3 units per start index (cc_round_levels()) */
162
163/* The CC column-syndrome hash, for the last two levels of a cluster (see cc_walk_last2()). The hash of a set S of rows
164 * of H is the XOR of the random 64-bit values zr[r], r in S (Zobrist hashing): the hash of the syndrome s + h_c, where
165 * h_c is the column c of H, is that of s XOR hc[c]. The nonzero columns are sorted by (hash, column), and each
166 * distinct hash value `key` has an entry {key, beg, cnt} in the open-addressing table `tab` (linear probing from the
167 * slot key >> tshift) for its run of columns cols[beg..beg+cnt-1]. The bit (key & bmask) of the filter `bits` is set
168 * for each key, so that most failed lookups only read one bit. */
169typedef struct {
170 uint64_t key;
171 int beg, cnt; /* cnt = 0: empty slot */
173
174typedef struct {
175 uint64_t *zr; /* random value of each row of H */
176 uint64_t *hc; /* hash of each column of H (0 for a zero column) */
177 uint64_t *bits; /* filter: bit (key & bmask) set for each key */
178 uint64_t bmask;
179 cc_hash_slot_t *tab; /* 2^(64-tshift) slots */
181 int tmask;
182 int *cols; /* the nonzero columns sorted by (hash, column) */
183 int ncols; /* their number */
184 int nkeys; /* number of distinct keys */
185} cc_hash_t;
186
187typedef struct {
189 const cc_hash_t *cc_hash; /* CC column-syndrome hash for cc_walk() (NULL with smax > 0: start_CC_recurs_mt()) */
191 int rw_threads; /* workers 0..rw_threads-1 may run RW (dense RW matrices); all workers may run CC */
192 double timeout;
194 int dexp;
195
196 /* Distance bounds & stop flags (cache-line isolated for read-mostly access) */
197 _Alignas(64) atomic_int dmin; /* dmin-1 is max cluster size analyzed without success */
198 atomic_int dmax; /* smallest weight codeword found (0 if none) */
199 atomic_int cc_found_weight; /* smallest weight of a codeword found by CC (0 if none) */
200 atomic_bool cc_exact; /* cc_found_weight is certified exact (found in a round w=dmin) */
201 atomic_bool stop_flag; /* signals all threads to terminate */
202 atomic_bool rw_stop_flag; /* signals RW workers to stop (in method 3) */
203 atomic_int stop_reason; /* why the run ended (STOP_NONE until then), see ctx_stop() */
204 atomic_int cw_feed_limit; /* codeword_feed_limit() (`min_hits` statistic), updated under `cw_mutex` */
205
206 /* RW state (cache-line isolated from CC and read-mostly bounds) */
207 _Alignas(64) long total_rw_steps;
208 atomic_long rw_steps_started;
209 atomic_long rw_steps_completed;
210 atomic_llong rw_busy_ns; /* thread time in ns of the timed RW steps, for the method-3 timing model */
211 atomic_long rw_timed_steps; /* number of RW steps timed in `rw_busy_ns` */
212 atomic_int rw_rank; /* rank of H from the RW elimination (n - nu with ksub > 0), 0 if not known */
213 atomic_long rw_unif_steps; /* completed full-matrix RW steps with uniform random permutations (no window) */
214 atomic_int rw_pairs_l; /* the number l of pivot rows of the RW pairs (0: none), see rw_pairs_step() */
215 atomic_llong rw_pairs_checked; /* pairs of non-pivot columns checked (the weight of their sum computed) */
216 atomic_llong rw_hits1; /* RW codewords recorded with weight up to the upper bound: from single columns */
217 atomic_llong rw_hits2; /* and from the pairs */
218 const uint64_t *rw_lhash; /* hashes of the columns of L for the RW pairs (NULL: none), see rw_lhash_init() */
219
220 /* CC state for current weight (cache-line isolated) */
221 _Alignas(64) atomic_llong cc_next; /* weight, fixed levels, next work unit of the current CC round, see `cc_pack()` */
222 csr_t *mHT_cc;
223 int max_col_W;
224 bool even_weight_only; /* 1_n in rowspan(H) and smax=0: all codewords have even weight */
225 int cc_B; /* CC work units: radix B, the maximum row weight of H */
226 int cc_R[CC_SPLIT_MAX_LEVELS + 1]; /* work units per start index with L fixed levels: B^L (0 if too large) */
227 double cc_branch; /* typical number of branches per level: average row weight of H - 1 */
228 atomic_int cc_active_workers;
229 atomic_int cc_target_workers;
230 atomic_int cc_round_active;
231 atomic_llong cc_busy_ns; /* total CC thread time in ns (all rounds), for the CC timing model */
232 atomic_llong cc_nodes; /* total calls of the CC recursion (smax = 0, all rounds), for DBG_TIMING */
233
234 /* Codeword synchronization */
235 pthread_mutex_t cw_mutex;
236 /* irred=1: the reducible codewords not recorded (the union of a codeword and a disjoint nonzero zero-syndrome
237 * vector), found by CC and by RW (pairs of disjoint circuits, ksub rows) */
238 atomic_llong cw_red_cc;
239 atomic_llong cw_red_rw;
240
241 /* Subspace sketching (ksub) state */
242 mzd_t *N_global;
243 int nu;
244 pthread_rwlock_t basis_rwlock;
245 atomic_long next_refresh_step;
246
247 /* Timing stats: CC work of each completed round in thread-seconds (total CC thread time) */
248 double cc_time_per_weight[MAX_W];
249
250 /* Coordinator only: the current CC round (for the status and stop messages), and the periodic status */
251 int round_open; /* 1 from the start of a CC round until it is completed */
252 int round_w; /* weight of the current (or last) CC round */
253 int round_beg; /* its first start index */
254 int round_end; /* its last start index */
255 int round_R; /* its number of work units per start index */
256 int round_threads; /* its planned number of CC threads */
257 double round_t0; /* its start time */
258 double round_est; /* its estimated CC work in thread-seconds (0: unknown) */
259 const char *cc_idle; /* method 3: why no CC round runs (NULL: none) */
260 int stop_w; /* CC weight for the stop message (STOP_CC_DONE, STOP_CC_SLOW) */
261 double status_next; /* time since the start of the next status line (DBG_STATUS) */
262 double status_dt; /* interval to the status line after that */
263 double status_t; /* time since the start of the last status line */
264 long status_steps; /* RW steps completed at the last status line */
265
266 /* Thread handles */
267 pthread_t *threads;
268 struct worker_arg *wargs; /* the arguments of the num_threads workers */
269 int spawned; /* number of workers started (main thread only), see ctx_spawn() */
270 int spawn_initial; /* number of workers started at first (fewer in method 2, see CC_SPAWN_INITIAL) */
271 bool spawn_failed; /* a worker thread could not be created: no more are started */
273
274/* Reasons why the run ended (the first one recorded, see ctx_stop()), reported with DBG_SUMMARY */
275enum {
277 STOP_BOUNDS, /* the bounds coincide: dmin = dmax */
278 STOP_CC_FOUND, /* CC found a codeword (the exact distance, unless lower weights were not scanned) */
279 STOP_RW_CONVERGED, /* method 1: RW `min_hits` convergence */
280 STOP_RW_STEPS, /* method 1: all RW steps done */
281 STOP_TIMEOUT, /* timeout reached */
282 STOP_CC_DONE, /* CC done up to the largest weight needed (wmax), and RW ended */
283 STOP_CC_SLOW, /* method 3: the next CC round is predicted not to finish before the timeout, and RW ended */
284 STOP_WMIN, /* a codeword of weight <= wmin found */
285 STOP_MAXC, /* maxC codewords collected */
286 STOP_COLLECTED, /* the CC rounds w = d..d+dW enumerated all codewords to collect (outC, maxC) */
287 STOP_DSTOP /* the lower bound reached the stop target: dmin >= dstop */
288};
289
290/* Stop all threads, recording the `reason` unless one is already recorded */
291static inline void ctx_stop(distfork_ctx_t * const ctx, const int reason) {
292 int none = STOP_NONE;
293 atomic_compare_exchange_strong(&ctx->stop_reason, &none, reason);
294 atomic_store(&ctx->stop_flag, true);
295}
296
297/* The target t of the CC rounds: the upper bound dmax, or the stop target dstop if smaller (0: neither). The rounds
298 * w <= t-1 certify dmin = t (with outC, the rounds w = t..t+dW collect codewords). Unlike dmax, dstop is not an upper
299 * bound: once dmin >= dstop, the run ends with the bounds [dmin, dmax] (dmax = 0 if no codeword was found). */
300static inline int ctx_target(const distfork_ctx_t * const ctx, const int cur_dmax) {
301 const int dstop = ctx->p->dstop;
302 return (dstop > 0 && (cur_dmax == 0 || dstop < cur_dmax)) ? dstop : cur_dmax;
303}
304
305typedef struct worker_arg {
307 int tid;
309 cols_ws_t *irr_ws; /* irred=1: the workspace of the test of the CC (and ksub RW) codewords, see cw_irreducible() */
311
312/* A CC round at weight w hands out the work units u = beg*R .. (end+1)*R-1 one at a time, where beg..end are the start
313 * indices of the round (see cc_round_beg(), cc_round_end()) and R = B^L (see cc_round_levels()). The unit u = i*R + k
314 * grows the clusters from the start index i = u / R, with the branches of the first L levels fixed by the digits
315 * k_0, k_1, ... of k = k_0 + k_1*B + ... in radix B, the maximum row weight of H: at level l = 1..L (a cluster of l
316 * columns), start_CC_recurs_mt() only adds the column at position k_{l-1} of the row it extends (none if the row is
317 * shorter: an empty unit). The units of a start index explore together the same clusters as the start index itself
318 * (L = 0), in pieces small enough to balance the load if there are few start indices per thread (the clusters from the
319 * first start column, which only grow to larger columns, are about w/n of the round). The weight, the number of fixed
320 * levels L, and the next unit are packed into the single atomic word `cc_next`, so that a worker always processes a
321 * claimed unit with the parameters of its round, even if it joins just as the coordinator publishes the next round. */
322#define CC_IDX_BITS 32
323#define CC_LEV_SHIFT 48
324static inline long long cc_pack(const int w, const int lev, const int idx) {
325 return ((long long)lev << CC_LEV_SHIFT) | ((long long)w << CC_IDX_BITS) | (long long)(unsigned int)idx;
326}
327static inline int cc_unpack_w(const long long v) { return (int)((v >> CC_IDX_BITS) & 0xffff); }
328static inline int cc_unpack_lev(const long long v) { return (int)((v >> CC_LEV_SHIFT) & 0xf); }
329static inline int cc_unpack_idx(const long long v) { return (int)(v & 0xffffffffLL); }
330
331/* First start index of a CC round: index into the expert `start` list, or the first start column (cbeg) */
332static inline int cc_round_beg(const params_t * const p) {
333 return (p->start_num > 0) ? 0 : ((p->cbeg >= 0) ? p->cbeg : 0);
334}
335
336/* Last start index of a CC round at weight w: a cluster started at column i only uses columns > i, so it
337 * starts at column n-w at the latest (expert `start` list: all listed columns, clusters are unlimited) */
338static inline int cc_round_end(const params_t * const p, const int w) {
339 if (p->start_num > 0) return p->start_num - 1;
340 const int nvar = p->spaH->cols;
341 return (p->cend >= 0) ? minint(p->cend, nvar - w) : (nvar - w);
342}
343
344/* Last work unit of the CC round at weight w with `lev` fixed levels (in range, see cc_round_levels()) */
345static inline int cc_unit_last(const distfork_ctx_t * const ctx, const int w, const int lev) {
346 return (cc_round_end(ctx->p, w) + 1) * ctx->cc_R[lev] - 1;
347}
348
349#define CC_SPLIT_UNITS_PER_THREAD 4.0 /* aim at >= 4 work units of the size of the largest one per CC thread */
350#define CC_SPLIT_MIN_WORK 1e-3 /* rounds predicted to take < 1 ms on all threads (on the start indices of an
351 * expert `start` list, if they are fewer): whole start indices */
352#define CC_SPLIT_CLAIMS_PER_WORK 1e4 /* at most 10^4 work units per thread-second of predicted work */
353#define CC_SPLIT_CLAIMS_UNKNOWN 65536 /* at most this many work units if the work of the round is not known */
354
355/* Number of fixed levels L of the work units of the CC round at weight w over the start indices beg..end, with the
356 * estimated work `est` (thread-seconds, 0 if not known, see cc_work_estimate()), see `cc_pack()`: the smallest L which
357 * gives CC_SPLIT_UNITS_PER_THREAD units per thread, where the largest unit is taken as the unit size: whole start
358 * indices count as n/w units (the clusters from the first start column are about w/n of the round; the columns of the
359 * expert `start` list are alike, with unlimited clusters), and each fixed level divides the units by the typical
360 * number of branches. L = 0 for short rounds (with a short `start` list, only if they are short on its start indices,
361 * which are then all that can work in parallel), and L <= min(3, w-3), so that at least two levels of a unit are free.
362 * The number of units (each one claim of the shared counter, empty units included) is limited by the predicted work,
363 * and all units must have an index < INT_MAX - num_threads (over-claims at the end of the round). */
364static int cc_round_levels(const distfork_ctx_t * const ctx, const int w, const int beg, const int end,
365 const double est) {
366 const int nthr = ctx->num_threads;
367 const int n_start = end - beg + 1;
368 const int lmax = minint(CC_SPLIT_MAX_LEVELS, w - 3);
369 if (nthr <= 1 || n_start <= 0 || lmax <= 0 || ctx->cc_B <= 1) return 0;
370 const int busy = (ctx->p->start_num > 0) ? minint(nthr, n_start) : nthr; /* threads busy with whole start indices */
371 if (est > 0.0 && est < CC_SPLIT_MIN_WORK * busy) return 0;
372 const double max_units = (est > 0.0) ? CC_SPLIT_CLAIMS_PER_WORK * est : CC_SPLIT_CLAIMS_UNKNOWN;
373 double units = (ctx->p->start_num > 0) ? (double)n_start : (double)n_start / w;
374 int lev = 0;
375 while (lev < lmax && units < CC_SPLIT_UNITS_PER_THREAD * nthr) {
376 const long long R = ctx->cc_R[lev + 1];
377 if (R <= 0 || (double)n_start * (double)R > max_units || ((long long)end + 1) * R > (long long)INT_MAX - nthr - 1)
378 break;
379 lev++;
380 units *= ctx->cc_branch;
381 }
382 return lev;
383}
384
385/* Update the RW weight limit for the `min_hits` statistic after a change of the codeword hash (under `cw_mutex`). It
386 * is stored only if changed, as it shares the cache line of the bounds, which RW threads read for every candidate. */
387static inline void ctx_update_feed_limit(distfork_ctx_t * const ctx) {
388 const int feed = codeword_feed_limit(ctx->p);
389 if (atomic_load_explicit(&ctx->cw_feed_limit, memory_order_relaxed) != feed) {
390 atomic_store_explicit(&ctx->cw_feed_limit, feed, memory_order_relaxed);
391 }
392}
393
394/* irred=1: is the codeword of the `cnt` columns `vec` irreducible, i.e., not the union of a codeword and a disjoint
395 * nonzero zero-syndrome vector? Iff the rank of its columns of H is cnt - 1 (see csr_cols_rank()). Always true with
396 * irred=0. */
397static inline bool cw_irreducible(const distfork_ctx_t * const ctx, cols_ws_t * const ws, const int cnt,
398 const int vec[]) {
399 if (!ctx->p->irred || !ws || cnt < 2) return true;
400 return csr_cols_rank(ctx->mHT_cc, cnt, vec, ws) == cnt - 1;
401}
402
403/* Record the CC codeword `err` (sorted columns, nonzero syndrome of L) found by worker `warg`, and update the upper
404 * bound; returns true if the run is to stop (the first codeword without outC and maxC, or maxC codewords). With
405 * irred=1, a reducible codeword is not recorded (counted in `cw_red_cc`); the CC search finds every irreducible
406 * codeword, but only some reducible ones, from the weight d + 3 on for a circuit DEM (doc/cc_completeness.tex). */
407static __attribute__((noinline)) bool cc_record(distfork_ctx_t * const ctx, const worker_arg_t * const warg,
408 const one_vec_t * const err) {
409 params_t * const p = ctx->p;
410 if (!cw_irreducible(ctx, warg->irr_ws, err->wei, err->vec)) {
411 atomic_fetch_add_explicit(&ctx->cw_red_cc, 1, memory_order_relaxed);
412 return false;
413 }
414 bool stop = false;
415 pthread_mutex_lock(&ctx->cw_mutex);
416 p->codewords = codeword_add_maybe(p, err->vec, err->wei, CW_SRC_OTHER);
417 ctx_update_feed_limit(ctx);
418 int cur_d = atomic_load(&ctx->dmax);
419 if (p->min_w < cur_d || cur_d == 0) {
420 atomic_store(&ctx->dmax, p->min_w);
421 if ((p->debug & DBG_CODEWORDS) && err->wei == p->min_w) {
422 char prefix[48];
423 snprintf(prefix, sizeof(prefix), "# [thread %d] CC codeword", warg->tid);
424 print_codeword_support(stderr, prefix, err->vec, err->wei, p->debug);
425 }
426 }
427 int cur_cc_found = atomic_load(&ctx->cc_found_weight);
428 if (cur_cc_found == 0 || err->wei < cur_cc_found) {
429 atomic_store(&ctx->cc_found_weight, err->wei);
430 }
431 if (!p->outC && p->maxC == 0) {
432 ctx_stop(ctx, STOP_CC_FOUND);
433 stop = true;
434 }
436 ctx_stop(ctx, STOP_MAXC);
437 stop = true;
438 }
439 pthread_mutex_unlock(&ctx->cw_mutex);
440 return stop;
441}
442
443static inline int bit_get(const uint64_t * const b, const int i) { return (int)((b[i >> 6] >> (i & 63)) & 1); }
444static inline void bit_flip(uint64_t * const b, const int i) { b[i >> 6] ^= (uint64_t)1 << (i & 63); }
445
446/* The first set bit of the bitset b in from..limit-1 (limit if none) */
447static inline int bits_first(const uint64_t * const b, const int from, const int limit) {
448 if (from >= limit) return limit;
449 int k = from >> 6;
450 const int k_last = (limit - 1) >> 6;
451 uint64_t x = b[k] & (~(uint64_t)0 << (from & 63));
452 while (x == 0) {
453 if (++k > k_last) return limit;
454 x = b[k];
455 }
456 const int r = (k << 6) + __builtin_ctzll(x);
457 return (r < limit) ? r : limit;
458}
459
460/* The state of the CC walk of a worker (smax = 0, see cc_walk() and, for cc_prune >= 1, cc_bnb()) */
461typedef struct {
464 const csr_t *mH; /* H (sorted rows) */
465 const csr_t *mHT; /* its transpose: the columns of H (sorted rows) */
466 const cc_hash_t *hs; /* the column-syndrome hash */
467 uint64_t *S; /* the syndrome of the cluster: bitset of the rows of H */
468 uint64_t *E; /* the columns of the cluster: bitset */
469 int *cols; /* the columns of the cluster in the order added (cols[0]: the start column) */
470 one_vec_t *cw; /* a codeword found: its sorted columns */
471 int w; /* the number of columns of the cluster */
472 int col_min; /* columns are added only if larger (-1: unlimited clusters with the expert `start` list) */
473 int w_limit; /* the weight of the CC round */
474 int max_col_wt; /* the maximum column weight of H */
475 int m; /* the number of rows of H */
476 const int *fix; /* the fixed levels 1..nfix of the work unit, see `cc_pack()` */
477 int nfix;
478 long long nodes; /* calls of the recursion (cc_walk() or cc_bnb()), added to ctx->cc_nodes after each round */
479 int count_w; /* the nodes of weight w >= count_w are counted: a node at a fixed level is visited by all the
480 * work units with the same earlier digits, and counted in the one whose later digits are 0 */
481 /* the branch and bound (cc_prune >= 1, see cc_bnb()): a column u is available if u > col_min and u is neither in
482 * the cluster S nor in the forbidden set F */
483 int prune; /* cc_prune: 1, or 2 with the coverage and fractional cover bounds */
484 int max_row_wt; /* the maximum row weight of H (at most this many branches per node) */
485 uint64_t *X; /* the columns of S and F: bitset */
486 int *acnt; /* the number of available columns of each row of H */
487 int acnt_col; /* the value of col_min for which acnt is set up (INT_MIN: not set up) */
488 int *kfirst; /* the position in mH of the first column u > col_min of each row (the columns of a row are
489 * sorted, so that the loops over the available columns of a row start there) */
490 int *fstk; /* the forbidden columns in the order forbidden (unwound when the node returns) */
491 int fsp; /* their number */
492 int *brl; /* the branches of the nodes: max_row_wt entries per depth */
493 int *ulist; /* the unsatisfied rows of the current node */
494 int *uacnt; /* their numbers of available columns (acnt), for the packing bound */
495 int *ord; /* the unsatisfied rows ordered by their available columns, for the packing bound */
496 int *bcnt; /* counts of the rows by their available columns (max_row_wt + 2 entries) */
497 uint32_t *mark; /* columns marked in generation `gen` (packing and coverage bounds) */
498 uint32_t gen;
499 int *cv; /* cc_prune=2: the unsatisfied rows of each available column (generation `gen`) */
500 int *touched; /* cc_prune=2: the available columns of the unsatisfied rows */
501 int *hist; /* cc_prune=2: histogram of cv (max_col_wt + 1 entries) */
502} cc_walk_t;
503
504/* Record the codeword: the cluster with the columns c1 (unless negative) and c2, if its syndrome of L is nonzero;
505 * returns true if the run is to stop */
506static bool cc_walk_found(cc_walk_t * const t, const int c1, const int c2) {
507 one_vec_t * const cw = t->cw;
508 int n = 0;
509 for (int i = 0; i < t->w; i++) cw->vec[n++] = t->cols[i];
510 if (c1 >= 0) cw->vec[n++] = c1;
511 cw->vec[n++] = c2;
512 for (int i = 1; i < n; i++) { /* insertion sort */
513 const int x = cw->vec[i];
514 int j = i - 1;
515 while (j >= 0 && cw->vec[j] > x) {
516 cw->vec[j + 1] = cw->vec[j];
517 j--;
518 }
519 cw->vec[j + 1] = x;
520 }
521 cw->wei = n;
522 const colmask_t * const mL = t->ctx->p->maskL; /* NULL for a classical code */
523 if (mL && !colmask_syndrome_non_zero(mL, n, cw->vec)) return false;
524 return cc_record(t->ctx, t->warg, cw);
525}
526
527/* Is the syndrome S (bitset of weight sw) the sum h_{c1} + h_{c2} of the columns c1 and c2 of H (rows of mHT)? */
528static bool cc_sum2_equal(const uint64_t * const S, const int sw, const csr_t * const mHT, const int c1, const int c2) {
529 int a = mHT->p[c1], b = mHT->p[c2], k = 0;
530 const int a_end = mHT->p[c1 + 1], b_end = mHT->p[c2 + 1];
531 while (a < a_end || b < b_end) {
532 int r;
533 if (b >= b_end) r = mHT->i[a++];
534 else if (a >= a_end) r = mHT->i[b++];
535 else if (mHT->i[a] < mHT->i[b]) r = mHT->i[a++];
536 else if (mHT->i[b] < mHT->i[a]) r = mHT->i[b++];
537 else { /* a common row cancels */
538 a++;
539 b++;
540 continue;
541 }
542 if (!bit_get(S, r)) return false;
543 k++;
544 }
545 return k == sw;
546}
547
548/* The last two levels of cc_walk() at the level w = t->w with w + 2 = current_limit (the leaf level w + 1 is not a
549 * fixed level of the work unit; `i1_beg`..`i1_end`: the positions in mH of the columns of the row r0 = min(S) to add).
550 * There, the recursion adds each column c1 > col_min of r0 not in the cluster with the syndrome s = S + h_{c1} nonzero,
551 * and the leaf level adds each column c2 > col_min of the row min(s) not in the cluster with h_{c2} = s, which gives a
552 * codeword. These columns c2 are found by a lookup of the hash h ^ hc[c1] of s instead (cc_hash_t, verified exactly).
553 * The pairs (c1, c2) are the same and come in the same order (c2 in ascending order, as in the row min(s)). As at the
554 * entry of the leaf level, a codeword is not recorded once the stop flag is set or the weight limit has dropped (a
555 * smaller upper bound). Returns 1 if the run is to stop. */
556static __attribute__((noinline)) int cc_walk_last2(cc_walk_t * const t, const int sw, const uint64_t h,
557 const int i1_beg, const int i1_end) {
558 distfork_ctx_t * const ctx = t->ctx;
559 const cc_hash_t * const hs = t->hs;
560 const uint64_t * const hc = hs->hc;
561 const uint64_t * const bits = hs->bits;
562 const uint64_t bmask = hs->bmask;
563 const csr_t * const mH = t->mH;
564 const int col_min = t->col_min;
565 for (int i1 = i1_beg; i1 < i1_end; i1++) {
566 const int c1 = mH->i[i1];
567 if (c1 <= col_min) continue;
568 const uint64_t key = h ^ hc[c1]; /* the hash of s = S + h_{c1} */
569 const uint64_t fb = key & bmask;
570 if (!((bits[fb >> 6] >> (fb & 63)) & 1)) continue;
571 int j = (int)(key >> hs->tshift);
572 while (hs->tab[j].cnt > 0 && hs->tab[j].key != key) j = (j + 1) & hs->tmask;
573 if (hs->tab[j].cnt == 0) continue;
574 /* columns c2 with the hash of s (s is nonzero: no zero columns in the table) */
575 if (bit_get(t->E, c1)) continue;
576 const int k_end = hs->tab[j].beg + hs->tab[j].cnt;
577 for (int k = hs->tab[j].beg; k < k_end; k++) {
578 const int c2 = hs->cols[k];
579 if (c2 <= col_min || c2 == c1 || bit_get(t->E, c2)) continue;
580 if (!cc_sum2_equal(t->S, sw, t->mHT, c1, c2)) continue; /* a hash collision */
581 /* the checks at the entry of the leaf level */
582 if (atomic_load_explicit(&ctx->stop_flag, memory_order_relaxed)) return 0;
583 const int cur_dmax = atomic_load_explicit(&ctx->dmax, memory_order_relaxed);
584 if (cur_dmax > 0 && ctx->p->dW >= 0 && minint(t->w_limit, cur_dmax + ctx->p->dW) < t->w + 2) return 0;
585 if (cc_walk_found(t, c1, c2)) return 1;
586 }
587 }
588 return 0;
589}
590
591/* The CC recursion of start_CC_recurs_mt() for smax = 0, with the syndrome of the cluster as the bitset t->S (weight
592 * sw, smallest row r0, hash h, see cc_hash_t), and its columns as the bitset t->E: a column is added by flipping its
593 * rows in S (and back after the recursion) instead of merging sorted lists, and the weight of the new syndrome is
594 * known before. The clusters are the same, in the same order, and so are the codewords found (and their CC hit
595 * counts). The last two levels use hash lookups (cc_walk_last2()). Returns 1 if the run is to stop. */
596static int cc_walk(cc_walk_t * const t, const int sw, const int r0, const uint64_t h) {
597 distfork_ctx_t * const ctx = t->ctx;
598 if (atomic_load_explicit(&ctx->stop_flag, memory_order_relaxed)) return 0;
599 const params_t * const p = ctx->p;
600 const int w = t->w;
601 int current_limit = t->w_limit;
602 const int cur_dmax = atomic_load_explicit(&ctx->dmax, memory_order_relaxed);
603 if (cur_dmax > 0 && p->dW >= 0) current_limit = minint(t->w_limit, cur_dmax + p->dW);
604 if (w >= current_limit) return 0;
605 if (w >= t->count_w) t->nodes++;
606 const csr_t * const mH = t->mH;
607 const csr_t * const mHT = t->mHT;
608 /* the positions in mH of the columns of the row r0 to add: all of them, or one at a fixed level of the work unit */
609 int i1_beg = mH->p[r0], i1_end = mH->p[r0 + 1];
610 if (w <= t->nfix) {
611 i1_beg += t->fix[w - 1];
612 if (i1_beg >= i1_end) return 0;
613 i1_end = i1_beg + 1;
614 }
615 const int col_min = t->col_min;
616 uint64_t * const S = t->S;
617 uint64_t * const E = t->E;
618
619 if (w + 1 == current_limit) { /* leaf level: the columns c with h_c = S */
620 for (int i1 = i1_beg; i1 < i1_end; i1++) {
621 const int c = mH->i[i1];
622 if (c <= col_min) continue;
623 const int pb = mHT->p[c];
624 if (mHT->p[c + 1] - pb != sw) continue;
625 int k = 0;
626 while (k < sw && bit_get(S, mHT->i[pb + k])) k++;
627 if (k < sw || bit_get(E, c)) continue;
628 if (cc_walk_found(t, -1, c)) return 1;
629 }
630 return 0;
631 }
632 if (w + 2 == current_limit && w + 1 > t->nfix) return cc_walk_last2(t, sw, h, i1_beg, i1_end);
633
634 /* internal level */
635 const int max_reach = (current_limit - (w + 1)) * t->max_col_wt;
636 const uint64_t * const hc = t->hs->hc;
637 for (int i1 = i1_beg; i1 < i1_end; i1++) {
638 const int c1 = mH->i[i1];
639 if (c1 <= col_min) continue;
640 const int pb = mHT->p[c1], pe = mHT->p[c1 + 1];
641 if (sw - (pe - pb) > max_reach || bit_get(E, c1)) continue;
642 int nset = 0; /* the rows of c1 in S */
643 for (int k = pb; k < pe; k++) nset += bit_get(S, mHT->i[k]);
644 const int sw1 = sw + (pe - pb) - 2 * nset;
645 if (sw1 <= 0 || sw1 > max_reach) continue;
646 /* add c1: flip its rows; the smallest row of the new syndrome (r0 is in c1, all the other rows of S are larger) */
647 int rmin = t->m;
648 for (int k = pb; k < pe; k++) {
649 const int r = mHT->i[k];
650 if (!bit_get(S, r) && r < rmin) rmin = r;
651 bit_flip(S, r);
652 }
653 const int r1 = (rmin < r0) ? rmin : bits_first(S, r0 + 1, rmin);
654 bit_flip(E, c1);
655 t->cols[t->w++] = c1;
656 const int result = cc_walk(t, sw1, r1, h ^ hc[c1]);
657 t->w--;
658 bit_flip(E, c1);
659 for (int k = pb; k < pe; k++) bit_flip(S, mHT->i[k]);
660 if (result == 1) return 1;
661 }
662 return 0;
663}
664
665/* The CC work unit from the start column `col` (w_limit > 1, see cc_walk()) */
666static void cc_walk_unit(cc_walk_t * const t, const int col, const int w_limit, const int * const fix, const int nfix) {
667 const csr_t * const mHT = t->mHT;
668 const int pb = mHT->p[col], pe = mHT->p[col + 1];
669 for (int k = pb; k < pe; k++) bit_flip(t->S, mHT->i[k]);
670 bit_flip(t->E, col);
671 t->cols[0] = col;
672 t->w = 1;
673 t->col_min = (t->ctx->p->start_num > 0 && !t->ctx->p->start_scan) ? -1 : col;
674 t->w_limit = w_limit;
675 t->fix = fix;
676 t->nfix = nfix;
677 t->count_w = nfix + 1; /* (see cc_walk_t: count_w - 1 = the first fixed level with only zero digits from there on) */
678 while (t->count_w > 1 && fix[t->count_w - 2] == 0) t->count_w--;
679 cc_walk(t, pe - pb, mHT->i[pb], t->hs->hc[col]);
680 t->w = 0;
681 bit_flip(t->E, col);
682 for (int k = pb; k < pe; k++) bit_flip(t->S, mHT->i[k]);
683}
684
685/* ------------------------------------------------------------------------------------------------------------------
686 * The branch-and-bound CC search (cc_prune >= 1, smax = 0; see doc/cc_completeness.tex for the proof). A node is a
687 * cluster S with a nonzero syndrome s and a forbidden set F: a column u is available if u > col_min (the start column,
688 * F_0 = {u < start column}; with the expert `start` list, the columns listed before the start column) and u is not
689 * in S or F. With R = limit - |S| columns left, a node is pruned by admissible lower bounds on the number of columns
690 * needed to complete S to a codeword: |s| > R * max_col_wt (b1, as in cc_walk()), an unsatisfied row without
691 * available columns (dead end), more than R unsatisfied rows with pairwise disjoint sets of available columns (b2,
692 * packing, found greedily; with R >= 3), and with cc_prune=2 the coverage and fractional cover bounds (with R >= 4).
693 * Otherwise it branches on the unsatisfied row with the fewest available columns (the first such row in the row
694 * order), over its available columns v_1..v_q in column order: the branch i adds v_i to S, and v_i is forbidden in the
695 * later branches whatever happened in its own (sibling exclusion). Every irreducible codeword which contains the
696 * start column and avoids F_0 is found exactly once (one CC hit per codeword), reducible ones at most once. The
697 * choice of the row and the order of the branches depend only on the node, so that the work units (the fixed levels,
698 * see `cc_pack()`: the branch fix[l] among the available columns of the row, with the earlier ones forbidden)
699 * partition the search. The last two levels use the column-syndrome hash, as in cc_walk_last2().
700 * ------------------------------------------------------------------------------------------------------------------ */
701
702/* The column v becomes unavailable (added to S or to F) */
703static inline void bnb_take(cc_walk_t * const t, const int v) {
704 const csr_t * const mHT = t->mHT;
705 const int * const ri = mHT->i, ke = mHT->p[v + 1];
706 int * const acnt = t->acnt;
707 t->X[v >> 6] |= (uint64_t)1 << (v & 63);
708 for (int k = mHT->p[v]; k < ke; k++) acnt[ri[k]]--;
709}
710
711/* The column v becomes available again */
712static inline void bnb_give(cc_walk_t * const t, const int v) {
713 const csr_t * const mHT = t->mHT;
714 const int * const ri = mHT->i, ke = mHT->p[v + 1];
715 int * const acnt = t->acnt;
716 t->X[v >> 6] &= ~((uint64_t)1 << (v & 63));
717 for (int k = mHT->p[v]; k < ke; k++) acnt[ri[k]]++;
718}
719
720static inline bool bnb_avail(const cc_walk_t * const t, const int u) { return u > t->col_min && !bit_get(t->X, u); }
721
722/* bnb_avail() for a column u > col_min (from the position t->kfirst[r] on in its row r) */
723static inline bool bnb_free(const cc_walk_t * const t, const int u) { return !bit_get(t->X, u); }
724
725/* A new generation of the column marks (cleared when the counter wraps around) */
726static inline uint32_t bnb_next_gen(cc_walk_t * const t) {
727 if (++t->gen == 0) {
728 memset(t->mark, 0, (size_t)t->mHT->rows * sizeof(uint32_t));
729 t->gen = 1;
730 }
731 return t->gen;
732}
733
734/* As at the entry of a level: no codeword of weight |S| + extra is recorded once the stop flag is set or the weight
735 * limit has dropped below it (a smaller upper bound) */
736static inline bool bnb_record_ok(const cc_walk_t * const t, const int extra) {
737 distfork_ctx_t * const ctx = t->ctx;
738 if (atomic_load_explicit(&ctx->stop_flag, memory_order_relaxed)) return false;
739 const int cur_dmax = atomic_load_explicit(&ctx->dmax, memory_order_relaxed);
740 return !(cur_dmax > 0 && ctx->p->dW >= 0 && minint(t->w_limit, cur_dmax + ctx->p->dW) < t->w + extra);
741}
742
743/* The packing bound b2 for the nu unsatisfied rows t->ulist (t->uacnt: their amin..amax available columns): a greedy
744 * packing of rows with pairwise disjoint sets of available columns, the rows with the fewest available columns first
745 * (in row order); returns its size once it exceeds R, or once the rows left cannot make it exceed R */
746static int bnb_packing(cc_walk_t * const t, const int R, const int nu, const int amin, const int amax) {
747 int * const bcnt = t->bcnt; /* bcnt[a - amin]: first the number of the rows with a - 1 available columns */
748 const int * const ulist = t->ulist, * const uacnt = t->uacnt;
749 int * const ord = t->ord;
750 const int na = amax - amin + 1;
751 for (int a = 0; a <= na; a++) bcnt[a] = 0;
752 for (int i = 0; i < nu; i++) bcnt[uacnt[i] - amin + 1]++;
753 for (int a = 1; a <= na; a++) bcnt[a] += bcnt[a - 1]; /* then the first position of the rows with a */
754 for (int i = 0; i < nu; i++) ord[bcnt[uacnt[i] - amin]++] = ulist[i];
755 const uint32_t g = bnb_next_gen(t);
756 uint32_t * const mark = t->mark; /* the columns of the rows packed so far (the available ones count) */
757 const uint64_t * const X = t->X;
758 const int * const rp = t->mH->p, * const ci = t->mH->i, * const kf = t->kfirst;
759 int cnt = 0;
760 for (int i = 0; i < nu; i++) {
761 const int r = ord[i], kb = kf[r], ke = rp[r + 1];
762 int k = kb;
763 while (k < ke && !(mark[ci[k]] == g && !bit_get(X, ci[k]))) k++;
764 if (k == ke) { /* disjoint from the rows packed so far */
765 if (++cnt > R || cnt + (nu - 1 - i) <= R) return cnt;
766 for (k = kb; k < ke; k++) mark[ci[k]] = g;
767 } else if (cnt + (nu - 1 - i) <= R) {
768 return cnt;
769 }
770 }
771 return cnt;
772}
773
774/* cc_prune=2: the coverage bound b3 (the least t such that the t largest values c_u of the available columns sum to at
775 * least |s|, where c_u is the number of unsatisfied rows of u) and the fractional cover bound b4 (the sum over the
776 * unsatisfied rows r of 1 / max c_u over the available columns u of r); true if one of them exceeds R */
777static bool bnb_cover(cc_walk_t * const t, const int R, const int nu) {
778 const csr_t * const mH = t->mH;
779 const uint32_t g = bnb_next_gen(t);
780 int nt = 0;
781 for (int i = 0; i < nu; i++) {
782 const int r = t->ulist[i];
783 for (int k = t->kfirst[r]; k < mH->p[r + 1]; k++) {
784 const int u = mH->i[k];
785 if (!bnb_free(t, u)) continue;
786 if (t->mark[u] != g) {
787 t->mark[u] = g;
788 t->cv[u] = 0;
789 t->touched[nt++] = u;
790 }
791 t->cv[u]++;
792 }
793 }
794 const int D = t->max_col_wt;
795 int * const hist = t->hist;
796 for (int c = 0; c <= D; c++) hist[c] = 0;
797 for (int i = 0; i < nt; i++) hist[t->cv[t->touched[i]]]++;
798 int need = nu, tc = 0;
799 for (int c = D; c >= 1 && need > 0; c--) {
800 if ((long long)hist[c] * c >= need) {
801 tc += (need + c - 1) / c;
802 need = 0;
803 } else {
804 tc += hist[c];
805 need -= hist[c] * c;
806 }
807 }
808 if (need > 0 || tc > R) return true;
809 double sum = 0.0; /* (the rows in row order: the same sum on every thread) */
810 for (int i = 0; i < nu; i++) {
811 const int r = t->ulist[i];
812 int mx = 0;
813 for (int k = t->kfirst[r]; k < mH->p[r + 1]; k++) {
814 const int u = mH->i[k];
815 if (bnb_free(t, u) && t->cv[u] > mx) mx = t->cv[u];
816 }
817 if (mx == 0) return true;
818 sum += 1.0 / mx;
819 /* the partial sums grow, by at most 1 per row, so that the decision may be known early (the rounding errors are
820 * far smaller than the margins) */
821 if (sum > R + 1e-9) return true;
822 if (sum + (nu - 1 - i) < R + 1e-9 - 1e-7) return false;
823 }
824 return false;
825}
826
827/* The hash slot of the key (a syndrome hash, see cc_hash_t), NULL if no nonzero column has this hash */
828static inline const cc_hash_slot_t *cc_hash_find(const cc_hash_t * const hs, const uint64_t key) {
829 const uint64_t fb = key & hs->bmask;
830 if (!((hs->bits[fb >> 6] >> (fb & 63)) & 1)) return NULL;
831 int j = (int)(key >> hs->tshift);
832 while (hs->tab[j].cnt > 0 && hs->tab[j].key != key) j = (j + 1) & hs->tmask;
833 return (hs->tab[j].cnt > 0) ? &hs->tab[j] : NULL;
834}
835
836/* The leaf level of cc_bnb() (one column left, not a fixed level): the available columns u with h_u = s, found by a
837 * lookup of the hash h of s (verified exactly) */
838static int bnb_leaf(cc_walk_t * const t, const int sw, const uint64_t h) {
839 const cc_hash_slot_t * const sl = cc_hash_find(t->hs, h);
840 if (!sl) return 0;
841 const csr_t * const mHT = t->mHT;
842 for (int k = sl->beg; k < sl->beg + sl->cnt; k++) {
843 const int u = t->hs->cols[k];
844 if (!bnb_avail(t, u)) continue;
845 const int pb = mHT->p[u];
846 if (mHT->p[u + 1] - pb != sw) continue;
847 int x = 0;
848 while (x < sw && bit_get(t->S, mHT->i[pb + x])) x++;
849 if (x < sw) continue;
850 if (!bnb_record_ok(t, 1)) return 0;
851 if (cc_walk_found(t, -1, u)) return 1;
852 }
853 return 0;
854}
855
856/* The last two levels of cc_bnb() (two columns left, not a fixed level): for each available column v of the branching
857 * row r (in column order), the available columns u != v with h_u = s + h_v, found by a lookup of the hash h ^ hc[v]
858 * (verified exactly), give the codewords S + {v, u}, each exactly once (doc/cc_completeness.tex, Proposition "the last
859 * two extensions by a hash table"; the columns v of the earlier branches need not be forbidden: such a column u has
860 * a zero in the row r, unlike v) */
861static int bnb_last2(cc_walk_t * const t, const int sw, const uint64_t h, const int r) {
862 const cc_hash_t * const hs = t->hs;
863 const csr_t * const mH = t->mH;
864 for (int i = t->kfirst[r]; i < mH->p[r + 1]; i++) {
865 const int v = mH->i[i];
866 if (!bnb_free(t, v)) continue;
867 const cc_hash_slot_t * const sl = cc_hash_find(hs, h ^ hs->hc[v]);
868 if (!sl) continue;
869 for (int k = sl->beg; k < sl->beg + sl->cnt; k++) {
870 const int u = hs->cols[k];
871 if (u == v || !bnb_avail(t, u)) continue;
872 if (!cc_sum2_equal(t->S, sw, t->mHT, v, u)) continue; /* a hash collision */
873 if (!bnb_record_ok(t, 2)) return 0;
874 if (cc_walk_found(t, v, u)) return 1;
875 }
876 }
877 return 0;
878}
879
880/* The branch-and-bound recursion at the node (S, F) with S = t->cols[0..w-1] (syndrome t->S of weight sw > 0, hash
881 * h), F = the columns of t->X not in S; returns 1 if the run is to stop. On return, t->X, t->acnt, and t->fsp are as
882 * on entry. */
883static int cc_bnb(cc_walk_t * const t, const int sw, const uint64_t h) {
884 distfork_ctx_t * const ctx = t->ctx;
885 if (atomic_load_explicit(&ctx->stop_flag, memory_order_relaxed)) return 0;
886 const params_t * const p = ctx->p;
887 const int w = t->w;
888 int current_limit = t->w_limit;
889 const int cur_dmax = atomic_load_explicit(&ctx->dmax, memory_order_relaxed);
890 if (cur_dmax > 0 && p->dW >= 0) current_limit = minint(t->w_limit, cur_dmax + p->dW);
891 const int R = current_limit - w; /* the number of columns left */
892 if (R <= 0 || sw > R * t->max_col_wt) return 0; /* b1 */
893 if (w >= t->count_w) t->nodes++;
894 const bool fixed = (w <= t->nfix); /* a fixed level of the work unit */
895 if (R == 1 && !fixed) return bnb_leaf(t, sw, h);
896 /* the unsatisfied rows, and the branching row: the one with the fewest available columns, the first in row order */
897 const int nwords = (t->m + 63) >> 6;
898 const int * const acnt = t->acnt;
899 int nu = 0, rbest = -1, abest = INT_MAX, amax = 0;
900 if (R == 2 && !fixed) { /* (only the branching row is needed) */
901 for (int k = 0; k < nwords; k++) {
902 uint64_t x = t->S[k];
903 while (x) {
904 const int r = (k << 6) + __builtin_ctzll(x);
905 x &= x - 1;
906 const int a = acnt[r];
907 if (a < abest) {
908 if (a == 0) return 0; /* a dead end */
909 abest = a;
910 rbest = r;
911 }
912 }
913 }
914 return bnb_last2(t, sw, h, rbest);
915 }
916 int * const ulist = t->ulist, * const uacnt = t->uacnt;
917 for (int k = 0; k < nwords; k++) {
918 uint64_t x = t->S[k];
919 while (x) {
920 const int r = (k << 6) + __builtin_ctzll(x);
921 x &= x - 1;
922 const int a = acnt[r];
923 ulist[nu] = r;
924 uacnt[nu++] = a;
925 if (a < abest) {
926 abest = a;
927 rbest = r;
928 }
929 if (a > amax) amax = a;
930 }
931 }
932 if (abest == 0) return 0; /* a dead end */
933 if (sw > R && R >= 3) { /* (the packing has at most |s| rows; with R <= 2, the hash levels are cheaper) */
934 if (bnb_packing(t, R, nu, abest, amax) > R) return 0;
935 /* (with R = 3, the cover bounds rarely prune what the packing leaves, and cost more than they save) */
936 if (t->prune >= 2 && R >= 4 && bnb_cover(t, R, nu)) return 0;
937 }
938 /* the branches: the available columns of the row rbest, in column order */
939 const csr_t * const mH = t->mH;
940 const csr_t * const mHT = t->mHT;
941 int * const brl = t->brl + (size_t)w * (size_t)t->max_row_wt;
942 int q = 0;
943 for (int k = t->kfirst[rbest]; k < mH->p[rbest + 1]; k++) {
944 const int u = mH->i[k];
945 if (bnb_free(t, u)) brl[q++] = u;
946 }
947 const int fsp0 = t->fsp;
948 int i_beg = 0, i_end = q;
949 if (fixed) { /* the branch fix[w-1] only, with the columns of the earlier branches forbidden */
950 const int d = t->fix[w - 1];
951 if (d >= q) return 0;
952 for (int i = 0; i < d; i++) {
953 bnb_take(t, brl[i]);
954 t->fstk[t->fsp++] = brl[i];
955 }
956 i_beg = d;
957 i_end = d + 1;
958 }
959 const int reach = (R - 1) * t->max_col_wt; /* the largest syndrome weight which R - 1 columns can complete */
960 const uint64_t * const hc = t->hs->hc;
961 uint64_t * const S = t->S;
962 int res = 0;
963 for (int i = i_beg; i < i_end && !res; i++) {
964 const int v = brl[i];
965 const int pb = mHT->p[v], pe = mHT->p[v + 1];
966 bnb_take(t, v); /* v is in S in its branch, and in F after it, whatever happens in the branch */
967 t->fstk[t->fsp++] = v;
968 if (sw - (pe - pb) > reach) continue; /* b1 of the branch: its syndrome weight is at least sw - |h_v| */
969 int nset = 0; /* the rows of v in s */
970 for (int k = pb; k < pe; k++) nset += bit_get(S, mHT->i[k]);
971 const int sw1 = sw + (pe - pb) - 2 * nset;
972 if (sw1 == 0) { /* a codeword if the cluster has the weight of the limit; nothing is searched below it */
973 if (R == 1 && bnb_record_ok(t, 1)) res = cc_walk_found(t, -1, v);
974 continue;
975 }
976 if (sw1 > reach) continue;
977 for (int k = pb; k < pe; k++) bit_flip(S, mHT->i[k]);
978 t->cols[t->w++] = v;
979 res = cc_bnb(t, sw1, h ^ hc[v]);
980 t->w--;
981 for (int k = pb; k < pe; k++) bit_flip(S, mHT->i[k]);
982 }
983 while (t->fsp > fsp0) bnb_give(t, t->fstk[--t->fsp]);
984 return res;
985}
986
987/* Set up t->acnt and t->kfirst for col_min = c (S and F empty): the columns u > c of each row. The start columns
988 * claimed by a worker grow within a round, so that both are updated incrementally from a smaller c (the columns
989 * c' < u <= c are dropped: they are the first columns > c' of their rows); otherwise (a new round, or the expert
990 * `start` list after other units) they are counted again. */
991static void bnb_acnt_setup(cc_walk_t * const t, const int c) {
992 if (t->acnt_col == c) return;
993 if (t->acnt_col != INT_MIN && t->acnt_col < c) {
994 const csr_t * const mHT = t->mHT;
995 for (int u = t->acnt_col + 1; u <= c; u++)
996 for (int k = mHT->p[u]; k < mHT->p[u + 1]; k++) {
997 t->acnt[mHT->i[k]]--;
998 t->kfirst[mHT->i[k]]++;
999 }
1000 } else {
1001 const csr_t * const mH = t->mH;
1002 for (int r = 0; r < t->m; r++) {
1003 int k = mH->p[r];
1004 while (k < mH->p[r + 1] && mH->i[k] <= c) k++; /* (the columns of a row are sorted) */
1005 t->acnt[r] = mH->p[r + 1] - k;
1006 t->kfirst[r] = k;
1007 }
1008 }
1009 t->acnt_col = c;
1010}
1011
1012/* The CC work unit from the start column `col` with the branch and bound (cc_prune >= 1, w_limit > 1, see cc_bnb()):
1013 * the clusters use only the columns u > col, or with the expert `start` list (unlimited clusters), all columns except
1014 * those listed before `col` (`idx`: its position in the list), which are forbidden: a codeword which contains one of
1015 * them is found from the first one it contains (doc/cc_completeness.tex, Proposition "start lists"). With
1016 * start_scan=1, the listed start columns use only the larger columns, as in the scan of all columns. */
1017static void cc_bnb_unit(cc_walk_t * const t, const int col, const int idx, const int w_limit, const int * const fix,
1018 const int nfix) {
1019 const params_t * const p = t->ctx->p;
1020 const csr_t * const mHT = t->mHT;
1021 const bool list = (p->start_num > 0 && !p->start_scan);
1022 t->col_min = list ? -1 : col;
1023 bnb_acnt_setup(t, t->col_min);
1024 t->fsp = 0;
1025 if (list) {
1026 for (int i = 0; i < idx; i++) {
1027 bnb_take(t, p->start_list[i]);
1028 t->fstk[t->fsp++] = p->start_list[i];
1029 }
1030 bnb_take(t, col); /* (with col_min = col, the start column is not available anyway) */
1031 }
1032 const int pb = mHT->p[col], pe = mHT->p[col + 1];
1033 for (int k = pb; k < pe; k++) bit_flip(t->S, mHT->i[k]);
1034 t->cols[0] = col;
1035 t->w = 1;
1036 t->w_limit = w_limit;
1037 t->fix = fix;
1038 t->nfix = nfix;
1039 t->count_w = nfix + 1; /* (see cc_walk_t: count_w - 1 = the first fixed level with only zero digits from there on) */
1040 while (t->count_w > 1 && fix[t->count_w - 2] == 0) t->count_w--;
1041 cc_bnb(t, pe - pb, t->hs->hc[col]);
1042 t->w = 0;
1043 for (int k = pb; k < pe; k++) bit_flip(t->S, mHT->i[k]);
1044 if (list) bnb_give(t, col);
1045 while (t->fsp > 0) bnb_give(t, t->fstk[--t->fsp]);
1046}
1047
1048/* Recursive CC worker function (interruptible), used with smax > 0 (otherwise, cc_walk() explores the same clusters).
1049 * At the fixed levels w = err->wei <= nfix of a CC work unit (see `cc_pack()`), only the column at position fix[w-1]
1050 * of the row is added (none if the row is shorter). */
1051static int start_CC_recurs_mt(one_vec_t *err, one_vec_t *urr, one_vec_t * const syn[],
1052 const int w_limit, const int max_col_wt,
1053 const csr_t * const mH, const csr_t * const mHT,
1054 worker_arg_t *warg, const int * const fix, const int nfix) {
1055 distfork_ctx_t *ctx = warg->ctx;
1056 if (atomic_load_explicit(&ctx->stop_flag, memory_order_relaxed)) {
1057 return 0;
1058 }
1059 params_t * const p = ctx->p;
1060 const int w = err->wei;
1061 int current_limit = w_limit;
1062 int cur_dmax = atomic_load_explicit(&ctx->dmax, memory_order_relaxed);
1063 if (cur_dmax > 0 && p->dW >= 0) {
1064 current_limit = minint(w_limit, cur_dmax + p->dW);
1065 }
1066 if (w >= current_limit) {
1067 return 0;
1068 }
1069
1070 const one_vec_t * const syn_w = syn[w];
1071 const int syn_w_wei = syn_w->wei;
1072 const int row = syn_w->vec[0];
1073 /* the positions in mH of the columns of the row to add: all of them, or one at a fixed level of the work unit */
1074 int i1_beg = mH->p[row], i1_end = mH->p[row + 1];
1075 if (w <= nfix) {
1076 i1_beg += fix[w - 1];
1077 if (i1_beg >= i1_end) return 0;
1078 i1_end = i1_beg + 1;
1079 }
1080 const colmask_t * const mL = p->maskL; /* NULL for a classical code */
1081 /* Clusters are grown only to columns larger than the start column, except with the expert `start`
1082 * list, where clusters are unlimited (columns already in `err` are skipped via `one_ordered_search`),
1083 * unless start_scan=1. */
1084 const int col_min = (p->start_num > 0 && !p->start_scan) ? -1 : urr->vec[0];
1085
1086 /* Leaf level: w + 1 == current_limit */
1087 if (w + 1 == current_limit) {
1088 int max_leaf_swei = 0;
1089 if (p->smax > 0 && current_limit < MAX_W) {
1090 int cur_min = warg->min_swei[current_limit] - 1;
1091 max_leaf_swei = (cur_min < p->smax) ? (cur_min > 0 ? cur_min : 0) : p->smax;
1092 }
1093
1094 for (int i1 = i1_beg; i1 < i1_end; i1++) {
1095 const int col = mH->i[i1];
1096 if (col <= col_min) continue;
1097
1098 const int p_beg = mHT->p[col];
1099 const int col_wt = mHT->p[col + 1] - p_beg;
1100 if (abs(syn_w_wei - col_wt) > max_leaf_swei) continue;
1101
1102 int swei;
1103 if (max_leaf_swei == 0) {
1104 /* Only swei == 0 is of interest: check if mHT[col] == syn[w] */
1105 if (mHT->i[p_beg] != row ||
1106 mHT->i[p_beg + col_wt - 1] != syn_w->vec[col_wt - 1]) {
1107 continue;
1108 }
1109 if (memcmp(syn_w->vec, &mHT->i[p_beg], (size_t)col_wt * sizeof(int)) != 0) {
1110 continue;
1111 }
1112 if (one_ordered_search(err, col) != -1) continue;
1113 swei = 0;
1114 } else {
1115 if (one_ordered_search(err, col) != -1) continue;
1116 syn[w + 1]->wei = 0;
1117 swei = one_csr_row_combine(syn[w + 1], syn_w, mHT, col);
1118 if (swei > 0) {
1119 if (swei <= max_leaf_swei) {
1120 warg->min_swei[w + 1] = swei;
1121 int cur_min = swei - 1;
1122 max_leaf_swei = (cur_min < p->smax) ? (cur_min > 0 ? cur_min : 0) : p->smax;
1123 }
1124 continue;
1125 }
1126 }
1127
1128 /* swei == 0: insert col into err to verify against mL and record codeword */
1129 int pos = one_ordered_ins(err, col);
1130 int nz = (!mL) || colmask_syndrome_non_zero(mL, err->wei, err->vec);
1131 if (nz && cc_record(ctx, warg, err)) {
1132 one_ordered_pos_del(err, col, pos);
1133 return 1;
1134 }
1135 one_ordered_pos_del(err, col, pos);
1136 }
1137 return 0;
1138 }
1139
1140 /* Internal level: w + 1 < current_limit */
1141 const int rem = current_limit - (w + 1);
1142 int max_s_needed = 0;
1143 if (p->smax > 0 && current_limit < MAX_W) { /* smax > 0 only without noscan and with dmin <= 1 (var_init) */
1144 int cur_min = warg->min_swei[current_limit] - 1;
1145 max_s_needed = (cur_min < p->smax) ? (cur_min > 0 ? cur_min : 0) : p->smax;
1146 }
1147 const int max_reach = rem * max_col_wt + max_s_needed;
1148
1149 for (int i1 = i1_beg; i1 < i1_end; i1++) {
1150 const int col = mH->i[i1];
1151 if (col > col_min) {
1152 const int col_wt = mHT->p[col + 1] - mHT->p[col];
1153 if (syn_w_wei - col_wt > max_reach) continue;
1154
1155 int pos = one_ordered_search(err, col);
1156 if (pos == -1) {
1157 syn[w + 1]->wei = 0;
1158 int swei = one_csr_row_combine(syn[w + 1], syn_w, mHT, col);
1159
1160 if (p->smax && swei > 0 && swei <= p->smax && (w + 1 < MAX_W)) {
1161 if (swei < warg->min_swei[w + 1]) {
1162 warg->min_swei[w + 1] = swei;
1163 }
1164 }
1165
1166 if (swei > 0 && swei <= max_reach) {
1167 urr->vec[w] = col;
1168 urr->wei++;
1169 pos = one_ordered_ins(err, col);
1170 int result = start_CC_recurs_mt(err, urr, syn, w_limit, max_col_wt,
1171 mH, mHT, warg, fix, nfix);
1172 urr->wei--;
1173 one_ordered_pos_del(err, col, pos);
1174 if (result == 1) {
1175 return 1;
1176 }
1177 }
1178 }
1179 }
1180 }
1181 return 0;
1182}
1183
1184/* The window of a hybrid RW step (kwin=0, n >= 500: an index window on every other global step), see
1185 * rw_hybrid_window() in util_io.h */
1186static inline int hybrid_window(const params_t * const p, const int nvar) {
1187 return rw_hybrid_window(p, nvar);
1188}
1189
1190/* Add the thread time `*acc_t` of `*acc_n` completed RW steps to the totals of the RW timing model, and reset them */
1191static inline void rw_time_publish(distfork_ctx_t *ctx, double *acc_t, int *acc_n) {
1192 if (*acc_n > 0) {
1193 atomic_fetch_add_explicit(&ctx->rw_busy_ns, (long long)(*acc_t * 1e9), memory_order_relaxed);
1194 atomic_fetch_add_explicit(&ctx->rw_timed_steps, *acc_n, memory_order_relaxed);
1195 }
1196 *acc_t = 0.0;
1197 *acc_n = 0;
1198}
1199
1200/* Time of a completed RW step since `*t_last`: published at least every 2 ms (after every step if steps are slow),
1201 * and right away for the first step of a batch (`first`), so that the coordinator gets the step time early */
1202static inline void rw_time_step(distfork_ctx_t *ctx, double *t_last, double *acc_t, int *acc_n, const bool first) {
1203 const double t_now = get_time_sec();
1204 *acc_t += t_now - *t_last;
1205 *t_last = t_now;
1206 (*acc_n)++;
1207 if (first || *acc_t >= 0.002) rw_time_publish(ctx, acc_t, acc_n);
1208}
1209
1210/* Weight limit (exclusive) of the RW candidate codewords, see codeword_rw_limit() */
1211static inline int rw_cw_limit(distfork_ctx_t * const ctx) {
1212 return codeword_rw_limit(ctx->p, atomic_load_explicit(&ctx->dmax, memory_order_relaxed),
1213 atomic_load_explicit(&ctx->cw_feed_limit, memory_order_relaxed));
1214}
1215
1216/* Record the RW codeword `ee` of weight `cnt` (sorted indices) found in a step with a uniform random permutation or
1217 * with a window (`src`: CW_SRC_RW_UNIFORM or CW_SRC_RW_WINDOW): add it to the hash (or count one more hit), update
1218 * the upper bound dmax and the stop flags (`ksub` > 0 only for the message) */
1219static void rw_record_codeword(distfork_ctx_t * const ctx, const rci_t * const ee, const int cnt, const int tid,
1220 const int ksub, const int src) {
1221 params_t * const p = ctx->p;
1222 /* with outC or maxC, codewords are collected: RW does not end the run when the bounds coincide or on wmin (in
1223 * method 3, the coordinator then runs the CC rounds which enumerate the codewords) */
1224 const bool collecting = (p->outC != NULL) || (p->maxC > 0);
1225 pthread_mutex_lock(&ctx->cw_mutex);
1226 p->codewords = codeword_add_maybe(p, ee, cnt, src);
1227 ctx_update_feed_limit(ctx);
1228 const int best = p->min_w; /* a codeword lighter than min_w is always added */
1229 const int old_dmax = atomic_load(&ctx->dmax);
1230 if (old_dmax == 0 || best < old_dmax) {
1231 atomic_store(&ctx->dmax, best);
1232 if (p->debug & DBG_PROGRESS) {
1233 int num_rw = (p->method == 1) ? ctx->num_threads
1234 : minint(ctx->rw_threads, ctx->num_threads - atomic_load(&ctx->cc_target_workers));
1235 if (num_rw < 1) num_rw = 1;
1236 if (ksub > 0) {
1237 fprintf(stderr, "# [thread %d] RW (ksub=%d) found new upper bound cw of weight %d (using %d RW threads)\n",
1238 tid, ksub, best, num_rw);
1239 } else {
1240 fprintf(stderr, "# [thread %d] RW found new upper bound cw of weight %d (using %d RW threads)\n",
1241 tid, best, num_rw);
1242 }
1243 }
1244 if ((p->debug & DBG_CODEWORDS) && cnt == best) {
1245 char prefix[48];
1246 snprintf(prefix, sizeof(prefix), "# [thread %d] RW codeword", tid);
1247 print_codeword_support(stderr, prefix, ee, cnt, p->debug);
1248 }
1249 const int cur_dmin = atomic_load(&ctx->dmin);
1250 if (!collecting && cur_dmin > 0 && best <= cur_dmin) {
1251 ctx_stop(ctx, STOP_BOUNDS);
1252 }
1253 }
1254 if (!collecting && p->wmin > 0 && best <= p->wmin) {
1255 ctx_stop(ctx, STOP_WMIN);
1256 }
1257 if (codeword_maxc_reached(p)) {
1258 ctx_stop(ctx, STOP_MAXC);
1259 }
1261 if ((p->debug & DBG_PROGRESS) && !atomic_load(&ctx->stop_flag) && !atomic_load(&ctx->rw_stop_flag)) {
1262 cw_hit_stats_t st;
1263 codeword_hit_stats(p, &st);
1264 fprintf(stderr,
1265 "# RW convergence reached: <n>=%.2f >= min_hits=%d average hits per codeword (%lld cws of weight "
1266 "%d..%d; w=%d: %lld cws, %lld hits), P_fail ~ exp(-<n>) = %.2g\n",
1267 st.avg, p->min_hits, st.set_cws, st.w_lo, st.w_hi, st.w_lo, st.min_cws, st.min_hits, exp(-st.avg));
1268 }
1269 if (p->method == 1) {
1270 ctx_stop(ctx, STOP_RW_CONVERGED);
1271 } else {
1272 atomic_store(&ctx->rw_stop_flag, true);
1273 }
1274 }
1275 pthread_mutex_unlock(&ctx->cw_mutex);
1276}
1277
1278/* dst ^= src, and dst = a ^ b, for rows of `width` words */
1279static inline void rw_row_xor(word * __restrict dst, const word * __restrict src, const wi_t width) {
1280 for (wi_t w = 0; w < width; w++) dst[w] ^= src[w];
1281}
1282static inline void rw_row_xor3(word * __restrict dst, const word * __restrict a, const word * __restrict b,
1283 const wi_t width) {
1284 for (wi_t w = 0; w < width; w++) dst[w] = a[w] ^ b[w];
1285}
1287#define RW_K_MAX 8 /* at most 2^8 rows per table of rw_eliminate() */
1288#define RW_NT_MAX 8 /* at most 8 tables, i.e., 64 pivots per pass over the rows */
1289
1290/* Parameters of rw_eliminate() for a matrix of `rows` x `width` words: the number of tables `*nt` and of pivots per
1291 * table `*k` (column by column: *nt = 0, below 128 rows). For wide rows, the tables are kept at about 2 MB or less
1292 * (cache): fewer rows per table, more tables. Measured (single thread): K = 6 with 4 tables is 1.7x to 2.2x faster
1293 * than column by column for 648 to 2736 rows (circuit DEMs, a 1500 x 3000 LDPC code), also with 16 threads; between
1294 * 128 and 512 rows, K = 5 with 4 tables is 0.75x to 1.4x as fast, depending on the matrix (see rw_elim_t). */
1295static void rw_block_params(const int rows, const wi_t width, int * const k, int * const nt) {
1296 *k = 0;
1297 *nt = 0;
1298 if (rows < 128) return;
1299 *k = (rows < 512) ? 5 : 6;
1300 *nt = 4;
1301 while (*k > 3 && ((size_t)*nt << *k) * (size_t)width * sizeof(word) > ((size_t)2 << 20)) {
1302 (*k)--;
1303 if (*nt + 2 <= RW_NT_MAX) *nt += 2;
1304 }
1305}
1306
1307/* RW elimination of a thread: the tables and parameters of rw_eliminate(), and the choice between it and the column-by-
1308 * column elimination for 128 to 512 rows, where the blocked one is not always faster: the steps 1..2*RW_TUNE_STEPS
1309 * (after the first one, from the sparse H) alternate between the two eliminations, which give the same results, and
1310 * the faster one (in total) is used for the following steps */
1311#define RW_TUNE_STEPS 4
1312typedef struct {
1313 word *table; /* the tables of rw_eliminate(): nt << k rows of tstride words, row 0 of each table zero */
1314 size_t tstride;
1315 int k, nt; /* pivots per table, number of tables (0: column by column), see rw_block_params() */
1316 int steps; /* RW steps completed while tuning */
1317 double t[2]; /* time of the timed eliminations: column by column (0), blocked (1) */
1318 int choice; /* -1: tuning, 0: column by column, 1: blocked */
1319} rw_elim_t;
1320
1321/* RW elimination with the "Method of the Four Russians" (as in M4RI's echelonization, but for the scattered columns
1322 * of a random permutation): Gauss-Jordan elimination of the columns order[0], order[1], ... in the rows
1323 * 0..nrows_eff-1 of M, until nrows_eff pivots are found, in blocks of up to nt*K pivots.
1324 * 1. The pivots of a block, in the order of the columns: a column is a pivot if a row r >= rank, reduced with the
1325 * previous pivots, has a 1 in it. The block's pivot rows are kept reduced with each other, so that the reduced bit
1326 * of a row is its bit plus the parity of its bits in the pivot columns of the block's pivot rows with a 1 in the
1327 * column: the rows are searched without row operations, and only the new pivot row is reduced.
1328 * 2. Tables of the 2^K combinations of K pivot rows each (built with one row XOR each, in Gray-code order; row 0 of
1329 * each table is zero; table t at T + (t << K) * tstride, see rw_elim_t).
1330 * 3. Every other row is reduced in one pass: for each table, the combination given by the row's bits in its pivot
1331 * columns (the row stays in the L1 cache, while it is read and written only once from memory).
1332 * The pivots, their rows, and the reduced matrix are the same as with gauss_one_rows() column by column, with about
1333 * 1/(nt*K) of the passes over the rows of M. The pivot columns are stored in pivs[0..rank-1] and set in piv_mask.
1334 * Returns the rank, or -1 if interrupted by a stop flag (checked every 16 columns, as before). (Not inlined, so that
1335 * the column-by-column elimination in run_rw_steps() stays as compact as before for small matrices.) */
1336__attribute__((noinline))
1337static int rw_eliminate(distfork_ctx_t * const ctx, mzd_t * const M, const int nrows_eff, const int nvar,
1338 const rci_t * const order, rci_t * const pivs, word * const piv_mask,
1339 const rw_elim_t * const el) {
1340 word * const T = el->table;
1341 const size_t tstride = el->tstride;
1342 const int K = el->k, nt = el->nt;
1343 const wi_t width = M->width;
1344 const int P = K * nt; /* pivots per block */
1345 int bw[RW_K_MAX * RW_NT_MAX], bs[RW_K_MAX * RW_NT_MAX]; /* word and bit of the pivot columns of the block */
1346 int rank = 0;
1347 int i = 0;
1348 while (i < nvar && rank < nrows_eff) {
1349 const int r0 = rank; /* the first pivot row of the block */
1350 int kk = 0;
1351 /* 1. up to P pivots */
1352 while (kk < P && i < nvar && rank < nrows_eff) {
1353 if ((i & 15) == 15 && (atomic_load_explicit(&ctx->stop_flag, memory_order_relaxed) ||
1354 atomic_load_explicit(&ctx->rw_stop_flag, memory_order_relaxed))) {
1355 return -1;
1356 }
1357 const int col = order[i++];
1358 const int cw = col >> 6, cb = col & 63;
1359 uint64_t mask = 0; /* the block's pivot rows with a 1 in the column */
1360 for (int l = 0; l < kk; l++) mask |= (uint64_t)((mzd_row(M, r0 + l)[cw] >> cb) & 1) << l;
1361 int found = -1;
1362 for (int r = rank; r < nrows_eff; r++) {
1363 const word * const row = mzd_row(M, r);
1364 word b = (row[cw] >> cb) & 1;
1365 for (uint64_t m2 = mask; m2; m2 &= m2 - 1) {
1366 const int l = __builtin_ctzll(m2);
1367 b ^= (row[bw[l]] >> bs[l]) & 1;
1368 }
1369 if (b) {
1370 found = r;
1371 break;
1372 }
1373 }
1374 if (found < 0) continue; /* not a pivot: the column depends on the previous ones */
1375 if (found != rank) mzd_row_swap(M, rank, found);
1376 word * const prow = mzd_row(M, rank);
1377 for (int l = 0; l < kk; l++) { /* reduce the new pivot row with the block's pivot rows */
1378 if ((prow[bw[l]] >> bs[l]) & 1) rw_row_xor(prow, mzd_row(M, r0 + l), width);
1379 }
1380 for (uint64_t m2 = mask; m2; m2 &= m2 - 1) /* and clear the column in them */
1381 rw_row_xor(mzd_row(M, r0 + __builtin_ctzll(m2)), prow, width);
1382 bw[kk] = cw;
1383 bs[kk] = cb;
1384 kk++;
1385 pivs[rank] = col;
1386 piv_mask[cw] |= (word)1 << cb;
1387 rank++;
1388 }
1389 if (kk == 0) break; /* no more columns */
1390 /* 2. the tables: row g of table t is the sum of the pivot rows r0 + t*K + l of the bits l of g */
1391 const int ntab = (kk + K - 1) / K;
1392 for (int t = 0; t < ntab; t++) {
1393 word * const Tt = T + ((size_t)t << K) * tstride;
1394 const int kt = (kk - t * K < K) ? kk - t * K : K;
1395 for (unsigned g = 1; g < (1u << kt); g++) {
1396 const unsigned gc = g ^ (g >> 1), gp = (g - 1) ^ ((g - 1) >> 1); /* Gray codes of g and g-1 */
1397 rw_row_xor3(Tt + gc * tstride, Tt + gp * tstride, mzd_row(M, r0 + t * K + __builtin_ctz(g)), width);
1398 }
1399 }
1400 /* 3. all other rows, in one pass */
1401 for (int r = 0; r < nrows_eff; r++) {
1402 if (r == r0) {
1403 r = rank - 1; /* (the pivot rows of the block) */
1404 continue;
1405 }
1406 word * const row = mzd_row(M, r);
1407 unsigned x[RW_NT_MAX];
1408 for (int t = 0; t < ntab; t++) { /* (the bits first: the XORs change the row's bits in the pivot columns) */
1409 const int lo = t * K, hi = (lo + K < kk) ? lo + K : kk;
1410 unsigned xt = 0;
1411 for (int l = lo; l < hi; l++) xt |= (unsigned)((row[bw[l]] >> bs[l]) & 1) << (l - lo);
1412 x[t] = xt;
1413 }
1414 for (int t = 0; t < ntab; t++) {
1415 if (x[t]) rw_row_xor(row, T + (((size_t)t << K) + x[t]) * tstride, width);
1416 }
1417 }
1418 }
1419 return rank;
1420}
1421
1422/* The RW pairs (p=2), see rw_pairs_bits(): a RW step also finds the codewords with exactly two non-pivot columns a, b,
1423 * the sums of the codewords of a and b (p=1), whose pivot part is the sum of the columns a and b of the reduced matrix.
1424 * The non-pivot columns are sorted by their bits in l random pivot rows (the bucket, as in Stern's algorithm), and only
1425 * the pairs in a bucket are checked. In a bucket, the columns are sorted by weight, so that only the pairs whose
1426 * weights differ by less than the weight limit are checked (the weight of the sum is at least the difference), and the
1427 * columns whose codewords have a zero L syndrome come first: the pairs of two of them are skipped (the codewords of
1428 * most columns of a circuit DEM are light and local, with a zero L syndrome), and so are the pairs with equal L
1429 * syndromes. With at most 64 rows of L, the L syndromes of all columns are known (the rows of L reduced with the pivot
1430 * rows); otherwise, only those of the light columns (a hash, see rw_lhash_init()). */
1431typedef struct {
1432 const word *row; /* the column in the pivot rows (a row of the transposed reduced matrix) */
1433 uint64_t sig; /* the L syndrome of the column's codeword (or its hash, see rw_lhash_init()), if known */
1434 int col; /* the column */
1435 int known; /* 1 if `sig` is known */
1436} rw_pair_t;
1437
1438/* LSD radix sort (8-bit digits) of `n` keys `a` by their lowest `bits` bits (`tmp`: n more) */
1439static void rw_radix_sort(uint64_t * const a, uint64_t * const tmp, const int n, const int bits) {
1440 uint64_t *src = a, *dst = tmp;
1441 for (int sh = 0; sh < bits; sh += 8) {
1442 int cnt[256];
1443 memset(cnt, 0, sizeof(cnt));
1444 for (int i = 0; i < n; i++) cnt[(src[i] >> sh) & 255]++;
1445 for (int d = 0, sum = 0; d < 256; d++) {
1446 const int c = cnt[d];
1447 cnt[d] = sum;
1448 sum += c;
1449 }
1450 for (int i = 0; i < n; i++) dst[cnt[(src[i] >> sh) & 255]++] = src[i];
1451 uint64_t * const t = src;
1452 src = dst;
1453 dst = t;
1454 }
1455 if (src != a) memcpy(a, src, (size_t)n * sizeof(uint64_t));
1456}
1457
1458/* The workspace of a thread for the RW pairs (see rw_pair_t, rw_pairs_step()) */
1459typedef struct {
1460 rw_pair_t *ent; /* the entries of the non-pivot columns (nvar of them) */
1461 uint64_t *srt; /* their sort keys (bucket, zero L syndrome or not, weight, entry), 2 nvar for the radix sort */
1462 int nl; /* the number of rows of L if at most 64 (0: a classical code, or more rows) */
1463 wi_t width; /* the words per row of the RW matrix (with nl > 0) */
1464 word *lrows; /* with nl > 0: the rows of L (nl rows of `width` words) */
1465 word *srows; /* and the rows of L reduced with the pivot rows of a RW step: the L syndromes of the codewords */
1467
1468/* The hashes of the columns of L for the RW pairs (see rw_pair_t): the hash of the L syndrome of a vector is the sum of
1469 * the hashes of its columns. With at most 64 rows of L, the hash of a column is its mask (the syndrome itself);
1470 * otherwise, the sum of 64-bit random hashes of its rows (a non-zero syndrome has the hash zero with probability
1471 * 2^-64). NULL for a classical code. */
1472static uint64_t *rw_lhash_init(const params_t * const p) {
1473 const colmask_t * const cm = p->maskL;
1474 const int nvar = p->spaH->cols;
1475 if (p->classical || !cm || cm->cols < nvar) return NULL;
1476 uint64_t * const h = malloc((size_t)nvar * sizeof(uint64_t));
1477 if (!h) ERROR("memory allocation");
1478 if (cm->kw == 1) {
1479 for (int j = 0; j < nvar; j++) h[j] = cm->mask[j];
1480 } else {
1481 uint64_t * const R = malloc((size_t)cm->kw * 64 * sizeof(uint64_t));
1482 if (!R) ERROR("memory allocation");
1483 uint64_t st = mix64((uint64_t)p->seed ^ 0x2545f4914f6cdd1dULL);
1484 for (int t = 0; t < cm->kw * 64; t++) R[t] = splitmix64(&st);
1485 for (int j = 0; j < nvar; j++) {
1486 uint64_t s = 0;
1487 for (int w = 0; w < cm->kw; w++) {
1488 for (word m = cm->mask[(size_t)j * cm->kw + w]; m; m &= m - 1) s ^= R[w * 64 + __builtin_ctzll(m)];
1489 }
1490 h[j] = s;
1491 }
1492 free(R);
1493 }
1494 return h;
1495}
1496
1497/* The workspace of the RW pairs of a thread (`ws->ent` NULL without pairs), for the RW matrix `mH` (`nvar` columns) */
1498static void rw_pairs_ws_init(rw_pairs_ws_t * const ws, const distfork_ctx_t * const ctx, const mzd_t * const mH,
1499 const int nvar) {
1500 memset(ws, 0, sizeof(*ws));
1501 if (ctx->p->pairs == 0) return;
1502 ws->ent = malloc((size_t)nvar * sizeof(rw_pair_t));
1503 ws->srt = malloc((size_t)2 * nvar * sizeof(uint64_t));
1504 if (!ws->ent || !ws->srt) ERROR("memory allocation");
1505 const colmask_t * const cm = ctx->p->maskL;
1506 if (ctx->rw_lhash && cm->kw == 1 && cm->rows > 0) {
1507 ws->nl = cm->rows;
1508 ws->width = mH->width;
1509 ws->lrows = calloc((size_t)ws->nl * ws->width, sizeof(word));
1510 ws->srows = malloc((size_t)ws->nl * ws->width * sizeof(word));
1511 if (!ws->lrows || !ws->srows) ERROR("memory allocation");
1512 for (int j = 0; j < nvar; j++) {
1513 for (word m = cm->mask[j]; m; m &= m - 1)
1514 ws->lrows[(size_t)__builtin_ctzll(m) * ws->width + (j >> 6)] |= (word)1 << (j & 63);
1515 }
1516 }
1517}
1518
1519static void rw_pairs_ws_free(rw_pairs_ws_t * const ws) {
1520 free(ws->ent);
1521 free(ws->srt);
1522 free(ws->lrows);
1523 free(ws->srows);
1524 memset(ws, 0, sizeof(*ws));
1525}
1526
1527/* A codeword of weight `cnt` found by RW is a hit (see rw_hits1, rw_hits2) if not heavier than the upper bound */
1528static inline bool rw_is_hit(distfork_ctx_t * const ctx, const int cnt) {
1529 const int dmax = atomic_load_explicit(&ctx->dmax, memory_order_relaxed);
1530 return (dmax <= 0) || (cnt <= dmax);
1531}
1532
1533/* Weight limit (exclusive) of the codewords of the RW pairs: as for the single columns (rw_cw_limit()), but at most the
1534 * upper bound plus one (plus dW with outC or maxC), and 0 without an upper bound: the pairs would otherwise give far
1535 * too many candidates (of any weight, or the heavier codewords of the `min_hits` statistic) */
1536static inline int rw_pairs_limit(distfork_ctx_t * const ctx) {
1537 const params_t * const p = ctx->p;
1538 const int dmax = atomic_load_explicit(&ctx->dmax, memory_order_relaxed);
1539 if (dmax <= 0) return 0;
1540 long long int cap = (long long int)dmax + 1;
1541 if ((p->outC != NULL || p->maxC > 0) && p->dW > 0) cap += p->dW;
1542 const int lim = rw_cw_limit(ctx);
1543 return (lim < cap) ? lim : (int)cap;
1544}
1545
1546/* The pairs visited between the polls of the stop flags and of the upper bound in rw_pairs_step(): with few rows l, a
1547 * bucket can hold many columns (e.g., with pairs=1, two buckets of (n - r)/2 columns each), so that a step would
1548 * otherwise not end in time at a timeout */
1549#define RW_PAIRS_POLL 65536
1550
1551/* The pairs of a RW step (see rw_pair_t), after its single columns: the reduced matrix `mH` (Gauss-Jordan: `rank` pivot
1552 * rows, whose pivot columns are `pivs`) and its transpose `mHT` (row c: the column c in the pivot rows), `l` pivot rows
1553 * of the buckets, the workspace `ws`, and the support `ee`; `src` as in rw_record_codeword(). Adds the number of the
1554 * pairs checked to `*checked`, and of the hits to `*hits` (see rw_is_hit()); returns l (0: no pairs). The bucket of a
1555 * column is a hash of its l bits (64 bits; the sort uses its top bits: the buckets with different bits are merged
1556 * rarely, which only adds pairs). */
1557static int rw_pairs_step(distfork_ctx_t * const ctx, mzd_t * const mH, mzd_t * const mHT,
1558 const rci_t * const pivs, const word * const piv_mask, const int rank, int l,
1559 rw_pairs_ws_t * const ws, rci_t * const ee, uint64_t * const rng_state, const int tid,
1560 const int src, long long * const checked, long long * const hits) {
1561 params_t * const p = ctx->p;
1562 const colmask_t * const maskL = p->maskL;
1563 const int classical = p->classical;
1564 const uint64_t * const lhash = ctx->rw_lhash;
1565 const int nvar = p->spaH->cols;
1566 rw_pair_t * const ent = ws->ent;
1567 uint64_t * const srt = ws->srt;
1568 int lim = rw_pairs_limit(ctx);
1569 if (lim < 3 || l < 1 || l > rank) return 0; /* (the weight of a pair is at least 2) */
1570 /* the sort keys: the top kb bits of the bucket's hash, the bit "the L syndrome is not known to be zero", the weight
1571 * (wb bits), and the entry (ib bits) */
1572 int wb = 1, ib = 1;
1573 while ((1L << wb) <= (long)rank) wb++;
1574 while ((1L << ib) < (long)(nvar - rank)) ib++;
1575 const int kb = (l + 8 < 63 - wb - ib) ? l + 8 : 63 - wb - ib;
1576 if (kb < 1) return 0;
1577 const int sh_grp = ib + wb, sh_key = sh_grp + 1;
1578 const uint64_t wmask = ((uint64_t)1 << wb) - 1, imask = ((uint64_t)1 << ib) - 1;
1579 const int aw = (rank + 63) >> 6;
1580 const word last_mask = (rank & 63) ? (((word)1 << (rank & 63)) - 1) : ~(word)0; /* bits of rows < rank */
1581 /* l distinct random pivot rows, as the masks `kmask` of the words `kword` of the rows */
1582 int nk = 0, kword[RW_PAIRS_MAX_BITS];
1583 word kmask[RW_PAIRS_MAX_BITS];
1584 for (int i = 0; i < l; i++) {
1585 int r, j;
1586 do {
1587 r = rand_uniform_thread(rank, rng_state);
1588 for (j = 0; j < nk && kword[j] != (r >> 6); j++) continue;
1589 } while (j < nk && ((kmask[j] >> (r & 63)) & 1));
1590 if (j == nk) {
1591 kword[nk] = r >> 6;
1592 kmask[nk++] = 0;
1593 }
1594 kmask[j] |= (word)1 << (r & 63);
1595 }
1596 /* the L syndromes of the codewords of all columns (with at most 64 rows of L): the rows of L reduced with the pivot
1597 * rows (`lhash` is then the column masks of L) */
1598 const int nl = ws->nl;
1599 const wi_t width = ws->width;
1600 if (nl > 0) {
1601 memcpy(ws->srows, ws->lrows, (size_t)nl * width * sizeof(word));
1602 for (int i = 0; i < rank; i++) {
1603 word g = lhash[pivs[i]];
1604 if (!g) continue;
1605 const word * const ri = mzd_row(mH, i);
1606 for (; g; g &= g - 1) rw_row_xor(ws->srows + (size_t)__builtin_ctzll(g) * width, ri, width);
1607 }
1608 }
1609 const int wsig = lim + 1; /* (otherwise) the L syndromes of the columns up to this weight */
1610 int K = 0;
1611 for (int col = 0; col < nvar; col++) {
1612 if ((piv_mask[col >> 6] >> (col & 63)) & 1) continue;
1613 const word * const row = mzd_row(mHT, col);
1614 int wt = 0;
1615 for (int iw = 0; iw < aw - 1; iw++) wt += __builtin_popcountll(row[iw]);
1616 wt += __builtin_popcountll(row[aw - 1] & last_mask);
1617 uint64_t key = 0;
1618 for (int j = 0; j < nk; j++) key = (key ^ (row[kword[j]] & kmask[j])) * 0x9e3779b97f4a7c15ULL;
1619 key >>= 64 - kb;
1620 uint64_t sig = 0;
1621 int known = 0;
1622 if (nl > 0) {
1623 const word * const sc = ws->srows + (col >> 6);
1624 for (int t = 0; t < nl; t++) sig |= ((sc[(size_t)t * width] >> (col & 63)) & 1) << t;
1625 known = 1;
1626 } else if (lhash && wt <= wsig) {
1627 sig = lhash[col];
1628 for (int iw = 0; iw < aw; iw++) {
1629 for (word x = (iw < aw - 1) ? row[iw] : (row[iw] & last_mask); x; x &= x - 1)
1630 sig ^= lhash[pivs[(iw << 6) + __builtin_ctzll(x)]];
1631 }
1632 known = 1;
1633 }
1634 const uint64_t grp = !known || (sig != 0);
1635 ent[K].row = row;
1636 ent[K].sig = sig;
1637 ent[K].col = col;
1638 ent[K].known = known;
1639 srt[K] = ((uint64_t)key << sh_key) | (grp << sh_grp) | ((uint64_t)wt << ib) | (uint64_t)K;
1640 K++;
1641 }
1642 rw_radix_sort(srt, srt + nvar, K, sh_key + kb);
1643
1644 /* with one row of L (known syndromes), the other columns have the same syndrome: only their pairs with the columns
1645 * with zero syndromes */
1646 const bool other_pairs = (nl != 1);
1647 const bool irred = (p->irred != 0);
1648 long long int nchk = 0, nhits = 0, nvis = 0, next_poll = RW_PAIRS_POLL; /* nvis: the pairs visited, also skipped */
1649 long long int nred = 0; /* irred=1: the pairs of disjoint circuits skipped (reducible sums) */
1650 for (int b0 = 0; b0 < K;) {
1651 const uint64_t bkey = srt[b0] >> sh_key;
1652 int b1 = b0 + 1;
1653 while (b1 < K && (srt[b1] >> sh_key) == bkey) b1++;
1654 if (b1 - b0 >= 2) {
1655 if (atomic_load_explicit(&ctx->stop_flag, memory_order_relaxed) ||
1656 atomic_load_explicit(&ctx->rw_stop_flag, memory_order_relaxed)) break;
1657 lim = rw_pairs_limit(ctx); /* (the upper bound may have decreased) */
1658 if (lim < 3) break;
1659 int lx = lim - 3; /* the largest weight of the sum of the two columns */
1660 int g0 = b0; /* [b0, g0): the columns with zero L syndromes; [g0, b1): the others */
1661 while (g0 < b1 && !((srt[g0] >> sh_grp) & 1)) g0++;
1662 int lo = g0;
1663 const int x_end = other_pairs ? b1 - 1 : g0;
1664 for (int x = b0; x < x_end; x++) {
1665 if (nvis >= next_poll) { /* a bucket can be large (few rows l): the stop flags and the upper bound, also here */
1666 next_poll = nvis + RW_PAIRS_POLL;
1667 if (atomic_load_explicit(&ctx->stop_flag, memory_order_relaxed) ||
1668 atomic_load_explicit(&ctx->rw_stop_flag, memory_order_relaxed)) goto done;
1669 lim = rw_pairs_limit(ctx);
1670 if (lim < 3) goto done;
1671 lx = lim - 3;
1672 }
1673 const int wx = (int)((srt[x] >> ib) & wmask);
1674 const rw_pair_t * const ex = ent + (srt[x] & imask);
1675 int y = x + 1;
1676 if (x < g0) { /* (a column with a zero L syndrome: only the pairs with the others) */
1677 while (lo < b1 && (int)((srt[lo] >> ib) & wmask) < wx - lx) lo++;
1678 y = lo;
1679 }
1680 const int y0 = y;
1681 for (; y < b1; y++) {
1682 const int wy = (int)((srt[y] >> ib) & wmask);
1683 if (wy - wx > lx) break;
1684 const rw_pair_t * const ey = ent + (srt[y] & imask);
1685 if (x >= g0 && ex->known && ey->known && ex->sig == ey->sig) continue;
1686 const word * const ra = ex->row;
1687 const word * const rb = ey->row;
1688 nchk++;
1689 int c = 0;
1690 for (int iw = 0; iw < aw - 1 && c <= lx; iw++) c += __builtin_popcountll(ra[iw] ^ rb[iw]);
1691 if (c <= lx) c += __builtin_popcountll((ra[aw - 1] ^ rb[aw - 1]) & last_mask);
1692 if (c > lx) continue;
1693 /* irred=1: the sum of the two circuits is reducible iff they are disjoint, i.e., the weight of the sum of
1694 * their pivot parts is wx + wy (a kernel vector on the pivots and the two columns is a sum of the two
1695 * circuits, so that the circuits are the only candidates for a proper subset with zero syndrome) */
1696 if (irred && c == wx + wy) {
1697 nred++;
1698 continue;
1699 }
1700 int cnt = 0;
1701 ee[cnt++] = ex->col;
1702 ee[cnt++] = ey->col;
1703 for (int iw = 0; iw < aw; iw++) {
1704 for (word z = (ra[iw] ^ rb[iw]) & ((iw < aw - 1) ? ~(word)0 : last_mask); z; z &= z - 1)
1705 ee[cnt++] = pivs[(iw << 6) + __builtin_ctzll(z)];
1706 }
1707 if (classical || colmask_syndrome_non_zero(maskL, cnt, ee)) {
1708 rci_quick_sort(ee, cnt);
1709 if (rw_is_hit(ctx, cnt)) nhits++;
1710 rw_record_codeword(ctx, ee, cnt, tid, 0, src);
1711 lim = rw_pairs_limit(ctx);
1712 if (lim < 3) goto done;
1713 lx = lim - 3;
1714 }
1715 }
1716 nvis += y - y0;
1717 }
1718 }
1719 b0 = b1;
1720 }
1721done:
1722 *checked += nchk;
1723 *hits += nhits;
1724 if (nred > 0) atomic_fetch_add_explicit(&ctx->cw_red_rw, nred, memory_order_relaxed);
1725 return l;
1726}
1727
1728/* Run RW batch of the global steps step0 .. step0+n_steps-1; returns the number of completed steps. `el`: the thread's
1729 * RW elimination (blocked or column by column, see rw_elim_t); `pws`: the workspace of the RW pairs (see
1730 * rw_pairs_ws_init()) */
1731static int run_rw_steps(distfork_ctx_t *ctx, long step0, int n_steps,
1732 mzd_t *mH, mzd_t *mHT, rci_t *ee,
1733 mzp_t *perm, mzp_t *pivs, word *piv_mask, rw_elim_t *el, rw_pairs_ws_t *pws,
1734 int *eff_nrows_ptr,
1735 int *visited_cols, int *visited_checks, int *col_queue,
1736 int *visit_marker, uint64_t *rng_state, int tid) {
1737 params_t * const p = ctx->p;
1738 const colmask_t * const maskL = p->maskL; /* NULL for a classical code */
1739 const int nvar = p->spaH->cols;
1740 const int classical = p->classical;
1741 const int kwin = p->kwin;
1742 const int win_mode = p->win_mode;
1743 int eff_nrows = *eff_nrows_ptr;
1744 int n_done = 0;
1745 long n_unif = 0; /* completed steps with uniform random permutations (for the information-set estimate) */
1746 long long int n_pairs = 0, hits1 = 0, hits2 = 0; /* see rw_pairs_checked, rw_hits1, rw_hits2 */
1747 double t_last = get_time_sec(), acc_t = 0.0;
1748 int acc_n = 0;
1749
1750 for (int step = 0; step < n_steps; step++) {
1751 if (atomic_load_explicit(&ctx->stop_flag, memory_order_relaxed) ||
1752 atomic_load_explicit(&ctx->rw_stop_flag, memory_order_relaxed)) {
1753 break;
1754 }
1755
1756 int eff_kwin = kwin;
1757 int eff_win_mode = win_mode;
1758 if (eff_kwin == 0 && nvar >= 500 && ((step0 + step) & 1) == 1) { /* hybrid: every other global step */
1759 eff_kwin = hybrid_window(p, nvar);
1760 eff_win_mode = 1;
1761 }
1762 const bool uniform = !(eff_kwin > 0 && eff_kwin < nvar);
1763
1764 if (eff_kwin > 0 && eff_kwin < nvar) {
1765 int seed_col = rand_uniform_thread(nvar, rng_state);
1766 localized_window_perm(perm, nvar, seed_col, eff_kwin, eff_win_mode,
1767 p->spaH, ctx->mHT_cc,
1768 visited_cols, visited_checks, col_queue,
1769 visit_marker, rng_state);
1770 } else {
1771 pivs = mzp_rand_thread(pivs, nvar, rng_state);
1772 mzp_set_ui(perm, 1);
1773 perm = perm_p_trans(perm, pivs, 0);
1774 }
1775
1776 memset(piv_mask, 0, mH->width * sizeof(word));
1777 /* the elimination (see rw_elim_t): column by column, or blocked */
1778 int timed = -1; /* tuning: time this step's elimination, column by column (0) or blocked (1) */
1779 if (el->choice < 0 && el->steps > 0) timed = el->steps & 1;
1780 const double t_elim = (timed >= 0) ? get_time_sec() : 0.0;
1781 int rank = 0;
1782 if (el->choice == 0 || timed == 0) {
1783 for (int i = 0; i < nvar && rank < eff_nrows; i++) {
1784 /* a step on a large matrix can take many seconds: check the stop flags also every 16 columns */
1785 if ((i & 15) == 15 && (atomic_load_explicit(&ctx->stop_flag, memory_order_relaxed) ||
1786 atomic_load_explicit(&ctx->rw_stop_flag, memory_order_relaxed))) {
1787 rank = -1;
1788 break;
1789 }
1790 int col = perm->values[i];
1791 if (gauss_one_rows(mH, col, rank, eff_nrows)) {
1792 pivs->values[rank++] = col;
1793 piv_mask[col >> 6] |= (word)1 << (col & 63);
1794 }
1795 }
1796 } else {
1797 rank = rw_eliminate(ctx, mH, eff_nrows, nvar, perm->values, pivs->values, piv_mask, el);
1798 }
1799 /* abandon the step: `eff_nrows` is kept, as rows rank..eff_nrows-1 are not processed (same row space) */
1800 if (rank < 0) break;
1801 if (el->choice < 0) {
1802 if (timed >= 0) el->t[timed] += get_time_sec() - t_elim;
1803 if (++el->steps > 2 * RW_TUNE_STEPS) {
1804 el->choice = (el->t[1] < el->t[0]) ? 1 : 0;
1805 if (tid == 0 && (p->debug & DBG_TIMING)) {
1806 fprintf(stderr, "# [thread 0] RW elimination: %s (blocked: %.2fx the time of column by column)\n",
1807 el->choice ? "blocked" : "column by column", el->t[1] / (el->t[0] > 0.0 ? el->t[0] : 1e-9));
1808 }
1809 }
1810 }
1811 eff_nrows = rank;
1812 *eff_nrows_ptr = eff_nrows;
1813 if (atomic_load_explicit(&ctx->rw_rank, memory_order_relaxed) != rank) { /* rank of H (the same in every step) */
1814 atomic_store_explicit(&ctx->rw_rank, rank, memory_order_relaxed);
1815 }
1816
1817 mzd_transpose(mHT, mH);
1818
1819 const int active_width = (rank + 63) >> 6;
1820 const word last_mask = (rank & 63) ? (((word)1 << (rank & 63)) - 1) : ~(word)0; /* bits of rows < rank */
1821 for (int col = 0; col < nvar; col++) {
1822 if ((piv_mask[col >> 6] >> (col & 63)) & 1) continue;
1823 const int limit = rw_cw_limit(ctx);
1824 word *rawrow = mzd_row(mHT, col);
1825 /* the weight 1 + (the number of pivot rows with a 1 in column col) first: word-wise popcount, which ends as soon
1826 * as the weight reaches the limit (most candidates are much heavier) */
1827 int wt = 1;
1828 for (int iw = 0; iw < active_width - 1 && wt < limit; iw++) wt += __builtin_popcountll(rawrow[iw]);
1829 if (wt < limit && active_width > 0) wt += __builtin_popcountll(rawrow[active_width - 1] & last_mask);
1830 if (wt >= limit) continue;
1831
1832 int cnt = 0;
1833 ee[cnt++] = col;
1834 rci_t j = -1;
1835 while (cnt < wt) {
1836 j = nextelement(rawrow, active_width, j);
1837 if (j == -1 || j >= rank) break;
1838 ee[cnt++] = pivs->values[j++];
1839 }
1840
1841 /* the check L c != 0 needs no sorted support: only the codewords recorded are sorted (hash key) */
1842 if (classical || colmask_syndrome_non_zero(maskL, cnt, ee)) {
1843 rci_quick_sort(ee, cnt);
1844 if (rw_is_hit(ctx, cnt)) hits1++;
1845 rw_record_codeword(ctx, ee, cnt, tid, 0, uniform ? CW_SRC_RW_UNIFORM : CW_SRC_RW_WINDOW);
1846 }
1847 }
1848 if (pws->ent) { /* the pairs of non-pivot columns (p=2) */
1849 int l = rw_pairs_bits(p, nvar, rank, atomic_load_explicit(&ctx->dmax, memory_order_relaxed));
1850 if (l > 0)
1851 l = rw_pairs_step(ctx, mH, mHT, pivs->values, piv_mask, rank, l, pws, ee, rng_state, tid,
1852 uniform ? CW_SRC_RW_UNIFORM : CW_SRC_RW_WINDOW, &n_pairs, &hits2);
1853 if (l > 0 && atomic_load_explicit(&ctx->rw_pairs_l, memory_order_relaxed) != l)
1854 atomic_store_explicit(&ctx->rw_pairs_l, l, memory_order_relaxed);
1855 }
1856 atomic_fetch_add(&ctx->rw_steps_completed, 1);
1857 n_done++;
1858 if (uniform) n_unif++;
1859 rw_time_step(ctx, &t_last, &acc_t, &acc_n, n_done == 1);
1860 }
1861 rw_time_publish(ctx, &acc_t, &acc_n);
1862 atomic_fetch_add_explicit(&ctx->rw_unif_steps, n_unif, memory_order_relaxed);
1863 if (n_pairs > 0) atomic_fetch_add_explicit(&ctx->rw_pairs_checked, n_pairs, memory_order_relaxed);
1864 if (hits1 > 0) atomic_fetch_add_explicit(&ctx->rw_hits1, hits1, memory_order_relaxed);
1865 if (hits2 > 0) atomic_fetch_add_explicit(&ctx->rw_hits2, hits2, memory_order_relaxed);
1866 return n_done;
1867}
1868
1869/* After a completed RW step, only the first `rank` = rank(H) rows of the reduced thread-local matrix `*mH` are non-zero
1870 * (they span the rows of H), and the later steps eliminate only these rows: keep just these rows, so that the
1871 * transposition in each RW step skips the zero rows (for H with redundant rows) */
1872static void rw_shrink_matrices(mzd_t **mH, mzd_t **mHT, const int rank) {
1873 pthread_mutex_lock(&m4ri_mem_mutex);
1874 mzd_t * const top = mzd_init_window(*mH, 0, 0, rank, (*mH)->ncols);
1875 mzd_t * const small = mzd_copy(NULL, top);
1876 mzd_free_window(top);
1877 mzd_free(*mH);
1878 *mH = small;
1879 mzd_free(*mHT);
1880 *mHT = mzd_init(small->ncols, rank);
1881 pthread_mutex_unlock(&m4ri_mem_mutex);
1882}
1883
1884/* Whether row `r` of N has a non-zero entry in a column of the current RW window (visited_cols[j] == marker) */
1885static inline bool ksub_row_in_window(const mzd_t * const N, const int r, const int nvar,
1886 const int * const visited_cols, const int marker) {
1887 const word * const raw_n = mzd_row_cons(N, r);
1888 for (int j = nextelement(raw_n, N->width, -1); j >= 0 && j < nvar; j = nextelement(raw_n, N->width, j + 1)) {
1889 if (visited_cols[j] == marker) return true;
1890 }
1891 return false;
1892}
1893
1894/* Run compact subspace RW batch (ksub > 0) of the global steps step0 .. step0+n_steps-1; returns the number of
1895 * completed steps. `nidx` is the thread's permutation of the row indices 0..nu-1 of N, used to sample ksub distinct
1896 * rows (partial Fisher-Yates shuffle) */
1897static int run_rw_steps_ksub(distfork_ctx_t *ctx, long step0, int n_steps,
1898 mzd_t *M_sub, rci_t *ee, mzp_t *perm, mzp_t *pivs, int *nidx,
1899 int *visited_cols, int *visited_checks, int *col_queue,
1900 int *visit_marker, uint64_t *rng_state, int tid) {
1901 params_t * const p = ctx->p;
1902 const colmask_t * const maskL = p->maskL; /* NULL for a classical code */
1903 const int nvar = p->spaH->cols;
1904 const int classical = p->classical;
1905 const int nu = ctx->nu;
1906 const int ksub = M_sub->nrows;
1907 const int kwin = p->kwin;
1908 const int win_mode = p->win_mode;
1909
1910 if (nu <= 0 || ksub <= 0 || !ctx->N_global) { /* not reached: no subspace to sample, count the steps as done */
1911 atomic_fetch_add(&ctx->rw_steps_completed, n_steps);
1912 return 0;
1913 }
1914
1915 int n_done = 0;
1916 double t_last = get_time_sec(), acc_t = 0.0;
1917 int acc_n = 0;
1918 pthread_rwlock_rdlock(&ctx->basis_rwlock);
1919 const mzd_t * const N = ctx->N_global;
1920
1921 for (int step = 0; step < n_steps; step++) {
1922 if (atomic_load_explicit(&ctx->stop_flag, memory_order_relaxed) ||
1923 atomic_load_explicit(&ctx->rw_stop_flag, memory_order_relaxed)) {
1924 break;
1925 }
1926
1927 /* 1. Generate column permutation (localized window or uniform) */
1928 int eff_kwin = kwin;
1929 int eff_win_mode = win_mode;
1930 if (eff_kwin == 0 && nvar >= 500 && ((step0 + step) & 1) == 1) { /* hybrid: every other global step */
1931 eff_kwin = hybrid_window(p, nvar);
1932 eff_win_mode = 1;
1933 }
1934
1935 if (eff_kwin > 0 && eff_kwin < nvar) {
1936 int seed_col = rand_uniform_thread(nvar, rng_state);
1937 localized_window_perm(perm, nvar, seed_col, eff_kwin, eff_win_mode,
1938 p->spaH, ctx->mHT_cc,
1939 visited_cols, visited_checks, col_queue,
1940 visit_marker, rng_state);
1941 } else {
1942 pivs = mzp_rand_thread(pivs, nvar, rng_state);
1943 mzp_set_ui(perm, 1);
1944 perm = perm_p_trans(perm, pivs, 0);
1945 }
1946
1947 /* 2. Sample ksub distinct rows of N into M_sub: partial Fisher-Yates shuffle of the row indices (rows drawn
1948 * with replacement would repeat and reduce the rank), with up to 4 redraws to prefer rows overlapping the window */
1949 const int marker = *visit_marker;
1950 const bool in_window = (eff_kwin > 0 && eff_kwin < nvar && visited_cols);
1951 for (int i = 0; i < ksub; i++) {
1952 int j = i + rand_uniform_thread(nu - i, rng_state);
1953 for (int attempt = 0; in_window && attempt < 4 && !ksub_row_in_window(N, nidx[j], nvar, visited_cols, marker);
1954 attempt++) {
1955 j = i + rand_uniform_thread(nu - i, rng_state);
1956 }
1957 SWAPINT(nidx[i], nidx[j]);
1958 mzd_copy_row(M_sub, i, N, nidx[i]);
1959 }
1960
1961 /* 3. Echelonize M_sub directly in L1/L2 cache using permuted column order */
1962 int rank = 0;
1963 for (int i = 0; i < nvar && rank < ksub; i++) {
1964 int col = perm->values[i];
1965 if (gauss_one(M_sub, col, rank)) {
1966 rank++;
1967 }
1968 }
1969
1970 /* 4. Extract candidate codewords directly from reduced rows (already in sorted order) */
1971 for (int ir = 0; ir < rank; ir++) {
1972 int cnt = 0;
1973 const int limit = rw_cw_limit(ctx);
1974
1975 word *rawrow = mzd_row(M_sub, ir);
1976 rci_t j = -1;
1977 while (cnt < limit) {
1978 j = nextelement(rawrow, M_sub->width, j);
1979 if (j == -1 || j >= nvar) break;
1980 ee[cnt++] = j++;
1981 }
1982
1983 if (cnt > 0 && cnt < limit && (classical || colmask_syndrome_non_zero(maskL, cnt, ee))) {
1984 /* irred=1: a reduced row of the sampled subspace is not a circuit, so it can be reducible */
1985 if (!cw_irreducible(ctx, ctx->wargs[tid].irr_ws, cnt, ee)) {
1986 atomic_fetch_add_explicit(&ctx->cw_red_rw, 1, memory_order_relaxed);
1987 continue;
1988 }
1989 rw_record_codeword(ctx, ee, cnt, tid, ksub,
1990 (eff_kwin > 0 && eff_kwin < nvar) ? CW_SRC_RW_WINDOW : CW_SRC_RW_UNIFORM);
1991 }
1992 }
1993 atomic_fetch_add(&ctx->rw_steps_completed, 1);
1994 n_done++;
1995 rw_time_step(ctx, &t_last, &acc_t, &acc_n, n_done == 1);
1996 }
1997 rw_time_publish(ctx, &acc_t, &acc_n);
1998
1999 pthread_rwlock_unlock(&ctx->basis_rwlock);
2000
2001 /* 5. Periodic adaptive basis refresh if enabled */
2002 if (p->refresh > 0 && !atomic_load_explicit(&ctx->stop_flag, memory_order_relaxed) &&
2003 !atomic_load_explicit(&ctx->rw_stop_flag, memory_order_relaxed)) {
2004 long done = atomic_load_explicit(&ctx->rw_steps_completed, memory_order_relaxed);
2005 long next_ref = atomic_load_explicit(&ctx->next_refresh_step, memory_order_relaxed);
2006 if (done >= next_ref && next_ref > 0) {
2007 if (atomic_compare_exchange_strong(&ctx->next_refresh_step, &next_ref,
2008 done + p->refresh)) {
2009 int cw_wt = 0;
2010 pthread_mutex_lock(&ctx->cw_mutex);
2011 if (p->codewords && p->min_w < nvar) {
2012 cw_vec_t *cw, *tmp;
2013 HASH_ITER(hh, p->codewords, cw, tmp) {
2014 if (cw->weight == p->min_w) {
2015 cw_wt = cw->weight;
2016 for (int k = 0; k < cw_wt; k++) ee[k] = cw->arr[k];
2017 break;
2018 }
2019 }
2020 }
2021 pthread_mutex_unlock(&ctx->cw_mutex);
2022
2023 pthread_rwlock_wrlock(&ctx->basis_rwlock);
2024 pthread_mutex_lock(&m4ri_mem_mutex);
2025 refresh_nullspace_basis(ctx->N_global, cw_wt > 0 ? ee : NULL, cw_wt, rng_state);
2026 pthread_mutex_unlock(&m4ri_mem_mutex);
2027 pthread_rwlock_unlock(&ctx->basis_rwlock);
2028 }
2029 }
2030 }
2031 return n_done;
2032}
2033
2034/* Worker thread main loop */
2035static void *worker_thread_func(void *arg) {
2036 worker_arg_t *warg = (worker_arg_t *)arg;
2037 distfork_ctx_t *ctx = warg->ctx;
2038 int tid = warg->tid;
2039 const int nvar = ctx->p->spaH->cols;
2040 /* only workers 0..rw_threads-1 run RW (method 1 or 3) and allocate dense RW matrices; all workers may run CC */
2041 const bool enable_rw = ((ctx->p->method & 1) != 0) && (tid < ctx->rw_threads);
2042 const bool use_ksub = enable_rw && (ctx->p->ksub > 0);
2043 const int ksub_eff = (use_ksub && ctx->nu > 0) ? minint(ctx->p->ksub, ctx->nu) : 0;
2044 /* (warg->min_swei is initialized by the main thread before the workers are launched) */
2045
2046 /* Thread-local RW matrices (allocated safely only if RW is enabled) */
2047 mzd_t *mH = NULL;
2048 mzd_t *mHT_rw = NULL;
2049 mzd_t *M_sub = NULL;
2050 int *nidx = NULL; /* ksub: permutation of the row indices of N, for sampling distinct rows */
2051 rci_t *ee = NULL;
2052 mzp_t *perm = NULL;
2053 mzp_t *pivs = NULL;
2054 word *piv_mask = NULL;
2055 rw_elim_t rw_el = {NULL, 0, 0, 0, 0, {0.0, 0.0}, 0}; /* the RW elimination of this thread */
2056 rw_pairs_ws_t rw_pws; /* the RW pairs of this thread (see rw_pairs_ws_init()) */
2057 memset(&rw_pws, 0, sizeof(rw_pws));
2058 int eff_nrows = ctx->p->spaH->rows;
2059 int *visited_cols = NULL;
2060 int *visited_checks = NULL;
2061 int *col_queue = NULL;
2062 int visit_marker = 0;
2063 /* RNG stream of this thread: a hashed starting point of splitmix64 (consecutive starting points would give
2064 * shifted copies of one stream) */
2065 uint64_t rng_state = mix64((uint64_t)ctx->p->seed ^ mix64((uint64_t)tid + 0x517cc1b727220a95ULL));
2066
2067 if (enable_rw) {
2068 ee = malloc((nvar + 2) * sizeof(rci_t));
2069 perm = safe_mzp_init(nvar);
2070 pivs = safe_mzp_init(nvar);
2071 if (ctx->p->kwin > 0 || nvar >= 500) {
2072 visited_cols = calloc(nvar, sizeof(int));
2073 visited_checks = calloc(ctx->p->spaH->rows, sizeof(int));
2074 col_queue = calloc(nvar, sizeof(int));
2075 }
2076 if (use_ksub) {
2077 if (ksub_eff > 0) {
2078 M_sub = safe_mzd_init(ksub_eff, nvar);
2079 nidx = malloc((size_t)ctx->nu * sizeof(int));
2080 if (!nidx) ERROR("memory allocation");
2081 for (int i = 0; i < ctx->nu; i++) nidx[i] = i;
2082 }
2083 } else {
2084 mH = safe_mzd_from_csr(NULL, ctx->p->spaH);
2085 mHT_rw = safe_mzd_init(nvar, ctx->p->spaH->rows);
2086 piv_mask = calloc(mH->width, sizeof(word));
2087 /* the tables of rw_eliminate(), with its parameters for this matrix (64-byte aligned rows) */
2088 rw_el.tstride = ((size_t)mH->width + 7) & ~(size_t)7;
2089 rw_block_params(mH->nrows, mH->width, &rw_el.k, &rw_el.nt);
2090 rw_el.choice = (rw_el.nt == 0) ? 0 : ((mH->nrows >= 512) ? 1 : -1); /* (tuning between 128 and 512 rows) */
2091 const size_t tbytes = (rw_el.nt > 0 ? ((size_t)rw_el.nt << rw_el.k) : 1) * rw_el.tstride * sizeof(word);
2092 rw_el.table = aligned_alloc(64, tbytes);
2093 if (!piv_mask || !rw_el.table) ERROR("memory allocation");
2094 memset(rw_el.table, 0, tbytes);
2095 rw_pairs_ws_init(&rw_pws, ctx, mH, nvar);
2096 }
2097 }
2098
2099 /* Thread-local CC memory (only for CC, method 2 or 3) */
2100 const int wmax_alloc = MAX_W - 1;
2101 one_vec_t *err = NULL, *urr = NULL, **syn = NULL;
2102 cc_walk_t walk; /* the CC walk with bitset syndromes (smax = 0: ctx->cc_hash, see cc_walk()) */
2103 memset(&walk, 0, sizeof(walk));
2104 if (ctx->p->method >= 2) {
2105 err = calloc(1, sizeof(one_vec_t) + sizeof(int) * (wmax_alloc + 2));
2106 urr = calloc(1, sizeof(one_vec_t) + sizeof(int) * (wmax_alloc + 2));
2107 syn = calloc(wmax_alloc + 3, sizeof(one_vec_t *));
2108 if (!err || !urr || !syn) ERROR("memory allocation");
2109 for (int i = 0; i <= wmax_alloc + 2; i++) {
2110 syn[i] = calloc(1, sizeof(one_vec_t) + sizeof(int) * (ctx->p->spaH->rows + 1));
2111 if (!syn[i]) ERROR("memory allocation");
2112 }
2113 if (ctx->cc_hash) {
2114 walk.ctx = ctx;
2115 walk.warg = warg;
2116 walk.mH = ctx->p->spaH;
2117 walk.mHT = ctx->mHT_cc;
2118 walk.hs = ctx->cc_hash;
2119 walk.m = ctx->p->spaH->rows;
2120 walk.max_col_wt = ctx->max_col_W;
2121 walk.S = calloc((size_t)(walk.m / 64 + 2), sizeof(uint64_t));
2122 walk.E = calloc((size_t)(nvar / 64 + 2), sizeof(uint64_t));
2123 walk.cols = calloc(wmax_alloc + 3, sizeof(int));
2124 walk.cw = calloc(1, sizeof(one_vec_t) + sizeof(int) * (wmax_alloc + 3));
2125 if (!walk.S || !walk.E || !walk.cols || !walk.cw) ERROR("memory allocation");
2126 walk.prune = ctx->p->cc_prune;
2127 if (walk.prune > 0) { /* the branch and bound, see cc_bnb() */
2128 const int B = (ctx->cc_B > 0) ? ctx->cc_B : 1;
2129 walk.max_row_wt = B;
2130 walk.acnt_col = INT_MIN;
2131 walk.X = calloc((size_t)(nvar / 64 + 2), sizeof(uint64_t));
2132 walk.acnt = calloc((size_t)walk.m + 1, sizeof(int));
2133 walk.kfirst = calloc((size_t)walk.m + 1, sizeof(int));
2134 walk.fstk = malloc(((size_t)nvar + 1) * sizeof(int));
2135 walk.brl = malloc((size_t)(wmax_alloc + 3) * (size_t)B * sizeof(int));
2136 walk.ulist = malloc(((size_t)walk.m + 1) * sizeof(int));
2137 walk.uacnt = malloc(((size_t)walk.m + 1) * sizeof(int));
2138 walk.ord = malloc(((size_t)walk.m + 1) * sizeof(int));
2139 walk.bcnt = malloc(((size_t)B + 2) * sizeof(int));
2140 walk.mark = calloc((size_t)nvar + 1, sizeof(uint32_t));
2141 if (!walk.X || !walk.acnt || !walk.kfirst || !walk.fstk || !walk.brl || !walk.ulist || !walk.uacnt ||
2142 !walk.ord || !walk.bcnt || !walk.mark)
2143 ERROR("memory allocation");
2144 if (walk.prune >= 2) {
2145 walk.cv = malloc(((size_t)nvar + 1) * sizeof(int));
2146 walk.touched = malloc(((size_t)nvar + 1) * sizeof(int));
2147 walk.hist = malloc(((size_t)walk.max_col_wt + 2) * sizeof(int));
2148 if (!walk.cv || !walk.touched || !walk.hist) ERROR("memory allocation");
2149 }
2150 }
2151 }
2152 }
2153
2154 /* irred=1: the workspace of the irreducibility test of the codewords found by CC and by the experimental ksub RW */
2155 if (ctx->p->irred && (ctx->p->method >= 2 || use_ksub)) warg->irr_ws = cols_ws_init(ctx->p->spaH->rows);
2156
2157 while (!atomic_load_explicit(&ctx->stop_flag, memory_order_relaxed)) {
2158 if (ctx->timeout > 0.0 && (get_time_sec() - ctx->start_time >= ctx->timeout)) {
2159 ctx_stop(ctx, STOP_TIMEOUT);
2160 break;
2161 }
2162
2163 bool did_work = false;
2164
2165 /* 1. Try to take CC work if CC is active (method 2 or 3) */
2166 if (ctx->p->method >= 2 && atomic_load_explicit(&ctx->cc_round_active, memory_order_acquire)) {
2167 int active = atomic_load_explicit(&ctx->cc_active_workers, memory_order_relaxed);
2168 int target = atomic_load_explicit(&ctx->cc_target_workers, memory_order_relaxed);
2169 /* A worker without RW work joins any CC round: method 2, a CC-only worker (tid >= rw_threads), RW stopped by
2170 * `min_hits`, or all RW steps already claimed by other workers (it would otherwise idle until the round ends) */
2171 if (!enable_rw || atomic_load_explicit(&ctx->rw_stop_flag, memory_order_relaxed) ||
2172 atomic_load_explicit(&ctx->rw_steps_started, memory_order_relaxed) >= ctx->total_rw_steps) {
2173 target = ctx->num_threads;
2174 }
2175 const long long next = atomic_load_explicit(&ctx->cc_next, memory_order_relaxed);
2176 if (active < target && cc_unpack_idx(next) <= cc_unit_last(ctx, cc_unpack_w(next), cc_unpack_lev(next))) {
2177 if (atomic_compare_exchange_weak(&ctx->cc_active_workers, &active, active + 1)) {
2178 const double t_join = get_time_sec();
2179 while (!atomic_load_explicit(&ctx->stop_flag, memory_order_relaxed)) {
2180 if (ctx->timeout > 0.0 && (get_time_sec() - ctx->start_time >= ctx->timeout)) {
2181 ctx_stop(ctx, STOP_TIMEOUT);
2182 break;
2183 }
2184 /* claim the next work unit together with the weight and the fixed levels of its round (see cc_pack()) */
2185 const long long claim = atomic_fetch_add_explicit(&ctx->cc_next, 1, memory_order_relaxed);
2186 const int w = cc_unpack_w(claim);
2187 const int lev = cc_unpack_lev(claim);
2188 const int unit = cc_unpack_idx(claim);
2189 if (unit > cc_unit_last(ctx, w, lev)) break;
2190 /* the start index of the unit, and its branches at the fixed levels: the digits of the unit in radix B */
2191 const int idx = unit / ctx->cc_R[lev];
2192 int fix[CC_SPLIT_MAX_LEVELS];
2193 for (int l = 0, k = unit - idx * ctx->cc_R[lev]; l < lev; l++, k /= ctx->cc_B) fix[l] = k % ctx->cc_B;
2194 /* with the expert `start` list, `idx` enumerates the listed start columns */
2195 const int col = (ctx->p->start_num > 0) ? ctx->p->start_list[idx] : idx;
2196
2197 if (walk.S) {
2198 const int swei = ctx->mHT_cc->p[col + 1] - ctx->mHT_cc->p[col];
2199 if (w > 1) {
2200 if (swei > 0 && swei <= (w - 1) * ctx->max_col_W) {
2201 if (walk.prune > 0) cc_bnb_unit(&walk, col, idx, w, fix, lev);
2202 else cc_walk_unit(&walk, col, w, fix, lev);
2203 }
2204 continue;
2205 }
2206 if (swei > 0) continue;
2207 }
2208
2209 err->vec[0] = urr->vec[0] = col;
2210 err->wei = urr->wei = 1;
2211 syn[1]->wei = 0;
2212 int swei = walk.S ? 0 : one_csr_row_combine(syn[1], syn[0], ctx->mHT_cc, col);
2213
2214 if (ctx->p->smax && swei > 0 && swei <= ctx->p->smax) {
2215 if (swei < warg->min_swei[1]) {
2216 warg->min_swei[1] = swei;
2217 }
2218 }
2219
2220 if (w > 1) {
2221 if (swei > 0 && swei <= (w - 1) * ctx->max_col_W + ctx->p->smax) {
2222 if (walk.S) {
2223 if (walk.prune > 0) cc_bnb_unit(&walk, col, idx, w, fix, lev);
2224 else cc_walk_unit(&walk, col, w, fix, lev);
2225 } else {
2226 start_CC_recurs_mt(err, urr, syn, w, ctx->max_col_W,
2227 ctx->p->spaH, ctx->mHT_cc, warg, fix, lev);
2228 }
2229 }
2230 } else {
2231 if (!swei) {
2232 int nz = (!ctx->p->maskL) || colmask_syndrome_non_zero(ctx->p->maskL, 1, err->vec);
2233 if (nz) {
2234 params_t * const p = ctx->p;
2235 pthread_mutex_lock(&ctx->cw_mutex);
2236 p->codewords = codeword_add_maybe(p, err->vec, 1, CW_SRC_OTHER);
2237 ctx_update_feed_limit(ctx);
2238 if ((p->debug & DBG_CODEWORDS) && atomic_load(&ctx->dmax) != 1) {
2239 char prefix[48];
2240 snprintf(prefix, sizeof(prefix), "# [thread %d] CC codeword", tid);
2241 print_codeword_support(stderr, prefix, err->vec, 1, p->debug);
2242 }
2243 atomic_store(&ctx->cc_found_weight, 1);
2244 atomic_store(&ctx->cc_exact, true); /* weight 1 is always the exact distance */
2245 atomic_store(&ctx->dmin, 1);
2246 atomic_store(&ctx->dmax, 1);
2247 /* d=1 is known; with outC or maxC, the round goes on to collect all codewords of weight 1 */
2248 if (codeword_maxc_reached(p)) {
2249 ctx_stop(ctx, STOP_MAXC);
2250 } else if (!p->outC && p->maxC == 0) {
2251 ctx_stop(ctx, STOP_CC_FOUND);
2252 }
2253 pthread_mutex_unlock(&ctx->cw_mutex);
2254 }
2255 }
2256 }
2257 err->wei = urr->wei = 0;
2258 }
2259 /* CC thread time (added before leaving the round, so the coordinator sees it once the round is over) */
2260 atomic_fetch_add_explicit(&ctx->cc_busy_ns, (long long)((get_time_sec() - t_join) * 1e9),
2261 memory_order_relaxed);
2262 atomic_fetch_add_explicit(&ctx->cc_nodes, walk.nodes, memory_order_relaxed);
2263 walk.nodes = 0;
2264 atomic_fetch_sub_explicit(&ctx->cc_active_workers, 1, memory_order_release);
2265 did_work = true;
2266 continue;
2267 }
2268 }
2269 }
2270
2271 /* 2. Try to take RW work if RW is active (method 1 or 3) */
2272 if (enable_rw && !atomic_load(&ctx->stop_flag) && !atomic_load(&ctx->rw_stop_flag)) {
2273 long cur_s = atomic_load(&ctx->rw_steps_started);
2274 if (cur_s < ctx->total_rw_steps) {
2275 long batch_size;
2276 if (ctx->p->chunk_size > 0) {
2277 batch_size = ctx->p->chunk_size;
2278 } else {
2279 int nvar = ctx->p->nvar;
2280 if (nvar < 500) {
2281 if (ctx->total_rw_steps >= 50000) batch_size = 500;
2282 else if (ctx->total_rw_steps >= 1000) batch_size = 250;
2283 else batch_size = 50;
2284 } else if (nvar < 5000) {
2285 if (ctx->total_rw_steps >= 10000) batch_size = 100;
2286 else batch_size = 50;
2287 } else {
2288 /* Large matrices (e.g. n >= 5000): keep chunk bounded to ~2-3s */
2289 if (ctx->total_rw_steps >= 10000) batch_size = 50;
2290 else batch_size = 25;
2291 }
2292 }
2293 /* Guided scheduling: a batch takes at most a quarter of the remaining steps per RW thread, so that no thread
2294 * monopolizes the steps and the RW threads finish together (no idle threads at the end of the steps) */
2295 if (ctx->rw_threads > 1) {
2296 const long guided = (ctx->total_rw_steps - cur_s + 4L * ctx->rw_threads - 1) / (4L * ctx->rw_threads);
2297 if (batch_size > guided) batch_size = (guided > 0) ? guided : 1;
2298 }
2299 long target_s = cur_s + batch_size;
2300 if (target_s > ctx->total_rw_steps) target_s = ctx->total_rw_steps;
2301 if (atomic_compare_exchange_weak(&ctx->rw_steps_started, &cur_s, target_s)) {
2302 int n_steps = (int)(target_s - cur_s);
2303 if (use_ksub) {
2304 if (ksub_eff > 0) {
2305 run_rw_steps_ksub(ctx, cur_s, n_steps, M_sub, ee, perm, pivs, nidx,
2306 visited_cols, visited_checks, col_queue,
2307 &visit_marker, &rng_state, tid);
2308 } else {
2309 atomic_fetch_add(&ctx->rw_steps_completed, n_steps);
2310 }
2311 } else {
2312 run_rw_steps(ctx, cur_s, n_steps, mH, mHT_rw, ee, perm, pivs,
2313 piv_mask, &rw_el, &rw_pws, &eff_nrows, visited_cols, visited_checks,
2314 col_queue, &visit_marker, &rng_state, tid);
2315 if (eff_nrows > 0 && eff_nrows < mH->nrows) rw_shrink_matrices(&mH, &mHT_rw, eff_nrows);
2316 }
2317 did_work = true;
2318 continue;
2319 }
2320 }
2321 }
2322
2323 if (!did_work) {
2324 usleep(100);
2325 }
2326 }
2327
2328 if (enable_rw) {
2329 free(piv_mask);
2330 free(rw_el.table);
2331 rw_pairs_ws_free(&rw_pws);
2332 safe_mzp_free(perm);
2333 safe_mzp_free(pivs);
2334 free(ee);
2335 free(visited_cols);
2336 free(visited_checks);
2337 free(col_queue);
2338 free(nidx);
2339 safe_mzd_free(M_sub);
2340 safe_mzd_free(mHT_rw);
2341 safe_mzd_free(mH);
2342 }
2343
2344 if (syn) {
2345 for (int i = 0; i <= wmax_alloc + 2; i++) free(syn[i]);
2346 free(syn);
2347 }
2348 free(err);
2349 free(urr);
2350 free(walk.S);
2351 free(walk.E);
2352 free(walk.cols);
2353 free(walk.cw);
2354 free(walk.X);
2355 free(walk.acnt);
2356 free(walk.kfirst);
2357 free(walk.fstk);
2358 free(walk.brl);
2359 free(walk.ulist);
2360 free(walk.uacnt);
2361 free(walk.ord);
2362 free(walk.bcnt);
2363 free(walk.mark);
2364 free(walk.cv);
2365 free(walk.touched);
2366 free(walk.hist);
2367 warg->irr_ws = cols_ws_free(warg->irr_ws);
2368
2369 return NULL;
2370}
2371
2372/* Percentage of the start indices of the current (or last) CC round claimed by the workers (in work units) */
2373static double cc_round_claimed(distfork_ctx_t * const ctx) {
2374 const long long beg = (long long)ctx->round_beg * ctx->round_R;
2375 const long long end = ((long long)ctx->round_end + 1) * ctx->round_R; /* one past the last work unit */
2376 if (end <= beg) return 100.0;
2377 long long idx = cc_unpack_idx(atomic_load(&ctx->cc_next));
2378 if (idx > end) idx = end;
2379 if (idx < beg) idx = beg;
2380 return 100.0 * (double)(idx - beg) / (double)(end - beg);
2381}
2382
2383/* State of RW once it no longer runs, for messages */
2384static const char *rw_end_state(distfork_ctx_t * const ctx) {
2385 if (atomic_load(&ctx->rw_stop_flag)) return "converged";
2386 if (atomic_load(&ctx->rw_steps_completed) >= ctx->total_rw_steps) return "all steps done";
2387 return "stopped";
2388}
2389
2390/* DBG_STATUS: periodic status line, after 1, 2, 4, ... s, then every 60 s (called by the coordinator only) */
2391static void ctx_status(distfork_ctx_t * const ctx) {
2392 params_t * const p = ctx->p;
2393 if (!(p->debug & DBG_STATUS)) return;
2394 const double t = get_time_sec() - ctx->start_time;
2395 if (t < ctx->status_next) return;
2396 ctx->status_next += ctx->status_dt;
2397 if (ctx->status_next <= t) ctx->status_next = t + ctx->status_dt;
2398 ctx->status_dt = (2.0 * ctx->status_dt < 60.0) ? 2.0 * ctx->status_dt : 60.0;
2399
2400 char cc_str[160] = "", rw_str[224] = "";
2401 if (p->method >= 2) {
2402 if (ctx->round_open) {
2403 int len = snprintf(cc_str, sizeof(cc_str), "; CC round w=%d: %.1f%% of start columns claimed, %.3gs",
2404 ctx->round_w, cc_round_claimed(ctx), get_time_sec() - ctx->round_t0);
2405 if (ctx->round_est > 0.0 && ctx->round_threads > 0 && len > 0 && len < (int)sizeof(cc_str))
2406 snprintf(cc_str + len, sizeof(cc_str) - len, " (predicted %.3gs on %d threads)",
2407 ctx->round_est / ctx->round_threads, ctx->round_threads);
2408 } else {
2409 snprintf(cc_str, sizeof(cc_str), "; CC %s", ctx->cc_idle ? ctx->cc_idle : "between rounds");
2410 }
2411 }
2412 if ((p->method & 1) && ctx->rw_threads > 0) {
2413 const long steps = atomic_load(&ctx->rw_steps_completed);
2414 const double rate = (t > ctx->status_t) ? (double)(steps - ctx->status_steps) / (t - ctx->status_t) : 0.0;
2415 ctx->status_steps = steps;
2416 ctx->status_t = t;
2417 const bool rw_on = !atomic_load(&ctx->rw_stop_flag) && steps < ctx->total_rw_steps;
2418 int len = snprintf(rw_str, sizeof(rw_str), "; RW: %ld of %ld steps (%.3g/s)%s%s", steps, ctx->total_rw_steps,
2419 rate, rw_on ? "" : ", ", rw_on ? "" : rw_end_state(ctx));
2420 if (p->min_hits > 0 && len > 0 && len < (int)sizeof(rw_str)) {
2421 cw_hit_stats_t st;
2422 pthread_mutex_lock(&ctx->cw_mutex);
2423 codeword_hit_stats(p, &st);
2424 pthread_mutex_unlock(&ctx->cw_mutex);
2425 if (st.set_cws > 0)
2426 snprintf(rw_str + len, sizeof(rw_str) - len, ", <n>=%.2f over %lld cws of weight %d..%d", st.avg,
2427 st.set_cws, st.w_lo, st.w_hi);
2428 }
2429 }
2430 fprintf(stderr, "# status %.1fs: bounds [%d, %d]%s%s\n", t, atomic_load(&ctx->dmin), atomic_load(&ctx->dmax),
2431 cc_str, rw_str);
2432}
2433
2434/* Start the workers ctx->spawned .. n-1 (main thread only). If a thread cannot be created, the run goes on with the
2435 * workers already started, and no more are started. */
2436static void ctx_spawn(distfork_ctx_t * const ctx, const int n) {
2437 for (int i = ctx->spawned; i < n && !ctx->spawn_failed; i++) {
2438 const int err = pthread_create(&ctx->threads[i], NULL, worker_thread_func, &ctx->wargs[i]);
2439 if (err != 0) {
2440 if (i == 0) ERROR("cannot create a worker thread (error %d)", err);
2441 fprintf(stderr, "# Warning: cannot create worker thread %d (error %d), running with %d threads\n", i, err, i);
2442 ctx->spawn_failed = true;
2443 break;
2444 }
2445 ctx->spawned = i + 1;
2446 }
2447}
2448
2449/* Method 2 (unless nothrottle=1) starts only CC_SPAWN_INITIAL workers, and the others for a CC round predicted to take
2450 * more than CC_SPAWN_WORK thread-seconds per started worker (or of unknown work), or once a round has run for
2451 * CC_SPAWN_TIME seconds: tiny runs avoid the start-up of many threads, longer runs use all of them (any worker can
2452 * join a CC round at any time) */
2453#define CC_SPAWN_INITIAL 4
2454#define CC_SPAWN_WORK 2e-3
2455#define CC_SPAWN_TIME 5e-3
2456
2457/* Method 2: some workers not started yet */
2458static inline bool m2_can_spawn(const distfork_ctx_t * const ctx) {
2459 return ctx->spawned < ctx->num_threads && !ctx->spawn_failed;
2460}
2461
2462/* Method 2: start the remaining workers during (or for) the CC round at weight w; `why`: for the message */
2463static void m2_spawn_rest(distfork_ctx_t * const ctx, const int w, const char * const why) {
2464 const int n0 = ctx->spawned;
2465 ctx_spawn(ctx, ctx->num_threads);
2466 ctx->round_threads = ctx->spawned;
2467 if ((ctx->p->debug & DBG_TIMING) && ctx->spawned > n0) {
2468 fprintf(stderr, "# CC round w=%d: started %d more threads, %d in total (%s)\n", w, ctx->spawned - n0, ctx->spawned,
2469 why);
2470 }
2471}
2472
2473/* Method 1 coordinator */
2474static void run_method1_coordinator(distfork_ctx_t *ctx) {
2475 while (!atomic_load(&ctx->stop_flag)) {
2476 if (ctx->timeout > 0.0 && (get_time_sec() - ctx->start_time >= ctx->timeout)) {
2477 ctx_stop(ctx, STOP_TIMEOUT);
2478 break;
2479 }
2480 if (atomic_load(&ctx->rw_steps_completed) >= ctx->total_rw_steps) {
2481 ctx_stop(ctx, STOP_RW_STEPS);
2482 break;
2483 }
2484 ctx_status(ctx);
2485 usleep(1000);
2486 }
2487}
2488
2489/* Estimated work of the CC round at weight w in thread-seconds (the unit of `cc_time_per_weight`, i.e., wall time
2490 * times the number of CC threads): the measured work W(w-1) of the previous round times the growth factor
2491 * W(w-1)/W(w-2), clamped to [2, 10] (4 if W(w-2) is not known), also returned in `*growth`. Returns 0 if W(w-1)
2492 * has not been measured (the first round at a supplied dmin > 1). */
2493static double cc_work_estimate(const distfork_ctx_t * const ctx, const int w, double * const growth) {
2494 if (ctx->even_weight_only && !(w & 1)) {
2495 *growth = 16.0;
2496 if (w <= 2) return 1e-4;
2497 if (w > MAX_W) return 0.0;
2498 const double prev = ctx->cc_time_per_weight[w - 2];
2499 if (!(prev > 0.0)) return 0.0;
2500 if (w >= 6 && ctx->cc_time_per_weight[w - 4] > 1e-4) {
2501 const double g = prev / ctx->cc_time_per_weight[w - 4];
2502 *growth = (g < 4.0) ? 4.0 : ((g > 100.0) ? 100.0 : g);
2503 }
2504 return ((prev > 1e-4) ? prev : 1e-4) * (*growth);
2505 }
2506 *growth = 4.0;
2507 if (w <= 1) return 1e-4; /* one syndrome per column */
2508 if (w > MAX_W) return 0.0;
2509 const double prev = ctx->cc_time_per_weight[w - 1];
2510 if (!(prev > 0.0)) return 0.0;
2511 if (w >= 3 && ctx->cc_time_per_weight[w - 2] > 1e-4) {
2512 const double g = prev / ctx->cc_time_per_weight[w - 2];
2513 *growth = (g < 2.0) ? 2.0 : ((g > 10.0) ? 10.0 : g);
2514 }
2515 return ((prev > 1e-4) ? prev : 1e-4) * (*growth);
2516}
2517
2518/* Number of collected codewords (the collection window, as exported with outC), for messages */
2519static long long ctx_num_cws(distfork_ctx_t * const ctx) {
2520 pthread_mutex_lock(&ctx->cw_mutex);
2521 const long long num = codeword_window_count(ctx->p);
2522 pthread_mutex_unlock(&ctx->cw_mutex);
2523 return num;
2524}
2525
2526/* Method 2 coordinator */
2527static void run_method2_coordinator(distfork_ctx_t *ctx) {
2528 const int wmax = ctx->p->wmax;
2529 const int w_start = ctx->p->noscan ? wmax : (ctx->p->dmin > 1 ? ctx->p->dmin : 1);
2530 /* with outC or maxC, CC does not stop at the first codeword but collects all codewords of a round */
2531 const bool collecting = (ctx->p->outC != NULL) || (ctx->p->maxC > 0);
2532 /* with outC and dW > 0, extra CC rounds export the codewords of weight up to d + dW */
2533 const int extra_w = (ctx->p->outC && ctx->p->dW > 0) ? ctx->p->dW : 0;
2534
2535 int w_limit = wmax;
2536 for (int w = w_start; ; w++) {
2537 if (atomic_load(&ctx->stop_flag)) break;
2538 /* The current upper bound dmax (given, read from finC, or found by CC) limits the rounds: since the CC kernel
2539 * caps the cluster weight at dmax + dW, a round w > dmax + extra_w would only enumerate the same clusters
2540 * again. Unless codewords are collected, the round w = dmax is not needed once dmin = dmax. */
2541 const int cur_dmax = atomic_load(&ctx->dmax);
2542 if (cur_dmax > 0) {
2543 if (!collecting && atomic_load(&ctx->dmin) >= cur_dmax) {
2544 if (ctx->p->debug & DBG_PROGRESS) {
2545 fprintf(stderr, "# bounds coincide: dmin = dmax = %d (CC round w=%d not needed)\n", cur_dmax, w);
2546 }
2547 ctx_stop(ctx, STOP_BOUNDS);
2548 break;
2549 }
2550 w_limit = minint(w_limit, cur_dmax + extra_w);
2551 }
2552 /* The stop target dstop (if below dmax): no rounds w >= dstop are needed, except the rounds w = dstop..dstop+dW
2553 * which collect codewords */
2554 const int dstop = ctx->p->dstop;
2555 if (dstop > 0 && (cur_dmax == 0 || dstop < cur_dmax)) {
2556 if (!collecting && atomic_load(&ctx->dmin) >= dstop) {
2557 if (ctx->p->debug & DBG_PROGRESS) {
2558 fprintf(stderr, "# lower bound dmin=%d reached dstop=%d (CC round w=%d not needed)\n",
2559 atomic_load(&ctx->dmin), dstop, w);
2560 }
2561 ctx_stop(ctx, STOP_DSTOP);
2562 break;
2563 }
2564 w_limit = minint(w_limit, collecting ? dstop + extra_w : dstop - 1);
2565 }
2566 if (w > w_limit) {
2567 ctx->stop_w = w_limit;
2568 ctx_stop(ctx, (dstop > 0 && atomic_load(&ctx->dmin) >= dstop) ? STOP_DSTOP : STOP_CC_DONE);
2569 break;
2570 }
2571 if (ctx->even_weight_only && (w & 1)) {
2572 if (w == atomic_load(&ctx->dmin)) atomic_store(&ctx->dmin, w + 1);
2573 continue;
2574 }
2575
2576 double now = get_time_sec();
2577 double remaining_time = ctx->timeout - (now - ctx->start_time);
2578 if (ctx->timeout > 0.0 && remaining_time <= 0.0) {
2579 ctx_stop(ctx, STOP_TIMEOUT);
2580 break;
2581 }
2582
2583 /* With a timeout, a round predicted not to finish in time is started anyway: it can still end early with a
2584 * codeword of weight w, which gives the exact distance (all lower weights have been analyzed) */
2585 double growth;
2586 const double t_cc_work = cc_work_estimate(ctx, w, &growth); /* thread-seconds (0: unknown) */
2587 if (ctx->timeout > 0.0 && (ctx->p->debug & DBG_PROGRESS)) {
2588 const double t_cc_est = t_cc_work / ctx->num_threads; /* wall time */
2589 if (t_cc_est > remaining_time) {
2590 fprintf(stderr, "# CC for w=%d (est %.2fs) may not finish in the remaining time %.2fs, searching anyway "
2591 "(dmin=%d)\n", w, t_cc_est, remaining_time, atomic_load(&ctx->dmin));
2592 }
2593 }
2594
2595 const int beg = cc_round_beg(ctx->p);
2596 const int end = cc_round_end(ctx->p, w);
2597 /* all weights below w analyzed (or a supplied dmin): a codeword of weight w found is exact */
2598 const bool certified = (w == atomic_load(&ctx->dmin));
2599 /* the workers not started yet, for a longer round (or one of unknown work) */
2600 if (m2_can_spawn(ctx) && !(t_cc_work > 0.0 && t_cc_work <= CC_SPAWN_WORK * ctx->spawned)) {
2601 char why[64];
2602 if (t_cc_work > 0.0) snprintf(why, sizeof(why), "predicted %.3g thread-s", t_cc_work);
2603 else snprintf(why, sizeof(why), "work not known");
2604 m2_spawn_rest(ctx, w, why);
2605 }
2606 const int lev = cc_round_levels(ctx, w, beg, end, t_cc_work); /* fixed levels of the CC work units */
2607 const int last = cc_unit_last(ctx, w, lev);
2608
2609 atomic_store(&ctx->cc_target_workers, ctx->num_threads);
2610 const long long busy_ns0 = atomic_load(&ctx->cc_busy_ns);
2611 const long long nodes0 = atomic_load(&ctx->cc_nodes);
2612 atomic_store(&ctx->cc_next, cc_pack(w, lev, beg * ctx->cc_R[lev]));
2613 atomic_store(&ctx->cc_round_active, 1);
2614
2615 double cc_start = get_time_sec();
2616 ctx->round_open = 1;
2617 ctx->round_w = w;
2618 ctx->round_beg = beg;
2619 ctx->round_end = end;
2620 ctx->round_R = ctx->cc_R[lev];
2621 ctx->round_threads = ctx->spawned;
2622 ctx->round_t0 = cc_start;
2623 ctx->round_est = t_cc_work;
2624
2625 if (ctx->p->debug & DBG_TIMING) {
2626 const char *note = (certified || atomic_load(&ctx->cc_exact)) ? "" : " (lower weights not scanned)";
2627 char units[64] = "";
2628 if (lev > 0) snprintf(units, sizeof(units), ", %d work units per start column", ctx->cc_R[lev]);
2629 if (ctx->p->start_num > 0) {
2630 fprintf(stderr, "# searching w=%d with %d CC threads, start list (%d columns%s)%s%s\n",
2631 w, ctx->spawned, ctx->p->start_num, ctx->p->start_scan ? ", start_scan=1" : "", units, note);
2632 } else {
2633 fprintf(stderr, "# searching w=%d with %d CC threads, columns [%d, %d]%s%s\n",
2634 w, ctx->spawned, beg, end, units, note);
2635 }
2636 }
2637
2638 bool round_completed = false;
2639 while (!atomic_load(&ctx->stop_flag)) {
2640 const double t = get_time_sec();
2641 if (ctx->timeout > 0.0 && (t - ctx->start_time >= ctx->timeout)) {
2642 ctx_stop(ctx, STOP_TIMEOUT);
2643 break;
2644 }
2645 if (cc_unpack_idx(atomic_load(&ctx->cc_next)) > last && atomic_load(&ctx->cc_active_workers) == 0) {
2646 round_completed = true;
2647 break;
2648 }
2649 if (m2_can_spawn(ctx) && t - cc_start > CC_SPAWN_TIME) {
2650 char why[64];
2651 snprintf(why, sizeof(why), "running for %.3fs", t - cc_start);
2652 m2_spawn_rest(ctx, w, why);
2653 }
2654 ctx_status(ctx);
2655 usleep(100);
2656 }
2657
2658 atomic_store(&ctx->cc_round_active, 0);
2659 if (round_completed) ctx->round_open = 0;
2660
2661 double cc_dur = get_time_sec() - cc_start;
2662 if (w < MAX_W) { /* CC work of the round in thread-seconds, as measured by the workers */
2663 ctx->cc_time_per_weight[w] = 1e-9 * (double)(atomic_load(&ctx->cc_busy_ns) - busy_ns0);
2664 if ((ctx->p->debug & DBG_TIMING) && round_completed) {
2665 char est_str[48] = "unknown";
2666 if (t_cc_work > 0.0) snprintf(est_str, sizeof(est_str), "%.3g thread-s", t_cc_work);
2667 fprintf(stderr, "# CC round w=%d work: %.3g thread-s measured, %s predicted (%.3fs on %d threads), %lld "
2668 "nodes\n", w, ctx->cc_time_per_weight[w], est_str, cc_dur, ctx->spawned,
2669 atomic_load(&ctx->cc_nodes) - nodes0);
2670 }
2671 }
2672
2673 int cw_found = atomic_load(&ctx->cc_found_weight);
2674 if (cw_found > 0 && certified && cw_found == w && !atomic_load(&ctx->cc_exact)) {
2675 atomic_store(&ctx->cc_exact, true);
2676 }
2677 const bool exact = (cw_found > 0) && atomic_load(&ctx->cc_exact);
2678 if (exact) {
2679 atomic_store(&ctx->dmin, cw_found);
2680 atomic_store(&ctx->dmax, cw_found);
2681 /* the distance is known: no rounds beyond w = cw_found + extra_w */
2682 w_limit = minint(w_limit, cw_found + extra_w);
2683 const int next_w = (ctx->even_weight_only && !(w & 1)) ? w + 2 : w + 1;
2684 const bool more_rounds = round_completed && (next_w <= w_limit);
2685 if (ctx->p->debug & DBG_PROGRESS) {
2686 if (w == cw_found && more_rounds) {
2687 fprintf(stderr,
2688 "# CC round w=%d finished in %.3fs (%d CC threads): found min-weight codewords "
2689 "(dmin=%d, continuing up to w=%d for dW=%d, total %lld cws)\n",
2690 w, cc_dur, ctx->spawned, cw_found, w_limit, ctx->p->dW, ctx_num_cws(ctx));
2691 } else if (w == cw_found) {
2692 fprintf(stderr, "# CC found min-weight codeword: d=%d (using %d CC threads, total %lld cws)\n",
2693 cw_found, ctx->spawned, ctx_num_cws(ctx));
2694 } else if (round_completed) {
2695 fprintf(stderr,
2696 "# CC round w=%d finished in %.3fs (%d CC threads): extra dW round completed "
2697 "(dmin=%d, total %lld cws)\n",
2698 w, cc_dur, ctx->spawned, cw_found, ctx_num_cws(ctx));
2699 }
2700 }
2701 if (!more_rounds) {
2702 /* (a stop reason recorded during the round, e.g., the timeout, is kept) */
2703 ctx_stop(ctx, collecting ? STOP_COLLECTED : STOP_CC_FOUND);
2704 break;
2705 }
2706 } else if (cw_found > 0) {
2707 /* noscan=1 without dmin=wmax: lower weights not scanned, the codeword only gives an upper bound */
2708 if (ctx->p->debug & DBG_PROGRESS) {
2709 fprintf(stderr,
2710 "# CC round w=%d finished in %.3fs (%d CC threads): found codeword of weight %d -> dmax=%d "
2711 "(dmin=%d not certified: lower weights not scanned)\n",
2712 w, cc_dur, ctx->spawned, cw_found, atomic_load(&ctx->dmax), atomic_load(&ctx->dmin));
2713 }
2714 ctx_stop(ctx, STOP_CC_FOUND);
2715 break;
2716 } else {
2717 if (!round_completed) {
2718 break;
2719 }
2720 if (certified) {
2721 /* Weight w analyzed without success */
2722 const int new_dmin = ctx->even_weight_only ? w + 2 : w + 1;
2723 atomic_store(&ctx->dmin, new_dmin);
2724 if (ctx->p->debug & DBG_PROGRESS) {
2725 fprintf(stderr, "# CC w=%d completed in %.3fs (%d CC threads): no codewords found -> dmin=%d\n",
2726 w, cc_dur, ctx->spawned, new_dmin);
2727 }
2728 } else if (ctx->p->debug & DBG_PROGRESS) {
2729 fprintf(stderr, "# CC w=%d completed in %.3fs (%d CC threads): no codewords found "
2730 "(dmin=%d not raised: lower weights not scanned)\n",
2731 w, cc_dur, ctx->spawned, atomic_load(&ctx->dmin));
2732 }
2733 }
2734 }
2735}
2736
2737/* Method 3: RW still runs (RW threads, RW steps not all completed, RW not stopped by min_hits) */
2738static inline bool m3_rw_running(distfork_ctx_t * const ctx) {
2739 return ctx->rw_threads > 0 && !atomic_load(&ctx->rw_stop_flag) &&
2740 atomic_load(&ctx->rw_steps_completed) < ctx->total_rw_steps;
2741}
2742
2743/* Method 3: RW thread time per step, measured continuously by the RW workers (before the first step is completed, a
2744 * step takes at least the time elapsed since the start) */
2745static double m3_rw_step_time(distfork_ctx_t * const ctx) {
2746 const long n = atomic_load(&ctx->rw_timed_steps);
2747 if (n > 0) return 1e-9 * (double)atomic_load(&ctx->rw_busy_ns) / (double)n;
2748 const double elapsed = get_time_sec() - ctx->start_time;
2749 return (elapsed > 5e-5) ? elapsed : 5e-5;
2750}
2751
2752/* Method 3: the largest CC weight needed: t-1 once the target t is known, i.e., the upper bound dmax or the stop
2753 * target dstop if smaller (with outC, t+dW: the rounds w = t..t+dW enumerate the codewords to export), otherwise wmax
2754 * (or n); at most MAX_W-2. (`dexp` only affects the thread split, see m3_plan_cc_threads().) */
2755static int m3_cc_target_w(const distfork_ctx_t * const ctx, const int cur_dmin, const int cur_dmax) {
2756 const params_t * const p = ctx->p;
2757 const int t = ctx_target(ctx, cur_dmax);
2758 int target;
2759 if (t > 0) {
2760 if (p->outC && (p->dW > 0 || cur_dmin >= t)) {
2761 target = t + (p->dW > 0 ? p->dW : 0);
2762 } else {
2763 target = t - 1;
2764 }
2765 } else {
2766 target = (p->wmax > 0) ? p->wmax : p->spaH->cols;
2767 }
2768 const int max_allowed_w = (p->wmax > 0) ? minint(p->wmax, MAX_W - 2) : (MAX_W - 2);
2769 int res = minint(target, max_allowed_w);
2770 if (ctx->even_weight_only && (res & 1)) res--;
2771 return res;
2772}
2774#define M3_DEADLINE_FRACTION 0.75 /* with a timeout, a round gets enough CC threads to finish in 3/4 of the time left */
2775#define M3_OVERRUN_FACTOR 1.5 /* a round taking 1.5 times (+20 ms) longer than predicted gets all threads */
2776
2777/* Method 3: number of CC threads for the round at weight w with the estimated work `est` (thread-seconds, 0 if not
2778 * known) and the growth factor `growth` (see cc_work_estimate()); the other threads run RW. Returns 0 if CC is
2779 * paused (`dexp`, below). */
2780static int m3_plan_cc_threads(distfork_ctx_t * const ctx, const int w, const double est, const double growth,
2781 const double remaining_time) {
2782 const int nthr = ctx->num_threads;
2783 if (nthr <= 1) return 1;
2784 /* RW finished, or all RW steps claimed: the RW workers join the round anyway */
2785 if (!m3_rw_running(ctx) || atomic_load(&ctx->rw_steps_started) >= ctx->total_rw_steps) return nthr;
2786 const int cur_dmin = atomic_load(&ctx->dmin);
2787 const int cur_dmax = atomic_load(&ctx->dmax);
2788 /* The round w = t-1 certifies dmin = t (t: dmax, or the stop target dstop if smaller; later rounds only collect
2789 * codewords). RW cannot improve this result, it could only find a codeword of weight w before CC does: all threads
2790 * run CC. */
2791 const int t = ctx_target(ctx, cur_dmax);
2792 const int cert_w = ctx->even_weight_only ? (t - ((t & 1) ? 1 : 2)) : (t - 1);
2793 if (t > 0 && (w >= cert_w || cur_dmin >= t)) return nthr;
2794 /* `dexp` hint: as long as RW has not found any codeword, CC rounds w > dexp run only on the threads which cannot
2795 * run RW (none: CC pauses) */
2796 if (cur_dmax == 0 && ctx->dexp > 0 && w > ctx->dexp) return nthr - ctx->rw_threads;
2797
2798 int n_cc;
2799 if (!(est > 0.0)) {
2800 n_cc = nthr / 2; /* the first round at a supplied dmin: its work is not known */
2801 } else if (est < 0.005) {
2802 n_cc = (nthr >= 4) ? 2 : 1;
2803 } else {
2804 /* Split by the remaining work in thread-seconds: the CC rounds w, w+1, w+2 up to the horizon (dmax-1, or dexp
2805 * before RW has found a codeword), and the remaining RW steps (at most 2000 once dmax is known), but not more RW
2806 * work than the RW threads can do before the timeout */
2807 int horizon = m3_cc_target_w(ctx, cur_dmin, cur_dmax);
2808 if (cur_dmax == 0 && ctx->dexp > 0 && ctx->dexp < horizon) horizon = ctx->dexp;
2809 double t_cc = est, t_k = est;
2810 const int step = ctx->even_weight_only ? 2 : 1;
2811 for (int k = w + step; k <= horizon && k <= w + 2 * step; k += step) {
2812 t_k *= growth;
2813 t_cc += t_k;
2814 }
2815 const long steps_rem = ctx->total_rw_steps - atomic_load(&ctx->rw_steps_completed);
2816 const long eff_steps = (cur_dmax > 0 && steps_rem > 2000) ? 2000 : steps_rem;
2817 double t_rw = (double)eff_steps * m3_rw_step_time(ctx);
2818 if (ctx->timeout > 0.0 && t_rw > remaining_time * ctx->rw_threads) t_rw = remaining_time * ctx->rw_threads;
2819 n_cc = (int)round((double)nthr * t_cc / (t_cc + t_rw));
2820 if (n_cc < 1) n_cc = 1;
2821 if (n_cc > nthr - 1) n_cc = nthr - 1; /* keep at least one RW thread */
2822 }
2823 /* timeout: enough CC threads to finish the round in time */
2824 if (ctx->timeout > 0.0 && est > 0.0 && remaining_time > 0.0) {
2825 const double n_min = ceil(est / (M3_DEADLINE_FRACTION * remaining_time));
2826 if (n_min > n_cc) n_cc = (n_min < nthr) ? (int)n_min : nthr;
2827 }
2828 /* only `rw_threads` workers can run RW (memory / steps limits): the other workers always run CC */
2829 if (nthr - n_cc > ctx->rw_threads) n_cc = nthr - ctx->rw_threads;
2830 return n_cc;
2831}
2832
2833/* Method 3: wait while RW runs, until the upper bound dmax differs from `dmax0`, RW ends, or the run stops; `why`:
2834 * the reason why no CC round runs, for the status line */
2835static void m3_wait_rw(distfork_ctx_t * const ctx, const int dmax0, const char * const why) {
2836 ctx->cc_idle = why;
2837 while (!atomic_load(&ctx->stop_flag) && m3_rw_running(ctx) && atomic_load(&ctx->dmax) == dmax0) {
2838 if (ctx->timeout > 0.0 && (get_time_sec() - ctx->start_time >= ctx->timeout)) {
2839 ctx_stop(ctx, STOP_TIMEOUT);
2840 break;
2841 }
2842 ctx_status(ctx);
2843 usleep(1000);
2844 }
2845 ctx->cc_idle = NULL;
2846}
2847
2848/* Method 3 coordinator */
2849static void run_method3_coordinator(distfork_ctx_t *ctx) {
2850 const int nvar = ctx->p->spaH->cols;
2851 const int nthr = ctx->num_threads;
2852 int w = ctx->p->noscan ? ctx->p->wmax : (ctx->p->dmin > 1 ? ctx->p->dmin : 1);
2853
2854 int init_dmax = atomic_load(&ctx->dmax);
2855 int init_dmin = atomic_load(&ctx->dmin);
2856 if (init_dmax > 0 && init_dmin >= init_dmax && !ctx->p->outC) {
2857 ctx_stop(ctx, STOP_BOUNDS);
2858 return;
2859 }
2860
2861 /* No RW probe: the RW step time is measured continuously by the RW workers (m3_rw_step_time()). CC is never ended
2862 * while RW still runs: if no CC round can be started (CC done up to wmax, a round predicted to exceed the timeout,
2863 * or CC paused by `dexp`), the coordinator waits for RW and re-plans when dmax changes or RW ends. */
2864 int wait_msg_w = 0; /* weight w for which a waiting message was printed (once per round) */
2865 while (!atomic_load(&ctx->stop_flag)) {
2866 if (ctx->even_weight_only && (w & 1)) {
2867 if (w == atomic_load(&ctx->dmin)) atomic_store(&ctx->dmin, w + 1);
2868 w++;
2869 continue;
2870 }
2871 double now = get_time_sec();
2872 double remaining_time = (ctx->timeout > 0.0) ? (ctx->timeout - (now - ctx->start_time)) : 1e9;
2873 if (ctx->timeout > 0.0 && remaining_time <= 0.0) {
2874 ctx_stop(ctx, STOP_TIMEOUT);
2875 break;
2876 }
2877
2878 int cur_dmax = atomic_load(&ctx->dmax);
2879 int cur_dmin = atomic_load(&ctx->dmin);
2880 const bool rw_on = m3_rw_running(ctx);
2881
2882 /* Target cluster size for CC */
2883 const int target_cc_w = m3_cc_target_w(ctx, cur_dmin, cur_dmax);
2884
2885 if (cur_dmax > 0 && cur_dmin >= cur_dmax && w > target_cc_w) {
2886 /* Bracketing converged and all requested dW rounds completed */
2887 atomic_store(&ctx->dmin, cur_dmax);
2888 ctx_stop(ctx, (ctx->p->outC && w > cur_dmax) ? STOP_COLLECTED : STOP_BOUNDS);
2889 break;
2890 }
2891 if (ctx->p->dstop > 0 && cur_dmin >= ctx->p->dstop && w > target_cc_w) {
2892 /* The stop target dstop reached (with outC, after the rounds w = dstop..dstop+dW which collect codewords) */
2893 ctx_stop(ctx, STOP_DSTOP);
2894 break;
2895 }
2896
2897 if (w > target_cc_w) {
2898 /* CC done up to wmax: only RW can still lower dmax */
2899 if (!rw_on) {
2900 const int max_allowed_w = (ctx->p->wmax > 0) ? minint(ctx->p->wmax, MAX_W - 2) : (MAX_W - 2);
2901 ctx->stop_w = (ctx->even_weight_only && target_cc_w + 1 == max_allowed_w) ? max_allowed_w : target_cc_w;
2902 ctx_stop(ctx, STOP_CC_DONE);
2903 break;
2904 }
2905 if ((ctx->p->debug & DBG_TIMING) && wait_msg_w != w) {
2906 wait_msg_w = w;
2907 fprintf(stderr, "# CC done up to w=%d, waiting for RW (bounds [%d, %d])\n", target_cc_w, cur_dmin, cur_dmax);
2908 }
2909 m3_wait_rw(ctx, cur_dmax, "done up to wmax, waiting for RW");
2910 continue;
2911 }
2912
2913 /* Estimated CC work of the round (thread-seconds). A round predicted not to finish in time on all threads is not
2914 * started while RW runs; once RW has ended, the run ends. Without RW (steps=0), as in method 2, the round is
2915 * started anyway: it can still end early with a codeword of weight w (exact if w = dmin). */
2916 double growth;
2917 const double t_cc_est = cc_work_estimate(ctx, w, &growth);
2918 if (ctx->timeout > 0.0 && t_cc_est / nthr > remaining_time) {
2919 if (rw_on) {
2920 if ((ctx->p->debug & DBG_TIMING) && wait_msg_w != w) {
2921 wait_msg_w = w;
2922 fprintf(stderr, "# CC for w=%d (est %.2fs) exceeds remaining timeout %.2fs, devoting %d threads to RW\n",
2923 w, t_cc_est / nthr, remaining_time, ctx->rw_threads);
2924 }
2925 m3_wait_rw(ctx, cur_dmax, "round predicted to exceed the timeout, waiting for RW");
2926 continue;
2927 }
2928 if (ctx->rw_threads > 0) {
2929 if (ctx->p->debug & DBG_PROGRESS) {
2930 fprintf(stderr, "# CC for w=%d (est %.2fs) exceeds remaining timeout %.2fs, RW ended: terminating early "
2931 "(dmin=%d)\n", w, t_cc_est / nthr, remaining_time, cur_dmin);
2932 }
2933 ctx->stop_w = w;
2934 ctx_stop(ctx, STOP_CC_SLOW);
2935 break;
2936 }
2937 if ((ctx->p->debug & DBG_PROGRESS) && wait_msg_w != w) {
2938 wait_msg_w = w;
2939 fprintf(stderr, "# CC for w=%d (est %.2fs) may not finish in the remaining time %.2fs, searching anyway "
2940 "(dmin=%d)\n", w, t_cc_est / nthr, remaining_time, cur_dmin);
2941 }
2942 }
2943
2944 /* Calculate thread balancing */
2945 int n_cc = m3_plan_cc_threads(ctx, w, t_cc_est, growth, remaining_time);
2946 if (n_cc <= 0) {
2947 if ((ctx->p->debug & DBG_TIMING) && wait_msg_w != w) {
2948 wait_msg_w = w;
2949 fprintf(stderr, "# CC paused before w=%d > dexp=%d until RW finds a codeword (or ends)\n", w, ctx->dexp);
2950 }
2951 m3_wait_rw(ctx, cur_dmax, "paused (w > dexp) until RW finds a codeword");
2952 continue;
2953 }
2954 const int n_cc0 = n_cc;
2955 int n_rw = nthr - n_cc;
2956 const long steps_rem = rw_on ? ctx->total_rw_steps - atomic_load(&ctx->rw_steps_completed) : 0;
2957
2958 const int beg = cc_round_beg(ctx->p);
2959 const int end = cc_round_end(ctx->p, w);
2960 /* all weights below w analyzed (or a supplied dmin): a codeword of weight w found is exact */
2961 const bool certified = (w == atomic_load(&ctx->dmin));
2962 const int lev = cc_round_levels(ctx, w, beg, end, t_cc_est); /* fixed levels of the CC work units */
2963 const int last = cc_unit_last(ctx, w, lev);
2964
2965 atomic_store(&ctx->cc_target_workers, n_cc);
2966 const long long busy_ns0 = atomic_load(&ctx->cc_busy_ns);
2967 const long long nodes0 = atomic_load(&ctx->cc_nodes);
2968 atomic_store(&ctx->cc_next, cc_pack(w, lev, beg * ctx->cc_R[lev]));
2969 atomic_store(&ctx->cc_round_active, 1);
2970
2971 if (ctx->p->debug & DBG_TIMING) {
2972 char est_str[64] = "unknown";
2973 char rw_str[64] = "";
2974 char units[64] = "";
2975 if (t_cc_est > 0.0) snprintf(est_str, sizeof(est_str), "%.3g thread-s", t_cc_est);
2976 if (rw_on) snprintf(rw_str, sizeof(rw_str), ", RW step %.3g s", m3_rw_step_time(ctx));
2977 if (lev > 0) snprintf(units, sizeof(units), ", %d work units per start column", ctx->cc_R[lev]);
2978 fprintf(stderr,
2979 "# CC round w=%d started: %d CC threads, %d RW threads "
2980 "(bounds [%d, %d], rem_rw=%ld, rem_time=%.2fs, est %s%s%s)\n",
2981 w, n_cc, n_rw, cur_dmin, cur_dmax, steps_rem, remaining_time, est_str, rw_str, units);
2982 }
2983
2984 double cc_start = get_time_sec();
2985 bool round_completed = false;
2986 int dmax_seen = cur_dmax;
2987 unsigned int poll = 0;
2988 ctx->round_open = 1;
2989 ctx->round_w = w;
2990 ctx->round_beg = beg;
2991 ctx->round_end = end;
2992 ctx->round_R = ctx->cc_R[lev];
2993 ctx->round_threads = n_cc;
2994 ctx->round_t0 = cc_start;
2995 ctx->round_est = t_cc_est;
2996
2997 while (!atomic_load(&ctx->stop_flag)) {
2998 const double t = get_time_sec();
2999 if (ctx->timeout > 0.0 && (t - ctx->start_time >= ctx->timeout)) {
3000 ctx_stop(ctx, STOP_TIMEOUT);
3001 break;
3002 }
3003 if (cc_unpack_idx(atomic_load(&ctx->cc_next)) > last && atomic_load(&ctx->cc_active_workers) == 0) {
3004 round_completed = true;
3005 break;
3006 }
3007 /* Re-plan during the round (only adding CC threads, workers do not leave a round): when RW finds a new upper
3008 * bound (e.g., this round now certifies dmin = dmax), and when the round takes much longer than predicted */
3009 if ((++poll & 15) == 0 && n_cc < nthr) {
3010 int n_new = n_cc;
3011 const char *why = "";
3012 const int d_now = atomic_load(&ctx->dmax);
3013 if (d_now != dmax_seen) {
3014 dmax_seen = d_now;
3015 const double rem = (ctx->timeout > 0.0) ? ctx->timeout - (t - ctx->start_time) : 1e9;
3016 n_new = m3_plan_cc_threads(ctx, w, t_cc_est, growth, rem);
3017 why = "new upper bound";
3018 }
3019 const bool paused = (d_now == 0 && ctx->dexp > 0 && w > ctx->dexp && m3_rw_running(ctx));
3020 if (!paused && t_cc_est > 0.0 && t - cc_start > M3_OVERRUN_FACTOR * t_cc_est / n_cc0 + 0.02) {
3021 n_new = nthr;
3022 why = "longer than predicted";
3023 }
3024 if (n_new > n_cc) {
3025 if (ctx->p->debug & DBG_TIMING) {
3026 fprintf(stderr, "# CC round w=%d: %d -> %d CC threads (%s, bounds [%d, %d], %.3fs into the round)\n",
3027 w, n_cc, n_new, why, atomic_load(&ctx->dmin), d_now, t - cc_start);
3028 }
3029 n_cc = n_new;
3030 n_rw = nthr - n_cc;
3031 ctx->round_threads = n_cc;
3032 atomic_store(&ctx->cc_target_workers, n_cc);
3033 }
3034 }
3035 ctx_status(ctx);
3036 usleep(100);
3037 }
3038
3039 atomic_store(&ctx->cc_round_active, 0);
3040 if (round_completed) ctx->round_open = 0;
3041
3042 double cc_dur = get_time_sec() - cc_start;
3043 if (w < MAX_W) {
3044 /* CC work of the round in thread-seconds, as measured by the workers: the number of CC threads may exceed
3045 * n_cc (CC-only workers, and RW workers that ran out of RW steps join the round) */
3046 ctx->cc_time_per_weight[w] = 1e-9 * (double)(atomic_load(&ctx->cc_busy_ns) - busy_ns0);
3047 if ((ctx->p->debug & DBG_TIMING) && round_completed) {
3048 char est_str[48] = "unknown";
3049 if (t_cc_est > 0.0) snprintf(est_str, sizeof(est_str), "%.3g thread-s", t_cc_est);
3050 fprintf(stderr, "# CC round w=%d work: %.3g thread-s measured, %s predicted (%.3fs, %d CC threads at the "
3051 "end), %lld nodes\n", w, ctx->cc_time_per_weight[w], est_str, cc_dur, n_cc,
3052 atomic_load(&ctx->cc_nodes) - nodes0);
3053 }
3054 }
3055
3056 int cw_found = atomic_load(&ctx->cc_found_weight);
3057 if (cw_found > 0 && certified && cw_found == w && !atomic_load(&ctx->cc_exact)) {
3058 atomic_store(&ctx->cc_exact, true);
3059 }
3060 const bool exact = (cw_found > 0) && atomic_load(&ctx->cc_exact);
3061 if (exact) {
3062 atomic_store(&ctx->dmin, cw_found);
3063 atomic_store(&ctx->dmax, cw_found);
3064
3065 int max_w_lim = minint(ctx->p->wmax > 0 ? ctx->p->wmax : nvar, cw_found + ctx->p->dW);
3066 if (ctx->p->dstop > 0 && ctx->p->dstop < cw_found) {
3067 /* a stop target dstop below the distance: the codewords of weight up to dstop + dW only (as in method 2) */
3068 max_w_lim = minint(max_w_lim, ctx->p->dstop + (ctx->p->dW > 0 ? ctx->p->dW : 0));
3069 }
3070 if (ctx->even_weight_only && (max_w_lim & 1)) max_w_lim--;
3071 if (ctx->p->outC && ctx->p->dW > 0 && w < max_w_lim) {
3072 if (ctx->p->debug & DBG_PROGRESS) {
3073 if (w == cw_found) {
3074 fprintf(stderr,
3075 "# CC round w=%d finished in %.3fs (%d CC threads, %d RW threads): found codewords "
3076 "(dmin=%d, continuing up to w=%d for dW=%d, total %lld cws)\n",
3077 w, cc_dur, n_cc, n_rw, cw_found, max_w_lim, ctx->p->dW, ctx_num_cws(ctx));
3078 } else if (round_completed) {
3079 fprintf(stderr,
3080 "# CC round w=%d finished in %.3fs (%d CC threads, %d RW threads): "
3081 "extra dW round completed (dmin=%d, total %lld cws)\n",
3082 w, cc_dur, n_cc, n_rw, cw_found, ctx_num_cws(ctx));
3083 }
3084 }
3085 } else {
3086 if (ctx->p->debug & DBG_PROGRESS) {
3087 if (w > cw_found) {
3088 if (round_completed) {
3089 fprintf(stderr,
3090 "# CC round w=%d finished in %.3fs (%d CC threads, %d RW threads): "
3091 "extra dW round completed (dmin=%d, total %lld cws)\n",
3092 w, cc_dur, n_cc, n_rw, cw_found, ctx_num_cws(ctx));
3093 }
3094 } else {
3095 fprintf(stderr, "# CC found min-weight codeword: d=%d (using %d CC threads, total %lld cws)\n",
3096 cw_found, n_cc, ctx_num_cws(ctx));
3097 }
3098 }
3099 /* (a stop reason recorded during the round, e.g., the timeout, is kept) */
3100 ctx_stop(ctx, (ctx->p->outC || ctx->p->maxC > 0) ? STOP_COLLECTED : STOP_CC_FOUND);
3101 break;
3102 }
3103 } else if (cur_dmax > 0 && cur_dmin >= cur_dmax) {
3104 /* Extra dW round completed */
3105 if (round_completed && (ctx->p->debug & DBG_PROGRESS)) {
3106 fprintf(stderr,
3107 "# CC round w=%d finished in %.3fs (%d CC threads, %d RW threads): "
3108 "extra dW round completed (dmin=%d, total %lld cws)\n",
3109 w, cc_dur, n_cc, n_rw, cur_dmin, ctx_num_cws(ctx));
3110 }
3111 if (!round_completed) {
3112 break;
3113 }
3114 } else {
3115 if (!round_completed) {
3116 break;
3117 }
3118
3119 /* Weight w analyzed without success */
3120 int new_dmin = ctx->even_weight_only ? w + 2 : w + 1;
3121 atomic_store(&ctx->dmin, new_dmin);
3122 if (ctx->p->debug & DBG_PROGRESS) {
3123 fprintf(stderr, "# CC round w=%d finished in %.3fs (%d CC threads, %d RW threads): no codewords -> dmin=%d\n",
3124 w, cc_dur, n_cc, n_rw, new_dmin);
3125 }
3126
3127 cur_dmax = atomic_load(&ctx->dmax);
3128 if (cur_dmax > 0 && new_dmin >= cur_dmax) {
3129 atomic_store(&ctx->dmin, cur_dmax);
3130 /* with outC, the CC rounds w = dmax..dmax+dW enumerate all codewords to export (RW may have missed some) */
3131 const int w_last = ctx->p->outC ? m3_cc_target_w(ctx, cur_dmax, cur_dmax) : 0;
3132 if (w_last > w) {
3133 if (ctx->p->debug & DBG_PROGRESS) {
3134 fprintf(stderr, "# bracketing bounds coincide: dmin = dmax = %d (continuing up to w=%d for dW=%d to export "
3135 "codewords)\n", cur_dmax, w_last, ctx->p->dW > 0 ? ctx->p->dW : 0);
3136 }
3137 } else {
3138 ctx_stop(ctx, STOP_BOUNDS);
3139 if (ctx->p->debug & DBG_PROGRESS) {
3140 fprintf(stderr, "# bracketing bounds coincide: dmin = dmax = %d\n", cur_dmax);
3141 }
3142 break;
3143 }
3144 } else if (ctx->p->dstop > 0 && new_dmin >= ctx->p->dstop) {
3145 /* the stop target dstop reached: no codeword of weight < dstop (RW may still find heavier codewords, but they
3146 * are not needed); with outC, the CC rounds w = dstop..dstop+dW collect codewords first */
3147 const int w_last = ctx->p->outC ? m3_cc_target_w(ctx, new_dmin, cur_dmax) : 0;
3148 if (w_last > w) {
3149 if (ctx->p->debug & DBG_PROGRESS) {
3150 fprintf(stderr, "# lower bound dmin=%d reached dstop=%d (continuing up to w=%d for dW=%d to export "
3151 "codewords)\n", new_dmin, ctx->p->dstop, w_last, ctx->p->dW > 0 ? ctx->p->dW : 0);
3152 }
3153 } else {
3154 ctx_stop(ctx, STOP_DSTOP);
3155 if (ctx->p->debug & DBG_PROGRESS) {
3156 fprintf(stderr, "# lower bound dmin=%d reached dstop=%d\n", new_dmin, ctx->p->dstop);
3157 }
3158 break;
3159 }
3160 }
3161 }
3162
3163 w++;
3164 }
3165}
3166
3167/* Default number of worker threads: the logical CPUs the process may run on, i.e., its CPU affinity mask (`taskset`,
3168 * cpusets) on Linux, otherwise the online CPUs (4 if not known). CPU quotas (e.g., `docker --cpus`) are not seen. */
3169static int available_cpus(void) {
3170#ifdef __linux__
3171 cpu_set_t set;
3172 CPU_ZERO(&set);
3173 if (sched_getaffinity(0, sizeof(set), &set) == 0) { /* fails with more than CPU_SETSIZE (1024) CPUs */
3174 const int cnt = CPU_COUNT(&set);
3175 if (cnt > 0) return cnt;
3176 }
3177#endif
3178 const long nprocs = sysconf(_SC_NPROCESSORS_ONLN);
3179 return (nprocs > 0) ? (int)nprocs : 4;
3180}
3181
3182/* The physical memory in bytes (0 if not known) */
3183static unsigned long long physical_memory_bytes(void) {
3184#if defined(_SC_PHYS_PAGES) && defined(_SC_PAGESIZE)
3185 const long pages = sysconf(_SC_PHYS_PAGES), psize = sysconf(_SC_PAGESIZE);
3186 if (pages > 0 && psize > 0) return (unsigned long long)pages * (unsigned long long)psize;
3187#endif
3188 return 0;
3189}
3190
3191/* Maximum number of RW workers for thread throttling (all workers in method 1, the RW share in method 3):
3192 * - dense memory: a full-matrix RW worker holds dense copies of H (m x n) and of its transpose (n x m), a worker
3193 * with ksub > 0 only the sampled subspace matrix (ksub x n). Above 15 MB per worker, RW is limited by the memory
3194 * bandwidth: the throughput peaks at 32-64 RW workers whatever the matrix size (e.g., 32 with 49 MB per worker,
3195 * 32-64 with 0.43 GB). Then at most 32 workers run RW, with their dense matrices in a quarter of the physical memory
3196 * (but at least 2 workers; 1.5 GB if the physical memory is not known);
3197 * - small step counts: at most ceil(steps/10) RW workers. */
3198static int rw_thread_cap(const params_t * const p) {
3199 int cap = INT_MAX;
3200 const int nvar = p->nvar;
3201 const int nrows = p->spaH ? p->spaH->rows : 0;
3202 if (nrows > 0 && nvar > 0) {
3203 const size_t words_n = ((size_t)nvar + 63) / 64;
3204 size_t dense_bytes_per_thread;
3205 if (p->ksub > 0) {
3206 dense_bytes_per_thread = (size_t)minint(p->ksub, nrows) * words_n * 8;
3207 } else {
3208 dense_bytes_per_thread = (size_t)nrows * words_n * 8 + (size_t)nvar * (((size_t)nrows + 63) / 64) * 8;
3209 }
3210 if (dense_bytes_per_thread > 15ULL * 1024 * 1024) {
3211 const unsigned long long ram = physical_memory_bytes();
3212 const unsigned long long budget = ram ? ram / 4 : 1536ULL * 1024 * 1024;
3213 const unsigned long long max_mem_threads = budget / dense_bytes_per_thread;
3214 cap = (max_mem_threads < 2) ? 2 : ((max_mem_threads > 32) ? 32 : (int)max_mem_threads);
3215 }
3216 }
3217 if (p->steps > 0) {
3218 const int max_step_threads = (int)(((long)p->steps + 9) / 10);
3219 cap = minint(cap, max_step_threads);
3220 }
3221 return cap;
3222}
3223
3224/* DBG_PROGRESS: the run plan (method, threads, RW steps, CC weights, timeout, rng seed) */
3225static void print_run_plan(const distfork_ctx_t * const ctx, const int init_dmax) {
3226 const params_t * const p = ctx->p;
3227 fprintf(stderr, "# run plan: method=%d (%s), %d threads", p->method,
3228 (p->method == 1) ? "RW" : ((p->method == 2) ? "CC" : "bracketing"), ctx->num_threads);
3229 if (ctx->spawn_initial < ctx->num_threads) fprintf(stderr, " (%d at first)", ctx->spawn_initial);
3230 if (p->method == 3) fprintf(stderr, " (RW on up to %d)", ctx->rw_threads);
3231 if ((p->method & 1) && ctx->rw_threads > 0)
3232 fprintf(stderr, ", steps=%ld, min_hits=%d, ksub=%d", ctx->total_rw_steps, p->min_hits, p->ksub);
3233 if (p->method & 2) {
3234 fprintf(stderr, ", CC from w=%d", p->noscan ? p->wmax : (p->dmin > 1 ? p->dmin : 1));
3235 if (p->wmax > 0) fprintf(stderr, " to wmax=%d", p->wmax);
3236 if (p->smax == 0) fprintf(stderr, " (cc_prune=%d)", p->cc_prune);
3237 if (p->method == 3 && ctx->dexp > 0) fprintf(stderr, ", dexp=%d", ctx->dexp);
3238 }
3239 if (init_dmax > 0) fprintf(stderr, ", dmax=%d", init_dmax);
3240 if (p->dstop > 0) fprintf(stderr, ", dstop=%d", p->dstop);
3241 if (ctx->timeout > 0.0) fprintf(stderr, ", timeout=%gs", ctx->timeout);
3242 else fprintf(stderr, ", no timeout");
3243 fprintf(stderr, ", seed=%d\n", p->seed);
3244}
3245
3246/* DBG_SUMMARY: why the run ended (see ctx_stop()) */
3247static void print_stop_reason(distfork_ctx_t * const ctx, const int final_dmax, const bool cc_exact) {
3248 params_t * const p = ctx->p;
3249 const int cc_found = atomic_load(&ctx->cc_found_weight);
3250 const bool rw_ran = (p->method & 1) && ctx->rw_threads > 0;
3251 fprintf(stderr, "# stopped after %.3fs: ", get_time_sec() - ctx->start_time);
3252 switch (atomic_load(&ctx->stop_reason)) {
3253 case STOP_BOUNDS:
3254 fprintf(stderr, "bounds coincide: dmin = dmax = %d", final_dmax);
3255 break;
3256 case STOP_CC_FOUND:
3257 if (cc_exact)
3258 fprintf(stderr, "CC found min-weight codeword: d=%d", cc_found);
3259 else
3260 fprintf(stderr, "CC found a codeword of weight %d (upper bound only: lower weights not scanned)", cc_found);
3261 break;
3262 case STOP_RW_CONVERGED: {
3263 cw_hit_stats_t st;
3264 codeword_hit_stats(p, &st);
3265 fprintf(stderr, "RW convergence reached (<n>=%.2f >= min_hits=%d after %ld RW steps)", st.avg, p->min_hits,
3266 atomic_load(&ctx->rw_steps_completed));
3267 break;
3268 }
3269 case STOP_RW_STEPS:
3270 fprintf(stderr, "all %ld RW steps done", ctx->total_rw_steps);
3271 break;
3272 case STOP_TIMEOUT:
3273 fprintf(stderr, "timeout=%gs reached", ctx->timeout);
3274 if (ctx->round_open)
3275 fprintf(stderr, " during CC round w=%d (%.1f%% of start columns claimed)", ctx->round_w, cc_round_claimed(ctx));
3276 if (rw_ran)
3277 fprintf(stderr, ", %ld of %ld RW steps done", atomic_load(&ctx->rw_steps_completed), ctx->total_rw_steps);
3278 break;
3279 case STOP_CC_DONE:
3280 fprintf(stderr, "CC done up to w=%d", ctx->stop_w);
3281 if (p->method == 3 && rw_ran) fprintf(stderr, ", RW ended (%s)", rw_end_state(ctx));
3282 break;
3283 case STOP_CC_SLOW:
3284 fprintf(stderr, "CC round w=%d predicted not to finish before the timeout, RW ended (%s)", ctx->stop_w,
3285 rw_end_state(ctx));
3286 break;
3287 case STOP_WMIN:
3288 fprintf(stderr, "found a codeword of weight %d <= wmin=%d", final_dmax, p->wmin);
3289 break;
3290 case STOP_MAXC:
3291 fprintf(stderr, "maxC=%lld codewords collected", p->maxC);
3292 break;
3293 case STOP_COLLECTED: {
3294 /* the collection rounds w = d..d+dW (with outC), but at most up to dstop+dW (a stop target dstop < d) and wmax */
3295 int w_hi = final_dmax;
3296 if (p->outC && p->dW > 0) {
3297 w_hi = final_dmax + p->dW;
3298 if (p->dstop > 0 && p->dstop < final_dmax) w_hi = minint(w_hi, p->dstop + p->dW);
3299 if (p->wmax > 0) w_hi = minint(w_hi, p->wmax);
3300 }
3301 fprintf(stderr, "CC enumerated all codewords of weight %d", final_dmax);
3302 if (w_hi > final_dmax) fprintf(stderr, "..%d", w_hi);
3303 if (p->outC) fprintf(stderr, " (for outC)");
3304 break;
3305 }
3306 case STOP_DSTOP:
3307 fprintf(stderr, "lower bound dmin=%d reached dstop=%d", atomic_load(&ctx->dmin), p->dstop);
3308 break;
3309 default:
3310 fprintf(stderr, "search ended");
3311 }
3312 fprintf(stderr, "\n");
3313}
3315typedef struct {
3316 uint64_t h;
3317 int c;
3319
3320static int cc_hash_col_cmp(const void *a, const void *b) {
3321 const cc_hash_col_t *x = a, *y = b;
3322 if (x->h != y->h) return (x->h < y->h) ? -1 : 1;
3323 return (x->c > y->c) - (x->c < y->c);
3324}
3325
3326/* The CC column-syndrome hash of H (mHT: its transpose with m rows), see cc_hash_t; NULL if H has no nonzero column */
3327static cc_hash_t *cc_hash_new(const csr_t * const mHT, const int m) {
3328 const int n = mHT->rows;
3329 cc_hash_t * const hs = calloc(1, sizeof(cc_hash_t));
3330 cc_hash_col_t * const tmp = malloc((size_t)(n > 0 ? n : 1) * sizeof(cc_hash_col_t));
3331 if (!hs || !tmp) ERROR("memory allocation");
3332 hs->zr = malloc((size_t)(m > 0 ? m : 1) * sizeof(uint64_t));
3333 hs->hc = calloc((size_t)(n > 0 ? n : 1), sizeof(uint64_t));
3334 if (!hs->zr || !hs->hc) ERROR("memory allocation");
3335 uint64_t state = 0x243f6a8885a308d3ULL; /* fixed: the results do not depend on the hash */
3336 for (int r = 0; r < m; r++) hs->zr[r] = splitmix64(&state);
3337 int nz = 0;
3338 for (int c = 0; c < n; c++) {
3339 if (mHT->p[c + 1] == mHT->p[c]) continue;
3340 uint64_t h = 0;
3341 for (int k = mHT->p[c]; k < mHT->p[c + 1]; k++) h ^= hs->zr[mHT->i[k]];
3342 hs->hc[c] = h;
3343 tmp[nz].h = h;
3344 tmp[nz++].c = c;
3345 }
3346 if (nz == 0) {
3347 free(tmp);
3348 free(hs->zr);
3349 free(hs->hc);
3350 free(hs);
3351 return NULL;
3352 }
3353 qsort(tmp, nz, sizeof(cc_hash_col_t), cc_hash_col_cmp);
3354 int nkeys = 0;
3355 for (int k = 0; k < nz; k++) nkeys += (k == 0 || tmp[k].h != tmp[k - 1].h);
3356 int lg = 0; /* ceil(log2(nkeys)) */
3357 while ((1LL << lg) < nkeys) lg++;
3358 const int tbits = lg + 1; /* table: at most half full */
3359 const int fbits = (lg + 4 > 6) ? lg + 4 : 6; /* filter: 16 bits per key */
3360 hs->tshift = 64 - tbits;
3361 hs->tmask = (int)((1ULL << tbits) - 1);
3362 hs->tab = calloc((size_t)1 << tbits, sizeof(cc_hash_slot_t));
3363 hs->bmask = ((uint64_t)1 << fbits) - 1;
3364 hs->bits = calloc((size_t)1 << (fbits - 6), sizeof(uint64_t));
3365 hs->cols = malloc((size_t)nz * sizeof(int));
3366 if (!hs->tab || !hs->bits || !hs->cols) ERROR("memory allocation");
3367 for (int k = 0; k < nz; k++) hs->cols[k] = tmp[k].c;
3368 for (int k = 0; k < nz;) {
3369 const uint64_t key = tmp[k].h;
3370 int k2 = k + 1;
3371 while (k2 < nz && tmp[k2].h == key) k2++;
3372 int j = (int)(key >> hs->tshift);
3373 while (hs->tab[j].cnt > 0) j = (j + 1) & hs->tmask;
3374 hs->tab[j].key = key;
3375 hs->tab[j].beg = k;
3376 hs->tab[j].cnt = k2 - k;
3377 const uint64_t fb = key & hs->bmask;
3378 hs->bits[fb >> 6] |= (uint64_t)1 << (fb & 63);
3379 k = k2;
3380 }
3381 hs->ncols = nz;
3382 hs->nkeys = nkeys;
3383 free(tmp);
3384 return hs;
3385}
3386
3387static void cc_hash_free(cc_hash_t * const hs) {
3388 if (!hs) return;
3389 free(hs->zr);
3390 free(hs->hc);
3391 free(hs->bits);
3392 free(hs->tab);
3393 free(hs->cols);
3394 free(hs);
3395}
3397int main(int argc, char **argv) {
3398 params_t * const p = &prm;
3399
3400 var_init(argc, argv, p);
3401
3402 if (p->finC) {
3403 nzlist_read(p->finC, p);
3404 }
3405
3406 /* Check ksub feasibility before thread throttling (require m >= nu >= n - m) */
3407 if ((p->method & 1) && p->ksub > 0 && p->spaH) {
3408 int nrows = p->spaH->rows;
3409 int nvar = p->spaH->cols;
3410 if (2 * nrows < nvar) {
3411 if (p->debug & DBG_SUMMARY) {
3412 fprintf(stderr,
3413 "# Warning: ksub=%d requested, but m=%d < n-m=%d (<= nu); "
3414 "falling back to full-matrix RW (ksub=0)\n",
3415 p->ksub, nrows, nvar - nrows);
3416 }
3417 p->ksub = 0;
3418 }
3419 }
3420
3421 /* Determine number of threads (default: all logical CPUs available to the process) */
3422 int requested_threads = p->threads;
3423 int num_threads = p->threads;
3424 if (num_threads <= 0) num_threads = available_cpus();
3425 /* Workers which may run RW (each allocates dense RW matrices): all workers in method 1, none in method 2.
3426 * In method 3, the RW share may be limited further while CC rounds can use all workers. */
3427 int rw_threads = 0;
3428
3429 if (!p->nothrottle) {
3430 /* Thread throttling for small codes and large-memory matrices */
3431 int nvar = p->nvar;
3432 int nrows = p->spaH ? p->spaH->rows : 0;
3433 unsigned long long n_elements = (unsigned long long)nrows * (unsigned long long)nvar;
3434
3435 /* 1. Small-code throttling by the RW work (methods 1 and 3): avoid the thread start-up overhead on tiny
3436 * workloads. The CC work is not known in advance: method 2 starts a few workers only, and the others once a
3437 * CC round takes longer (see CC_SPAWN_INITIAL) */
3438 if (p->method & 1) {
3439 const double rw_work = (double)n_elements * (double)(p->steps > 0 ? p->steps : 1);
3440 if (nvar < 60 || (n_elements < 20000ULL && rw_work < 5.0e7)) {
3441 if (num_threads > 4) num_threads = 4;
3442 } else if ((nvar < 150 || n_elements < 100000ULL) && rw_work < 2.0e8) {
3443 if (num_threads > 16) num_threads = 16;
3444 }
3445 }
3446
3447 /* 2. Large-matrix memory throttling and 3. steps throttling: these limit only the RW workers, i.e., all
3448 * workers in method 1 and the RW share in method 3; CC (method 2, CC rounds in method 3) is not limited */
3449 if (p->method & 1) {
3450 const int rw_cap = rw_thread_cap(p);
3451 if (p->method == 1 && num_threads > rw_cap) num_threads = rw_cap;
3452 rw_threads = minint(num_threads, rw_cap);
3453 }
3454
3455 if (requested_threads > 0 && num_threads != requested_threads && (p->debug & DBG_TIMING)) {
3456 fprintf(stderr, "# Note: throttled threads from %d to %d (n=%d, r=%d)\n",
3457 requested_threads, num_threads, nvar, nrows);
3458 }
3459 } else {
3460 if (p->method & 1) rw_threads = num_threads;
3461 if (p->debug & DBG_TIMING) {
3462 fprintf(stderr, "# Thread throttling disabled (nothrottle=1); running with %d threads\n", num_threads);
3463 }
3464 }
3465 if (p->method == 3 && p->steps == 0) {
3466 rw_threads = 0; /* no RW steps: pure CC through the bracketing coordinator, no RW matrices needed */
3467 }
3468 if (p->method == 3 && rw_threads < num_threads && (p->debug & DBG_TIMING)) {
3469 if (rw_threads == 0) {
3470 fprintf(stderr, "# Note: steps=0, no RW; CC rounds use all %d threads\n", num_threads);
3471 } else {
3472 fprintf(stderr, "# Note: RW limited to %d of %d threads (memory, steps); CC rounds can use all threads\n",
3473 rw_threads, num_threads);
3474 }
3475 }
3476
3477 double timeout = (p->timeout > 0.0) ? p->timeout : 0.0;
3478
3479 distfork_ctx_t ctx;
3480 memset(&ctx, 0, sizeof(ctx));
3481 ctx.p = p;
3482 ctx.num_threads = num_threads;
3483 ctx.rw_threads = rw_threads;
3484 ctx.timeout = timeout;
3485 ctx.start_time = get_time_sec();
3486 ctx.dexp = p->dexp;
3487 ctx.total_rw_steps = (p->steps >= 0) ? p->steps : 1;
3488
3489 /* Initialize dmin and dmax */
3490 atomic_init(&ctx.dmin, p->dmin > 1 ? p->dmin : 1);
3491 int init_dmax = 0;
3492 if (p->dmax > 0) {
3493 init_dmax = p->dmax;
3494 }
3495 if (p->min_w != INT_MAX) {
3496 if (init_dmax == 0 || p->min_w < init_dmax) {
3497 init_dmax = p->min_w;
3498 }
3499 }
3500 atomic_init(&ctx.dmax, init_dmax);
3501
3502 if (init_dmax > 0 && p->wmin > 0 && init_dmax <= p->wmin && !p->outC && p->maxC == 0) {
3503 if (p->debug & DBG_SUMMARY) {
3504 fprintf(stderr, "# stopped: known upper bound dmax=%d <= wmin=%d (no search)\n", init_dmax, p->wmin);
3505 }
3506 printf("%d %d 0\n", p->dmin > 1 ? p->dmin : 1, init_dmax);
3507 var_kill(p);
3508 return 0;
3509 }
3510
3511 if (p->method == 3 && init_dmax > 0 && p->dmin > 1 && p->dmin >= init_dmax && !p->outC) {
3512 if (p->debug & DBG_SUMMARY) {
3513 fprintf(stderr, "# stopped: bounds coincide: dmin = dmax = %d (supplied, no search)\n", init_dmax);
3514 }
3515 printf("%d %d 0\n", p->dmin, init_dmax);
3516 var_kill(p);
3517 return 0;
3518 }
3519
3520 /* the supplied dmin already reaches the stop target dstop (any method; with outC or maxC, the CC rounds which collect
3521 * codewords still run) */
3522 if (p->dstop > 0 && p->dmin >= p->dstop && !p->outC && p->maxC == 0) {
3523 if (p->debug & DBG_SUMMARY) {
3524 fprintf(stderr, "# stopped: lower bound dmin=%d >= dstop=%d (supplied, no search)\n", p->dmin, p->dstop);
3525 }
3526 printf("%d %d 0\n", (init_dmax > 0 && p->dmin >= init_dmax) ? init_dmax : p->dmin, init_dmax);
3527 var_kill(p);
3528 return 0;
3529 }
3530
3531 atomic_init(&ctx.cc_found_weight, 0);
3532 atomic_init(&ctx.cc_exact, false);
3533 atomic_init(&ctx.stop_flag, false);
3534 atomic_init(&ctx.rw_stop_flag, false);
3535 atomic_init(&ctx.stop_reason, STOP_NONE);
3536 ctx.status_next = 1.0; /* DBG_STATUS: after 1, 2, 4, ... s, then every 60 s */
3537 ctx.status_dt = 1.0;
3538 atomic_init(&ctx.cw_feed_limit, codeword_feed_limit(p));
3539 atomic_init(&ctx.rw_steps_started, 0);
3540 atomic_init(&ctx.rw_steps_completed, 0);
3541 atomic_init(&ctx.rw_busy_ns, 0);
3542 atomic_init(&ctx.rw_timed_steps, 0);
3543 atomic_init(&ctx.rw_rank, 0);
3544 atomic_init(&ctx.rw_unif_steps, 0);
3545 atomic_init(&ctx.rw_pairs_l, 0);
3546 atomic_init(&ctx.rw_pairs_checked, 0);
3547 atomic_init(&ctx.rw_hits1, 0);
3548 atomic_init(&ctx.rw_hits2, 0);
3549 uint64_t * const rw_lhash = (p->pairs != 0 && (p->method & 1) && p->ksub <= 0) ? rw_lhash_init(p) : NULL;
3550 ctx.rw_lhash = rw_lhash;
3551 atomic_init(&ctx.cc_next, cc_pack(0, 0, 0));
3552 atomic_init(&ctx.cc_active_workers, 0);
3553 atomic_init(&ctx.cc_target_workers, 0);
3554 atomic_init(&ctx.cc_round_active, 0);
3555 atomic_init(&ctx.cc_busy_ns, 0);
3556 atomic_init(&ctx.cc_nodes, 0);
3557 atomic_init(&ctx.cw_red_cc, 0);
3558 atomic_init(&ctx.cw_red_rw, 0);
3559 atomic_init(&ctx.next_refresh_step, p->refresh > 0 ? p->refresh : 0);
3560
3561 pthread_mutex_init(&ctx.cw_mutex, NULL);
3562 { /* ksub basis lock: writer-preferring (glibc), so that a waiting refresh (writer) is not starved by overlapping RW
3563 * batches (readers, which never hold the lock recursively) */
3564 pthread_rwlockattr_t rwattr;
3565 pthread_rwlockattr_init(&rwattr);
3566#ifdef __GLIBC__
3567 pthread_rwlockattr_setkind_np(&rwattr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP);
3568#endif
3569 pthread_rwlock_init(&ctx.basis_rwlock, &rwattr);
3570 pthread_rwlockattr_destroy(&rwattr);
3571 }
3572
3573 ctx.mHT_cc = csr_transpose(NULL, p->spaH);
3574 ctx.max_col_W = csr_max_row_wght(ctx.mHT_cc);
3575 /* the CC walk with bitset syndromes and the column-syndrome hash for its last two levels (see cc_walk(); not with
3576 * smax > 0, which needs the syndrome weights of the leaf level) */
3577 cc_hash_t * const cc_hash = ((p->method & 2) && p->smax == 0) ? cc_hash_new(ctx.mHT_cc, p->spaH->rows) : NULL;
3578 ctx.cc_hash = cc_hash;
3579 ctx.even_weight_only = ((p->method & 2) && p->smax == 0 && !p->noscan && p->spaH &&
3580 csr_all_ones_in_rowspan(p->spaH));
3581 if (cc_hash && (p->debug & DBG_TIMING)) {
3582 fprintf(stderr, "# CC: %s, bitset syndromes, hash of %d nonzero columns (%d distinct) for the last two levels"
3583 " (%d table slots)%s\n", (p->cc_prune == 0) ? "walk of the first unsatisfied row (cc_prune=0)"
3584 : ((p->cc_prune == 1) ? "branch and bound (cc_prune=1)" : "branch and bound with the cover bounds "
3585 "(cc_prune=2)"), cc_hash->ncols, cc_hash->nkeys, cc_hash->tmask + 1,
3586 ctx.even_weight_only ? ", even weights only (1 in rowspan(H))" : "");
3587 }
3588 /* CC work units (see `cc_pack()`): radix B = the maximum row weight of H, B^L units per start index */
3589 ctx.cc_B = csr_max_row_wght(p->spaH);
3590 ctx.cc_branch = (p->spaH->rows > 0) ? fmax(1.0, (double)p->spaH->p[p->spaH->rows] / p->spaH->rows - 1.0) : 1.0;
3591 ctx.cc_R[0] = 1;
3592 for (int l = 1; l <= CC_SPLIT_MAX_LEVELS; l++) {
3593 const int prev = ctx.cc_R[l - 1];
3594 ctx.cc_R[l] = (prev > 0 && ctx.cc_B > 0 && prev <= INT_MAX / ctx.cc_B) ? prev * ctx.cc_B : 0;
3595 }
3596 ctx.round_R = 1;
3597
3598 if ((p->method & 1) && p->ksub > 0 && ctx.rw_threads > 0) {
3599 double t_ker0 = get_time_sec();
3600 ctx.N_global = mzd_nullspace(p->spaH);
3601 ctx.nu = ctx.N_global ? ctx.N_global->nrows : 0;
3602 if (p->spaH->rows < ctx.nu || ctx.nu <= 0) {
3603 if (p->debug & DBG_SUMMARY) {
3604 fprintf(stderr,
3605 "# Warning: ksub=%d requested, but m=%d < nu=%d; "
3606 "falling back to full-matrix RW (ksub=0)\n",
3607 p->ksub, p->spaH->rows, ctx.nu);
3608 }
3609 safe_mzd_free(ctx.N_global);
3610 ctx.N_global = NULL;
3611 ctx.nu = 0;
3612 p->ksub = 0;
3613 if (!p->nothrottle) { /* full-matrix RW workers need more memory: re-apply the RW thread limit */
3614 const int rw_cap = rw_thread_cap(p);
3615 if (p->method == 1 && num_threads > rw_cap) {
3616 num_threads = rw_cap;
3617 ctx.num_threads = rw_cap;
3618 }
3619 if (ctx.rw_threads > rw_cap) ctx.rw_threads = rw_cap;
3620 if (p->debug & DBG_TIMING) {
3621 fprintf(stderr, "# Note: %d threads, %d of them for RW (full-matrix RW)\n", num_threads, ctx.rw_threads);
3622 }
3623 }
3624 } else if (p->debug & DBG_TIMING) {
3625 fprintf(stderr,
3626 "# computed ker(H) in %.4fs: nu=%d, n=%d, ksub=%d (eff=%d)\n",
3627 get_time_sec() - t_ker0, ctx.nu, p->spaH->cols, p->ksub,
3628 minint(p->ksub, ctx.nu));
3629 }
3630 if (ctx.nu > 0) atomic_store(&ctx.rw_rank, p->spaH->cols - ctx.nu); /* rank of H */
3631 }
3632
3633 /* method 2 starts a few workers only, and the others once a CC round takes longer (see CC_SPAWN_INITIAL) */
3634 ctx.spawn_initial = (p->method == 2 && !p->nothrottle && num_threads > CC_SPAWN_INITIAL) ? CC_SPAWN_INITIAL
3635 : num_threads;
3636 if (ctx.spawn_initial < num_threads && (p->debug & DBG_TIMING)) {
3637 fprintf(stderr, "# Note: CC starts %d of %d threads, the others when a CC round takes longer\n",
3638 ctx.spawn_initial, num_threads);
3639 }
3640
3641 if (p->debug & DBG_PROGRESS) print_run_plan(&ctx, init_dmax);
3642
3643 /* Allocate and launch worker threads */
3644 ctx.threads = malloc(num_threads * sizeof(pthread_t));
3645 worker_arg_t *args = malloc(num_threads * sizeof(worker_arg_t));
3646 if (!ctx.threads || !args) ERROR("memory allocation");
3647 ctx.wargs = args;
3648
3649 for (int i = 0; i < num_threads; i++) {
3650 args[i].ctx = &ctx;
3651 args[i].tid = i;
3652 args[i].irr_ws = NULL; /* (allocated by the worker, see worker_thread_func()) */
3653 for (int k = 0; k < MAX_W; k++) {
3654 args[i].min_swei[k] = p->spaH->rows + 1;
3655 }
3656 }
3657 ctx_spawn(&ctx, ctx.spawn_initial);
3658
3659 if (p->method == 1) {
3660 run_method1_coordinator(&ctx);
3661 } else if (p->method == 2) {
3662 run_method2_coordinator(&ctx);
3663 } else if (p->method == 3) {
3664 run_method3_coordinator(&ctx);
3665 } else {
3666 ERROR("invalid method %d\n", p->method);
3667 }
3668
3669 /* Signal stop and wait for all workers */
3670 atomic_store(&ctx.stop_flag, true);
3671 for (int i = 0; i < ctx.spawned; i++) {
3672 pthread_join(ctx.threads[i], NULL);
3673 }
3674
3675 int final_dmin = atomic_load(&ctx.dmin);
3676 int final_dmax = atomic_load(&ctx.dmax);
3677 int cc_found = atomic_load(&ctx.cc_found_weight);
3678 /* A CC codeword gives the exact distance only if found in a round where all lower weights had
3679 * been analyzed (not with noscan=1 unless dmin=wmax is supplied); see `cc_exact`. */
3680 const bool cc_exact = (cc_found > 0) && atomic_load(&ctx.cc_exact) &&
3681 (final_dmax == 0 || cc_found <= final_dmax);
3682
3683 if (cc_exact) {
3684 final_dmin = cc_found;
3685 final_dmax = cc_found;
3686 } else if (final_dmax > 0 && final_dmin >= final_dmax) {
3687 final_dmin = final_dmax;
3688 }
3689
3690 /* why the run ended (e.g., RW ends the run on wmin, unless codewords are collected with outC or maxC) */
3691 if (p->debug & DBG_SUMMARY) print_stop_reason(&ctx, final_dmax, cc_exact);
3692
3693 /* Confinement profile output (if smax > 0 and CC was run) */
3694 if (p->smax && p->method >= 2) {
3695 int max_w_analyzed = (final_dmin > 1) ? (final_dmin - 1) : ((p->wmax > 0) ? p->wmax : 0);
3696 if (cc_exact) max_w_analyzed = cc_found;
3697 if (max_w_analyzed > 0) {
3698 int global_swei[MAX_W];
3699 for (int i = 0; i < MAX_W; i++) global_swei[i] = p->spaH->rows + 1;
3700 for (int t = 0; t < ctx.spawned; t++) {
3701 for (int i = 1; i <= max_w_analyzed; i++) {
3702 if (args[t].min_swei[i] < global_swei[i]) {
3703 global_swei[i] = args[t].min_swei[i];
3704 }
3705 }
3706 }
3707 int skipped = 0;
3708 if (p->debug & DBG_SUMMARY) {
3709 for (int i = 1; i <= max_w_analyzed; i++) {
3710 if (global_swei[i] <= p->spaH->rows) {
3711 fprintf(stderr, "# w=%d min non-zero syndrome weight %d\n", i, global_swei[i]);
3712 } else {
3713 skipped = 1;
3714 }
3715 }
3716 } else {
3717 fprintf(stderr, "# confinement: ");
3718 for (int i = 1; i <= max_w_analyzed; i++) {
3719 if (global_swei[i] <= p->spaH->rows) {
3720 fprintf(stderr, "%d%s", global_swei[i], i < max_w_analyzed ? "," : "");
3721 } else {
3722 skipped = 1;
3723 fprintf(stderr, "?%s", i < max_w_analyzed ? "," : "");
3724 }
3725 }
3726 fprintf(stderr, "\n");
3727 }
3728 if (skipped) {
3729 fprintf(stderr,
3730 "# Note: Some weights were skipped in confinement profile. "
3731 "Try increasing smax (current: %d)\n", p->smax);
3732 }
3733 }
3734 }
3735
3736 long reported_rw_steps = 0;
3737 if (p->method != 2 && !cc_exact) {
3738 reported_rw_steps = atomic_load(&ctx.rw_steps_completed);
3739 }
3740
3741 if (p->debug & DBG_SUMMARY) {
3742 print_codeword_stats(stderr, p);
3743 const long long red_cc = atomic_load(&ctx.cw_red_cc), red_rw = atomic_load(&ctx.cw_red_rw);
3744 if (red_cc > 0 || red_rw > 0) /* (see cw_irreducible(); a codeword found several times is counted each time) */
3745 fprintf(stderr, "# reducible codewords skipped (irred=1, a codeword plus a disjoint nonzero vector with zero "
3746 "syndrome): %lld times in CC, %lld times in RW\n", red_cc, red_rw);
3747 /* RW data for the information-set estimate (see print_rw_infoset_estimate(); also used by the Python wrapper) */
3748 const long rw_done = atomic_load(&ctx.rw_steps_completed);
3749 const long rw_unif = atomic_load(&ctx.rw_unif_steps);
3750 const int rw_rank = atomic_load(&ctx.rw_rank);
3751 const long rw_timed = atomic_load(&ctx.rw_timed_steps);
3752 const double t_step = (rw_timed > 0) ? 1e-9 * (double)atomic_load(&ctx.rw_busy_ns) / (double)rw_timed : 0.0;
3753 const int rw_pairs_l = atomic_load(&ctx.rw_pairs_l);
3754 if (rw_done > 0 && rw_rank > 0) {
3755 fprintf(stderr, "# RW information sets: n=%d, rank(H)=%d, steps=%ld (uniform permutations: %ld), ksub=%d, "
3756 "pairs=%d, %.3g s/step per thread, %d RW threads\n", p->nvar, rw_rank, rw_done, rw_unif, p->ksub,
3757 rw_pairs_l, t_step, ctx.rw_threads);
3758 if (rw_pairs_l > 0)
3759 fprintf(stderr, "# RW pairs (p=2, buckets of l=%d random pivot rows): %.3g pairs checked per step; codewords "
3760 "recorded with weight up to the upper bound: %lld from pairs, %lld from single columns\n", rw_pairs_l,
3761 (double)atomic_load(&ctx.rw_pairs_checked) / (double)rw_done, (long long)atomic_load(&ctx.rw_hits2),
3762 (long long)atomic_load(&ctx.rw_hits1));
3763 if (rw_unif > 0 && rw_unif < rw_done) /* the codewords found by the uniform and by the window steps */
3764 print_rw_step_stats(stderr, p);
3765 }
3766 /* the RW upper bound is not certified: warn if the hit counts suggest that some codewords are much harder to
3767 * find than others of the same weight (one pass over the codewords in hash), and then also print the
3768 * information-set estimate of the probability that RW missed a lighter codeword (not after the stop target
3769 * dstop was reached: codewords of weight >= dstop are not needed) */
3770 if (reported_rw_steps > 0 && final_dmin < final_dmax && atomic_load(&ctx.stop_reason) != STOP_DSTOP &&
3771 codeword_hit_check(stderr, p) > 0 && rw_unif > 0 && rw_rank > 0) {
3772 print_rw_infoset_estimate(stderr, p->nvar, rw_rank, (final_dmin > 1) ? final_dmin : 1, final_dmax - 1,
3773 rw_unif, rw_done, (ctx.rw_threads > 0) ? t_step / ctx.rw_threads : 0.0, rw_pairs_l);
3774 }
3775 }
3776
3777 /* Output to stdout: dmin dmax rw_steps */
3778 printf("%d %d %ld\n", final_dmin, final_dmax, reported_rw_steps);
3779 fflush(stdout);
3780
3781 /* Codeword export */
3782 if (p->outC) {
3783 char comment[256];
3784 sprintf(comment, "generated by dist_m4ri");
3785 const long long num_written = nzlist_write(p->outC, comment, p);
3786 if (p->debug & DBG_SUMMARY) {
3787 fprintf(stderr, "# exported %lld codewords to %s\n", num_written, p->outC);
3788 }
3789 }
3790
3791 if (p->debug & DBG_CWDUMP) {
3792 cw_vec_t *cw;
3793 fprintf(stderr, "# all %lld codewords in hash (1-based columns, cnt: number of hits, of which u in RW steps with "
3794 "uniform permutations and w in RW steps with windows):\n", p->num_cws);
3795 for (cw = p->codewords; cw != NULL; cw = (cw_vec_t *)(cw->hh.next)) {
3796 fprintf(stderr, "# cw: [ ");
3797 for (int i = 0; i < cw->weight; i++) fprintf(stderr, "%d ", 1 + col_orig_index(p, cw->arr[i]));
3798 fprintf(stderr, "] cnt=%d (u=%d, w=%d)\n", cw->cnt, cw->cnt_u, cw->cnt_w);
3799 }
3800 }
3801
3802 /* Cleanup */
3803 free(rw_lhash);
3804 safe_mzd_free(ctx.N_global);
3805 cc_hash_free(cc_hash);
3806 csr_free(ctx.mHT_cc);
3807 free(ctx.threads);
3808 free(args);
3809 pthread_rwlock_destroy(&ctx.basis_rwlock);
3810 pthread_mutex_destroy(&ctx.cw_mutex);
3811
3812 var_kill(p);
3813
3814 return 0;
3815}
#define M3_DEADLINE_FRACTION
Definition dist_m4ri.c:2773
#define CC_SPLIT_UNITS_PER_THREAD
Definition dist_m4ri.c:349
#define CC_SPAWN_TIME
Definition dist_m4ri.c:2454
#define RW_TUNE_STEPS
Definition dist_m4ri.c:1310
#define CC_SPLIT_CLAIMS_UNKNOWN
Definition dist_m4ri.c:352
#define M3_OVERRUN_FACTOR
Definition dist_m4ri.c:2774
int main(int argc, char **argv)
Definition dist_m4ri.c:3396
struct worker_arg worker_arg_t
#define CC_SPAWN_INITIAL
Definition dist_m4ri.c:2452
#define RW_NT_MAX
Definition dist_m4ri.c:1287
#define CC_SPLIT_CLAIMS_PER_WORK
Definition dist_m4ri.c:351
#define RW_K_MAX
Definition dist_m4ri.c:1286
#define CC_LEV_SHIFT
Definition dist_m4ri.c:323
@ STOP_TIMEOUT
Definition dist_m4ri.c:281
@ STOP_CC_FOUND
Definition dist_m4ri.c:278
@ STOP_DSTOP
Definition dist_m4ri.c:287
@ STOP_NONE
Definition dist_m4ri.c:276
@ STOP_CC_DONE
Definition dist_m4ri.c:282
@ STOP_WMIN
Definition dist_m4ri.c:284
@ STOP_RW_CONVERGED
Definition dist_m4ri.c:279
@ STOP_RW_STEPS
Definition dist_m4ri.c:280
@ STOP_CC_SLOW
Definition dist_m4ri.c:283
@ STOP_BOUNDS
Definition dist_m4ri.c:277
@ STOP_COLLECTED
Definition dist_m4ri.c:286
@ STOP_MAXC
Definition dist_m4ri.c:285
#define CC_SPLIT_MIN_WORK
Definition dist_m4ri.c:350
#define CC_SPLIT_MAX_LEVELS
Definition dist_m4ri.c:161
#define CC_IDX_BITS
Definition dist_m4ri.c:322
#define RW_PAIRS_POLL
Definition dist_m4ri.c:1548
int arr[0]
Definition util_hash.h:42
int cnt_u
Definition util_hash.h:40
int cnt
Definition util_hash.h:39
UT_hash_handle hh
Definition util_hash.h:37
int cnt_w
Definition util_hash.h:41
int weight
Definition util_hash.h:38
int vec[0]
Definition util_hash.h:48
void * next
Definition uthash.h:1129
uint64_t key
Definition dist_m4ri.c:170
uint64_t * zr
Definition dist_m4ri.c:175
uint64_t bmask
Definition dist_m4ri.c:178
uint64_t * hc
Definition dist_m4ri.c:176
uint64_t * bits
Definition dist_m4ri.c:177
int * cols
Definition dist_m4ri.c:182
cc_hash_slot_t * tab
Definition dist_m4ri.c:179
int max_row_wt
Definition dist_m4ri.c:483
const csr_t * mH
Definition dist_m4ri.c:463
int col_min
Definition dist_m4ri.c:471
int * ulist
Definition dist_m4ri.c:492
uint32_t gen
Definition dist_m4ri.c:497
int * touched
Definition dist_m4ri.c:499
uint64_t * X
Definition dist_m4ri.c:484
int * acnt
Definition dist_m4ri.c:485
int acnt_col
Definition dist_m4ri.c:486
worker_arg_t * warg
Definition dist_m4ri.c:462
int * ord
Definition dist_m4ri.c:494
int w_limit
Definition dist_m4ri.c:472
const int * fix
Definition dist_m4ri.c:475
long long nodes
Definition dist_m4ri.c:477
int * kfirst
Definition dist_m4ri.c:487
uint64_t * E
Definition dist_m4ri.c:467
int * brl
Definition dist_m4ri.c:491
int count_w
Definition dist_m4ri.c:478
int * bcnt
Definition dist_m4ri.c:495
int * hist
Definition dist_m4ri.c:500
int * fstk
Definition dist_m4ri.c:489
int * cols
Definition dist_m4ri.c:468
int max_col_wt
Definition dist_m4ri.c:473
uint32_t * mark
Definition dist_m4ri.c:496
const cc_hash_t * hs
Definition dist_m4ri.c:465
distfork_ctx_t * ctx
Definition dist_m4ri.c:461
const csr_t * mHT
Definition dist_m4ri.c:464
one_vec_t * cw
Definition dist_m4ri.c:469
uint64_t * S
Definition dist_m4ri.c:466
int * cv
Definition dist_m4ri.c:498
int * uacnt
Definition dist_m4ri.c:493
word * mask
Definition util_m4ri.h:142
int * p
Definition util_m4ri.h:125
int * i
Definition util_m4ri.h:126
int rows
Definition util_m4ri.h:121
int cols
Definition util_m4ri.h:122
Hit statistics for the min_hits stopping criterion, see codeword_hit_stats().
Definition util_io.h:396
long long int min_cws
Definition util_io.h:397
long long int min_hits
Definition util_io.h:398
long long int set_cws
Definition util_io.h:399
double start_time
Definition dist_m4ri.c:193
const cc_hash_t * cc_hash
Definition dist_m4ri.c:189
params_t * p
Definition dist_m4ri.c:188
long long int maxC
Definition util_io.h:87
int dmin
Definition util_io.h:61
colmask_t * maskL
Definition util_io.h:133
int dstop
Definition util_io.h:136
int start_num
Definition util_io.h:78
int noscan
Definition util_io.h:66
char * outC
Definition util_io.h:90
int ksub
Definition util_io.h:141
csr_t * spaH
Definition util_io.h:130
int irred
Definition util_io.h:91
int smax
Definition util_io.h:56
int nvar
Definition util_io.h:85
int chunk_size
Definition util_io.h:140
int method
Definition util_io.h:54
int wmax
Definition util_io.h:60
int * start_list
Definition util_io.h:76
int dW
Definition util_io.h:88
int debug
Definition util_io.h:51
int pairs
Definition util_io.h:148
int start_scan
Definition util_io.h:79
int cc_prune
Definition util_io.h:124
int kwin
Definition util_io.h:142
int seed
Definition util_io.h:68
word * table
Definition dist_m4ri.c:1312
size_t tstride
Definition dist_m4ri.c:1313
double t[2]
Definition dist_m4ri.c:1316
uint64_t sig
Definition dist_m4ri.c:1432
const word * row
Definition dist_m4ri.c:1431
uint64_t * srt
Definition dist_m4ri.c:1460
rw_pair_t * ent
Definition dist_m4ri.c:1459
cols_ws_t * irr_ws
Definition dist_m4ri.c:309
int min_swei[MAX_W]
Definition dist_m4ri.c:308
distfork_ctx_t * ctx
Definition dist_m4ri.c:306
#define HASH_ITER(hh, head, el, tmp)
Definition uthash.h:1060
Utility functions for use with uthash.h.
struct ONE_VEC_T one_vec_t
long long int codeword_window_count(const params_t *const p)
Number of codewords in the collection window (weight up to min_w + dW with outC or maxC,...
Definition util_io.c:2635
void codeword_hit_stats(const params_t *const p, cw_hit_stats_t *const st)
Compute the hit statistics of the min_hits stopping criterion.
Definition util_io.c:2809
int check_min_hits_convergence(const params_t *const p)
Check whether the QDistRnd-style hit count stopping condition is met.
Definition util_io.c:2906
void var_kill(params_t *const p)
Clean up and free memory allocated in the params_t structure.
Definition util_io.c:1492
params_t *const p
Definition util_io.c:93
long long int nzlist_read(const char fnam[], params_t *p)
Read codewords from a .nz list file and add them to the codeword hash.
Definition util_io.c:3073
int codeword_maxc_reached(const params_t *const p)
Returns 1 if maxC > 0 and maxC codewords of the collection window are collected.
Definition util_io.c:2644
int codeword_feed_limit(const params_t *const p)
Weight limit (exclusive) of RW codewords needed for the min_hits statistic.
Definition util_io.c:2648
void print_rw_infoset_estimate(FILE *stream, const int n, const int rank, const int w_lo, const int w_hi, const long steps, const long steps_total, const double t_step, const int pairs_l)
Print the information-set estimate of the probability that RW missed a codeword lighter than the uppe...
Definition util_io.c:3010
void print_codeword_support(FILE *stream, const char *prefix, const int arr[], const int weight, const int debug)
Print the support of a codeword (1-based column indices) in one line, for DBG_CODEWORDS.
Definition util_io.c:3186
int codeword_hit_check(FILE *stream, const params_t *const p)
Check the uniformity of the hit counts within each weight class of the representative set.
Definition util_io.c:2939
void print_rw_step_stats(FILE *stream, const params_t *const p)
DBG_SUMMARY: the codewords of the lowest three weights in hash found by the RW steps with a uniform r...
Definition util_io.c:2881
long long int nzlist_write(const char fnam[], const char comment[], params_t *p)
Write the collected codewords from the hash table to a .nz file.
Definition util_io.c:3197
cw_vec_t * codeword_add_maybe(params_t *const p, const int arr[], int weight, int src)
Add a candidate codeword to the hash table, or count one more hit of a known codeword.
Definition util_io.c:2693
params_t prm
Definition util_io.c:8
void print_codeword_stats(FILE *stream, const params_t *const p)
Print accumulated codeword and hit statistics to the given stream.
Definition util_io.c:2828
void var_init(int argc, char **argv, params_t *const p)
Initialize parameters and load matrices from command line arguments.
Definition util_io.c:766
#define DBG_PROGRESS
Definition util_io.h:32
#define DBG_TIMING
Definition util_io.h:34
struct CW_VEC_T cw_vec_t
Definition util_io.h:49
#define DBG_SUMMARY
Definition util_io.h:31
#define DBG_CODEWORDS
Definition util_io.h:36
@ CW_SRC_RW_WINDOW
Definition util_io.h:253
@ CW_SRC_OTHER
Definition util_io.h:253
@ CW_SRC_RW_UNIFORM
Definition util_io.h:253
#define RW_PAIRS_MAX_BITS
The largest number of random pivot rows of the buckets of the RW pairs (pairs, a 64-bit key).
Definition util_io.h:278
#define DBG_STATUS
Definition util_io.h:33
#define DBG_CWDUMP
Definition util_io.h:39
#define MAX_W
Definition util_io.h:47
mzd_t * mzd_nullspace(const csr_t *const H)
Compute the null space basis N = ker(H) of dimension nu x n.
Definition util_m4ri.c:974
cols_ws_t * cols_ws_init(const int m)
Allocate the workspace of csr_cols_rank() and csr_cols_dependency() for a matrix H with m rows.
Definition util_m4ri.c:1011
cols_ws_t * cols_ws_free(cols_ws_t *ws)
Free the workspace allocated by cols_ws_init().
Definition util_m4ri.c:1022
int csr_cols_rank(const csr_t *const HT, const int cnt, const int cols[], cols_ws_t *const ws)
Rank of a set of columns of a sparse binary matrix H.
Definition util_m4ri.c:1110
csr_t * csr_transpose(csr_t *dst, const csr_t *const p)
Transpose a compressed CSR sparse matrix.
Definition util_m4ri.c:163
mzd_t * mzd_from_csr(mzd_t *dst, const csr_t *p)
Convert a CSR sparse matrix to an MZD dense matrix.
Definition util_m4ri.c:186
mzp_t * perm_p_trans(mzp_t *q, const mzp_t *p, const rci_t start)
Apply transposed pivot permutation p to permutation q in-place.
Definition util_m4ri.c:505
csr_t * csr_free(csr_t *p)
Free memory allocated for a CSR sparse matrix.
Definition util_m4ri.c:522
int csr_max_row_wght(const csr_t *const p)
return max row weight of CSR matrix p TODO: add code for List of Pairs
Definition util_m4ri.c:145
void localized_window_perm(mzp_t *perm, int nvar, int seed_col, int kwin, int win_mode, const csr_t *spaH, const csr_t *mHT, int *visited_cols, int *visited_checks, int *col_queue, int *visit_marker, uint64_t *rng_state)
Construct a column permutation with a localized window at the front.
Definition util_m4ri.c:1160
void refresh_nullspace_basis(mzd_t *N, const int *cw_arr, int cw_wt, uint64_t *rng_state)
Refresh the null space basis N under a random column permutation and optionally inject a low-weight c...
Definition util_m4ri.c:1249
int csr_all_ones_in_rowspan(const csr_t *const H)
Check whether the all-ones vector lies in the row span of a binary CSR matrix H over GF(2).
Definition util_m4ri.c:1312
#define ERROR(fmt,...)
Definition util_m4ri.h:13
#define SWAPINT(a, b)
Definition util_m4ri.h:11