Skip to content

Commit

Permalink
Trac #28481: free_module method for finite fields, number fields and …
Browse files Browse the repository at this point in the history
…p-adics

In support of the ring extension classes in #21413, we want to
generalize the functionality of the `vector_space` method to free
modules over other rings.  This ticket renames the method to
`free_module` and implements it for p-adic extensions.

URL: https://trac.sagemath.org/28481
Reported by: roed
Ticket author(s): David Roe
Reviewer(s): Xavier Caruso
  • Loading branch information
Release Manager committed Oct 3, 2019
2 parents 6d1e0cc + 242fecc commit 1579338
Show file tree
Hide file tree
Showing 31 changed files with 1,029 additions and 227 deletions.
2 changes: 1 addition & 1 deletion src/doc/en/thematic_tutorials/coercion_and_categories.rst
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@ And indeed, ``MS2`` has *more* methods than ``MS1``::
sage: len([s for s in dir(MS1) if inspect.ismethod(getattr(MS1,s,None))])
81
sage: len([s for s in dir(MS2) if inspect.ismethod(getattr(MS2,s,None))])
119
120

This is because the class of ``MS2`` also inherits from the parent
class for algebras::
Expand Down
31 changes: 31 additions & 0 deletions src/sage/categories/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,37 @@ def _pow_int(self, n):
from sage.modules.all import FreeModule
return FreeModule(self, n)

def vector_space(self, *args, **kwds):
r"""
Gives an isomorphism of this field with a vector space over a subfield.
This method is an alias for ``free_module``, which may have more documentation.
INPUT:
- ``base`` -- a subfield or morphism into this field (defaults to the base field)
- ``basis`` -- a basis of the field as a vector space
over the subfield; if not given, one is chosen automatically
- ``map`` -- whether to return maps from and to the vector space
OUTPUT:
- ``V`` -- a vector space over ``base``
- ``from_V`` -- an isomorphism from ``V`` to this field
- ``to_V`` -- the inverse isomorphism from this field to ``V``
EXAMPLES::
sage: K.<a> = Qq(125)
sage: V, fr, to = K.vector_space()
sage: v = V([1,2,3])
sage: fr(v, 7)
(3*a^2 + 2*a + 1) + O(5^7)
"""
return self.free_module(*args, **kwds)

class ElementMethods:
def euclidean_degree(self):
r"""
Expand Down
67 changes: 67 additions & 0 deletions src/sage/categories/rings.py
Original file line number Diff line number Diff line change
Expand Up @@ -1086,6 +1086,73 @@ def normalize_arg(arg):
from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
return PolynomialRing(self, elts)

def free_module(self, base=None, basis=None, map=True):
"""
Return a free module `V` over the specified subring together with maps to and from `V`.
The default implementation only supports the case that the base ring is the ring itself.
INPUT:
- ``base`` -- a subring `R` so that this ring is isomorphic
to a finite-rank free `R`-module `V`
- ``basis`` -- (optional) a basis for this ring over the base
- ``map`` -- boolean (default ``True``), whether to return
`R`-linear maps to and from `V`
OUTPUT:
- A finite-rank free `R`-module `V`
- An `R`-module isomorphism from `V` to this ring
(only included if ``map`` is ``True``)
- An `R`-module isomorphism from this ring to `V`
(only included if ``map`` is ``True``)
EXAMPLES::
sage: R.<x> = QQ[[]]
sage: V, from_V, to_V = R.free_module(R)
sage: v = to_V(1+x); v
(1 + x)
sage: from_V(v)
1 + x
sage: W, from_W, to_W = R.free_module(R, basis=(1-x))
sage: W is V
True
sage: w = to_W(1+x); w
(1 - x^2)
sage: from_W(w)
1 + x + O(x^20)
"""
if base is None:
base = self.base_ring()
if base is self:
V = self**1
if not map:
return V
if basis is not None:
if isinstance(basis, (list, tuple)):
if len(basis) != 1:
raise ValueError("Basis must have length 1")
basis = basis[0]
basis = self(basis)
if not basis.is_unit():
raise ValueError("Basis element must be a unit")
from sage.modules.free_module_morphism import BaseIsomorphism1D_from_FM, BaseIsomorphism1D_to_FM
Hfrom = V.Hom(self)
Hto = self.Hom(V)
from_V = Hfrom.__make_element_class__(BaseIsomorphism1D_from_FM)(Hfrom, basis=basis)
to_V = Hto.__make_element_class__(BaseIsomorphism1D_to_FM)(Hto, basis=basis)
return V, from_V, to_V
else:
if not self.has_coerce_map_from(base):
raise ValueError("base must be a subring of this ring")
raise NotImplementedError

class ElementMethods:
def is_unit(self):
r"""
Expand Down
12 changes: 5 additions & 7 deletions src/sage/crypto/mq/sr.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,7 @@ def __init__(self, n=1, r=1, c=1, e=4, star=False, **kwargs):
self._reverse_variables = bool(kwargs.get("reverse_variables", True))

with AllowZeroInversionsContext(self):
sub_byte_lookup = dict([(e, self.sub_byte(e)) for e in self._base])
sub_byte_lookup = dict([(v, self.sub_byte(v)) for v in self._base])
self._sub_byte_lookup = sub_byte_lookup

if self._gf2:
Expand Down Expand Up @@ -1643,7 +1643,7 @@ def vars(self, name, nr, rc=None, e=None):
"""
gd = self.variable_dict()
return tuple([gd[e] for e in self.varstrs(name, nr, rc, e)])
return tuple([gd[s] for s in self.varstrs(name, nr, rc, e)])

def variable_dict(self):
"""
Expand Down Expand Up @@ -2498,13 +2498,12 @@ def field_polynomials(self, name, i, l=None):
r = self._r
c = self._c
e = self._e
n = self._n

if l is None:
l = r*c

_vars = self.vars(name, i, l, e)
return [_vars[e*j+i]**2 - _vars[e*j+(i+1)%e] for j in range(l) for i in range(e)]
return [_vars[e*j+k]**2 - _vars[e*j+(k+1)%e] for j in range(l) for k in range(e)]

class SR_gf2(SR_generic):
def __init__(self, n=1, r=1, c=1, e=4, star=False, **kwargs):
Expand Down Expand Up @@ -2674,7 +2673,7 @@ def antiphi(self, l):
True
"""
e = self.e
V = self.k.vector_space()
V = self.k.vector_space(map=False)

if is_Matrix(l):
l2 = l.transpose().list()
Expand Down Expand Up @@ -3225,15 +3224,14 @@ def field_polynomials(self, name, i, l=None):
r = self._r
c = self._c
e = self._e
n = self._n

if l is None:
l = r*c

if self._polybori:
return []
_vars = self.vars(name, i, l, e)
return [_vars[e*j+i]**2 - _vars[e*j+i] for j in range(l) for i in range(e)]
return [_vars[e*j+k]**2 - _vars[e*j+k] for j in range(l) for k in range(e)]

class SR_gf2_2(SR_gf2):
"""
Expand Down
2 changes: 1 addition & 1 deletion src/sage/modules/free_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -5588,7 +5588,7 @@ def _element_constructor_(self, e, *args, **kwds):
EXAMPLES::
sage: k.<a> = GF(3^4)
sage: VS = k.vector_space()
sage: VS = k.vector_space(map=False)
sage: VS(a)
(0, 1, 0, 0)
"""
Expand Down
177 changes: 177 additions & 0 deletions src/sage/modules/free_module_morphism.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@

import sage.modules.free_module as free_module
from . import matrix_morphism
from sage.categories.morphism import Morphism
from sage.structure.sequence import Sequence
from sage.structure.richcmp import richcmp, rich_to_bool

from . import free_module_homspace

Expand Down Expand Up @@ -612,3 +614,178 @@ def minimal_polynomial(self,var='x'):
raise TypeError("not an endomorphism")

minpoly = minimal_polynomial

class BaseIsomorphism1D(Morphism):
"""
An isomorphism between a ring and a free rank-1 module over the ring.
EXAMPLES::
sage: R.<x,y> = QQ[]
sage: V, from_V, to_V = R.free_module(R)
sage: from_V
Isomorphism morphism:
From: Ambient free module of rank 1 over the integral domain Multivariate Polynomial Ring in x, y over Rational Field
To: Multivariate Polynomial Ring in x, y over Rational Field
"""
def _repr_type(self):
r"""
EXAMPLES::
sage: R.<x,y> = QQ[]
sage: V, from_V, to_V = R.free_module(R)
sage: from_V._repr_type()
'Isomorphism'
"""
return "Isomorphism"

def is_injective(self):
r"""
EXAMPLES::
sage: R.<x,y> = QQ[]
sage: V, from_V, to_V = R.free_module(R)
sage: from_V.is_injective()
True
"""
return True

def is_surjective(self):
r"""
EXAMPLES::
sage: R.<x,y> = QQ[]
sage: V, from_V, to_V = R.free_module(R)
sage: from_V.is_surjective()
True
"""
return True

def _richcmp_(self, other, op):
r"""
EXAMPLES::
sage: R.<x,y> = QQ[]
sage: V, fr, to = R.free_module(R)
sage: fr == loads(dumps(fr))
True
"""
if isinstance(other, BaseIsomorphism1D):
return richcmp(self._basis, other._basis, op)
else:
return rich_to_bool(op, 1)

class BaseIsomorphism1D_to_FM(BaseIsomorphism1D):
"""
An isomorphism from a ring to its 1-dimensional free module
INPUT:
- ``parent`` -- the homset
- ``basis`` -- (default 1) an invertible element of the ring
EXAMPLES::
sage: R = Zmod(8)
sage: V, from_V, to_V = R.free_module(R)
sage: v = to_V(2); v
(2)
sage: from_V(v)
2
sage: W, from_W, to_W = R.free_module(R, basis=3)
sage: W is V
True
sage: w = to_W(2); w
(6)
sage: from_W(w)
2
The basis vector has to be a unit so that the map is an isomorphism::
sage: W, from_W, to_W = R.free_module(R, basis=4)
Traceback (most recent call last):
...
ValueError: Basis element must be a unit
"""
def __init__(self, parent, basis=None):
"""
TESTS::
sage: R = Zmod(8)
sage: W, from_W, to_W = R.free_module(R, basis=3)
sage: TestSuite(to_W).run()
"""
Morphism.__init__(self, parent)
self._basis = basis

def _call_(self, x):
"""
TESTS::
sage: R = Zmod(8)
sage: W, from_W, to_W = R.free_module(R, basis=3)
sage: to_W(6) # indirect doctest
(2)
"""
if self._basis is not None:
x *= self._basis
return self.codomain()([x])

class BaseIsomorphism1D_from_FM(BaseIsomorphism1D):
"""
An isomorphism to a ring from its 1-dimensional free module
INPUT:
- ``parent`` -- the homset
- ``basis`` -- (default 1) an invertible element of the ring
EXAMPLES::
sage: R.<x> = QQ[[]]
sage: V, from_V, to_V = R.free_module(R)
sage: v = to_V(1+x); v
(1 + x)
sage: from_V(v)
1 + x
sage: W, from_W, to_W = R.free_module(R, basis=(1-x))
sage: W is V
True
sage: w = to_W(1+x); w
(1 - x^2)
sage: from_W(w)
1 + x + O(x^20)
The basis vector has to be a unit so that the map is an isomorphism::
sage: W, from_W, to_W = R.free_module(R, basis=x)
Traceback (most recent call last):
...
ValueError: Basis element must be a unit
"""
def __init__(self, parent, basis=None):
"""
TESTS::
sage: R.<x> = QQ[[]]
sage: W, from_W, to_W = R.free_module(R, basis=(1-x))
sage: TestSuite(from_W).run(skip='_test_nonzero_equal')
"""
Morphism.__init__(self, parent)
self._basis = basis

def _call_(self, x):
"""
TESTS::
sage: R.<x> = QQ[[]]
sage: W, from_W, to_W = R.free_module(R, basis=(1-x))
sage: w = to_W(1+x); w
(1 - x^2)
sage: from_W(w)
1 + x + O(x^20)
"""
if self._basis is None:
return x[0]
else:
return self.codomain()(x[0] / self._basis)
6 changes: 3 additions & 3 deletions src/sage/rings/finite_rings/element_base.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,8 @@ cdef class FinitePolyExtElement(FiniteRingElement):

def _vector_(self, reverse=False):
"""
Return a vector in self.parent().vector_space() matching
self. The most significant bit is to the right.
Return a vector matching this element in the vector space attached
to the parent. The most significant bit is to the right.
INPUT:
Expand Down Expand Up @@ -240,7 +240,7 @@ cdef class FinitePolyExtElement(FiniteRingElement):

if reverse:
ret.reverse()
return k.vector_space()(ret)
return k.vector_space(map=False)(ret)

def matrix(self, reverse=False):
r"""
Expand Down
Loading

0 comments on commit 1579338

Please sign in to comment.