From 3eb493515c9fcf6d35bc76bd1fc54b209aa369f2 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Thu, 12 Sep 2024 10:58:49 +0200 Subject: [PATCH] rewrite some methods, add documentation --- src/sage/modules/free_module.py | 122 ++++- .../modules/free_module_pseudohomspace.py | 371 +++++++------ .../modules/free_module_pseudomorphism.py | 490 ++++++++++++------ 3 files changed, 648 insertions(+), 335 deletions(-) diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index df629169b2a..a78ca3cd3e7 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -3112,40 +3112,122 @@ def hom(self, im_gens, codomain=None, **kwds): codomain = R**n return super().hom(im_gens, codomain, **kwds) - def PseudoHom(self, twist=None, codomain=None): + def PseudoHom(self, twist, codomain=None): r""" - Create the Pseudo Hom space corresponding to given twist data. + Return the Pseudo Hom space corresponding to given data. + + INPUT: + + - ``twist`` -- the twisting morphism or the twisting derivation + + - ``codomain`` (default: ``None``) -- the codomain of the pseudo + morphisms; if ``None``, the codomain is the same than the domain EXAMPLES:: - sage: F = GF(25); M = F^2; twist = F.frobenius_endomorphism() - sage: PHS = M.PseudoHom(twist); PHS - Set of Pseudomorphisms from Vector space of dimension 2 over Finite Field in z2 of size 5^2 to Vector space of dimension 2 over Finite Field in z2 of size 5^2 - Twisted by the morphism Frobenius endomorphism z2 |--> z2^5 on Finite Field in z2 of size 5^2 + sage: F = GF(25) + sage: Frob = F.frobenius_endomorphism() + sage: M = F^2 + sage: M.PseudoHom(Frob) + Set of Pseudoendomorphisms (twisted by z2 |--> z2^5) of Vector space of dimension 2 over Finite Field in z2 of size 5^2 + .. SEEALSO:: + + :meth:`pseudohom` """ from sage.modules.free_module_pseudohomspace import FreeModulePseudoHomspace - return FreeModulePseudoHomspace(self, codomain=codomain, twist=twist) + if codomain is None: + codomain = self + return FreeModulePseudoHomspace(self, codomain, twist) - def pseudohom(self, morphism, twist=None, codomain=None, **kwds): + def pseudohom(self, f, twist, codomain=None, side="left"): r""" - Create a pseudomorphism defined by a given morphism and twist. - Let A be a ring and M a free module over A. Let \theta: A \to A + Return the pseudohomomorphism corresponding to the given data. + + We recall that, given two `R`-modules `M` and `M'` together with + a ring homomorphism `\theta: R \to R` and a `\theta`-derivation, + `\delta: R \to R` (that is, a map such that: + `\delta(xy) = \theta(x)\delta(y) + \delta(x)y`), a + pseudomorphism `f : M \to M'` is a additive map such that + + .. MATH: + + f(\lambda x) = \theta(\lambda) f(x) + \delta(\lambda) x + + When `\delta` is nonzero, this requires that `M` coerces into `M'`. + + INPUT: + + - ``f`` -- a matrix (or any other data) defining the + pseudomorphism + + - ``twist`` -- the twisting morphism or the twisting derivation + + - ``codomain`` (default: ``None``) -- the codomain of the pseudo + morphisms; if ``None``, the codomain is the same than the domain + + - ``side`` (default: ``left``) -- side of the vectors acted on by + the matrix EXAMPLES:: - sage: F = GF(25); M = F^2; twist = F.frobenius_endomorphism() - sage: ph = M.pseudohom([[1, 2], [0, 1]], twist, side="right"); ph - Free module pseudomorphism defined as left-multiplication by the matrix - [1 2] - [0 1] - twisted by the morphism Frobenius endomorphism z2 |--> z2^5 on Finite Field in z2 of size 5^2 - Domain: Vector space of dimension 2 over Finite Field in z2 of size 5^2 - Codomain: Vector space of dimension 2 over Finite Field in z2 of size 5^2 + sage: K. = GF(5^5) + sage: Frob = K.frobenius_endomorphism() + sage: V = K^2; W = K^3 + sage: f = V.pseudohom([[1, z, z^2], [1, z^2, z^4]], Frob, codomain=W) + sage: f + Free module pseudomorphism (twisted by z |--> z^5) defined by the matrix + [ 1 z z^2] + [ 1 z^2 z^4] + Domain: Vector space of dimension 2 over Finite Field in z of size 5^5 + Codomain: Vector space of dimension 3 over Finite Field in z of size 5^5 + + We check that `f` is indeed semi-linear:: + + sage: v = V([z+1, z+2]) + sage: f(z*v) + (2*z^2 + z + 4, z^4 + 2*z^3 + 3*z^2 + z, 4*z^4 + 2*z^2 + 3*z + 2) + sage: z^5 * f(v) + (2*z^2 + z + 4, z^4 + 2*z^3 + 3*z^2 + z, 4*z^4 + 2*z^2 + 3*z + 2) + + An example with a derivation:: + + sage: R. = ZZ[] + sage: d = R.derivation() + sage: M = R^2 + sage: Nabla = M.pseudohom([[1, t], [t^2, t^3]], d, side='right') + sage: Nabla + Free module pseudomorphism (twisted by d/dt) defined as left-multiplication by the matrix + [ 1 t] + [t^2 t^3] + Domain: Ambient free module of rank 2 over the integral domain Univariate Polynomial Ring in t over Integer Ring + Codomain: Ambient free module of rank 2 over the integral domain Univariate Polynomial Ring in t over Integer Ring + + sage: v = M([1,1]) + sage: Nabla(v) + (t + 1, t^3 + t^2) + sage: Nabla(t*v) + (t^2 + t + 1, t^4 + t^3 + 1) + sage: Nabla(t*v) == t * Nabla(v) + v + True + + If the twisting derivation is not zero, the domain must + coerce into the codomain + + sage: N = R^3 + sage: M.pseudohom([[1, t, t^2], [1, t^2, t^4]], d, codomain=N) + Traceback (most recent call last): + ... + ValueError: the domain does not coerce into the codomain + + .. SEEALSO:: + + :meth:`PseudoHom` """ from sage.modules.free_module_pseudomorphism import FreeModulePseudoMorphism - side = kwds.get("side", "left") - return FreeModulePseudoMorphism(self.PseudoHom(twist=twist, codomain=codomain), morphism, side) + parent = self.PseudoHom(twist, codomain) + return FreeModulePseudoMorphism(parent, f, side) + def inner_product_matrix(self): """ diff --git a/src/sage/modules/free_module_pseudohomspace.py b/src/sage/modules/free_module_pseudohomspace.py index 65af841f37b..93377f58183 100644 --- a/src/sage/modules/free_module_pseudohomspace.py +++ b/src/sage/modules/free_module_pseudohomspace.py @@ -3,11 +3,12 @@ AUTHORS: - - Yossef Musleh (2024-02): initial version + - Xavier Caruso, Yossef Musleh (2024-09): initial version """ # **************************************************************************** -# Copyright (C) 2024 Yossef Musleh +# Copyright (C) 2024 Xavier Caruso +# Yossef Musleh # # Distributed under the terms of the GNU General Public License (GPL) # @@ -20,16 +21,19 @@ # # https://www.gnu.org/licenses/ # **************************************************************************** -import sage.categories.homset -from sage.structure.element import is_Matrix -from sage.matrix.constructor import matrix, identity_matrix + +from sage.structure.unique_representation import UniqueRepresentation + +from sage.categories.homset import HomsetWithBase from sage.matrix.matrix_space import MatrixSpace -from sage.categories.morphism import Morphism -from sage.misc.lazy_import import lazy_import +from sage.structure.sequence import Sequence +from sage.rings.polynomial.ore_polynomial_ring import OrePolynomialRing +from sage.modules.free_module_pseudomorphism import FreeModulePseudoMorphism + -lazy_import('sage.rings.derivation', 'RingDerivation') +class FreeModulePseudoHomspace(UniqueRepresentation, HomsetWithBase): + Element = FreeModulePseudoMorphism -class FreeModulePseudoHomspace(sage.categories.homset.HomsetWithBase): r""" This class implements the space of Pseudomorphisms with a fixed twist. @@ -38,219 +42,256 @@ class FreeModulePseudoHomspace(sage.categories.homset.HomsetWithBase): TESTS:: - sage: F = GF(125); M = F^2; twist = F.frobenius_endomorphism() - sage: PHS = M.PseudoHom(twist) + sage: F = GF(125) + sage: M = F^2 + sage: Frob = F.frobenius_endomorphism() + sage: PHS = M.PseudoHom(Frob) sage: h = PHS([[1, 2], [1, 1]]) sage: e = M((4*F.gen()^2 + F.gen() + 2, 4*F.gen()^2 + 4*F.gen() + 4)) sage: h(e) (z3, 2*z3^2 + 3*z3 + 3) """ - def __init__(self, domain, codomain=None, twist=None): + @staticmethod + def __classcall_private__(cls, domain, codomain, twist): r""" Constructs the space of pseudomorphisms with a given twist. INPUT: - - ``domain`` - the domain of the pseudomorphism; a free module - - ``codomain`` - the codomain of the pseudomorphism; a free - module (default: None) + - ``domain`` -- a free module, the domain of this pseudomorphism - - ``twist`` - a twisting morphism, this is either a morphism or - a derivation (default: None) + - ``codomain`` -- a free module, the codomain of this pseudomorphism - EXAMPLES:: + - ``twist`` -- a twisting morphism or a twisting derivation + + TESTS:: + + sage: F = GF(125) + sage: Frob = F.frobenius_endomorphism() + sage: M = F^2 + sage: H = M.PseudoHom(Frob) + sage: type(H) + + + sage: # Testsuite(H).run() + + """ + ring = domain.base_ring() + if codomain.base_ring() is not ring: + raise ValueError("the domain and the codomain must be defined over the same ring") + ore = OrePolynomialRing(ring, twist, names='x') + if isinstance(ore, OrePolynomialRing) and ore._derivation is not None: + if not codomain.has_coerce_map_from(domain): + raise ValueError("the domain does not coerce into the codomain") + return cls.__classcall__(cls, domain, codomain, ore) + + def __init__(self, domain, codomain, ore): + r""" + Initialize this pseudohom space. + + INPUT: + + - ``domain`` -- a free module, the domain of this pseudomorphism + + - ``codomain`` -- a free module, the codomain of this pseudomorphism + + - ``ore`` -- the underlying Ore polynomial ring + + TESTS:: + + sage: F = GF(125) + sage: Frob = F.frobenius_endomorphism() + sage: M = F^2 + sage: M.PseudoHom(Frob) + Set of Pseudoendomorphisms (twisted by z3 |--> z3^5) of Vector space of dimension 2 over Finite Field in z3 of size 5^3 - sage: F = GF(125); M = F^2; twist = F.frobenius_endomorphism() - sage: PHS = M.PseudoHom(twist); PHS - Set of Pseudomorphisms from Vector space of dimension 2 over Finite Field in z3 of size 5^3 to Vector space of dimension 2 over Finite Field in z3 of size 5^3 - Twisted by the morphism Frobenius endomorphism z3 |--> z3^5 on Finite Field in z3 of size 5^3 """ self._domain = domain - self._codomain = domain - if codomain is not None: - self._codomain = codomain - super().__init__(self._domain, self._codomain, category=None) - self.base_homspace = self._domain.Hom(self._codomain) - self.twist = twist - self.twist_morphism = None - self.derivation = None - if twist is None: - return - if (twist.domain() is not self.domain().coordinate_ring() - or twist.codomain() is not self.codomain().coordinate_ring()): - raise TypeError("twisting morphism domain/codomain do not match\ - coordinate rings of the modules") - elif isinstance(twist, Morphism): - self.twist_morphism = twist - elif isinstance(twist, RingDerivation): - self.derivation = twist + self._codomain = codomain + super().__init__(domain, codomain, category=None) + self._ore = ore + if isinstance(ore, OrePolynomialRing): + self._morphism = ore._morphism + self._derivation = ore._derivation else: - raise TypeError("twist is not a ring morphism or derivation") + self._morphism = self._derivation = None + ring = ore.base_ring() + self._matrix_space = MatrixSpace(ring, domain.dimension(), codomain.dimension()) - def __call__(self, A, **kwds): + def _element_constructor_(self, f, side="left"): r""" - Coerce a matrix or free module homomorphism into a pseudomorphism. + Return the element of this parent constructed from the + given data. - INPUTS: - - ``A`` - either a matrix defining the morphism or a free module - morphism + TESTS:: - EXAMPLES:: + sage: F. = GF(5^3) + sage: Frob = F.frobenius_endomorphism() + sage: V = F^2 + sage: H = V.PseudoHom(Frob) + + sage: H([[1, z], [z, z^2]]) + Free module pseudomorphism (twisted by z |--> z^5) defined by the matrix + [ 1 z] + [ z z^2] + Domain: Vector space of dimension 2 over Finite Field in z of size 5^3 + Codomain: Vector space of dimension 2 over Finite Field in z of size 5^3 - sage: F = GF(125); M = F^2; twist = F.frobenius_endomorphism() - sage: PHS = M.PseudoHom(twist) - sage: h = PHS([[1, 2], [1, 1]]); h - Free module pseudomorphism defined by the matrix - [1 2] - [1 1] - twisted by the morphism Frobenius endomorphism z3 |--> z3^5 on Finite Field in z3 of size 5^3 - Domain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 - Codomain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 """ - return self._element_constructor_(A, **kwds) + return self.element_class(self, f, side) - def __repr__(self): + def __reduce__(self): r""" - Returns the string representation of the pseudomorphism space. - - EXAMPLES:: + TESTS:: - sage: Fq = GF(343); M = Fq^2; frob = Fq.frobenius_endomorphism() - sage: PHS = M.PseudoHom(frob); PHS - Set of Pseudomorphisms from Vector space of dimension 2 over Finite Field in z3 of size 7^3 to Vector space of dimension 2 over Finite Field in z3 of size 7^3 - Twisted by the morphism Frobenius endomorphism z3 |--> z3^7 on Finite Field in z3 of size 7^3 + sage: F = GF(125) + sage: Frob = F.frobenius_endomorphism() + sage: M = F^2 + sage: H = M.PseudoHom(Frob) + sage: loads(dumps(M)) is M + True """ - r = "Set of Pseudomorphisms from {} to {} {} {}" - morph = "" - if self.twist_morphism is not None: - morph = "\nTwisted by the morphism {}" - morph = morph.format(self.twist_morphism.__repr__()) - deriv = "" - if self.derivation is not None: - deriv = "\nTwisted by the derivation {}" - deriv = deriv.format(self.derivation.__repr__()) - return r.format(self.domain(), self.codomain(), morph, deriv) - - def _element_constructor_(self, A, **kwds): + if self._derivation is None: + twist = self._morphism + else: + twist = self._derivation + return FreeModulePseudoHomspace, (self.domain(), self.codomain(), twist) + + def _repr_twist(self): r""" - Coerce a matrix or free module homomorphism into a pseudomorphism. + Return a string representation of the twisting morphisms. - INPUTS: - - ``A`` - either a matrix defining the morphism or a free module - morphism + This is a helper method. TESTS:: - sage: F = GF(125); M = F^2; twist = F.frobenius_endomorphism() - sage: PHS = M.PseudoHom(twist) - sage: h = PHS._element_constructor_([[1, 2], [1, 1]]); h - Free module pseudomorphism defined by the matrix - [1 2] - [1 1] - twisted by the morphism Frobenius endomorphism z3 |--> z3^5 on Finite Field in z3 of size 5^3 - Domain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 - Codomain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 + sage: F. = GF(5^3) + sage: Frob = F.frobenius_endomorphism() + sage: M = F^2 - :: + sage: M.PseudoHom(Frob)._repr_twist() + 'twisted by z |--> z^5' + + sage: M.PseudoHom(Frob^3)._repr_twist() + 'untwisted' - sage: F = GF(125); M = F^2; twist = F.frobenius_endomorphism() - sage: PHS = M.PseudoHom(twist) - sage: morph = M.hom(matrix([[1, 2], [1, 1]])) - sage: phi = PHS._element_constructor_(morph, side="right"); phi - Free module pseudomorphism defined as left-multiplication by the matrix - [1 2] - [1 1] - twisted by the morphism Frobenius endomorphism z3 |--> z3^5 on Finite Field in z3 of size 5^3 - Domain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 - Codomain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 """ - from . import free_module_pseudomorphism as pseudo - side = kwds.get("side", "left") - if not self.codomain().base_ring().has_coerce_map_from( - self.domain().base_ring()) and not A.is_zero(): - raise TypeError("nontrivial morphisms require a coercion map" - "from the base ring of the domain to the base ring of the" - "codomain") - return pseudo.FreeModulePseudoMorphism(self, A, side=side) - - def _matrix_space(self): + s = "" + if self._morphism is not None: + s += self._morphism._repr_short() + if self._derivation is not None: + if s != "": + s += " and " + s += self._derivation._repr_() + if s == "": + return "untwisted" + else: + return "twisted by " + s + + def _repr_(self): r""" - Return the full matrix space of the underlying morphisms. + Returns a string representation of this pseudomorphism space. EXAMPLES:: - sage: Fq = GF(343); M = Fq^2; frob = Fq.frobenius_endomorphism() - sage: PHS = M.PseudoHom(frob) - sage: PHS._matrix_space() - Full MatrixSpace of 2 by 2 dense matrices over Finite Field in z3 of size 7^3 + sage: Fq = GF(7^3) + sage: Frob = Fq.frobenius_endomorphism() + sage: V = Fq^2 + sage: V.PseudoHom(Frob) # indirect doctest + Set of Pseudoendomorphisms (twisted by z3 |--> z3^7) of Vector space of dimension 2 over Finite Field in z3 of size 7^3 + + :: + + sage: V.PseudoHom(Frob, codomain=Fq^3) # indirect doctest + Set of Pseudomorphism (twisted by z3 |--> z3^7) from Vector space of dimension 2 over Finite Field in z3 of size 7^3 to Vector space of dimension 3 over Finite Field in z3 of size 7^3 + + :: + + sage: A. = QQ[] + sage: d = A.derivation() + sage: M = A^3 + sage: M.PseudoHom(d) + Set of Pseudoendomorphisms (twisted by d/dt) of Ambient free module of rank 3 over the principal ideal domain Univariate Polynomial Ring in t over Rational Field + """ - return self.base_homspace._matrix_space() + twist = self._repr_twist() + if self.domain() is self.codomain(): + return "Set of Pseudoendomorphisms (%s) of %s" % (twist, self.domain()) + else: + return "Set of Pseudomorphism (%s) from %s to %s" % (twist, self.domain(), self.codomain()) - def basis(self, side="left"): + def ore_ring(self, var='x'): r""" - Return a basis for the underlying matrix space. + Return the underlying Ore polynomial ring. + + INPUT: + + - ``var`` (default: ``x``) -- a string, the name of + tha variable EXAMPLES:: - sage: Fq = GF(343); M = Fq^2; frob = Fq.frobenius_endomorphism() - sage: PHS = M.PseudoHom(frob) - sage: PHS.basis() - (Vector space morphism represented by the matrix: - [1 0] - [0 0] - Domain: Vector space of dimension 2 over Finite Field in z3 of size 7^3 - Codomain: Vector space of dimension 2 over Finite Field in z3 of size 7^3, - Vector space morphism represented by the matrix: - [0 1] - [0 0] - Domain: Vector space of dimension 2 over Finite Field in z3 of size 7^3 - Codomain: Vector space of dimension 2 over Finite Field in z3 of size 7^3, - Vector space morphism represented by the matrix: - [0 0] - [1 0] - Domain: Vector space of dimension 2 over Finite Field in z3 of size 7^3 - Codomain: Vector space of dimension 2 over Finite Field in z3 of size 7^3, - Vector space morphism represented by the matrix: - [0 0] - [0 1] - Domain: Vector space of dimension 2 over Finite Field in z3 of size 7^3 - Codomain: Vector space of dimension 2 over Finite Field in z3 of size 7^3) + sage: Fq. = GF(7^3) + sage: Frob = Fq.frobenius_endomorphism() + sage: V = Fq^2 + sage: H = V.PseudoHom(Frob) + + sage: H.ore_ring() + Ore Polynomial Ring in x over Finite Field in z of size 7^3 twisted by z |--> z^7 + + sage: H.ore_ring('y') + Ore Polynomial Ring in y over Finite Field in z of size 7^3 twisted by z |--> z^7 + """ - return self.base_homspace.basis(side) + return self._ore.change_var(var) - def identity(self): + def matrix_space(self): r""" - Return the pseudomorphism corresponding to the identity transformation + Return the matrix space used for representing the + pseudomorphism in this space. EXAMPLES:: - sage: F = GF(125); M = F^2; twist = F.frobenius_endomorphism() - sage: PHS = M.PseudoHom(twist) - sage: PHS.identity() - Free module pseudomorphism defined by the matrix - [1 0] - [0 1] - twisted by the morphism Frobenius endomorphism z3 |--> z3^5 on Finite Field in z3 of size 5^3 - Domain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 - Codomain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 + sage: Fq. = GF(7^3) + sage: Frob = Fq.frobenius_endomorphism() + sage: V = Fq^2 + sage: W = Fq^3 + sage: H = V.PseudoHom(Frob, codomain=W) + sage: H.matrix_space() + Full MatrixSpace of 2 by 3 dense matrices over Finite Field in z of size 7^3 + """ - return self(self.base_homspace.identity()) + return self._matrix_space - def zero(self): + def basis(self, side="left"): r""" - Return the zero pseudomorphism. This corresponds to the zero matrix. + Return a basis for the underlying matrix space. EXAMPLES:: - sage: F = GF(125); M = F^2; twist = F.frobenius_endomorphism() - sage: PHS = M.PseudoHom(twist) - sage: PHS.zero() - Free module pseudomorphism defined by the matrix + sage: Fq = GF(7^3) + sage: Frob = Fq.frobenius_endomorphism() + sage: V = Fq^2 + sage: PHS = V.PseudoHom(Frob) + sage: PHS.basis() + [Free module pseudomorphism (twisted by z3 |--> z3^7) defined by the matrix + [1 0] + [0 0] + Domain: Vector space of dimension 2 over Finite Field in z3 of size 7^3 + Codomain: Vector space of dimension 2 over Finite Field in z3 of size 7^3, Free module pseudomorphism (twisted by z3 |--> z3^7) defined by the matrix + [0 1] + [0 0] + Domain: Vector space of dimension 2 over Finite Field in z3 of size 7^3 + Codomain: Vector space of dimension 2 over Finite Field in z3 of size 7^3, Free module pseudomorphism (twisted by z3 |--> z3^7) defined by the matrix [0 0] + [1 0] + Domain: Vector space of dimension 2 over Finite Field in z3 of size 7^3 + Codomain: Vector space of dimension 2 over Finite Field in z3 of size 7^3, Free module pseudomorphism (twisted by z3 |--> z3^7) defined by the matrix [0 0] - twisted by the morphism Frobenius endomorphism z3 |--> z3^5 on Finite Field in z3 of size 5^3 - Domain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 - Codomain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 + [0 1] + Domain: Vector space of dimension 2 over Finite Field in z3 of size 7^3 + Codomain: Vector space of dimension 2 over Finite Field in z3 of size 7^3] + """ - return self(self.base_homspace.zero()) + return Sequence(self(mat) for mat in self._matrix_space.basis()) diff --git a/src/sage/modules/free_module_pseudomorphism.py b/src/sage/modules/free_module_pseudomorphism.py index 0051aaf9528..e32a6f1bb8b 100644 --- a/src/sage/modules/free_module_pseudomorphism.py +++ b/src/sage/modules/free_module_pseudomorphism.py @@ -3,11 +3,12 @@ AUTHORS: - - Yossef Musleh (2024-02): initial version + - Xavier Caruso, Yossef Musleh (2024-09): initial version """ -#################################################################################### -# Copyright (C) 2024 Yossef Musleh +# **************************************************************************** +# Copyright (C) 2024 Xavier Caruso +# Yossef Musleh # # Distributed under the terms of the GNU General Public License (GPL) # @@ -21,243 +22,432 @@ # http://www.gnu.org/licenses/ #################################################################################### -import sage.modules.free_module as free_module from sage.categories.morphism import Morphism -from sage.modules import free_module_homspace, matrix_morphism from sage.structure.richcmp import rich_to_bool, richcmp -from sage.structure.sequence import Sequence -from sage.structure.all import parent -from sage.misc.lazy_import import lazy_import from sage.modules.free_module_morphism import FreeModuleMorphism -from sage.modules.free_module_homspace import FreeModuleHomspace, is_FreeModuleHomspace -from sage.matrix.constructor import matrix -from sage.matrix.matrix_space import MatrixSpace -lazy_import('sage.rings.derivation', 'RingDerivation') class FreeModulePseudoMorphism(Morphism): r""" - Let `M, M'` be free modules over a ring `R`, `\theta: R \to R` a ring - homomorphism, and `\delta: R \to R` a `\theta`-derivation, which is a map - such that: + Let `M, M'` be modules over a ring `R`, `\theta: R \to R` a + ring homomorphism, and `\delta: R \to R` a `\theta`-derivation, + which is a map such that: - `\delta(xy) = \theta(x)\delta(y) + \delta(x)y`. + .. MATH: - Then a pseudomorphism `f : M to M` is a map such that + \delta(xy) = \theta(x)\delta(y) + \delta(x)y. - `f(x + y) = f(x) + f(y)` - `f(\lambda x) = `\theta(\lambda)f(x) + \delta(\lambda)x` + A pseudomorphism `f : M \to M` is an additive map such that - The pair `(\theta, \delta)` may be referred to as the *twist* of - the morphism. + .. MATH: - TESTS:: + f(\lambda x) = \theta(\lambda)f(x) + \delta(\lambda) x - sage: V = ZZ^2 - sage: f = V.pseudohom([V.1, -2*V.0]); f - Free module pseudomorphism defined by the matrix - [ 0 1] - [-2 0] - Domain: Ambient free module of rank 2 over the principal ideal domain Integer Ring - Codomain: Ambient free module of rank 2 over the principal ideal domain Integer Ring - sage: f(V((1, 2))) - (-4, 1) + The map `\theta` (resp. `\delta`) is referred to as the + twisting endomorphism (resp. the twisting derivation) of `f`. - :: + TESTS:: - sage: P. = ZZ[]; deriv = P.derivation() + sage: P. = ZZ[] + sage: d = P.derivation() sage: M = P^2 - sage: f = M.pseudohom([[1, 2*x], [x, 1]], deriv, side="right"); f - Free module pseudomorphism defined as left-multiplication by the matrix + sage: f = M.pseudohom([[1, 2*x], [x, 1]], d); f + Free module pseudomorphism (twisted by d/dx) defined by the matrix [ 1 2*x] [ x 1] - twisted by the derivation d/dx Domain: Ambient free module of rank 2 over the integral domain Univariate Polynomial Ring in x over Integer Ring Codomain: Ambient free module of rank 2 over the integral domain Univariate Polynomial Ring in x over Integer Ring sage: e = M((2*x^2 + 3*x + 1, x^3 + 7*x + 4)) sage: f(e) - (2*x^4 + 16*x^2 + 15*x + 4, 3*x^3 + 6*x^2 + 8*x + 11) - sage: f = M.pseudohom([[1, 2], [1, 1]], deriv) + (x^4 + 9*x^2 + 11*x + 4, 5*x^3 + 9*x^2 + 9*x + 11) + sage: f = M.pseudohom([[1, 2], [1, 1]], d) sage: f(e) (x^3 + 2*x^2 + 14*x + 8, x^3 + 7*x^2 + 13*x + 13) :: - sage: Fq = GF(343); M = Fq^3; N = Fq^2; frob = Fq.frobenius_endomorphism(); z = Fq.gen() - sage: phi = M.pseudohom([[2, 3, 1], [1, 4, 6]], frob, N, side="right"); phi - Free module pseudomorphism defined as left-multiplication by the matrix + sage: Fq. = GF(7^3) + sage: Frob = Fq.frobenius_endomorphism() + sage: M = Fq^3 + sage: N = Fq^2 + sage: phi = M.pseudohom([[2, 3, 1], [1, 4, 6]], Frob, codomain=N, side="right") + sage: phi + Free module pseudomorphism (twisted by z |--> z^7) defined as left-multiplication by the matrix [2 3 1] [1 4 6] - twisted by the morphism Frobenius endomorphism z3 |--> z3^7 on Finite Field in z3 of size 7^3 - Domain: Vector space of dimension 3 over Finite Field in z3 of size 7^3 - Codomain: Vector space of dimension 2 over Finite Field in z3 of size 7^3 - sage: elem = (4*z^2 + 4*z + 3, 2, z + 5) - sage: phi(elem) - (2*z3 + 1, 6*z3^2 + 4*z3 + 5) + Domain: Vector space of dimension 3 over Finite Field in z of size 7^3 + Codomain: Vector space of dimension 2 over Finite Field in z of size 7^3 + sage: v = (4*z^2 + 4*z + 3, 2, z + 5) + sage: phi(v) + (2*z + 1, 6*z^2 + 4*z + 5) + """ - def __init__(self, pseudohomspace, base_morphism, side="left"): + def __init__(self, parent, f, side): """ Constructs a pseudomorphism of free modules. INPUT: - - ``pseudohomspace`` - the parent space of pseudomorphisms, - containing - - ``base_morphism`` - either a morphism or a matrix defining a - morphism + - ``parent`` -- the parent space of pseudomorphisms - - side -- side of the vectors acted on by the matrix - (default: ``"left"``) + - ``f`` -- a pseudomorphism or a matrix defining this + pseudomorphism - EXAMPLES:: + - ``side`` -- side of the vectors acted on by the matrix + + TESTS:: - sage: F = GF(25); M = F^3; twist = F.frobenius_endomorphism(5) - sage: phi = M.pseudohom(matrix(F,3,[1..9]), twist) - sage: type(phi) - + sage: F. = GF(5^3) + sage: Frob = F.frobenius_endomorphism() + sage: M = F^2 + sage: H = M.PseudoHom(Frob) + sage: H + Set of Pseudoendomorphisms (twisted by z |--> z^5) of Vector space of dimension 2 over Finite Field in z of size 5^3 + + The attribute ``f`` can be a matrix:: + + sage: mat = matrix(F, 2, [1, z, z^2, z^3]) + sage: f = H(mat) + sage: f + Free module pseudomorphism (twisted by z |--> z^5) defined by the matrix + [ 1 z] + [ z^2 2*z + 2] + Domain: Vector space of dimension 2 over Finite Field in z of size 5^3 + Codomain: Vector space of dimension 2 over Finite Field in z of size 5^3 + + sage: type(f) + + + or a pseudomorphism with the same parent:: + + sage: H(f) + Free module pseudomorphism (twisted by z |--> z^5) defined by the matrix + [ 1 z] + [ z^2 2*z + 2] + Domain: Vector space of dimension 2 over Finite Field in z of size 5^3 + Codomain: Vector space of dimension 2 over Finite Field in z of size 5^3 + + When the twisting morphism and the twisting derivation are both trivial, + pseudomorphisms are just linear applications and coercion between those + works:: + + sage: id = End(F).identity() + sage: g = M.hom(mat) + sage: M.PseudoHom(id)(g) + Free module pseudomorphism (untwisted) defined by the matrix + [ 1 z] + [ z^2 2*z + 2] + Domain: Vector space of dimension 2 over Finite Field in z of size 5^3 + Codomain: Vector space of dimension 2 over Finite Field in z of size 5^3 + + An example with ``side=right``: + + sage: M.pseudohom(mat, Frob, side="right") + Free module pseudomorphism (twisted by z |--> z^5) defined as left-multiplication by the matrix + [ 1 z] + [ z^2 2*z + 2] + Domain: Vector space of dimension 2 over Finite Field in z of size 5^3 + Codomain: Vector space of dimension 2 over Finite Field in z of size 5^3 :: - sage: F = GF(125); M = F^2; twist = F.frobenius_endomorphism() - sage: morph = M.hom(matrix([[1,2],[0,1]])) - sage: phi = M.pseudohom(morph, twist, side="right"); phi - Free module pseudomorphism defined as left-multiplication by the matrix - [1 2] - [0 1] - twisted by the morphism Frobenius endomorphism z3 |--> z3^5 on Finite Field in z3 of size 5^3 - Domain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 - Codomain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 + sage: M.pseudohom(mat, Frob, side="middle") + Traceback (most recent call last): + ... + ValueError: the side must be either 'left' or 'right' + """ - Morphism.__init__(self, pseudohomspace) - dom = pseudohomspace.domain() - codom = pseudohomspace.codomain() - rows = dom.dimension() - cols = codom.dimension() - if side == "right": - rows = codom.dimension() - cols = dom.dimension() - matrix_space = MatrixSpace(dom.coordinate_ring(), rows, cols) - if isinstance(base_morphism, FreeModuleMorphism): - self._base_matrix = matrix_space(base_morphism.matrix()) + Morphism.__init__(self, parent) + dom = parent.domain() + codom = parent.codomain() + if side != "left" and side != "right": + raise ValueError("the side must be either 'left' or 'right'") + matrix_space = parent.matrix_space() + if ((isinstance(f, FreeModulePseudoMorphism) and f.parent() is parent) + or (isinstance(f, FreeModuleMorphism) + and f.domain() is dom and f.codomain() is codom + and parent._morphism is None and parent._derivation is None)): + if f.side() == 'right': + self._matrix = f.matrix().transpose() + else: + self._matrix = f.matrix() else: - self._base_matrix = matrix_space(base_morphism) - self.derivation = pseudohomspace.derivation - self.twist_morphism = pseudohomspace.twist_morphism - self.side = side + if side == "right": + self._matrix = matrix_space.transposed(f).transpose() + else: + self._matrix = matrix_space(f) + self._morphism = parent._morphism + self._derivation = parent._derivation + self._side = side def _call_(self, x): r""" - Return the result of applying a pseudomorphism to an element of the - free module. + Return the result of applying this pseudomorphism to ``x``. TESTS:: - sage: Fq = GF(343); M = Fq^3; frob = Fq.frobenius_endomorphism() - sage: ph = M.pseudohom([[1, Fq.gen(), 3], [0, 1, 1], [2, 1, 1]], frob, side="right") - sage: e = M((3*Fq.gen()^2 + 5*Fq.gen() + 2, 6*Fq.gen()^2 + 2*Fq.gen() + 2, Fq.gen() + 4)) - sage: ph(e) - (z3^2 + 6*z3 + 2, z3^2 + 2*z3 + 1, 2*z3^2 + 4*z3) + sage: Fq. = GF(7^3) + sage: M = Fq^3 + sage: Frob = Fq.frobenius_endomorphism() + sage: f = M.pseudohom([[1, z, 3], [0, 1, 1], [2, 1, 1]], Frob) + sage: e = M((3*z^2 + 5*z + 2, 6*z^2 + 2*z + 2, z + 4)) + sage: f(e) + (3*z^2 + 4*z + 4, 6*z^2 + 5*z + 6, 6*z^2 + 5*z + 3) + + :: + + sage: g = M.pseudohom([[1, z, 3], [0, 1, 1], [2, 1, 1]], Frob, side="right") + sage: g(e) + (z^2 + 6*z + 2, z^2 + 2*z + 1, 2*z^2 + 4*z) + """ - if self.domain().is_ambient(): + D = self.domain() + C = self.codomain() + if D.is_ambient(): x = x.element() else: - x = self.domain().coordinate_vector(x) - C = self.codomain() - if self.twist_morphism is None: + x = D.coordinate_vector(x) + if self._morphism is None: x_twist = x else: - x_twist = self.domain()(list(map(self.twist_morphism, x))) - if self.side == "left": - v = x_twist * self._base_matrix - else: - v = self._base_matrix * x_twist - if self.derivation is not None: - v += self.domain()(list(map(self.derivation, x))) + x_twist = D(list(map(self._morphism, x))) + v = x_twist * self._matrix + if self._derivation is not None: + v += D(list(map(self._derivation, x))) if not C.is_ambient(): v = C.linear_combination_of_basis(v) return C._element_constructor_(v) - def __repr__(self): + def _repr_(self): r""" Return the string representation of a pseudomorphism. TESTS:: - sage: Fq = GF(343); M = Fq^3; frob = Fq.frobenius_endomorphism() - sage: ph = M.pseudohom([[1,1,1],[2,2,2],[3,3,3]], frob); ph - Free module pseudomorphism defined by the matrix + sage: Fq. = GF(7^3) + sage: M = Fq^3 + sage: Frob = Fq.frobenius_endomorphism() + + sage: f = M.pseudohom([[1,1,1], [2,2,2], [3,3,3]], Frob) + sage: f # indirect doctest + Free module pseudomorphism (twisted by z |--> z^7) defined by the matrix [1 1 1] [2 2 2] [3 3 3] - twisted by the morphism Frobenius endomorphism z3 |--> z3^7 on Finite Field in z3 of size 7^3 - Domain: Vector space of dimension 3 over Finite Field in z3 of size 7^3 - Codomain: Vector space of dimension 3 over Finite Field in z3 of size 7^3 + Domain: Vector space of dimension 3 over Finite Field in z of size 7^3 + Codomain: Vector space of dimension 3 over Finite Field in z of size 7^3 + + sage: g = M.pseudohom([[1,1,1], [2,2,2], [3,3,3]], Frob, side="right") + sage: g # indirect doctest + Free module pseudomorphism (twisted by z |--> z^7) defined as left-multiplication by the matrix + [1 1 1] + [2 2 2] + [3 3 3] + Domain: Vector space of dimension 3 over Finite Field in z of size 7^3 + Codomain: Vector space of dimension 3 over Finite Field in z of size 7^3 """ - r = "Free module pseudomorphism defined {}by the "\ - "matrix\n{!r}{}{}\nDomain: {}\nCodomain: {}" - act = "" - if self.side == "right": - act = "as left-multiplication " - morph = "" - if self.twist_morphism is not None: - morph = "\ntwisted by the morphism {}" - morph = morph.format(self.twist_morphism.__repr__()) - deriv = "" - if self.derivation is not None: - deriv = "\ntwisted by the derivation {}" - deriv = deriv.format(self.derivation.__repr__()) - return r.format(act, self.matrix(), morph, deriv, - self.domain(), self.codomain()) + twist = self.parent()._repr_twist() + s = "Free module pseudomorphism (%s) defined " % twist + if self._side == "right": + s += "as left-multiplication " + s += "by the matrix\n%s\n" % self.matrix() + s += "Domain: %s\n" % self.domain() + s += "Codomain: %s" % self.codomain() + return s def matrix(self): r""" - Return the underlying matrix of a pseudomorphism. + Return the underlying matrix of this pseudomorphism. - If a pseudomorphism `f` on free module `M` has matrix m acting on - the left on elements `v \in M`, with twisting morphism `\theta`. - Then we have + It is defined as the matrix `M` whose lines (resp. columns if + ``side`` is ``right``) are the coordinates of the images of + the distinguished basis of the domain. - `f(v) = m*\theta(v)` + EXAMPLES:: - where `\theta` acts of the coefficients of `v` in terms of the basis - for `m`. + sage: Fq. = GF(7^3) + sage: Frob = Fq.frobenius_endomorphism() + sage: M = Fq^3 + sage: f = M.pseudohom([[1, z, 3], [0, 1, z^2], [z+1, 1, 1]], Frob) + sage: f.matrix() + [ 1 z 3] + [ 0 1 z^2] + [z + 1 1 1] + + sage: e1, e2, e3 = M.basis() + sage: f(e1) + (1, z, 3) + sage: f(e2) + (0, 1, z^2) + sage: f(e3) + (z + 1, 1, 1) - EXAMPLES:: + TESTS:: - sage: Fq = GF(343); M = Fq^3; frob = Fq.frobenius_endomorphism() - sage: ph = M.pseudohom([[1, 2, 3], [0, 1, 1], [2, 1, 1]], frob, side="right") - sage: e = M((3*Fq.gen()^2 + 5*Fq.gen() + 2, 6*Fq.gen()^2 + 2*Fq.gen() + 2, Fq.gen() + 4)) - sage: ph.matrix() - [1 2 3] - [0 1 1] - [2 1 1] - sage: ph(e) == ph.matrix()*vector([frob(c) for c in e]) + sage: v = M.random_element() + sage: f(v) == vector([Frob(c) for c in v]) * f.matrix() True + """ - return self._base_matrix + if self._side == "left": + return self._matrix + else: + return self._matrix.transpose() def twisting_derivation(self): r""" - Return the twisting derivation of the pseudomorphism. + Return the twisting derivation of the pseudomorphism + (or ``None`` if the twisting derivation is zero). EXAMPLES:: - sage: P. = ZZ[]; deriv = P.derivation(); M = P^2 - sage: f = M.pseudohom([[1, 2*x], [x, 1]], deriv, side="right") + sage: P. = ZZ[] + sage: d = P.derivation() + sage: M = P^2 + sage: f = M.pseudohom([[1, 2*x], [x, 1]], d) sage: f.twisting_derivation() d/dx + + :: + + sage: Fq. = GF(7^3) + sage: Frob = Fq.frobenius_endomorphism() + sage: V = Fq^2 + sage: f = V.pseudohom([[1, z], [0, z^2]], Frob) + sage: f.twisting_derivation() + """ - return self.derivation + return self._derivation def twisting_morphism(self): r""" - Return the twisting homomorphism of the pseudomorphism. + Return the twisting morphism of the pseudomorphism + (or ``None`` if the twisting morphism is the identity). + + EXAMPLES:: + + sage: Fq. = GF(7^3) + sage: Frob = Fq.frobenius_endomorphism() + sage: V = Fq^2 + sage: f = V.pseudohom([[1, z], [0, z^2]], Frob) + sage: f.twisting_morphism() + Frobenius endomorphism z |--> z^7 on Finite Field in z of size 7^3 + + :: + + sage: P. = ZZ[] + sage: d = P.derivation() + sage: M = P^2 + sage: f = M.pseudohom([[1, 2*x], [x, 1]], d) + sage: f.twisting_morphism() + + """ + return self._morphism + + def side(self): + """ + Return the side of vectors acted on, relative to the matrix. EXAMPLES:: - sage: Fq = GF(343); M = Fq^3; frob = Fq.frobenius_endomorphism() - sage: ph = M.pseudohom([[1, 2, 3], [0, 1, 1], [2, 1, 1]], frob, side="right") - sage: ph.twisting_morphism() - Frobenius endomorphism z3 |--> z3^7 on Finite Field in z3 of size 7^3 + sage: Fq. = GF(7^3) + sage: Frob = Fq.frobenius_endomorphism() + sage: V = Fq^2 + + sage: m = matrix(2, [1, z, z^2, z^3]) + sage: h1 = V.pseudohom(m, Frob) + sage: h1.side() + 'left' + sage: h1([1, 0]) + (1, z) + + sage: h2 = V.pseudohom(m, Frob, side="right") + sage: h2.side() + 'right' + sage: h2([1, 0]) + (1, z^2) + """ + + return self._side + + def side_switch(self): + """ + Return the same morphism, acting on vectors on the opposite side. + + EXAMPLES:: + + sage: Fq. = GF(7^3) + sage: Frob = Fq.frobenius_endomorphism() + sage: V = Fq^2 + + sage: m = matrix(2, [1, z, z^2, z^3]) + sage: h1 = V.pseudohom(m, Frob) + sage: h1 + Free module pseudomorphism (twisted by z |--> z^7) defined by the matrix + [ 1 z] + [ z^2 z^2 + 3] + Domain: Vector space of dimension 2 over Finite Field in z of size 7^3 + Codomain: Vector space of dimension 2 over Finite Field in z of size 7^3 + + sage: h2 = h1.side_switch() + sage: h2 + Free module pseudomorphism (twisted by z |--> z^7) defined as left-multiplication by the matrix + [ 1 z^2] + [ z z^2 + 3] + Domain: Vector space of dimension 2 over Finite Field in z of size 7^3 + Codomain: Vector space of dimension 2 over Finite Field in z of size 7^3 + + We check that ``h1`` and ``h2`` are the same:: + + sage: v = V.random_element() + sage: h1(v) == h2(v) + True + """ - return self.twist_morphism + if self._side == "left": + side = "right" + mat = self._matrix.transpose() + else: + side = "left" + mat = self._matrix + return self.parent()(mat, side) + + + def __eq__(self, other): + r""" + Compare this morphism with ``other``. + + TESTS:: + + sage: Fq. = GF(7^3) + sage: Frob = Fq.frobenius_endomorphism() + sage: V = Fq^2 + sage: m = random_matrix(Fq, 2) + + sage: f = V.pseudohom(m, Frob) + sage: g = V.pseudohom(m.transpose(), Frob, side="right") + sage: f == g + True + + sage: g = V.pseudohom(m.transpose(), Frob) + sage: f == g + False + + sage: g = V.pseudohom(m, Frob^2) + sage: f == g + False + + sage: g = V.pseudohom(m, Frob^3) + sage: h = V.hom(m) + sage: g == h + True + + """ + if isinstance(other, FreeModuleMorphism): + try: + other = self.parent()(other) + except ValueError: + return False + if isinstance(other, FreeModulePseudoMorphism): + return self.parent() is other.parent() and self._matrix == other._matrix +