From 034e897a6ad295d874e62d0630b49e22171c7720 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Sun, 21 Sep 2025 12:34:14 +0700 Subject: [PATCH 1/3] Make RingExtension work with tower of finite field extension --- src/sage/categories/commutative_rings.py | 19 +++++- .../polynomial/polynomial_quotient_ring.py | 63 +++++++++++++++++++ src/sage/rings/ring_extension.pyx | 49 +++++++++++---- src/sage/rings/ring_extension_element.pyx | 38 ++++++++++- 4 files changed, 153 insertions(+), 16 deletions(-) diff --git a/src/sage/categories/commutative_rings.py b/src/sage/categories/commutative_rings.py index 693c159740f..8284d9e50a4 100644 --- a/src/sage/categories/commutative_rings.py +++ b/src/sage/categories/commutative_rings.py @@ -211,7 +211,7 @@ def _test_divides(self, **options): else: tester.assertTrue(test) - def over(self, base=None, gen=None, gens=None, name=None, names=None): + def over(self, base=None, gen=None, *, gens=None, name=None, names=None, check=True): r""" Return this ring, considered as an extension of ``base``. @@ -232,6 +232,10 @@ def over(self, base=None, gen=None, gens=None, name=None, names=None): - ``names`` -- list or a tuple of variable names or ``None`` (default: ``None``) + - ``check`` -- forwarded to :class:`sage.rings.ring_extension.RingExtensionWithGen` + to check that the provided generator indeed generates the ring. + Might be ignored if :class:`sage.rings.ring_extension.RingExtension_generic` is selected + EXAMPLES: We construct an extension of finite fields:: @@ -330,6 +334,17 @@ def over(self, base=None, gen=None, gens=None, name=None, names=None): [Field in b with defining polynomial x^2 - a over its base, Field in a with defining polynomial x^2 - 2 over its base, Rational Field] + + TESTS: + + Even when ``check=False``, the generator must be valid, the behavior is not guaranteed + otherwise. The behavior with ``check=False`` below may change any time:: + + sage: GF(5^2).over(GF(5), gen=1, name='t', check=True) + Traceback (most recent call last): + ... + ValueError: the given family is not a basis + sage: ignore = GF(5^2).over(GF(5), gen=1, name='t', check=False) """ from sage.rings.ring_extension import RingExtension if name is not None: @@ -340,7 +355,7 @@ def over(self, base=None, gen=None, gens=None, name=None, names=None): if gens is not None: raise ValueError("keyword argument 'gen' cannot be combined with 'gens'") gens = (gen,) - return RingExtension(self, base, gens, names) + return RingExtension(self, base, gens, names, check=check) def frobenius_endomorphism(self, n=1): """ diff --git a/src/sage/rings/polynomial/polynomial_quotient_ring.py b/src/sage/rings/polynomial/polynomial_quotient_ring.py index c5c644b9335..2b94727e328 100644 --- a/src/sage/rings/polynomial/polynomial_quotient_ring.py +++ b/src/sage/rings/polynomial/polynomial_quotient_ring.py @@ -1262,6 +1262,69 @@ def polynomial_ring(self): """ return self.__ring + def free_module(self, base=None, basis=None, map=True): + """ + See :meth:`sage.categories.rings.Rings.ParentMethods.free_module`. + + TESTS:: + + sage: R. = QQ[] + sage: S. = R.quotient(x^2+x+1)[] + sage: T = S.quotient(y^3-2) + sage: V, from_V, to_V = S.base_ring().free_module(QQ) + sage: from_V(V([1, 2])) + 2*xbar + 1 + sage: to_V(from_V(V([1, 2]))) + (1, 2) + sage: V, from_V, to_V = T.free_module(QQ) + sage: V + Vector space of dimension 6 over Rational Field + sage: from_V(V([1, 2, 3, 4, 5, 6])) + (6*xbar + 5)*ybar^2 + (4*xbar + 3)*ybar + 2*xbar + 1 + sage: to_V(from_V(V([1, 2, 3, 4, 5, 6]))) + (1, 2, 3, 4, 5, 6) + sage: V, from_V, to_V = T.free_module(S.base_ring()) + sage: V + Vector space of dimension 3 over Univariate Quotient Polynomial Ring in xbar over Rational Field with modulus x^2 + x + 1 + sage: from_V(V([1*x+2, 3*x+4, 5*x+6])) + (5*xbar + 6)*ybar^2 + (3*xbar + 4)*ybar + xbar + 2 + sage: to_V(from_V(V([1*x+2, 3*x+4, 5*x+6]))) + (xbar + 2, 3*xbar + 4, 5*xbar + 6) + """ + if basis is not None: + raise NotImplementedError + if base is None: + base = self.base_ring() + if base != self.base_ring(): + V1, from_V1, to_V1 = self.base_ring().free_module(base=base, basis=None, map=True) + assert V1.base_ring() == base + d = self.degree() + V1_degree = V1.degree() + V = base ** (V1_degree * d) + + def from_V(v): + assert len(v) == V1_degree * d + return self([from_V1(V1(v[i*V1_degree:(i+1)*V1_degree])) for i in range(d)]) + + def to_V(f): + list_f = list(f) + return V([c for coefficient in f for c in to_V1(coefficient)]) + + else: + V = base ** self.degree() + + def from_V(v): + return self(list(v)) + + def to_V(f): + return V(list(f)) + + if not map: + return V + from sage.categories.morphism import SetMorphism + from sage.categories.homset import Hom + return V, SetMorphism(Hom(V, self), from_V), SetMorphism(Hom(self, V), to_V) + cover_ring = polynomial_ring def random_element(self, degree=None, *args, **kwds): diff --git a/src/sage/rings/ring_extension.pyx b/src/sage/rings/ring_extension.pyx index 4cbf5b1809f..bfb6d175095 100644 --- a/src/sage/rings/ring_extension.pyx +++ b/src/sage/rings/ring_extension.pyx @@ -121,6 +121,7 @@ from sage.structure.factory import UniqueFactory from sage.structure.parent cimport Parent from sage.structure.element cimport Element from sage.structure.category_object import normalize_names +from sage.rings.polynomial.polynomial_quotient_ring import PolynomialQuotientRing_generic from sage.categories.map cimport Map from sage.categories.commutative_rings import CommutativeRings from sage.categories.fields import Fields @@ -183,17 +184,27 @@ def tower_bases(ring, degree): bases.append(base) if degree: degrees.append(deg) - try: - d = base.relative_degree() - except AttributeError: + if isinstance(base, PolynomialQuotientRing_generic): + d = base.degree() + assert d != Infinity + if d == 0: + break + else: try: - d = base.degree() + d = base.relative_degree() except AttributeError: - break - if d is Infinity: break + try: + d = base.degree() + except AttributeError: + break + if d is Infinity: break deg *= d - newbase = base._base - if newbase is base: break + if isinstance(base, PolynomialQuotientRing_generic): + newbase = base.base_ring() + assert newbase is not base + else: + newbase = base._base + if newbase is base: break base = newbase return bases, degrees @@ -345,7 +356,7 @@ class RingExtensionFactory(UniqueFactory): sage: E2 is E # needs sage.rings.number_field False """ - def create_key_and_extra_args(self, ring, defining_morphism=None, gens=None, names=None, constructors=None): + def create_key_and_extra_args(self, ring, defining_morphism=None, gens=None, names=None, constructors=None, check=True): """ Create a key and return it together with a list of constructors of the object. @@ -389,7 +400,7 @@ class RingExtensionFactory(UniqueFactory): To: Finite Field in z4 of size 5^4 Defn: z2 |--> z4^3 + z4^2 + z4 + 3, (z4,), ('a',)), {'constructors': [(, - {'gen': z4, 'is_backend_exposed': True, 'names': ('a',)})]}) + {'check': True, 'gen': z4, 'is_backend_exposed': True, 'names': ('a',)})]}) """ use_generic_constructor = True is_backend_exposed = True @@ -457,7 +468,7 @@ class RingExtensionFactory(UniqueFactory): constructors = [] if gens is not None and len(gens) == 1: constructors.append((RingExtensionWithGen, - {'gen': gens[0], 'names': names, + {'gen': gens[0], 'names': names, 'check': check, 'is_backend_exposed': is_backend_exposed})) if use_generic_constructor: constructors.append((RingExtension_generic, @@ -2572,6 +2583,22 @@ cdef class RingExtensionWithGen(RingExtensionWithBasis): Field in a with defining polynomial x^3 + 3*x + 1 over its base sage: TestSuite(E).run() # needs sage.rings.number_field + + sage: p = 886368969471450739924935101400677 + sage: K = GF(p) + sage: Kx. = K[] + sage: K3. = K.extension(Kx([4, 1, 0, 1])) + sage: K3y. = K3[] + sage: K6. = K3.extension(K3y([2, 0, 1])) + sage: K6t. = K6.over(K, gen=t) + Traceback (most recent call last): + ... + ValueError: the given family is not a basis + sage: K6t. = K6.over(K, gen=t+u) + sage: K6t(t1).minpoly() + x^6 + 8*x^4 + 8*x^3 + 13*x^2 + 886368969471450739924935101400637*x + 18 + sage: K6t(t1).minpoly()(t1) + 0 """ self._name = names[0] backend_base = backend_parent(defining_morphism.domain()) diff --git a/src/sage/rings/ring_extension_element.pyx b/src/sage/rings/ring_extension_element.pyx index c96172db27c..7189f66aa7a 100644 --- a/src/sage/rings/ring_extension_element.pyx +++ b/src/sage/rings/ring_extension_element.pyx @@ -113,6 +113,20 @@ cdef class RingExtensionElement(CommutativeAlgebraElement): True sage: a.continued_fraction() [1; (2)*] + + TESTS:: + + sage: p = 886368969471450739924935101400677 + sage: K = GF(p) + sage: Kx. = K[] + sage: K3. = K.extension(Kx([4, 1, 0, 1])) + sage: K3y. = K3[] + sage: K6. = K3.extension(K3y([2, 0, 1])) + sage: K6t = K6.over(K) + sage: K6t(t).minpoly() + Traceback (most recent call last): + ... + NotImplementedError: expected a minimal polynomial over Finite Field of size ... """ try: return self.getattr_from_category(name) @@ -124,9 +138,27 @@ cdef class RingExtensionElement(CommutativeAlgebraElement): if not callable(method): raise AttributeError(AttributeErrorMessage(self, name)) - def wrapper(*args, **kwargs): - output = method(*to_backend(args), **to_backend(kwargs)) - return from_backend(output, self._parent) + if name == 'minpoly': + def wrapper(*args, **kwargs): + output = method(*to_backend(args), **to_backend(kwargs)) + # output is a polynomial, so no need for from_backend + # nonetheless we check if the output is sane + expected_base_ring = (self._parent)._base + # we guess the signature of 'method', e.g. (self, base=None) + # to see what base the user likely want + if args and isinstance(args[0], Parent): + expected_base_ring = args[0] + elif 'base' in kwargs: + expected_base_ring = kwargs['base'] + if output.base_ring() is not expected_base_ring: + raise NotImplementedError(f'expected a minimal polynomial over {expected_base_ring}, ' + f'got a minimal polynomial over {output.base_ring()}. ' + f'Passing explicit generator/basis of the ring extension might help') + return output + else: + def wrapper(*args, **kwargs): + output = method(*to_backend(args), **to_backend(kwargs)) + return from_backend(output, self._parent) wrapper.__doc__ = method.__doc__ return wrapper From d096221a488bb2ad0cc4d80fb292af1ef13a55c9 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Sun, 21 Sep 2025 17:15:49 +0700 Subject: [PATCH 2/3] Minor documentation polish --- src/sage/rings/ring_extension.pyx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/ring_extension.pyx b/src/sage/rings/ring_extension.pyx index bfb6d175095..13bd967b4bb 100644 --- a/src/sage/rings/ring_extension.pyx +++ b/src/sage/rings/ring_extension.pyx @@ -376,8 +376,8 @@ class RingExtensionFactory(UniqueFactory): (default: ``None``) - ``constructors`` -- list of constructors; each constructor - is a pair `(class, arguments)` where `class` is the class - implementing the extension and `arguments` is the dictionary + is a pair ``(class, arguments)`` where ``class`` is the class + implementing the extension and ``arguments`` is the dictionary of arguments to pass in to init function TESTS:: @@ -2397,7 +2397,7 @@ cdef class RingExtensionWithBasis(RingExtension_generic): Vector space of dimension 6 over Finite Field of size 11 In this case, the isomorphisms between `V` and `L` are given by the - basis `(1, a, b, ab, b^2, ab^2)`: + basis `(1, a, b, ab, b^2, ab^2)`:: sage: j(a*b) # needs sage.rings.finite_rings (0, 0, 0, 1, 0, 0) From cbb04eff0253b9b4714a08252bc29ba08f8baaa7 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Sat, 18 Oct 2025 22:50:05 +0700 Subject: [PATCH 3/3] Apply suggestion Co-authored-by: Travis Scrimshaw --- src/sage/rings/polynomial/polynomial_quotient_ring.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/polynomial_quotient_ring.py b/src/sage/rings/polynomial/polynomial_quotient_ring.py index 2b94727e328..8ab78445321 100644 --- a/src/sage/rings/polynomial/polynomial_quotient_ring.py +++ b/src/sage/rings/polynomial/polynomial_quotient_ring.py @@ -1263,7 +1263,10 @@ def polynomial_ring(self): return self.__ring def free_module(self, base=None, basis=None, map=True): - """ + r""" + Return a free module `V` over the specified subring together with + maps to and from `V`. + See :meth:`sage.categories.rings.Rings.ParentMethods.free_module`. TESTS::