From fec8a10f2659dfb9733a5735fd7237d8231b3ab0 Mon Sep 17 00:00:00 2001 From: Robert Dougherty-Bliss Date: Thu, 20 Mar 2025 15:33:39 -0400 Subject: [PATCH 1/6] Add resultant methods to fmpz_poly and fmpq_poly --- src/flint/types/fmpq_poly.pyx | 26 +++++++++++++++++ src/flint/types/fmpz_poly.pyx | 54 +++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/src/flint/types/fmpq_poly.pyx b/src/flint/types/fmpq_poly.pyx index 4262dcf7..cd59ec01 100644 --- a/src/flint/types/fmpq_poly.pyx +++ b/src/flint/types/fmpq_poly.pyx @@ -415,6 +415,32 @@ cdef class fmpq_poly(flint_poly): fmpq_poly_gcd(res.val, self.val, (other).val) return res + def resultant(self, other): + """ + Returns the resultant of *self* and *other*. + + >>> A = fmpq_poly([1, 0, -1]); B = fmpq_poly([1, -1]) + >>> A.resultant(B) + 0 + >>> C = fmpq_poly([1, 0, 0, 0, 0, -1, 1]) + >>> D = fmpq_poly([1, 0, 0, -1, 0, 0, 1]) + >>> C.resultant(D) + 3 + >>> f = fmpq_poly([1, -1] + [0] * 98 + [1]) + >>> g = fmpq_poly([1] + [0] * 50 + [-1] + [0] * 48 + [1]) + >>> f.resultant(g) + 1125899906842623 + + """ + cdef fmpq res + other = any_as_fmpq_poly(other) + if other is NotImplemented: + raise TypeError("cannot convert input to fmpq_poly") + + res = fmpq.__new__(fmpq) + fmpq_poly_resultant(res.val, self.val, (other).val) + return res + def xgcd(self, other): cdef fmpq_poly res1, res2, res3 other = any_as_fmpq_poly(other) diff --git a/src/flint/types/fmpz_poly.pyx b/src/flint/types/fmpz_poly.pyx index 1c85163c..c8bdb557 100644 --- a/src/flint/types/fmpz_poly.pyx +++ b/src/flint/types/fmpz_poly.pyx @@ -397,6 +397,60 @@ cdef class fmpz_poly(flint_poly): fmpz_poly_gcd(res.val, self.val, (other).val) return res + def resultant(self, other): + """ + Returns the resultant of *self* and *other*. + + >>> A = fmpz_poly([1, 0, -1]); B = fmpz_poly([1, -1]) + >>> A.resultant(B) + 0 + >>> C = fmpz_poly([1, 0, 0, 0, 0, -1, 1]) + >>> D = fmpz_poly([1, 0, 0, -1, 0, 0, 1]) + >>> C.resultant(D) + 3 + >>> f = fmpz_poly([1, -1] + [0] * 98 + [1]) + >>> g = fmpz_poly([1] + [0] * 50 + [-1] + [0] * 48 + [1]) + >>> f.resultant(g) + 1125899906842623 + + """ + cdef fmpz res + other = any_as_fmpz_poly(other) + if other is NotImplemented: + raise TypeError("cannot convert input to fmpz_poly") + + res = fmpz.__new__(fmpz) + fmpz_poly_resultant(res.val, self.val, (other).val) + return res + + def resultant_modular(self, other): + """ + Returns the resultant of *self* and *other* using Collins' 1971 modular + algorithm. + + >>> A = fmpz_poly([1, 0, -1]); B = fmpz_poly([1, -1]) + >>> A.resultant_modular(B) + 0 + >>> C = fmpz_poly([1, 0, 0, 0, 0, -1, 1]) + >>> D = fmpz_poly([1, 0, 0, -1, 0, 0, 1]) + >>> C.resultant_modular(D) + 3 + >>> f = fmpz_poly([1, -1] + [0] * 98 + [1]) + >>> g = fmpz_poly([1] + [0] * 50 + [-1] + [0] * 48 + [1]) + >>> f.resultant_modular(g) + 1125899906842623 + + """ + cdef fmpz res + other = any_as_fmpz_poly(other) + if other is NotImplemented: + raise TypeError("cannot convert input to fmpz_poly") + + res = fmpz.__new__(fmpz) + fmpz_poly_resultant_modular(res.val, self.val, (other).val) + return res + + def factor(self): """ Factors self into irreducible factors, returning a tuple From 86ffb151a4e3c0d80992e9850ffd4c3d22783063 Mon Sep 17 00:00:00 2001 From: Robert Dougherty-Bliss Date: Fri, 21 Mar 2025 00:40:09 -0400 Subject: [PATCH 2/6] Add resultant tests and nmod_poly implementation --- src/flint/test/test_all.py | 31 ++++++++++++++++++++++++++++++- src/flint/types/fmpz_poly.pyx | 1 - src/flint/types/nmod_poly.pyx | 20 ++++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/flint/test/test_all.py b/src/flint/test/test_all.py index a887f73f..e6a57c75 100644 --- a/src/flint/test/test_all.py +++ b/src/flint/test/test_all.py @@ -2834,6 +2834,36 @@ def setbad(obj, i, val): if type(p) == flint.fq_default_poly: assert raises(lambda: p.integral(), NotImplementedError) + # resultant checks. + + if is_field and characteristic == 0: + # Check that the resultant of two cyclotomic polynomials is right. + # See Dresden's 2012 "Resultants of Cyclotomic Polynomials" + for m in range(1, 50): + for n in range(m + 1, 50): + a = flint.fmpz_poly.cyclotomic(m) + b = flint.fmpz_poly.cyclotomic(n) + q, r = divmod(flint.fmpz(n), flint.fmpz(m)) + fs = q.factor() + if r != 0 or len(fs) > 1: + assert a.resultant(b) == 1 + else: + prime = fs[0][0] + tot = flint.fmpz(m).euler_phi() + assert a.resultant(b) == prime**tot + + x = P([0, 1]) + # Flint does not implement resultants over GF(q) for nonprime q, so we + # there's nothing for us to check. + if composite_characteristic or type(x) == flint.fq_default_poly: + pass + else: + assert x.resultant(x) == 0 + assert x.resultant(x**2 + x - x) == 0 + + for k in range(-10, 10): + assert x.resultant(x + S(k)) == S(k) + assert x.resultant(x**10 - x**5 + 1) == S(1) def _all_mpolys(): return [ @@ -2869,7 +2899,6 @@ def _all_mpolys(): ), ] - def test_mpolys(): for P, get_context, S, is_field, characteristic in _all_mpolys(): diff --git a/src/flint/types/fmpz_poly.pyx b/src/flint/types/fmpz_poly.pyx index c8bdb557..73e7301d 100644 --- a/src/flint/types/fmpz_poly.pyx +++ b/src/flint/types/fmpz_poly.pyx @@ -450,7 +450,6 @@ cdef class fmpz_poly(flint_poly): fmpz_poly_resultant_modular(res.val, self.val, (other).val) return res - def factor(self): """ Factors self into irreducible factors, returning a tuple diff --git a/src/flint/types/nmod_poly.pyx b/src/flint/types/nmod_poly.pyx index ea46a944..e304463c 100644 --- a/src/flint/types/nmod_poly.pyx +++ b/src/flint/types/nmod_poly.pyx @@ -621,6 +621,26 @@ cdef class nmod_poly(flint_poly): nmod_poly_gcd(res.val, self.val, (other).val) return res + def resultant(self, other): + """ + Returns the resultant of *self* and *other*. + + >>> f = nmod_poly([1, 2, 3], 3) + >>> g = nmod_poly([1, 0, 1], 3) + >>> f.resultant(f) + 0 + >>> f.resultant(g) + 2 + + """ + cdef ulong res + other = any_as_nmod_poly(other, (self).val.mod) + if other is NotImplemented: + raise TypeError("cannot convert input to nmod_poly") + + res = nmod_poly_resultant(self.val, (other).val) + return res + def xgcd(self, other): cdef nmod_poly res1, res2, res3 other = any_as_nmod_poly(other, (self).val.mod) From 658e502a8b6e9bda588b3f7f0f6f06453405dc63 Mon Sep 17 00:00:00 2001 From: Robert Dougherty-Bliss Date: Fri, 21 Mar 2025 12:12:00 -0400 Subject: [PATCH 3/6] Check for composite moduli in resultants --- src/flint/test/test_all.py | 16 ++++++++++++---- src/flint/types/fmpz_mod_poly.pyx | 3 +++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/flint/test/test_all.py b/src/flint/test/test_all.py index e6a57c75..52c0965a 100644 --- a/src/flint/test/test_all.py +++ b/src/flint/test/test_all.py @@ -2853,17 +2853,25 @@ def setbad(obj, i, val): assert a.resultant(b) == prime**tot x = P([0, 1]) - # Flint does not implement resultants over GF(q) for nonprime q, so we - # there's nothing for us to check. - if composite_characteristic or type(x) == flint.fq_default_poly: + + if composite_characteristic and type(x) == flint.fmpz_mod_poly: + # Flint crashes in this case, even though the resultant could be + # computed. + divisor = characteristic.factor()[0][0] + if type(x) == flint.fmpz_mod_poly: + assert raises(lambda: x.resultant(x + divisor), ValueError) + elif type(x) == flint.fq_default_poly: + # Flint does not implement resultants over GF(q) for nonprime q, so + # there's nothing for us to check. pass else: assert x.resultant(x) == 0 assert x.resultant(x**2 + x - x) == 0 + assert x.resultant(x**10 - x**5 + 1) == S(1) + assert (x - 1).resultant(x**5 + 1) == S(2) for k in range(-10, 10): assert x.resultant(x + S(k)) == S(k) - assert x.resultant(x**10 - x**5 + 1) == S(1) def _all_mpolys(): return [ diff --git a/src/flint/types/fmpz_mod_poly.pyx b/src/flint/types/fmpz_mod_poly.pyx index 3ab4f2bb..d854dc26 100644 --- a/src/flint/types/fmpz_mod_poly.pyx +++ b/src/flint/types/fmpz_mod_poly.pyx @@ -1465,6 +1465,9 @@ cdef class fmpz_mod_poly(flint_poly): """ cdef fmpz_mod res + if not self.ctx.mod.is_prime(): + raise ValueError("cannot compute fmpz_mod_poly resultants with composite moduli") + other = self.ctx.any_as_fmpz_mod_poly(other) if other is NotImplemented: raise TypeError(f"Cannot interpret {other} as a polynomial") From 8bac286b896bb0310f9a89f6db8369a7f9d0769e Mon Sep 17 00:00:00 2001 From: Robert Dougherty-Bliss Date: Mon, 31 Mar 2025 15:29:41 -0400 Subject: [PATCH 4/6] Refactor resultant tests --- src/flint/test/test_all.py | 44 +++++++++++++++++------------------ src/flint/types/nmod_poly.pyx | 5 ++++ 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/src/flint/test/test_all.py b/src/flint/test/test_all.py index 52c0965a..3b74abd4 100644 --- a/src/flint/test/test_all.py +++ b/src/flint/test/test_all.py @@ -2835,31 +2835,13 @@ def setbad(obj, i, val): assert raises(lambda: p.integral(), NotImplementedError) # resultant checks. - - if is_field and characteristic == 0: - # Check that the resultant of two cyclotomic polynomials is right. - # See Dresden's 2012 "Resultants of Cyclotomic Polynomials" - for m in range(1, 50): - for n in range(m + 1, 50): - a = flint.fmpz_poly.cyclotomic(m) - b = flint.fmpz_poly.cyclotomic(n) - q, r = divmod(flint.fmpz(n), flint.fmpz(m)) - fs = q.factor() - if r != 0 or len(fs) > 1: - assert a.resultant(b) == 1 - else: - prime = fs[0][0] - tot = flint.fmpz(m).euler_phi() - assert a.resultant(b) == prime**tot - x = P([0, 1]) - if composite_characteristic and type(x) == flint.fmpz_mod_poly: - # Flint crashes in this case, even though the resultant could be - # computed. + if composite_characteristic and type(x) in [flint.fmpz_mod_poly, flint.nmod_poly]: + # Flint sometimes crashes in this case, even though the resultant + # could be computed. divisor = characteristic.factor()[0][0] - if type(x) == flint.fmpz_mod_poly: - assert raises(lambda: x.resultant(x + divisor), ValueError) + assert raises(lambda: x.resultant(x + divisor), ValueError) elif type(x) == flint.fq_default_poly: # Flint does not implement resultants over GF(q) for nonprime q, so # there's nothing for us to check. @@ -2873,6 +2855,22 @@ def setbad(obj, i, val): for k in range(-10, 10): assert x.resultant(x + S(k)) == S(k) +def test_poly_resultants(): + # Check that the resultant of two cyclotomic polynomials is right. + # See Dresden's 2012 "Resultants of Cyclotomic Polynomials" + for m in range(1, 50): + for n in range(m + 1, 50): + a = flint.fmpz_poly.cyclotomic(m) + b = flint.fmpz_poly.cyclotomic(n) + q, r = divmod(flint.fmpz(n), flint.fmpz(m)) + fs = q.factor() + if r != 0 or len(fs) > 1: + assert a.resultant(b) == 1 + else: + prime = fs[0][0] + tot = flint.fmpz(m).euler_phi() + assert a.resultant(b) == prime**tot + def _all_mpolys(): return [ (flint.fmpz_mpoly, flint.fmpz_mpoly_ctx.get, flint.fmpz, False, flint.fmpz(0)), @@ -4869,6 +4867,8 @@ def test_all_tests(): test_polys, test_mpolys, + test_poly_resultants, + test_fmpz_mpoly_vec, test_matrices_eq, diff --git a/src/flint/types/nmod_poly.pyx b/src/flint/types/nmod_poly.pyx index e304463c..9eb0be5c 100644 --- a/src/flint/types/nmod_poly.pyx +++ b/src/flint/types/nmod_poly.pyx @@ -634,6 +634,11 @@ cdef class nmod_poly(flint_poly): """ cdef ulong res + + mod = any_as_fmpz(self.val.mod.n) + if not mod.is_prime(): + raise ValueError("cannot compute nmod_poly resultants with composite moduli") + other = any_as_nmod_poly(other, (self).val.mod) if other is NotImplemented: raise TypeError("cannot convert input to nmod_poly") From 296c96a88d08478c716644cda31fc1a015d1bfd7 Mon Sep 17 00:00:00 2001 From: Oscar Benjamin Date: Wed, 9 Apr 2025 12:27:20 +0100 Subject: [PATCH 5/6] Remove fmpz_poly.resultant_modular --- src/flint/types/fmpz_poly.pyx | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/src/flint/types/fmpz_poly.pyx b/src/flint/types/fmpz_poly.pyx index 73e7301d..241eaa44 100644 --- a/src/flint/types/fmpz_poly.pyx +++ b/src/flint/types/fmpz_poly.pyx @@ -423,33 +423,6 @@ cdef class fmpz_poly(flint_poly): fmpz_poly_resultant(res.val, self.val, (other).val) return res - def resultant_modular(self, other): - """ - Returns the resultant of *self* and *other* using Collins' 1971 modular - algorithm. - - >>> A = fmpz_poly([1, 0, -1]); B = fmpz_poly([1, -1]) - >>> A.resultant_modular(B) - 0 - >>> C = fmpz_poly([1, 0, 0, 0, 0, -1, 1]) - >>> D = fmpz_poly([1, 0, 0, -1, 0, 0, 1]) - >>> C.resultant_modular(D) - 3 - >>> f = fmpz_poly([1, -1] + [0] * 98 + [1]) - >>> g = fmpz_poly([1] + [0] * 50 + [-1] + [0] * 48 + [1]) - >>> f.resultant_modular(g) - 1125899906842623 - - """ - cdef fmpz res - other = any_as_fmpz_poly(other) - if other is NotImplemented: - raise TypeError("cannot convert input to fmpz_poly") - - res = fmpz.__new__(fmpz) - fmpz_poly_resultant_modular(res.val, self.val, (other).val) - return res - def factor(self): """ Factors self into irreducible factors, returning a tuple From 4870eec9d27ff50bbd27efd7fc2f058e862df242 Mon Sep 17 00:00:00 2001 From: Oscar Benjamin Date: Wed, 9 Apr 2025 12:59:07 +0100 Subject: [PATCH 6/6] Add CHANGELOG for gh-274 --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index f07df79c..a9f591e3 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,18 @@ CHANGELOG Next release (0.8.0)... ----------------------- +Contributors + +- Robert Dougherty-Bliss (RDB) + +Changes + +- [gh-274](https://github.com/flintlib/python-flint/pull/274), + Add resultant methods to `fmpz_poly`, `fmpq_poly` and + `nmod_poly`. Now all univariate and polynomial types have the + resultant method except for `fq_default_poly`. (RDB) + + 0.7.0 -----