Skip to content

Commit 51a49ab

Browse files
committed
Make RingExtension work with tower of finite field extension
1 parent 28b7af0 commit 51a49ab

File tree

4 files changed

+152
-15
lines changed

4 files changed

+152
-15
lines changed

src/sage/categories/commutative_rings.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ def _test_divides(self, **options):
211211
else:
212212
tester.assertTrue(test)
213213

214-
def over(self, base=None, gen=None, gens=None, name=None, names=None):
214+
def over(self, base=None, gen=None, *, gens=None, name=None, names=None, check=True):
215215
r"""
216216
Return this ring, considered as an extension of ``base``.
217217
@@ -232,6 +232,10 @@ def over(self, base=None, gen=None, gens=None, name=None, names=None):
232232
- ``names`` -- list or a tuple of variable names or ``None``
233233
(default: ``None``)
234234
235+
- ``check`` -- forwarded to :class:`sage.rings.ring_extension.RingExtensionWithGen`
236+
to check that the provided generator indeed generates the ring.
237+
Might be ignored if :class:`sage.rings.ring_extension.RingExtension_generic` is selected
238+
235239
EXAMPLES:
236240
237241
We construct an extension of finite fields::
@@ -330,6 +334,17 @@ def over(self, base=None, gen=None, gens=None, name=None, names=None):
330334
[Field in b with defining polynomial x^2 - a over its base,
331335
Field in a with defining polynomial x^2 - 2 over its base,
332336
Rational Field]
337+
338+
TESTS:
339+
340+
Even when ``check=False``, the generator must be valid, the behavior is not guaranteed
341+
otherwise. The behavior with ``check=False`` below may change any time::
342+
343+
sage: GF(5^2).over(GF(5), gen=1, name='t', check=True)
344+
Traceback (most recent call last):
345+
...
346+
ValueError: the given family is not a basis
347+
sage: ignore = GF(5^2).over(GF(5), gen=1, name='t', check=False)
333348
"""
334349
from sage.rings.ring_extension import RingExtension
335350
if name is not None:
@@ -340,7 +355,7 @@ def over(self, base=None, gen=None, gens=None, name=None, names=None):
340355
if gens is not None:
341356
raise ValueError("keyword argument 'gen' cannot be combined with 'gens'")
342357
gens = (gen,)
343-
return RingExtension(self, base, gens, names)
358+
return RingExtension(self, base, gens, names, check=check)
344359

345360
def frobenius_endomorphism(self, n=1):
346361
"""

src/sage/rings/polynomial/polynomial_quotient_ring.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1262,6 +1262,69 @@ def polynomial_ring(self):
12621262
"""
12631263
return self.__ring
12641264

1265+
def free_module(self, base=None, basis=None, map=True):
1266+
"""
1267+
See :meth:`sage.categories.rings.Rings.ParentMethods.free_module`.
1268+
1269+
TESTS::
1270+
1271+
sage: R.<x> = QQ[]
1272+
sage: S.<y> = R.quotient(x^2+x+1)[]
1273+
sage: T = S.quotient(y^3-2)
1274+
sage: V, from_V, to_V = S.base_ring().free_module(QQ)
1275+
sage: from_V(V([1, 2]))
1276+
2*xbar + 1
1277+
sage: to_V(from_V(V([1, 2])))
1278+
(1, 2)
1279+
sage: V, from_V, to_V = T.free_module(QQ)
1280+
sage: V
1281+
Vector space of dimension 6 over Rational Field
1282+
sage: from_V(V([1, 2, 3, 4, 5, 6]))
1283+
(6*xbar + 5)*ybar^2 + (4*xbar + 3)*ybar + 2*xbar + 1
1284+
sage: to_V(from_V(V([1, 2, 3, 4, 5, 6])))
1285+
(1, 2, 3, 4, 5, 6)
1286+
sage: V, from_V, to_V = T.free_module(S.base_ring())
1287+
sage: V
1288+
Vector space of dimension 3 over Univariate Quotient Polynomial Ring in xbar over Rational Field with modulus x^2 + x + 1
1289+
sage: from_V(V([1*x+2, 3*x+4, 5*x+6]))
1290+
(5*xbar + 6)*ybar^2 + (3*xbar + 4)*ybar + xbar + 2
1291+
sage: to_V(from_V(V([1*x+2, 3*x+4, 5*x+6])))
1292+
(xbar + 2, 3*xbar + 4, 5*xbar + 6)
1293+
"""
1294+
if basis is not None:
1295+
raise NotImplementedError
1296+
if base is None:
1297+
base = self.base_ring()
1298+
if base != self.base_ring():
1299+
V1, from_V1, to_V1 = self.base_ring().free_module(base=base, basis=None, map=True)
1300+
assert V1.base_ring() == base
1301+
d = self.degree()
1302+
V1_degree = V1.degree()
1303+
V = base ** (V1_degree * d)
1304+
1305+
def from_V(v):
1306+
assert len(v) == V1_degree * d
1307+
return self([from_V1(V1(v[i*V1_degree:(i+1)*V1_degree])) for i in range(d)])
1308+
1309+
def to_V(f):
1310+
list_f = list(f)
1311+
return V([c for coefficient in f for c in to_V1(coefficient)])
1312+
1313+
else:
1314+
V = base ** self.degree()
1315+
1316+
def from_V(v):
1317+
return self(list(v))
1318+
1319+
def to_V(f):
1320+
return V(list(f))
1321+
1322+
if not map:
1323+
return V
1324+
from sage.categories.morphism import SetMorphism
1325+
from sage.categories.homset import Hom
1326+
return V, SetMorphism(Hom(V, self), from_V), SetMorphism(Hom(self, V), to_V)
1327+
12651328
cover_ring = polynomial_ring
12661329

12671330
def random_element(self, degree=None, *args, **kwds):

src/sage/rings/ring_extension.pyx

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ from sage.structure.factory import UniqueFactory
121121
from sage.structure.parent cimport Parent
122122
from sage.structure.element cimport Element
123123
from sage.structure.category_object import normalize_names
124+
from sage.rings.polynomial.polynomial_quotient_ring import PolynomialQuotientRing_generic
124125
from sage.categories.map cimport Map
125126
from sage.categories.commutative_rings import CommutativeRings
126127
from sage.categories.fields import Fields
@@ -183,17 +184,27 @@ def tower_bases(ring, degree):
183184
bases.append(base)
184185
if degree:
185186
degrees.append(deg)
186-
try:
187-
d = base.relative_degree()
188-
except AttributeError:
187+
if isinstance(base, PolynomialQuotientRing_generic):
188+
d = base.degree()
189+
assert d != Infinity
190+
if d == 0:
191+
break
192+
else:
189193
try:
190-
d = base.degree()
194+
d = base.relative_degree()
191195
except AttributeError:
192-
break
193-
if d is Infinity: break
196+
try:
197+
d = base.degree()
198+
except AttributeError:
199+
break
200+
if d is Infinity: break
194201
deg *= d
195-
newbase = base._base
196-
if newbase is base: break
202+
if isinstance(base, PolynomialQuotientRing_generic):
203+
newbase = base.base_ring()
204+
assert newbase is not base
205+
else:
206+
newbase = base._base
207+
if newbase is base: break
197208
base = newbase
198209
return bases, degrees
199210

@@ -345,7 +356,7 @@ class RingExtensionFactory(UniqueFactory):
345356
sage: E2 is E # needs sage.rings.number_field
346357
False
347358
"""
348-
def create_key_and_extra_args(self, ring, defining_morphism=None, gens=None, names=None, constructors=None):
359+
def create_key_and_extra_args(self, ring, defining_morphism=None, gens=None, names=None, constructors=None, check=True):
349360
"""
350361
Create a key and return it together with a list of constructors
351362
of the object.
@@ -457,7 +468,7 @@ class RingExtensionFactory(UniqueFactory):
457468
constructors = []
458469
if gens is not None and len(gens) == 1:
459470
constructors.append((RingExtensionWithGen,
460-
{'gen': gens[0], 'names': names,
471+
{'gen': gens[0], 'names': names, 'check': check,
461472
'is_backend_exposed': is_backend_exposed}))
462473
if use_generic_constructor:
463474
constructors.append((RingExtension_generic,
@@ -2572,6 +2583,22 @@ cdef class RingExtensionWithGen(RingExtensionWithBasis):
25722583
Field in a with defining polynomial x^3 + 3*x + 1 over its base
25732584
25742585
sage: TestSuite(E).run() # needs sage.rings.number_field
2586+
2587+
sage: p = 886368969471450739924935101400677
2588+
sage: K = GF(p)
2589+
sage: Kx.<x> = K[]
2590+
sage: K3.<u> = K.extension(Kx([4, 1, 0, 1]))
2591+
sage: K3y.<y> = K3[]
2592+
sage: K6.<t> = K3.extension(K3y([2, 0, 1]))
2593+
sage: K6t.<t1> = K6.over(K, gen=t)
2594+
Traceback (most recent call last):
2595+
...
2596+
ValueError: the given family is not a basis
2597+
sage: K6t.<t1> = K6.over(K, gen=t+u)
2598+
sage: K6t(t1).minpoly()
2599+
x^6 + 8*x^4 + 8*x^3 + 13*x^2 + 886368969471450739924935101400637*x + 18
2600+
sage: K6t(t1).minpoly()(t1)
2601+
0
25752602
"""
25762603
self._name = names[0]
25772604
backend_base = backend_parent(defining_morphism.domain())

src/sage/rings/ring_extension_element.pyx

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,20 @@ cdef class RingExtensionElement(CommutativeAlgebraElement):
113113
True
114114
sage: a.continued_fraction()
115115
[1; (2)*]
116+
117+
TESTS::
118+
119+
sage: p = 886368969471450739924935101400677
120+
sage: K = GF(p)
121+
sage: Kx.<x> = K[]
122+
sage: K3.<u> = K.extension(Kx([4, 1, 0, 1]))
123+
sage: K3y.<y> = K3[]
124+
sage: K6.<t> = K3.extension(K3y([2, 0, 1]))
125+
sage: K6t = K6.over(K)
126+
sage: K6t(t).minpoly()
127+
Traceback (most recent call last):
128+
...
129+
NotImplementedError: expected a minimal polynomial over Finite Field of size ...
116130
"""
117131
try:
118132
return self.getattr_from_category(name)
@@ -124,9 +138,27 @@ cdef class RingExtensionElement(CommutativeAlgebraElement):
124138
if not callable(method):
125139
raise AttributeError(AttributeErrorMessage(self, name))
126140

127-
def wrapper(*args, **kwargs):
128-
output = method(*to_backend(args), **to_backend(kwargs))
129-
return from_backend(output, self._parent)
141+
if name == 'minpoly':
142+
def wrapper(*args, **kwargs):
143+
output = method(*to_backend(args), **to_backend(kwargs))
144+
# output is a polynomial, so no need for from_backend
145+
# nonetheless we check if the output is sane
146+
expected_base_ring = (<RingExtension_generic>self._parent)._base
147+
# we guess the signature of 'method', e.g. (self, base=None)
148+
# to see what base the user likely want
149+
if args and isinstance(args[0], Parent):
150+
expected_base_ring = args[0]
151+
elif 'base' in kwargs:
152+
expected_base_ring = kwargs['base']
153+
if output.base_ring() is not expected_base_ring:
154+
raise NotImplementedError(f'expected a minimal polynomial over {expected_base_ring}, '
155+
f'got a minimal polynomial over {output.base_ring()}. '
156+
f'Passing explicit generator/basis of the ring extension might help')
157+
return output
158+
else:
159+
def wrapper(*args, **kwargs):
160+
output = method(*to_backend(args), **to_backend(kwargs))
161+
return from_backend(output, self._parent)
130162
wrapper.__doc__ = method.__doc__
131163
return wrapper
132164

0 commit comments

Comments
 (0)