49static pthread_mutex_t m4ri_mem_mutex = PTHREAD_MUTEX_INITIALIZER;
51static inline mzd_t *safe_mzd_from_csr(mzd_t *dst,
const csr_t *
p) {
52 pthread_mutex_lock(&m4ri_mem_mutex);
54 pthread_mutex_unlock(&m4ri_mem_mutex);
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);
65static inline void safe_mzd_free(mzd_t *M) {
67 pthread_mutex_lock(&m4ri_mem_mutex);
69 pthread_mutex_unlock(&m4ri_mem_mutex);
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);
79static inline void safe_mzp_free(mzp_t *P) {
81 pthread_mutex_lock(&m4ri_mem_mutex);
83 pthread_mutex_unlock(&m4ri_mem_mutex);
86static inline double get_time_sec(
void) {
88 clock_gettime(CLOCK_MONOTONIC, &ts);
89 return (
double)ts.tv_sec + (double)ts.tv_nsec * 1e-9;
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;
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);
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);
109 for (
int i = length - 1; i < (int)q->length; i++) {
163 const int w_limit,
const int max_col_wt,
164 const csr_t *
const mH,
const csr_t *
const mHT,
167 if (atomic_load_explicit(&ctx->
stop_flag, memory_order_relaxed)) {
171 const int w = err->
wei;
172 int row = syn[w]->
vec[0];
174 const int col_min = urr->
vec[0];
176 for (
int i1 = mH->
p[row]; i1 < mH->
p[row+1]; i1++) {
177 const int col = mH->
i[i1];
179 int pos = one_ordered_search(err, col);
183 pos = one_ordered_ins(err, col);
185 int swei = one_csr_row_combine(syn[w+1], syn[w], mHT, col);
187 if (
p->
smax && swei > 0 && swei <= p->smax && (w + 1 <
MAX_W)) {
188 if (swei < warg->min_swei[w + 1]) {
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);
199 if (err->
wei < current_limit) {
201 int result = start_CC_recurs_mt(err, urr, syn, w_limit, max_col_wt,
205 one_ordered_pos_del(err, col, pos);
211 int nz = (!mL) || sparse_syndrome_non_zero(mL, err->
wei, err->
vec);
216 int cur_d = atomic_load(&ctx->
dmax);
217 if (
p->
min_w < cur_d || cur_d == 0) {
221 if (cur_cc_found == 0 || err->
wei < cur_cc_found) {
232 pthread_mutex_unlock(&ctx->
cw_mutex);
236 one_ordered_pos_del(err, col, pos);
243 one_ordered_pos_del(err, col, pos);
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) {
260 for (
int step = 0; step < n_steps; step++) {
261 if (atomic_load_explicit(&ctx->
stop_flag, memory_order_relaxed))
break;
263 pivs = mzp_rand_thread(pivs, nvar, rng_state);
268 for (
int i = 0; i < nvar; i++) {
269 int col = perm->values[i];
270 int ret = gauss_one(mH, col, rank);
272 pivs->values[rank++] = col;
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++) {
281 end = pivs_srtd->values[i];
282 for (
int j = beg; j < end; j++) {
283 skip_pivs->values[num++] = j;
286 for (
int j = end + 1; j < nvar; j++) {
287 skip_pivs->values[num++] = j;
289 skip_pivs->length = num;
291 mzd_transpose(mHT, mH);
294 for (
int ir = 0; ir < k; ir++) {
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);
301 limit = minint(limit, cur_dmax +
p->
dW + 1);
303 limit = minint(limit, cur_dmax);
307 word *rawrow = mzd_row(mHT, col);
309 while (cnt < limit) {
310 j = nextelement(rawrow, mHT->width, j);
311 if (j == -1 || j >= rank)
break;
312 ee[cnt++] = pivs->values[j++];
316 qsort(ee, cnt,
sizeof(rci_t), cmp_rci_t);
317 int nz = classical ? 1 : sparse_syndrome_non_zero(spaL0, cnt, ee);
321 if (cnt < p->min_w)
p->
min_w = cnt;
323 int old_dmax = atomic_load(&ctx->
dmax);
324 if (old_dmax == 0 || best < old_dmax) {
325 atomic_store(&ctx->
dmax, best);
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);
331 int cur_dmin = atomic_load(&ctx->
dmin);
332 if (cur_dmin > 0 && best <= cur_dmin) {
336 if (
p->
wmin > 0 && best <= p->wmin) {
342 pthread_mutex_unlock(&ctx->
cw_mutex);
351static void *worker_thread_func(
void *arg) {
356 const bool enable_rw = (ctx->
p->
method & 1) != 0;
359 for (
int i = 0; i <
MAX_W; i++) {
365 mzd_t *mHT_rw = 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;
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);
387 1,
sizeof(
one_vec_t) +
sizeof(
int) * (wmax_alloc + 2)
390 1,
sizeof(
one_vec_t) +
sizeof(
int) * (wmax_alloc + 2)
393 for (
int i = 0; i <= wmax_alloc + 2; i++) {
399 while (!atomic_load_explicit(&ctx->
stop_flag, memory_order_relaxed)) {
405 bool did_work =
false;
411 if (active < target) {
418 err->
vec[0] = urr->
vec[0] = col;
421 int swei = one_csr_row_combine(syn[1], syn[0], ctx->
mHT_cc, col);
423 if (ctx->
p->
smax && swei > 0 && swei <= ctx->
p->
smax) {
424 if (swei < warg->min_swei[1]) {
435 int nz = (!ctx->
p->
spaL) || sparse_syndrome_non_zero(ctx->
p->
spaL, 1, err->
vec);
440 atomic_store(&ctx->
dmin, 1);
441 atomic_store(&ctx->
dmax, 1);
443 pthread_mutex_unlock(&ctx->
cw_mutex);
456 if (enable_rw && !atomic_load(&ctx->
stop_flag)) {
458 if (cur_s < ctx->total_rw_steps) {
459 long target_s = cur_s + 10;
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);
476 safe_mzp_free(skip_pivs);
477 safe_mzp_free(pivs_srtd);
481 safe_mzd_free(mHT_rw);
485 for (
int i = 0; i <= wmax_alloc + 2; i++) free(syn[i]);
496 fprintf(stderr,
"# running method=1 (multithreaded RW) with %d threads, total steps=%ld\n",
515 const int wmax = ctx->
p->
wmax;
516 const int w_start = ctx->
p->
noscan ? wmax : (ctx->
p->
dmin > 1 ? ctx->
p->
dmin : 1);
519 fprintf(stderr,
"# running method=2 (multithreaded CC) with %d threads, w_start=%d wmax=%d\n",
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);
528 w_limit = minint(wmax > 0 ? wmax : ctx->
p->dmax, ctx->
p->dmax);
532 for (
int w = w_start; w <= w_limit; w++) {
534 double now = get_time_sec();
536 if (ctx->
timeout > 0.0 && remaining_time <= 0.0) {
542 if (ctx->
timeout > 0.0 && w > w_start) {
544 if (prev <= 0.0001) prev = 0.001;
548 if (growth < 2.0) growth = 2.0;
549 if (growth > 10.0) growth = 10.0;
551 double t_cc_est = prev * growth;
552 if ((t_cc_est / ctx->
num_threads) > remaining_time * 1.5) {
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));
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);
572 double cc_start = get_time_sec();
575 fprintf(stderr,
"# searching w=%d with %d CC threads, columns [%d, %d]\n",
579 bool round_completed =
false;
586 round_completed =
true;
594 double cc_dur = get_time_sec() - cc_start;
601 atomic_store(&ctx->
dmin, cw_found);
602 atomic_store(&ctx->
dmax, cw_found);
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);
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",
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",
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",
623 fprintf(stderr,
"# CC found min-weight codeword: d=%d (using %d CC threads, total %lld cws)\n",
627 if (w >= w_limit || !round_completed) {
633 if (!round_completed) {
637 atomic_store(&ctx->
dmin, w + 1);
639 fprintf(stderr,
"# CC w=%d completed in %.3fs (%d CC threads): no codewords found -> dmin=%d\n",
652 fprintf(stderr,
"# running method=3 (bracketing mode) with %d threads, timeout=%.1fs, dexp=%d\n",
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) {
664 double t_rw_start = get_time_sec();
666 double t_rw_dur = get_time_sec() - t_rw_start;
668 if (initial_steps > 0) {
675 double now = get_time_sec();
677 if (remaining_time <= 0.0) {
682 int cur_dmax = atomic_load(&ctx->
dmax);
683 int cur_dmin = atomic_load(&ctx->
dmin);
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);
691 target_cc_w = cur_dmax - 1;
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;
701 int max_allowed_w = (ctx->
p->
wmax > 0)
703 if (target_cc_w > max_allowed_w) {
704 target_cc_w = max_allowed_w;
707 if (cur_dmax > 0 && cur_dmin >= cur_dmax && w > target_cc_w) {
709 atomic_store(&ctx->
dmin, cur_dmax);
714 if (w > target_cc_w) {
732 if (prev <= 0.0001) prev = 0.001;
736 if (growth < 2.0) growth = 2.0;
737 if (growth > 10.0) growth = 10.0;
739 t_cc_est = prev * growth;
743 if (t_cc_est / ctx->
num_threads > remaining_time * 1.5) {
745 fprintf(stderr,
"# CC for w=%d (est %.2fs) exceeds remaining timeout %.2fs, devoting %d threads to RW\n",
763 }
else if (steps_rem == 0) {
765 }
else if (t_cc_est < 0.005) {
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++) {
773 t_cc_total_1t += est_accum;
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;
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);
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);
798 double cc_start = get_time_sec();
799 bool round_completed =
false;
807 round_completed =
true;
815 double cc_dur = get_time_sec() - cc_start;
822 atomic_store(&ctx->
dmin, cw_found);
823 atomic_store(&ctx->
dmax, cw_found);
825 if (ctx->
p->
outC && ctx->
p->
dW > 0 && w < minint(ctx->
p->
wmax > 0 ? ctx->
p->
wmax : nvar, cw_found + ctx->
p->dW)) {
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);
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);
843 fprintf(stderr,
"# CC found min-weight codeword: d=%d (using %d CC threads, total %lld cws)\n",
850 }
else if (cur_dmax > 0 && cur_dmin >= cur_dmax) {
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);
856 if (!round_completed) {
860 if (!round_completed) {
865 int new_dmin = w + 1;
866 atomic_store(&ctx->
dmin, new_dmin);
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);
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) {
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);
883 fprintf(stderr,
"# bracketing bounds coincide: dmin = dmax = %d\n", cur_dmax);
894int main(
int argc,
char **argv) {
905 if (num_threads <= 0) {
906 long nprocs = sysconf(_SC_NPROCESSORS_ONLN);
907 num_threads = (nprocs > 0) ? (
int)nprocs : 4;
913 memset(&ctx, 0,
sizeof(ctx));
927 if (
p->
min_w != INT_MAX) {
928 if (init_dmax == 0 ||
p->
min_w < init_dmax) {
932 atomic_init(&ctx.
dmax, init_dmax);
934 if (init_dmax > 0 &&
p->
wmin > 0 && init_dmax <= p->wmin && !
p->
outC) {
936 fprintf(stderr,
"# early termination due to wmin=%d (known dmax=%d <= wmin)\n",
p->
wmin, init_dmax);
938 printf(
"%d %d 0\n",
p->
dmin > 1 ?
p->
dmin : 1, init_dmax);
945 fprintf(stderr,
"# running method=3 (bracketing mode) with %d threads, timeout=%.1fs, dexp=%d\n",
946 num_threads, timeout,
p->
dexp);
948 printf(
"%d %d 0\n",
p->
dmin, init_dmax);
963 pthread_mutex_init(&ctx.
cw_mutex, NULL);
969 ctx.
threads = malloc(num_threads *
sizeof(pthread_t));
972 for (
int i = 0; i < num_threads; i++) {
975 for (
int k = 0; k <
MAX_W; k++) {
978 pthread_create(&ctx.
threads[i], NULL, worker_thread_func, &args[i]);
982 run_method1_coordinator(&ctx);
984 run_method2_coordinator(&ctx);
986 run_method3_coordinator(&ctx);
993 for (
int i = 0; i < num_threads; i++) {
994 pthread_join(ctx.
threads[i], NULL);
997 int final_dmin = atomic_load(&ctx.
dmin);
998 int final_dmax = atomic_load(&ctx.
dmax);
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;
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);
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];
1028 for (
int i = 1; i <= max_w_analyzed; i++) {
1030 fprintf(stderr,
"# w=%d min non-zero syndrome weight %d\n", i, global_swei[i]);
1036 fprintf(stderr,
"# confinement: ");
1037 for (
int i = 1; i <= max_w_analyzed; i++) {
1039 fprintf(stderr,
"%d%s", global_swei[i], i < max_w_analyzed ?
"," :
"");
1042 fprintf(stderr,
"?%s", i < max_w_analyzed ?
"," :
"");
1045 fprintf(stderr,
"\n");
1048 fprintf(stderr,
"# Note: Some weights were skipped in confinement profile. Try increasing smax (current: %d)\n",
p->
smax);
1053 long reported_rw_steps = 0;
1054 if (
p->
method != 2 && cc_found == 0) {
1059 printf(
"%d %d %ld\n", final_dmin, final_dmax, reported_rw_steps);
1065 sprintf(comment,
"generated by dist_m4ri");
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);
1082 pthread_mutex_destroy(&ctx.
cw_mutex);
int main(int argc, char **argv)
atomic_long rw_steps_started
double cc_time_per_weight[MAX_W]
atomic_int cc_found_weight
atomic_int cc_target_workers
atomic_int cc_round_active
atomic_int cc_active_workers
atomic_long rw_steps_completed
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.
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.
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.
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.
void var_init(int argc, char **argv, params_t *const p)
Initialize parameters and load matrices from command line arguments.
csr_t * csr_transpose(csr_t *dst, const csr_t *const p)
Transpose a compressed CSR sparse matrix.
mzd_t * mzd_from_csr(mzd_t *dst, const csr_t *p)
Convert a CSR sparse matrix to an MZD dense matrix.
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.
csr_t * csr_free(csr_t *p)
Free memory allocated for a CSR sparse matrix.
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