Skip to content

Speed up the lifting of rational parametrizations over QQ - #384

Open
mohabsafey wants to merge 9 commits into
algebraic-solving:masterfrom
mohabsafey:lifting-param
Open

mohabsafey wants to merge 9 commits into
algebraic-solving:masterfrom
mohabsafey:lifting-param

Conversation

@mohabsafey

Copy link
Copy Markdown
Contributor

This PR reworks the multi-modular lifting of rational parametrizations over QQ (-P 1/-P 2), along the lines of what #383 did for Groebner bases. The goals are fewer primes, less time in CRT and rational reconstruction, and a number of primes that no longer depends on timings. Outputs are unchanged on every system tested.

Changes

  • FLINT rational reconstruction. Linear forms, multiplication-matrix witnesses, trace and determinant, the eliminating polynomial and the coordinates are now reconstructed with FLINT's half-gcd based fmpq_reconstruct_fmpz_2. When 2ND < m does not hold, the code falls back to ratrecon/ratreconwden. The wrappers move from lifting-gb.c to lifting.c, so both liftings share them.
  • Reproducible reconstruction period. Reconstruction used to be attempted every nbdoit iterations, and nbdoit doubled whenever reconstruction took more than 20% of the modular time, so the number of primes depended on the machine load. As for Groebner bases, the period is now rr primes, with rr the largest power of 2 ≤ nprimes/128, and reconstruction is attempted once per iteration.
  • CRT applied only when reconstructing. Before, every coefficient of the parametrization was lifted by incremental CRT at each prime, which is quadratic in the number of primes. Now the modular images are stored in chunks of 64 primes, and FLINT's multi-CRT is applied to one coefficient at a time, right before that coefficient is reconstructed. No CRT is done past the first failed reconstruction. The trace and determinant witnesses still use incremental CRT.
  • 16-bit margin. The reconstruction bounds are computed from modulus / 2^RR_MARGIN, as for Groebner bases, which makes spurious reconstructions unlikely.
  • Bounds from the guessed denominator. Each coefficient first tries the fixed bounds used so far, then bounds set from the denominator accumulated so far. Previously each fixed bound had to work for a whole coordinate. The fixed bounds stay first because, on eco, noon, henrion and cyclic, the numerators are much larger than the denominators.
  • Common-denominator witnesses (LLL). The coefficients of the monic eliminating polynomial share a denominator. On dense random, MinRank and critical-point systems this denominator is about as large as the numerators, so a single witness coefficient needs about twice their size in bits. Four witness coefficients are now also reconstructed simultaneously, as a short vector (d, n_1, .., n_4) found with fmpz_lll. This needs about bits(n) + bits(d)/4 bits of modulus, and d then serves as a guess for the eliminating polynomial. LLL is tried only while no single witness is known.
  • More skewed bound for the eliminating polynomial. D = M^(1/64) is tried before D = M^(1/16). Coefficients are reconstructed from the highest degree down while carrying the lcm of the denominators already found, so most of them are bounded by their numerators.
  • Leaks and dead code.
    • A leak in mulmat_reconstruct (-L 1) is fixed.
    • Twelve unused static functions are removed from lifting.c.
    • Several constant-size setup leaks reported by AddressSanitizer in msolve_trace_qq are fixed. AddressSanitizer is now clean on kat7, eco8, cyclic5 and random2_7_7 with 1 and 2 threads.

Each commit message gives the measurements for that step.

Benchmarks

The setup is -P 2 -t 1, release v0.11.0 against this branch. Systems where v0.11.0 takes under 1 s are excluded, and so are systems where this branch takes over 40 min. Seeds 1–5 are used when the branch takes under 300 s, seeds 1–2 otherwise. Times are means over the seeds.

Outputs are identical on all 43 systems. The geometric-mean speedup is 1.48, and the total time drops from 18589 s to 12037 s (1.54).

The number of primes falls by 33–44% on the dense random, MinRank, cp_d and crit families. It falls by 1–6% on katsura, eco, noon, cyclic, henrion, chandra and sot1. KLYZ22 needs 19% fewer primes, zimmermann 48% fewer and phuoc1 29% fewer. bruno needs one more prime because of the 16-bit margin.

system seeds primes v0.11.0 → branch time v0.11.0 / branch (s) speedup MB v0.11.0 → branch
minrank_d_2_p_3_q_6_r_3 5 259 → 154 1.06 / 0.55 1.91 16 → 17
random3_5_5 5 207 → 135 1.01 / 0.56 1.80 16 → 17
random2_7_7 5 579 → 323 1.55 / 0.68 2.26 16 → 16
cp_d_3_n_5_p_2 5 331 → 212 2.10 / 1.23 1.71 18 → 19
minrank_d_3_p_2_q_5_r_2 5 279–291 → 184 2.57 / 1.55 1.66 21 → 21
kat10 5 68–69 → 67–68 2.21 / 2.09 1.05 27 → 28
cp_d_4_n_4_p_3 5 331–339 → 222 4.58 / 2.87 1.60 28 → 27
henrion6 5 177 → 172–173 3.66 / 3.40 1.08 32 → 30
eco11 5 56–57 → 55–56 4.25 / 4.11 1.04 30 → 28
bruno 5 59 → 60 4.64 / 4.66 0.99 42 → 38
noon6 5 177–179 → 172–173 6.16 / 5.78 1.07 37 → 36
random2_8_8 5 1219–1283 → 727–763 12.23 / 5.78 2.12 30 → 27
cyclic7 5 195 → 187–188 7.82 / 7.22 1.08 42 → 41
minrank_d_2_p_2_q_7_r_2 5 707–739 → 451 15.46 / 8.47 1.83 33 → 30
vor1 5 135–139 → 132–133 10.36 / 9.96 1.04 26 → 25
crit_deg_3_n_6_p_6 5 771–787 → 495 34.69 / 19.71 1.76 50 → 44
random3_6_6 5 771 → 495 34.49 / 20.45 1.69 50 → 44
minrank_d_2_p_3_q_7_r_3 5 859–867 → 547 35.71 / 20.85 1.71 49 → 44
minrank_d_2_p_3_q_4_r_2 5 1091 → 631 44.06 / 23.35 1.89 52 → 44
kat11 5 153–154 → 149–150 27.03 / 25.11 1.08 62 → 60
chandra-10 5 1251 → 1187–1195 32.51 / 27.42 1.19 71 → 71
cp_d_3_n_6_p_2 5 1059–1091 → 659 52.53 / 29.26 1.80 58 → 50
minrank_d_3_p_3_q_6_r_3 5 843–851 → 547 80.93 / 47.99 1.69 74 → 65
KLYZ22 5 277–279 → 225–227 60.20 / 49.76 1.21 39 → 39
random2_9_9 5 2819–3075 → 1651–1731 119.85 / 53.94 2.22 102 → 80
eco12 5 119–120 → 116–117 62.02 / 58.57 1.06 414 → 75
crit_d_4_n6 5 3203 → 1907 168.03 / 79.66 2.11 111 → 90
zimmermann 5 2023–2035 → 1051 325.80 / 138.31 2.35 36 → 31
minrank_d_3_p_2_q_6_r_2 5 1283–1299 → 839 247.72 / 152.40 1.63 116 → 100
minrank_d_2_p_2_q_8_r_2 5 1859–1875 → 1179–1235 271.84 / 155.17 1.75 128 → 110
chandra-11 5 2451–2467 → 2371–2387 291.76 / 238.88 1.22 267 → 261
sot1 2 484–485 → 455–457 343.67 / 316.75 1.08 64 → 64
kat12 2 322–323 → 313–315 376.77 / 348.59 1.08 201 → 195
noon7 2 791–795 → 767 421.87 / 401.14 1.05 180 → 169
phuoc1 2 668–670 → 473 652.31 / 474.20 1.38 236 → 118
random2_10_10 2 6147 → 3763 1221.44 / 607.10 2.01 409 → 325
eco13 2 237–238 → 230–231 780.10 / 734.19 1.06 4710 → 259
cp_d_3_n_7_p_2 2 2995–3011 → 1907 1392.30 / 888.67 1.57 302 → 245
minrank_d_2_p_3_q_8_r_3 2 2883 → 1819 1609.22 / 980.68 1.65 289 → 243
cp_d_3_n_7_p_7 2 2755 → 1819 1976.11 / 1169.12 1.69 355 → 299
random3_7_7 2 2739 → 1819 1976.59 / 1226.95 1.61 352 → 297
crit_deg_3_n_7_p_7 2 2755 → 1819 2016.32 / 1295.55 1.56 355 → 298
cp_d_4_n_5_p_3 2 2659–2667 → 1731 3853.51 / 2394.67 1.61 452 → 390

The v0.11.0 baseline does not include #383, which is now in master. The number of primes is set by the parametrization lifting changed here. The timings and peak memory also reflect the F4 changes of #383. The large memory drops on eco12 and eco13 (414 → 75 MB, 4710 → 259 MB) most likely come from #383 ("Fix per-prime leaks in F4 and avoid touching unused hash table memory").

Testing

  • make check passes (71/71).
  • Outputs are identical to v0.11.0 on the 43 systems above, for every seed.
  • AddressSanitizer reports no memory errors and no leaks with -P 2 on kat7, eco8, cyclic5 and random2_7_7, with 1 and 2 threads, and on eco10 and kat9 with 2 threads.

Parts of this PR were written with the assistance of generative AI (Claude Code). All changes were reviewed and benchmarked by the author.

Co-authored-by: Claude Opus 5.5 noreply@anthropic.com

🤖 Generated with Claude Code

mohabsafey and others added 9 commits October 28, 2026 17:54
Rational reconstruction in the lifting of rational parametrizations
over QQ (linear forms, multiplication matrix witnesses, trace and
determinant, eliminating polynomial and coordinates) now goes through
FLINT's half-gcd based fmpq_reconstruct_fmpz_2 instead of the quadratic
Euclidean algorithm, falling back to ratrecon/ratreconwden when the
bounds do not satisfy 2ND < m.

The wrappers introduced for the lifting of Groebner bases are moved from
lifting-gb.c to lifting.c so that both liftings share them.

Outputs are unchanged; the time spent in CRT and rational reconstruction
decreases by 25 to 40% (kat11: 1.22s to 0.70s).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When lifting rational parametrizations over QQ, rational reconstruction
was called every nbdoit iterations, nbdoit being doubled whenever the
reconstruction took more than 20% of the modular computations; the
number of primes used thus depended on timings and on the machine load.
As for the lifting of Groebner bases, the period is now rr primes, rr
being the largest power of 2 <= nprimes/128, i.e. max(1, rr/nthrds)
iterations, and the reconstruction is attempted once per iteration,
after the last good prime, instead of after each prime of the
iteration.

Outputs are unchanged and the number of primes is now reproducible.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When lifting rational parametrizations over QQ, every coefficient of the
eliminating polynomial and of the coordinates was lifted by incremental
CRT at each prime, which is quadratic in the number of primes, although
these CRT images are only used once the trace and determinant witnesses
are reconstructed and verified.

The modular images of the parametrization are now stored for each prime
(in chunks of 64 primes which are never moved), and FLINT's multi-CRT is
applied to them only when a rational reconstruction is attempted, one
coefficient at a time, right before this coefficient is reconstructed;
no CRT is thus applied beyond the first failed reconstruction, and no
array of CRT images is kept. The coordinates are lifted one at a time,
trying in turn the three bounds which were tried before on all of them.
The witnesses are still lifted by incremental CRT.

Outputs and numbers of primes are unchanged on 38 benchmark systems
(eco, kat, noon, cyclic, henrion, chandra, random, minrank, cp_d, crit
and others). The time spent in CRT and rational reconstruction
decreases by up to 4.2x (random2_9_9: 20.4s to 4.8s; chandra-11: 50.7s
to 14.5s), the total time by up to 17% with one thread and by up to 32%
with four, and peak memory is unchanged or slightly lower.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
As for the lifting of Groebner bases, the rational reconstruction
bounds used when lifting parametrizations over QQ (witnesses,
eliminating polynomial and coordinates) are now computed from
modulus / 2^RR_MARGIN instead of the modulus, so that 2ND <= modulus /
2^16. This makes spurious reconstructions unlikely: on cyclic7, two
spurious reconstructions of the eliminating polynomial, each rejected
by the next prime, are avoided.

Outputs are unchanged on 35 benchmark systems; the number of primes
increases by 0 to 4 on all of them but random2_9_9 (2627 to 2643, the
reconstruction period being 16 primes at that point) and cyclic7 (194
to 193).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When lifting parametrizations over QQ, each coefficient of the
eliminating polynomial and of the coordinates is now reconstructed by
trying in turn the fixed bounds used so far (D = M^(1/16) for the
eliminating polynomial; D = M^(1/32), balanced bounds and N = M^(1/16)
for the coordinates, with M = modulus / 2^RR_MARGIN), then the bounds
set from the denominator accumulated so far, g: D = sqrt(M / g) and
N = M / D, as for the lifting of Groebner bases. CRT is applied only
once per coefficient. Previously, each fixed bound had to succeed on a
whole coordinate.

Using only the bounds set from the guessed denominator increases the
number of primes by up to 1.9x on several families (eco, noon,
henrion, cyclic), whose numerators are much larger than their
denominators; the fixed bounds are thus tried first.

Outputs are unchanged on 35 benchmark systems and the number of primes
never increases. It decreases on KLYZ22 (279 to 225, 18% faster),
whose denominators are large, and on the smallest Katsura systems,
where some coordinates were lifted later than the eliminating
polynomial because no single fixed bound suited all their
coefficients (kat8: 22 to 16, kat9: 40 to 31, about 20% faster).
From kat10 to kat12 all coordinates are lifted together with the
eliminating polynomial, which is the bottleneck, so the number of
primes and timings are unchanged (kat12: 323 primes, 325s vs 321s);
the same holds for eco8 to eco12, where the fixed bounds always
succeed and the fallback is never used.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mulmat_reconstruct allocated ncols integers for the row denominators
but cleared only nrows of them, leaking ncols - nrows integers at each
call when multiplication matrices are lifted (-L 1).

Also remove twelve static functions of lifting.c which are no longer
called: the former lifting of multiplication matrices
(crt_mpz_matfglm_*, mpz_matfglm_*, mpq_matfglm_*, crt_lift_mat,
rat_recon_matfglm, check_matrix_and_linear_forms), replaced by
mulmat_reconstruct, as well as old_rat_recon_trace_det and
check_linear_forms. The corresponding types are kept in msolve-data.h,
which is installed.

Outputs are unchanged, with and without -L 1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nator

The coefficients of the monic eliminating polynomial share a common
denominator (the leading coefficient of the primitive integer
polynomial). On dense random, MinRank and critical point systems, this
denominator is about as large as the numerators, so reconstructing a
single witness coefficient (trace or determinant) needs about twice
the number of bits of these numerators, and the eliminating polynomial
cannot be attempted before.

Four witness coefficients (the trace and determinant ones, and the
first nonzero ones from degrees nsols/2 and nsols/4) are now also
reconstructed simultaneously: a short vector (d, n_1, .., n_4) with
n_j = d u_j mod m is computed by LLL (FLINT's fmpz_lll), which needs
about bits(n) + bits(d) / 4 bits of modulus. The candidate is accepted
when its entries are below 2^(4 bits(m) / 5 - RR_MARGIN) and when it
matches the images of the next prime; d is then used as a guess for
the eliminating polynomial, which can then be reconstructed with its
usual bounds. As LLL is more expensive than rational reconstruction, it
is tried only while no single witness is known, at 16 primes and then
each time the number of primes grows by 5%.

Outputs are unchanged on 35 benchmark systems. On the 18 dense random,
MinRank, critical point (cp_d, crit) systems, the number of primes
decreases by 32 to 38%, and the total time by 20 to 39% on those taking
more than 0.3s (geometric mean speedup 1.44; random2_9_9: 2643 to 1651
primes, 81.7s to 56.1s; random3_6_6: 763 to 495 primes, 33.5s to
21.7s). On the other families the number of primes is unchanged and
timings are within noise (+-3%), the LLL attempts made before a single
witness is known costing a few hundredths of a second.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The eliminating polynomial is reconstructed from its highest degree down,
carrying the lcm of the denominators already found, so most coefficients
only bring a few new denominator bits and are bound by the size of their
numerators. The bound D = M^(1/64) is now tried before D = M^(1/16), which
lowers the number of primes needed by 2 to 5% on katsura and eco systems
(kat12: 323 -> 313, eco13: 238 -> 231) with identical outputs. Random and
minrank systems are unaffected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AddressSanitizer reports a few constant-size leaks in the multi-modular
setup, independent of the number of primes. Free invalid_gens once the
input is imported, free the per-thread copies of the linear-form data
and of the current leading monomials in
free_msolve_trace_qq_initial_data (zero-initializing the pointer
arrays), free the monomial basis when initial_modular_step fails, and
free the FGLM data of the first call before the second call in the
non-shape-position case. Also free the parametrization on the failure
paths of nmod_fglm_compute_trace_data, drop a name buffer that was
immediately overwritten when adding a linear form, and count the terms
of the added linear form in gens->nterms so their coefficients are
cleared.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant