From 83279d911e1c5cd3de63199a19130c1781b224f4 Mon Sep 17 00:00:00 2001 From: Jake-Moss Date: Tue, 20 Aug 2024 01:11:42 +1000 Subject: [PATCH 1/5] Add Groebner basis related functions for fmpz_mpoly_vec --- src/flint/types/fmpz_mpoly.pyx | 183 +++++++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) diff --git a/src/flint/types/fmpz_mpoly.pyx b/src/flint/types/fmpz_mpoly.pyx index b964050d..b36f51ef 100644 --- a/src/flint/types/fmpz_mpoly.pyx +++ b/src/flint/types/fmpz_mpoly.pyx @@ -15,6 +15,8 @@ from flint.flintlib.fmpz cimport fmpz_set from flint.flintlib.fmpz_mpoly cimport ( fmpz_mpoly_add, fmpz_mpoly_add_fmpz, + fmpz_mpoly_buchberger_naive, + fmpz_mpoly_buchberger_naive_with_limits, fmpz_mpoly_clear, fmpz_mpoly_compose_fmpz_mpoly, fmpz_mpoly_ctx_init, @@ -41,6 +43,7 @@ from flint.flintlib.fmpz_mpoly cimport ( fmpz_mpoly_neg, fmpz_mpoly_pow_fmpz, fmpz_mpoly_push_term_fmpz_ffmpz, + fmpz_mpoly_reduction_primitive_part, fmpz_mpoly_scalar_divides_fmpz, fmpz_mpoly_scalar_mul_fmpz, fmpz_mpoly_set, @@ -48,13 +51,18 @@ from flint.flintlib.fmpz_mpoly cimport ( fmpz_mpoly_set_fmpz, fmpz_mpoly_set_str_pretty, fmpz_mpoly_sort_terms, + fmpz_mpoly_spoly, fmpz_mpoly_sqrt_heap, fmpz_mpoly_sub, fmpz_mpoly_sub_fmpz, fmpz_mpoly_total_degree_fmpz, + fmpz_mpoly_vec_autoreduction, + fmpz_mpoly_vec_autoreduction_groebner, fmpz_mpoly_vec_clear, fmpz_mpoly_vec_entry, fmpz_mpoly_vec_init, + fmpz_mpoly_vec_is_autoreduced, + fmpz_mpoly_vec_is_groebner, ) from flint.flintlib.fmpz_mpoly_factor cimport ( fmpz_mpoly_factor, @@ -1049,6 +1057,53 @@ cdef class fmpz_mpoly(flint_mpoly): fmpz_mpoly_integral(res.val, scale.val, self.val, i, self.ctx.val) return scale, res + def spoly(self, g): + """ + Compute the S-polynomial of `self` and `g`, scaled to an integer polynomial by computing the LCM of the + leading coefficients. + + >>> from flint import Ordering + >>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, nametup=('x', 'y')) + >>> f = ctx.from_dict({(2, 0): 1, (0, 1): -1}) + >>> g = ctx.from_dict({(3, 0): 1, (1, 0): -1}) + >>> f.spoly(g) + -x*y + x + + """ + cdef fmpz_mpoly res = create_fmpz_mpoly(self.ctx) + + if not typecheck(g, fmpz_mpoly): + raise TypeError(f"expected fmpz_mpoly, got {type(g)}") + + self.ctx.compatible_context_check((g).ctx) + fmpz_mpoly_spoly(res.val, self.val, (g).val, self.ctx.val) + return res + + def reduction_primitive_part(self, vec): + """ + Compute the the primitive part of the reduction (remainder of multivariate quasi-division with remainder) + with respect to the polynomials `vec`. + + >>> from flint import Ordering + >>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, nametup=('x', 'y')) + >>> f = ctx.from_dict({(3, 0): 2, (2, 1): -1, (0, 3): 1, (0, 1): 3}) + >>> g1 = ctx.from_dict({(2, 0): 1, (0, 2): 1, (0, 0): 1}) + >>> g2 = ctx.from_dict({(1, 1): 1, (0, 0): -2}) + >>> vec = fmpz_mpoly_vec([g1, g2], ctx) + >>> vec + fmpz_mpoly_vec([x^2 + y^2 + 1, x*y - 2], ctx=fmpz_mpoly_ctx(2, '', ('x', 'y'))) + >>> f.reduction_primitive_part(vec) + x - y^3 + + """ + cdef fmpz_mpoly res = create_fmpz_mpoly(self.ctx) + if not typecheck(vec, fmpz_mpoly_vec): + raise TypeError(f"expected fmpz_mpoly, got {type(vec)}") + + self.ctx.compatible_context_check((vec).ctx) + fmpz_mpoly_reduction_primitive_part(res.val, self.val, (vec).val, self.ctx.val) + return res + cdef class fmpz_mpoly_vec: """ @@ -1121,3 +1176,131 @@ cdef class fmpz_mpoly_vec: def to_tuple(self): return tuple(self[i] for i in range(self.val.length)) + + def is_groebner(self, other=None) -> bool: + """ + Check if self is a Gröbner basis. If `other` is not None then check if self is a Gröbner basis for `other`. + + >>> from flint import Ordering + >>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, nametup=('x', 'y')) + >>> f = ctx.from_dict({(2, 0): 1, (0, 1): -1}) + >>> g = ctx.from_dict({(3, 0): 1, (1, 0): -1}) + >>> k = ctx.from_dict({(1, 1): 1, (1, 0): -1}) + >>> h = ctx.from_dict({(0, 2): 1, (0, 1): -1}) + >>> vec = fmpz_mpoly_vec([f, k, h], ctx) + >>> vec + fmpz_mpoly_vec([x^2 - y, x*y - x, y^2 - y], ctx=fmpz_mpoly_ctx(2, '', ('x', 'y'))) + >>> vec.is_groebner() + True + >>> vec.is_groebner(fmpz_mpoly_vec([f, g], ctx)) + True + >>> vec.is_groebner(fmpz_mpoly_vec([f, ctx.from_dict({(3, 0): 1})], ctx)) + False + + """ + if other is None: + return fmpz_mpoly_vec_is_groebner(self.val, NULL, self.ctx.val) + elif typecheck(other, fmpz_mpoly_vec): + self.ctx.compatible_context_check((other).ctx) + return fmpz_mpoly_vec_is_groebner(self.val, (other).val, self.ctx.val) + else: + raise TypeError(f"expected either None or a fmpz_mpoly_vec, got {type(other)}") + + def is_autoreduced(self) -> bool: + """ + Check if self is auto-reduced (or inter-reduced). + + >>> from flint import Ordering + >>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, nametup=('x', 'y')) + >>> f = ctx.from_dict({(2, 0): 3, (0, 1): -1}) + >>> k1 = ctx.from_dict({(1, 1): 1, (1, 0): -1}) + >>> k2 = ctx.from_dict({(1, 1): 3, (1, 0): -3}) + >>> h = ctx.from_dict({(0, 2): 1, (0, 1): -1}) + >>> vec = fmpz_mpoly_vec([f, k1, h], ctx) + >>> vec + fmpz_mpoly_vec([3*x^2 - y, x*y - x, y^2 - y], ctx=fmpz_mpoly_ctx(2, '', ('x', 'y'))) + >>> vec.is_autoreduced() + True + >>> vec = fmpz_mpoly_vec([f, k2, h], ctx) + >>> vec + fmpz_mpoly_vec([3*x^2 - y, 3*x*y - 3*x, y^2 - y], ctx=fmpz_mpoly_ctx(2, '', ('x', 'y'))) + >>> vec.is_autoreduced() + False + + """ + return fmpz_mpoly_vec_is_autoreduced(self.val, self.ctx.val) + + def autoreduction(self, groebner=False) -> fmpz_mpoly_vec: + """ + Compute the autoreduction of `self`. If `groebner` is True and `self` is a Gröbner basis, compute the reduced + reduced Gröbner basis of `self`, throws an `RuntimeError` otherwise. + + >>> from flint import Ordering + >>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, nametup=('x', 'y')) + >>> f = ctx.from_dict({(2, 0): 3, (0, 1): -1}) + >>> k2 = ctx.from_dict({(1, 1): 3, (1, 0): -3}) + >>> h = ctx.from_dict({(0, 2): 1, (0, 1): -1}) + >>> vec = fmpz_mpoly_vec([f, k2, h], ctx) + >>> vec + fmpz_mpoly_vec([3*x^2 - y, 3*x*y - 3*x, y^2 - y], ctx=fmpz_mpoly_ctx(2, '', ('x', 'y'))) + >>> vec.is_autoreduced() + False + >>> vec2 = vec.autoreduction() + >>> vec2.is_autoreduced() + True + >>> vec2 + fmpz_mpoly_vec([3*x^2 - y, x*y - x, y^2 - y], ctx=fmpz_mpoly_ctx(2, '', ('x', 'y'))) + + """ + + cdef fmpz_mpoly_vec h = fmpz_mpoly_vec(0, self.ctx) + + if groebner: + if not self.is_groebner(): + raise RuntimeError("reduced Gröbner basis construction requires that `self` is a Gröbner basis.") + fmpz_mpoly_vec_autoreduction_groebner(h.val, self.val, self.ctx.val) + else: + fmpz_mpoly_vec_autoreduction(h.val, self.val, self.ctx.val) + + return h + + def buchberger_naive(self, limits=None): + """ + Compute the Gröbner basis of `self` using a naive implementation of Buchberger’s algorithm. + + Provide `limits` in the form of a tuple of `(ideal_len_limit, poly_len_limit, poly_bits_limit)` to halt + execution if the length of the ideal basis set exceeds `ideal_len_limit`, the length of any polynomial exceeds + `poly_len_limit`, or the size of the coefficients of any polynomial exceeds `poly_bits_limit`. + + If limits is provided return a tuple of `(result, success)`. If `success` is False then `result` is a valid + basis for `self`, but it may not be a Gröbner basis. + + >>> from flint import Ordering + >>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, nametup=('x', 'y')) + >>> f = ctx.from_dict({(2, 0): 1, (0, 1): -1}) + >>> g = ctx.from_dict({(3, 1): 1, (1, 0): -1}) + >>> vec = fmpz_mpoly_vec([f, g], ctx) + >>> vec + fmpz_mpoly_vec([x^2 - y, x^3*y - x], ctx=fmpz_mpoly_ctx(2, '', ('x', 'y'))) + >>> vec.is_groebner() + False + >>> vec.buchberger_naive() + fmpz_mpoly_vec([x^2 - y, x^3*y - x, x*y^2 - x, y^3 - y], ctx=fmpz_mpoly_ctx(2, '', ('x', 'y'))) + >>> vec.buchberger_naive(limits=(2, 2, 512)) + (fmpz_mpoly_vec([x^2 - y, x^3*y - x], ctx=fmpz_mpoly_ctx(2, '', ('x', 'y'))), False) + + """ + + cdef: + fmpz_mpoly_vec g = fmpz_mpoly_vec(0, self.ctx) + slong ideal_len_limit, poly_len_limit, poly_bits_limit + + if limits is not None: + ideal_len_limit, poly_len_limit, poly_bits_limit = limits + if fmpz_mpoly_buchberger_naive_with_limits(g.val, self.val, ideal_len_limit, poly_len_limit, poly_bits_limit, self.ctx.val): + return g, True + else: + return g, False + else: + fmpz_mpoly_buchberger_naive(g.val, self.val, self.ctx.val) + return g From f379dc093f2ebc7d37fb2c468cab12f3bfbe80be Mon Sep 17 00:00:00 2001 From: Jake-Moss Date: Tue, 20 Aug 2024 23:40:26 +1000 Subject: [PATCH 2/5] Use prettier doc strings --- src/flint/types/fmpz_mpoly.pyx | 40 +++++++++++++++------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/src/flint/types/fmpz_mpoly.pyx b/src/flint/types/fmpz_mpoly.pyx index b36f51ef..df5d5d12 100644 --- a/src/flint/types/fmpz_mpoly.pyx +++ b/src/flint/types/fmpz_mpoly.pyx @@ -1183,13 +1183,12 @@ cdef class fmpz_mpoly_vec: >>> from flint import Ordering >>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, nametup=('x', 'y')) - >>> f = ctx.from_dict({(2, 0): 1, (0, 1): -1}) - >>> g = ctx.from_dict({(3, 0): 1, (1, 0): -1}) - >>> k = ctx.from_dict({(1, 1): 1, (1, 0): -1}) - >>> h = ctx.from_dict({(0, 2): 1, (0, 1): -1}) + >>> x, y = ctx.gens() + >>> f = x**2 - y + >>> g = x**3 - x + >>> k = x*y - x + >>> h = y**2 - y >>> vec = fmpz_mpoly_vec([f, k, h], ctx) - >>> vec - fmpz_mpoly_vec([x^2 - y, x*y - x, y^2 - y], ctx=fmpz_mpoly_ctx(2, '', ('x', 'y'))) >>> vec.is_groebner() True >>> vec.is_groebner(fmpz_mpoly_vec([f, g], ctx)) @@ -1212,18 +1211,15 @@ cdef class fmpz_mpoly_vec: >>> from flint import Ordering >>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, nametup=('x', 'y')) - >>> f = ctx.from_dict({(2, 0): 3, (0, 1): -1}) - >>> k1 = ctx.from_dict({(1, 1): 1, (1, 0): -1}) - >>> k2 = ctx.from_dict({(1, 1): 3, (1, 0): -3}) - >>> h = ctx.from_dict({(0, 2): 1, (0, 1): -1}) + >>> x, y = ctx.gens() + >>> f = 3*x**2 - y + >>> k1 = x*y - x + >>> k2 = 3*x*y - 3*x + >>> h = y**2 - y >>> vec = fmpz_mpoly_vec([f, k1, h], ctx) - >>> vec - fmpz_mpoly_vec([3*x^2 - y, x*y - x, y^2 - y], ctx=fmpz_mpoly_ctx(2, '', ('x', 'y'))) >>> vec.is_autoreduced() True >>> vec = fmpz_mpoly_vec([f, k2, h], ctx) - >>> vec - fmpz_mpoly_vec([3*x^2 - y, 3*x*y - 3*x, y^2 - y], ctx=fmpz_mpoly_ctx(2, '', ('x', 'y'))) >>> vec.is_autoreduced() False @@ -1237,12 +1233,11 @@ cdef class fmpz_mpoly_vec: >>> from flint import Ordering >>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, nametup=('x', 'y')) - >>> f = ctx.from_dict({(2, 0): 3, (0, 1): -1}) - >>> k2 = ctx.from_dict({(1, 1): 3, (1, 0): -3}) - >>> h = ctx.from_dict({(0, 2): 1, (0, 1): -1}) + >>> x, y = ctx.gens() + >>> f = 3*x**2 - y + >>> k2 = 3*x*y - 3*x + >>> h = y**2 - y >>> vec = fmpz_mpoly_vec([f, k2, h], ctx) - >>> vec - fmpz_mpoly_vec([3*x^2 - y, 3*x*y - 3*x, y^2 - y], ctx=fmpz_mpoly_ctx(2, '', ('x', 'y'))) >>> vec.is_autoreduced() False >>> vec2 = vec.autoreduction() @@ -1277,11 +1272,10 @@ cdef class fmpz_mpoly_vec: >>> from flint import Ordering >>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, nametup=('x', 'y')) - >>> f = ctx.from_dict({(2, 0): 1, (0, 1): -1}) - >>> g = ctx.from_dict({(3, 1): 1, (1, 0): -1}) + >>> x, y = ctx.gens() + >>> f = x**2 - y + >>> g = x**3*y - x >>> vec = fmpz_mpoly_vec([f, g], ctx) - >>> vec - fmpz_mpoly_vec([x^2 - y, x^3*y - x], ctx=fmpz_mpoly_ctx(2, '', ('x', 'y'))) >>> vec.is_groebner() False >>> vec.buchberger_naive() From 97cdd97ecc1274ca9f5e5f74954595df0d075f93 Mon Sep 17 00:00:00 2001 From: Jake Moss Date: Wed, 28 Aug 2024 15:42:39 +1000 Subject: [PATCH 3/5] Add tests, add note to `buchberger_naive`, add equality comparison --- src/flint/test/test_all.py | 61 ++++++++++++++++++++++++++++++++-- src/flint/types/fmpq_mpoly.pyx | 11 ++++++ src/flint/types/fmpz_mpoly.pyx | 29 +++++++++++----- 3 files changed, 91 insertions(+), 10 deletions(-) diff --git a/src/flint/test/test_all.py b/src/flint/test/test_all.py index 783a4084..e6cf8154 100644 --- a/src/flint/test/test_all.py +++ b/src/flint/test/test_all.py @@ -3208,8 +3208,11 @@ def _all_mpoly_vecs(): def test_fmpz_mpoly_vec(): for context, mpoly_vec in _all_mpoly_vecs(): + has_groebner_functions = mpoly_vec is flint.fmpz_mpoly_vec + ctx = context.get_context(nvars=2) ctx1 = context.get_context(nvars=4) + x, y = ctx.gens() vec = mpoly_vec(3, ctx) @@ -3225,14 +3228,68 @@ def test_fmpz_mpoly_vec(): assert raises(lambda: vec[None], TypeError) assert raises(lambda: vec[-1], IndexError) - vec[1] = ctx.from_dict({(1, 1): 1}) - assert vec.to_tuple() == mpoly_vec([ctx.from_dict({}), ctx.from_dict({(1, 1): 1}), ctx.from_dict({})], ctx).to_tuple() + vec[1] = x * y + assert vec == mpoly_vec([ctx.from_dict({}), x * y, ctx.from_dict({})], ctx) + assert vec != mpoly_vec([x, x * y, ctx.from_dict({})], ctx) + assert vec != mpoly_vec([ctx.from_dict({})], ctx) + assert vec != mpoly_vec([ctx1.from_dict({})], ctx1) + assert vec.to_tuple() == mpoly_vec([ctx.from_dict({}), x * y, ctx.from_dict({})], ctx).to_tuple() assert raises(lambda: vec.__setitem__(None, 0), TypeError) assert raises(lambda: vec.__setitem__(-1, 0), IndexError) assert raises(lambda: vec.__setitem__(0, 0), TypeError) assert raises(lambda: vec.__setitem__(0, ctx1.from_dict({})), IncompatibleContextError) + if has_groebner_functions: + ctx = context.get_context(3, flint.Ordering.lex, nametup=('x', 'y', 'z')) + + # Examples here cannibalised from + # https://en.wikipedia.org/wiki/Gr%C3%B6bner_basis#Example_and_counterexample + x, y, z = ctx.gens() + f = x**2 - y + f2 = 3 * x**2 - y + g = x**3 - x + g2 = x**3 * y - x + k = x * y - x + k2 = 3 * x * y - 3 * x + h = y**2 - y + + vec = mpoly_vec([f, k, h], ctx) + assert vec.is_groebner() + assert vec.is_groebner(mpoly_vec([f, g], ctx)) + assert not vec.is_groebner(mpoly_vec([f, x**3], ctx)) + + assert mpoly_vec([f2, k, h], ctx).is_autoreduced() + assert not mpoly_vec([f2, k2, h], ctx).is_autoreduced() + + vec = mpoly_vec([f2, k2, h], ctx) + vec2 = vec.autoreduction() + assert not vec.is_autoreduced() + assert vec2.is_autoreduced() + assert vec2 == mpoly_vec([3 * x**2 - y, x * y - x, y**2 - y], ctx) + + vec = mpoly_vec([f, g2], ctx) + assert not vec.is_groebner() + assert vec.buchberger_naive() == mpoly_vec([x**2 - y, x**3 * y - x, x * y**2 - x, y**3 - y], ctx) + assert vec.buchberger_naive(limits=(2, 2, 512)) == (mpoly_vec([x**2 - y, x**3 * y - x], ctx), False) + + unreduced_basis = mpoly_vec([x**2 - 2, y**2 - 3, z - x - y], ctx).buchberger_naive() + assert list(unreduced_basis) == [ + x**2 - 2, + y**2 - 3, + x + y - z, + 2*y*z - z**2 - 1, + 2*y + z**3 - 11*z, + z**4 - 10*z**2 + 1 + ] + + assert list(unreduced_basis.autoreduction()) == [ + z**4 - 10 * z**2 + 1, + 2*y + z**3 - 11 * z, + 2 * x - z**3 + 9 * z + ] + + def _all_matrices(): """Return a list of matrix types and scalar types.""" R163 = flint.fmpz_mod_ctx(163) diff --git a/src/flint/types/fmpq_mpoly.pyx b/src/flint/types/fmpq_mpoly.pyx index 88219b54..0322d31b 100644 --- a/src/flint/types/fmpq_mpoly.pyx +++ b/src/flint/types/fmpq_mpoly.pyx @@ -1112,5 +1112,16 @@ cdef class fmpq_mpoly_vec: def __repr__(self): return f"fmpq_mpoly_vec({self}, ctx={self.ctx})" + def __richcmp__(self, other, int op): + if not (op == Py_EQ or op == Py_NE): + return NotImplemented + elif typecheck(other, fmpq_mpoly_vec): + if (self).ctx is (other).ctx and len(self) == len(other): + return (op == Py_NE) ^ all(x == y for x, y in zip(self, other)) + else: + return op == Py_NE + else: + return NotImplemented + def to_tuple(self): return tuple(self[i] for i in range(self.length)) diff --git a/src/flint/types/fmpz_mpoly.pyx b/src/flint/types/fmpz_mpoly.pyx index df5d5d12..20d011e3 100644 --- a/src/flint/types/fmpz_mpoly.pyx +++ b/src/flint/types/fmpz_mpoly.pyx @@ -1174,6 +1174,17 @@ cdef class fmpz_mpoly_vec: def __repr__(self): return f"fmpz_mpoly_vec({self}, ctx={self.ctx})" + def __richcmp__(self, other, int op): + if not (op == Py_EQ or op == Py_NE): + return NotImplemented + elif typecheck(other, fmpz_mpoly_vec): + if (self).ctx is (other).ctx and len(self) == len(other): + return (op == Py_NE) ^ all(x == y for x, y in zip(self, other)) + else: + return op == Py_NE + else: + return NotImplemented + def to_tuple(self): return tuple(self[i] for i in range(self.val.length)) @@ -1193,7 +1204,7 @@ cdef class fmpz_mpoly_vec: True >>> vec.is_groebner(fmpz_mpoly_vec([f, g], ctx)) True - >>> vec.is_groebner(fmpz_mpoly_vec([f, ctx.from_dict({(3, 0): 1})], ctx)) + >>> vec.is_groebner(fmpz_mpoly_vec([f, x**3], ctx)) False """ @@ -1212,14 +1223,14 @@ cdef class fmpz_mpoly_vec: >>> from flint import Ordering >>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, nametup=('x', 'y')) >>> x, y = ctx.gens() - >>> f = 3*x**2 - y - >>> k1 = x*y - x + >>> f2 = 3*x**2 - y + >>> k = x*y - x >>> k2 = 3*x*y - 3*x >>> h = y**2 - y - >>> vec = fmpz_mpoly_vec([f, k1, h], ctx) + >>> vec = fmpz_mpoly_vec([f2, k, h], ctx) >>> vec.is_autoreduced() True - >>> vec = fmpz_mpoly_vec([f, k2, h], ctx) + >>> vec = fmpz_mpoly_vec([f2, k2, h], ctx) >>> vec.is_autoreduced() False @@ -1234,10 +1245,10 @@ cdef class fmpz_mpoly_vec: >>> from flint import Ordering >>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, nametup=('x', 'y')) >>> x, y = ctx.gens() - >>> f = 3*x**2 - y + >>> f2 = 3*x**2 - y >>> k2 = 3*x*y - 3*x >>> h = y**2 - y - >>> vec = fmpz_mpoly_vec([f, k2, h], ctx) + >>> vec = fmpz_mpoly_vec([f2, k2, h], ctx) >>> vec.is_autoreduced() False >>> vec2 = vec.autoreduction() @@ -1270,6 +1281,9 @@ cdef class fmpz_mpoly_vec: If limits is provided return a tuple of `(result, success)`. If `success` is False then `result` is a valid basis for `self`, but it may not be a Gröbner basis. + NOTE: This function is exposed only for convenience, it is a naive implementation and does not compute a reduced + basis. + >>> from flint import Ordering >>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, nametup=('x', 'y')) >>> x, y = ctx.gens() @@ -1282,7 +1296,6 @@ cdef class fmpz_mpoly_vec: fmpz_mpoly_vec([x^2 - y, x^3*y - x, x*y^2 - x, y^3 - y], ctx=fmpz_mpoly_ctx(2, '', ('x', 'y'))) >>> vec.buchberger_naive(limits=(2, 2, 512)) (fmpz_mpoly_vec([x^2 - y, x^3*y - x], ctx=fmpz_mpoly_ctx(2, '', ('x', 'y'))), False) - """ cdef: From 09071316340694a473eddf311322bcaad1d384ee Mon Sep 17 00:00:00 2001 From: Jake-Moss Date: Thu, 29 Aug 2024 21:33:01 +1000 Subject: [PATCH 4/5] Prefer `gens` to `from_dict` --- src/flint/types/fmpz_mpoly.pyx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/flint/types/fmpz_mpoly.pyx b/src/flint/types/fmpz_mpoly.pyx index 106e41bf..94844f34 100644 --- a/src/flint/types/fmpz_mpoly.pyx +++ b/src/flint/types/fmpz_mpoly.pyx @@ -961,9 +961,10 @@ cdef class fmpz_mpoly(flint_mpoly): >>> from flint import Ordering >>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, nametup=('x', 'y')) - >>> f = ctx.from_dict({(3, 0): 2, (2, 1): -1, (0, 3): 1, (0, 1): 3}) - >>> g1 = ctx.from_dict({(2, 0): 1, (0, 2): 1, (0, 0): 1}) - >>> g2 = ctx.from_dict({(1, 1): 1, (0, 0): -2}) + >>> x, y = ctx.gens() + >>> f = 2 * x**3 -x**2 * y + y**3 + 3 * y + >>> g1 = x**2 + y**2 + 1 + >>> g2 = x * y - 2 >>> vec = fmpz_mpoly_vec([g1, g2], ctx) >>> vec fmpz_mpoly_vec([x^2 + y^2 + 1, x*y - 2], ctx=fmpz_mpoly_ctx(2, '', ('x', 'y'))) From e95013ad8a6307673d2a25a4d3d42552b0282ccf Mon Sep 17 00:00:00 2001 From: Jake-Moss Date: Thu, 29 Aug 2024 21:42:36 +1000 Subject: [PATCH 5/5] Add auto-reduction example to `buchberger_naive`. Reflow to 88 chars --- src/flint/types/fmpz_mpoly.pyx | 86 +++++++++++++++++++++++----------- 1 file changed, 58 insertions(+), 28 deletions(-) diff --git a/src/flint/types/fmpz_mpoly.pyx b/src/flint/types/fmpz_mpoly.pyx index 94844f34..43420ad2 100644 --- a/src/flint/types/fmpz_mpoly.pyx +++ b/src/flint/types/fmpz_mpoly.pyx @@ -934,8 +934,8 @@ cdef class fmpz_mpoly(flint_mpoly): def spoly(self, g): """ - Compute the S-polynomial of `self` and `g`, scaled to an integer polynomial by computing the LCM of the - leading coefficients. + Compute the S-polynomial of `self` and `g`, scaled to an integer polynomial + by computing the LCM of the leading coefficients. >>> from flint import Ordering >>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, nametup=('x', 'y')) @@ -943,7 +943,6 @@ cdef class fmpz_mpoly(flint_mpoly): >>> g = ctx.from_dict({(3, 0): 1, (1, 0): -1}) >>> f.spoly(g) -x*y + x - """ cdef fmpz_mpoly res = create_fmpz_mpoly(self.ctx) @@ -956,8 +955,8 @@ cdef class fmpz_mpoly(flint_mpoly): def reduction_primitive_part(self, vec): """ - Compute the the primitive part of the reduction (remainder of multivariate quasi-division with remainder) - with respect to the polynomials `vec`. + Compute the the primitive part of the reduction (remainder of multivariate + quasi-division with remainder) with respect to the polynomials `vec`. >>> from flint import Ordering >>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, nametup=('x', 'y')) @@ -970,7 +969,6 @@ cdef class fmpz_mpoly(flint_mpoly): fmpz_mpoly_vec([x^2 + y^2 + 1, x*y - 2], ctx=fmpz_mpoly_ctx(2, '', ('x', 'y'))) >>> f.reduction_primitive_part(vec) x - y^3 - """ cdef fmpz_mpoly res = create_fmpz_mpoly(self.ctx) if not typecheck(vec, fmpz_mpoly_vec): @@ -986,7 +984,9 @@ cdef class fmpz_mpoly_vec: A class representing a vector of fmpz_mpolys. """ - def __cinit__(self, iterable_or_len, fmpz_mpoly_ctx ctx, bint double_indirect = False): + def __cinit__( + self, iterable_or_len, fmpz_mpoly_ctx ctx, bint double_indirect = False + ): if isinstance(iterable_or_len, int): length = iterable_or_len else: @@ -996,7 +996,9 @@ cdef class fmpz_mpoly_vec: fmpz_mpoly_vec_init(self.val, length, self.ctx.val) if double_indirect: - self.double_indirect = libc.stdlib.malloc(length * sizeof(fmpz_mpoly_struct *)) + self.double_indirect = libc.stdlib.malloc( + length * sizeof(fmpz_mpoly_struct *) + ) if self.double_indirect is NULL: raise MemoryError("malloc returned a null pointer") # pragma: no cover @@ -1034,7 +1036,9 @@ cdef class fmpz_mpoly_vec: elif (y).ctx is not self.ctx: raise IncompatibleContextError(f"{(y).ctx} is not {self.ctx}") - fmpz_mpoly_set(fmpz_mpoly_vec_entry(self.val, x), (y).val, self.ctx.val) + fmpz_mpoly_set( + fmpz_mpoly_vec_entry(self.val, x), (y).val, self.ctx.val + ) def __len__(self): return self.val.length @@ -1054,7 +1058,10 @@ cdef class fmpz_mpoly_vec: if not (op == Py_EQ or op == Py_NE): return NotImplemented elif typecheck(other, fmpz_mpoly_vec): - if (self).ctx is (other).ctx and len(self) == len(other): + if ( + (self).ctx is (other).ctx + and len(self) == len(other) + ): return (op == Py_NE) ^ all(x == y for x, y in zip(self, other)) else: return op == Py_NE @@ -1066,7 +1073,8 @@ cdef class fmpz_mpoly_vec: def is_groebner(self, other=None) -> bool: """ - Check if self is a Gröbner basis. If `other` is not None then check if self is a Gröbner basis for `other`. + Check if self is a Gröbner basis. If `other` is not None then check if self + is a Gröbner basis for `other`. >>> from flint import Ordering >>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, nametup=('x', 'y')) @@ -1082,15 +1090,18 @@ cdef class fmpz_mpoly_vec: True >>> vec.is_groebner(fmpz_mpoly_vec([f, x**3], ctx)) False - """ if other is None: return fmpz_mpoly_vec_is_groebner(self.val, NULL, self.ctx.val) elif typecheck(other, fmpz_mpoly_vec): self.ctx.compatible_context_check((other).ctx) - return fmpz_mpoly_vec_is_groebner(self.val, (other).val, self.ctx.val) + return fmpz_mpoly_vec_is_groebner( + self.val, (other).val, self.ctx.val + ) else: - raise TypeError(f"expected either None or a fmpz_mpoly_vec, got {type(other)}") + raise TypeError( + f"expected either None or a fmpz_mpoly_vec, got {type(other)}" + ) def is_autoreduced(self) -> bool: """ @@ -1115,8 +1126,9 @@ cdef class fmpz_mpoly_vec: def autoreduction(self, groebner=False) -> fmpz_mpoly_vec: """ - Compute the autoreduction of `self`. If `groebner` is True and `self` is a Gröbner basis, compute the reduced - reduced Gröbner basis of `self`, throws an `RuntimeError` otherwise. + Compute the autoreduction of `self`. If `groebner` is True and `self` is a + Gröbner basis, compute the reduced reduced Gröbner basis of `self`, throws an + `RuntimeError` otherwise. >>> from flint import Ordering >>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, nametup=('x', 'y')) @@ -1132,14 +1144,16 @@ cdef class fmpz_mpoly_vec: True >>> vec2 fmpz_mpoly_vec([3*x^2 - y, x*y - x, y^2 - y], ctx=fmpz_mpoly_ctx(2, '', ('x', 'y'))) - """ cdef fmpz_mpoly_vec h = fmpz_mpoly_vec(0, self.ctx) if groebner: if not self.is_groebner(): - raise RuntimeError("reduced Gröbner basis construction requires that `self` is a Gröbner basis.") + raise RuntimeError( + "reduced Gröbner basis construction requires that `self` is a " + "Gröbner basis." + ) fmpz_mpoly_vec_autoreduction_groebner(h.val, self.val, self.ctx.val) else: fmpz_mpoly_vec_autoreduction(h.val, self.val, self.ctx.val) @@ -1148,18 +1162,22 @@ cdef class fmpz_mpoly_vec: def buchberger_naive(self, limits=None): """ - Compute the Gröbner basis of `self` using a naive implementation of Buchberger’s algorithm. - - Provide `limits` in the form of a tuple of `(ideal_len_limit, poly_len_limit, poly_bits_limit)` to halt - execution if the length of the ideal basis set exceeds `ideal_len_limit`, the length of any polynomial exceeds - `poly_len_limit`, or the size of the coefficients of any polynomial exceeds `poly_bits_limit`. + Compute the Gröbner basis of `self` using a naive implementation of + Buchberger’s algorithm. - If limits is provided return a tuple of `(result, success)`. If `success` is False then `result` is a valid - basis for `self`, but it may not be a Gröbner basis. + Provide `limits` in the form of a tuple of `(ideal_len_limit, poly_len_limit, + poly_bits_limit)` to halt execution if the length of the ideal basis set exceeds + `ideal_len_limit`, the length of any polynomial exceeds `poly_len_limit`, or the + size of the coefficients of any polynomial exceeds `poly_bits_limit`. - NOTE: This function is exposed only for convenience, it is a naive implementation and does not compute a reduced + If limits is provided return a tuple of `(result, success)`. If `success` is + False then `result` is a valid basis for `self`, but it may not be a Gröbner basis. + NOTE: This function is exposed only for convenience, it is a naive + implementation and does not compute a reduced basis. To construct a reduced + basis use `autoreduce`. + >>> from flint import Ordering >>> ctx = fmpz_mpoly_ctx.get_context(2, Ordering.lex, nametup=('x', 'y')) >>> x, y = ctx.gens() @@ -1168,10 +1186,15 @@ cdef class fmpz_mpoly_vec: >>> vec = fmpz_mpoly_vec([f, g], ctx) >>> vec.is_groebner() False - >>> vec.buchberger_naive() + >>> vec2 = vec.buchberger_naive() + >>> vec2 fmpz_mpoly_vec([x^2 - y, x^3*y - x, x*y^2 - x, y^3 - y], ctx=fmpz_mpoly_ctx(2, '', ('x', 'y'))) >>> vec.buchberger_naive(limits=(2, 2, 512)) (fmpz_mpoly_vec([x^2 - y, x^3*y - x], ctx=fmpz_mpoly_ctx(2, '', ('x', 'y'))), False) + >>> vec2.is_autoreduced() + False + >>> vec2.autoreduction() + fmpz_mpoly_vec([x^2 - y, y^3 - y, x*y^2 - x], ctx=fmpz_mpoly_ctx(2, '', ('x', 'y'))) """ cdef: @@ -1180,7 +1203,14 @@ cdef class fmpz_mpoly_vec: if limits is not None: ideal_len_limit, poly_len_limit, poly_bits_limit = limits - if fmpz_mpoly_buchberger_naive_with_limits(g.val, self.val, ideal_len_limit, poly_len_limit, poly_bits_limit, self.ctx.val): + if fmpz_mpoly_buchberger_naive_with_limits( + g.val, + self.val, + ideal_len_limit, + poly_len_limit, + poly_bits_limit, + self.ctx.val + ): return g, True else: return g, False