Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions src/sage/categories/commutative_rings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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``.

Expand All @@ -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::
Expand Down Expand Up @@ -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:
Expand All @@ -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):
"""
Expand Down
66 changes: 66 additions & 0 deletions src/sage/rings/polynomial/polynomial_quotient_ring.py
Original file line number Diff line number Diff line change
Expand Up @@ -1262,6 +1262,72 @@ 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::

sage: R.<x> = QQ[]
sage: S.<y> = 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):
Expand Down
55 changes: 41 additions & 14 deletions src/sage/rings/ring_extension.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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.
Expand All @@ -365,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::
Expand All @@ -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': [(<class 'sage.rings.ring_extension.RingExtensionWithGen'>,
{'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
Expand Down Expand Up @@ -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,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it is good to have check be part of the key for the extension. It doesn't matter if it gets checked or not; it is still the same object.

'is_backend_exposed': is_backend_exposed}))
if use_generic_constructor:
constructors.append((RingExtension_generic,
Expand Down Expand Up @@ -2386,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)
Expand Down Expand Up @@ -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.<x> = K[]
sage: K3.<u> = K.extension(Kx([4, 1, 0, 1]))
sage: K3y.<y> = K3[]
sage: K6.<t> = K3.extension(K3y([2, 0, 1]))
sage: K6t.<t1> = K6.over(K, gen=t)
Traceback (most recent call last):
...
ValueError: the given family is not a basis
sage: K6t.<t1> = 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())
Expand Down
38 changes: 35 additions & 3 deletions src/sage/rings/ring_extension_element.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -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.<x> = K[]
sage: K3.<u> = K.extension(Kx([4, 1, 0, 1]))
sage: K3y.<y> = K3[]
sage: K6.<t> = 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)
Expand All @@ -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 = (<RingExtension_generic>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

Expand Down
Loading