Dist m4ri 0.0.1.alpha
Computing distance of a classical or quantum CSS code
Loading...
Searching...
No Matches
dist_m4ri.c
Go to the documentation of this file.
1
26#define _GNU_SOURCE
27#include <inttypes.h>
28#include <strings.h>
29#include <stdlib.h>
30#include <stdio.h>
31#include <stdbool.h>
32#include <stdatomic.h>
33#include <time.h>
34#include <unistd.h>
35#include <pthread.h>
36#include <math.h>
37#include <limits.h>
38#include <m4ri/m4ri.h>
39
40#include "mmio.h"
41#include "uthash.h"
42#include "util_hash.h"
43#include "util_m4ri.h"
44#include "util_io.h"
45#include "dist_m4ri.h"
46#include "dist_cc.h"
47
48/* Mutex protecting M4RI's internal non-thread-safe MMC memory cache */
49static pthread_mutex_t m4ri_mem_mutex = PTHREAD_MUTEX_INITIALIZER;
50
51static inline mzd_t *safe_mzd_from_csr(mzd_t *dst, const csr_t *p) {
52 pthread_mutex_lock(&m4ri_mem_mutex);
53 mzd_t *res = mzd_from_csr(dst, p);
54 pthread_mutex_unlock(&m4ri_mem_mutex);
55 return res;
56}
57
58static inline mzd_t *safe_mzd_init(rci_t r, rci_t c) {
59 pthread_mutex_lock(&m4ri_mem_mutex);
60 mzd_t *res = mzd_init(r, c);
61 pthread_mutex_unlock(&m4ri_mem_mutex);
62 return res;
63}
64
65static inline void safe_mzd_free(mzd_t *M) {
66 if (!M) return;
67 pthread_mutex_lock(&m4ri_mem_mutex);
68 mzd_free(M);
69 pthread_mutex_unlock(&m4ri_mem_mutex);
70}
71
72static inline mzp_t *safe_mzp_init(rci_t length) {
73 pthread_mutex_lock(&m4ri_mem_mutex);
74 mzp_t *res = mzp_init(length);
75 pthread_mutex_unlock(&m4ri_mem_mutex);
76 return res;
77}
78
79static inline void safe_mzp_free(mzp_t *P) {
80 if (!P) return;
81 pthread_mutex_lock(&m4ri_mem_mutex);
82 mzp_free(P);
83 pthread_mutex_unlock(&m4ri_mem_mutex);
84}
85
86static inline double get_time_sec(void) {
87 struct timespec ts;
88 clock_gettime(CLOCK_MONOTONIC, &ts);
89 return (double)ts.tv_sec + (double)ts.tv_nsec * 1e-9;
90}
91
92static inline uint64_t splitmix64(uint64_t *state) {
93 uint64_t z = (*state += 0x9e3779b97f4a7c15ULL);
94 z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9ULL;
95 z = (z ^ (z >> 27)) * 0x94d049bb133111ebULL;
96 return z ^ (z >> 31);
97}
98
99static inline int rand_uniform_thread(int max, uint64_t *state) {
100 if (max <= 1) return 0;
101 return (int)(splitmix64(state) % (uint64_t)max);
102}
103
104static inline mzp_t * mzp_rand_thread(mzp_t *q, rci_t length, uint64_t *state) {
105 if (q == NULL) return NULL;
106 for (int i = 0; i <= (int)length - 2; i++) {
107 q->values[i] = i + rand_uniform_thread(length - i, state);
108 }
109 for (int i = length - 1; i < (int)q->length; i++) {
110 q->values[i] = i;
111 }
112 return q;
113}
114
115typedef struct {
118 double timeout;
120 int dexp;
121
122 /* Distance bounds */
123 atomic_int dmin; /* dmin-1 is max cluster size analyzed without success */
124 atomic_int dmax; /* smallest weight codeword found (0 if none) */
125 atomic_int cc_found_weight; /* weight of codeword if CC found exact */
126 atomic_bool stop_flag; /* signals all threads to terminate */
127
128 /* RW state */
130 atomic_long rw_steps_started;
132
133 /* CC state for current weight */
134 atomic_int cc_weight;
135 atomic_int cc_col_next;
142 atomic_int cc_round_active;
143
144 /* Codeword synchronization */
145 pthread_mutex_t cw_mutex;
146
147 /* Timing stats */
148 double cc_time_per_weight[MAX_W];
150
151 /* Thread handles */
152 pthread_t *threads;
154
155typedef struct {
157 int tid;
158 int min_swei[MAX_W];
160
161/* Recursive CC worker function (interruptible) */
162static int start_CC_recurs_mt(one_vec_t *err, one_vec_t *urr, one_vec_t * const syn[],
163 const int w_limit, const int max_col_wt,
164 const csr_t * const mH, const csr_t * const mHT,
165 worker_arg_t *warg) {
166 distfork_ctx_t *ctx = warg->ctx;
167 if (atomic_load_explicit(&ctx->stop_flag, memory_order_relaxed)) {
168 return 0;
169 }
170 params_t * const p = ctx->p;
171 const int w = err->wei;
172 int row = syn[w]->vec[0];
173 const csr_t * const mL = p->spaL;
174 const int col_min = urr->vec[0];
175
176 for (int i1 = mH->p[row]; i1 < mH->p[row+1]; i1++) {
177 const int col = mH->i[i1];
178 if (col > col_min) {
179 int pos = one_ordered_search(err, col);
180 if (pos == -1) {
181 urr->vec[w] = col;
182 urr->wei++;
183 pos = one_ordered_ins(err, col);
184 syn[w+1]->wei = 0;
185 int swei = one_csr_row_combine(syn[w+1], syn[w], mHT, col);
186
187 if (p->smax && swei > 0 && swei <= p->smax && (w + 1 < MAX_W)) {
188 if (swei < warg->min_swei[w + 1]) {
189 warg->min_swei[w + 1] = swei;
190 }
191 }
192
193 int current_limit = w_limit;
194 int cur_dmax = atomic_load_explicit(&ctx->dmax, memory_order_relaxed);
195 if (cur_dmax > 0 && p->dW >= 0) {
196 current_limit = minint(w_limit, cur_dmax + p->dW);
197 }
198
199 if (err->wei < current_limit) {
200 if (swei) {
201 int result = start_CC_recurs_mt(err, urr, syn, w_limit, max_col_wt,
202 mH, mHT, warg);
203 if (result == 1) {
204 urr->wei--;
205 one_ordered_pos_del(err, col, pos);
206 return 1;
207 }
208 }
209 } else {
210 if (!swei) {
211 int nz = (!mL) || sparse_syndrome_non_zero(mL, err->wei, err->vec);
212 if (nz) {
213 bool stop = false;
214 pthread_mutex_lock(&ctx->cw_mutex);
215 p->codewords = codeword_add_maybe(p, err->vec, err->wei);
216 int cur_d = atomic_load(&ctx->dmax);
217 if (p->min_w < cur_d || cur_d == 0) {
218 atomic_store(&ctx->dmax, p->min_w);
219 }
220 int cur_cc_found = atomic_load(&ctx->cc_found_weight);
221 if (cur_cc_found == 0 || err->wei < cur_cc_found) {
222 atomic_store(&ctx->cc_found_weight, err->wei);
223 }
224 if (!p->outC && p->maxC == 0) {
225 atomic_store(&ctx->stop_flag, true);
226 stop = true;
227 }
228 if (p->maxC && p->num_cws >= p->maxC) {
229 atomic_store(&ctx->stop_flag, true);
230 stop = true;
231 }
232 pthread_mutex_unlock(&ctx->cw_mutex);
233
234 if (stop) {
235 urr->wei--;
236 one_ordered_pos_del(err, col, pos);
237 return 1;
238 }
239 }
240 }
241 }
242 urr->wei--;
243 one_ordered_pos_del(err, col, pos);
244 }
245 }
246 }
247 return 0;
248}
249
250/* Run RW batch */
251static void run_rw_steps(distfork_ctx_t *ctx, int n_steps,
252 mzd_t *mH, mzd_t *mHT, rci_t *ee,
253 mzp_t *perm, mzp_t *pivs, mzp_t *pivs_srtd, mzp_t *skip_pivs,
254 uint64_t *rng_state, int tid) {
255 params_t * const p = ctx->p;
256 const csr_t * const spaL0 = p->spaL;
257 const int nvar = p->spaH->cols;
258 const int classical = p->classical;
259
260 for (int step = 0; step < n_steps; step++) {
261 if (atomic_load_explicit(&ctx->stop_flag, memory_order_relaxed)) break;
262
263 pivs = mzp_rand_thread(pivs, nvar, rng_state);
264 mzp_set_ui(perm, 1);
265 perm = perm_p_trans(perm, pivs, 0);
266
267 int rank = 0;
268 for (int i = 0; i < nvar; i++) {
269 int col = perm->values[i];
270 int ret = gauss_one(mH, col, rank);
271 if (ret) {
272 pivs->values[rank++] = col;
273 }
274 }
275
276 pivs_srtd = mzp_copy(pivs_srtd, pivs);
277 qsort(pivs_srtd->values, rank, sizeof(pivs->values[0]), cmp_rci_t);
278 int end = -1, num = 0;
279 for (int i = 0; i < rank; i++) {
280 int beg = end + 1;
281 end = pivs_srtd->values[i];
282 for (int j = beg; j < end; j++) {
283 skip_pivs->values[num++] = j;
284 }
285 }
286 for (int j = end + 1; j < nvar; j++) {
287 skip_pivs->values[num++] = j;
288 }
289 skip_pivs->length = num;
290
291 mzd_transpose(mHT, mH);
292
293 int k = nvar - rank;
294 for (int ir = 0; ir < k; ir++) {
295 int cnt = 0;
296 const int col = ee[cnt++] = skip_pivs->values[ir];
297 int limit = nvar + 1;
298 int cur_dmax = atomic_load_explicit(&ctx->dmax, memory_order_relaxed);
299 if (cur_dmax > 0) {
300 if ((p->outC || p->maxC || p->dW > 0) && p->dW >= 0) {
301 limit = minint(limit, cur_dmax + p->dW + 1);
302 } else {
303 limit = minint(limit, cur_dmax);
304 }
305 }
306
307 word *rawrow = mzd_row(mHT, col);
308 rci_t j = -1;
309 while (cnt < limit) {
310 j = nextelement(rawrow, mHT->width, j);
311 if (j == -1 || j >= rank) break;
312 ee[cnt++] = pivs->values[j++];
313 }
314
315 if (cnt < limit) {
316 qsort(ee, cnt, sizeof(rci_t), cmp_rci_t);
317 int nz = classical ? 1 : sparse_syndrome_non_zero(spaL0, cnt, ee);
318 if (nz) {
319 pthread_mutex_lock(&ctx->cw_mutex);
320 p->codewords = codeword_add_maybe(p, ee, cnt);
321 if (cnt < p->min_w) p->min_w = cnt;
322 int best = p->min_w;
323 int old_dmax = atomic_load(&ctx->dmax);
324 if (old_dmax == 0 || best < old_dmax) {
325 atomic_store(&ctx->dmax, best);
326 if (p->debug & 16) {
327 int num_rw = (ctx->p->method == 1) ? ctx->num_threads : (ctx->num_threads - atomic_load(&ctx->cc_target_workers));
328 if (num_rw < 1) num_rw = 1;
329 fprintf(stderr, "# [thread %d] RW found new upper bound cw of weight %d (using %d RW threads)\n", tid, best, num_rw);
330 }
331 int cur_dmin = atomic_load(&ctx->dmin);
332 if (cur_dmin > 0 && best <= cur_dmin) {
333 atomic_store(&ctx->stop_flag, true);
334 }
335 }
336 if (p->wmin > 0 && best <= p->wmin) {
337 atomic_store(&ctx->stop_flag, true);
338 }
339 if (p->maxC && p->num_cws >= p->maxC) {
340 atomic_store(&ctx->stop_flag, true);
341 }
342 pthread_mutex_unlock(&ctx->cw_mutex);
343 }
344 }
345 }
346 atomic_fetch_add(&ctx->rw_steps_completed, 1);
347 }
348}
349
350/* Worker thread main loop */
351static void *worker_thread_func(void *arg) {
352 worker_arg_t *warg = (worker_arg_t *)arg;
353 distfork_ctx_t *ctx = warg->ctx;
354 int tid = warg->tid;
355 const int nvar = ctx->p->spaH->cols;
356 const bool enable_rw = (ctx->p->method & 1) != 0;
357
358 /* Initialize min_swei for this thread */
359 for (int i = 0; i < MAX_W; i++) {
360 warg->min_swei[i] = ctx->p->spaH->rows + 1;
361 }
362
363 /* Thread-local RW matrices (allocated safely only if RW is enabled) */
364 mzd_t *mH = NULL;
365 mzd_t *mHT_rw = NULL;
366 rci_t *ee = NULL;
367 mzp_t *perm = NULL;
368 mzp_t *pivs = NULL;
369 mzp_t *pivs_srtd = NULL;
370 mzp_t *skip_pivs = NULL;
371 uint64_t rng_state = (uint64_t)ctx->p->seed + (uint64_t)tid * 0x9e3779b97f4a7c15ULL + 0x517cc1b727220a95ULL;
372
373 if (enable_rw) {
374 mH = safe_mzd_from_csr(NULL, ctx->p->spaH);
375 mHT_rw = safe_mzd_init(nvar, ctx->p->spaH->rows);
376 ee = malloc((nvar + 2) * sizeof(rci_t));
377 perm = safe_mzp_init(nvar);
378 pivs = safe_mzp_init(nvar);
379 pivs_srtd = safe_mzp_init(nvar);
380 skip_pivs = safe_mzp_init(nvar);
381 }
382
383 /* Thread-local CC memory */
384 const int wmax_alloc = (ctx->p->wmax > 0 && ctx->p->wmax < MAX_W)
385 ? ctx->p->wmax : (MAX_W - 1);
386 one_vec_t *err = calloc(
387 1, sizeof(one_vec_t) + sizeof(int) * (wmax_alloc + 2)
388 );
389 one_vec_t *urr = calloc(
390 1, sizeof(one_vec_t) + sizeof(int) * (wmax_alloc + 2)
391 );
392 one_vec_t **syn = calloc(wmax_alloc + 3, sizeof(one_vec_t *));
393 for (int i = 0; i <= wmax_alloc + 2; i++) {
394 syn[i] = calloc(
395 1, sizeof(one_vec_t) + sizeof(int) * (ctx->p->spaH->rows + 1)
396 );
397 }
398
399 while (!atomic_load_explicit(&ctx->stop_flag, memory_order_relaxed)) {
400 if (get_time_sec() - ctx->start_time >= ctx->timeout) {
401 atomic_store(&ctx->stop_flag, true);
402 break;
403 }
404
405 bool did_work = false;
406
407 /* 1. Try to take CC work if CC is active (method 2 or 3) */
408 if (ctx->p->method >= 2 && atomic_load(&ctx->cc_round_active)) {
409 int active = atomic_load(&ctx->cc_active_workers);
410 int target = atomic_load(&ctx->cc_target_workers);
411 if (active < target) {
412 int col = atomic_fetch_add(&ctx->cc_col_next, 1);
413 int end = ctx->cc_col_end;
414 if (col <= end) {
415 atomic_fetch_add(&ctx->cc_active_workers, 1);
416 int w = atomic_load(&ctx->cc_weight);
417
418 err->vec[0] = urr->vec[0] = col;
419 err->wei = urr->wei = 1;
420 syn[1]->wei = 0;
421 int swei = one_csr_row_combine(syn[1], syn[0], ctx->mHT_cc, col);
422
423 if (ctx->p->smax && swei > 0 && swei <= ctx->p->smax) {
424 if (swei < warg->min_swei[1]) {
425 warg->min_swei[1] = swei;
426 }
427 }
428
429 if (w > 1) {
430 if (swei) {
431 start_CC_recurs_mt(err, urr, syn, w, ctx->max_col_W, ctx->p->spaH, ctx->mHT_cc, warg);
432 }
433 } else {
434 if (!swei) {
435 int nz = (!ctx->p->spaL) || sparse_syndrome_non_zero(ctx->p->spaL, 1, err->vec);
436 if (nz) {
437 pthread_mutex_lock(&ctx->cw_mutex);
438 ctx->p->codewords = codeword_add_maybe(ctx->p, err->vec, 1);
439 atomic_store(&ctx->cc_found_weight, 1);
440 atomic_store(&ctx->dmin, 1);
441 atomic_store(&ctx->dmax, 1);
442 atomic_store(&ctx->stop_flag, true);
443 pthread_mutex_unlock(&ctx->cw_mutex);
444 }
445 }
446 }
447 err->wei = urr->wei = 0;
448 atomic_fetch_sub(&ctx->cc_active_workers, 1);
449 did_work = true;
450 continue;
451 }
452 }
453 }
454
455 /* 2. Try to take RW work if RW is active (method 1 or 3) */
456 if (enable_rw && !atomic_load(&ctx->stop_flag)) {
457 long cur_s = atomic_load(&ctx->rw_steps_started);
458 if (cur_s < ctx->total_rw_steps) {
459 long target_s = cur_s + 10;
460 if (target_s > ctx->total_rw_steps) target_s = ctx->total_rw_steps;
461 if (atomic_compare_exchange_weak(&ctx->rw_steps_started, &cur_s, target_s)) {
462 int n_steps = (int)(target_s - cur_s);
463 run_rw_steps(ctx, n_steps, mH, mHT_rw, ee, perm, pivs, pivs_srtd, skip_pivs, &rng_state, tid);
464 did_work = true;
465 continue;
466 }
467 }
468 }
469
470 if (!did_work) {
471 usleep(100);
472 }
473 }
474
475 if (enable_rw) {
476 safe_mzp_free(skip_pivs);
477 safe_mzp_free(pivs_srtd);
478 safe_mzp_free(perm);
479 safe_mzp_free(pivs);
480 free(ee);
481 safe_mzd_free(mHT_rw);
482 safe_mzd_free(mH);
483 }
484
485 for (int i = 0; i <= wmax_alloc + 2; i++) free(syn[i]);
486 free(syn);
487 free(err);
488 free(urr);
489
490 return NULL;
491}
492
493/* Method 1 coordinator */
494static void run_method1_coordinator(distfork_ctx_t *ctx) {
495 if (ctx->p->debug & 2) {
496 fprintf(stderr, "# running method=1 (multithreaded RW) with %d threads, total steps=%ld\n",
497 ctx->num_threads, ctx->total_rw_steps);
498 }
499
500 while (!atomic_load(&ctx->stop_flag)) {
501 if (get_time_sec() - ctx->start_time >= ctx->timeout) {
502 atomic_store(&ctx->stop_flag, true);
503 break;
504 }
505 if (atomic_load(&ctx->rw_steps_completed) >= ctx->total_rw_steps) {
506 break;
507 }
508 usleep(1000);
509 }
510}
511
512/* Method 2 coordinator */
513static void run_method2_coordinator(distfork_ctx_t *ctx) {
514 const int nvar = ctx->p->spaH->cols;
515 const int wmax = ctx->p->wmax;
516 const int w_start = ctx->p->noscan ? wmax : (ctx->p->dmin > 1 ? ctx->p->dmin : 1);
517
518 if (ctx->p->debug & 2) {
519 fprintf(stderr, "# running method=2 (multithreaded CC) with %d threads, w_start=%d wmax=%d\n",
520 ctx->num_threads, w_start, wmax);
521 }
522
523 int w_limit = wmax;
524 if (ctx->p->dmax > 0) {
525 if (ctx->p->outC && ctx->p->dW > 0) {
526 w_limit = minint(wmax > 0 ? wmax : ctx->p->dmax + ctx->p->dW, ctx->p->dmax + ctx->p->dW);
527 } else {
528 w_limit = minint(wmax > 0 ? wmax : ctx->p->dmax, ctx->p->dmax);
529 }
530 }
531
532 for (int w = w_start; w <= w_limit; w++) {
533 if (atomic_load(&ctx->stop_flag)) break;
534 double now = get_time_sec();
535 double remaining_time = ctx->timeout - (now - ctx->start_time);
536 if (ctx->timeout > 0.0 && remaining_time <= 0.0) {
537 atomic_store(&ctx->stop_flag, true);
538 break;
539 }
540
541 /* Estimate CC time for weight w if timeout > 0 */
542 if (ctx->timeout > 0.0 && w > w_start) {
543 double prev = ctx->cc_time_per_weight[w - 1];
544 if (prev <= 0.0001) prev = 0.001;
545 double growth = 4.0;
546 if (w >= 3 && ctx->cc_time_per_weight[w - 2] > 0.0001) {
547 growth = ctx->cc_time_per_weight[w - 1] / ctx->cc_time_per_weight[w - 2];
548 if (growth < 2.0) growth = 2.0;
549 if (growth > 10.0) growth = 10.0;
550 }
551 double t_cc_est = prev * growth;
552 if ((t_cc_est / ctx->num_threads) > remaining_time * 1.5) {
553 if (ctx->p->debug & 1) {
554 fprintf(stderr, "# CC for w=%d (est %.2fs) exceeds remaining timeout %.2fs, terminating early (dmin=%d)\n",
555 w, t_cc_est / ctx->num_threads, remaining_time, atomic_load(&ctx->dmin));
556 }
557 atomic_store(&ctx->stop_flag, true);
558 break;
559 }
560 }
561
562 int beg = (ctx->p->cbeg >= 0) ? ctx->p->cbeg : 0;
563 int end = (ctx->p->cend >= 0) ? minint(ctx->p->cend, nvar - w) : (nvar - w);
564
565 atomic_store(&ctx->cc_weight, w);
566 ctx->cc_col_beg = beg;
567 ctx->cc_col_end = end;
568 atomic_store(&ctx->cc_col_next, beg);
569 atomic_store(&ctx->cc_target_workers, ctx->num_threads);
570 atomic_store(&ctx->cc_round_active, 1);
571
572 double cc_start = get_time_sec();
573
574 if (ctx->p->debug & 2) {
575 fprintf(stderr, "# searching w=%d with %d CC threads, columns [%d, %d]\n",
576 w, ctx->num_threads, beg, end);
577 }
578
579 bool round_completed = false;
580 while (!atomic_load(&ctx->stop_flag)) {
581 if (get_time_sec() - ctx->start_time >= ctx->timeout) {
582 atomic_store(&ctx->stop_flag, true);
583 break;
584 }
585 if (atomic_load(&ctx->cc_col_next) > end && atomic_load(&ctx->cc_active_workers) == 0) {
586 round_completed = true;
587 break;
588 }
589 usleep(100);
590 }
591
592 atomic_store(&ctx->cc_round_active, 0);
593
594 double cc_dur = get_time_sec() - cc_start;
595 if (w < MAX_W) {
596 ctx->cc_time_per_weight[w] = cc_dur;
597 }
598
599 int cw_found = atomic_load(&ctx->cc_found_weight);
600 if (cw_found > 0) {
601 atomic_store(&ctx->dmin, cw_found);
602 atomic_store(&ctx->dmax, cw_found);
603
604 if (ctx->p->outC && ctx->p->dW > 0 && w < minint(wmax, cw_found + ctx->p->dW)) {
605 w_limit = minint(wmax, cw_found + ctx->p->dW);
606 if (ctx->p->debug & 1) {
607 if (w == cw_found) {
608 fprintf(stderr, "# CC round w=%d finished in %.3fs (%d CC threads): found min-weight codewords (dmin=%d, continuing up to w=%d for dW=%d, total %lld cws)\n",
609 w, cc_dur, ctx->num_threads, cw_found, w_limit, ctx->p->dW, ctx->p->num_cws);
610 } else if (round_completed) {
611 fprintf(stderr, "# CC round w=%d finished in %.3fs (%d CC threads): extra dW round completed (dmin=%d, total %lld cws)\n",
612 w, cc_dur, ctx->num_threads, cw_found, ctx->p->num_cws);
613 }
614 }
615 } else {
616 if (ctx->p->debug & 1) {
617 if (w > cw_found) {
618 if (round_completed) {
619 fprintf(stderr, "# CC round w=%d finished in %.3fs (%d CC threads): extra dW round completed (dmin=%d, total %lld cws)\n",
620 w, cc_dur, ctx->num_threads, cw_found, ctx->p->num_cws);
621 }
622 } else {
623 fprintf(stderr, "# CC found min-weight codeword: d=%d (using %d CC threads, total %lld cws)\n",
624 cw_found, ctx->num_threads, ctx->p->num_cws);
625 }
626 }
627 if (w >= w_limit || !round_completed) {
628 atomic_store(&ctx->stop_flag, true);
629 break;
630 }
631 }
632 } else {
633 if (!round_completed) {
634 break;
635 }
636 /* Weight w analyzed without success */
637 atomic_store(&ctx->dmin, w + 1);
638 if (ctx->p->debug & 1) {
639 fprintf(stderr, "# CC w=%d completed in %.3fs (%d CC threads): no codewords found -> dmin=%d\n",
640 w, cc_dur, ctx->num_threads, w + 1);
641 }
642 }
643 }
644}
645
646/* Method 3 coordinator */
647static void run_method3_coordinator(distfork_ctx_t *ctx) {
648 const int nvar = ctx->p->spaH->cols;
649 int w = ctx->p->noscan ? ctx->p->wmax : (ctx->p->dmin > 1 ? ctx->p->dmin : 1);
650
651 if (ctx->p->debug & 2) {
652 fprintf(stderr, "# running method=3 (bracketing mode) with %d threads, timeout=%.1fs, dexp=%d\n",
653 ctx->num_threads, ctx->timeout, ctx->dexp);
654 }
655
656 int init_dmax = atomic_load(&ctx->dmax);
657 int init_dmin = atomic_load(&ctx->dmin);
658 if (init_dmax > 0 && init_dmin >= init_dmax && !ctx->p->outC) {
659 atomic_store(&ctx->stop_flag, true);
660 return;
661 }
662
663 /* Initial RW probe to measure average step time */
664 double t_rw_start = get_time_sec();
665 usleep(2000);
666 double t_rw_dur = get_time_sec() - t_rw_start;
667 long initial_steps = atomic_load(&ctx->rw_steps_completed);
668 if (initial_steps > 0) {
669 ctx->avg_rw_step_time = (t_rw_dur * (double)ctx->num_threads) / (double)initial_steps;
670 } else {
671 ctx->avg_rw_step_time = 0.00005;
672 }
673
674 while (!atomic_load(&ctx->stop_flag)) {
675 double now = get_time_sec();
676 double remaining_time = ctx->timeout - (now - ctx->start_time);
677 if (remaining_time <= 0.0) {
678 atomic_store(&ctx->stop_flag, true);
679 break;
680 }
681
682 int cur_dmax = atomic_load(&ctx->dmax);
683 int cur_dmin = atomic_load(&ctx->dmin);
684
685 /* Target cluster size for CC */
686 int target_cc_w;
687 if (cur_dmax > 0) {
688 if (ctx->p->outC && (ctx->p->dW > 0 || cur_dmin >= cur_dmax)) {
689 target_cc_w = cur_dmax + (ctx->p->dW > 0 ? ctx->p->dW : 0);
690 } else {
691 target_cc_w = cur_dmax - 1;
692 }
693 } else if (ctx->dexp > 0) {
694 target_cc_w = ctx->dexp;
695 } else if (ctx->p->wmax > 0) {
696 target_cc_w = ctx->p->wmax;
697 } else {
698 target_cc_w = nvar;
699 }
700
701 int max_allowed_w = (ctx->p->wmax > 0)
702 ? minint(ctx->p->wmax, MAX_W - 2) : (MAX_W - 2);
703 if (target_cc_w > max_allowed_w) {
704 target_cc_w = max_allowed_w;
705 }
706
707 if (cur_dmax > 0 && cur_dmin >= cur_dmax && w > target_cc_w) {
708 /* Bracketing converged and all requested dW rounds completed */
709 atomic_store(&ctx->dmin, cur_dmax);
710 atomic_store(&ctx->stop_flag, true);
711 break;
712 }
713
714 if (w > target_cc_w) {
715 /* Let remaining RW steps finish */
716 while (!atomic_load(&ctx->stop_flag)) {
717 if (get_time_sec() - ctx->start_time >= ctx->timeout) break;
718 if (atomic_load(&ctx->rw_steps_completed) >= ctx->total_rw_steps) break;
719 usleep(1000);
720 }
721 break;
722 }
723
724 /* Estimate CC time for weight w */
725 double t_cc_est;
726 if (w == 1) {
727 t_cc_est = 0.0001;
728 } else if (w == 2) {
729 t_cc_est = 0.001;
730 } else {
731 double prev = ctx->cc_time_per_weight[w - 1];
732 if (prev <= 0.0001) prev = 0.001;
733 double growth = 4.0;
734 if (w >= 3 && ctx->cc_time_per_weight[w - 2] > 0.0001) {
735 growth = ctx->cc_time_per_weight[w - 1] / ctx->cc_time_per_weight[w - 2];
736 if (growth < 2.0) growth = 2.0;
737 if (growth > 10.0) growth = 10.0;
738 }
739 t_cc_est = prev * growth;
740 }
741
742 /* Check if CC for weight w can finish within timeout */
743 if (t_cc_est / ctx->num_threads > remaining_time * 1.5) {
744 if (ctx->p->debug & 2) {
745 fprintf(stderr, "# CC for w=%d (est %.2fs) exceeds remaining timeout %.2fs, devoting %d threads to RW\n",
746 w, t_cc_est / ctx->num_threads, remaining_time, ctx->num_threads);
747 }
748 while (!atomic_load(&ctx->stop_flag)) {
749 if (get_time_sec() - ctx->start_time >= ctx->timeout) break;
750 if (atomic_load(&ctx->rw_steps_completed) >= ctx->total_rw_steps) break;
751 usleep(1000);
752 }
753 break;
754 }
755
756 /* Calculate thread balancing */
757 long steps_done = atomic_load(&ctx->rw_steps_completed);
758 long steps_rem = (ctx->total_rw_steps > steps_done) ? (ctx->total_rw_steps - steps_done) : 0;
759
760 int n_cc;
761 if (ctx->num_threads == 1) {
762 n_cc = 1;
763 } else if (steps_rem == 0) {
764 n_cc = ctx->num_threads;
765 } else if (t_cc_est < 0.005) {
766 n_cc = (ctx->num_threads >= 4) ? 2 : 1;
767 } else {
768 double t_rw_total_1t = (double)steps_rem * ctx->avg_rw_step_time;
769 double t_cc_total_1t = t_cc_est;
770 double est_accum = t_cc_est;
771 for (int k = w + 1; k <= target_cc_w && k <= w + 2; k++) {
772 est_accum *= 4.0;
773 t_cc_total_1t += est_accum;
774 }
775 double ratio = t_cc_total_1t / (t_cc_total_1t + t_rw_total_1t);
776 n_cc = (int)round((double)ctx->num_threads * ratio);
777 if (n_cc < 1) n_cc = 1;
778 if (n_cc >= ctx->num_threads && steps_rem > 0) n_cc = ctx->num_threads - 1;
779 }
780
781 int n_rw = ctx->num_threads - n_cc;
782
783 int beg = (ctx->p->cbeg >= 0) ? ctx->p->cbeg : 0;
784 int end = (ctx->p->cend >= 0) ? minint(ctx->p->cend, nvar - w) : (nvar - w);
785
786 atomic_store(&ctx->cc_weight, w);
787 ctx->cc_col_beg = beg;
788 ctx->cc_col_end = end;
789 atomic_store(&ctx->cc_col_next, beg);
790 atomic_store(&ctx->cc_target_workers, n_cc);
791 atomic_store(&ctx->cc_round_active, 1);
792
793 if (ctx->p->debug & 2) {
794 fprintf(stderr, "# CC round w=%d started: %d CC threads, %d RW threads (bounds [%d, %d], rem_rw=%ld, rem_time=%.2fs)\n",
795 w, n_cc, n_rw, cur_dmin, cur_dmax, steps_rem, remaining_time);
796 }
797
798 double cc_start = get_time_sec();
799 bool round_completed = false;
800
801 while (!atomic_load(&ctx->stop_flag)) {
802 if (get_time_sec() - ctx->start_time >= ctx->timeout) {
803 atomic_store(&ctx->stop_flag, true);
804 break;
805 }
806 if (atomic_load(&ctx->cc_col_next) > end && atomic_load(&ctx->cc_active_workers) == 0) {
807 round_completed = true;
808 break;
809 }
810 usleep(100);
811 }
812
813 atomic_store(&ctx->cc_round_active, 0);
814
815 double cc_dur = get_time_sec() - cc_start;
816 if (w < MAX_W) {
817 ctx->cc_time_per_weight[w] = cc_dur * (double)n_cc;
818 }
819
820 int cw_found = atomic_load(&ctx->cc_found_weight);
821 if (cw_found > 0) {
822 atomic_store(&ctx->dmin, cw_found);
823 atomic_store(&ctx->dmax, cw_found);
824
825 if (ctx->p->outC && ctx->p->dW > 0 && w < minint(ctx->p->wmax > 0 ? ctx->p->wmax : nvar, cw_found + ctx->p->dW)) {
826 if (ctx->p->debug & 1) {
827 if (w == cw_found) {
828 fprintf(stderr, "# CC round w=%d finished in %.3fs (%d CC threads, %d RW threads): found codewords (dmin=%d, continuing up to w=%d for dW=%d, total %lld cws)\n",
829 w, cc_dur, n_cc, n_rw, cw_found, minint(ctx->p->wmax > 0 ? ctx->p->wmax : nvar, cw_found + ctx->p->dW), ctx->p->dW, ctx->p->num_cws);
830 } else if (round_completed) {
831 fprintf(stderr, "# CC round w=%d finished in %.3fs (%d CC threads, %d RW threads): extra dW round completed (dmin=%d, total %lld cws)\n",
832 w, cc_dur, n_cc, n_rw, cw_found, ctx->p->num_cws);
833 }
834 }
835 } else {
836 if (ctx->p->debug & 1) {
837 if (w > cw_found) {
838 if (round_completed) {
839 fprintf(stderr, "# CC round w=%d finished in %.3fs (%d CC threads, %d RW threads): extra dW round completed (dmin=%d, total %lld cws)\n",
840 w, cc_dur, n_cc, n_rw, cw_found, ctx->p->num_cws);
841 }
842 } else {
843 fprintf(stderr, "# CC found min-weight codeword: d=%d (using %d CC threads, total %lld cws)\n",
844 cw_found, n_cc, ctx->p->num_cws);
845 }
846 }
847 atomic_store(&ctx->stop_flag, true);
848 break;
849 }
850 } else if (cur_dmax > 0 && cur_dmin >= cur_dmax) {
851 /* Extra dW round completed */
852 if (round_completed && (ctx->p->debug & 1)) {
853 fprintf(stderr, "# CC round w=%d finished in %.3fs (%d CC threads, %d RW threads): extra dW round completed (dmin=%d, total %lld cws)\n",
854 w, cc_dur, n_cc, n_rw, cur_dmin, ctx->p->num_cws);
855 }
856 if (!round_completed) {
857 break;
858 }
859 } else {
860 if (!round_completed) {
861 break;
862 }
863
864 /* Weight w analyzed without success */
865 int new_dmin = w + 1;
866 atomic_store(&ctx->dmin, new_dmin);
867 if (ctx->p->debug & 1) {
868 fprintf(stderr, "# CC round w=%d finished in %.3fs (%d CC threads, %d RW threads): no codewords -> dmin=%d\n",
869 w, cc_dur, n_cc, n_rw, new_dmin);
870 }
871
872 cur_dmax = atomic_load(&ctx->dmax);
873 if (cur_dmax > 0 && new_dmin >= cur_dmax) {
874 atomic_store(&ctx->dmin, cur_dmax);
875 if (ctx->p->outC && ctx->p->dW > 0 && cur_dmax + ctx->p->dW > cur_dmax) {
876 if (ctx->p->debug & 1) {
877 fprintf(stderr, "# bracketing bounds coincide: dmin = dmax = %d (continuing up to w=%d for dW=%d)\n",
878 cur_dmax, cur_dmax + ctx->p->dW, ctx->p->dW);
879 }
880 } else {
881 atomic_store(&ctx->stop_flag, true);
882 if (ctx->p->debug & 1) {
883 fprintf(stderr, "# bracketing bounds coincide: dmin = dmax = %d\n", cur_dmax);
884 }
885 break;
886 }
887 }
888 }
889
890 w++;
891 }
892}
893
894int main(int argc, char **argv) {
895 params_t * const p = &prm;
896
897 var_init(argc, argv, p);
898
899 if (p->finC) {
900 nzlist_read(p->finC, p);
901 }
902
903 /* Determine number of threads */
904 int num_threads = p->threads;
905 if (num_threads <= 0) {
906 long nprocs = sysconf(_SC_NPROCESSORS_ONLN);
907 num_threads = (nprocs > 0) ? (int)nprocs : 4;
908 }
909
910 double timeout = (p->timeout > 0.0) ? p->timeout : 60.0;
911
912 distfork_ctx_t ctx;
913 memset(&ctx, 0, sizeof(ctx));
914 ctx.p = p;
915 ctx.num_threads = num_threads;
916 ctx.timeout = timeout;
917 ctx.start_time = get_time_sec();
918 ctx.dexp = p->dexp;
919 ctx.total_rw_steps = (p->steps > 0) ? p->steps : 1;
920
921 /* Initialize dmin and dmax */
922 atomic_init(&ctx.dmin, p->dmin > 1 ? p->dmin : 1);
923 int init_dmax = 0;
924 if (p->dmax > 0) {
925 init_dmax = p->dmax;
926 }
927 if (p->min_w != INT_MAX) {
928 if (init_dmax == 0 || p->min_w < init_dmax) {
929 init_dmax = p->min_w;
930 }
931 }
932 atomic_init(&ctx.dmax, init_dmax);
933
934 if (init_dmax > 0 && p->wmin > 0 && init_dmax <= p->wmin && !p->outC) {
935 if (p->debug & 2) {
936 fprintf(stderr, "# early termination due to wmin=%d (known dmax=%d <= wmin)\n", p->wmin, init_dmax);
937 }
938 printf("%d %d 0\n", p->dmin > 1 ? p->dmin : 1, init_dmax);
939 var_kill(p);
940 return 0;
941 }
942
943 if (p->method == 3 && init_dmax > 0 && p->dmin > 1 && p->dmin >= init_dmax && !p->outC) {
944 if (p->debug & 2) {
945 fprintf(stderr, "# running method=3 (bracketing mode) with %d threads, timeout=%.1fs, dexp=%d\n",
946 num_threads, timeout, p->dexp);
947 }
948 printf("%d %d 0\n", p->dmin, init_dmax);
949 var_kill(p);
950 return 0;
951 }
952
953 atomic_init(&ctx.cc_found_weight, 0);
954 atomic_init(&ctx.stop_flag, false);
955 atomic_init(&ctx.rw_steps_started, 0);
956 atomic_init(&ctx.rw_steps_completed, 0);
957 atomic_init(&ctx.cc_weight, 1);
958 atomic_init(&ctx.cc_col_next, 0);
959 atomic_init(&ctx.cc_active_workers, 0);
960 atomic_init(&ctx.cc_target_workers, 0);
961 atomic_init(&ctx.cc_round_active, 0);
962
963 pthread_mutex_init(&ctx.cw_mutex, NULL);
964
965 ctx.mHT_cc = csr_transpose(NULL, p->spaH);
967
968 /* Allocate and launch worker threads */
969 ctx.threads = malloc(num_threads * sizeof(pthread_t));
970 worker_arg_t *args = malloc(num_threads * sizeof(worker_arg_t));
971
972 for (int i = 0; i < num_threads; i++) {
973 args[i].ctx = &ctx;
974 args[i].tid = i;
975 for (int k = 0; k < MAX_W; k++) {
976 args[i].min_swei[k] = p->spaH->rows + 1;
977 }
978 pthread_create(&ctx.threads[i], NULL, worker_thread_func, &args[i]);
979 }
980
981 if (p->method == 1) {
982 run_method1_coordinator(&ctx);
983 } else if (p->method == 2) {
984 run_method2_coordinator(&ctx);
985 } else if (p->method == 3) {
986 run_method3_coordinator(&ctx);
987 } else {
988 ERROR("invalid method %d\n", p->method);
989 }
990
991 /* Signal stop and wait for all workers */
992 atomic_store(&ctx.stop_flag, true);
993 for (int i = 0; i < num_threads; i++) {
994 pthread_join(ctx.threads[i], NULL);
995 }
996
997 int final_dmin = atomic_load(&ctx.dmin);
998 int final_dmax = atomic_load(&ctx.dmax);
999 int cc_found = atomic_load(&ctx.cc_found_weight);
1000
1001 if (cc_found > 0) {
1002 final_dmin = cc_found;
1003 final_dmax = cc_found;
1004 } else if (final_dmax > 0 && final_dmin >= final_dmax) {
1005 final_dmin = final_dmax;
1006 }
1007
1008 if (p->wmin > 0 && final_dmax > 0 && final_dmax <= p->wmin) {
1009 fprintf(stderr, "# early termination due to wmin=%d (cw of weight %d <= wmin found)\n", p->wmin, final_dmax);
1010 }
1011
1012 /* Confinement profile output (if smax > 0 and CC was run) */
1013 if (p->smax && p->method >= 2) {
1014 int max_w_analyzed = (final_dmin > 1) ? (final_dmin - 1) : ((p->wmax > 0) ? p->wmax : 0);
1015 if (cc_found > 0) max_w_analyzed = cc_found;
1016 if (max_w_analyzed > 0) {
1017 int global_swei[MAX_W];
1018 for (int i = 0; i < MAX_W; i++) global_swei[i] = p->spaH->rows + 1;
1019 for (int t = 0; t < num_threads; t++) {
1020 for (int i = 1; i <= max_w_analyzed; i++) {
1021 if (args[t].min_swei[i] < global_swei[i]) {
1022 global_swei[i] = args[t].min_swei[i];
1023 }
1024 }
1025 }
1026 int skipped = 0;
1027 if (p->debug & 1) {
1028 for (int i = 1; i <= max_w_analyzed; i++) {
1029 if (global_swei[i] <= p->spaH->rows) {
1030 fprintf(stderr, "# w=%d min non-zero syndrome weight %d\n", i, global_swei[i]);
1031 } else {
1032 skipped = 1;
1033 }
1034 }
1035 } else {
1036 fprintf(stderr, "# confinement: ");
1037 for (int i = 1; i <= max_w_analyzed; i++) {
1038 if (global_swei[i] <= p->spaH->rows) {
1039 fprintf(stderr, "%d%s", global_swei[i], i < max_w_analyzed ? "," : "");
1040 } else {
1041 skipped = 1;
1042 fprintf(stderr, "?%s", i < max_w_analyzed ? "," : "");
1043 }
1044 }
1045 fprintf(stderr, "\n");
1046 }
1047 if (skipped) {
1048 fprintf(stderr, "# Note: Some weights were skipped in confinement profile. Try increasing smax (current: %d)\n", p->smax);
1049 }
1050 }
1051 }
1052
1053 long reported_rw_steps = 0;
1054 if (p->method != 2 && cc_found == 0) {
1055 reported_rw_steps = atomic_load(&ctx.rw_steps_completed);
1056 }
1057
1058 /* Output to stdout: dmin dmax rw_steps */
1059 printf("%d %d %ld\n", final_dmin, final_dmax, reported_rw_steps);
1060 fflush(stdout);
1061
1062 /* Codeword export */
1063 if (p->outC) {
1064 char comment[256];
1065 sprintf(comment, "generated by dist_m4ri");
1066 nzlist_write(p->outC, comment, p);
1067 }
1068
1069 if (p->debug & 32) {
1070 cw_vec_t *cw;
1071 for (cw = p->codewords; cw != NULL; cw = (cw_vec_t *)(cw->hh.next)) {
1072 fprintf(stderr, "# cw: [ ");
1073 for (int i = 0; i < cw->weight; i++) fprintf(stderr, "%d ", 1 + cw->arr[i]);
1074 fprintf(stderr, "] cnt=%d\n", cw->cnt);
1075 }
1076 }
1077
1078 /* Cleanup */
1079 csr_free(ctx.mHT_cc);
1080 free(ctx.threads);
1081 free(args);
1082 pthread_mutex_destroy(&ctx.cw_mutex);
1083
1084 var_kill(p);
1085
1086 return 0;
1087}
int main(int argc, char **argv)
Definition dist_m4ri.c:894
int arr[0]
Definition util_hash.h:40
int cnt
Definition util_hash.h:39
UT_hash_handle hh
Definition util_hash.h:37
int weight
Definition util_hash.h:38
int vec[0]
Definition util_hash.h:46
void * next
Definition uthash.h:1129
int * p
Definition util_m4ri.h:115
int * i
Definition util_m4ri.h:116
int rows
Definition util_m4ri.h:111
int cols
Definition util_m4ri.h:112
atomic_long rw_steps_started
Definition dist_m4ri.c:130
double start_time
Definition dist_m4ri.c:119
atomic_int dmin
Definition dist_m4ri.c:123
double cc_time_per_weight[MAX_W]
Definition dist_m4ri.c:148
pthread_mutex_t cw_mutex
Definition dist_m4ri.c:145
csr_t * mHT_cc
Definition dist_m4ri.c:138
double avg_rw_step_time
Definition dist_m4ri.c:149
atomic_int cc_found_weight
Definition dist_m4ri.c:125
atomic_int cc_weight
Definition dist_m4ri.c:134
atomic_bool stop_flag
Definition dist_m4ri.c:126
long total_rw_steps
Definition dist_m4ri.c:129
atomic_int cc_target_workers
Definition dist_m4ri.c:141
atomic_int cc_round_active
Definition dist_m4ri.c:142
atomic_int cc_col_next
Definition dist_m4ri.c:135
atomic_int cc_active_workers
Definition dist_m4ri.c:140
pthread_t * threads
Definition dist_m4ri.c:152
params_t * p
Definition dist_m4ri.c:116
atomic_long rw_steps_completed
Definition dist_m4ri.c:131
atomic_int dmax
Definition dist_m4ri.c:124
double timeout
Definition util_io.h:82
char * finC
Definition util_io.h:66
long long int maxC
Definition util_io.h:64
int dmin
Definition util_io.h:42
int dexp
Definition util_io.h:81
int noscan
Definition util_io.h:48
csr_t * spaL
Definition util_io.h:79
char * outC
Definition util_io.h:67
csr_t * spaH
Definition util_io.h:77
int min_w
Definition util_io.h:70
int cbeg
Definition util_io.h:58
int smax
Definition util_io.h:37
int method
Definition util_io.h:35
int wmax
Definition util_io.h:41
int dmax
Definition util_io.h:43
int wmin
Definition util_io.h:44
int cend
Definition util_io.h:59
int steps
Definition util_io.h:36
int dW
Definition util_io.h:65
int debug
Definition util_io.h:32
long long int num_cws
Definition util_io.h:69
int threads
Definition util_io.h:80
int classical
Definition util_io.h:33
cw_vec_t * codewords
Definition util_io.h:68
int seed
Definition util_io.h:49
int min_swei[MAX_W]
Definition dist_m4ri.c:158
distfork_ctx_t * ctx
Definition dist_m4ri.c:156
Utility functions for use with uthash.h
void var_kill(params_t *const p)
Clean up and free memory allocated in the params_t structure.
Definition util_io.c:473
params_t *const p
Definition util_io.c:52
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:840
cw_vec_t * codeword_add_maybe(params_t *const p, const int arr[], int weight)
Add a candidate codeword to the hash table if it meets weight limits.
Definition util_io.c:797
long long int nzlist_write(const char fnam[], const char comment[], params_t *p)
Write the found codewords from the hash table to a .nz file.
Definition util_io.c:893
params_t prm
Definition util_io.c:7
void var_init(int argc, char **argv, params_t *const p)
Initialize parameters and load matrices from command line arguments.
Definition util_io.c:54
#define MAX_W
Definition util_io.h:28
csr_t * csr_transpose(csr_t *dst, const csr_t *const p)
Transpose a compressed CSR sparse matrix.
Definition util_m4ri.c:133
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:156
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:442
csr_t * csr_free(csr_t *p)
Free memory allocated for a CSR sparse matrix.
Definition util_m4ri.c:459
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:115
#define ERROR(fmt,...)
Definition util_m4ri.h:13