From 1f857a5f2bf5b08e01b80a6588f834989debea48 Mon Sep 17 00:00:00 2001 From: Weijia Wang <9713184+wegank@users.noreply.github.com> Date: Thu, 1 Oct 2026 09:27:34 +0200 Subject: [PATCH 1/3] Fix random multipliers in probabilistic linear algebra The random multipliers of the probabilistic linear algebra were drawn as rand() & fc, i.e. masked with the field characteristic itself and not reduced modulo it. For fc = 2^30+3 this only yields the values 0, 1, 2, 3, 2^30, ..., 2^30+3, so the multipliers lie in {0, +-1, +-2, +-3} mod fc, and fc itself (which is 0 mod fc) passes the nonzero check. Rows were then dropped from the random combinations, and since a block stops as soon as one combination reduces to zero, pivots were lost on small matrices. With -l 42 this gave wrong Groebner bases for about 40% of the seeds on nonradical-radicalshape-31. Draw the multipliers as rand() % fc instead, which is uniform in [1, fc-1] after rejecting 0; the 8- and 16-bit variants had the same problem. Add a regression test running -l 42 on 40 consecutive seeds. Co-Authored-By: Claude Opus 5.5 --- Makefile.am | 1 + configure.ac | 1 + src/neogb/la_ff_16.c | 6 ++-- src/neogb/la_ff_32.c | 4 +-- src/neogb/la_ff_8.c | 6 ++-- test/diff/diff_bug-prob-sparse-dense-la.sh | 36 ++++++++++++++++++++++ 6 files changed, 46 insertions(+), 8 deletions(-) create mode 100755 test/diff/diff_bug-prob-sparse-dense-la.sh diff --git a/Makefile.am b/Makefile.am index cb3948a8..54ca36a9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -16,6 +16,7 @@ check_PROGRAMS = neogb_io \ checkdiff = test/diff/diff_bug-2nd-prime-bad.sh \ test/diff/diff_bug-68.sh \ test/diff/diff_bug-empty-tracer.sh \ + test/diff/diff_bug-prob-sparse-dense-la.sh \ test/diff/diff_cp-d3-n4-p2.sh \ test/diff/diff_cyclic5-16.sh \ test/diff/diff_cyclic5-31.sh \ diff --git a/configure.ac b/configure.ac index 2221b5a0..7c065fe5 100644 --- a/configure.ac +++ b/configure.ac @@ -128,6 +128,7 @@ AC_CONFIG_LINKS([ input_files/bug-empty-tracer.ms:input_files/bug-empty-tracer.ms output_files/bug-empty-tracer.g2.res:output_files/bug-empty-tracer.g2.res test/diff/diff_bug-empty-tracer.sh:test/diff/diff_bug-empty-tracer.sh + test/diff/diff_bug-prob-sparse-dense-la.sh:test/diff/diff_bug-prob-sparse-dense-la.sh input_files/choice-linear-form-qq.ms:input_files/choice-linear-form-qq.ms output_files/choice-linear-form-qq.P2.d0.res:output_files/choice-linear-form-qq.P2.d0.res output_files/choice-linear-form-qq.p128.res:output_files/choice-linear-form-qq.p128.res diff --git a/src/neogb/la_ff_16.c b/src/neogb/la_ff_16.c index de7ba538..cf0cc399 100644 --- a/src/neogb/la_ff_16.c +++ b/src/neogb/la_ff_16.c @@ -779,7 +779,7 @@ static void probabilistic_sparse_reduced_echelon_form_ff_16( /* fill random value array */ for (j = 0; j < nrbl; ++j) { do { - mull[j] = (int64_t)rand() & fc; + mull[j] = (int64_t)rand() % fc; } while (mull[j] == 0); } /* generate one dense row as random linear combination @@ -1678,7 +1678,7 @@ static cf16_t **probabilistic_dense_linear_algebra_ff_16( /* fill random value array */ for (j = 0; j < nrbl; ++j) { do { - mull[j] = (int64_t)rand() & fc; + mull[j] = (int64_t)rand() % fc; } while (mull[j] == 0); } /* generate one dense row as random linear combination @@ -1812,7 +1812,7 @@ static cf16_t **probabilistic_sparse_dense_echelon_form_ff_16( /* fill random value array */ for (j = 0; j < nrbl; ++j) { do { - mull[j] = (int64_t)rand() & fc; + mull[j] = (int64_t)rand() % fc; } while (mull[j] == 0); } /* generate one dense row as random linear combination diff --git a/src/neogb/la_ff_32.c b/src/neogb/la_ff_32.c index c5794cf8..11649fa6 100644 --- a/src/neogb/la_ff_32.c +++ b/src/neogb/la_ff_32.c @@ -187,7 +187,7 @@ static int is_kernel_trivial( /* fill random value array */ for (i = 0; i < ncr; ++i) { do { - mull[j] = (int64_t)rand() & fc; + mull[j] = (int64_t)rand() % fc; } while (mull[j] == 0); } for (i = 0; i < sat->ld; ++i) { @@ -3738,7 +3738,7 @@ static cf32_t **probabilistic_sparse_dense_echelon_form_ff_32( /* fill random value array */ for (j = 0; j < nrbl; ++j) { do { - mull[j] = (int64_t)rand() & fc; + mull[j] = (int64_t)rand() % fc; } while (mull[j] == 0); } /* generate one dense row as random linear combination diff --git a/src/neogb/la_ff_8.c b/src/neogb/la_ff_8.c index 2f740081..71d1dbfb 100644 --- a/src/neogb/la_ff_8.c +++ b/src/neogb/la_ff_8.c @@ -928,7 +928,7 @@ static void probabilistic_sparse_reduced_echelon_form_ff_8( /* fill random value array */ for (j = 0; j < nrbl; ++j) { do { - mull[j] = (int64_t)rand() & fc; + mull[j] = (int64_t)rand() % fc; } while (mull[j] == 0); } /* generate one dense row as random linear combination @@ -1807,7 +1807,7 @@ static cf8_t **probabilistic_dense_linear_algebra_ff_8( /* fill random value array */ for (j = 0; j < nrbl; ++j) { do { - mull[j] = (int64_t)rand() & fc; + mull[j] = (int64_t)rand() % fc; } while (mull[j] == 0); } /* generate one dense row as random linear combination @@ -1941,7 +1941,7 @@ static cf8_t **probabilistic_sparse_dense_echelon_form_ff_8( /* fill random value array */ for (j = 0; j < nrbl; ++j) { do { - mull[j] = (int64_t)rand() & fc; + mull[j] = (int64_t)rand() % fc; } while (mull[j] == 0); } /* generate one dense row as random linear combination diff --git a/test/diff/diff_bug-prob-sparse-dense-la.sh b/test/diff/diff_bug-prob-sparse-dense-la.sh new file mode 100755 index 00000000..f2491ec5 --- /dev/null +++ b/test/diff/diff_bug-prob-sparse-dense-la.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash + +# Probabilistic sparse/dense linear algebra (-l 42) used to lose rows on +# small matrices since the random multipliers were not uniformly chosen +# in [1, p-1]. Then msolve wrote an empty output file while exiting with +# status 0. The failure depends on the random seed, thus we run several +# consecutive seeds. Line 5 of the output (the random linear form) depends +# on the seed and is not compared. + +file=nonradical-radicalshape-31 + +source test/diff/diff_source.sh + +sed 5d output_files/$file.res > test/diff/$file.prob-la.ref + +for i in $(seq 0 39); do + for t in 1 2; do + $(pwd)/msolve -f input_files/$file.ms -o test/diff/$file.prob-la.res \ + --random-seed $((seed + i)) -P 1 -l 42 -t $t + if [ $? -gt 0 ]; then + print_exit 1 + fi + + sed 5d test/diff/$file.prob-la.res | \ + diff - test/diff/$file.prob-la.ref + if [ $? -gt 0 ]; then + print_exit 2 + fi + + rm test/diff/$file.prob-la.res + done +done + +rm test/diff/$file.prob-la.ref + +normal_exit From 467ef28f826c2c631ce485a37c703678e0c36f4d Mon Sep 17 00:00:00 2001 From: Weijia Wang <9713184+wegank@users.noreply.github.com> Date: Thu, 1 Oct 2026 09:27:34 +0200 Subject: [PATCH 2/3] Fix buffer overflow in probabilistic_sparse_dense_echelon_form_ff_32 The array of random multipliers is indexed with rpb entries per thread, but it was allocated with ncols entries per thread. When a block has more rows than the matrix has columns, which happens for small matrices, this wrote past the end of the array. Allocate rpb entries per thread as in the 8- and 16-bit versions. Co-Authored-By: Claude Opus 5.5 --- src/neogb/la_ff_32.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/neogb/la_ff_32.c b/src/neogb/la_ff_32.c index 11649fa6..7b8a4726 100644 --- a/src/neogb/la_ff_32.c +++ b/src/neogb/la_ff_32.c @@ -3716,7 +3716,7 @@ static cf32_t **probabilistic_sparse_dense_echelon_form_ff_32( int64_t *dr = (int64_t *)malloc( (uint64_t)ncols * st->nthrds * sizeof(int64_t)); int64_t *mul = (int64_t *)malloc( - (uint64_t)ncols * st->nthrds * sizeof(int64_t)); + (uint64_t)rpb * st->nthrds * sizeof(int64_t)); /* reduction process to get all possible pivots, no interreduction here */ #pragma omp parallel for num_threads(st->nthrds) \ From 8658e1582bfeec4f0ca16fc091d69e48fc1741d1 Mon Sep 17 00:00:00 2001 From: Weijia Wang <9713184+wegank@users.noreply.github.com> Date: Thu, 1 Oct 2026 10:29:21 +0200 Subject: [PATCH 3/3] Loop over blocks in probabilistic_dense_linear_algebra The parallel loop iterates over the blocks of rows but ran up to the number of rows ntr instead of the number of blocks nb. The additional iterations have no rows to handle, so this only removes useless work. Co-Authored-By: Claude Opus 5.5 --- src/neogb/la_ff_16.c | 2 +- src/neogb/la_ff_32.c | 2 +- src/neogb/la_ff_8.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/neogb/la_ff_16.c b/src/neogb/la_ff_16.c index cf0cc399..9b08e8df 100644 --- a/src/neogb/la_ff_16.c +++ b/src/neogb/la_ff_16.c @@ -1660,7 +1660,7 @@ static cf16_t **probabilistic_dense_linear_algebra_ff_16( #pragma omp parallel for num_threads(st->nthrds) \ private(i, j, k, l) shared(nps, tbr) \ schedule(dynamic) - for (i = 0; i < ntr; ++i) { + for (i = 0; i < nb; ++i) { int64_t *drl = dr + (omp_get_thread_num() * (uint64_t)ncr); int64_t *mull = mul + (omp_get_thread_num() * (uint64_t)rpb); const int32_t nbl = (int32_t) (ntr > (i+1)*rpb ? (i+1)*rpb : ntr); diff --git a/src/neogb/la_ff_32.c b/src/neogb/la_ff_32.c index 7b8a4726..52c912b6 100644 --- a/src/neogb/la_ff_32.c +++ b/src/neogb/la_ff_32.c @@ -3586,7 +3586,7 @@ static cf32_t **probabilistic_dense_linear_algebra_ff_32( #pragma omp parallel for num_threads(st->nthrds) \ private(i, j, k, l) shared(nps, tbr) \ schedule(dynamic) - for (i = 0; i < ntr; ++i) { + for (i = 0; i < nb; ++i) { int64_t *drl = dr + (omp_get_thread_num() * (uint64_t)ncr); int64_t *mull = mul + (omp_get_thread_num() * (uint64_t)rpb); const int32_t nbl = (int32_t) (ntr > (i+1)*rpb ? (i+1)*rpb : ntr); diff --git a/src/neogb/la_ff_8.c b/src/neogb/la_ff_8.c index 71d1dbfb..89af2c54 100644 --- a/src/neogb/la_ff_8.c +++ b/src/neogb/la_ff_8.c @@ -1789,7 +1789,7 @@ static cf8_t **probabilistic_dense_linear_algebra_ff_8( #pragma omp parallel for num_threads(st->nthrds) \ private(i, j, k, l) shared(nps, tbr) \ schedule(dynamic) - for (i = 0; i < ntr; ++i) { + for (i = 0; i < nb; ++i) { int64_t *drl = dr + (omp_get_thread_num() * (uint64_t)ncr); int64_t *mull = mul + (omp_get_thread_num() * (uint64_t)rpb); const int32_t nbl = (int32_t) (ntr > (i+1)*rpb ? (i+1)*rpb : ntr);