Conversation
Adds _gr_poly_resultant_multipoint / gr_poly_resultant_multipoint, a specialization of the resultant for bivariate polynomials, i.e. for the case where the coefficient ring is itself a polynomial ring. The coefficients of both inputs are evaluated at a geometric progression of points, the resultants of the resulting univariate polynomials are computed, and the result is interpolated back, using the geometric evaluation/interpolation added in flintlib#2449. Currently only a base ring of nmod (word-size prime modulus) is supported; fmpz, fmpq and fmpz_mod can follow later. _gr_poly_resultant dispatches to it whenever it applies, replacing the Sylvester determinant that was previously used for these rings. Follows the algorithm in PML's lzz_pXY, with the blockwise evaluation of Antoine Bak's fork so that the working space stays proportional to the input and output sizes: the points are processed in blocks sized to a fixed memory target, each block being reduced to the same progression 1, q, q^2, ... by a substitution x -> s x on the inputs, so that a single evaluation precomputation serves for all of them. Two further refinements over PML: the evaluation points are scaled by a random constant (the FLINT geometric progression always starts at 1, so retrying alone would not avoid a leading coefficient vanishing at 1), and only the leading coefficient of the first argument is required not to vanish at the evaluation points, the drop in degree of the specialisations of the second argument being corrected for afterwards. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Nice! Concerning the evaluation at |
|
(note that this adds a dependency that is not wanted in case you plan to introduce multithreading in the evaluation step) |
|
Overall, I made a few tests, and it's about the same performance, though extrapolation is slightly worse in theory (it takes 2L + 2B scalar muls vs. 2L + B for the current version) for L, the total length, and B, the batch length. It also loses the opportunity of parallelization and seems to also be slightly worse for cache / branching. It's still not bad and makes the code easier to read for sure. I'd also argue that since we want to port this code to |
|
Guess crediting Vincent and Antoine now breaks the CI... windows is interesting |
Adds _gr_poly_resultant_modular / gr_poly_resultant_modular, which computes
the resultant of bivariate polynomials over Z and Q by reducing modulo
word-size primes, calling the geometric multipoint algorithm for each of
them, and reconstructing by Chinese remaindering. _gr_poly_resultant
dispatches to it above a degree cutoff, below which the subresultant PRS
is still faster.
Over Q, denominators are cleared first, using the homogeneity
res(a f, b g) = a^deg_y(g) b^deg_y(f) res(f, g); the same identity divides
out the contents of the inputs in Z and in Z[x] beforehand.
The number of primes is bounded by
||res_y(f,g)||_oo <= (sum_i ||f_i||_1^2)^(deg_y(g)/2)
(sum_j ||g_j||_1^2)^(deg_y(f)/2)
which is the univariate Hadamard-type bound already used by
fmpz_poly_resultant_modular, applied to the maximum modulus of the
resultant on the unit circle. That bound is only a limit: primes are added
until the reconstruction stops changing, which is detected on a random
linear combination of the coefficients and then confirmed against a fresh
prime. This makes the cost track the actual size of the resultant, which
matters most when it is far below the bound, for instance when the inputs
have a common factor and the resultant vanishes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ok, yes (these are negligible compared to the
Giving it a second thought, instead of doing the extrapolations with the same offset one after another, we may as well use different offsets
Agreed.
Ok, thanks for having investigated the extrapolation version. I agree that consistency is a plus. There's no clear winner it seems, so, just pick the version you prefer! For the rest, I'll read the code more carefully in the next few days. |
|
Ok, then I'll keep the current version and finish my prototype of
|
|
I'm trying to use FFT-primes for multimodular @vneiger maybe we should make some kind of like "whatever multipoint" that depending on the primes either gives dft points (the right one) or geometric or in last resort subproduct tree. This could be usefull for some other things no ? More generally we could implement this algorithm for generic dense |
|
Noticed this is a comment:
Normally they do, but e.g. for |
When the nmod modulus satisfies the fft_small bounds and p - 1 is divisible by a large enough power of two, _gr_poly_resultant_multipoint evaluates the coefficients in y at roots of unity with one sd_fft transform each, instead of the Bluestein product used for a geometric progression. Measured with alternating rounds on the same prime and inputs, this is 1.10x to 1.26x faster overall, best at small degrees in y. A transform produces all its points at once, so this path keeps every value resident rather than working in blocks; it is used only when they fit a fixed memory budget, and the blocked geometric evaluation still handles everything else. The points come out in bit-reversed order, which is undone by gathering through that permutation, and the geometric interpolation is reused with the square root of the transform's root of unity as its ratio. Not used for the Z and Q path: fft_small accepts primes of at most 50 bits, and needing 1.24x more of them cancels the gain, measured at 0.94x to 1.04x end to end there, so those keep 62-bit primes and the geometric evaluation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nmod, nmod8 and nmod32 answered GR_METHOD_CTX_IS_INTEGRAL_DOMAIN and GR_METHOD_CTX_IS_FIELD with their primality predicate but left GR_METHOD_CTX_IS_UNIQUE_FACTORIZATION_DOMAIN unimplemented, so it fell back to the generic predicate and returned T_UNKNOWN. fmpz_mod, the same situation of a modulus that may or may not be prime, already answers all three with the same predicate; Z/nZ is a UFD exactly when n is prime, since otherwise it is not even a domain. Anything dispatching on this over nmod, or over a ring built on top of one, was taking a worse branch. For instance _gr_poly_resultant skipped the subresultant PRS for bivariate polynomials over nmod and fell through to the Sylvester determinant, which for degree 8 in y and 8 in x over Z/101Z is 23 times slower. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
With nmod rings now reporting whether they are a UFD, _gr_poly_resultant reaches the subresultant PRS for bivariate polynomials over nmod instead of falling through to the Sylvester determinant. The subresultant PRS is faster than the multipoint algorithm at the smallest sizes, up to about 1.7 times at degree 3 in y, so the cutoff that was measured against it applies again. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|
||
| /* a prime p = m 2^16 + 1, for which the evaluation can use a DFT */ | ||
| static ulong | ||
| _fft_prime(flint_rand_t state, int bits) |
There was a problem hiding this comment.
is there alrady a function that gives fft_primes ?
There was a problem hiding this comment.
You can have a look at next_fft_number and mpn_ctx_init from fft_small/mpn_mul.c.
The one you write here looks "dangerous" when you call it later in the file with bits potentially as low as 24. Then the first value of m in the loop just below could be negative... well, a ulong so it could be around 64 bits, which I guess is not the intended behaviour.
|
A few things: For the FFT-friendly idea: yes, but no. Basically, for
It seems like it wasn't reported as far as I understand; see a97b017 |
|
@fredrik-johansson For multiple algebraic attacks, I've used a parallelized version of the resultant algorithm (the inner loop in embarrassingly parallel). I'm not very familiar with how FLINT handles it; the documentation says, "Each version of FLINT brings new functions that are threaded by default," but also examples like "fmpz_mod_mat_mul_classical_threaded" specify it in the name. Should I make a function |
This could surely be useful for other things. But having something like this is not clear to me at all, at the moment. Subproduct trees should probably not be used much except when the reason is that others are not implement, right? And arithmetic progressions may be useful in some cases. DFT points are rarely possible except when we can choose the prime (but then there is no reason to consider other types of points, so the "whatever multipoint" is not that useful). And I'm not sure how easy it will be to make something both general and easy to use (without losing any efficiency)... Maybe this can wait until we have a couple of other relevant situations, to have a better view? |
If making it parallel by default does not impact the single-threaded performance, I guess that's ok to make it the default. |
|
|
||
| /* a prime p = m 2^16 + 1, for which the evaluation can use a DFT */ | ||
| static ulong | ||
| _fft_prime(flint_rand_t state, int bits) |
There was a problem hiding this comment.
You can have a look at next_fft_number and mpn_ctx_init from fft_small/mpn_mul.c.
The one you write here looks "dangerous" when you call it later in the file with bits potentially as low as 24. Then the first value of m in the loop just below could be negative... well, a ulong so it could be around 64 bits, which I guess is not the intended behaviour.
|
To make things deterministic, for methods via geometric progressions, this could rely on:
Similar things were considered in PML and for those two things for the moment we use:
(feel free to re-use these directly if they are helpful) |
…riginating from PML) + explicitate test
Co-authored-by: Vincent Neiger <vneiger@users.noreply.github.com>
|
Playing a bit with the profile and larger lengths, we can see the impact of the DFT prime case, for some ranges of parameters: (I took FFT primes containing So I guess it would make sense to consider (not necessarily within this PR) doing the multi-modular functions using primarily FFT primes as the moduli. |
Oh thats really nice, do we need to tune things to see that happen in practice or should this work as in your test with the curren code ? |
Co-authored-by: Vincent Neiger <vneiger@users.noreply.github.com>
Co-authored-by: Vincent Neiger <vneiger@users.noreply.github.com>
|
(I see lots of CI failures: I think you applied part of the change but not the main one on the function _sd_fft_get_nmod) |
You're too fast to answer x) Im doing it right now |
No tuning, I just took the code like it is and in the profile file:
|
Do you think we should integrate tests/profiles in this PR with larger values to better be able to see the FFT performance ? |
Yes, in the profiles having a few of these cases could be useful, to observe this range where DFT becomes useful. In the test, it's trickier, as this should remain very fast. If adding one case with relatively large depth (e.g. 18) can be done without big impact on the test time, that could be useful. And something else, looking at it I'm not sure that currently the threaded version is tested in the test files? |
|
Looks good to me, thanks. The last thing that would be needed before merging: that the test suite does cover the case with multiple threads. I think it is not the case currently (?). |
There is a profile for multithread but no "test", do you want to test correctness ? |
Yes, that's it, I guess it would be good that CI tests or make check includes some runs that use the multithreaded variant on a few threads? This would just mean to add a few |
Co-authored-by: Vincent Neiger <vneiger@users.noreply.github.com>
Co-authored-by: Vincent Neiger <vneiger@users.noreply.github.com>
|
I think the The So it would make sense to me to move the Then overload Then also wrap the bivariate algorithms in AI review comments:
|
This should be done, not going to lie, I checked how Opus handled this to be sure it didn't mess anything up in the process and it seem ok..
All the small thing should be settled, I think that Opus also removed some trailing whitespace in other files (probably used a handy command), if that's a problem, let me know. Also I think this might be a good idea for when we will want to support multipoint resultant over dense multivariate rings (not just bivariate), we can achieve |
|
Opus also refactored things into |
|
Went over the code again, quickly, and detected no obvious issue with the reorganization. I was just surprised by some things ending up in |
Well... The bpoly are in |
Ah, ok. I had seen this for the |
|
As far as I'm concerned, everything that I wanted to do is done and if everything is good this can be merged. My only concern is with the big restructure requested by @fredrik-johansson but if that is good then should be ok. I also checked performance on the restructured version and it does not make any difference. |
Following Issue #2605, this PR adds
_gr_poly_resultant_multipoint/gr_poly_resultant_multipoint, a specialization for the resultant for bivariate polynomials, i.e. for the case where the coefficient ring is itself a polynomial ring. The coefficients of both inputs are evaluated at a geometric progression of points, the resultants of the resulting univariate polynomials are computed, and the result is interpolated back, using the geometric evaluation/interpolation added in #2449.Currently only a base ring of nmod (word-size prime modulus) is supported;
fmpz,fmpqandfmpz_modcan follow later (see discussion in the related issue)._gr_poly_resultantdispatches to it whenever it applies, replacing the Sylvester determinant that was previously used for these rings.Follows the algorithm in PML's lzz_pXY, with the blockwise evaluation of @AntoineBak fork so that memory doesnt grow as n^3.
Two further refinements over PML: the evaluation points are scaled by a random constant (the FLINT geometric progression always starts at 1, so retrying alone would not avoid a leading coefficient vanishing at 1), and only the leading coefficient of the first argument is required not to vanish at the evaluation points.
The timing is unsurprisingly much better than before (on my small Zen 3 laptop)
This PR is partially made using Claude Opus 5.