From cad8d359ee514de4489860530846fe80bd3bc8a2 Mon Sep 17 00:00:00 2001 From: DC Date: Sun, 23 Aug 2026 16:22:28 -0700 Subject: [PATCH] Fix CLI I/O: reject malformed input and stop labeling unlifted GBs as char 0 Parser: reject parentheses, post-monomial division (x/2), a leading *, repeated variables, and (over a prime field) coefficients that overflow long. These previously produced a wrong Groebner basis or a segfault with exit code 0. -g over Q: if rational reconstruction did not run (empty basis, -g 1 leading monomials, empty elimination basis) print the first modular prime and a "#lifted to Q: no" line instead of "field characteristic: 0". Co-Authored-By: Grok via the JC2 campaign --- Makefile.am | 1 + README.md | 14 +- input_files/bad-div.ms | 4 + input_files/bad-overflow-ff.ms | 3 + input_files/bad-paren.ms | 3 + input_files/bad-star.ms | 3 + input_files/good-leading-rat.ms | 3 + output_files/empty-elim-qq.g2.e2.res | 3 +- output_files/good-leading-rat.g2.res | 8 + output_files/one-qq.g2.res | 3 +- src/msolve/iofiles.c | 246 +++++++++++++++++++++++---- src/msolve/lifting-gb.c | 14 +- src/msolve/main.c | 8 +- test/diff/diff_reject-malformed.sh | 36 ++++ test/diff/diff_source-gb-elim-red.sh | 16 +- test/diff/diff_source-gb-red.sh | 8 +- test/diff/diff_source.sh | 15 ++ 17 files changed, 334 insertions(+), 54 deletions(-) create mode 100644 input_files/bad-div.ms create mode 100644 input_files/bad-overflow-ff.ms create mode 100644 input_files/bad-paren.ms create mode 100644 input_files/bad-star.ms create mode 100644 input_files/good-leading-rat.ms create mode 100644 output_files/good-leading-rat.g2.res create mode 100755 test/diff/diff_reject-malformed.sh diff --git a/Makefile.am b/Makefile.am index 78d13349..c16a13ea 100644 --- a/Makefile.am +++ b/Makefile.am @@ -65,6 +65,7 @@ checkdiff = test/diff/diff_bug-2nd-prime-bad.sh \ test/diff/diff_quadratic-nonradical-qq.sh \ test/diff/diff_radical-shape-31.sh \ test/diff/diff_radical-shape-qq.sh \ + test/diff/diff_reject-malformed.sh \ test/diff/diff_realroot1.sh \ test/diff/diff_realroot-extraction-exact-root.sh \ test/diff/diff_reals-dim0-chgvar.sh \ diff --git a/README.md b/README.md index f3612103..e383b0e9 100644 --- a/README.md +++ b/README.md @@ -47,11 +47,13 @@ x1+x2+x3+x4, ``` Polynomials may be multiline, thus `,` as a separator. -Coefficients can be rational, using `/`, e.g. `-2/3*x2*y1^2+...`. +Coefficients can be rational, using `/`, e.g. `-2/3*x2*y1^2+...` +(leading rational form only: `1/2*x`, not `x/2`). -In each polynomial, a single occurrence of each monomial is expected; the -behaviour of msolve's parser is undefined if some monomial is repeated, as in -`x1+x2+x3+x4-x1`. +In each polynomial, a single occurrence of each monomial is expected. +Parentheses, post-monomial division (`x/2`), a leading `*`, repeated +monomials, and (over a prime field) coefficients that do not fit in a +machine word are rejected with a nonzero exit code. # Basic usage @@ -99,6 +101,10 @@ Using the `-g 2` flag as follows ``` will return the reduced Groebner basis for the graded reverse lexicographic ordering. +Over a prime field this is exact. Over the rationals, coefficients are +lifted to Q when possible; if lifting is skipped (in particular when the +basis is `{1}`, or when only leading monomials are requested with `-g 1`) +the header prints the first modular prime rather than characteristic 0. `msolve` also allows you to perform Groebner bases computations using **one-block elimination monomial order** diff --git a/input_files/bad-div.ms b/input_files/bad-div.ms new file mode 100644 index 00000000..368b38dd --- /dev/null +++ b/input_files/bad-div.ms @@ -0,0 +1,4 @@ +x, y +0 +x/2 - y, +y - 3 diff --git a/input_files/bad-overflow-ff.ms b/input_files/bad-overflow-ff.ms new file mode 100644 index 00000000..ee0410f5 --- /dev/null +++ b/input_files/bad-overflow-ff.ms @@ -0,0 +1,3 @@ +x +65521 +9223372036854775808*x diff --git a/input_files/bad-paren.ms b/input_files/bad-paren.ms new file mode 100644 index 00000000..d366bab8 --- /dev/null +++ b/input_files/bad-paren.ms @@ -0,0 +1,3 @@ +x +0 +x-(3+1) diff --git a/input_files/bad-star.ms b/input_files/bad-star.ms new file mode 100644 index 00000000..4d446f3f --- /dev/null +++ b/input_files/bad-star.ms @@ -0,0 +1,3 @@ +x +0 +*x diff --git a/input_files/good-leading-rat.ms b/input_files/good-leading-rat.ms new file mode 100644 index 00000000..55a58bc1 --- /dev/null +++ b/input_files/good-leading-rat.ms @@ -0,0 +1,3 @@ +x +0 +1/2*x-1 diff --git a/output_files/empty-elim-qq.g2.e2.res b/output_files/empty-elim-qq.g2.e2.res index 5d4a5a6d..66b9b97f 100644 --- a/output_files/empty-elim-qq.g2.e2.res +++ b/output_files/empty-elim-qq.g2.e2.res @@ -1,6 +1,7 @@ #Reduced Groebner basis data #--- -#field characteristic: 0 +#field characteristic: +#lifted to Q: no (first modular prime only) #variable order: z #monomial order: graded reverse lexicographical #length of basis: 0 elements sorted by increasing leading monomials diff --git a/output_files/good-leading-rat.g2.res b/output_files/good-leading-rat.g2.res new file mode 100644 index 00000000..ff792502 --- /dev/null +++ b/output_files/good-leading-rat.g2.res @@ -0,0 +1,8 @@ +#Reduced Groebner basis data +#--- +#field characteristic: 0 +#variable order: x +#monomial order: graded reverse lexicographical +#length of basis: 1 element +#--- +[x-2]: diff --git a/output_files/one-qq.g2.res b/output_files/one-qq.g2.res index 3b3c6af4..aa82f7a2 100644 --- a/output_files/one-qq.g2.res +++ b/output_files/one-qq.g2.res @@ -1,6 +1,7 @@ #Reduced Groebner basis data #--- -#field characteristic: 0 +#field characteristic: +#lifted to Q: no (first modular prime only) #variable order: x, y, z #monomial order: graded reverse lexicographical #length of basis: 1 element diff --git a/src/msolve/iofiles.c b/src/msolve/iofiles.c index 2517019f..ca94e016 100644 --- a/src/msolve/iofiles.c +++ b/src/msolve/iofiles.c @@ -22,29 +22,75 @@ #include "streams.h" -static inline void store_exponent(const char *term, data_gens_ff_t *gens, int32_t pos) +#include +#include + +/* Reject syntax the rest of the parser cannot represent, rather than + * silently computing a different system (parens, x/2, *x, ...). */ +static int check_polynomial_syntax(const char *line) +{ + const char *p; + if (line == NULL) { + return 1; + } + for (p = line; *p != '\0'; ++p) { + if (*p == '(' || *p == ')') { + fprintf(ERRSTREAM, + "Error when parsing polynomial: parentheses are not supported " + "(write expanded monomial sums, e.g. x-4 not x-(3+1)).\n"); + return 1; + } + } + return 0; +} + +static inline int store_exponent(const char *term, data_gens_ff_t *gens, int32_t pos) { len_t i, j, k; len_t op = 0; + len_t index = 0; + int res = 0; + mpz_t tmp_z; + mpq_t tmp_q; char *var = NULL; char *ev = NULL; + char *end = NULL; + + if (gens->field_char > 0) { + mpz_init(tmp_z); + } else { + mpq_init(tmp_q); + } + for (i = 0; i < strlen(term)+1; ++i) { if (term[i] == '*' || i == strlen(term)) { j = op; while (j < i && term[j] != '^') { ++j; } - if (term[j-1] == ',') { - --j; - } - while(term[op] == ' ' || term[op] == '+' || term[op] == '-') { + while (term[op] == ' ' || term[op] == '+' || term[op] == '-') { ++op; } + if (op >= j) { + fprintf(ERRSTREAM, + "Error when parsing term %s (missing operand for *)\n", + term); + res = 1; + break; + } var = realloc(var, sizeof(char)*(j-op+1)); memcpy(var, term+op, j-op); var[j-op] = '\0'; - if (term[j] == '^') { + int has_exponent = (j < i && term[j] == '^'); + if (has_exponent) { + if (j + 1 >= i) { + fprintf(ERRSTREAM, + "Error when parsing term %s (missing exponent)\n", + term); + res = 1; + break; + } ev = realloc(ev, (sizeof(char)*(i-j))); ev = memcpy(ev, term+(j+1), i-j-1); ev[i-j-1] = '\0'; @@ -53,17 +99,118 @@ static inline void store_exponent(const char *term, data_gens_ff_t *gens, int32_ ev[0] = '1'; ev[1] ='\0'; } + int is_var = 0; for (k = 0; k < gens->nvars; ++k) { if (strcmp(gens->vnames[k], var) == 0) { - ((gens->exps) + pos)[k] = strtol(ev, NULL, 10); + is_var = 1; + if (((gens->exps) + pos)[k] != 0) { + fprintf(ERRSTREAM, + "Error when parsing term %s (variable appears multiple times)\n", + term); + res = 1; + break; + } + errno = 0; + long val = strtol(ev, &end, 10); + if (ev == end || (end != NULL && *end != '\0')) { + fprintf(ERRSTREAM, + "Error when parsing term %s (invalid exponent)\n", + term); + res = 1; + break; + } + if (errno == ERANGE || val > INT_MAX) { + fprintf(ERRSTREAM, + "Error when parsing term %s (exponent out of range)\n", + term); + res = 1; + break; + } + if (val <= 0) { + fprintf(ERRSTREAM, + "Error when parsing term %s (exponent must be positive)\n", + term); + res = 1; + break; + } + ((gens->exps) + pos)[k] = (int32_t)val; break; } } + if (res == 1) { + break; + } + if (is_var == 0) { + if (has_exponent) { + fprintf(ERRSTREAM, + "Error when parsing term %s (coefficient cannot have an exponent)\n", + term); + res = 1; + break; + } + if (index > 0) { + fprintf(ERRSTREAM, + "Error when parsing term %s (multiple coefficients are not allowed)\n", + term); + res = 1; + break; + } + if (strchr(var, '/') != NULL && gens->field_char > 0) { + fprintf(ERRSTREAM, + "Error when parsing term %s " + "(division is only allowed in a leading rational coefficient " + "over Q, e.g. 1/2*x, not x/2)\n", + term); + res = 1; + break; + } + if (gens->field_char > 0) { + if (mpz_set_str(tmp_z, var, 10) != 0) { + if (strchr(var, '/') != NULL) { + fprintf(ERRSTREAM, + "Error when parsing term %s " + "(division is only allowed in a leading rational " + "coefficient over Q, e.g. 1/2*x, not x/2)\n", + term); + } else { + fprintf(ERRSTREAM, + "Error when parsing term %s (invalid coefficient for finite field)\n", + term); + } + res = 1; + break; + } + } else { + if (mpq_set_str(tmp_q, var, 10) != 0) { + if (strchr(var, '/') != NULL) { + fprintf(ERRSTREAM, + "Error when parsing term %s " + "(division is only allowed in a leading rational " + "coefficient, e.g. 1/2*x, not x/2)\n", + term); + } else { + fprintf(ERRSTREAM, + "Error when parsing term %s (invalid coefficient)\n", + term); + } + res = 1; + break; + } + } + } op = i+1; + index++; } } + free(var); free(ev); + if (gens->field_char > 0) { + mpz_clear(tmp_z); + } else { + mpq_clear(tmp_q); + } + return res; } @@ -647,6 +794,10 @@ static void get_nterms_and_all_nterms(FILE *fh, i--; continue; } + if (check_polynomial_syntax(line)) { + free(line); + exit(1); + } *nterms = get_number_of_terms(line); gens->lens[i] = *nterms; *all_nterms += *nterms; @@ -737,6 +888,36 @@ static inline void get_term(const char *line, char **prev_pos, } +/* Coefficients that do not fit in long are rejected (strtol would clamp). */ +static int parse_ff_coeff_from_term(const char *term, int32_t field_char, + int64_t *out) +{ + errno = 0; + long val = strtol(term, NULL, 10); + if (errno == ERANGE) { + fprintf(ERRSTREAM, + "Error when parsing term %s (coefficient does not fit in a machine word; " + "reduce modulo %d before input).\n", + term, field_char); + return 1; + } + if (val == 0) { + switch (term[0]) { + case '0': + val = 0; + break; + case '-': + val = -1; + break; + default: + val = 1; + break; + } + } + *out = val; + return 0; +} + /*assumes that coeffs in file fit in word size */ static int get_coefficient_ff_and_term_from_line(char *line, int32_t nterms, int32_t field_char, @@ -749,21 +930,12 @@ static int get_coefficient_ff_and_term_from_line(char *line, int32_t nterms, prev_pos = line; get_term(line, &prev_pos, &term, &term_size); if(term != NULL){ - int32_t iv_tmp = (int32_t)strtol(term, NULL, 10); - if (iv_tmp == 0) { - switch (term[0]) { - case '0': - iv_tmp = 0; - break; - case '-': - iv_tmp = -1; - break; - default: - iv_tmp = 1; - break; - } + int64_t iv64 = 0; + if (parse_ff_coeff_from_term(term, field_char, &iv64)) { + free(term); + return 1; } - iv_tmp %= field_char; + int32_t iv_tmp = (int32_t)(iv64 % field_char); if (iv_tmp == 0) { fprintf(stderr, "Error when parsing term %s (coefficient cannot be 0 modulo %d).\n", term, field_char); free(term); @@ -773,19 +945,18 @@ static int get_coefficient_ff_and_term_from_line(char *line, int32_t nterms, iv_tmp += field_char; //MS change int -> long int } gens->cfs[pos] = (int32_t)iv_tmp; - store_exponent(term, gens, pos*gens->nvars); + if (store_exponent(term, gens, pos*gens->nvars)) { + free(term); + return 1; + } for(int j = 1; j < nterms; j++){ get_term(line, &prev_pos, &term, &term_size); if (term != NULL) { - cf_tmp = (int64_t)strtol(term, NULL, 10); - - if (cf_tmp == 0) { - if (term[0] == '-') { - cf_tmp = -1; - } else { - cf_tmp = 1; - } + if (parse_ff_coeff_from_term(term, field_char, &cf_tmp)) { + free(term); + return 1; } + cf_tmp %= field_char; if (cf_tmp == 0) { fprintf(stderr, "Error when parsing term %s (coefficient cannot be 0 modulo %d).\n", term, field_char); @@ -796,7 +967,10 @@ static int get_coefficient_ff_and_term_from_line(char *line, int32_t nterms, cf_tmp += field_char; } gens->cfs[pos+j] = (int32_t)cf_tmp; - store_exponent(term, gens, (pos+j)*gens->nvars); + if (store_exponent(term, gens, (pos+j)*gens->nvars)) { + free(term); + return 1; + } } // store_exponent(term, basis, ht); } @@ -884,11 +1058,17 @@ static int get_coefficient_mpz_and_term_from_line(char *line, int32_t nterms, if(term != NULL){ beginning_strterm_to_mpz(term, gens->mpz_cfs[pos], gens->mpz_cfs[pos+1]); - store_exponent(term, gens, pos/2*gens->nvars); + if (store_exponent(term, gens, pos/2*gens->nvars)) { + free(term); + return 1; + } for(int j = 2; j < 2*nterms; j+=2){ get_term(line, &prev_pos, &term, &term_size); inner_strterm_to_mpz(term, gens->mpz_cfs[pos+j], gens->mpz_cfs[pos+j+1]); - store_exponent(term, gens, ((pos+j)/2)*gens->nvars); + if (store_exponent(term, gens, ((pos+j)/2)*gens->nvars)) { + free(term); + return 1; + } } free(term); return 0; diff --git a/src/msolve/lifting-gb.c b/src/msolve/lifting-gb.c index bda35a8b..74d4bb60 100644 --- a/src/msolve/lifting-gb.c +++ b/src/msolve/lifting-gb.c @@ -57,6 +57,7 @@ typedef struct { flint) */ uint32_t ld; /* number of polynomials */ int nv; /* number of variables */ + int lifted; /* 1 if coefficients were reconstructed over Q */ int32_t *ldm; /* lead monomials */ ht_t *bht; /* hash table */ hm_t **hm; /* hashed monomials representing exponents */ @@ -179,6 +180,7 @@ static inline void gb_modpoly_init(gb_modpoly_t modgbs, modgbs->cf_64 = (mp_limb_t *)calloc(alloc, sizeof(mp_limb_t)); modgbs->ld = ld; modgbs->nv = nv; + modgbs->lifted = 0; modgbs->modpolys = (modpolys_t *)malloc(sizeof(modpolys_t) * ld); modgbs->ldm = (int32_t *)calloc(nv*ld, sizeof(int32_t)); @@ -1621,6 +1623,7 @@ gb_modpoly_t *core_groebner_qq( free_rrec_data(recdata1); free_rrec_data(recdata2); + (*modgbsp)->lifted = 1; return modgbsp; } @@ -1850,7 +1853,16 @@ void print_msolve_gbtrace_qq(data_gens_ff_t *gens, } } fprintf(ofile, "#---\n"); - fprintf(ofile, "#field characteristic: 0\n"); + if ((*modgbsp)->lifted) { + fprintf(ofile, "#field characteristic: 0\n"); + } else { + uint32_t p = 0; + if ((*modgbsp)->nprimes > 0) { + p = (uint32_t)(*modgbsp)->primes[0]; + } + fprintf(ofile, "#field characteristic: %u\n", p); + fprintf(ofile, "#lifted to Q: no (first modular prime only)\n"); + } fprintf(ofile, "#variable order: "); for (int i = gens->elim; i < gens->nvars-1; ++i) { fprintf(ofile, "%s, ", gens->vnames[i]); diff --git a/src/msolve/main.c b/src/msolve/main.c index 1a90b2d2..898a154f 100644 --- a/src/msolve/main.c +++ b/src/msolve/main.c @@ -101,8 +101,12 @@ static inline void display_help(char *str){ fprintf(OUTSTREAM, "\nAdvanced options:\n\n"); display_option_help('F', "", "FILE", "File name encoding parametrizations in binary format.\n\n"); - display_option_help('g', "groebner-basis", "GB", "Prints reduced Groebner bases of input system for\n"); - display_option_help_noopt("first prime characteristic w.r.t. grevlex ordering.\n"); + display_option_help('g', "groebner-basis", "GB", "Prints reduced Groebner bases w.r.t. grevlex ordering.\n"); + display_option_help_noopt("Over a prime field the basis is exact for that field.\n"); + display_option_help_noopt("Over Q, -g 2 lifts coefficients to Q when possible.\n"); + display_option_help_noopt("If lifting is skipped (empty basis, or -g 1 leading\n"); + display_option_help_noopt("monomials only) the printed characteristic is the first\n"); + display_option_help_noopt("modular prime, not 0, and a '#lifted to Q: no' line is added.\n"); display_option_help_noopt("One element per line is printed, commata separated.\n"); display_option_help_noopt("0 - Nothing is printed. (default)\n"); display_option_help_noopt("1 - Leading ideal is printed.\n"); diff --git a/test/diff/diff_reject-malformed.sh b/test/diff/diff_reject-malformed.sh new file mode 100755 index 00000000..d5faa3b6 --- /dev/null +++ b/test/diff/diff_reject-malformed.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash + +file=reject-malformed + +source test/diff/diff_source.sh + +expect_fail() { + local in=$1 + local excode=$2 + $(pwd)/msolve -g 2 -t 1 --random-seed $seed \ + -f input_files/$in -o test/diff/$file.out + if [ $? -eq 0 ]; then + print_exit $excode + fi +} + +# Parentheses, post-monomial division, a leading "*", and a coefficient +# that does not fit in a machine word must all be rejected. +expect_fail bad-paren.ms 1 +expect_fail bad-div.ms 3 +expect_fail bad-star.ms 5 +expect_fail bad-overflow-ff.ms 7 + +# Documented leading-rational form must still parse. +$(pwd)/msolve -g 2 -t 1 --random-seed $seed \ + -f input_files/good-leading-rat.ms -o test/diff/$file.good.res +if [ $? -gt 0 ]; then + print_exit 9 +fi +diff_gb_output test/diff/$file.good.res output_files/good-leading-rat.g2.res +if [ $? -gt 0 ]; then + print_exit 10 +fi +rm -f test/diff/$file.out test/diff/$file.good.res + +normal_exit diff --git a/test/diff/diff_source-gb-elim-red.sh b/test/diff/diff_source-gb-elim-red.sh index ed446de9..9fb44605 100755 --- a/test/diff/diff_source-gb-elim-red.sh +++ b/test/diff/diff_source-gb-elim-red.sh @@ -5,7 +5,7 @@ if [ $? -gt 0 ]; then print_exit 5 fi -diff test/diff/$file.5.res output_files/$file.g2.e1.res +diff_gb_output test/diff/$file.5.res output_files/$file.g2.e1.res if [ $? -gt 0 ]; then print_exit 6 fi @@ -19,7 +19,7 @@ if [ $? -gt 0 ]; then print_exit 7 fi -diff test/diff/$file.7.res output_files/$file.g2.e2.res +diff_gb_output test/diff/$file.7.res output_files/$file.g2.e2.res if [ $? -gt 0 ]; then print_exit 8 fi @@ -33,7 +33,7 @@ if [ $? -gt 0 ]; then print_exit 25 fi -diff test/diff/$file.25.res output_files/$file.g2.e1.res +diff_gb_output test/diff/$file.25.res output_files/$file.g2.e1.res if [ $? -gt 0 ]; then print_exit 26 fi @@ -47,7 +47,7 @@ if [ $? -gt 0 ]; then print_exit 27 fi -diff test/diff/$file.27.res output_files/$file.g2.e2.res +diff_gb_output test/diff/$file.27.res output_files/$file.g2.e2.res if [ $? -gt 0 ]; then print_exit 28 fi @@ -61,7 +61,7 @@ if [ $? -gt 0 ]; then print_exit 45 fi -diff test/diff/$file.45.res output_files/$file.g2.e1.res +diff_gb_output test/diff/$file.45.res output_files/$file.g2.e1.res if [ $? -gt 0 ]; then print_exit 46 fi @@ -75,7 +75,7 @@ if [ $? -gt 0 ]; then print_exit 47 fi -diff test/diff/$file.47.res output_files/$file.g2.e2.res +diff_gb_output test/diff/$file.47.res output_files/$file.g2.e2.res if [ $? -gt 0 ]; then print_exit 48 fi @@ -89,7 +89,7 @@ if [ $? -gt 0 ]; then print_exit 65 fi -diff test/diff/$file.65.res output_files/$file.g2.e1.res +diff_gb_output test/diff/$file.65.res output_files/$file.g2.e1.res if [ $? -gt 0 ]; then print_exit 66 fi @@ -103,7 +103,7 @@ if [ $? -gt 0 ]; then print_exit 67 fi -diff test/diff/$file.67.res output_files/$file.g2.e2.res +diff_gb_output test/diff/$file.67.res output_files/$file.g2.e2.res if [ $? -gt 0 ]; then print_exit 68 fi diff --git a/test/diff/diff_source-gb-red.sh b/test/diff/diff_source-gb-red.sh index d155a902..0ed96aef 100755 --- a/test/diff/diff_source-gb-red.sh +++ b/test/diff/diff_source-gb-red.sh @@ -5,7 +5,7 @@ if [ $? -gt 0 ]; then print_exit 3 fi -diff test/diff/$file.3.res output_files/$file.g2.res +diff_gb_output test/diff/$file.3.res output_files/$file.g2.res if [ $? -gt 0 ]; then print_exit 4 fi @@ -19,7 +19,7 @@ if [ $? -gt 0 ]; then print_exit 23 fi -diff test/diff/$file.23.res output_files/$file.g2.res +diff_gb_output test/diff/$file.23.res output_files/$file.g2.res if [ $? -gt 0 ]; then print_exit 24 fi @@ -33,7 +33,7 @@ if [ $? -gt 0 ]; then print_exit 43 fi -diff test/diff/$file.43.res output_files/$file.g2.res +diff_gb_output test/diff/$file.43.res output_files/$file.g2.res if [ $? -gt 0 ]; then print_exit 44 fi @@ -47,7 +47,7 @@ if [ $? -gt 0 ]; then print_exit 63 fi -diff test/diff/$file.63.res output_files/$file.g2.res +diff_gb_output test/diff/$file.63.res output_files/$file.g2.res if [ $? -gt 0 ]; then print_exit 64 fi diff --git a/test/diff/diff_source.sh b/test/diff/diff_source.sh index 67b68814..a45fdba5 100755 --- a/test/diff/diff_source.sh +++ b/test/diff/diff_source.sh @@ -63,6 +63,21 @@ print_exit() { exit "$excode" } +# Unlifted char-0 Groebner output prints the first modular prime, which +# depends on --random-seed. Normalize that one header line before diff, +# but only when the "#lifted to Q:" marker is present so prime-field +# output is compared as-is. +diff_gb_output() { + local got=$1 + local expected=$2 + if grep -q '^#lifted to Q:' "$got"; then + sed -E 's/^#field characteristic: [1-9][0-9]*$/#field characteristic: /' "$got" \ + | diff - "$expected" + else + diff "$got" "$expected" + fi +} + # each diff_example.sh is built by running msolve on $file.ms # with options -L 0 -l 2 -t 1 # if the execution fails, print_exit 1